Cleanup after splitting into two packages (#386)

* Move PosterWithAudio to FFMpegCore

* Reduce windows only tests

* Update Directory.Build.props

* Create .editorconfig

* More cleanup

* Enable implicit usings

* Remove unused method

* Apply dotnet format

* Fix unused variable in AudioGateArgument

* Fix boolean conditions in AudioGateArgument

* Merge boolean conditions into pattern

* Use target-typed new

* Add linting to CI

* Add CUDA to HardwareAccelerationDevice enum

* Increase timeout for Video_Join_Image_Sequence

* Adjust Video_Join_Image_Sequence timeout

* Fix expected seconds in Video_Join_Image_Sequence

* Increase timeout for Video_TranscodeToMemory due to macos agents

Former-commit-id: f9f7161686
This commit is contained in:
Malte Rosenbjerg 2023-02-02 21:19:45 +01:00 committed by GitHub
parent 7dfefbdfdb
commit 693acabac4
126 changed files with 1212 additions and 1045 deletions

275
.editorconfig Normal file
View file

@ -0,0 +1,275 @@
# EditorConfig is awesome:http://EditorConfig.org
# top-most EditorConfig file
root = true
# Don't use tabs for indentation.
[*]
indent_style = space
# (Please don't specify an indent_size here; that has too many unintended consequences.)
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom
# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
# JSON files
[*.json]
indent_size = 2
[*.{sh}]
end_of_line = lf
indent_size = 2
# Dotnet code style settings:
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:error
dotnet_style_qualification_for_property = false:error
dotnet_style_qualification_for_method = false:error
dotnet_style_qualification_for_event = false:error
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:error
dotnet_style_predefined_type_for_member_access = true:error
# Suggest more modern language features when available
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
# Whitespace options
dotnet_style_allow_multiple_blank_lines_experimental = false
dotnet_style_allow_statement_immediately_after_block_experimental = false
# Non-private static fields are PascalCase
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
# Non-private readonly fields are PascalCase
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields
dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style
dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field
dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly
dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case
# Constants are PascalCase
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
dotnet_naming_symbols.constants.applicable_kinds = field, local
dotnet_naming_symbols.constants.required_modifiers = const
dotnet_naming_style.constant_style.capitalization = pascal_case
# Static fields are camelCase and start with s_
dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_style.static_field_style.capitalization = camel_case
dotnet_naming_style.static_field_style.required_prefix = _
# Instance fields are camelCase and start with _
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
dotnet_naming_symbols.instance_fields.applicable_kinds = field
dotnet_naming_style.instance_field_style.capitalization = camel_case
dotnet_naming_style.instance_field_style.required_prefix = _
# Locals and parameters are camelCase
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
dotnet_naming_style.camel_case_style.capitalization = camel_case
# Local functions are PascalCase
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
dotnet_naming_style.local_function_style.capitalization = pascal_case
# By default, name items with PascalCase
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.all_members.applicable_kinds = *
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# IDE0073: File header
dotnet_diagnostic.IDE0073.severity = warning
# IDE0035: Remove unreachable code
dotnet_diagnostic.IDE0035.severity = warning
# IDE0036: Order modifiers
dotnet_diagnostic.IDE0036.severity = warning
# IDE0043: Format string contains invalid placeholder
dotnet_diagnostic.IDE0043.severity = warning
# IDE0044: Make field readonly
dotnet_diagnostic.IDE0044.severity = warning
# IDE0011: Add braces
csharp_prefer_braces = when_multiline:warning
# NOTE: We need the below severity entry for Add Braces due to https://github.com/dotnet/roslyn/issues/44201
dotnet_diagnostic.IDE0011.severity = warning
# IDE0040: Add accessibility modifiers
dotnet_diagnostic.IDE0040.severity = warning
# CONSIDER: Are IDE0051 and IDE0052 too noisy to be warnings for IDE editing scenarios? Should they be made build-only warnings?
# IDE0051: Remove unused private member
dotnet_diagnostic.IDE0051.severity = warning
# IDE0052: Remove unread private member
dotnet_diagnostic.IDE0052.severity = warning
# IDE0059: Unnecessary assignment to a value
dotnet_diagnostic.IDE0059.severity = warning
# IDE0060: Remove unused parameter
dotnet_diagnostic.IDE0060.severity = warning
# CA1012: Abstract types should not have public constructors
dotnet_diagnostic.CA1012.severity = warning
# CA1822: Make member static
dotnet_diagnostic.CA1822.severity = warning
# IDE0005: Using directive is unnecessary
dotnet_diagnostic.IDE0005.severity = warning
# dotnet_style_allow_multiple_blank_lines_experimental
dotnet_diagnostic.IDE2000.severity = warning
# csharp_style_allow_embedded_statements_on_same_line_experimental
dotnet_diagnostic.IDE2001.severity = warning
# csharp_style_allow_blank_lines_between_consecutive_braces_experimental
dotnet_diagnostic.IDE2002.severity = warning
# dotnet_style_allow_statement_immediately_after_block_experimental
dotnet_diagnostic.IDE2003.severity = warning
# csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental
dotnet_diagnostic.IDE2004.severity = warning
# CSharp code style settings:
[*.cs]
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Whitespace options
csharp_style_allow_embedded_statements_on_same_line_experimental = false
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false
# Prefer "var" everywhere
dotnet_diagnostic.IDE0007.severity = error
csharp_style_var_for_built_in_types = true:error
csharp_style_var_when_type_is_apparent = true:error
csharp_style_var_elsewhere = true:error
# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:error
csharp_style_expression_bodied_indexers = true:error
csharp_style_expression_bodied_accessors = true:error
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:error
csharp_style_pattern_matching_over_as_with_null_check = true:error
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:error
csharp_style_conditional_delegate_call = true:suggestion
# Spacing
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Blocks are allowed
csharp_prefer_braces = true:silent
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true

View file

@ -27,6 +27,9 @@ jobs:
with: with:
dotnet-version: '7.0.x' dotnet-version: '7.0.x'
- name: Lint with dotnet
run: dotnet format FFMpegCore.sln --severity warn --verify-no-changes
- name: Prepare FFMpeg - name: Prepare FFMpeg
uses: FedericoCarboni/setup-ffmpeg@v2 uses: FedericoCarboni/setup-ffmpeg@v2
with: with:

View file

@ -5,6 +5,8 @@
<AssemblyVersion>5.0.0.0</AssemblyVersion> <AssemblyVersion>5.0.0.0</AssemblyVersion>
<LangVersion>default</LangVersion> <LangVersion>default</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings>
<RepositoryType>GitHub</RepositoryType> <RepositoryType>GitHub</RepositoryType>
<RepositoryUrl>https://github.com/rosenbjerg/FFMpegCore</RepositoryUrl> <RepositoryUrl>https://github.com/rosenbjerg/FFMpegCore</RepositoryUrl>

View file

@ -1,11 +1,8 @@
using System; using System.Drawing;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using FFMpegCore; using FFMpegCore;
using FFMpegCore.Enums; using FFMpegCore.Enums;
using FFMpegCore.Pipes;
using FFMpegCore.Extensions.System.Drawing.Common; using FFMpegCore.Extensions.System.Drawing.Common;
using FFMpegCore.Pipes;
var inputPath = "/path/to/input"; var inputPath = "/path/to/input";
var outputPath = "/path/to/output"; var outputPath = "/path/to/output";
@ -61,11 +58,7 @@ await FFMpegArguments
} }
{ {
FFMpegImage.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1, FFMpeg.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1, @"..\1.png", @"..\2.png", @"..\3.png");
ImageInfo.FromPath(@"..\1.png"),
ImageInfo.FromPath(@"..\2.png"),
ImageInfo.FromPath(@"..\3.png")
);
} }
{ {
@ -83,17 +76,19 @@ await FFMpegArguments
var inputImagePath = "/path/to/input/image"; var inputImagePath = "/path/to/input/image";
{ {
FFMpegImage.PosterWithAudio(inputPath, inputAudioPath, outputPath); FFMpeg.PosterWithAudio(inputPath, inputAudioPath, outputPath);
// or // or
#pragma warning disable CA1416
using var image = Image.FromFile(inputImagePath); using var image = Image.FromFile(inputImagePath);
image.AddAudio(inputAudioPath, outputPath); image.AddAudio(inputAudioPath, outputPath);
#pragma warning restore CA1416
} }
IVideoFrame GetNextFrame() => throw new NotImplementedException(); IVideoFrame GetNextFrame() => throw new NotImplementedException();
{ {
IEnumerable<IVideoFrame> CreateFrames(int count) IEnumerable<IVideoFrame> CreateFrames(int count)
{ {
for(int i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
yield return GetNextFrame(); //method of generating new frames yield return GetNextFrame(); //method of generating new frames
} }

View file

@ -1,6 +1,4 @@
using System; using System.Drawing;
using System.Drawing;
using System.IO;
namespace FFMpegCore.Extensions.System.Drawing.Common namespace FFMpegCore.Extensions.System.Drawing.Common
{ {
@ -12,11 +10,14 @@ public static bool AddAudio(this Image poster, string audio, string output)
poster.Save(destination); poster.Save(destination);
try try
{ {
return FFMpegImage.PosterWithAudio(destination, audio, output); return FFMpeg.PosterWithAudio(destination, audio, output);
} }
finally finally
{ {
if (File.Exists(destination)) File.Delete(destination); if (File.Exists(destination))
{
File.Delete(destination);
}
} }
} }
} }

View file

@ -1,10 +1,6 @@
using System; using System.Drawing;
using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore.Extensions.System.Drawing.Common namespace FFMpegCore.Extensions.System.Drawing.Common

View file

@ -1,84 +1,10 @@
using System; using System.Drawing;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using FFMpegCore.Enums;
using FFMpegCore.Helpers;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore.Extensions.System.Drawing.Common namespace FFMpegCore.Extensions.System.Drawing.Common
{ {
public static class FFMpegImage public static class FFMpegImage
{ {
public static void ConversionSizeExceptionCheck(Image image)
=> FFMpegHelper.ConversionSizeExceptionCheck(image.Size.Width, image.Size.Height);
/// <summary>
/// Converts an image sequence to a video.
/// </summary>
/// <param name="output">Output video file.</param>
/// <param name="frameRate">FPS</param>
/// <param name="images">Image sequence collection</param>
/// <returns>Output video information.</returns>
public static bool JoinImageSequence(string output, double frameRate = 30, params ImageInfo[] images)
{
var tempFolderName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, Guid.NewGuid().ToString());
var temporaryImageFiles = images.Select((imageInfo, index) =>
{
using var image = Image.FromFile(imageInfo.FullName);
FFMpegHelper.ConversionSizeExceptionCheck(image.Width, image.Height);
var destinationPath = Path.Combine(tempFolderName, $"{index.ToString().PadLeft(9, '0')}{imageInfo.Extension}");
Directory.CreateDirectory(tempFolderName);
File.Copy(imageInfo.FullName, destinationPath);
return destinationPath;
}).ToArray();
var firstImage = images.First();
try
{
return FFMpegArguments
.FromFileInput(Path.Combine(tempFolderName, "%09d.png"), false)
.OutputToFile(output, true, options => options
.ForcePixelFormat("yuv420p")
.Resize(firstImage.Width, firstImage.Height)
.WithFramerate(frameRate))
.ProcessSynchronously();
}
finally
{
Cleanup(temporaryImageFiles);
Directory.Delete(tempFolderName);
}
}
/// <summary>
/// Adds a poster image to an audio file.
/// </summary>
/// <param name="image">Source image file.</param>
/// <param name="audio">Source audio file.</param>
/// <param name="output">Output video file.</param>
/// <returns></returns>
public static bool PosterWithAudio(string image, string audio, string output)
{
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
using (var img = Image.FromFile(image))
FFMpegHelper.ConversionSizeExceptionCheck(img.Width, img.Height);
return FFMpegArguments
.FromFileInput(image, false, options => options
.Loop(1)
.ForceFormat("image2"))
.AddFileInput(audio)
.OutputToFile(output, true, options => options
.ForcePixelFormat("yuv420p")
.WithVideoCodec(VideoCodec.LibX264)
.WithConstantRateFactor(21)
.WithAudioBitrate(AudioQuality.Normal)
.UsingShortest())
.ProcessSynchronously();
}
/// <summary> /// <summary>
/// Saves a 'png' thumbnail to an in-memory bitmap /// Saves a 'png' thumbnail to an in-memory bitmap
/// </summary> /// </summary>
@ -126,13 +52,5 @@ await arguments
ms.Position = 0; ms.Position = 0;
return new Bitmap(ms); return new Bitmap(ms);
} }
private static void Cleanup(IEnumerable<string> pathList)
{
foreach (var path in pathList)
{
if (File.Exists(path))
File.Delete(path);
}
}
} }
} }

View file

@ -1,179 +0,0 @@
using System;
using System.Drawing;
using System.IO;
using FFMpegCore.Enums;
using FFMpegCore.Helpers;
namespace FFMpegCore
{
public class ImageInfo
{
private FileInfo _file;
/// <summary>
/// Create a image information object from a target path.
/// </summary>
/// <param name="fileInfo">Image file information.</param>
public ImageInfo(FileInfo fileInfo)
{
if (!fileInfo.Extension.ToLowerInvariant().EndsWith(FileExtension.Png))
{
throw new Exception("Image joining currently suppors only .png file types");
}
fileInfo.Refresh();
Size = fileInfo.Length / (1024 * 1024);
using (var image = Image.FromFile(fileInfo.FullName))
{
Width = image.Width;
Height = image.Height;
var cd = FFProbeHelper.Gcd(Width, Height);
Ratio = $"{Width / cd}:{Height / cd}";
}
if (!fileInfo.Exists)
throw new ArgumentException($"Input file {fileInfo.FullName} does not exist!");
_file = fileInfo;
}
/// <summary>
/// Create a image information object from a target path.
/// </summary>
/// <param name="path">Path to image.</param>
public ImageInfo(string path) : this(new FileInfo(path)) { }
/// <summary>
/// Aspect ratio.
/// </summary>
public string Ratio { get; internal set; }
/// <summary>
/// Height of the image file.
/// </summary>
public int Height { get; internal set; }
/// <summary>
/// Width of the image file.
/// </summary>
public int Width { get; internal set; }
/// <summary>
/// Image file size in MegaBytes (MB).
/// </summary>
public double Size { get; internal set; }
/// <summary>
/// Gets the name of the file.
/// </summary>
public string Name => _file.Name;
/// <summary>
/// Gets the full path of the file.
/// </summary>
public string FullName => _file.FullName;
/// <summary>
/// Gets the file extension.
/// </summary>
public string Extension => _file.Extension;
/// <summary>
/// Gets a flag indicating if the file is read-only.
/// </summary>
public bool IsReadOnly => _file.IsReadOnly;
/// <summary>
/// Gets a flag indicating if the file exists (no cache, per call verification).
/// </summary>
public bool Exists => File.Exists(FullName);
/// <summary>
/// Gets the creation date.
/// </summary>
public DateTime CreationTime => _file.CreationTime;
/// <summary>
/// Gets the parent directory information.
/// </summary>
public DirectoryInfo Directory => _file.Directory;
/// <summary>
/// Create a image information object from a file information object.
/// </summary>
/// <param name="fileInfo">Image file information.</param>
/// <returns></returns>
public static ImageInfo FromFileInfo(FileInfo fileInfo)
{
return FromPath(fileInfo.FullName);
}
/// <summary>
/// Create a image information object from a target path.
/// </summary>
/// <param name="path">Path to image.</param>
/// <returns></returns>
public static ImageInfo FromPath(string path)
{
return new ImageInfo(path);
}
/// <summary>
/// Pretty prints the image information.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return "Image Path : " + FullName + Environment.NewLine +
"Image Root : " + Directory.FullName + Environment.NewLine +
"Image Name: " + Name + Environment.NewLine +
"Image Extension : " + Extension + Environment.NewLine +
"Aspect Ratio : " + Ratio + Environment.NewLine +
"Resolution : " + Width + "x" + Height + Environment.NewLine +
"Size : " + Size + " MB";
}
/// <summary>
/// Open a file stream.
/// </summary>
/// <param name="mode">Opens a file in a specified mode.</param>
/// <returns>File stream of the image file.</returns>
public FileStream FileOpen(FileMode mode)
{
return _file.Open(mode);
}
/// <summary>
/// Move file to a specific directory.
/// </summary>
/// <param name="destination"></param>
public void MoveTo(DirectoryInfo destination)
{
var newLocation = $"{destination.FullName}{Path.DirectorySeparatorChar}{Name}{Extension}";
_file.MoveTo(newLocation);
_file = new FileInfo(newLocation);
}
/// <summary>
/// Delete the file.
/// </summary>
public void Delete()
{
_file.Delete();
}
/// <summary>
/// Converts image info to file info.
/// </summary>
/// <returns>A new FileInfo instance.</returns>
public FileInfo ToFileInfo()
{
return new FileInfo(_file.FullName);
}
}
}

View file

@ -1,7 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using FFMpegCore.Arguments;
using System;
using FFMpegCore.Arguments;
using FFMpegCore.Enums; using FFMpegCore.Enums;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FFMpegCore.Test namespace FFMpegCore.Test
{ {
@ -10,7 +9,6 @@ public class ArgumentBuilderTest
{ {
private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4" }; private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4" };
[TestMethod] [TestMethod]
public void Builder_BuildString_IO_1() public void Builder_BuildString_IO_1()
{ {
@ -53,7 +51,6 @@ public void Builder_BuildString_Quiet()
Assert.AreEqual("-hide_banner -loglevel error -i \"input.mp4\" \"output.mp4\"", str); Assert.AreEqual("-hide_banner -loglevel error -i \"input.mp4\" \"output.mp4\"", str);
} }
[TestMethod] [TestMethod]
public void Builder_BuildString_AudioCodec_Fluent() public void Builder_BuildString_AudioCodec_Fluent()
{ {
@ -400,7 +397,6 @@ public void Builder_BuildString_Codec_Override()
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);
} }
[TestMethod] [TestMethod]
public void Builder_BuildString_Duration() public void Builder_BuildString_Duration()
{ {
@ -421,7 +417,6 @@ public void Builder_BuildString_Raw()
Assert.AreEqual("-i \"input.mp4\" -acodec copy \"output.mp4\"", str); Assert.AreEqual("-i \"input.mp4\" -acodec copy \"output.mp4\"", str);
} }
[TestMethod] [TestMethod]
public void Builder_BuildString_ForcePixelFormat() public void Builder_BuildString_ForcePixelFormat()
{ {

View file

@ -4,13 +4,6 @@
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
using FFMpegCore.Test.Resources; using FFMpegCore.Test.Resources;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using FFMpegCore.Extensions.System.Drawing.Common;
using FFMpegCore.Test.Utilities;
namespace FFMpegCore.Test namespace FFMpegCore.Test
{ {
@ -66,11 +59,11 @@ public void Audio_Add()
Assert.IsTrue(File.Exists(outputFile)); Assert.IsTrue(File.Exists(outputFile));
} }
[WindowsOnlyTestMethod] [TestMethod]
public void Image_AddAudio() public void Image_AddAudio()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
FFMpegImage.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile); FFMpeg.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile);
var analysis = FFProbe.Analyse(TestResources.Mp3Audio); var analysis = FFProbe.Analyse(TestResources.Mp3Audio);
Assert.IsTrue(analysis.Duration.TotalSeconds > 0); Assert.IsTrue(analysis.Duration.TotalSeconds > 0);
Assert.IsTrue(File.Exists(outputFile)); Assert.IsTrue(File.Exists(outputFile));

View file

@ -1,7 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Reflection;
using FluentAssertions;
using System.Reflection;
using FFMpegCore.Arguments; using FFMpegCore.Arguments;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FFMpegCore.Test namespace FFMpegCore.Test
{ {
@ -20,7 +20,6 @@ private static FFMpegArgumentProcessor CreateArgumentProcessor() => FFMpegArgume
.FromFileInput("") .FromFileInput("")
.OutputToFile(""); .OutputToFile("");
[TestMethod] [TestMethod]
public void Processor_GlobalOptions_GetUsed() public void Processor_GlobalOptions_GetUsed()
{ {
@ -44,14 +43,12 @@ public void Processor_SessionOptions_GetUsed()
options.WorkingDirectory.Should().Be(sessionWorkingDir); options.WorkingDirectory.Should().Be(sessionWorkingDir);
} }
[TestMethod] [TestMethod]
public void Processor_Options_CanBeOverridden_And_Configured() public void Processor_Options_CanBeOverridden_And_Configured()
{ {
var globalConfig = "Whatever"; var globalConfig = "Whatever";
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig }); GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig });
var processor = CreateArgumentProcessor(); var processor = CreateArgumentProcessor();
var sessionTempDir = "./CurrentRunWorkingDir"; var sessionTempDir = "./CurrentRunWorkingDir";
@ -65,7 +62,6 @@ public void Processor_Options_CanBeOverridden_And_Configured()
options.BinaryFolder.Should().NotBeEquivalentTo(globalConfig); options.BinaryFolder.Should().NotBeEquivalentTo(globalConfig);
} }
[TestMethod] [TestMethod]
public void Options_Global_And_Session_Options_Can_Differ() public void Options_Global_And_Session_Options_Can_Differ()
{ {
@ -78,7 +74,6 @@ public void Options_Global_And_Session_Options_Can_Differ()
var options1 = processor1.GetConfiguredOptions(null); var options1 = processor1.GetConfiguredOptions(null);
options1.WorkingDirectory.Should().Be(sessionWorkingDir); options1.WorkingDirectory.Should().Be(sessionWorkingDir);
var processor2 = CreateArgumentProcessor(); var processor2 = CreateArgumentProcessor();
var options2 = processor2.GetConfiguredOptions(null); var options2 = processor2.GetConfiguredOptions(null);
options2.WorkingDirectory.Should().Be(globalWorkingDir); options2.WorkingDirectory.Should().Be(globalWorkingDir);
@ -98,7 +93,6 @@ public void Audible_Aaxc_Test()
arg.Text.Should().Be($"-audible_key 123 -audible_iv 456"); arg.Text.Should().Be($"-audible_key 123 -audible_iv 456");
} }
[TestMethod] [TestMethod]
public void Audible_Aax_Test() public void Audible_Aax_Test()
{ {

View file

@ -1,6 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.IO;
namespace FFMpegCore.Test namespace FFMpegCore.Test
{ {

View file

@ -1,10 +1,5 @@
using FFMpegCore.Test.Resources; using FFMpegCore.Test.Resources;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace FFMpegCore.Test namespace FFMpegCore.Test
{ {
@ -55,7 +50,6 @@ public async Task PacketAnalysis_Async()
Assert.AreEqual(1362, packets.Last().Size); Assert.AreEqual(1362, packets.Last().Size);
} }
[TestMethod] [TestMethod]
public void PacketAnalysis_Sync() public void PacketAnalysis_Sync()
{ {

View file

@ -1,7 +1,6 @@
using FFMpegCore.Builders.MetaData; using System.Text.RegularExpressions;
using FFMpegCore.Builders.MetaData;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Text.RegularExpressions;
namespace FFMpegCore.Test namespace FFMpegCore.Test
{ {
@ -64,8 +63,6 @@ public void TestMapMetadata()
.AddMetaData("WhatEver3") .AddMetaData("WhatEver3")
.Text; .Text;
Assert.IsTrue(Regex.IsMatch(text0, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 1"), "map_metadata index is calculated incorrectly."); Assert.IsTrue(Regex.IsMatch(text0, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 1"), "map_metadata index is calculated incorrectly.");
Assert.IsTrue(Regex.IsMatch(text1, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 2"), "map_metadata index is calculated incorrectly."); Assert.IsTrue(Regex.IsMatch(text1, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 2"), "map_metadata index is calculated incorrectly.");
} }

View file

@ -1,7 +1,4 @@
using System; namespace FFMpegCore.Test
using System.IO;
namespace FFMpegCore.Test
{ {
public class TemporaryFile : IDisposable public class TemporaryFile : IDisposable
{ {
@ -16,7 +13,9 @@ public TemporaryFile(string filename)
public void Dispose() public void Dispose()
{ {
if (File.Exists(_path)) if (File.Exists(_path))
{
File.Delete(_path); File.Delete(_path);
} }
} }
} }
}

View file

@ -1,6 +1,4 @@
using System; using System.Drawing;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.Numerics; using System.Numerics;
using System.Runtime.Versioning; using System.Runtime.Versioning;
@ -10,11 +8,11 @@
namespace FFMpegCore.Test.Utilities namespace FFMpegCore.Test.Utilities
{ {
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
static class BitmapSource internal static class BitmapSource
{ {
public static IEnumerable<IVideoFrame> CreateBitmaps(int count, PixelFormat fmt, int w, int h) public static IEnumerable<IVideoFrame> CreateBitmaps(int count, PixelFormat fmt, int w, int h)
{ {
for (int i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
using (var frame = CreateVideoFrame(i, fmt, w, h, 0.025f, 0.025f * w * 0.03f)) using (var frame = CreateVideoFrame(i, fmt, w, h, 0.025f, 0.025f * w * 0.03f))
{ {
@ -29,8 +27,9 @@ public static BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fm
offset = offset * index; offset = offset * index;
for (int y = 0; y < h; y++) for (var y = 0; y < h; y++)
for (int x = 0; x < w; x++) {
for (var x = 0; x < w; x++)
{ {
var xf = x / (float)w; var xf = x / (float)w;
var yf = y / (float)h; var yf = y / (float)h;
@ -43,6 +42,7 @@ public static BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fm
bitmap.SetPixel(x, y, color); bitmap.SetPixel(x, y, color);
} }
}
return new BitmapVideoFrameWrapper(bitmap); return new BitmapVideoFrameWrapper(bitmap);
} }
@ -55,7 +55,7 @@ public static BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fm
// Based on the original implementation by Ken Perlin // Based on the original implementation by Ken Perlin
// http://mrl.nyu.edu/~perlin/noise/ // http://mrl.nyu.edu/~perlin/noise/
// //
static class Perlin private static class Perlin
{ {
#region Noise functions #region Noise functions
@ -128,6 +128,7 @@ public static float Fbm(float x, int octave)
x *= 2.0f; x *= 2.0f;
w *= 0.5f; w *= 0.5f;
} }
return f; return f;
} }
@ -141,6 +142,7 @@ public static float Fbm(Vector2 coord, int octave)
coord *= 2.0f; coord *= 2.0f;
w *= 0.5f; w *= 0.5f;
} }
return f; return f;
} }
@ -159,6 +161,7 @@ public static float Fbm(Vector3 coord, int octave)
coord *= 2.0f; coord *= 2.0f;
w *= 0.5f; w *= 0.5f;
} }
return f; return f;
} }
@ -171,27 +174,27 @@ public static float Fbm(float x, float y, float z, int octave)
#region Private functions #region Private functions
static float Fade(float t) private static float Fade(float t)
{ {
return t * t * t * (t * (t * 6 - 15) + 10); return t * t * t * (t * (t * 6 - 15) + 10);
} }
static float Lerp(float t, float a, float b) private static float Lerp(float t, float a, float b)
{ {
return a + t * (b - a); return a + t * (b - a);
} }
static float Grad(int hash, float x) private static float Grad(int hash, float x)
{ {
return (hash & 1) == 0 ? x : -x; return (hash & 1) == 0 ? x : -x;
} }
static float Grad(int hash, float x, float y) private static float Grad(int hash, float x, float y)
{ {
return ((hash & 1) == 0 ? x : -x) + ((hash & 2) == 0 ? y : -y); return ((hash & 1) == 0 ? x : -x) + ((hash & 2) == 0 ? y : -y);
} }
static float Grad(int hash, float x, float y, float z) private static float Grad(int hash, float x, float y, float z)
{ {
var h = hash & 15; var h = hash & 15;
var u = h < 8 ? x : y; var u = h < 8 ? x : y;
@ -199,7 +202,7 @@ static float Grad(int hash, float x, float y, float z)
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
} }
static int[] perm = { private static readonly int[] perm = {
151,160,137,91,90,15, 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,

View file

@ -1,21 +1,14 @@
using FFMpegCore.Arguments; using System.Drawing.Imaging;
using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
using FFMpegCore.Pipes;
using FFMpegCore.Test.Resources;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using System.Text; using System.Text;
using System.Threading; using FFMpegCore.Arguments;
using System.Threading.Tasks; using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
using FFMpegCore.Extensions.System.Drawing.Common; using FFMpegCore.Extensions.System.Drawing.Common;
using FFMpegCore.Pipes;
using FFMpegCore.Test.Resources;
using FFMpegCore.Test.Utilities; using FFMpegCore.Test.Utilities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FFMpegCore.Test namespace FFMpegCore.Test
{ {
@ -213,7 +206,6 @@ await FFMpegArguments
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
public void Video_StreamFile_OutputToMemoryStream() public void Video_StreamFile_OutputToMemoryStream()
{ {
@ -245,7 +237,6 @@ public void Video_ToMP4_Args_StreamOutputPipe_Failure()
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
public async Task Video_ToMP4_Args_StreamOutputPipe_Async() public async Task Video_ToMP4_Args_StreamOutputPipe_Async()
{ {
@ -357,7 +348,7 @@ public async Task Video_ToOGV_Resize()
[WindowsOnlyDataTestMethod, Timeout(10000)] [WindowsOnlyDataTestMethod, Timeout(10000)]
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
// [DataRow(PixelFormat.Format48bppRgb)] [DataRow(System.Drawing.Imaging.PixelFormat.Format48bppRgb)]
public void RawVideoPipeSource_Ogv_Scale(System.Drawing.Imaging.PixelFormat pixelFormat) public void RawVideoPipeSource_Ogv_Scale(System.Drawing.Imaging.PixelFormat pixelFormat)
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}");
@ -411,16 +402,15 @@ public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixe
[WindowsOnlyTestMethod, Timeout(10000)] [WindowsOnlyTestMethod, Timeout(10000)]
public void Video_Snapshot_InMemory() public void Video_Snapshot_InMemory()
{ {
var input = FFProbe.Analyse(TestResources.Mp4Video);
using var bitmap = FFMpegImage.Snapshot(TestResources.Mp4Video); using var bitmap = FFMpegImage.Snapshot(TestResources.Mp4Video);
var input = FFProbe.Analyse(TestResources.Mp4Video);
Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width); Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width);
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height); Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png); Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
} }
[SupportedOSPlatform("windows")] [TestMethod, Timeout(10000)]
[WindowsOnlyTestMethod, Timeout(10000)]
public void Video_Snapshot_PersistSnapshot() public void Video_Snapshot_PersistSnapshot()
{ {
var outputPath = new TemporaryFile("out.png"); var outputPath = new TemporaryFile("out.png");
@ -428,10 +418,10 @@ public void Video_Snapshot_PersistSnapshot()
FFMpeg.Snapshot(TestResources.Mp4Video, outputPath); FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
using var bitmap = Image.FromFile(outputPath); var analysis = FFProbe.Analyse(outputPath);
Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width); Assert.AreEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height); Assert.AreEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png); Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
} }
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
@ -456,28 +446,29 @@ public void Video_Join()
Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width); Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width);
} }
[WindowsOnlyTestMethod, Timeout(10000)] [TestMethod, Timeout(20000)]
public void Video_Join_Image_Sequence() public void Video_Join_Image_Sequence()
{ {
var imageSet = new List<ImageInfo>(); var imageSet = new List<string>();
Directory.EnumerateFiles(TestResources.ImageCollection) Directory.EnumerateFiles(TestResources.ImageCollection, "*.png")
.Where(file => file.ToLower().EndsWith(".png"))
.ToList() .ToList()
.ForEach(file => .ForEach(file =>
{ {
for (var i = 0; i < 15; i++) for (var i = 0; i < 5; i++)
{ {
imageSet.Add(new ImageInfo(file)); imageSet.Add(file);
} }
}); });
var imageAnalysis = FFProbe.Analyse(imageSet.First());
var outputFile = new TemporaryFile("out.mp4"); var outputFile = new TemporaryFile("out.mp4");
var success = FFMpegImage.JoinImageSequence(outputFile, images: imageSet.ToArray()); var success = FFMpeg.JoinImageSequence(outputFile, frameRate: 10, images: imageSet.ToArray());
Assert.IsTrue(success); Assert.IsTrue(success);
var result = FFProbe.Analyse(outputFile); var result = FFProbe.Analyse(outputFile);
Assert.AreEqual(3, result.Duration.Seconds);
Assert.AreEqual(imageSet.First().Width, result.PrimaryVideoStream!.Width); Assert.AreEqual(1, result.Duration.Seconds);
Assert.AreEqual(imageSet.First().Height, result.PrimaryVideoStream.Height); Assert.AreEqual(imageAnalysis.PrimaryVideoStream!.Width, result.PrimaryVideoStream!.Width);
Assert.AreEqual(imageAnalysis.PrimaryVideoStream!.Height, result.PrimaryVideoStream.Height);
} }
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
@ -520,12 +511,18 @@ public void Video_UpdatesProgress()
void OnPercentageProgess(double percentage) void OnPercentageProgess(double percentage)
{ {
if (percentage < 100) percentageDone = percentage; if (percentage < 100)
{
percentageDone = percentage;
}
} }
void OnTimeProgess(TimeSpan time) void OnTimeProgess(TimeSpan time)
{ {
if (time < analysis.Duration) timeDone = time; if (time < analysis.Duration)
{
timeDone = time;
}
} }
var success = FFMpegArguments var success = FFMpegArguments
@ -586,6 +583,24 @@ public void Video_TranscodeInMemory()
Assert.AreEqual(vi.PrimaryVideoStream.Height, 128); Assert.AreEqual(vi.PrimaryVideoStream.Height, 128);
} }
[TestMethod, Timeout(20000)]
public void Video_TranscodeToMemory()
{
using var memoryStream = new MemoryStream();
FFMpegArguments
.FromFileInput(TestResources.WebmVideo)
.OutputToPipe(new StreamPipeSink(memoryStream), opt => opt
.WithVideoCodec("vp9")
.ForceFormat("webm"))
.ProcessSynchronously();
memoryStream.Position = 0;
var vi = FFProbe.Analyse(memoryStream);
Assert.AreEqual(vi.PrimaryVideoStream!.Width, 640);
Assert.AreEqual(vi.PrimaryVideoStream.Height, 360);
}
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
public async Task Video_Cancel_Async() public async Task Video_Cancel_Async()
{ {

View file

@ -1,6 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Extend
namespace FFMpegCore.Extend
{ {
internal static class KeyValuePairExtensions internal static class KeyValuePairExtensions
{ {

View file

@ -1,7 +1,4 @@
using System.IO; using FFMpegCore.Pipes;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Pipes;
namespace FFMpegCore.Extend namespace FFMpegCore.Extend
{ {

View file

@ -1,11 +1,10 @@
using System.Collections.Generic; using System.Text;
using System.Text;
namespace FFMpegCore.Extend namespace FFMpegCore.Extend
{ {
internal static class StringExtensions internal static class StringExtensions
{ {
private static Dictionary<char, string> CharactersSubstitution { get; } = new Dictionary<char, string> private static Dictionary<char, string> CharactersSubstitution { get; } = new()
{ {
{ '\\', @"\\" }, { '\\', @"\\" },
{ ':', @"\:" }, { ':', @"\:" },

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Extend
namespace FFMpegCore.Extend
{ {
public static class UriExtensions public static class UriExtensions
{ {

View file

@ -9,7 +9,6 @@ public class AudibleEncryptionKeyArgument : IArgument
private readonly string? _activationBytes; private readonly string? _activationBytes;
public AudibleEncryptionKeyArgument(string activationBytes) public AudibleEncryptionKeyArgument(string activationBytes)
{ {
_activationBytes = activationBytes; _activationBytes = activationBytes;

View file

@ -14,7 +14,6 @@ public AudioBitrateArgument(int bitrate)
Bitrate = bitrate; Bitrate = bitrate;
} }
public string Text => $"-b:a {Bitrate}k"; public string Text => $"-b:a {Bitrate}k";
} }
} }

View file

@ -13,7 +13,9 @@ public class AudioCodecArgument : IArgument
public AudioCodecArgument(Codec audioCodec) public AudioCodecArgument(Codec audioCodec)
{ {
if (audioCodec.Type != CodecType.Audio) if (audioCodec.Type != CodecType.Audio)
{
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{audioCodec.Name}\" is not an audio codec"); throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{audioCodec.Name}\" is not an audio codec");
}
AudioCodec = audioCodec.Name; AudioCodec = audioCodec.Name;
} }

View file

@ -1,6 +1,4 @@
using System.Collections.Generic; using FFMpegCore.Exceptions;
using System.Linq;
using FFMpegCore.Exceptions;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
@ -18,7 +16,9 @@ public AudioFiltersArgument(AudioFilterOptions options)
private string GetText() private string GetText()
{ {
if (!Options.Arguments.Any()) if (!Options.Arguments.Any())
{
throw new FFMpegArgumentException("No audio-filter arguments provided"); throw new FFMpegArgumentException("No audio-filter arguments provided");
}
var arguments = Options.Arguments var arguments = Options.Arguments
.Where(arg => !string.IsNullOrEmpty(arg.Value)) .Where(arg => !string.IsNullOrEmpty(arg.Value))
@ -40,7 +40,7 @@ public interface IAudioFilterArgument
public class AudioFilterOptions public class AudioFilterOptions
{ {
public List<IAudioFilterArgument> Arguments { get; } = new List<IAudioFilterArgument>(); public List<IAudioFilterArgument> Arguments { get; } = new();
public AudioFilterOptions Pan(string channelLayout, params string[] outputDefinitions) => WithArgument(new PanArgument(channelLayout, outputDefinitions)); public AudioFilterOptions Pan(string channelLayout, params string[] outputDefinitions) => WithArgument(new PanArgument(channelLayout, outputDefinitions));
public AudioFilterOptions Pan(int channels, params string[] outputDefinitions) => WithArgument(new PanArgument(channels, outputDefinitions)); public AudioFilterOptions Pan(int channels, params string[] outputDefinitions) => WithArgument(new PanArgument(channels, outputDefinitions));

View file

@ -1,18 +1,15 @@
using System; using System.Globalization;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
public class AudioGateArgument : IAudioFilterArgument public class AudioGateArgument : IAudioFilterArgument
{ {
private readonly Dictionary<string, string> _arguments = new Dictionary<string, string>(); private readonly Dictionary<string, string> _arguments = new();
/// <summary> /// <summary>
/// Audio Gate. <see href="https://ffmpeg.org/ffmpeg-filters.html#agate"/> /// Audio Gate. <see href="https://ffmpeg.org/ffmpeg-filters.html#agate"/>
/// </summary> /// </summary>
/// <param name="level_in">Set input level before filtering. Default is 1. Allowed range is from 0.015625 to 64.</param> /// <param name="levelIn">Set input level before filtering. Default is 1. Allowed range is from 0.015625 to 64.</param>
/// <param name="mode">Set the mode of operation. Can be upward or downward. Default is downward. If set to upward mode, higher parts of signal will be amplified, expanding dynamic range in upward direction. Otherwise, in case of downward lower parts of signal will be reduced.</param> /// <param name="mode">Set the mode of operation. Can be upward or downward. Default is downward. If set to upward mode, higher parts of signal will be amplified, expanding dynamic range in upward direction. Otherwise, in case of downward lower parts of signal will be reduced.</param>
/// <param name="range">Set the level of gain reduction when the signal is below the threshold. Default is 0.06125. Allowed range is from 0 to 1. Setting this to 0 disables reduction and then filter behaves like expander.</param> /// <param name="range">Set the level of gain reduction when the signal is below the threshold. Default is 0.06125. Allowed range is from 0 to 1. Setting this to 0 disables reduction and then filter behaves like expander.</param>
/// <param name="threshold">If a signal rises above this level the gain reduction is released. Default is 0.125. Allowed range is from 0 to 1.</param> /// <param name="threshold">If a signal rises above this level the gain reduction is released. Default is 0.125. Allowed range is from 0 to 1.</param>
@ -23,29 +20,75 @@ public class AudioGateArgument : IAudioFilterArgument
/// <param name="knee">Curve the sharp knee around the threshold to enter gain reduction more softly. Default is 2.828427125. Allowed range is from 1 to 8.</param> /// <param name="knee">Curve the sharp knee around the threshold to enter gain reduction more softly. Default is 2.828427125. Allowed range is from 1 to 8.</param>
/// <param name="detection">Choose if exact signal should be taken for detection or an RMS like one. Default is rms. Can be peak or rms.</param> /// <param name="detection">Choose if exact signal should be taken for detection or an RMS like one. Default is rms. Can be peak or rms.</param>
/// <param name="link">Choose if the average level between all channels or the louder channel affects the reduction. Default is average. Can be average or maximum.</param> /// <param name="link">Choose if the average level between all channels or the louder channel affects the reduction. Default is average. Can be average or maximum.</param>
public AudioGateArgument(double level_in = 1, string mode = "downward", double range = 0.06125, double threshold = 0.125, int ratio = 2, double attack = 20, double release = 250, int makeup = 1, double knee = 2.828427125, string detection = "rms", string link = "average") public AudioGateArgument(double levelIn = 1, string mode = "downward", double range = 0.06125, double threshold = 0.125, int ratio = 2,
double attack = 20, double release = 250, int makeup = 1, double knee = 2.828427125, string detection = "rms", string link = "average")
{ {
if (level_in < 0.015625 || level_in > 64) throw new ArgumentOutOfRangeException(nameof(level_in), "Level in must be between 0.015625 to 64"); if (levelIn is < 0.015625 or > 64)
if (!(mode == "upward" || mode == "downward")) throw new ArgumentOutOfRangeException(nameof(mode), "Mode must be either upward or downward"); {
if (range <= 0 || range > 1) throw new ArgumentOutOfRangeException(nameof(range)); throw new ArgumentOutOfRangeException(nameof(levelIn), "Level in must be between 0.015625 to 64");
if (threshold < 0 || threshold > 1) throw new ArgumentOutOfRangeException(nameof(threshold), "Threshold must be between 0 and 1"); }
if (ratio < 1 || ratio > 9000) throw new ArgumentOutOfRangeException(nameof(ratio), "Ratio must be between 1 and 9000");
if (attack < 0.01 || attack > 9000) throw new ArgumentOutOfRangeException(nameof(attack), "Attack must be between 0.01 and 9000");
if (release < 0.01 || release > 9000) throw new ArgumentOutOfRangeException(nameof(release), "Release must be between 0.01 and 9000");
if (makeup < 1 || makeup > 64) throw new ArgumentOutOfRangeException(nameof(makeup), "Makeup Gain must be between 1 and 64");
if (!(detection == "peak" || detection == "rms")) throw new ArgumentOutOfRangeException(nameof(detection), "Detection must be either peak or rms");
if (!(link != "average" || link != "maximum")) throw new ArgumentOutOfRangeException(nameof(link), "Link must be either average or maximum");
_arguments.Add("level_in", level_in.ToString("0.00", CultureInfo.InvariantCulture)); if (mode != "upward" && mode != "downward")
_arguments.Add("mode", mode.ToString()); {
throw new ArgumentOutOfRangeException(nameof(mode), "Mode must be either upward or downward");
}
if (range is <= 0 or > 1)
{
throw new ArgumentOutOfRangeException(nameof(range));
}
if (threshold is < 0 or > 1)
{
throw new ArgumentOutOfRangeException(nameof(threshold), "Threshold must be between 0 and 1");
}
if (ratio is < 1 or > 9000)
{
throw new ArgumentOutOfRangeException(nameof(ratio), "Ratio must be between 1 and 9000");
}
if (attack is < 0.01 or > 9000)
{
throw new ArgumentOutOfRangeException(nameof(attack), "Attack must be between 0.01 and 9000");
}
if (release is < 0.01 or > 9000)
{
throw new ArgumentOutOfRangeException(nameof(release), "Release must be between 0.01 and 9000");
}
if (makeup is < 1 or > 64)
{
throw new ArgumentOutOfRangeException(nameof(makeup), "Makeup Gain must be between 1 and 64");
}
if (knee is < 1 or > 64)
{
throw new ArgumentOutOfRangeException(nameof(makeup), "Knee must be between 1 and 8");
}
if (detection != "peak" && detection != "rms")
{
throw new ArgumentOutOfRangeException(nameof(detection), "Detection must be either peak or rms");
}
if (link != "average" && link != "maximum")
{
throw new ArgumentOutOfRangeException(nameof(link), "Link must be either average or maximum");
}
_arguments.Add("level_in", levelIn.ToString("0.00", CultureInfo.InvariantCulture));
_arguments.Add("mode", mode);
_arguments.Add("range", range.ToString("0.00", CultureInfo.InvariantCulture)); _arguments.Add("range", range.ToString("0.00", CultureInfo.InvariantCulture));
_arguments.Add("threshold", threshold.ToString("0.00", CultureInfo.InvariantCulture)); _arguments.Add("threshold", threshold.ToString("0.00", CultureInfo.InvariantCulture));
_arguments.Add("ratio", ratio.ToString()); _arguments.Add("ratio", ratio.ToString());
_arguments.Add("attack", attack.ToString("0.00", CultureInfo.InvariantCulture)); _arguments.Add("attack", attack.ToString("0.00", CultureInfo.InvariantCulture));
_arguments.Add("release", release.ToString("0.00", CultureInfo.InvariantCulture)); _arguments.Add("release", release.ToString("0.00", CultureInfo.InvariantCulture));
_arguments.Add("makeup", makeup.ToString()); _arguments.Add("makeup", makeup.ToString());
_arguments.Add("detection", detection.ToString()); _arguments.Add("knee", knee.ToString("0.00", CultureInfo.InvariantCulture));
_arguments.Add("link", link.ToString()); _arguments.Add("detection", detection);
_arguments.Add("link", link);
} }
public string Key { get; } = "agate"; public string Key { get; } = "agate";

View file

@ -1,8 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Arguments
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Arguments
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Constant Rate Factor (CRF) argument /// Constant Rate Factor (CRF) argument

View file

@ -1,11 +1,4 @@
using System; namespace FFMpegCore.Arguments
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Represents parameter of concat argument /// Represents parameter of concat argument

View file

@ -13,7 +13,10 @@ public class DisableChannelArgument : IArgument
public DisableChannelArgument(Channel channel) public DisableChannelArgument(Channel channel)
{ {
if (channel == Channel.Both) if (channel == Channel.Both)
{
throw new FFMpegException(FFMpegExceptionType.Conversion, "Cannot disable both channels"); throw new FFMpegException(FFMpegExceptionType.Conversion, "Cannot disable both channels");
}
Channel = channel; Channel = channel;
} }

View file

@ -1,7 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Arguments
using System.Linq;
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Drawtext video filter argument /// Drawtext video filter argument

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Arguments
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Represents duration parameter /// Represents duration parameter

View file

@ -1,13 +1,10 @@
using System; using System.Globalization;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
public class DynamicNormalizerArgument : IAudioFilterArgument public class DynamicNormalizerArgument : IAudioFilterArgument
{ {
private readonly Dictionary<string, string> _arguments = new Dictionary<string, string>(); private readonly Dictionary<string, string> _arguments = new();
/// <summary> /// <summary>
/// Dynamic Audio Normalizer. <see href="https://ffmpeg.org/ffmpeg-filters.html#dynaudnorm"/> /// Dynamic Audio Normalizer. <see href="https://ffmpeg.org/ffmpeg-filters.html#dynaudnorm"/>
@ -23,13 +20,40 @@ public class DynamicNormalizerArgument : IAudioFilterArgument
/// <param name="compressorFactor">Set the compress factor. In range from 0.0 to 30.0. Default is 0.0 (disabled).</param> /// <param name="compressorFactor">Set the compress factor. In range from 0.0 to 30.0. Default is 0.0 (disabled).</param>
public DynamicNormalizerArgument(int frameLength = 500, int filterWindow = 31, double targetPeak = 0.95, double gainFactor = 10.0, double targetRms = 0.0, bool channelCoupling = true, bool enableDcBiasCorrection = false, bool enableAlternativeBoundary = false, double compressorFactor = 0.0) public DynamicNormalizerArgument(int frameLength = 500, int filterWindow = 31, double targetPeak = 0.95, double gainFactor = 10.0, double targetRms = 0.0, bool channelCoupling = true, bool enableDcBiasCorrection = false, bool enableAlternativeBoundary = false, double compressorFactor = 0.0)
{ {
if (frameLength < 10 || frameLength > 8000) throw new ArgumentOutOfRangeException(nameof(frameLength),"Frame length must be between 10 to 8000"); if (frameLength < 10 || frameLength > 8000)
if (filterWindow < 3 || filterWindow > 31) throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be between 3 to 31"); {
if (filterWindow % 2 == 0) throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be an odd number"); throw new ArgumentOutOfRangeException(nameof(frameLength), "Frame length must be between 10 to 8000");
if (targetPeak <= 0 || targetPeak > 1) throw new ArgumentOutOfRangeException(nameof(targetPeak)); }
if (gainFactor < 1 || gainFactor > 100) throw new ArgumentOutOfRangeException(nameof(gainFactor), "Gain factor must be between 1.0 to 100.0");
if (targetRms < 0 || targetRms > 1) throw new ArgumentOutOfRangeException(nameof(targetRms), "Target RMS must be between 0.0 and 1.0"); if (filterWindow < 3 || filterWindow > 31)
if (compressorFactor < 0 || compressorFactor > 30) throw new ArgumentOutOfRangeException(nameof(compressorFactor), "Compressor factor must be between 0.0 and 30.0"); {
throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be between 3 to 31");
}
if (filterWindow % 2 == 0)
{
throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be an odd number");
}
if (targetPeak <= 0 || targetPeak > 1)
{
throw new ArgumentOutOfRangeException(nameof(targetPeak));
}
if (gainFactor < 1 || gainFactor > 100)
{
throw new ArgumentOutOfRangeException(nameof(gainFactor), "Gain factor must be between 1.0 to 100.0");
}
if (targetRms < 0 || targetRms > 1)
{
throw new ArgumentOutOfRangeException(nameof(targetRms), "Target RMS must be between 0.0 and 1.0");
}
if (compressorFactor < 0 || compressorFactor > 30)
{
throw new ArgumentOutOfRangeException(nameof(compressorFactor), "Compressor factor must be between 0.0 and 30.0");
}
_arguments.Add("f", frameLength.ToString()); _arguments.Add("f", frameLength.ToString());
_arguments.Add("g", filterWindow.ToString()); _arguments.Add("g", filterWindow.ToString());

View file

@ -1,16 +1,13 @@
using System; using System.Globalization;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
public class HighPassFilterArgument : IAudioFilterArgument public class HighPassFilterArgument : IAudioFilterArgument
{ {
private readonly Dictionary<string, string> _arguments = new Dictionary<string, string>(); private readonly Dictionary<string, string> _arguments = new();
private readonly List<string> _widthTypes = new List<string>{"h", "q", "o", "s", "k"}; private readonly List<string> _widthTypes = new() { "h", "q", "o", "s", "k" };
private readonly List<string> _transformTypes = new List<string>{"di", "dii", "tdi", "tdii", "latt", "svf", "zdf"}; private readonly List<string> _transformTypes = new() { "di", "dii", "tdi", "tdii", "latt", "svf", "zdf" };
private readonly List<string> _precision = new List<string> { "auto", "s16", "s32", "f32", "f64" }; private readonly List<string> _precision = new() { "auto", "s16", "s32", "f32", "f64" };
/// <summary> /// <summary>
/// HighPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#highpass"/> /// HighPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#highpass"/>
/// </summary> /// </summary>
@ -26,11 +23,30 @@ public class HighPassFilterArgument : IAudioFilterArgument
/// <param name="block_size">Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just produce nasty artifacts.</param> /// <param name="block_size">Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just produce nasty artifacts.</param>
public HighPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto", int? block_size = null) public HighPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
{ {
if (frequency < 0) throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number"); if (frequency < 0)
if (poles < 1 || poles > 2) throw new ArgumentOutOfRangeException(nameof(poles), "Poles must be either 1 or 2"); {
if (!_widthTypes.Contains(width_type)) throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes.ToString()); throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number");
if (mix < 0 || mix > 1) throw new ArgumentOutOfRangeException(nameof(mix), "Mix must be between 0 and 1"); }
if (!_precision.Contains(precision)) throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision.ToString());
if (poles < 1 || poles > 2)
{
throw new ArgumentOutOfRangeException(nameof(poles), "Poles must be either 1 or 2");
}
if (!_widthTypes.Contains(width_type))
{
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes.ToString());
}
if (mix < 0 || mix > 1)
{
throw new ArgumentOutOfRangeException(nameof(mix), "Mix must be between 0 and 1");
}
if (!_precision.Contains(precision))
{
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision.ToString());
}
_arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture)); _arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture));
_arguments.Add("p", poles.ToString()); _arguments.Add("p", poles.ToString());
@ -41,11 +57,13 @@ public HighPassFilterArgument(double frequency = 3000, int poles = 2, string wid
{ {
_arguments.Add("c", channels); _arguments.Add("c", channels);
} }
_arguments.Add("n", (normalize ? 1 : 0).ToString()); _arguments.Add("n", (normalize ? 1 : 0).ToString());
if (transform != "" && _transformTypes.Contains(transform)) if (transform != "" && _transformTypes.Contains(transform))
{ {
_arguments.Add("a", transform); _arguments.Add("a", transform);
} }
_arguments.Add("r", precision); _arguments.Add("r", precision);
if (block_size != null && block_size >= 0) if (block_size != null && block_size >= 0)
{ {

View file

@ -1,6 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Arguments
namespace FFMpegCore.Arguments
{ {
public interface IDynamicArgument public interface IDynamicArgument
{ {

View file

@ -1,7 +1,4 @@
using System.Threading; namespace FFMpegCore.Arguments
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{ {
public interface IInputOutputArgument : IArgument public interface IInputOutputArgument : IArgument
{ {

View file

@ -1,8 +1,4 @@
using System.IO; namespace FFMpegCore.Arguments
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Represents input parameter /// Represents input parameter
@ -23,8 +19,10 @@ public InputArgument(string path, bool verifyExists) : this(verifyExists, path)
public void Pre() public void Pre()
{ {
if (VerifyExists && !File.Exists(FilePath)) if (VerifyExists && !File.Exists(FilePath))
{
throw new FileNotFoundException("Input file not found", FilePath); throw new FileNotFoundException("Input file not found", FilePath);
} }
}
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
public void Post() { } public void Post() { }

View file

@ -1,7 +1,4 @@
using System.Threading; namespace FFMpegCore.Arguments
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Represents an input device parameter /// Represents an input device parameter

View file

@ -1,7 +1,4 @@
using System; using System.IO.Pipes;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
@ -24,7 +21,10 @@ protected override async Task ProcessDataAsync(CancellationToken token)
{ {
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false); await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
if (!Pipe.IsConnected) if (!Pipe.IsConnected)
{
throw new OperationCanceledException(); throw new OperationCanceledException();
}
await Writer.WriteAsync(Pipe, token).ConfigureAwait(false); await Writer.WriteAsync(Pipe, token).ConfigureAwait(false);
} }
} }

View file

@ -1,16 +1,13 @@
using System; using System.Globalization;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
public class LowPassFilterArgument : IAudioFilterArgument public class LowPassFilterArgument : IAudioFilterArgument
{ {
private readonly Dictionary<string, string> _arguments = new Dictionary<string, string>(); private readonly Dictionary<string, string> _arguments = new();
private readonly List<string> _widthTypes = new List<string> { "h", "q", "o", "s", "k" }; private readonly List<string> _widthTypes = new() { "h", "q", "o", "s", "k" };
private readonly List<string> _transformTypes = new List<string> { "di", "dii", "tdi", "tdii", "latt", "svf", "zdf" }; private readonly List<string> _transformTypes = new() { "di", "dii", "tdi", "tdii", "latt", "svf", "zdf" };
private readonly List<string> _precision = new List<string> { "auto", "s16", "s32", "f32", "f64" }; private readonly List<string> _precision = new() { "auto", "s16", "s32", "f32", "f64" };
/// <summary> /// <summary>
/// LowPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#lowpass"/> /// LowPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#lowpass"/>
/// </summary> /// </summary>
@ -26,11 +23,30 @@ public class LowPassFilterArgument : IAudioFilterArgument
/// <param name="block_size">Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just produce nasty artifacts.</param> /// <param name="block_size">Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just produce nasty artifacts.</param>
public LowPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto", int? block_size = null) public LowPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
{ {
if (frequency < 0) throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number"); if (frequency < 0)
if (poles < 1 || poles > 2) throw new ArgumentOutOfRangeException(nameof(poles), "Poles must be either 1 or 2"); {
if (!_widthTypes.Contains(width_type)) throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes.ToString()); throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number");
if (mix < 0 || mix > 1) throw new ArgumentOutOfRangeException(nameof(mix), "Mix must be between 0 and 1"); }
if (!_precision.Contains(precision)) throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision.ToString());
if (poles < 1 || poles > 2)
{
throw new ArgumentOutOfRangeException(nameof(poles), "Poles must be either 1 or 2");
}
if (!_widthTypes.Contains(width_type))
{
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes.ToString());
}
if (mix < 0 || mix > 1)
{
throw new ArgumentOutOfRangeException(nameof(mix), "Mix must be between 0 and 1");
}
if (!_precision.Contains(precision))
{
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision.ToString());
}
_arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture)); _arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture));
_arguments.Add("p", poles.ToString()); _arguments.Add("p", poles.ToString());
@ -41,11 +57,13 @@ public LowPassFilterArgument(double frequency = 3000, int poles = 2, string widt
{ {
_arguments.Add("c", channels); _arguments.Add("c", channels);
} }
_arguments.Add("n", (normalize ? 1 : 0).ToString()); _arguments.Add("n", (normalize ? 1 : 0).ToString());
if (transform != "" && _transformTypes.Contains(transform)) if (transform != "" && _transformTypes.Contains(transform))
{ {
_arguments.Add("a", transform); _arguments.Add("a", transform);
} }
_arguments.Add("r", precision); _arguments.Add("r", precision);
if (block_size != null && block_size >= 0) if (block_size != null && block_size >= 0)
{ {

View file

@ -1,10 +1,4 @@
using System; namespace FFMpegCore.Arguments
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{ {
public class MapMetadataArgument : IInputArgument, IDynamicArgument public class MapMetadataArgument : IInputArgument, IDynamicArgument
{ {
@ -55,7 +49,5 @@ public void Post()
public void Pre() public void Pre()
{ {
} }
} }
} }

View file

@ -19,6 +19,7 @@ public MapStreamArgument(int streamIndex, int inputFileIndex, Channel channel =
// "Both" is not valid in this case and probably means all stream types // "Both" is not valid in this case and probably means all stream types
channel = Channel.All; channel = Channel.All;
} }
_inputFileIndex = inputFileIndex; _inputFileIndex = inputFileIndex;
_streamIndex = streamIndex; _streamIndex = streamIndex;
_channel = channel; _channel = channel;

View file

@ -1,11 +1,4 @@
using System; namespace FFMpegCore.Arguments
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{ {
public class MetaDataArgument : IInputArgument, IDynamicArgument public class MetaDataArgument : IInputArgument, IDynamicArgument
{ {
@ -21,7 +14,6 @@ public MetaDataArgument(string metaDataContent)
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
public void Pre() => File.WriteAllText(_tempFileName, _metaDataContent); public void Pre() => File.WriteAllText(_tempFileName, _metaDataContent);
public void Post() => File.Delete(_tempFileName); public void Post() => File.Delete(_tempFileName);

View file

@ -1,8 +1,4 @@
using System; using FFMpegCore.Exceptions;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Exceptions;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
@ -23,8 +19,10 @@ public OutputArgument(string path, bool overwrite = true)
public void Pre() public void Pre()
{ {
if (!Overwrite && File.Exists(Path)) if (!Overwrite && File.Exists(Path))
{
throw new FFMpegException(FFMpegExceptionType.File, "Output file already exists and overwrite is disabled"); throw new FFMpegException(FFMpegExceptionType.File, "Output file already exists and overwrite is disabled");
} }
}
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
public void Post() public void Post()
{ {

View file

@ -1,6 +1,4 @@
using System.IO.Pipes; using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
@ -20,7 +18,10 @@ protected override async Task ProcessDataAsync(CancellationToken token)
{ {
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false); await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
if (!Pipe.IsConnected) if (!Pipe.IsConnected)
{
throw new TaskCanceledException(); throw new TaskCanceledException();
}
await Reader.ReadAsync(Pipe, token).ConfigureAwait(false); await Reader.ReadAsync(Pipe, token).ConfigureAwait(false);
} }
} }

View file

@ -1,7 +1,4 @@
using System.Threading; namespace FFMpegCore.Arguments
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Represents outputting to url using supported protocols /// Represents outputting to url using supported protocols

View file

@ -1,7 +1,4 @@
using System; using FFMpegCore.Extend;
using System.Collections.Generic;
using System.Linq;
using FFMpegCore.Extend;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
@ -21,7 +18,7 @@ public PadArgument(PadOptions options)
public class PadOptions public class PadOptions
{ {
public readonly Dictionary<string, string> Parameters = new Dictionary<string, string>(); public readonly Dictionary<string, string> Parameters = new();
internal string TextInternal => string.Join(":", Parameters.Select(parameter => parameter.FormatArgumentPair(true))); internal string TextInternal => string.Join(":", Parameters.Select(parameter => parameter.FormatArgumentPair(true)));
@ -47,10 +44,12 @@ private PadOptions(string? width, string? height)
{ {
throw new Exception("At least one of the parameters must be not null"); throw new Exception("At least one of the parameters must be not null");
} }
if (width != null) if (width != null)
{ {
Parameters.Add("width", width); Parameters.Add("width", width);
} }
if (height != null) if (height != null)
{ {
Parameters.Add("height", height); Parameters.Add("height", height);

View file

@ -1,7 +1,4 @@
using System; namespace FFMpegCore.Arguments
using System.Linq;
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Mix channels with specific gain levels. /// Mix channels with specific gain levels.
@ -41,10 +38,15 @@ public PanArgument(string channelLayout, params string[] outputDefinitions)
/// </param> /// </param>
public PanArgument(int channels, params string[] outputDefinitions) public PanArgument(int channels, params string[] outputDefinitions)
{ {
if (channels <= 0) throw new ArgumentOutOfRangeException(nameof(channels)); if (channels <= 0)
{
throw new ArgumentOutOfRangeException(nameof(channels));
}
if (outputDefinitions.Length > channels) if (outputDefinitions.Length > channels)
{
throw new ArgumentException("The number of output definitions must be equal or lower than number of channels", nameof(outputDefinitions)); throw new ArgumentException("The number of output definitions must be equal or lower than number of channels", nameof(outputDefinitions));
}
ChannelLayout = $"{channels}c"; ChannelLayout = $"{channels}c";

View file

@ -1,8 +1,5 @@
using System; using System.Diagnostics;
using System.Diagnostics;
using System.IO.Pipes; using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
@ -24,7 +21,9 @@ protected PipeArgument(PipeDirection direction)
public void Pre() public void Pre()
{ {
if (Pipe != null) if (Pipe != null)
{
throw new InvalidOperationException("Pipe already has been opened"); throw new InvalidOperationException("Pipe already has been opened");
}
Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
} }
@ -50,9 +49,11 @@ public async Task During(CancellationToken cancellationToken = default)
{ {
Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}"); Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}");
if (Pipe is { IsConnected: true }) if (Pipe is { IsConnected: true })
{
Pipe.Disconnect(); Pipe.Disconnect();
} }
} }
}
protected abstract Task ProcessDataAsync(CancellationToken token); protected abstract Task ProcessDataAsync(CancellationToken token);
public abstract string Text { get; } public abstract string Text { get; }

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Arguments
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Represents seek parameter /// Represents seek parameter
@ -14,15 +12,18 @@ public SeekArgument(TimeSpan? seekTo)
SeekTo = seekTo; SeekTo = seekTo;
} }
public string Text { public string Text
get { {
get
{
if (SeekTo.HasValue) if (SeekTo.HasValue)
{ {
int hours = SeekTo.Value.Hours; var hours = SeekTo.Value.Hours;
if (SeekTo.Value.Days > 0) if (SeekTo.Value.Days > 0)
{ {
hours += SeekTo.Value.Days * 24; hours += SeekTo.Value.Days * 24;
} }
return $"-ss {hours.ToString("00")}:{SeekTo.Value.Minutes.ToString("00")}:{SeekTo.Value.Seconds.ToString("00")}.{SeekTo.Value.Milliseconds.ToString("000")}"; return $"-ss {hours.ToString("00")}:{SeekTo.Value.Minutes.ToString("00")}:{SeekTo.Value.Seconds.ToString("00")}.{SeekTo.Value.Milliseconds.ToString("000")}";
} }
else else

View file

@ -1,5 +1,4 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
using System;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
@ -14,11 +13,8 @@ public SetMirroringArgument(Mirroring mirroring)
public string Key => string.Empty; public string Key => string.Empty;
public string Value public string Value =>
{ Mirroring switch
get
{
return Mirroring switch
{ {
Mirroring.Horizontal => "hflip", Mirroring.Horizontal => "hflip",
Mirroring.Vertical => "vflip", Mirroring.Vertical => "vflip",
@ -26,5 +22,3 @@ public string Value
}; };
} }
} }
}
}

View file

@ -1,13 +1,10 @@
using System; using System.Globalization;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
public class SilenceDetectArgument : IAudioFilterArgument public class SilenceDetectArgument : IAudioFilterArgument
{ {
private readonly Dictionary<string, string> _arguments = new Dictionary<string, string>(); private readonly Dictionary<string, string> _arguments = new();
/// <summary> /// <summary>
/// Silence Detection. <see href="https://ffmpeg.org/ffmpeg-filters.html#silencedetect"/> /// Silence Detection. <see href="https://ffmpeg.org/ffmpeg-filters.html#silencedetect"/>
/// </summary> /// </summary>
@ -26,7 +23,11 @@ public SilenceDetectArgument(string noise_type = "db", double noise = 60, double
{ {
_arguments.Add("n", noise.ToString("0.00", CultureInfo.InvariantCulture)); _arguments.Add("n", noise.ToString("0.00", CultureInfo.InvariantCulture));
} }
else throw new ArgumentOutOfRangeException(nameof(noise_type), "Noise type must be either db or ar"); else
{
throw new ArgumentOutOfRangeException(nameof(noise_type), "Noise type must be either db or ar");
}
_arguments.Add("d", duration.ToString("0.00", CultureInfo.InvariantCulture)); _arguments.Add("d", duration.ToString("0.00", CultureInfo.InvariantCulture));
_arguments.Add("m", (mono ? 1 : 0).ToString()); _arguments.Add("m", (mono ? 1 : 0).ToString());
} }

View file

@ -1,7 +1,5 @@
using FFMpegCore.Extend; using System.Drawing;
using System.Collections.Generic; using FFMpegCore.Extend;
using System.Drawing;
using System.Linq;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
@ -23,7 +21,7 @@ public class SubtitleHardBurnOptions
{ {
private readonly string _subtitle; private readonly string _subtitle;
public readonly Dictionary<string, string> Parameters = new Dictionary<string, string>(); public readonly Dictionary<string, string> Parameters = new();
/// <summary> /// <summary>
/// Create a new <see cref="SubtitleHardBurnOptions"/> using a provided subtitle file or a video file /// Create a new <see cref="SubtitleHardBurnOptions"/> using a provided subtitle file or a video file
@ -110,7 +108,7 @@ public SubtitleHardBurnOptions WithParameter(string key, string value)
public class StyleOptions public class StyleOptions
{ {
public readonly Dictionary<string, string> Parameters = new Dictionary<string, string>(); public readonly Dictionary<string, string> Parameters = new();
public static StyleOptions Create() public static StyleOptions Create()
{ {

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Arguments
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Represents threads parameter /// Represents threads parameter

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Arguments
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Variable Bitrate Argument (VBR) argument /// Variable Bitrate Argument (VBR) argument

View file

@ -18,7 +18,9 @@ public VideoCodecArgument(string codec)
public VideoCodecArgument(Codec value) public VideoCodecArgument(Codec value)
{ {
if (value.Type != CodecType.Video) if (value.Type != CodecType.Video)
{
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{value.Name}\" is not a video codec"); throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{value.Name}\" is not a video codec");
}
Codec = value.Name; Codec = value.Name;
} }

View file

@ -1,6 +1,4 @@
using System.Collections.Generic; using System.Drawing;
using System.Drawing;
using System.Linq;
using FFMpegCore.Enums; using FFMpegCore.Enums;
using FFMpegCore.Exceptions; using FFMpegCore.Exceptions;
@ -20,7 +18,9 @@ public VideoFiltersArgument(VideoFilterOptions options)
private string GetText() private string GetText()
{ {
if (!Options.Arguments.Any()) if (!Options.Arguments.Any())
{
throw new FFMpegArgumentException("No video-filter arguments provided"); throw new FFMpegArgumentException("No video-filter arguments provided");
}
var arguments = Options.Arguments var arguments = Options.Arguments
.Where(arg => !string.IsNullOrEmpty(arg.Value)) .Where(arg => !string.IsNullOrEmpty(arg.Value))
@ -42,7 +42,7 @@ public interface IVideoFilterArgument
public class VideoFilterOptions public class VideoFilterOptions
{ {
public List<IVideoFilterArgument> Arguments { get; } = new List<IVideoFilterArgument>(); public List<IVideoFilterArgument> Arguments { get; } = new();
public VideoFilterOptions Scale(VideoSize videoSize) => WithArgument(new ScaleArgument(videoSize)); public VideoFilterOptions Scale(VideoSize videoSize) => WithArgument(new ScaleArgument(videoSize));
public VideoFilterOptions Scale(int width, int height) => WithArgument(new ScaleArgument(width, height)); public VideoFilterOptions Scale(int width, int height) => WithArgument(new ScaleArgument(width, height));

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Builders.MetaData
namespace FFMpegCore.Builders.MetaData
{ {
public class ChapterData public class ChapterData
{ {

View file

@ -1,6 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Builders.MetaData
namespace FFMpegCore.Builders.MetaData
{ {
public interface IReadOnlyMetaData public interface IReadOnlyMetaData

View file

@ -1,15 +1,12 @@
using System.Collections.Generic; namespace FFMpegCore.Builders.MetaData
using System.Linq;
namespace FFMpegCore.Builders.MetaData
{ {
public class MetaData : IReadOnlyMetaData public class MetaData : IReadOnlyMetaData
{ {
public Dictionary<string, string> Entries { get; private set; } public Dictionary<string, string> Entries { get; private set; }
public List<ChapterData> Chapters { get; private set; } public List<ChapterData> Chapters { get; private set; }
IReadOnlyList<ChapterData> IReadOnlyMetaData.Chapters => this.Chapters; IReadOnlyList<ChapterData> IReadOnlyMetaData.Chapters => Chapters;
IReadOnlyDictionary<string, string> IReadOnlyMetaData.Entries => this.Entries; IReadOnlyDictionary<string, string> IReadOnlyMetaData.Entries => Entries;
public MetaData() public MetaData()
{ {

View file

@ -1,18 +1,14 @@
using System; namespace FFMpegCore.Builders.MetaData
using System.Collections.Generic;
using System.Linq;
namespace FFMpegCore.Builders.MetaData
{ {
public class MetaDataBuilder public class MetaDataBuilder
{ {
private MetaData _metaData = new MetaData(); private readonly MetaData _metaData = new();
public MetaDataBuilder WithEntry(string key, string entry) public MetaDataBuilder WithEntry(string key, string entry)
{ {
if (_metaData.Entries.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value)) if (_metaData.Entries.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value))
{ {
entry = String.Concat(value, "; ", entry); entry = string.Concat(value, "; ", entry);
} }
_metaData.Entries[key] = entry; _metaData.Entries[key] = entry;
@ -20,10 +16,10 @@ public MetaDataBuilder WithEntry(string key, string entry)
} }
public MetaDataBuilder WithEntry(string key, params string[] values) public MetaDataBuilder WithEntry(string key, params string[] values)
=> this.WithEntry(key, String.Join("; ", values)); => WithEntry(key, string.Join("; ", values));
public MetaDataBuilder WithEntry(string key, IEnumerable<string> values) public MetaDataBuilder WithEntry(string key, IEnumerable<string> values)
=> this.WithEntry(key, String.Join("; ", values)); => WithEntry(key, string.Join("; ", values));
public MetaDataBuilder AddChapter(ChapterData chapterData) public MetaDataBuilder AddChapter(ChapterData chapterData)
{ {
@ -33,7 +29,7 @@ public MetaDataBuilder AddChapter(ChapterData chapterData)
public MetaDataBuilder AddChapters<T>(IEnumerable<T> values, Func<T, (TimeSpan duration, string title)> chapterGetter) public MetaDataBuilder AddChapters<T>(IEnumerable<T> values, Func<T, (TimeSpan duration, string title)> chapterGetter)
{ {
foreach (T value in values) foreach (var value in values)
{ {
var (duration, title) = chapterGetter(value); var (duration, title) = chapterGetter(value);
AddChapter(duration, title); AddChapter(duration, title);
@ -46,13 +42,13 @@ public MetaDataBuilder AddChapter(TimeSpan duration, string? title = null)
{ {
var start = _metaData.Chapters.LastOrDefault()?.End ?? TimeSpan.Zero; var start = _metaData.Chapters.LastOrDefault()?.End ?? TimeSpan.Zero;
var end = start + duration; var end = start + duration;
title = String.IsNullOrEmpty(title) ? $"Chapter {_metaData.Chapters.Count + 1}" : title; title = string.IsNullOrEmpty(title) ? $"Chapter {_metaData.Chapters.Count + 1}" : title;
_metaData.Chapters.Add(new ChapterData _metaData.Chapters.Add(new ChapterData
( (
start: start, start: start,
end: end, end: end,
title: title ?? String.Empty title: title ?? string.Empty
)); ));
return this; return this;
@ -102,8 +98,6 @@ public MetaDataBuilder AddChapter(TimeSpan duration, string? title = null)
//encoder=Lavf58.47.100 //encoder=Lavf58.47.100
public MetaDataBuilder WithEncoder(string value) => WithEntry("encoder", value); public MetaDataBuilder WithEncoder(string value) => WithEntry("encoder", value);
public ReadOnlyMetaData Build() => new(_metaData);
public ReadOnlyMetaData Build() => new ReadOnlyMetaData(_metaData);
} }
} }

View file

@ -1,11 +1,10 @@
using System.Linq; using System.Text;
using System.Text;
namespace FFMpegCore.Builders.MetaData namespace FFMpegCore.Builders.MetaData
{ {
public class MetaDataSerializer public class MetaDataSerializer
{ {
public static readonly MetaDataSerializer Instance = new MetaDataSerializer(); public static readonly MetaDataSerializer Instance = new();
public string Serialize(IReadOnlyMetaData metaData) public string Serialize(IReadOnlyMetaData metaData)
{ {
@ -17,7 +16,7 @@ public string Serialize(IReadOnlyMetaData metaData)
sb.AppendLine($"{value.Key}={value.Value}"); sb.AppendLine($"{value.Key}={value.Value}");
} }
int chapterNumber = 0; var chapterNumber = 0;
foreach (var chapter in metaData.Chapters ?? Enumerable.Empty<ChapterData>()) foreach (var chapter in metaData.Chapters ?? Enumerable.Empty<ChapterData>())
{ {
chapterNumber++; chapterNumber++;

View file

@ -1,7 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Builders.MetaData
using System.Linq;
namespace FFMpegCore.Builders.MetaData
{ {
public class ReadOnlyMetaData : IReadOnlyMetaData public class ReadOnlyMetaData : IReadOnlyMetaData
{ {

View file

@ -1,6 +1,5 @@
using FFMpegCore.Exceptions; using System.Text.RegularExpressions;
using System; using FFMpegCore.Exceptions;
using System.Text.RegularExpressions;
namespace FFMpegCore.Enums namespace FFMpegCore.Enums
{ {
@ -13,8 +12,8 @@ public enum FeatureStatus
public class Codec 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 _codecsFormatRegex = new(@"([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 _decodersEncodersFormatRegex = new(@"([VASD\.])([F\.])([S\.])([X\.])([B\.])([D\.])\s+([a-z0-9_-]+)\s+(.+)");
public class FeatureLevel public class FeatureLevel
{ {
@ -133,7 +132,9 @@ internal static bool TryParseFromEncodersDecoders(string line, out Codec codec,
internal void Merge(Codec other) internal void Merge(Codec other)
{ {
if (Name != other.Name) if (Name != other.Name)
{
throw new FFMpegException(FFMpegExceptionType.Operation, "different codecs enable to merge"); throw new FFMpegException(FFMpegExceptionType.Operation, "different codecs enable to merge");
}
Type |= other.Type; Type |= other.Type;
DecodingSupported |= other.DecodingSupported; DecodingSupported |= other.DecodingSupported;
@ -146,7 +147,9 @@ internal void Merge(Codec other)
DecoderFeatureLevel.Merge(other.DecoderFeatureLevel); DecoderFeatureLevel.Merge(other.DecoderFeatureLevel);
if (Description != other.Description) if (Description != other.Description)
{
Description += "\r\n" + other.Description; Description += "\r\n" + other.Description;
} }
} }
} }
}

View file

@ -4,7 +4,7 @@ namespace FFMpegCore.Enums
{ {
public class ContainerFormat public class ContainerFormat
{ {
private static readonly Regex FormatRegex = new Regex(@"([D ])([E ])\s+([a-z0-9_]+)\s+(.+)"); private static readonly Regex FormatRegex = new(@"([D ])([E ])\s+([a-z0-9_]+)\s+(.+)");
public string Name { get; private set; } public string Name { get; private set; }
public bool DemuxingSupported { get; private set; } public bool DemuxingSupported { get; private set; }
@ -16,7 +16,10 @@ public string Extension
get get
{ {
if (GlobalFFOptions.Current.ExtensionOverrides.ContainsKey(Name)) if (GlobalFFOptions.Current.ExtensionOverrides.ContainsKey(Name))
{
return GlobalFFOptions.Current.ExtensionOverrides[Name]; return GlobalFFOptions.Current.ExtensionOverrides[Name];
}
return "." + Name; return "." + Name;
} }
} }

View file

@ -84,5 +84,4 @@ public static string StreamType(this Channel channel)
}; };
} }
} }
} }

View file

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

View file

@ -7,6 +7,7 @@ public enum HardwareAccelerationDevice
DXVA2, DXVA2,
QSV, QSV,
CUVID, CUVID,
CUDA,
VDPAU, VDPAU,
VAAPI, VAAPI,
LibMFX LibMFX

View file

@ -4,7 +4,7 @@ namespace FFMpegCore.Enums
{ {
public class PixelFormat 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(@"([I\.])([O\.])([H\.])([P\.])([B\.])\s+(\S+)\s+([0-9]+)\s+([0-9]+)");
public bool InputConversionSupported { get; private set; } public bool InputConversionSupported { get; private set; }
public bool OutputConversionSupported { get; private set; } public bool OutputConversionSupported { get; private set; }
@ -41,10 +41,16 @@ internal static bool TryParse(string line, out PixelFormat fmt)
fmt.IsPaletted = match.Groups[4].Value != "."; fmt.IsPaletted = match.Groups[4].Value != ".";
fmt.IsBitstream = match.Groups[5].Value != "."; fmt.IsBitstream = match.Groups[5].Value != ".";
if (!int.TryParse(match.Groups[7].Value, out var nbComponents)) if (!int.TryParse(match.Groups[7].Value, out var nbComponents))
{
return false; return false;
}
fmt.Components = nbComponents; fmt.Components = nbComponents;
if (!int.TryParse(match.Groups[8].Value, out var bpp)) if (!int.TryParse(match.Groups[8].Value, out var bpp))
{
return false; return false;
}
fmt.BitsPerPixel = bpp; fmt.BitsPerPixel = bpp;
return true; return true;

View file

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

View file

@ -1,69 +1,11 @@
using FFMpegCore.Enums; using System.Drawing;
using FFMpegCore.Enums;
using FFMpegCore.Exceptions; using FFMpegCore.Exceptions;
using FFMpegCore.Helpers; using FFMpegCore.Helpers;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Instances; using Instances;
namespace FFMpegCore namespace FFMpegCore
{ {
public static class SnapshotArgumentBuilder
{
public static (FFMpegArguments, Action<FFMpegArgumentOptions> outputOptions) BuildSnapshotArguments(
string input,
IMediaAnalysis source,
Size? size = null,
TimeSpan? captureTime = null,
int? streamIndex = null,
int inputFileIndex = 0)
{
captureTime ??= TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3);
size = PrepareSnapshotSize(source, size);
streamIndex ??= source.PrimaryVideoStream?.Index
?? source.VideoStreams.FirstOrDefault()?.Index
?? 0;
return (FFMpegArguments
.FromFileInput(input, false, options => options
.Seek(captureTime)),
options => options
.SelectStream((int)streamIndex, inputFileIndex)
.WithVideoCodec(VideoCodec.Png)
.WithFrameOutputCount(1)
.Resize(size));
}
private static Size? PrepareSnapshotSize(IMediaAnalysis source, Size? wantedSize)
{
if (wantedSize == null || (wantedSize.Value.Height <= 0 && wantedSize.Value.Width <= 0) || source.PrimaryVideoStream == null)
return null;
var currentSize = new Size(source.PrimaryVideoStream.Width, source.PrimaryVideoStream.Height);
if (source.PrimaryVideoStream.Rotation == 90 || source.PrimaryVideoStream.Rotation == 180)
currentSize = new Size(source.PrimaryVideoStream.Height, source.PrimaryVideoStream.Width);
if (wantedSize.Value.Width != currentSize.Width || wantedSize.Value.Height != currentSize.Height)
{
if (wantedSize.Value.Width <= 0 && wantedSize.Value.Height > 0)
{
var ratio = (double)wantedSize.Value.Height / currentSize.Height;
return new Size((int)(currentSize.Width * ratio), (int)(currentSize.Height * ratio));
}
if (wantedSize.Value.Height <= 0 && wantedSize.Value.Width > 0)
{
var ratio = (double)wantedSize.Value.Width / currentSize.Width;
return new Size((int)(currentSize.Width * ratio), (int)(currentSize.Height * ratio));
}
return wantedSize;
}
return null;
}
}
public static class FFMpeg public static class FFMpeg
{ {
/// <summary> /// <summary>
@ -79,7 +21,9 @@ public static class FFMpeg
public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
{ {
if (Path.GetExtension(output) != FileExtension.Png) if (Path.GetExtension(output) != FileExtension.Png)
{
output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Png); output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Png);
}
var source = FFProbe.Analyse(input); var source = FFProbe.Analyse(input);
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
@ -101,7 +45,9 @@ public static bool Snapshot(string input, string output, Size? size = null, Time
public static async Task<bool> SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) public static async Task<bool> SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
{ {
if (Path.GetExtension(output) != FileExtension.Png) if (Path.GetExtension(output) != FileExtension.Png)
{
output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Png); output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Png);
}
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false); var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
@ -111,6 +57,73 @@ public static async Task<bool> SnapshotAsync(string input, string output, Size?
.ProcessAsynchronously(); .ProcessAsynchronously();
} }
/// <summary>
/// Converts an image sequence to a video.
/// </summary>
/// <param name="output">Output video file.</param>
/// <param name="frameRate">FPS</param>
/// <param name="images">Image sequence collection</param>
/// <returns>Output video information.</returns>
public static bool JoinImageSequence(string output, double frameRate = 30, params string[] images)
{
int? width = null, height = null;
var tempFolderName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, Guid.NewGuid().ToString());
var temporaryImageFiles = images.Select((imagePath, index) =>
{
var analysis = FFProbe.Analyse(imagePath);
FFMpegHelper.ConversionSizeExceptionCheck(analysis.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Height);
width ??= analysis.PrimaryVideoStream.Width;
height ??= analysis.PrimaryVideoStream.Height;
var destinationPath = Path.Combine(tempFolderName, $"{index.ToString().PadLeft(9, '0')}{Path.GetExtension(imagePath)}");
Directory.CreateDirectory(tempFolderName);
File.Copy(imagePath, destinationPath);
return destinationPath;
}).ToArray();
try
{
return FFMpegArguments
.FromFileInput(Path.Combine(tempFolderName, "%09d.png"), false)
.OutputToFile(output, true, options => options
.ForcePixelFormat("yuv420p")
.Resize(width!.Value, height!.Value)
.WithFramerate(frameRate))
.ProcessSynchronously();
}
finally
{
Cleanup(temporaryImageFiles);
Directory.Delete(tempFolderName);
}
}
/// <summary>
/// Adds a poster image to an audio file.
/// </summary>
/// <param name="image">Source image file.</param>
/// <param name="audio">Source audio file.</param>
/// <param name="output">Output video file.</param>
/// <returns></returns>
public static bool PosterWithAudio(string image, string audio, string output)
{
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
var analysis = FFProbe.Analyse(image);
FFMpegHelper.ConversionSizeExceptionCheck(analysis.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Height);
return FFMpegArguments
.FromFileInput(image, false, options => options
.Loop(1)
.ForceFormat("image2"))
.AddFileInput(audio)
.OutputToFile(output, true, options => options
.ForcePixelFormat("yuv420p")
.WithVideoCodec(VideoCodec.LibX264)
.WithConstantRateFactor(21)
.WithAudioBitrate(AudioQuality.Normal)
.UsingShortest())
.ProcessSynchronously();
}
/// <summary> /// <summary>
/// Convert a video do a different format. /// Convert a video do a different format.
@ -140,7 +153,9 @@ public static bool Convert(
var outputSize = new Size((int)(source.PrimaryVideoStream!.Width / scale), (int)(source.PrimaryVideoStream.Height / scale)); var outputSize = new Size((int)(source.PrimaryVideoStream!.Width / scale), (int)(source.PrimaryVideoStream.Height / scale));
if (outputSize.Width % 2 != 0) if (outputSize.Width % 2 != 0)
{
outputSize.Width += 1; outputSize.Width += 1;
}
return format.Name switch return format.Name switch
{ {
@ -235,7 +250,9 @@ public static bool SaveM3U8Stream(Uri uri, string output)
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4); FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
if (uri.Scheme != "http" && uri.Scheme != "https") if (uri.Scheme != "http" && uri.Scheme != "https")
{
throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream."); throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream.");
}
return FFMpegArguments return FFMpegArguments
.FromUrlInput(uri) .FromUrlInput(uri)
@ -315,12 +332,16 @@ internal static IReadOnlyList<PixelFormat> GetPixelFormatsInternal()
processArguments.OutputDataReceived += (e, data) => processArguments.OutputDataReceived += (e, data) =>
{ {
if (PixelFormat.TryParse(data, out var format)) if (PixelFormat.TryParse(data, out var format))
{
list.Add(format); list.Add(format);
}
}; };
var result = processArguments.StartAndWaitForExit(); var result = processArguments.StartAndWaitForExit();
if (result.ExitCode != 0) if (result.ExitCode != 0)
{
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData)); throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
}
return list.AsReadOnly(); return list.AsReadOnly();
} }
@ -328,7 +349,10 @@ internal static IReadOnlyList<PixelFormat> GetPixelFormatsInternal()
public static IReadOnlyList<PixelFormat> GetPixelFormats() public static IReadOnlyList<PixelFormat> GetPixelFormats()
{ {
if (!GlobalFFOptions.Current.UseCache) if (!GlobalFFOptions.Current.UseCache)
{
return GetPixelFormatsInternal(); return GetPixelFormatsInternal();
}
return FFMpegCache.PixelFormats.Values.ToList().AsReadOnly(); return FFMpegCache.PixelFormats.Values.ToList().AsReadOnly();
} }
@ -340,13 +364,18 @@ public static bool TryGetPixelFormat(string name, out PixelFormat format)
return format != null; return format != null;
} }
else else
{
return FFMpegCache.PixelFormats.TryGetValue(name, out format); return FFMpegCache.PixelFormats.TryGetValue(name, out format);
} }
}
public static PixelFormat GetPixelFormat(string name) public static PixelFormat GetPixelFormat(string name)
{ {
if (TryGetPixelFormat(name, out var fmt)) if (TryGetPixelFormat(name, out var fmt))
{
return fmt; return fmt;
}
throw new FFMpegException(FFMpegExceptionType.Operation, $"Pixel format \"{name}\" not supported"); throw new FFMpegException(FFMpegExceptionType.Operation, $"Pixel format \"{name}\" not supported");
} }
#endregion #endregion
@ -362,14 +391,23 @@ private static void ParsePartOfCodecs(Dictionary<string, Codec> codecs, string a
{ {
var codec = parser(data); var codec = parser(data);
if (codec != null) if (codec != null)
{
if (codecs.TryGetValue(codec.Name, out var parentCodec)) if (codecs.TryGetValue(codec.Name, out var parentCodec))
{
parentCodec.Merge(codec); parentCodec.Merge(codec);
}
else else
{
codecs.Add(codec.Name, codec); codecs.Add(codec.Name, codec);
}
}
}; };
var result = processArguments.StartAndWaitForExit(); var result = processArguments.StartAndWaitForExit();
if (result.ExitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData)); if (result.ExitCode != 0)
{
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
}
} }
internal static Dictionary<string, Codec> GetCodecsInternal() internal static Dictionary<string, Codec> GetCodecsInternal()
@ -378,19 +416,28 @@ internal static Dictionary<string, Codec> GetCodecsInternal()
ParsePartOfCodecs(res, "-codecs", (s) => ParsePartOfCodecs(res, "-codecs", (s) =>
{ {
if (Codec.TryParseFromCodecs(s, out var codec)) if (Codec.TryParseFromCodecs(s, out var codec))
{
return codec; return codec;
}
return null; return null;
}); });
ParsePartOfCodecs(res, "-encoders", (s) => ParsePartOfCodecs(res, "-encoders", (s) =>
{ {
if (Codec.TryParseFromEncodersDecoders(s, out var codec, true)) if (Codec.TryParseFromEncodersDecoders(s, out var codec, true))
{
return codec; return codec;
}
return null; return null;
}); });
ParsePartOfCodecs(res, "-decoders", (s) => ParsePartOfCodecs(res, "-decoders", (s) =>
{ {
if (Codec.TryParseFromEncodersDecoders(s, out var codec, false)) if (Codec.TryParseFromEncodersDecoders(s, out var codec, false))
{
return codec; return codec;
}
return null; return null;
}); });
@ -400,14 +447,20 @@ internal static Dictionary<string, Codec> GetCodecsInternal()
public static IReadOnlyList<Codec> GetCodecs() public static IReadOnlyList<Codec> GetCodecs()
{ {
if (!GlobalFFOptions.Current.UseCache) if (!GlobalFFOptions.Current.UseCache)
{
return GetCodecsInternal().Values.ToList().AsReadOnly(); return GetCodecsInternal().Values.ToList().AsReadOnly();
}
return FFMpegCache.Codecs.Values.ToList().AsReadOnly(); return FFMpegCache.Codecs.Values.ToList().AsReadOnly();
} }
public static IReadOnlyList<Codec> GetCodecs(CodecType type) public static IReadOnlyList<Codec> GetCodecs(CodecType type)
{ {
if (!GlobalFFOptions.Current.UseCache) if (!GlobalFFOptions.Current.UseCache)
{
return GetCodecsInternal().Values.Where(x => x.Type == type).ToList().AsReadOnly(); return GetCodecsInternal().Values.Where(x => x.Type == type).ToList().AsReadOnly();
}
return FFMpegCache.Codecs.Values.Where(x => x.Type == type).ToList().AsReadOnly(); return FFMpegCache.Codecs.Values.Where(x => x.Type == type).ToList().AsReadOnly();
} }
@ -424,13 +477,18 @@ public static bool TryGetCodec(string name, out Codec codec)
return codec != null; return codec != null;
} }
else else
{
return FFMpegCache.Codecs.TryGetValue(name, out codec); return FFMpegCache.Codecs.TryGetValue(name, out codec);
} }
}
public static Codec GetCodec(string name) public static Codec GetCodec(string name)
{ {
if (TryGetCodec(name, out var codec) && codec != null) if (TryGetCodec(name, out var codec) && codec != null)
{
return codec; return codec;
}
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{name}\" not supported"); throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{name}\" not supported");
} }
#endregion #endregion
@ -445,11 +503,16 @@ internal static IReadOnlyList<ContainerFormat> GetContainersFormatsInternal()
instance.OutputDataReceived += (e, data) => instance.OutputDataReceived += (e, data) =>
{ {
if (ContainerFormat.TryParse(data, out var fmt)) if (ContainerFormat.TryParse(data, out var fmt))
{
list.Add(fmt); list.Add(fmt);
}
}; };
var result = instance.StartAndWaitForExit(); var result = instance.StartAndWaitForExit();
if (result.ExitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData)); if (result.ExitCode != 0)
{
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
}
return list.AsReadOnly(); return list.AsReadOnly();
} }
@ -457,7 +520,10 @@ internal static IReadOnlyList<ContainerFormat> GetContainersFormatsInternal()
public static IReadOnlyList<ContainerFormat> GetContainerFormats() public static IReadOnlyList<ContainerFormat> GetContainerFormats()
{ {
if (!GlobalFFOptions.Current.UseCache) if (!GlobalFFOptions.Current.UseCache)
{
return GetContainersFormatsInternal(); return GetContainersFormatsInternal();
}
return FFMpegCache.ContainerFormats.Values.ToList().AsReadOnly(); return FFMpegCache.ContainerFormats.Values.ToList().AsReadOnly();
} }
@ -469,13 +535,18 @@ public static bool TryGetContainerFormat(string name, out ContainerFormat fmt)
return fmt != null; return fmt != null;
} }
else else
{
return FFMpegCache.ContainerFormats.TryGetValue(name, out fmt); return FFMpegCache.ContainerFormats.TryGetValue(name, out fmt);
} }
}
public static ContainerFormat GetContainerFormat(string name) public static ContainerFormat GetContainerFormat(string name)
{ {
if (TryGetContainerFormat(name, out var fmt)) if (TryGetContainerFormat(name, out var fmt))
{
return fmt; return fmt;
}
throw new FFMpegException(FFMpegExceptionType.Operation, $"Container format \"{name}\" not supported"); throw new FFMpegException(FFMpegExceptionType.Operation, $"Container format \"{name}\" not supported");
} }
#endregion #endregion
@ -485,8 +556,10 @@ private static void Cleanup(IEnumerable<string> pathList)
foreach (var path in pathList) foreach (var path in pathList)
{ {
if (File.Exists(path)) if (File.Exists(path))
{
File.Delete(path); File.Delete(path);
} }
} }
} }
} }
}

View file

@ -1,7 +1,4 @@
using System; using System.Drawing;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using FFMpegCore.Arguments; using FFMpegCore.Arguments;
using FFMpegCore.Enums; using FFMpegCore.Enums;
@ -20,8 +17,6 @@ internal FFMpegArgumentOptions() { }
public FFMpegArgumentOptions Resize(int width, int height) => WithArgument(new SizeArgument(width, height)); public FFMpegArgumentOptions Resize(int width, int height) => WithArgument(new SizeArgument(width, height));
public FFMpegArgumentOptions Resize(Size? size) => WithArgument(new SizeArgument(size)); public FFMpegArgumentOptions Resize(Size? size) => WithArgument(new SizeArgument(size));
public FFMpegArgumentOptions WithBitStreamFilter(Channel channel, Filter filter) => WithArgument(new BitStreamFilterArgument(channel, filter)); public FFMpegArgumentOptions WithBitStreamFilter(Channel channel, Filter filter) => WithArgument(new BitStreamFilterArgument(channel, filter));
public FFMpegArgumentOptions WithConstantRateFactor(int crf) => WithArgument(new ConstantRateFactorArgument(crf)); public FFMpegArgumentOptions WithConstantRateFactor(int crf) => WithArgument(new ConstantRateFactorArgument(crf));
public FFMpegArgumentOptions CopyChannel(Channel channel = Channel.Both) => WithArgument(new CopyArgument(channel)); public FFMpegArgumentOptions CopyChannel(Channel channel = Channel.Both) => WithArgument(new CopyArgument(channel));
@ -81,7 +76,6 @@ public FFMpegArgumentOptions DeselectStreams(IEnumerable<int> streamIndices, int
public FFMpegArgumentOptions WithAudibleActivationBytes(string activationBytes) => WithArgument(new AudibleEncryptionKeyArgument(activationBytes)); public FFMpegArgumentOptions WithAudibleActivationBytes(string activationBytes) => WithArgument(new AudibleEncryptionKeyArgument(activationBytes));
public FFMpegArgumentOptions WithTagVersion(int id3v2Version = 3) => WithArgument(new ID3V2VersionArgument(id3v2Version)); public FFMpegArgumentOptions WithTagVersion(int id3v2Version = 3) => WithArgument(new ID3V2VersionArgument(id3v2Version));
public FFMpegArgumentOptions WithArgument(IArgument argument) public FFMpegArgumentOptions WithArgument(IArgument argument)
{ {
Arguments.Add(argument); Arguments.Add(argument);

View file

@ -1,20 +1,16 @@
using FFMpegCore.Exceptions; using System.Diagnostics;
using FFMpegCore.Helpers;
using Instances;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Enums; using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
using FFMpegCore.Helpers;
using Instances;
namespace FFMpegCore namespace FFMpegCore
{ {
public class FFMpegArgumentProcessor public class FFMpegArgumentProcessor
{ {
private static readonly Regex ProgressRegex = new Regex(@"time=(\d\d:\d\d:\d\d.\d\d?)", RegexOptions.Compiled); private static readonly Regex ProgressRegex = new(@"time=(\d\d:\d\d:\d\d.\d\d?)", RegexOptions.Compiled);
private readonly List<Action<FFOptions>> _configurations; private readonly List<Action<FFOptions>> _configurations;
private readonly FFMpegArguments _ffMpegArguments; private readonly FFMpegArguments _ffMpegArguments;
private Action<double>? _onPercentageProgress; private Action<double>? _onPercentageProgress;
@ -110,8 +106,10 @@ public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOpti
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
if (throwOnError) if (throwOnError)
{
throw; throw;
} }
}
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>()); return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
} }
@ -129,8 +127,10 @@ public async Task<bool> ProcessAsynchronously(bool throwOnError = true, FFOption
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
if (throwOnError) if (throwOnError)
{
throw; throw;
} }
}
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>()); return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
} }
@ -154,6 +154,7 @@ void OnCancelEvent(object sender, int timeout)
instance.Kill(); instance.Kill();
} }
} }
CancelEvent += OnCancelEvent; CancelEvent += OnCancelEvent;
try try
@ -181,10 +182,15 @@ await Task.WhenAll(instance.WaitForExitAsync().ContinueWith(t =>
private bool HandleCompletion(bool throwOnError, int exitCode, IReadOnlyList<string> errorData) private bool HandleCompletion(bool throwOnError, int exitCode, IReadOnlyList<string> errorData)
{ {
if (throwOnError && exitCode != 0) if (throwOnError && exitCode != 0)
{
throw new FFMpegException(FFMpegExceptionType.Process, $"ffmpeg exited with non-zero exit-code ({exitCode} - {string.Join("\n", errorData)})", null, string.Join("\n", errorData)); throw new FFMpegException(FFMpegExceptionType.Process, $"ffmpeg exited with non-zero exit-code ({exitCode} - {string.Join("\n", errorData)})", null, string.Join("\n", errorData));
}
_onPercentageProgress?.Invoke(100.0); _onPercentageProgress?.Invoke(100.0);
if (_totalTimespan.HasValue) _onTimeProgress?.Invoke(_totalTimespan.Value); if (_totalTimespan.HasValue)
{
_onTimeProgress?.Invoke(_totalTimespan.Value);
}
return exitCode == 0; return exitCode == 0;
} }
@ -207,16 +213,18 @@ private ProcessArguments PrepareProcessArguments(FFOptions ffOptions,
FFMpegHelper.RootExceptionCheck(); FFMpegHelper.RootExceptionCheck();
FFMpegHelper.VerifyFFMpegExists(ffOptions); FFMpegHelper.VerifyFFMpegExists(ffOptions);
string? arguments = _ffMpegArguments.Text; var arguments = _ffMpegArguments.Text;
//If local loglevel is null, set the global. //If local loglevel is null, set the global.
if (_logLevel == null) if (_logLevel == null)
{
_logLevel = ffOptions.LogLevel; _logLevel = ffOptions.LogLevel;
}
//If neither local nor global loglevel is null, set the argument. //If neither local nor global loglevel is null, set the argument.
if (_logLevel != null) if (_logLevel != null)
{ {
string normalizedLogLevel = _logLevel.ToString() var normalizedLogLevel = _logLevel.ToString()
.ToLower(); .ToLower();
arguments += $" -v {normalizedLogLevel}"; arguments += $" -v {normalizedLogLevel}";
} }
@ -233,10 +241,14 @@ private ProcessArguments PrepareProcessArguments(FFOptions ffOptions,
cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource = new CancellationTokenSource();
if (_onOutput != null) if (_onOutput != null)
{
processArguments.OutputDataReceived += OutputData; processArguments.OutputDataReceived += OutputData;
}
if (_onError != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null)) if (_onError != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null))
{
processArguments.ErrorDataReceived += ErrorData; processArguments.ErrorDataReceived += ErrorData;
}
return processArguments; return processArguments;
} }
@ -246,12 +258,19 @@ private void ErrorData(object sender, string msg)
_onError?.Invoke(msg); _onError?.Invoke(msg);
var match = ProgressRegex.Match(msg); var match = ProgressRegex.Match(msg);
if (!match.Success) return; if (!match.Success)
{
return;
}
var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture); var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
_onTimeProgress?.Invoke(processed); _onTimeProgress?.Invoke(processed);
if (_onPercentageProgress == null || _totalTimespan == null) return; if (_onPercentageProgress == null || _totalTimespan == null)
{
return;
}
var percentage = Math.Round(processed.TotalSeconds / _totalTimespan.Value.TotalSeconds * 100, 2); var percentage = Math.Round(processed.TotalSeconds / _totalTimespan.Value.TotalSeconds * 100, 2);
_onPercentageProgress(percentage); _onPercentageProgress(percentage);
} }

View file

@ -1,11 +1,4 @@
using System; using FFMpegCore.Arguments;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Arguments;
using FFMpegCore.Builders.MetaData; using FFMpegCore.Builders.MetaData;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
@ -13,7 +6,7 @@ namespace FFMpegCore
{ {
public sealed class FFMpegArguments : FFMpegArgumentsBase public sealed class FFMpegArguments : FFMpegArgumentsBase
{ {
private readonly FFMpegGlobalArguments _globalArguments = new FFMpegGlobalArguments(); private readonly FFMpegGlobalArguments _globalArguments = new();
private FFMpegArguments() { } private FFMpegArguments() { }
@ -33,7 +26,6 @@ private string GetText()
public static FFMpegArguments FromDeviceInput(string device, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new InputDeviceArgument(device), addArguments); public static FFMpegArguments FromDeviceInput(string device, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new InputDeviceArgument(device), addArguments);
public static FFMpegArguments FromPipeInput(IPipeSource sourcePipe, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new InputPipeArgument(sourcePipe), addArguments); public static FFMpegArguments FromPipeInput(IPipeSource sourcePipe, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new InputPipeArgument(sourcePipe), addArguments);
public FFMpegArguments WithGlobalOptions(Action<FFMpegGlobalArguments> configureOptions) public FFMpegArguments WithGlobalOptions(Action<FFMpegGlobalArguments> configureOptions)
{ {
configureOptions(_globalArguments); configureOptions(_globalArguments);
@ -50,7 +42,6 @@ public FFMpegArguments WithGlobalOptions(Action<FFMpegGlobalArguments> configure
public FFMpegArguments AddMetaData(string content, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new MetaDataArgument(content), addArguments); public FFMpegArguments AddMetaData(string content, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new MetaDataArgument(content), addArguments);
public FFMpegArguments AddMetaData(IReadOnlyMetaData metaData, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new MetaDataArgument(MetaDataSerializer.Instance.Serialize(metaData)), addArguments); public FFMpegArguments AddMetaData(IReadOnlyMetaData metaData, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new MetaDataArgument(MetaDataSerializer.Instance.Serialize(metaData)), addArguments);
/// <summary> /// <summary>
/// Maps the metadata of the given stream /// Maps the metadata of the given stream
/// </summary> /// </summary>
@ -83,8 +74,10 @@ private FFMpegArgumentProcessor ToProcessor(IOutputArgument argument, Action<FFM
internal void Pre() internal void Pre()
{ {
foreach (var argument in Arguments.OfType<IInputOutputArgument>()) foreach (var argument in Arguments.OfType<IInputOutputArgument>())
{
argument.Pre(); argument.Pre();
} }
}
internal async Task During(CancellationToken cancellationToken = default) internal async Task During(CancellationToken cancellationToken = default)
{ {
var inputOutputArguments = Arguments.OfType<IInputOutputArgument>(); var inputOutputArguments = Arguments.OfType<IInputOutputArgument>();
@ -93,7 +86,9 @@ internal async Task During(CancellationToken cancellationToken = default)
internal void Post() internal void Post()
{ {
foreach (var argument in Arguments.OfType<IInputOutputArgument>()) foreach (var argument in Arguments.OfType<IInputOutputArgument>())
{
argument.Post(); argument.Post();
} }
} }
} }
}

View file

@ -1,10 +1,9 @@
using System.Collections.Generic; using FFMpegCore.Arguments;
using FFMpegCore.Arguments;
namespace FFMpegCore namespace FFMpegCore
{ {
public abstract class FFMpegArgumentsBase public abstract class FFMpegArgumentsBase
{ {
internal readonly List<IArgument> Arguments = new List<IArgument>(); internal readonly List<IArgument> Arguments = new();
} }
} }

View file

@ -1,12 +1,10 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
using System.Collections.Generic;
using System.Linq;
namespace FFMpegCore namespace FFMpegCore
{ {
static class FFMpegCache internal static class FFMpegCache
{ {
private static readonly object _syncObject = new object(); private static readonly object _syncObject = new();
private static Dictionary<string, PixelFormat>? _pixelFormats; private static Dictionary<string, PixelFormat>? _pixelFormats;
private static Dictionary<string, Codec>? _codecs; private static Dictionary<string, Codec>? _codecs;
private static Dictionary<string, ContainerFormat>? _containers; private static Dictionary<string, ContainerFormat>? _containers;
@ -16,39 +14,54 @@ public static IReadOnlyDictionary<string, PixelFormat> PixelFormats
get get
{ {
if (_pixelFormats == null) //First check not thread safe if (_pixelFormats == null) //First check not thread safe
{
lock (_syncObject) lock (_syncObject)
{
if (_pixelFormats == null)//Second check thread safe if (_pixelFormats == null)//Second check thread safe
{
_pixelFormats = FFMpeg.GetPixelFormatsInternal().ToDictionary(x => x.Name); _pixelFormats = FFMpeg.GetPixelFormatsInternal().ToDictionary(x => x.Name);
}
}
}
return _pixelFormats; return _pixelFormats;
} }
} }
public static IReadOnlyDictionary<string, Codec> Codecs public static IReadOnlyDictionary<string, Codec> Codecs
{ {
get get
{ {
if (_codecs == null) //First check not thread safe if (_codecs == null) //First check not thread safe
{
lock (_syncObject) lock (_syncObject)
{
if (_codecs == null)//Second check thread safe if (_codecs == null)//Second check thread safe
{
_codecs = FFMpeg.GetCodecsInternal(); _codecs = FFMpeg.GetCodecsInternal();
}
}
}
return _codecs; return _codecs;
} }
} }
public static IReadOnlyDictionary<string, ContainerFormat> ContainerFormats public static IReadOnlyDictionary<string, ContainerFormat> ContainerFormats
{ {
get get
{ {
if (_containers == null) //First check not thread safe if (_containers == null) //First check not thread safe
{
lock (_syncObject) lock (_syncObject)
{
if (_containers == null)//Second check thread safe if (_containers == null)//Second check thread safe
{
_containers = FFMpeg.GetContainersFormatsInternal().ToDictionary(x => x.Name); _containers = FFMpeg.GetContainersFormatsInternal().ToDictionary(x => x.Name);
}
}
}
return _containers; return _containers;
} }
} }
} }
} }

View file

@ -13,6 +13,5 @@ private FFMpegGlobalArguments WithOption(IArgument argument)
Arguments.Add(argument); Arguments.Add(argument);
return this; return this;
} }
} }
} }

View file

@ -1,8 +1,4 @@
using System.IO; namespace FFMpegCore.Pipes
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Pipes
{ {
/// <summary> /// <summary>
/// Interface for Audio sample /// Interface for Audio sample

View file

@ -1,8 +1,4 @@
using System.IO; namespace FFMpegCore.Pipes
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Pipes
{ {
public interface IPipeSink public interface IPipeSink
{ {

View file

@ -1,8 +1,4 @@
using System.IO; namespace FFMpegCore.Pipes
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Pipes
{ {
/// <summary> /// <summary>
/// Interface for ffmpeg pipe source data IO /// Interface for ffmpeg pipe source data IO

View file

@ -1,8 +1,4 @@
using System.IO; namespace FFMpegCore.Pipes
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Pipes
{ {
/// <summary> /// <summary>
/// Interface for Video frame /// Interface for Video frame

View file

@ -1,17 +1,18 @@
using System; using System.Runtime.InteropServices;
using System.IO;
using System.Runtime.InteropServices;
namespace FFMpegCore.Pipes namespace FFMpegCore.Pipes
{ {
static class PipeHelpers internal static class PipeHelpers
{ {
public static string GetUnqiuePipeName() => $"FFMpegCore_{Guid.NewGuid().ToString("N").Substring(0, 5)}"; public static string GetUnqiuePipeName() => $"FFMpegCore_{Guid.NewGuid().ToString("N").Substring(0, 5)}";
public static string GetPipePath(string pipeName) public static string GetPipePath(string pipeName)
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return $@"\\.\pipe\{pipeName}"; return $@"\\.\pipe\{pipeName}";
}
return $"unix:{Path.Combine(Path.GetTempPath(), $"CoreFxPipe_{pipeName}")}"; return $"unix:{Path.Combine(Path.GetTempPath(), $"CoreFxPipe_{pipeName}")}";
} }
} }

View file

@ -1,9 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Pipes
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Pipes
{ {
/// <summary> /// <summary>
/// Implementation of <see cref="IPipeSource"/> for a raw audio stream that is gathered from <see cref="IEnumerator{IAudioFrame}"/>. /// Implementation of <see cref="IPipeSource"/> for a raw audio stream that is gathered from <see cref="IEnumerator{IAudioFrame}"/>.

View file

@ -1,9 +1,4 @@
using System; using System.Globalization;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Exceptions; using FFMpegCore.Exceptions;
namespace FFMpegCore.Pipes namespace FFMpegCore.Pipes
@ -35,8 +30,11 @@ public string GetStreamArguments()
if (_framesEnumerator.Current == null) if (_framesEnumerator.Current == null)
{ {
if (!_framesEnumerator.MoveNext()) if (!_framesEnumerator.MoveNext())
{
throw new InvalidOperationException("Enumerator is empty, unable to get frame"); throw new InvalidOperationException("Enumerator is empty, unable to get frame");
} }
}
StreamFormat = _framesEnumerator.Current!.Format; StreamFormat = _framesEnumerator.Current!.Format;
Width = _framesEnumerator.Current!.Width; Width = _framesEnumerator.Current!.Width;
Height = _framesEnumerator.Current!.Height; Height = _framesEnumerator.Current!.Height;
@ -65,9 +63,11 @@ public async Task WriteAsync(Stream outputStream, CancellationToken cancellation
private void CheckFrameAndThrow(IVideoFrame frame) private void CheckFrameAndThrow(IVideoFrame frame)
{ {
if (frame.Width != Width || frame.Height != Height || frame.Format != StreamFormat) if (frame.Width != Width || frame.Height != Height || frame.Format != StreamFormat)
{
throw new FFMpegStreamFormatException(FFMpegExceptionType.Operation, "Video frame is not the same format as created raw video stream\r\n" + throw new FFMpegStreamFormatException(FFMpegExceptionType.Operation, "Video frame is not the same format as created raw video stream\r\n" +
$"Frame format: {frame.Width}x{frame.Height} pix_fmt: {frame.Format}\r\n" + $"Frame format: {frame.Width}x{frame.Height} pix_fmt: {frame.Format}\r\n" +
$"Stream format: {Width}x{Height} pix_fmt: {StreamFormat}"); $"Stream format: {Width}x{Height} pix_fmt: {StreamFormat}");
} }
} }
} }
}

View file

@ -1,9 +1,4 @@
using System; namespace FFMpegCore.Pipes
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Pipes
{ {
public class StreamPipeSink : IPipeSink public class StreamPipeSink : IPipeSink
{ {

View file

@ -1,8 +1,4 @@
using System.IO; namespace FFMpegCore.Pipes
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Pipes
{ {
/// <summary> /// <summary>
/// Implementation of <see cref="IPipeSource"/> used for stream redirection /// Implementation of <see cref="IPipeSource"/> used for stream redirection

View file

@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
using System.Drawing;
using FFMpegCore.Enums;
namespace FFMpegCore;
public static class SnapshotArgumentBuilder
{
public static (FFMpegArguments, Action<FFMpegArgumentOptions> outputOptions) BuildSnapshotArguments(
string input,
IMediaAnalysis source,
Size? size = null,
TimeSpan? captureTime = null,
int? streamIndex = null,
int inputFileIndex = 0)
{
captureTime ??= TimeSpan.FromSeconds(source.Duration.TotalSeconds / 3);
size = PrepareSnapshotSize(source, size);
streamIndex ??= source.PrimaryVideoStream?.Index
?? source.VideoStreams.FirstOrDefault()?.Index
?? 0;
return (FFMpegArguments
.FromFileInput(input, false, options => options
.Seek(captureTime)),
options => options
.SelectStream((int)streamIndex, inputFileIndex)
.WithVideoCodec(VideoCodec.Png)
.WithFrameOutputCount(1)
.Resize(size));
}
private static Size? PrepareSnapshotSize(IMediaAnalysis source, Size? wantedSize)
{
if (wantedSize == null || (wantedSize.Value.Height <= 0 && wantedSize.Value.Width <= 0) || source.PrimaryVideoStream == null)
{
return null;
}
var currentSize = new Size(source.PrimaryVideoStream.Width, source.PrimaryVideoStream.Height);
if (source.PrimaryVideoStream.Rotation == 90 || source.PrimaryVideoStream.Rotation == 180)
{
currentSize = new Size(source.PrimaryVideoStream.Height, source.PrimaryVideoStream.Width);
}
if (wantedSize.Value.Width != currentSize.Width || wantedSize.Value.Height != currentSize.Height)
{
if (wantedSize.Value.Width <= 0 && wantedSize.Value.Height > 0)
{
var ratio = (double)wantedSize.Value.Height / currentSize.Height;
return new Size((int)(currentSize.Width * ratio), (int)(currentSize.Height * ratio));
}
if (wantedSize.Value.Height <= 0 && wantedSize.Value.Width > 0)
{
var ratio = (double)wantedSize.Value.Width / currentSize.Width;
return new Size((int)(currentSize.Width * ratio), (int)(currentSize.Height * ratio));
}
return wantedSize;
}
return null;
}
}

View file

@ -1,8 +1,5 @@
using FFMpegCore.Enums; using System.Text;
using System; using FFMpegCore.Enums;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace FFMpegCore namespace FFMpegCore
{ {
@ -40,7 +37,7 @@ public class FFOptions : ICloneable
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public Dictionary<string, string> ExtensionOverrides { get; set; } = new Dictionary<string, string> public Dictionary<string, string> ExtensionOverrides { get; set; } = new()
{ {
{ "mpegts", ".ts" }, { "mpegts", ".ts" },
}; };

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Exceptions
namespace FFMpegCore.Exceptions
{ {
public class FFProbeException : Exception public class FFProbeException : Exception
{ {

View file

@ -1,7 +1,4 @@
using System; namespace FFMpegCore.Exceptions
using System.Collections.Generic;
namespace FFMpegCore.Exceptions
{ {
public class FFProbeProcessException : FFProbeException public class FFProbeProcessException : FFProbeException
{ {

View file

@ -1,9 +1,5 @@
using System; using System.Diagnostics;
using System.Diagnostics;
using System.IO;
using System.Text.Json; using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Arguments; using FFMpegCore.Arguments;
using FFMpegCore.Exceptions; using FFMpegCore.Exceptions;
using FFMpegCore.Helpers; using FFMpegCore.Helpers;
@ -72,6 +68,7 @@ public static IMediaAnalysis Analyse(Stream stream, FFOptions? ffOptions = null)
{ {
pipeArgument.Post(); pipeArgument.Post();
} }
var result = task.ConfigureAwait(false).GetAwaiter().GetResult(); var result = task.ConfigureAwait(false).GetAwaiter().GetResult();
ThrowIfExitCodeNotZero(result); ThrowIfExitCodeNotZero(result);
@ -143,6 +140,7 @@ public static async Task<IMediaAnalysis> AnalyseAsync(Stream stream, FFOptions?
{ {
pipeArgument.Post(); pipeArgument.Post();
} }
var result = await task.ConfigureAwait(false); var result = await task.ConfigureAwait(false);
ThrowIfExitCodeNotZero(result); ThrowIfExitCodeNotZero(result);
@ -166,7 +164,9 @@ private static IMediaAnalysis ParseOutput(IProcessResult instance)
}); });
if (ffprobeAnalysis?.Format == null) if (ffprobeAnalysis?.Format == null)
{
throw new FormatNullException(); throw new FormatNullException();
}
ffprobeAnalysis.ErrorData = instance.ErrorData; ffprobeAnalysis.ErrorData = instance.ErrorData;
return new MediaAnalysis(ffprobeAnalysis); return new MediaAnalysis(ffprobeAnalysis);

View file

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace FFMpegCore namespace FFMpegCore
{ {
@ -138,7 +137,10 @@ public static class TagExtensions
private static string? TryGetTagValue(ITagsContainer tagsContainer, string key) private static string? TryGetTagValue(ITagsContainer tagsContainer, string key)
{ {
if (tagsContainer.Tags != null && tagsContainer.Tags.TryGetValue(key, out var tagValue)) if (tagsContainer.Tags != null && tagsContainer.Tags.TryGetValue(key, out var tagValue))
{
return tagValue; return tagValue;
}
return null; return null;
} }
@ -153,7 +155,10 @@ public static class DispositionExtensions
private static int? TryGetDispositionValue(IDispositionContainer dispositionContainer, string key) private static int? TryGetDispositionValue(IDispositionContainer dispositionContainer, string key)
{ {
if (dispositionContainer.Disposition != null && dispositionContainer.Disposition.TryGetValue(key, out var dispositionValue)) if (dispositionContainer.Disposition != null && dispositionContainer.Disposition.TryGetValue(key, out var dispositionValue))
{
return dispositionValue; return dispositionValue;
}
return null; return null;
} }

View file

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace FFMpegCore namespace FFMpegCore
{ {

View file

@ -1,7 +1,4 @@
using System; namespace FFMpegCore
using System.Collections.Generic;
namespace FFMpegCore
{ {
public interface IMediaAnalysis public interface IMediaAnalysis
{ {

View file

@ -1,7 +1,4 @@
using System; using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace FFMpegCore namespace FFMpegCore
{ {
@ -119,12 +116,11 @@ private SubtitleStream ParseSubtitleStream(FFProbeStream stream)
Tags = stream.Tags.ToCaseInsensitive(), Tags = stream.Tags.ToCaseInsensitive(),
}; };
} }
} }
public static class MediaAnalysisUtils public static class MediaAnalysisUtils
{ {
private static readonly Regex DurationRegex = new Regex(@"^(\d+):(\d{1,2}):(\d{1,2})\.(\d{1,3})", RegexOptions.Compiled); private static readonly Regex DurationRegex = new(@"^(\d+):(\d{1,2}):(\d{1,2})\.(\d{1,3})", RegexOptions.Compiled);
internal static Dictionary<string, string>? ToCaseInsensitive(this Dictionary<string, string>? dictionary) internal static Dictionary<string, string>? ToCaseInsensitive(this Dictionary<string, string>? dictionary)
{ {
@ -134,14 +130,22 @@ public static class MediaAnalysisUtils
public static (int, int) ParseRatioInt(string input, char separator) public static (int, int) ParseRatioInt(string input, char separator)
{ {
if (string.IsNullOrEmpty(input)) return (0, 0); if (string.IsNullOrEmpty(input))
{
return (0, 0);
}
var ratio = input.Split(separator); var ratio = input.Split(separator);
return (ParseIntInvariant(ratio[0]), ParseIntInvariant(ratio[1])); return (ParseIntInvariant(ratio[0]), ParseIntInvariant(ratio[1]));
} }
public static (double, double) ParseRatioDouble(string input, char separator) public static (double, double) ParseRatioDouble(string input, char separator)
{ {
if (string.IsNullOrEmpty(input)) return (0, 0); if (string.IsNullOrEmpty(input))
{
return (0, 0);
}
var ratio = input.Split(separator); var ratio = input.Split(separator);
return (ratio.Length > 0 ? ParseDoubleInvariant(ratio[0]) : 0, ratio.Length > 1 ? ParseDoubleInvariant(ratio[1]) : 0); return (ratio.Length > 0 ? ParseDoubleInvariant(ratio[0]) : 0, ratio.Length > 1 ? ParseDoubleInvariant(ratio[1]) : 0);
} }
@ -155,7 +159,6 @@ public static int ParseIntInvariant(string line) =>
public static long ParseLongInvariant(string line) => public static long ParseLongInvariant(string line) =>
long.Parse(line, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture); long.Parse(line, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture);
public static TimeSpan ParseDuration(string duration) public static TimeSpan ParseDuration(string duration)
{ {
if (!string.IsNullOrEmpty(duration)) if (!string.IsNullOrEmpty(duration))

View file

@ -1,7 +1,4 @@
using System; namespace FFMpegCore
using System.Collections.Generic;
namespace FFMpegCore
{ {
public class MediaFormat public class MediaFormat
{ {

View file

@ -1,8 +1,5 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
using System;
using System.Collections.Generic;
namespace FFMpegCore namespace FFMpegCore
{ {
public abstract class MediaStream public abstract class MediaStream

View file

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace FFMpegCore namespace FFMpegCore
{ {

View file

@ -1,6 +1,4 @@
using System.Threading; using Instances;
using System.Threading.Tasks;
using Instances;
namespace FFMpegCore namespace FFMpegCore
{ {

View file

@ -1,30 +1,22 @@
using System; using System.Runtime.InteropServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.Json; using System.Text.Json;
namespace FFMpegCore namespace FFMpegCore
{ {
public static class GlobalFFOptions public static class GlobalFFOptions
{ {
private static readonly string ConfigFile = "ffmpeg.config.json"; private const string ConfigFile = "ffmpeg.config.json";
private static FFOptions? _current; private static FFOptions? _current;
public static FFOptions Current public static FFOptions Current => _current ??= LoadFFOptions();
{
get { return _current ??= LoadFFOptions(); } public static void Configure(Action<FFOptions> optionsAction) => optionsAction.Invoke(Current);
}
public static void Configure(Action<FFOptions> optionsAction)
{
optionsAction?.Invoke(Current);
}
public static void Configure(FFOptions ffOptions) public static void Configure(FFOptions ffOptions)
{ {
_current = ffOptions ?? throw new ArgumentNullException(nameof(ffOptions)); _current = ffOptions ?? throw new ArgumentNullException(nameof(ffOptions));
} }
public static string GetFFMpegBinaryPath(FFOptions? ffOptions = null) => GetFFBinaryPath("FFMpeg", ffOptions ?? Current); public static string GetFFMpegBinaryPath(FFOptions? ffOptions = null) => GetFFBinaryPath("FFMpeg", ffOptions ?? Current);
public static string GetFFProbeBinaryPath(FFOptions? ffOptions = null) => GetFFBinaryPath("FFProbe", ffOptions ?? Current); public static string GetFFProbeBinaryPath(FFOptions? ffOptions = null) => GetFFBinaryPath("FFProbe", ffOptions ?? Current);
@ -33,25 +25,24 @@ private static string GetFFBinaryPath(string name, FFOptions ffOptions)
{ {
var ffName = name.ToLowerInvariant(); var ffName = name.ToLowerInvariant();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
ffName += ".exe"; ffName += ".exe";
}
var target = Environment.Is64BitProcess ? "x64" : "x86"; var target = Environment.Is64BitProcess ? "x64" : "x86";
if (Directory.Exists(Path.Combine(ffOptions.BinaryFolder, target))) if (Directory.Exists(Path.Combine(ffOptions.BinaryFolder, target)))
{
ffName = Path.Combine(target, ffName); ffName = Path.Combine(target, ffName);
}
return Path.Combine(ffOptions.BinaryFolder, ffName); return Path.Combine(ffOptions.BinaryFolder, ffName);
} }
private static FFOptions LoadFFOptions() private static FFOptions LoadFFOptions()
{ {
if (File.Exists(ConfigFile)) return File.Exists(ConfigFile)
{ ? JsonSerializer.Deserialize<FFOptions>(File.ReadAllText(ConfigFile))!
return JsonSerializer.Deserialize<FFOptions>(File.ReadAllText(ConfigFile))!; : new FFOptions();
}
else
{
return new FFOptions();
}
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show more