mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
Merge branch 'master' into feature/aaxc_support
This commit is contained in:
commit
1ba796596d
10 changed files with 177 additions and 159 deletions
|
@ -11,13 +11,6 @@ namespace FFMpegCore.Test
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class FFProbeTests
|
public class FFProbeTests
|
||||||
{
|
{
|
||||||
[TestMethod]
|
|
||||||
public void Probe_TooLongOutput()
|
|
||||||
{
|
|
||||||
Assert.ThrowsException<System.Text.Json.JsonException>(() => FFProbe.Analyse(TestResources.Mp4Video, 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public async Task Audio_FromStream_Duration()
|
public async Task Audio_FromStream_Duration()
|
||||||
{
|
{
|
||||||
|
|
|
@ -544,7 +544,7 @@ public void Video_OutputsData()
|
||||||
.WithVerbosityLevel(VerbosityLevel.Info))
|
.WithVerbosityLevel(VerbosityLevel.Info))
|
||||||
.OutputToFile(outputFile, false, opt => opt
|
.OutputToFile(outputFile, false, opt => opt
|
||||||
.WithDuration(TimeSpan.FromSeconds(2)))
|
.WithDuration(TimeSpan.FromSeconds(2)))
|
||||||
.NotifyOnOutput((_, _) => dataReceived = true)
|
.NotifyOnError(_ => dataReceived = true)
|
||||||
.ProcessSynchronously();
|
.ProcessSynchronously();
|
||||||
|
|
||||||
Assert.IsTrue(dataReceived);
|
Assert.IsTrue(dataReceived);
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Instances;
|
||||||
|
|
||||||
namespace FFMpegCore
|
namespace FFMpegCore
|
||||||
{
|
{
|
||||||
|
@ -417,15 +418,16 @@ internal static IReadOnlyList<PixelFormat> GetPixelFormatsInternal()
|
||||||
FFMpegHelper.RootExceptionCheck();
|
FFMpegHelper.RootExceptionCheck();
|
||||||
|
|
||||||
var list = new List<PixelFormat>();
|
var list = new List<PixelFormat>();
|
||||||
using var instance = new Instances.Instance(GlobalFFOptions.GetFFMpegBinaryPath(), "-pix_fmts");
|
var processArguments = new ProcessArguments(GlobalFFOptions.GetFFMpegBinaryPath(), "-pix_fmts");
|
||||||
instance.DataReceived += (e, args) =>
|
processArguments.OutputDataReceived += (e, data) =>
|
||||||
{
|
{
|
||||||
if (PixelFormat.TryParse(args.Data, out var format))
|
if (PixelFormat.TryParse(data, out var format))
|
||||||
list.Add(format);
|
list.Add(format);
|
||||||
};
|
};
|
||||||
|
|
||||||
var exitCode = instance.BlockUntilFinished();
|
var result = processArguments.StartAndWaitForExit();
|
||||||
if (exitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", instance.OutputData));
|
if (result.ExitCode != 0)
|
||||||
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
|
||||||
|
|
||||||
return list.AsReadOnly();
|
return list.AsReadOnly();
|
||||||
}
|
}
|
||||||
|
@ -462,10 +464,10 @@ private static void ParsePartOfCodecs(Dictionary<string, Codec> codecs, string a
|
||||||
{
|
{
|
||||||
FFMpegHelper.RootExceptionCheck();
|
FFMpegHelper.RootExceptionCheck();
|
||||||
|
|
||||||
using var instance = new Instances.Instance(GlobalFFOptions.GetFFMpegBinaryPath(), arguments);
|
var processArguments = new ProcessArguments(GlobalFFOptions.GetFFMpegBinaryPath(), arguments);
|
||||||
instance.DataReceived += (e, args) =>
|
processArguments.OutputDataReceived += (e, data) =>
|
||||||
{
|
{
|
||||||
var codec = parser(args.Data);
|
var codec = parser(data);
|
||||||
if(codec != null)
|
if(codec != null)
|
||||||
if (codecs.TryGetValue(codec.Name, out var parentCodec))
|
if (codecs.TryGetValue(codec.Name, out var parentCodec))
|
||||||
parentCodec.Merge(codec);
|
parentCodec.Merge(codec);
|
||||||
|
@ -473,8 +475,8 @@ private static void ParsePartOfCodecs(Dictionary<string, Codec> codecs, string a
|
||||||
codecs.Add(codec.Name, codec);
|
codecs.Add(codec.Name, codec);
|
||||||
};
|
};
|
||||||
|
|
||||||
var exitCode = instance.BlockUntilFinished();
|
var result = processArguments.StartAndWaitForExit();
|
||||||
if (exitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", instance.OutputData));
|
if (result.ExitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Dictionary<string, Codec> GetCodecsInternal()
|
internal static Dictionary<string, Codec> GetCodecsInternal()
|
||||||
|
@ -546,15 +548,15 @@ internal static IReadOnlyList<ContainerFormat> GetContainersFormatsInternal()
|
||||||
FFMpegHelper.RootExceptionCheck();
|
FFMpegHelper.RootExceptionCheck();
|
||||||
|
|
||||||
var list = new List<ContainerFormat>();
|
var list = new List<ContainerFormat>();
|
||||||
using var instance = new Instances.Instance(GlobalFFOptions.GetFFMpegBinaryPath(), "-formats");
|
var instance = new ProcessArguments(GlobalFFOptions.GetFFMpegBinaryPath(), "-formats");
|
||||||
instance.DataReceived += (e, args) =>
|
instance.OutputDataReceived += (e, data) =>
|
||||||
{
|
{
|
||||||
if (ContainerFormat.TryParse(args.Data, out var fmt))
|
if (ContainerFormat.TryParse(data, out var fmt))
|
||||||
list.Add(fmt);
|
list.Add(fmt);
|
||||||
};
|
};
|
||||||
|
|
||||||
var exitCode = instance.BlockUntilFinished();
|
var result = instance.StartAndWaitForExit();
|
||||||
if (exitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", instance.OutputData));
|
if (result.ExitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
|
||||||
|
|
||||||
return list.AsReadOnly();
|
return list.AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,8 @@ public class FFMpegArgumentProcessor
|
||||||
private readonly FFMpegArguments _ffMpegArguments;
|
private readonly FFMpegArguments _ffMpegArguments;
|
||||||
private Action<double>? _onPercentageProgress;
|
private Action<double>? _onPercentageProgress;
|
||||||
private Action<TimeSpan>? _onTimeProgress;
|
private Action<TimeSpan>? _onTimeProgress;
|
||||||
private Action<string, DataType>? _onOutput;
|
private Action<string>? _onOutput;
|
||||||
|
private Action<string>? _onError;
|
||||||
private TimeSpan? _totalTimespan;
|
private TimeSpan? _totalTimespan;
|
||||||
|
|
||||||
internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments)
|
internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments)
|
||||||
|
@ -57,11 +58,16 @@ public FFMpegArgumentProcessor NotifyOnProgress(Action<TimeSpan> onTimeProgress)
|
||||||
/// Register action that will be invoked during the ffmpeg processing, when a line is output
|
/// Register action that will be invoked during the ffmpeg processing, when a line is output
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="onOutput"></param>
|
/// <param name="onOutput"></param>
|
||||||
public FFMpegArgumentProcessor NotifyOnOutput(Action<string, DataType> onOutput)
|
public FFMpegArgumentProcessor NotifyOnOutput(Action<string> onOutput)
|
||||||
{
|
{
|
||||||
_onOutput = onOutput;
|
_onOutput = onOutput;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public FFMpegArgumentProcessor NotifyOnError(Action<string> onError)
|
||||||
|
{
|
||||||
|
_onError = onError;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
public FFMpegArgumentProcessor CancellableThrough(out Action cancel, int timeout = 0)
|
public FFMpegArgumentProcessor CancellableThrough(out Action cancel, int timeout = 0)
|
||||||
{
|
{
|
||||||
cancel = () => CancelEvent?.Invoke(this, timeout);
|
cancel = () => CancelEvent?.Invoke(this, timeout);
|
||||||
|
@ -80,43 +86,47 @@ public FFMpegArgumentProcessor Configure(Action<FFOptions> configureOptions)
|
||||||
public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
|
public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
|
||||||
{
|
{
|
||||||
var options = GetConfiguredOptions(ffMpegOptions);
|
var options = GetConfiguredOptions(ffMpegOptions);
|
||||||
using var instance = PrepareInstance(options, out var cancellationTokenSource);
|
var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource);
|
||||||
|
processArguments.Exited += delegate { cancellationTokenSource.Cancel(); };
|
||||||
|
|
||||||
void OnCancelEvent(object sender, int timeout)
|
IProcessResult? processResult = null;
|
||||||
{
|
|
||||||
instance.SendInput("q");
|
|
||||||
|
|
||||||
if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
|
|
||||||
{
|
|
||||||
cancellationTokenSource.Cancel();
|
|
||||||
instance.Started = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CancelEvent += OnCancelEvent;
|
|
||||||
instance.Exited += delegate { cancellationTokenSource.Cancel(); };
|
|
||||||
|
|
||||||
var errorCode = -1;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
errorCode = Process(instance, cancellationTokenSource).ConfigureAwait(false).GetAwaiter().GetResult();
|
processResult = Process(processArguments, cancellationTokenSource).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
if (!HandleException(throwOnError, e, instance.ErrorData)) return false;
|
if (!HandleException(throwOnError, e, processResult?.ErrorData ?? Array.Empty<string>())) return false;
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
CancelEvent -= OnCancelEvent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return HandleCompletion(throwOnError, errorCode, instance.ErrorData);
|
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> ProcessAsynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
|
public async Task<bool> ProcessAsynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
|
||||||
{
|
{
|
||||||
var options = GetConfiguredOptions(ffMpegOptions);
|
var options = GetConfiguredOptions(ffMpegOptions);
|
||||||
using var instance = PrepareInstance(options, out var cancellationTokenSource);
|
var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource);
|
||||||
|
|
||||||
|
IProcessResult? processResult = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
processResult = await Process(processArguments, cancellationTokenSource).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (!HandleException(throwOnError, e, processResult?.ErrorData ?? Array.Empty<string>())) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<IProcessResult> Process(ProcessArguments processArguments, CancellationTokenSource cancellationTokenSource)
|
||||||
|
{
|
||||||
|
IProcessResult processResult = null!;
|
||||||
|
|
||||||
|
_ffMpegArguments.Pre();
|
||||||
|
|
||||||
|
using var instance = processArguments.Start();
|
||||||
void OnCancelEvent(object sender, int timeout)
|
void OnCancelEvent(object sender, int timeout)
|
||||||
{
|
{
|
||||||
instance.SendInput("q");
|
instance.SendInput("q");
|
||||||
|
@ -124,41 +134,26 @@ void OnCancelEvent(object sender, int timeout)
|
||||||
if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
|
if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
|
||||||
{
|
{
|
||||||
cancellationTokenSource.Cancel();
|
cancellationTokenSource.Cancel();
|
||||||
instance.Started = false;
|
instance.Kill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CancelEvent += OnCancelEvent;
|
CancelEvent += OnCancelEvent;
|
||||||
|
|
||||||
var errorCode = -1;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
errorCode = await Process(instance, cancellationTokenSource).ConfigureAwait(false);
|
await Task.WhenAll(instance.WaitForExitAsync().ContinueWith(t =>
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
{
|
||||||
if (!HandleException(throwOnError, e, instance.ErrorData)) return false;
|
processResult = t.Result;
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
_ffMpegArguments.Post();
|
||||||
|
}), _ffMpegArguments.During(cancellationTokenSource.Token)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return processResult;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
CancelEvent -= OnCancelEvent;
|
CancelEvent -= OnCancelEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HandleCompletion(throwOnError, errorCode, instance.ErrorData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<int> Process(Instance instance, CancellationTokenSource cancellationTokenSource)
|
|
||||||
{
|
|
||||||
var errorCode = -1;
|
|
||||||
|
|
||||||
_ffMpegArguments.Pre();
|
|
||||||
await Task.WhenAll(instance.FinishedRunning().ContinueWith(t =>
|
|
||||||
{
|
|
||||||
errorCode = t.Result;
|
|
||||||
cancellationTokenSource.Cancel();
|
|
||||||
_ffMpegArguments.Post();
|
|
||||||
}), _ffMpegArguments.During(cancellationTokenSource.Token)).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return errorCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HandleCompletion(bool throwOnError, int exitCode, IReadOnlyList<string> errorData)
|
private bool HandleCompletion(bool throwOnError, int exitCode, IReadOnlyList<string> errorData)
|
||||||
|
@ -184,7 +179,7 @@ internal FFOptions GetConfiguredOptions(FFOptions? ffOptions)
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Instance PrepareInstance(FFOptions ffOptions,
|
private ProcessArguments PrepareProcessArguments(FFOptions ffOptions,
|
||||||
out CancellationTokenSource cancellationTokenSource)
|
out CancellationTokenSource cancellationTokenSource)
|
||||||
{
|
{
|
||||||
FFMpegHelper.RootExceptionCheck();
|
FFMpegHelper.RootExceptionCheck();
|
||||||
|
@ -197,17 +192,25 @@ private Instance PrepareInstance(FFOptions ffOptions,
|
||||||
StandardErrorEncoding = ffOptions.Encoding,
|
StandardErrorEncoding = ffOptions.Encoding,
|
||||||
WorkingDirectory = ffOptions.WorkingDirectory
|
WorkingDirectory = ffOptions.WorkingDirectory
|
||||||
};
|
};
|
||||||
var instance = new Instance(startInfo);
|
var processArguments = new ProcessArguments(startInfo);
|
||||||
cancellationTokenSource = new CancellationTokenSource();
|
cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
if (_onOutput != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null))
|
if (_onOutput != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null))
|
||||||
instance.DataReceived += OutputData;
|
processArguments.OutputDataReceived += OutputData;
|
||||||
|
|
||||||
return instance;
|
if (_onError != null)
|
||||||
|
processArguments.ErrorDataReceived += ErrorData;
|
||||||
|
|
||||||
|
return processArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ErrorData(object sender, string msg)
|
||||||
|
{
|
||||||
|
_onError?.Invoke(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static bool HandleException(bool throwOnError, Exception e, IReadOnlyList<string> errorData)
|
private static bool HandleException(bool throwOnError, Exception e, IEnumerable<string> errorData)
|
||||||
{
|
{
|
||||||
if (!throwOnError)
|
if (!throwOnError)
|
||||||
return false;
|
return false;
|
||||||
|
@ -215,12 +218,12 @@ private static bool HandleException(bool throwOnError, Exception e, IReadOnlyLis
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, "Exception thrown during processing", e, 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)
|
private void OutputData(object sender, string msg)
|
||||||
{
|
{
|
||||||
Debug.WriteLine(msg.Data);
|
Debug.WriteLine(msg);
|
||||||
_onOutput?.Invoke(msg.Data, msg.Type);
|
_onOutput?.Invoke(msg);
|
||||||
|
|
||||||
var match = ProgressRegex.Match(msg.Data);
|
var match = ProgressRegex.Match(msg);
|
||||||
if (!match.Success) return;
|
if (!match.Success) return;
|
||||||
|
|
||||||
var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
|
var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
|
||||||
|
|
|
@ -32,9 +32,9 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Instances" Version="1.6.1" />
|
<PackageReference Include="Instances" Version="2.0.0" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
|
<PackageReference Include="System.Drawing.Common" Version="5.0.2" />
|
||||||
<PackageReference Include="System.Text.Json" Version="5.0.1" />
|
<PackageReference Include="System.Text.Json" Version="6.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -13,61 +13,61 @@ namespace FFMpegCore
|
||||||
{
|
{
|
||||||
public static class FFProbe
|
public static class FFProbe
|
||||||
{
|
{
|
||||||
public static IMediaAnalysis Analyse(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
|
public static IMediaAnalysis Analyse(string filePath, FFOptions? ffOptions = null)
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
||||||
|
|
||||||
using var instance = PrepareStreamAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
|
var processArguments = PrepareStreamAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
|
||||||
var exitCode = instance.BlockUntilFinished();
|
var result = processArguments.StartAndWaitForExit();
|
||||||
if (exitCode != 0)
|
if (result.ExitCode != 0)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
|
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
|
||||||
|
|
||||||
return ParseOutput(instance);
|
return ParseOutput(result);
|
||||||
}
|
}
|
||||||
public static FFProbeFrames GetFrames(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
|
public static FFProbeFrames GetFrames(string filePath, FFOptions? ffOptions = null)
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
||||||
|
|
||||||
using var instance = PrepareFrameAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
|
var instance = PrepareFrameAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
|
||||||
var exitCode = instance.BlockUntilFinished();
|
var result = instance.StartAndWaitForExit();
|
||||||
if (exitCode != 0)
|
if (result.ExitCode != 0)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
|
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
|
||||||
|
|
||||||
return ParseFramesOutput(instance);
|
return ParseFramesOutput(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static FFProbePackets GetPackets(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
|
public static FFProbePackets GetPackets(string filePath, FFOptions? ffOptions = null)
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
||||||
|
|
||||||
using var instance = PreparePacketAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
|
var instance = PreparePacketAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
|
||||||
var exitCode = instance.BlockUntilFinished();
|
var result = instance.StartAndWaitForExit();
|
||||||
if (exitCode != 0)
|
if (result.ExitCode != 0)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
|
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
|
||||||
|
|
||||||
return ParsePacketsOutput(instance);
|
return ParsePacketsOutput(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IMediaAnalysis Analyse(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
|
public static IMediaAnalysis Analyse(Uri uri, FFOptions? ffOptions = null)
|
||||||
{
|
{
|
||||||
using var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
|
var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current);
|
||||||
var exitCode = instance.BlockUntilFinished();
|
var result = instance.StartAndWaitForExit();
|
||||||
if (exitCode != 0)
|
if (result.ExitCode != 0)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
|
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
|
||||||
|
|
||||||
return ParseOutput(instance);
|
return ParseOutput(result);
|
||||||
}
|
}
|
||||||
public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
|
public static IMediaAnalysis Analyse(Stream stream, FFOptions? ffOptions = null)
|
||||||
{
|
{
|
||||||
var streamPipeSource = new StreamPipeSource(stream);
|
var streamPipeSource = new StreamPipeSource(stream);
|
||||||
var pipeArgument = new InputPipeArgument(streamPipeSource);
|
var pipeArgument = new InputPipeArgument(streamPipeSource);
|
||||||
using var instance = PrepareStreamAnalysisInstance(pipeArgument.PipePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
|
var instance = PrepareStreamAnalysisInstance(pipeArgument.PipePath, ffOptions ?? GlobalFFOptions.Current);
|
||||||
pipeArgument.Pre();
|
pipeArgument.Pre();
|
||||||
|
|
||||||
var task = instance.FinishedRunning();
|
var task = instance.StartAndWaitForExitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
pipeArgument.During().ConfigureAwait(false).GetAwaiter().GetResult();
|
pipeArgument.During().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
|
@ -77,62 +77,62 @@ public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.Max
|
||||||
{
|
{
|
||||||
pipeArgument.Post();
|
pipeArgument.Post();
|
||||||
}
|
}
|
||||||
var exitCode = task.ConfigureAwait(false).GetAwaiter().GetResult();
|
var result = task.ConfigureAwait(false).GetAwaiter().GetResult();
|
||||||
if (exitCode != 0)
|
if (result.ExitCode != 0)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
|
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
|
||||||
|
|
||||||
return ParseOutput(instance);
|
return ParseOutput(result);
|
||||||
}
|
}
|
||||||
public static async Task<IMediaAnalysis> AnalyseAsync(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
|
public static async Task<IMediaAnalysis> AnalyseAsync(string filePath, FFOptions? ffOptions = null)
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
||||||
|
|
||||||
using var instance = PrepareStreamAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
|
var instance = PrepareStreamAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
|
||||||
var exitCode = await instance.FinishedRunning().ConfigureAwait(false);
|
var result = await instance.StartAndWaitForExitAsync().ConfigureAwait(false);
|
||||||
if (exitCode != 0)
|
if (result.ExitCode != 0)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
|
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
|
||||||
|
|
||||||
return ParseOutput(instance);
|
return ParseOutput(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<FFProbeFrames> GetFramesAsync(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
|
public static async Task<FFProbeFrames> GetFramesAsync(string filePath, FFOptions? ffOptions = null)
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
||||||
|
|
||||||
using var instance = PrepareFrameAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
|
var instance = PrepareFrameAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
|
||||||
await instance.FinishedRunning().ConfigureAwait(false);
|
var result = await instance.StartAndWaitForExitAsync().ConfigureAwait(false);
|
||||||
return ParseFramesOutput(instance);
|
return ParseFramesOutput(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<FFProbePackets> GetPacketsAsync(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
|
public static async Task<FFProbePackets> GetPacketsAsync(string filePath, FFOptions? ffOptions = null)
|
||||||
{
|
{
|
||||||
if (!File.Exists(filePath))
|
if (!File.Exists(filePath))
|
||||||
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
|
||||||
|
|
||||||
using var instance = PreparePacketAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
|
var instance = PreparePacketAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
|
||||||
await instance.FinishedRunning().ConfigureAwait(false);
|
var result = await instance.StartAndWaitForExitAsync().ConfigureAwait(false);
|
||||||
return ParsePacketsOutput(instance);
|
return ParsePacketsOutput(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IMediaAnalysis> AnalyseAsync(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
|
public static async Task<IMediaAnalysis> AnalyseAsync(Uri uri, FFOptions? ffOptions = null)
|
||||||
{
|
{
|
||||||
using var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
|
var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current);
|
||||||
var exitCode = await instance.FinishedRunning().ConfigureAwait(false);
|
var result = await instance.StartAndWaitForExitAsync().ConfigureAwait(false);
|
||||||
if (exitCode != 0)
|
if (result.ExitCode != 0)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
|
throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
|
||||||
|
|
||||||
return ParseOutput(instance);
|
return ParseOutput(result);
|
||||||
}
|
}
|
||||||
public static async Task<IMediaAnalysis> AnalyseAsync(Stream stream, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
|
public static async Task<IMediaAnalysis> AnalyseAsync(Stream stream, FFOptions? ffOptions = null)
|
||||||
{
|
{
|
||||||
var streamPipeSource = new StreamPipeSource(stream);
|
var streamPipeSource = new StreamPipeSource(stream);
|
||||||
var pipeArgument = new InputPipeArgument(streamPipeSource);
|
var pipeArgument = new InputPipeArgument(streamPipeSource);
|
||||||
using var instance = PrepareStreamAnalysisInstance(pipeArgument.PipePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
|
var instance = PrepareStreamAnalysisInstance(pipeArgument.PipePath, ffOptions ?? GlobalFFOptions.Current);
|
||||||
pipeArgument.Pre();
|
pipeArgument.Pre();
|
||||||
|
|
||||||
var task = instance.FinishedRunning();
|
var task = instance.StartAndWaitForExitAsync();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await pipeArgument.During().ConfigureAwait(false);
|
await pipeArgument.During().ConfigureAwait(false);
|
||||||
|
@ -144,15 +144,15 @@ public static async Task<IMediaAnalysis> AnalyseAsync(Stream stream, int outputC
|
||||||
{
|
{
|
||||||
pipeArgument.Post();
|
pipeArgument.Post();
|
||||||
}
|
}
|
||||||
var exitCode = await task.ConfigureAwait(false);
|
var result = await task.ConfigureAwait(false);
|
||||||
if (exitCode != 0)
|
if (result.ExitCode != 0)
|
||||||
throw new FFProbeProcessException($"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", instance.ErrorData);
|
throw new FFProbeProcessException($"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", result.ErrorData);
|
||||||
|
|
||||||
pipeArgument.Post();
|
pipeArgument.Post();
|
||||||
return ParseOutput(instance);
|
return ParseOutput(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IMediaAnalysis ParseOutput(Instance instance)
|
private static IMediaAnalysis ParseOutput(IProcessResult instance)
|
||||||
{
|
{
|
||||||
var json = string.Join(string.Empty, instance.OutputData);
|
var json = string.Join(string.Empty, instance.OutputData);
|
||||||
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbeAnalysis>(json, new JsonSerializerOptions
|
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbeAnalysis>(json, new JsonSerializerOptions
|
||||||
|
@ -165,7 +165,7 @@ private static IMediaAnalysis ParseOutput(Instance instance)
|
||||||
|
|
||||||
return new MediaAnalysis(ffprobeAnalysis);
|
return new MediaAnalysis(ffprobeAnalysis);
|
||||||
}
|
}
|
||||||
private static FFProbeFrames ParseFramesOutput(Instance instance)
|
private static FFProbeFrames ParseFramesOutput(IProcessResult instance)
|
||||||
{
|
{
|
||||||
var json = string.Join(string.Empty, instance.OutputData);
|
var json = string.Join(string.Empty, instance.OutputData);
|
||||||
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbeFrames>(json, new JsonSerializerOptions
|
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbeFrames>(json, new JsonSerializerOptions
|
||||||
|
@ -177,7 +177,7 @@ private static FFProbeFrames ParseFramesOutput(Instance instance)
|
||||||
return ffprobeAnalysis;
|
return ffprobeAnalysis;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FFProbePackets ParsePacketsOutput(Instance instance)
|
private static FFProbePackets ParsePacketsOutput(IProcessResult instance)
|
||||||
{
|
{
|
||||||
var json = string.Join(string.Empty, instance.OutputData);
|
var json = string.Join(string.Empty, instance.OutputData);
|
||||||
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbePackets>(json, new JsonSerializerOptions
|
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbePackets>(json, new JsonSerializerOptions
|
||||||
|
@ -190,25 +190,24 @@ private static FFProbePackets ParsePacketsOutput(Instance instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Instance PrepareStreamAnalysisInstance(string filePath, int outputCapacity, FFOptions ffOptions)
|
private static ProcessArguments PrepareStreamAnalysisInstance(string filePath, FFOptions ffOptions)
|
||||||
=> PrepareInstance($"-loglevel error -print_format json -show_format -sexagesimal -show_streams \"{filePath}\"", outputCapacity, ffOptions);
|
=> PrepareInstance($"-loglevel error -print_format json -show_format -sexagesimal -show_streams \"{filePath}\"", ffOptions);
|
||||||
private static Instance PrepareFrameAnalysisInstance(string filePath, int outputCapacity, FFOptions ffOptions)
|
private static ProcessArguments PrepareFrameAnalysisInstance(string filePath, FFOptions ffOptions)
|
||||||
=> PrepareInstance($"-loglevel error -print_format json -show_frames -v quiet -sexagesimal \"{filePath}\"", outputCapacity, ffOptions);
|
=> PrepareInstance($"-loglevel error -print_format json -show_frames -v quiet -sexagesimal \"{filePath}\"", ffOptions);
|
||||||
private static Instance PreparePacketAnalysisInstance(string filePath, int outputCapacity, FFOptions ffOptions)
|
private static ProcessArguments PreparePacketAnalysisInstance(string filePath, FFOptions ffOptions)
|
||||||
=> PrepareInstance($"-loglevel error -print_format json -show_packets -v quiet -sexagesimal \"{filePath}\"", outputCapacity, ffOptions);
|
=> PrepareInstance($"-loglevel error -print_format json -show_packets -v quiet -sexagesimal \"{filePath}\"", ffOptions);
|
||||||
|
|
||||||
private static Instance PrepareInstance(string arguments, int outputCapacity, FFOptions ffOptions)
|
private static ProcessArguments PrepareInstance(string arguments, FFOptions ffOptions)
|
||||||
{
|
{
|
||||||
FFProbeHelper.RootExceptionCheck();
|
FFProbeHelper.RootExceptionCheck();
|
||||||
FFProbeHelper.VerifyFFProbeExists(ffOptions);
|
FFProbeHelper.VerifyFFProbeExists(ffOptions);
|
||||||
var startInfo = new ProcessStartInfo(GlobalFFOptions.GetFFProbeBinaryPath(), arguments)
|
var startInfo = new ProcessStartInfo(GlobalFFOptions.GetFFProbeBinaryPath(ffOptions), arguments)
|
||||||
{
|
{
|
||||||
StandardOutputEncoding = ffOptions.Encoding,
|
StandardOutputEncoding = ffOptions.Encoding,
|
||||||
StandardErrorEncoding = ffOptions.Encoding,
|
StandardErrorEncoding = ffOptions.Encoding,
|
||||||
WorkingDirectory = ffOptions.WorkingDirectory
|
WorkingDirectory = ffOptions.WorkingDirectory
|
||||||
};
|
};
|
||||||
var instance = new Instance(startInfo) { DataBufferCapacity = outputCapacity };
|
return new ProcessArguments(startInfo);
|
||||||
return instance;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
FFMpegCore/FFProbe/ProcessArgumentsExtensions.cs
Normal file
20
FFMpegCore/FFProbe/ProcessArgumentsExtensions.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Instances;
|
||||||
|
|
||||||
|
namespace FFMpegCore
|
||||||
|
{
|
||||||
|
public static class ProcessArgumentsExtensions
|
||||||
|
{
|
||||||
|
public static IProcessResult StartAndWaitForExit(this ProcessArguments processArguments)
|
||||||
|
{
|
||||||
|
using var instance = processArguments.Start();
|
||||||
|
return instance.WaitForExit();
|
||||||
|
}
|
||||||
|
public static async Task<IProcessResult> StartAndWaitForExitAsync(this ProcessArguments processArguments, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var instance = processArguments.Start();
|
||||||
|
return await instance.WaitForExitAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,8 +38,8 @@ public static void RootExceptionCheck()
|
||||||
public static void VerifyFFMpegExists(FFOptions ffMpegOptions)
|
public static void VerifyFFMpegExists(FFOptions ffMpegOptions)
|
||||||
{
|
{
|
||||||
if (_ffmpegVerified) return;
|
if (_ffmpegVerified) return;
|
||||||
var (exitCode, _) = Instance.Finish(GlobalFFOptions.GetFFMpegBinaryPath(ffMpegOptions), "-version");
|
var result = Instance.Finish(GlobalFFOptions.GetFFMpegBinaryPath(ffMpegOptions), "-version");
|
||||||
_ffmpegVerified = exitCode == 0;
|
_ffmpegVerified = result.ExitCode == 0;
|
||||||
if (!_ffmpegVerified)
|
if (!_ffmpegVerified)
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation, "ffmpeg was not found on your system");
|
throw new FFMpegException(FFMpegExceptionType.Operation, "ffmpeg was not found on your system");
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,8 @@ public static void RootExceptionCheck()
|
||||||
public static void VerifyFFProbeExists(FFOptions ffMpegOptions)
|
public static void VerifyFFProbeExists(FFOptions ffMpegOptions)
|
||||||
{
|
{
|
||||||
if (_ffprobeVerified) return;
|
if (_ffprobeVerified) return;
|
||||||
var (exitCode, _) = Instance.Finish(GlobalFFOptions.GetFFProbeBinaryPath(ffMpegOptions), "-version");
|
var result = Instance.Finish(GlobalFFOptions.GetFFProbeBinaryPath(ffMpegOptions), "-version");
|
||||||
_ffprobeVerified = exitCode == 0;
|
_ffprobeVerified = result.ExitCode == 0;
|
||||||
if (!_ffprobeVerified)
|
if (!_ffprobeVerified)
|
||||||
throw new FFProbeException("ffprobe was not found on your system");
|
throw new FFProbeException("ffprobe was not found on your system");
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
[![GitHub issues](https://img.shields.io/github/issues/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/issues)
|
[![GitHub issues](https://img.shields.io/github/issues/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/issues)
|
||||||
[![GitHub stars](https://img.shields.io/github/stars/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/stargazers)
|
[![GitHub stars](https://img.shields.io/github/stars/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/stargazers)
|
||||||
[![GitHub](https://img.shields.io/github/license/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/blob/master/LICENSE)
|
[![GitHub](https://img.shields.io/github/license/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/blob/master/LICENSE)
|
||||||
[![CI](https://github.com/rosenbjerg/FFMpegCore/workflows/CI/badge.svg)](https://github.com/rosenbjerg/FFMpegCore/actions?query=workflow%3ACI)
|
[![CI](https://github.com/rosenbjerg/FFMpegCore/workflows/CI/badge.svg)](https://github.com/rosenbjerg/FFMpegCore/actions/workflows/ci.yml)
|
||||||
|
[![GitHub code contributors](https://img.shields.io/github/contributors/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/graphs/contributors)
|
||||||
|
|
||||||
A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications. Supports both synchronous and asynchronous calls
|
A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications. Supports both synchronous and asynchronous calls
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue