Added Codec, PixelFormat and ContainerFormat classes

Former-commit-id: bbc9305e2b
This commit is contained in:
Максим Багрянцев 2020-05-12 17:44:14 +03:00
parent 684f2f6c08
commit 6416cea390
22 changed files with 729 additions and 107 deletions

View file

@ -169,8 +169,8 @@ public void Builder_BuildString_CpuSpeed()
[TestMethod] [TestMethod]
public void Builder_BuildString_ForceFormat() public void Builder_BuildString_ForceFormat()
{ {
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").ForceFormat(VideoCodec.LibX264).OutputToFile("output.mp4").Arguments; var str = FFMpegArguments.FromInputFiles(true, "input.mp4").ForceFormat(VideoType.Mp4).OutputToFile("output.mp4").Arguments;
Assert.AreEqual("-i \"input.mp4\" -f libx264 \"output.mp4\"", str); Assert.AreEqual("-i \"input.mp4\" -f mp4 \"output.mp4\"", str);
} }
[TestMethod] [TestMethod]
@ -278,13 +278,13 @@ public void Builder_BuildString_Threads_2()
public void Builder_BuildString_Codec() public void Builder_BuildString_Codec()
{ {
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithVideoCodec(VideoCodec.LibX264).OutputToFile("output.mp4").Arguments; var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithVideoCodec(VideoCodec.LibX264).OutputToFile("output.mp4").Arguments;
Assert.AreEqual("-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\"", str); Assert.AreEqual("-i \"input.mp4\" -c:v libx264 \"output.mp4\"", str);
} }
[TestMethod] [TestMethod]
public void Builder_BuildString_Codec_Override() public void Builder_BuildString_Codec_Override()
{ {
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithVideoCodec(VideoCodec.LibX264).OutputToFile("output.mp4", true).Arguments; var str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithVideoCodec(VideoCodec.LibX264).ForcePixelFormat("yuv420p").OutputToFile("output.mp4", true).Arguments;
Assert.AreEqual("-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\" -y", str); Assert.AreEqual("-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\" -y", str);
} }
@ -305,5 +305,13 @@ public void Builder_BuildString_Raw()
str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithCustomArgument("-acodec copy").OutputToFile("output.mp4").Arguments; str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithCustomArgument("-acodec copy").OutputToFile("output.mp4").Arguments;
Assert.AreEqual("-i \"input.mp4\" -acodec copy \"output.mp4\"", str); Assert.AreEqual("-i \"input.mp4\" -acodec copy \"output.mp4\"", str);
} }
[TestMethod]
public void Builder_BuildString_ForcePixelFormat()
{
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").ForcePixelFormat("yuv444p").OutputToFile("output.mp4").Arguments;
Assert.AreEqual("-i \"input.mp4\" -pix_fmt yuv444p \"output.mp4\"", str);
}
} }
} }

View file

@ -0,0 +1,44 @@
using FFMpegCore.Exceptions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
namespace FFMpegCore.Test
{
[TestClass]
public class PixelFormatTests
{
[TestMethod]
public void PixelFormats_Enumerate()
{
var formats = FFMpeg.GetPixelFormats();
Assert.IsTrue(formats.Count > 0);
}
[TestMethod]
public void PixelFormats_TryGetExisting()
{
Assert.IsTrue(FFMpeg.TryGetPixelFormat("yuv420p", out _));
}
[TestMethod]
public void PixelFormats_TryGetNotExisting()
{
Assert.IsFalse(FFMpeg.TryGetPixelFormat("yuv420pppUnknown", out _));
}
[TestMethod]
public void PixelFormats_GetExisting()
{
var fmt = FFMpeg.GetPixelFormat("yuv420p");
Assert.IsTrue(fmt.Components == 3 && fmt.BitsPerPixel == 12);
}
[TestMethod]
public void PixelFormats_GetNotExisting()
{
Assert.ThrowsException<FFMpegException>(() => FFMpeg.GetPixelFormat("yuv420pppUnknown"));
}
}
}

View file

@ -25,25 +25,25 @@ public static class VideoLibrary
public static readonly FileInfo ImageDirectory = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images"); public static readonly FileInfo ImageDirectory = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images");
public static readonly FileInfo ImageJoinOutput = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images{Path.DirectorySeparatorChar}output.mp4"); public static readonly FileInfo ImageJoinOutput = new FileInfo($".{Path.DirectorySeparatorChar}Resources{Path.DirectorySeparatorChar}images{Path.DirectorySeparatorChar}output.mp4");
public static string OutputLocation(this FileInfo file, VideoType type) public static string OutputLocation(this FileInfo file, ContainerFormat type)
{ {
return OutputLocation(file, type, "_converted"); return OutputLocation(file, type.Extension, "_converted");
} }
public static string OutputLocation(this FileInfo file, AudioType type) public static string OutputLocation(this FileInfo file, AudioType type)
{ {
return OutputLocation(file, type, "_audio"); return OutputLocation(file, type.ToString(), "_audio");
} }
public static string OutputLocation(this FileInfo file, ImageType type) public static string OutputLocation(this FileInfo file, ImageType type)
{ {
return OutputLocation(file, type, "_screenshot"); return OutputLocation(file, type.ToString(), "_screenshot");
} }
public static string OutputLocation(this FileInfo file, Enum type, string keyword) public static string OutputLocation(this FileInfo file, string type, string keyword)
{ {
string originalLocation = file.Directory.FullName, string originalLocation = file.Directory.FullName,
outputFile = file.Name.Replace(file.Extension, keyword + "." + type.ToString().ToLowerInvariant()); outputFile = file.Name.Replace(file.Extension, keyword + "." + type.ToLowerInvariant());
return $"{originalLocation}{Path.DirectorySeparatorChar}{outputFile}"; return $"{originalLocation}{Path.DirectorySeparatorChar}{outputFile}";
} }

