From 4608b590e16d3f44b870c3a724baa6c382e752b3 Mon Sep 17 00:00:00 2001 From: Alex Zhukov Date: Mon, 8 Nov 2021 06:28:16 -0800 Subject: [PATCH] parse ffprobes -show_packets output Former-commit-id: 239e2aef4208db7bda06a500c1b0fde5cb295e40 --- FFMpegCore.Test/FFProbeTests.cs | 38 ++++++++++++++++++++++ FFMpegCore/FFProbe/FFProbe.cs | 39 +++++++++++++++++++++++ FFMpegCore/FFProbe/PacketAnalysis.cs | 47 ++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 FFMpegCore/FFProbe/PacketAnalysis.cs diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index f990c7f..c2e6e5a 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -50,6 +51,43 @@ public async Task FrameAnalysis_Async() Assert.IsTrue(frameAnalysis.Frames.All(f => f.MediaType == "video")); } + [TestMethod] + public async Task PacketAnalysis_Async() + { + var packetAnalysis = await FFProbe.GetPacketsAsync(TestResources.WebmVideo); + var packets = packetAnalysis.Packets; + Assert.AreEqual(96, packets.Count); + Assert.IsTrue(packets.All(f => f.CodecType == "video")); + Assert.AreEqual("K_", packets[0].Flags); + Assert.AreEqual(1362, packets.Last().Size); + } + + + [TestMethod] + public void PacketAnalysis_Sync() + { + var packets = FFProbe.GetPackets(TestResources.WebmVideo).Packets; + + Assert.AreEqual(96, packets.Count); + Assert.IsTrue(packets.All(f => f.CodecType == "video")); + Assert.AreEqual("K_", packets[0].Flags); + Assert.AreEqual(1362, packets.Last().Size); + } + + [TestMethod] + public void PacketAnalysisAudioVideo_Sync() + { + var packets = FFProbe.GetPackets(TestResources.Mp4Video).Packets; + + Assert.AreEqual(216, packets.Count); + var actual = packets.Select(f => f.CodecType).Distinct().ToList(); + var expected = new List {"audio", "video"}; + CollectionAssert.AreEquivalent(expected, actual); + Assert.IsTrue(packets.Where(t=>t.CodecType == "audio").All(f => f.Flags == "K_")); + Assert.AreEqual(75, packets.Count(t => t.CodecType == "video")); + Assert.AreEqual(141, packets.Count(t => t.CodecType == "audio")); + } + [DataTestMethod] [DataRow("0:00:03.008000", 0, 0, 0, 3, 8)] [DataRow("05:12:59.177", 0, 5, 12, 59, 177)] diff --git a/FFMpegCore/FFProbe/FFProbe.cs b/FFMpegCore/FFProbe/FFProbe.cs index d0e8ea8..36f050c 100644 --- a/FFMpegCore/FFProbe/FFProbe.cs +++ b/FFMpegCore/FFProbe/FFProbe.cs @@ -37,6 +37,20 @@ public static FFProbeFrames GetFrames(string filePath, int outputCapacity = int. return ParseFramesOutput(instance); } + + public static FFProbePackets GetPackets(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 = PreparePacketAnalysisInstance(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 ParsePacketsOutput(instance); + } + public static IMediaAnalysis Analyse(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) { using var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, outputCapacity, ffOptions ?? GlobalFFOptions.Current); @@ -91,6 +105,17 @@ public static async Task GetFramesAsync(string filePath, int outp await instance.FinishedRunning().ConfigureAwait(false); return ParseFramesOutput(instance); } + + public static async Task GetPacketsAsync(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 = PreparePacketAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); + await instance.FinishedRunning().ConfigureAwait(false); + return ParsePacketsOutput(instance); + } + public static async Task AnalyseAsync(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) { using var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, outputCapacity, ffOptions ?? GlobalFFOptions.Current); @@ -152,11 +177,25 @@ private static FFProbeFrames ParseFramesOutput(Instance instance) return ffprobeAnalysis; } + private static FFProbePackets ParsePacketsOutput(Instance instance) + { + var json = string.Join(string.Empty, instance.OutputData); + var ffprobeAnalysis = JsonSerializer.Deserialize(json, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString | System.Text.Json.Serialization.JsonNumberHandling.WriteAsString + }) ; + + 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 PreparePacketAnalysisInstance(string filePath, int outputCapacity, FFOptions ffOptions) + => PrepareInstance($"-loglevel error -print_format json -show_packets -v quiet -sexagesimal \"{filePath}\"", outputCapacity, ffOptions); private static Instance PrepareInstance(string arguments, int outputCapacity, FFOptions ffOptions) { diff --git a/FFMpegCore/FFProbe/PacketAnalysis.cs b/FFMpegCore/FFProbe/PacketAnalysis.cs new file mode 100644 index 0000000..d4da0f5 --- /dev/null +++ b/FFMpegCore/FFProbe/PacketAnalysis.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace FFMpegCore +{ + public class FFProbePacketAnalysis + { + [JsonPropertyName("codec_type")] + public string CodecType { get; set; } + + [JsonPropertyName("stream_index")] + public int StreamIndex { get; set; } + + [JsonPropertyName("pts")] + public long Pts { get; set; } + + [JsonPropertyName("pts_time")] + public string PtsTime { get; set; } + + [JsonPropertyName("dts")] + public long Dts { get; set; } + + [JsonPropertyName("dts_time")] + public string DtsTime { get; set; } + + [JsonPropertyName("duration")] + public int Duration { get; set; } + + [JsonPropertyName("duration_time")] + public string DurationTime { get; set; } + + [JsonPropertyName("size")] + public int Size { get; set; } + + [JsonPropertyName("pos")] + public long Pos { get; set; } + + [JsonPropertyName("flags")] + public string Flags { get; set; } + } + + public class FFProbePackets + { + [JsonPropertyName("packets")] + public List Packets { get; set; } + } +}