mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
Merge branch 'master' into fix/pix_fmt_order
Former-commit-id: ba43feb213
This commit is contained in:
commit
20e2df244d
23 changed files with 747 additions and 133 deletions
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
@ -6,6 +6,7 @@ on:
|
|||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Prepare FFMpeg
|
||||
|
|
|
@ -169,8 +169,8 @@ public void Builder_BuildString_CpuSpeed()
|
|||
[TestMethod]
|
||||
public void Builder_BuildString_ForceFormat()
|
||||
{
|
||||
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").ForceFormat(VideoCodec.LibX264).OutputToFile("output.mp4").Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -f libx264 \"output.mp4\"", str);
|
||||
var str = FFMpegArguments.FromInputFiles(true, "input.mp4").ForceFormat(VideoType.Mp4).OutputToFile("output.mp4").Arguments;
|
||||
Assert.AreEqual("-i \"input.mp4\" -f mp4 \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -278,13 +278,13 @@ public void Builder_BuildString_Threads_2()
|
|||
public void Builder_BuildString_Codec()
|
||||
{
|
||||
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]
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -305,5 +305,13 @@ public void Builder_BuildString_Raw()
|
|||
str = FFMpegArguments.FromInputFiles(true, "input.mp4").WithCustomArgument("-acodec copy").OutputToFile("output.mp4").Arguments;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
44
FFMpegCore.Test/PixelFormatTests.cs
Normal file
44
FFMpegCore.Test/PixelFormatTests.cs
Normal 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"));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 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)
|
||||
{
|
||||
return OutputLocation(file, type, "_audio");
|
||||
return OutputLocation(file, type.ToString(), "_audio");
|
||||
}
|
||||
|
||||
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,
|
||||
outputFile = file.Name.Replace(file.Extension, keyword + "." + type.ToString().ToLowerInvariant());
|
||||
outputFile = file.Name.Replace(file.Extension, keyword + "." + type.ToLowerInvariant());
|
||||
|
||||
return $"{originalLocation}{Path.DirectorySeparatorChar}{outputFile}";
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace FFMpegCore.Test
|
|||
[TestClass]
|
||||
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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -178,7 +178,7 @@ public void Convert(VideoType type, params IArgument[] inputArguments)
|
|||
|
||||
Assert.IsTrue(File.Exists(output));
|
||||
Assert.AreEqual(outputVideo.Duration.TotalSeconds, input.Duration.TotalSeconds, 0.1);
|
||||
|
||||
validationMethod?.Invoke(outputVideo);
|
||||
if (scaling?.Size == null)
|
||||
{
|
||||
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, System.Drawing.Imaging.PixelFormat fmt, params IArgument[] inputArguments)
|
||||
{
|
||||
var output = Input.OutputLocation(type);
|
||||
|
||||
|
@ -261,6 +266,13 @@ public void Video_ToMP4()
|
|||
Convert(VideoType.Mp4);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Video_ToMP4_YUV444p()
|
||||
{
|
||||
Convert(VideoType.Mp4, (a) => Assert.IsTrue(a.VideoStreams.First().PixelFormat == "yuv444p"),
|
||||
new ForcePixelFormat("yuv444p"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Video_ToMP4_Args()
|
||||
{
|
||||
|
@ -268,10 +280,10 @@ public void Video_ToMP4_Args()
|
|||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(PixelFormat.Format24bppRgb)]
|
||||
[DataRow(PixelFormat.Format32bppArgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
||||
public void Video_ToMP4_Args_Pipe(PixelFormat pixelFormat)
|
||||
public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||
{
|
||||
ConvertFromPipe(VideoType.Mp4, pixelFormat, new VideoCodecArgument(VideoCodec.LibX264));
|
||||
}
|
||||
|
@ -342,16 +354,16 @@ public void Video_ToTS_Args()
|
|||
Convert(VideoType.Ts,
|
||||
new CopyArgument(),
|
||||
new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB),
|
||||
new ForceFormatArgument(VideoCodec.MpegTs));
|
||||
new ForceFormatArgument(VideoType.MpegTs));
|
||||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(PixelFormat.Format24bppRgb)]
|
||||
[DataRow(PixelFormat.Format32bppArgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
||||
public void Video_ToTS_Args_Pipe(PixelFormat pixelFormat)
|
||||
public void Video_ToTS_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||
{
|
||||
ConvertFromPipe(VideoType.Ts, pixelFormat, new ForceFormatArgument(VideoCodec.MpegTs));
|
||||
ConvertFromPipe(VideoType.Ts, pixelFormat, new ForceFormatArgument(VideoType.Ts));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
@ -367,10 +379,10 @@ public void Video_ToOGV_Resize_Args()
|
|||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(PixelFormat.Format24bppRgb)]
|
||||
[DataRow(PixelFormat.Format32bppArgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
||||
public void Video_ToOGV_Resize_Args_Pipe(PixelFormat pixelFormat)
|
||||
public void Video_ToOGV_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||
{
|
||||
ConvertFromPipe(VideoType.Ogv, pixelFormat, new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora));
|
||||
}
|
||||
|
@ -388,10 +400,10 @@ public void Video_ToMP4_Resize_Args()
|
|||
}
|
||||
|
||||
[DataTestMethod]
|
||||
[DataRow(PixelFormat.Format24bppRgb)]
|
||||
[DataRow(PixelFormat.Format32bppArgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
||||
public void Video_ToMP4_Resize_Args_Pipe(PixelFormat pixelFormat)
|
||||
public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||
{
|
||||
ConvertFromPipe(VideoType.Mp4, pixelFormat, new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264));
|
||||
}
|
||||
|
@ -466,7 +478,7 @@ public void Video_Snapshot_PersistSnapshot()
|
|||
public void Video_Join()
|
||||
{
|
||||
var output = Input.OutputLocation(VideoType.Mp4);
|
||||
var newInput = Input.OutputLocation(VideoType.Mp4, "duplicate");
|
||||
var newInput = Input.OutputLocation(VideoType.Mp4.Name, "duplicate");
|
||||
try
|
||||
{
|
||||
var input = FFProbe.Analyse(Input.FullName);
|
||||
|
@ -613,7 +625,7 @@ public void Video_TranscodeInMemory()
|
|||
{
|
||||
using var resStream = new MemoryStream();
|
||||
var reader = new StreamPipeDataReader(resStream);
|
||||
var writer = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, PixelFormat.Format24bppRgb, 128, 128));
|
||||
var writer = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, System.Drawing.Imaging.PixelFormat.Format24bppRgb, 128, 128));
|
||||
|
||||
FFMpegArguments
|
||||
.FromPipe(writer)
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
namespace FFMpegCore.Enums
|
||||
{
|
||||
public enum VideoType
|
||||
{
|
||||
Mp4,
|
||||
Ogv,
|
||||
Ts,
|
||||
WebM
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
|
@ -7,8 +8,17 @@ namespace FFMpegCore.Arguments
|
|||
/// </summary>
|
||||
public class AudioCodecArgument : IArgument
|
||||
{
|
||||
public readonly AudioCodec AudioCodec;
|
||||
public AudioCodecArgument(AudioCodec audioCodec)
|
||||
public readonly string 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;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,10 @@ public ForceFormatArgument(string format)
|
|||
_format = format;
|
||||
}
|
||||
|
||||
public ForceFormatArgument(VideoCodec value) : this(value.ToString().ToLowerInvariant()) { }
|
||||
public ForceFormatArgument(ContainerFormat format)
|
||||
{
|
||||
_format = format.Name;
|
||||
}
|
||||
|
||||
public string Text => $"-f {_format}";
|
||||
}
|
||||
|
|
17
FFMpegCore/FFMpeg/Arguments/ForcePixelFormat.cs
Normal file
17
FFMpegCore/FFMpeg/Arguments/ForcePixelFormat.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
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) { }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
|
@ -14,8 +15,14 @@ public VideoCodecArgument(string 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}";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,34 +1,154 @@
|
|||
namespace FFMpegCore.Enums
|
||||
using FFMpegCore.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace FFMpegCore.Enums
|
||||
{
|
||||
public enum VideoCodec
|
||||
public enum FeatureStatus
|
||||
{
|
||||
LibX264,
|
||||
LibVpx,
|
||||
LibTheora,
|
||||
Png,
|
||||
MpegTs
|
||||
Unknown,
|
||||
NotSupported,
|
||||
Supported,
|
||||
}
|
||||
|
||||
public enum AudioCodec
|
||||
public class Codec
|
||||
{
|
||||
Aac,
|
||||
LibVorbis,
|
||||
LibFdk_Aac,
|
||||
Ac3,
|
||||
Eac3,
|
||||
LibMp3Lame
|
||||
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 enum Filter
|
||||
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)
|
||||
{
|
||||
H264_Mp4ToAnnexB,
|
||||
Aac_AdtstoAsc
|
||||
EncoderFeatureLevel = new FeatureLevel();
|
||||
DecoderFeatureLevel = new FeatureLevel();
|
||||
Name = name;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public enum Channel
|
||||
internal static bool TryParseFromCodecs(string line, out Codec codec)
|
||||
{
|
||||
Audio,
|
||||
Video,
|
||||
Both
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
47
FFMpegCore/FFMpeg/Enums/ContainerFormat.cs
Normal file
47
FFMpegCore/FFMpeg/Enums/ContainerFormat.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
54
FFMpegCore/FFMpeg/Enums/Enums.cs
Normal file
54
FFMpegCore/FFMpeg/Enums/Enums.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
namespace FFMpegCore.Enums
|
||||
{
|
||||
public enum CodecType
|
||||
{
|
||||
Unknown = 0,
|
||||
Video = 1 << 1,
|
||||
Audio = 1 << 2,
|
||||
Subtitle = 1 << 3,
|
||||
Data = 1 << 4,
|
||||
}
|
||||
|
||||
public static class VideoCodec
|
||||
{
|
||||
public static Codec LibX264 => FFMpeg.GetCodec("libx264");
|
||||
public static Codec LibVpx => FFMpeg.GetCodec("libvpx");
|
||||
public static Codec LibTheora => FFMpeg.GetCodec("libtheora");
|
||||
public static Codec Png => FFMpeg.GetCodec("png");
|
||||
public static Codec MpegTs => FFMpeg.GetCodec("mpegts");
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
H264_Mp4ToAnnexB,
|
||||
Aac_AdtstoAsc
|
||||
}
|
||||
|
||||
public enum Channel
|
||||
{
|
||||
Audio,
|
||||
Video,
|
||||
Both
|
||||
}
|
||||
}
|
26
FFMpegCore/FFMpeg/Enums/FileExtension.cs
Normal file
26
FFMpegCore/FFMpeg/Enums/FileExtension.cs
Normal 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";
|
||||
}
|
||||
}
|
56
FFMpegCore/FFMpeg/Enums/PixelFormat.cs
Normal file
56
FFMpegCore/FFMpeg/Enums/PixelFormat.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace FFMpegCore.Enums
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
using FFMpegCore.Helpers;
|
||||
|
||||
namespace FFMpegCore
|
||||
|
@ -90,13 +91,13 @@ public static Bitmap Snapshot(MediaAnalysis source, string output, Size? size =
|
|||
public static bool Convert(
|
||||
MediaAnalysis source,
|
||||
string output,
|
||||
VideoType type = VideoType.Mp4,
|
||||
ContainerFormat format,
|
||||
Speed speed = Speed.SuperFast,
|
||||
VideoSize size = VideoSize.Original,
|
||||
AudioQuality audioQuality = AudioQuality.Normal,
|
||||
bool multithreaded = false)
|
||||
{
|
||||
FFMpegHelper.ExtensionExceptionCheck(output, type.Extension());
|
||||
FFMpegHelper.ExtensionExceptionCheck(output, format.Extension);
|
||||
FFMpegHelper.ConversionSizeExceptionCheck(source);
|
||||
|
||||
var scale = VideoSize.Original == size ? 1 : (double)source.PrimaryVideoStream.Height / (int)size;
|
||||
|
@ -105,9 +106,9 @@ public static bool Convert(
|
|||
if (outputSize.Width % 2 != 0)
|
||||
outputSize.Width += 1;
|
||||
|
||||
return type switch
|
||||
return format.Name switch
|
||||
{
|
||||
VideoType.Mp4 => FFMpegArguments
|
||||
"mp4" => FFMpegArguments
|
||||
.FromInputFiles(true, source.Path)
|
||||
.UsingMultithreading(multithreaded)
|
||||
.WithVideoCodec(VideoCodec.LibX264)
|
||||
|
@ -118,7 +119,7 @@ public static bool Convert(
|
|||
.WithAudioBitrate(audioQuality)
|
||||
.OutputToFile(output)
|
||||
.ProcessSynchronously(),
|
||||
VideoType.Ogv => FFMpegArguments
|
||||
"ogv" => FFMpegArguments
|
||||
.FromInputFiles(true, source.Path)
|
||||
.UsingMultithreading(multithreaded)
|
||||
.WithVideoCodec(VideoCodec.LibTheora)
|
||||
|
@ -129,14 +130,14 @@ public static bool Convert(
|
|||
.WithAudioBitrate(audioQuality)
|
||||
.OutputToFile(output)
|
||||
.ProcessSynchronously(),
|
||||
VideoType.Ts => FFMpegArguments
|
||||
"mpegts" => FFMpegArguments
|
||||
.FromInputFiles(true, source.Path)
|
||||
.CopyChannel()
|
||||
.WithBitStreamFilter(Channel.Video, Filter.H264_Mp4ToAnnexB)
|
||||
.ForceFormat(VideoCodec.MpegTs)
|
||||
.ForceFormat(VideoType.Ts)
|
||||
.OutputToFile(output)
|
||||
.ProcessSynchronously(),
|
||||
VideoType.WebM => FFMpegArguments
|
||||
"webm" => FFMpegArguments
|
||||
.FromInputFiles(true, source.Path)
|
||||
.UsingMultithreading(multithreaded)
|
||||
.WithVideoCodec(VideoCodec.LibVpx)
|
||||
|
@ -147,7 +148,7 @@ public static bool Convert(
|
|||
.WithAudioBitrate(audioQuality)
|
||||
.OutputToFile(output)
|
||||
.ProcessSynchronously(),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format))
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -323,6 +324,179 @@ public static bool ReplaceAudio(string input, string inputAudio, string output,
|
|||
.ProcessSynchronously();
|
||||
}
|
||||
|
||||
#region PixelFormats
|
||||
internal static IReadOnlyList<Enums.PixelFormat> GetPixelFormatsInternal()
|
||||
{
|
||||
FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
|
||||
|
||||
var list = new List<Enums.PixelFormat>();
|
||||
using var instance = new Instances.Instance(FFMpegOptions.Options.FFmpegBinary, "-pix_fmts");
|
||||
instance.DataReceived += (e, args) =>
|
||||
{
|
||||
if (Enums.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<Enums.PixelFormat> GetPixelFormats()
|
||||
{
|
||||
if (!FFMpegOptions.Options.UseCache)
|
||||
return GetPixelFormatsInternal();
|
||||
return FFMpegCache.PixelFormats.Values.ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public static bool TryGetPixelFormat(string name, out Enums.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 Enums.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)
|
||||
{
|
||||
foreach (var path in pathList)
|
||||
|
|
|
@ -35,7 +35,8 @@ private FFMpegArguments(IInputArgument inputArgument)
|
|||
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(int bitrate) => WithArgument(new AudioBitrateArgument(bitrate));
|
||||
public FFMpegArguments WithAudioSamplingRate(int samplingRate = 48000) => WithArgument(new AudioSamplingRateArgument(samplingRate));
|
||||
|
@ -61,7 +62,7 @@ private FFMpegArguments(IInputArgument inputArgument)
|
|||
public FFMpegArguments UsingMultithreading(bool multithread) => WithArgument(new ThreadsArgument(multithread));
|
||||
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 WithVideoBitrate(int bitrate) => WithArgument(new VideoBitrateArgument(bitrate));
|
||||
public FFMpegArguments WithFramerate(double framerate) => WithArgument(new FrameRateArgument(framerate));
|
||||
|
@ -77,8 +78,11 @@ private FFMpegArguments(IInputArgument inputArgument)
|
|||
public FFMpegArguments OverwriteExisting() => WithArgument(new OverwriteArgument());
|
||||
public FFMpegArguments WithVerbosityLevel(VerbosityLevel verbosityLevel = VerbosityLevel.Error) => WithArgument(new VerbosityLevelArgument(verbosityLevel));
|
||||
|
||||
public FFMpegArguments ForceFormat(VideoCodec videoCodec) => WithArgument(new ForceFormatArgument(videoCodec));
|
||||
public FFMpegArguments ForceFormat(string videoCodec) => WithArgument(new ForceFormatArgument(videoCodec));
|
||||
public FFMpegArguments ForceFormat(ContainerFormat format) => WithArgument(new ForceFormatArgument(format));
|
||||
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 FFMpegArgumentProcessor OutputToFile(string file, bool overwrite = false) => ToProcessor(new OutputArgument(file, overwrite));
|
||||
|
|
54
FFMpegCore/FFMpeg/FFMpegCache.cs
Normal file
54
FFMpegCore/FFMpeg/FFMpegCache.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using FFMpegCore.Enums;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
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 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();
|
||||
|
||||
|
@ -25,7 +30,11 @@ public static void Configure(FFMpegOptions options)
|
|||
static FFMpegOptions()
|
||||
{
|
||||
if (File.Exists(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;
|
||||
|
@ -34,6 +43,10 @@ static FFMpegOptions()
|
|||
|
||||
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)
|
||||
{
|
||||
var ffName = name.ToLowerInvariant();
|
||||
|
|
|
@ -32,4 +32,8 @@
|
|||
<PackageReference Include="System.Text.Json" Version="4.7.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="FFMpeg\Models\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using FFMpegCore.Enums;
|
||||
using System;
|
||||
|
||||
namespace FFMpegCore
|
||||
{
|
||||
|
@ -9,5 +10,7 @@ public class MediaStream
|
|||
public string CodecLongName { get; internal set; } = null!;
|
||||
public int BitRate { get; internal set; }
|
||||
public TimeSpan Duration { get; internal set; }
|
||||
|
||||
public Codec GetCodecInfo() => FFMpeg.GetCodec(CodecName);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
namespace FFMpegCore
|
||||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore
|
||||
{
|
||||
public class VideoStream : MediaStream
|
||||
{
|
||||
|
@ -10,5 +12,7 @@ public class VideoStream : MediaStream
|
|||
public int Height { get; internal set; }
|
||||
public double FrameRate { get; internal set; }
|
||||
public string PixelFormat { get; internal set; } = null!;
|
||||
|
||||
public PixelFormat GetPixelFormatInfo() => FFMpeg.GetPixelFormat(PixelFormat);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue