Merge pull request #255 from andyjmorgan/ffprobe-frames

Adding  support for ffprobe show frames
This commit is contained in:
Malte Rosenbjerg 2021-10-21 23:02:50 +02:00 committed by GitHub
commit 5107ef0ec6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 154 additions and 8 deletions

View file

@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using FFMpegCore.Test.Resources; using FFMpegCore.Test.Resources;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -25,6 +26,30 @@ public async Task Audio_FromStream_Duration()
Assert.IsTrue(fileAnalysis.Duration == streamAnalysis.Duration); Assert.IsTrue(fileAnalysis.Duration == streamAnalysis.Duration);
} }
[TestMethod]
public void FrameAnalysis_Sync()
{
var frameAnalysis = FFProbe.GetFrames(TestResources.WebmVideo);
Assert.AreEqual(90, frameAnalysis.Frames.Count);
Assert.IsTrue(frameAnalysis.Frames.All(f => f.PixelFormat == "yuv420p"));
Assert.IsTrue(frameAnalysis.Frames.All(f => f.Height == 360));
Assert.IsTrue(frameAnalysis.Frames.All(f => f.Width == 640));
Assert.IsTrue(frameAnalysis.Frames.All(f => f.MediaType == "video"));
}
[TestMethod]
public async Task FrameAnalysis_Async()
{
var frameAnalysis = await FFProbe.GetFramesAsync(TestResources.WebmVideo);
Assert.AreEqual(90, frameAnalysis.Frames.Count);
Assert.IsTrue(frameAnalysis.Frames.All(f => f.PixelFormat == "yuv420p"));
Assert.IsTrue(frameAnalysis.Frames.All(f => f.Height == 360));
Assert.IsTrue(frameAnalysis.Frames.All(f => f.Width == 640));
Assert.IsTrue(frameAnalysis.Frames.All(f => f.MediaType == "video"));
}
[DataTestMethod] [DataTestMethod]
[DataRow("0:00:03.008000", 0, 0, 0, 3, 8)] [DataRow("0:00:03.008000", 0, 0, 0, 3, 8)]
[DataRow("05:12:59.177", 0, 5, 12, 59, 177)] [DataRow("05:12:59.177", 0, 5, 12, 59, 177)]

View file

