From be477197cc6c88faba5458e07f03a89ea6bf5cf4 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Wed, 9 Dec 2020 17:07:41 +0100 Subject: [PATCH 1/7] Support specifying output encoding for ffmpeg and ffprobe output Former-commit-id: a4aba666cda695e71a2252be78741aebeb5a844f --- FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs | 9 ++++++++- FFMpegCore/FFMpeg/FFMpegOptions.cs | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs index 5961ed3..e9369a5 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs @@ -130,7 +130,14 @@ private Instance PrepareInstance(out CancellationTokenSource cancellationTokenSo { FFMpegHelper.RootExceptionCheck(); FFMpegHelper.VerifyFFMpegExists(); - var instance = new Instance(FFMpegOptions.Options.FFmpegBinary(), _ffMpegArguments.Text); + var startInfo = new ProcessStartInfo + { + FileName = FFMpegOptions.Options.FFmpegBinary(), + Arguments = _ffMpegArguments.Text, + StandardOutputEncoding = FFMpegOptions.Options.Encoding, + StandardErrorEncoding = FFMpegOptions.Options.Encoding, + }; + var instance = new Instance(startInfo); cancellationTokenSource = new CancellationTokenSource(); if (_onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null)) diff --git a/FFMpegCore/FFMpeg/FFMpegOptions.cs b/FFMpegCore/FFMpeg/FFMpegOptions.cs index 947f942..5bdb3a9 100644 --- a/FFMpegCore/FFMpeg/FFMpegOptions.cs +++ b/FFMpegCore/FFMpeg/FFMpegOptions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; +using System.Text; using System.Text.Json; namespace FFMpegCore @@ -48,6 +49,7 @@ static FFMpegOptions() public Dictionary ExtensionOverrides { get; private set; } = new Dictionary(); public bool UseCache { get; set; } = true; + public Encoding Encoding { get; set; } = Encoding.Default; private static string FFBinary(string name) { From 3d640f9e08a3ff60d62959cac5dc2043f0afe03c Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Fri, 18 Dec 2020 00:40:09 +0100 Subject: [PATCH 2/7] Add NotifyOnOutput Former-commit-id: cfee86199b07900d6c5c9e51aa7a902cb2bd88b8 --- FFMpegCore.Test/VideoTest.cs | 22 ++++++++++++++++++++ FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs | 12 +++++++++-- FFMpegCore/FFMpeg/FFMpegOptions.cs | 5 ++--- FFMpegCore/FFMpegCore.csproj | 5 +++-- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index b2bff02..eb5b46b 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -7,6 +7,7 @@ using System.Drawing.Imaging; using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using FFMpegCore.Arguments; using FFMpegCore.Exceptions; @@ -550,6 +551,27 @@ public void Video_UpdatesProgress() Assert.AreNotEqual(TimeSpan.Zero, timeDone); } + [TestMethod, Timeout(10000)] + public void Video_OutputsData() + { + var outputFile = new TemporaryFile("out.mp4"); + var dataReceived = false; + + FFMpegOptions.Configure(opt => opt.Encoding = Encoding.UTF8); + var success = FFMpegArguments + .FromFileInput(TestResources.Mp4Video) + .WithGlobalOptions(options => options + .WithVerbosityLevel(VerbosityLevel.Info)) + .OutputToFile(outputFile, false, opt => opt + .WithDuration(TimeSpan.FromSeconds(2))) + .NotifyOnOutput((_, _) => dataReceived = true) + .ProcessSynchronously(); + + Assert.IsTrue(dataReceived); + Assert.IsTrue(success); + Assert.IsTrue(File.Exists(outputFile)); + } + [TestMethod, Timeout(10000)] public void Video_TranscodeInMemory() { diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs index e9369a5..cfbe42a 100644 --- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs +++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs @@ -17,6 +17,7 @@ public class FFMpegArgumentProcessor private readonly FFMpegArguments _ffMpegArguments; private Action? _onPercentageProgress; private Action? _onTimeProgress; + private Action? _onOutput; private TimeSpan? _totalTimespan; internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments) @@ -39,6 +40,11 @@ public FFMpegArgumentProcessor NotifyOnProgress(Action onTimeProgress) _onTimeProgress = onTimeProgress; return this; } + public FFMpegArgumentProcessor NotifyOnOutput(Action onOutput) + { + _onOutput = onOutput; + return this; + } public FFMpegArgumentProcessor CancellableThrough(out Action cancel) { cancel = () => CancelEvent?.Invoke(this, EventArgs.Empty); @@ -140,7 +146,7 @@ private Instance PrepareInstance(out CancellationTokenSource cancellationTokenSo var instance = new Instance(startInfo); cancellationTokenSource = new CancellationTokenSource(); - if (_onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null)) + if (_onOutput != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null)) instance.DataReceived += OutputData; return instance; @@ -157,8 +163,10 @@ private static bool HandleException(bool throwOnError, Exception e, IReadOnlyLis private void OutputData(object sender, (DataType Type, string Data) msg) { - var match = ProgressRegex.Match(msg.Data); Debug.WriteLine(msg.Data); + _onOutput?.Invoke(msg.Data, msg.Type); + + var match = ProgressRegex.Match(msg.Data); if (!match.Success) return; var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); diff --git a/FFMpegCore/FFMpeg/FFMpegOptions.cs b/FFMpegCore/FFMpeg/FFMpegOptions.cs index 5bdb3a9..a7d29b4 100644 --- a/FFMpegCore/FFMpeg/FFMpegOptions.cs +++ b/FFMpegCore/FFMpeg/FFMpegOptions.cs @@ -41,6 +41,8 @@ static FFMpegOptions() public string RootDirectory { get; set; } = DefaultRoot; public string TempDirectory { get; set; } = DefaultTemp; + public bool UseCache { get; set; } = true; + public Encoding Encoding { get; set; } = Encoding.Default; public string FFmpegBinary() => FFBinary("FFMpeg"); @@ -48,9 +50,6 @@ static FFMpegOptions() public Dictionary ExtensionOverrides { get; private set; } = new Dictionary(); - public bool UseCache { get; set; } = true; - public Encoding Encoding { get; set; } = Encoding.Default; - private static string FFBinary(string name) { var ffName = name.ToLowerInvariant(); diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index 43247f7..ad8ad15 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -9,9 +9,10 @@ 3.0.0.0 3.0.0.0 3.0.0.0 - - Also include ffmpeg output data on non-zero exit code + - Support for changing encoding used for parsing ffmpeg/ffprobe output +- Support for registrering action to invoke on ffmpeg output 8 - 3.2.4 + 3.3.0 MIT Malte Rosenbjerg, Vlad Jerca ffmpeg ffprobe convert video audio mediafile resize analyze muxing From ce12f753c069bdbd3e9ae1f5ac1d3899201955c0 Mon Sep 17 00:00:00 2001 From: hey-red Date: Sat, 23 Jan 2021 05:40:03 +0300 Subject: [PATCH 3/7] Fix incorrect condition Former-commit-id: d16efbda313187704fe32b9ccec6775d46ab90b3 --- FFMpegCore/FFMpeg/Enums/ContainerFormat.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/FFMpegCore/FFMpeg/Enums/ContainerFormat.cs b/FFMpegCore/FFMpeg/Enums/ContainerFormat.cs index 37c2bda..90da0d2 100644 --- a/FFMpegCore/FFMpeg/Enums/ContainerFormat.cs +++ b/FFMpegCore/FFMpeg/Enums/ContainerFormat.cs @@ -10,6 +10,7 @@ public class ContainerFormat public bool DemuxingSupported { get; private set; } public bool MuxingSupported { get; private set; } public string Description { get; private set; } = null!; + public string Extension { get @@ -36,11 +37,11 @@ internal static bool TryParse(string line, out ContainerFormat fmt) fmt = new ContainerFormat(match.Groups[3].Value) { - DemuxingSupported = match.Groups[1].Value == " ", - MuxingSupported = match.Groups[2].Value == " ", + DemuxingSupported = match.Groups[1].Value != " ", + MuxingSupported = match.Groups[2].Value != " ", Description = match.Groups[4].Value }; return true; } } -} +} \ No newline at end of file From e9ac0951eee08fd540546e62be38e1e889a8258e Mon Sep 17 00:00:00 2001 From: hey-red Date: Fri, 29 Jan 2021 16:03:59 +0300 Subject: [PATCH 4/7] Add tags on media stream and format Former-commit-id: f603163e250b092da96a9138ca1f31c1dfcd89f4 --- FFMpegCore/FFProbe/MediaAnalysis.cs | 16 ++++++++++++---- FFMpegCore/FFProbe/MediaFormat.cs | 2 ++ FFMpegCore/FFProbe/MediaStream.cs | 3 +++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/FFMpegCore/FFProbe/MediaAnalysis.cs b/FFMpegCore/FFProbe/MediaAnalysis.cs index 4717a6f..5a43aa2 100644 --- a/FFMpegCore/FFProbe/MediaAnalysis.cs +++ b/FFMpegCore/FFProbe/MediaAnalysis.cs @@ -8,6 +8,7 @@ namespace FFMpegCore 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) { Format = ParseFormat(analysis.Format); @@ -27,14 +28,15 @@ private MediaFormat ParseFormat(Format analysisFormat) FormatLongName = analysisFormat.FormatLongName, StreamCount = analysisFormat.NbStreams, ProbeScore = analysisFormat.ProbeScore, - BitRate = long.Parse(analysisFormat.BitRate ?? "0") + BitRate = long.Parse(analysisFormat.BitRate ?? "0"), + Tags = analysisFormat.Tags, }; } public string Path { get; } public string Extension => System.IO.Path.GetExtension(Path); - public TimeSpan Duration => new [] + public TimeSpan Duration => new[] { Format.Duration, PrimaryVideoStream?.Duration ?? TimeSpan.Zero, @@ -67,7 +69,8 @@ private VideoStream ParseVideoStream(FFProbeStream stream) Profile = stream.Profile, PixelFormat = stream.PixelFormat, Rotation = (int)float.Parse(stream.GetRotate() ?? "0"), - Language = stream.GetLanguage() + Language = stream.GetLanguage(), + Tags = stream.Tags, }; } @@ -77,6 +80,7 @@ private static TimeSpan ParseDuration(FFProbeStream ffProbeStream) ? TimeSpan.Parse(ffProbeStream.Duration) : TimeSpan.Parse(TrimTimeSpan(ffProbeStream.GetDuration()) ?? "0"); } + private static string? TrimTimeSpan(string? durationTag) { var durationMatch = DurationRegex.Match(durationTag ?? ""); @@ -96,17 +100,20 @@ private AudioStream ParseAudioStream(FFProbeStream stream) Duration = ParseDuration(stream), SampleRateHz = !string.IsNullOrEmpty(stream.SampleRate) ? ParseIntInvariant(stream.SampleRate) : default, Profile = stream.Profile, - Language = stream.GetLanguage() + Language = stream.GetLanguage(), + Tags = stream.Tags, }; } private static double DivideRatio((double, double) ratio) => ratio.Item1 / ratio.Item2; + private static (int, int) ParseRatioInt(string input, char separator) { if (string.IsNullOrEmpty(input)) return (0, 0); var ratio = input.Split(separator); return (ParseIntInvariant(ratio[0]), ParseIntInvariant(ratio[1])); } + private static (double, double) ParseRatioDouble(string input, char separator) { if (string.IsNullOrEmpty(input)) return (0, 0); @@ -116,6 +123,7 @@ private static (double, double) ParseRatioDouble(string input, char separator) private static double ParseDoubleInvariant(string line) => double.Parse(line, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture); + private static int ParseIntInvariant(string line) => int.Parse(line, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture); } diff --git a/FFMpegCore/FFProbe/MediaFormat.cs b/FFMpegCore/FFProbe/MediaFormat.cs index ea5c6f3..874317c 100644 --- a/FFMpegCore/FFProbe/MediaFormat.cs +++ b/FFMpegCore/FFProbe/MediaFormat.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace FFMpegCore { @@ -10,5 +11,6 @@ public class MediaFormat public int StreamCount { get; set; } public double ProbeScore { get; set; } public double BitRate { get; set; } + public Dictionary? Tags { get; set; } } } \ No newline at end of file diff --git a/FFMpegCore/FFProbe/MediaStream.cs b/FFMpegCore/FFProbe/MediaStream.cs index 61d548f..0780c8e 100644 --- a/FFMpegCore/FFProbe/MediaStream.cs +++ b/FFMpegCore/FFProbe/MediaStream.cs @@ -1,5 +1,7 @@ using FFMpegCore.Enums; + using System; +using System.Collections.Generic; namespace FFMpegCore { @@ -11,6 +13,7 @@ public class MediaStream public int BitRate { get; internal set; } public TimeSpan Duration { get; internal set; } public string? Language { get; internal set; } + public Dictionary? Tags { get; internal set; } public Codec GetCodecInfo() => FFMpeg.GetCodec(CodecName); } From 08c898b56d8719ffe3c7c70b7884432e166b05b2 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Wed, 3 Feb 2021 23:20:12 +0100 Subject: [PATCH 5/7] Return null when no format detected during analysis Former-commit-id: 66cdb302efb3bc490b6c4d5eb137ab741a444fea --- FFMpegCore/FFProbe/FFProbe.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/FFMpegCore/FFProbe/FFProbe.cs b/FFMpegCore/FFProbe/FFProbe.cs index 076bbfd..8bb68db 100644 --- a/FFMpegCore/FFProbe/FFProbe.cs +++ b/FFMpegCore/FFProbe/FFProbe.cs @@ -12,7 +12,7 @@ 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}'"); @@ -21,13 +21,13 @@ public static IMediaAnalysis Analyse(string filePath, int outputCapacity = int.M instance.BlockUntilFinished(); return ParseOutput(filePath, 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); } - 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); @@ -50,7 +50,7 @@ public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.Max return ParseOutput(pipeArgument.PipePath, 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}'"); @@ -59,13 +59,13 @@ public static async Task AnalyseAsync(string filePath, int outpu await instance.FinishedRunning(); return ParseOutput(filePath, 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); } - 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); @@ -92,13 +92,14 @@ public static async Task AnalyseAsync(Stream stream, int outputC return ParseOutput(pipeArgument.PipePath, instance); } - private static IMediaAnalysis ParseOutput(string filePath, Instance instance) + private static IMediaAnalysis? ParseOutput(string filePath, 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); } From 10726acc69786209ed9cd6df7d88f0e177ced05a Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Wed, 3 Feb 2021 23:20:26 +0100 Subject: [PATCH 6/7] Update FFMpegCore.csproj Former-commit-id: 4cb80432f6c5f4c9d54a8fb4201d0c00ecc3bc8f --- FFMpegCore/FFMpegCore.csproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index ad8ad15..cb5d906 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -9,10 +9,10 @@ 3.0.0.0 3.0.0.0 3.0.0.0 - - Support for changing encoding used for parsing ffmpeg/ffprobe output -- Support for registrering action to invoke on ffmpeg output + - return null from FFProbe.Analyse* when no media format was detected +- Expose tags as string dictionary on IMediaAnalysis (thanks hey-red) 8 - 3.3.0 + 3.4.0 MIT Malte Rosenbjerg, Vlad Jerca ffmpeg ffprobe convert video audio mediafile resize analyze muxing @@ -31,7 +31,7 @@ - + From cbb6c5a055bfd3470dd6fc24645684355a8bab90 Mon Sep 17 00:00:00 2001 From: Malte Rosenbjerg Date: Wed, 3 Feb 2021 23:58:11 +0100 Subject: [PATCH 7/7] Added tiesont to contributors list Helping other users of this library Former-commit-id: af67cc2fbb10c0438fc0a936304b5544f192fafe --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0012def..6a9fc35 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,7 @@ The root and temp directory for the ffmpeg binaries can be configured via the `f + ### License