From 77e24039029387a5894d558319ecc0b140922600 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Fri, 5 Mar 2021 18:06:40 +0100 Subject: [PATCH] Init Former-commit-id: 4f2898397268193555a63507a98814f61c9623df --- FFMpegCore.Test/ArgumentBuilderTest.cs | 188 ++++++++---- FFMpegCore.Test/FFProbeTests.cs | 16 +- FFMpegCore.Test/TasksExtensions.cs | 10 - .../{ => Utilities}/BitmapSources.cs | 0 FFMpegCore.Test/VideoTest.cs | 273 ++++++++---------- .../FFMpeg/Arguments/DrawTextArgument.cs | 5 +- FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs | 7 +- FFMpegCore/FFMpeg/Arguments/SizeArgument.cs | 17 +- .../FFMpeg/Arguments/TransposeArgument.cs | 5 +- .../FFMpeg/Arguments/VideoFiltersArgument.cs | 50 ++++ .../FFMpeg/Exceptions/FFMpegException.cs | 44 ++- FFMpegCore/FFMpeg/FFMpeg.cs | 16 +- FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs | 14 +- FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs | 21 +- FFMpegCore/FFMpeg/FFMpegArguments.cs | 2 - FFMpegCore/FFMpeg/FFMpegOptions.cs | 2 +- FFMpegCore/FFMpegCore.csproj.DotSettings | 3 +- FFMpegCore/FFProbe/FFProbe.cs | 49 ++-- FFMpegCore/FFProbe/IMediaAnalysis.cs | 6 +- FFMpegCore/FFProbe/MediaAnalysis.cs | 12 +- FFMpegCore/Helpers/FFMpegHelper.cs | 16 +- 21 files changed, 432 insertions(+), 324 deletions(-) delete mode 100644 FFMpegCore.Test/TasksExtensions.cs rename FFMpegCore.Test/{ => Utilities}/BitmapSources.cs (100%) create mode 100644 FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index de625e2..543354f 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -8,7 +8,7 @@ namespace FFMpegCore.Test [TestClass] public class ArgumentBuilderTest { - private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4"}; + private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4" }; [TestMethod] @@ -21,28 +21,35 @@ public void Builder_BuildString_IO_1() [TestMethod] public void Builder_BuildString_Scale() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", true, opt => opt.Scale(VideoSize.Hd)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", true, opt => opt + .WithVideoFilters(filterOptions => filterOptions + .Scale(VideoSize.Hd))) + .Arguments; Assert.AreEqual("-i \"input.mp4\" -vf scale=-1:720 \"output.mp4\" -y", str); } - + [TestMethod] public void Builder_BuildString_AudioCodec() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", true, opt => opt.WithAudioCodec(AudioCodec.Aac)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", true, opt => opt.WithAudioCodec(AudioCodec.Aac)).Arguments; Assert.AreEqual("-i \"input.mp4\" -c:a aac \"output.mp4\" -y", str); } - + [TestMethod] public void Builder_BuildString_AudioBitrate() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", true, opt => opt.WithAudioBitrate(AudioQuality.Normal)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", true, opt => opt.WithAudioBitrate(AudioQuality.Normal)).Arguments; Assert.AreEqual("-i \"input.mp4\" -b:a 128k \"output.mp4\" -y", str); } - + [TestMethod] public void Builder_BuildString_Quiet() { - var str = FFMpegArguments.FromFileInput("input.mp4").WithGlobalOptions(opt => opt.WithVerbosityLevel()).OutputToFile("output.mp4", false).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4").WithGlobalOptions(opt => opt.WithVerbosityLevel()) + .OutputToFile("output.mp4", false).Arguments; Assert.AreEqual("-hide_banner -loglevel error -i \"input.mp4\" \"output.mp4\"", str); } @@ -50,27 +57,32 @@ public void Builder_BuildString_Quiet() [TestMethod] public void Builder_BuildString_AudioCodec_Fluent() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithAudioCodec(AudioCodec.Aac).WithAudioBitrate(128)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, + opt => opt.WithAudioCodec(AudioCodec.Aac).WithAudioBitrate(128)).Arguments; Assert.AreEqual("-i \"input.mp4\" -c:a aac -b:a 128k \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_BitStream() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithBitStreamFilter(Channel.Audio, Filter.H264_Mp4ToAnnexB)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, + opt => opt.WithBitStreamFilter(Channel.Audio, Filter.H264_Mp4ToAnnexB)).Arguments; Assert.AreEqual("-i \"input.mp4\" -bsf:a h264_mp4toannexb \"output.mp4\"", str); } - + [TestMethod] public void Builder_BuildString_HardwareAcceleration_Auto() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithHardwareAcceleration()).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithHardwareAcceleration()).Arguments; Assert.AreEqual("-i \"input.mp4\" -hwaccel \"output.mp4\"", str); } + [TestMethod] public void Builder_BuildString_HardwareAcceleration_Specific() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithHardwareAcceleration(HardwareAccelerationDevice.CUVID)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, + opt => opt.WithHardwareAcceleration(HardwareAccelerationDevice.CUVID)).Arguments; Assert.AreEqual("-i \"input.mp4\" -hwaccel cuvid \"output.mp4\"", str); } @@ -84,140 +96,175 @@ public void Builder_BuildString_Concat() [TestMethod] public void Builder_BuildString_Copy_Audio() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.CopyChannel(Channel.Audio)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.CopyChannel(Channel.Audio)).Arguments; Assert.AreEqual("-i \"input.mp4\" -c:a copy \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Copy_Video() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.CopyChannel(Channel.Video)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.CopyChannel(Channel.Video)).Arguments; Assert.AreEqual("-i \"input.mp4\" -c:v copy \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Copy_Both() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.CopyChannel()).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.CopyChannel()).Arguments; Assert.AreEqual("-i \"input.mp4\" -c copy \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_DisableChannel_Audio() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.DisableChannel(Channel.Audio)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.DisableChannel(Channel.Audio)).Arguments; Assert.AreEqual("-i \"input.mp4\" -an \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_DisableChannel_Video() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.DisableChannel(Channel.Video)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.DisableChannel(Channel.Video)).Arguments; Assert.AreEqual("-i \"input.mp4\" -vn \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_AudioSamplingRate_Default() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithAudioSamplingRate()).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithAudioSamplingRate()).Arguments; Assert.AreEqual("-i \"input.mp4\" -ar 48000 \"output.mp4\"", str); } - + [TestMethod] public void Builder_BuildString_AudioSamplingRate() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithAudioSamplingRate(44000)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithAudioSamplingRate(44000)).Arguments; Assert.AreEqual("-i \"input.mp4\" -ar 44000 \"output.mp4\"", str); } - + [TestMethod] public void Builder_BuildString_VariableBitrate() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithVariableBitrate(5)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithVariableBitrate(5)).Arguments; Assert.AreEqual("-i \"input.mp4\" -vbr 5 \"output.mp4\"", str); } - + [TestMethod] public void Builder_BuildString_Faststart() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithFastStart()).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithFastStart()).Arguments; Assert.AreEqual("-i \"input.mp4\" -movflags faststart \"output.mp4\"", str); } - + [TestMethod] public void Builder_BuildString_Overwrite() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.OverwriteExisting()).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.OverwriteExisting()).Arguments; Assert.AreEqual("-i \"input.mp4\" -y \"output.mp4\"", str); } - + [TestMethod] public void Builder_BuildString_RemoveMetadata() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithoutMetadata()).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithoutMetadata()).Arguments; Assert.AreEqual("-i \"input.mp4\" -map_metadata -1 \"output.mp4\"", str); } - + [TestMethod] public void Builder_BuildString_Transpose() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.Transpose(Transposition.CounterClockwise90)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt + .WithVideoFilters(filterOptions => filterOptions + .Transpose(Transposition.CounterClockwise90))) + .Arguments; Assert.AreEqual("-i \"input.mp4\" -vf \"transpose=2\" \"output.mp4\"", str); } + [TestMethod] + public void Builder_BuildString_TransposeScale() + { + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt + .WithVideoFilters(filterOptions => filterOptions + .Transpose(Transposition.CounterClockwise90) + .Scale(200, 300))) + .Arguments; + Assert.AreEqual("-i \"input.mp4\" -vf \"transpose=2, scale=200:300\" \"output.mp4\"", str); + } + [TestMethod] public void Builder_BuildString_ForceFormat() { - var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.ForceFormat(VideoType.Mp4)).OutputToFile("output.mp4", false, opt => opt.ForceFormat(VideoType.Mp4)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.ForceFormat(VideoType.Mp4)) + .OutputToFile("output.mp4", false, opt => opt.ForceFormat(VideoType.Mp4)).Arguments; Assert.AreEqual("-f mp4 -i \"input.mp4\" -f mp4 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_FrameOutputCount() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithFrameOutputCount(50)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithFrameOutputCount(50)).Arguments; Assert.AreEqual("-i \"input.mp4\" -vframes 50 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_FrameRate() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithFramerate(50)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithFramerate(50)).Arguments; Assert.AreEqual("-i \"input.mp4\" -r 50 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Loop() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.Loop(50)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.Loop(50)) + .Arguments; Assert.AreEqual("-i \"input.mp4\" -loop 50 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Seek() { - var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))).OutputToFile("output.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))) + .OutputToFile("output.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))).Arguments; Assert.AreEqual("-ss 00:00:10 -i \"input.mp4\" -ss 00:00:10 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Shortest() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.UsingShortest()).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.UsingShortest()).Arguments; Assert.AreEqual("-i \"input.mp4\" -shortest \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Size() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.Resize(1920, 1080)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.Resize(1920, 1080)).Arguments; Assert.AreEqual("-i \"input.mp4\" -s 1920x1080 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Speed() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithSpeedPreset(Speed.Fast)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithSpeedPreset(Speed.Fast)).Arguments; Assert.AreEqual("-i \"input.mp4\" -preset fast \"output.mp4\"", str); } @@ -227,18 +274,21 @@ public void Builder_BuildString_DrawtextFilter() var str = FFMpegArguments .FromFileInput("input.mp4") .OutputToFile("output.mp4", false, opt => opt - .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"))) + .WithVideoFilters(filterOptions => filterOptions + .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")))) .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] @@ -247,45 +297,53 @@ public void Builder_BuildString_DrawtextFilter_Alt() var str = FFMpegArguments .FromFileInput("input.mp4") .OutputToFile("output.mp4", false, opt => opt - .DrawText(DrawTextOptions - .Create("Stack Overflow", "/path/to/font.ttf", ("fontcolor", "white"), ("fontsize", "24")))) + .WithVideoFilters(filterOptions => filterOptions + .DrawText(DrawTextOptions + .Create("Stack Overflow", "/path/to/font.ttf", ("fontcolor", "white"), ("fontsize", "24"))))) .Arguments; - Assert.AreEqual("-i \"input.mp4\" -vf drawtext=\"text='Stack Overflow':fontfile=/path/to/font.ttf:fontcolor=white:fontsize=24\" \"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 = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithStartNumber(50)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithStartNumber(50)).Arguments; Assert.AreEqual("-i \"input.mp4\" -start_number 50 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Threads_1() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.UsingThreads(50)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.UsingThreads(50)).Arguments; Assert.AreEqual("-i \"input.mp4\" -threads 50 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Threads_2() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.UsingMultithreading(true)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.UsingMultithreading(true)).Arguments; Assert.AreEqual($"-i \"input.mp4\" -threads {Environment.ProcessorCount} \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Codec() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithVideoCodec(VideoCodec.LibX264)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithVideoCodec(VideoCodec.LibX264)).Arguments; Assert.AreEqual("-i \"input.mp4\" -c:v libx264 \"output.mp4\"", str); } [TestMethod] public void Builder_BuildString_Codec_Override() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", true, opt => opt.WithVideoCodec(VideoCodec.LibX264).ForcePixelFormat("yuv420p")).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", true, + opt => opt.WithVideoCodec(VideoCodec.LibX264).ForcePixelFormat("yuv420p")).Arguments; Assert.AreEqual("-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\" -y", str); } @@ -293,17 +351,20 @@ public void Builder_BuildString_Codec_Override() [TestMethod] public void Builder_BuildString_Duration() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithDuration(TimeSpan.FromSeconds(20))).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithDuration(TimeSpan.FromSeconds(20))).Arguments; Assert.AreEqual("-i \"input.mp4\" -t 00:00:20 \"output.mp4\"", str); } - + [TestMethod] public void Builder_BuildString_Raw() { - var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.WithCustomArgument(null!)).OutputToFile("output.mp4", false, opt => opt.WithCustomArgument(null!)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.WithCustomArgument(null!)) + .OutputToFile("output.mp4", false, opt => opt.WithCustomArgument(null!)).Arguments; Assert.AreEqual(" -i \"input.mp4\" \"output.mp4\"", str); - str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.WithCustomArgument("-acodec copy")).Arguments; + str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.WithCustomArgument("-acodec copy")).Arguments; Assert.AreEqual("-i \"input.mp4\" -acodec copy \"output.mp4\"", str); } @@ -311,7 +372,8 @@ public void Builder_BuildString_Raw() [TestMethod] public void Builder_BuildString_ForcePixelFormat() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.ForcePixelFormat("yuv444p")).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4") + .OutputToFile("output.mp4", false, opt => opt.ForcePixelFormat("yuv444p")).Arguments; Assert.AreEqual("-i \"input.mp4\" -pix_fmt yuv444p \"output.mp4\"", str); } } diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index 21d9d34..5ac83a1 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Threading.Tasks; using FFMpegCore.Test.Resources; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -23,16 +24,21 @@ public async Task Audio_FromStream_Duration() var streamAnalysis = await FFProbe.AnalyseAsync(inputStream); Assert.IsTrue(fileAnalysis.Duration == streamAnalysis.Duration); } + + [TestMethod] + public async Task Uri_Duration() + { + var fileAnalysis = await FFProbe.AnalyseAsync(new Uri("https://github.com/rosenbjerg/FFMpegCore/raw/master/FFMpegCore.Test/Resources/input_3sec.webm")); + Assert.IsNotNull(fileAnalysis); + } [TestMethod] public void Probe_Success() { var info = FFProbe.Analyse(TestResources.Mp4Video); Assert.AreEqual(3, info.Duration.Seconds); - Assert.AreEqual(".mp4", info.Extension); - Assert.AreEqual(TestResources.Mp4Video, info.Path); - Assert.AreEqual("5.1", info.PrimaryAudioStream.ChannelLayout); + 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); @@ -40,7 +46,7 @@ public void Probe_Success() Assert.AreEqual(377351, info.PrimaryAudioStream.BitRate); Assert.AreEqual(48000, info.PrimaryAudioStream.SampleRateHz); - Assert.AreEqual(1471810, info.PrimaryVideoStream.BitRate); + Assert.AreEqual(1471810, info.PrimaryVideoStream!.BitRate); Assert.AreEqual(16, info.PrimaryVideoStream.DisplayAspectRatio.Width); Assert.AreEqual(9, info.PrimaryVideoStream.DisplayAspectRatio.Height); Assert.AreEqual("yuv420p", info.PrimaryVideoStream.PixelFormat); diff --git a/FFMpegCore.Test/TasksExtensions.cs b/FFMpegCore.Test/TasksExtensions.cs deleted file mode 100644 index c9549ca..0000000 --- a/FFMpegCore.Test/TasksExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Threading.Tasks; - -namespace FFMpegCore.Test -{ - static class TasksExtensions - { - public static T WaitForResult(this Task task) => - task.ConfigureAwait(false).GetAwaiter().GetResult(); - } -} diff --git a/FFMpegCore.Test/BitmapSources.cs b/FFMpegCore.Test/Utilities/BitmapSources.cs similarity index 100% rename from FFMpegCore.Test/BitmapSources.cs rename to FFMpegCore.Test/Utilities/BitmapSources.cs diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index eb5b46b..ec80fe3 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -57,95 +57,6 @@ public bool Convert(ContainerFormat type, bool multithreaded = false, VideoSize ); } - private void ConvertFromStreamPipe(ContainerFormat type, params IArgument[] arguments) - { - using var outputFile = new TemporaryFile($"out{type.Extension}"); - - var input = FFProbe.Analyse(TestResources.WebmVideo); - using var inputStream = File.OpenRead(input.Path); - var processor = FFMpegArguments - .FromPipeInput(new StreamPipeSource(inputStream)) - .OutputToFile(outputFile, false, opt => - { - foreach (var arg in arguments) - opt.WithArgument(arg); - }); - - var scaling = arguments.OfType().FirstOrDefault(); - - var success = processor.ProcessSynchronously(); - - var outputVideo = FFProbe.Analyse(outputFile); - - Assert.IsTrue(success); - Assert.IsTrue(File.Exists(outputFile)); - Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate); - - if (scaling?.Size == null) - { - Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); - Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height); - } - else - { - if (scaling.Size.Value.Width != -1) - { - Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width); - } - - if (scaling.Size.Value.Height != -1) - { - Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height); - } - - Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); - Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height); - } - } - - private void ConvertToStreamPipe(params IArgument[] arguments) - { - using var ms = new MemoryStream(); - var processor = FFMpegArguments - .FromFileInput(TestResources.Mp4Video) - .OutputToPipe(new StreamPipeSink(ms), opt => - { - foreach (var arg in arguments) - opt.WithArgument(arg); - }); - - var scaling = arguments.OfType().FirstOrDefault(); - - processor.ProcessSynchronously(); - - ms.Position = 0; - var outputVideo = FFProbe.Analyse(ms); - - var input = FFProbe.Analyse(TestResources.Mp4Video); - // Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate); - - if (scaling?.Size == null) - { - Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); - Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height); - } - else - { - if (scaling.Size.Value.Width != -1) - { - Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width); - } - - if (scaling.Size.Value.Height != -1) - { - Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height); - } - - Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); - Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, input.PrimaryVideoStream.Height); - } - } - public void Convert(ContainerFormat type, Action validationMethod, params IArgument[] arguments) { using var outputFile = new TemporaryFile($"out{type.Extension}"); @@ -195,45 +106,6 @@ public void Convert(ContainerFormat type, params IArgument[] inputArguments) Convert(type, null, inputArguments); } - public void ConvertFromPipe(ContainerFormat type, System.Drawing.Imaging.PixelFormat fmt, params IArgument[] arguments) - { - using var outputFile = new TemporaryFile($"out{type.Extension}"); - - var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, fmt, 256, 256)); - var processor = FFMpegArguments.FromPipeInput(videoFramesSource).OutputToFile(outputFile, false, opt => - { - foreach (var arg in arguments) - opt.WithArgument(arg); - }); - var scaling = arguments.OfType().FirstOrDefault(); - processor.ProcessSynchronously(); - - var outputVideo = FFProbe.Analyse(outputFile); - - Assert.IsTrue(File.Exists(outputFile)); - - if (scaling?.Size == null) - { - Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, videoFramesSource.Width); - Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, videoFramesSource.Height); - } - else - { - if (scaling.Size.Value.Width != -1) - { - Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, scaling.Size.Value.Width); - } - - if (scaling.Size.Value.Height != -1) - { - Assert.AreEqual(outputVideo.PrimaryVideoStream.Height, scaling.Size.Value.Height); - } - - Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Width, videoFramesSource.Width); - Assert.AreNotEqual(outputVideo.PrimaryVideoStream.Height, videoFramesSource.Height); - } - } - [TestMethod, Timeout(10000)] public void Video_ToMP4() { @@ -258,13 +130,31 @@ public void Video_ToMP4_Args() [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat) { - ConvertFromPipe(VideoType.Mp4, pixelFormat, new VideoCodecArgument(VideoCodec.LibX264)); + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256)); + var success = FFMpegArguments + .FromPipeInput(videoFramesSource) + .OutputToFile(outputFile, false, opt => opt + .WithVideoCodec(VideoCodec.LibX264)) + .ProcessSynchronously(); + Assert.IsTrue(success); } [TestMethod, Timeout(10000)] public void Video_ToMP4_Args_StreamPipe() { - ConvertFromStreamPipe(VideoType.Mp4, new VideoCodecArgument(VideoCodec.LibX264)); + using var input = File.OpenRead(TestResources.WebmVideo); + using var output = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var success = FFMpegArguments + .FromPipeInput(new StreamPipeSource(input)) + .OutputToFile(output, false, opt => opt + .WithVideoCodec(VideoCodec.LibX264)) + .ProcessSynchronously(); + Assert.IsTrue(success); + + var outputVideo = FFProbe.Analyse(output); } [TestMethod, Timeout(10000)] @@ -286,8 +176,9 @@ public void Video_StreamFile_OutputToMemoryStream() var output = new MemoryStream(); FFMpegArguments - .FromPipeInput(new StreamPipeSource(File.OpenRead(TestResources.WebmVideo)), options => options.ForceFormat("webm")) - .OutputToPipe(new StreamPipeSink(output), options => options + .FromPipeInput(new StreamPipeSource(File.OpenRead(TestResources.WebmVideo)), opt => opt + .ForceFormat("webm")) + .OutputToPipe(new StreamPipeSink(output), opt => opt .ForceFormat("mpegts")) .ProcessSynchronously(); @@ -299,22 +190,31 @@ public void Video_StreamFile_OutputToMemoryStream() [TestMethod, Timeout(10000)] public void Video_ToMP4_Args_StreamOutputPipe_Failure() { - Assert.ThrowsException(() => ConvertToStreamPipe(new ForceFormatArgument("mkv"))); + Assert.ThrowsException(() => + { + using var ms = new MemoryStream(); + var processor = FFMpegArguments + .FromFileInput(TestResources.Mp4Video) + .OutputToPipe(new StreamPipeSink(ms), opt => opt + .ForceFormat("mkv")) + .ProcessSynchronously(); + ms.Position = 0; + var outputVideo = FFProbe.Analyse(ms); + }); } [TestMethod, Timeout(10000)] - public void Video_ToMP4_Args_StreamOutputPipe_Async() + public async Task Video_ToMP4_Args_StreamOutputPipe_Async() { - using var ms = new MemoryStream(); + await using var ms = new MemoryStream(); var pipeSource = new StreamPipeSink(ms); - FFMpegArguments + await FFMpegArguments .FromFileInput(TestResources.Mp4Video) .OutputToPipe(pipeSource, opt => opt .WithVideoCodec(VideoCodec.LibX264) .ForceFormat("matroska")) - .ProcessAsynchronously() - .WaitForResult(); + .ProcessAsynchronously(); } [TestMethod, Timeout(10000)] @@ -334,7 +234,19 @@ await FFMpegArguments.FromFileInput(TestResources.Mp4Video) [TestMethod, Timeout(10000)] public void Video_ToMP4_Args_StreamOutputPipe() { - ConvertToStreamPipe(new VideoCodecArgument(VideoCodec.LibX264), new ForceFormatArgument("matroska")); + using var input = new MemoryStream(); + var success = FFMpegArguments + .FromFileInput(TestResources.Mp4Video) + .OutputToPipe(new StreamPipeSink(input), opt => opt + .WithVideoCodec(VideoCodec.LibVpx) + .ForceFormat("matroska")) + .ProcessSynchronously(); + Assert.IsTrue(success); + + input.Position = 0; + var inputAnalysis = FFProbe.Analyse(TestResources.Mp4Video); + var outputAnalysis = FFProbe.Analyse(input); + Assert.AreEqual(inputAnalysis.Duration.TotalSeconds, outputAnalysis.Duration.TotalSeconds, 0.3); } [TestMethod, Timeout(10000)] @@ -355,42 +267,73 @@ public void Video_ToTS_Args() [DataTestMethod, Timeout(10000)] [DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] - public void Video_ToTS_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat) + public async Task Video_ToTS_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat) { - ConvertFromPipe(VideoType.Ts, pixelFormat, new ForceFormatArgument(VideoType.Ts)); + using var output = new TemporaryFile($"out{VideoType.Ts.Extension}"); + var input = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256)); + + var success = await FFMpegArguments + .FromPipeInput(input) + .OutputToFile(output, false, opt => opt + .ForceFormat(VideoType.Ts)) + .ProcessAsynchronously(); + Assert.IsTrue(success); + + var analysis = await FFProbe.AnalyseAsync(output); + Assert.AreEqual(VideoType.Ts.Name, analysis.Format.FormatName); } [TestMethod, Timeout(10000)] - public void Video_ToOGV_Resize() + public async Task Video_ToOGV_Resize() { - Convert(VideoType.Ogv, true, VideoSize.Ed); - } - - [TestMethod, Timeout(10000)] - public void Video_ToOGV_Resize_Args() - { - Convert(VideoType.Ogv, new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora)); + using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}"); + var success = await FFMpegArguments + .FromFileInput(TestResources.Mp4Video) + .OutputToFile(outputFile, false, opt => opt + .Resize(VideoSize.Ed) + .WithVideoCodec(VideoCodec.LibTheora)) + .ProcessAsynchronously(); + Assert.IsTrue(success); } [DataTestMethod, Timeout(10000)] [DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] // [DataRow(PixelFormat.Format48bppRgb)] - public void Video_ToOGV_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat) + public void RawVideoPipeSource_Ogv_Scale(System.Drawing.Imaging.PixelFormat pixelFormat) { - ConvertFromPipe(VideoType.Ogv, pixelFormat, new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora)); + using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}"); + var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256)); + + FFMpegArguments + .FromPipeInput(videoFramesSource) + .OutputToFile(outputFile, false, opt => opt + .WithVideoFilters(filterOptions => filterOptions + .Scale(VideoSize.Ed)) + .WithVideoCodec(VideoCodec.LibTheora)) + .ProcessSynchronously(); + + var analysis = FFProbe.Analyse(outputFile); + Assert.Equals((int)VideoSize.Ed, analysis!.PrimaryVideoStream.Width); } [TestMethod, Timeout(10000)] - public void Video_ToMP4_Resize() + public void Scale_Mp4_Multithreaded() { - Convert(VideoType.Mp4, true, VideoSize.Ed); - } + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + + var success = FFMpegArguments + .FromFileInput(TestResources.Mp4Video) + .OutputToFile(outputFile, false, opt => opt + .UsingMultithreading(true) + .WithVideoFilters(filterOptions => filterOptions + .Scale(VideoSize.Ld)) + .WithVideoCodec(VideoCodec.LibX264)) + .ProcessSynchronously(); + Assert.IsTrue(success); - [TestMethod, Timeout(10000)] - public void Video_ToMP4_Resize_Args() - { - Convert(VideoType.Mp4, new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264)); + var analysis = FFProbe.Analyse(outputFile); + Assert.AreEqual((int)VideoSize.Ld, analysis!.PrimaryVideoStream.Width); } [DataTestMethod, Timeout(10000)] @@ -399,7 +342,19 @@ public void Video_ToMP4_Resize_Args() // [DataRow(PixelFormat.Format48bppRgb)] public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat) { - ConvertFromPipe(VideoType.Mp4, pixelFormat, new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264)); + using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); + var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256)); + + var success = FFMpegArguments + .FromPipeInput(videoFramesSource) + .OutputToFile(outputFile, false, opt => opt + .Resize(VideoSize.Ld) + .WithVideoCodec(VideoCodec.LibX264)) + .ProcessSynchronously(); + Assert.IsTrue(success); + + var analysis = FFProbe.Analyse(outputFile); + Assert.AreEqual((int)VideoSize.Ld, analysis!.PrimaryVideoStream.Width); } [TestMethod, Timeout(10000)] diff --git a/FFMpegCore/FFMpeg/Arguments/DrawTextArgument.cs b/FFMpegCore/FFMpeg/Arguments/DrawTextArgument.cs index d4eabb8..c148328 100644 --- a/FFMpegCore/FFMpeg/Arguments/DrawTextArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/DrawTextArgument.cs @@ -6,7 +6,7 @@ namespace FFMpegCore.Arguments /// /// Drawtext video filter argument /// - public class DrawTextArgument : IArgument + public class DrawTextArgument : IVideoFilterArgument { public readonly DrawTextOptions Options; @@ -15,7 +15,8 @@ public DrawTextArgument(DrawTextOptions options) Options = options; } - public string Text => $"-vf drawtext=\"{Options.TextInternal}\""; + public string Key { get; } = "drawtext"; + public string Value => Options.TextInternal; } public class DrawTextOptions diff --git a/FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs b/FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs index 40e98d0..6ed2b31 100644 --- a/FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/ScaleArgument.cs @@ -6,7 +6,7 @@ namespace FFMpegCore.Arguments /// /// Represents scale parameter /// - public class ScaleArgument : IArgument + public class ScaleArgument : IVideoFilterArgument { public readonly Size? Size; public ScaleArgument(Size? size) @@ -18,9 +18,10 @@ 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); + Size = videosize == VideoSize.Original ? null : (Size?)new Size(-1, (int)videosize); } - public virtual string Text => Size.HasValue ? $"-vf scale={Size.Value.Width}:{Size.Value.Height}" : string.Empty; + public string Key { get; } = "scale"; + public string Value => Size == null ? string.Empty : $"{Size.Value.Width}:{Size.Value.Height}"; } } diff --git a/FFMpegCore/FFMpeg/Arguments/SizeArgument.cs b/FFMpegCore/FFMpeg/Arguments/SizeArgument.cs index 2ccde92..e22f29c 100644 --- a/FFMpegCore/FFMpeg/Arguments/SizeArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/SizeArgument.cs @@ -6,14 +6,21 @@ namespace FFMpegCore.Arguments /// /// Represents size parameter /// - public class SizeArgument : ScaleArgument + public class SizeArgument : IArgument { - public SizeArgument(Size? value) : base(value) { } + public readonly Size? Size; + public SizeArgument(Size? size) + { + Size = size; + } - public SizeArgument(VideoSize videosize) : base(videosize) { } + public SizeArgument(int width, int height) : this(new Size(width, height)) { } - public SizeArgument(int width, int height) : base(width, height) { } + public SizeArgument(VideoSize videosize) + { + Size = videosize == VideoSize.Original ? new Size(-1, -1) : new Size(-1, (int)videosize); + } - public override string Text => Size.HasValue ? $"-s {Size.Value.Width}x{Size.Value.Height}" : string.Empty; + public string Text => Size == null ? string.Empty : $"-s {Size.Value.Width}x{Size.Value.Height}"; } } diff --git a/FFMpegCore/FFMpeg/Arguments/TransposeArgument.cs b/FFMpegCore/FFMpeg/Arguments/TransposeArgument.cs index acc26f4..bd15c47 100644 --- a/FFMpegCore/FFMpeg/Arguments/TransposeArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/TransposeArgument.cs @@ -9,7 +9,7 @@ namespace FFMpegCore.Arguments /// 2 = 90CounterClockwise /// 3 = 90Clockwise and Vertical Flip /// - public class TransposeArgument : IArgument + public class TransposeArgument : IVideoFilterArgument { public readonly Transposition Transposition; public TransposeArgument(Transposition transposition) @@ -17,6 +17,7 @@ public TransposeArgument(Transposition transposition) Transposition = transposition; } - public string Text => $"-vf \"transpose={(int)Transposition}\""; + public string Key { get; } = "transpose"; + public string Value => ((int)Transposition).ToString(); } } \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs b/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs new file mode 100644 index 0000000..317d4be --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using FFMpegCore.Enums; +using FFMpegCore.Exceptions; + +namespace FFMpegCore.Arguments +{ + public class VideoFiltersArgument : IArgument + { + public readonly VideoFilterOptions Options; + + public VideoFiltersArgument(VideoFilterOptions options) + { + Options = options; + } + public string Text { get; set; } + + public string GetText() + { + if (!Options.Arguments.Any()) + throw new FFMpegArgumentException("No video-filter arguments provided"); + + return $"-vf \"{string.Join(", ", Options.Arguments.Where(arg => !string.IsNullOrEmpty(arg.Value)).Select(arg => $"{arg.Key}={arg.Value.Replace(",", "\\,")}"))}\""; + } + } + + public interface IVideoFilterArgument + { + public string Key { get; } + public string Value { get; } + } + + public class VideoFilterOptions + { + public List Arguments { get; } = new List(); + + public VideoFilterOptions Scale(VideoSize videoSize) => WithArgument(new ScaleArgument(videoSize)); + public VideoFilterOptions Scale(int width, int height) => WithArgument(new ScaleArgument(width, height)); + public VideoFilterOptions Scale(Size size) => WithArgument(new ScaleArgument(size)); + public VideoFilterOptions Transpose(Transposition transposition) => WithArgument(new TransposeArgument(transposition)); + public VideoFilterOptions DrawText(DrawTextOptions drawTextOptions) => WithArgument(new DrawTextArgument(drawTextOptions)); + + private VideoFilterOptions WithArgument(IVideoFilterArgument argument) + { + Arguments.Add(argument); + return this; + } + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/Exceptions/FFMpegException.cs b/FFMpegCore/FFMpeg/Exceptions/FFMpegException.cs index fc154ac..bf7c41f 100644 --- a/FFMpegCore/FFMpeg/Exceptions/FFMpegException.cs +++ b/FFMpegCore/FFMpeg/Exceptions/FFMpegException.cs @@ -11,18 +11,52 @@ public enum FFMpegExceptionType Process } + public abstract class FFException : Exception + { + protected FFException(string message) : base(message) { } + protected FFException(string message, Exception innerException) : base(message, innerException) { } + } + public abstract class FFProcessException : FFException + { + protected FFProcessException(string process, int exitCode, string errorOutput) + : base($"{process} exited with non-zero exit-code {exitCode}\n{errorOutput}") + { + ExitCode = exitCode; + ErrorOutput = errorOutput; + } + + public int ExitCode { get; } + public string ErrorOutput { get; } + } + public class FFMpegProcessException : FFProcessException + { + public FFMpegProcessException(int exitCode, string errorOutput) + : base("ffmpeg", exitCode, errorOutput) { } + } + public class FFProbeProcessException : FFProcessException + { + public FFProbeProcessException(int exitCode, string errorOutput) + : base("ffprobe", exitCode, errorOutput) { } + } + public class FFMpegException : Exception { - public FFMpegException(FFMpegExceptionType type, string? message = null, Exception? innerException = null, string ffmpegErrorOutput = "", string ffmpegOutput = "") + public FFMpegException(FFMpegExceptionType type, string? message = null, Exception? innerException = null, string ffMpegErrorOutput = "") : base(message, innerException) { - FfmpegOutput = ffmpegOutput; - FfmpegErrorOutput = ffmpegErrorOutput; + FFMpegErrorOutput = ffMpegErrorOutput; Type = type; } public FFMpegExceptionType Type { get; } - public string FfmpegOutput { get; } - public string FfmpegErrorOutput { get; } + public string FFMpegErrorOutput { get; } + } + + public class FFMpegArgumentException : Exception + { + public FFMpegArgumentException(string? message = null, Exception? innerException = null) + : base(message, innerException) + { + } } } \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs index 90c2265..5de955a 100644 --- a/FFMpegCore/FFMpeg/FFMpeg.cs +++ b/FFMpegCore/FFMpeg/FFMpeg.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using FFMpegCore.Arguments; namespace FFMpegCore { @@ -171,7 +172,8 @@ public static bool Convert( .UsingMultithreading(multithreaded) .WithVideoCodec(VideoCodec.LibX264) .WithVideoBitrate(2400) - .Scale(outputSize) + .WithVideoFilters(filterOptions => filterOptions + .Scale(outputSize)) .WithSpeedPreset(speed) .WithAudioCodec(AudioCodec.Aac) .WithAudioBitrate(audioQuality)) @@ -182,7 +184,8 @@ public static bool Convert( .UsingMultithreading(multithreaded) .WithVideoCodec(VideoCodec.LibTheora) .WithVideoBitrate(2400) - .Scale(outputSize) + .WithVideoFilters(filterOptions => filterOptions + .Scale(outputSize)) .WithSpeedPreset(speed) .WithAudioCodec(AudioCodec.LibVorbis) .WithAudioBitrate(audioQuality)) @@ -200,7 +203,8 @@ public static bool Convert( .UsingMultithreading(multithreaded) .WithVideoCodec(VideoCodec.LibVpx) .WithVideoBitrate(2400) - .Scale(outputSize) + .WithVideoFilters(filterOptions => filterOptions + .Scale(outputSize)) .WithSpeedPreset(speed) .WithAudioCodec(AudioCodec.LibVorbis) .WithAudioBitrate(audioQuality)) @@ -398,7 +402,7 @@ internal static IReadOnlyList GetPixelFormatsInternal() FFMpegHelper.RootExceptionCheck(); var list = new List(); - using var instance = new Instances.Instance(FFMpegOptions.Options.FFmpegBinary(), "-pix_fmts"); + using var instance = new Instances.Instance(FFMpegOptions.Options.FFMpegBinary(), "-pix_fmts"); instance.DataReceived += (e, args) => { if (PixelFormat.TryParse(args.Data, out var format)) @@ -443,7 +447,7 @@ private static void ParsePartOfCodecs(Dictionary codecs, string a { FFMpegHelper.RootExceptionCheck(); - using var instance = new Instances.Instance(FFMpegOptions.Options.FFmpegBinary(), arguments); + using var instance = new Instances.Instance(FFMpegOptions.Options.FFMpegBinary(), arguments); instance.DataReceived += (e, args) => { var codec = parser(args.Data); @@ -527,7 +531,7 @@ internal static IReadOnlyList GetContainersFormatsInternal() FFMpegHelper.RootExceptionCheck(); var list = new List(); - using var instance = new Instances.Instance(FFMpegOptions.Options.FFmpegBinary(), "-formats"); + using var instance = new Instances.Instance(FFMpegOptions.Options.FFMpegBinary(), "-formats"); instance.DataReceived += (e, args) => { if (ContainerFormat.TryParse(args.Data, out var fmt)) diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs index e41f8ee..9872e0a 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs @@ -20,9 +20,7 @@ internal FFMpegArgumentOptions() { } public FFMpegArgumentOptions Resize(int width, int height) => WithArgument(new SizeArgument(width, height)); public FFMpegArgumentOptions Resize(Size? size) => WithArgument(new SizeArgument(size)); - public FFMpegArgumentOptions Scale(VideoSize videoSize) => WithArgument(new ScaleArgument(videoSize)); - public FFMpegArgumentOptions Scale(int width, int height) => WithArgument(new ScaleArgument(width, height)); - public FFMpegArgumentOptions Scale(Size size) => WithArgument(new ScaleArgument(size)); + public FFMpegArgumentOptions WithBitStreamFilter(Channel channel, Filter filter) => WithArgument(new BitStreamFilterArgument(channel, filter)); public FFMpegArgumentOptions WithConstantRateFactor(int crf) => WithArgument(new ConstantRateFactorArgument(crf)); @@ -40,6 +38,13 @@ internal FFMpegArgumentOptions() { } public FFMpegArgumentOptions WithVideoCodec(Codec videoCodec) => WithArgument(new VideoCodecArgument(videoCodec)); public FFMpegArgumentOptions WithVideoCodec(string videoCodec) => WithArgument(new VideoCodecArgument(videoCodec)); public FFMpegArgumentOptions WithVideoBitrate(int bitrate) => WithArgument(new VideoBitrateArgument(bitrate)); + public FFMpegArgumentOptions WithVideoFilters(Action videoFilterOptions) + { + var videoFilterOptionsObj = new VideoFilterOptions(); + videoFilterOptions(videoFilterOptionsObj); + return WithArgument(new VideoFiltersArgument(videoFilterOptionsObj)); + } + public FFMpegArgumentOptions WithFramerate(double framerate) => WithArgument(new FrameRateArgument(framerate)); public FFMpegArgumentOptions WithoutMetadata() => WithArgument(new RemoveMetadataArgument()); public FFMpegArgumentOptions WithSpeedPreset(Speed speed) => WithArgument(new SpeedPresetArgument(speed)); @@ -47,7 +52,6 @@ internal FFMpegArgumentOptions() { } public FFMpegArgumentOptions WithCustomArgument(string argument) => WithArgument(new CustomArgument(argument)); public FFMpegArgumentOptions Seek(TimeSpan? seekTo) => WithArgument(new SeekArgument(seekTo)); - public FFMpegArgumentOptions Transpose(Transposition transposition) => WithArgument(new TransposeArgument(transposition)); public FFMpegArgumentOptions Loop(int times) => WithArgument(new LoopArgument(times)); public FFMpegArgumentOptions OverwriteExisting() => WithArgument(new OverwriteArgument()); @@ -56,8 +60,6 @@ internal FFMpegArgumentOptions() { } public FFMpegArgumentOptions ForcePixelFormat(string pixelFormat) => WithArgument(new ForcePixelFormat(pixelFormat)); public FFMpegArgumentOptions ForcePixelFormat(PixelFormat pixelFormat) => WithArgument(new ForcePixelFormat(pixelFormat)); - public FFMpegArgumentOptions DrawText(DrawTextOptions drawTextOptions) => WithArgument(new DrawTextArgument(drawTextOptions)); - public FFMpegArgumentOptions WithArgument(IArgument argument) { Arguments.Add(argument); diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs index cfbe42a..9d0a4ad 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs @@ -83,18 +83,18 @@ void OnCancelEvent(object sender, EventArgs args) CancelEvent -= OnCancelEvent; } - return HandleCompletion(throwOnError, errorCode, instance.ErrorData, instance.OutputData); + return HandleCompletion(throwOnError, errorCode, instance.ErrorData); } - private bool HandleCompletion(bool throwOnError, int errorCode, IReadOnlyList errorData, IReadOnlyList outputData) + private bool HandleCompletion(bool throwOnError, int exitCode, IReadOnlyList errorData) { - if (throwOnError && errorCode != 0) - throw new FFMpegException(FFMpegExceptionType.Conversion, "FFMpeg exited with non-zero exitcode.", null, string.Join("\n", errorData), string.Join("\n", outputData)); + if (throwOnError && exitCode != 0) + throw new FFMpegProcessException(exitCode, string.Join("\n", errorData)); _onPercentageProgress?.Invoke(100.0); if (_totalTimespan.HasValue) _onTimeProgress?.Invoke(_totalTimespan.Value); - return errorCode == 0; + return exitCode == 0; } public async Task ProcessAsynchronously(bool throwOnError = true) @@ -122,14 +122,14 @@ await Task.WhenAll(instance.FinishedRunning().ContinueWith(t => } catch (Exception e) { - if (!HandleException(throwOnError, e, instance.ErrorData, instance.OutputData)) return false; + if (!HandleException(throwOnError, e, instance.ErrorData)) return false; } finally { CancelEvent -= OnCancelEvent; } - return HandleCompletion(throwOnError, errorCode, instance.ErrorData, instance.OutputData); + return HandleCompletion(throwOnError, errorCode, instance.ErrorData); } private Instance PrepareInstance(out CancellationTokenSource cancellationTokenSource) @@ -138,7 +138,7 @@ private Instance PrepareInstance(out CancellationTokenSource cancellationTokenSo FFMpegHelper.VerifyFFMpegExists(); var startInfo = new ProcessStartInfo { - FileName = FFMpegOptions.Options.FFmpegBinary(), + FileName = FFMpegOptions.Options.FFMpegBinary(), Arguments = _ffMpegArguments.Text, StandardOutputEncoding = FFMpegOptions.Options.Encoding, StandardErrorEncoding = FFMpegOptions.Options.Encoding, @@ -153,12 +153,13 @@ private Instance PrepareInstance(out CancellationTokenSource cancellationTokenSo } - private static bool HandleException(bool throwOnError, Exception e, IReadOnlyList errorData, IReadOnlyList outputData) + private static bool HandleException(bool throwOnError, Exception e, IReadOnlyList errorData) { if (!throwOnError) return false; - throw new FFMpegException(FFMpegExceptionType.Process, "Exception thrown during processing", e, string.Join("\n", errorData), string.Join("\n", outputData)); + throw new FFMpegProcessException(exitCode, string.Join("\n", errorData)); + throw new FFMpegException(FFMpegExceptionType.Process, "Exception thrown during processing", e, string.Join("\n", errorData)); } private void OutputData(object sender, (DataType Type, string Data) msg) diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs index 44e20d2..37dc36f 100644 --- a/FFMpegCore/FFMpeg/FFMpegArguments.cs +++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs @@ -21,7 +21,6 @@ private FFMpegArguments() { } public static FFMpegArguments FromDemuxConcatInput(IEnumerable filePaths, Action? addArguments = null) => new FFMpegArguments().WithInput(new DemuxConcatArgument(filePaths), addArguments); public static FFMpegArguments FromFileInput(string filePath, bool verifyExists = true, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(verifyExists, filePath), addArguments); public static FFMpegArguments FromFileInput(FileInfo fileInfo, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(fileInfo.FullName, false), addArguments); - public static FFMpegArguments FromFileInput(IMediaAnalysis mediaAnalysis, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(mediaAnalysis.Path, false), addArguments); public static FFMpegArguments FromUrlInput(Uri uri, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments); public static FFMpegArguments FromPipeInput(IPipeSource sourcePipe, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputPipeArgument(sourcePipe), addArguments); @@ -36,7 +35,6 @@ public FFMpegArguments WithGlobalOptions(Action configureOp public FFMpegArguments AddDemuxConcatInput(IEnumerable filePaths, Action? addArguments = null) => WithInput(new DemuxConcatArgument(filePaths), addArguments); public FFMpegArguments AddFileInput(string filePath, bool verifyExists = true, Action? addArguments = null) => WithInput(new InputArgument(verifyExists, filePath), addArguments); public FFMpegArguments AddFileInput(FileInfo fileInfo, Action? addArguments = null) => WithInput(new InputArgument(fileInfo.FullName, false), addArguments); - public FFMpegArguments AddFileInput(IMediaAnalysis mediaAnalysis, Action? addArguments = null) => WithInput(new InputArgument(mediaAnalysis.Path, false), addArguments); public FFMpegArguments AddUrlInput(Uri uri, Action? addArguments = null) => WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments); public FFMpegArguments AddPipeInput(IPipeSource sourcePipe, Action? addArguments = null) => WithInput(new InputPipeArgument(sourcePipe), addArguments); diff --git a/FFMpegCore/FFMpeg/FFMpegOptions.cs b/FFMpegCore/FFMpeg/FFMpegOptions.cs index a7d29b4..4a09d7a 100644 --- a/FFMpegCore/FFMpeg/FFMpegOptions.cs +++ b/FFMpegCore/FFMpeg/FFMpegOptions.cs @@ -44,7 +44,7 @@ static FFMpegOptions() public bool UseCache { get; set; } = true; public Encoding Encoding { get; set; } = Encoding.Default; - public string FFmpegBinary() => FFBinary("FFMpeg"); + public string FFMpegBinary() => FFBinary("FFMpeg"); public string FFProbeBinary() => FFBinary("FFProbe"); diff --git a/FFMpegCore/FFMpegCore.csproj.DotSettings b/FFMpegCore/FFMpegCore.csproj.DotSettings index 69be7ec..7a8d17a 100644 --- a/FFMpegCore/FFMpegCore.csproj.DotSettings +++ b/FFMpegCore/FFMpegCore.csproj.DotSettings @@ -1,2 +1,3 @@  - True \ No newline at end of file + True + True \ No newline at end of file diff --git a/FFMpegCore/FFProbe/FFProbe.cs b/FFMpegCore/FFProbe/FFProbe.cs index 8bb68db..573a2ad 100644 --- a/FFMpegCore/FFProbe/FFProbe.cs +++ b/FFMpegCore/FFProbe/FFProbe.cs @@ -12,22 +12,28 @@ namespace FFMpegCore { public static class FFProbe { - public static IMediaAnalysis? Analyse(string filePath, int outputCapacity = int.MaxValue) + public static IMediaAnalysis Analyse(string filePath, int outputCapacity = int.MaxValue) { if (!File.Exists(filePath)) throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'"); using var instance = PrepareInstance(filePath, outputCapacity); - instance.BlockUntilFinished(); - return ParseOutput(filePath, instance); + var exitCode = instance.BlockUntilFinished(); + if (exitCode != 0) + throw new FFProbeProcessException(exitCode, string.Join("\n", instance.ErrorData)); + + return ParseOutput(instance); } - public static IMediaAnalysis? Analyse(Uri uri, int outputCapacity = int.MaxValue) + public static IMediaAnalysis Analyse(Uri uri, int outputCapacity = int.MaxValue) { using var instance = PrepareInstance(uri.AbsoluteUri, outputCapacity); - instance.BlockUntilFinished(); - return ParseOutput(uri.AbsoluteUri, instance); + var exitCode = instance.BlockUntilFinished(); + if (exitCode != 0) + throw new FFProbeProcessException(exitCode, string.Join("\n", instance.ErrorData)); + + return ParseOutput(instance); } - public static IMediaAnalysis? Analyse(Stream stream, int outputCapacity = int.MaxValue) + public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.MaxValue) { var streamPipeSource = new StreamPipeSource(stream); var pipeArgument = new InputPipeArgument(streamPipeSource); @@ -46,26 +52,26 @@ public static class FFProbe } var exitCode = task.ConfigureAwait(false).GetAwaiter().GetResult(); if (exitCode != 0) - throw new FFMpegException(FFMpegExceptionType.Process, $"FFProbe process returned exit status {exitCode}", null, string.Join("\n", instance.ErrorData), string.Join("\n", instance.OutputData)); + throw new FFProbeProcessException(exitCode, string.Join("\n", instance.ErrorData)); - return ParseOutput(pipeArgument.PipePath, instance); + return ParseOutput(instance); } - public static async Task AnalyseAsync(string filePath, int outputCapacity = int.MaxValue) + public static async Task AnalyseAsync(string filePath, int outputCapacity = int.MaxValue) { if (!File.Exists(filePath)) throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'"); using var instance = PrepareInstance(filePath, outputCapacity); await instance.FinishedRunning(); - return ParseOutput(filePath, instance); + return ParseOutput(instance); } - public static async Task AnalyseAsync(Uri uri, int outputCapacity = int.MaxValue) + public static async Task AnalyseAsync(Uri uri, int outputCapacity = int.MaxValue) { using var instance = PrepareInstance(uri.AbsoluteUri, outputCapacity); await instance.FinishedRunning(); - return ParseOutput(uri.AbsoluteUri, instance); + return ParseOutput(instance); } - public static async Task AnalyseAsync(Stream stream, int outputCapacity = int.MaxValue) + public static async Task AnalyseAsync(Stream stream, int outputCapacity = int.MaxValue) { var streamPipeSource = new StreamPipeSource(stream); var pipeArgument = new InputPipeArgument(streamPipeSource); @@ -86,21 +92,24 @@ public static class FFProbe } var exitCode = await task; if (exitCode != 0) - throw new FFMpegException(FFMpegExceptionType.Process, $"FFProbe process returned exit status {exitCode}", null, string.Join("\n", instance.ErrorData), string.Join("\n", instance.OutputData)); + throw new FFMpegException(FFMpegExceptionType.Process, $"FFProbe process returned exit status {exitCode}", null, string.Join("\n", instance.ErrorData)); pipeArgument.Post(); - return ParseOutput(pipeArgument.PipePath, instance); + return ParseOutput(instance); } - private static IMediaAnalysis? ParseOutput(string filePath, Instance instance) + private static IMediaAnalysis ParseOutput(Instance instance) { var json = string.Join(string.Empty, instance.OutputData); var ffprobeAnalysis = JsonSerializer.Deserialize(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true - })!; - if (ffprobeAnalysis?.Format == null) return null; - return new MediaAnalysis(filePath, ffprobeAnalysis); + }); + + if (ffprobeAnalysis?.Format == null) + throw new Exception(); + + return new MediaAnalysis(ffprobeAnalysis); } private static Instance PrepareInstance(string filePath, int outputCapacity) diff --git a/FFMpegCore/FFProbe/IMediaAnalysis.cs b/FFMpegCore/FFProbe/IMediaAnalysis.cs index 660d776..4e67d4f 100644 --- a/FFMpegCore/FFProbe/IMediaAnalysis.cs +++ b/FFMpegCore/FFProbe/IMediaAnalysis.cs @@ -5,12 +5,10 @@ namespace FFMpegCore { public interface IMediaAnalysis { - string Path { get; } - string Extension { get; } TimeSpan Duration { get; } MediaFormat Format { get; } - AudioStream PrimaryAudioStream { get; } - VideoStream PrimaryVideoStream { get; } + AudioStream? PrimaryAudioStream { get; } + VideoStream? PrimaryVideoStream { get; } List VideoStreams { get; } List AudioStreams { get; } } diff --git a/FFMpegCore/FFProbe/MediaAnalysis.cs b/FFMpegCore/FFProbe/MediaAnalysis.cs index 5a43aa2..f1b2f82 100644 --- a/FFMpegCore/FFProbe/MediaAnalysis.cs +++ b/FFMpegCore/FFProbe/MediaAnalysis.cs @@ -9,14 +9,11 @@ internal class MediaAnalysis : IMediaAnalysis { private static readonly Regex DurationRegex = new Regex("^(\\d{1,2}:\\d{1,2}:\\d{1,2}(.\\d{1,7})?)", RegexOptions.Compiled); - internal MediaAnalysis(string path, FFProbeAnalysis analysis) + internal MediaAnalysis(FFProbeAnalysis analysis) { Format = ParseFormat(analysis.Format); 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; } private MediaFormat ParseFormat(Format analysisFormat) @@ -33,9 +30,6 @@ private MediaFormat ParseFormat(Format analysisFormat) }; } - public string Path { get; } - public string Extension => System.IO.Path.GetExtension(Path); - public TimeSpan Duration => new[] { Format.Duration, @@ -44,9 +38,9 @@ private MediaFormat ParseFormat(Format analysisFormat) }.Max(); public MediaFormat Format { get; } - public AudioStream PrimaryAudioStream { get; } + public AudioStream? PrimaryAudioStream => AudioStreams.OrderBy(stream => stream.Index).FirstOrDefault(); - public VideoStream PrimaryVideoStream { get; } + public VideoStream? PrimaryVideoStream => VideoStreams.OrderBy(stream => stream.Index).FirstOrDefault(); public List VideoStreams { get; } public List AudioStreams { get; } diff --git a/FFMpegCore/Helpers/FFMpegHelper.cs b/FFMpegCore/Helpers/FFMpegHelper.cs index f2a214e..676f6d3 100644 --- a/FFMpegCore/Helpers/FFMpegHelper.cs +++ b/FFMpegCore/Helpers/FFMpegHelper.cs @@ -11,21 +11,15 @@ public static class FFMpegHelper private static bool _ffmpegVerified; public static void ConversionSizeExceptionCheck(Image image) - { - ConversionSizeExceptionCheck(image.Size); - } + => ConversionSizeExceptionCheck(image.Size.Width, image.Size.Height); public static void ConversionSizeExceptionCheck(IMediaAnalysis info) - { - ConversionSizeExceptionCheck(new Size(info.PrimaryVideoStream.Width, info.PrimaryVideoStream.Height)); - } + => ConversionSizeExceptionCheck(info.PrimaryVideoStream!.Width, info.PrimaryVideoStream.Height); - private static void ConversionSizeExceptionCheck(Size size) + private static void ConversionSizeExceptionCheck(int width, int height) { - if (size.Height % 2 != 0 || size.Width % 2 != 0 ) - { + if (height % 2 != 0 || width % 2 != 0 ) throw new ArgumentException("FFMpeg yuv420p encoding requires the width and height to be a multiple of 2!"); - } } public static void ExtensionExceptionCheck(string filename, string extension) @@ -45,7 +39,7 @@ public static void RootExceptionCheck() public static void VerifyFFMpegExists() { if (_ffmpegVerified) return; - var (exitCode, _) = Instance.Finish(FFMpegOptions.Options.FFmpegBinary(), "-version"); + var (exitCode, _) = Instance.Finish(FFMpegOptions.Options.FFMpegBinary(), "-version"); _ffmpegVerified = exitCode == 0; if (!_ffmpegVerified) throw new FFMpegException(FFMpegExceptionType.Operation, "ffmpeg was not found on your system"); }