FFMpegCore/FFMpegCore/FFProbe/FFProbe.cs

236 lines
10 KiB
C#

using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using FFMpegCore.Arguments;
using FFMpegCore.Exceptions;
using FFMpegCore.Helpers;
using FFMpegCore.Pipes;
using Instances;
namespace FFMpegCore;
public static class FFProbe
{
public static IMediaAnalysis Analyse(string filePath, FFOptions? ffOptions = null, string? customArguments = null)
{
return AnalyseAsync(filePath, ffOptions, CancellationToken.None, customArguments).ConfigureAwait(false).GetAwaiter().GetResult();
}
public static IMediaAnalysis Analyse(Uri uri, FFOptions? ffOptions = null, string? customArguments = null)
{
return AnalyseAsync(uri, ffOptions, CancellationToken.None, customArguments).ConfigureAwait(false).GetAwaiter().GetResult();
}
public static IMediaAnalysis Analyse(Stream stream, FFOptions? ffOptions = null, string? customArguments = null)
{
return AnalyseAsync(stream, ffOptions, CancellationToken.None, customArguments).ConfigureAwait(false).GetAwaiter().GetResult();
}
public static async Task<IMediaAnalysis> AnalyseAsync(string filePath, FFOptions? ffOptions = null, CancellationToken cancellationToken = default,
string? customArguments = null)
{
ThrowIfInputFileDoesNotExist(filePath);
return await AnalyseCoreAsync(filePath, ffOptions, cancellationToken, customArguments).ConfigureAwait(false);
}
public static async Task<IMediaAnalysis> AnalyseAsync(Uri uri, FFOptions? ffOptions = null, CancellationToken cancellationToken = default,
string? customArguments = null)
{
return await AnalyseCoreAsync(uri.AbsoluteUri, ffOptions, cancellationToken, customArguments).ConfigureAwait(false);
}
public static async Task<IMediaAnalysis> AnalyseAsync(Stream stream, FFOptions? ffOptions = null, CancellationToken cancellationToken = default,
string? customArguments = null)
{
var streamPipeSource = new StreamPipeSource(stream);
var pipeArgument = new InputPipeArgument(streamPipeSource);
var task = AnalyseCoreAsync(pipeArgument.PipePath, ffOptions ?? GlobalFFOptions.Current, cancellationToken, customArguments);
pipeArgument.Pre();
try
{
await pipeArgument.During(cancellationToken).ConfigureAwait(false);
}
catch (IOException)
{
}
finally
{
pipeArgument.Post();
}
return await task.ConfigureAwait(false);
}
public static FFProbeFrames GetFrames(string filePath, FFOptions? ffOptions = null, string? customArguments = null)
{
return GetFramesAsync(filePath, ffOptions, CancellationToken.None, customArguments).ConfigureAwait(false).GetAwaiter().GetResult();
}
public static FFProbeFrames GetFrames(Uri uri, FFOptions? ffOptions = null, string? customArguments = null)
{
return GetFramesAsync(uri, ffOptions, CancellationToken.None, customArguments).ConfigureAwait(false).GetAwaiter().GetResult();
}
public static FFProbeFrames GetFrames(Stream stream, FFOptions? ffOptions = null, string? customArguments = null)
{
return GetFramesAsync(stream, ffOptions, CancellationToken.None, customArguments).ConfigureAwait(false).GetAwaiter().GetResult();
}
public static async Task<FFProbeFrames> GetFramesAsync(string filePath, FFOptions? ffOptions = null, CancellationToken cancellationToken = default,
string? customArguments = null)
{
ThrowIfInputFileDoesNotExist(filePath);
return await GetFramesCoreAsync(filePath, ffOptions, cancellationToken, customArguments).ConfigureAwait(false);
}
public static async Task<FFProbeFrames> GetFramesAsync(Uri uri, FFOptions? ffOptions = null, CancellationToken cancellationToken = default,
string? customArguments = null)
{
return await GetFramesCoreAsync(uri.AbsoluteUri, ffOptions, cancellationToken, customArguments).ConfigureAwait(false);
}
public static async Task<FFProbeFrames> GetFramesAsync(Stream stream, FFOptions? ffOptions = null, CancellationToken cancellationToken = default,
string? customArguments = null)
{
var streamPipeSource = new StreamPipeSource(stream);
var pipeArgument = new InputPipeArgument(streamPipeSource);
var task = GetFramesCoreAsync(pipeArgument.PipePath, ffOptions ?? GlobalFFOptions.Current, cancellationToken, customArguments);
pipeArgument.Pre();
try
{
await pipeArgument.During(cancellationToken).ConfigureAwait(false);
}
catch (IOException) { }
finally
{
pipeArgument.Post();
}
return await task.ConfigureAwait(false);
}
private static async Task<IMediaAnalysis> AnalyseCoreAsync(string inputPath, FFOptions? ffOptions, CancellationToken cancellationToken, string? customArguments)
{
var instance = PrepareStreamAnalysisInstance(inputPath, ffOptions ?? GlobalFFOptions.Current, customArguments);
var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
ThrowIfExitCodeNotZero(result);
return ParseOutput(result);
}
private static async Task<FFProbeFrames> GetFramesCoreAsync(string inputPath, FFOptions? ffOptions, CancellationToken cancellationToken, string? customArguments)
{
var instance = PrepareFrameAnalysisInstance(inputPath, ffOptions ?? GlobalFFOptions.Current, customArguments);
var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
ThrowIfExitCodeNotZero(result);
return ParseFramesOutput(result);
}
public static FFProbePackets GetPackets(string filePath, FFOptions? ffOptions = null, string? customArguments = null)
{
return GetPacketsAsync(filePath, ffOptions, CancellationToken.None, customArguments).ConfigureAwait(false).GetAwaiter().GetResult();
}
public static async Task<FFProbePackets> GetPacketsAsync(string filePath, FFOptions? ffOptions = null, CancellationToken cancellationToken = default,
string? customArguments = null)
{
ThrowIfInputFileDoesNotExist(filePath);
var instance = PreparePacketAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current, customArguments);
var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false);
return ParsePacketsOutput(result);
}
private static IMediaAnalysis ParseOutput(IProcessResult instance)
{
var json = string.Join(string.Empty, instance.OutputData);
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbeAnalysis>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (ffprobeAnalysis?.Format == null)
{
throw new FormatNullException();
}
ffprobeAnalysis.ErrorData = instance.ErrorData;
return new MediaAnalysis(ffprobeAnalysis);
}
private static FFProbeFrames ParseFramesOutput(IProcessResult instance)
{
var json = string.Join(string.Empty, instance.OutputData);
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbeFrames>(json,
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString
});
return ffprobeAnalysis!;
}
private static FFProbePackets ParsePacketsOutput(IProcessResult instance)
{
var json = string.Join(string.Empty, instance.OutputData);
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbePackets>(json,
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
NumberHandling = JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString
});
return ffprobeAnalysis!;
}
private static void ThrowIfInputFileDoesNotExist(string filePath)
{
if (!File.Exists(filePath))
{
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
}
}
private static void ThrowIfExitCodeNotZero(IProcessResult result)
{
if (result.ExitCode != 0)
{
var message = $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})";
throw new FFMpegException(FFMpegExceptionType.Process, message, null, string.Join("\n", result.ErrorData));
}
}
private static ProcessArguments PrepareStreamAnalysisInstance(string filePath, FFOptions ffOptions, string? customArguments)
{
return PrepareInstance($"-loglevel error -print_format json -show_format -sexagesimal -show_streams -show_chapters \"{filePath}\"", ffOptions,
customArguments);
}
private static ProcessArguments PrepareFrameAnalysisInstance(string filePath, FFOptions ffOptions, string? customArguments)
{
return PrepareInstance($"-loglevel error -print_format json -show_frames -v quiet -sexagesimal \"{filePath}\"", ffOptions, customArguments);
}
private static ProcessArguments PreparePacketAnalysisInstance(string filePath, FFOptions ffOptions, string? customArguments)
{
return PrepareInstance($"-loglevel error -print_format json -show_packets -v quiet -sexagesimal \"{filePath}\"", ffOptions, customArguments);
}
private static ProcessArguments PrepareInstance(string arguments, FFOptions ffOptions, string? customArguments)
{
FFProbeHelper.RootExceptionCheck();
FFProbeHelper.VerifyFFProbeExists(ffOptions);
var startInfo = new ProcessStartInfo(GlobalFFOptions.GetFFProbeBinaryPath(ffOptions), $"{arguments} {customArguments}")
{
StandardOutputEncoding = ffOptions.Encoding,
StandardErrorEncoding = ffOptions.Encoding,
WorkingDirectory = ffOptions.WorkingDirectory
};
return new ProcessArguments(startInfo);
}
}