View file

@ -15,7 +15,7 @@ namespace FFMpegCore.Test
[TestClass] [TestClass]
public class VideoTest : BaseTest public class VideoTest : BaseTest
{ {
public bool Convert(VideoType type, bool multithreaded = false, VideoSize size = VideoSize.Original) public bool Convert(ContainerFormat type, bool multithreaded = false, VideoSize size = VideoSize.Original)
{ {
var output = Input.OutputLocation(type); var output = Input.OutputLocation(type);
@ -61,7 +61,7 @@ public bool Convert(VideoType type, bool multithreaded = false, VideoSize size =
} }
} }
private void ConvertFromStreamPipe(VideoType type, params IArgument[] inputArguments) private void ConvertFromStreamPipe(ContainerFormat type, params IArgument[] inputArguments)
{ {
var output = Input.OutputLocation(type); var output = Input.OutputLocation(type);
@ -81,7 +81,7 @@ private void ConvertFromStreamPipe(VideoType type, params IArgument[] inputArgum
var success = processor.ProcessSynchronously(); var success = processor.ProcessSynchronously();
var outputVideo = FFProbe.Analyse(output); var outputVideo = FFProbe.Analyse(output);
Assert.IsTrue(success); Assert.IsTrue(success);
Assert.IsTrue(File.Exists(output)); Assert.IsTrue(File.Exists(output));
Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate); Assert.IsTrue(Math.Abs((outputVideo.Duration - input.Duration).TotalMilliseconds) < 1000.0 / input.PrimaryVideoStream.FrameRate);
@ -157,7 +157,7 @@ private void ConvertToStreamPipe(params IArgument[] inputArguments)
} }
} }
public void Convert(VideoType type, params IArgument[] inputArguments) public void Convert(ContainerFormat type, Action<MediaAnalysis> validationMethod, params IArgument[] inputArguments)
{ {
var output = Input.OutputLocation(type); var output = Input.OutputLocation(type);
@ -178,7 +178,7 @@ public void Convert(VideoType type, params IArgument[] inputArguments)
Assert.IsTrue(File.Exists(output)); Assert.IsTrue(File.Exists(output));
Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1); Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1);
validationMethod?.Invoke(outputVideo);
if (scaling?.Size == null) if (scaling?.Size == null)
{ {
Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width); Assert.AreEqual(outputVideo.PrimaryVideoStream.Width, input.PrimaryVideoStream.Width);
@ -207,7 +207,12 @@ public void Convert(VideoType type, params IArgument[] inputArguments)
} }
} }
public void ConvertFromPipe(VideoType type, PixelFormat fmt, params IArgument[] inputArguments) public void Convert(ContainerFormat type, params IArgument[] inputArguments)
{
Convert(type, null, inputArguments);
}
public void ConvertFromPipe(ContainerFormat type, PixelFormat fmt, params IArgument[] inputArguments)
{ {
var output = Input.OutputLocation(type); var output = Input.OutputLocation(type);
@ -218,7 +223,7 @@ public void ConvertFromPipe(VideoType type, PixelFormat fmt, params IArgument[]
foreach (var arg in inputArguments) foreach (var arg in inputArguments)
arguments.WithArgument(arg); arguments.WithArgument(arg);
var processor = arguments.OutputToFile(output); var processor = arguments.OutputToFile(output);
var scaling = arguments.Find<ScaleArgument>(); var scaling = arguments.Find<ScaleArgument>();
processor.ProcessSynchronously(); processor.ProcessSynchronously();
@ -261,6 +266,13 @@ public void Video_ToMP4()
Convert(VideoType.Mp4); Convert(VideoType.Mp4);
} }
[TestMethod]
public void Video_ToMP4_YUV444p()
{
Convert(VideoType.Mp4, (a) => Assert.IsTrue(a.VideoStreams.First().PixelFormat == "yuv444p"),
new ForcePixelFormat("yuv444p"));
}
[TestMethod] [TestMethod]
public void Video_ToMP4_Args() public void Video_ToMP4_Args()
{ {
@ -339,10 +351,10 @@ public void Video_ToTS()
[TestMethod] [TestMethod]
public void Video_ToTS_Args() public void Video_ToTS_Args()
{ {
Convert(VideoType.Ts, Convert(VideoType.Ts,
new CopyArgument(), new CopyArgument(),
new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB), new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB),
new ForceFormatArgument(VideoCodec.MpegTs)); new ForceFormatArgument(VideoType.MpegTs));
} }
[DataTestMethod] [DataTestMethod]
@ -351,7 +363,7 @@ public void Video_ToTS_Args()
// [DataRow(PixelFormat.Format48bppRgb)] // [DataRow(PixelFormat.Format48bppRgb)]
public void Video_ToTS_Args_Pipe(PixelFormat pixelFormat) public void Video_ToTS_Args_Pipe(PixelFormat pixelFormat)
{ {
ConvertFromPipe(VideoType.Ts, pixelFormat, new ForceFormatArgument(VideoCodec.MpegTs)); ConvertFromPipe(VideoType.Ts, pixelFormat, new ForceFormatArgument(VideoType.Ts));
} }
[TestMethod] [TestMethod]
@ -466,7 +478,7 @@ public void Video_Snapshot_PersistSnapshot()
public void Video_Join() public void Video_Join()
{ {
var output = Input.OutputLocation(VideoType.Mp4); var output = Input.OutputLocation(VideoType.Mp4);
var newInput = Input.OutputLocation(VideoType.Mp4, "duplicate"); var newInput = Input.OutputLocation(VideoType.Mp4.Name, "duplicate");
try try
{ {
var input = FFProbe.Analyse(Input.FullName); var input = FFProbe.Analyse(Input.FullName);
@ -475,7 +487,7 @@ public void Video_Join()
var success = FFMpeg.Join(output, input, input2); var success = FFMpeg.Join(output, input, input2);
Assert.IsTrue(success); Assert.IsTrue(success);
Assert.IsTrue(File.Exists(output)); Assert.IsTrue(File.Exists(output));
var expectedDuration = input.Duration * 2; var expectedDuration = input.Duration * 2;
var result = FFProbe.Analyse(output); var result = FFProbe.Analyse(output);
@ -549,7 +561,7 @@ public void Video_Duration()
{ {
var video = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName); var video = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
var output = Input.OutputLocation(VideoType.Mp4); var output = Input.OutputLocation(VideoType.Mp4);
try try
{ {
FFMpegArguments FFMpegArguments
@ -584,7 +596,7 @@ public void Video_UpdatesProgress()
void OnTimeProgess(TimeSpan time) => timeDone = time; void OnTimeProgess(TimeSpan time) => timeDone = time;
var analysis = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName); var analysis = FFProbe.Analyse(VideoLibrary.LocalVideo.FullName);
try try
{ {

View file

@ -1,37 +0,0 @@
using System;
namespace FFMpegCore.Enums
{
public static class FileExtension
{
public static string Extension(this VideoType type)
{
return type switch
{
VideoType.Mp4 => Mp4,
VideoType.Ogv => Ogv,
VideoType.Ts => Ts,
VideoType.WebM => WebM,
_ => throw new Exception("The extension for this video type is not defined.")
};
}
public static string Extension(this VideoCodec type)
{
return type switch
{
VideoCodec.LibX264 => Mp4,
VideoCodec.LibVpx => WebM,
VideoCodec.LibTheora => Ogv,
VideoCodec.MpegTs => Ts,
VideoCodec.Png => Png,
_ => throw new Exception("The extension for this video type is not defined.")
};
}
public static readonly string Mp4 = ".mp4";
public static readonly string Mp3 = ".mp3";
public static readonly string Ts = ".ts";
public static readonly string Ogv = ".ogv";
public static readonly string Png = ".png";
public static readonly string WebM = ".webm";
}
}

View file

@ -1,10 +0,0 @@
namespace FFMpegCore.Enums
{
public enum VideoType
{
Mp4,
Ogv,
Ts,
WebM
}
}

View file

@ -1,4 +1,5 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
@ -7,8 +8,17 @@ namespace FFMpegCore.Arguments
/// </summary> /// </summary>
public class AudioCodecArgument : IArgument public class AudioCodecArgument : IArgument
{ {
public readonly AudioCodec AudioCodec; public readonly string AudioCodec;
public AudioCodecArgument(AudioCodec audioCodec)
public AudioCodecArgument(Codec audioCodec)
{
if (audioCodec.Type != CodecType.Audio)
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{audioCodec.Name}\" is not an audio codec");
AudioCodec = audioCodec.Name;
}
public AudioCodecArgument(string audioCodec)
{ {
AudioCodec = audioCodec; AudioCodec = audioCodec;
} }

View file

@ -13,7 +13,10 @@ public ForceFormatArgument(string format)
_format = format; _format = format;
} }
public ForceFormatArgument(VideoCodec value) : this(value.ToString().ToLowerInvariant()) { } public ForceFormatArgument(ContainerFormat format)
{
_format = format.Name;
}
public string Text => $"-f {_format}"; public string Text => $"-f {_format}";
} }

View file

@ -0,0 +1,20 @@
using FFMpegCore.Models;
using System;
using System.Collections.Generic;
using System.Text;
namespace FFMpegCore.Arguments
{
public class ForcePixelFormat : IArgument
{
public string PixelFormat { get; private set; }
public string Text => $"-pix_fmt {PixelFormat}";
public ForcePixelFormat(string format)
{
PixelFormat = format;
}
public ForcePixelFormat(PixelFormat format) : this(format.Name) { }
}
}

View file

@ -1,4 +1,5 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
@ -14,8 +15,14 @@ public VideoCodecArgument(string codec)
Codec = codec; Codec = codec;
} }
public VideoCodecArgument(VideoCodec value) : this(value.ToString().ToLowerInvariant()) { } public VideoCodecArgument(Codec value)
{
if (value.Type != CodecType.Video)
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{value.Name}\" is not a video codec");
public string Text => $"-c:v {Codec} -pix_fmt yuv420p"; Codec = value.Name;
}
public string Text => $"-c:v {Codec}";
} }
} }

