using FFMpegCore;
using FFMpegCore.Enums;
using Standard;
using System;
using System.IO;
using System.Threading.Tasks;
using VideoConcat.Common.Tools;
using static System.Windows.Forms.DataFormats;
namespace VideoConcat.Services.Video
{
public class VideoProcess
{
///
/// 同步删除视频指定帧和对应音频片段
///
/// 输入文件路径
/// 输出文件路径
/// 要删除的帧编号(从1开始)
/// 操作是否成功
public static async Task RemoveFrameRandomeAsync(string inputPath, string outputPath)
{
if (!File.Exists(inputPath))
return false;
// 创建临时目录
string tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
try
{
// 1. 获取视频信息
IMediaAnalysis mediaInfo = await FFProbe.AnalyseAsync(inputPath);
var videoStream = mediaInfo.PrimaryVideoStream;
if (videoStream == null)
{
Console.WriteLine("没有找到视频流");
return false;
}
bool isHevc = videoStream.CodecName == "hevc";
Directory.CreateDirectory(tempDir);
if (isHevc)
{
try
{
// 临时文件路径
string videoConvert = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4");
await FFMpegArguments.FromFileInput(inputPath)
.OutputToFile(videoConvert, true, opt => // 设置输出格式
opt.WithVideoCodec("libx264")
).ProcessAsynchronously();
mediaInfo = await FFProbe.AnalyseAsync(videoConvert);
inputPath = videoConvert;
}
catch (Exception)
{
throw new Exception("转换失败!");
}
}
// 1. 获取视频信息
mediaInfo = await FFProbe.AnalyseAsync(inputPath);
videoStream = mediaInfo.PrimaryVideoStream;
if (videoStream == null)
{
Console.WriteLine("没有找到视频流");
return false;
}
// 视频总时长(秒)
double totalDuration = mediaInfo.Duration.TotalSeconds;
double frameRate = videoStream.FrameRate;
double frameDuration = Math.Round(1.0 / frameRate, 6); // 一帧时长(秒)
int totalFram = (int)(totalDuration * frameRate);
// 确保视频时长足够(至少20秒)
if (totalDuration < 20)
{
LogUtils.Error($"视频时长太短({totalDuration}秒),无法抽帧");
return false;
}
var random = new Random();
// 随机选择要删除的帧时间点(避开开头和结尾)
int maxFrameTime = (int)totalDuration - 5; // 确保有足够的空间
int minFrameTime = 20; // 从20秒开始
if (maxFrameTime <= minFrameTime)
{
maxFrameTime = minFrameTime + 1;
}
var randomFrame = random.Next(minFrameTime, maxFrameTime);
//return RemoveVideoFrame(inputPath, outputPath, randomFrame);
string videoPart1 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4");
string videoPart2 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4");
LogUtils.Info($"开始抽帧:输入={inputPath}, 输出={outputPath}, 删除帧时间={randomFrame}秒");
bool hasSubVideo1 = SubVideo(inputPath, videoPart1, 0, randomFrame - 0.016);
if (!hasSubVideo1)
{
LogUtils.Error($"裁剪第一部分视频失败:{videoPart1}");
return false;
}
bool hasSubVideo2 = SubVideo(inputPath, videoPart2, randomFrame, totalDuration);
if (!hasSubVideo2)
{
LogUtils.Error($"裁剪第二部分视频失败:{videoPart2}");
return false;
}
LogUtils.Info($"视频裁剪成功,开始合并:{videoPart1} + {videoPart2} -> {outputPath}");
bool isJoinSuccess = JoinVideo(outputPath, [videoPart1, videoPart2]);
if (!isJoinSuccess)
{
LogUtils.Error($"合并视频失败:{outputPath}");
return false;
}
// 验证输出文件是否存在
if (File.Exists(outputPath))
{
LogUtils.Info($"抽帧成功:{outputPath}");
return true;
}
else
{
LogUtils.Error($"抽帧失败:输出文件不存在 - {outputPath}");
return false;
}
}
catch (Exception ex)
{
LogUtils.Error("抽帧失败", ex);
Console.WriteLine($"操作失败: {ex.Message}");
return false;
}
finally
{
// 清理临时目录
try
{
if (Directory.Exists(tempDir))
{
string[] files = Directory.GetFiles(tempDir);
foreach (string file in files)
{
try
{
File.Delete(file);
}
catch
{
// 忽略删除失败
}
}
try
{
Directory.Delete(tempDir);
}
catch
{
// 忽略删除失败
}
}
}
catch
{
// 忽略清理错误
}
}
}
///
/// 通过修改视频元数据(添加注释)改变MD5
///
public static bool ModifyByMetadata(string inputPath, string outputPath, string comment = "JSY")
{
// 添加或修改视频元数据中的注释信息
return FFMpegArguments
.FromFileInput(inputPath)
.OutputToFile(outputPath, true, options => options
//.WithVideoCodec("copy") // 直接复制视频流,不重新编码
//.WithAudioCodec("copy") // 直接复制音频流
.CopyChannel()
.WithCustomArgument($"-metadata comment=\"{comment}_{Guid.NewGuid()}\"") // 添加唯一注释
)
.ProcessSynchronously();
}
public static bool SubVideo(string inputPath, string outputPath, Double startSec, Double endSec)
{
try
{
// 计算时长
double duration = endSec - startSec;
if (duration <= 0)
{
LogUtils.Error($"SubVideo失败:时长无效,开始={startSec}秒, 结束={endSec}秒");
return false;
}
LogUtils.Info($"SubVideo:输入={inputPath}, 输出={outputPath}, 开始={startSec}秒, 结束={endSec}秒, 时长={duration}秒");
// 使用 -ss 和 -t 参数进行裁剪
bool result = FFMpegArguments
.FromFileInput(inputPath, true, options => options
.Seek(TimeSpan.FromSeconds(startSec))
.WithCustomArgument($"-t {duration}")) // 指定时长而不是结束时间
.OutputToFile(outputPath, true, options => options
.CopyChannel()) // 复制流,不重新编码
.ProcessSynchronously();
// 验证输出文件是否存在
if (result && File.Exists(outputPath))
{
FileInfo fileInfo = new FileInfo(outputPath);
LogUtils.Info($"SubVideo成功:{outputPath}, 文件大小={fileInfo.Length / 1024 / 1024}MB");
return true;
}
else
{
LogUtils.Error($"SubVideo失败:输入={inputPath}, 输出={outputPath}, 开始={startSec}秒, 结束={endSec}秒, 结果={result}, 文件存在={File.Exists(outputPath)}");
return false;
}
}
catch (Exception ex)
{
LogUtils.Error($"SubVideo异常:输入={inputPath}, 输出={outputPath}", ex);
return false;
}
}
public static bool SubAudio(string inputPath, string outputPath, Double startSec, Double endSec)
{
return FFMpegArguments
.FromFileInput(inputPath, true, options => options.Seek(TimeSpan.FromSeconds(startSec)).EndSeek(TimeSpan.FromSeconds(endSec)))
.OutputToFile(outputPath, true, options => options.CopyChannel())
.ProcessSynchronously();
}
public static bool JoinVideo(string outPutPath, string[] videoParts)
{
try
{
// 验证所有输入文件是否存在
foreach (var part in videoParts)
{
if (!File.Exists(part))
{
LogUtils.Error($"JoinVideo失败:输入文件不存在 - {part}");
return false;
}
}
bool result = FFMpeg.Join(outPutPath, videoParts);
// 验证输出文件是否存在
if (result && File.Exists(outPutPath))
{
LogUtils.Info($"JoinVideo成功:{outPutPath}");
return true;
}
else
{
LogUtils.Error($"JoinVideo失败:输出文件不存在 - {outPutPath}");
return false;
}
}
catch (Exception ex)
{
LogUtils.Error($"JoinVideo异常:输出={outPutPath}", ex);
return false;
}
}
public async Task ProcessVideo(string inputPath, string outputPath, string tempDir)
{
// 1. 获取视频信息
IMediaAnalysis mediaInfo = await FFProbe.AnalyseAsync(inputPath);
var videoStream = mediaInfo.PrimaryVideoStream;
if (videoStream == null)
{
Console.WriteLine("没有找到视频流");
return false;
}
// 视频总时长(秒)
var totalDuration = mediaInfo.Duration.TotalSeconds;
// 计算帧时间参数
double frameRate = videoStream.FrameRate;
double frameDuration = Math.Round(1.0 / frameRate, 6); // 一帧时长(秒)
int totalFram = (int)(totalDuration * frameRate);
// 2. 随机生成要删除的帧的时间点(避开最后一帧,防止越界)
var random = new Random();
var randomFrame = random.Next(20, totalFram - 10);
double frameTime = Math.Round((randomFrame - 1) * frameDuration, 6); // 目标帧开始时间
double nextFrameTime = Math.Round((randomFrame + 1) * frameDuration, 6); // 下一帧开始时间
// 临时文件路径
string videoPart1 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4");
string videoPart2 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4");
string audioPath = Path.Combine(tempDir, $"{Guid.NewGuid()}.aac");
string videoNoaudioPath = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4");
string finalyPath = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4");
string audioPart1 = Path.Combine(tempDir, $"{Guid.NewGuid()}.aac");
string audioPart2 = Path.Combine(tempDir, $"{Guid.NewGuid()}.aac");
// 分离视频流(不含音频)
await FFMpegArguments
.FromFileInput(inputPath)
.OutputToFile(videoNoaudioPath, true, opt => opt.WithCustomArgument("-an -c:v copy"))
.ProcessAsynchronously();
if (!File.Exists(videoNoaudioPath))
return false;
// 分离音频流(不含视频)(如有音频)
if (mediaInfo.PrimaryAudioStream != null)
{
await FFMpegArguments
.FromFileInput(inputPath)
.OutputToFile(audioPath, true, opt => opt.WithCustomArgument("-vn -c:a copy"))
.ProcessAsynchronously();
if (!File.Exists(audioPath))
return false;
}
inputPath = videoNoaudioPath;
bool hasSubVideo1 = SubVideo(inputPath, videoPart1, 0, frameTime);
bool hasSubVideo2 = SubVideo(inputPath, videoPart2, nextFrameTime, totalDuration);
if (!hasSubVideo1 || !hasSubVideo2)
{
return false;
}
bool isJoinSuccess = JoinVideo(finalyPath, [videoPart1, videoPart2]);
if (!isJoinSuccess)
{
return false;
}
return await FFMpegArguments.FromFileInput(finalyPath)
.AddFileInput(audioPath)
.OutputToFile(outputPath, true, opt =>
opt.WithCustomArgument("-c copy -shortest -y"))
.ProcessAsynchronously();
}
public static bool RemoveVideoFrame(string inputPath, string outputPath, int frameToRemove)
{
// 使用FFMpegArguments构建转换流程
return FFMpegArguments
.FromFileInput(inputPath)
.OutputToFile(outputPath, true, options => options
// 使用select过滤器排除指定帧(帧序号从0开始)
.WithCustomArgument($"select=not(eq(n\\,{frameToRemove}))")
// $"not(eq(n\\,{frameToRemove}))"); // 注意:在C#中需要转义反斜杠
//// 保持其他编码参数
.WithVideoCodec("libx264")
.WithAudioCodec("copy") // 如果不需要处理音频,直接复制
)
.ProcessSynchronously();
}
}
}