Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of FFMpegCore v5.4.0
BepInEx/core/FFMpegCore/netstandard2.0/FFMpegCore.dll
Decompiled 5 months ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Globalization; using System.IO; using System.IO.Pipes; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using FFMpegCore.Arguments; using FFMpegCore.Builders.MetaData; using FFMpegCore.Enums; using FFMpegCore.Exceptions; using FFMpegCore.Extend; using FFMpegCore.Helpers; using FFMpegCore.Pipes; using Instances; using Instances.Exceptions; using Microsoft.CodeAnalysis; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("FFMpegCore.Test")] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications")] [assembly: AssemblyFileVersion("5.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+ed8f899d04a1f71c481e02b2123176d5cd097a4a")] [assembly: AssemblyProduct("FFMpegCore")] [assembly: AssemblyTitle("FFMpegCore")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/rosenbjerg/FFMpegCore")] [assembly: NeutralResourcesLanguage("en")] [assembly: AssemblyVersion("5.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace FFMpegCore { public class FFMetadataBuilder { private class FFMetadataChapter { public string Title { get; } public long DurationMs { get; } public FFMetadataChapter(string title, long durationMs) { Title = title; DurationMs = durationMs; base..ctor(); } } private Dictionary<string, string> Tags { get; } = new Dictionary<string, string>(); private List<FFMetadataChapter> Chapters { get; } = new List<FFMetadataChapter>(); public static FFMetadataBuilder Empty() { return new FFMetadataBuilder(); } public FFMetadataBuilder WithTag(string key, string value) { Tags.Add(key, value); return this; } public FFMetadataBuilder WithChapter(string title, long durationMs) { Chapters.Add(new FFMetadataChapter(title, durationMs)); return this; } public FFMetadataBuilder WithChapter(string title, double durationSeconds) { Chapters.Add(new FFMetadataChapter(title, Convert.ToInt64(durationSeconds * 1000.0))); return this; } public string GetMetadataFileContent() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(";FFMETADATA1"); foreach (KeyValuePair<string, string> tag in Tags) { stringBuilder.AppendLine(tag.Key + "=" + tag.Value); } long num = 0L; foreach (FFMetadataChapter chapter in Chapters) { stringBuilder.AppendLine("[CHAPTER]"); stringBuilder.AppendLine("TIMEBASE=1/1000"); stringBuilder.AppendLine($"START={num}"); stringBuilder.AppendLine($"END={num + chapter.DurationMs}"); stringBuilder.AppendLine("title=" + chapter.Title); num += chapter.DurationMs; } return stringBuilder.ToString(); } } public static class FFMpeg { public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) { CheckSnapshotOutputExtension(output, FileExtension.Image.All); IMediaAnalysis source = FFProbe.Analyse(input); return SnapshotProcess(input, output, source, size, captureTime, streamIndex, inputFileIndex).ProcessSynchronously(); } public static async Task<bool> SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0, CancellationToken cancellationToken = default(CancellationToken)) { CheckSnapshotOutputExtension(output, FileExtension.Image.All); return await SnapshotProcess(input, output, await FFProbe.AnalyseAsync(input, null, cancellationToken).ConfigureAwait(continueOnCapturedContext: false), size, captureTime, streamIndex, inputFileIndex).CancellableThrough(cancellationToken).ProcessAsynchronously().ConfigureAwait(continueOnCapturedContext: false); } public static bool GifSnapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, TimeSpan? duration = null, int? streamIndex = null) { CheckSnapshotOutputExtension(output, new List<string>(1) { FileExtension.Gif }); IMediaAnalysis source = FFProbe.Analyse(input); return GifSnapshotProcess(input, output, source, size, captureTime, duration, streamIndex).ProcessSynchronously(); } public static async Task<bool> GifSnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, TimeSpan? duration = null, int? streamIndex = null, CancellationToken cancellationToken = default(CancellationToken)) { CheckSnapshotOutputExtension(output, new List<string>(1) { FileExtension.Gif }); return await GifSnapshotProcess(input, output, await FFProbe.AnalyseAsync(input, null, cancellationToken).ConfigureAwait(continueOnCapturedContext: false), size, captureTime, duration, streamIndex).CancellableThrough(cancellationToken).ProcessAsynchronously().ConfigureAwait(continueOnCapturedContext: false); } private static FFMpegArgumentProcessor SnapshotProcess(string input, string output, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) { var (fFMpegArguments, addArguments) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, output, source, size, captureTime, streamIndex, inputFileIndex); return fFMpegArguments.OutputToFile(output, overwrite: true, addArguments); } private static FFMpegArgumentProcessor GifSnapshotProcess(string input, string output, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, TimeSpan? duration = null, int? streamIndex = null) { var (fFMpegArguments, addArguments) = SnapshotArgumentBuilder.BuildGifSnapshotArguments(input, source, size, captureTime, duration, streamIndex); return fFMpegArguments.OutputToFile(output, overwrite: true, addArguments); } private static void CheckSnapshotOutputExtension(string output, List<string> extensions) { if (!extensions.Contains(Path.GetExtension(output).ToLower())) { throw new ArgumentException("Invalid snapshot output extension: " + output + ", needed: " + string.Join(",", FileExtension.Image.All)); } } public static bool JoinImageSequence(string output, double frameRate = 30.0, params string[] images) { string?[] array = images.Select(Path.GetExtension).Distinct().ToArray(); if (array.Length != 1) { throw new ArgumentException("All images must have the same extension", "images"); } string text = array[0].ToLowerInvariant(); int? width = null; int? height = null; string text2 = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, Guid.NewGuid().ToString()); Directory.CreateDirectory(text2); try { int num = 0; foreach (string obj in images) { IMediaAnalysis mediaAnalysis = FFProbe.Analyse(obj); FFMpegHelper.ConversionSizeExceptionCheck(mediaAnalysis.PrimaryVideoStream.Width, mediaAnalysis.PrimaryVideoStream.Height); int valueOrDefault = width.GetValueOrDefault(); if (!width.HasValue) { valueOrDefault = mediaAnalysis.PrimaryVideoStream.Width; width = valueOrDefault; } valueOrDefault = height.GetValueOrDefault(); if (!height.HasValue) { valueOrDefault = mediaAnalysis.PrimaryVideoStream.Height; height = valueOrDefault; } string destFileName = Path.Combine(text2, num++.ToString().PadLeft(9, '0') + text); File.Copy(obj, destFileName); } return FFMpegArguments.FromFileInput(Path.Combine(text2, "%09d" + text), verifyExists: false, delegate(FFMpegArgumentOptions options) { options.WithFramerate(frameRate); }).OutputToFile(output, overwrite: true, delegate(FFMpegArgumentOptions options) { options.ForcePixelFormat("yuv420p").Resize(width.Value, height.Value).WithFramerate(frameRate); }).ProcessSynchronously(); } finally { Directory.Delete(text2, recursive: true); } } public static bool PosterWithAudio(string image, string audio, string output) { FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4); IMediaAnalysis mediaAnalysis = FFProbe.Analyse(image); FFMpegHelper.ConversionSizeExceptionCheck(mediaAnalysis.PrimaryVideoStream.Width, mediaAnalysis.PrimaryVideoStream.Height); return FFMpegArguments.FromFileInput(image, verifyExists: false, delegate(FFMpegArgumentOptions options) { options.Loop(1).ForceFormat("image2"); }).AddFileInput(audio).OutputToFile(output, overwrite: true, delegate(FFMpegArgumentOptions options) { options.ForcePixelFormat("yuv420p").WithVideoCodec(VideoCodec.LibX264).WithConstantRateFactor(21) .WithAudioBitrate(AudioQuality.Normal) .UsingShortest(); }) .ProcessSynchronously(); } public static bool Convert(string input, string output, ContainerFormat format, Speed speed = Speed.SuperFast, VideoSize size = VideoSize.Original, AudioQuality audioQuality = AudioQuality.Normal, bool multithreaded = false) { FFMpegHelper.ExtensionExceptionCheck(output, format.Extension); IMediaAnalysis mediaAnalysis = FFProbe.Analyse(input); FFMpegHelper.ConversionSizeExceptionCheck(mediaAnalysis); double num = ((VideoSize.Original == size) ? 1.0 : ((double)mediaAnalysis.PrimaryVideoStream.Height / (double)size)); Size outputSize = new Size((int)((double)mediaAnalysis.PrimaryVideoStream.Width / num), (int)((double)mediaAnalysis.PrimaryVideoStream.Height / num)); if (outputSize.Width % 2 != 0) { outputSize.Width++; } return format.Name switch { "mp4" => FFMpegArguments.FromFileInput(input).OutputToFile(output, overwrite: true, delegate(FFMpegArgumentOptions options) { options.UsingMultithreading(multithreaded).WithVideoCodec(VideoCodec.LibX264).WithVideoBitrate(2400) .WithVideoFilters(delegate(VideoFilterOptions filterOptions) { filterOptions.Scale(outputSize); }) .WithSpeedPreset(speed) .WithAudioCodec(AudioCodec.Aac) .WithAudioBitrate(audioQuality); }).ProcessSynchronously(), "ogv" => FFMpegArguments.FromFileInput(input).OutputToFile(output, overwrite: true, delegate(FFMpegArgumentOptions options) { options.UsingMultithreading(multithreaded).WithVideoCodec(VideoCodec.LibTheora).WithVideoBitrate(2400) .WithVideoFilters(delegate(VideoFilterOptions filterOptions) { filterOptions.Scale(outputSize); }) .WithSpeedPreset(speed) .WithAudioCodec(AudioCodec.LibVorbis) .WithAudioBitrate(audioQuality); }).ProcessSynchronously(), "mpegts" => FFMpegArguments.FromFileInput(input).OutputToFile(output, overwrite: true, delegate(FFMpegArgumentOptions options) { options.CopyChannel().WithBitStreamFilter(Channel.Video, Filter.H264_Mp4ToAnnexB).ForceFormat(VideoType.Ts); }).ProcessSynchronously(), "webm" => FFMpegArguments.FromFileInput(input).OutputToFile(output, overwrite: true, delegate(FFMpegArgumentOptions options) { options.UsingMultithreading(multithreaded).WithVideoCodec(VideoCodec.LibVpx).WithVideoBitrate(2400) .WithVideoFilters(delegate(VideoFilterOptions filterOptions) { filterOptions.Scale(outputSize); }) .WithSpeedPreset(speed) .WithAudioCodec(AudioCodec.LibVorbis) .WithAudioBitrate(audioQuality); }).ProcessSynchronously(), _ => throw new ArgumentOutOfRangeException("format"), }; } public static bool Join(string output, params string[] videos) { string[] array = videos.Select(delegate(string videoPath) { FFMpegHelper.ConversionSizeExceptionCheck(FFProbe.Analyse(videoPath)); string text = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, Path.GetFileNameWithoutExtension(videoPath) + FileExtension.Ts); Directory.CreateDirectory(GlobalFFOptions.Current.TemporaryFilesFolder); Convert(videoPath, text, VideoType.Ts); return text; }).ToArray(); try { return FFMpegArguments.FromConcatInput(array).OutputToFile(output, overwrite: true, delegate(FFMpegArgumentOptions options) { options.CopyChannel().WithBitStreamFilter(Channel.Audio, Filter.Aac_AdtstoAsc); }).ProcessSynchronously(); } finally { Cleanup(array); } } private static FFMpegArgumentProcessor BaseSubVideo(string input, string output, TimeSpan startTime, TimeSpan endTime) { if (Path.GetExtension(input) != Path.GetExtension(output)) { output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output), Path.GetExtension(input)); } return FFMpegArguments.FromFileInput(input, verifyExists: true, delegate(FFMpegArgumentOptions options) { options.Seek(startTime).EndSeek(endTime); }).OutputToFile(output, overwrite: true, delegate(FFMpegArgumentOptions options) { options.CopyChannel(); }); } public static bool SubVideo(string input, string output, TimeSpan startTime, TimeSpan endTime) { return BaseSubVideo(input, output, startTime, endTime).ProcessSynchronously(); } public static async Task<bool> SubVideoAsync(string input, string output, TimeSpan startTime, TimeSpan endTime, CancellationToken cancellationToken = default(CancellationToken)) { return await BaseSubVideo(input, output, startTime, endTime).CancellableThrough(cancellationToken).ProcessAsynchronously().ConfigureAwait(continueOnCapturedContext: false); } public static bool SaveM3U8Stream(Uri uri, string output) { FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4); if (uri.Scheme != "http" && uri.Scheme != "https") { throw new ArgumentException("Uri: " + uri.AbsoluteUri + ", does not point to a valid http(s) stream."); } return FFMpegArguments.FromUrlInput(uri, delegate(FFMpegArgumentOptions options) { options.WithCopyCodec(); }).OutputToFile(output).ProcessSynchronously(); } public static bool Mute(string input, string output) { FFMpegHelper.ConversionSizeExceptionCheck(FFProbe.Analyse(input)); return FFMpegArguments.FromFileInput(input).OutputToFile(output, overwrite: true, delegate(FFMpegArgumentOptions options) { options.CopyChannel(Channel.Video).DisableChannel(Channel.Audio); }).ProcessSynchronously(); } public static bool ExtractAudio(string input, string output) { FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp3); return FFMpegArguments.FromFileInput(input).OutputToFile(output, overwrite: true, delegate(FFMpegArgumentOptions options) { options.DisableChannel(Channel.Video); }).ProcessSynchronously(); } public static bool ReplaceAudio(string input, string inputAudio, string output, bool stopAtShortest = false) { FFMpegHelper.ConversionSizeExceptionCheck(FFProbe.Analyse(input)); return FFMpegArguments.FromFileInput(input).AddFileInput(inputAudio).OutputToFile(output, overwrite: true, delegate(FFMpegArgumentOptions options) { options.CopyChannel().WithAudioCodec(AudioCodec.Aac).WithAudioBitrate(AudioQuality.Good) .UsingShortest(stopAtShortest); }) .ProcessSynchronously(); } private static void Cleanup(IEnumerable<string> pathList) { foreach (string path in pathList) { if (File.Exists(path)) { File.Delete(path); } } } internal static IReadOnlyList<PixelFormat> GetPixelFormatsInternal() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown FFMpegHelper.RootExceptionCheck(); List<PixelFormat> list = new List<PixelFormat>(); ProcessArguments val = new ProcessArguments(GlobalFFOptions.GetFFMpegBinaryPath(), "-pix_fmts"); val.OutputDataReceived += delegate(object e, string data) { if (PixelFormat.TryParse(data, out PixelFormat fmt)) { list.Add(fmt); } }; IProcessResult val2 = ProcessArgumentsExtensions.StartAndWaitForExit(val); if (val2.ExitCode != 0) { throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", val2.OutputData)); } return list.AsReadOnly(); } public static IReadOnlyList<PixelFormat> GetPixelFormats() { if (!GlobalFFOptions.Current.UseCache) { return GetPixelFormatsInternal(); } return FFMpegCache.PixelFormats.Values.ToList().AsReadOnly(); } public static bool TryGetPixelFormat(string name, out PixelFormat format) { string name2 = name; if (!GlobalFFOptions.Current.UseCache) { format = GetPixelFormatsInternal().FirstOrDefault((PixelFormat x) => x.Name == name2.ToLowerInvariant().Trim()); return format != null; } return FFMpegCache.PixelFormats.TryGetValue(name2, out format); } public static PixelFormat GetPixelFormat(string name) { if (TryGetPixelFormat(name, out PixelFormat format)) { return format; } throw new FFMpegException(FFMpegExceptionType.Operation, "Pixel format \"" + name + "\" not supported"); } private static void ParsePartOfCodecs(Dictionary<string, Codec> codecs, string arguments, Func<string, Codec?> parser) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown Func<string, Codec?> parser2 = parser; Dictionary<string, Codec> codecs2 = codecs; FFMpegHelper.RootExceptionCheck(); ProcessArguments val = new ProcessArguments(GlobalFFOptions.GetFFMpegBinaryPath(), arguments); val.OutputDataReceived += delegate(object e, string data) { Codec codec = parser2(data); if (codec != null) { if (codecs2.TryGetValue(codec.Name, out Codec value)) { value.Merge(codec); } else { codecs2.Add(codec.Name, codec); } } }; IProcessResult val2 = ProcessArgumentsExtensions.StartAndWaitForExit(val); if (val2.ExitCode != 0) { throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", val2.OutputData)); } } internal static Dictionary<string, Codec> GetCodecsInternal() { Dictionary<string, Codec> dictionary = new Dictionary<string, Codec>(); ParsePartOfCodecs(dictionary, "-codecs", (string s) => Codec.TryParseFromCodecs(s, out Codec codec3) ? codec3 : null); ParsePartOfCodecs(dictionary, "-encoders", (string s) => Codec.TryParseFromEncodersDecoders(s, out Codec codec2, isEncoder: true) ? codec2 : null); ParsePartOfCodecs(dictionary, "-decoders", (string s) => Codec.TryParseFromEncodersDecoders(s, out Codec codec, isEncoder: false) ? codec : null); return dictionary; } public static IReadOnlyList<Codec> GetCodecs() { if (!GlobalFFOptions.Current.UseCache) { return GetCodecsInternal().Values.ToList().AsReadOnly(); } return FFMpegCache.Codecs.Values.ToList().AsReadOnly(); } public static IReadOnlyList<Codec> GetCodecs(CodecType type) { if (!GlobalFFOptions.Current.UseCache) { return GetCodecsInternal().Values.Where((Codec x) => x.Type == type).ToList().AsReadOnly(); } return FFMpegCache.Codecs.Values.Where((Codec x) => x.Type == type).ToList().AsReadOnly(); } public static IReadOnlyList<Codec> GetVideoCodecs() { return GetCodecs(CodecType.Video); } public static IReadOnlyList<Codec> GetAudioCodecs() { return GetCodecs(CodecType.Audio); } public static IReadOnlyList<Codec> GetSubtitleCodecs() { return GetCodecs(CodecType.Subtitle); } public static IReadOnlyList<Codec> GetDataCodecs() { return GetCodecs(CodecType.Data); } public static bool TryGetCodec(string name, out Codec codec) { string name2 = name; if (!GlobalFFOptions.Current.UseCache) { codec = GetCodecsInternal().Values.FirstOrDefault((Codec x) => x.Name == name2.ToLowerInvariant().Trim()); return codec != null; } return FFMpegCache.Codecs.TryGetValue(name2, out codec); } public static Codec GetCodec(string name) { if (TryGetCodec(name, out Codec codec) && codec != null) { return codec; } throw new FFMpegException(FFMpegExceptionType.Operation, "Codec \"" + name + "\" not supported"); } internal static IReadOnlyList<ContainerFormat> GetContainersFormatsInternal() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown FFMpegHelper.RootExceptionCheck(); List<ContainerFormat> list = new List<ContainerFormat>(); ProcessArguments val = new ProcessArguments(GlobalFFOptions.GetFFMpegBinaryPath(), "-formats"); val.OutputDataReceived += delegate(object e, string data) { if (ContainerFormat.TryParse(data, out ContainerFormat fmt)) { list.Add(fmt); } }; IProcessResult val2 = ProcessArgumentsExtensions.StartAndWaitForExit(val); if (val2.ExitCode != 0) { throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", val2.OutputData)); } return list.AsReadOnly(); } public static IReadOnlyList<ContainerFormat> GetContainerFormats() { if (!GlobalFFOptions.Current.UseCache) { return GetContainersFormatsInternal(); } return FFMpegCache.ContainerFormats.Values.ToList().AsReadOnly(); } public static bool TryGetContainerFormat(string name, out ContainerFormat fmt) { string name2 = name; if (!GlobalFFOptions.Current.UseCache) { fmt = GetContainersFormatsInternal().FirstOrDefault((ContainerFormat x) => x.Name == name2.ToLowerInvariant().Trim()); return fmt != null; } return FFMpegCache.ContainerFormats.TryGetValue(name2, out fmt); } public static ContainerFormat GetContainerFormat(string name) { if (TryGetContainerFormat(name, out ContainerFormat fmt)) { return fmt; } throw new FFMpegException(FFMpegExceptionType.Operation, "Container format \"" + name + "\" not supported"); } } public class FFMpegArgumentOptions : FFMpegArgumentsBase { internal FFMpegArgumentOptions() { } public FFMpegArgumentOptions WithAudioCodec(Codec audioCodec) { return WithArgument(new AudioCodecArgument(audioCodec)); } public FFMpegArgumentOptions WithAudioCodec(string audioCodec) { return WithArgument(new AudioCodecArgument(audioCodec)); } public FFMpegArgumentOptions WithAudioBitrate(AudioQuality audioQuality) { return WithArgument(new AudioBitrateArgument(audioQuality)); } public FFMpegArgumentOptions WithAudioBitrate(int bitrate) { return WithArgument(new AudioBitrateArgument(bitrate)); } public FFMpegArgumentOptions WithAudioSamplingRate(int samplingRate = 48000) { return WithArgument(new AudioSamplingRateArgument(samplingRate)); } public FFMpegArgumentOptions WithVariableBitrate(int vbr) { return WithArgument(new VariableBitRateArgument(vbr)); } public FFMpegArgumentOptions Resize(int width, int height) { return WithArgument(new SizeArgument(width, height)); } public FFMpegArgumentOptions Resize(Size? size) { return WithArgument(new SizeArgument(size)); } public FFMpegArgumentOptions Crop(Size? size, int left, int top) { return WithArgument(new CropArgument(size, top, left)); } public FFMpegArgumentOptions Crop(int width, int height, int left, int top) { return WithArgument(new CropArgument(new Size(width, height), top, left)); } public FFMpegArgumentOptions Crop(Size? size) { return WithArgument(new CropArgument(size, 0, 0)); } public FFMpegArgumentOptions Crop(int width, int height) { return WithArgument(new CropArgument(new Size(width, height), 0, 0)); } public FFMpegArgumentOptions WithBitStreamFilter(Channel channel, Filter filter) { return WithArgument(new BitStreamFilterArgument(channel, filter)); } public FFMpegArgumentOptions WithConstantRateFactor(int crf) { return WithArgument(new ConstantRateFactorArgument(crf)); } public FFMpegArgumentOptions CopyChannel(Channel channel = Channel.Both) { return WithArgument(new CopyArgument(channel)); } public FFMpegArgumentOptions DisableChannel(Channel channel) { return WithArgument(new DisableChannelArgument(channel)); } public FFMpegArgumentOptions WithDuration(TimeSpan? duration) { return WithArgument(new DurationArgument(duration)); } public FFMpegArgumentOptions WithFastStart() { return WithArgument(new FaststartArgument()); } public FFMpegArgumentOptions WithFrameOutputCount(int frames) { return WithArgument(new FrameOutputCountArgument(frames)); } public FFMpegArgumentOptions WithHardwareAcceleration(HardwareAccelerationDevice hardwareAccelerationDevice = HardwareAccelerationDevice.Auto) { return WithArgument(new HardwareAccelerationArgument(hardwareAccelerationDevice)); } public FFMpegArgumentOptions UsingShortest(bool shortest = true) { return WithArgument(new ShortestArgument(shortest)); } public FFMpegArgumentOptions UsingMultithreading(bool multithread) { return WithArgument(new ThreadsArgument(multithread)); } public FFMpegArgumentOptions UsingThreads(int threads) { return WithArgument(new ThreadsArgument(threads)); } public FFMpegArgumentOptions WithVideoCodec(Codec videoCodec) { return WithArgument(new VideoCodecArgument(videoCodec)); } public FFMpegArgumentOptions WithVideoCodec(string videoCodec) { return WithArgument(new VideoCodecArgument(videoCodec)); } public FFMpegArgumentOptions WithVideoBitrate(int bitrate) { return WithArgument(new VideoBitrateArgument(bitrate)); } public FFMpegArgumentOptions WithVideoFilters(Action<VideoFilterOptions> videoFilterOptions) { VideoFilterOptions videoFilterOptions2 = new VideoFilterOptions(); videoFilterOptions(videoFilterOptions2); return WithArgument(new VideoFiltersArgument(videoFilterOptions2)); } public FFMpegArgumentOptions WithAudioFilters(Action<AudioFilterOptions> audioFilterOptions) { AudioFilterOptions audioFilterOptions2 = new AudioFilterOptions(); audioFilterOptions(audioFilterOptions2); return WithArgument(new AudioFiltersArgument(audioFilterOptions2)); } public FFMpegArgumentOptions WithFramerate(double framerate) { return WithArgument(new FrameRateArgument(framerate)); } public FFMpegArgumentOptions WithoutMetadata() { return WithArgument(new RemoveMetadataArgument()); } public FFMpegArgumentOptions WithSpeedPreset(Speed speed) { return WithArgument(new SpeedPresetArgument(speed)); } public FFMpegArgumentOptions WithStartNumber(int startNumber) { return WithArgument(new StartNumberArgument(startNumber)); } public FFMpegArgumentOptions WithCustomArgument(string argument) { return WithArgument(new CustomArgument(argument)); } public FFMpegArgumentOptions Seek(TimeSpan? seekTo) { return WithArgument(new SeekArgument(seekTo)); } public FFMpegArgumentOptions EndSeek(TimeSpan? seekTo) { return WithArgument(new EndSeekArgument(seekTo)); } public FFMpegArgumentOptions Loop(int times) { return WithArgument(new LoopArgument(times)); } public FFMpegArgumentOptions OverwriteExisting() { return WithArgument(new OverwriteArgument()); } public FFMpegArgumentOptions SelectStream(int streamIndex, int inputFileIndex = 0, Channel channel = Channel.All) { return WithArgument(new MapStreamArgument(streamIndex, inputFileIndex, channel)); } public FFMpegArgumentOptions SelectStreams(IEnumerable<int> streamIndices, int inputFileIndex = 0, Channel channel = Channel.All) { return streamIndices.Aggregate(this, (FFMpegArgumentOptions options, int streamIndex) => options.SelectStream(streamIndex, inputFileIndex, channel)); } public FFMpegArgumentOptions DeselectStream(int streamIndex, int inputFileIndex = 0, Channel channel = Channel.All) { return WithArgument(new MapStreamArgument(streamIndex, inputFileIndex, channel, negativeMap: true)); } public FFMpegArgumentOptions DeselectStreams(IEnumerable<int> streamIndices, int inputFileIndex = 0, Channel channel = Channel.All) { return streamIndices.Aggregate(this, (FFMpegArgumentOptions options, int streamIndex) => options.DeselectStream(streamIndex, inputFileIndex, channel)); } public FFMpegArgumentOptions ForceFormat(ContainerFormat format) { return WithArgument(new ForceFormatArgument(format)); } public FFMpegArgumentOptions ForceFormat(string format) { return WithArgument(new ForceFormatArgument(format)); } public FFMpegArgumentOptions ForcePixelFormat(string pixelFormat) { return WithArgument(new ForcePixelFormat(pixelFormat)); } public FFMpegArgumentOptions ForcePixelFormat(PixelFormat pixelFormat) { return WithArgument(new ForcePixelFormat(pixelFormat)); } public FFMpegArgumentOptions WithAudibleEncryptionKeys(string key, string iv) { return WithArgument(new AudibleEncryptionKeyArgument(key, iv)); } public FFMpegArgumentOptions WithAudibleActivationBytes(string activationBytes) { return WithArgument(new AudibleEncryptionKeyArgument(activationBytes)); } public FFMpegArgumentOptions WithTagVersion(int id3v2Version = 3) { return WithArgument(new ID3V2VersionArgument(id3v2Version)); } public FFMpegArgumentOptions WithGifPaletteArgument(int streamIndex, Size? size, double fps = 12.0) { return WithArgument(new GifPaletteArgument(streamIndex, fps, size)); } public FFMpegArgumentOptions WithCopyCodec() { return WithArgument(new CopyCodecArgument()); } public FFMpegArgumentOptions WithArgument(IArgument argument) { Arguments.Add(argument); return this; } } public class FFMpegArgumentProcessor { private static readonly Regex ProgressRegex = new Regex("time=(\\d\\d:\\d\\d:\\d\\d.\\d\\d?)", RegexOptions.Compiled); private readonly List<Action<FFOptions>> _configurations; private readonly FFMpegArguments _ffMpegArguments; private CancellationTokenRegistration? _cancellationTokenRegistration; private bool _cancelled; private FFMpegLogLevel? _logLevel; private Action<string>? _onError; private Action<string>? _onOutput; private Action<double>? _onPercentageProgress; private Action<TimeSpan>? _onTimeProgress; private TimeSpan? _totalTimespan; public string Arguments => _ffMpegArguments.Text; private event EventHandler<int> CancelEvent; internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments) { _configurations = new List<Action<FFOptions>>(); _ffMpegArguments = ffMpegArguments; } public FFMpegArgumentProcessor NotifyOnProgress(Action<double> onPercentageProgress, TimeSpan totalTimeSpan) { _totalTimespan = totalTimeSpan; _onPercentageProgress = onPercentageProgress; return this; } public FFMpegArgumentProcessor NotifyOnProgress(Action<TimeSpan> onTimeProgress) { _onTimeProgress = onTimeProgress; return this; } public FFMpegArgumentProcessor NotifyOnOutput(Action<string> onOutput) { _onOutput = onOutput; return this; } public FFMpegArgumentProcessor NotifyOnError(Action<string> onError) { _onError = onError; return this; } private void Cancel(int timeout) { _cancelled = true; this.CancelEvent?.Invoke(this, timeout); } public FFMpegArgumentProcessor CancellableThrough(out Action cancel, int timeout = 0) { cancel = delegate { Cancel(timeout); }; return this; } public FFMpegArgumentProcessor CancellableThrough(CancellationToken token, int timeout = 0) { _cancellationTokenRegistration?.Dispose(); _cancellationTokenRegistration = token.Register(delegate { Cancel(timeout); }); return this; } public FFMpegArgumentProcessor Configure(Action<FFOptions> configureOptions) { _configurations.Add(configureOptions); return this; } public FFMpegArgumentProcessor WithLogLevel(FFMpegLogLevel logLevel) { _logLevel = logLevel; return this; } public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null) { FFOptions configuredOptions = GetConfiguredOptions(ffMpegOptions); ProcessArguments processArguments = PrepareProcessArguments(configuredOptions); using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); IProcessResult val = null; try { val = Process(processArguments, cancellationTokenSource).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); } catch (OperationCanceledException) { if (throwOnError) { throw; } } return HandleCompletion(throwOnError, (val != null) ? val.ExitCode : (-1), ((val != null) ? val.ErrorData : null) ?? Array.Empty<string>()); } public async Task<bool> ProcessAsynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null) { FFOptions configuredOptions = GetConfiguredOptions(ffMpegOptions); ProcessArguments processArguments = PrepareProcessArguments(configuredOptions); using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); IProcessResult processResult = null; try { processResult = await Process(processArguments, cancellationTokenSource).ConfigureAwait(continueOnCapturedContext: false); } catch (OperationCanceledException) { if (throwOnError) { throw; } } FFMpegArgumentProcessor fFMpegArgumentProcessor = this; IProcessResult obj = processResult; int exitCode = ((obj != null) ? obj.ExitCode : (-1)); IProcessResult obj2 = processResult; return fFMpegArgumentProcessor.HandleCompletion(throwOnError, exitCode, ((obj2 != null) ? obj2.ErrorData : null) ?? Array.Empty<string>()); } private async Task<IProcessResult> Process(ProcessArguments processArguments, CancellationTokenSource cancellationTokenSource) { CancellationTokenSource cancellationTokenSource2 = cancellationTokenSource; IProcessResult processResult = null; if (_cancelled) { _cancellationTokenRegistration?.Dispose(); throw new OperationCanceledException("cancelled before starting processing"); } _ffMpegArguments.Pre(); IProcessInstance instance = processArguments.Start(); try { CancelEvent += OnCancelEvent; try { await Task.WhenAll(instance.WaitForExitAsync(default(CancellationToken)).ContinueWith(delegate(Task<IProcessResult> t) { processResult = t.Result; cancellationTokenSource2.Cancel(); _ffMpegArguments.Post(); }), _ffMpegArguments.During(cancellationTokenSource2.Token)).ConfigureAwait(continueOnCapturedContext: false); if (_cancelled) { _cancellationTokenRegistration?.Dispose(); throw new OperationCanceledException("ffmpeg processing was cancelled"); } return processResult; } finally { CancelEvent -= OnCancelEvent; _cancellationTokenRegistration?.Dispose(); } } finally { if (instance != null) { ((IDisposable)instance).Dispose(); } } static void ExecuteIgnoringFinishedProcessExceptions(Action action) { try { action(); } catch (InstanceProcessAlreadyExitedException) { } catch (ObjectDisposedException) { } } void OnCancelEvent(object sender, int timeout) { ExecuteIgnoringFinishedProcessExceptions(delegate { instance.SendInput("q"); }); if (!cancellationTokenSource2.Token.WaitHandle.WaitOne(timeout, exitContext: true)) { cancellationTokenSource2.Cancel(); ExecuteIgnoringFinishedProcessExceptions(delegate { instance.Kill(); }); } } } private bool HandleCompletion(bool throwOnError, int exitCode, IReadOnlyList<string> errorData) { if (throwOnError && exitCode != 0) { throw new FFMpegException(FFMpegExceptionType.Process, string.Format("ffmpeg exited with non-zero exit-code ({0} - {1})", exitCode, string.Join("\n", errorData)), null, string.Join("\n", errorData)); } _onPercentageProgress?.Invoke(100.0); if (_totalTimespan.HasValue) { _onTimeProgress?.Invoke(_totalTimespan.Value); } return exitCode == 0; } internal FFOptions GetConfiguredOptions(FFOptions? ffOptions) { FFOptions fFOptions = ffOptions ?? GlobalFFOptions.Current.Clone(); foreach (Action<FFOptions> configuration in _configurations) { configuration(fFOptions); } return fFOptions; } private ProcessArguments PrepareProcessArguments(FFOptions ffOptions) { //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown FFMpegHelper.RootExceptionCheck(); FFMpegHelper.VerifyFFMpegExists(ffOptions); string text = _ffMpegArguments.Text; if (!_logLevel.HasValue) { _logLevel = ffOptions.LogLevel; } if (_logLevel.HasValue) { string text2 = _logLevel.ToString().ToLower(); text = text + " -v " + text2; } ProcessArguments val = new ProcessArguments(new ProcessStartInfo { FileName = GlobalFFOptions.GetFFMpegBinaryPath(ffOptions), Arguments = text, StandardOutputEncoding = ffOptions.Encoding, StandardErrorEncoding = ffOptions.Encoding, WorkingDirectory = ffOptions.WorkingDirectory }); if (_onOutput != null) { val.OutputDataReceived += OutputData; } if (_onError != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan.HasValue)) { val.ErrorDataReceived += ErrorData; } return val; } private void ErrorData(object sender, string msg) { _onError?.Invoke(msg); Match match = ProgressRegex.Match(msg); if (match.Success) { TimeSpan obj = MediaAnalysisUtils.ParseDuration(match.Groups[1].Value); _onTimeProgress?.Invoke(obj); if (_onPercentageProgress != null && _totalTimespan.HasValue) { double obj2 = Math.Round(obj.TotalSeconds / _totalTimespan.Value.TotalSeconds * 100.0, 2); _onPercentageProgress(obj2); } } } private void OutputData(object sender, string msg) { _onOutput?.Invoke(msg); } } public sealed class FFMpegArguments : FFMpegArgumentsBase { private readonly FFMpegGlobalArguments _globalArguments = new FFMpegGlobalArguments(); public string Text => GetText(); private FFMpegArguments() { } private string GetText() { IArgument[] allArguments = _globalArguments.Arguments.Concat(Arguments).ToArray(); return string.Join(" ", allArguments.Select((IArgument arg) => (!(arg is IDynamicArgument dynamicArgument)) ? arg.Text : dynamicArgument.GetText(allArguments))); } public static FFMpegArguments FromConcatInput(IEnumerable<string> filePaths, Action<FFMpegArgumentOptions>? addArguments = null) { return new FFMpegArguments().WithInput(new ConcatArgument(filePaths), addArguments); } public static FFMpegArguments FromDemuxConcatInput(IEnumerable<string> filePaths, Action<FFMpegArgumentOptions>? addArguments = null) { return new FFMpegArguments().WithInput(new DemuxConcatArgument(filePaths), addArguments); } public static FFMpegArguments FromFileInput(string filePath, bool verifyExists = true, Action<FFMpegArgumentOptions>? addArguments = null) { return new FFMpegArguments().WithInput(new InputArgument(verifyExists, filePath), addArguments); } public static FFMpegArguments FromFileInput(IEnumerable<string> filePath, bool verifyExists = true, Action<FFMpegArgumentOptions>? addArguments = null) { return new FFMpegArguments().WithInput(new MultiInputArgument(verifyExists, filePath), addArguments); } public static FFMpegArguments FromFileInput(FileInfo fileInfo, Action<FFMpegArgumentOptions>? addArguments = null) { return new FFMpegArguments().WithInput(new InputArgument(fileInfo.FullName, verifyExists: false), addArguments); } public static FFMpegArguments FromUrlInput(Uri uri, Action<FFMpegArgumentOptions>? addArguments = null) { return new FFMpegArguments().WithInput(new InputArgument(uri.AbsoluteUri, verifyExists: false), addArguments); } public static FFMpegArguments FromDeviceInput(string device, Action<FFMpegArgumentOptions>? addArguments = null) { return new FFMpegArguments().WithInput(new InputDeviceArgument(device), addArguments); } public static FFMpegArguments FromPipeInput(IPipeSource sourcePipe, Action<FFMpegArgumentOptions>? addArguments = null) { return new FFMpegArguments().WithInput(new InputPipeArgument(sourcePipe), addArguments); } public FFMpegArguments WithGlobalOptions(Action<FFMpegGlobalArguments> configureOptions) { configureOptions(_globalArguments); return this; } public FFMpegArguments AddConcatInput(IEnumerable<string> filePaths, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new ConcatArgument(filePaths), addArguments); } public FFMpegArguments AddDemuxConcatInput(IEnumerable<string> filePaths, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new DemuxConcatArgument(filePaths), addArguments); } public FFMpegArguments AddFileInput(string filePath, bool verifyExists = true, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new InputArgument(verifyExists, filePath), addArguments); } public FFMpegArguments AddFileInput(IEnumerable<string> filePath, bool verifyExists = true, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new MultiInputArgument(verifyExists, filePath), addArguments); } public FFMpegArguments AddFileInput(FileInfo fileInfo, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new InputArgument(fileInfo.FullName, verifyExists: false), addArguments); } public FFMpegArguments AddUrlInput(Uri uri, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new InputArgument(uri.AbsoluteUri, verifyExists: false), addArguments); } public FFMpegArguments AddDeviceInput(string device, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new InputDeviceArgument(device), addArguments); } public FFMpegArguments AddPipeInput(IPipeSource sourcePipe, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new InputPipeArgument(sourcePipe), addArguments); } public FFMpegArguments AddMetaData(string content, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new MetaDataArgument(content), addArguments); } public FFMpegArguments AddMetaData(FFMetadataBuilder metaDataBuilder, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new MetaDataArgument(metaDataBuilder.GetMetadataFileContent()), addArguments); } public FFMpegArguments AddMetaData(IReadOnlyMetaData metaData, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new MetaDataArgument(MetaDataSerializer.Instance.Serialize(metaData)), addArguments); } public FFMpegArguments MapMetaData(int? inputIndex = null, Action<FFMpegArgumentOptions>? addArguments = null) { return WithInput(new MapMetadataArgument(inputIndex), addArguments); } private FFMpegArguments WithInput(IInputArgument inputArgument, Action<FFMpegArgumentOptions>? addArguments) { FFMpegArgumentOptions fFMpegArgumentOptions = new FFMpegArgumentOptions(); addArguments?.Invoke(fFMpegArgumentOptions); Arguments.AddRange(fFMpegArgumentOptions.Arguments); Arguments.Add(inputArgument); return this; } public FFMpegArgumentProcessor OutputToFile(string file, bool overwrite = true, Action<FFMpegArgumentOptions>? addArguments = null) { return ToProcessor(new OutputArgument(file, overwrite), addArguments); } public FFMpegArgumentProcessor OutputToUrl(string uri, Action<FFMpegArgumentOptions>? addArguments = null) { return ToProcessor(new OutputUrlArgument(uri), addArguments); } public FFMpegArgumentProcessor OutputToUrl(Uri uri, Action<FFMpegArgumentOptions>? addArguments = null) { return ToProcessor(new OutputUrlArgument(uri.ToString()), addArguments); } public FFMpegArgumentProcessor OutputToPipe(IPipeSink reader, Action<FFMpegArgumentOptions>? addArguments = null) { return ToProcessor(new OutputPipeArgument(reader), addArguments); } private FFMpegArgumentProcessor ToProcessor(IOutputArgument argument, Action<FFMpegArgumentOptions>? addArguments) { FFMpegArgumentOptions fFMpegArgumentOptions = new FFMpegArgumentOptions(); addArguments?.Invoke(fFMpegArgumentOptions); Arguments.AddRange(fFMpegArgumentOptions.Arguments); Arguments.Add(argument); return new FFMpegArgumentProcessor(this); } public FFMpegArgumentProcessor OutputToTee(Action<FFMpegMultiOutputOptions> addOutputs, Action<FFMpegArgumentOptions>? addArguments = null) { FFMpegMultiOutputOptions fFMpegMultiOutputOptions = new FFMpegMultiOutputOptions(); addOutputs(fFMpegMultiOutputOptions); return ToProcessor(new OutputTeeArgument(fFMpegMultiOutputOptions), addArguments); } public FFMpegArgumentProcessor MultiOutput(Action<FFMpegMultiOutputOptions> addOutputs) { FFMpegMultiOutputOptions fFMpegMultiOutputOptions = new FFMpegMultiOutputOptions(); addOutputs(fFMpegMultiOutputOptions); Arguments.AddRange(fFMpegMultiOutputOptions.Arguments); return new FFMpegArgumentProcessor(this); } internal void Pre() { foreach (IInputOutputArgument item in Arguments.OfType<IInputOutputArgument>()) { item.Pre(); } } internal async Task During(CancellationToken cancellationToken = default(CancellationToken)) { await Task.WhenAll(from io in Arguments.OfType<IInputOutputArgument>() select io.During(cancellationToken)).ConfigureAwait(continueOnCapturedContext: false); } internal void Post() { foreach (IInputOutputArgument item in Arguments.OfType<IInputOutputArgument>()) { item.Post(); } } } public abstract class FFMpegArgumentsBase { internal readonly List<IArgument> Arguments = new List<IArgument>(); } internal static class FFMpegCache { private static readonly object _syncObject = new object(); private static Dictionary<string, PixelFormat>? _pixelFormats; private static Dictionary<string, Codec>? _codecs; private static Dictionary<string, ContainerFormat>? _containers; public static IReadOnlyDictionary<string, PixelFormat> PixelFormats { get { if (_pixelFormats == null) { lock (_syncObject) { if (_pixelFormats == null) { _pixelFormats = FFMpeg.GetPixelFormatsInternal().ToDictionary((PixelFormat x) => x.Name); } } } return _pixelFormats; } } public static IReadOnlyDictionary<string, Codec> Codecs { get { if (_codecs == null) { lock (_syncObject) { if (_codecs == null) { _codecs = FFMpeg.GetCodecsInternal(); } } } return _codecs; } } public static IReadOnlyDictionary<string, ContainerFormat> ContainerFormats { get { if (_containers == null) { lock (_syncObject) { if (_containers == null) { _containers = FFMpeg.GetContainersFormatsInternal().ToDictionary((ContainerFormat x) => x.Name); } } } return _containers; } } } public sealed class FFMpegGlobalArguments : FFMpegArgumentsBase { internal FFMpegGlobalArguments() { } public FFMpegGlobalArguments WithVerbosityLevel(VerbosityLevel verbosityLevel = VerbosityLevel.Error) { return WithOption(new VerbosityLevelArgument(verbosityLevel)); } private FFMpegGlobalArguments WithOption(IArgument argument) { Arguments.Add(argument); return this; } } public class FFMpegMultiOutputOptions { internal readonly List<FFMpegArgumentOptions> Outputs = new List<FFMpegArgumentOptions>(); public IEnumerable<IArgument> Arguments => Outputs.SelectMany((FFMpegArgumentOptions o) => o.Arguments); public FFMpegMultiOutputOptions OutputToFile(string file, bool overwrite = true, Action<FFMpegArgumentOptions>? addArguments = null) { return AddOutput(new OutputArgument(file, overwrite), addArguments); } public FFMpegMultiOutputOptions OutputToUrl(string uri, Action<FFMpegArgumentOptions>? addArguments = null) { return AddOutput(new OutputUrlArgument(uri), addArguments); } public FFMpegMultiOutputOptions OutputToUrl(Uri uri, Action<FFMpegArgumentOptions>? addArguments = null) { return AddOutput(new OutputUrlArgument(uri.ToString()), addArguments); } public FFMpegMultiOutputOptions OutputToPipe(IPipeSink reader, Action<FFMpegArgumentOptions>? addArguments = null) { return AddOutput(new OutputPipeArgument(reader), addArguments); } public FFMpegMultiOutputOptions AddOutput(IOutputArgument argument, Action<FFMpegArgumentOptions>? addArguments) { FFMpegArgumentOptions fFMpegArgumentOptions = new FFMpegArgumentOptions(); addArguments?.Invoke(fFMpegArgumentOptions); fFMpegArgumentOptions.Arguments.Add(argument); Outputs.Add(fFMpegArgumentOptions); return this; } } public static class SnapshotArgumentBuilder { public static (FFMpegArguments, Action<FFMpegArgumentOptions> outputOptions) BuildSnapshotArguments(string input, string output, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) { return BuildSnapshotArguments(input, VideoCodec.Image.GetByExtension(output), source, size, captureTime, streamIndex, inputFileIndex); } public static (FFMpegArguments, Action<FFMpegArgumentOptions> outputOptions) BuildSnapshotArguments(string input, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) { return BuildSnapshotArguments(input, VideoCodec.Image.Png, source, size, captureTime, streamIndex, inputFileIndex); } private static (FFMpegArguments, Action<FFMpegArgumentOptions> outputOptions) BuildSnapshotArguments(string input, Codec codec, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) { Codec codec2 = codec; TimeSpan valueOrDefault = captureTime.GetValueOrDefault(); if (!captureTime.HasValue) { valueOrDefault = TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3.0); captureTime = valueOrDefault; } size = PrepareSnapshotSize(source, size); int valueOrDefault2 = streamIndex.GetValueOrDefault(); if (!streamIndex.HasValue) { valueOrDefault2 = source.PrimaryVideoStream?.Index ?? source.VideoStreams.FirstOrDefault()?.Index ?? 0; streamIndex = valueOrDefault2; } return (FFMpegArguments.FromFileInput(input, verifyExists: false, delegate(FFMpegArgumentOptions options) { options.Seek(captureTime); }), delegate(FFMpegArgumentOptions options) { options.SelectStream(streamIndex.Value, inputFileIndex).WithVideoCodec(codec2).WithFrameOutputCount(1) .Resize(size); }); } public static (FFMpegArguments, Action<FFMpegArgumentOptions> outputOptions) BuildGifSnapshotArguments(string input, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, TimeSpan? duration = null, int? streamIndex = null, double fps = 12.0) { Size defaultValue = new Size(480, -1); TimeSpan valueOrDefault = captureTime.GetValueOrDefault(); if (!captureTime.HasValue) { valueOrDefault = TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3.0); captureTime = valueOrDefault; } size = PrepareSnapshotSize(source, size).GetValueOrDefault(defaultValue); int valueOrDefault2 = streamIndex.GetValueOrDefault(); if (!streamIndex.HasValue) { valueOrDefault2 = source.PrimaryVideoStream?.Index ?? source.VideoStreams.FirstOrDefault()?.Index ?? 0; streamIndex = valueOrDefault2; } return (FFMpegArguments.FromFileInput(input, verifyExists: false, delegate(FFMpegArgumentOptions options) { options.Seek(captureTime).WithDuration(duration); }), delegate(FFMpegArgumentOptions options) { options.WithGifPaletteArgument(streamIndex.Value, size, fps); }); } private static Size? PrepareSnapshotSize(IMediaAnalysis source, Size? wantedSize) { if (!wantedSize.HasValue || (wantedSize.Value.Height <= 0 && wantedSize.Value.Width <= 0) || source.PrimaryVideoStream == null) { return null; } Size size = new Size(source.PrimaryVideoStream.Width, source.PrimaryVideoStream.Height); if (IsRotated(source.PrimaryVideoStream.Rotation)) { size = new Size(source.PrimaryVideoStream.Height, source.PrimaryVideoStream.Width); } if (wantedSize.Value.Width != size.Width || wantedSize.Value.Height != size.Height) { if (wantedSize.Value.Width <= 0 && wantedSize.Value.Height > 0) { double num = (double)wantedSize.Value.Height / (double)size.Height; return new Size((int)((double)size.Width * num), (int)((double)size.Height * num)); } if (wantedSize.Value.Height <= 0 && wantedSize.Value.Width > 0) { double num2 = (double)wantedSize.Value.Width / (double)size.Width; return new Size((int)((double)size.Width * num2), (int)((double)size.Height * num2)); } return wantedSize; } return null; } private static bool IsRotated(int rotation) { int num = Math.Abs(rotation); if (num != 90) { return num == 180; } return true; } } public class FFOptions : ICloneable { public string WorkingDirectory { get; set; } = string.Empty; public string BinaryFolder { get; set; } = string.Empty; public string TemporaryFilesFolder { get; set; } = Path.GetTempPath(); public string EncodingWebName { get; set; } = System.Text.Encoding.Default.WebName; [JsonIgnore] public Encoding Encoding { get { return System.Text.Encoding.GetEncoding(EncodingWebName); } set { EncodingWebName = value?.WebName ?? System.Text.Encoding.Default.WebName; } } public FFMpegLogLevel? LogLevel { get; set; } public Dictionary<string, string> ExtensionOverrides { get; set; } = new Dictionary<string, string> { { "mpegts", ".ts" } }; public bool UseCache { get; set; } = true; object ICloneable.Clone() { return Clone(); } public FFOptions Clone() { return (FFOptions)MemberwiseClone(); } } public class AudioStream : MediaStream { public int Channels { get; set; } public string ChannelLayout { get; set; } public int SampleRateHz { get; set; } public string Profile { get; set; } } public static class FFProbe { public static IMediaAnalysis Analyse(string filePath, FFOptions? ffOptions = null, string? customArguments = null) { ThrowIfInputFileDoesNotExist(filePath); IProcessResult obj = PrepareStreamAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current, customArguments).StartAndWaitForExit(); ThrowIfExitCodeNotZero(obj); return ParseOutput(obj); } public static FFProbeFrames GetFrames(string filePath, FFOptions? ffOptions = null, string? customArguments = null) { ThrowIfInputFileDoesNotExist(filePath); IProcessResult obj = PrepareFrameAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current, customArguments).StartAndWaitForExit(); ThrowIfExitCodeNotZero(obj); return ParseFramesOutput(obj); } public static FFProbePackets GetPackets(string filePath, FFOptions? ffOptions = null, string? customArguments = null) { ThrowIfInputFileDoesNotExist(filePath); IProcessResult obj = PreparePacketAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current, customArguments).StartAndWaitForExit(); ThrowIfExitCodeNotZero(obj); return ParsePacketsOutput(obj); } public static IMediaAnalysis Analyse(Uri uri, FFOptions? ffOptions = null, string? customArguments = null) { IProcessResult obj = PrepareStreamAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current, customArguments).StartAndWaitForExit(); ThrowIfExitCodeNotZero(obj); return ParseOutput(obj); } public static IMediaAnalysis Analyse(Stream stream, FFOptions? ffOptions = null, string? customArguments = null) { InputPipeArgument inputPipeArgument = new InputPipeArgument(new StreamPipeSource(stream)); ProcessArguments processArguments = PrepareStreamAnalysisInstance(inputPipeArgument.PipePath, ffOptions ?? GlobalFFOptions.Current, customArguments); inputPipeArgument.Pre(); Task<IProcessResult> task = processArguments.StartAndWaitForExitAsync(); try { inputPipeArgument.During().ConfigureAwait(continueOnCapturedContext: false).GetAwaiter() .GetResult(); } catch (IOException) { } finally { inputPipeArgument.Post(); } IProcessResult result = task.ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); ThrowIfExitCodeNotZero(result); return ParseOutput(result); } public static async Task<IMediaAnalysis> AnalyseAsync(string filePath, FFOptions? ffOptions = null, CancellationToken cancellationToken = default(CancellationToken), string? customArguments = null) { ThrowIfInputFileDoesNotExist(filePath); IProcessResult obj = await PrepareStreamAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current, customArguments).StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); cancellationToken.ThrowIfCancellationRequested(); ThrowIfExitCodeNotZero(obj); return ParseOutput(obj); } public static FFProbeFrames GetFrames(Uri uri, FFOptions? ffOptions = null, string? customArguments = null) { IProcessResult obj = PrepareFrameAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current, customArguments).StartAndWaitForExit(); ThrowIfExitCodeNotZero(obj); return ParseFramesOutput(obj); } public static async Task<FFProbeFrames> GetFramesAsync(string filePath, FFOptions? ffOptions = null, CancellationToken cancellationToken = default(CancellationToken), string? customArguments = null) { ThrowIfInputFileDoesNotExist(filePath); return ParseFramesOutput(await PrepareFrameAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current, customArguments).StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); } public static async Task<FFProbePackets> GetPacketsAsync(string filePath, FFOptions? ffOptions = null, CancellationToken cancellationToken = default(CancellationToken), string? customArguments = null) { ThrowIfInputFileDoesNotExist(filePath); return ParsePacketsOutput(await PreparePacketAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current, customArguments).StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); } public static async Task<IMediaAnalysis> AnalyseAsync(Uri uri, FFOptions? ffOptions = null, CancellationToken cancellationToken = default(CancellationToken), string? customArguments = null) { IProcessResult obj = await PrepareStreamAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current, customArguments).StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); cancellationToken.ThrowIfCancellationRequested(); ThrowIfExitCodeNotZero(obj); return ParseOutput(obj); } public static async Task<IMediaAnalysis> AnalyseAsync(Stream stream, FFOptions? ffOptions = null, CancellationToken cancellationToken = default(CancellationToken), string? customArguments = null) { StreamPipeSource writer = new StreamPipeSource(stream); InputPipeArgument pipeArgument = new InputPipeArgument(writer); ProcessArguments processArguments = PrepareStreamAnalysisInstance(pipeArgument.PipePath, ffOptions ?? GlobalFFOptions.Current, customArguments); pipeArgument.Pre(); Task<IProcessResult> task = processArguments.StartAndWaitForExitAsync(cancellationToken); try { await pipeArgument.During(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } catch (IOException) { } finally { pipeArgument.Post(); } IProcessResult obj = await task.ConfigureAwait(continueOnCapturedContext: false); cancellationToken.ThrowIfCancellationRequested(); ThrowIfExitCodeNotZero(obj); pipeArgument.Post(); return ParseOutput(obj); } public static async Task<FFProbeFrames> GetFramesAsync(Uri uri, FFOptions? ffOptions = null, CancellationToken cancellationToken = default(CancellationToken), string? customArguments = null) { return ParseFramesOutput(await PrepareFrameAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current, customArguments).StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false)); } private static IMediaAnalysis ParseOutput(IProcessResult instance) { FFProbeAnalysis? fFProbeAnalysis = JsonSerializer.Deserialize<FFProbeAnalysis>(string.Join(string.Empty, instance.OutputData), 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) { return JsonSerializer.Deserialize<FFProbeFrames>(string.Join(string.Empty, instance.OutputData), new JsonSerializerOptions { PropertyNameCaseInsensitive = true, NumberHandling = (JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString) }); } private static FFProbePackets ParsePacketsOutput(IProcessResult instance) { return JsonSerializer.Deserialize<FFProbePackets>(string.Join(string.Empty, instance.OutputData), new JsonSerializerOptions { PropertyNameCaseInsensitive = true, NumberHandling = (JsonNumberHandling.AllowReadingFromString | JsonNumberHandling.WriteAsString) }); } 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) { string message = string.Format("ffprobe exited with non-zero exit-code ({0} - {1})", 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) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown FFProbeHelper.RootExceptionCheck(); FFProbeHelper.VerifyFFProbeExists(ffOptions); return new ProcessArguments(new ProcessStartInfo(GlobalFFOptions.GetFFProbeBinaryPath(ffOptions), arguments + " " + customArguments) { StandardOutputEncoding = ffOptions.Encoding, StandardErrorEncoding = ffOptions.Encoding, WorkingDirectory = ffOptions.WorkingDirectory }); } } public class FFProbeAnalysis { [JsonPropertyName("streams")] public List<FFProbeStream> Streams { get; set; } [JsonPropertyName("format")] public Format Format { get; set; } [JsonPropertyName("chapters")] public List<Chapter> Chapters { get; set; } [JsonIgnore] public IReadOnlyList<string> ErrorData { get; set; } = new List<string>(); } public class FFProbeStream : ITagsContainer, IDispositionContainer { [JsonPropertyName("index")] public int Index { get; set; } [JsonPropertyName("avg_frame_rate")] public string AvgFrameRate { get; set; } [JsonPropertyName("bits_per_raw_sample")] public string BitsPerRawSample { get; set; } [JsonPropertyName("bits_per_sample")] public int BitsPerSample { get; set; } [JsonPropertyName("bit_rate")] public string BitRate { get; set; } [JsonPropertyName("channels")] public int? Channels { get; set; } [JsonPropertyName("channel_layout")] public string ChannelLayout { get; set; } [JsonPropertyName("codec_type")] public string CodecType { get; set; } [JsonPropertyName("codec_name")] public string CodecName { get; set; } [JsonPropertyName("codec_long_name")] public string CodecLongName { get; set; } [JsonPropertyName("codec_tag")] public string CodecTag { get; set; } [JsonPropertyName("codec_tag_string")] public string CodecTagString { get; set; } [JsonPropertyName("display_aspect_ratio")] public string DisplayAspectRatio { get; set; } [JsonPropertyName("sample_aspect_ratio")] public string SampleAspectRatio { get; set; } [JsonPropertyName("start_time")] public string StartTime { get; set; } [JsonPropertyName("duration")] public string Duration { get; set; } [JsonPropertyName("profile")] public string Profile { get; set; } [JsonPropertyName("width")] public int? Width { get; set; } [JsonPropertyName("height")] public int? Height { get; set; } [JsonPropertyName("r_frame_rate")] public string FrameRate { get; set; } [JsonPropertyName("pix_fmt")] public string PixelFormat { get; set; } [JsonPropertyName("level")] public int Level { get; set; } [JsonPropertyName("sample_rate")] public string SampleRate { get; set; } [JsonPropertyName("side_data_list")] public List<Dictionary<string, JsonValue>> SideData { get; set; } [JsonPropertyName("color_range")] public string ColorRange { get; set; } [JsonPropertyName("color_space")] public string ColorSpace { get; set; } [JsonPropertyName("color_transfer")] public string ColorTransfer { get; set; } [JsonPropertyName("color_primaries")] public string ColorPrimaries { get; set; } [JsonPropertyName("disposition")] public Dictionary<string, int> Disposition { get; set; } [JsonPropertyName("tags")] public Dictionary<string, string>? Tags { get; set; } } public class Format : ITagsContainer { [JsonPropertyName("filename")] public string Filename { get; set; } [JsonPropertyName("nb_streams")] public int NbStreams { get; set; } [JsonPropertyName("nb_programs")] public int NbPrograms { get; set; } [JsonPropertyName("format_name")] public string FormatName { get; set; } [JsonPropertyName("format_long_name")] public string FormatLongName { get; set; } [JsonPropertyName("start_time")] public string StartTime { get; set; } [JsonPropertyName("duration")] public string Duration { get; set; } [JsonPropertyName("size")] public string Size { get; set; } [JsonPropertyName("bit_rate")] public string? BitRate { get; set; } [JsonPropertyName("probe_score")] public int ProbeScore { get; set; } [JsonPropertyName("tags")] public Dictionary<string, string>? Tags { get; set; } } public class Chapter : ITagsContainer { [JsonPropertyName("id")] public long Id { get; set; } [JsonPropertyName("time_base")] public string TimeBase { get; set; } [JsonPropertyName("start")] public long Start { get; set; } [JsonPropertyName("start_time")] public string StartTime { get; set; } [JsonPropertyName("end")] public long End { get; set; } [JsonPropertyName("end_time")] public string EndTime { get; set; } [JsonPropertyName("tags")] public Dictionary<string, string>? Tags { get; set; } } public interface IDispositionContainer { Dictionary<string, int> Disposition { get; set; } } public interface ITagsContainer { Dictionary<string, string>? Tags { get; set; } } public static class TagExtensions { private static string? TryGetTagValue(ITagsContainer tagsContainer, string key) { if (tagsContainer.Tags != null && tagsContainer.Tags.TryGetValue(key, out string value)) { return value; } return null; } public static string? GetLanguage(this ITagsContainer tagsContainer) { return TryGetTagValue(tagsContainer, "language"); } public static string? GetCreationTime(this ITagsContainer tagsContainer) { return TryGetTagValue(tagsContainer, "creation_time"); } public static string? GetRotate(this ITagsContainer tagsContainer) { return TryGetTagValue(tagsContainer, "rotate"); } public static string? GetDuration(this ITagsContainer tagsContainer) { return TryGetTagValue(tagsContainer, "duration"); } } public static class DispositionExtensions { private static int? TryGetDispositionValue(IDispositionContainer dispositionContainer, string key) { if (dispositionContainer.Disposition != null && dispositionContainer.Disposition.TryGetValue(key, out var value)) { return value; } return null; } public static int? GetDefault(this IDispositionContainer tagsContainer) { return TryGetDispositionValue(tagsContainer, "default"); } public static int? GetForced(this IDispositionContainer tagsContainer) { return TryGetDispositionValue(tagsContainer, "forced"); } } 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; } } public interface IMediaAnalysis { TimeSpan Duration { get; } MediaFormat Format { get; } List<ChapterData> Chapters { get; } AudioStream? PrimaryAudioStream { get; } VideoStream? PrimaryVideoStream { get; } SubtitleStream? PrimarySubtitleStream { get; } List<VideoStream> VideoStreams { get; } List<AudioStream> AudioStreams { get; } List<SubtitleStream> SubtitleStreams { get; } IReadOnlyList<string> ErrorData { get; } } internal class MediaAnalysis : IMediaAnalysis { public TimeSpan Duration => new TimeSpan[3] { Format.Duration, PrimaryVideoStream?.Duration ?? TimeSpan.Zero, PrimaryAudioStream?.Duration ?? TimeSpan.Zero }.Max(); public MediaFormat Format { get; } public List<ChapterData> Chapters { get; } public AudioStream? PrimaryAudioStream => AudioStreams.OrderBy((AudioStream stream) => stream.Index).FirstOrDefault(); public VideoStream? PrimaryVideoStream => VideoStreams.OrderBy((VideoStream stream) => stream.Index).FirstOrDefault(); public SubtitleStream? PrimarySubtitleStream => SubtitleStreams.OrderBy((SubtitleStream stream) => stream.Index).FirstOrDefault(); public List<VideoStream> VideoStreams { get; } public List<AudioStream> AudioStreams { get; } public List<SubtitleStream> SubtitleStreams { get; } public IReadOnlyList<string> ErrorData { get; } internal MediaAnalysis(FFProbeAnalysis analysis) { Format = ParseFormat(analysis.Format); Chapters = analysis.Chapters.Select((Chapter c) => ParseChapter(c)).ToList(); VideoStreams = analysis.Streams.Where((FFProbeStream stream) => stream.CodecType == "video").Select(ParseVideoStream).ToList(); AudioStreams = analysis.Streams.Where((FFProbeStream stream) => stream.CodecType == "audio").Select(ParseAudioStream).ToList(); SubtitleStreams = analysis.Streams.Where((FFProbeStream stream) => stream.CodecType == "subtitle").Select(ParseSubtitleStream).ToList(); ErrorData = analysis.ErrorData; } private MediaFormat ParseFormat(Format analysisFormat) { return new MediaFormat { Duration = MediaAnalysisUtils.ParseDuration(analysisFormat.Duration), StartTime = MediaAnalysisUtils.ParseDuration(analysisFormat.StartTime), FormatName = analysisFormat.FormatName, FormatLongName = analysisFormat.FormatLongName, StreamCount = analysisFormat.NbStreams, ProbeScore = analysisFormat.ProbeScore, BitRate = long.Parse(analysisFormat.BitRate ?? "0"), Tags = analysisFormat.Tags.ToCaseInsensitive() }; } private string GetValue(string tagName, Dictionary<string, string>? tags, string defaultValue) { if (tags != null) { if (!tags.TryGetValue(tagName, out string value)) { return defaultValue; } return value; } return defaultValue; } private ChapterData ParseChapter(Chapter analysisChapter) { string value = GetValue("title", analysisChapter.Tags, "TitleValueNotSet"); TimeSpan start = MediaAnalysisUtils.ParseDuration(analysisChapter.StartTime); TimeSpan end = MediaAnalysisUtils.ParseDuration(analysisChapter.EndTime); return new ChapterData(value, start, end); } private int? GetBitDepth(FFProbeStream stream) { int result; int num = (int.TryParse(stream.BitsPerRawSample, out result) ? result : stream.BitsPerSample); if (num != 0) { return num; } return null; } private VideoStream ParseVideoStream(FFProbeStream stream) { return new VideoStream { Index = stream.Index, AvgFrameRate = MediaAnalysisUtils.DivideRatio(MediaAnalysisUtils.ParseRatioDouble(stream.AvgFrameRate, '/')), BitRate = ((!string.IsNullOrEmpty(stream.BitRate)) ? MediaAnalysisUtils.ParseLongInvariant(stream.BitRate) : 0), BitsPerRawSample = ((!string.IsNullOrEmpty(stream.BitsPerRawSample)) ? MediaAnalysisUtils.ParseIntInvariant(stream.BitsPerRawSample) : 0), CodecName = stream.CodecName, CodecLongName = stream.CodecLongName, CodecTag = stream.CodecTag, CodecTagString = stream.CodecTagString, DisplayAspectRatio = MediaAnalysisUtils.ParseRatioInt(stream.DisplayAspectRatio, ':'), SampleAspectRatio = MediaAnalysisUtils.ParseRatioInt(stream.SampleAspectRatio, ':'), Duration = MediaAnalysisUtils.ParseDuration(stream.Duration), StartTime = MediaAnalysisUtils.ParseDuration(stream.StartTime), FrameRate = MediaAnalysisUtils.DivideRatio(MediaAnalysisUtils.ParseRatioDouble(stream.FrameRate, '/')), Height = stream.Height.GetValueOrDefault(), Width = stream.Width.GetValueOrDefault(), Profile = stream.Profile, PixelFormat = stream.PixelFormat, Level = stream.Level, ColorRange = stream.ColorRange, ColorSpace = stream.ColorSpace, ColorTransfer = stream.ColorTransfer, ColorPrimaries = stream.ColorPrimaries, Rotation = MediaAnalysisUtils.ParseRotation(stream), Language = stream.GetLanguage(), Disposition = MediaAnalysisUtils.FormatDisposition(stream.Disposition), Tags = stream.Tags.ToCaseInsensitive(), BitDepth = GetBitDepth(stream) }; } private AudioStream ParseAudioStream(FFProbeStream stream) { return new AudioStream { Index = stream.Index, BitRate = ((!string.IsNullOrEmpty(stream.BitRate)) ? MediaAnalysisUtils.ParseLongInvariant(stream.BitRate) : 0), CodecName = stream.CodecName, CodecLongName = stream.CodecLongName, CodecTag = stream.CodecTag, CodecTagString = stream.CodecTagString, Channels = stream.Channels.GetValueOrDefault(), ChannelLayout = stream.ChannelLayout, Duration = MediaAnalysisUtils.ParseDuration(stream.Duration), StartTime = MediaAnalysisUtils.ParseDuration(stream.StartTime), SampleRateHz = ((!string.IsNullOrEmpty(stream.SampleRate)) ? MediaAnalysisUtils.ParseIntInvariant(stream.SampleRate) : 0), Profile = stream.Profile, Language = stream.GetLanguage(), Disposition = MediaAnalysisUtils.FormatDisposition(stream.Disposition), Tags = stream.Tags.ToCaseInsensitive(), BitDepth = GetBitDepth(stream) }; } private SubtitleStream ParseSubtitleStream(FFProbeStream stream) { return new SubtitleStream { Index = stream.Index, BitRate = ((!string.IsNullOrEmpty(stream.BitRate)) ? MediaAnalysisUtils.ParseLongInvariant(stream.BitRate) : 0), CodecName = stream.CodecName, CodecLongName = stream.CodecLongName, Duration = MediaAnalysisUtils.ParseDuration(stream.Duration), StartTime = MediaAnalysisUtils.ParseDuration(stream.StartTime), Language = stream.GetLanguage(), Disposition = MediaAnalysisUtils.FormatDisposition(stream.Disposition), Tags = stream.Tags.ToCaseInsensitive() }; } } public static class MediaAnalysisUtils { private static readonly Regex DurationRegex = new Regex("^(\\d+):(\\d{1,2}):(\\d{1,2})\\.(\\d{1,3})", RegexOptions.Compiled); internal static Dictionary<string, string> ToCaseInsensitive(this Dictionary<string, string>? dictionary) { return dictionary?.ToDictionary<KeyValuePair<string, string>, string, string>((KeyValuePair<string, string> tag) => tag.Key, (KeyValuePair<string, string> tag) => tag.Value, StringComparer.OrdinalIgnoreCase) ?? new Dictionary<string, string>(); } public static double DivideRatio((double, double) ratio) { return ratio.Item1 / ratio.Item2; } public static (int, int) ParseRatioInt(string input, char separator) { if (string.IsNullOrEmpty(input)) { return (0, 0); } string[] array = input.Split(new char[1] { separator }); return (ParseIntInvariant(array[0]), ParseIntInvariant(array[1])); } public static (double, double) ParseRatioDouble(string input, char separator) { if (string.IsNullOrEmpty(input)) { return (0.0, 0.0); } string[] array = input.Split(new char[1] { separator }); return ((array.Length != 0) ? ParseDoubleInvariant(array[0]) : 0.0, (array.Length > 1) ? ParseDoubleInvariant(array[1]) : 0.0); } public static double ParseDoubleInvariant(string line) { return double.Parse(line, NumberStyles.Any, CultureInfo.InvariantCulture); } public static int ParseIntInvariant(string line) { return int.Parse(line, NumberStyles.Any, CultureInfo.InvariantCulture); } public static long ParseLongInvariant(string line) { return long.Parse(line, NumberStyles.Any, CultureInfo.InvariantCulture); } public static TimeSpan ParseDuration(string duration) { if (!string.IsNullOrEmpty(duration)) { Match match = DurationRegex.Match(duration); if (match.Success) { string text = match.Groups[4].Value; if (text.Length < 3) { text = text.PadRight(3, '0'); } int hours = int.Parse(match.Groups[1].Value); int minutes = int.Parse(match.Groups[2].Value); int seconds = int.Parse(match.Groups[3].Value); int milliseconds = int.Parse(text); return new TimeSpan(0, hours, minutes, seconds, milliseconds); } return TimeSpan.Zero; } return TimeSpan.Zero; } public static int ParseRotation(FFProbeStream fFProbeStream) { JsonValue value2; Dictionary<string, JsonValue>? obj = fFProbeStream.SideData?.Find((Dictionary<string, JsonValue> item) => item.TryGetValue("side_data_type", out value2) && value2.ToString() == "Display Matrix"); if (obj != null && obj.TryGetValue("rotation", out JsonValue value)) { return (int)float.Parse(value.ToString()); } return (int)float.Parse(fFProbeStream.GetRotate() ?? "0"); } public static Dictionary<string, bool>? FormatDisposition(Dictionary<string, int>? disposition) { if (disposition == null) { return null; } Dictionary<string, bool> dictionary = new Dictionary<string, bool>(disposition.Count, StringComparer.Ordinal); foreach (KeyValuePair<string, int> item in disposition) { dictionary.Add(item.Key, ToBool(item.Value)); } return dictionary; static bool ToBool(int value) { return value switch { 0 => false, 1 => true, _ => throw new ArgumentOutOfRangeException("value", $"Not expected disposition state value: {value}"), }; } } } public class MediaFormat : ITagsContainer { public TimeSpan Duration { get; set; } public TimeSpan StartTime { get; set; } public string FormatName { get; set; } public string FormatLongName { get; set; } public int StreamCount { get; set; } public double ProbeScore { get; set; } public double BitRate { get; set; } public Dictionary<string, string>? Tags { get; set; } } public abstract class MediaStream : ITagsContainer { public int Index { get; set; } public string CodecName { get; set; } public string CodecLongName { get; set; } public string CodecTagString { get; set; } public string CodecTag { get; set; } public long BitRate { get; set; } public TimeSpan StartTime { get; set; } public TimeSpan Duration { get; set; } public string? Language { get; set; } public Dictionary<string, bool>? Disposition { get; set; } public int? BitDepth { get; set; } public Dictionary<string, string>? Tags { get; set; } public Codec GetCodecInfo() { return FFMpeg.GetCodec(CodecName); } } 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<FFProbePacketAnalysis> Packets { get; set; } } public static class ProcessArgumentsExtensions { public static IProcessResult StartAndWaitForExit(this ProcessArguments processArguments) { IProcessInstance val = processArguments.Start(); try { return val.WaitForExit(); } finally { ((IDisposable)val)?.Dispose(); } } public static async Task<IProcessResult> StartAndWaitForExitAsync(this ProcessArguments processArguments, CancellationToken cancellationToken = default(CancellationToken)) { IProcessInstance instance = processArguments.Start(); try { return await instance.WaitForExitAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } finally { ((IDisposable)instance)?.Dispose(); } } } public class SubtitleStream : MediaStream { } public class VideoStream : MediaStream { public double AvgFrameRate { get; set; } public int BitsPerRawSample { get; set; } public (int Width, int Height) DisplayAspectRatio { get; set; } public (int Width, int Height) SampleAspectRatio { get; set; } public string Profile { get; set; } public int Width { get; set; } public int Height { get; set; } public double FrameRate { get; set; } public string PixelFormat { get; set; } public int Level { get; set; } public int Rotation { get; set; } public double AverageFrameRate { get; set; } public string ColorRange { get; set; } public string ColorSpace { get; set; } public string ColorTransfer { get; set; } public string ColorPrimaries { get; set; } public PixelFormat GetPixelFormatInfo() { return FFMpeg.GetPixelFormat(PixelFormat); } } public static class GlobalFFOptions { private const string ConfigFile = "ffmpeg.config.json"; private static FFOptions? _current; public static FFOptions Current => _current ?? (_current = LoadFFOptions()); public static void Configure(Action<FFOptions> optionsAction) { optionsAction(Current); } public static void Configure(FFOptions ffOptions) { _current = ffOptions ?? throw new ArgumentNullException("ffOptions"); } public static string GetFFMpegBinaryPath(FFOptions? ffOptions = null) { return GetFFBinaryPath("FFMpeg", ffOptions ?? Current); } public static string GetFFProbeBinaryPath(FFOptions? ffOptions = null) { return GetFFBinaryPath("FFProbe", ffOptions ?? Current); } private static string GetFFBinaryPath(string name, FFOptions ffOptions) { string text = name.ToLowerInvariant(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { text += ".exe"; } string path = (Environment.Is64BitProcess ? "x64" : "x86"); foreach (string item in new List<string> { Path.Combine(ffOptions.BinaryFolder, path), ffOptions.BinaryFolder }) { string text2 = Path.Combine(item, text); if (File.Exists(text2)) { return text2; } } return text; } private static FFOptions LoadFFOptions() { if (!File.Exists("ffmpeg.config.json")) { return new FFOptions(); } return JsonSerializer.Deserialize<FFOptions>(File.ReadAllText("ffmpeg.config.json")); } } } namespace FFMpegCore.Helpers { public static class FFMpegHelper { private static bool _ffmpegVerified; public static void ConversionSizeExceptionCheck(IMediaAnalysis info) { ConversionSizeExceptionCheck(info.PrimaryVideoStream.Width, info.PrimaryVideoStream.Height); } public static void ConversionSizeExceptionCheck(int width, int height) { if (height % 2 != 0 || width % 2 != 0) { throw new ArgumentException("FFMpeg yuv420p encoding requires the width and height to be a multiple of 2!"); } } public static void ExtensionExceptionCheck(string filename, string extension) { if (!extension.Equals(Path.GetExtension(filename), StringComparison.OrdinalIgnoreCase)) { throw new FFMpegException(FFMpegExceptionType.File, "Invalid output file. File extension should be '" + extension + "' required."); } } public static void RootExceptionCheck() { if (GlobalFFOptions.Current.BinaryFolder == null) { throw new FFOptionsException("FFMpeg root is not configured in app config. Missing key 'BinaryFolder'."); } } public static void VerifyFFMpegExists(FFOptions ffMpegOptions) { if (!_ffmpegVerified) { _ffmpegVerified = Instance.Finish(GlobalFFOptions.GetFFMpegBinaryPath(ffMpegOptions), "-version", (EventHandler<string>)null, (EventHandler<string>)null).ExitCode == 0; if (!_ffmpegVerified) { throw new FFMpegException(FFMpegExceptionType.Operation, "ffmpeg was not found on your system"); } } } } public static class FFProbeHelper { private static bool _ffprobeVerified; public static void RootExceptionCheck() { if (GlobalFFOptions.Current.BinaryFolder == null) { throw new FFOptionsException("FFProbe root is not configured in app config. Missing key 'BinaryFolder'."); } } public static void VerifyFFProbeExists(FFOptions ffMpegOptions) { if (!_ffprobeVerified) { _ffprobeVerified = Instance.Finish(GlobalFFOptions.GetFFProbeBinaryPath(ffMpegOptions), "-version", (EventHandler<string>)null, (EventHandler<string>)null).ExitCode == 0; if (!_ffprobeVerified) { throw new FFProbeException("ffprobe was not found on your system"); } } } } } namespace FFMpegCore.Pipes { public interface IAudioSample { void Serialize(Stream stream); Task SerializeAsync(Stream stream, CancellationToken token); } public interface IPipeSink { Task ReadAsync(Stream inputStream, CancellationToken cancellationToken); string GetFormat(); } public interface IPipeSource { string GetStreamArguments(); Task WriteAsync(Stream outputStream, CancellationToken cancellationToken); } public interface IVideoFrame { int Width { get; } int Height { get; } string Format { get; } void Serialize(Stream pipe); Task SerializeAsync(Stream pipe, CancellationToken token); } internal static class PipeHelpers { public static string GetUniquePipeName() { return "FFMpegCore_" + Guid.NewGuid().ToString("N").Substring(0, 16); } public static string GetPipePath(string pipeName) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return "\\\\.\\pipe\\" + pipeName; } return "unix:" + Path.Combine(Path.GetTempPath(), "CoreFxPipe_" + pipeName); } } public class RawAudioPipeSource : IPipeSource { private readonly IEnumerator<IAudioSample> _sampleEnumerator; public string Format { get; set; } = "s16le"; public uint SampleRate { get; set; } = 8000u; public uint Channels { get; set; } = 1u; public RawAudioPipeSource(IEnumerator<IAudioSample> sampleEnumerator) { _sampleEnumerator = sampleEnumerator; } public RawAudioPipeSource(IEnumerable<IAudioSample> sampleEnumerator) : this(sampleEnumerator.GetEnumerator()) { } public string GetStreamArguments() { return $"-f {Format} -ar {SampleRate} -ac {Channels}"; } public async Task WriteAsync(Stream outputStream, CancellationToken cancellationToken) { if (_sampleEnumerator.MoveNext() && _sampleEnumerator.Current != null) { await _sampleEnumerator.Current.SerializeAsync(outputStream, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } while (_sampleEnumerator.MoveNext()) { await _sampleEnumerator.Current.SerializeAsync(outputStream, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } } } public class RawVideoPipeSource : IPipeSource { private readonly IEnumerator<IVideoFrame> _framesEnumerator; private bool _formatInitialized; public string StreamFormat { get; private set; } public int Width { get; private set; } public int Height { get; private set; } public double FrameRate { get; set; } = 25.0; public RawVideoPipeSource(IEnumerable<IVideoFrame> framesEnumerator) { _framesEnumerator = framesEnumerator.GetEnumerator(); } public string GetStreamArguments() { if (!_formatInitialized) { if (_framesEnumerator.Current == null && !_framesEnumerator.MoveNext()) { throw new InvalidOperationException("Enumerator is empty, unable to get frame"); } StreamFormat = _framesEnumerator.Current.Format; Width = _framesEnumerator.Current.Width; Height = _framesEnumerator.Current.Height; _formatInitialized = true; } return $"-f rawvideo -r {FrameRate.ToString(CultureInfo.InvariantCulture)} -pix_fmt {StreamFormat} -s {Width}x{Height}"; } public async Task WriteAsync(Stream outputStream, CancellationToken cancellationToken) { if (_framesEnumerator.Current != null) { CheckFrameAndThrow(_framesEnumerator.Current); await _framesEnumerator.Current.SerializeAsync(outputStream, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } while (_framesEnumerator.MoveNext()) { CheckFrameAndThrow(_framesEnumerator.Current); await _framesEnumerator.Current.SerializeAsync(outputStream, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } } private void CheckFrameAndThrow(IVideoFrame frame) { if (frame.Width != Width || frame.Height != Height || frame.Format != StreamFormat) { throw new FFMpegStreamFormatException(FFMpegExceptionType.Operation, "Video frame is not the same format as created raw video stream\r\n" + $"Frame format: {frame.Width}x{frame.Height} pix_fmt: {frame.Format}\r\n" + $"Stream format: {Width}x{Height} pix_fmt: {StreamFormat}"); } } } public class StreamPipeSink : IPipeSink { public Func<Stream, CancellationToken, Task> Writer { get; } public int BlockSize { get; set; } = 4096; public string Format { get; set; } = string.Empty; public StreamPipeSink(Func<Stream, CancellationToken, Task> writer) { Writer = writer; } public StreamPipeSink(Stream destination) { Stream destination2 = destination; base..ctor(); StreamPipeSink streamPipeSink = this; Writer = (Stream inputStream, CancellationToken cancellationToken) => inputStream.CopyToAsync(destination2, streamPipeSink.BlockSize, cancellationToken); } public async Task ReadAsync(Stream inputStream, CancellationToken cancellationToken) { await Writer(inputStream, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } public string GetFormat() { return Format; } } public class StreamPipeSource : IPipeSource { public Stream Source { get; } public int BlockSize { get; } = 4096; public string StreamFormat { get; } = string.Empty; public StreamPipeSource(Stream source) { Source = source; } public string GetStreamArguments() { return StreamFormat; } public Task WriteAsync(Stream outputStream, CancellationToken cancellationToken) { return Source.CopyToAsync(outputStream, BlockSize, cancellationToken); } } } namespace FFMpegCore.Exceptions { public enum FFMpegExceptionType { Conversion, File, Operation, Process } public class FFMpegException : Exception { public FFMpegExceptionType Type { get; } public string FFMpegErrorOutput { get; } public FFMpegException(FFMpegExceptionType type, string message, Exception? innerException = null, string ffMpegErrorOutput = "") : base(message, innerException) { FFMpegErrorOutput = ffMpegErrorOutput; Type = type; } public FFMpegException(FFMpegExceptionType type, string message, string ffMpegErrorOutput = "") : base(message) { FFMpegErrorOutput = ffMpegErrorOutput; Type = type; } public FFMpegException(FFMpegExceptionType type, string message) : base(message) { FFMpegErrorOutput = string.Empty; Type = type; } } public class FFOptionsException : Exception { public FFOptionsException(string message, Exception? innerException = null) : base(message, innerException) { } } public class FFMpegArgumentException : Exception { public FFMpegArgumentException(string? message = null, Exception? innerException = null) : base(message, innerException) { } } public class FFMpegStreamFormatException : FFMpegException { public FFMpegStreamFormatException(FFMpegExceptionType type, string message, Exception? innerException = null) : base(type, message, innerException) { } } public class FFProbeException : Exception { public FFProbeException(string message, Exception? inner = null) : base(message, inner) { } } public class FFProbeProcessException : FFProbeException { public IReadOnlyCollection<string> ProcessErrors { get; } public FFProbeProcessException(string message, IReadOnlyCollection<string> processErrors, Exception? inner = null) : base(message, inner) { ProcessErrors = processErrors; } } public class FormatNullException : FFProbeException { public FormatNullException() : base("Format not specified") { } } } namespace FFMpegCore.Enums { public enum AudioQuality { Ultra = 384, VeryHigh = 256, Good = 192, Normal = 128, BelowNormal = 96, Low = 64 } public enum FeatureStatus { Unknown, NotSupported, Supported } public class Codec { public class FeatureLevel { public bool IsExperimental { get; internal set; } public FeatureStatus SupportsFrameLevelMultithreading { get; internal set; } public FeatureStatus SupportsSliceLevelMultithreading { get; internal set; } public FeatureStatus SupportsDrawHorizBand { get; internal set; } public FeatureStatus SupportsDirectRendering { get; internal set; } internal void Merge(FeatureLevel other) { IsExperimental |= other.IsExperimental; SupportsFrameLevelMultithreading = (FeatureStatus)Math.Max((int)SupportsFrameLevelMultithreading, (int)other.SupportsFrameLevelMultithreading); SupportsSliceLevelMultithreading = (FeatureStatus)Math.Max((int)SupportsSliceLevelMultithreading, (int)other.SupportsSliceLevelMultithreading); SupportsDrawHorizBand = (FeatureStatus)Math.Max((int)SupportsDrawHorizBand, (int)other.SupportsDrawHorizBand); SupportsDirectRendering = (FeatureStatus)Math.Max((int)SupportsDirectRendering, (int)other.SupportsDirectRendering); } } private static readonly Regex _codecsFormatRegex = new Regex("([D\\.])([E\\.])([VASD\\.])([I\\.])([L\\.])([S\\.])\\s+([a-z0-9_-]+)\\s+(.+)"); private static readonly Regex _decodersEncodersFormatRegex = new Regex("([VASD\\.])([F\\.])([S\\.])([X\\.])([B\\.])([D\\.])\\s+([a-z0-9_-]+)\\s+(.+)"); public string Name { get; } public CodecType Type { get; private set; } public bool DecodingSupported { get; private set; } public bool EncodingSupported { get; private set; } public bool IsIntraFrameOnly { get; private set; } public bool IsLossy { get; private set; } public bool IsLossless { get; private set; } public string Description { get; private set; } public FeatureLevel EncoderFeatureLevel { get; } public FeatureLevel DecoderFeatureLevel { get; } internal Codec(string name, CodecType type) { EncoderFeatureLevel = new FeatureLevel(); DecoderFeatureLevel = new FeatureLevel(); Name = name; Type = type; } internal static bool TryParseFromCodecs(string line, out Codec codec) { Match match = _codecsFormatRegex.Match(line); if (!match.Success) { codec = null; return false; } string value = match.Groups[7].Value; CodecType codecType = match.Groups[3].Value switch { "V" => CodecType.Video, "A" => CodecType.Audio, "D" => CodecType.Data, "S" => CodecType.Subtitle, _ => CodecType.Unknown, }; if (codecType == CodecType.Unknown) { codec = null; return false; } codec = new Codec(value, codecType); codec.DecodingSupported = match.Groups[1].Value != "."; codec.EncodingSupported = match.Groups[2].Value != "."; codec.IsIntraFrameOnly = match.Groups[4].Value != "."; codec.IsLossy = match.Groups[5].Value != "."; codec.IsLossless = match.Groups[6].Value != "."; codec.Description = m