From e49290b217e5b9c8da82a6cca3cdba1e4a7b5781 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Sat, 6 Mar 2021 21:25:17 +0100 Subject: [PATCH] Fix tests --- FFMpegCore.Test/ArgumentBuilderTest.cs | 6 +- FFMpegCore.Test/VideoTest.cs | 55 ++++++------- FFMpegCore/FFMpeg/Arguments/SizeArgument.cs | 5 -- .../FFMpeg/Arguments/VideoFiltersArgument.cs | 3 +- FFMpegCore/FFMpeg/FFMpeg.cs | 78 +++++++++---------- FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs | 2 - FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs | 3 +- FFMpegCore/FFMpegCore.csproj | 2 +- 8 files changed, 73 insertions(+), 81 deletions(-) diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index 543354f..f0792ef 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -26,7 +26,7 @@ public void Builder_BuildString_Scale() .WithVideoFilters(filterOptions => filterOptions .Scale(VideoSize.Hd))) .Arguments; - Assert.AreEqual("-i \"input.mp4\" -vf scale=-1:720 \"output.mp4\" -y", str); + Assert.AreEqual("-i \"input.mp4\" -vf \"scale=-1:720\" \"output.mp4\" -y", str); } [TestMethod] @@ -287,7 +287,7 @@ public void Builder_BuildString_DrawtextFilter() .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\"", + "-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); } @@ -303,7 +303,7 @@ public void Builder_BuildString_DrawtextFilter_Alt() .Arguments; Assert.AreEqual( - "-i \"input.mp4\" -vf drawtext=\"text='Stack Overflow':fontfile=/path/to/font.ttf:fontcolor=white:fontsize=24\" \"output.mp4\"", + "-i \"input.mp4\" -vf \"drawtext=text='Stack Overflow':fontfile=/path/to/font.ttf:fontcolor=white:fontsize=24\" \"output.mp4\"", str); } diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 33f51e4..ec6b8e2 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -24,7 +24,7 @@ public bool Convert(ContainerFormat type, bool multithreaded = false, VideoSize using var outputFile = new TemporaryFile($"out{type.Extension}"); var input = FFProbe.Analyse(TestResources.Mp4Video); - FFMpeg.Convert(input, outputFile, type, size: size, multithreaded: multithreaded); + FFMpeg.Convert(TestResources.Mp4Video, outputFile, type, size: size, multithreaded: multithreaded); var outputVideo = FFProbe.Analyse(outputFile); Assert.IsTrue(File.Exists(outputFile)); @@ -116,6 +116,16 @@ public void Video_ToMP4() [TestMethod, Timeout(10000)] public void Video_ToMP4_YUV444p() { + using var outputFile = new TemporaryFile($"out{VideoType.WebM.Extension}"); + + var success = FFMpegArguments + .FromFileInput(TestResources.WebmVideo) + .OutputToFile(outputFile, false, opt => opt + .WithVideoCodec(VideoCodec.LibX264)) + .ProcessSynchronously(); + Assert.IsTrue(success); + var analysis = FFProbe.Analyse(outputFile); + Convert(VideoType.Mp4, (a) => Assert.IsTrue(a.VideoStreams.First().PixelFormat == "yuv444p"), new ForcePixelFormat("yuv444p")); } @@ -161,13 +171,13 @@ public void Video_ToMP4_Args_StreamPipe() [TestMethod, Timeout(10000)] public async Task Video_ToMP4_Args_StreamOutputPipe_Async_Failure() { - await Assert.ThrowsExceptionAsync(async () => + await Assert.ThrowsExceptionAsync(async () => { await using var ms = new MemoryStream(); var pipeSource = new StreamPipeSink(ms); await FFMpegArguments .FromFileInput(TestResources.Mp4Video) - .OutputToPipe(pipeSource, opt => opt.ForceFormat("mkv")) + .OutputToPipe(pipeSource, opt => opt.ForceFormat("mp4")) .ProcessAsynchronously(); }); } @@ -191,7 +201,7 @@ public void Video_StreamFile_OutputToMemoryStream() [TestMethod, Timeout(10000)] public void Video_ToMP4_Args_StreamOutputPipe_Failure() { - Assert.ThrowsException(() => + Assert.ThrowsException(() => { using var ms = new MemoryStream(); var processor = FFMpegArguments @@ -221,11 +231,13 @@ await FFMpegArguments [TestMethod, Timeout(10000)] public async Task TestDuplicateRun() { - FFMpegArguments.FromFileInput(TestResources.Mp4Video) + FFMpegArguments + .FromFileInput(TestResources.Mp4Video) .OutputToFile("temporary.mp4") .ProcessSynchronously(); - await FFMpegArguments.FromFileInput(TestResources.Mp4Video) + await FFMpegArguments + .FromFileInput(TestResources.Mp4Video) .OutputToFile("temporary.mp4") .ProcessAsynchronously(); @@ -233,20 +245,20 @@ await FFMpegArguments.FromFileInput(TestResources.Mp4Video) } [TestMethod, Timeout(10000)] - public void Video_ToMP4_Args_StreamOutputPipe() + public void TranscodeToMemoryStream_Success() { - using var input = new MemoryStream(); + using var output = new MemoryStream(); var success = FFMpegArguments - .FromFileInput(TestResources.Mp4Video) - .OutputToPipe(new StreamPipeSink(input), opt => opt + .FromFileInput(TestResources.WebmVideo) + .OutputToPipe(new StreamPipeSink(output), 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); + output.Position = 0; + var inputAnalysis = FFProbe.Analyse(TestResources.WebmVideo); + var outputAnalysis = FFProbe.Analyse(output); Assert.AreEqual(inputAnalysis.Duration.TotalSeconds, outputAnalysis.Duration.TotalSeconds, 0.3); } @@ -291,7 +303,7 @@ public async Task Video_ToOGV_Resize() var success = await FFMpegArguments .FromFileInput(TestResources.Mp4Video) .OutputToFile(outputFile, false, opt => opt - .Resize(VideoSize.Ed) + .Resize(200, 200) .WithVideoCodec(VideoCodec.LibTheora)) .ProcessAsynchronously(); Assert.IsTrue(success); @@ -315,7 +327,7 @@ public void RawVideoPipeSource_Ogv_Scale(System.Drawing.Imaging.PixelFormat pixe .ProcessSynchronously(); var analysis = FFProbe.Analyse(outputFile); - Assert.Equals((int)VideoSize.Ed, analysis!.PrimaryVideoStream.Width); + Assert.AreEqual((int)VideoSize.Ed, analysis!.PrimaryVideoStream.Width); } [TestMethod, Timeout(10000)] @@ -327,14 +339,9 @@ public void Scale_Mp4_Multithreaded() .FromFileInput(TestResources.Mp4Video) .OutputToFile(outputFile, false, opt => opt .UsingMultithreading(true) - .WithVideoFilters(filterOptions => filterOptions - .Scale(VideoSize.Ld)) .WithVideoCodec(VideoCodec.LibX264)) .ProcessSynchronously(); Assert.IsTrue(success); - - var analysis = FFProbe.Analyse(outputFile); - Assert.AreEqual((int)VideoSize.Ld, analysis!.PrimaryVideoStream.Width); } [DataTestMethod, Timeout(10000)] @@ -349,13 +356,9 @@ public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixe 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)] @@ -386,7 +389,7 @@ public void Video_ToOGV_MultiThread() public void Video_Snapshot_InMemory() { var input = FFProbe.Analyse(TestResources.Mp4Video); - using var bitmap = FFMpeg.Snapshot(input); + using var bitmap = FFMpeg.Snapshot(TestResources.Mp4Video); Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width); Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height); @@ -399,7 +402,7 @@ public void Video_Snapshot_PersistSnapshot() var outputPath = new TemporaryFile("out.png"); var input = FFProbe.Analyse(TestResources.Mp4Video); - FFMpeg.Snapshot(input, outputPath); + FFMpeg.Snapshot(TestResources.Mp4Video, outputPath); using var bitmap = Image.FromFile(outputPath); Assert.AreEqual(input.PrimaryVideoStream.Width, bitmap.Width); diff --git a/FFMpegCore/FFMpeg/Arguments/SizeArgument.cs b/FFMpegCore/FFMpeg/Arguments/SizeArgument.cs index e22f29c..04fe615 100644 --- a/FFMpegCore/FFMpeg/Arguments/SizeArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/SizeArgument.cs @@ -16,11 +16,6 @@ public SizeArgument(Size? size) public SizeArgument(int width, int height) : this(new Size(width, height)) { } - public SizeArgument(VideoSize videosize) - { - Size = videosize == VideoSize.Original ? new Size(-1, -1) : new Size(-1, (int)videosize); - } - public string Text => Size == null ? string.Empty : $"-s {Size.Value.Width}x{Size.Value.Height}"; } } diff --git a/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs b/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs index 317d4be..f7fef93 100644 --- a/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs @@ -14,7 +14,8 @@ public VideoFiltersArgument(VideoFilterOptions options) { Options = options; } - public string Text { get; set; } + + public string Text => GetText(); public string GetText() { diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs index 5de955a..b242c7b 100644 --- a/FFMpegCore/FFMpeg/FFMpeg.cs +++ b/FFMpegCore/FFMpeg/FFMpeg.cs @@ -8,7 +8,6 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -using FFMpegCore.Arguments; namespace FFMpegCore { @@ -17,17 +16,18 @@ public static class FFMpeg /// /// Saves a 'png' thumbnail from the input video to drive /// - /// Source video analysis + /// Source video analysis /// Output video file path /// Seek position where the thumbnail should be taken. /// Thumbnail size. If width or height equal 0, the other will be computed automatically. /// Bitmap with the requested snapshot. - public static bool Snapshot(IMediaAnalysis source, string output, Size? size = null, TimeSpan? captureTime = null) + public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; - var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime); + var source = FFProbe.Analyse(input); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime); return arguments .OutputToFile(output, true, outputOptions) @@ -36,32 +36,35 @@ public static bool Snapshot(IMediaAnalysis source, string output, Size? size = n /// /// Saves a 'png' thumbnail from the input video to drive /// - /// Source video analysis + /// Source video analysis /// Output video file path /// Seek position where the thumbnail should be taken. /// Thumbnail size. If width or height equal 0, the other will be computed automatically. /// Bitmap with the requested snapshot. - public static Task SnapshotAsync(IMediaAnalysis source, string output, Size? size = null, TimeSpan? captureTime = null) + public static async Task SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; - var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime); + var source = await FFProbe.AnalyseAsync(input); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime); - return arguments + return await arguments .OutputToFile(output, true, outputOptions) .ProcessAsynchronously(); } + /// /// Saves a 'png' thumbnail to an in-memory bitmap /// - /// Source video file. + /// Source video file. /// Seek position where the thumbnail should be taken. /// Thumbnail size. If width or height equal 0, the other will be computed automatically. /// Bitmap with the requested snapshot. - public static Bitmap Snapshot(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null) + public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null) { - var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime); + var source = FFProbe.Analyse(input); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime); using var ms = new MemoryStream(); arguments @@ -76,13 +79,14 @@ public static Bitmap Snapshot(IMediaAnalysis source, Size? size = null, TimeSpan /// /// Saves a 'png' thumbnail to an in-memory bitmap /// - /// Source video file. + /// Source video file. /// Seek position where the thumbnail should be taken. /// Thumbnail size. If width or height equal 0, the other will be computed automatically. /// Bitmap with the requested snapshot. - public static async Task SnapshotAsync(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null) + public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null) { - var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime); + var source = await FFProbe.AnalyseAsync(input); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime); using var ms = new MemoryStream(); await arguments @@ -94,13 +98,13 @@ await arguments return new Bitmap(ms); } - private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null) + private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments(string input, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null) { captureTime ??= TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3); size = PrepareSnapshotSize(source, size); return (FFMpegArguments - .FromFileInput(source, options => options + .FromFileInput(input, false, options => options .Seek(captureTime)), options => options .WithVideoCodec(VideoCodec.Png) @@ -110,7 +114,7 @@ private static (FFMpegArguments, Action outputOptions) Bu private static Size? PrepareSnapshotSize(IMediaAnalysis source, Size? wantedSize) { - if (wantedSize == null || (wantedSize.Value.Height <= 0 && wantedSize.Value.Width <= 0)) + if (wantedSize == null || (wantedSize.Value.Height <= 0 && wantedSize.Value.Width <= 0) || source.PrimaryVideoStream == null) return null; var currentSize = new Size(source.PrimaryVideoStream.Width, source.PrimaryVideoStream.Height); @@ -147,7 +151,7 @@ private static (FFMpegArguments, Action outputOptions) Bu /// Is encoding multithreaded. /// Output video information. public static bool Convert( - IMediaAnalysis source, + string input, string output, ContainerFormat format, Speed speed = Speed.SuperFast, @@ -156,6 +160,7 @@ public static bool Convert( bool multithreaded = false) { FFMpegHelper.ExtensionExceptionCheck(output, format.Extension); + var source = FFProbe.Analyse(input); FFMpegHelper.ConversionSizeExceptionCheck(source); var scale = VideoSize.Original == size ? 1 : (double)source.PrimaryVideoStream.Height / (int)size; @@ -167,7 +172,7 @@ public static bool Convert( return format.Name switch { "mp4" => FFMpegArguments - .FromFileInput(source) + .FromFileInput(input) .OutputToFile(output, true, options => options .UsingMultithreading(multithreaded) .WithVideoCodec(VideoCodec.LibX264) @@ -179,7 +184,7 @@ public static bool Convert( .WithAudioBitrate(audioQuality)) .ProcessSynchronously(), "ogv" => FFMpegArguments - .FromFileInput(source) + .FromFileInput(input) .OutputToFile(output, true, options => options .UsingMultithreading(multithreaded) .WithVideoCodec(VideoCodec.LibTheora) @@ -191,14 +196,14 @@ public static bool Convert( .WithAudioBitrate(audioQuality)) .ProcessSynchronously(), "mpegts" => FFMpegArguments - .FromFileInput(source) + .FromFileInput(input) .OutputToFile(output, true, options => options .CopyChannel() .WithBitStreamFilter(Channel.Video, Filter.H264_Mp4ToAnnexB) .ForceFormat(VideoType.Ts)) .ProcessSynchronously(), "webm" => FFMpegArguments - .FromFileInput(source) + .FromFileInput(input) .OutputToFile(output, true, options => options .UsingMultithreading(multithreaded) .WithVideoCodec(VideoCodec.LibVpx) @@ -236,21 +241,22 @@ public static bool PosterWithAudio(string image, string audio, string output) .UsingShortest()) .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 IMediaAnalysis[] videos) + public static bool Join(string output, params string[] videos) { - var temporaryVideoParts = videos.Select(video => + var temporaryVideoParts = videos.Select(videoPath => { + var video = FFProbe.Analyse(videoPath); FFMpegHelper.ConversionSizeExceptionCheck(video); - var destinationPath = Path.Combine(FFMpegOptions.Options.TempDirectory, $"{Path.GetFileNameWithoutExtension(video.Path)}{FileExtension.Ts}"); + var destinationPath = Path.Combine(FFMpegOptions.Options.TempDirectory, $"{Path.GetFileNameWithoutExtension(videoPath)}{FileExtension.Ts}"); Directory.CreateDirectory(FFMpegOptions.Options.TempDirectory); - Convert(video, destinationPath, VideoType.Ts); + Convert(videoPath, destinationPath, VideoType.Ts); return destinationPath; }).ToArray(); @@ -268,16 +274,6 @@ public static bool Join(string output, params IMediaAnalysis[] videos) Cleanup(temporaryVideoParts); } } - /// - /// 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 string[] videos) - { - return Join(output, videos.Select(videoPath => FFProbe.Analyse(videoPath)).ToArray()); - } /// /// Converts an image sequence to a video. @@ -344,10 +340,10 @@ public static bool Mute(string input, string output) { var source = FFProbe.Analyse(input); FFMpegHelper.ConversionSizeExceptionCheck(source); - FFMpegHelper.ExtensionExceptionCheck(output, source.Extension); + // FFMpegHelper.ExtensionExceptionCheck(output, source.Extension); return FFMpegArguments - .FromFileInput(source) + .FromFileInput(input) .OutputToFile(output, true, options => options .CopyChannel(Channel.Video) .DisableChannel(Channel.Audio)) @@ -383,10 +379,10 @@ public static bool ReplaceAudio(string input, string inputAudio, string output, { var source = FFProbe.Analyse(input); FFMpegHelper.ConversionSizeExceptionCheck(source); - FFMpegHelper.ExtensionExceptionCheck(output, source.Extension); + // FFMpegHelper.ExtensionExceptionCheck(output, source.Format.); return FFMpegArguments - .FromFileInput(source) + .FromFileInput(input) .AddFileInput(inputAudio) .OutputToFile(output, true, options => options .CopyChannel() diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs index 9872e0a..c87e029 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs @@ -15,8 +15,6 @@ internal FFMpegArgumentOptions() { } public FFMpegArgumentOptions WithAudioBitrate(int bitrate) => WithArgument(new AudioBitrateArgument(bitrate)); public FFMpegArgumentOptions WithAudioSamplingRate(int samplingRate = 48000) => WithArgument(new AudioSamplingRateArgument(samplingRate)); public FFMpegArgumentOptions WithVariableBitrate(int vbr) => WithArgument(new VariableBitRateArgument(vbr)); - - public FFMpegArgumentOptions Resize(VideoSize videoSize) => WithArgument(new SizeArgument(videoSize)); public FFMpegArgumentOptions Resize(int width, int height) => WithArgument(new SizeArgument(width, height)); public FFMpegArgumentOptions Resize(Size? size) => WithArgument(new SizeArgument(size)); diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs index 75b98f4..ecc568f 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs @@ -80,7 +80,7 @@ void OnCancelEvent(object sender, int timeout) } catch (Exception e) { - if (!HandleException(throwOnError, e, instance.ErrorData, instance.OutputData)) return false; + if (!HandleException(throwOnError, e, instance.ErrorData)) return false; } finally { @@ -166,7 +166,6 @@ private static bool HandleException(bool throwOnError, Exception e, IReadOnlyLis if (!throwOnError) return false; - throw new FFMpegProcessException(exitCode, string.Join("\n", errorData)); throw new FFMpegException(FFMpegExceptionType.Process, "Exception thrown during processing", e, string.Join("\n", errorData)); } diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index cb5d906..ffbc8b8 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -12,7 +12,7 @@ - return null from FFProbe.Analyse* when no media format was detected - Expose tags as string dictionary on IMediaAnalysis (thanks hey-red) 8 - 3.4.0 + 4.0.0 MIT Malte Rosenbjerg, Vlad Jerca ffmpeg ffprobe convert video audio mediafile resize analyze muxing