View file

@ -1,22 +1,42 @@
namespace FFMpegCore.Enums namespace FFMpegCore.Enums
{ {
public enum VideoCodec public enum CodecType
{ {
LibX264, Unknown = 0,
LibVpx, Video = 1 << 1,
LibTheora, Audio = 1 << 2,
Png, Subtitle = 1 << 3,
MpegTs Data = 1 << 4,
} }
public enum AudioCodec public static class VideoCodec
{ {
Aac, public static Codec LibX264 => FFMpeg.GetCodec("libx264");
LibVorbis, public static Codec LibVpx => FFMpeg.GetCodec("libvpx");
LibFdk_Aac, public static Codec LibTheora => FFMpeg.GetCodec("libtheora");
Ac3, public static Codec Png => FFMpeg.GetCodec("png");
Eac3, public static Codec MpegTs => FFMpeg.GetCodec("mpegts");
LibMp3Lame }
public static class AudioCodec
{
public static Codec Aac => FFMpeg.GetCodec("aac");
public static Codec LibVorbis => FFMpeg.GetCodec("libvorbis");
public static Codec LibFdk_Aac => FFMpeg.GetCodec("libfdk_aac");
public static Codec Ac3 => FFMpeg.GetCodec("ac3");
public static Codec Eac3 => FFMpeg.GetCodec("eac3");
public static Codec LibMp3Lame => FFMpeg.GetCodec("libmp3lame");
}
public static class VideoType
{
public static ContainerFormat MpegTs => FFMpeg.GetContinerFormat("mpegts");
public static ContainerFormat Ts => FFMpeg.GetContinerFormat("mpegts");
public static ContainerFormat Mp4 => FFMpeg.GetContinerFormat("mp4");
public static ContainerFormat Mov => FFMpeg.GetContinerFormat("mov");
public static ContainerFormat Avi => FFMpeg.GetContinerFormat("avi");
public static ContainerFormat Ogv => FFMpeg.GetContinerFormat("ogv");
public static ContainerFormat WebM => FFMpeg.GetContinerFormat("webm");
} }
public enum Filter public enum Filter

View file

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace FFMpegCore.Enums
{
public class ContainerFormat
{
private static readonly Regex _formatRegex = new Regex(@"([D ])([E ])\s+([a-z0-9_]+)\s+(.+)");
public string Name { get; private set; }
public bool DemuxingSupported { get; private set; }
public bool MuxingSupported { get; private set; }
public string Description { get; private set; } = null!;
public string Extension
{
get
{
if (FFMpegOptions.Options.ExtensionOverrides.ContainsKey(Name))
return FFMpegOptions.Options.ExtensionOverrides[Name];
return "." + Name;
}
}
internal ContainerFormat(string name)
{
Name = name;
}
internal static bool TryParse(string line, out ContainerFormat fmt)
{
var match = _formatRegex.Match(line);
if (!match.Success)
{
fmt = null!;
return false;
}
fmt = new ContainerFormat(match.Groups[3].Value);
fmt.DemuxingSupported = match.Groups[1].Value == " ";
fmt.MuxingSupported = match.Groups[2].Value == " ";
fmt.Description = match.Groups[4].Value;
return true;
}
}
}

View file

@ -0,0 +1,26 @@
using System;
namespace FFMpegCore.Enums
{
public static class FileExtension
{
public static string Extension(this Codec type)
{
return type.Name switch
{
"libx264" => Mp4,
"libxvpx" => WebM,
"libxtheora" => Ogv,
"mpegts" => Ts,
"png" => Png,
_ => throw new Exception("The extension for this video type is not defined.")
};
}
public static readonly string Mp4 = ".mp4";
public static readonly string Mp3 = ".mp3";
public static readonly string Ts = ".ts";
public static readonly string Ogv = ".ogv";
public static readonly string Png = ".png";
public static readonly string WebM = ".webm";
}
}

View file

@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace FFMpegCore.Models
{
public class PixelFormat
{
private static readonly Regex _formatRegex = new Regex(@"([I\.])([O\.])([H\.])([P\.])([B\.])\s+(\S+)\s+([0-9]+)\s+([0-9]+)");
public bool InputConversionSupported { get; private set; }
public bool OutputConversionSupported { get; private set; }
public bool HardwareAccelerationSupported { get; private set; }
public bool IsPaletted { get; private set; }
public bool IsBitstream { get; private set; }
public string Name { get; private set; }
public int Components { get; private set; }
public int BitsPerPixel { get; private set; }
public bool CanConvertTo(PixelFormat other)
{
return InputConversionSupported && other.OutputConversionSupported;
}
internal PixelFormat(string name)
{
Name = name;
}
internal static bool TryParse(string line, out PixelFormat fmt)
{
var match = _formatRegex.Match(line);
if (!match.Success)
{
fmt = null!;
return false;
}
fmt = new PixelFormat(match.Groups[6].Value);
fmt.InputConversionSupported = match.Groups[1].Value != ".";
fmt.OutputConversionSupported = match.Groups[2].Value != ".";
fmt.HardwareAccelerationSupported = match.Groups[3].Value != ".";
fmt.IsPaletted = match.Groups[4].Value != ".";
fmt.IsBitstream = match.Groups[5].Value != ".";
if (!int.TryParse(match.Groups[7].Value, out var nbComponents))
return false;
fmt.Components = nbComponents;
if (!int.TryParse(match.Groups[8].Value, out var bpp))
return false;
fmt.BitsPerPixel = bpp;
return true;
}
}
}

View file

@ -0,0 +1,154 @@
using FFMpegCore.Exceptions;
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace FFMpegCore.Enums
{
public enum FeatureStatus
{
Unknown,
NotSupported,
Supported,
}
public class Codec
{
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 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);
}
}
public string Name { get; private set; }
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; } = null!;
public FeatureLevel EncoderFeatureLevel { get; private set; }
public FeatureLevel DecoderFeatureLevel { get; private set; }
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)
{
var match = _codecsFormatRegex.Match(line);
if (!match.Success)
{
codec = null!;
return false;
}
var name = match.Groups[7].Value;
var type = match.Groups[3].Value switch
{
"V" => CodecType.Video,
"A" => CodecType.Audio,
"D" => CodecType.Data,
"S" => CodecType.Subtitle,
_ => CodecType.Unknown
};
if(type == CodecType.Unknown)
{
codec = null!;
return false;
}
codec = new Codec(name, type);
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 = match.Groups[8].Value;
return true;
}
internal static bool TryParseFromEncodersDecoders(string line, out Codec codec, bool isEncoder)
{
var match = _decodersEncodersFormatRegex.Match(line);
if (!match.Success)
{
codec = null!;
return false;
}
var name = match.Groups[7].Value;
var type = match.Groups[1].Value switch
{
"V" => CodecType.Video,
"A" => CodecType.Audio,
"D" => CodecType.Data,
"S" => CodecType.Subtitle,
_ => CodecType.Unknown
};
if (type == CodecType.Unknown)
{
codec = null!;
return false;
}
codec = new Codec(name, type);
var featureLevel = isEncoder ? codec.EncoderFeatureLevel : codec.DecoderFeatureLevel;
codec.DecodingSupported = !isEncoder;
codec.EncodingSupported = isEncoder;
featureLevel.SupportsFrameLevelMultithreading = match.Groups[2].Value != "." ? FeatureStatus.Supported : FeatureStatus.NotSupported;
featureLevel.SupportsSliceLevelMultithreading = match.Groups[3].Value != "." ? FeatureStatus.Supported : FeatureStatus.NotSupported;
featureLevel.IsExperimental = match.Groups[4].Value != ".";
featureLevel.SupportsDrawHorizBand = match.Groups[5].Value != "." ? FeatureStatus.Supported : FeatureStatus.NotSupported;
featureLevel.SupportsDirectRendering = match.Groups[6].Value != "." ? FeatureStatus.Supported : FeatureStatus.NotSupported;
codec.Description = match.Groups[8].Value;
return true;
}
internal void Merge(Codec other)
{
if (Name != other.Name)
throw new FFMpegException(FFMpegExceptionType.Operation, "different codecs enable to merge");
Type |= other.Type;
DecodingSupported |= other.DecodingSupported;
EncodingSupported |= other.EncodingSupported;
IsIntraFrameOnly |= other.IsIntraFrameOnly;
IsLossy |= other.IsLossy;
IsLossless |= other.IsLossless;
EncoderFeatureLevel.Merge(other.EncoderFeatureLevel);
DecoderFeatureLevel.Merge(other.DecoderFeatureLevel);
if (Description != other.Description)
Description += "\r\n" + other.Description;
}
}
}

