264 lines
10 KiB
C#
264 lines
10 KiB
C#
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
|
||
{
|
||
/// <summary>
|
||
/// 同步删除视频指定帧和对应音频片段
|
||
/// </summary>
|
||
/// <param name="inputPath">输入文件路径</param>
|
||
/// <param name="outputPath">输出文件路径</param>
|
||
/// <param name="frameNumber">要删除的帧编号(从1开始)</param>
|
||
/// <returns>操作是否成功</returns>
|
||
public static async Task<bool> 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);
|
||
|
||
var random = new Random();
|
||
var randomFrame = random.Next(20, (int)totalDuration);
|
||
|
||
//return RemoveVideoFrame(inputPath, outputPath, randomFrame);
|
||
|
||
|
||
string videoPart1 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4");
|
||
string videoPart2 = Path.Combine(tempDir, $"{Guid.NewGuid()}.mp4");
|
||
bool hasSubVideo1 = SubVideo(inputPath, videoPart1, 0, randomFrame - 0.016);
|
||
bool hasSubVideo2 = SubVideo(inputPath, videoPart2, randomFrame, totalDuration);
|
||
if (!hasSubVideo1 || !hasSubVideo2)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
|
||
bool isJoinSuccess = JoinVideo(outputPath, [videoPart1, videoPart2]);
|
||
if (!isJoinSuccess)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
return false;
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogUtils.Error("抽帧失败", ex);
|
||
Console.WriteLine($"操作失败: {ex.Message}");
|
||
return false;
|
||
}
|
||
finally
|
||
{
|
||
string[] files = Directory.GetFiles(tempDir);
|
||
foreach (string file in files)
|
||
{
|
||
File.Delete(file);
|
||
}
|
||
File.Delete(tempDir);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 通过修改视频元数据(添加注释)改变MD5
|
||
/// </summary>
|
||
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)
|
||
{
|
||
return FFMpegArguments
|
||
.FromFileInput(inputPath, true, options => options.Seek(TimeSpan.FromSeconds(startSec)).EndSeek(TimeSpan.FromSeconds(endSec)))
|
||
.OutputToFile(outputPath, true, options => options.CopyChannel()) //.WithCustomArgument("-an") 去掉音频
|
||
.ProcessSynchronously();
|
||
}
|
||
|
||
|
||
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)
|
||
{
|
||
return FFMpeg.Join(outPutPath, videoParts);
|
||
}
|
||
|
||
public async Task<bool> 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();
|
||
}
|
||
}
|
||
}
|