mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
Merge pull request #255 from andyjmorgan/ffprobe-frames
Adding support for ffprobe show frames
Former-commit-id: 5107ef0ec6
This commit is contained in:
commit
61cfae2f1b
3 changed files with 154 additions and 8 deletions
|
@ -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)]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
83
FFMpegCore/FFProbe/FrameAnalysis.cs
Normal file
83
FFMpegCore/FFProbe/FrameAnalysis.cs
Normal 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; }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue