Files
to_mo_to a00b1e187a feat: 去水印工具加入水印定位 (#319)
* feat: 去水印工具加入水印定位

* 代码还原

* 帧提取修改为管道

* 还原意外删除的行
2025-06-21 23:53:09 +08:00

239 lines
7.8 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 DownKyi.Core.Logging;
using DownKyi.Core.Settings;
using FFMpegCore;
using FFMpegCore.Enums;
using FFMpegCore.Helpers;
using FFMpegCore.Pipes;
namespace DownKyi.Core.FFMpeg;
public class FFMpeg
{
private const string Tag = "FFmpegHelper";
private static readonly FFMpeg instance = new();
static FFMpeg()
{
}
private FFMpeg()
{
GlobalFFOptions.Configure(new FFOptions { BinaryFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ffmpeg") });
FFMpegHelper.VerifyFFMpegExists(GlobalFFOptions.Current);
}
public static FFMpeg Instance => instance;
/// <summary>
/// 合并音频和视频
/// </summary>
/// <param name="audio">音频</param>
/// <param name="video">视频</param>
/// <param name="destVideo"></param>
public bool MergeVideo(string? audio, string? video, string destVideo)
{
if (!File.Exists(audio) && !File.Exists(video)) return false;
var arguments = FFMpegArguments
.FromFileInput(audio)
.AddFileInput(video)
.OutputToFile(destVideo, true, options => options
.WithCustomArgument("-strict -2")
.WithAudioCodec("copy")
.WithVideoCodec("copy")
.ForceFormat("mp4")
);
if (audio == null || !File.Exists(audio))
{
arguments = FFMpegArguments.FromFileInput(video).OutputToFile(
destVideo,
true,
options => options.WithCustomArgument("-strict -2").WithVideoCodec("copy").WithAudioCodec("copy").ForceFormat("mp4")
);
}
if (video == null || !File.Exists(video))
{
if (SettingsManager.GetInstance().GetIsTranscodingAacToMp3() == AllowStatus.Yes)
{
arguments = FFMpegArguments.FromFileInput(audio).OutputToFile(
destVideo,
true,
options => options.WithCustomArgument("-strict -2").DisableChannel(Channel.Video).ForceFormat("mp3")
);
}
else
{
arguments = FFMpegArguments.FromFileInput(audio).OutputToFile(
destVideo,
true,
options => options.WithCustomArgument("-strict -2").DisableChannel(Channel.Video).WithAudioCodec("copy")
);
}
}
LogManager.Debug(Tag, arguments.Arguments);
arguments
.NotifyOnError(s => LogManager.Debug(Tag, s))
.ProcessSynchronously(false);
try
{
if (audio != null)
{
File.Delete(audio);
}
if (video != null)
{
File.Delete(video);
}
}
catch (IOException e)
{
Console.WriteLine("MergeVideo()发生IO异常: {0}", e);
LogManager.Error(Tag, e);
}
return true;
}
/// <summary>
/// 去水印非常消耗cpu资源
/// </summary>
/// <param name="video"></param>
/// <param name="destVideo"></param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="action"></param>
public void Delogo(string video, string destVideo, int x, int y, int width, int height, Action<string> action)
{
FFMpegArguments
.FromFileInput(video)
.OutputToFile(
destVideo,
true,
option => option
.WithCustomArgument($"-vf delogo=x={x}:y={y}:w={width}:h={height}:show=0 -hide_banner"))
.NotifyOnOutput(action.Invoke)
.NotifyOnError(action.Invoke)
.ProcessSynchronously(false);
}
/// <summary>
/// 从一个视频中仅提取音频
/// </summary>
/// <param name="video">源视频</param>
/// <param name="audio">目标音频</param>
/// <param name="action">输出信息</param>
public void ExtractAudio(string video, string audio, Action<string> action)
{
FFMpegArguments
.FromFileInput(video)
.OutputToFile(audio,
true,
options => options
.WithCustomArgument("-hide_banner")
.WithAudioCodec("copy")
.DisableChannel(Channel.Video)
)
.NotifyOnOutput(action.Invoke)
.NotifyOnError(action.Invoke)
.ProcessSynchronously(false);
}
/// <summary>
/// 从一个视频中仅提取视频
/// </summary>
/// <param name="video">源视频</param>
/// <param name="destVideo">目标视频</param>
/// <param name="action">输出信息</param>
public void ExtractVideo(string video, string destVideo, Action<string> action)
{
FFMpegArguments.FromFileInput(video)
.OutputToFile(
destVideo,
true,
options => options
.WithCustomArgument("-hide_banner")
.WithVideoCodec("copy")
.DisableChannel(Channel.Audio))
.NotifyOnOutput(action.Invoke)
.NotifyOnError(action.Invoke)
.ProcessSynchronously(false);
}
public async Task<MemoryStream> ExtractVideoFrame(string inputPath,TimeSpan timestamp)
{
var ms = new MemoryStream();
await FFMpegArguments
.FromFileInput(inputPath, false, options => options.Seek(timestamp))
.OutputToPipe(new StreamPipeSink(ms), options => options
.WithFrameOutputCount(1)
.ForceFormat("image2")
.WithVideoCodec("mjpeg")
)
.NotifyOnError(x => Console.WriteLine(x))
.ProcessAsynchronously(false);
ms.Position = 0;
return ms;
}
/// <summary>
/// 合并多个FLV视频片段为一个完整视频
/// </summary>
/// <param name="inputFlvs">FLV片段路径列表(按顺序)</param>
/// <param name="outputVideo">输出视频路径</param>
/// <param name="action">进度回调</param>
/// <returns>是否成功</returns>
public bool ConcatVideos(List<string> inputFlvs, string outputVideo, Action<string> action)
{
try
{
if (inputFlvs == null || inputFlvs.Count == 0)
{
return false;
}
// 验证所有输入文件都存在
foreach (var video in inputFlvs)
{
if (!File.Exists(video))
{
action?.Invoke($"文件不存在: {video}");
return false;
}
}
LogManager.Debug(Tag, $"开始合并 {inputFlvs.Count} 个视频到 {outputVideo}");
var listFile = Path.Combine(Path.GetTempPath(), $"flvlist_{DateTime.Now:yyyyMMddHHmmss}.txt");
File.WriteAllLines(listFile, inputFlvs.Select(f => $"file '{f.Replace("'", "'\\''")}'"));
FFMpegArguments
.FromFileInput(listFile, false, options => options
.WithCustomArgument("-f concat -safe 0"))
.OutputToFile(outputVideo, true, options => options
.WithVideoCodec("libx264")
.WithAudioCodec("aac")
.WithCustomArgument("-movflags +faststart")
.WithCustomArgument("-avoid_negative_ts make_zero")
)
.NotifyOnOutput(action.Invoke)
.NotifyOnError(action.Invoke)
.ProcessSynchronously(false);
try { File.Delete(listFile); } catch { }
LogManager.Debug(Tag, "视频合并完成");
return true;
}
catch (Exception ex)
{
LogManager.Error(Tag, ex);
return false;
}
}
}