View file

@ -5,7 +5,9 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using FFMpegCore.Enums; using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
using FFMpegCore.Helpers; using FFMpegCore.Helpers;
using FFMpegCore.Models;
namespace FFMpegCore namespace FFMpegCore
{ {
@ -57,8 +59,8 @@ public static Bitmap Snapshot(MediaAnalysis source, string output, Size? size =
.Seek(captureTime) .Seek(captureTime)
.OutputToFile(output) .OutputToFile(output)
.ProcessSynchronously(); .ProcessSynchronously();
if (!success) if (!success)
throw new OperationCanceledException("Could not take snapshot!"); throw new OperationCanceledException("Could not take snapshot!");
@ -90,13 +92,13 @@ public static Bitmap Snapshot(MediaAnalysis source, string output, Size? size =
public static bool Convert( public static bool Convert(
MediaAnalysis source, MediaAnalysis source,
string output, string output,
VideoType type = VideoType.Mp4, ContainerFormat format,
Speed speed = Speed.SuperFast, Speed speed = Speed.SuperFast,
VideoSize size = VideoSize.Original, VideoSize size = VideoSize.Original,
AudioQuality audioQuality = AudioQuality.Normal, AudioQuality audioQuality = AudioQuality.Normal,
bool multithreaded = false) bool multithreaded = false)
{ {
FFMpegHelper.ExtensionExceptionCheck(output, type.Extension()); FFMpegHelper.ExtensionExceptionCheck(output, format.Extension);
FFMpegHelper.ConversionSizeExceptionCheck(source); FFMpegHelper.ConversionSizeExceptionCheck(source);
var scale = VideoSize.Original == size ? 1 : (double)source.PrimaryVideoStream.Height / (int)size; var scale = VideoSize.Original == size ? 1 : (double)source.PrimaryVideoStream.Height / (int)size;
@ -105,9 +107,9 @@ public static bool Convert(
if (outputSize.Width % 2 != 0) if (outputSize.Width % 2 != 0)
outputSize.Width += 1; outputSize.Width += 1;
return type switch return format.Name switch
{ {
VideoType.Mp4 => FFMpegArguments "mp4" => FFMpegArguments
.FromInputFiles(true, source.Path) .FromInputFiles(true, source.Path)
.UsingMultithreading(multithreaded) .UsingMultithreading(multithreaded)
.WithVideoCodec(VideoCodec.LibX264) .WithVideoCodec(VideoCodec.LibX264)
@ -118,7 +120,7 @@ public static bool Convert(
.WithAudioBitrate(audioQuality) .WithAudioBitrate(audioQuality)
.OutputToFile(output) .OutputToFile(output)
.ProcessSynchronously(), .ProcessSynchronously(),
VideoType.Ogv => FFMpegArguments "ogv" => FFMpegArguments
.FromInputFiles(true, source.Path) .FromInputFiles(true, source.Path)
.UsingMultithreading(multithreaded) .UsingMultithreading(multithreaded)
.WithVideoCodec(VideoCodec.LibTheora) .WithVideoCodec(VideoCodec.LibTheora)
@ -129,14 +131,14 @@ public static bool Convert(
.WithAudioBitrate(audioQuality) .WithAudioBitrate(audioQuality)
.OutputToFile(output) .OutputToFile(output)
.ProcessSynchronously(), .ProcessSynchronously(),
VideoType.Ts => FFMpegArguments "mpegts" => FFMpegArguments
.FromInputFiles(true, source.Path) .FromInputFiles(true, source.Path)
.CopyChannel() .CopyChannel()
.WithBitStreamFilter(Channel.Video, Filter.H264_Mp4ToAnnexB) .WithBitStreamFilter(Channel.Video, Filter.H264_Mp4ToAnnexB)
.ForceFormat(VideoCodec.MpegTs) .ForceFormat(VideoType.Ts)
.OutputToFile(output) .OutputToFile(output)
.ProcessSynchronously(), .ProcessSynchronously(),
VideoType.WebM => FFMpegArguments "webm" => FFMpegArguments
.FromInputFiles(true, source.Path) .FromInputFiles(true, source.Path)
.UsingMultithreading(multithreaded) .UsingMultithreading(multithreaded)
.WithVideoCodec(VideoCodec.LibVpx) .WithVideoCodec(VideoCodec.LibVpx)
@ -147,7 +149,7 @@ public static bool Convert(
.WithAudioBitrate(audioQuality) .WithAudioBitrate(audioQuality)
.OutputToFile(output) .OutputToFile(output)
.ProcessSynchronously(), .ProcessSynchronously(),
_ => throw new ArgumentOutOfRangeException(nameof(type)) _ => throw new ArgumentOutOfRangeException(nameof(format))
}; };
} }
@ -322,7 +324,180 @@ public static bool ReplaceAudio(string input, string inputAudio, string output,
.OutputToFile(output) .OutputToFile(output)
.ProcessSynchronously(); .ProcessSynchronously();
} }
#region PixelFormats
internal static IReadOnlyList<Models.PixelFormat> GetPixelFormatsInternal()
{
FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
var list = new List<Models.PixelFormat>();
using var instance = new Instances.Instance(FFMpegOptions.Options.FFmpegBinary, "-pix_fmts");
instance.DataReceived += (e, args) =>
{
if (Models.PixelFormat.TryParse(args.Data, out var fmt))
list.Add(fmt);
};
var exitCode = instance.BlockUntilFinished();
if (exitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", instance.OutputData));
return list.AsReadOnly();
}
public static IReadOnlyList<Models.PixelFormat> GetPixelFormats()
{
if (!FFMpegOptions.Options.UseCache)
return GetPixelFormatsInternal();
return FFMpegCache.PixelFormats.Values.ToList().AsReadOnly();
}
public static bool TryGetPixelFormat(string name, out Models.PixelFormat fmt)
{
if (!FFMpegOptions.Options.UseCache)
{
fmt = GetPixelFormatsInternal().FirstOrDefault(x => x.Name == name.ToLowerInvariant().Trim());
return fmt != null;
}
else
return FFMpegCache.PixelFormats.TryGetValue(name, out fmt);
}
public static Models.PixelFormat GetPixelFormat(string name)
{
if (TryGetPixelFormat(name, out var fmt))
return fmt;
throw new FFMpegException(FFMpegExceptionType.Operation, $"Pixel format \"{name}\" not supported");
}
#endregion
#region Codecs
internal static void ParsePartOfCodecs(Dictionary<string, Codec> codecs, string arguments, Func<string, Codec?> parser)
{
FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
using var instance = new Instances.Instance(FFMpegOptions.Options.FFmpegBinary, arguments);
instance.DataReceived += (e, args) =>
{
var codec = parser(args.Data);
if(codec != null)
if (codecs.TryGetValue(codec.Name, out var parentCodec))
parentCodec.Merge(codec);
else
codecs.Add(codec.Name, codec);
};
var exitCode = instance.BlockUntilFinished();
if (exitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", instance.OutputData));
}
internal static Dictionary<string, Codec> GetCodecsInternal()
{
var res = new Dictionary<string, Codec>();
ParsePartOfCodecs(res, "-codecs", (s) =>
{
if (Codec.TryParseFromCodecs(s, out var codec))
return codec;
return null;
});
ParsePartOfCodecs(res, "-encoders", (s) =>
{
if (Codec.TryParseFromEncodersDecoders(s, out var codec, true))
return codec;
return null;
});
ParsePartOfCodecs(res, "-decoders", (s) =>
{
if (Codec.TryParseFromEncodersDecoders(s, out var codec, false))
return codec;
return null;
});
return res;
}
public static IReadOnlyList<Codec> GetCodecs()
{
if (!FFMpegOptions.Options.UseCache)
return GetCodecsInternal().Values.ToList().AsReadOnly();
return FFMpegCache.Codecs.Values.ToList().AsReadOnly();
}
public static IReadOnlyList<Codec> GetCodecs(CodecType type)
{
if (!FFMpegOptions.Options.UseCache)
return GetCodecsInternal().Values.Where(x => x.Type == type).ToList().AsReadOnly();
return FFMpegCache.Codecs.Values.Where(x=>x.Type == type).ToList().AsReadOnly();
}
public static IReadOnlyList<Codec> GetVideoCodecs() => GetCodecs(CodecType.Video);
public static IReadOnlyList<Codec> GetAudioCodecs() => GetCodecs(CodecType.Audio);
public static IReadOnlyList<Codec> GetSubtitleCodecs() => GetCodecs(CodecType.Subtitle);
public static IReadOnlyList<Codec> GetDataCodecs() => GetCodecs(CodecType.Data);
public static bool TryGetCodec(string name, out Codec codec)
{
if (!FFMpegOptions.Options.UseCache)
{
codec = GetCodecsInternal().Values.FirstOrDefault(x => x.Name == name.ToLowerInvariant().Trim());
return codec != null;
}
else
return FFMpegCache.Codecs.TryGetValue(name, out codec);
}
public static Codec GetCodec(string name)
{
if (TryGetCodec(name, out var codec) && codec != null)
return codec;
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{name}\" not supported");
}
#endregion
#region ContainerFormats
internal static IReadOnlyList<ContainerFormat> GetContainersFormatsInternal()
{
FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
var list = new List<ContainerFormat>();
using var instance = new Instances.Instance(FFMpegOptions.Options.FFmpegBinary, "-formats");
instance.DataReceived += (e, args) =>
{
if (ContainerFormat.TryParse(args.Data, out var fmt))
list.Add(fmt);
};
var exitCode = instance.BlockUntilFinished();
if (exitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", instance.OutputData));
return list.AsReadOnly();
}
public static IReadOnlyList<ContainerFormat> GetContainerFormats()
{
if (!FFMpegOptions.Options.UseCache)
return GetContainersFormatsInternal();
return FFMpegCache.ContainerFormats.Values.ToList().AsReadOnly();
}
public static bool TryGetContainerFormat(string name, out ContainerFormat fmt)
{
if (!FFMpegOptions.Options.UseCache)
{
fmt = GetContainersFormatsInternal().FirstOrDefault(x => x.Name == name.ToLowerInvariant().Trim());
return fmt != null;
}
else
return FFMpegCache.ContainerFormats.TryGetValue(name, out fmt);
}
public static ContainerFormat GetContinerFormat(string name)
{
if (TryGetContainerFormat(name, out var fmt))
return fmt;
throw new FFMpegException(FFMpegExceptionType.Operation, $"Container format \"{name}\" not supported");
}
#endregion
private static void Cleanup(IEnumerable<string> pathList) private static void Cleanup(IEnumerable<string> pathList)
{ {
foreach (var path in pathList) foreach (var path in pathList)

View file

@ -7,6 +7,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using FFMpegCore.Arguments; using FFMpegCore.Arguments;
using FFMpegCore.Enums; using FFMpegCore.Enums;
using FFMpegCore.Models;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore namespace FFMpegCore
@ -35,7 +36,8 @@ private FFMpegArguments(IInputArgument inputArgument)
public static FFMpegArguments FromPipe(IPipeDataWriter writer) => new FFMpegArguments(new InputPipeArgument(writer)); public static FFMpegArguments FromPipe(IPipeDataWriter writer) => new FFMpegArguments(new InputPipeArgument(writer));
public FFMpegArguments WithAudioCodec(AudioCodec audioCodec) => WithArgument(new AudioCodecArgument(audioCodec)); public FFMpegArguments WithAudioCodec(Codec audioCodec) => WithArgument(new AudioCodecArgument(audioCodec));
public FFMpegArguments WithAudioCodec(string audioCodec) => WithArgument(new AudioCodecArgument(audioCodec));
public FFMpegArguments WithAudioBitrate(AudioQuality audioQuality) => WithArgument(new AudioBitrateArgument(audioQuality)); public FFMpegArguments WithAudioBitrate(AudioQuality audioQuality) => WithArgument(new AudioBitrateArgument(audioQuality));
public FFMpegArguments WithAudioBitrate(int bitrate) => WithArgument(new AudioBitrateArgument(bitrate)); public FFMpegArguments WithAudioBitrate(int bitrate) => WithArgument(new AudioBitrateArgument(bitrate));
public FFMpegArguments WithAudioSamplingRate(int samplingRate = 48000) => WithArgument(new AudioSamplingRateArgument(samplingRate)); public FFMpegArguments WithAudioSamplingRate(int samplingRate = 48000) => WithArgument(new AudioSamplingRateArgument(samplingRate));
@ -61,7 +63,7 @@ private FFMpegArguments(IInputArgument inputArgument)
public FFMpegArguments UsingMultithreading(bool multithread) => WithArgument(new ThreadsArgument(multithread)); public FFMpegArguments UsingMultithreading(bool multithread) => WithArgument(new ThreadsArgument(multithread));
public FFMpegArguments UsingThreads(int threads) => WithArgument(new ThreadsArgument(threads)); public FFMpegArguments UsingThreads(int threads) => WithArgument(new ThreadsArgument(threads));
public FFMpegArguments WithVideoCodec(VideoCodec videoCodec) => WithArgument(new VideoCodecArgument(videoCodec)); public FFMpegArguments WithVideoCodec(Codec videoCodec) => WithArgument(new VideoCodecArgument(videoCodec));
public FFMpegArguments WithVideoCodec(string videoCodec) => WithArgument(new VideoCodecArgument(videoCodec)); public FFMpegArguments WithVideoCodec(string videoCodec) => WithArgument(new VideoCodecArgument(videoCodec));
public FFMpegArguments WithVideoBitrate(int bitrate) => WithArgument(new VideoBitrateArgument(bitrate)); public FFMpegArguments WithVideoBitrate(int bitrate) => WithArgument(new VideoBitrateArgument(bitrate));
public FFMpegArguments WithFramerate(double framerate) => WithArgument(new FrameRateArgument(framerate)); public FFMpegArguments WithFramerate(double framerate) => WithArgument(new FrameRateArgument(framerate));
@ -77,8 +79,11 @@ private FFMpegArguments(IInputArgument inputArgument)
public FFMpegArguments OverwriteExisting() => WithArgument(new OverwriteArgument()); public FFMpegArguments OverwriteExisting() => WithArgument(new OverwriteArgument());
public FFMpegArguments WithVerbosityLevel(VerbosityLevel verbosityLevel = VerbosityLevel.Error) => WithArgument(new VerbosityLevelArgument(verbosityLevel)); public FFMpegArguments WithVerbosityLevel(VerbosityLevel verbosityLevel = VerbosityLevel.Error) => WithArgument(new VerbosityLevelArgument(verbosityLevel));
public FFMpegArguments ForceFormat(VideoCodec videoCodec) => WithArgument(new ForceFormatArgument(videoCodec)); public FFMpegArguments ForceFormat(ContainerFormat format) => WithArgument(new ForceFormatArgument(format));
public FFMpegArguments ForceFormat(string videoCodec) => WithArgument(new ForceFormatArgument(videoCodec)); public FFMpegArguments ForceFormat(string format) => WithArgument(new ForceFormatArgument(format));
public FFMpegArguments ForcePixelFormat(string pixelFormat) => WithArgument(new ForcePixelFormat(pixelFormat));
public FFMpegArguments ForcePixelFormat(PixelFormat pixelFormat) => WithArgument(new ForcePixelFormat(pixelFormat));
public FFMpegArguments DrawText(DrawTextOptions drawTextOptions) => WithArgument(new DrawTextArgument(drawTextOptions)); public FFMpegArguments DrawText(DrawTextOptions drawTextOptions) => WithArgument(new DrawTextArgument(drawTextOptions));
public FFMpegArgumentProcessor OutputToFile(string file, bool overwrite = false) => ToProcessor(new OutputArgument(file, overwrite)); public FFMpegArgumentProcessor OutputToFile(string file, bool overwrite = false) => ToProcessor(new OutputArgument(file, overwrite));

View file

@ -0,0 +1,57 @@
using FFMpegCore.Enums;
using FFMpegCore.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FFMpegCore
{
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) //First check not thread safe
lock (_syncObject)
if (_pixelFormats == null)//Second check thread safe
_pixelFormats = FFMpeg.GetPixelFormatsInternal().ToDictionary(x => x.Name);
return _pixelFormats;
}
}
public static IReadOnlyDictionary<string, Codec> Codecs
{
get
{
if (_codecs == null) //First check not thread safe
lock (_syncObject)
if (_codecs == null)//Second check thread safe
_codecs = FFMpeg.GetCodecsInternal();
return _codecs;
}
}
public static IReadOnlyDictionary<string, ContainerFormat> ContainerFormats
{
get
{
if (_containers == null) //First check not thread safe
lock (_syncObject)
if (_containers == null)//Second check thread safe
_containers = FFMpeg.GetContainersFormatsInternal().ToDictionary(x => x.Name);
return _containers;
}
}
}
}

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.Json; using System.Text.Json;
@ -9,6 +10,10 @@ public class FFMpegOptions
{ {
private static readonly string ConfigFile = Path.Combine(".", "ffmpeg.config.json"); private static readonly string ConfigFile = Path.Combine(".", "ffmpeg.config.json");
private static readonly string DefaultRoot = Path.Combine(".", "FFMPEG", "bin"); private static readonly string DefaultRoot = Path.Combine(".", "FFMPEG", "bin");
private static readonly Dictionary<string, string> DefaultExtensionsOverrides = new Dictionary<string, string>
{
{ "mpegts", ".ts" },
};
public static FFMpegOptions Options { get; private set; } = new FFMpegOptions(); public static FFMpegOptions Options { get; private set; } = new FFMpegOptions();
@ -25,7 +30,11 @@ public static void Configure(FFMpegOptions options)
static FFMpegOptions() static FFMpegOptions()
{ {
if (File.Exists(ConfigFile)) if (File.Exists(ConfigFile))
{
Options = JsonSerializer.Deserialize<FFMpegOptions>(File.ReadAllText(ConfigFile)); Options = JsonSerializer.Deserialize<FFMpegOptions>(File.ReadAllText(ConfigFile));
foreach (var kv in DefaultExtensionsOverrides)
if (!Options.ExtensionOverrides.ContainsKey(kv.Key)) Options.ExtensionOverrides.Add(kv.Key, kv.Value);
}
} }
public string RootDirectory { get; set; } = DefaultRoot; public string RootDirectory { get; set; } = DefaultRoot;
@ -34,6 +43,10 @@ static FFMpegOptions()
public string FFProbeBinary => FFBinary("FFProbe"); public string FFProbeBinary => FFBinary("FFProbe");
public Dictionary<string, string> ExtensionOverrides { get; private set; } = new Dictionary<string, string>();
public bool UseCache { get; set; } = true;
private static string FFBinary(string name) private static string FFBinary(string name)
{ {
var ffName = name.ToLowerInvariant(); var ffName = name.ToLowerInvariant();

View file

@ -32,4 +32,9 @@
<PackageReference Include="System.Text.Json" Version="4.7.1" /> <PackageReference Include="System.Text.Json" Version="4.7.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Enums\" />
<Folder Include="FFMpeg\Models\" />
</ItemGroup>
</Project> </Project>

View file

@ -1,4 +1,5 @@
using System; using FFMpegCore.Enums;
using System;
namespace FFMpegCore namespace FFMpegCore
{ {
@ -9,5 +10,7 @@ public class MediaStream
public string CodecLongName { get; internal set; } = null!; public string CodecLongName { get; internal set; } = null!;
public int BitRate { get; internal set; } public int BitRate { get; internal set; }
public TimeSpan Duration { get; internal set; } public TimeSpan Duration { get; internal set; }
public Codec GetCodecInfo() => FFMpeg.GetCodec(CodecName);
} }
} }

View file

@ -1,4 +1,6 @@
namespace FFMpegCore using FFMpegCore.Models;
namespace FFMpegCore
{ {
public class VideoStream : MediaStream public class VideoStream : MediaStream
{ {
@ -10,5 +12,7 @@ public class VideoStream : MediaStream
public int Height { get; internal set; } public int Height { get; internal set; }
public double FrameRate { get; internal set; } public double FrameRate { get; internal set; }
public string PixelFormat { get; internal set; } = null!; public string PixelFormat { get; internal set; } = null!;
public PixelFormat GetPixelFormatInfo() => FFMpeg.GetPixelFormat(PixelFormat);
} }
} }