diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj index 2e6adf74..e7ff13be 100644 --- a/FFMpegCore.Test/FFMpegCore.Test.csproj +++ b/FFMpegCore.Test/FFMpegCore.Test.csproj @@ -93,6 +93,9 @@ PreserveNewest + + PreserveNewest + diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs index 4b1e519c..63ff3539 100644 --- a/FFMpegCore.Test/FFProbeTests.cs +++ b/FFMpegCore.Test/FFProbeTests.cs @@ -164,6 +164,39 @@ public void Probe_Success() Assert.AreEqual("0x31637661", info.PrimaryVideoStream.CodecTag); } + [TestMethod] + public void Probe_BrdiskISO() + { + var info = FFProbe.Analyse(TestResources.BrdiskISO); + Assert.AreEqual(3, info.Duration.Minutes); + + Assert.AreEqual("mono", info.PrimaryAudioStream!.ChannelLayout); + Assert.AreEqual(1, info.PrimaryAudioStream.Channels); + Assert.AreEqual("ATSC A/52A (AC-3)", info.PrimaryAudioStream.CodecLongName); + Assert.AreEqual("ac3", info.PrimaryAudioStream.CodecName); + Assert.AreEqual(32000, info.PrimaryAudioStream.BitRate); + Assert.AreEqual(44100, info.PrimaryAudioStream.SampleRateHz); + Assert.AreEqual("AC-3", info.PrimaryAudioStream.CodecTagString); + Assert.AreEqual("0x332d4341", info.PrimaryAudioStream.CodecTag); + + Assert.AreEqual(30000/1001m, info.PrimaryVideoStream.FrameRate); + Assert.AreEqual(30000/1001m, info.PrimaryVideoStream.AvgFrameRate); + Assert.AreEqual(20, info.PrimaryVideoStream.DisplayAspectRatio.Width); + Assert.AreEqual(11, info.PrimaryVideoStream.DisplayAspectRatio.Height); + Assert.AreEqual("yuv420p", info.PrimaryVideoStream.PixelFormat); + Assert.AreEqual("smpte170m", info.PrimaryVideoStream.ColorSpace); + Assert.AreEqual("smpte170m", info.PrimaryVideoStream.ColorPrimaries); + Assert.AreEqual("smpte170m", info.PrimaryVideoStream.ColorTransfer); + Assert.AreEqual(720, info.PrimaryVideoStream.Width); + Assert.AreEqual(480, info.PrimaryVideoStream.Height); + Assert.AreEqual("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", info.PrimaryVideoStream.CodecLongName); + Assert.AreEqual("h264", info.PrimaryVideoStream.CodecName); + Assert.AreEqual(8, info.PrimaryVideoStream.BitsPerRawSample); + Assert.AreEqual("High", info.PrimaryVideoStream.Profile); + Assert.AreEqual("HDMV", info.PrimaryVideoStream.CodecTagString); + Assert.AreEqual("0x564d4448", info.PrimaryVideoStream.CodecTag); + } + [TestMethod] public void Probe_Dolby_Vision() { diff --git a/FFMpegCore.Test/Resources/TestResources.cs b/FFMpegCore.Test/Resources/TestResources.cs index 77580c79..0ad7c79a 100644 --- a/FFMpegCore.Test/Resources/TestResources.cs +++ b/FFMpegCore.Test/Resources/TestResources.cs @@ -1,4 +1,6 @@ -namespace FFMpegCore.Test.Resources +using System; + +namespace FFMpegCore.Test.Resources { public enum AudioType { @@ -23,5 +25,6 @@ public static class TestResources public static readonly string SrtSubtitle = "./Resources/sample.srt"; public static readonly string Dovi = "./Resources/dovi.mp4"; public static readonly string Interlaced = "./Resources/interlaced.mkv"; + public static readonly Uri BrdiskISO = new Uri("bluray:./Resources/brdisk.iso"); } } diff --git a/FFMpegCore.Test/Resources/brdisk.iso b/FFMpegCore.Test/Resources/brdisk.iso new file mode 100644 index 00000000..8d0b90e6 Binary files /dev/null and b/FFMpegCore.Test/Resources/brdisk.iso differ diff --git a/FFMpegCore/FFProbe/FFProbe.cs b/FFMpegCore/FFProbe/FFProbe.cs index 9af9a88a..9c4ff5c8 100644 --- a/FFMpegCore/FFProbe/FFProbe.cs +++ b/FFMpegCore/FFProbe/FFProbe.cs @@ -29,10 +29,15 @@ public static class FFProbe public static string GetStreamJson(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) { - if (!File.Exists(filePath)) - throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'"); + return GetStreamJson(FileUri(filePath), outputCapacity, ffOptions); + } + public static string GetStreamJson(Uri uri, int outputCapacity = int.MaxValue, + FFOptions? ffOptions = null) + { + if (IsLocalFile(uri) && !File.Exists(uri.LocalPath)) + throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{uri.LocalPath}'"); - using var instance = PrepareStreamAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); + using var instance = PrepareStreamAnalysisInstance(InputArgument(uri), 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)); @@ -49,10 +54,14 @@ public static IMediaAnalysis AnalyseStreamJson(string json) } public static IMediaAnalysis Analyse(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) { - if (!File.Exists(filePath)) - throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'"); + return Analyse(FileUri(filePath), outputCapacity, ffOptions); + } + public static IMediaAnalysis Analyse(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) + { + if (IsLocalFile(uri) && !File.Exists(uri.LocalPath)) + throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{uri.LocalPath}'"); - using var instance = PrepareStreamAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); + using var instance = PrepareStreamAnalysisInstance(InputArgument(uri), 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)); @@ -62,10 +71,15 @@ public static IMediaAnalysis Analyse(string filePath, int outputCapacity = int.M public static string GetFrameJson(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) { - if (!File.Exists(filePath)) - throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'"); + return GetFrameJson(FileUri(filePath), outputCapacity, ffOptions); + } + public static string GetFrameJson(Uri uri, int outputCapacity = int.MaxValue, + FFOptions? ffOptions = null) + { + if (IsLocalFile(uri) && !File.Exists(uri.LocalPath)) + throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{uri.LocalPath}'"); - using var instance = PrepareFrameAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); + using var instance = PrepareFrameAnalysisInstance(InputArgument(uri), 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)); @@ -78,10 +92,14 @@ public static FFProbeFrames AnalyseFrameJson(string json) } 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}'"); + return GetFrames(FileUri(filePath), outputCapacity, ffOptions); + } + public static FFProbeFrames GetFrames(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) + { + if (IsLocalFile(uri) && !File.Exists(uri.LocalPath)) + throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{uri.LocalPath}'"); - using var instance = PrepareFrameAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); + using var instance = PrepareFrameAnalysisInstance(InputArgument(uri), 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)); @@ -91,10 +109,14 @@ public static FFProbeFrames GetFrames(string filePath, int outputCapacity = int. 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}'"); + return GetPackets(FileUri(filePath), outputCapacity, ffOptions); + } + public static FFProbePackets GetPackets(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) + { + if (IsLocalFile(uri) && !File.Exists(uri.LocalPath)) + throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{uri.LocalPath}'"); - using var instance = PreparePacketAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); + using var instance = PreparePacketAnalysisInstance(InputArgument(uri), 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)); @@ -102,15 +124,6 @@ public static FFProbePackets GetPackets(string filePath, int outputCapacity = in 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); - 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 ParseOutput(instance); - } public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) { var streamPipeSource = new StreamPipeSource(stream); @@ -136,10 +149,14 @@ public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.Max } public static async Task AnalyseAsync(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) { - if (!File.Exists(filePath)) - throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'"); + return await AnalyseAsync(FileUri(filePath), outputCapacity, ffOptions); + } + public static async Task AnalyseAsync(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) + { + if (IsLocalFile(uri) && !File.Exists(uri.LocalPath)) + throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{uri.LocalPath}'"); - using var instance = PrepareStreamAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); + using var instance = PrepareStreamAnalysisInstance(InputArgument(uri), outputCapacity, ffOptions ?? GlobalFFOptions.Current); var exitCode = await instance.FinishedRunning().ConfigureAwait(false); 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)); @@ -149,33 +166,32 @@ public static async Task AnalyseAsync(string filePath, int outpu public static async Task GetFramesAsync(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) { - if (!File.Exists(filePath)) - throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'"); + return await GetFramesAsync(FileUri(filePath), outputCapacity, ffOptions); + } + public static async Task GetFramesAsync(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) + { + if (IsLocalFile(uri) && !File.Exists(uri.LocalPath)) + throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{uri.LocalPath}'"); - using var instance = PrepareFrameAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); + using var instance = PrepareFrameAnalysisInstance(InputArgument(uri), outputCapacity, ffOptions ?? GlobalFFOptions.Current); 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}'"); + return await GetPacketsAsync(FileUri(filePath), outputCapacity, ffOptions); + } + public static async Task GetPacketsAsync(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) + { + if (IsLocalFile(uri) && !File.Exists(uri.LocalPath)) + throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{uri.LocalPath}'"); - using var instance = PreparePacketAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current); + using var instance = PreparePacketAnalysisInstance(InputArgument(uri), 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); - var exitCode = await instance.FinishedRunning().ConfigureAwait(false); - 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 ParseOutput(instance); - } public static async Task AnalyseAsync(Stream stream, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null) { var streamPipeSource = new StreamPipeSource(stream); @@ -245,13 +261,25 @@ private static FFProbePackets ParsePacketsOutput(Instance instance) return ffprobeAnalysis; } + private static bool IsLocalFile(Uri uri) + { + return uri.IsFile || uri.Scheme == "bluray"; + } + private static Uri FileUri(string filePath) + { + return new Uri(Path.GetFullPath(filePath)); + } + private static string InputArgument(Uri uri) + { + return uri.IsFile ? uri.LocalPath : uri.GetComponents(UriComponents.AbsoluteUri, UriFormat.Unescaped); + } - private static Instance PrepareStreamAnalysisInstance(string filePath, int outputCapacity, FFOptions ffOptions) - => PrepareInstance($"-loglevel error -print_format json -show_format -sexagesimal -show_streams {ffOptions.ExtraArguments} \"{filePath}\"", outputCapacity, ffOptions); - private static Instance PrepareFrameAnalysisInstance(string filePath, int outputCapacity, FFOptions ffOptions) - => PrepareInstance($"-loglevel error -print_format json -show_frames -v quiet -sexagesimal {ffOptions.ExtraArguments} \"{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 PrepareStreamAnalysisInstance(string input, int outputCapacity, FFOptions ffOptions) + => PrepareInstance($"-loglevel error -print_format json -show_format -sexagesimal -show_streams {ffOptions.ExtraArguments} \"{input}\"", outputCapacity, ffOptions); + private static Instance PrepareFrameAnalysisInstance(string input, int outputCapacity, FFOptions ffOptions) + => PrepareInstance($"-loglevel error -print_format json -show_frames -v quiet -sexagesimal {ffOptions.ExtraArguments} \"{input}\"", outputCapacity, ffOptions); + private static Instance PreparePacketAnalysisInstance(string input, int outputCapacity, FFOptions ffOptions) + => PrepareInstance($"-loglevel error -print_format json -show_packets -v quiet -sexagesimal \"{input}\"", outputCapacity, ffOptions); private static Instance PrepareInstance(string arguments, int outputCapacity, FFOptions ffOptions) {