From e60fb71ff836e482915241309924e1b61f6b0802 Mon Sep 17 00:00:00 2001 From: Fedor Zhilkin Date: Thu, 17 Sep 2020 20:50:38 +0300 Subject: [PATCH 1/7] -map filtering Add map filtering to choose videostream --- .../FFMpeg/Arguments/MapStreamArgument.cs | 17 +++++++++++++++++ FFMpegCore/FFMpeg/FFMpegArguments.cs | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs diff --git a/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs b/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs new file mode 100644 index 0000000..f6d9977 --- /dev/null +++ b/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs @@ -0,0 +1,17 @@ +namespace FFMpegCore.Arguments +{ + /// + /// Represents choice of video stream, works with one input file + /// + public class MapStreamArgument : IArgument + { + public readonly int VideoStream; + + public MapStreamArgument(int videoStreamNum) + { + VideoStream = videoStreamNum; + } + + public string Text => $"-map 0:{VideoStream}"; + } +} \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs index b3285f6..db129ee 100644 --- a/FFMpegCore/FFMpeg/FFMpegArguments.cs +++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs @@ -59,7 +59,8 @@ private FFMpegArguments(IInputArgument inputArgument) 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 WithVideoStream(int videoStreamNumber) => WithArgument(new MapStreamArgument(videoStreamNumber)); + 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)); From 981b3294c5d43da7ecc0e5f04cc39e14641672a0 Mon Sep 17 00:00:00 2001 From: Fedor Zhilkin Date: Thu, 17 Sep 2020 20:56:28 +0300 Subject: [PATCH 2/7] Use map filtering Use map filtering doing snapshot --- FFMpegCore/FFMpeg/FFMpeg.cs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs index 7aad74f..0f516e8 100644 --- a/FFMpegCore/FFMpeg/FFMpeg.cs +++ b/FFMpegCore/FFMpeg/FFMpeg.cs @@ -20,13 +20,14 @@ public static class FFMpeg /// 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. + /// Number of video stream in input file. Default it is 0. /// Bitmap with the requested snapshot. - public static bool Snapshot(IMediaAnalysis source, string output, Size? size = null, TimeSpan? captureTime = null) + public static bool Snapshot(IMediaAnalysis source, string output, Size? size = null, TimeSpan? captureTime = null, int videoStreamNumber = 0) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; - var arguments = BuildSnapshotArguments(source, size, captureTime); + var arguments = BuildSnapshotArguments(source, size, captureTime, videoStreamNumber); return arguments .OutputToFile(output) @@ -39,13 +40,14 @@ public static bool Snapshot(IMediaAnalysis source, string output, Size? size = n /// 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. + /// Number of video stream in input file. Default it is 0. /// Bitmap with the requested snapshot. - public static Task SnapshotAsync(IMediaAnalysis source, string output, Size? size = null, TimeSpan? captureTime = null) + public static Task SnapshotAsync(IMediaAnalysis source, string output, Size? size = null, TimeSpan? captureTime = null, int videoStreamNumber = 0) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; - var arguments = BuildSnapshotArguments(source, size, captureTime); + var arguments = BuildSnapshotArguments(source, size, captureTime, videoStreamNumber); return arguments .OutputToFile(output) @@ -57,10 +59,11 @@ public static Task SnapshotAsync(IMediaAnalysis source, string output, Siz /// 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. + /// Number of video stream in input file. Default it is 0. /// Bitmap with the requested snapshot. - public static Bitmap Snapshot(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null) + public static Bitmap Snapshot(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int videoStreamNumber = 0) { - var arguments = BuildSnapshotArguments(source, size, captureTime); + var arguments = BuildSnapshotArguments(source, size, captureTime, videoStreamNumber); using var ms = new MemoryStream(); arguments @@ -77,10 +80,11 @@ public static Bitmap Snapshot(IMediaAnalysis source, Size? size = null, TimeSpan /// 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. + /// Number of video stream in input file. Default it is 0. /// Bitmap with the requested snapshot. - public static async Task SnapshotAsync(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null) + public static async Task SnapshotAsync(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int videoStreamNumber = 0) { - var arguments = BuildSnapshotArguments(source, size, captureTime); + var arguments = BuildSnapshotArguments(source, size, captureTime, videoStreamNumber); using var ms = new MemoryStream(); await arguments @@ -92,13 +96,14 @@ await arguments return new Bitmap(ms); } - private static FFMpegArguments BuildSnapshotArguments(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null) + private static FFMpegArguments BuildSnapshotArguments(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int videoStreamNumber = 0) { captureTime ??= TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3); size = PrepareSnapshotSize(source, size); return FFMpegArguments .FromSeekedFiles((source.Path, captureTime ?? TimeSpan.Zero)) + .WithVideoStream(videoStreamNumber) .WithVideoCodec(VideoCodec.Png) .WithFrameOutputCount(1) .Resize(size); From f2fca0c1b6afa70cd768161e41ea008decf46744 Mon Sep 17 00:00:00 2001 From: Fedor Zhilkin Date: Thu, 17 Sep 2020 21:02:33 +0300 Subject: [PATCH 3/7] Add test for map filtering --- FFMpegCore.Test/ArgumentBuilderTest.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index 7a058a2..ee954c5 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -180,6 +180,13 @@ public void Builder_BuildString_FrameOutputCount() Assert.AreEqual("-i \"input.mp4\" -vframes 50 \"output.mp4\"", str); } + [TestMethod] + public void Builder_BuildString_VideoStreamNumber() + { + var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithVideoStream(1).OutputToFile("output.mp4", false).Arguments; + Assert.AreEqual("-i \"input.mp4\" -map 0:1 \"output.mp4\"", str); + } + [TestMethod] public void Builder_BuildString_FrameRate() { From c47214b7aa10b1d8f2f23c0f52522da0a9cbd2f3 Mon Sep 17 00:00:00 2001 From: Fedor Zhilkin Date: Fri, 13 Nov 2020 02:09:52 +0300 Subject: [PATCH 4/7] Refactoring --- .../FFMpeg/Arguments/MapStreamArgument.cs | 8 +-- FFMpegCore/FFMpeg/FFMpeg.cs | 67 +++++++++++-------- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs b/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs index f6d9977..01a537d 100644 --- a/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs @@ -5,13 +5,13 @@ /// public class MapStreamArgument : IArgument { - public readonly int VideoStream; + private readonly int _streamIndex; - public MapStreamArgument(int videoStreamNum) + public MapStreamArgument(int index) { - VideoStream = videoStreamNum; + _streamIndex = index; } - public string Text => $"-map 0:{VideoStream}"; + public string Text => $"-map 0:{_streamIndex}"; } } \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs index f252489..8a442ed 100644 --- a/FFMpegCore/FFMpeg/FFMpeg.cs +++ b/FFMpegCore/FFMpeg/FFMpeg.cs @@ -20,15 +20,15 @@ public static class FFMpeg /// 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. - /// Number of video stream in input file. Default it is 0. + /// Index of video stream in input file. Default it is 0. /// Bitmap with the requested snapshot. - public static bool Snapshot(IMediaAnalysis source, string output, Size? size = null, TimeSpan? captureTime = null, int videoStreamNumber = 0) + public static bool Snapshot(IMediaAnalysis source, string output, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; - - var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime, videoStreamNumber); - + + var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime, streamIndex); + return arguments .OutputToFile(output, true, outputOptions) .ProcessSynchronously(); @@ -40,15 +40,15 @@ public static bool Snapshot(IMediaAnalysis source, string output, Size? size = n /// 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. - /// Number of video stream in input file. Default it is 0. + /// Index of video stream in input file. Default it is 0. /// Bitmap with the requested snapshot. - public static Task SnapshotAsync(IMediaAnalysis source, string output, Size? size = null, TimeSpan? captureTime = null, int videoStreamNumber = 0) + public static Task SnapshotAsync(IMediaAnalysis source, string output, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; - - var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime, videoStreamNumber); - + + var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime, streamIndex); + return arguments .OutputToFile(output, true, outputOptions) .ProcessAsynchronously(); @@ -59,13 +59,13 @@ public static Task SnapshotAsync(IMediaAnalysis source, string output, Siz /// 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. - /// Number of video stream in input file. Default it is 0. + /// Index of video stream in input file. Default it is 0. /// Bitmap with the requested snapshot. - public static Bitmap Snapshot(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int videoStreamNumber = 0) - { - var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime, videoStreamNumber); + public static Bitmap Snapshot(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) + { + var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime, streamIndex); using var ms = new MemoryStream(); - + arguments .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options .ForceFormat("rawvideo"))) @@ -80,13 +80,13 @@ public static Bitmap Snapshot(IMediaAnalysis source, Size? size = null, TimeSpan /// 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. - /// Number of video stream in input file. Default it is 0. + /// Index of video stream in input file. Default it is 0. /// Bitmap with the requested snapshot. - public static async Task SnapshotAsync(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int videoStreamNumber = 0) + public static async Task SnapshotAsync(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) { - var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime, videoStreamNumber); + var (arguments, outputOptions) = BuildSnapshotArguments(source, size, captureTime, streamIndex); using var ms = new MemoryStream(); - + await arguments .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options .ForceFormat("rawvideo"))) @@ -95,17 +95,26 @@ await arguments ms.Position = 0; return new Bitmap(ms); } - - private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int videoStreamNumber = 0) + + private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments(IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) { captureTime ??= TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3); size = PrepareSnapshotSize(source, size); - + + // If user will know about numeration of streams (user passes index of necessary video stream) + int index = source.VideoStreams.Where(videoStream => videoStream.Index == streamIndex).FirstOrDefault().Index; + + // User passes number of video stream + // E.g: user can pass 0, but index of first video stream will be 1 + /*int index = 0; + try { index = source.VideoStreams[streamIndex].Index; } + catch { };*/ + return (FFMpegArguments .FromFileInput(source, options => options - .Seek(captureTime)), + .Seek(captureTime)), options => options - .SelectStream(videoStreamNumber) + .SelectStream(index) .WithVideoCodec(VideoCodec.Png) .WithFrameOutputCount(1) .Resize(size)); @@ -115,11 +124,11 @@ private static (FFMpegArguments, Action outputOptions) Bu { if (wantedSize == null || (wantedSize.Value.Height <= 0 && wantedSize.Value.Width <= 0)) return null; - + var currentSize = new Size(source.PrimaryVideoStream.Width, source.PrimaryVideoStream.Height); if (source.PrimaryVideoStream.Rotation == 90 || source.PrimaryVideoStream.Rotation == 180) currentSize = new Size(source.PrimaryVideoStream.Height, source.PrimaryVideoStream.Width); - + if (wantedSize.Value.Width != currentSize.Width || wantedSize.Value.Height != currentSize.Height) { if (wantedSize.Value.Width <= 0 && wantedSize.Value.Height > 0) @@ -327,7 +336,7 @@ public static bool SaveM3U8Stream(Uri uri, string output) if (uri.Scheme != "http" && uri.Scheme != "https") throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream."); - + return FFMpegArguments .FromUrlInput(uri) .OutputToFile(output) @@ -451,7 +460,7 @@ private static void ParsePartOfCodecs(Dictionary codecs, string a instance.DataReceived += (e, args) => { var codec = parser(args.Data); - if(codec != null) + if (codec != null) if (codecs.TryGetValue(codec.Name, out var parentCodec)) parentCodec.Merge(codec); else @@ -498,7 +507,7 @@ public static IReadOnlyList GetCodecs(CodecType type) { if (!FFMpegOptions.Options.UseCache) return GetCodecsInternal().Values.Where(x => x.Type == type).ToList().AsReadOnly(); - return FFMpegCache.Codecs.Values.Where(x=>x.Type == type).ToList().AsReadOnly(); + return FFMpegCache.Codecs.Values.Where(x => x.Type == type).ToList().AsReadOnly(); } public static IReadOnlyList GetVideoCodecs() => GetCodecs(CodecType.Video); From c30cf4c7ad87da71dcf3820ee35f5dabdc3d98ec Mon Sep 17 00:00:00 2001 From: Fedor Zhilkin Date: Tue, 3 Aug 2021 21:58:40 +0300 Subject: [PATCH 5/7] Style guide fixes and minor refactoring --- FFMpegCore/FFMpeg/FFMpeg.cs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs index 42c344b..14556b4 100644 --- a/FFMpegCore/FFMpeg/FFMpeg.cs +++ b/FFMpegCore/FFMpeg/FFMpeg.cs @@ -20,14 +20,15 @@ public static class FFMpeg /// 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. + /// Selected video stream index. /// Bitmap with the requested snapshot. - public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null) + public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; var source = FFProbe.Analyse(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex); return arguments .OutputToFile(output, true, outputOptions) @@ -40,14 +41,15 @@ public static bool Snapshot(string input, string output, Size? size = null, Time /// 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. + /// Selected video stream index. /// Bitmap with the requested snapshot. - public static async Task SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null) + public static async Task SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; var source = await FFProbe.AnalyseAsync(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex); return await arguments .OutputToFile(output, true, outputOptions) @@ -60,11 +62,12 @@ public static async Task SnapshotAsync(string input, string output, Size? /// 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. + /// Selected video stream index. /// Bitmap with the requested snapshot. - public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null) + public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) { var source = FFProbe.Analyse(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex); using var ms = new MemoryStream(); arguments @@ -82,11 +85,12 @@ public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? capture /// 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. + /// Selected video stream index. /// Bitmap with the requested snapshot. - public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null) + public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) { var source = await FFProbe.AnalyseAsync(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex); using var ms = new MemoryStream(); await arguments @@ -98,15 +102,17 @@ await arguments return new Bitmap(ms); } - private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments(string input, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null) + private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments(string input, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) { captureTime ??= TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3); size = PrepareSnapshotSize(source, size); + var index = source.VideoStreams.FirstOrDefault(videoStream => videoStream.Index == streamIndex)?.Index; return (FFMpegArguments .FromFileInput(input, false, options => options .Seek(captureTime)), options => options + .SelectStream(index ?? 0) .WithVideoCodec(VideoCodec.Png) .WithFrameOutputCount(1) .Resize(size)); From 562a50d874d061f78c1c0386d3c3ab18fa98a5e9 Mon Sep 17 00:00:00 2001 From: Fedor Zhilkin Date: Wed, 11 Aug 2021 01:21:06 +0300 Subject: [PATCH 6/7] Nullable streamIndex and inputFileIndex --- FFMpegCore.Test/ArgumentBuilderTest.cs | 2 +- .../FFMpeg/Arguments/MapStreamArgument.cs | 10 ++-- FFMpegCore/FFMpeg/FFMpeg.cs | 46 +++++++++++-------- FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs | 4 +- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index 082d9bf..5da3e0b 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -234,7 +234,7 @@ public void Builder_BuildString_FrameOutputCount() [TestMethod] public void Builder_BuildString_VideoStreamNumber() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.SelectStream(1)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.SelectStream(0,1)).Arguments; Assert.AreEqual("-i \"input.mp4\" -map 0:1 \"output.mp4\"", str); } diff --git a/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs b/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs index 01a537d..6ce1dbe 100644 --- a/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs @@ -1,17 +1,19 @@ namespace FFMpegCore.Arguments { /// - /// Represents choice of video stream, works with one input file + /// Represents choice of video stream /// public class MapStreamArgument : IArgument { + private readonly int _inputFileIndex; private readonly int _streamIndex; - public MapStreamArgument(int index) + public MapStreamArgument(int inputFileIndex, int streamIndex) { - _streamIndex = index; + _inputFileIndex = inputFileIndex; + _streamIndex = streamIndex; } - public string Text => $"-map 0:{_streamIndex}"; + public string Text => $"-map {_inputFileIndex}:{_streamIndex}"; } } \ No newline at end of file diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs index 14556b4..bffbd1a 100644 --- a/FFMpegCore/FFMpeg/FFMpeg.cs +++ b/FFMpegCore/FFMpeg/FFMpeg.cs @@ -20,16 +20,17 @@ public static class FFMpeg /// 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. + /// Input file index /// Selected video stream index. /// Bitmap with the requested snapshot. - public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) + public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int inputFileIndex = 0, int? streamIndex = null) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; var source = FFProbe.Analyse(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex); - + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, inputFileIndex, streamIndex); + return arguments .OutputToFile(output, true, outputOptions) .ProcessSynchronously(); @@ -41,16 +42,17 @@ public static bool Snapshot(string input, string output, Size? size = null, Time /// 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. + /// Input file index /// Selected video stream index. /// Bitmap with the requested snapshot. - public static async Task SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) + public static async Task SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int inputFileIndex = 0, int? streamIndex = null) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; var source = await FFProbe.AnalyseAsync(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex); - + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, inputFileIndex, streamIndex); + return await arguments .OutputToFile(output, true, outputOptions) .ProcessAsynchronously(); @@ -62,14 +64,15 @@ public static async Task SnapshotAsync(string input, string output, Size? /// 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. + /// Input file index /// Selected video stream index. /// Bitmap with the requested snapshot. - public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) + public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int inputFileIndex = 0, int? streamIndex = null) { var source = FFProbe.Analyse(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, inputFileIndex, streamIndex); using var ms = new MemoryStream(); - + arguments .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options .ForceFormat("rawvideo"))) @@ -85,14 +88,15 @@ public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? capture /// 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. + /// Input file index /// Selected video stream index. /// Bitmap with the requested snapshot. - public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) + public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int inputFileIndex = 0, int? streamIndex = null) { var source = await FFProbe.AnalyseAsync(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, inputFileIndex, streamIndex); using var ms = new MemoryStream(); - + await arguments .OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options .ForceFormat("rawvideo"))) @@ -102,17 +106,23 @@ await arguments return new Bitmap(ms); } - private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments(string input, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int streamIndex = 0) + private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments( + string input, + IMediaAnalysis source, + Size? size = null, + TimeSpan? captureTime = null, + int inputFileIndex = 0, + int? streamIndex = null) { captureTime ??= TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3); size = PrepareSnapshotSize(source, size); - var index = source.VideoStreams.FirstOrDefault(videoStream => videoStream.Index == streamIndex)?.Index; + streamIndex = streamIndex == null ? 0 : source.VideoStreams.FirstOrDefault(videoStream => videoStream.Index == streamIndex).Index; return (FFMpegArguments .FromFileInput(input, false, options => options - .Seek(captureTime)), + .Seek(captureTime)), options => options - .SelectStream(index ?? 0) + .SelectStream((int)streamIndex, inputFileIndex) .WithVideoCodec(VideoCodec.Png) .WithFrameOutputCount(1) .Resize(size)); @@ -122,11 +132,11 @@ private static (FFMpegArguments, Action outputOptions) Bu { 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); if (source.PrimaryVideoStream.Rotation == 90 || source.PrimaryVideoStream.Rotation == 180) currentSize = new Size(source.PrimaryVideoStream.Height, source.PrimaryVideoStream.Width); - + if (wantedSize.Value.Width != currentSize.Width || wantedSize.Value.Height != currentSize.Height) { if (wantedSize.Value.Width <= 0 && wantedSize.Value.Height > 0) diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs index 94b1cb2..1126471 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs @@ -48,11 +48,11 @@ public FFMpegArgumentOptions WithVideoFilters(Action videoFi public FFMpegArgumentOptions WithSpeedPreset(Speed speed) => WithArgument(new SpeedPresetArgument(speed)); public FFMpegArgumentOptions WithStartNumber(int startNumber) => WithArgument(new StartNumberArgument(startNumber)); public FFMpegArgumentOptions WithCustomArgument(string argument) => WithArgument(new CustomArgument(argument)); - + public FFMpegArgumentOptions Seek(TimeSpan? seekTo) => WithArgument(new SeekArgument(seekTo)); public FFMpegArgumentOptions Loop(int times) => WithArgument(new LoopArgument(times)); public FFMpegArgumentOptions OverwriteExisting() => WithArgument(new OverwriteArgument()); - public FFMpegArgumentOptions SelectStream(int index) => WithArgument(new MapStreamArgument(index)); + public FFMpegArgumentOptions SelectStream(int streamIndex, int inputFileIndex = 0) => WithArgument(new MapStreamArgument(inputFileIndex, streamIndex)); public FFMpegArgumentOptions ForceFormat(ContainerFormat format) => WithArgument(new ForceFormatArgument(format)); public FFMpegArgumentOptions ForceFormat(string format) => WithArgument(new ForceFormatArgument(format)); From 86ea16c432a35e27e7c4e1986bd8a8e49549d4ce Mon Sep 17 00:00:00 2001 From: Fedor Zhilkin Date: Wed, 11 Aug 2021 15:54:58 +0300 Subject: [PATCH 7/7] Fix params order --- FFMpegCore.Test/ArgumentBuilderTest.cs | 2 +- .../FFMpeg/Arguments/MapStreamArgument.cs | 2 +- FFMpegCore/FFMpeg/FFMpeg.cs | 28 +++++++++---------- FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs | 12 ++++---- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index 5da3e0b..082d9bf 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -234,7 +234,7 @@ public void Builder_BuildString_FrameOutputCount() [TestMethod] public void Builder_BuildString_VideoStreamNumber() { - var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.SelectStream(0,1)).Arguments; + var str = FFMpegArguments.FromFileInput("input.mp4").OutputToFile("output.mp4", false, opt => opt.SelectStream(1)).Arguments; Assert.AreEqual("-i \"input.mp4\" -map 0:1 \"output.mp4\"", str); } diff --git a/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs b/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs index 6ce1dbe..b904be5 100644 --- a/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/MapStreamArgument.cs @@ -8,7 +8,7 @@ public class MapStreamArgument : IArgument private readonly int _inputFileIndex; private readonly int _streamIndex; - public MapStreamArgument(int inputFileIndex, int streamIndex) + public MapStreamArgument(int streamIndex, int inputFileIndex) { _inputFileIndex = inputFileIndex; _streamIndex = streamIndex; diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs index bffbd1a..c46d864 100644 --- a/FFMpegCore/FFMpeg/FFMpeg.cs +++ b/FFMpegCore/FFMpeg/FFMpeg.cs @@ -20,16 +20,16 @@ public static class FFMpeg /// 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. - /// Input file index /// Selected video stream index. + /// Input file index /// Bitmap with the requested snapshot. - public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int inputFileIndex = 0, int? streamIndex = null) + public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; var source = FFProbe.Analyse(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, inputFileIndex, streamIndex); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); return arguments .OutputToFile(output, true, outputOptions) @@ -42,16 +42,16 @@ public static bool Snapshot(string input, string output, Size? size = null, Time /// 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. - /// Input file index /// Selected video stream index. + /// Input file index /// Bitmap with the requested snapshot. - public static async Task SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int inputFileIndex = 0, int? streamIndex = null) + public static async Task SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) { if (Path.GetExtension(output) != FileExtension.Png) output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png; var source = await FFProbe.AnalyseAsync(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, inputFileIndex, streamIndex); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); return await arguments .OutputToFile(output, true, outputOptions) @@ -64,13 +64,13 @@ public static async Task SnapshotAsync(string input, string output, Size? /// 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. - /// Input file index /// Selected video stream index. + /// Input file index /// Bitmap with the requested snapshot. - public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int inputFileIndex = 0, int? streamIndex = null) + public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) { var source = FFProbe.Analyse(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, inputFileIndex, streamIndex); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); using var ms = new MemoryStream(); arguments @@ -88,13 +88,13 @@ public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? capture /// 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. - /// Input file index /// Selected video stream index. + /// Input file index /// Bitmap with the requested snapshot. - public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int inputFileIndex = 0, int? streamIndex = null) + public static async Task SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) { var source = await FFProbe.AnalyseAsync(input); - var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, inputFileIndex, streamIndex); + var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); using var ms = new MemoryStream(); await arguments @@ -111,8 +111,8 @@ private static (FFMpegArguments, Action outputOptions) Bu IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, - int inputFileIndex = 0, - int? streamIndex = null) + int? streamIndex = null, + int inputFileIndex = 0) { captureTime ??= TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3); size = PrepareSnapshotSize(source, size); diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs index 1126471..41ac38c 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs @@ -8,7 +8,7 @@ namespace FFMpegCore public class FFMpegArgumentOptions : FFMpegArgumentsBase { internal FFMpegArgumentOptions() { } - + public FFMpegArgumentOptions WithAudioCodec(Codec audioCodec) => WithArgument(new AudioCodecArgument(audioCodec)); public FFMpegArgumentOptions WithAudioCodec(string audioCodec) => WithArgument(new AudioCodecArgument(audioCodec)); public FFMpegArgumentOptions WithAudioBitrate(AudioQuality audioQuality) => WithArgument(new AudioBitrateArgument(audioQuality)); @@ -17,9 +17,9 @@ internal FFMpegArgumentOptions() { } public FFMpegArgumentOptions WithVariableBitrate(int vbr) => WithArgument(new VariableBitRateArgument(vbr)); public FFMpegArgumentOptions Resize(int width, int height) => WithArgument(new SizeArgument(width, height)); public FFMpegArgumentOptions Resize(Size? size) => WithArgument(new SizeArgument(size)); - - + + public FFMpegArgumentOptions WithBitStreamFilter(Channel channel, Filter filter) => WithArgument(new BitStreamFilterArgument(channel, filter)); public FFMpegArgumentOptions WithConstantRateFactor(int crf) => WithArgument(new ConstantRateFactorArgument(crf)); public FFMpegArgumentOptions CopyChannel(Channel channel = Channel.Both) => WithArgument(new CopyArgument(channel)); @@ -27,12 +27,12 @@ internal FFMpegArgumentOptions() { } public FFMpegArgumentOptions WithDuration(TimeSpan? duration) => WithArgument(new DurationArgument(duration)); public FFMpegArgumentOptions WithFastStart() => WithArgument(new FaststartArgument()); public FFMpegArgumentOptions WithFrameOutputCount(int frames) => WithArgument(new FrameOutputCountArgument(frames)); - public FFMpegArgumentOptions WithHardwareAcceleration(HardwareAccelerationDevice hardwareAccelerationDevice = HardwareAccelerationDevice.Auto) => WithArgument(new HardwareAccelerationArgument(hardwareAccelerationDevice)); + public FFMpegArgumentOptions WithHardwareAcceleration(HardwareAccelerationDevice hardwareAccelerationDevice = HardwareAccelerationDevice.Auto) => WithArgument(new HardwareAccelerationArgument(hardwareAccelerationDevice)); public FFMpegArgumentOptions UsingShortest(bool shortest = true) => WithArgument(new ShortestArgument(shortest)); public FFMpegArgumentOptions UsingMultithreading(bool multithread) => WithArgument(new ThreadsArgument(multithread)); public FFMpegArgumentOptions UsingThreads(int threads) => WithArgument(new ThreadsArgument(threads)); - + 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)); @@ -52,7 +52,7 @@ public FFMpegArgumentOptions WithVideoFilters(Action videoFi public FFMpegArgumentOptions Seek(TimeSpan? seekTo) => WithArgument(new SeekArgument(seekTo)); public FFMpegArgumentOptions Loop(int times) => WithArgument(new LoopArgument(times)); public FFMpegArgumentOptions OverwriteExisting() => WithArgument(new OverwriteArgument()); - public FFMpegArgumentOptions SelectStream(int streamIndex, int inputFileIndex = 0) => WithArgument(new MapStreamArgument(inputFileIndex, streamIndex)); + public FFMpegArgumentOptions SelectStream(int streamIndex, int inputFileIndex = 0) => WithArgument(new MapStreamArgument(streamIndex, inputFileIndex)); public FFMpegArgumentOptions ForceFormat(ContainerFormat format) => WithArgument(new ForceFormatArgument(format)); public FFMpegArgumentOptions ForceFormat(string format) => WithArgument(new ForceFormatArgument(format));