VideoConcat/Services/Video/VideoService.cs

245 lines
9.1 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using FFMpegCore.Enums;
using FFMpegCore.Helpers;
using FFMpegCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VideoConcat.Common.Tools;
using System.Security.Cryptography;
using System.IO;
using VideoConcat.Models;
using static VideoConcat.Models.VideoModel;
namespace VideoConcat.Services.Video
{
internal class VideoService : BaseService
{
/// <summary>
/// 将视频文件转换为ts格式
/// </summary>
public static string ConvertVideos(string videoPath)
{
var video = FFProbe.Analyse(videoPath);
string mediaInfo = "";
mediaInfo += $"视频: {videoPath}";
mediaInfo += $"时长: {video.Duration}";
mediaInfo += $"格式: {video.Format}";
// 视频流信息
foreach (var videoStream in video.VideoStreams)
{
mediaInfo += $"视频编码: {videoStream.CodecName}, 分辨率: {videoStream.Width}x{videoStream.Height}";
}
// 音频流信息
foreach (var audioStream in video.AudioStreams)
{
mediaInfo += $"音频编码: {audioStream.CodecName}, 采样率: {audioStream.SampleRateHz} Hz";
}
LogUtils.Info(mediaInfo);
FFMpegHelper.ConversionSizeExceptionCheck(video);
string _tempMd5Name = GetLargeFileMD5(videoPath);
//GlobalFFOptions.Current.TemporaryFilesFolder
var destinationPath = Path.Combine(Path.GetTempPath(), $"{_tempMd5Name}{FileExtension.Ts}");
if (File.Exists(destinationPath))
{
return destinationPath;
}
//Directory.CreateDirectory(GlobalFFOptions.Current.TemporaryFilesFolder);
try
{
FFMpeg.Convert(videoPath, destinationPath, VideoType.Ts);
}
catch (Exception ex)
{
LogUtils.Info("视频转换失败!尝试另外一种转换");
// 创建FFmpeg参数
try
{
FFMpegArguments
.FromFileInput(videoPath)
.OutputToFile(destinationPath, true, o => o
.WithVideoCodec("libx264") // 设置视频编码器
.WithAudioCodec("aac") // 设置音频编码器
.WithAudioSamplingRate(44100)
.WithAudioBitrate(128000)
.WithConstantRateFactor(23) // 设置质量调整参数
.WithCustomArgument("-vf fps=30") // 强制指定帧率(例如 30fps
.WithFastStart()
//.WithCustomArgument("-movflags +faststart") // 确保 moov atom 正确写入
//.CopyChannel()
//.WithBitStreamFilter(Channel.Video, Filter.H264_Mp4ToAnnexB)
.ForceFormat(VideoType.Ts)
)
//.NotifyOnOutput(Console.WriteLine) // 打印 FFmpeg 详细日志
.ProcessSynchronously(true);
//FFMpeg.Convert(destinationPathExecp, destinationPath, VideoType.Ts);
}
catch (Exception e)
{
LogUtils.Error($"{videoPath} 转换失败", ex);
LogUtils.Error($"{videoPath} 转换再次失败", e);
}
}
return destinationPath;
}
public static string GetLargeFileMD5(string filePath)
{
using var md5 = MD5.Create();
using var stream = File.OpenRead(filePath);
byte[] buffer = new byte[8192]; // 8KB 缓冲区
int bytesRead;
long totalBytesRead = 0;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
md5.TransformBlock(buffer, 0, bytesRead, null, 0);
totalBytesRead += bytesRead;
// 可在此添加进度显示Console.WriteLine($"已读取 {totalBytesRead / 1024 / 1024}MB");
}
md5.TransformFinalBlock(buffer, 0, 0);
return BitConverter.ToString(value: md5.Hash ?? []).Replace("-", "").ToLowerInvariant();
}
/// <summary>
/// 清理临时文件
/// </summary>
public static void Cleanup(List<string> pathList)
{
foreach (var path in pathList)
{
if (File.Exists(path))
{
File.Delete(path);
}
}
}
public static void JoinVideos(List<string> combination)
{
VideoModel videoModel = new();
if (Directory.Exists($"{videoModel.FolderPath}\\output") == false)
{
Directory.CreateDirectory($"{videoModel.FolderPath}\\output");
}
Random random = new();
string _tempFileName = $"{DateTime.Now:yyyyMMddHHmmss}{random.Next(100000, 999999)}.mp4";
string _outPutName = Path.Combine($"{videoModel.FolderPath}", "output", _tempFileName); ;
var temporaryVideoParts = combination.Select((_videoPath) =>
{
string _tempMd5Name = VideoService.GetLargeFileMD5(_videoPath);
//GlobalFFOptions.Current.TemporaryFilesFolder
return Path.Combine(Path.GetTempPath(), $"{_tempMd5Name}{FileExtension.Ts}");
}).ToArray();
bool _isSuccess = false;
try
{
_isSuccess = FFMpegArguments
.FromConcatInput(temporaryVideoParts)
.OutputToFile(_outPutName, true, options => options
.CopyChannel()
.WithBitStreamFilter(Channel.Audio, Filter.Aac_AdtstoAsc)
.WithFastStart()
.WithVideoCodec("copy") // 复制视频流
.WithAudioCodec("aac") // 重新编码音频
.WithAudioSamplingRate(44100) // 强制采样率
.WithCustomArgument("-movflags +faststart -analyzeduration 100M -probesize 100M")
)
.ProcessSynchronously();
}
catch (Exception ex1)
{
{
LogUtils.Error("拼接视频失败", ex1);
}
//bool _isSuccess = FFMpeg.Join(_outPutName, [.. combination]);
videoModel.Dispatcher.Invoke(() =>
{
IMediaAnalysis _mediaInfo = FFProbe.Analyse(_outPutName);
FileInfo fileInfo = new(_outPutName);
videoModel.ConcatVideos.Add(new ConcatVideo()
{
Index = videoModel.ConcatVideos.Count + 1,
FileName = _tempFileName,
Size = $"{fileInfo.Length / 1024 / 1024}MB",
Seconds = ((int)_mediaInfo.Duration.TotalSeconds),
Status = _isSuccess ? "拼接成功" : "拼接失败",
Progress = "100%",
});
});
if (_isSuccess && videoModel.AuditImagePath != "")
{
// 使用 FFMpegCore 执行添加图片到视频的操作
try
{
// 配置 FFmpeg 二进制文件位置(如果 FFmpeg 不在系统路径中)
// GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "path/to/ffmpeg/bin" });
string _outPutNameImg = $"{videoModel.FolderPath}\\output\\{DateTime.Now:yyyyMMddHHmmss}{random.Next(100000, 999999)}.mp4";
string _customArg = "-filter_complex \"[0:v][1:v] overlay=0:H-h\" ";
// 使用 FFMpegArguments 构建命令
bool _isCoverSuccess = FFMpegArguments
.FromFileInput(_outPutName)
.AddFileInput(videoModel.AuditImagePath)
.OutputToFile(
_outPutNameImg,
true,
options => options
.WithCustomArgument(_customArg)// 或显式指定编码器参数
.WithCustomArgument("-movflags +faststart") // 确保 moov atom 正确写入
)
.ProcessSynchronously();
LogUtils.Info($"图片已成功添加到视频中,输出文件:{_outPutName}");
}
catch (Exception ex)
{
LogUtils.Error($"图片添加到视频中失败", ex);
}
}
LogUtils.Info($"当前视频-[${_outPutName}] {string.Join("", combination)} 合并成功");
}
}
}
}