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 { /// /// 将视频文件转换为ts格式 /// 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(); } /// /// 清理临时文件 /// public static void Cleanup(List pathList) { foreach (var path in pathList) { if (File.Exists(path)) { File.Delete(path); } } } public static void JoinVideos(List 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)} 合并成功"); } } } }