mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
Merge pull request #55 from rosenbjerg/refactor
Major refactoring and fluent builder pattern
Former-commit-id: 92c1d0f457
This commit is contained in:
commit
70f77cfe0c
160 changed files with 2184 additions and 3393 deletions
|
@ -1,478 +1,309 @@
|
||||||
using FFMpegCore.FFMPEG.Argument;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using FFMpegCore.FFMPEG.Argument.Fluent;
|
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using FFMpegCore.Arguments;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
using FFMpegCore.Exceptions;
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class ArgumentBuilderTest : BaseTest
|
public class ArgumentBuilderTest : BaseTest
|
||||||
{
|
{
|
||||||
private List<string> concatFiles = new List<string>
|
private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4"};
|
||||||
{ "1.mp4", "2.mp4", "3.mp4", "4.mp4"};
|
|
||||||
|
|
||||||
private FFArgumentBuilder builder;
|
|
||||||
|
|
||||||
public ArgumentBuilderTest() : base()
|
|
||||||
{
|
|
||||||
builder = new FFArgumentBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetArgumentsString(params Argument[] args)
|
|
||||||
{
|
|
||||||
var container = new ArgumentContainer { new InputArgument("input.mp4") };
|
|
||||||
foreach (var a in args)
|
|
||||||
{
|
|
||||||
container.Add(a);
|
|
||||||
}
|
|
||||||
container.Add(new OutputArgument("output.mp4"));
|
|
||||||
|
|
||||||
return builder.BuildArguments(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetArgumentsString(ArgumentContainer container)
|
|
||||||
{
|
|
||||||
var resContainer = new ArgumentContainer { new InputArgument("input.mp4") };
|
|
||||||
foreach (var a in container)
|
|
||||||
{
|
|
||||||
resContainer.Add(a.Value);
|
|
||||||
}
|
|
||||||
resContainer.Add(new OutputArgument("output.mp4"));
|
|
||||||
|
|
||||||
return builder.BuildArguments(resContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_IO_1()
|
public void Builder_BuildString_IO_1()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString();
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Scale()
|
public void Builder_BuildString_Scale()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new ScaleArgument(VideoSize.Hd));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").Scale(VideoSize.Hd).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -vf scale=-1:720 \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -vf scale=-1:720 \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Scale_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().Scale(VideoSize.Hd));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -vf scale=-1:720 \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_AudioCodec()
|
public void Builder_BuildString_AudioCodec()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new AudioCodecArgument(AudioCodec.Aac));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithAudioCodec(AudioCodec.Aac).OutputToFile("output.mp4").Arguments;
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:a aac \"output.mp4\"");
|
Assert.AreEqual("-i \"input.mp4\" -c:a aac \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_AudioBitrate()
|
public void Builder_BuildString_AudioBitrate()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new AudioBitrateArgument(AudioQuality.Normal));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithAudioBitrate(AudioQuality.Normal).OutputToFile("output.mp4").Arguments;
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -b:a 128k \"output.mp4\"");
|
Assert.AreEqual("-i \"input.mp4\" -b:a 128k \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Quiet()
|
public void Builder_BuildString_Quiet()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new QuietArgument());
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").Quiet().OutputToFile("output.mp4").Arguments;
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -hide_banner -loglevel warning \"output.mp4\"");
|
Assert.AreEqual("-i \"input.mp4\" -hide_banner -loglevel warning \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_AudioCodec_Fluent()
|
public void Builder_BuildString_AudioCodec_Fluent()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new ArgumentContainer().AudioCodec(AudioCodec.Aac).AudioBitrate(128));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithAudioCodec(AudioCodec.Aac).WithAudioBitrate(128).OutputToFile("output.mp4").Arguments;
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:a aac -b:a 128k \"output.mp4\"");
|
Assert.AreEqual("-i \"input.mp4\" -c:a aac -b:a 128k \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_BitStream()
|
public void Builder_BuildString_BitStream()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new BitStreamFilterArgument(Channel.Audio, Filter.H264_Mp4ToAnnexB));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithBitStreamFilter(Channel.Audio, Filter.H264_Mp4ToAnnexB).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -bsf:a h264_mp4toannexb \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -bsf:a h264_mp4toannexb \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_BitStream_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().BitStreamFilter(Channel.Audio, Filter.H264_Mp4ToAnnexB));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -bsf:a h264_mp4toannexb \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Concat()
|
public void Builder_BuildString_Concat()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer { new ConcatArgument(concatFiles), new OutputArgument("output.mp4") };
|
var str = FFMpegArguments.FromConcatenation(_concatFiles).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"concat:1.mp4|2.mp4|3.mp4|4.mp4\" \"output.mp4\"", str);
|
||||||
var str = builder.BuildArguments(container);
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"concat:1.mp4|2.mp4|3.mp4|4.mp4\" \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Concat_Fluent()
|
|
||||||
{
|
|
||||||
var container = new ArgumentContainer()
|
|
||||||
.Concat(concatFiles)
|
|
||||||
.Output("output.mp4");
|
|
||||||
|
|
||||||
|
|
||||||
var str = builder.BuildArguments(container);
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"concat:1.mp4|2.mp4|3.mp4|4.mp4\" \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Copy_Audio()
|
public void Builder_BuildString_Copy_Audio()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new CopyArgument(Channel.Audio));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").CopyChannel(Channel.Audio).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -c:a copy \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:a copy \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Copy_Audio_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().Copy(Channel.Audio));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:a copy \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Copy_Video()
|
public void Builder_BuildString_Copy_Video()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new CopyArgument(Channel.Video));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").CopyChannel(Channel.Video).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -c:v copy \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:v copy \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Copy_Video_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().Copy(Channel.Video));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:v copy \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Copy_Both()
|
public void Builder_BuildString_Copy_Both()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new CopyArgument(Channel.Both));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").CopyChannel().OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -c copy \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c copy \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Copy_Both_Fluent()
|
public void Builder_BuildString_DisableChannel_Audio()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new ArgumentContainer().Copy(Channel.Both));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").DisableChannel(Channel.Audio).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -an \"output.mp4\"", str);
|
||||||
|
}
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c copy \"output.mp4\"");
|
[TestMethod]
|
||||||
|
public void Builder_BuildString_DisableChannel_Video()
|
||||||
|
{
|
||||||
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").DisableChannel(Channel.Video).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -vn \"output.mp4\"", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Builder_BuildString_DisableChannel_Both()
|
||||||
|
{
|
||||||
|
Assert.ThrowsException<FFMpegException>(() => FFMpegArguments.FromInputFiles(true, "input.mp4").DisableChannel(Channel.Both));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Builder_BuildString_AudioSamplingRate_Default()
|
||||||
|
{
|
||||||
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithAudioSamplingRate().OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -ar 48000 \"output.mp4\"", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Builder_BuildString_AudioSamplingRate()
|
||||||
|
{
|
||||||
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithAudioSamplingRate(44000).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -ar 44000 \"output.mp4\"", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Builder_BuildString_VariableBitrate()
|
||||||
|
{
|
||||||
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithVariableBitrate(5).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -vbr 5 \"output.mp4\"", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Builder_BuildString_Faststart()
|
||||||
|
{
|
||||||
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithFastStart().OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -movflags faststart \"output.mp4\"", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Builder_BuildString_Overwrite()
|
||||||
|
{
|
||||||
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").OverwriteExisting().OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -y \"output.mp4\"", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Builder_BuildString_RemoveMetadata()
|
||||||
|
{
|
||||||
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithoutMetadata().OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -map_metadata -1 \"output.mp4\"", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Builder_BuildString_Transpose()
|
||||||
|
{
|
||||||
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").Transpose(Transposition.CounterClockwise90).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -vf \"transpose=2\" \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_CpuSpeed()
|
public void Builder_BuildString_CpuSpeed()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new CpuSpeedArgument(10));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithCpuSpeed(10).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -quality good -cpu-used 10 -deadline realtime \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -quality good -cpu-used 10 -deadline realtime \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_CpuSpeed_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().CpuSpeed(10));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -quality good -cpu-used 10 -deadline realtime \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_ForceFormat()
|
public void Builder_BuildString_ForceFormat()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new ForceFormatArgument(VideoCodec.LibX264));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").ForceFormat(VideoCodec.LibX264).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -f libx264 \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -f libx264 \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_ForceFormat_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().ForceFormat(VideoCodec.LibX264));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -f libx264 \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_FrameOutputCount()
|
public void Builder_BuildString_FrameOutputCount()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new FrameOutputCountArgument(50));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithFrameOutputCount(50).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -vframes 50 \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -vframes 50 \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_FrameOutputCount_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().FrameOutputCount(50));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -vframes 50 \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_FrameRate()
|
public void Builder_BuildString_FrameRate()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new FrameRateArgument(50));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithFramerate(50).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -r 50 \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -r 50 \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_FrameRate_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().FrameRate(50));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -r 50 \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Loop()
|
public void Builder_BuildString_Loop()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new LoopArgument(50));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").Loop(50).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -loop 50 \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -loop 50 \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Loop_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().Loop(50));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -loop 50 \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Seek()
|
public void Builder_BuildString_Seek()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new SeekArgument(TimeSpan.FromSeconds(10)));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").Seek(TimeSpan.FromSeconds(10)).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -ss 00:00:10 \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -ss 00:00:10 \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Seek_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().Seek(TimeSpan.FromSeconds(10)));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -ss 00:00:10 \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Shortest()
|
public void Builder_BuildString_Shortest()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new ShortestArgument(true));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").UsingShortest().OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -shortest \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -shortest \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Shortest_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().Shortest());
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -shortest \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Size()
|
public void Builder_BuildString_Size()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new SizeArgument(1920, 1080));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").Resize(1920, 1080).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -s 1920x1080 \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -s 1920x1080 \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Size_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().Size(1920, 1080));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -s 1920x1080 \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Speed()
|
public void Builder_BuildString_Speed()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new SpeedArgument(Speed.Fast));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithSpeedPreset(Speed.Fast).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -preset fast \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -preset fast \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Speed_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().Speed(Speed.Fast));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -preset fast \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_DrawtextFilter()
|
public void Builder_BuildString_DrawtextFilter()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new DrawTextArgument("Stack Overflow", "/path/to/font.ttf",
|
var str = FFMpegArguments
|
||||||
("fontcolor", "white"),
|
.FromInputFiles(true, "input.mp4")
|
||||||
("fontsize", "24"),
|
.DrawText(DrawTextOptions
|
||||||
("box", "1"),
|
.Create("Stack Overflow", "/path/to/font.ttf")
|
||||||
("boxcolor", "black@0.5"),
|
.WithParameter("fontcolor", "white")
|
||||||
("boxborderw", "5"),
|
.WithParameter("fontsize", "24")
|
||||||
("x", "(w-text_w)/2"),
|
.WithParameter("box", "1")
|
||||||
("y", "(h-text_h)/2")));
|
.WithParameter("boxcolor", "black@0.5")
|
||||||
|
.WithParameter("boxborderw", "5")
|
||||||
|
.WithParameter("x", "(w-text_w)/2")
|
||||||
|
.WithParameter("y", "(h-text_h)/2"))
|
||||||
|
.OutputToFile("output.mp4").Arguments;
|
||||||
|
|
||||||
Assert.AreEqual("-i \"input.mp4\" -vf drawtext=\"text='Stack Overflow':fontfile=/path/to/font.ttf:fontcolor=white:fontsize=24:box=1:boxcolor=black@0.5:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2\" \"output.mp4\"", str);
|
Assert.AreEqual("-i \"input.mp4\" -vf drawtext=\"text='Stack Overflow':fontfile=/path/to/font.ttf:fontcolor=white:fontsize=24:box=1:boxcolor=black@0.5:boxborderw=5:x=(w-text_w)/2:y=(h-text_h)/2\" \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_DrawtextFilter_Fluent()
|
public void Builder_BuildString_DrawtextFilter_Alt()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer().
|
var str = FFMpegArguments
|
||||||
DrawText((options) =>
|
.FromInputFiles(true, "input.mp4")
|
||||||
{
|
.DrawText(DrawTextOptions
|
||||||
options.Text = "Stack Overflow";
|
.Create("Stack Overflow", "/path/to/font.ttf", ("fontcolor", "white"), ("fontsize", "24")))
|
||||||
options.FontPath = "/path/to/font.ttf";
|
.OutputToFile("output.mp4").Arguments;
|
||||||
options.AddParam("fontcolor", "white")
|
|
||||||
.AddParam("fontsize", "24")
|
|
||||||
.AddParam("box", "1")
|
|
||||||
.AddParam("boxcolor", "black@0.5")
|
|
||||||
.AddParam("boxborderw", "5")
|
|
||||||
.AddParam("x", "(w-text_w)/2")
|
|
||||||
.AddParam("y", "(h-text_h)/2");
|
|
||||||
});
|
|
||||||
var str = GetArgumentsString(container);
|
|
||||||
|
|
||||||
Assert.AreEqual("-i \"input.mp4\" -vf drawtext=\"text='Stack Overflow': fontfile=/path/to/font.ttf: fontcolor=white: fontsize=24: box=1: boxcolor=black@0.5: boxborderw=5: x=(w-text_w)/2: y=(h-text_h)/2\" \"output.mp4\"", str);
|
Assert.AreEqual("-i \"input.mp4\" -vf drawtext=\"text='Stack Overflow':fontfile=/path/to/font.ttf:fontcolor=white:fontsize=24\" \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_StartNumber()
|
public void Builder_BuildString_StartNumber()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new StartNumberArgument(50));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithStartNumber(50).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -start_number 50 \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -start_number 50 \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_StartNumber_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().StartNumber(50));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -start_number 50 \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Threads_1()
|
public void Builder_BuildString_Threads_1()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new ThreadsArgument(50));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").UsingThreads(50).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -threads 50 \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -threads 50 \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Threads_1_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().Threads(50));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -threads 50 \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Threads_2()
|
public void Builder_BuildString_Threads_2()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new ThreadsArgument(true));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").UsingMultithreading(true).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual($"-i \"input.mp4\" -threads {Environment.ProcessorCount} \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, $"-i \"input.mp4\" -threads {Environment.ProcessorCount} \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Threads_2_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().MultiThreaded());
|
|
||||||
|
|
||||||
Assert.AreEqual(str, $"-i \"input.mp4\" -threads {Environment.ProcessorCount} \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Codec()
|
public void Builder_BuildString_Codec()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new VideoCodecArgument(VideoCodec.LibX264));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithVideoCodec(VideoCodec.LibX264).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Codec_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().VideoCodec(VideoCodec.LibX264));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Codec_Override()
|
public void Builder_BuildString_Codec_Override()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new VideoCodecArgument(VideoCodec.LibX264), new OverrideArgument());
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithVideoCodec(VideoCodec.LibX264).OutputToFile("output.mp4", true).Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\" -y", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p -y \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Codec_Override_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().VideoCodec(VideoCodec.LibX264).Override());
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p -y \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Duration()
|
public void Builder_BuildString_Duration()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new DurationArgument(TimeSpan.FromSeconds(20)));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithDuration(TimeSpan.FromSeconds(20)).OutputToFile("output.mp4").Arguments;
|
||||||
|
Assert.AreEqual("-i \"input.mp4\" -t 00:00:20 \"output.mp4\"", str);
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -t 00:00:20 \"output.mp4\"");
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void Builder_BuildString_Duration_Fluent()
|
|
||||||
{
|
|
||||||
var str = GetArgumentsString(new ArgumentContainer().Duration(TimeSpan.FromSeconds(20)));
|
|
||||||
|
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -t 00:00:20 \"output.mp4\"");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Raw()
|
public void Builder_BuildString_Raw()
|
||||||
{
|
{
|
||||||
var str = GetArgumentsString(new CustomArgument(null));
|
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithCustomArgument(null).OutputToFile("output.mp4").Arguments;
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" \"output.mp4\"");
|
Assert.AreEqual("-i \"input.mp4\" \"output.mp4\"", str);
|
||||||
|
|
||||||
str = GetArgumentsString(new CustomArgument("-acodec copy"));
|
str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithCustomArgument("-acodec copy").OutputToFile("output.mp4").Arguments;
|
||||||
Assert.AreEqual(str, "-i \"input.mp4\" -acodec copy \"output.mp4\"");
|
Assert.AreEqual("-i \"input.mp4\" -acodec copy \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
using FFMpegCore.Enums;
|
using System;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
using FFMpegCore.Test.Resources;
|
using FFMpegCore.Test.Resources;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -15,14 +16,12 @@ public void Audio_Remove()
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Encoder.Mute(VideoInfo.FromFileInfo(Input), output);
|
FFMpeg.Mute(Input.FullName, output);
|
||||||
|
Assert.IsTrue(File.Exists(output));
|
||||||
Assert.IsTrue(File.Exists(output.FullName));
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output)) File.Delete(output);
|
||||||
output.Delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,14 +32,12 @@ public void Audio_Save()
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Encoder.ExtractAudio(VideoInfo.FromFileInfo(Input), output);
|
FFMpeg.ExtractAudio(Input.FullName, output);
|
||||||
|
Assert.IsTrue(File.Exists(output));
|
||||||
Assert.IsTrue(File.Exists(output.FullName));
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output)) File.Delete(output);
|
||||||
output.Delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,16 +47,17 @@ public void Audio_Add()
|
||||||
var output = Input.OutputLocation(VideoType.Mp4);
|
var output = Input.OutputLocation(VideoType.Mp4);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoNoAudio);
|
var success = FFMpeg.ReplaceAudio(VideoLibrary.LocalVideoNoAudio.FullName, VideoLibrary.LocalAudio.FullName, output);
|
||||||
Encoder.ReplaceAudio(input, VideoLibrary.LocalAudio, output);
|
Assert.IsTrue(success);
|
||||||
|
var audioAnalysis = FFProbe.Analyse(VideoLibrary.LocalVideoNoAudio.FullName);
|
||||||
Assert.AreEqual(input.Duration, VideoInfo.FromFileInfo(output).Duration);
|
var videoAnalysis = FFProbe.Analyse(VideoLibrary.LocalAudio.FullName);
|
||||||
Assert.IsTrue(File.Exists(output.FullName));
|
var outputAnalysis = FFProbe.Analyse(output);
|
||||||
|
Assert.AreEqual(Math.Max(videoAnalysis.Duration.TotalSeconds, audioAnalysis.Duration.TotalSeconds), outputAnalysis.Duration.TotalSeconds, 0.15);
|
||||||
|
Assert.IsTrue(File.Exists(output));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output)) File.Delete(output);
|
||||||
output.Delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,14 +68,14 @@ public void Image_AddAudio()
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = Encoder.PosterWithAudio(new FileInfo(VideoLibrary.LocalCover.FullName), VideoLibrary.LocalAudio, output);
|
FFMpeg.PosterWithAudio(VideoLibrary.LocalCover.FullName, VideoLibrary.LocalAudio.FullName, output);
|
||||||
Assert.IsTrue(result.Duration.TotalSeconds > 0);
|
var analysis = FFProbe.Analyse(VideoLibrary.LocalAudio.FullName);
|
||||||
Assert.IsTrue(result.Exists);
|
Assert.IsTrue(analysis.Duration.TotalSeconds > 0);
|
||||||
|
Assert.IsTrue(File.Exists(output));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output)) File.Delete(output);
|
||||||
output.Delete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
using FFMpegCore.FFMPEG;
|
using FFMpegCore.Test.Resources;
|
||||||
using FFMpegCore.Test.Resources;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
public class BaseTest
|
public class BaseTest
|
||||||
{
|
{
|
||||||
protected FFMpeg Encoder;
|
|
||||||
protected FileInfo Input;
|
protected FileInfo Input;
|
||||||
|
|
||||||
public BaseTest()
|
public BaseTest()
|
||||||
{
|
{
|
||||||
Encoder = new FFMpeg();
|
|
||||||
Input = VideoLibrary.LocalVideo;
|
Input = VideoLibrary.LocalVideo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
using FFMpegCore.Extend;
|
using FFMpegCore.Extend;
|
||||||
using FFMpegCore.FFMPEG.Pipes;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text;
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
|
||||||
|
<LangVersion>default</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using FFMpegCore.FFMPEG;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using FFMpegCore.Enums;
|
using System.Threading.Tasks;
|
||||||
using FFMpegCore.FFMPEG;
|
|
||||||
using FFMpegCore.FFMPEG.Argument;
|
|
||||||
using FFMpegCore.Test.Resources;
|
using FFMpegCore.Test.Resources;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
|
@ -14,47 +11,59 @@ public class FFProbeTests
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Probe_TooLongOutput()
|
public void Probe_TooLongOutput()
|
||||||
{
|
{
|
||||||
var output = new FFProbe(5);
|
Assert.ThrowsException<System.Text.Json.JsonException>(() => FFProbe.Analyse(VideoLibrary.LocalVideo.FullName, 5));
|
||||||
|
|
||||||
Assert.ThrowsException<JsonSerializationException>(() =>
|
|
||||||
{
|
|
||||||
output.ParseVideoInfo(VideoLibrary.LocalVideo.FullName);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Probe_Success()
|
public void Probe_Success()
|
||||||
{
|
{
|
||||||
var output = new FFProbe();
|
var info = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
||||||
|
Assert.AreEqual(13, info.Duration.Seconds);
|
||||||
|
Assert.AreEqual(".mp4", info.Extension);
|
||||||
|
Assert.AreEqual(VideoLibrary.LocalVideo.FullName, info.Path);
|
||||||
|
|
||||||
var info = output.ParseVideoInfo(VideoLibrary.LocalVideo.FullName);
|
Assert.AreEqual("5.1", info.PrimaryAudioStream.ChannelLayout);
|
||||||
|
Assert.AreEqual(6, info.PrimaryAudioStream.Channels);
|
||||||
|
Assert.AreEqual("AAC (Advanced Audio Coding)", info.PrimaryAudioStream.CodecLongName);
|
||||||
|
Assert.AreEqual("aac", info.PrimaryAudioStream.CodecName);
|
||||||
|
Assert.AreEqual(381988, info.PrimaryAudioStream.BitRate);
|
||||||
|
Assert.AreEqual(48000, info.PrimaryAudioStream.SampleRateHz);
|
||||||
|
|
||||||
|
Assert.AreEqual(862991, info.PrimaryVideoStream.BitRate);
|
||||||
|
Assert.AreEqual(16, info.PrimaryVideoStream.DisplayAspectRatio.Width);
|
||||||
|
Assert.AreEqual(9, info.PrimaryVideoStream.DisplayAspectRatio.Height);
|
||||||
|
Assert.AreEqual("yuv420p", info.PrimaryVideoStream.PixelFormat);
|
||||||
|
Assert.AreEqual(1280, info.PrimaryVideoStream.Width);
|
||||||
|
Assert.AreEqual(720, info.PrimaryVideoStream.Height);
|
||||||
|
Assert.AreEqual(25, info.PrimaryVideoStream.AvgFrameRate);
|
||||||
|
Assert.AreEqual(25, info.PrimaryVideoStream.FrameRate);
|
||||||
|
Assert.AreEqual("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", info.PrimaryVideoStream.CodecLongName);
|
||||||
|
Assert.AreEqual("h264", info.PrimaryVideoStream.CodecName);
|
||||||
|
Assert.AreEqual(8, info.PrimaryVideoStream.BitsPerRawSample);
|
||||||
|
Assert.AreEqual("Main", info.PrimaryVideoStream.Profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public async Task Probe_Async_Success()
|
||||||
|
{
|
||||||
|
var info = await FFProbe.AnalyseAsync(VideoLibrary.LocalVideo.FullName);
|
||||||
Assert.AreEqual(13, info.Duration.Seconds);
|
Assert.AreEqual(13, info.Duration.Seconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Probe_Success_FromStream()
|
public void Probe_Success_FromStream()
|
||||||
{
|
{
|
||||||
var output = new FFProbe();
|
using var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName);
|
||||||
|
var info = FFProbe.Analyse(stream);
|
||||||
using (var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName))
|
|
||||||
{
|
|
||||||
var info = output.ParseVideoInfo(stream);
|
|
||||||
Assert.AreEqual(10, info.Duration.Seconds);
|
Assert.AreEqual(10, info.Duration.Seconds);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod]
|
||||||
public void Probe_Success_FromStream_Async()
|
public async Task Probe_Success_FromStream_Async()
|
||||||
{
|
{
|
||||||
var output = new FFProbe();
|
await using var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName);
|
||||||
|
var info = await FFProbe.AnalyseAsync(stream);
|
||||||
using (var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName))
|
|
||||||
{
|
|
||||||
var info = output.ParseVideoInfoAsync(stream).WaitForResult();
|
|
||||||
|
|
||||||
Assert.AreEqual(10, info.Duration.Seconds);
|
Assert.AreEqual(10, info.Duration.Seconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
|
@ -25,27 +25,27 @@ public static class VideoLibrary
|
||||||
public static readonly FileInfo ImageDirectory = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images");
|
public static readonly FileInfo ImageDirectory = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images");
|
||||||
public static readonly FileInfo ImageJoinOutput = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images{Path.DirectorySeparatorChar}output.mp4");
|
public static readonly FileInfo ImageJoinOutput = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images{Path.DirectorySeparatorChar}output.mp4");
|
||||||
|
|
||||||
public static FileInfo OutputLocation(this FileInfo file, VideoType type)
|
public static string OutputLocation(this FileInfo file, VideoType type)
|
||||||
{
|
{
|
||||||
return OutputLocation(file, type, "_converted");
|
return OutputLocation(file, type, "_converted");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileInfo OutputLocation(this FileInfo file, AudioType type)
|
public static string OutputLocation(this FileInfo file, AudioType type)
|
||||||
{
|
{
|
||||||
return OutputLocation(file, type, "_audio");
|
return OutputLocation(file, type, "_audio");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileInfo OutputLocation(this FileInfo file, ImageType type)
|
public static string OutputLocation(this FileInfo file, ImageType type)
|
||||||
{
|
{
|
||||||
return OutputLocation(file, type, "_screenshot");
|
return OutputLocation(file, type, "_screenshot");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FileInfo OutputLocation(this FileInfo file, Enum type, string keyword)
|
public static string OutputLocation(this FileInfo file, Enum type, string keyword)
|
||||||
{
|
{
|
||||||
string originalLocation = file.Directory.FullName,
|
string originalLocation = file.Directory.FullName,
|
||||||
outputFile = file.Name.Replace(file.Extension, keyword + "." + type.ToString().ToLower());
|
outputFile = file.Name.Replace(file.Extension, keyword + "." + type.ToString().ToLower());
|
||||||
|
|
||||||
return new FileInfo($"{originalLocation}{Path.DirectorySeparatorChar}{outputFile}");
|
return $"{originalLocation}{Path.DirectorySeparatorChar}{outputFile}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
using System.Threading.Tasks;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
using FFMpegCore.Enums;
|
using FFMpegCore.Enums;
|
||||||
using FFMpegCore.FFMPEG.Argument;
|
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
using FFMpegCore.FFMPEG.Exceptions;
|
|
||||||
using FFMpegCore.FFMPEG.Pipes;
|
|
||||||
using FFMpegCore.Test.Resources;
|
using FFMpegCore.Test.Resources;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using System;
|
using System;
|
||||||
|
@ -10,6 +6,10 @@
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FFMpegCore.Arguments;
|
||||||
|
using FFMpegCore.Exceptions;
|
||||||
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
|
@ -22,257 +22,243 @@ public bool Convert(VideoType type, bool multithreaded = false, VideoSize size =
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var input = VideoInfo.FromFileInfo(Input);
|
var input = FFProbe.Analyse(Input.FullName);
|
||||||
|
FFMpeg.Convert(input, output, type, size: size, multithreaded: multithreaded);
|
||||||
|
var outputVideo = FFProbe.Analyse(output);
|
||||||
|
|
||||||
Encoder.Convert(input, output, type, size: size, multithreaded: multithreaded);
|
Assert.IsTrue(File.Exists(output));
|
||||||
|
Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1);
|
||||||
var outputVideo = new VideoInfo(output.FullName);
|
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output.FullName));
|
|
||||||
Assert.AreEqual(outputVideo.Duration, input.Duration);
|
|
||||||
if (size == VideoSize.Original)
|
if (size == VideoSize.Original)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(outputVideo.Width, input.Width);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||||
Assert.AreEqual(outputVideo.Height, input.Height);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assert.AreNotEqual(outputVideo.Width, input.Width);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||||
Assert.AreNotEqual(outputVideo.Height, input.Height);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||||
Assert.AreEqual(outputVideo.Height, (int)size);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, (int)size);
|
||||||
}
|
}
|
||||||
return File.Exists(output.FullName) &&
|
return File.Exists(output) &&
|
||||||
outputVideo.Duration == input.Duration &&
|
outputVideo.Duration == input.Duration &&
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
size == VideoSize.Original &&
|
size == VideoSize.Original &&
|
||||||
outputVideo.Width == input.Width &&
|
outputVideo.PrimaryVideoStream.Width == input.PrimaryVideoStream.Width &&
|
||||||
outputVideo.Height == input.Height
|
outputVideo.PrimaryVideoStream.Height == input.PrimaryVideoStream.Height
|
||||||
) ||
|
) ||
|
||||||
(
|
(
|
||||||
size != VideoSize.Original &&
|
size != VideoSize.Original &&
|
||||||
outputVideo.Width != input.Width &&
|
outputVideo.PrimaryVideoStream.Width != input.PrimaryVideoStream.Width &&
|
||||||
outputVideo.Height != input.Height &&
|
outputVideo.PrimaryVideoStream.Height != input.PrimaryVideoStream.Height &&
|
||||||
outputVideo.Height == (int)size
|
outputVideo.PrimaryVideoStream.Height == (int)size
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output))
|
||||||
File.Delete(output.FullName);
|
File.Delete(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConvertFromStreamPipe(VideoType type, ArgumentContainer container)
|
private void ConvertFromStreamPipe(VideoType type, params IArgument[] inputArguments)
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(type);
|
var output = Input.OutputLocation(type);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm);
|
var input = FFProbe.Analyse(VideoLibrary.LocalVideoWebm.FullName);
|
||||||
using (var inputStream = System.IO.File.OpenRead(input.FullName))
|
using (var inputStream = System.IO.File.OpenRead(input.Path))
|
||||||
{
|
{
|
||||||
var pipeSource = new StreamPipeDataWriter(inputStream);
|
var pipeSource = new StreamPipeDataWriter(inputStream);
|
||||||
var arguments = new ArgumentContainer { new InputPipeArgument(pipeSource) };
|
var arguments = FFMpegArguments.FromPipe(pipeSource);
|
||||||
foreach (var arg in container)
|
foreach (var arg in inputArguments)
|
||||||
|
arguments.WithArgument(arg);
|
||||||
|
var processor = arguments.OutputToFile(output);
|
||||||
|
|
||||||
|
var scaling = arguments.Find<ScaleArgument>();
|
||||||
|
|
||||||
|
var success = processor.ProcessSynchronously();
|
||||||
|
|
||||||
|
var outputVideo = FFProbe.Analyse(output);
|
||||||
|
|
||||||
|
Assert.IsTrue(success);
|
||||||
|
Assert.IsTrue(File.Exists(output));
|
||||||
|
Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate);
|
||||||
|
|
||||||
|
if (scaling?.Size == null)
|
||||||
{
|
{
|
||||||
arguments.Add(arg.Value);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||||
}
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||||
arguments.Add(new OutputArgument(output));
|
|
||||||
|
|
||||||
var scaling = container.Find<ScaleArgument>();
|
|
||||||
|
|
||||||
Encoder.Convert(arguments);
|
|
||||||
|
|
||||||
var outputVideo = new VideoInfo(output.FullName);
|
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output.FullName));
|
|
||||||
Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.FrameRate);
|
|
||||||
|
|
||||||
if (scaling == null)
|
|
||||||
{
|
|
||||||
Assert.AreEqual(outputVideo.Width, input.Width);
|
|
||||||
Assert.AreEqual(outputVideo.Height, input.Height);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (scaling.Value.Width != -1)
|
if (scaling.Size.Value.Width != -1)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(outputVideo.Width, scaling.Value.Width);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scaling.Value.Height != -1)
|
if (scaling.Size.Value.Height != -1)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(outputVideo.Height, scaling.Value.Height);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.AreNotEqual(outputVideo.Width, input.Width);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||||
Assert.AreNotEqual(outputVideo.Height, input.Height);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output))
|
||||||
File.Delete(output.FullName);
|
File.Delete(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConvertToStreamPipe(VideoType type, ArgumentContainer container)
|
private void ConvertToStreamPipe(params IArgument[] inputArguments)
|
||||||
{
|
{
|
||||||
using (var ms = new MemoryStream())
|
using var ms = new MemoryStream();
|
||||||
{
|
var arguments = FFMpegArguments.FromInputFiles(VideoLibrary.LocalVideo);
|
||||||
var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideo);
|
foreach (var arg in inputArguments)
|
||||||
var arguments = new ArgumentContainer { new InputArgument(input) };
|
arguments.WithArgument(arg);
|
||||||
|
|
||||||
foreach (var arg in container)
|
|
||||||
{
|
|
||||||
arguments.Add(arg.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
var streamPipeDataReader = new StreamPipeDataReader(ms);
|
var streamPipeDataReader = new StreamPipeDataReader(ms);
|
||||||
streamPipeDataReader.BlockSize = streamPipeDataReader.BlockSize * 16;
|
var processor = arguments.OutputToPipe(streamPipeDataReader);
|
||||||
arguments.Add(new OutputPipeArgument(streamPipeDataReader));
|
|
||||||
|
|
||||||
var scaling = container.Find<ScaleArgument>();
|
var scaling = arguments.Find<ScaleArgument>();
|
||||||
|
|
||||||
Encoder.Convert(arguments);
|
processor.ProcessSynchronously();
|
||||||
|
|
||||||
ms.Position = 0;
|
ms.Position = 0;
|
||||||
var outputVideo = VideoInfo.FromStream(ms);
|
var outputVideo = FFProbe.Analyse(ms);
|
||||||
|
|
||||||
//Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.FrameRate);
|
var input = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
||||||
|
// Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate);
|
||||||
|
|
||||||
if (scaling == null)
|
if (scaling?.Size == null)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(outputVideo.Width, input.Width);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||||
Assert.AreEqual(outputVideo.Height, input.Height);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (scaling.Value.Width != -1)
|
if (scaling.Size.Value.Width != -1)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(outputVideo.Width, scaling.Value.Width);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scaling.Value.Height != -1)
|
if (scaling.Size.Value.Height != -1)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(outputVideo.Height, scaling.Value.Height);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.AreNotEqual(outputVideo.Width, input.Width);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||||
Assert.AreNotEqual(outputVideo.Height, input.Height);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Convert(VideoType type, ArgumentContainer container)
|
public void Convert(VideoType type, params IArgument[] inputArguments)
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(type);
|
var output = Input.OutputLocation(type);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var input = VideoInfo.FromFileInfo(Input);
|
var input = FFProbe.Analyse(Input.FullName);
|
||||||
|
|
||||||
var arguments = new ArgumentContainer { new InputArgument(input) };
|
var arguments = FFMpegArguments.FromInputFiles(VideoLibrary.LocalVideo.FullName);
|
||||||
foreach (var arg in container)
|
foreach (var arg in inputArguments)
|
||||||
|
arguments.WithArgument(arg);
|
||||||
|
|
||||||
|
var processor = arguments.OutputToFile(output);
|
||||||
|
|
||||||
|
var scaling = arguments.Find<ScaleArgument>();
|
||||||
|
processor.ProcessSynchronously();
|
||||||
|
|
||||||
|
var outputVideo = FFProbe.Analyse(output);
|
||||||
|
|
||||||
|
Assert.IsTrue(File.Exists(output));
|
||||||
|
Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1);
|
||||||
|
|
||||||
|
if (scaling?.Size == null)
|
||||||
{
|
{
|
||||||
arguments.Add(arg.Value);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||||
}
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||||
arguments.Add(new OutputArgument(output));
|
|
||||||
|
|
||||||
var scaling = container.Find<ScaleArgument>();
|
|
||||||
|
|
||||||
Encoder.Convert(arguments);
|
|
||||||
|
|
||||||
var outputVideo = new VideoInfo(output.FullName);
|
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output.FullName));
|
|
||||||
Assert.AreEqual(outputVideo.Duration, input.Duration);
|
|
||||||
|
|
||||||
if (scaling == null)
|
|
||||||
{
|
|
||||||
Assert.AreEqual(outputVideo.Width, input.Width);
|
|
||||||
Assert.AreEqual(outputVideo.Height, input.Height);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (scaling.Value.Width != -1)
|
if (scaling.Size.Value.Width != -1)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(outputVideo.Width, scaling.Value.Width);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scaling.Value.Height != -1)
|
if (scaling.Size.Value.Height != -1)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(outputVideo.Height, scaling.Value.Height);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.AreNotEqual(outputVideo.Width, input.Width);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
|
||||||
Assert.AreNotEqual(outputVideo.Height, input.Height);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output))
|
||||||
File.Delete(output.FullName);
|
File.Delete(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConvertFromPipe(VideoType type, ArgumentContainer container)
|
public void ConvertFromPipe(VideoType type, params IArgument[] inputArguments)
|
||||||
{
|
{
|
||||||
ConvertFromPipe(type, container, PixelFormat.Format24bppRgb);
|
ConvertFromPipe(type, PixelFormat.Format24bppRgb, inputArguments);
|
||||||
ConvertFromPipe(type, container, PixelFormat.Format32bppArgb);
|
ConvertFromPipe(type, PixelFormat.Format32bppArgb, inputArguments);
|
||||||
ConvertFromPipe(type, container, PixelFormat.Format48bppRgb);
|
ConvertFromPipe(type, PixelFormat.Format48bppRgb, inputArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConvertFromPipe(VideoType type, ArgumentContainer container, PixelFormat fmt)
|
public void ConvertFromPipe(VideoType type, PixelFormat fmt, params IArgument[] inputArguments)
|
||||||
{
|
{
|
||||||
var output = Input.OutputLocation(type);
|
var output = Input.OutputLocation(type);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var videoFramesSource = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, fmt, 256, 256));
|
var videoFramesSource = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, fmt, 256, 256));
|
||||||
var arguments = new ArgumentContainer { new InputPipeArgument(videoFramesSource) };
|
var arguments = FFMpegArguments.FromPipe(videoFramesSource);
|
||||||
foreach (var arg in container)
|
foreach (var arg in inputArguments)
|
||||||
|
arguments.WithArgument(arg);
|
||||||
|
var processor = arguments.OutputToFile(output);
|
||||||
|
|
||||||
|
var scaling = arguments.Find<ScaleArgument>();
|
||||||
|
processor.ProcessSynchronously();
|
||||||
|
|
||||||
|
var outputVideo = FFProbe.Analyse(output);
|
||||||
|
|
||||||
|
Assert.IsTrue(File.Exists(output));
|
||||||
|
|
||||||
|
if (scaling?.Size == null)
|
||||||
{
|
{
|
||||||
arguments.Add(arg.Value);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, videoFramesSource.Width);
|
||||||
}
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, videoFramesSource.Height);
|
||||||
arguments.Add(new OutputArgument(output));
|
|
||||||
|
|
||||||
var scaling = container.Find<ScaleArgument>();
|
|
||||||
|
|
||||||
Encoder.Convert(arguments);
|
|
||||||
|
|
||||||
var outputVideo = new VideoInfo(output.FullName);
|
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output.FullName));
|
|
||||||
|
|
||||||
if (scaling == null)
|
|
||||||
{
|
|
||||||
Assert.AreEqual(outputVideo.Width, videoFramesSource.Width);
|
|
||||||
Assert.AreEqual(outputVideo.Height, videoFramesSource.Height);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (scaling.Value.Width != -1)
|
if (scaling.Size.Value.Width != -1)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(outputVideo.Width, scaling.Value.Width);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scaling.Value.Height != -1)
|
if (scaling.Size.Value.Height != -1)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(outputVideo.Height, scaling.Value.Height);
|
Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.AreNotEqual(outputVideo.Width, videoFramesSource.Width);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, videoFramesSource.Width);
|
||||||
Assert.AreNotEqual(outputVideo.Height, videoFramesSource.Height);
|
Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, videoFramesSource.Height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output))
|
||||||
File.Delete(output.FullName);
|
File.Delete(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -286,22 +272,19 @@ public void Video_ToMP4()
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_ToMP4_Args()
|
public void Video_ToMP4_Args()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer { new VideoCodecArgument(VideoCodec.LibX264) };
|
Convert(VideoType.Mp4, new VideoCodecArgument(VideoCodec.LibX264));
|
||||||
Convert(VideoType.Mp4, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_ToMP4_Args_Pipe()
|
public void Video_ToMP4_Args_Pipe()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer { new VideoCodecArgument(VideoCodec.LibX264) };
|
ConvertFromPipe(VideoType.Mp4, new VideoCodecArgument(VideoCodec.LibX264));
|
||||||
ConvertFromPipe(VideoType.Mp4, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_ToMP4_Args_StreamPipe()
|
public void Video_ToMP4_Args_StreamPipe()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer { new VideoCodecArgument(VideoCodec.LibX264) };
|
ConvertFromStreamPipe(VideoType.Mp4, new VideoCodecArgument(VideoCodec.LibX264));
|
||||||
ConvertFromStreamPipe(VideoType.Mp4, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(45000)]
|
[TestMethod, Timeout(45000)]
|
||||||
|
@ -309,20 +292,14 @@ public void Video_ToMP4_Args_StreamOutputPipe_Async_Failure()
|
||||||
{
|
{
|
||||||
Assert.ThrowsException<FFMpegException>(() =>
|
Assert.ThrowsException<FFMpegException>(() =>
|
||||||
{
|
{
|
||||||
using (var ms = new MemoryStream())
|
using var ms = new MemoryStream();
|
||||||
{
|
|
||||||
var pipeSource = new StreamPipeDataReader(ms);
|
var pipeSource = new StreamPipeDataReader(ms);
|
||||||
var container = new ArgumentContainer
|
FFMpegArguments
|
||||||
{
|
.FromInputFiles(VideoLibrary.LocalVideo)
|
||||||
new InputArgument(VideoLibrary.LocalVideoWebm),
|
.ForceFormat("mkv")
|
||||||
new VideoCodecArgument(VideoCodec.LibX264),
|
.OutputToPipe(pipeSource)
|
||||||
new ForceFormatArgument("mkv"),
|
.ProcessAsynchronously()
|
||||||
new OutputPipeArgument(pipeSource)
|
.WaitForResult();
|
||||||
};
|
|
||||||
|
|
||||||
var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm);
|
|
||||||
Encoder.ConvertAsync(container).WaitForResult();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,11 +308,7 @@ public void Video_ToMP4_Args_StreamOutputPipe_Failure()
|
||||||
{
|
{
|
||||||
Assert.ThrowsException<FFMpegException>(() =>
|
Assert.ThrowsException<FFMpegException>(() =>
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer
|
ConvertToStreamPipe(new ForceFormatArgument("mkv"));
|
||||||
{
|
|
||||||
new ForceFormatArgument("mkv")
|
|
||||||
};
|
|
||||||
ConvertToStreamPipe(VideoType.Mp4, container);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,31 +316,21 @@ public void Video_ToMP4_Args_StreamOutputPipe_Failure()
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_ToMP4_Args_StreamOutputPipe_Async()
|
public void Video_ToMP4_Args_StreamOutputPipe_Async()
|
||||||
{
|
{
|
||||||
using (var ms = new MemoryStream())
|
using var ms = new MemoryStream();
|
||||||
{
|
|
||||||
var pipeSource = new StreamPipeDataReader(ms);
|
var pipeSource = new StreamPipeDataReader(ms);
|
||||||
var container = new ArgumentContainer
|
FFMpegArguments
|
||||||
{
|
.FromInputFiles(VideoLibrary.LocalVideo)
|
||||||
new InputArgument(VideoLibrary.LocalVideo),
|
.WithVideoCodec(VideoCodec.LibX264)
|
||||||
new VideoCodecArgument(VideoCodec.LibX264),
|
.ForceFormat("matroska")
|
||||||
new ForceFormatArgument("matroska"),
|
.OutputToPipe(pipeSource)
|
||||||
new OutputPipeArgument(pipeSource)
|
.ProcessAsynchronously()
|
||||||
};
|
.WaitForResult();
|
||||||
|
|
||||||
var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm);
|
|
||||||
Encoder.ConvertAsync(container).WaitForResult();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_ToMP4_Args_StreamOutputPipe()
|
public void Video_ToMP4_Args_StreamOutputPipe()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer
|
ConvertToStreamPipe(new VideoCodecArgument(VideoCodec.LibX264), new ForceFormatArgument("matroska"));
|
||||||
{
|
|
||||||
new VideoCodecArgument(VideoCodec.LibX264),
|
|
||||||
new ForceFormatArgument("matroska")
|
|
||||||
};
|
|
||||||
ConvertToStreamPipe(VideoType.Mp4, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -379,23 +342,16 @@ public void Video_ToTS()
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_ToTS_Args()
|
public void Video_ToTS_Args()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer
|
Convert(VideoType.Ts,
|
||||||
{
|
|
||||||
new CopyArgument(),
|
new CopyArgument(),
|
||||||
new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB),
|
new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB),
|
||||||
new ForceFormatArgument(VideoCodec.MpegTs)
|
new ForceFormatArgument(VideoCodec.MpegTs));
|
||||||
};
|
|
||||||
Convert(VideoType.Ts, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_ToTS_Args_Pipe()
|
public void Video_ToTS_Args_Pipe()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer
|
ConvertFromPipe(VideoType.Ts, new ForceFormatArgument(VideoCodec.MpegTs));
|
||||||
{
|
|
||||||
new ForceFormatArgument(VideoCodec.MpegTs)
|
|
||||||
};
|
|
||||||
ConvertFromPipe(VideoType.Ts, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -407,23 +363,13 @@ public void Video_ToOGV_Resize()
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_ToOGV_Resize_Args()
|
public void Video_ToOGV_Resize_Args()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer
|
Convert(VideoType.Ogv, new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora));
|
||||||
{
|
|
||||||
new ScaleArgument(VideoSize.Ed),
|
|
||||||
new VideoCodecArgument(VideoCodec.LibTheora)
|
|
||||||
};
|
|
||||||
Convert(VideoType.Ogv, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_ToOGV_Resize_Args_Pipe()
|
public void Video_ToOGV_Resize_Args_Pipe()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer
|
ConvertFromPipe(VideoType.Ogv, new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora));
|
||||||
{
|
|
||||||
new ScaleArgument(VideoSize.Ed),
|
|
||||||
new VideoCodecArgument(VideoCodec.LibTheora)
|
|
||||||
};
|
|
||||||
ConvertFromPipe(VideoType.Ogv, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -435,23 +381,13 @@ public void Video_ToMP4_Resize()
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_ToMP4_Resize_Args()
|
public void Video_ToMP4_Resize_Args()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer
|
Convert(VideoType.Mp4, new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264));
|
||||||
{
|
|
||||||
new ScaleArgument(VideoSize.Ld),
|
|
||||||
new VideoCodecArgument(VideoCodec.LibX264)
|
|
||||||
};
|
|
||||||
Convert(VideoType.Mp4, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_ToMP4_Resize_Args_Pipe()
|
public void Video_ToMP4_Resize_Args_Pipe()
|
||||||
{
|
{
|
||||||
var container = new ArgumentContainer
|
ConvertFromPipe(VideoType.Mp4, new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264));
|
||||||
{
|
|
||||||
new ScaleArgument(VideoSize.Ld),
|
|
||||||
new VideoCodecArgument(VideoCodec.LibX264)
|
|
||||||
};
|
|
||||||
ConvertFromPipe(VideoType.Mp4, container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
@ -485,17 +421,17 @@ public void Video_Snapshot()
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var input = VideoInfo.FromFileInfo(Input);
|
var input = FFProbe.Analyse(Input.FullName);
|
||||||
|
|
||||||
using var bitmap = Encoder.Snapshot(input, output);
|
using var bitmap = FFMpeg.Snapshot(input, output);
|
||||||
Assert.AreEqual(input.Width, bitmap.Width);
|
Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width);
|
||||||
Assert.AreEqual(input.Height, bitmap.Height);
|
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
||||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output))
|
||||||
File.Delete(output.FullName);
|
File.Delete(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -505,18 +441,18 @@ public void Video_Snapshot_PersistSnapshot()
|
||||||
var output = Input.OutputLocation(ImageType.Png);
|
var output = Input.OutputLocation(ImageType.Png);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var input = VideoInfo.FromFileInfo(Input);
|
var input = FFProbe.Analyse(Input.FullName);
|
||||||
|
|
||||||
using var bitmap = Encoder.Snapshot(input, output, persistSnapshotOnFileSystem: true);
|
using var bitmap = FFMpeg.Snapshot(input, output, persistSnapshotOnFileSystem: true);
|
||||||
Assert.AreEqual(input.Width, bitmap.Width);
|
Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width);
|
||||||
Assert.AreEqual(input.Height, bitmap.Height);
|
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
||||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||||
Assert.IsTrue(File.Exists(output.FullName));
|
Assert.IsTrue(File.Exists(output));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output))
|
||||||
File.Delete(output.FullName);
|
File.Delete(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,28 +463,30 @@ public void Video_Join()
|
||||||
var newInput = Input.OutputLocation(VideoType.Mp4, "duplicate");
|
var newInput = Input.OutputLocation(VideoType.Mp4, "duplicate");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var input = VideoInfo.FromFileInfo(Input);
|
var input = FFProbe.Analyse(Input.FullName);
|
||||||
File.Copy(input.FullName, newInput.FullName);
|
File.Copy(input.Path, newInput);
|
||||||
var input2 = VideoInfo.FromFileInfo(newInput);
|
var input2 = FFProbe.Analyse(newInput);
|
||||||
|
|
||||||
var result = Encoder.Join(output, input, input2);
|
var success = FFMpeg.Join(output, input, input2);
|
||||||
|
Assert.IsTrue(success);
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output.FullName));
|
Assert.IsTrue(File.Exists(output));
|
||||||
var expectedDuration = input.Duration * 2;
|
var expectedDuration = input.Duration * 2;
|
||||||
|
var result = FFProbe.Analyse(output);
|
||||||
Assert.AreEqual(expectedDuration.Days, result.Duration.Days);
|
Assert.AreEqual(expectedDuration.Days, result.Duration.Days);
|
||||||
Assert.AreEqual(expectedDuration.Hours, result.Duration.Hours);
|
Assert.AreEqual(expectedDuration.Hours, result.Duration.Hours);
|
||||||
Assert.AreEqual(expectedDuration.Minutes, result.Duration.Minutes);
|
Assert.AreEqual(expectedDuration.Minutes, result.Duration.Minutes);
|
||||||
Assert.AreEqual(expectedDuration.Seconds, result.Duration.Seconds);
|
Assert.AreEqual(expectedDuration.Seconds, result.Duration.Seconds);
|
||||||
Assert.AreEqual(input.Height, result.Height);
|
Assert.AreEqual(input.PrimaryVideoStream.Height, result.PrimaryVideoStream.Height);
|
||||||
Assert.AreEqual(input.Width, result.Width);
|
Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output))
|
||||||
File.Delete(output.FullName);
|
File.Delete(output);
|
||||||
|
|
||||||
if (File.Exists(newInput.FullName))
|
if (File.Exists(newInput))
|
||||||
File.Delete(newInput.FullName);
|
File.Delete(newInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -569,14 +507,15 @@ public void Video_Join_Image_Sequence()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var result = Encoder.JoinImageSequence(VideoLibrary.ImageJoinOutput, images: imageSet.ToArray());
|
var success = FFMpeg.JoinImageSequence(VideoLibrary.ImageJoinOutput.FullName, images: imageSet.ToArray());
|
||||||
|
var result = FFProbe.Analyse(VideoLibrary.ImageJoinOutput.FullName);
|
||||||
|
|
||||||
VideoLibrary.ImageJoinOutput.Refresh();
|
VideoLibrary.ImageJoinOutput.Refresh();
|
||||||
|
|
||||||
Assert.IsTrue(VideoLibrary.ImageJoinOutput.Exists);
|
Assert.IsTrue(VideoLibrary.ImageJoinOutput.Exists);
|
||||||
Assert.AreEqual(3, result.Duration.Seconds);
|
Assert.AreEqual(3, result.Duration.Seconds);
|
||||||
Assert.AreEqual(imageSet.First().Width, result.Width);
|
Assert.AreEqual(imageSet.First().Width, result.PrimaryVideoStream.Width);
|
||||||
Assert.AreEqual(imageSet.First().Height, result.Height);
|
Assert.AreEqual(imageSet.First().Height, result.PrimaryVideoStream.Height);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
@ -591,32 +530,29 @@ public void Video_Join_Image_Sequence()
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_With_Only_Audio_Should_Extract_Metadata()
|
public void Video_With_Only_Audio_Should_Extract_Metadata()
|
||||||
{
|
{
|
||||||
var video = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoAudioOnly);
|
var video = FFProbe.Analyse(VideoLibrary.LocalVideoAudioOnly.FullName);
|
||||||
Assert.AreEqual("none", video.VideoFormat);
|
Assert.AreEqual(null, video.PrimaryVideoStream);
|
||||||
Assert.AreEqual("aac", video.AudioFormat);
|
Assert.AreEqual("aac", video.PrimaryAudioStream.CodecName);
|
||||||
Assert.AreEqual(79.5, video.Duration.TotalSeconds, 0.5);
|
Assert.AreEqual(79.5, video.Duration.TotalSeconds, 0.5);
|
||||||
Assert.AreEqual(1.25, video.Size);
|
// Assert.AreEqual(1.25, video.Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_Duration()
|
public void Video_Duration()
|
||||||
{
|
{
|
||||||
var video = VideoInfo.FromFileInfo(VideoLibrary.LocalVideo);
|
var video = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
||||||
var output = Input.OutputLocation(VideoType.Mp4);
|
var output = Input.OutputLocation(VideoType.Mp4);
|
||||||
|
|
||||||
var arguments = new ArgumentContainer
|
|
||||||
{
|
|
||||||
new InputArgument(VideoLibrary.LocalVideo),
|
|
||||||
new DurationArgument(TimeSpan.FromSeconds(video.Duration.TotalSeconds - 5)),
|
|
||||||
new OutputArgument(output)
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Encoder.Convert(arguments);
|
FFMpegArguments
|
||||||
|
.FromInputFiles(VideoLibrary.LocalVideo)
|
||||||
|
.WithDuration(TimeSpan.FromSeconds(video.Duration.TotalSeconds - 5))
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output.FullName));
|
Assert.IsTrue(File.Exists(output));
|
||||||
var outputVideo = new VideoInfo(output.FullName);
|
var outputVideo = FFProbe.Analyse(output);
|
||||||
|
|
||||||
Assert.AreEqual(video.Duration.Days, outputVideo.Duration.Days);
|
Assert.AreEqual(video.Duration.Days, outputVideo.Duration.Days);
|
||||||
Assert.AreEqual(video.Duration.Hours, outputVideo.Duration.Hours);
|
Assert.AreEqual(video.Duration.Hours, outputVideo.Duration.Hours);
|
||||||
|
@ -625,8 +561,8 @@ public void Video_Duration()
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output))
|
||||||
output.Delete();
|
File.Delete(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -636,54 +572,53 @@ public void Video_UpdatesProgress()
|
||||||
var output = Input.OutputLocation(VideoType.Mp4);
|
var output = Input.OutputLocation(VideoType.Mp4);
|
||||||
|
|
||||||
var percentageDone = 0.0;
|
var percentageDone = 0.0;
|
||||||
void OnProgess(double percentage) => percentageDone = percentage;
|
var timeDone = TimeSpan.Zero;
|
||||||
Encoder.OnProgress += OnProgess;
|
void OnPercentageProgess(double percentage) => percentageDone = percentage;
|
||||||
|
void OnTimeProgess(TimeSpan time) => timeDone = time;
|
||||||
|
|
||||||
|
var analysis = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
|
||||||
|
|
||||||
var arguments = new ArgumentContainer
|
|
||||||
{
|
|
||||||
new InputArgument(VideoLibrary.LocalVideo),
|
|
||||||
new DurationArgument(TimeSpan.FromSeconds(8)),
|
|
||||||
new OutputArgument(output)
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Encoder.Convert(arguments);
|
var success = FFMpegArguments
|
||||||
Encoder.OnProgress -= OnProgess;
|
.FromInputFiles(VideoLibrary.LocalVideo)
|
||||||
|
.WithDuration(TimeSpan.FromSeconds(8))
|
||||||
|
.OutputToFile(output)
|
||||||
|
.NotifyOnProgress(OnPercentageProgess, analysis.Duration)
|
||||||
|
.NotifyOnProgress(OnTimeProgess)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
|
||||||
Assert.IsTrue(File.Exists(output.FullName));
|
Assert.IsTrue(success);
|
||||||
|
Assert.IsTrue(File.Exists(output));
|
||||||
Assert.AreNotEqual(0.0, percentageDone);
|
Assert.AreNotEqual(0.0, percentageDone);
|
||||||
|
Assert.AreNotEqual(TimeSpan.Zero, timeDone);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(output.FullName))
|
if (File.Exists(output))
|
||||||
output.Delete();
|
File.Delete(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Video_TranscodeInMemory()
|
public void Video_TranscodeInMemory()
|
||||||
{
|
{
|
||||||
using (var resStream = new MemoryStream())
|
using var resStream = new MemoryStream();
|
||||||
{
|
|
||||||
var reader = new StreamPipeDataReader(resStream);
|
var reader = new StreamPipeDataReader(resStream);
|
||||||
var writer = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, PixelFormat.Format24bppRgb, 128, 128));
|
var writer = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, PixelFormat.Format24bppRgb, 128, 128));
|
||||||
|
|
||||||
var container = new ArgumentContainer
|
FFMpegArguments
|
||||||
{
|
.FromPipe(writer)
|
||||||
new InputPipeArgument(writer),
|
.WithVideoCodec("vp9")
|
||||||
new VideoCodecArgument("vp9"),
|
.ForceFormat("webm")
|
||||||
new ForceFormatArgument("webm"),
|
.OutputToPipe(reader)
|
||||||
new OutputPipeArgument(reader)
|
.ProcessSynchronously();
|
||||||
};
|
|
||||||
|
|
||||||
Encoder.Convert(container);
|
|
||||||
|
|
||||||
resStream.Position = 0;
|
resStream.Position = 0;
|
||||||
var vi = VideoInfo.FromStream(resStream);
|
var vi = FFProbe.Analyse(resStream);
|
||||||
Assert.AreEqual(vi.Width, 128);
|
Assert.AreEqual(vi.PrimaryVideoStream.Width, 128);
|
||||||
Assert.AreEqual(vi.Height, 128);
|
Assert.AreEqual(vi.PrimaryVideoStream.Height, 128);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"RootDirectory": "/usr/bin"
|
"RootDirectory": ""
|
||||||
}
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
using System;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Enums
|
namespace FFMpegCore.Enums
|
||||||
{
|
{
|
||||||
public static class FileExtension
|
public static class FileExtension
|
||||||
{
|
{
|
||||||
public static string ForType(VideoType type)
|
public static string Extension(this VideoType type)
|
||||||
{
|
{
|
||||||
return type switch
|
return type switch
|
||||||
{
|
{
|
||||||
|
@ -16,7 +15,7 @@ public static string ForType(VideoType type)
|
||||||
_ => throw new Exception("The extension for this video type is not defined.")
|
_ => throw new Exception("The extension for this video type is not defined.")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public static string ForCodec(VideoCodec type)
|
public static string Extension(this VideoCodec type)
|
||||||
{
|
{
|
||||||
return type switch
|
return type switch
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,26 +1,22 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using FFMpegCore.FFMPEG;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Extend
|
namespace FFMpegCore.Extend
|
||||||
{
|
{
|
||||||
public static class BitmapExtensions
|
public static class BitmapExtensions
|
||||||
{
|
{
|
||||||
public static VideoInfo AddAudio(this Bitmap poster, FileInfo audio, FileInfo output)
|
public static bool AddAudio(this Bitmap poster, string audio, string output)
|
||||||
{
|
{
|
||||||
var destination = $"{Environment.TickCount}.png";
|
var destination = $"{Environment.TickCount}.png";
|
||||||
|
|
||||||
poster.Save(destination);
|
poster.Save(destination);
|
||||||
|
|
||||||
var tempFile = new FileInfo(destination);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new FFMpeg().PosterWithAudio(tempFile, audio, output);
|
return FFMpeg.PosterWithAudio(destination, audio, output);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
tempFile.Delete();
|
if (File.Exists(destination)) File.Delete(destination);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
using FFMpegCore.FFMPEG.Pipes;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
namespace FFMpegCore.Extend
|
namespace FFMpegCore.Extend
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using FFMpegCore.FFMPEG;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Extend
|
namespace FFMpegCore.Extend
|
||||||
{
|
{
|
||||||
public static class UriExtensions
|
public static class UriExtensions
|
||||||
{
|
{
|
||||||
public static VideoInfo SaveStream(this Uri uri, FileInfo output)
|
public static bool SaveStream(this Uri uri, string output)
|
||||||
{
|
{
|
||||||
return new FFMpeg().SaveM3U8Stream(uri, output);
|
return FFMpeg.SaveM3U8Stream(uri, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Abstract class implements basic functionality of ffmpeg arguments
|
|
||||||
/// </summary>
|
|
||||||
public abstract class Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// String representation of the argument
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>String representation of the argument</returns>
|
|
||||||
public abstract string GetStringValue();
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return GetStringValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Abstract class implements basic functionality of ffmpeg arguments with one value property
|
|
||||||
/// </summary>
|
|
||||||
public abstract class Argument<T> : Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Value type of <see cref="T"/>
|
|
||||||
/// </summary>
|
|
||||||
public T Value { get; protected set; }
|
|
||||||
|
|
||||||
public Argument() { }
|
|
||||||
|
|
||||||
public Argument(T value)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Abstract class implements basic functionality of ffmpeg arguments with two values properties
|
|
||||||
/// </summary>
|
|
||||||
public abstract class Argument<T1, T2> : Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// First value type of <see cref="T1"/>
|
|
||||||
/// </summary>
|
|
||||||
public T1 First { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Second value type of <see cref="T2"/>
|
|
||||||
/// </summary>
|
|
||||||
public T2 Second { get; }
|
|
||||||
|
|
||||||
public Argument() { }
|
|
||||||
|
|
||||||
public Argument(T1 first, T2 second)
|
|
||||||
{
|
|
||||||
First = first;
|
|
||||||
Second = second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Container of arguments represented parameters of FFMPEG process
|
|
||||||
/// </summary>
|
|
||||||
public class ArgumentContainer : IDictionary<Type, Argument>
|
|
||||||
{
|
|
||||||
IDictionary<Type, Argument> _args;
|
|
||||||
|
|
||||||
public ArgumentContainer(params Argument[] arguments)
|
|
||||||
{
|
|
||||||
_args = new Dictionary<Type, Argument>();
|
|
||||||
|
|
||||||
foreach (var argument in arguments)
|
|
||||||
{
|
|
||||||
Add(argument);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Argument this[Type key] { get => _args[key]; set => _args[key] = value; }
|
|
||||||
|
|
||||||
public bool TryGetArgument<T>(out T output)
|
|
||||||
where T : Argument
|
|
||||||
{
|
|
||||||
if (_args.TryGetValue(typeof(T), out var arg))
|
|
||||||
{
|
|
||||||
output = (T)arg;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
output = default;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICollection<Type> Keys => _args.Keys;
|
|
||||||
|
|
||||||
public ICollection<Argument> Values => _args.Values;
|
|
||||||
|
|
||||||
public int Count => _args.Count;
|
|
||||||
|
|
||||||
public bool IsReadOnly => _args.IsReadOnly;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This method is not supported, left for <see cref="{IDictionary<Type, Argument>}"/> interface support
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
[Obsolete]
|
|
||||||
public void Add(Type key, Argument value)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Not supported operation");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This method is not supported, left for <see cref="{IDictionary<Type, Argument>}"/> interface support
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key"></param>
|
|
||||||
/// <param name="value"></param>
|
|
||||||
[Obsolete]
|
|
||||||
public void Add(KeyValuePair<Type, Argument> item)
|
|
||||||
{
|
|
||||||
Add(item.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears collection of arguments
|
|
||||||
/// </summary>
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
_args.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns if contains item
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">Searching item</param>
|
|
||||||
/// <returns>Returns if contains item</returns>
|
|
||||||
public bool Contains(KeyValuePair<Type, Argument> item)
|
|
||||||
{
|
|
||||||
return _args.Contains(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds argument to collection
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">Argument that should be added to collection</param>
|
|
||||||
public void Add(params Argument[] values)
|
|
||||||
{
|
|
||||||
foreach (var value in values)
|
|
||||||
{
|
|
||||||
_args.Add(value.GetType(), value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if container contains output and input parameters
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public bool ContainsInputOutput()
|
|
||||||
{
|
|
||||||
return ContainsOnlyOneOf(typeof(InputArgument), typeof(ConcatArgument), typeof(InputPipeArgument)) &&
|
|
||||||
ContainsOnlyOneOf(typeof(OutputArgument), typeof(OutputPipeArgument));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if contains argument of type
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="key">Type of argument is seraching</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public bool ContainsKey(Type key)
|
|
||||||
{
|
|
||||||
return _args.ContainsKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ContainsOnlyOneOf(params Type[] types)
|
|
||||||
{
|
|
||||||
return types.Count(t => _args.ContainsKey(t)) == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CopyTo(KeyValuePair<Type, Argument>[] array, int arrayIndex)
|
|
||||||
{
|
|
||||||
_args.CopyTo(array, arrayIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<Type, Argument>> GetEnumerator()
|
|
||||||
{
|
|
||||||
return _args.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(Type key)
|
|
||||||
{
|
|
||||||
return _args.Remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(KeyValuePair<Type, Argument> item)
|
|
||||||
{
|
|
||||||
return _args.Remove(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetValue(Type key, out Argument value)
|
|
||||||
{
|
|
||||||
return _args.TryGetValue(key, out value);
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return _args.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shortcut for finding arguments inside collection
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type of argument</typeparam>
|
|
||||||
/// <returns></returns>
|
|
||||||
public T Find<T>() where T : Argument
|
|
||||||
{
|
|
||||||
if (ContainsKey(typeof(T)))
|
|
||||||
return (T)_args[typeof(T)];
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Shortcut for checking if contains arguments inside collection
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">Type of argument</typeparam>
|
|
||||||
/// <returns></returns>
|
|
||||||
public bool Contains<T>() where T : Argument
|
|
||||||
{
|
|
||||||
if (ContainsKey(typeof(T)))
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,356 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument.Fluent
|
|
||||||
{
|
|
||||||
public static class ArgumentContainerFluentExtensions
|
|
||||||
{
|
|
||||||
public static ArgumentContainer AudioCodec(this ArgumentContainer container, AudioCodec codec)
|
|
||||||
{
|
|
||||||
container.Add(new AudioCodecArgument(codec));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer AudioBitrate(this ArgumentContainer container, AudioQuality audioQuality)
|
|
||||||
{
|
|
||||||
container.Add(new AudioBitrateArgument(audioQuality));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
public static ArgumentContainer AudioBitrate(this ArgumentContainer container, int bitrate)
|
|
||||||
{
|
|
||||||
container.Add(new AudioBitrateArgument(bitrate));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer AudioSamplingRate(this ArgumentContainer container)
|
|
||||||
{
|
|
||||||
container.Add(new AudioSamplingRateArgument());
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer AudioSamplingRate(this ArgumentContainer container, int sampleRate)
|
|
||||||
{
|
|
||||||
container.Add(new AudioSamplingRateArgument(sampleRate));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer BitStreamFilter(this ArgumentContainer container, Channel first, Filter second)
|
|
||||||
{
|
|
||||||
container.Add(new BitStreamFilterArgument(first, second));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Concat(this ArgumentContainer container, IEnumerable<string> paths)
|
|
||||||
{
|
|
||||||
container.Add(new ConcatArgument(paths));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer ConstantRateFactor(this ArgumentContainer container, int crf)
|
|
||||||
{
|
|
||||||
container.Add(new ConstantRateFactorArgument(crf));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Copy(this ArgumentContainer container)
|
|
||||||
{
|
|
||||||
container.Add(new CopyArgument());
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Copy(this ArgumentContainer container, Channel value)
|
|
||||||
{
|
|
||||||
container.Add(new CopyArgument(value));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer CpuSpeed(this ArgumentContainer container, int value)
|
|
||||||
{
|
|
||||||
container.Add(new CpuSpeedArgument(value));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer DisableChannel(this ArgumentContainer container, Channel channel)
|
|
||||||
{
|
|
||||||
container.Add(new DisableChannelArgument(channel));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DrawTextOptions
|
|
||||||
{
|
|
||||||
public string Text { get; set; }
|
|
||||||
public string FontPath { get; set; }
|
|
||||||
public List<(string, string)> Params { get; private set; }
|
|
||||||
|
|
||||||
public DrawTextOptions()
|
|
||||||
{
|
|
||||||
Params = new List<(string, string)>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DrawTextOptions AddParam(string key, string value)
|
|
||||||
{
|
|
||||||
Params.Add((key, value));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer DrawText(this ArgumentContainer container, Action<DrawTextOptions> builder)
|
|
||||||
{
|
|
||||||
var argumentParams = new DrawTextOptions();
|
|
||||||
builder.Invoke(argumentParams);
|
|
||||||
container.Add(new DrawTextArgument(argumentParams.Text, argumentParams.FontPath, argumentParams.Params.ToArray()));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer DrawText(this ArgumentContainer container, string text, string fontPath, params (string, string)[] optionalArguments)
|
|
||||||
{
|
|
||||||
container.Add(new DrawTextArgument(text, fontPath, optionalArguments));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Duration(this ArgumentContainer container, TimeSpan? duration)
|
|
||||||
{
|
|
||||||
container.Add(new DurationArgument(duration));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer FastStart(this ArgumentContainer container)
|
|
||||||
{
|
|
||||||
container.Add(new FaststartArgument());
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer ForceFormat(this ArgumentContainer container, VideoCodec codec)
|
|
||||||
{
|
|
||||||
container.Add(new ForceFormatArgument(codec));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer FrameOutputCount(this ArgumentContainer container, int count)
|
|
||||||
{
|
|
||||||
container.Add(new FrameOutputCountArgument(count));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer FrameRate(this ArgumentContainer container, double framerate)
|
|
||||||
{
|
|
||||||
container.Add(new FrameRateArgument(framerate));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, string path)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(path));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, IEnumerable<string> paths)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(paths.ToArray()));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, params string[] paths)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(paths));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, VideoInfo path)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(path));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, IEnumerable<VideoInfo> paths)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(paths.ToArray()));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, params VideoInfo[] paths)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(paths));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, FileInfo path)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(path));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, IEnumerable<FileInfo> paths)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(paths.ToArray()));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, params FileInfo[] paths)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(paths));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, Uri uri)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(uri));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, IEnumerable<Uri> uris)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(uris.ToArray()));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Input(this ArgumentContainer container, params Uri[] uris)
|
|
||||||
{
|
|
||||||
container.Add(new InputArgument(uris));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Loop(this ArgumentContainer container, int times)
|
|
||||||
{
|
|
||||||
container.Add(new LoopArgument(times));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Output(this ArgumentContainer container, string path)
|
|
||||||
{
|
|
||||||
container.Add(new OutputArgument(path));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Output(this ArgumentContainer container, VideoInfo path)
|
|
||||||
{
|
|
||||||
container.Add(new OutputArgument(path));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Output(this ArgumentContainer container, FileInfo path)
|
|
||||||
{
|
|
||||||
container.Add(new OutputArgument(path));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Output(this ArgumentContainer container, Uri path)
|
|
||||||
{
|
|
||||||
container.Add(new OutputArgument(path));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Override(this ArgumentContainer container)
|
|
||||||
{
|
|
||||||
container.Add(new OverrideArgument());
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer RemoveMetadata(this ArgumentContainer container)
|
|
||||||
{
|
|
||||||
container.Add(new RemoveMetadataArgument());
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Scale(this ArgumentContainer container, Size value)
|
|
||||||
{
|
|
||||||
container.Add(new ScaleArgument(value));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Scale(this ArgumentContainer container, VideoSize value)
|
|
||||||
{
|
|
||||||
container.Add(new ScaleArgument(value));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Scale(this ArgumentContainer container, int width, int height)
|
|
||||||
{
|
|
||||||
container.Add(new ScaleArgument(width, height));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Seek(this ArgumentContainer container, TimeSpan? value)
|
|
||||||
{
|
|
||||||
container.Add(new SeekArgument(value));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Shortest(this ArgumentContainer container)
|
|
||||||
{
|
|
||||||
container.Add(new ShortestArgument(true));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Size(this ArgumentContainer container, Size value)
|
|
||||||
{
|
|
||||||
container.Add(new SizeArgument(value));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Size(this ArgumentContainer container, VideoSize value)
|
|
||||||
{
|
|
||||||
container.Add(new SizeArgument(value));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Size(this ArgumentContainer container, int width, int height)
|
|
||||||
{
|
|
||||||
container.Add(new SizeArgument(width, height));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Speed(this ArgumentContainer container, Speed value)
|
|
||||||
{
|
|
||||||
container.Add(new SpeedArgument(value));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer StartNumber(this ArgumentContainer container, int value)
|
|
||||||
{
|
|
||||||
container.Add(new StartNumberArgument(value));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Threads(this ArgumentContainer container, int value)
|
|
||||||
{
|
|
||||||
container.Add(new ThreadsArgument(value));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer MultiThreaded(this ArgumentContainer container)
|
|
||||||
{
|
|
||||||
container.Add(new ThreadsArgument(true));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer Transpose(this ArgumentContainer container, int transpose)
|
|
||||||
{
|
|
||||||
container.Add(new TransposeArgument(transpose));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer VariableBitRate(this ArgumentContainer container, int vbr)
|
|
||||||
{
|
|
||||||
container.Add(new VariableBitRateArgument(vbr));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer VideoCodec(this ArgumentContainer container, VideoCodec codec)
|
|
||||||
{
|
|
||||||
container.Add(new VideoCodecArgument(codec));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ArgumentContainer VideoCodec(this ArgumentContainer container, VideoCodec codec, int bitrate)
|
|
||||||
{
|
|
||||||
container.Add(new VideoCodecArgument(codec, bitrate));
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents parameter of audio codec and it's quality
|
|
||||||
/// </summary>
|
|
||||||
public class AudioBitrateArgument : Argument<int>
|
|
||||||
{
|
|
||||||
public AudioBitrateArgument(AudioQuality value) : base((int)value) { }
|
|
||||||
public AudioBitrateArgument(int bitrate) : base(bitrate) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-b:a {Value}k";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents parameter of audio codec and it's quality
|
|
||||||
/// </summary>
|
|
||||||
public class AudioCodecArgument : Argument<AudioCodec>
|
|
||||||
{
|
|
||||||
public AudioCodecArgument(AudioCodec value) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-c:a {Value.ToString().ToLower()}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Audio sampling rate argument. Defaults to 48000 (Hz)
|
|
||||||
/// </summary>
|
|
||||||
public class AudioSamplingRateArgument : Argument<int>
|
|
||||||
{
|
|
||||||
public AudioSamplingRateArgument() : base(48000) { }
|
|
||||||
|
|
||||||
public AudioSamplingRateArgument(int samplingRate) : base(samplingRate) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-ar {Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents parameter of bitstream filter
|
|
||||||
/// </summary>
|
|
||||||
public class BitStreamFilterArgument : Argument<Channel, Filter>
|
|
||||||
{
|
|
||||||
public BitStreamFilterArgument() { }
|
|
||||||
|
|
||||||
public BitStreamFilterArgument(Channel first, Filter second) : base(first, second) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return First switch
|
|
||||||
{
|
|
||||||
Channel.Audio => $"-bsf:a {Second.ToString().ToLower()}",
|
|
||||||
Channel.Video => $"-bsf:v {Second.ToString().ToLower()}",
|
|
||||||
_ => string.Empty
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents parameter of concat argument
|
|
||||||
/// Used for creating video from multiple images or videos
|
|
||||||
/// </summary>
|
|
||||||
public class ConcatArgument : Argument<IEnumerable<string>>, IEnumerable<string>
|
|
||||||
{
|
|
||||||
public ConcatArgument() : base(new List<string>()) { }
|
|
||||||
|
|
||||||
public ConcatArgument(IEnumerable<string> value) : base(value) { }
|
|
||||||
|
|
||||||
public IEnumerator<string> GetEnumerator()
|
|
||||||
{
|
|
||||||
return Value.GetEnumerator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-i \"concat:{string.Join(@"|", Value)}\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
return GetEnumerator();
|
|
||||||
}
|
|
||||||
public VideoInfo[] GetAsVideoInfo()
|
|
||||||
{
|
|
||||||
return Value.Select(v => new VideoInfo(v)).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents parameter of copy parameter
|
|
||||||
/// Defines if channel (audio, video or both) should be copied to output file
|
|
||||||
/// </summary>
|
|
||||||
public class CopyArgument : Argument<Channel>
|
|
||||||
{
|
|
||||||
public CopyArgument(Channel value = Channel.Both) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return Value switch
|
|
||||||
{
|
|
||||||
Channel.Audio => "-c:a copy",
|
|
||||||
Channel.Video => "-c:v copy",
|
|
||||||
_ => "-c copy"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents cpu speed parameter
|
|
||||||
/// </summary>
|
|
||||||
public class CpuSpeedArgument : Argument<int>
|
|
||||||
{
|
|
||||||
public CpuSpeedArgument() { }
|
|
||||||
|
|
||||||
public CpuSpeedArgument(int value) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-quality good -cpu-used {Value} -deadline realtime";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
public class CustomArgument : Argument<string>
|
|
||||||
{
|
|
||||||
public CustomArgument(string argument) : base(argument)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return Value ?? string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents cpu speed parameter
|
|
||||||
/// </summary>
|
|
||||||
public class DisableChannelArgument : Argument<Channel>
|
|
||||||
{
|
|
||||||
public DisableChannelArgument() { }
|
|
||||||
|
|
||||||
public DisableChannelArgument(Channel value) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return Value switch
|
|
||||||
{
|
|
||||||
Channel.Video => "-vn",
|
|
||||||
Channel.Audio => "-an",
|
|
||||||
_ => string.Empty
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Drawtext video filter argument
|
|
||||||
/// </summary>
|
|
||||||
public class DrawTextArgument : Argument<IEnumerable<(string key, string value)>>
|
|
||||||
{
|
|
||||||
public DrawTextArgument(string text, string fontPath, params (string, string)[] optionalArguments)
|
|
||||||
: base(new[] {("text", text), ("fontfile", fontPath)}.Concat(optionalArguments)) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-vf drawtext=\"{string.Join(": ", Value.Select(FormatArgumentPair))}\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FormatArgumentPair((string key, string value) pair)
|
|
||||||
{
|
|
||||||
return $"{pair.key}={EncloseIfContainsSpace(pair.value)}";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string EncloseIfContainsSpace(string input)
|
|
||||||
{
|
|
||||||
return input.Contains(" ") ? $"'{input}'" : input;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents duration parameter
|
|
||||||
/// </summary>
|
|
||||||
public class DurationArgument : Argument<TimeSpan?>
|
|
||||||
{
|
|
||||||
public DurationArgument() { }
|
|
||||||
|
|
||||||
public DurationArgument(TimeSpan? value) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return !Value.HasValue ? string.Empty : $"-t {Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Faststart argument - for moving moov atom to the start of file
|
|
||||||
/// </summary>
|
|
||||||
public class FaststartArgument : Argument
|
|
||||||
{
|
|
||||||
public FaststartArgument() { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return "-movflags faststart";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents force format parameter
|
|
||||||
/// </summary>
|
|
||||||
public class ForceFormatArgument : Argument<string>
|
|
||||||
{
|
|
||||||
public ForceFormatArgument() { }
|
|
||||||
public ForceFormatArgument(string format) : base(format) { }
|
|
||||||
|
|
||||||
public ForceFormatArgument(VideoCodec value) : base(value.ToString().ToLower()) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-f {Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents frame output count parameter
|
|
||||||
/// </summary>
|
|
||||||
public class FrameOutputCountArgument : Argument<int>
|
|
||||||
{
|
|
||||||
public FrameOutputCountArgument() { }
|
|
||||||
|
|
||||||
public FrameOutputCountArgument(int value) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-vframes {Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents frame rate parameter
|
|
||||||
/// </summary>
|
|
||||||
public class FrameRateArgument : Argument<double>
|
|
||||||
{
|
|
||||||
public FrameRateArgument() { }
|
|
||||||
|
|
||||||
public FrameRateArgument(double value) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-r {Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents input parameter
|
|
||||||
/// </summary>
|
|
||||||
public class InputArgument : Argument<string[]>
|
|
||||||
{
|
|
||||||
public InputArgument() { }
|
|
||||||
|
|
||||||
public InputArgument(params string[] values) : base(values) { }
|
|
||||||
|
|
||||||
public InputArgument(params VideoInfo[] values) : base(values.Select(v => v.FullName).ToArray()) { }
|
|
||||||
|
|
||||||
public InputArgument(params FileInfo[] values) : base(values.Select(v => v.FullName).ToArray()) { }
|
|
||||||
|
|
||||||
public InputArgument(params Uri[] values) : base(values.Select(v => v.AbsoluteUri).ToArray()) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return string.Join(" ", Value.Select(v => $"-i \"{v}\""));
|
|
||||||
}
|
|
||||||
public VideoInfo[] GetAsVideoInfo()
|
|
||||||
{
|
|
||||||
return Value.Select(v => new VideoInfo(v)).ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents loop parameter
|
|
||||||
/// </summary>
|
|
||||||
public class LoopArgument : Argument<int>
|
|
||||||
{
|
|
||||||
public LoopArgument() { }
|
|
||||||
|
|
||||||
public LoopArgument(int value) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-loop {Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents output parameter
|
|
||||||
/// </summary>
|
|
||||||
public class OutputArgument : Argument<string>
|
|
||||||
{
|
|
||||||
public OutputArgument() { }
|
|
||||||
|
|
||||||
public OutputArgument(string value) : base(value) { }
|
|
||||||
|
|
||||||
public OutputArgument(VideoInfo value) : base(value.FullName) { }
|
|
||||||
|
|
||||||
public OutputArgument(FileInfo value) : base(value.FullName) { }
|
|
||||||
|
|
||||||
public OutputArgument(Uri value) : base(value.AbsolutePath) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"\"{Value}\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileInfo GetAsFileInfo()
|
|
||||||
{
|
|
||||||
return new FileInfo(Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents override parameter
|
|
||||||
/// If output file should be overrided if exists
|
|
||||||
/// </summary>
|
|
||||||
public class OverrideArgument : Argument
|
|
||||||
{
|
|
||||||
public OverrideArgument() { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return "-y";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Pipes;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO.Pipes;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
public abstract class PipeArgument : Argument
|
|
||||||
{
|
|
||||||
public string PipeName { get; private set; }
|
|
||||||
public string PipePath => PipeHelpers.GetPipePath(PipeName);
|
|
||||||
|
|
||||||
protected NamedPipeServerStream Pipe { get; private set; }
|
|
||||||
private PipeDirection direction;
|
|
||||||
|
|
||||||
protected PipeArgument(PipeDirection direction)
|
|
||||||
{
|
|
||||||
PipeName = PipeHelpers.GetUnqiuePipeName();
|
|
||||||
this.direction = direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OpenPipe()
|
|
||||||
{
|
|
||||||
if (Pipe != null)
|
|
||||||
throw new InvalidOperationException("Pipe already has been opened");
|
|
||||||
|
|
||||||
Pipe = new NamedPipeServerStream(PipeName, direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClosePipe()
|
|
||||||
{
|
|
||||||
Pipe?.Dispose();
|
|
||||||
Pipe = null;
|
|
||||||
}
|
|
||||||
public async Task ProcessDataAsync()
|
|
||||||
{
|
|
||||||
await ProcessDataAsync(CancellationToken.None).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract Task ProcessDataAsync(CancellationToken token);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
public class QuietArgument : Argument
|
|
||||||
{
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return "-hide_banner -loglevel warning";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Remove metadata argument
|
|
||||||
/// </summary>
|
|
||||||
public class RemoveMetadataArgument : Argument
|
|
||||||
{
|
|
||||||
public RemoveMetadataArgument() { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-map_metadata -1";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
using System.Drawing;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents scale parameter
|
|
||||||
/// </summary>
|
|
||||||
public class ScaleArgument : Argument<Size>
|
|
||||||
{
|
|
||||||
public ScaleArgument() { }
|
|
||||||
|
|
||||||
public ScaleArgument(Size value) : base(value) { }
|
|
||||||
|
|
||||||
public ScaleArgument(int width, int height) : base(new Size(width, height)) { }
|
|
||||||
|
|
||||||
public ScaleArgument(VideoSize videosize)
|
|
||||||
{
|
|
||||||
Value = videosize == VideoSize.Original ? new Size(-1, -1) : new Size(-1, (int)videosize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-vf scale={Value.Width}:{Value.Height}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents seek parameter
|
|
||||||
/// </summary>
|
|
||||||
public class SeekArgument : Argument<TimeSpan?>
|
|
||||||
{
|
|
||||||
public SeekArgument() { }
|
|
||||||
|
|
||||||
public SeekArgument(TimeSpan? value) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return !Value.HasValue ? string.Empty : $"-ss {Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents shortest parameter
|
|
||||||
/// </summary>
|
|
||||||
public class ShortestArgument : Argument<bool>
|
|
||||||
{
|
|
||||||
public ShortestArgument() { }
|
|
||||||
|
|
||||||
public ShortestArgument(bool value) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return Value ? "-shortest" : string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
using System.Drawing;
|
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents size parameter
|
|
||||||
/// </summary>
|
|
||||||
public class SizeArgument : ScaleArgument
|
|
||||||
{
|
|
||||||
public SizeArgument() { }
|
|
||||||
|
|
||||||
public SizeArgument(Size? value) : base(value ?? default) { }
|
|
||||||
|
|
||||||
public SizeArgument(VideoSize videosize) : base(videosize) { }
|
|
||||||
|
|
||||||
public SizeArgument(int width, int height) : base(width, height) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return Value == default ? string.Empty : $"-s {Value.Width}x{Value.Height}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents speed parameter
|
|
||||||
/// </summary>
|
|
||||||
public class SpeedArgument : Argument<Speed>
|
|
||||||
{
|
|
||||||
public SpeedArgument()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public SpeedArgument(Speed value) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-preset {Value.ToString().ToLower()}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents start number parameter
|
|
||||||
/// </summary>
|
|
||||||
public class StartNumberArgument : Argument<int>
|
|
||||||
{
|
|
||||||
public StartNumberArgument() { }
|
|
||||||
|
|
||||||
public StartNumberArgument(int value) : base(value) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-start_number {Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents threads parameter
|
|
||||||
/// Number of threads used for video encoding
|
|
||||||
/// </summary>
|
|
||||||
public class ThreadsArgument : Argument<int>
|
|
||||||
{
|
|
||||||
public ThreadsArgument() { }
|
|
||||||
|
|
||||||
public ThreadsArgument(int value) : base(value) { }
|
|
||||||
|
|
||||||
public ThreadsArgument(bool isMultiThreaded) :
|
|
||||||
base(isMultiThreaded
|
|
||||||
? Environment.ProcessorCount
|
|
||||||
: 1) { }
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-threads {Value}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Transpose argument.
|
|
||||||
/// 0 = 90CounterCLockwise and Vertical Flip (default)
|
|
||||||
/// 1 = 90Clockwise
|
|
||||||
/// 2 = 90CounterClockwise
|
|
||||||
/// 3 = 90Clockwise and Vertical Flip
|
|
||||||
/// </summary>
|
|
||||||
public class TransposeArgument : Argument<int>
|
|
||||||
{
|
|
||||||
public TransposeArgument() { }
|
|
||||||
|
|
||||||
public TransposeArgument(int transpose) : base(transpose)
|
|
||||||
{
|
|
||||||
if (transpose < 0 || transpose > 3)
|
|
||||||
{
|
|
||||||
throw new ArgumentException("Argument is outside range (0 - 3)", nameof(transpose));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-vf \"transpose={Value}\"";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents video codec parameter
|
|
||||||
/// </summary>
|
|
||||||
public class VideoCodecArgument : Argument<string>
|
|
||||||
{
|
|
||||||
public int Bitrate { get; protected set; } = 0;
|
|
||||||
|
|
||||||
public VideoCodecArgument() { }
|
|
||||||
|
|
||||||
public VideoCodecArgument(string codec) : base(codec) { }
|
|
||||||
|
|
||||||
public VideoCodecArgument(VideoCodec value) : base(value.ToString().ToLower()) { }
|
|
||||||
|
|
||||||
public VideoCodecArgument(VideoCodec value, int bitrate) : base(value.ToString().ToLower())
|
|
||||||
{
|
|
||||||
Bitrate = bitrate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
var video = $"-c:v {Value} -pix_fmt yuv420p";
|
|
||||||
|
|
||||||
if (Bitrate != default)
|
|
||||||
{
|
|
||||||
video += $" -b:v {Bitrate}k";
|
|
||||||
}
|
|
||||||
|
|
||||||
return video;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Builds parameters string from <see cref="ArgumentContainer"/> that would be passed to ffmpeg process
|
|
||||||
/// </summary>
|
|
||||||
public class FFArgumentBuilder : IArgumentBuilder
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Builds parameters string from <see cref="ArgumentContainer"/> that would be passed to ffmpeg process
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="container">Container of arguments</param>
|
|
||||||
/// <returns>Parameters string</returns>
|
|
||||||
public string BuildArguments(ArgumentContainer container)
|
|
||||||
{
|
|
||||||
if (!container.ContainsInputOutput())
|
|
||||||
throw new ArgumentException("No input or output parameter found", nameof(container));
|
|
||||||
|
|
||||||
return string.Join(" ", container.Select(argument => argument.Value.GetStringValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
|
||||||
{
|
|
||||||
public interface IArgumentBuilder
|
|
||||||
{
|
|
||||||
string BuildArguments(ArgumentContainer container);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,639 +0,0 @@
|
||||||
using FFMpegCore.Enums;
|
|
||||||
using FFMpegCore.FFMPEG.Argument;
|
|
||||||
using FFMpegCore.FFMPEG.Enums;
|
|
||||||
using FFMpegCore.FFMPEG.Exceptions;
|
|
||||||
using FFMpegCore.Helpers;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Instances;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG
|
|
||||||
{
|
|
||||||
public delegate void ConversionHandler(double percentage);
|
|
||||||
|
|
||||||
public class FFMpeg
|
|
||||||
{
|
|
||||||
IArgumentBuilder ArgumentBuilder { get; set; } = new FFArgumentBuilder();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Intializes the FFMPEG encoder.
|
|
||||||
/// </summary>
|
|
||||||
public FFMpeg() : base()
|
|
||||||
{
|
|
||||||
FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
|
|
||||||
_ffmpegPath = FFMpegOptions.Options.FFmpegBinary;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the percentage of the current conversion progress.
|
|
||||||
/// </summary>
|
|
||||||
public event ConversionHandler OnProgress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Saves a 'png' thumbnail from the input video.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">Source video file.</param>
|
|
||||||
/// <param name="output">Output video file</param>
|
|
||||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
|
||||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
|
||||||
/// <param name="persistSnapshotOnFileSystem">By default, it deletes the created image on disk. If set to true, it won't delete the image</param>
|
|
||||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
|
||||||
public Bitmap Snapshot(VideoInfo source, FileInfo output, Size? size = null, TimeSpan? captureTime = null,
|
|
||||||
bool persistSnapshotOnFileSystem = false)
|
|
||||||
{
|
|
||||||
if (captureTime == null)
|
|
||||||
captureTime = TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3);
|
|
||||||
|
|
||||||
if (output.Extension.ToLower() != FileExtension.Png)
|
|
||||||
output = new FileInfo(output.FullName.Replace(output.Extension, FileExtension.Png));
|
|
||||||
|
|
||||||
if (size == null || (size.Value.Height == 0 && size.Value.Width == 0))
|
|
||||||
{
|
|
||||||
size = new Size(source.Width, source.Height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size.Value.Width != size.Value.Height)
|
|
||||||
{
|
|
||||||
if (size.Value.Width == 0)
|
|
||||||
{
|
|
||||||
var ratio = source.Width / (double)size.Value.Width;
|
|
||||||
|
|
||||||
size = new Size((int)(source.Width * ratio), (int)(source.Height * ratio));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size.Value.Height == 0)
|
|
||||||
{
|
|
||||||
var ratio = source.Height / (double)size.Value.Height;
|
|
||||||
|
|
||||||
size = new Size((int)(source.Width * ratio), (int)(source.Height * ratio));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
|
||||||
var container = new ArgumentContainer(
|
|
||||||
new InputArgument(source),
|
|
||||||
new VideoCodecArgument(VideoCodec.Png),
|
|
||||||
new FrameOutputCountArgument(1),
|
|
||||||
new SeekArgument(captureTime),
|
|
||||||
new SizeArgument(size),
|
|
||||||
new OutputArgument(output)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!RunProcess(container, output, false))
|
|
||||||
{
|
|
||||||
throw new OperationCanceledException("Could not take snapshot!");
|
|
||||||
}
|
|
||||||
|
|
||||||
output.Refresh();
|
|
||||||
|
|
||||||
Bitmap result;
|
|
||||||
using (var bmp = (Bitmap)Image.FromFile(output.FullName))
|
|
||||||
{
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
bmp.Save(ms, ImageFormat.Png);
|
|
||||||
result = new Bitmap(ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output.Exists && !persistSnapshotOnFileSystem)
|
|
||||||
{
|
|
||||||
output.Delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Convert a video do a different format.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">Input video source.</param>
|
|
||||||
/// <param name="output">Output information.</param>
|
|
||||||
/// <param name="type">Target conversion video type.</param>
|
|
||||||
/// <param name="speed">Conversion target speed/quality (faster speed = lower quality).</param>
|
|
||||||
/// <param name="size">Video size.</param>
|
|
||||||
/// <param name="audioQuality">Conversion target audio quality.</param>
|
|
||||||
/// <param name="multithreaded">Is encoding multithreaded.</param>
|
|
||||||
/// <returns>Output video information.</returns>
|
|
||||||
public VideoInfo Convert(
|
|
||||||
VideoInfo source,
|
|
||||||
FileInfo output,
|
|
||||||
VideoType type = VideoType.Mp4,
|
|
||||||
Speed speed = Speed.SuperFast,
|
|
||||||
VideoSize size = VideoSize.Original,
|
|
||||||
AudioQuality audioQuality = AudioQuality.Normal,
|
|
||||||
bool multithreaded = false)
|
|
||||||
{
|
|
||||||
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
|
||||||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.ForType(type));
|
|
||||||
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
|
||||||
|
|
||||||
var scale = VideoSize.Original == size ? 1 : (double)source.Height / (int)size;
|
|
||||||
var outputSize = new Size((int)(source.Width / scale), (int)(source.Height / scale));
|
|
||||||
|
|
||||||
if (outputSize.Width % 2 != 0)
|
|
||||||
outputSize.Width += 1;
|
|
||||||
|
|
||||||
return type switch
|
|
||||||
{
|
|
||||||
VideoType.Mp4 => Convert(new ArgumentContainer(
|
|
||||||
new InputArgument(source),
|
|
||||||
new ThreadsArgument(multithreaded),
|
|
||||||
new ScaleArgument(outputSize),
|
|
||||||
new VideoCodecArgument(VideoCodec.LibX264, 2400),
|
|
||||||
new SpeedArgument(speed),
|
|
||||||
new AudioCodecArgument(AudioCodec.Aac),
|
|
||||||
new AudioBitrateArgument(audioQuality),
|
|
||||||
new OutputArgument(output))),
|
|
||||||
VideoType.Ogv => Convert(new ArgumentContainer(
|
|
||||||
new InputArgument(source),
|
|
||||||
new ThreadsArgument(multithreaded),
|
|
||||||
new ScaleArgument(outputSize),
|
|
||||||
new VideoCodecArgument(VideoCodec.LibTheora, 2400),
|
|
||||||
new SpeedArgument(speed),
|
|
||||||
new AudioCodecArgument(AudioCodec.LibVorbis),
|
|
||||||
new AudioBitrateArgument(audioQuality),
|
|
||||||
new OutputArgument(output))),
|
|
||||||
VideoType.Ts => Convert(new ArgumentContainer(
|
|
||||||
new InputArgument(source),
|
|
||||||
new CopyArgument(),
|
|
||||||
new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB),
|
|
||||||
new ForceFormatArgument(VideoCodec.MpegTs),
|
|
||||||
new OutputArgument(output))),
|
|
||||||
VideoType.WebM => Convert(new ArgumentContainer(
|
|
||||||
new InputArgument(source),
|
|
||||||
new ThreadsArgument(multithreaded),
|
|
||||||
new ScaleArgument(outputSize),
|
|
||||||
new VideoCodecArgument(VideoCodec.LibVpx, 2400),
|
|
||||||
new SpeedArgument(speed),
|
|
||||||
new AudioCodecArgument(AudioCodec.LibVorbis),
|
|
||||||
new AudioBitrateArgument(audioQuality),
|
|
||||||
new OutputArgument(output))),
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a poster image to an audio file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="image">Source image file.</param>
|
|
||||||
/// <param name="audio">Source audio file.</param>
|
|
||||||
/// <param name="output">Output video file.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public VideoInfo PosterWithAudio(FileInfo image, FileInfo audio, FileInfo output)
|
|
||||||
{
|
|
||||||
FFMpegHelper.InputsExistExceptionCheck(image, audio);
|
|
||||||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
|
||||||
FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image.FullName));
|
|
||||||
|
|
||||||
var container = new ArgumentContainer(
|
|
||||||
new InputArgument(image.FullName, audio.FullName),
|
|
||||||
new LoopArgument(1),
|
|
||||||
new VideoCodecArgument(VideoCodec.LibX264, 2400),
|
|
||||||
new AudioCodecArgument(AudioCodec.Aac),
|
|
||||||
new AudioBitrateArgument(AudioQuality.Normal),
|
|
||||||
new ShortestArgument(true),
|
|
||||||
new OutputArgument(output)
|
|
||||||
);
|
|
||||||
if (!RunProcess(container, output, false))
|
|
||||||
{
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation,
|
|
||||||
"An error occured while adding the audio file to the image.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new VideoInfo(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Joins a list of video files.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="output">Output video file.</param>
|
|
||||||
/// <param name="videos">List of vides that need to be joined together.</param>
|
|
||||||
/// <returns>Output video information.</returns>
|
|
||||||
public VideoInfo Join(FileInfo output, params VideoInfo[] videos)
|
|
||||||
{
|
|
||||||
FFMpegHelper.OutputExistsExceptionCheck(output);
|
|
||||||
FFMpegHelper.InputsExistExceptionCheck(videos.Select(video => video.ToFileInfo()).ToArray());
|
|
||||||
|
|
||||||
var temporaryVideoParts = videos.Select(video =>
|
|
||||||
{
|
|
||||||
FFMpegHelper.ConversionSizeExceptionCheck(video);
|
|
||||||
var destinationPath = video.FullName.Replace(video.Extension, FileExtension.Ts);
|
|
||||||
Convert(video, new FileInfo(destinationPath), VideoType.Ts);
|
|
||||||
return destinationPath;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return Convert(new ArgumentContainer(
|
|
||||||
new ConcatArgument(temporaryVideoParts),
|
|
||||||
new CopyArgument(),
|
|
||||||
new BitStreamFilterArgument(Channel.Audio, Filter.Aac_AdtstoAsc),
|
|
||||||
new OutputArgument(output)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Cleanup(temporaryVideoParts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Converts an image sequence to a video.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="output">Output video file.</param>
|
|
||||||
/// <param name="frameRate">FPS</param>
|
|
||||||
/// <param name="images">Image sequence collection</param>
|
|
||||||
/// <returns>Output video information.</returns>
|
|
||||||
public VideoInfo JoinImageSequence(FileInfo output, double frameRate = 30, params ImageInfo[] images)
|
|
||||||
{
|
|
||||||
var temporaryImageFiles = images.Select((image, index) =>
|
|
||||||
{
|
|
||||||
FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image.FullName));
|
|
||||||
var destinationPath =
|
|
||||||
image.FullName.Replace(image.Name, $"{index.ToString().PadLeft(9, '0')}{image.Extension}");
|
|
||||||
File.Copy(image.FullName, destinationPath);
|
|
||||||
|
|
||||||
return destinationPath;
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
var firstImage = images.First();
|
|
||||||
|
|
||||||
var container = new ArgumentContainer(
|
|
||||||
new FrameRateArgument(frameRate),
|
|
||||||
new SizeArgument(firstImage.Width, firstImage.Height),
|
|
||||||
new StartNumberArgument(0),
|
|
||||||
new InputArgument($"{firstImage.Directory}{Path.DirectorySeparatorChar}%09d.png"),
|
|
||||||
new FrameOutputCountArgument(images.Length),
|
|
||||||
new VideoCodecArgument(VideoCodec.LibX264),
|
|
||||||
new OutputArgument(output)
|
|
||||||
);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!RunProcess(container, output, false))
|
|
||||||
{
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation,
|
|
||||||
"Could not join the provided image sequence.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new VideoInfo(output);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Cleanup(temporaryImageFiles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Records M3U8 streams to the specified output.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="uri">URI to pointing towards stream.</param>
|
|
||||||
/// <param name="output">Output file</param>
|
|
||||||
/// <returns>Success state.</returns>
|
|
||||||
public VideoInfo SaveM3U8Stream(Uri uri, FileInfo output)
|
|
||||||
{
|
|
||||||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
|
||||||
|
|
||||||
if (uri.Scheme == "http" || uri.Scheme == "https")
|
|
||||||
{
|
|
||||||
return Convert(new ArgumentContainer(
|
|
||||||
new InputArgument(uri),
|
|
||||||
new OutputArgument(output)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Strips a video file of audio.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">Source video file.</param>
|
|
||||||
/// <param name="output">Output video file.</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public VideoInfo Mute(VideoInfo source, FileInfo output)
|
|
||||||
{
|
|
||||||
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
|
||||||
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
|
||||||
FFMpegHelper.ExtensionExceptionCheck(output, source.Extension);
|
|
||||||
|
|
||||||
return Convert(new ArgumentContainer(
|
|
||||||
new InputArgument(source),
|
|
||||||
new CopyArgument(Channel.Video),
|
|
||||||
new DisableChannelArgument(Channel.Audio),
|
|
||||||
new OutputArgument(output)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Saves audio from a specific video file to disk.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">Source video file.</param>
|
|
||||||
/// <param name="output">Output audio file.</param>
|
|
||||||
/// <returns>Success state.</returns>
|
|
||||||
public FileInfo ExtractAudio(VideoInfo source, FileInfo output)
|
|
||||||
{
|
|
||||||
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
|
||||||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp3);
|
|
||||||
|
|
||||||
var container = new ArgumentContainer(
|
|
||||||
new InputArgument(source),
|
|
||||||
new DisableChannelArgument(Channel.Video),
|
|
||||||
new OutputArgument(output)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!RunProcess(container, output, false))
|
|
||||||
{
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation,
|
|
||||||
"Could not extract the audio from the requested video.");
|
|
||||||
}
|
|
||||||
|
|
||||||
output.Refresh();
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds audio to a video file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">Source video file.</param>
|
|
||||||
/// <param name="audio">Source audio file.</param>
|
|
||||||
/// <param name="output">Output video file.</param>
|
|
||||||
/// <param name="stopAtShortest">Indicates if the encoding should stop at the shortest input file.</param>
|
|
||||||
/// <returns>Success state</returns>
|
|
||||||
public VideoInfo ReplaceAudio(VideoInfo source, FileInfo audio, FileInfo output, bool stopAtShortest = false)
|
|
||||||
{
|
|
||||||
FFMpegHelper.ConversionExceptionCheck(source.ToFileInfo(), output);
|
|
||||||
FFMpegHelper.InputsExistExceptionCheck(audio);
|
|
||||||
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
|
||||||
FFMpegHelper.ExtensionExceptionCheck(output, source.Extension);
|
|
||||||
|
|
||||||
return Convert(new ArgumentContainer(
|
|
||||||
new InputArgument(source.FullName, audio.FullName),
|
|
||||||
new CopyArgument(),
|
|
||||||
new AudioCodecArgument(AudioCodec.Aac),
|
|
||||||
new AudioBitrateArgument(AudioQuality.Hd),
|
|
||||||
new ShortestArgument(stopAtShortest),
|
|
||||||
new OutputArgument(output)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public VideoInfo Convert(ArgumentContainer arguments, bool skipExistsCheck = false)
|
|
||||||
{
|
|
||||||
var (sources, output) = GetInputOutput(arguments);
|
|
||||||
if (sources != null)
|
|
||||||
_totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
|
|
||||||
|
|
||||||
if (!RunProcess(arguments, output, skipExistsCheck))
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Conversion, "Could not process file without error");
|
|
||||||
|
|
||||||
_totalTime = TimeSpan.MinValue;
|
|
||||||
|
|
||||||
return output != null && output.Exists ? new VideoInfo(output) : null;
|
|
||||||
}
|
|
||||||
public async Task<VideoInfo> ConvertAsync(ArgumentContainer arguments, bool skipExistsCheck = false)
|
|
||||||
{
|
|
||||||
var (sources, output) = GetInputOutput(arguments);
|
|
||||||
if (sources != null)
|
|
||||||
_totalTime = TimeSpan.FromSeconds(sources.Sum(source => source.Duration.TotalSeconds));
|
|
||||||
|
|
||||||
if (!await RunProcessAsync(arguments, output, skipExistsCheck))
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Conversion, "Could not process file without error");
|
|
||||||
|
|
||||||
_totalTime = TimeSpan.MinValue;
|
|
||||||
|
|
||||||
return output != null && output.Exists ? new VideoInfo(output) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (VideoInfo[] Input, FileInfo Output) GetInputOutput(ArgumentContainer arguments)
|
|
||||||
{
|
|
||||||
FileInfo output;
|
|
||||||
if (arguments.TryGetArgument<OutputArgument>(out var outputArg))
|
|
||||||
output = outputArg.GetAsFileInfo();
|
|
||||||
else if (arguments.TryGetArgument<OutputPipeArgument>(out var outputPipeArg))
|
|
||||||
output = null;
|
|
||||||
else
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation, "No output argument found");
|
|
||||||
|
|
||||||
VideoInfo[] sources;
|
|
||||||
if (arguments.TryGetArgument<InputArgument>(out var input))
|
|
||||||
sources = input.GetAsVideoInfo();
|
|
||||||
else if (arguments.TryGetArgument<ConcatArgument>(out var concat))
|
|
||||||
sources = concat.GetAsVideoInfo();
|
|
||||||
else if (arguments.TryGetArgument<InputPipeArgument>(out var pipe))
|
|
||||||
sources = null;
|
|
||||||
else
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation, "No input or concat argument found");
|
|
||||||
return (sources, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if the associated process is still alive/running.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsWorking => _instance.Started;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops any current job that FFMpeg is running.
|
|
||||||
/// </summary>
|
|
||||||
public void Stop()
|
|
||||||
{
|
|
||||||
if (IsWorking)
|
|
||||||
{
|
|
||||||
_instance.SendInput("q").Wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Private Members & Methods
|
|
||||||
|
|
||||||
private readonly string _ffmpegPath;
|
|
||||||
private TimeSpan _totalTime;
|
|
||||||
|
|
||||||
private bool RunProcess(ArgumentContainer container, FileInfo output, bool skipExistsCheck)
|
|
||||||
{
|
|
||||||
_instance?.Dispose();
|
|
||||||
var arguments = ArgumentBuilder.BuildArguments(container);
|
|
||||||
var exitCode = -1;
|
|
||||||
|
|
||||||
if (container.TryGetArgument<InputPipeArgument>(out var inputPipeArgument))
|
|
||||||
{
|
|
||||||
inputPipeArgument.OpenPipe();
|
|
||||||
}
|
|
||||||
if (container.TryGetArgument<OutputPipeArgument>(out var outputPipeArgument))
|
|
||||||
{
|
|
||||||
outputPipeArgument.OpenPipe();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_instance = new Instance(_ffmpegPath, arguments);
|
|
||||||
_instance.DataReceived += OutputData;
|
|
||||||
|
|
||||||
if (inputPipeArgument != null || outputPipeArgument != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
|
||||||
{
|
|
||||||
var concurrentTasks = new List<Task>();
|
|
||||||
concurrentTasks.Add(_instance.FinishedRunning()
|
|
||||||
.ContinueWith((t =>
|
|
||||||
{
|
|
||||||
exitCode = t.Result;
|
|
||||||
if (exitCode != 0)
|
|
||||||
tokenSource.Cancel();
|
|
||||||
})));
|
|
||||||
if (inputPipeArgument != null)
|
|
||||||
concurrentTasks.Add(inputPipeArgument.ProcessDataAsync(tokenSource.Token)
|
|
||||||
.ContinueWith((t) =>
|
|
||||||
{
|
|
||||||
inputPipeArgument.ClosePipe();
|
|
||||||
if (t.Exception != null)
|
|
||||||
throw t.Exception;
|
|
||||||
}));
|
|
||||||
if (outputPipeArgument != null)
|
|
||||||
concurrentTasks.Add(outputPipeArgument.ProcessDataAsync(tokenSource.Token)
|
|
||||||
.ContinueWith((t) =>
|
|
||||||
{
|
|
||||||
outputPipeArgument.ClosePipe();
|
|
||||||
if (t.Exception != null)
|
|
||||||
throw t.Exception;
|
|
||||||
}));
|
|
||||||
|
|
||||||
Task.WaitAll(concurrentTasks.ToArray()/*, tokenSource.Token*/);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
inputPipeArgument?.ClosePipe();
|
|
||||||
outputPipeArgument?.ClosePipe();
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData), ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
exitCode = _instance.BlockUntilFinished();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(exitCode != 0)
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
|
|
||||||
|
|
||||||
if (outputPipeArgument == null && !skipExistsCheck && (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0))
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
|
|
||||||
|
|
||||||
return exitCode == 0;
|
|
||||||
}
|
|
||||||
private async Task<bool> RunProcessAsync(ArgumentContainer container, FileInfo output, bool skipExistsCheck)
|
|
||||||
{
|
|
||||||
_instance?.Dispose();
|
|
||||||
var arguments = ArgumentBuilder.BuildArguments(container);
|
|
||||||
var exitCode = -1;
|
|
||||||
|
|
||||||
if (container.TryGetArgument<InputPipeArgument>(out var inputPipeArgument))
|
|
||||||
{
|
|
||||||
inputPipeArgument.OpenPipe();
|
|
||||||
}
|
|
||||||
if (container.TryGetArgument<OutputPipeArgument>(out var outputPipeArgument))
|
|
||||||
{
|
|
||||||
outputPipeArgument.OpenPipe();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_instance = new Instance(_ffmpegPath, arguments);
|
|
||||||
_instance.DataReceived += OutputData;
|
|
||||||
|
|
||||||
if (inputPipeArgument != null || outputPipeArgument != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var tokenSource = new CancellationTokenSource())
|
|
||||||
{
|
|
||||||
var concurrentTasks = new List<Task>();
|
|
||||||
concurrentTasks.Add(_instance.FinishedRunning()
|
|
||||||
.ContinueWith((t =>
|
|
||||||
{
|
|
||||||
exitCode = t.Result;
|
|
||||||
if (exitCode != 0)
|
|
||||||
tokenSource.Cancel();
|
|
||||||
})));
|
|
||||||
if (inputPipeArgument != null)
|
|
||||||
concurrentTasks.Add(inputPipeArgument.ProcessDataAsync(tokenSource.Token)
|
|
||||||
.ContinueWith((t) =>
|
|
||||||
{
|
|
||||||
inputPipeArgument.ClosePipe();
|
|
||||||
if (t.Exception != null)
|
|
||||||
throw t.Exception;
|
|
||||||
}));
|
|
||||||
if (outputPipeArgument != null)
|
|
||||||
concurrentTasks.Add(outputPipeArgument.ProcessDataAsync(tokenSource.Token)
|
|
||||||
.ContinueWith((t) =>
|
|
||||||
{
|
|
||||||
outputPipeArgument.ClosePipe();
|
|
||||||
if (t.Exception != null)
|
|
||||||
throw t.Exception;
|
|
||||||
}));
|
|
||||||
|
|
||||||
await Task.WhenAll(concurrentTasks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
inputPipeArgument?.ClosePipe();
|
|
||||||
outputPipeArgument?.ClosePipe();
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData), ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
exitCode = await _instance.FinishedRunning();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exitCode != 0)
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
|
|
||||||
|
|
||||||
if (outputPipeArgument == null && !skipExistsCheck && (!File.Exists(output.FullName) || new FileInfo(output.FullName).Length == 0))
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\n", _instance.ErrorData));
|
|
||||||
|
|
||||||
return exitCode == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Cleanup(IEnumerable<string> pathList)
|
|
||||||
{
|
|
||||||
foreach (var path in pathList)
|
|
||||||
{
|
|
||||||
if (File.Exists(path))
|
|
||||||
{
|
|
||||||
File.Delete(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Regex ProgressRegex = new Regex(@"time=(\d\d:\d\d:\d\d.\d\d?)", RegexOptions.Compiled);
|
|
||||||
private Instance _instance;
|
|
||||||
|
|
||||||
private void OutputData(object sender, (DataType Type, string Data) msg)
|
|
||||||
{
|
|
||||||
#if DEBUG
|
|
||||||
Trace.WriteLine(msg.Data);
|
|
||||||
#endif
|
|
||||||
if (OnProgress == null) return;
|
|
||||||
|
|
||||||
var match = ProgressRegex.Match(msg.Data);
|
|
||||||
if (!match.Success) return;
|
|
||||||
|
|
||||||
var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
|
|
||||||
var percentage = Math.Round(processed.TotalSeconds / _totalTime.TotalSeconds * 100, 2);
|
|
||||||
OnProgress(percentage);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG
|
|
||||||
{
|
|
||||||
internal class Stream
|
|
||||||
{
|
|
||||||
[JsonProperty("index")]
|
|
||||||
internal int Index { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("codec_name")]
|
|
||||||
internal string CodecName { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("bit_rate")]
|
|
||||||
internal string BitRate { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("profile")]
|
|
||||||
internal string Profile { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("codec_type")]
|
|
||||||
internal string CodecType { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("width")]
|
|
||||||
internal int Width { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("height")]
|
|
||||||
internal int Height { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("duration")]
|
|
||||||
internal string Duration { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("r_frame_rate")]
|
|
||||||
internal string FrameRate { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("tags")]
|
|
||||||
internal Tags Tags { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class Tags
|
|
||||||
{
|
|
||||||
[JsonProperty("DURATION")]
|
|
||||||
internal string Duration { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class FFMpegStreamMetadata
|
|
||||||
{
|
|
||||||
[JsonProperty("streams")]
|
|
||||||
internal List<Stream> Streams { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,228 +0,0 @@
|
||||||
using FFMpegCore.FFMPEG.Exceptions;
|
|
||||||
using FFMpegCore.Helpers;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Instances;
|
|
||||||
using FFMpegCore.FFMPEG.Argument;
|
|
||||||
using FFMpegCore.FFMPEG.Pipes;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG
|
|
||||||
{
|
|
||||||
public sealed class FFProbe
|
|
||||||
{
|
|
||||||
private readonly int _outputCapacity;
|
|
||||||
static readonly double BITS_TO_MB = 1024 * 1024 * 8;
|
|
||||||
private readonly string _ffprobePath;
|
|
||||||
|
|
||||||
public FFProbe(int outputCapacity = int.MaxValue)
|
|
||||||
{
|
|
||||||
_outputCapacity = outputCapacity;
|
|
||||||
FFProbeHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
|
|
||||||
_ffprobePath = FFMpegOptions.Options.FFProbeBinary;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Probes the targeted video file and retrieves all available details.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">Source video file.</param>
|
|
||||||
/// <returns>A video info object containing all details necessary.</returns>
|
|
||||||
public VideoInfo ParseVideoInfo(string source)
|
|
||||||
{
|
|
||||||
return ParseVideoInfo(new VideoInfo(source));
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Probes the targeted video file asynchronously and retrieves all available details.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="source">Source video file.</param>
|
|
||||||
/// <returns>A task for the video info object containing all details necessary.</returns>
|
|
||||||
public Task<VideoInfo> ParseVideoInfoAsync(string source)
|
|
||||||
{
|
|
||||||
return ParseVideoInfoAsync(new VideoInfo(source));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Probes the targeted video file and retrieves all available details.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="info">Source video file.</param>
|
|
||||||
/// <returns>A video info object containing all details necessary.</returns>
|
|
||||||
public VideoInfo ParseVideoInfo(VideoInfo info)
|
|
||||||
{
|
|
||||||
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info.FullName)) {DataBufferCapacity = _outputCapacity};
|
|
||||||
instance.BlockUntilFinished();
|
|
||||||
var output = string.Join("", instance.OutputData);
|
|
||||||
return ParseVideoInfoInternal(info, output);
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Probes the targeted video file asynchronously and retrieves all available details.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="info">Source video file.</param>
|
|
||||||
/// <returns>A video info object containing all details necessary.</returns>
|
|
||||||
public async Task<VideoInfo> ParseVideoInfoAsync(VideoInfo info)
|
|
||||||
{
|
|
||||||
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(info.FullName)) {DataBufferCapacity = _outputCapacity};
|
|
||||||
await instance.FinishedRunning();
|
|
||||||
var output = string.Join("", instance.OutputData);
|
|
||||||
return ParseVideoInfoInternal(info, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Probes the targeted video stream and retrieves all available details.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stream">Encoded video stream.</param>
|
|
||||||
/// <returns>A video info object containing all details necessary.</returns>
|
|
||||||
public VideoInfo ParseVideoInfo(System.IO.Stream stream)
|
|
||||||
{
|
|
||||||
var info = new VideoInfo();
|
|
||||||
var streamPipeSource = new StreamPipeDataWriter(stream);
|
|
||||||
var pipeArgument = new InputPipeArgument(streamPipeSource);
|
|
||||||
|
|
||||||
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(pipeArgument.PipePath)) { DataBufferCapacity = _outputCapacity };
|
|
||||||
pipeArgument.OpenPipe();
|
|
||||||
|
|
||||||
var task = instance.FinishedRunning();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
pipeArgument.ProcessDataAsync().ConfigureAwait(false).GetAwaiter().GetResult();
|
|
||||||
pipeArgument.ClosePipe();
|
|
||||||
}
|
|
||||||
catch(IOException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
pipeArgument.ClosePipe();
|
|
||||||
}
|
|
||||||
var exitCode = task.ConfigureAwait(false).GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
if (exitCode != 0)
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, "FFProbe process returned exit status " + exitCode);
|
|
||||||
|
|
||||||
var output = string.Join("", instance.OutputData);
|
|
||||||
return ParseVideoInfoInternal(info, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Probes the targeted video stream asynchronously and retrieves all available details.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stream">Encoded video stream.</param>
|
|
||||||
/// <returns>A video info object containing all details necessary.</returns>
|
|
||||||
public async Task<VideoInfo> ParseVideoInfoAsync(System.IO.Stream stream)
|
|
||||||
{
|
|
||||||
var info = new VideoInfo();
|
|
||||||
var streamPipeSource = new StreamPipeDataWriter(stream);
|
|
||||||
var pipeArgument = new InputPipeArgument(streamPipeSource);
|
|
||||||
|
|
||||||
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(pipeArgument.PipePath)) { DataBufferCapacity = _outputCapacity };
|
|
||||||
pipeArgument.OpenPipe();
|
|
||||||
|
|
||||||
var task = instance.FinishedRunning();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await pipeArgument.ProcessDataAsync();
|
|
||||||
pipeArgument.ClosePipe();
|
|
||||||
}
|
|
||||||
catch (IOException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
pipeArgument.ClosePipe();
|
|
||||||
}
|
|
||||||
var exitCode = await task;
|
|
||||||
|
|
||||||
if (exitCode != 0)
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, "FFProbe process returned exit status " + exitCode);
|
|
||||||
|
|
||||||
var output = string.Join("", instance.OutputData);
|
|
||||||
return ParseVideoInfoInternal(info, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string BuildFFProbeArguments(string fullPath) =>
|
|
||||||
$"-v quiet -print_format json -show_streams \"{fullPath}\"";
|
|
||||||
|
|
||||||
private VideoInfo ParseVideoInfoInternal(VideoInfo info, string probeOutput)
|
|
||||||
{
|
|
||||||
var metadata = JsonConvert.DeserializeObject<FFMpegStreamMetadata>(probeOutput);
|
|
||||||
|
|
||||||
if (metadata?.Streams == null || metadata.Streams.Count == 0)
|
|
||||||
{
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.File, $"No video or audio streams could be detected. Source: ${info.FullName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var video = metadata.Streams.Find(s => s.CodecType == "video");
|
|
||||||
var audio = metadata.Streams.Find(s => s.CodecType == "audio");
|
|
||||||
|
|
||||||
var videoSize = 0d;
|
|
||||||
var audioSize = 0d;
|
|
||||||
|
|
||||||
var sDuration = (video ?? audio).Duration;
|
|
||||||
var duration = TimeSpan.Zero;
|
|
||||||
if (sDuration != null)
|
|
||||||
{
|
|
||||||
duration = TimeSpan.FromSeconds(double.TryParse(sDuration, NumberStyles.Any, CultureInfo.InvariantCulture, out var output) ? output : 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sDuration = (video ?? audio).Tags.Duration;
|
|
||||||
if (sDuration != null)
|
|
||||||
TimeSpan.TryParse(sDuration.Remove(sDuration.LastIndexOf('.') + 8), CultureInfo.InvariantCulture, out duration); // TimeSpan fractions only allow up to 7 digits
|
|
||||||
}
|
|
||||||
info.Duration = duration;
|
|
||||||
|
|
||||||
if (video != null)
|
|
||||||
{
|
|
||||||
var bitRate = Convert.ToDouble(video.BitRate, CultureInfo.InvariantCulture);
|
|
||||||
var fr = video.FrameRate.Split('/');
|
|
||||||
var commonDenominator = FFProbeHelper.Gcd(video.Width, video.Height);
|
|
||||||
|
|
||||||
videoSize = bitRate * duration.TotalSeconds / BITS_TO_MB;
|
|
||||||
|
|
||||||
info.VideoFormat = video.CodecName;
|
|
||||||
info.Width = video.Width;
|
|
||||||
info.Height = video.Height;
|
|
||||||
info.FrameRate = Math.Round(
|
|
||||||
Convert.ToDouble(fr[0], CultureInfo.InvariantCulture) /
|
|
||||||
Convert.ToDouble(fr[1], CultureInfo.InvariantCulture),
|
|
||||||
3);
|
|
||||||
info.Ratio = video.Width / commonDenominator + ":" + video.Height / commonDenominator;
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
info.VideoFormat = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audio != null)
|
|
||||||
{
|
|
||||||
var bitRate = Convert.ToDouble(audio.BitRate, CultureInfo.InvariantCulture);
|
|
||||||
info.AudioFormat = audio.CodecName;
|
|
||||||
audioSize = bitRate * duration.TotalSeconds / BITS_TO_MB;
|
|
||||||
} else
|
|
||||||
{
|
|
||||||
info.AudioFormat = "none";
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
info.Size = Math.Round(videoSize + audioSize, 2);
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal FFMpegStreamMetadata GetMetadata(string path)
|
|
||||||
{
|
|
||||||
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(path)) { DataBufferCapacity = _outputCapacity };
|
|
||||||
instance.BlockUntilFinished();
|
|
||||||
var output = string.Join("", instance.OutputData);
|
|
||||||
return JsonConvert.DeserializeObject<FFMpegStreamMetadata>(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal async Task<FFMpegStreamMetadata> GetMetadataAsync(string path)
|
|
||||||
{
|
|
||||||
var instance = new Instance(_ffprobePath, BuildFFProbeArguments(path)) { DataBufferCapacity = _outputCapacity };
|
|
||||||
await instance.FinishedRunning();
|
|
||||||
var output = string.Join("", instance.OutputData);
|
|
||||||
return JsonConvert.DeserializeObject<FFMpegStreamMetadata>(output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Pipes
|
|
||||||
{
|
|
||||||
static class PipeHelpers
|
|
||||||
{
|
|
||||||
public static string GetUnqiuePipeName() => "FFMpegCore_Pipe_" + Guid.NewGuid();
|
|
||||||
|
|
||||||
public static string GetPipePath(string pipeName)
|
|
||||||
{
|
|
||||||
return $@"\\.\pipe\{pipeName}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Pipes
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Implementation of <see cref="IPipeDataWriter"/> used for stream redirection
|
|
||||||
/// </summary>
|
|
||||||
public class StreamPipeDataWriter : IPipeDataWriter
|
|
||||||
{
|
|
||||||
public System.IO.Stream Source { get; private set; }
|
|
||||||
public int BlockSize { get; set; } = 4096;
|
|
||||||
public string StreamFormat { get; set; } = string.Empty;
|
|
||||||
|
|
||||||
public StreamPipeDataWriter(System.IO.Stream stream)
|
|
||||||
{
|
|
||||||
Source = stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteData(System.IO.Stream pipe)=>
|
|
||||||
Source.CopyTo(pipe, BlockSize);
|
|
||||||
|
|
||||||
public Task WriteDataAsync(System.IO.Stream pipe) =>
|
|
||||||
Source.CopyToAsync(pipe, BlockSize);
|
|
||||||
|
|
||||||
public string GetFormat()
|
|
||||||
{
|
|
||||||
return StreamFormat;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
20
FFMpegCore/FFMpeg/Arguments/AudioBitrateArgument.cs
Normal file
20
FFMpegCore/FFMpeg/Arguments/AudioBitrateArgument.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents parameter of audio codec and it's quality
|
||||||
|
/// </summary>
|
||||||
|
public class AudioBitrateArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly int Bitrate;
|
||||||
|
public AudioBitrateArgument(AudioQuality value) : this((int)value) { }
|
||||||
|
public AudioBitrateArgument(int bitrate)
|
||||||
|
{
|
||||||
|
Bitrate = bitrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string Text => $"-b:a {Bitrate}k";
|
||||||
|
}
|
||||||
|
}
|
18
FFMpegCore/FFMpeg/Arguments/AudioCodecArgument.cs
Normal file
18
FFMpegCore/FFMpeg/Arguments/AudioCodecArgument.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents parameter of audio codec and it's quality
|
||||||
|
/// </summary>
|
||||||
|
public class AudioCodecArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly AudioCodec AudioCodec;
|
||||||
|
public AudioCodecArgument(AudioCodec audioCodec)
|
||||||
|
{
|
||||||
|
AudioCodec = audioCodec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-c:a {AudioCodec.ToString().ToLower()}";
|
||||||
|
}
|
||||||
|
}
|
16
FFMpegCore/FFMpeg/Arguments/AudioSamplingRateArgument.cs
Normal file
16
FFMpegCore/FFMpeg/Arguments/AudioSamplingRateArgument.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Audio sampling rate argument. Defaults to 48000 (Hz)
|
||||||
|
/// </summary>
|
||||||
|
public class AudioSamplingRateArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly int SamplingRate;
|
||||||
|
public AudioSamplingRateArgument(int samplingRate = 48000)
|
||||||
|
{
|
||||||
|
SamplingRate = samplingRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-ar {SamplingRate}";
|
||||||
|
}
|
||||||
|
}
|
26
FFMpegCore/FFMpeg/Arguments/BitStreamFilterArgument.cs
Normal file
26
FFMpegCore/FFMpeg/Arguments/BitStreamFilterArgument.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents parameter of bitstream filter
|
||||||
|
/// </summary>
|
||||||
|
public class BitStreamFilterArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly Channel Channel;
|
||||||
|
public readonly Filter Filter;
|
||||||
|
|
||||||
|
public BitStreamFilterArgument(Channel channel, Filter filter)
|
||||||
|
{
|
||||||
|
Channel = channel;
|
||||||
|
Filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => Channel switch
|
||||||
|
{
|
||||||
|
Channel.Audio => $"-bsf:a {Filter.ToString().ToLower()}",
|
||||||
|
Channel.Video => $"-bsf:v {Filter.ToString().ToLower()}",
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
20
FFMpegCore/FFMpeg/Arguments/ConcatArgument.cs
Normal file
20
FFMpegCore/FFMpeg/Arguments/ConcatArgument.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents parameter of concat argument
|
||||||
|
/// Used for creating video from multiple images or videos
|
||||||
|
/// </summary>
|
||||||
|
public class ConcatArgument : IInputArgument
|
||||||
|
{
|
||||||
|
public readonly IEnumerable<string> Values;
|
||||||
|
public ConcatArgument(IEnumerable<string> values)
|
||||||
|
{
|
||||||
|
Values = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-i \"concat:{string.Join(@"|", Values)}\"";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,24 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constant Rate Factor (CRF) argument
|
/// Constant Rate Factor (CRF) argument
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ConstantRateFactorArgument : Argument<int>
|
public class ConstantRateFactorArgument : IArgument
|
||||||
{
|
{
|
||||||
public ConstantRateFactorArgument(int crf) : base(crf)
|
public readonly int Crf;
|
||||||
|
|
||||||
|
public ConstantRateFactorArgument(int crf)
|
||||||
{
|
{
|
||||||
if (crf < 0 || crf > 63)
|
if (crf < 0 || crf > 63)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Argument is outside range (0 - 63)", nameof(crf));
|
throw new ArgumentException("Argument is outside range (0 - 63)", nameof(crf));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Crf = crf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
public string Text => $"-crf {Crf}";
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-crf {Value}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
24
FFMpegCore/FFMpeg/Arguments/CopyArgument.cs
Normal file
24
FFMpegCore/FFMpeg/Arguments/CopyArgument.cs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents parameter of copy parameter
|
||||||
|
/// Defines if channel (audio, video or both) should be copied to output file
|
||||||
|
/// </summary>
|
||||||
|
public class CopyArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly Channel Channel;
|
||||||
|
public CopyArgument(Channel channel = Channel.Both)
|
||||||
|
{
|
||||||
|
Channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => Channel switch
|
||||||
|
{
|
||||||
|
Channel.Audio => "-c:a copy",
|
||||||
|
Channel.Video => "-c:v copy",
|
||||||
|
_ => "-c copy"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
16
FFMpegCore/FFMpeg/Arguments/CpuSpeedArgument.cs
Normal file
16
FFMpegCore/FFMpeg/Arguments/CpuSpeedArgument.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents cpu speed parameter
|
||||||
|
/// </summary>
|
||||||
|
public class CpuSpeedArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly int CpuSpeed;
|
||||||
|
public CpuSpeedArgument(int cpuSpeed)
|
||||||
|
{
|
||||||
|
CpuSpeed = cpuSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-quality good -cpu-used {CpuSpeed} -deadline realtime";
|
||||||
|
}
|
||||||
|
}
|
14
FFMpegCore/FFMpeg/Arguments/CustomArgument.cs
Normal file
14
FFMpegCore/FFMpeg/Arguments/CustomArgument.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
public class CustomArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly string Argument;
|
||||||
|
|
||||||
|
public CustomArgument(string argument)
|
||||||
|
{
|
||||||
|
Argument = argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => Argument ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
27
FFMpegCore/FFMpeg/Arguments/DisableChannelArgument.cs
Normal file
27
FFMpegCore/FFMpeg/Arguments/DisableChannelArgument.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
using FFMpegCore.Exceptions;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents cpu speed parameter
|
||||||
|
/// </summary>
|
||||||
|
public class DisableChannelArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly Channel Channel;
|
||||||
|
|
||||||
|
public DisableChannelArgument(Channel channel)
|
||||||
|
{
|
||||||
|
if (channel == Channel.Both)
|
||||||
|
throw new FFMpegException(FFMpegExceptionType.Conversion, "Cannot disable both channels");
|
||||||
|
Channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => Channel switch
|
||||||
|
{
|
||||||
|
Channel.Video => "-vn",
|
||||||
|
Channel.Audio => "-an",
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
61
FFMpegCore/FFMpeg/Arguments/DrawTextArgument.cs
Normal file
61
FFMpegCore/FFMpeg/Arguments/DrawTextArgument.cs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Drawtext video filter argument
|
||||||
|
/// </summary>
|
||||||
|
public class DrawTextArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly DrawTextOptions Options;
|
||||||
|
|
||||||
|
public DrawTextArgument(DrawTextOptions options)
|
||||||
|
{
|
||||||
|
Options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-vf drawtext=\"{Options.TextInternal}\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DrawTextOptions
|
||||||
|
{
|
||||||
|
public readonly string Text;
|
||||||
|
public readonly string Font;
|
||||||
|
public readonly List<(string key, string value)> Parameters;
|
||||||
|
|
||||||
|
public static DrawTextOptions Create(string text, string font)
|
||||||
|
{
|
||||||
|
return new DrawTextOptions(text, font, new List<(string, string)>());
|
||||||
|
}
|
||||||
|
public static DrawTextOptions Create(string text, string font, params (string key, string value)[] parameters)
|
||||||
|
{
|
||||||
|
return new DrawTextOptions(text, font, parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string TextInternal => string.Join(":", new[] {("text", Text), ("fontfile", Font)}.Concat(Parameters).Select(FormatArgumentPair));
|
||||||
|
|
||||||
|
private static string FormatArgumentPair((string key, string value) pair)
|
||||||
|
{
|
||||||
|
return $"{pair.key}={EncloseIfContainsSpace(pair.value)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EncloseIfContainsSpace(string input)
|
||||||
|
{
|
||||||
|
return input.Contains(" ") ? $"'{input}'" : input;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawTextOptions(string text, string font, IEnumerable<(string, string)> parameters)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
Font = font;
|
||||||
|
Parameters = parameters.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrawTextOptions WithParameter(string key, string value)
|
||||||
|
{
|
||||||
|
Parameters.Add((key, value));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
FFMpegCore/FFMpeg/Arguments/DurationArgument.cs
Normal file
18
FFMpegCore/FFMpeg/Arguments/DurationArgument.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents duration parameter
|
||||||
|
/// </summary>
|
||||||
|
public class DurationArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly TimeSpan? Duration;
|
||||||
|
public DurationArgument(TimeSpan? duration)
|
||||||
|
{
|
||||||
|
Duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => !Duration.HasValue ? string.Empty : $"-t {Duration.Value}";
|
||||||
|
}
|
||||||
|
}
|
10
FFMpegCore/FFMpeg/Arguments/FaststartArgument.cs
Normal file
10
FFMpegCore/FFMpeg/Arguments/FaststartArgument.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Faststart argument - for moving moov atom to the start of file
|
||||||
|
/// </summary>
|
||||||
|
public class FaststartArgument : IArgument
|
||||||
|
{
|
||||||
|
public string Text => "-movflags faststart";
|
||||||
|
}
|
||||||
|
}
|
20
FFMpegCore/FFMpeg/Arguments/ForceFormatArgument.cs
Normal file
20
FFMpegCore/FFMpeg/Arguments/ForceFormatArgument.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents force format parameter
|
||||||
|
/// </summary>
|
||||||
|
public class ForceFormatArgument : IArgument
|
||||||
|
{
|
||||||
|
private readonly string _format;
|
||||||
|
public ForceFormatArgument(string format)
|
||||||
|
{
|
||||||
|
_format = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForceFormatArgument(VideoCodec value) : this(value.ToString().ToLower()) { }
|
||||||
|
|
||||||
|
public string Text => $"-f {_format}";
|
||||||
|
}
|
||||||
|
}
|
16
FFMpegCore/FFMpeg/Arguments/FrameOutputCountArgument.cs
Normal file
16
FFMpegCore/FFMpeg/Arguments/FrameOutputCountArgument.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents frame output count parameter
|
||||||
|
/// </summary>
|
||||||
|
public class FrameOutputCountArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly int Frames;
|
||||||
|
public FrameOutputCountArgument(int frames)
|
||||||
|
{
|
||||||
|
Frames = frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-vframes {Frames}";
|
||||||
|
}
|
||||||
|
}
|
17
FFMpegCore/FFMpeg/Arguments/FrameRateArgument.cs
Normal file
17
FFMpegCore/FFMpeg/Arguments/FrameRateArgument.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents frame rate parameter
|
||||||
|
/// </summary>
|
||||||
|
public class FrameRateArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly double Framerate;
|
||||||
|
|
||||||
|
public FrameRateArgument(double framerate)
|
||||||
|
{
|
||||||
|
Framerate = framerate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-r {Framerate}";
|
||||||
|
}
|
||||||
|
}
|
63
FFMpegCore/FFMpeg/Arguments/InputArgument.cs
Normal file
63
FFMpegCore/FFMpeg/Arguments/InputArgument.cs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents input parameter
|
||||||
|
/// </summary>
|
||||||
|
public class InputArgument : IInputArgument
|
||||||
|
{
|
||||||
|
public readonly bool VerifyExists;
|
||||||
|
public readonly string[] FilePaths;
|
||||||
|
|
||||||
|
public InputArgument(bool verifyExists, params string[] filePaths)
|
||||||
|
{
|
||||||
|
VerifyExists = verifyExists;
|
||||||
|
FilePaths = filePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputArgument(params string[] filePaths) : this(true, filePaths) { }
|
||||||
|
public InputArgument(params FileInfo[] fileInfos) : this(false, fileInfos) { }
|
||||||
|
public InputArgument(params Uri[] uris) : this(false, uris) { }
|
||||||
|
public InputArgument(bool verifyExists, params FileInfo[] fileInfos) : this(verifyExists, fileInfos.Select(v => v.FullName).ToArray()) { }
|
||||||
|
public InputArgument(bool verifyExists, params Uri[] uris) : this(verifyExists, uris.Select(v => v.AbsoluteUri).ToArray()) { }
|
||||||
|
|
||||||
|
public void Pre()
|
||||||
|
{
|
||||||
|
if (!VerifyExists) return;
|
||||||
|
foreach (var filePath in FilePaths)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
throw new FileNotFoundException("Input file not found", filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => string.Join(" ", FilePaths.Select(v => $"-i \"{v}\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IArgument
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The textual representation of the argument
|
||||||
|
/// </summary>
|
||||||
|
string Text { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IInputOutputArgument : IArgument
|
||||||
|
{
|
||||||
|
void Pre() {}
|
||||||
|
Task During(CancellationToken? cancellationToken = null) => Task.CompletedTask;
|
||||||
|
void Post() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IInputArgument : IInputOutputArgument
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public interface IOutputArgument : IInputOutputArgument
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,32 +1,23 @@
|
||||||
using FFMpegCore.FFMPEG.Pipes;
|
using System.IO.Pipes;
|
||||||
using Instances;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.IO.Pipes;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents input parameter for a named pipe
|
/// Represents input parameter for a named pipe
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class InputPipeArgument : PipeArgument
|
public class InputPipeArgument : PipeArgument
|
||||||
{
|
{
|
||||||
public IPipeDataWriter Writer { get; private set; }
|
public readonly IPipeDataWriter Writer;
|
||||||
|
|
||||||
public InputPipeArgument(IPipeDataWriter writer) : base(PipeDirection.Out)
|
public InputPipeArgument(IPipeDataWriter writer) : base(PipeDirection.Out)
|
||||||
{
|
{
|
||||||
Writer = writer;
|
Writer = writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetStringValue()
|
public override string Text => $"-y {Writer.GetFormat()} -i \"{PipePath}\"";
|
||||||
{
|
|
||||||
return $"-y {Writer.GetFormat()} -i \"{PipePath}\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task ProcessDataAsync(CancellationToken token)
|
public override async Task ProcessDataAsync(CancellationToken token)
|
||||||
{
|
{
|
16
FFMpegCore/FFMpeg/Arguments/LoopArgument.cs
Normal file
16
FFMpegCore/FFMpeg/Arguments/LoopArgument.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents loop parameter
|
||||||
|
/// </summary>
|
||||||
|
public class LoopArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly int Times;
|
||||||
|
public LoopArgument(int times)
|
||||||
|
{
|
||||||
|
Times = times;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-loop {Times}";
|
||||||
|
}
|
||||||
|
}
|
38
FFMpegCore/FFMpeg/Arguments/OutputArgument.cs
Normal file
38
FFMpegCore/FFMpeg/Arguments/OutputArgument.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using FFMpegCore.Exceptions;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents output parameter
|
||||||
|
/// </summary>
|
||||||
|
public class OutputArgument : IOutputArgument
|
||||||
|
{
|
||||||
|
public readonly string Path;
|
||||||
|
public readonly bool Overwrite;
|
||||||
|
|
||||||
|
public OutputArgument(string path, bool overwrite = false)
|
||||||
|
{
|
||||||
|
Path = path;
|
||||||
|
Overwrite = overwrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Pre()
|
||||||
|
{
|
||||||
|
if (!Overwrite && File.Exists(Path))
|
||||||
|
throw new FFMpegException(FFMpegExceptionType.File, "Output file already exists and overwrite is disabled");
|
||||||
|
}
|
||||||
|
public void Post()
|
||||||
|
{
|
||||||
|
if (!File.Exists(Path))
|
||||||
|
throw new FFMpegException(FFMpegExceptionType.File, "Output file was not created");
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputArgument(FileInfo value) : this(value.FullName) { }
|
||||||
|
|
||||||
|
public OutputArgument(Uri value) : this(value.AbsolutePath) { }
|
||||||
|
|
||||||
|
public string Text => $"\"{Path}\"{(Overwrite ? " -y" : string.Empty)}";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,26 +1,20 @@
|
||||||
using FFMpegCore.FFMPEG.Pipes;
|
using System.IO.Pipes;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO.Pipes;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
public class OutputPipeArgument : PipeArgument
|
public class OutputPipeArgument : PipeArgument
|
||||||
{
|
{
|
||||||
public IPipeDataReader Reader { get; private set; }
|
public readonly IPipeDataReader Reader;
|
||||||
|
|
||||||
public OutputPipeArgument(IPipeDataReader reader) : base(PipeDirection.In)
|
public OutputPipeArgument(IPipeDataReader reader) : base(PipeDirection.In)
|
||||||
{
|
{
|
||||||
Reader = reader;
|
Reader = reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string GetStringValue()
|
public override string Text => $"\"{PipePath}\" -y";
|
||||||
{
|
|
||||||
return $"\"{PipePath}\" -y";
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task ProcessDataAsync(CancellationToken token)
|
public override async Task ProcessDataAsync(CancellationToken token)
|
||||||
{
|
{
|
11
FFMpegCore/FFMpeg/Arguments/OverwriteArgument.cs
Normal file
11
FFMpegCore/FFMpeg/Arguments/OverwriteArgument.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents overwrite parameter
|
||||||
|
/// If output file should be overwritten if exists
|
||||||
|
/// </summary>
|
||||||
|
public class OverwriteArgument : IArgument
|
||||||
|
{
|
||||||
|
public string Text => "-y";
|
||||||
|
}
|
||||||
|
}
|
46
FFMpegCore/FFMpeg/Arguments/PipeArgument.cs
Normal file
46
FFMpegCore/FFMpeg/Arguments/PipeArgument.cs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
using System;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
public abstract class PipeArgument : IInputArgument, IOutputArgument
|
||||||
|
{
|
||||||
|
private string PipeName { get; }
|
||||||
|
public string PipePath => PipeHelpers.GetPipePath(PipeName);
|
||||||
|
|
||||||
|
protected NamedPipeServerStream Pipe { get; private set; } = null!;
|
||||||
|
private readonly PipeDirection _direction;
|
||||||
|
|
||||||
|
protected PipeArgument(PipeDirection direction)
|
||||||
|
{
|
||||||
|
PipeName = PipeHelpers.GetUnqiuePipeName();
|
||||||
|
_direction = direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Pre()
|
||||||
|
{
|
||||||
|
if (Pipe != null)
|
||||||
|
throw new InvalidOperationException("Pipe already has been opened");
|
||||||
|
|
||||||
|
Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Post()
|
||||||
|
{
|
||||||
|
Pipe?.Dispose();
|
||||||
|
Pipe = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task During(CancellationToken? cancellationToken = null)
|
||||||
|
{
|
||||||
|
await ProcessDataAsync(cancellationToken ?? CancellationToken.None).ConfigureAwait(false);
|
||||||
|
Post();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task ProcessDataAsync(CancellationToken token);
|
||||||
|
public abstract string Text { get; }
|
||||||
|
}
|
||||||
|
}
|
7
FFMpegCore/FFMpeg/Arguments/QuietArgument.cs
Normal file
7
FFMpegCore/FFMpeg/Arguments/QuietArgument.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
public class QuietArgument : IArgument
|
||||||
|
{
|
||||||
|
public string Text => "-hide_banner -loglevel warning";
|
||||||
|
}
|
||||||
|
}
|
10
FFMpegCore/FFMpeg/Arguments/RemoveMetadataArgument.cs
Normal file
10
FFMpegCore/FFMpeg/Arguments/RemoveMetadataArgument.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Remove metadata argument
|
||||||
|
/// </summary>
|
||||||
|
public class RemoveMetadataArgument : IArgument
|
||||||
|
{
|
||||||
|
public string Text => "-map_metadata -1";
|
||||||
|
}
|
||||||
|
}
|
26
FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs
Normal file
26
FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Drawing;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents scale parameter
|
||||||
|
/// </summary>
|
||||||
|
public class ScaleArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly Size? Size;
|
||||||
|
public ScaleArgument(Size? size)
|
||||||
|
{
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ScaleArgument(int width, int height) : this(new Size(width, height)) { }
|
||||||
|
|
||||||
|
public ScaleArgument(VideoSize videosize)
|
||||||
|
{
|
||||||
|
Size = videosize == VideoSize.Original ? new Size(-1, -1) : new Size(-1, (int)videosize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual string Text => Size.HasValue ? $"-vf scale={Size.Value.Width}:{Size.Value.Height}" : string.Empty;
|
||||||
|
}
|
||||||
|
}
|
18
FFMpegCore/FFMpeg/Arguments/SeekArgument.cs
Normal file
18
FFMpegCore/FFMpeg/Arguments/SeekArgument.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents seek parameter
|
||||||
|
/// </summary>
|
||||||
|
public class SeekArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly TimeSpan? SeekTo;
|
||||||
|
public SeekArgument(TimeSpan? seekTo)
|
||||||
|
{
|
||||||
|
SeekTo = seekTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => !SeekTo.HasValue ? string.Empty : $"-ss {SeekTo.Value}";
|
||||||
|
}
|
||||||
|
}
|
17
FFMpegCore/FFMpeg/Arguments/ShortestArgument.cs
Normal file
17
FFMpegCore/FFMpeg/Arguments/ShortestArgument.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents shortest parameter
|
||||||
|
/// </summary>
|
||||||
|
public class ShortestArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly bool Shortest;
|
||||||
|
|
||||||
|
public ShortestArgument(bool shortest)
|
||||||
|
{
|
||||||
|
Shortest = shortest;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => Shortest ? "-shortest" : string.Empty;
|
||||||
|
}
|
||||||
|
}
|
19
FFMpegCore/FFMpeg/Arguments/SizeArgument.cs
Normal file
19
FFMpegCore/FFMpeg/Arguments/SizeArgument.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using System.Drawing;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents size parameter
|
||||||
|
/// </summary>
|
||||||
|
public class SizeArgument : ScaleArgument
|
||||||
|
{
|
||||||
|
public SizeArgument(Size? value) : base(value) { }
|
||||||
|
|
||||||
|
public SizeArgument(VideoSize videosize) : base(videosize) { }
|
||||||
|
|
||||||
|
public SizeArgument(int width, int height) : base(width, height) { }
|
||||||
|
|
||||||
|
public override string Text => Size.HasValue ? $"-s {Size.Value.Width}x{Size.Value.Height}" : string.Empty;
|
||||||
|
}
|
||||||
|
}
|
19
FFMpegCore/FFMpeg/Arguments/SpeedPresetArgument.cs
Normal file
19
FFMpegCore/FFMpeg/Arguments/SpeedPresetArgument.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents speed parameter
|
||||||
|
/// </summary>
|
||||||
|
public class SpeedPresetArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly Speed Speed;
|
||||||
|
|
||||||
|
public SpeedPresetArgument(Speed speed)
|
||||||
|
{
|
||||||
|
Speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-preset {Speed.ToString().ToLower()}";
|
||||||
|
}
|
||||||
|
}
|
17
FFMpegCore/FFMpeg/Arguments/StartNumberArgument.cs
Normal file
17
FFMpegCore/FFMpeg/Arguments/StartNumberArgument.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents start number parameter
|
||||||
|
/// </summary>
|
||||||
|
public class StartNumberArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly int StartNumber;
|
||||||
|
|
||||||
|
public StartNumberArgument(int startNumber)
|
||||||
|
{
|
||||||
|
StartNumber = startNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-start_number {StartNumber}";
|
||||||
|
}
|
||||||
|
}
|
21
FFMpegCore/FFMpeg/Arguments/ThreadsArgument.cs
Normal file
21
FFMpegCore/FFMpeg/Arguments/ThreadsArgument.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents threads parameter
|
||||||
|
/// Number of threads used for video encoding
|
||||||
|
/// </summary>
|
||||||
|
public class ThreadsArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly int Threads;
|
||||||
|
public ThreadsArgument(int threads)
|
||||||
|
{
|
||||||
|
Threads = threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ThreadsArgument(bool isMultiThreaded) : this(isMultiThreaded ? Environment.ProcessorCount : 1) { }
|
||||||
|
|
||||||
|
public string Text => $"-threads {Threads}";
|
||||||
|
}
|
||||||
|
}
|
22
FFMpegCore/FFMpeg/Arguments/TransposeArgument.cs
Normal file
22
FFMpegCore/FFMpeg/Arguments/TransposeArgument.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Transpose argument.
|
||||||
|
/// 0 = 90CounterCLockwise and Vertical Flip (default)
|
||||||
|
/// 1 = 90Clockwise
|
||||||
|
/// 2 = 90CounterClockwise
|
||||||
|
/// 3 = 90Clockwise and Vertical Flip
|
||||||
|
/// </summary>
|
||||||
|
public class TransposeArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly Transposition Transposition;
|
||||||
|
public TransposeArgument(Transposition transposition)
|
||||||
|
{
|
||||||
|
Transposition = transposition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-vf \"transpose={(int)Transposition}\"";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,24 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Argument
|
namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Variable Bitrate Argument (VBR) argument
|
/// Variable Bitrate Argument (VBR) argument
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class VariableBitRateArgument : Argument<int>
|
public class VariableBitRateArgument : IArgument
|
||||||
{
|
{
|
||||||
public VariableBitRateArgument(int vbr) : base(vbr)
|
public readonly int Vbr;
|
||||||
|
|
||||||
|
public VariableBitRateArgument(int vbr)
|
||||||
{
|
{
|
||||||
if (vbr < 0 || vbr > 5)
|
if (vbr < 0 || vbr > 5)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Argument is outside range (0 - 5)", nameof(vbr));
|
throw new ArgumentException("Argument is outside range (0 - 5)", nameof(vbr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vbr = vbr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
public string Text => $"-vbr {Vbr}";
|
||||||
public override string GetStringValue()
|
|
||||||
{
|
|
||||||
return $"-vbr {Value}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
17
FFMpegCore/FFMpeg/Arguments/VideoBitrateArgument.cs
Normal file
17
FFMpegCore/FFMpeg/Arguments/VideoBitrateArgument.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents video bitrate parameter
|
||||||
|
/// </summary>
|
||||||
|
public class VideoBitrateArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly int Bitrate;
|
||||||
|
|
||||||
|
public VideoBitrateArgument(int bitrate)
|
||||||
|
{
|
||||||
|
Bitrate = bitrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text => $"-b:v {Bitrate}k";
|
||||||
|
}
|
||||||
|
}
|
21
FFMpegCore/FFMpeg/Arguments/VideoCodecArgument.cs
Normal file
21
FFMpegCore/FFMpeg/Arguments/VideoCodecArgument.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents video codec parameter
|
||||||
|
/// </summary>
|
||||||
|
public class VideoCodecArgument : IArgument
|
||||||
|
{
|
||||||
|
public readonly string Codec;
|
||||||
|
|
||||||
|
public VideoCodecArgument(string codec)
|
||||||
|
{
|
||||||
|
Codec = codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoCodecArgument(VideoCodec value) : this(value.ToString().ToLower()) { }
|
||||||
|
|
||||||
|
public string Text => $"-c:v {Codec} -pix_fmt yuv420p";
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
namespace FFMpegCore.FFMPEG.Enums
|
namespace FFMpegCore.Enums
|
||||||
{
|
{
|
||||||
public enum AudioQuality
|
public enum AudioQuality
|
||||||
{
|
{
|
||||||
Ultra = 384,
|
Ultra = 384,
|
||||||
VeryHigh = 256,
|
VeryHigh = 256,
|
||||||
Hd = 192,
|
Good = 192,
|
||||||
Normal = 128,
|
Normal = 128,
|
||||||
BelowNormal = 96,
|
BelowNormal = 96,
|
||||||
Low = 64
|
Low = 64
|
|
@ -1,4 +1,4 @@
|
||||||
namespace FFMpegCore.FFMPEG.Enums
|
namespace FFMpegCore.Enums
|
||||||
{
|
{
|
||||||
public enum VideoCodec
|
public enum VideoCodec
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace FFMpegCore.FFMPEG.Enums
|
namespace FFMpegCore.Enums
|
||||||
{
|
{
|
||||||
public enum Speed
|
public enum Speed
|
||||||
{
|
{
|
10
FFMpegCore/FFMpeg/Enums/Transposition.cs
Normal file
10
FFMpegCore/FFMpeg/Enums/Transposition.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace FFMpegCore.Enums
|
||||||
|
{
|
||||||
|
public enum Transposition
|
||||||
|
{
|
||||||
|
CounterClockwise90VerticalFlip = 0,
|
||||||
|
Clockwise90 = 1,
|
||||||
|
CounterClockwise90 = 2,
|
||||||
|
Clockwise90VerticalFlip = 3
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
namespace FFMpegCore.FFMPEG.Enums
|
namespace FFMpegCore.Enums
|
||||||
{
|
{
|
||||||
public enum VideoSize
|
public enum VideoSize
|
||||||
{
|
{
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace FFMpegCore.FFMPEG.Exceptions
|
namespace FFMpegCore.Exceptions
|
||||||
{
|
{
|
||||||
public enum FFMpegExceptionType
|
public enum FFMpegExceptionType
|
||||||
{
|
{
|
||||||
|
@ -14,11 +13,9 @@ public enum FFMpegExceptionType
|
||||||
|
|
||||||
public class FFMpegException : Exception
|
public class FFMpegException : Exception
|
||||||
{
|
{
|
||||||
public FFMpegException(FFMpegExceptionType type, StringBuilder sb): this(type, sb.ToString(), null) { }
|
|
||||||
|
|
||||||
public FFMpegException(FFMpegExceptionType type, string message): this(type, message, null) { }
|
public FFMpegException(FFMpegExceptionType type, string message): this(type, message, null) { }
|
||||||
|
|
||||||
public FFMpegException(FFMpegExceptionType type, string message = null, Exception innerException = null)
|
public FFMpegException(FFMpegExceptionType type, string? message = null, Exception? innerException = null)
|
||||||
: base(message, innerException)
|
: base(message, innerException)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
338
FFMpegCore/FFMpeg/FFMpeg.cs
Normal file
338
FFMpegCore/FFMpeg/FFMpeg.cs
Normal file
|
@ -0,0 +1,338 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
|
using FFMpegCore.Helpers;
|
||||||
|
|
||||||
|
namespace FFMpegCore
|
||||||
|
{
|
||||||
|
public static class FFMpeg
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Saves a 'png' thumbnail from the input video.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">Source video file.</param>
|
||||||
|
/// <param name="output">Output video file</param>
|
||||||
|
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||||
|
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||||
|
/// <param name="persistSnapshotOnFileSystem">By default, it deletes the created image on disk. If set to true, it won't delete the image</param>
|
||||||
|
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||||
|
public static Bitmap Snapshot(MediaAnalysis source, string output, Size? size = null, TimeSpan? captureTime = null,
|
||||||
|
bool persistSnapshotOnFileSystem = false)
|
||||||
|
{
|
||||||
|
if (captureTime == null)
|
||||||
|
captureTime = TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3);
|
||||||
|
|
||||||
|
if (Path.GetExtension(output) != FileExtension.Png)
|
||||||
|
output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png;
|
||||||
|
|
||||||
|
if (size == null || (size.Value.Height == 0 && size.Value.Width == 0))
|
||||||
|
size = new Size(source.PrimaryVideoStream.Width, source.PrimaryVideoStream.Height);
|
||||||
|
|
||||||
|
if (size.Value.Width != size.Value.Height)
|
||||||
|
{
|
||||||
|
if (size.Value.Width == 0)
|
||||||
|
{
|
||||||
|
var ratio = source.PrimaryVideoStream.Width / (double)size.Value.Width;
|
||||||
|
|
||||||
|
size = new Size((int)(source.PrimaryVideoStream.Width * ratio), (int)(source.PrimaryVideoStream.Height * ratio));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size.Value.Height == 0)
|
||||||
|
{
|
||||||
|
var ratio = source.PrimaryVideoStream.Height / (double)size.Value.Height;
|
||||||
|
|
||||||
|
size = new Size((int)(source.PrimaryVideoStream.Width * ratio), (int)(source.PrimaryVideoStream.Height * ratio));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = FFMpegArguments
|
||||||
|
.FromInputFiles(true, source.Path)
|
||||||
|
.WithVideoCodec(VideoCodec.Png)
|
||||||
|
.WithFrameOutputCount(1)
|
||||||
|
.Resize(size)
|
||||||
|
.Seek(captureTime)
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
throw new OperationCanceledException("Could not take snapshot!");
|
||||||
|
|
||||||
|
Bitmap result;
|
||||||
|
using (var bmp = (Bitmap)Image.FromFile(output))
|
||||||
|
{
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
bmp.Save(ms, ImageFormat.Png);
|
||||||
|
result = new Bitmap(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(output) && !persistSnapshotOnFileSystem)
|
||||||
|
File.Delete(output);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a video do a different format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">Input video source.</param>
|
||||||
|
/// <param name="output">Output information.</param>
|
||||||
|
/// <param name="type">Target conversion video type.</param>
|
||||||
|
/// <param name="speed">Conversion target speed/quality (faster speed = lower quality).</param>
|
||||||
|
/// <param name="size">Video size.</param>
|
||||||
|
/// <param name="audioQuality">Conversion target audio quality.</param>
|
||||||
|
/// <param name="multithreaded">Is encoding multithreaded.</param>
|
||||||
|
/// <returns>Output video information.</returns>
|
||||||
|
public static bool Convert(
|
||||||
|
MediaAnalysis source,
|
||||||
|
string output,
|
||||||
|
VideoType type = VideoType.Mp4,
|
||||||
|
Speed speed = Speed.SuperFast,
|
||||||
|
VideoSize size = VideoSize.Original,
|
||||||
|
AudioQuality audioQuality = AudioQuality.Normal,
|
||||||
|
bool multithreaded = false)
|
||||||
|
{
|
||||||
|
FFMpegHelper.ExtensionExceptionCheck(output, type.Extension());
|
||||||
|
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
||||||
|
|
||||||
|
var scale = VideoSize.Original == size ? 1 : (double)source.PrimaryVideoStream.Height / (int)size;
|
||||||
|
var outputSize = new Size((int)(source.PrimaryVideoStream.Width / scale), (int)(source.PrimaryVideoStream.Height / scale));
|
||||||
|
|
||||||
|
if (outputSize.Width % 2 != 0)
|
||||||
|
outputSize.Width += 1;
|
||||||
|
|
||||||
|
return type switch
|
||||||
|
{
|
||||||
|
VideoType.Mp4 => FFMpegArguments
|
||||||
|
.FromInputFiles(true, source.Path)
|
||||||
|
.UsingMultithreading(multithreaded)
|
||||||
|
.WithVideoCodec(VideoCodec.LibX264)
|
||||||
|
.WithVideoBitrate(2400)
|
||||||
|
.Scale(outputSize)
|
||||||
|
.WithSpeedPreset(speed)
|
||||||
|
.WithAudioCodec(AudioCodec.Aac)
|
||||||
|
.WithAudioBitrate(audioQuality)
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously(),
|
||||||
|
VideoType.Ogv => FFMpegArguments
|
||||||
|
.FromInputFiles(true, source.Path)
|
||||||
|
.UsingMultithreading(multithreaded)
|
||||||
|
.WithVideoCodec(VideoCodec.LibTheora)
|
||||||
|
.WithVideoBitrate(2400)
|
||||||
|
.Scale(outputSize)
|
||||||
|
.WithSpeedPreset(speed)
|
||||||
|
.WithAudioCodec(AudioCodec.LibVorbis)
|
||||||
|
.WithAudioBitrate(audioQuality)
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously(),
|
||||||
|
VideoType.Ts => FFMpegArguments
|
||||||
|
.FromInputFiles(true, source.Path)
|
||||||
|
.CopyChannel()
|
||||||
|
.WithBitStreamFilter(Channel.Video, Filter.H264_Mp4ToAnnexB)
|
||||||
|
.ForceFormat(VideoCodec.MpegTs)
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously(),
|
||||||
|
VideoType.WebM => FFMpegArguments
|
||||||
|
.FromInputFiles(true, source.Path)
|
||||||
|
.UsingMultithreading(multithreaded)
|
||||||
|
.WithVideoCodec(VideoCodec.LibVpx)
|
||||||
|
.WithVideoBitrate(2400)
|
||||||
|
.Scale(outputSize)
|
||||||
|
.WithSpeedPreset(speed)
|
||||||
|
.WithAudioCodec(AudioCodec.LibVorbis)
|
||||||
|
.WithAudioBitrate(audioQuality)
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously(),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a poster image to an audio file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="image">Source image file.</param>
|
||||||
|
/// <param name="audio">Source audio file.</param>
|
||||||
|
/// <param name="output">Output video file.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool PosterWithAudio(string image, string audio, string output)
|
||||||
|
{
|
||||||
|
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
||||||
|
FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image));
|
||||||
|
|
||||||
|
return FFMpegArguments
|
||||||
|
.FromInputFiles(true, image, audio)
|
||||||
|
.Loop(1)
|
||||||
|
.WithVideoCodec(VideoCodec.LibX264)
|
||||||
|
.WithConstantRateFactor(21)
|
||||||
|
.WithAudioBitrate(AudioQuality.Normal)
|
||||||
|
.UsingShortest()
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Joins a list of video files.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="output">Output video file.</param>
|
||||||
|
/// <param name="videos">List of vides that need to be joined together.</param>
|
||||||
|
/// <returns>Output video information.</returns>
|
||||||
|
public static bool Join(string output, params MediaAnalysis[] videos)
|
||||||
|
{
|
||||||
|
var temporaryVideoParts = videos.Select(video =>
|
||||||
|
{
|
||||||
|
FFMpegHelper.ConversionSizeExceptionCheck(video);
|
||||||
|
var destinationPath = video.Path.Replace(video.Extension, FileExtension.Ts);
|
||||||
|
Convert(video, destinationPath, VideoType.Ts);
|
||||||
|
return destinationPath;
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return FFMpegArguments
|
||||||
|
.FromConcatenation(temporaryVideoParts)
|
||||||
|
.CopyChannel()
|
||||||
|
.WithBitStreamFilter(Channel.Audio, Filter.Aac_AdtstoAsc)
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Cleanup(temporaryVideoParts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts an image sequence to a video.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="output">Output video file.</param>
|
||||||
|
/// <param name="frameRate">FPS</param>
|
||||||
|
/// <param name="images">Image sequence collection</param>
|
||||||
|
/// <returns>Output video information.</returns>
|
||||||
|
public static bool JoinImageSequence(string output, double frameRate = 30, params ImageInfo[] images)
|
||||||
|
{
|
||||||
|
var temporaryImageFiles = images.Select((image, index) =>
|
||||||
|
{
|
||||||
|
FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image.FullName));
|
||||||
|
var destinationPath = image.FullName.Replace(image.Name, $"{index.ToString().PadLeft(9, '0')}{image.Extension}");
|
||||||
|
File.Copy(image.FullName, destinationPath);
|
||||||
|
return destinationPath;
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
var firstImage = images.First();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return FFMpegArguments
|
||||||
|
.FromInputFiles(false, Path.Join(firstImage.Directory.FullName, "%09d.png"))
|
||||||
|
.WithVideoCodec(VideoCodec.LibX264)
|
||||||
|
.Resize(firstImage.Width, firstImage.Height)
|
||||||
|
.WithFrameOutputCount(images.Length)
|
||||||
|
.WithStartNumber(0)
|
||||||
|
.WithFramerate(frameRate)
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Cleanup(temporaryImageFiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Records M3U8 streams to the specified output.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="uri">URI to pointing towards stream.</param>
|
||||||
|
/// <param name="output">Output file</param>
|
||||||
|
/// <returns>Success state.</returns>
|
||||||
|
public static bool SaveM3U8Stream(Uri uri, string output)
|
||||||
|
{
|
||||||
|
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
||||||
|
|
||||||
|
if (uri.Scheme == "http" || uri.Scheme == "https")
|
||||||
|
{
|
||||||
|
return FFMpegArguments
|
||||||
|
.FromInputFiles(false, uri)
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Strips a video file of audio.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Input video file.</param>
|
||||||
|
/// <param name="output">Output video file.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool Mute(string input, string output)
|
||||||
|
{
|
||||||
|
var source = FFProbe.Analyse(input);
|
||||||
|
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
||||||
|
FFMpegHelper.ExtensionExceptionCheck(output, source.Extension);
|
||||||
|
|
||||||
|
return FFMpegArguments
|
||||||
|
.FromInputFiles(true, source.Path)
|
||||||
|
.CopyChannel(Channel.Video)
|
||||||
|
.DisableChannel(Channel.Audio)
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves audio from a specific video file to disk.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Source video file.</param>
|
||||||
|
/// <param name="output">Output audio file.</param>
|
||||||
|
/// <returns>Success state.</returns>
|
||||||
|
public static bool ExtractAudio(string input, string output)
|
||||||
|
{
|
||||||
|
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp3);
|
||||||
|
|
||||||
|
return FFMpegArguments
|
||||||
|
.FromInputFiles(true, input)
|
||||||
|
.DisableChannel(Channel.Video)
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds audio to a video file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Source video file.</param>
|
||||||
|
/// <param name="inputAudio">Source audio file.</param>
|
||||||
|
/// <param name="output">Output video file.</param>
|
||||||
|
/// <param name="stopAtShortest">Indicates if the encoding should stop at the shortest input file.</param>
|
||||||
|
/// <returns>Success state</returns>
|
||||||
|
public static bool ReplaceAudio(string input, string inputAudio, string output, bool stopAtShortest = false)
|
||||||
|
{
|
||||||
|
var source = FFProbe.Analyse(input);
|
||||||
|
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
||||||
|
FFMpegHelper.ExtensionExceptionCheck(output, source.Extension);
|
||||||
|
|
||||||
|
return FFMpegArguments
|
||||||
|
.FromInputFiles(true, source.Path, inputAudio)
|
||||||
|
.CopyChannel()
|
||||||
|
.WithAudioCodec(AudioCodec.Aac)
|
||||||
|
.WithAudioBitrate(AudioQuality.Good)
|
||||||
|
.UsingShortest(stopAtShortest)
|
||||||
|
.OutputToFile(output)
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Cleanup(IEnumerable<string> pathList)
|
||||||
|
{
|
||||||
|
foreach (var path in pathList)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue