This commit is contained in:
Malte Rosenbjerg 2020-05-12 21:05:00 +02:00
parent 3855215000
commit d95f687e46
47 changed files with 485 additions and 410 deletions

View file

@ -2,7 +2,8 @@
using System;
using FFMpegCore.Arguments;
using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
using FFMpegCore.Models;
using FFMpegCore.Utils;
namespace FFMpegCore.Test
{

View file

@ -1,8 +1,8 @@
using System;
using FFMpegCore.Enums;
using FFMpegCore.Test.Resources;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
using FFMpegCore.Utils;
namespace FFMpegCore.Test
{

View file

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

View file

@ -1,6 +1,5 @@
using System;
using System.IO;
using FFMpegCore.Enums;
using System.IO;
using FFMpegCore.Models;
namespace FFMpegCore.Test.Resources
{

View file

@ -7,8 +7,9 @@
using System.IO;
using System.Linq;
using FFMpegCore.Arguments;
using FFMpegCore.Exceptions;
using FFMpegCore.Models;
using FFMpegCore.Pipes;
using FFMpegCore.Utils;
namespace FFMpegCore.Test
{
@ -127,7 +128,8 @@ private void ConvertToStreamPipe(params IArgument[] inputArguments)
var scaling = arguments.Find<ScaleArgument>();
processor.ProcessSynchronously();
var result = processor.ProcessSynchronously();
Assert.IsTrue(result);
ms.Position = 0;
var outputVideo = FFProbe.Analyse(ms);
@ -157,7 +159,7 @@ private void ConvertToStreamPipe(params IArgument[] inputArguments)
}
}
public void Convert(ContainerFormat type, Action<MediaAnalysis> validationMethod, params IArgument[] inputArguments)
private void Convert(ContainerFormat type, Action<MediaAnalysis> validationMethod, params IArgument[] inputArguments)
{
var output = Input.OutputLocation(type);
@ -207,12 +209,7 @@ public void Convert(ContainerFormat type, Action<MediaAnalysis> validationMethod
}
}
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)
private void ConvertFromPipe(ContainerFormat type, System.Drawing.Imaging.PixelFormat fmt, params IArgument[] inputArguments)
{
var output = Input.OutputLocation(type);
@ -276,7 +273,7 @@ public void Video_ToMP4_YUV444p()
[TestMethod]
public void Video_ToMP4_Args()
{
Convert(VideoType.Mp4, new VideoCodecArgument(VideoCodec.LibX264));
Convert(VideoType.Mp4, null, new VideoCodecArgument(VideoCodec.LibX264));
}
[DataTestMethod]
@ -321,13 +318,14 @@ public void Video_ToMP4_Args_StreamOutputPipe_Async()
{
using var ms = new MemoryStream();
var pipeSource = new StreamPipeDataReader(ms);
FFMpegArguments
var result = FFMpegArguments
.FromInputFiles(VideoLibrary.LocalVideo)
.WithVideoCodec(VideoCodec.LibX264)
.ForceFormat("matroska")
.OutputToPipe(pipeSource)
.ProcessAsynchronously()
.WaitForResult();
Assert.IsTrue(result);
}
[TestMethod]
@ -345,7 +343,7 @@ public void Video_ToTS()
[TestMethod]
public void Video_ToTS_Args()
{
Convert(VideoType.Ts,
Convert(VideoType.Ts, null,
new CopyArgument(),
new BitStreamFilterArgument(Channel.Video, Filter.H264_Mp4ToAnnexB),
new ForceFormatArgument(VideoType.MpegTs));
@ -369,7 +367,7 @@ public void Video_ToOGV_Resize()
[TestMethod]
public void Video_ToOGV_Resize_Args()
{
Convert(VideoType.Ogv, new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora));
Convert(VideoType.Ogv, null, new ScaleArgument(VideoSize.Ed), new VideoCodecArgument(VideoCodec.LibTheora));
}
[DataTestMethod]
@ -390,7 +388,7 @@ public void Video_ToMP4_Resize()
[TestMethod]
public void Video_ToMP4_Resize_Args()
{
Convert(VideoType.Mp4, new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264));
Convert(VideoType.Mp4, null, new ScaleArgument(VideoSize.Ld), new VideoCodecArgument(VideoCodec.LibX264));
}
[DataTestMethod]
@ -547,7 +545,6 @@ public void Video_With_Only_Audio_Should_Extract_Metadata()
Assert.AreEqual(null, video.PrimaryVideoStream);
Assert.AreEqual("aac", video.PrimaryAudioStream.CodecName);
Assert.AreEqual(79.5, video.Duration.TotalSeconds, 0.5);
// Assert.AreEqual(1.25, video.Size);
}
[TestMethod]
@ -621,12 +618,13 @@ public void Video_TranscodeInMemory()
var reader = new StreamPipeDataReader(resStream);
var writer = new RawVideoPipeDataWriter(BitmapSource.CreateBitmaps(128, System.Drawing.Imaging.PixelFormat.Format24bppRgb, 128, 128));
FFMpegArguments
var result = FFMpegArguments
.FromPipe(writer)
.WithVideoCodec("vp9")
.ForceFormat("webm")
.OutputToPipe(reader)
.ProcessSynchronously();
Assert.IsTrue(result);
resStream.Position = 0;
var vi = FFProbe.Analyse(resStream);

View file

@ -1,5 +1,5 @@
using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
using FFMpegCore.Models;
namespace FFMpegCore.Arguments
{

View file

@ -1,5 +1,5 @@
using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
using FFMpegCore.Models;
namespace FFMpegCore.Arguments
{

View file

@ -1,4 +1,4 @@
using FFMpegCore.Enums;
using FFMpegCore.Models;
namespace FFMpegCore.Arguments
{

View file

@ -1,4 +1,4 @@
using FFMpegCore.Enums;
using FFMpegCore.Models;
namespace FFMpegCore.Arguments
{

View file

@ -50,7 +50,7 @@ public interface IArgument
public interface IInputOutputArgument : IArgument
{
void Pre() {}
Task During(CancellationToken? cancellationToken = null) => Task.CompletedTask;
Task During(CancellationToken cancellationToken) => Task.CompletedTask;
void Post() {}
}

View file

@ -1,6 +1,6 @@
using System;
using System.IO;
using FFMpegCore.Exceptions;
using FFMpegCore.Models;
namespace FFMpegCore.Arguments
{
@ -11,11 +11,13 @@ public class OutputArgument : IOutputArgument
{
public readonly string Path;
public readonly bool Overwrite;
public readonly bool VerifyOutputExists;
public OutputArgument(string path, bool overwrite = false)
public OutputArgument(string path, bool overwrite = false, bool verifyOutputExists = true)
{
Path = path;
Overwrite = overwrite;
VerifyOutputExists = verifyOutputExists;
}
public void Pre()
@ -25,7 +27,7 @@ public void Pre()
}
public void Post()
{
if (!File.Exists(Path))
if (VerifyOutputExists && !File.Exists(Path))
throw new FFMpegException(FFMpegExceptionType.File, "Output file was not created");
}

View file

@ -34,16 +34,15 @@ public void Post()
Pipe = null!;
}
public async Task During(CancellationToken? cancellationToken = null)
public async Task During(CancellationToken cancellationToken)
{
try
{
await ProcessDataAsync(cancellationToken ?? CancellationToken.None).ConfigureAwait(false);
await ProcessDataAsync(cancellationToken).ConfigureAwait(false);
}
catch (TaskCanceledException)
{
}
Post();
}
public abstract Task ProcessDataAsync(CancellationToken token);

View file

@ -1,5 +1,5 @@
using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
using FFMpegCore.Models;
namespace FFMpegCore.Arguments
{

View file

@ -0,0 +1,9 @@
namespace FFMpegCore.Enums
{
public enum Channel
{
Audio,
Video,
Both
}
}

View file

@ -0,0 +1,14 @@
using System;
namespace FFMpegCore.Enums
{
[Flags]
public enum CodecType
{
Unknown = 0,
Video = 1 << 1,
Audio = 1 << 2,
Subtitle = 1 << 3,
Data = 1 << 4,
}
}

View file

@ -1,54 +0,0 @@
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
}
}

View file

@ -0,0 +1,9 @@
namespace FFMpegCore.Enums
{
public enum FeatureStatus
{
Unknown,
NotSupported,
Supported,
}
}

View file

@ -0,0 +1,8 @@
namespace FFMpegCore.Enums
{
public enum Filter
{
H264_Mp4ToAnnexB,
Aac_AdtstoAsc
}
}

View file

@ -5,12 +5,13 @@
using System.IO;
using System.Linq;
using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
using FFMpegCore.Helpers;
using FFMpegCore.Models;
using FFMpegCore.Utils;
namespace FFMpegCore
{
public static class FFMpeg
public static partial class FFMpeg
{
/// <summary>
/// Saves a 'png' thumbnail from the input video.
@ -324,178 +325,7 @@ 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)
{

View file

@ -4,8 +4,8 @@
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Exceptions;
using FFMpegCore.Helpers;
using FFMpegCore.Models;
using Instances;
namespace FFMpegCore

View file

@ -7,6 +7,7 @@
using System.Threading.Tasks;
using FFMpegCore.Arguments;
using FFMpegCore.Enums;
using FFMpegCore.Models;
using FFMpegCore.Pipes;
namespace FFMpegCore
@ -85,8 +86,8 @@ private FFMpegArguments(IInputArgument inputArgument)
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(Uri uri, bool overwrite = false) => ToProcessor(new OutputArgument(uri.AbsolutePath, overwrite));
public FFMpegArgumentProcessor OutputToFile(string file, bool overwrite = false, bool verifyOutputExists = true) => ToProcessor(new OutputArgument(file, overwrite, verifyOutputExists));
public FFMpegArgumentProcessor OutputToFile(Uri uri, bool overwrite = false, bool verifyOutputExists = true) => ToProcessor(new OutputArgument(uri.AbsolutePath, overwrite, verifyOutputExists));
public FFMpegArgumentProcessor OutputToPipe(IPipeDataReader reader) => ToProcessor(new OutputPipeArgument(reader));
public FFMpegArguments WithArgument(IArgument argument)
@ -106,7 +107,7 @@ internal void Pre()
_inputArgument.Pre();
_outputArgument.Pre();
}
internal async Task During(CancellationToken? cancellationToken = null)
internal async Task During(CancellationToken cancellationToken)
{
await Task.WhenAll(_inputArgument.During(cancellationToken), _outputArgument.During(cancellationToken)).ConfigureAwait(false);
}

View file

@ -1,6 +1,6 @@
using FFMpegCore.Enums;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using FFMpegCore.Models;
namespace FFMpegCore
{
@ -18,7 +18,7 @@ public static IReadOnlyDictionary<string, PixelFormat> PixelFormats
if (_pixelFormats == null) //First check not thread safe
lock (_syncObject)
if (_pixelFormats == null)//Second check thread safe
_pixelFormats = FFMpeg.GetPixelFormatsInternal().ToDictionary(x => x.Name);
_pixelFormats = FFMpegUtils.GetPixelFormatsInternal().ToDictionary(x => x.Name);
return _pixelFormats;
}
@ -31,7 +31,7 @@ public static IReadOnlyDictionary<string, Codec> Codecs
if (_codecs == null) //First check not thread safe
lock (_syncObject)
if (_codecs == null)//Second check thread safe
_codecs = FFMpeg.GetCodecsInternal();
_codecs = FFMpegUtils.GetCodecsInternal();
return _codecs;
}
@ -44,7 +44,7 @@ public static IReadOnlyDictionary<string, ContainerFormat> ContainerFormats
if (_containers == null) //First check not thread safe
lock (_syncObject)
if (_containers == null)//Second check thread safe
_containers = FFMpeg.GetContainersFormatsInternal().ToDictionary(x => x.Name);
_containers = FFMpegUtils.GetContainersFormatsInternal().ToDictionary(x => x.Name);
return _containers;
}

View file

@ -0,0 +1,189 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FFMpegCore.Enums;
using FFMpegCore.Helpers;
using FFMpegCore.Models;
namespace FFMpegCore
{
public static class FFMpegUtils
{
#region PixelFormats
internal static IReadOnlyList<PixelFormat> GetPixelFormatsInternal()
{
FFMpegHelper.RootExceptionCheck(FFMpegOptions.Options.RootDirectory);
var list = new List<PixelFormat>();
using var instance = new Instances.Instance(FFMpegOptions.Options.FFmpegBinary(), "-pix_fmts");
instance.DataReceived += (e, args) =>
{
if (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<PixelFormat> GetPixelFormats()
{
if (!FFMpegOptions.Options.UseCache)
return GetPixelFormatsInternal();
return FFMpegCache.PixelFormats.Values.ToList().AsReadOnly();
}
public static bool TryGetPixelFormat(string name, out 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 PixelFormat GetPixelFormat(string name)
{
if (TryGetPixelFormat(name, out var fmt))
return fmt;
throw new FFMpegException(FFMpegExceptionType.Operation, $"Pixel format \"{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 GetContainerFormat(string name)
{
if (TryGetContainerFormat(name, out var fmt))
return fmt;
throw new FFMpegException(FFMpegExceptionType.Operation, $"Container 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
}
}

View file

@ -1,22 +1,13 @@
using FFMpegCore.Exceptions;
using System;
using System.Collections.Generic;
using System.Text;
using System;
using System.Text.RegularExpressions;
using FFMpegCore.Enums;
namespace FFMpegCore.Enums
namespace FFMpegCore.Models
{
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+(.+)");
private static readonly Regex CodecsFormatRegex = new Regex(@"([D\.])([E\.])([VASD\.])([I\.])([L\.])([S\.])\s+([a-z0-9_-]+)\s+(.+)", RegexOptions.Compiled);
private static readonly Regex DecodersEncodersFormatRegex = new Regex(@"([VASD\.])([F\.])([S\.])([X\.])([B\.])([D\.])\s+([a-z0-9_-]+)\s+(.+)", RegexOptions.Compiled);
public class FeatureLevel
{
@ -58,7 +49,7 @@ internal Codec(string name, CodecType type)
internal static bool TryParseFromCodecs(string line, out Codec codec)
{
var match = _codecsFormatRegex.Match(line);
var match = CodecsFormatRegex.Match(line);
if (!match.Success)
{
codec = null!;
@ -81,20 +72,21 @@ internal static bool TryParseFromCodecs(string line, out Codec codec)
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;
codec = new Codec(name, type)
{
DecodingSupported = match.Groups[1].Value != ".",
EncodingSupported = match.Groups[2].Value != ".",
IsIntraFrameOnly = match.Groups[4].Value != ".",
IsLossy = match.Groups[5].Value != ".",
IsLossless = match.Groups[6].Value != ".",
Description = match.Groups[8].Value
};
return true;
}
internal static bool TryParseFromEncodersDecoders(string line, out Codec codec, bool isEncoder)
{
var match = _decodersEncodersFormatRegex.Match(line);
var match = DecodersEncodersFormatRegex.Match(line);
if (!match.Success)
{
codec = null!;
@ -135,7 +127,7 @@ internal static bool TryParseFromEncodersDecoders(string line, out Codec codec,
internal void Merge(Codec other)
{
if (Name != other.Name)
throw new FFMpegException(FFMpegExceptionType.Operation, "different codecs enable to merge");
throw new FFMpegException(FFMpegExceptionType.Operation, "different codecs unable to merge");
Type |= other.Type;
DecodingSupported |= other.DecodingSupported;

View file

@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
namespace FFMpegCore.Enums
namespace FFMpegCore.Models
{
public class ContainerFormat
{

View file

@ -1,6 +1,6 @@
using System;
namespace FFMpegCore.Exceptions
namespace FFMpegCore.Models
{
public enum FFMpegExceptionType
{

View file

@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
namespace FFMpegCore.Enums
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]+)");
private static readonly Regex FormatRegex = new Regex(@"([I\.])([O\.])([H\.])([P\.])([B\.])\s+(\S+)\s+([0-9]+)\s+([0-9]+)", RegexOptions.Compiled);
public bool InputConversionSupported { get; private set; }
public bool OutputConversionSupported { get; private set; }
@ -30,7 +27,7 @@ internal PixelFormat(string name)
internal static bool TryParse(string line, out PixelFormat fmt)
{
var match = _formatRegex.Match(line);
var match = FormatRegex.Match(line);
if (!match.Success)
{
fmt = null!;

View file

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Exceptions;
using FFMpegCore.Models;
namespace FFMpegCore.Pipes
{

View file

@ -0,0 +1,14 @@
using FFMpegCore.Models;
namespace FFMpegCore.Utils
{
public static class AudioCodec
{
public static Codec Aac => FFMpegUtils.GetCodec("aac");
public static Codec LibVorbis => FFMpegUtils.GetCodec("libvorbis");
public static Codec LibFdkAac => FFMpegUtils.GetCodec("libfdk_aac");
public static Codec Ac3 => FFMpegUtils.GetCodec("ac3");
public static Codec Eac3 => FFMpegUtils.GetCodec("eac3");
public static Codec LibMp3Lame => FFMpegUtils.GetCodec("libmp3lame");
}
}

View file

@ -1,6 +1,7 @@
using System;
using FFMpegCore.Models;
namespace FFMpegCore.Enums
namespace FFMpegCore.Utils
{
public static class FileExtension
{

View file

@ -0,0 +1,13 @@
using FFMpegCore.Models;
namespace FFMpegCore.Utils
{
public static class VideoCodec
{
public static Codec LibX264 => FFMpegUtils.GetCodec("libx264");
public static Codec LibVpx => FFMpegUtils.GetCodec("libvpx");
public static Codec LibTheora => FFMpegUtils.GetCodec("libtheora");
public static Codec Png => FFMpegUtils.GetCodec("png");
public static Codec MpegTs => FFMpegUtils.GetCodec("mpegts");
}
}

View file

@ -0,0 +1,15 @@
using FFMpegCore.Models;
namespace FFMpegCore.Utils
{
public static class VideoType
{
public static ContainerFormat MpegTs => FFMpegUtils.GetContainerFormat("mpegts");
public static ContainerFormat Ts => FFMpegUtils.GetContainerFormat("mpegts");
public static ContainerFormat Mp4 => FFMpegUtils.GetContainerFormat("mp4");
public static ContainerFormat Mov => FFMpegUtils.GetContainerFormat("mov");
public static ContainerFormat Avi => FFMpegUtils.GetContainerFormat("avi");
public static ContainerFormat Ogv => FFMpegUtils.GetContainerFormat("ogv");
public static ContainerFormat WebM => FFMpegUtils.GetContainerFormat("webm");
}
}

View file

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

View file

@ -1,10 +1,10 @@
using System;
using System.IO;
using System.IO;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Arguments;
using FFMpegCore.Exceptions;
using FFMpegCore.Helpers;
using FFMpegCore.Models;
using FFMpegCore.Pipes;
using Instances;
@ -28,7 +28,7 @@ public static MediaAnalysis Analyse(System.IO.Stream stream, int outputCapacity
var task = instance.FinishedRunning();
try
{
pipeArgument.During().ConfigureAwait(false).GetAwaiter().GetResult();
pipeArgument.During(CancellationToken.None).ConfigureAwait(false).GetAwaiter().GetResult();
}
catch (IOException) { }
finally
@ -57,7 +57,7 @@ public static async Task<MediaAnalysis> AnalyseAsync(System.IO.Stream stream, in
var task = instance.FinishedRunning();
try
{
await pipeArgument.During();
await pipeArgument.During(CancellationToken.None);
}
catch (IOException)
{

View file

@ -8,67 +8,4 @@ public class FFProbeAnalysis
[JsonPropertyName("streams")]
public List<Stream> Streams { get; set; } = null!;
}
public class Stream
{
[JsonPropertyName("index")]
public int Index { get; set; }
[JsonPropertyName("avg_frame_rate")]
public string AvgFrameRate { get; set; } = null!;
[JsonPropertyName("bits_per_raw_sample")]
public string BitsPerRawSample { get; set; } = null!;
[JsonPropertyName("bit_rate")]
public string BitRate { get; set; } = null!;
[JsonPropertyName("channels")]
public int? Channels { get; set; }
[JsonPropertyName("channel_layout")]
public string ChannelLayout { get; set; } = null!;
[JsonPropertyName("codec_type")]
public string CodecType { get; set; } = null!;
[JsonPropertyName("codec_name")]
public string CodecName { get; set; } = null!;
[JsonPropertyName("codec_long_name")]
public string CodecLongName { get; set; } = null!;
[JsonPropertyName("display_aspect_ratio")]
public string DisplayAspectRatio { get; set; } = null!;
[JsonPropertyName("duration")]
public string Duration { get; set; } = null!;
[JsonPropertyName("profile")]
public string Profile { get; set; } = null!;
[JsonPropertyName("width")]
public int? Width { get; set; }
[JsonPropertyName("height")]
public int? Height { get; set; }
[JsonPropertyName("r_frame_rate")]
public string FrameRate { get; set; } = null!;
[JsonPropertyName("pix_fmt")]
public string PixelFormat { get; set; } = null!;
[JsonPropertyName("sample_rate")]
public string SampleRate { get; set; } = null!;
[JsonPropertyName("tags")]
public Tags Tags { get; set; } = null!;
}
public class Tags
{
[JsonPropertyName("DURATION")]
public string Duration { get; set; } = null!;
}
}

View file

@ -10,24 +10,29 @@ internal MediaAnalysis(string path, FFProbeAnalysis analysis)
{
VideoStreams = analysis.Streams.Where(stream => stream.CodecType == "video").Select(ParseVideoStream).ToList();
AudioStreams = analysis.Streams.Where(stream => stream.CodecType == "audio").Select(ParseAudioStream).ToList();
TextStreams = analysis.Streams.Where(stream => stream.CodecTagString == "text").Select(ParseTextStream).ToList();
PrimaryVideoStream = VideoStreams.OrderBy(stream => stream.Index).FirstOrDefault();
PrimaryAudioStream = AudioStreams.OrderBy(stream => stream.Index).FirstOrDefault();
PrimaryTextStream = TextStreams.OrderBy(stream => stream.Index).FirstOrDefault();
Path = path;
}
public string Path { get; }
public string Extension => System.IO.Path.GetExtension(Path);
public TimeSpan Duration => TimeSpan.FromSeconds(Math.Max(
PrimaryVideoStream?.Duration.TotalSeconds ?? 0,
PrimaryAudioStream?.Duration.TotalSeconds ?? 0));
public AudioStream PrimaryAudioStream { get; }
public VideoStream PrimaryVideoStream { get; }
public AudioStream PrimaryAudioStream { get; }
public TextStream PrimaryTextStream { get; }
public List<VideoStream> VideoStreams { get; }
public List<AudioStream> AudioStreams { get; }
public List<TextStream> TextStreams { get; set; }
private VideoStream ParseVideoStream(Stream stream)
{
@ -45,7 +50,8 @@ private VideoStream ParseVideoStream(Stream stream)
Height = stream.Height!.Value,
Width = stream.Width!.Value,
Profile = stream.Profile,
PixelFormat = stream.PixelFormat
PixelFormat = stream.PixelFormat,
Language = stream.Tags.Language
};
}
@ -67,7 +73,19 @@ private AudioStream ParseAudioStream(Stream stream)
Channels = stream.Channels ?? default,
ChannelLayout = stream.ChannelLayout,
Duration = TimeSpan.FromSeconds(ParseDoubleInvariant(stream.Duration ?? stream.Tags.Duration ?? "0")),
SampleRateHz = !string.IsNullOrEmpty(stream.SampleRate) ? ParseIntInvariant(stream.SampleRate) : default
SampleRateHz = !string.IsNullOrEmpty(stream.SampleRate) ? ParseIntInvariant(stream.SampleRate) : default,
Language = stream.Tags.Language
};
}
private TextStream ParseTextStream(Stream stream)
{
return new TextStream
{
Index = stream.Index,
CodecName = stream.CodecName,
CodecLongName = stream.CodecLongName,
Duration = TimeSpan.FromSeconds(ParseDoubleInvariant(stream.Duration ?? stream.Tags.Duration ?? "0")),
Language = stream.Tags.Language
};
}

View file

@ -0,0 +1,12 @@
using FFMpegCore.Models;
namespace FFMpegCore
{
public abstract class MediaStream : SimpleStream
{
public int BitRate { get; internal set; }
public Codec GetCodecInfo() => FFMpegUtils.GetCodec(CodecName);
}
}

View file

@ -1,16 +1,13 @@
using FFMpegCore.Enums;
using System;
using System;
namespace FFMpegCore
{
public class MediaStream
public abstract class SimpleStream
{
public int Index { get; internal set; }
public string CodecName { get; internal set; } = null!;
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);
public string? Language { get; internal set; }
}
}

View file

@ -0,0 +1,7 @@
namespace FFMpegCore
{
public class TextStream : SimpleStream
{
}
}

View file

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

View file

@ -0,0 +1,64 @@
using System.Text.Json.Serialization;
namespace FFMpegCore
{
public class Stream
{
[JsonPropertyName("index")]
public int Index { get; set; }
[JsonPropertyName("avg_frame_rate")]
public string AvgFrameRate { get; set; } = null!;
[JsonPropertyName("bits_per_raw_sample")]
public string BitsPerRawSample { get; set; } = null!;
[JsonPropertyName("bit_rate")]
public string BitRate { get; set; } = null!;
[JsonPropertyName("channels")]
public int? Channels { get; set; }
[JsonPropertyName("channel_layout")]
public string ChannelLayout { get; set; } = null!;
[JsonPropertyName("codec_type")]
public string CodecType { get; set; } = null!;
[JsonPropertyName("codec_name")]
public string CodecName { get; set; } = null!;
[JsonPropertyName("codec_long_name")]
public string CodecLongName { get; set; } = null!;
[JsonPropertyName("codec_tag_string")]
public string CodecTagString { get; set; } = null!;
[JsonPropertyName("display_aspect_ratio")]
public string DisplayAspectRatio { get; set; } = null!;
[JsonPropertyName("duration")]
public string Duration { get; set; } = null!;
[JsonPropertyName("profile")]
public string Profile { get; set; } = null!;
[JsonPropertyName("width")]
public int? Width { get; set; }
[JsonPropertyName("height")]
public int? Height { get; set; }
[JsonPropertyName("r_frame_rate")]
public string FrameRate { get; set; } = null!;
[JsonPropertyName("pix_fmt")]
public string PixelFormat { get; set; } = null!;
[JsonPropertyName("sample_rate")]
public string SampleRate { get; set; } = null!;
[JsonPropertyName("tags")]
public Tags Tags { get; set; } = null!;
}
}

View file

@ -0,0 +1,13 @@
using System.Text.Json.Serialization;
namespace FFMpegCore
{
public class Tags
{
[JsonPropertyName("duration")]
public string Duration { get; set; } = null!;
[JsonPropertyName("language")]
public string Language { get; set; } = null!;
}
}

View file

@ -1,7 +1,7 @@
using System;
using System.Drawing;
using System.IO;
using FFMpegCore.Exceptions;
using FFMpegCore.Models;
namespace FFMpegCore.Helpers
{

View file

@ -1,4 +1,4 @@
using FFMpegCore.Exceptions;
using FFMpegCore.Models;
namespace FFMpegCore.Helpers
{

View file

@ -1,8 +1,8 @@
using System;
using System.Drawing;
using System.IO;
using FFMpegCore.Enums;
using FFMpegCore.Helpers;
using FFMpegCore.Utils;
namespace FFMpegCore
{