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)} 合并成功");
}
}
}
}