@ -18,16 +18,28 @@ public static IMediaAnalysis Analyse(string filePath, int outputCapacity = int.M
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 = PrepareInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); using var instance = PrepareStreamAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
var exitCode = instance.BlockUntilFinished(); var exitCode = instance.BlockUntilFinished();
if (exitCode != 0) if (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 ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
return ParseOutput(instance); return ParseOutput(instance);
} }
public static FFProbeFrames GetFrames(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
{
if (!File.Exists(filePath))
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
using var instance = PrepareFrameAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
var exitCode = instance.BlockUntilFinished();
if (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));
return ParseFramesOutput(instance);
}
public static IMediaAnalysis Analyse(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) public static IMediaAnalysis Analyse(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
{ {
using var instance = PrepareInstance(uri.AbsoluteUri, outputCapacity, ffOptions ?? GlobalFFOptions.Current); using var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
var exitCode = instance.BlockUntilFinished(); var exitCode = instance.BlockUntilFinished();
if (exitCode != 0) if (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 ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
@ -38,7 +50,7 @@ public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.Max
{ {
var streamPipeSource = new StreamPipeSource(stream); var streamPipeSource = new StreamPipeSource(stream);
var pipeArgument = new InputPipeArgument(streamPipeSource); var pipeArgument = new InputPipeArgument(streamPipeSource);
using var instance = PrepareInstance(pipeArgument.PipePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); using var instance = PrepareStreamAnalysisInstance(pipeArgument.PipePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
pipeArgument.Pre(); pipeArgument.Pre();
var task = instance.FinishedRunning(); var task = instance.FinishedRunning();
@ -62,16 +74,26 @@ public static async Task<IMediaAnalysis> AnalyseAsync(string filePath, int outpu
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 = PrepareInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); using var instance = PrepareStreamAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
var exitCode = await instance.FinishedRunning().ConfigureAwait(false); var exitCode = await instance.FinishedRunning().ConfigureAwait(false);
if (exitCode != 0) if (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 ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
return ParseOutput(instance); return ParseOutput(instance);
} }
public static async Task<FFProbeFrames> GetFramesAsync(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
{
if (!File.Exists(filePath))
throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
using var instance = PrepareFrameAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
await instance.FinishedRunning().ConfigureAwait(false);
return ParseFramesOutput(instance);
}
public static async Task<IMediaAnalysis> AnalyseAsync(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) public static async Task<IMediaAnalysis> AnalyseAsync(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
{ {
using var instance = PrepareInstance(uri.AbsoluteUri, outputCapacity, ffOptions ?? GlobalFFOptions.Current); using var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
var exitCode = await instance.FinishedRunning().ConfigureAwait(false); var exitCode = await instance.FinishedRunning().ConfigureAwait(false);
if (exitCode != 0) if (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 ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
@ -82,7 +104,7 @@ public static async Task<IMediaAnalysis> AnalyseAsync(Stream stream, int outputC
{ {
var streamPipeSource = new StreamPipeSource(stream); var streamPipeSource = new StreamPipeSource(stream);
var pipeArgument = new InputPipeArgument(streamPipeSource); var pipeArgument = new InputPipeArgument(streamPipeSource);
using var instance = PrepareInstance(pipeArgument.PipePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); using var instance = PrepareStreamAnalysisInstance(pipeArgument.PipePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
pipeArgument.Pre(); pipeArgument.Pre();
var task = instance.FinishedRunning(); var task = instance.FinishedRunning();
@ -118,12 +140,28 @@ private static IMediaAnalysis ParseOutput(Instance instance)
return new MediaAnalysis(ffprobeAnalysis); return new MediaAnalysis(ffprobeAnalysis);
} }
private static FFProbeFrames ParseFramesOutput(Instance instance)
{
var json = string.Join(string.Empty, instance.OutputData);
var ffprobeAnalysis = JsonSerializer.Deserialize<FFProbeFrames>(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString | System.Text.Json.Serialization.JsonNumberHandling.WriteAsString
}) ;
private static Instance PrepareInstance(string filePath, int outputCapacity, FFOptions ffOptions) return ffprobeAnalysis;
}
private static Instance PrepareStreamAnalysisInstance(string filePath, int outputCapacity, FFOptions ffOptions)
=> PrepareInstance($"-loglevel error -print_format json -show_format -sexagesimal -show_streams \"{filePath}\"", outputCapacity, ffOptions);
private static Instance PrepareFrameAnalysisInstance(string filePath, int outputCapacity, FFOptions ffOptions)
=> PrepareInstance($"-loglevel error -print_format json -show_frames -v quiet -sexagesimal \"{filePath}\"", outputCapacity, ffOptions);
private static Instance PrepareInstance(string arguments, int outputCapacity, FFOptions ffOptions)
{ {
FFProbeHelper.RootExceptionCheck(); FFProbeHelper.RootExceptionCheck();
FFProbeHelper.VerifyFFProbeExists(ffOptions); FFProbeHelper.VerifyFFProbeExists(ffOptions);
var arguments = $"-loglevel error -print_format json -show_format -sexagesimal -show_streams \"{filePath}\"";
var startInfo = new ProcessStartInfo(GlobalFFOptions.GetFFProbeBinaryPath(), arguments) var startInfo = new ProcessStartInfo(GlobalFFOptions.GetFFProbeBinaryPath(), arguments)
{ {
StandardOutputEncoding = ffOptions.Encoding, StandardOutputEncoding = ffOptions.Encoding,

View file

@ -0,0 +1,83 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace FFMpegCore
{
public class FFProbeFrameAnalysis
{
[JsonPropertyName("media_type")]
public string MediaType { get; set; }
[JsonPropertyName("stream_index")]
public int StreamIndex { get; set; }
[JsonPropertyName("key_frame")]
public int KeyFrame { get; set; }
[JsonPropertyName("pkt_pts")]
public long PacketPts { get; set; }
[JsonPropertyName("pkt_pts_time")]
public string PacketPtsTime { get; set; }
[JsonPropertyName("pkt_dts")]
public long PacketDts { get; set; }
[JsonPropertyName("pkt_dts_time")]
public string PacketDtsTime { get; set; }
[JsonPropertyName("best_effort_timestamp")]
public long BestEffortTimestamp { get; set; }
[JsonPropertyName("best_effort_timestamp_time")]
public string BestEffortTimestampTime { get; set; }
[JsonPropertyName("pkt_duration")]
public int PacketDuration { get; set; }
[JsonPropertyName("pkt_duration_time")]
public string PacketDurationTime { get; set; }
[JsonPropertyName("pkt_pos")]
public long PacketPos { get; set; }
[JsonPropertyName("pkt_size")]
public int PacketSize { get; set; }
[JsonPropertyName("width")]
public long Width { get; set; }
[JsonPropertyName("height")]
public long Height { get; set; }
[JsonPropertyName("pix_fmt")]
public string PixelFormat { get; set; }
[JsonPropertyName("pict_type")]
public string PictureType { get; set; }
[JsonPropertyName("coded_picture_number")]
public long CodedPictureNumber { get; set; }
[JsonPropertyName("display_picture_number")]
public long DisplayPictureNumber { get; set; }
[JsonPropertyName("interlaced_frame")]
public int InterlacedFrame { get; set; }
[JsonPropertyName("top_field_first")]
public int TopFieldFirst { get; set; }
[JsonPropertyName("repeat_pict")]
public int RepeatPicture { get; set; }
[JsonPropertyName("chroma_location")]
public string ChromaLocation { get; set; }
}
public class FFProbeFrames
{
[JsonPropertyName("frames")]
public List<FFProbeFrameAnalysis> Frames { get; set; }
}
}