diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index 8dbe641..fb170f1 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -1,478 +1,309 @@ -using FFMpegCore.FFMPEG.Argument; -using FFMpegCore.FFMPEG.Argument.Fluent; -using FFMpegCore.FFMPEG.Enums; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using System; -using System.Collections.Generic; +using FFMpegCore.Arguments; +using FFMpegCore.Enums; +using FFMpegCore.Exceptions; namespace FFMpegCore.Test { [TestClass] public class ArgumentBuilderTest : BaseTest { - private List concatFiles = new List - { "1.mp4", "2.mp4", "3.mp4", "4.mp4"}; + private readonly string[] _concatFiles = { "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] public void Builder_BuildString_IO_1() { - var str = GetArgumentsString(); - - Assert.AreEqual(str, "-i \"input.mp4\" \"output.mp4\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Scale() { - var str = GetArgumentsString(new ScaleArgument(VideoSize.Hd)); - - Assert.AreEqual(str, "-i \"input.mp4\" -vf scale=-1:720 \"output.mp4\""); + 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); } - - - [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] public void Builder_BuildString_AudioCodec() { - var str = GetArgumentsString(new AudioCodecArgument(AudioCodec.Aac)); - Assert.AreEqual(str, "-i \"input.mp4\" -c:a aac \"output.mp4\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithAudioCodec(AudioCodec.Aac).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -c:a aac \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_AudioBitrate() { - var str = GetArgumentsString(new AudioBitrateArgument(AudioQuality.Normal)); - Assert.AreEqual(str, "-i \"input.mp4\" -b:a 128k \"output.mp4\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithAudioBitrate(AudioQuality.Normal).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -b:a 128k \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Quiet() { - var str = GetArgumentsString(new QuietArgument()); - Assert.AreEqual(str, "-i \"input.mp4\" -hide_banner -loglevel warning \"output.mp4\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").Quiet().OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -hide_banner -loglevel warning \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_AudioCodec_Fluent() { - var str = GetArgumentsString(new ArgumentContainer().AudioCodec(AudioCodec.Aac).AudioBitrate(128)); - Assert.AreEqual(str, "-i \"input.mp4\" -c:a aac -b:a 128k \"output.mp4\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithAudioCodec(AudioCodec.Aac).WithAudioBitrate(128).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -c:a aac -b:a 128k \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_BitStream() { - var str = GetArgumentsString(new BitStreamFilterArgument(Channel.Audio, Filter.H264_Mp4ToAnnexB)); - - 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\""); + 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); } [TestMethod] public void Builder_BuildString_Concat() { - var container = new ArgumentContainer { new ConcatArgument(concatFiles), new OutputArgument("output.mp4") }; - - 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\""); + var str = FFMpegArguments.FromConcatenation(_concatFiles).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"concat:1.mp4|2.mp4|3.mp4|4.mp4\" \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Copy_Audio() { - var str = GetArgumentsString(new CopyArgument(Channel.Audio)); - - 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\""); + 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); } [TestMethod] public void Builder_BuildString_Copy_Video() { - var str = GetArgumentsString(new CopyArgument(Channel.Video)); - - Assert.AreEqual(str, "-i \"input.mp4\" -c:v copy \"output.mp4\""); + 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); } - [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] public void Builder_BuildString_Copy_Both() { - var str = GetArgumentsString(new CopyArgument(Channel.Both)); - - Assert.AreEqual(str, "-i \"input.mp4\" -c copy \"output.mp4\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").CopyChannel().OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -c copy \"output.mp4\"", str); } [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(() => 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] public void Builder_BuildString_CpuSpeed() { - var str = GetArgumentsString(new CpuSpeedArgument(10)); - - 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\""); + 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); } [TestMethod] public void Builder_BuildString_ForceFormat() { - var str = GetArgumentsString(new ForceFormatArgument(VideoCodec.LibX264)); - - 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\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").ForceFormat(VideoCodec.LibX264).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -f libx264 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_FrameOutputCount() { - var str = GetArgumentsString(new FrameOutputCountArgument(50)); - - 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\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithFrameOutputCount(50).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -vframes 50 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_FrameRate() { - var str = GetArgumentsString(new FrameRateArgument(50)); - - 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\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithFramerate(50).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -r 50 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Loop() { - var str = GetArgumentsString(new LoopArgument(50)); - - 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\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").Loop(50).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -loop 50 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Seek() { - var str = GetArgumentsString(new SeekArgument(TimeSpan.FromSeconds(10))); - - 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\""); + 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); } [TestMethod] public void Builder_BuildString_Shortest() { - var str = GetArgumentsString(new ShortestArgument(true)); - - 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\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").UsingShortest().OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -shortest \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Size() { - var str = GetArgumentsString(new SizeArgument(1920, 1080)); - - 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\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").Resize(1920, 1080).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -s 1920x1080 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Speed() { - var str = GetArgumentsString(new SpeedArgument(Speed.Fast)); - - 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\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithSpeedPreset(Speed.Fast).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -preset fast \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_DrawtextFilter() { - var str = GetArgumentsString(new DrawTextArgument("Stack Overflow", "/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"))); + var str = FFMpegArguments + .FromInputFiles(true, "input.mp4") + .DrawText(DrawTextOptions + .Create("Stack Overflow", "/path/to/font.ttf") + .WithParameter("fontcolor", "white") + .WithParameter("fontsize", "24") + .WithParameter("box", "1") + .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] - public void Builder_BuildString_DrawtextFilter_Fluent() + public void Builder_BuildString_DrawtextFilter_Alt() { - var container = new ArgumentContainer(). - DrawText((options) => - { - options.Text = "Stack Overflow"; - options.FontPath = "/path/to/font.ttf"; - 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); + var str = FFMpegArguments + .FromInputFiles(true, "input.mp4") + .DrawText(DrawTextOptions + .Create("Stack Overflow", "/path/to/font.ttf", ("fontcolor", "white"), ("fontsize", "24"))) + .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\" \"output.mp4\"", str); } - + [TestMethod] public void Builder_BuildString_StartNumber() { - var str = GetArgumentsString(new StartNumberArgument(50)); - - 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\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithStartNumber(50).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -start_number 50 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Threads_1() { - var str = GetArgumentsString(new ThreadsArgument(50)); - - 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\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").UsingThreads(50).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -threads 50 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Threads_2() { - var str = GetArgumentsString(new ThreadsArgument(true)); - - 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\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").UsingMultithreading(true).OutputToFile("output.mp4").Arguments; + Assert.AreEqual($"-i \"input.mp4\" -threads {Environment.ProcessorCount} \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Codec() { - var str = GetArgumentsString(new VideoCodecArgument(VideoCodec.LibX264)); - - 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\""); + 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); } [TestMethod] public void Builder_BuildString_Codec_Override() { - var str = GetArgumentsString(new VideoCodecArgument(VideoCodec.LibX264), new OverrideArgument()); - - 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\""); + 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); } [TestMethod] public void Builder_BuildString_Duration() { - var str = GetArgumentsString(new DurationArgument(TimeSpan.FromSeconds(20))); - - 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\""); + 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); } [TestMethod] public void Builder_BuildString_Raw() { - var str = GetArgumentsString(new CustomArgument(null)); - Assert.AreEqual(str, "-i \"input.mp4\" \"output.mp4\""); + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithCustomArgument(null).OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" \"output.mp4\"", str); - str = GetArgumentsString(new CustomArgument("-acodec copy")); - Assert.AreEqual(str, "-i \"input.mp4\" -acodec copy \"output.mp4\""); + str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithCustomArgument("-acodec copy").OutputToFile("output.mp4").Arguments; + Assert.AreEqual("-i \"input.mp4\" -acodec copy \"output.mp4\"", str); } } } \ No newline at end of file diff --git a/FFMpegCore.Test/AudioTest.cs b/FFMpegCore.Test/AudioTest.cs index 0594404..552ca24 100644 --- a/FFMpegCore.Test/AudioTest.cs +++ b/FFMpegCore.Test/AudioTest.cs @@ -1,4 +1,5 @@ -using FFMpegCore.Enums; +using System; +using FFMpegCore.Enums; using FFMpegCore.Test.Resources; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.IO; @@ -15,14 +16,12 @@ public void Audio_Remove() try { - Encoder.Mute(VideoInfo.FromFileInfo(Input), output); - - Assert.IsTrue(File.Exists(output.FullName)); + FFMpeg.Mute(Input.FullName, output); + Assert.IsTrue(File.Exists(output)); } finally { - if (File.Exists(output.FullName)) - output.Delete(); + if (File.Exists(output)) File.Delete(output); } } @@ -33,14 +32,12 @@ public void Audio_Save() try { - Encoder.ExtractAudio(VideoInfo.FromFileInfo(Input), output); - - Assert.IsTrue(File.Exists(output.FullName)); + FFMpeg.ExtractAudio(Input.FullName, output); + Assert.IsTrue(File.Exists(output)); } finally { - if (File.Exists(output.FullName)) - output.Delete(); + if (File.Exists(output)) File.Delete(output); } } @@ -50,16 +47,17 @@ public void Audio_Add() var output = Input.OutputLocation(VideoType.Mp4); try { - var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoNoAudio); - Encoder.ReplaceAudio(input, VideoLibrary.LocalAudio, output); - - Assert.AreEqual(input.Duration, VideoInfo.FromFileInfo(output).Duration); - Assert.IsTrue(File.Exists(output.FullName)); + var success = FFMpeg.ReplaceAudio(VideoLibrary.LocalVideoNoAudio.FullName, VideoLibrary.LocalAudio.FullName, output); + Assert.IsTrue(success); + var audioAnalysis = FFProbe.Analyse(VideoLibrary.LocalVideoNoAudio.FullName); + var videoAnalysis = FFProbe.Analyse(VideoLibrary.LocalAudio.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 { - if (File.Exists(output.FullName)) - output.Delete(); + if (File.Exists(output)) File.Delete(output); } } @@ -70,14 +68,14 @@ public void Image_AddAudio() try { - var result = Encoder.PosterWithAudio(new FileInfo(VideoLibrary.LocalCover.FullName), VideoLibrary.LocalAudio, output); - Assert.IsTrue(result.Duration.TotalSeconds > 0); - Assert.IsTrue(result.Exists); + FFMpeg.PosterWithAudio(VideoLibrary.LocalCover.FullName, VideoLibrary.LocalAudio.FullName, output); + var analysis = FFProbe.Analyse(VideoLibrary.LocalAudio.FullName); + Assert.IsTrue(analysis.Duration.TotalSeconds > 0); + Assert.IsTrue(File.Exists(output)); } finally { - if (File.Exists(output.FullName)) - output.Delete(); + if (File.Exists(output)) File.Delete(output); } } } diff --git a/FFMpegCore.Test/BaseTest.cs b/FFMpegCore.Test/BaseTest.cs index 4e0eab3..38a5bcc 100644 --- a/FFMpegCore.Test/BaseTest.cs +++ b/FFMpegCore.Test/BaseTest.cs @@ -1,17 +1,14 @@ -using FFMpegCore.FFMPEG; -using FFMpegCore.Test.Resources; +using FFMpegCore.Test.Resources; using System.IO; namespace FFMpegCore.Test { public class BaseTest { - protected FFMpeg Encoder; protected FileInfo Input; public BaseTest() { - Encoder = new FFMpeg(); Input = VideoLibrary.LocalVideo; } } diff --git a/FFMpegCore.Test/BitmapSources.cs b/FFMpegCore.Test/BitmapSources.cs index 33c8035..2a81d57 100644 --- a/FFMpegCore.Test/BitmapSources.cs +++ b/FFMpegCore.Test/BitmapSources.cs @@ -1,11 +1,10 @@ using FFMpegCore.Extend; -using FFMpegCore.FFMPEG.Pipes; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Numerics; -using System.Text; +using FFMpegCore.Pipes; namespace FFMpegCore.Test { diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj index 3f264c7..1ca7207 100644 --- a/FFMpegCore.Test/FFMpegCore.Test.csproj +++ b/FFMpegCore.Test/FFMpegCore.Test.csproj @@ -4,6 +4,10 @@ netcoreapp3.1 false + + disable + + default diff --git a/FFMpegCore.Test/FFMpegOptions.cs b/FFMpegCore.Test/FFMpegOptions.cs index efdd6e3..b7359a4 100644 --- a/FFMpegCore.Test/FFMpegOptions.cs +++ b/FFMpegCore.Test/FFMpegOptions.cs @@ -1,5 +1,4 @@ -using FFMpegCore.FFMPEG; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json; using System.IO; diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index bddef38..558f29c 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -1,10 +1,7 @@ using System.IO; -using FFMpegCore.Enums; -using FFMpegCore.FFMPEG; -using FFMpegCore.FFMPEG.Argument; +using System.Threading.Tasks; using FFMpegCore.Test.Resources; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json; namespace FFMpegCore.Test { @@ -14,47 +11,59 @@ public class FFProbeTests [TestMethod] public void Probe_TooLongOutput() { - var output = new FFProbe(5); - - Assert.ThrowsException(() => - { - output.ParseVideoInfo(VideoLibrary.LocalVideo.FullName); - }); + Assert.ThrowsException(() => FFProbe.Analyse(VideoLibrary.LocalVideo.FullName, 5)); } [TestMethod] public void Probe_Success() { - var output = new FFProbe(); - - var info = output.ParseVideoInfo(VideoLibrary.LocalVideo.FullName); + 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); + 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); } [TestMethod, Timeout(10000)] public void Probe_Success_FromStream() { - var output = new FFProbe(); - - using (var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName)) - { - var info = output.ParseVideoInfo(stream); - Assert.AreEqual(10, info.Duration.Seconds); - } + using var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName); + var info = FFProbe.Analyse(stream); + Assert.AreEqual(10, info.Duration.Seconds); } - [TestMethod, Timeout(10000)] - public void Probe_Success_FromStream_Async() + [TestMethod] + public async Task Probe_Success_FromStream_Async() { - var output = new FFProbe(); - - using (var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName)) - { - var info = output.ParseVideoInfoAsync(stream).WaitForResult(); - - Assert.AreEqual(10, info.Duration.Seconds); - } + await using var stream = File.OpenRead(VideoLibrary.LocalVideoWebm.FullName); + var info = await FFProbe.AnalyseAsync(stream); + Assert.AreEqual(10, info.Duration.Seconds); } } } \ No newline at end of file diff --git a/FFMpegCore.Test/Resources/VideoLibrary.cs b/FFMpegCore.Test/Resources/VideoLibrary.cs index f630273..26ad6aa 100644 --- a/FFMpegCore.Test/Resources/VideoLibrary.cs +++ b/FFMpegCore.Test/Resources/VideoLibrary.cs @@ -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 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"); } - public static FileInfo OutputLocation(this FileInfo file, AudioType type) + public static string OutputLocation(this FileInfo file, AudioType type) { 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"); } - 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, outputFile = file.Name.Replace(file.Extension, keyword + "." + type.ToString().ToLower()); - return new FileInfo($"{originalLocation}{Path.DirectorySeparatorChar}{outputFile}"); + return $"{originalLocation}{Path.DirectorySeparatorChar}{outputFile}"; } } } diff --git a/FFMpegCore.Test/TasksExtensions.cs b/FFMpegCore.Test/TasksExtensions.cs index 67163a7..c9549ca 100644 --- a/FFMpegCore.Test/TasksExtensions.cs +++ b/FFMpegCore.Test/TasksExtensions.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; namespace FFMpegCore.Test { diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 1c9085d..8198bf3 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -1,8 +1,4 @@ using FFMpegCore.Enums; -using FFMpegCore.FFMPEG.Argument; -using FFMpegCore.FFMPEG.Enums; -using FFMpegCore.FFMPEG.Exceptions; -using FFMpegCore.FFMPEG.Pipes; using FFMpegCore.Test.Resources; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -10,6 +6,10 @@ using System.Drawing.Imaging; using System.IO; using System.Linq; +using System.Threading.Tasks; +using FFMpegCore.Arguments; +using FFMpegCore.Exceptions; +using FFMpegCore.Pipes; namespace FFMpegCore.Test { @@ -22,257 +22,243 @@ public bool Convert(VideoType type, bool multithreaded = false, VideoSize size = 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); - - var outputVideo = new VideoInfo(output.FullName); - - Assert.IsTrue(File.Exists(output.FullName)); - Assert.AreEqual(outputVideo.Duration, input.Duration); + Assert.IsTrue(File.Exists(output)); + Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1); if (size == VideoSize.Original) { - Assert.AreEqual(outputVideo.Width, input.Width); - Assert.AreEqual(outputVideo.Height, input.Height); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height); } else { - Assert.AreNotEqual(outputVideo.Width, input.Width); - Assert.AreNotEqual(outputVideo.Height, input.Height); - Assert.AreEqual(outputVideo.Height, (int)size); + Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); + Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, (int)size); } - return File.Exists(output.FullName) && + return File.Exists(output) && outputVideo.Duration == input.Duration && ( ( size == VideoSize.Original && - outputVideo.Width == input.Width && - outputVideo.Height == input.Height + outputVideo.PrimaryVideoStream.Width == input.PrimaryVideoStream.Width && + outputVideo.PrimaryVideoStream.Height == input.PrimaryVideoStream.Height ) || ( size != VideoSize.Original && - outputVideo.Width != input.Width && - outputVideo.Height != input.Height && - outputVideo.Height == (int)size + outputVideo.PrimaryVideoStream.Width != input.PrimaryVideoStream.Width && + outputVideo.PrimaryVideoStream.Height != input.PrimaryVideoStream.Height && + outputVideo.PrimaryVideoStream.Height == (int)size ) ); } finally { - if (File.Exists(output.FullName)) - File.Delete(output.FullName); + if (File.Exists(output)) + File.Delete(output); } } - private void ConvertFromStreamPipe(VideoType type, ArgumentContainer container) + private void ConvertFromStreamPipe(VideoType type, params IArgument[] inputArguments) { var output = Input.OutputLocation(type); try { - var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm); - using (var inputStream = System.IO.File.OpenRead(input.FullName)) + var input = FFProbe.Analyse(VideoLibrary.LocalVideoWebm.FullName); + using (var inputStream = System.IO.File.OpenRead(input.Path)) { var pipeSource = new StreamPipeDataWriter(inputStream); - var arguments = new ArgumentContainer { new InputPipeArgument(pipeSource) }; - foreach (var arg in container) + var arguments = FFMpegArguments.FromPipe(pipeSource); + foreach (var arg in inputArguments) + arguments.WithArgument(arg); + var processor = arguments.OutputToFile(output); + + var scaling = arguments.Find(); + + 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); - } - arguments.Add(new OutputArgument(output)); - - var scaling = container.Find(); - - 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); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height); } 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.Height, input.Height); + Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); + Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height); } } } finally { - if (File.Exists(output.FullName)) - File.Delete(output.FullName); + if (File.Exists(output)) + 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); + foreach (var arg in inputArguments) + arguments.WithArgument(arg); + + var streamPipeDataReader = new StreamPipeDataReader(ms); + var processor = arguments.OutputToPipe(streamPipeDataReader); + + var scaling = arguments.Find(); + + processor.ProcessSynchronously(); + + ms.Position = 0; + var outputVideo = FFProbe.Analyse(ms); + + var input = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName); + // Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate); + + if (scaling?.Size == null) { - var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideo); - var arguments = new ArgumentContainer { new InputArgument(input) }; - - foreach (var arg in container) + Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height); + } + else + { + if (scaling.Size.Value.Width != -1) { - arguments.Add(arg.Value); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width); } - var streamPipeDataReader = new StreamPipeDataReader(ms); - streamPipeDataReader.BlockSize = streamPipeDataReader.BlockSize * 16; - arguments.Add(new OutputPipeArgument(streamPipeDataReader)); - - var scaling = container.Find(); - - Encoder.Convert(arguments); - - ms.Position = 0; - var outputVideo = VideoInfo.FromStream(ms); - - //Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.FrameRate); - - if (scaling == null) + if (scaling.Size.Value.Height != -1) { - Assert.AreEqual(outputVideo.Width, input.Width); - Assert.AreEqual(outputVideo.Height, input.Height); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height); } - else - { - if (scaling.Value.Width != -1) - { - Assert.AreEqual(outputVideo.Width, scaling.Value.Width); - } - if (scaling.Value.Height != -1) - { - Assert.AreEqual(outputVideo.Height, scaling.Value.Height); - } - - Assert.AreNotEqual(outputVideo.Width, input.Width); - Assert.AreNotEqual(outputVideo.Height, input.Height); - } + Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); + 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); try { - var input = VideoInfo.FromFileInfo(Input); + var input = FFProbe.Analyse(Input.FullName); - var arguments = new ArgumentContainer { new InputArgument(input) }; - foreach (var arg in container) + var arguments = FFMpegArguments.FromInputFiles(VideoLibrary.LocalVideo.FullName); + foreach (var arg in inputArguments) + arguments.WithArgument(arg); + + var processor = arguments.OutputToFile(output); + + var scaling = arguments.Find(); + 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); - } - arguments.Add(new OutputArgument(output)); - - var scaling = container.Find(); - - 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); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height); } 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.Height, input.Height); + Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); + Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height); } } finally { - if (File.Exists(output.FullName)) - File.Delete(output.FullName); + if (File.Exists(output)) + 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, container, PixelFormat.Format32bppArgb); - ConvertFromPipe(type, container, PixelFormat.Format48bppRgb); + ConvertFromPipe(type, PixelFormat.Format24bppRgb, inputArguments); + ConvertFromPipe(type, PixelFormat.Format32bppArgb, inputArguments); + 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); try { var videoFramesSource = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, fmt, 256, 256)); - var arguments = new ArgumentContainer { new InputPipeArgument(videoFramesSource) }; - foreach (var arg in container) + var arguments = FFMpegArguments.FromPipe(videoFramesSource); + foreach (var arg in inputArguments) + arguments.WithArgument(arg); + var processor = arguments.OutputToFile(output); + + var scaling = arguments.Find(); + processor.ProcessSynchronously(); + + var outputVideo = FFProbe.Analyse(output); + + Assert.IsTrue(File.Exists(output)); + + if (scaling?.Size == null) { - arguments.Add(arg.Value); - } - arguments.Add(new OutputArgument(output)); - - var scaling = container.Find(); - - 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); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, videoFramesSource.Width); + Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, videoFramesSource.Height); } 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.Height, videoFramesSource.Height); + Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, videoFramesSource.Width); + Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, videoFramesSource.Height); } } finally { - if (File.Exists(output.FullName)) - File.Delete(output.FullName); + if (File.Exists(output)) + File.Delete(output); } } @@ -286,22 +272,19 @@ public void Video_ToMP4() [TestMethod] public void Video_ToMP4_Args() { - var container = new ArgumentContainer { new VideoCodecArgument(VideoCodec.LibX264) }; - Convert(VideoType.Mp4, container); + Convert(VideoType.Mp4, new VideoCodecArgument(VideoCodec.LibX264)); } [TestMethod] public void Video_ToMP4_Args_Pipe() { - var container = new ArgumentContainer { new VideoCodecArgument(VideoCodec.LibX264) }; - ConvertFromPipe(VideoType.Mp4, container); + ConvertFromPipe(VideoType.Mp4, new VideoCodecArgument(VideoCodec.LibX264)); } [TestMethod] public void Video_ToMP4_Args_StreamPipe() { - var container = new ArgumentContainer { new VideoCodecArgument(VideoCodec.LibX264) }; - ConvertFromStreamPipe(VideoType.Mp4, container); + ConvertFromStreamPipe(VideoType.Mp4, new VideoCodecArgument(VideoCodec.LibX264)); } [TestMethod, Timeout(45000)] @@ -309,20 +292,14 @@ public void Video_ToMP4_Args_StreamOutputPipe_Async_Failure() { Assert.ThrowsException(() => { - using (var ms = new MemoryStream()) - { - var pipeSource = new StreamPipeDataReader(ms); - var container = new ArgumentContainer - { - new InputArgument(VideoLibrary.LocalVideoWebm), - new VideoCodecArgument(VideoCodec.LibX264), - new ForceFormatArgument("mkv"), - new OutputPipeArgument(pipeSource) - }; - - var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm); - Encoder.ConvertAsync(container).WaitForResult(); - } + using var ms = new MemoryStream(); + var pipeSource = new StreamPipeDataReader(ms); + FFMpegArguments + .FromInputFiles(VideoLibrary.LocalVideo) + .ForceFormat("mkv") + .OutputToPipe(pipeSource) + .ProcessAsynchronously() + .WaitForResult(); }); } @@ -331,11 +308,7 @@ public void Video_ToMP4_Args_StreamOutputPipe_Failure() { Assert.ThrowsException(() => { - var container = new ArgumentContainer - { - new ForceFormatArgument("mkv") - }; - ConvertToStreamPipe(VideoType.Mp4, container); + ConvertToStreamPipe(new ForceFormatArgument("mkv")); }); } @@ -343,31 +316,21 @@ public void Video_ToMP4_Args_StreamOutputPipe_Failure() [TestMethod] public void Video_ToMP4_Args_StreamOutputPipe_Async() { - using (var ms = new MemoryStream()) - { - var pipeSource = new StreamPipeDataReader(ms); - var container = new ArgumentContainer - { - new InputArgument(VideoLibrary.LocalVideo), - new VideoCodecArgument(VideoCodec.LibX264), - new ForceFormatArgument("matroska"), - new OutputPipeArgument(pipeSource) - }; - - var input = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoWebm); - Encoder.ConvertAsync(container).WaitForResult(); - } + using var ms = new MemoryStream(); + var pipeSource = new StreamPipeDataReader(ms); + FFMpegArguments + .FromInputFiles(VideoLibrary.LocalVideo) + .WithVideoCodec(VideoCodec.LibX264) + .ForceFormat("matroska") + .OutputToPipe(pipeSource) + .ProcessAsynchronously() + .WaitForResult(); } [TestMethod] public void Video_ToMP4_Args_StreamOutputPipe() { - var container = new ArgumentContainer - { - new VideoCodecArgument(VideoCodec.LibX264), - new ForceFormatArgument("matroska") - }; - ConvertToStreamPipe(VideoType.Mp4, container); + ConvertToStreamPipe(new VideoCodecArgument(VideoCodec.LibX264), new ForceFormatArgument("matroska")); } [TestMethod] @@ -379,23 +342,16 @@ public void Video_ToTS() [TestMethod] public void Video_ToTS_Args() { - var container = new ArgumentContainer - { + Convert(VideoType.Ts, new CopyArgument(), new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB), - new ForceFormatArgument(VideoCodec.MpegTs) - }; - Convert(VideoType.Ts, container); + new ForceFormatArgument(VideoCodec.MpegTs)); } [TestMethod] public void Video_ToTS_Args_Pipe() { - var container = new ArgumentContainer - { - new ForceFormatArgument(VideoCodec.MpegTs) - }; - ConvertFromPipe(VideoType.Ts, container); + ConvertFromPipe(VideoType.Ts, new ForceFormatArgument(VideoCodec.MpegTs)); } [TestMethod] @@ -407,23 +363,13 @@ public void Video_ToOGV_Resize() [TestMethod] public void Video_ToOGV_Resize_Args() { - var container = new ArgumentContainer - { - new ScaleArgument(VideoSize.Ed), - new VideoCodecArgument(VideoCodec.LibTheora) - }; - Convert(VideoType.Ogv, container); + Convert(VideoType.Ogv, new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora)); } [TestMethod] public void Video_ToOGV_Resize_Args_Pipe() { - var container = new ArgumentContainer - { - new ScaleArgument(VideoSize.Ed), - new VideoCodecArgument(VideoCodec.LibTheora) - }; - ConvertFromPipe(VideoType.Ogv, container); + ConvertFromPipe(VideoType.Ogv, new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora)); } [TestMethod] @@ -435,23 +381,13 @@ public void Video_ToMP4_Resize() [TestMethod] public void Video_ToMP4_Resize_Args() { - var container = new ArgumentContainer - { - new ScaleArgument(VideoSize.Ld), - new VideoCodecArgument(VideoCodec.LibX264) - }; - Convert(VideoType.Mp4, container); + Convert(VideoType.Mp4, new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264)); } [TestMethod] public void Video_ToMP4_Resize_Args_Pipe() { - var container = new ArgumentContainer - { - new ScaleArgument(VideoSize.Ld), - new VideoCodecArgument(VideoCodec.LibX264) - }; - ConvertFromPipe(VideoType.Mp4, container); + ConvertFromPipe(VideoType.Mp4, new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264)); } [TestMethod] @@ -485,17 +421,17 @@ public void Video_Snapshot() try { - var input = VideoInfo.FromFileInfo(Input); + var input = FFProbe.Analyse(Input.FullName); - using var bitmap = Encoder.Snapshot(input, output); - Assert.AreEqual(input.Width, bitmap.Width); - Assert.AreEqual(input.Height, bitmap.Height); + using var bitmap = FFMpeg.Snapshot(input, output); + Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width); + Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height); Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png); } finally { - if (File.Exists(output.FullName)) - File.Delete(output.FullName); + if (File.Exists(output)) + File.Delete(output); } } @@ -505,18 +441,18 @@ public void Video_Snapshot_PersistSnapshot() var output = Input.OutputLocation(ImageType.Png); try { - var input = VideoInfo.FromFileInfo(Input); + var input = FFProbe.Analyse(Input.FullName); - using var bitmap = Encoder.Snapshot(input, output, persistSnapshotOnFileSystem: true); - Assert.AreEqual(input.Width, bitmap.Width); - Assert.AreEqual(input.Height, bitmap.Height); + using var bitmap = FFMpeg.Snapshot(input, output, persistSnapshotOnFileSystem: true); + Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width); + Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height); Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png); - Assert.IsTrue(File.Exists(output.FullName)); + Assert.IsTrue(File.Exists(output)); } finally { - if (File.Exists(output.FullName)) - File.Delete(output.FullName); + if (File.Exists(output)) + File.Delete(output); } } @@ -527,28 +463,30 @@ public void Video_Join() var newInput = Input.OutputLocation(VideoType.Mp4, "duplicate"); try { - var input = VideoInfo.FromFileInfo(Input); - File.Copy(input.FullName, newInput.FullName); - var input2 = VideoInfo.FromFileInfo(newInput); + var input = FFProbe.Analyse(Input.FullName); + File.Copy(input.Path, newInput); + var input2 = FFProbe.Analyse(newInput); - var result = Encoder.Join(output, input, input2); - - Assert.IsTrue(File.Exists(output.FullName)); + var success = FFMpeg.Join(output, input, input2); + Assert.IsTrue(success); + + Assert.IsTrue(File.Exists(output)); var expectedDuration = input.Duration * 2; + var result = FFProbe.Analyse(output); Assert.AreEqual(expectedDuration.Days, result.Duration.Days); Assert.AreEqual(expectedDuration.Hours, result.Duration.Hours); Assert.AreEqual(expectedDuration.Minutes, result.Duration.Minutes); Assert.AreEqual(expectedDuration.Seconds, result.Duration.Seconds); - Assert.AreEqual(input.Height, result.Height); - Assert.AreEqual(input.Width, result.Width); + Assert.AreEqual(input.PrimaryVideoStream.Height, result.PrimaryVideoStream.Height); + Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width); } finally { - if (File.Exists(output.FullName)) - File.Delete(output.FullName); + if (File.Exists(output)) + File.Delete(output); - if (File.Exists(newInput.FullName)) - File.Delete(newInput.FullName); + if (File.Exists(newInput)) + 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(); Assert.IsTrue(VideoLibrary.ImageJoinOutput.Exists); Assert.AreEqual(3, result.Duration.Seconds); - Assert.AreEqual(imageSet.First().Width, result.Width); - Assert.AreEqual(imageSet.First().Height, result.Height); + Assert.AreEqual(imageSet.First().Width, result.PrimaryVideoStream.Width); + Assert.AreEqual(imageSet.First().Height, result.PrimaryVideoStream.Height); } finally { @@ -591,32 +530,29 @@ public void Video_Join_Image_Sequence() [TestMethod] public void Video_With_Only_Audio_Should_Extract_Metadata() { - var video = VideoInfo.FromFileInfo(VideoLibrary.LocalVideoAudioOnly); - Assert.AreEqual("none", video.VideoFormat); - Assert.AreEqual("aac", video.AudioFormat); + var video = FFProbe.Analyse(VideoLibrary.LocalVideoAudioOnly.FullName); + Assert.AreEqual(null, video.PrimaryVideoStream); + Assert.AreEqual("aac", video.PrimaryAudioStream.CodecName); Assert.AreEqual(79.5, video.Duration.TotalSeconds, 0.5); - Assert.AreEqual(1.25, video.Size); + // Assert.AreEqual(1.25, video.Size); } [TestMethod] public void Video_Duration() { - var video = VideoInfo.FromFileInfo(VideoLibrary.LocalVideo); + var video = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName); 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 { - Encoder.Convert(arguments); + FFMpegArguments + .FromInputFiles(VideoLibrary.LocalVideo) + .WithDuration(TimeSpan.FromSeconds(video.Duration.TotalSeconds - 5)) + .OutputToFile(output) + .ProcessSynchronously(); - Assert.IsTrue(File.Exists(output.FullName)); - var outputVideo = new VideoInfo(output.FullName); + Assert.IsTrue(File.Exists(output)); + var outputVideo = FFProbe.Analyse(output); Assert.AreEqual(video.Duration.Days, outputVideo.Duration.Days); Assert.AreEqual(video.Duration.Hours, outputVideo.Duration.Hours); @@ -625,8 +561,8 @@ public void Video_Duration() } finally { - if (File.Exists(output.FullName)) - output.Delete(); + if (File.Exists(output)) + File.Delete(output); } } @@ -636,54 +572,53 @@ public void Video_UpdatesProgress() var output = Input.OutputLocation(VideoType.Mp4); var percentageDone = 0.0; - void OnProgess(double percentage) => percentageDone = percentage; - Encoder.OnProgress += OnProgess; + var timeDone = TimeSpan.Zero; + void OnPercentageProgess(double percentage) => percentageDone = percentage; + void OnTimeProgess(TimeSpan time) => timeDone = time; - var arguments = new ArgumentContainer - { - new InputArgument(VideoLibrary.LocalVideo), - new DurationArgument(TimeSpan.FromSeconds(8)), - new OutputArgument(output) - }; + var analysis = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName); + try { - Encoder.Convert(arguments); - Encoder.OnProgress -= OnProgess; + var success = FFMpegArguments + .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(TimeSpan.Zero, timeDone); } finally { - if (File.Exists(output.FullName)) - output.Delete(); + if (File.Exists(output)) + File.Delete(output); } } [TestMethod] public void Video_TranscodeInMemory() { - using (var resStream = new MemoryStream()) - { - var reader = new StreamPipeDataReader(resStream); - var writer = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, PixelFormat.Format24bppRgb, 128, 128)); + using var resStream = new MemoryStream(); + var reader = new StreamPipeDataReader(resStream); + var writer = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, PixelFormat.Format24bppRgb, 128, 128)); - var container = new ArgumentContainer - { - new InputPipeArgument(writer), - new VideoCodecArgument("vp9"), - new ForceFormatArgument("webm"), - new OutputPipeArgument(reader) - }; + FFMpegArguments + .FromPipe(writer) + .WithVideoCodec("vp9") + .ForceFormat("webm") + .OutputToPipe(reader) + .ProcessSynchronously(); - Encoder.Convert(container); - - resStream.Position = 0; - var vi = VideoInfo.FromStream(resStream); - Assert.AreEqual(vi.Width, 128); - Assert.AreEqual(vi.Height, 128); - } + resStream.Position = 0; + var vi = FFProbe.Analyse(resStream); + Assert.AreEqual(vi.PrimaryVideoStream.Width, 128); + Assert.AreEqual(vi.PrimaryVideoStream.Height, 128); } } } diff --git a/FFMpegCore.Test/ffmpeg.config.json b/FFMpegCore.Test/ffmpeg.config.json index d80b26e..b9c9a56 100644 --- a/FFMpegCore.Test/ffmpeg.config.json +++ b/FFMpegCore.Test/ffmpeg.config.json @@ -1,3 +1,3 @@ { - "RootDirectory": "/usr/bin" + "RootDirectory": "" } \ No newline at end of file diff --git a/FFMpegCore/Enums/FileExtension.cs b/FFMpegCore/Enums/FileExtension.cs index e92fbe9..8661c7d 100644 --- a/FFMpegCore/Enums/FileExtension.cs +++ b/FFMpegCore/Enums/FileExtension.cs @@ -1,11 +1,10 @@ -using FFMpegCore.FFMPEG.Enums; -using System; +using System; namespace FFMpegCore.Enums { public static class FileExtension { - public static string ForType(VideoType type) + public static string Extension(this VideoType type) { 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.") }; } - public static string ForCodec(VideoCodec type) + public static string Extension(this VideoCodec type) { return type switch { diff --git a/FFMpegCore/Extend/BitmapExtensions.cs b/FFMpegCore/Extend/BitmapExtensions.cs index b784be6..bf10336 100644 --- a/FFMpegCore/Extend/BitmapExtensions.cs +++ b/FFMpegCore/Extend/BitmapExtensions.cs @@ -1,26 +1,22 @@ using System; using System.Drawing; using System.IO; -using FFMpegCore.FFMPEG; namespace FFMpegCore.Extend { 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"; - poster.Save(destination); - - var tempFile = new FileInfo(destination); try { - return new FFMpeg().PosterWithAudio(tempFile, audio, output); + return FFMpeg.PosterWithAudio(destination, audio, output); } finally { - tempFile.Delete(); + if (File.Exists(destination)) File.Delete(destination); } } } diff --git a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs index bcfdab7..c8645ab 100644 --- a/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs +++ b/FFMpegCore/Extend/BitmapVideoFrameWrapper.cs @@ -1,11 +1,9 @@ -using FFMpegCore.FFMPEG.Pipes; -using System; -using System.Collections.Generic; +using System; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; -using System.Text; using System.Threading.Tasks; +using FFMpegCore.Pipes; namespace FFMpegCore.Extend { diff --git a/FFMpegCore/Extend/UriExtensions.cs b/FFMpegCore/Extend/UriExtensions.cs index 0d7629e..ebe92c0 100644 --- a/FFMpegCore/Extend/UriExtensions.cs +++ b/FFMpegCore/Extend/UriExtensions.cs @@ -1,14 +1,12 @@ using System; -using System.IO; -using FFMpegCore.FFMPEG; namespace FFMpegCore.Extend { 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); } } } \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/Argument/Argument.cs b/FFMpegCore/FFMPEG/Argument/Argument.cs deleted file mode 100644 index 353aaeb..0000000 --- a/FFMpegCore/FFMPEG/Argument/Argument.cs +++ /dev/null @@ -1,61 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Abstract class implements basic functionality of ffmpeg arguments - /// - public abstract class Argument - { - /// - /// String representation of the argument - /// - /// String representation of the argument - public abstract string GetStringValue(); - - public override string ToString() - { - return GetStringValue(); - } - } - - /// - /// Abstract class implements basic functionality of ffmpeg arguments with one value property - /// - public abstract class Argument : Argument - { - /// - /// Value type of - /// - public T Value { get; protected set; } - - public Argument() { } - - public Argument(T value) - { - Value = value; - } - } - - /// - /// Abstract class implements basic functionality of ffmpeg arguments with two values properties - /// - public abstract class Argument : Argument - { - /// - /// First value type of - /// - public T1 First { get; } - - /// - /// Second value type of - /// - public T2 Second { get; } - - public Argument() { } - - public Argument(T1 first, T2 second) - { - First = first; - Second = second; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs b/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs deleted file mode 100644 index 829b511..0000000 --- a/FFMpegCore/FFMPEG/Argument/ArgumentContainer.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Container of arguments represented parameters of FFMPEG process - /// - public class ArgumentContainer : IDictionary - { - IDictionary _args; - - public ArgumentContainer(params Argument[] arguments) - { - _args = new Dictionary(); - - foreach (var argument in arguments) - { - Add(argument); - } - } - - public Argument this[Type key] { get => _args[key]; set => _args[key] = value; } - - public bool TryGetArgument(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 Keys => _args.Keys; - - public ICollection Values => _args.Values; - - public int Count => _args.Count; - - public bool IsReadOnly => _args.IsReadOnly; - - /// - /// This method is not supported, left for interface support - /// - /// - /// - [Obsolete] - public void Add(Type key, Argument value) - { - throw new InvalidOperationException("Not supported operation"); - } - - /// - /// This method is not supported, left for interface support - /// - /// - /// - [Obsolete] - public void Add(KeyValuePair item) - { - Add(item.Value); - } - - /// - /// Clears collection of arguments - /// - public void Clear() - { - _args.Clear(); - } - - /// - /// Returns if contains item - /// - /// Searching item - /// Returns if contains item - public bool Contains(KeyValuePair item) - { - return _args.Contains(item); - } - - /// - /// Adds argument to collection - /// - /// Argument that should be added to collection - public void Add(params Argument[] values) - { - foreach (var value in values) - { - _args.Add(value.GetType(), value); - } - } - - /// - /// Checks if container contains output and input parameters - /// - /// - public bool ContainsInputOutput() - { - return ContainsOnlyOneOf(typeof(InputArgument), typeof(ConcatArgument), typeof(InputPipeArgument)) && - ContainsOnlyOneOf(typeof(OutputArgument), typeof(OutputPipeArgument)); - } - - /// - /// Checks if contains argument of type - /// - /// Type of argument is seraching - /// - 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[] array, int arrayIndex) - { - _args.CopyTo(array, arrayIndex); - } - - public IEnumerator> GetEnumerator() - { - return _args.GetEnumerator(); - } - - public bool Remove(Type key) - { - return _args.Remove(key); - } - - public bool Remove(KeyValuePair 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(); - } - - /// - /// Shortcut for finding arguments inside collection - /// - /// Type of argument - /// - public T Find() where T : Argument - { - if (ContainsKey(typeof(T))) - return (T)_args[typeof(T)]; - return null; - } - /// - /// Shortcut for checking if contains arguments inside collection - /// - /// Type of argument - /// - public bool Contains() where T : Argument - { - if (ContainsKey(typeof(T))) - return true; - return false; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/ArgumentContainerFluentExtensions.cs b/FFMpegCore/FFMPEG/Argument/ArgumentContainerFluentExtensions.cs deleted file mode 100644 index 8643804..0000000 --- a/FFMpegCore/FFMPEG/Argument/ArgumentContainerFluentExtensions.cs +++ /dev/null @@ -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 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 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 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 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 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 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; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/AudioBitrateArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/AudioBitrateArgument.cs deleted file mode 100644 index 7ecde09..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/AudioBitrateArgument.cs +++ /dev/null @@ -1,19 +0,0 @@ -using FFMpegCore.FFMPEG.Enums; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents parameter of audio codec and it's quality - /// - public class AudioBitrateArgument : Argument - { - public AudioBitrateArgument(AudioQuality value) : base((int)value) { } - public AudioBitrateArgument(int bitrate) : base(bitrate) { } - - /// - public override string GetStringValue() - { - return $"-b:a {Value}k"; - } - } -} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/AudioCodecArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/AudioCodecArgument.cs deleted file mode 100644 index 9c75386..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/AudioCodecArgument.cs +++ /dev/null @@ -1,18 +0,0 @@ -using FFMpegCore.FFMPEG.Enums; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents parameter of audio codec and it's quality - /// - public class AudioCodecArgument : Argument - { - public AudioCodecArgument(AudioCodec value) : base(value) { } - - /// - public override string GetStringValue() - { - return $"-c:a {Value.ToString().ToLower()}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/AudioSamplingRateArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/AudioSamplingRateArgument.cs deleted file mode 100644 index ee612b8..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/AudioSamplingRateArgument.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Audio sampling rate argument. Defaults to 48000 (Hz) - /// - public class AudioSamplingRateArgument : Argument - { - public AudioSamplingRateArgument() : base(48000) { } - - public AudioSamplingRateArgument(int samplingRate) : base(samplingRate) { } - - /// - public override string GetStringValue() - { - return $"-ar {Value}"; - } - } -} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/BitStreamFilterArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/BitStreamFilterArgument.cs deleted file mode 100644 index 6b6d484..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/BitStreamFilterArgument.cs +++ /dev/null @@ -1,25 +0,0 @@ -using FFMpegCore.FFMPEG.Enums; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents parameter of bitstream filter - /// - public class BitStreamFilterArgument : Argument - { - public BitStreamFilterArgument() { } - - public BitStreamFilterArgument(Channel first, Filter second) : base(first, second) { } - - /// - public override string GetStringValue() - { - return First switch - { - Channel.Audio => $"-bsf:a {Second.ToString().ToLower()}", - Channel.Video => $"-bsf:v {Second.ToString().ToLower()}", - _ => string.Empty - }; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/ConcatArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/ConcatArgument.cs deleted file mode 100644 index 280153e..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/ConcatArgument.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace FFMpegCore.FFMPEG.Argument -{ - - /// - /// Represents parameter of concat argument - /// Used for creating video from multiple images or videos - /// - public class ConcatArgument : Argument>, IEnumerable - { - public ConcatArgument() : base(new List()) { } - - public ConcatArgument(IEnumerable value) : base(value) { } - - public IEnumerator GetEnumerator() - { - return Value.GetEnumerator(); - } - - /// - 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(); - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/CopyArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/CopyArgument.cs deleted file mode 100644 index dc72397..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/CopyArgument.cs +++ /dev/null @@ -1,24 +0,0 @@ -using FFMpegCore.FFMPEG.Enums; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents parameter of copy parameter - /// Defines if channel (audio, video or both) should be copied to output file - /// - public class CopyArgument : Argument - { - public CopyArgument(Channel value = Channel.Both) : base(value) { } - - /// - public override string GetStringValue() - { - return Value switch - { - Channel.Audio => "-c:a copy", - Channel.Video => "-c:v copy", - _ => "-c copy" - }; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/CpuSpeedArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/CpuSpeedArgument.cs deleted file mode 100644 index 576bab2..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/CpuSpeedArgument.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents cpu speed parameter - /// - public class CpuSpeedArgument : Argument - { - public CpuSpeedArgument() { } - - public CpuSpeedArgument(int value) : base(value) { } - - /// - public override string GetStringValue() - { - return $"-quality good -cpu-used {Value} -deadline realtime"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/CustomArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/CustomArgument.cs deleted file mode 100644 index 6a38b4e..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/CustomArgument.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - public class CustomArgument : Argument - { - public CustomArgument(string argument) : base(argument) - { - } - - public override string GetStringValue() - { - return Value ?? string.Empty; - } - } -} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/DisableChannelArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/DisableChannelArgument.cs deleted file mode 100644 index 7aaa730..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/DisableChannelArgument.cs +++ /dev/null @@ -1,25 +0,0 @@ -using FFMpegCore.FFMPEG.Enums; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents cpu speed parameter - /// - public class DisableChannelArgument : Argument - { - public DisableChannelArgument() { } - - public DisableChannelArgument(Channel value) : base(value) { } - - /// - public override string GetStringValue() - { - return Value switch - { - Channel.Video => "-vn", - Channel.Audio => "-an", - _ => string.Empty - }; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/DrawTextArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/DrawTextArgument.cs deleted file mode 100644 index a3781e2..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/DrawTextArgument.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Drawtext video filter argument - /// - public class DrawTextArgument : Argument> - { - public DrawTextArgument(string text, string fontPath, params (string, string)[] optionalArguments) - : base(new[] {("text", text), ("fontfile", fontPath)}.Concat(optionalArguments)) { } - - /// - 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; - } - } -} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/DurationArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/DurationArgument.cs deleted file mode 100644 index 6576afa..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/DurationArgument.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents duration parameter - /// - public class DurationArgument : Argument - { - public DurationArgument() { } - - public DurationArgument(TimeSpan? value) : base(value) { } - - /// - public override string GetStringValue() - { - return !Value.HasValue ? string.Empty : $"-t {Value}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/FaststartArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/FaststartArgument.cs deleted file mode 100644 index b358183..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/FaststartArgument.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Faststart argument - for moving moov atom to the start of file - /// - public class FaststartArgument : Argument - { - public FaststartArgument() { } - - /// - public override string GetStringValue() - { - return "-movflags faststart"; - } - } -} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/ForceFormatArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/ForceFormatArgument.cs deleted file mode 100644 index c2322e0..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/ForceFormatArgument.cs +++ /dev/null @@ -1,21 +0,0 @@ -using FFMpegCore.FFMPEG.Enums; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents force format parameter - /// - public class ForceFormatArgument : Argument - { - public ForceFormatArgument() { } - public ForceFormatArgument(string format) : base(format) { } - - public ForceFormatArgument(VideoCodec value) : base(value.ToString().ToLower()) { } - - /// - public override string GetStringValue() - { - return $"-f {Value}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/FrameOutputCountArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/FrameOutputCountArgument.cs deleted file mode 100644 index 5d0ebf6..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/FrameOutputCountArgument.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents frame output count parameter - /// - public class FrameOutputCountArgument : Argument - { - public FrameOutputCountArgument() { } - - public FrameOutputCountArgument(int value) : base(value) { } - - /// - public override string GetStringValue() - { - return $"-vframes {Value}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/FrameRateArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/FrameRateArgument.cs deleted file mode 100644 index 9e57704..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/FrameRateArgument.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents frame rate parameter - /// - public class FrameRateArgument : Argument - { - public FrameRateArgument() { } - - public FrameRateArgument(double value) : base(value) { } - - /// - public override string GetStringValue() - { - return $"-r {Value}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/InputArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/InputArgument.cs deleted file mode 100644 index d421b0a..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/InputArgument.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.IO; -using System.Linq; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents input parameter - /// - public class InputArgument : Argument - { - 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()) { } - - /// - public override string GetStringValue() - { - return string.Join(" ", Value.Select(v => $"-i \"{v}\"")); - } - public VideoInfo[] GetAsVideoInfo() - { - return Value.Select(v => new VideoInfo(v)).ToArray(); - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/LoopArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/LoopArgument.cs deleted file mode 100644 index 9885afc..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/LoopArgument.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents loop parameter - /// - public class LoopArgument : Argument - { - public LoopArgument() { } - - public LoopArgument(int value) : base(value) { } - - /// - public override string GetStringValue() - { - return $"-loop {Value}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/OutputArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/OutputArgument.cs deleted file mode 100644 index 36ecc33..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/OutputArgument.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.IO; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents output parameter - /// - public class OutputArgument : Argument - { - 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) { } - - /// - public override string GetStringValue() - { - return $"\"{Value}\""; - } - - public FileInfo GetAsFileInfo() - { - return new FileInfo(Value); - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/OverrideArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/OverrideArgument.cs deleted file mode 100644 index ea02968..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/OverrideArgument.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents override parameter - /// If output file should be overrided if exists - /// - public class OverrideArgument : Argument - { - public OverrideArgument() { } - - /// - public override string GetStringValue() - { - return "-y"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/PipeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/PipeArgument.cs deleted file mode 100644 index 5f843ff..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/PipeArgument.cs +++ /dev/null @@ -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); - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/QuietArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/QuietArgument.cs deleted file mode 100644 index a5d5c2e..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/QuietArgument.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - public class QuietArgument : Argument - { - public override string GetStringValue() - { - return "-hide_banner -loglevel warning"; - } - } -} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/RemoveMetadataArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/RemoveMetadataArgument.cs deleted file mode 100644 index 544ba84..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/RemoveMetadataArgument.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Remove metadata argument - /// - public class RemoveMetadataArgument : Argument - { - public RemoveMetadataArgument() { } - - /// - public override string GetStringValue() - { - return $"-map_metadata -1"; - } - } -} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/ScaleArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/ScaleArgument.cs deleted file mode 100644 index 7c85b39..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/ScaleArgument.cs +++ /dev/null @@ -1,28 +0,0 @@ -using FFMpegCore.FFMPEG.Enums; -using System.Drawing; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents scale parameter - /// - public class ScaleArgument : Argument - { - 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); - } - - /// - public override string GetStringValue() - { - return $"-vf scale={Value.Width}:{Value.Height}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/SeekArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/SeekArgument.cs deleted file mode 100644 index beaa44e..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/SeekArgument.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents seek parameter - /// - public class SeekArgument : Argument - { - public SeekArgument() { } - - public SeekArgument(TimeSpan? value) : base(value) { } - - /// - public override string GetStringValue() - { - return !Value.HasValue ? string.Empty : $"-ss {Value}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/ShortestArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/ShortestArgument.cs deleted file mode 100644 index bc67e64..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/ShortestArgument.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents shortest parameter - /// - public class ShortestArgument : Argument - { - public ShortestArgument() { } - - public ShortestArgument(bool value) : base(value) { } - - /// - public override string GetStringValue() - { - return Value ? "-shortest" : string.Empty; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/SizeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/SizeArgument.cs deleted file mode 100644 index 2c27b20..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/SizeArgument.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Drawing; -using FFMpegCore.FFMPEG.Enums; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents size parameter - /// - 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) { } - - /// - public override string GetStringValue() - { - return Value == default ? string.Empty : $"-s {Value.Width}x{Value.Height}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/SpeedArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/SpeedArgument.cs deleted file mode 100644 index 49cf64d..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/SpeedArgument.cs +++ /dev/null @@ -1,22 +0,0 @@ -using FFMpegCore.FFMPEG.Enums; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents speed parameter - /// - public class SpeedArgument : Argument - { - public SpeedArgument() - { - } - - public SpeedArgument(Speed value) : base(value) { } - - /// - public override string GetStringValue() - { - return $"-preset {Value.ToString().ToLower()}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/StartNumberArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/StartNumberArgument.cs deleted file mode 100644 index daf6740..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/StartNumberArgument.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents start number parameter - /// - public class StartNumberArgument : Argument - { - public StartNumberArgument() { } - - public StartNumberArgument(int value) : base(value) { } - - /// - public override string GetStringValue() - { - return $"-start_number {Value}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/ThreadsArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/ThreadsArgument.cs deleted file mode 100644 index 32f017f..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/ThreadsArgument.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents threads parameter - /// Number of threads used for video encoding - /// - public class ThreadsArgument : Argument - { - public ThreadsArgument() { } - - public ThreadsArgument(int value) : base(value) { } - - public ThreadsArgument(bool isMultiThreaded) : - base(isMultiThreaded - ? Environment.ProcessorCount - : 1) { } - - /// - public override string GetStringValue() - { - return $"-threads {Value}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/TransposeArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/TransposeArgument.cs deleted file mode 100644 index 9bbe851..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/TransposeArgument.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Transpose argument. - /// 0 = 90CounterCLockwise and Vertical Flip (default) - /// 1 = 90Clockwise - /// 2 = 90CounterClockwise - /// 3 = 90Clockwise and Vertical Flip - /// - public class TransposeArgument : Argument - { - public TransposeArgument() { } - - public TransposeArgument(int transpose) : base(transpose) - { - if (transpose < 0 || transpose > 3) - { - throw new ArgumentException("Argument is outside range (0 - 3)", nameof(transpose)); - } - } - - /// - public override string GetStringValue() - { - return $"-vf \"transpose={Value}\""; - } - } -} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/VideoCodecArgument.cs b/FFMpegCore/FFMPEG/Argument/Atoms/VideoCodecArgument.cs deleted file mode 100644 index ac35f35..0000000 --- a/FFMpegCore/FFMPEG/Argument/Atoms/VideoCodecArgument.cs +++ /dev/null @@ -1,36 +0,0 @@ -using FFMpegCore.FFMPEG.Enums; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Represents video codec parameter - /// - public class VideoCodecArgument : Argument - { - 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; - } - - /// - public override string GetStringValue() - { - var video = $"-c:v {Value} -pix_fmt yuv420p"; - - if (Bitrate != default) - { - video += $" -b:v {Bitrate}k"; - } - - return video; - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/FFArgumentBuilder.cs b/FFMpegCore/FFMPEG/Argument/FFArgumentBuilder.cs deleted file mode 100644 index 13932fc..0000000 --- a/FFMpegCore/FFMPEG/Argument/FFArgumentBuilder.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Linq; - -namespace FFMpegCore.FFMPEG.Argument -{ - /// - /// Builds parameters string from that would be passed to ffmpeg process - /// - public class FFArgumentBuilder : IArgumentBuilder - { - /// - /// Builds parameters string from that would be passed to ffmpeg process - /// - /// Container of arguments - /// Parameters string - 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())); - } - } -} diff --git a/FFMpegCore/FFMPEG/Argument/IArgumentBuilder.cs b/FFMpegCore/FFMPEG/Argument/IArgumentBuilder.cs deleted file mode 100644 index 0248c36..0000000 --- a/FFMpegCore/FFMPEG/Argument/IArgumentBuilder.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FFMpegCore.FFMPEG.Argument -{ - public interface IArgumentBuilder - { - string BuildArguments(ArgumentContainer container); - } -} diff --git a/FFMpegCore/FFMPEG/FFMpeg.cs b/FFMpegCore/FFMPEG/FFMpeg.cs deleted file mode 100644 index 79929bf..0000000 --- a/FFMpegCore/FFMPEG/FFMpeg.cs +++ /dev/null @@ -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(); - - /// - /// Intializes the FFMPEG encoder. - /// - public FFMpeg() : base() - { - FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory); - _ffmpegPath = FFMpegOptions.Options.FFmpegBinary; - } - - /// - /// Returns the percentage of the current conversion progress. - /// - public event ConversionHandler OnProgress; - - /// - /// Saves a 'png' thumbnail from the input video. - /// - /// Source video file. - /// Output video file - /// Seek position where the thumbnail should be taken. - /// Thumbnail size. If width or height equal 0, the other will be computed automatically. - /// By default, it deletes the created image on disk. If set to true, it won't delete the image - /// Bitmap with the requested snapshot. - 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; - } - - /// - /// Convert a video do a different format. - /// - /// Input video source. - /// Output information. - /// Target conversion video type. - /// Conversion target speed/quality (faster speed = lower quality). - /// Video size. - /// Conversion target audio quality. - /// Is encoding multithreaded. - /// Output video information. - 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)) - }; - } - - /// - /// Adds a poster image to an audio file. - /// - /// Source image file. - /// Source audio file. - /// Output video file. - /// - 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); - } - - /// - /// Joins a list of video files. - /// - /// Output video file. - /// List of vides that need to be joined together. - /// Output video information. - 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); - } - } - - /// - /// Converts an image sequence to a video. - /// - /// Output video file. - /// FPS - /// Image sequence collection - /// Output video information. - 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); - } - } - - /// - /// Records M3U8 streams to the specified output. - /// - /// URI to pointing towards stream. - /// Output file - /// Success state. - 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."); - } - - /// - /// Strips a video file of audio. - /// - /// Source video file. - /// Output video file. - /// - 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) - )); - } - - /// - /// Saves audio from a specific video file to disk. - /// - /// Source video file. - /// Output audio file. - /// Success state. - 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; - } - - /// - /// Adds audio to a video file. - /// - /// Source video file. - /// Source audio file. - /// Output video file. - /// Indicates if the encoding should stop at the shortest input file. - /// Success state - 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 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(out var outputArg)) - output = outputArg.GetAsFileInfo(); - else if (arguments.TryGetArgument(out var outputPipeArg)) - output = null; - else - throw new FFMpegException(FFMpegExceptionType.Operation, "No output argument found"); - - VideoInfo[] sources; - if (arguments.TryGetArgument(out var input)) - sources = input.GetAsVideoInfo(); - else if (arguments.TryGetArgument(out var concat)) - sources = concat.GetAsVideoInfo(); - else if (arguments.TryGetArgument(out var pipe)) - sources = null; - else - throw new FFMpegException(FFMpegExceptionType.Operation, "No input or concat argument found"); - return (sources, output); - } - - /// - /// Returns true if the associated process is still alive/running. - /// - public bool IsWorking => _instance.Started; - - /// - /// Stops any current job that FFMpeg is running. - /// - 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(out var inputPipeArgument)) - { - inputPipeArgument.OpenPipe(); - } - if (container.TryGetArgument(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(); - 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 RunProcessAsync(ArgumentContainer container, FileInfo output, bool skipExistsCheck) - { - _instance?.Dispose(); - var arguments = ArgumentBuilder.BuildArguments(container); - var exitCode = -1; - - if (container.TryGetArgument(out var inputPipeArgument)) - { - inputPipeArgument.OpenPipe(); - } - if (container.TryGetArgument(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(); - 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 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 - } -} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/FFMpegStreamMetadata.cs b/FFMpegCore/FFMPEG/FFMpegStreamMetadata.cs deleted file mode 100644 index 3a85b7a..0000000 --- a/FFMpegCore/FFMPEG/FFMpegStreamMetadata.cs +++ /dev/null @@ -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 Streams { get; set; } - } -} diff --git a/FFMpegCore/FFMPEG/FFProbe.cs b/FFMpegCore/FFMPEG/FFProbe.cs deleted file mode 100644 index d73a8f5..0000000 --- a/FFMpegCore/FFMPEG/FFProbe.cs +++ /dev/null @@ -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; - } - - /// - /// Probes the targeted video file and retrieves all available details. - /// - /// Source video file. - /// A video info object containing all details necessary. - public VideoInfo ParseVideoInfo(string source) - { - return ParseVideoInfo(new VideoInfo(source)); - } - /// - /// Probes the targeted video file asynchronously and retrieves all available details. - /// - /// Source video file. - /// A task for the video info object containing all details necessary. - public Task ParseVideoInfoAsync(string source) - { - return ParseVideoInfoAsync(new VideoInfo(source)); - } - - /// - /// Probes the targeted video file and retrieves all available details. - /// - /// Source video file. - /// A video info object containing all details necessary. - 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); - } - /// - /// Probes the targeted video file asynchronously and retrieves all available details. - /// - /// Source video file. - /// A video info object containing all details necessary. - public async Task 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); - } - - /// - /// Probes the targeted video stream and retrieves all available details. - /// - /// Encoded video stream. - /// A video info object containing all details necessary. - 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); - } - - /// - /// Probes the targeted video stream asynchronously and retrieves all available details. - /// - /// Encoded video stream. - /// A video info object containing all details necessary. - public async Task 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(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(output); - } - - internal async Task GetMetadataAsync(string path) - { - var instance = new Instance(_ffprobePath, BuildFFProbeArguments(path)) { DataBufferCapacity = _outputCapacity }; - await instance.FinishedRunning(); - var output = string.Join("", instance.OutputData); - return JsonConvert.DeserializeObject(output); - } - } -} diff --git a/FFMpegCore/FFMPEG/Pipes/PipeHelpers.cs b/FFMpegCore/FFMPEG/Pipes/PipeHelpers.cs deleted file mode 100644 index 6717dac..0000000 --- a/FFMpegCore/FFMPEG/Pipes/PipeHelpers.cs +++ /dev/null @@ -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}"; - } - } -} diff --git a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs b/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs deleted file mode 100644 index e2b5120..0000000 --- a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataWriter.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace FFMpegCore.FFMPEG.Pipes -{ - /// - /// Implementation of used for stream redirection - /// - 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; - } - } -} diff --git a/FFMpegCore/FFMPEG/ProbeInfo.cs b/FFMpegCore/FFMPEG/ProbeInfo.cs deleted file mode 100644 index e69de29..0000000 diff --git a/FFMpegCore/FFMpeg/Arguments/AudioBitrateArgument.cs b/FFMpegCore/FFMpeg/Arguments/AudioBitrateArgument.cs new file mode 100644 index 0000000..9c7e813 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/AudioBitrateArgument.cs @@ -0,0 +1,20 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents parameter of audio codec and it's quality + /// + 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"; + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/AudioCodecArgument.cs b/FFMpegCore/FFMpeg/Arguments/AudioCodecArgument.cs new file mode 100644 index 0000000..3b81ddd --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/AudioCodecArgument.cs @@ -0,0 +1,18 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents parameter of audio codec and it's quality + /// + public class AudioCodecArgument : IArgument + { + public readonly AudioCodec AudioCodec; + public AudioCodecArgument(AudioCodec audioCodec) + { + AudioCodec = audioCodec; + } + + public string Text => $"-c:a {AudioCodec.ToString().ToLower()}"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/AudioSamplingRateArgument.cs b/FFMpegCore/FFMpeg/Arguments/AudioSamplingRateArgument.cs new file mode 100644 index 0000000..6f1365d --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/AudioSamplingRateArgument.cs @@ -0,0 +1,16 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Audio sampling rate argument. Defaults to 48000 (Hz) + /// + public class AudioSamplingRateArgument : IArgument + { + public readonly int SamplingRate; + public AudioSamplingRateArgument(int samplingRate = 48000) + { + SamplingRate = samplingRate; + } + + public string Text => $"-ar {SamplingRate}"; + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/BitStreamFilterArgument.cs b/FFMpegCore/FFMpeg/Arguments/BitStreamFilterArgument.cs new file mode 100644 index 0000000..871df5e --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/BitStreamFilterArgument.cs @@ -0,0 +1,26 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents parameter of bitstream filter + /// + 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 + }; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/ConcatArgument.cs b/FFMpegCore/FFMpeg/Arguments/ConcatArgument.cs new file mode 100644 index 0000000..b0908b4 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/ConcatArgument.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace FFMpegCore.Arguments +{ + + /// + /// Represents parameter of concat argument + /// Used for creating video from multiple images or videos + /// + public class ConcatArgument : IInputArgument + { + public readonly IEnumerable Values; + public ConcatArgument(IEnumerable values) + { + Values = values; + } + + public string Text => $"-i \"concat:{string.Join(@"|", Values)}\""; + } +} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/ConstantRateFactorArgument.cs b/FFMpegCore/FFMpeg/Arguments/ConstantRateFactorArgument.cs similarity index 50% rename from FFMpegCore/FFMPEG/Argument/Atoms/ConstantRateFactorArgument.cs rename to FFMpegCore/FFMpeg/Arguments/ConstantRateFactorArgument.cs index f6a8f99..c02cfa3 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/ConstantRateFactorArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/ConstantRateFactorArgument.cs @@ -1,24 +1,24 @@ using System; -namespace FFMpegCore.FFMPEG.Argument +namespace FFMpegCore.Arguments { /// /// Constant Rate Factor (CRF) argument /// - public class ConstantRateFactorArgument : Argument + public class ConstantRateFactorArgument : IArgument { - public ConstantRateFactorArgument(int crf) : base(crf) + public readonly int Crf; + + public ConstantRateFactorArgument(int crf) { if (crf < 0 || crf > 63) { throw new ArgumentException("Argument is outside range (0 - 63)", nameof(crf)); } + + Crf = crf; } - /// - public override string GetStringValue() - { - return $"-crf {Value}"; - } + public string Text => $"-crf {Crf}"; } } \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/CopyArgument.cs b/FFMpegCore/FFMpeg/Arguments/CopyArgument.cs new file mode 100644 index 0000000..91419d5 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/CopyArgument.cs @@ -0,0 +1,24 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents parameter of copy parameter + /// Defines if channel (audio, video or both) should be copied to output file + /// + 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" + }; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/CpuSpeedArgument.cs b/FFMpegCore/FFMpeg/Arguments/CpuSpeedArgument.cs new file mode 100644 index 0000000..228262e --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/CpuSpeedArgument.cs @@ -0,0 +1,16 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Represents cpu speed parameter + /// + public class CpuSpeedArgument : IArgument + { + public readonly int CpuSpeed; + public CpuSpeedArgument(int cpuSpeed) + { + CpuSpeed = cpuSpeed; + } + + public string Text => $"-quality good -cpu-used {CpuSpeed} -deadline realtime"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/CustomArgument.cs b/FFMpegCore/FFMpeg/Arguments/CustomArgument.cs new file mode 100644 index 0000000..8eedb12 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/CustomArgument.cs @@ -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; + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/DisableChannelArgument.cs b/FFMpegCore/FFMpeg/Arguments/DisableChannelArgument.cs new file mode 100644 index 0000000..d683775 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/DisableChannelArgument.cs @@ -0,0 +1,27 @@ +using FFMpegCore.Enums; +using FFMpegCore.Exceptions; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents cpu speed parameter + /// + 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 + }; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/DrawTextArgument.cs b/FFMpegCore/FFMpeg/Arguments/DrawTextArgument.cs new file mode 100644 index 0000000..d4eabb8 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/DrawTextArgument.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; + +namespace FFMpegCore.Arguments +{ + /// + /// Drawtext video filter argument + /// + 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; + } + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/DurationArgument.cs b/FFMpegCore/FFMpeg/Arguments/DurationArgument.cs new file mode 100644 index 0000000..e47b966 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/DurationArgument.cs @@ -0,0 +1,18 @@ +using System; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents duration parameter + /// + public class DurationArgument : IArgument + { + public readonly TimeSpan? Duration; + public DurationArgument(TimeSpan? duration) + { + Duration = duration; + } + + public string Text => !Duration.HasValue ? string.Empty : $"-t {Duration.Value}"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/FaststartArgument.cs b/FFMpegCore/FFMpeg/Arguments/FaststartArgument.cs new file mode 100644 index 0000000..54cdd6f --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/FaststartArgument.cs @@ -0,0 +1,10 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Faststart argument - for moving moov atom to the start of file + /// + public class FaststartArgument : IArgument + { + public string Text => "-movflags faststart"; + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/ForceFormatArgument.cs b/FFMpegCore/FFMpeg/Arguments/ForceFormatArgument.cs new file mode 100644 index 0000000..503c6ec --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/ForceFormatArgument.cs @@ -0,0 +1,20 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents force format parameter + /// + 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}"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/FrameOutputCountArgument.cs b/FFMpegCore/FFMpeg/Arguments/FrameOutputCountArgument.cs new file mode 100644 index 0000000..08bc56b --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/FrameOutputCountArgument.cs @@ -0,0 +1,16 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Represents frame output count parameter + /// + public class FrameOutputCountArgument : IArgument + { + public readonly int Frames; + public FrameOutputCountArgument(int frames) + { + Frames = frames; + } + + public string Text => $"-vframes {Frames}"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/FrameRateArgument.cs b/FFMpegCore/FFMpeg/Arguments/FrameRateArgument.cs new file mode 100644 index 0000000..e000e2e --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/FrameRateArgument.cs @@ -0,0 +1,17 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Represents frame rate parameter + /// + public class FrameRateArgument : IArgument + { + public readonly double Framerate; + + public FrameRateArgument(double framerate) + { + Framerate = framerate; + } + + public string Text => $"-r {Framerate}"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/InputArgument.cs b/FFMpegCore/FFMpeg/Arguments/InputArgument.cs new file mode 100644 index 0000000..c201c1f --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/InputArgument.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents input parameter + /// + 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 + { + /// + /// The textual representation of the argument + /// + 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 + { + } +} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs b/FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs similarity index 61% rename from FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs rename to FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs index 6197a23..9fcace9 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/InputPipeArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs @@ -1,32 +1,23 @@ -using FFMpegCore.FFMPEG.Pipes; -using Instances; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.IO.Pipes; -using System.Text; +using System.IO.Pipes; using System.Threading; using System.Threading.Tasks; +using FFMpegCore.Pipes; -namespace FFMpegCore.FFMPEG.Argument +namespace FFMpegCore.Arguments { /// /// Represents input parameter for a named pipe /// public class InputPipeArgument : PipeArgument { - public IPipeDataWriter Writer { get; private set; } + public readonly IPipeDataWriter Writer; public InputPipeArgument(IPipeDataWriter writer) : base(PipeDirection.Out) { Writer = writer; } - public override string GetStringValue() - { - return $"-y {Writer.GetFormat()} -i \"{PipePath}\""; - } + public override string Text => $"-y {Writer.GetFormat()} -i \"{PipePath}\""; public override async Task ProcessDataAsync(CancellationToken token) { diff --git a/FFMpegCore/FFMpeg/Arguments/LoopArgument.cs b/FFMpegCore/FFMpeg/Arguments/LoopArgument.cs new file mode 100644 index 0000000..26adc3e --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/LoopArgument.cs @@ -0,0 +1,16 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Represents loop parameter + /// + public class LoopArgument : IArgument + { + public readonly int Times; + public LoopArgument(int times) + { + Times = times; + } + + public string Text => $"-loop {Times}"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/OutputArgument.cs b/FFMpegCore/FFMpeg/Arguments/OutputArgument.cs new file mode 100644 index 0000000..5a53886 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/OutputArgument.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; +using FFMpegCore.Exceptions; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents output parameter + /// + 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)}"; + } +} diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/OutputPipeArgument.cs b/FFMpegCore/FFMpeg/Arguments/OutputPipeArgument.cs similarity index 63% rename from FFMpegCore/FFMPEG/Argument/Atoms/OutputPipeArgument.cs rename to FFMpegCore/FFMpeg/Arguments/OutputPipeArgument.cs index fd02df2..0988f1c 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/OutputPipeArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/OutputPipeArgument.cs @@ -1,26 +1,20 @@ -using FFMpegCore.FFMPEG.Pipes; -using System; -using System.Collections.Generic; -using System.IO.Pipes; -using System.Text; +using System.IO.Pipes; using System.Threading; using System.Threading.Tasks; +using FFMpegCore.Pipes; -namespace FFMpegCore.FFMPEG.Argument +namespace FFMpegCore.Arguments { public class OutputPipeArgument : PipeArgument { - public IPipeDataReader Reader { get; private set; } + public readonly IPipeDataReader Reader; public OutputPipeArgument(IPipeDataReader reader) : base(PipeDirection.In) { Reader = reader; } - public override string GetStringValue() - { - return $"\"{PipePath}\" -y"; - } + public override string Text => $"\"{PipePath}\" -y"; public override async Task ProcessDataAsync(CancellationToken token) { diff --git a/FFMpegCore/FFMpeg/Arguments/OverwriteArgument.cs b/FFMpegCore/FFMpeg/Arguments/OverwriteArgument.cs new file mode 100644 index 0000000..3a633af --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/OverwriteArgument.cs @@ -0,0 +1,11 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Represents overwrite parameter + /// If output file should be overwritten if exists + /// + public class OverwriteArgument : IArgument + { + public string Text => "-y"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs b/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs new file mode 100644 index 0000000..cc8ffab --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs @@ -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; } + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/QuietArgument.cs b/FFMpegCore/FFMpeg/Arguments/QuietArgument.cs new file mode 100644 index 0000000..2bab939 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/QuietArgument.cs @@ -0,0 +1,7 @@ +namespace FFMpegCore.Arguments +{ + public class QuietArgument : IArgument + { + public string Text => "-hide_banner -loglevel warning"; + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/RemoveMetadataArgument.cs b/FFMpegCore/FFMpeg/Arguments/RemoveMetadataArgument.cs new file mode 100644 index 0000000..29cdac6 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/RemoveMetadataArgument.cs @@ -0,0 +1,10 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Remove metadata argument + /// + public class RemoveMetadataArgument : IArgument + { + public string Text => "-map_metadata -1"; + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs b/FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs new file mode 100644 index 0000000..40e98d0 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs @@ -0,0 +1,26 @@ +using System.Drawing; +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents scale parameter + /// + 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; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/SeekArgument.cs b/FFMpegCore/FFMpeg/Arguments/SeekArgument.cs new file mode 100644 index 0000000..1057b88 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/SeekArgument.cs @@ -0,0 +1,18 @@ +using System; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents seek parameter + /// + public class SeekArgument : IArgument + { + public readonly TimeSpan? SeekTo; + public SeekArgument(TimeSpan? seekTo) + { + SeekTo = seekTo; + } + + public string Text => !SeekTo.HasValue ? string.Empty : $"-ss {SeekTo.Value}"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/ShortestArgument.cs b/FFMpegCore/FFMpeg/Arguments/ShortestArgument.cs new file mode 100644 index 0000000..d85813e --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/ShortestArgument.cs @@ -0,0 +1,17 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Represents shortest parameter + /// + public class ShortestArgument : IArgument + { + public readonly bool Shortest; + + public ShortestArgument(bool shortest) + { + Shortest = shortest; + } + + public string Text => Shortest ? "-shortest" : string.Empty; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/SizeArgument.cs b/FFMpegCore/FFMpeg/Arguments/SizeArgument.cs new file mode 100644 index 0000000..2ccde92 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/SizeArgument.cs @@ -0,0 +1,19 @@ +using System.Drawing; +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents size parameter + /// + 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; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/SpeedPresetArgument.cs b/FFMpegCore/FFMpeg/Arguments/SpeedPresetArgument.cs new file mode 100644 index 0000000..401e2dd --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/SpeedPresetArgument.cs @@ -0,0 +1,19 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents speed parameter + /// + public class SpeedPresetArgument : IArgument + { + public readonly Speed Speed; + + public SpeedPresetArgument(Speed speed) + { + Speed = speed; + } + + public string Text => $"-preset {Speed.ToString().ToLower()}"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/StartNumberArgument.cs b/FFMpegCore/FFMpeg/Arguments/StartNumberArgument.cs new file mode 100644 index 0000000..f7c09da --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/StartNumberArgument.cs @@ -0,0 +1,17 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Represents start number parameter + /// + public class StartNumberArgument : IArgument + { + public readonly int StartNumber; + + public StartNumberArgument(int startNumber) + { + StartNumber = startNumber; + } + + public string Text => $"-start_number {StartNumber}"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/ThreadsArgument.cs b/FFMpegCore/FFMpeg/Arguments/ThreadsArgument.cs new file mode 100644 index 0000000..6fd94e6 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/ThreadsArgument.cs @@ -0,0 +1,21 @@ +using System; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents threads parameter + /// Number of threads used for video encoding + /// + 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}"; + } +} diff --git a/FFMpegCore/FFMpeg/Arguments/TransposeArgument.cs b/FFMpegCore/FFMpeg/Arguments/TransposeArgument.cs new file mode 100644 index 0000000..acc26f4 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/TransposeArgument.cs @@ -0,0 +1,22 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments +{ + /// + /// Transpose argument. + /// 0 = 90CounterCLockwise and Vertical Flip (default) + /// 1 = 90Clockwise + /// 2 = 90CounterClockwise + /// 3 = 90Clockwise and Vertical Flip + /// + public class TransposeArgument : IArgument + { + public readonly Transposition Transposition; + public TransposeArgument(Transposition transposition) + { + Transposition = transposition; + } + + public string Text => $"-vf \"transpose={(int)Transposition}\""; + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/Argument/Atoms/VariableBitRateArgument.cs b/FFMpegCore/FFMpeg/Arguments/VariableBitRateArgument.cs similarity index 51% rename from FFMpegCore/FFMPEG/Argument/Atoms/VariableBitRateArgument.cs rename to FFMpegCore/FFMpeg/Arguments/VariableBitRateArgument.cs index 15bea02..b656ec4 100644 --- a/FFMpegCore/FFMPEG/Argument/Atoms/VariableBitRateArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/VariableBitRateArgument.cs @@ -1,24 +1,24 @@ using System; -namespace FFMpegCore.FFMPEG.Argument +namespace FFMpegCore.Arguments { /// /// Variable Bitrate Argument (VBR) argument /// - public class VariableBitRateArgument : Argument + public class VariableBitRateArgument : IArgument { - public VariableBitRateArgument(int vbr) : base(vbr) + public readonly int Vbr; + + public VariableBitRateArgument(int vbr) { if (vbr < 0 || vbr > 5) { throw new ArgumentException("Argument is outside range (0 - 5)", nameof(vbr)); } + + Vbr = vbr; } - /// - public override string GetStringValue() - { - return $"-vbr {Value}"; - } + public string Text => $"-vbr {Vbr}"; } } \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/VideoBitrateArgument.cs b/FFMpegCore/FFMpeg/Arguments/VideoBitrateArgument.cs new file mode 100644 index 0000000..ea5e641 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/VideoBitrateArgument.cs @@ -0,0 +1,17 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Represents video bitrate parameter + /// + public class VideoBitrateArgument : IArgument + { + public readonly int Bitrate; + + public VideoBitrateArgument(int bitrate) + { + Bitrate = bitrate; + } + + public string Text => $"-b:v {Bitrate}k"; + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/VideoCodecArgument.cs b/FFMpegCore/FFMpeg/Arguments/VideoCodecArgument.cs new file mode 100644 index 0000000..4e2b782 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/VideoCodecArgument.cs @@ -0,0 +1,21 @@ +using FFMpegCore.Enums; + +namespace FFMpegCore.Arguments +{ + /// + /// Represents video codec parameter + /// + 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"; + } +} diff --git a/FFMpegCore/FFMPEG/Enums/AudioQuality.cs b/FFMpegCore/FFMpeg/Enums/AudioQuality.cs similarity index 73% rename from FFMpegCore/FFMPEG/Enums/AudioQuality.cs rename to FFMpegCore/FFMpeg/Enums/AudioQuality.cs index 7597cec..60ba0eb 100644 --- a/FFMpegCore/FFMPEG/Enums/AudioQuality.cs +++ b/FFMpegCore/FFMpeg/Enums/AudioQuality.cs @@ -1,10 +1,10 @@ -namespace FFMpegCore.FFMPEG.Enums +namespace FFMpegCore.Enums { public enum AudioQuality { Ultra = 384, VeryHigh = 256, - Hd = 192, + Good = 192, Normal = 128, BelowNormal = 96, Low = 64 diff --git a/FFMpegCore/FFMPEG/Enums/Codec.cs b/FFMpegCore/FFMpeg/Enums/Codec.cs similarity index 91% rename from FFMpegCore/FFMPEG/Enums/Codec.cs rename to FFMpegCore/FFMpeg/Enums/Codec.cs index 95f58b9..7576501 100644 --- a/FFMpegCore/FFMPEG/Enums/Codec.cs +++ b/FFMpegCore/FFMpeg/Enums/Codec.cs @@ -1,4 +1,4 @@ -namespace FFMpegCore.FFMPEG.Enums +namespace FFMpegCore.Enums { public enum VideoCodec { diff --git a/FFMpegCore/FFMPEG/Enums/Speed.cs b/FFMpegCore/FFMpeg/Enums/Speed.cs similarity index 83% rename from FFMpegCore/FFMPEG/Enums/Speed.cs rename to FFMpegCore/FFMpeg/Enums/Speed.cs index 089ed9c..52272f0 100644 --- a/FFMpegCore/FFMPEG/Enums/Speed.cs +++ b/FFMpegCore/FFMpeg/Enums/Speed.cs @@ -1,4 +1,4 @@ -namespace FFMpegCore.FFMPEG.Enums +namespace FFMpegCore.Enums { public enum Speed { diff --git a/FFMpegCore/FFMpeg/Enums/Transposition.cs b/FFMpegCore/FFMpeg/Enums/Transposition.cs new file mode 100644 index 0000000..bacfccc --- /dev/null +++ b/FFMpegCore/FFMpeg/Enums/Transposition.cs @@ -0,0 +1,10 @@ +namespace FFMpegCore.Enums +{ + public enum Transposition + { + CounterClockwise90VerticalFlip = 0, + Clockwise90 = 1, + CounterClockwise90 = 2, + Clockwise90VerticalFlip = 3 + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/Enums/VideoSize.cs b/FFMpegCore/FFMpeg/Enums/VideoSize.cs similarity index 79% rename from FFMpegCore/FFMPEG/Enums/VideoSize.cs rename to FFMpegCore/FFMpeg/Enums/VideoSize.cs index 501be83..d774b95 100644 --- a/FFMpegCore/FFMPEG/Enums/VideoSize.cs +++ b/FFMpegCore/FFMpeg/Enums/VideoSize.cs @@ -1,4 +1,4 @@ -namespace FFMpegCore.FFMPEG.Enums +namespace FFMpegCore.Enums { public enum VideoSize { diff --git a/FFMpegCore/FFMPEG/Exceptions/FFMpegException.cs b/FFMpegCore/FFMpeg/Exceptions/FFMpegException.cs similarity index 61% rename from FFMpegCore/FFMPEG/Exceptions/FFMpegException.cs rename to FFMpegCore/FFMpeg/Exceptions/FFMpegException.cs index 0895d02..00d3c19 100644 --- a/FFMpegCore/FFMPEG/Exceptions/FFMpegException.cs +++ b/FFMpegCore/FFMpeg/Exceptions/FFMpegException.cs @@ -1,7 +1,6 @@ using System; -using System.Text; -namespace FFMpegCore.FFMPEG.Exceptions +namespace FFMpegCore.Exceptions { public enum FFMpegExceptionType { @@ -14,11 +13,9 @@ public enum FFMpegExceptionType 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 = null, Exception innerException = null) + public FFMpegException(FFMpegExceptionType type, string? message = null, Exception? innerException = null) : base(message, innerException) { Type = type; diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs new file mode 100644 index 0000000..0d9786a --- /dev/null +++ b/FFMpegCore/FFMpeg/FFMpeg.cs @@ -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 + { + /// + /// Saves a 'png' thumbnail from the input video. + /// + /// Source video file. + /// Output video file + /// Seek position where the thumbnail should be taken. + /// Thumbnail size. If width or height equal 0, the other will be computed automatically. + /// By default, it deletes the created image on disk. If set to true, it won't delete the image + /// Bitmap with the requested snapshot. + 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; + } + + /// + /// Convert a video do a different format. + /// + /// Input video source. + /// Output information. + /// Target conversion video type. + /// Conversion target speed/quality (faster speed = lower quality). + /// Video size. + /// Conversion target audio quality. + /// Is encoding multithreaded. + /// Output video information. + 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)) + }; + } + + /// + /// Adds a poster image to an audio file. + /// + /// Source image file. + /// Source audio file. + /// Output video file. + /// + 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(); + } + + /// + /// Joins a list of video files. + /// + /// Output video file. + /// List of vides that need to be joined together. + /// Output video information. + 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); + } + } + + /// + /// Converts an image sequence to a video. + /// + /// Output video file. + /// FPS + /// Image sequence collection + /// Output video information. + 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); + } + } + + /// + /// Records M3U8 streams to the specified output. + /// + /// URI to pointing towards stream. + /// Output file + /// Success state. + 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."); + } + + /// + /// Strips a video file of audio. + /// + /// Input video file. + /// Output video file. + /// + 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(); + } + + /// + /// Saves audio from a specific video file to disk. + /// + /// Source video file. + /// Output audio file. + /// Success state. + public static bool ExtractAudio(string input, string output) + { + FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp3); + + return FFMpegArguments + .FromInputFiles(true, input) + .DisableChannel(Channel.Video) + .OutputToFile(output) + .ProcessSynchronously(); + } + + /// + /// Adds audio to a video file. + /// + /// Source video file. + /// Source audio file. + /// Output video file. + /// Indicates if the encoding should stop at the shortest input file. + /// Success state + 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 pathList) + { + foreach (var path in pathList) + { + if (File.Exists(path)) + { + File.Delete(path); + } + } + } + + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs new file mode 100644 index 0000000..e86bf5c --- /dev/null +++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs @@ -0,0 +1,104 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using FFMpegCore.Helpers; +using Instances; + +namespace FFMpegCore +{ + public class FFMpegArgumentProcessor + { + private readonly FFMpegArguments _ffMpegArguments; + + internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments) + { + _ffMpegArguments = ffMpegArguments; + } + + /// + /// Returns the percentage of the current conversion progress. + /// + // public event ConversionHandler OnProgress; + + public string Arguments => _ffMpegArguments.Text; + + public FFMpegArgumentProcessor NotifyOnProgress(Action? onPercentageProgress, TimeSpan? totalTimeSpan) + { + _totalTimespan = totalTimeSpan; + _onPercentageProgress = onPercentageProgress; + return this; + } + public FFMpegArgumentProcessor NotifyOnProgress(Action? onTimeProgress) + { + _onTimeProgress = onTimeProgress; + return this; + } + public bool ProcessSynchronously() + { + FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory); + var instance = new Instance(FFMpegOptions.Options.FFmpegBinary, _ffMpegArguments.Text); + instance.DataReceived += OutputData; + var errorCode = -1; + + _ffMpegArguments.Pre(); + + var cancellationTokenSource = new CancellationTokenSource(); + Task.WaitAll(instance.FinishedRunning().ContinueWith(t => + { + errorCode = t.Result; + cancellationTokenSource.Cancel(); + }), _ffMpegArguments.During(cancellationTokenSource.Token)); + + _ffMpegArguments.Post(); + + return errorCode == 0; + } + + public async Task ProcessAsynchronously() + { + FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory); + using var instance = new Instance(FFMpegOptions.Options.FFmpegBinary, _ffMpegArguments.Text); + instance.DataReceived += OutputData; + var errorCode = -1; + + _ffMpegArguments.Pre(); + + var cancellationTokenSource = new CancellationTokenSource(); + await Task.WhenAll(instance.FinishedRunning().ContinueWith(t => + { + errorCode = t.Result; + cancellationTokenSource.Cancel(); + }), _ffMpegArguments.During(cancellationTokenSource.Token)).ConfigureAwait(false); + _ffMpegArguments.Post(); + + return errorCode == 0; + } + + + private static readonly Regex ProgressRegex = new Regex(@"time=(\d\d:\d\d:\d\d.\d\d?)", RegexOptions.Compiled); + private Action? _onPercentageProgress; + private Action? _onTimeProgress; + private TimeSpan? _totalTimespan; + + private void OutputData(object sender, (DataType Type, string Data) msg) + { +#if DEBUG + Trace.WriteLine(msg.Data); +#endif + if (_onTimeProgress == null && (_onPercentageProgress == null || _totalTimespan == null)) return; + + var match = ProgressRegex.Match(msg.Data); + if (!match.Success) return; + + var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); + _onTimeProgress?.Invoke(processed); + + if (_onPercentageProgress == null || _totalTimespan == null) return; + var percentage = Math.Round(processed.TotalSeconds / _totalTimespan.Value.TotalSeconds * 100, 2); + _onPercentageProgress(percentage); + } + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs new file mode 100644 index 0000000..bb0def7 --- /dev/null +++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using FFMpegCore.Arguments; +using FFMpegCore.Enums; +using FFMpegCore.Pipes; + +namespace FFMpegCore +{ + public class FFMpegArguments + { + private readonly IInputArgument _inputArgument; + private IOutputArgument _outputArgument = null!; + private readonly List _arguments; + + private FFMpegArguments(IInputArgument inputArgument) + { + _inputArgument = inputArgument; + _arguments = new List { inputArgument }; + } + + public string Text => string.Join(" ", _arguments.Select(arg => arg.Text)); + + public static FFMpegArguments FromInputFiles(params string[] files) => new FFMpegArguments(new InputArgument(true, files)); + public static FFMpegArguments FromInputFiles(bool verifyExists, params string[] files) => new FFMpegArguments(new InputArgument(verifyExists, files)); + public static FFMpegArguments FromInputFiles(params Uri[] uris) => new FFMpegArguments(new InputArgument(false, uris)); + public static FFMpegArguments FromInputFiles(bool verifyExists, params Uri[] uris) => new FFMpegArguments(new InputArgument(verifyExists, uris)); + public static FFMpegArguments FromInputFiles(params FileInfo[] files) => new FFMpegArguments(new InputArgument(false, files)); + public static FFMpegArguments FromInputFiles(bool verifyExists, params FileInfo[] files) => new FFMpegArguments(new InputArgument(verifyExists, files)); + public static FFMpegArguments FromConcatenation(params string[] files) => new FFMpegArguments(new ConcatArgument(files)); + public static FFMpegArguments FromPipe(IPipeDataWriter writer) => new FFMpegArguments(new InputPipeArgument(writer)); + + + public FFMpegArguments WithAudioCodec(AudioCodec audioCodec) => WithArgument(new AudioCodecArgument(audioCodec)); + public FFMpegArguments WithAudioBitrate(AudioQuality audioQuality) => WithArgument(new AudioBitrateArgument(audioQuality)); + public FFMpegArguments WithAudioBitrate(int bitrate) => WithArgument(new AudioBitrateArgument(bitrate)); + public FFMpegArguments WithAudioSamplingRate(int samplingRate = 48000) => WithArgument(new AudioSamplingRateArgument(samplingRate)); + public FFMpegArguments WithVariableBitrate(int vbr) => WithArgument(new VariableBitRateArgument(vbr)); + + public FFMpegArguments Resize(VideoSize videoSize) => WithArgument(new SizeArgument(videoSize)); + public FFMpegArguments Resize(int width, int height) => WithArgument(new SizeArgument(width, height)); + public FFMpegArguments Resize(Size? size) => WithArgument(new SizeArgument(size)); + + public FFMpegArguments Scale(VideoSize videoSize) => WithArgument(new ScaleArgument(videoSize)); + public FFMpegArguments Scale(int width, int height) => WithArgument(new ScaleArgument(width, height)); + public FFMpegArguments Scale(Size size) => WithArgument(new ScaleArgument(size)); + + public FFMpegArguments WithBitStreamFilter(Channel channel, Filter filter) => WithArgument(new BitStreamFilterArgument(channel, filter)); + public FFMpegArguments WithConstantRateFactor(int crf) => WithArgument(new ConstantRateFactorArgument(crf)); + public FFMpegArguments CopyChannel(Channel channel = Channel.Both) => WithArgument(new CopyArgument(channel)); + public FFMpegArguments DisableChannel(Channel channel) => WithArgument(new DisableChannelArgument(channel)); + public FFMpegArguments WithDuration(TimeSpan? duration) => WithArgument(new DurationArgument(duration)); + public FFMpegArguments WithFastStart() => WithArgument(new FaststartArgument()); + public FFMpegArguments WithFrameOutputCount(int frames) => WithArgument(new FrameOutputCountArgument(frames)); + + public FFMpegArguments UsingShortest(bool shortest = true) => WithArgument(new ShortestArgument(shortest)); + public FFMpegArguments UsingMultithreading(bool multithread) => WithArgument(new ThreadsArgument(multithread)); + public FFMpegArguments UsingThreads(int threads) => WithArgument(new ThreadsArgument(threads)); + + public FFMpegArguments WithVideoCodec(VideoCodec videoCodec) => WithArgument(new VideoCodecArgument(videoCodec)); + public FFMpegArguments WithVideoCodec(string videoCodec) => WithArgument(new VideoCodecArgument(videoCodec)); + public FFMpegArguments WithVideoBitrate(int bitrate) => WithArgument(new VideoBitrateArgument(bitrate)); + public FFMpegArguments WithFramerate(double framerate) => WithArgument(new FrameRateArgument(framerate)); + public FFMpegArguments WithoutMetadata() => WithArgument(new RemoveMetadataArgument()); + public FFMpegArguments WithSpeedPreset(Speed speed) => WithArgument(new SpeedPresetArgument(speed)); + public FFMpegArguments WithStartNumber(int startNumber) => WithArgument(new StartNumberArgument(startNumber)); + public FFMpegArguments WithCpuSpeed(int cpuSpeed) => WithArgument(new CpuSpeedArgument(cpuSpeed)); + public FFMpegArguments WithCustomArgument(string argument) => WithArgument(new CustomArgument(argument)); + + public FFMpegArguments Seek(TimeSpan? seekTo) => WithArgument(new SeekArgument(seekTo)); + public FFMpegArguments Transpose(Transposition transposition) => WithArgument(new TransposeArgument(transposition)); + public FFMpegArguments Loop(int times) => WithArgument(new LoopArgument(times)); + public FFMpegArguments OverwriteExisting() => WithArgument(new OverwriteArgument()); + public FFMpegArguments Quiet() => WithArgument(new QuietArgument()); + + public FFMpegArguments ForceFormat(VideoCodec videoCodec) => WithArgument(new ForceFormatArgument(videoCodec)); + public FFMpegArguments ForceFormat(string videoCodec) => WithArgument(new ForceFormatArgument(videoCodec)); + public FFMpegArguments DrawText(DrawTextOptions drawTextOptions) => WithArgument(new DrawTextArgument(drawTextOptions)); + + public FFMpegArgumentProcessor OutputToFile(string file, bool overwrite = false) => ToProcessor(new OutputArgument(file, overwrite)); + public FFMpegArgumentProcessor OutputToFile(Uri uri, bool overwrite = false) => ToProcessor(new OutputArgument(uri.AbsolutePath, overwrite)); + public FFMpegArgumentProcessor OutputToPipe(IPipeDataReader reader) => ToProcessor(new OutputPipeArgument(reader)); + + public FFMpegArguments WithArgument(IArgument argument) + { + _arguments.Add(argument); + return this; + } + private FFMpegArgumentProcessor ToProcessor(IOutputArgument argument) + { + _arguments.Add(argument); + _outputArgument = argument; + return new FFMpegArgumentProcessor(this); + } + + internal void Pre() + { + _inputArgument.Pre(); + _outputArgument.Pre(); + } + internal Task During(CancellationToken? cancellationToken = null) + { + return Task.WhenAll(_inputArgument.During(cancellationToken), _outputArgument.During(cancellationToken)); + } + internal void Post() + { + _inputArgument.Post(); + _outputArgument.Post(); + } + + public TArgument? Find() where TArgument : class, IArgument + { + return _arguments.FirstOrDefault(arg => arg is TArgument) as TArgument; + } + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMPEG/FFMpegOptions.cs b/FFMpegCore/FFMpeg/FFMpegOptions.cs similarity index 78% rename from FFMpegCore/FFMPEG/FFMpegOptions.cs rename to FFMpegCore/FFMpeg/FFMpegOptions.cs index f857122..175c011 100644 --- a/FFMpegCore/FFMPEG/FFMpegOptions.cs +++ b/FFMpegCore/FFMpeg/FFMpegOptions.cs @@ -1,9 +1,9 @@ -using Newtonsoft.Json; -using System; +using System; using System.IO; using System.Runtime.InteropServices; +using System.Text.Json; -namespace FFMpegCore.FFMPEG +namespace FFMpegCore { public class FFMpegOptions { @@ -19,19 +19,13 @@ public static void Configure(Action optionsAction) public static void Configure(FFMpegOptions options) { - if (null == options) - { - throw new ArgumentNullException(nameof(options)); - } - Options = options; + Options = options ?? throw new ArgumentNullException(nameof(options)); } static FFMpegOptions() { if (File.Exists(ConfigFile)) - { - Options = JsonConvert.DeserializeObject(File.ReadAllText(ConfigFile)); - } + Options = JsonSerializer.Deserialize(File.ReadAllText(ConfigFile)); } public string RootDirectory { get; set; } = DefaultRoot; @@ -48,9 +42,7 @@ private static string FFBinary(string name) var target = Environment.Is64BitProcess ? "x64" : "x86"; if (Directory.Exists(Path.Combine(Options.RootDirectory, target))) - { ffName = Path.Combine(target, ffName); - } return Path.Combine(Options.RootDirectory, ffName); } diff --git a/FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs b/FFMpegCore/FFMpeg/Pipes/IPipeDataReader.cs similarity index 57% rename from FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs rename to FFMpegCore/FFMpeg/Pipes/IPipeDataReader.cs index 3912cb3..370124f 100644 --- a/FFMpegCore/FFMPEG/Pipes/IPipeDataReader.cs +++ b/FFMpegCore/FFMpeg/Pipes/IPipeDataReader.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; -namespace FFMpegCore.FFMPEG.Pipes +namespace FFMpegCore.Pipes { public interface IPipeDataReader { diff --git a/FFMpegCore/FFMPEG/Pipes/IPipeDataWriter.cs b/FFMpegCore/FFMpeg/Pipes/IPipeDataWriter.cs similarity index 61% rename from FFMpegCore/FFMPEG/Pipes/IPipeDataWriter.cs rename to FFMpegCore/FFMpeg/Pipes/IPipeDataWriter.cs index aa4bbc8..bd80ec4 100644 --- a/FFMpegCore/FFMPEG/Pipes/IPipeDataWriter.cs +++ b/FFMpegCore/FFMpeg/Pipes/IPipeDataWriter.cs @@ -1,10 +1,6 @@ -using FFMpegCore.FFMPEG.Argument; -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; -namespace FFMpegCore.FFMPEG.Pipes +namespace FFMpegCore.Pipes { /// /// Interface for ffmpeg pipe source data IO diff --git a/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs b/FFMpegCore/FFMpeg/Pipes/IVideoFrame.cs similarity index 69% rename from FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs rename to FFMpegCore/FFMpeg/Pipes/IVideoFrame.cs index 60de429..e3be232 100644 --- a/FFMpegCore/FFMPEG/Pipes/IVideoFrame.cs +++ b/FFMpegCore/FFMpeg/Pipes/IVideoFrame.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; -namespace FFMpegCore.FFMPEG.Pipes +namespace FFMpegCore.Pipes { /// /// Interface for Video frame diff --git a/FFMpegCore/FFMpeg/Pipes/PipeHelpers.cs b/FFMpegCore/FFMpeg/Pipes/PipeHelpers.cs new file mode 100644 index 0000000..ddd788c --- /dev/null +++ b/FFMpegCore/FFMpeg/Pipes/PipeHelpers.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace FFMpegCore.Pipes +{ + static class PipeHelpers + { + public static string GetUnqiuePipeName() => "FFMpegCore_" + Guid.NewGuid(); + + public static string GetPipePath(string pipeName) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return $@"\\.\pipe\{pipeName}"; + else + return $"unix:/tmp/CoreFxPipe_{pipeName}"; // dotnet uses unix sockets on unix, for more see https://github.com/dotnet/runtime/issues/24390 + } + } +} diff --git a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs b/FFMpegCore/FFMpeg/Pipes/RawVideoPipeDataWriter.cs similarity index 59% rename from FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs rename to FFMpegCore/FFMpeg/Pipes/RawVideoPipeDataWriter.cs index ce6bcdf..7f2737c 100644 --- a/FFMpegCore/FFMPEG/Pipes/RawVideoPipeDataWriter.cs +++ b/FFMpegCore/FFMpeg/Pipes/RawVideoPipeDataWriter.cs @@ -1,46 +1,44 @@ -using FFMpegCore.FFMPEG.Argument; -using FFMpegCore.FFMPEG.Exceptions; -using System; +using System; using System.Collections.Generic; -using System.Text; using System.Threading.Tasks; +using FFMpegCore.Exceptions; -namespace FFMpegCore.FFMPEG.Pipes +namespace FFMpegCore.Pipes { /// /// Implementation of for a raw video stream that is gathered from /// public class RawVideoPipeDataWriter : IPipeDataWriter { - public string StreamFormat { get; private set; } + public string StreamFormat { get; private set; } = null!; public int Width { get; private set; } public int Height { get; private set; } public int FrameRate { get; set; } = 25; - private bool formatInitialized = false; - private IEnumerator framesEnumerator; + private bool _formatInitialized; + private readonly IEnumerator _framesEnumerator; public RawVideoPipeDataWriter(IEnumerator framesEnumerator) { - this.framesEnumerator = framesEnumerator; + _framesEnumerator = framesEnumerator; } public RawVideoPipeDataWriter(IEnumerable framesEnumerator) : this(framesEnumerator.GetEnumerator()) { } public string GetFormat() { - if (!formatInitialized) + if (!_formatInitialized) { //see input format references https://lists.ffmpeg.org/pipermail/ffmpeg-user/2012-July/007742.html - if (framesEnumerator.Current == null) + if (_framesEnumerator.Current == null) { - if (!framesEnumerator.MoveNext()) + if (!_framesEnumerator.MoveNext()) throw new InvalidOperationException("Enumerator is empty, unable to get frame"); } - StreamFormat = framesEnumerator.Current.Format; - Width = framesEnumerator.Current.Width; - Height = framesEnumerator.Current.Height; + StreamFormat = _framesEnumerator.Current!.Format; + Width = _framesEnumerator.Current!.Width; + Height = _framesEnumerator.Current!.Height; - formatInitialized = true; + _formatInitialized = true; } return $"-f rawvideo -r {FrameRate} -pix_fmt {StreamFormat} -s {Width}x{Height}"; @@ -48,29 +46,29 @@ public string GetFormat() public void WriteData(System.IO.Stream stream) { - if (framesEnumerator.Current != null) + if (_framesEnumerator.Current != null) { - CheckFrameAndThrow(framesEnumerator.Current); - framesEnumerator.Current.Serialize(stream); + CheckFrameAndThrow(_framesEnumerator.Current); + _framesEnumerator.Current.Serialize(stream); } - while (framesEnumerator.MoveNext()) + while (_framesEnumerator.MoveNext()) { - CheckFrameAndThrow(framesEnumerator.Current); - framesEnumerator.Current.Serialize(stream); + CheckFrameAndThrow(_framesEnumerator.Current!); + _framesEnumerator.Current!.Serialize(stream); } } public async Task WriteDataAsync(System.IO.Stream stream) { - if (framesEnumerator.Current != null) + if (_framesEnumerator.Current != null) { - await framesEnumerator.Current.SerializeAsync(stream); + await _framesEnumerator.Current.SerializeAsync(stream); } - while (framesEnumerator.MoveNext()) + while (_framesEnumerator.MoveNext()) { - await framesEnumerator.Current.SerializeAsync(stream); + await _framesEnumerator.Current!.SerializeAsync(stream); } } diff --git a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs b/FFMpegCore/FFMpeg/Pipes/StreamPipeDataReader.cs similarity index 84% rename from FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs rename to FFMpegCore/FFMpeg/Pipes/StreamPipeDataReader.cs index 1c43dd2..cb39cfa 100644 --- a/FFMpegCore/FFMPEG/Pipes/StreamPipeDataReader.cs +++ b/FFMpegCore/FFMpeg/Pipes/StreamPipeDataReader.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; +using System.Threading.Tasks; -namespace FFMpegCore.FFMPEG.Pipes +namespace FFMpegCore.Pipes { public class StreamPipeDataReader : IPipeDataReader { diff --git a/FFMpegCore/FFMpeg/Pipes/StreamPipeDataWriter.cs b/FFMpegCore/FFMpeg/Pipes/StreamPipeDataWriter.cs new file mode 100644 index 0000000..af9cf7f --- /dev/null +++ b/FFMpegCore/FFMpeg/Pipes/StreamPipeDataWriter.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; + +namespace FFMpegCore.Pipes +{ + /// + /// Implementation of used for stream redirection + /// + public class StreamPipeDataWriter : IPipeDataWriter + { + public System.IO.Stream Source { get; } + public int BlockSize { get; } = 4096; + public string StreamFormat { get; } = 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() => StreamFormat; + } +} diff --git a/FFMpegCore/FFMPEG/bin/presets/ffprobe.xsd b/FFMpegCore/FFMpeg/bin/presets/ffprobe.xsd similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/ffprobe.xsd rename to FFMpegCore/FFMpeg/bin/presets/ffprobe.xsd diff --git a/FFMpegCore/FFMPEG/bin/presets/libvpx-1080p.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libvpx-1080p.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libvpx-1080p.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libvpx-1080p.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libvpx-1080p50_60.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libvpx-1080p50_60.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libvpx-1080p50_60.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libvpx-1080p50_60.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libvpx-360p.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libvpx-360p.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libvpx-360p.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libvpx-360p.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libvpx-720p.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libvpx-720p.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libvpx-720p.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libvpx-720p.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libvpx-720p50_60.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libvpx-720p50_60.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libvpx-720p50_60.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libvpx-720p50_60.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libvpx-ultrafast.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libvpx-ultrafast.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libvpx-ultrafast.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libvpx-ultrafast.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-baseline.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-baseline.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-baseline.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-baseline.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-fast.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-fast.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-fast.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-fast.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-fast_firstpass.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-fast_firstpass.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-fast_firstpass.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-fast_firstpass.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-faster.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-faster.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-faster.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-faster.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-faster_firstpass.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-faster_firstpass.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-faster_firstpass.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-faster_firstpass.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-ipod320.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-ipod320.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-ipod320.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-ipod320.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-ipod640.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-ipod640.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-ipod640.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-ipod640.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-lossless_fast.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-lossless_fast.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-lossless_fast.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-lossless_fast.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-lossless_max.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-lossless_max.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-lossless_max.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-lossless_max.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-lossless_medium.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-lossless_medium.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-lossless_medium.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-lossless_medium.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-lossless_slow.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-lossless_slow.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-lossless_slow.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-lossless_slow.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-lossless_slower.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-lossless_slower.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-lossless_slower.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-lossless_slower.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-lossless_ultrafast.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-lossless_ultrafast.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-lossless_ultrafast.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-lossless_ultrafast.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-main.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-main.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-main.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-main.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-medium.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-medium.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-medium.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-medium.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-medium_firstpass.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-medium_firstpass.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-medium_firstpass.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-medium_firstpass.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-placebo.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-placebo.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-placebo.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-placebo.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-placebo_firstpass.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-placebo_firstpass.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-placebo_firstpass.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-placebo_firstpass.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-slow.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-slow.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-slow.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-slow.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-slow_firstpass.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-slow_firstpass.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-slow_firstpass.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-slow_firstpass.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-slower.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-slower.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-slower.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-slower.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-slower_firstpass.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-slower_firstpass.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-slower_firstpass.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-slower_firstpass.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-superfast.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-superfast.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-superfast.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-superfast.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-superfast_firstpass.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-superfast_firstpass.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-superfast_firstpass.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-superfast_firstpass.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-ultrafast.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-ultrafast.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-ultrafast.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-ultrafast.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-ultrafast_firstpass.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-ultrafast_firstpass.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-ultrafast_firstpass.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-ultrafast_firstpass.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-veryfast.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-veryfast.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-veryfast.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-veryfast.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-veryfast_firstpass.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-veryfast_firstpass.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-veryfast_firstpass.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-veryfast_firstpass.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-veryslow.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-veryslow.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-veryslow.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-veryslow.ffpreset diff --git a/FFMpegCore/FFMPEG/bin/presets/libx264-veryslow_firstpass.ffpreset b/FFMpegCore/FFMpeg/bin/presets/libx264-veryslow_firstpass.ffpreset similarity index 100% rename from FFMpegCore/FFMPEG/bin/presets/libx264-veryslow_firstpass.ffpreset rename to FFMpegCore/FFMpeg/bin/presets/libx264-veryslow_firstpass.ffpreset diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index f400dc0..c3c624e 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -1,7 +1,7 @@  - netstandard2.0 + netstandard2.1 en https://github.com/rosenbjerg/FFMpegCore https://github.com/rosenbjerg/FFMpegCore @@ -10,18 +10,14 @@ 1.0.12 1.1.0.0 1.1.0.0 - - Streaming input/output support -- CustomArgument -- Fluent ArgumentContainer extensions -- Cleanup and fixes -Thanks to max619 and WeihanLi - + Major refactoring 8 - 1.4.0 + 2.0.0 Vlad Jerca, Malte Rosenbjerg ffmpeg ffprobe convert video audio mediafile resize analyze muxing GitHub true + enable @@ -32,8 +28,8 @@ Thanks to max619 and WeihanLi - + diff --git a/FFMpegCore/FFMpegCore.csproj.DotSettings b/FFMpegCore/FFMpegCore.csproj.DotSettings new file mode 100644 index 0000000..7a8d17a --- /dev/null +++ b/FFMpegCore/FFMpegCore.csproj.DotSettings @@ -0,0 +1,3 @@ + + True + True \ No newline at end of file diff --git a/FFMpegCore/FFMpegCore.nuspec b/FFMpegCore/FFMpegCore.nuspec index 5270043..35e8526 100644 --- a/FFMpegCore/FFMpegCore.nuspec +++ b/FFMpegCore/FFMpegCore.nuspec @@ -6,18 +6,13 @@ $title$ Vlad Jerca Vlad Jerca - https://github.com/vladjerca/FFMpegCore + https://github.com/rosenbjerg/FFMpegCore false $description$ - More information available @ https://github.com/vladjerca/FFMpegCore/blob/master/README.md + More information available @ https://github.com/rosenbjerg/FFMpegCore Copyright 2020 - ffmpeg video conversion FFMpegCore mp4 ogv net.core core net - - - - - + ffmpeg ffprobe video audio media conversion analysis ffmpegcore mp4 ogv diff --git a/FFMpegCore/FFProbe/AudioStream.cs b/FFMpegCore/FFProbe/AudioStream.cs new file mode 100644 index 0000000..ad1e513 --- /dev/null +++ b/FFMpegCore/FFProbe/AudioStream.cs @@ -0,0 +1,9 @@ +namespace FFMpegCore +{ + public class AudioStream : MediaStream + { + public int Channels { get; internal set; } + public string ChannelLayout { get; internal set; } = null!; + public int SampleRateHz { get; internal set; } + } +} \ No newline at end of file diff --git a/FFMpegCore/FFProbe/FFProbe.cs b/FFMpegCore/FFProbe/FFProbe.cs new file mode 100644 index 0000000..bceb4ca --- /dev/null +++ b/FFMpegCore/FFProbe/FFProbe.cs @@ -0,0 +1,96 @@ +using System; +using System.IO; +using System.Text.Json; +using System.Threading.Tasks; +using FFMpegCore.Arguments; +using FFMpegCore.Exceptions; +using FFMpegCore.Helpers; +using FFMpegCore.Pipes; +using Instances; + +namespace FFMpegCore +{ + public static class FFProbe + { + public static MediaAnalysis Analyse(string filePath, int outputCapacity = int.MaxValue) + { + using var instance = PrepareInstance(filePath, outputCapacity); + instance.BlockUntilFinished(); + return ParseOutput(filePath, instance); + } + public static MediaAnalysis Analyse(System.IO.Stream stream, int outputCapacity = int.MaxValue) + { + var streamPipeSource = new StreamPipeDataWriter(stream); + var pipeArgument = new InputPipeArgument(streamPipeSource); + using var instance = PrepareInstance(pipeArgument.PipePath, outputCapacity); + pipeArgument.Pre(); + + var task = instance.FinishedRunning(); + try + { + pipeArgument.During().ConfigureAwait(false).GetAwaiter().GetResult(); + } + catch (IOException) { } + finally + { + pipeArgument.Post(); + } + var exitCode = task.ConfigureAwait(false).GetAwaiter().GetResult(); + if (exitCode != 0) + throw new FFMpegException(FFMpegExceptionType.Process, $"FFProbe process returned exit status {exitCode}: {string.Join("\n", instance.OutputData)} {string.Join("\n", instance.ErrorData)}"); + + return ParseOutput(pipeArgument.PipePath, instance); + } + public static async Task AnalyseAsync(string filePath, int outputCapacity = int.MaxValue) + { + using var instance = PrepareInstance(filePath, outputCapacity); + await instance.FinishedRunning(); + return ParseOutput(filePath, instance); + } + public static async Task AnalyseAsync(System.IO.Stream stream, int outputCapacity = int.MaxValue) + { + var streamPipeSource = new StreamPipeDataWriter(stream); + var pipeArgument = new InputPipeArgument(streamPipeSource); + using var instance = PrepareInstance(pipeArgument.PipePath, outputCapacity); + pipeArgument.Pre(); + + var task = instance.FinishedRunning(); + try + { + await pipeArgument.During(); + } + catch(IOException) + { + } + finally + { + pipeArgument.Post(); + } + var exitCode = await task; + if (exitCode != 0) + throw new FFMpegException(FFMpegExceptionType.Process, $"FFProbe process returned exit status {exitCode}: {string.Join("\n", instance.OutputData)} {string.Join("\n", instance.ErrorData)}"); + + pipeArgument.Post(); + return ParseOutput(pipeArgument.PipePath, instance); + } + + private static MediaAnalysis ParseOutput(string filePath, Instance instance) + { + var json = string.Join(string.Empty, instance.OutputData); + var ffprobeAnalysis = JsonSerializer.Deserialize(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true + }); + return new MediaAnalysis(filePath, ffprobeAnalysis); + } + + private static Instance PrepareInstance(string filePath, int outputCapacity) + { + FFProbeHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory); + var ffprobe = FFMpegOptions.Options.FFProbeBinary; + var arguments = $"-print_format json -show_streams \"{filePath}\""; + var instance = new Instance(ffprobe, arguments) {DataBufferCapacity = outputCapacity}; + return instance; + } + } +} diff --git a/FFMpegCore/FFProbe/FFProbeAnalysis.cs b/FFMpegCore/FFProbe/FFProbeAnalysis.cs new file mode 100644 index 0000000..2a82528 --- /dev/null +++ b/FFMpegCore/FFProbe/FFProbeAnalysis.cs @@ -0,0 +1,74 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace FFMpegCore +{ + public class FFProbeAnalysis + { + [JsonPropertyName("streams")] + public List Streams { get; set; } = null!; + } + + public class Stream + { + [JsonPropertyName("index")] + public int Index { get; set; } + + [JsonPropertyName("avg_frame_rate")] + public string AvgFrameRate { get; set; } = null!; + + [JsonPropertyName("bits_per_raw_sample")] + public string BitsPerRawSample { get; set; } = null!; + + [JsonPropertyName("bit_rate")] + public string BitRate { get; set; } = null!; + + [JsonPropertyName("channels")] + public int? Channels { get; set; } + + [JsonPropertyName("channel_layout")] + public string ChannelLayout { get; set; } = null!; + + [JsonPropertyName("codec_type")] + public string CodecType { get; set; } = null!; + + [JsonPropertyName("codec_name")] + public string CodecName { get; set; } = null!; + + [JsonPropertyName("codec_long_name")] + public string CodecLongName { get; set; } = null!; + + [JsonPropertyName("display_aspect_ratio")] + public string DisplayAspectRatio { get; set; } = null!; + + [JsonPropertyName("duration")] + public string Duration { get; set; } = null!; + + [JsonPropertyName("profile")] + public string Profile { get; set; } = null!; + + [JsonPropertyName("width")] + public int? Width { get; set; } + + [JsonPropertyName("height")] + public int? Height { get; set; } + + [JsonPropertyName("r_frame_rate")] + public string FrameRate { get; set; } = null!; + + [JsonPropertyName("pix_fmt")] + public string PixelFormat { get; set; } = null!; + + [JsonPropertyName("sample_rate")] + public string SampleRate { get; set; } = null!; + + [JsonPropertyName("tags")] + public Tags Tags { get; set; } = null!; + } + + public class Tags + { + [JsonPropertyName("DURATION")] + public string Duration { get; set; } = null!; + } +} diff --git a/FFMpegCore/FFProbe/MediaAnalysis.cs b/FFMpegCore/FFProbe/MediaAnalysis.cs new file mode 100644 index 0000000..223abef --- /dev/null +++ b/FFMpegCore/FFProbe/MediaAnalysis.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FFMpegCore +{ + public class MediaAnalysis + { + internal MediaAnalysis(string path, FFProbeAnalysis analysis) + { + VideoStreams = analysis.Streams.Where(stream => stream.CodecType == "video").Select(ParseVideoStream).ToList(); + AudioStreams = analysis.Streams.Where(stream => stream.CodecType == "audio").Select(ParseAudioStream).ToList(); + PrimaryVideoStream = VideoStreams.OrderBy(stream => stream.Index).FirstOrDefault(); + PrimaryAudioStream = AudioStreams.OrderBy(stream => stream.Index).FirstOrDefault(); + Path = path; + } + + + public string Path { get; } + public string Extension => System.IO.Path.GetExtension(Path); + + public TimeSpan Duration => TimeSpan.FromSeconds(Math.Max( + PrimaryVideoStream?.Duration.TotalSeconds ?? 0, + PrimaryAudioStream?.Duration.TotalSeconds ?? 0)); + public AudioStream PrimaryAudioStream { get; } + + public VideoStream PrimaryVideoStream { get; } + + public List VideoStreams { get; } + public List AudioStreams { get; } + + private VideoStream ParseVideoStream(Stream stream) + { + return new VideoStream + { + Index = stream.Index, + AvgFrameRate = DivideRatio(ParseRatioDouble(stream.AvgFrameRate, '/')), + BitRate = !string.IsNullOrEmpty(stream.BitRate) ? int.Parse(stream.BitRate) : default, + BitsPerRawSample = !string.IsNullOrEmpty(stream.BitsPerRawSample) ? int.Parse(stream.BitsPerRawSample) : default, + CodecName = stream.CodecName, + CodecLongName = stream.CodecLongName, + DisplayAspectRatio = ParseRatioInt(stream.DisplayAspectRatio, ':'), + Duration = ParseDuration(stream), + FrameRate = DivideRatio(ParseRatioDouble(stream.FrameRate, '/')), + Height = stream.Height!.Value, + Width = stream.Width!.Value, + Profile = stream.Profile, + PixelFormat = stream.PixelFormat + }; + } + + private static TimeSpan ParseDuration(Stream stream) + { + return stream.Duration != null + ? TimeSpan.FromSeconds(double.Parse(stream.Duration)) + : TimeSpan.Parse(stream.Tags.Duration ?? "0"); + } + + private AudioStream ParseAudioStream(Stream stream) + { + return new AudioStream + { + Index = stream.Index, + BitRate = !string.IsNullOrEmpty(stream.BitRate) ? int.Parse(stream.BitRate) : default, + CodecName = stream.CodecName, + CodecLongName = stream.CodecLongName, + Channels = stream.Channels ?? default, + ChannelLayout = stream.ChannelLayout, + Duration = TimeSpan.FromSeconds(double.Parse(stream.Duration ?? stream.Tags.Duration ?? "0")), + SampleRateHz = !string.IsNullOrEmpty(stream.SampleRate) ? int.Parse(stream.SampleRate) : default + }; + } + + private static double DivideRatio((double, double) ratio) => ratio.Item1 / ratio.Item2; + private static (int, int) ParseRatioInt(string input, char separator) + { + if (string.IsNullOrEmpty(input)) return (0, 0); + var ratio = input.Split(separator); + return (int.Parse(ratio[0]), int.Parse(ratio[1])); + } + private static (double, double) ParseRatioDouble(string input, char separator) + { + if (string.IsNullOrEmpty(input)) return (0, 0); + var ratio = input.Split(separator); + return (ratio.Length > 0 ? double.Parse(ratio[0]) : 0, ratio.Length > 1 ? double.Parse(ratio[1]) : 0); + } + } +} \ No newline at end of file diff --git a/FFMpegCore/FFProbe/MediaStream.cs b/FFMpegCore/FFProbe/MediaStream.cs new file mode 100644 index 0000000..bdc32e9 --- /dev/null +++ b/FFMpegCore/FFProbe/MediaStream.cs @@ -0,0 +1,13 @@ +using System; + +namespace FFMpegCore +{ + public class MediaStream + { + public int Index { get; internal set; } + public string CodecName { get; internal set; } = null!; + public string CodecLongName { get; internal set; } = null!; + public int BitRate { get; internal set; } + public TimeSpan Duration { get; internal set; } + } +} \ No newline at end of file diff --git a/FFMpegCore/FFProbe/VideoStream.cs b/FFMpegCore/FFProbe/VideoStream.cs new file mode 100644 index 0000000..05b273c --- /dev/null +++ b/FFMpegCore/FFProbe/VideoStream.cs @@ -0,0 +1,14 @@ +namespace FFMpegCore +{ + public class VideoStream : MediaStream + { + public double AvgFrameRate { get; internal set; } + public int BitsPerRawSample { get; internal set; } + public (int Width, int Height) DisplayAspectRatio { get; internal set; } + public string Profile { get; internal set; } = null!; + public int Width { get; internal set; } + public int Height { get; internal set; } + public double FrameRate { get; internal set; } + public string PixelFormat { get; internal set; } = null!; + } +} \ No newline at end of file diff --git a/FFMpegCore/Helpers/FFMpegHelper.cs b/FFMpegCore/Helpers/FFMpegHelper.cs index 9804869..063760c 100644 --- a/FFMpegCore/Helpers/FFMpegHelper.cs +++ b/FFMpegCore/Helpers/FFMpegHelper.cs @@ -1,7 +1,7 @@ using System; using System.Drawing; using System.IO; -using FFMpegCore.FFMPEG.Exceptions; +using FFMpegCore.Exceptions; namespace FFMpegCore.Helpers { @@ -12,9 +12,9 @@ public static void ConversionSizeExceptionCheck(Image image) ConversionSizeExceptionCheck(image.Size); } - public static void ConversionSizeExceptionCheck(VideoInfo info) + public static void ConversionSizeExceptionCheck(MediaAnalysis info) { - ConversionSizeExceptionCheck(new Size(info.Width, info.Height)); + ConversionSizeExceptionCheck(new Size(info.PrimaryVideoStream.Width, info.PrimaryVideoStream.Height)); } public static void ConversionSizeExceptionCheck(Size size) @@ -28,43 +28,11 @@ public static void ConversionSizeExceptionCheck(Size size) } } - public static void OutputExistsExceptionCheck(FileInfo output) + public static void ExtensionExceptionCheck(string filename, string extension) { - if (File.Exists(output.FullName)) - { + if (!extension.Equals(Path.GetExtension(filename), StringComparison.OrdinalIgnoreCase)) throw new FFMpegException(FFMpegExceptionType.File, - $"The output file: {output} already exists!"); - } - } - - public static void InputExistsExceptionCheck(FileInfo input) - { - if (!File.Exists(input.FullName)) - { - throw new FFMpegException(FFMpegExceptionType.File, - $"Input {input.FullName} does not exist!"); - } - } - - public static void ConversionExceptionCheck(FileInfo originalVideo, FileInfo convertedPath) - { - OutputExistsExceptionCheck(convertedPath); - InputExistsExceptionCheck(originalVideo); - } - - public static void InputsExistExceptionCheck(params FileInfo[] paths) - { - foreach (var path in paths) - { - InputExistsExceptionCheck(path); - } - } - - public static void ExtensionExceptionCheck(FileInfo output, string expected) - { - if (!expected.Equals(new FileInfo(output.FullName).Extension, StringComparison.OrdinalIgnoreCase)) - throw new FFMpegException(FFMpegExceptionType.File, - $"Invalid output file. File extension should be '{expected}' required."); + $"Invalid output file. File extension should be '{extension}' required."); } public static void RootExceptionCheck(string root) diff --git a/FFMpegCore/Helpers/FFProbeHelper.cs b/FFMpegCore/Helpers/FFProbeHelper.cs index 5b6b423..eed1b7a 100644 --- a/FFMpegCore/Helpers/FFProbeHelper.cs +++ b/FFMpegCore/Helpers/FFProbeHelper.cs @@ -1,4 +1,4 @@ -using FFMpegCore.FFMPEG.Exceptions; +using FFMpegCore.Exceptions; namespace FFMpegCore.Helpers { diff --git a/FFMpegCore/ImageInfo.cs b/FFMpegCore/ImageInfo.cs index dcf0e28..f4b9aa4 100644 --- a/FFMpegCore/ImageInfo.cs +++ b/FFMpegCore/ImageInfo.cs @@ -1,8 +1,8 @@ -using FFMpegCore.Enums; -using FFMpegCore.Helpers; -using System; +using System; using System.Drawing; using System.IO; +using FFMpegCore.Enums; +using FFMpegCore.Helpers; namespace FFMpegCore { diff --git a/FFMpegCore/VideoInfo.cs b/FFMpegCore/VideoInfo.cs deleted file mode 100644 index 3a4cb75..0000000 --- a/FFMpegCore/VideoInfo.cs +++ /dev/null @@ -1,203 +0,0 @@ -using FFMpegCore.FFMPEG; -using FFMpegCore.FFMPEG.Argument; -using FFMpegCore.FFMPEG.Pipes; -using System; -using System.IO; - -namespace FFMpegCore -{ - public class VideoInfo - { - private const string NoVideoPlaceholder = "NULL"; - private FileInfo _file; - - internal VideoInfo() - { - - } - /// - /// Create a video information object from a file information object. - /// - /// Video file information. - public VideoInfo(FileInfo fileInfo, int outputCapacity = int.MaxValue) - { - fileInfo.Refresh(); - - if (!fileInfo.Exists) - throw new ArgumentException($"Input file {fileInfo.FullName} does not exist!"); - - _file = fileInfo; - - new FFProbe(outputCapacity).ParseVideoInfo(this); - } - - /// - /// Create a video information object from a target path. - /// - /// Path to video. - /// Max amount of outputlines - public VideoInfo(string path, int outputCapacity = int.MaxValue) : this(new FileInfo(path), outputCapacity) { } - - /// - /// Duration of the video file. - /// - public TimeSpan Duration { get; internal set; } - - /// - /// Audio format of the video file. - /// - public string AudioFormat { get; internal set; } - - /// - /// Video format of the video file. - /// - public string VideoFormat { get; internal set; } - - /// - /// Aspect ratio. - /// - public string Ratio { get; internal set; } - - /// - /// Video frame rate. - /// - public double FrameRate { get; internal set; } - - /// - /// Height of the video file. - /// - public int Height { get; internal set; } - - /// - /// Width of the video file. - /// - public int Width { get; internal set; } - - /// - /// Video file size in MegaBytes (MB). - /// - public double Size { get; internal set; } - - /// - /// Gets the name of the file. - /// - public string Name => _file != null ? _file.Name : throw new FileNotFoundException(); - - /// - /// Gets the full path of the file. - /// - public string FullName => _file != null ? _file.FullName : throw new FileNotFoundException(); - - /// - /// Gets the file extension. - /// - public string Extension => _file != null ? _file.Extension : throw new FileNotFoundException(); - - /// - /// Gets a flag indicating if the file is read-only. - /// - public bool IsReadOnly => _file != null ? _file.IsReadOnly : throw new FileNotFoundException(); - - /// - /// Gets a flag indicating if the file exists (no cache, per call verification). - /// - public bool Exists => _file != null ? File.Exists(FullName) : false; - - /// - /// Gets the creation date. - /// - public DateTime CreationTime => _file != null ? _file.CreationTime : throw new FileNotFoundException(); - - /// - /// Gets the parent directory information. - /// - public DirectoryInfo Directory => _file != null ? _file.Directory : throw new FileNotFoundException(); - - /// - /// Create a video information object from a file information object. - /// - /// Video file information. - /// - public static VideoInfo FromFileInfo(FileInfo fileInfo) - { - return FromPath(fileInfo.FullName); - } - - /// - /// Create a video information object from a target path. - /// - /// Path to video. - /// - public static VideoInfo FromPath(string path) - { - return new VideoInfo(path); - } - - /// - /// Create a video information object from a encoded stream. - /// - /// Encoded video stream. - /// - public static VideoInfo FromStream(System.IO.Stream stream) - { - return new FFProbe().ParseVideoInfo(stream); - } - - /// - /// Pretty prints the video information. - /// - /// - public override string ToString() - { - return "Video Path : " + (_file != null ? FullName : NoVideoPlaceholder) + Environment.NewLine + - "Video Root : " + (_file != null ? Directory.FullName : NoVideoPlaceholder) + Environment.NewLine + - "Video Name: " + (_file != null ? Name : NoVideoPlaceholder) + Environment.NewLine + - "Video Extension : " + (_file != null ? Extension : NoVideoPlaceholder) + Environment.NewLine + - "Video Duration : " + Duration + Environment.NewLine + - "Audio Format : " + AudioFormat + Environment.NewLine + - "Video Format : " + VideoFormat + Environment.NewLine + - "Aspect Ratio : " + Ratio + Environment.NewLine + - "Framerate : " + FrameRate + "fps" + Environment.NewLine + - "Resolution : " + Width + "x" + Height + Environment.NewLine + - "Size : " + Size + " MB"; - } - - /// - /// Open a file stream. - /// - /// Opens a file in a specified mode. - /// File stream of the video file. - public FileStream FileOpen(FileMode mode) - { - return _file.Open(mode); - } - - /// - /// Move file to a specific directory. - /// - /// - public void MoveTo(DirectoryInfo destination) - { - var newLocation = $"{destination.FullName}{Path.DirectorySeparatorChar}{Name}{Extension}"; - _file.MoveTo(newLocation); - _file = new FileInfo(newLocation); - } - - /// - /// Delete the file. - /// - public void Delete() - { - _file.Delete(); - } - - /// - /// Converts video info to file info. - /// - /// FileInfo - public FileInfo ToFileInfo() - { - return new FileInfo(_file.FullName); - } - } -}