mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2025-02-18 11:02:31 +00:00
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:
parent
7dfefbdfdb
commit
693acabac4
126 changed files with 1212 additions and 1045 deletions
275
.editorconfig
Normal file
275
.editorconfig
Normal 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
|
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -27,6 +27,9 @@ jobs:
|
|||
with:
|
||||
dotnet-version: '7.0.x'
|
||||
|
||||
- name: Lint with dotnet
|
||||
run: dotnet format FFMpegCore.sln --severity warn --verify-no-changes
|
||||
|
||||
- name: Prepare FFMpeg
|
||||
uses: FedericoCarboni/setup-ffmpeg@v2
|
||||
with:
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
<AssemblyVersion>5.0.0.0</AssemblyVersion>
|
||||
<LangVersion>default</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<RepositoryType>GitHub</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/rosenbjerg/FFMpegCore</RepositoryUrl>
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
using FFMpegCore;
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Pipes;
|
||||
using FFMpegCore.Extensions.System.Drawing.Common;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
var inputPath = "/path/to/input";
|
||||
var outputPath = "/path/to/output";
|
||||
|
@ -61,11 +58,7 @@ var outputStream = new MemoryStream();
|
|||
}
|
||||
|
||||
{
|
||||
FFMpegImage.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1,
|
||||
ImageInfo.FromPath(@"..\1.png"),
|
||||
ImageInfo.FromPath(@"..\2.png"),
|
||||
ImageInfo.FromPath(@"..\3.png")
|
||||
);
|
||||
FFMpeg.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1, @"..\1.png", @"..\2.png", @"..\3.png");
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -83,17 +76,19 @@ var inputAudioPath = "/path/to/input/audio";
|
|||
|
||||
var inputImagePath = "/path/to/input/image";
|
||||
{
|
||||
FFMpegImage.PosterWithAudio(inputPath, inputAudioPath, outputPath);
|
||||
FFMpeg.PosterWithAudio(inputPath, inputAudioPath, outputPath);
|
||||
// or
|
||||
#pragma warning disable CA1416
|
||||
using var image = Image.FromFile(inputImagePath);
|
||||
image.AddAudio(inputAudioPath, outputPath);
|
||||
#pragma warning restore CA1416
|
||||
}
|
||||
|
||||
IVideoFrame GetNextFrame() => throw new NotImplementedException();
|
||||
{
|
||||
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
|
||||
}
|
||||
|
@ -131,4 +126,4 @@ IVideoFrame GetNextFrame() => throw new NotImplementedException();
|
|||
.Configure(options => options.WorkingDirectory = "./CurrentRunWorkingDir")
|
||||
.Configure(options => options.TemporaryFilesFolder = "./CurrentRunTmpFolder")
|
||||
.ProcessAsynchronously();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Drawing;
|
||||
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common
|
||||
{
|
||||
|
@ -12,12 +10,15 @@ namespace FFMpegCore.Extensions.System.Drawing.Common
|
|||
poster.Save(destination);
|
||||
try
|
||||
{
|
||||
return FFMpegImage.PosterWithAudio(destination, audio, output);
|
||||
return FFMpeg.PosterWithAudio(destination, audio, output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(destination)) File.Delete(destination);
|
||||
if (File.Exists(destination))
|
||||
{
|
||||
File.Delete(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
using System;
|
||||
using System.Drawing;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common
|
||||
|
|
|
@ -1,84 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Helpers;
|
||||
using System.Drawing;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common
|
||||
{
|
||||
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>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
|
@ -126,13 +52,5 @@ namespace FFMpegCore.Extensions.System.Drawing.Common
|
|||
ms.Position = 0;
|
||||
return new Bitmap(ms);
|
||||
}
|
||||
private static void Cleanup(IEnumerable<string> pathList)
|
||||
{
|
||||
foreach (var path in pathList)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using FFMpegCore.Arguments;
|
||||
using FFMpegCore.Arguments;
|
||||
using FFMpegCore.Enums;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
|
@ -10,7 +9,6 @@ namespace FFMpegCore.Test
|
|||
{
|
||||
private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4" };
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_IO_1()
|
||||
{
|
||||
|
@ -53,7 +51,6 @@ namespace FFMpegCore.Test
|
|||
Assert.AreEqual("-hide_banner -loglevel error -i \"input.mp4\" \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_AudioCodec_Fluent()
|
||||
{
|
||||
|
@ -400,7 +397,6 @@ namespace FFMpegCore.Test
|
|||
Assert.AreEqual("-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\" -y", str);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_Duration()
|
||||
{
|
||||
|
@ -421,7 +417,6 @@ namespace FFMpegCore.Test
|
|||
Assert.AreEqual("-i \"input.mp4\" -acodec copy \"output.mp4\"", str);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_ForcePixelFormat()
|
||||
{
|
||||
|
@ -536,4 +531,4 @@ namespace FFMpegCore.Test
|
|||
str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,27 +4,20 @@ using FFMpegCore.Extend;
|
|||
using FFMpegCore.Pipes;
|
||||
using FFMpegCore.Test.Resources;
|
||||
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
|
||||
{
|
||||
[TestClass]
|
||||
public class AudioTest
|
||||
public class AudioTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void Audio_Remove()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
||||
|
||||
FFMpeg.Mute(TestResources.Mp4Video, outputFile);
|
||||
var analysis = FFProbe.Analyse(outputFile);
|
||||
|
||||
|
||||
Assert.IsTrue(analysis.VideoStreams.Any());
|
||||
Assert.IsTrue(!analysis.AudioStreams.Any());
|
||||
}
|
||||
|
@ -33,10 +26,10 @@ namespace FFMpegCore.Test
|
|||
public void Audio_Save()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp3");
|
||||
|
||||
|
||||
FFMpeg.ExtractAudio(TestResources.Mp4Video, outputFile);
|
||||
var analysis = FFProbe.Analyse(outputFile);
|
||||
|
||||
|
||||
Assert.IsTrue(!analysis.VideoStreams.Any());
|
||||
Assert.IsTrue(analysis.AudioStreams.Any());
|
||||
}
|
||||
|
@ -50,27 +43,27 @@ namespace FFMpegCore.Test
|
|||
.OutputToPipe(new StreamPipeSink(memoryStream), options => options.ForceFormat("mp3"))
|
||||
.ProcessAsynchronously();
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Audio_Add()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
||||
|
||||
var success = FFMpeg.ReplaceAudio(TestResources.Mp4WithoutAudio, TestResources.Mp3Audio, outputFile);
|
||||
var videoAnalysis = FFProbe.Analyse(TestResources.Mp4WithoutAudio);
|
||||
var audioAnalysis = FFProbe.Analyse(TestResources.Mp3Audio);
|
||||
var outputAnalysis = FFProbe.Analyse(outputFile);
|
||||
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(Math.Max(videoAnalysis.Duration.TotalSeconds, audioAnalysis.Duration.TotalSeconds), outputAnalysis.Duration.TotalSeconds, 0.15);
|
||||
Assert.IsTrue(File.Exists(outputFile));
|
||||
}
|
||||
|
||||
[WindowsOnlyTestMethod]
|
||||
[TestMethod]
|
||||
public void Image_AddAudio()
|
||||
{
|
||||
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);
|
||||
Assert.IsTrue(analysis.Duration.TotalSeconds > 0);
|
||||
Assert.IsTrue(File.Exists(outputFile));
|
||||
|
@ -332,4 +325,4 @@ namespace FFMpegCore.Test
|
|||
.ProcessSynchronously());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using FluentAssertions;
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using FFMpegCore.Arguments;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
|
@ -20,7 +20,6 @@ namespace FFMpegCore.Test
|
|||
.FromFileInput("")
|
||||
.OutputToFile("");
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Processor_GlobalOptions_GetUsed()
|
||||
{
|
||||
|
@ -44,14 +43,12 @@ namespace FFMpegCore.Test
|
|||
options.WorkingDirectory.Should().Be(sessionWorkingDir);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Processor_Options_CanBeOverridden_And_Configured()
|
||||
{
|
||||
var globalConfig = "Whatever";
|
||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig });
|
||||
|
||||
|
||||
var processor = CreateArgumentProcessor();
|
||||
|
||||
var sessionTempDir = "./CurrentRunWorkingDir";
|
||||
|
@ -65,7 +62,6 @@ namespace FFMpegCore.Test
|
|||
options.BinaryFolder.Should().NotBeEquivalentTo(globalConfig);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Options_Global_And_Session_Options_Can_Differ()
|
||||
{
|
||||
|
@ -78,7 +74,6 @@ namespace FFMpegCore.Test
|
|||
var options1 = processor1.GetConfiguredOptions(null);
|
||||
options1.WorkingDirectory.Should().Be(sessionWorkingDir);
|
||||
|
||||
|
||||
var processor2 = CreateArgumentProcessor();
|
||||
var options2 = processor2.GetConfiguredOptions(null);
|
||||
options2.WorkingDirectory.Should().Be(globalWorkingDir);
|
||||
|
@ -98,7 +93,6 @@ namespace FFMpegCore.Test
|
|||
arg.Text.Should().Be($"-audible_key 123 -audible_iv 456");
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void Audible_Aax_Test()
|
||||
{
|
||||
|
@ -106,4 +100,4 @@ namespace FFMpegCore.Test
|
|||
arg.Text.Should().Be($"-activation_bytes 62689101");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Newtonsoft.Json;
|
||||
using System.IO;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
|
@ -23,7 +22,7 @@ namespace FFMpegCore.Test
|
|||
public void Options_Loaded_From_File()
|
||||
{
|
||||
Assert.AreEqual(
|
||||
GlobalFFOptions.Current.BinaryFolder,
|
||||
GlobalFFOptions.Current.BinaryFolder,
|
||||
JsonConvert.DeserializeObject<FFOptions>(File.ReadAllText("ffmpeg.config.json")).BinaryFolder
|
||||
);
|
||||
}
|
||||
|
@ -31,7 +30,7 @@ namespace FFMpegCore.Test
|
|||
[TestMethod]
|
||||
public void Options_Set_Programmatically()
|
||||
{
|
||||
var original = GlobalFFOptions.Current;
|
||||
var original = GlobalFFOptions.Current;
|
||||
try
|
||||
{
|
||||
GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "Whatever" });
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
using FFMpegCore.Test.Resources;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
|
@ -55,7 +50,6 @@ namespace FFMpegCore.Test
|
|||
Assert.AreEqual(1362, packets.Last().Size);
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void PacketAnalysis_Sync()
|
||||
{
|
||||
|
@ -145,7 +139,7 @@ namespace FFMpegCore.Test
|
|||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth);
|
||||
// This video's audio stream is AAC, which is lossy, so bit depth is meaningless.
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
@ -183,53 +177,53 @@ namespace FFMpegCore.Test
|
|||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_Disposition_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream.Disposition);
|
||||
Assert.AreEqual(true, info.PrimaryAudioStream.Disposition["default"]);
|
||||
Assert.AreEqual(false, info.PrimaryAudioStream.Disposition["forced"]);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream.Disposition);
|
||||
Assert.AreEqual(true, info.PrimaryAudioStream.Disposition["default"]);
|
||||
Assert.AreEqual(false, info.PrimaryAudioStream.Disposition["forced"]);
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_Mp3AudioBitDepthNull_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp3Audio);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
// mp3 is lossy, so bit depth is meaningless.
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp3Audio);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
// mp3 is lossy, so bit depth is meaningless.
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_VocAudioBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.AiffAudio);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(16, info.PrimaryAudioStream.BitDepth);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.AiffAudio);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(16, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_MkvVideoBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.MkvVideo);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth);
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.MkvVideo);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth);
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_24BitWavBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav24Bit);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(24, info.PrimaryAudioStream.BitDepth);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav24Bit);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(24, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_32BitWavBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav32Bit);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(32, info.PrimaryAudioStream.BitDepth);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav32Bit);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(32, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using FFMpegCore.Builders.MetaData;
|
||||
using System.Text.RegularExpressions;
|
||||
using FFMpegCore.Builders.MetaData;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
|
@ -64,8 +63,6 @@ namespace FFMpegCore.Test
|
|||
.AddMetaData("WhatEver3")
|
||||
.Text;
|
||||
|
||||
|
||||
|
||||
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.");
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace FFMpegCore.Test
|
|||
{
|
||||
Assert.IsFalse(FFMpeg.TryGetPixelFormat("yuv420pppUnknown", out _));
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void PixelFormats_GetExisting()
|
||||
{
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
public class TemporaryFile : IDisposable
|
||||
{
|
||||
|
@ -16,7 +13,9 @@ namespace FFMpegCore.Test
|
|||
public void Dispose()
|
||||
{
|
||||
if (File.Exists(_path))
|
||||
{
|
||||
File.Delete(_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Numerics;
|
||||
using System.Runtime.Versioning;
|
||||
|
@ -10,11 +8,11 @@ using FFMpegCore.Pipes;
|
|||
namespace FFMpegCore.Test.Utilities
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
static class BitmapSource
|
||||
internal static class BitmapSource
|
||||
{
|
||||
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))
|
||||
{
|
||||
|
@ -29,8 +27,9 @@ namespace FFMpegCore.Test.Utilities
|
|||
|
||||
offset = offset * index;
|
||||
|
||||
for (int y = 0; y < h; y++)
|
||||
for (int x = 0; x < w; x++)
|
||||
for (var y = 0; y < h; y++)
|
||||
{
|
||||
for (var x = 0; x < w; x++)
|
||||
{
|
||||
var xf = x / (float)w;
|
||||
var yf = y / (float)h;
|
||||
|
@ -43,6 +42,7 @@ namespace FFMpegCore.Test.Utilities
|
|||
|
||||
bitmap.SetPixel(x, y, color);
|
||||
}
|
||||
}
|
||||
|
||||
return new BitmapVideoFrameWrapper(bitmap);
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ namespace FFMpegCore.Test.Utilities
|
|||
// Based on the original implementation by Ken Perlin
|
||||
// http://mrl.nyu.edu/~perlin/noise/
|
||||
//
|
||||
static class Perlin
|
||||
private static class Perlin
|
||||
{
|
||||
#region Noise functions
|
||||
|
||||
|
@ -128,6 +128,7 @@ namespace FFMpegCore.Test.Utilities
|
|||
x *= 2.0f;
|
||||
w *= 0.5f;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
|
@ -141,6 +142,7 @@ namespace FFMpegCore.Test.Utilities
|
|||
coord *= 2.0f;
|
||||
w *= 0.5f;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
|
@ -159,6 +161,7 @@ namespace FFMpegCore.Test.Utilities
|
|||
coord *= 2.0f;
|
||||
w *= 0.5f;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
|
@ -171,27 +174,27 @@ namespace FFMpegCore.Test.Utilities
|
|||
|
||||
#region Private functions
|
||||
|
||||
static float Fade(float t)
|
||||
private static float Fade(float t)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
static float Grad(int hash, float x)
|
||||
private static float Grad(int hash, float 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);
|
||||
}
|
||||
|
||||
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 u = h < 8 ? x : y;
|
||||
|
@ -199,7 +202,7 @@ namespace FFMpegCore.Test.Utilities
|
|||
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,
|
||||
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,
|
||||
|
|
|
@ -17,7 +17,7 @@ public class WindowsOnlyDataTestMethod : DataTestMethodAttribute
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return base.Execute(testMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ public class WindowsOnlyTestMethod : TestMethodAttribute
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return base.Execute(testMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,14 @@
|
|||
using FFMpegCore.Arguments;
|
||||
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.Drawing.Imaging;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Arguments;
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
using FFMpegCore.Extensions.System.Drawing.Common;
|
||||
using FFMpegCore.Pipes;
|
||||
using FFMpegCore.Test.Resources;
|
||||
using FFMpegCore.Test.Utilities;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
|
@ -164,7 +157,7 @@ namespace FFMpegCore.Test
|
|||
.WithVideoCodec(VideoCodec.LibX264))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[WindowsOnlyTestMethod, Timeout(10000)]
|
||||
public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Async()
|
||||
|
@ -213,7 +206,6 @@ namespace FFMpegCore.Test
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_StreamFile_OutputToMemoryStream()
|
||||
{
|
||||
|
@ -245,7 +237,6 @@ namespace FFMpegCore.Test
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Video_ToMP4_Args_StreamOutputPipe_Async()
|
||||
{
|
||||
|
@ -357,7 +348,7 @@ namespace FFMpegCore.Test
|
|||
[WindowsOnlyDataTestMethod, Timeout(10000)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[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)
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}");
|
||||
|
@ -411,16 +402,15 @@ namespace FFMpegCore.Test
|
|||
[WindowsOnlyTestMethod, Timeout(10000)]
|
||||
public void Video_Snapshot_InMemory()
|
||||
{
|
||||
var input = FFProbe.Analyse(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.Height, bitmap.Height);
|
||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[WindowsOnlyTestMethod, Timeout(10000)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Video_Snapshot_PersistSnapshot()
|
||||
{
|
||||
var outputPath = new TemporaryFile("out.png");
|
||||
|
@ -428,10 +418,10 @@ namespace FFMpegCore.Test
|
|||
|
||||
FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
|
||||
|
||||
using var bitmap = Image.FromFile(outputPath);
|
||||
Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width);
|
||||
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||
var analysis = FFProbe.Analyse(outputPath);
|
||||
Assert.AreEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
|
||||
Assert.AreEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
|
||||
Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
|
@ -456,28 +446,29 @@ namespace FFMpegCore.Test
|
|||
Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width);
|
||||
}
|
||||
|
||||
[WindowsOnlyTestMethod, Timeout(10000)]
|
||||
[TestMethod, Timeout(20000)]
|
||||
public void Video_Join_Image_Sequence()
|
||||
{
|
||||
var imageSet = new List<ImageInfo>();
|
||||
Directory.EnumerateFiles(TestResources.ImageCollection)
|
||||
.Where(file => file.ToLower().EndsWith(".png"))
|
||||
var imageSet = new List<string>();
|
||||
Directory.EnumerateFiles(TestResources.ImageCollection, "*.png")
|
||||
.ToList()
|
||||
.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 success = FFMpegImage.JoinImageSequence(outputFile, images: imageSet.ToArray());
|
||||
var success = FFMpeg.JoinImageSequence(outputFile, frameRate: 10, images: imageSet.ToArray());
|
||||
Assert.IsTrue(success);
|
||||
var result = FFProbe.Analyse(outputFile);
|
||||
Assert.AreEqual(3, result.Duration.Seconds);
|
||||
Assert.AreEqual(imageSet.First().Width, result.PrimaryVideoStream!.Width);
|
||||
Assert.AreEqual(imageSet.First().Height, result.PrimaryVideoStream.Height);
|
||||
|
||||
Assert.AreEqual(1, result.Duration.Seconds);
|
||||
Assert.AreEqual(imageAnalysis.PrimaryVideoStream!.Width, result.PrimaryVideoStream!.Width);
|
||||
Assert.AreEqual(imageAnalysis.PrimaryVideoStream!.Height, result.PrimaryVideoStream.Height);
|
||||
}
|
||||
|
||||
[TestMethod, Timeout(10000)]
|
||||
|
@ -520,12 +511,18 @@ namespace FFMpegCore.Test
|
|||
|
||||
void OnPercentageProgess(double percentage)
|
||||
{
|
||||
if (percentage < 100) percentageDone = percentage;
|
||||
if (percentage < 100)
|
||||
{
|
||||
percentageDone = percentage;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTimeProgess(TimeSpan time)
|
||||
{
|
||||
if (time < analysis.Duration) timeDone = time;
|
||||
if (time < analysis.Duration)
|
||||
{
|
||||
timeDone = time;
|
||||
}
|
||||
}
|
||||
|
||||
var success = FFMpegArguments
|
||||
|
@ -586,6 +583,24 @@ namespace FFMpegCore.Test
|
|||
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)]
|
||||
public async Task Video_Cancel_Async()
|
||||
{
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("FFMpegCore.Test")]
|
||||
[assembly: InternalsVisibleTo("FFMpegCore.Test")]
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace FFMpegCore.Extend
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
internal static class KeyValuePairExtensions
|
||||
{
|
||||
|
@ -21,4 +19,4 @@ namespace FFMpegCore.Extend
|
|||
return $"{key}={value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Pipes;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
internal static class StringExtensions
|
||||
{
|
||||
private static Dictionary<char, string> CharactersSubstitution { get; } = new Dictionary<char, string>
|
||||
private static Dictionary<char, string> CharactersSubstitution { get; } = new()
|
||||
{
|
||||
{ '\\', @"\\" },
|
||||
{ ':', @"\:" },
|
||||
|
@ -67,4 +66,4 @@ namespace FFMpegCore.Extend
|
|||
return parsedString.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Extend
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
public static class UriExtensions
|
||||
{
|
||||
|
@ -9,4 +7,4 @@ namespace FFMpegCore.Extend
|
|||
return FFMpeg.SaveM3U8Stream(uri, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
public class AudibleEncryptionKeyArgument : IArgument
|
||||
{
|
||||
private readonly bool _aaxcMode;
|
||||
|
||||
|
||||
private readonly string? _key;
|
||||
private readonly string? _iv;
|
||||
|
||||
private readonly string? _activationBytes;
|
||||
|
||||
|
||||
public AudibleEncryptionKeyArgument(string activationBytes)
|
||||
{
|
||||
_activationBytes = activationBytes;
|
||||
|
|
|
@ -14,7 +14,6 @@ namespace FFMpegCore.Arguments
|
|||
Bitrate = bitrate;
|
||||
}
|
||||
|
||||
|
||||
public string Text => $"-b:a {Bitrate}k";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,9 @@ namespace FFMpegCore.Arguments
|
|||
public AudioCodecArgument(Codec audioCodec)
|
||||
{
|
||||
if (audioCodec.Type != CodecType.Audio)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{audioCodec.Name}\" is not an audio codec");
|
||||
}
|
||||
|
||||
AudioCodec = audioCodec.Name;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FFMpegCore.Exceptions;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
|
@ -18,7 +16,9 @@ namespace FFMpegCore.Arguments
|
|||
private string GetText()
|
||||
{
|
||||
if (!Options.Arguments.Any())
|
||||
{
|
||||
throw new FFMpegArgumentException("No audio-filter arguments provided");
|
||||
}
|
||||
|
||||
var arguments = Options.Arguments
|
||||
.Where(arg => !string.IsNullOrEmpty(arg.Value))
|
||||
|
@ -40,7 +40,7 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
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(int channels, params string[] outputDefinitions) => WithArgument(new PanArgument(channels, outputDefinitions));
|
||||
|
@ -50,16 +50,16 @@ namespace FFMpegCore.Arguments
|
|||
double compressorFactor = 0.0) => WithArgument(new DynamicNormalizerArgument(frameLength, filterWindow,
|
||||
targetPeak, gainFactor, targetRms, channelCoupling, enableDcBiasCorrection, enableAlternativeBoundary,
|
||||
compressorFactor));
|
||||
public AudioFilterOptions HighPass(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",
|
||||
public AudioFilterOptions HighPass(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? blocksize = null) => WithArgument(new HighPassFilterArgument(frequency, poles, width_type, width, mix, channels, normalize, transform, precision, blocksize));
|
||||
public AudioFilterOptions LowPass(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",
|
||||
double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto",
|
||||
int? blocksize = null) => WithArgument(new LowPassFilterArgument(frequency, poles, width_type, width, mix, channels, normalize, transform, precision, blocksize));
|
||||
public AudioFilterOptions AudioGate(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",
|
||||
int ratio = 2, double attack = 20, double release = 250, int makeup = 1, double knee = 2.828427125, string detection = "rms",
|
||||
string link = "average") => WithArgument(new AudioGateArgument(level_in, mode, range, threshold, ratio, attack, release, makeup, knee, detection, link));
|
||||
public AudioFilterOptions SilenceDetect(string noise_type = "db", double noise = 60, double duration = 2,
|
||||
public AudioFilterOptions SilenceDetect(string noise_type = "db", double noise = 60, double duration = 2,
|
||||
bool mono = false) => WithArgument(new SilenceDetectArgument(noise_type, noise, duration, mono));
|
||||
|
||||
private AudioFilterOptions WithArgument(IAudioFilterArgument argument)
|
||||
|
@ -68,4 +68,4 @@ namespace FFMpegCore.Arguments
|
|||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
|
||||
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>
|
||||
/// Audio Gate. <see href="https://ffmpeg.org/ffmpeg-filters.html#agate"/>
|
||||
/// </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="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>
|
||||
|
@ -23,29 +20,75 @@ namespace FFMpegCore.Arguments
|
|||
/// <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="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 (!(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));
|
||||
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");
|
||||
if (levelIn is < 0.015625 or > 64)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(levelIn), "Level in must be between 0.015625 to 64");
|
||||
}
|
||||
|
||||
_arguments.Add("level_in", level_in.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("mode", mode.ToString());
|
||||
if (mode != "upward" && mode != "downward")
|
||||
{
|
||||
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("threshold", threshold.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("ratio", ratio.ToString());
|
||||
_arguments.Add("attack", attack.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("release", release.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("makeup", makeup.ToString());
|
||||
_arguments.Add("detection", detection.ToString());
|
||||
_arguments.Add("link", link.ToString());
|
||||
_arguments.Add("knee", knee.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("detection", detection);
|
||||
_arguments.Add("link", link);
|
||||
}
|
||||
|
||||
public string Key { get; } = "agate";
|
||||
|
|
|
@ -13,4 +13,4 @@
|
|||
|
||||
public string Text => $"-ar {SamplingRate}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
|
@ -20,7 +16,7 @@ namespace FFMpegCore.Arguments
|
|||
public void Pre() { }
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() { }
|
||||
|
||||
|
||||
public string Text => $"-i \"concat:{string.Join(@"|", Values)}\"";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant Rate Factor (CRF) argument
|
||||
|
@ -21,4 +19,4 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
public string Text => $"-crf {Crf}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,4 +11,4 @@
|
|||
|
||||
public string Text => Argument ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of concat argument
|
||||
|
@ -26,7 +19,7 @@ namespace FFMpegCore.Arguments
|
|||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
private string Escape(string value) => value.Replace("'", @"'\''");
|
||||
|
||||
|
||||
private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"concat_{Guid.NewGuid()}.txt");
|
||||
|
||||
public void Pre() => File.WriteAllLines(_tempFileName, Values);
|
||||
|
|
|
@ -13,7 +13,10 @@ namespace FFMpegCore.Arguments
|
|||
public DisableChannelArgument(Channel channel)
|
||||
{
|
||||
if (channel == Channel.Both)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Conversion, "Cannot disable both channels");
|
||||
}
|
||||
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Drawtext video filter argument
|
||||
|
@ -9,16 +6,16 @@ namespace FFMpegCore.Arguments
|
|||
public class DrawTextArgument : IVideoFilterArgument
|
||||
{
|
||||
public readonly DrawTextOptions Options;
|
||||
|
||||
|
||||
public DrawTextArgument(DrawTextOptions options)
|
||||
{
|
||||
Options = options;
|
||||
}
|
||||
|
||||
|
||||
public string Key { get; } = "drawtext";
|
||||
public string Value => Options.TextInternal;
|
||||
}
|
||||
|
||||
|
||||
public class DrawTextOptions
|
||||
{
|
||||
public readonly string Text;
|
||||
|
@ -34,7 +31,7 @@ namespace FFMpegCore.Arguments
|
|||
return new DrawTextOptions(text, font, parameters);
|
||||
}
|
||||
|
||||
internal string TextInternal => string.Join(":", new[] {("text", Text), ("fontfile", Font)}.Concat(Parameters).Select(FormatArgumentPair));
|
||||
internal string TextInternal => string.Join(":", new[] { ("text", Text), ("fontfile", Font) }.Concat(Parameters).Select(FormatArgumentPair));
|
||||
|
||||
private static string FormatArgumentPair((string key, string value) pair)
|
||||
{
|
||||
|
@ -59,4 +56,4 @@ namespace FFMpegCore.Arguments
|
|||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents duration parameter
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class DynamicNormalizerArgument : IAudioFilterArgument
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new Dictionary<string, string>();
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
/// <summary>
|
||||
/// Dynamic Audio Normalizer. <see href="https://ffmpeg.org/ffmpeg-filters.html#dynaudnorm"/>
|
||||
|
@ -23,13 +20,40 @@ namespace FFMpegCore.Arguments
|
|||
/// <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)
|
||||
{
|
||||
if (frameLength < 10 || frameLength > 8000) throw new ArgumentOutOfRangeException(nameof(frameLength),"Frame length must be between 10 to 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");
|
||||
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");
|
||||
if (frameLength < 10 || frameLength > 8000)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(frameLength), "Frame length must be between 10 to 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");
|
||||
}
|
||||
|
||||
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("g", filterWindow.ToString());
|
||||
|
@ -46,4 +70,4 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
{
|
||||
public string Text => "-movflags faststart";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,4 +13,4 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
public string Text => $"-hwaccel {HardwareAccelerationDevice.ToString().ToLower()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class HighPassFilterArgument : IAudioFilterArgument
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new Dictionary<string, string>();
|
||||
private readonly List<string> _widthTypes = new List<string>{"h", "q", "o", "s", "k"};
|
||||
private readonly List<string> _transformTypes = new List<string>{"di", "dii", "tdi", "tdii", "latt", "svf", "zdf"};
|
||||
private readonly List<string> _precision = new List<string> { "auto", "s16", "s32", "f32", "f64" };
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
private readonly List<string> _widthTypes = new() { "h", "q", "o", "s", "k" };
|
||||
private readonly List<string> _transformTypes = new() { "di", "dii", "tdi", "tdii", "latt", "svf", "zdf" };
|
||||
private readonly List<string> _precision = new() { "auto", "s16", "s32", "f32", "f64" };
|
||||
/// <summary>
|
||||
/// HighPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#highpass"/>
|
||||
/// </summary>
|
||||
|
@ -26,11 +23,30 @@ namespace FFMpegCore.Arguments
|
|||
/// <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)
|
||||
{
|
||||
if (frequency < 0) throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number");
|
||||
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());
|
||||
if (frequency < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number");
|
||||
}
|
||||
|
||||
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("p", poles.ToString());
|
||||
|
@ -41,11 +57,13 @@ namespace FFMpegCore.Arguments
|
|||
{
|
||||
_arguments.Add("c", channels);
|
||||
}
|
||||
|
||||
_arguments.Add("n", (normalize ? 1 : 0).ToString());
|
||||
if (transform != "" && _transformTypes.Contains(transform))
|
||||
{
|
||||
_arguments.Add("a", transform);
|
||||
}
|
||||
|
||||
_arguments.Add("r", precision);
|
||||
if (block_size != null && block_size >= 0)
|
||||
{
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
/// </summary>
|
||||
string Text { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public interface IDynamicArgument
|
||||
{
|
||||
|
@ -12,4 +10,4 @@ namespace FFMpegCore.Arguments
|
|||
//public string GetText(StringBuilder context);
|
||||
public string GetText(IEnumerable<IArgument> context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
public interface IInputArgument : IInputOutputArgument
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public interface IInputOutputArgument : IArgument
|
||||
{
|
||||
|
@ -9,4 +6,4 @@ namespace FFMpegCore.Arguments
|
|||
Task During(CancellationToken cancellationToken = default);
|
||||
void Post();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
public interface IOutputArgument : IInputOutputArgument
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents input parameter
|
||||
|
@ -11,7 +7,7 @@ namespace FFMpegCore.Arguments
|
|||
{
|
||||
public readonly bool VerifyExists;
|
||||
public readonly string FilePath;
|
||||
|
||||
|
||||
public InputArgument(bool verifyExists, string filePaths)
|
||||
{
|
||||
VerifyExists = verifyExists;
|
||||
|
@ -23,12 +19,14 @@ namespace FFMpegCore.Arguments
|
|||
public void Pre()
|
||||
{
|
||||
if (VerifyExists && !File.Exists(FilePath))
|
||||
{
|
||||
throw new FileNotFoundException("Input file not found", FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() { }
|
||||
|
||||
|
||||
public string Text => $"-i \"{FilePath}\"";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an input device parameter
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO.Pipes;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
|
@ -24,7 +21,10 @@ namespace FFMpegCore.Arguments
|
|||
{
|
||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||
if (!Pipe.IsConnected)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
|
||||
await Writer.WriteAsync(Pipe, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class LowPassFilterArgument : IAudioFilterArgument
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new Dictionary<string, string>();
|
||||
private readonly List<string> _widthTypes = new List<string> { "h", "q", "o", "s", "k" };
|
||||
private readonly List<string> _transformTypes = new List<string> { "di", "dii", "tdi", "tdii", "latt", "svf", "zdf" };
|
||||
private readonly List<string> _precision = new List<string> { "auto", "s16", "s32", "f32", "f64" };
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
private readonly List<string> _widthTypes = new() { "h", "q", "o", "s", "k" };
|
||||
private readonly List<string> _transformTypes = new() { "di", "dii", "tdi", "tdii", "latt", "svf", "zdf" };
|
||||
private readonly List<string> _precision = new() { "auto", "s16", "s32", "f32", "f64" };
|
||||
/// <summary>
|
||||
/// LowPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#lowpass"/>
|
||||
/// </summary>
|
||||
|
@ -26,11 +23,30 @@ namespace FFMpegCore.Arguments
|
|||
/// <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)
|
||||
{
|
||||
if (frequency < 0) throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number");
|
||||
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());
|
||||
if (frequency < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number");
|
||||
}
|
||||
|
||||
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("p", poles.ToString());
|
||||
|
@ -41,11 +57,13 @@ namespace FFMpegCore.Arguments
|
|||
{
|
||||
_arguments.Add("c", channels);
|
||||
}
|
||||
|
||||
_arguments.Add("n", (normalize ? 1 : 0).ToString());
|
||||
if (transform != "" && _transformTypes.Contains(transform))
|
||||
{
|
||||
_arguments.Add("a", transform);
|
||||
}
|
||||
|
||||
_arguments.Add("r", precision);
|
||||
if (block_size != null && block_size >= 0)
|
||||
{
|
||||
|
|
|
@ -1,10 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class MapMetadataArgument : IInputArgument, IDynamicArgument
|
||||
{
|
||||
|
@ -55,7 +49,5 @@ namespace FFMpegCore.Arguments
|
|||
public void Pre()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,13 @@ namespace FFMpegCore.Arguments
|
|||
// "Both" is not valid in this case and probably means all stream types
|
||||
channel = Channel.All;
|
||||
}
|
||||
|
||||
_inputFileIndex = inputFileIndex;
|
||||
_streamIndex = streamIndex;
|
||||
_channel = channel;
|
||||
_negativeMap = negativeMap;
|
||||
}
|
||||
|
||||
public string Text => $"-map {(_negativeMap?"-":"")}{_inputFileIndex}{_channel.StreamType()}:{_streamIndex}";
|
||||
public string Text => $"-map {(_negativeMap ? "-" : "")}{_inputFileIndex}{_channel.StreamType()}:{_streamIndex}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class MetaDataArgument : IInputArgument, IDynamicArgument
|
||||
{
|
||||
|
@ -21,7 +14,6 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
|
||||
|
||||
public void Pre() => File.WriteAllText(_tempFileName, _metaDataContent);
|
||||
|
||||
public void Post() => File.Delete(_tempFileName);
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Exceptions;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
|
@ -23,7 +19,9 @@ namespace FFMpegCore.Arguments
|
|||
public void Pre()
|
||||
{
|
||||
if (!Overwrite && File.Exists(Path))
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.File, "Output file already exists and overwrite is disabled");
|
||||
}
|
||||
}
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post()
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
|
@ -20,7 +18,10 @@ namespace FFMpegCore.Arguments
|
|||
{
|
||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||
if (!Pipe.IsConnected)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
await Reader.ReadAsync(Pipe, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents outputting to url using supported protocols
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FFMpegCore.Extend;
|
||||
using FFMpegCore.Extend;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
|
@ -21,7 +18,7 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
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)));
|
||||
|
||||
|
@ -47,10 +44,12 @@ namespace FFMpegCore.Arguments
|
|||
{
|
||||
throw new Exception("At least one of the parameters must be not null");
|
||||
}
|
||||
|
||||
if (width != null)
|
||||
{
|
||||
Parameters.Add("width", width);
|
||||
}
|
||||
|
||||
if (height != null)
|
||||
{
|
||||
Parameters.Add("height", height);
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Mix channels with specific gain levels.
|
||||
|
@ -24,11 +21,11 @@ namespace FFMpegCore.Arguments
|
|||
{
|
||||
if (string.IsNullOrWhiteSpace(channelLayout))
|
||||
{
|
||||
throw new ArgumentException("The channel layout must be set" ,nameof(channelLayout));
|
||||
throw new ArgumentException("The channel layout must be set", nameof(channelLayout));
|
||||
}
|
||||
|
||||
ChannelLayout = channelLayout;
|
||||
|
||||
|
||||
_outputDefinitions = outputDefinitions;
|
||||
}
|
||||
|
||||
|
@ -41,11 +38,16 @@ namespace FFMpegCore.Arguments
|
|||
/// </param>
|
||||
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)
|
||||
{
|
||||
throw new ArgumentException("The number of output definitions must be equal or lower than number of channels", nameof(outputDefinitions));
|
||||
|
||||
}
|
||||
|
||||
ChannelLayout = $"{channels}c";
|
||||
|
||||
_outputDefinitions = outputDefinitions;
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
|
@ -24,7 +21,9 @@ namespace FFMpegCore.Arguments
|
|||
public void Pre()
|
||||
{
|
||||
if (Pipe != null)
|
||||
{
|
||||
throw new InvalidOperationException("Pipe already has been opened");
|
||||
}
|
||||
|
||||
Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
||||
}
|
||||
|
@ -40,7 +39,7 @@ namespace FFMpegCore.Arguments
|
|||
{
|
||||
try
|
||||
{
|
||||
await ProcessDataAsync(cancellationToken).ConfigureAwait(false);
|
||||
await ProcessDataAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
@ -50,7 +49,9 @@ namespace FFMpegCore.Arguments
|
|||
{
|
||||
Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}");
|
||||
if (Pipe is { IsConnected: true })
|
||||
{
|
||||
Pipe.Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
{
|
||||
public string Text => "-map_metadata -1";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents seek parameter
|
||||
|
@ -14,15 +12,18 @@ namespace FFMpegCore.Arguments
|
|||
SeekTo = seekTo;
|
||||
}
|
||||
|
||||
public string Text {
|
||||
get {
|
||||
if(SeekTo.HasValue)
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
if (SeekTo.HasValue)
|
||||
{
|
||||
int hours = SeekTo.Value.Hours;
|
||||
if(SeekTo.Value.Days > 0)
|
||||
var hours = SeekTo.Value.Hours;
|
||||
if (SeekTo.Value.Days > 0)
|
||||
{
|
||||
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")}";
|
||||
}
|
||||
else
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using FFMpegCore.Enums;
|
||||
using System;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
|
@ -14,17 +13,12 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
public string Key => string.Empty;
|
||||
|
||||
public string Value
|
||||
{
|
||||
get
|
||||
public string Value =>
|
||||
Mirroring switch
|
||||
{
|
||||
return Mirroring switch
|
||||
{
|
||||
Mirroring.Horizontal => "hflip",
|
||||
Mirroring.Vertical => "vflip",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(Mirroring))
|
||||
};
|
||||
}
|
||||
}
|
||||
Mirroring.Horizontal => "hflip",
|
||||
Mirroring.Vertical => "vflip",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(Mirroring))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class SilenceDetectArgument : IAudioFilterArgument
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new Dictionary<string, string>();
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
/// <summary>
|
||||
/// Silence Detection. <see href="https://ffmpeg.org/ffmpeg-filters.html#silencedetect"/>
|
||||
/// </summary>
|
||||
|
@ -18,17 +15,21 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
public SilenceDetectArgument(string noise_type = "db", double noise = 60, double duration = 2, bool mono = false)
|
||||
{
|
||||
if(noise_type == "db")
|
||||
if (noise_type == "db")
|
||||
{
|
||||
_arguments.Add("n", $"{noise.ToString("0.0", CultureInfo.InvariantCulture)}dB");
|
||||
}
|
||||
else if (noise_type == "ar")
|
||||
else if (noise_type == "ar")
|
||||
{
|
||||
_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("m", (mono ? 1 : 0).ToString());
|
||||
_arguments.Add("m", (mono ? 1 : 0).ToString());
|
||||
}
|
||||
|
||||
public string Key { get; } = "silencedetect";
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
public class StartNumberArgument : IArgument
|
||||
{
|
||||
public readonly int StartNumber;
|
||||
|
||||
|
||||
public StartNumberArgument(int startNumber)
|
||||
{
|
||||
StartNumber = startNumber;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
using FFMpegCore.Extend;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Drawing;
|
||||
using FFMpegCore.Extend;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
|
@ -23,7 +21,7 @@ namespace FFMpegCore.Arguments
|
|||
{
|
||||
private readonly string _subtitle;
|
||||
|
||||
public readonly Dictionary<string, string> Parameters = new Dictionary<string, string>();
|
||||
public readonly Dictionary<string, string> Parameters = new();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SubtitleHardBurnOptions"/> using a provided subtitle file or a video file
|
||||
|
@ -110,7 +108,7 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
public class StyleOptions
|
||||
{
|
||||
public readonly Dictionary<string, string> Parameters = new Dictionary<string, string>();
|
||||
public readonly Dictionary<string, string> Parameters = new();
|
||||
|
||||
public static StyleOptions Create()
|
||||
{
|
||||
|
@ -131,4 +129,4 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
internal string TextInternal => string.Join(",", Parameters.Select(parameter => parameter.FormatArgumentPair(enclose: false)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents threads parameter
|
||||
|
|
|
@ -20,4 +20,4 @@ namespace FFMpegCore.Arguments
|
|||
public string Key { get; } = "transpose";
|
||||
public string Value => ((int)Transposition).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Variable Bitrate Argument (VBR) argument
|
||||
|
@ -21,4 +19,4 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
public string Text => $"-vbr {Vbr}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,4 +22,4 @@
|
|||
Debug = 48,
|
||||
Trace = 56
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,4 +14,4 @@
|
|||
|
||||
public string Text => $"-b:v {Bitrate}k";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,10 +15,12 @@ namespace FFMpegCore.Arguments
|
|||
Codec = codec;
|
||||
}
|
||||
|
||||
public VideoCodecArgument(Codec value)
|
||||
public VideoCodecArgument(Codec value)
|
||||
{
|
||||
if (value.Type != CodecType.Video)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{value.Name}\" is not a video codec");
|
||||
}
|
||||
|
||||
Codec = value.Name;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Drawing;
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
|
@ -9,7 +7,7 @@ namespace FFMpegCore.Arguments
|
|||
public class VideoFiltersArgument : IArgument
|
||||
{
|
||||
public readonly VideoFilterOptions Options;
|
||||
|
||||
|
||||
public VideoFiltersArgument(VideoFilterOptions options)
|
||||
{
|
||||
Options = options;
|
||||
|
@ -20,7 +18,9 @@ namespace FFMpegCore.Arguments
|
|||
private string GetText()
|
||||
{
|
||||
if (!Options.Arguments.Any())
|
||||
{
|
||||
throw new FFMpegArgumentException("No video-filter arguments provided");
|
||||
}
|
||||
|
||||
var arguments = Options.Arguments
|
||||
.Where(arg => !string.IsNullOrEmpty(arg.Value))
|
||||
|
@ -42,8 +42,8 @@ namespace FFMpegCore.Arguments
|
|||
|
||||
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(int width, int height) => WithArgument(new ScaleArgument(width, height));
|
||||
public VideoFilterOptions Scale(Size size) => WithArgument(new ScaleArgument(size));
|
||||
|
@ -61,4 +61,4 @@ namespace FFMpegCore.Arguments
|
|||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Builders.MetaData
|
||||
namespace FFMpegCore.Builders.MetaData
|
||||
{
|
||||
public class ChapterData
|
||||
{
|
||||
|
@ -15,4 +13,4 @@ namespace FFMpegCore.Builders.MetaData
|
|||
End = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace FFMpegCore.Builders.MetaData
|
||||
namespace FFMpegCore.Builders.MetaData
|
||||
{
|
||||
|
||||
public interface IReadOnlyMetaData
|
||||
|
@ -8,4 +6,4 @@ namespace FFMpegCore.Builders.MetaData
|
|||
IReadOnlyList<ChapterData> Chapters { get; }
|
||||
IReadOnlyDictionary<string, string> Entries { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FFMpegCore.Builders.MetaData
|
||||
namespace FFMpegCore.Builders.MetaData
|
||||
{
|
||||
public class MetaData : IReadOnlyMetaData
|
||||
{
|
||||
public Dictionary<string, string> Entries { get; private set; }
|
||||
public List<ChapterData> Chapters { get; private set; }
|
||||
|
||||
IReadOnlyList<ChapterData> IReadOnlyMetaData.Chapters => this.Chapters;
|
||||
IReadOnlyDictionary<string, string> IReadOnlyMetaData.Entries => this.Entries;
|
||||
IReadOnlyList<ChapterData> IReadOnlyMetaData.Chapters => Chapters;
|
||||
IReadOnlyDictionary<string, string> IReadOnlyMetaData.Entries => Entries;
|
||||
|
||||
public MetaData()
|
||||
{
|
||||
|
@ -30,4 +27,4 @@ namespace FFMpegCore.Builders.MetaData
|
|||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FFMpegCore.Builders.MetaData
|
||||
namespace FFMpegCore.Builders.MetaData
|
||||
{
|
||||
public class MetaDataBuilder
|
||||
{
|
||||
private MetaData _metaData = new MetaData();
|
||||
private readonly MetaData _metaData = new();
|
||||
|
||||
public MetaDataBuilder WithEntry(string key, string entry)
|
||||
{
|
||||
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;
|
||||
|
@ -20,10 +16,10 @@ namespace FFMpegCore.Builders.MetaData
|
|||
}
|
||||
|
||||
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)
|
||||
=> this.WithEntry(key, String.Join("; ", values));
|
||||
=> WithEntry(key, string.Join("; ", values));
|
||||
|
||||
public MetaDataBuilder AddChapter(ChapterData chapterData)
|
||||
{
|
||||
|
@ -33,7 +29,7 @@ namespace FFMpegCore.Builders.MetaData
|
|||
|
||||
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);
|
||||
AddChapter(duration, title);
|
||||
|
@ -46,13 +42,13 @@ namespace FFMpegCore.Builders.MetaData
|
|||
{
|
||||
var start = _metaData.Chapters.LastOrDefault()?.End ?? TimeSpan.Zero;
|
||||
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
|
||||
(
|
||||
start: start,
|
||||
end: end,
|
||||
title: title ?? String.Empty
|
||||
title: title ?? string.Empty
|
||||
));
|
||||
|
||||
return this;
|
||||
|
@ -102,8 +98,6 @@ namespace FFMpegCore.Builders.MetaData
|
|||
//encoder=Lavf58.47.100
|
||||
public MetaDataBuilder WithEncoder(string value) => WithEntry("encoder", value);
|
||||
|
||||
|
||||
|
||||
public ReadOnlyMetaData Build() => new ReadOnlyMetaData(_metaData);
|
||||
public ReadOnlyMetaData Build() => new(_metaData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace FFMpegCore.Builders.MetaData
|
||||
{
|
||||
public class MetaDataSerializer
|
||||
{
|
||||
public static readonly MetaDataSerializer Instance = new MetaDataSerializer();
|
||||
public static readonly MetaDataSerializer Instance = new();
|
||||
|
||||
public string Serialize(IReadOnlyMetaData metaData)
|
||||
{
|
||||
|
@ -17,7 +16,7 @@ namespace FFMpegCore.Builders.MetaData
|
|||
sb.AppendLine($"{value.Key}={value.Value}");
|
||||
}
|
||||
|
||||
int chapterNumber = 0;
|
||||
var chapterNumber = 0;
|
||||
foreach (var chapter in metaData.Chapters ?? Enumerable.Empty<ChapterData>())
|
||||
{
|
||||
chapterNumber++;
|
||||
|
@ -35,4 +34,4 @@ namespace FFMpegCore.Builders.MetaData
|
|||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FFMpegCore.Builders.MetaData
|
||||
namespace FFMpegCore.Builders.MetaData
|
||||
{
|
||||
public class ReadOnlyMetaData : IReadOnlyMetaData
|
||||
{
|
||||
|
@ -22,4 +19,4 @@ namespace FFMpegCore.Builders.MetaData
|
|||
.AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,4 +9,4 @@
|
|||
BelowNormal = 96,
|
||||
Low = 64
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using FFMpegCore.Exceptions;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Enums
|
||||
{
|
||||
|
@ -13,8 +12,8 @@ namespace FFMpegCore.Enums
|
|||
|
||||
public class Codec
|
||||
{
|
||||
private static readonly Regex _codecsFormatRegex = new Regex(@"([D\.])([E\.])([VASD\.])([I\.])([L\.])([S\.])\s+([a-z0-9_-]+)\s+(.+)");
|
||||
private static readonly Regex _decodersEncodersFormatRegex = new Regex(@"([VASD\.])([F\.])([S\.])([X\.])([B\.])([D\.])\s+([a-z0-9_-]+)\s+(.+)");
|
||||
private static readonly Regex _codecsFormatRegex = new(@"([D\.])([E\.])([VASD\.])([I\.])([L\.])([S\.])\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
|
||||
{
|
||||
|
@ -73,7 +72,7 @@ namespace FFMpegCore.Enums
|
|||
_ => CodecType.Unknown
|
||||
};
|
||||
|
||||
if(type == CodecType.Unknown)
|
||||
if (type == CodecType.Unknown)
|
||||
{
|
||||
codec = null!;
|
||||
return false;
|
||||
|
@ -133,7 +132,9 @@ namespace FFMpegCore.Enums
|
|||
internal void Merge(Codec other)
|
||||
{
|
||||
if (Name != other.Name)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, "different codecs enable to merge");
|
||||
}
|
||||
|
||||
Type |= other.Type;
|
||||
DecodingSupported |= other.DecodingSupported;
|
||||
|
@ -146,7 +147,9 @@ namespace FFMpegCore.Enums
|
|||
DecoderFeatureLevel.Merge(other.DecoderFeatureLevel);
|
||||
|
||||
if (Description != other.Description)
|
||||
{
|
||||
Description += "\r\n" + other.Description;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace FFMpegCore.Enums
|
|||
{
|
||||
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 bool DemuxingSupported { get; private set; }
|
||||
|
@ -16,7 +16,10 @@ namespace FFMpegCore.Enums
|
|||
get
|
||||
{
|
||||
if (GlobalFFOptions.Current.ExtensionOverrides.ContainsKey(Name))
|
||||
{
|
||||
return GlobalFFOptions.Current.ExtensionOverrides[Name];
|
||||
}
|
||||
|
||||
return "." + Name;
|
||||
}
|
||||
}
|
||||
|
@ -44,4 +47,4 @@ namespace FFMpegCore.Enums
|
|||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
public static Codec LibFdk_Aac => FFMpeg.GetCodec("libfdk_aac");
|
||||
public static Codec Ac3 => FFMpeg.GetCodec("ac3");
|
||||
public static Codec Eac3 => FFMpeg.GetCodec("eac3");
|
||||
public static Codec LibMp3Lame => FFMpeg.GetCodec("libmp3lame");
|
||||
public static Codec LibMp3Lame => FFMpeg.GetCodec("libmp3lame");
|
||||
}
|
||||
|
||||
public static class VideoType
|
||||
|
@ -84,5 +84,4 @@
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Enums
|
||||
namespace FFMpegCore.Enums
|
||||
{
|
||||
public static class FileExtension
|
||||
{
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
DXVA2,
|
||||
QSV,
|
||||
CUVID,
|
||||
CUDA,
|
||||
VDPAU,
|
||||
VAAPI,
|
||||
LibMFX
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace FFMpegCore.Enums
|
|||
{
|
||||
public class PixelFormat
|
||||
{
|
||||
private static readonly Regex _formatRegex = new Regex(@"([I\.])([O\.])([H\.])([P\.])([B\.])\s+(\S+)\s+([0-9]+)\s+([0-9]+)");
|
||||
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 OutputConversionSupported { get; private set; }
|
||||
|
@ -41,10 +41,16 @@ namespace FFMpegCore.Enums
|
|||
fmt.IsPaletted = match.Groups[4].Value != ".";
|
||||
fmt.IsBitstream = match.Groups[5].Value != ".";
|
||||
if (!int.TryParse(match.Groups[7].Value, out var nbComponents))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fmt.Components = nbComponents;
|
||||
if (!int.TryParse(match.Groups[8].Value, out var bpp))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fmt.BitsPerPixel = bpp;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -12,4 +12,4 @@
|
|||
SuperFast,
|
||||
UltraFast
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,4 @@
|
|||
CounterClockwise90 = 2,
|
||||
Clockwise90VerticalFlip = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,4 +8,4 @@
|
|||
Ld = 360,
|
||||
Original = -1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
|
||||
namespace FFMpegCore.Exceptions
|
||||
namespace FFMpegCore.Exceptions
|
||||
{
|
||||
public enum FFMpegExceptionType
|
||||
{
|
||||
|
@ -30,7 +28,7 @@ namespace FFMpegCore.Exceptions
|
|||
FFMpegErrorOutput = string.Empty;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
|
||||
public FFMpegExceptionType Type { get; }
|
||||
public string FFMpegErrorOutput { get; }
|
||||
}
|
||||
|
@ -57,4 +55,4 @@ namespace FFMpegCore.Exceptions
|
|||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,69 +1,11 @@
|
|||
using FFMpegCore.Enums;
|
||||
using System.Drawing;
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
using FFMpegCore.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Instances;
|
||||
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -79,7 +21,9 @@ namespace FFMpegCore
|
|||
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)
|
||||
{
|
||||
output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Png);
|
||||
}
|
||||
|
||||
var source = FFProbe.Analyse(input);
|
||||
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||
|
@ -101,7 +45,9 @@ namespace FFMpegCore
|
|||
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)
|
||||
{
|
||||
output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Png);
|
||||
}
|
||||
|
||||
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
|
||||
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||
|
@ -111,6 +57,73 @@ namespace FFMpegCore
|
|||
.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>
|
||||
/// Convert a video do a different format.
|
||||
|
@ -140,7 +153,9 @@ namespace FFMpegCore
|
|||
var outputSize = new Size((int)(source.PrimaryVideoStream!.Width / scale), (int)(source.PrimaryVideoStream.Height / scale));
|
||||
|
||||
if (outputSize.Width % 2 != 0)
|
||||
{
|
||||
outputSize.Width += 1;
|
||||
}
|
||||
|
||||
return format.Name switch
|
||||
{
|
||||
|
@ -235,7 +250,9 @@ namespace FFMpegCore
|
|||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
||||
|
||||
if (uri.Scheme != "http" && uri.Scheme != "https")
|
||||
{
|
||||
throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream.");
|
||||
}
|
||||
|
||||
return FFMpegArguments
|
||||
.FromUrlInput(uri)
|
||||
|
@ -315,12 +332,16 @@ namespace FFMpegCore
|
|||
processArguments.OutputDataReceived += (e, data) =>
|
||||
{
|
||||
if (PixelFormat.TryParse(data, out var format))
|
||||
{
|
||||
list.Add(format);
|
||||
}
|
||||
};
|
||||
|
||||
var result = processArguments.StartAndWaitForExit();
|
||||
if (result.ExitCode != 0)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
|
||||
}
|
||||
|
||||
return list.AsReadOnly();
|
||||
}
|
||||
|
@ -328,7 +349,10 @@ namespace FFMpegCore
|
|||
public static IReadOnlyList<PixelFormat> GetPixelFormats()
|
||||
{
|
||||
if (!GlobalFFOptions.Current.UseCache)
|
||||
{
|
||||
return GetPixelFormatsInternal();
|
||||
}
|
||||
|
||||
return FFMpegCache.PixelFormats.Values.ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
|
@ -340,13 +364,18 @@ namespace FFMpegCore
|
|||
return format != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FFMpegCache.PixelFormats.TryGetValue(name, out format);
|
||||
}
|
||||
}
|
||||
|
||||
public static PixelFormat GetPixelFormat(string name)
|
||||
{
|
||||
if (TryGetPixelFormat(name, out var fmt))
|
||||
{
|
||||
return fmt;
|
||||
}
|
||||
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Pixel format \"{name}\" not supported");
|
||||
}
|
||||
#endregion
|
||||
|
@ -362,14 +391,23 @@ namespace FFMpegCore
|
|||
{
|
||||
var codec = parser(data);
|
||||
if (codec != null)
|
||||
{
|
||||
if (codecs.TryGetValue(codec.Name, out var parentCodec))
|
||||
{
|
||||
parentCodec.Merge(codec);
|
||||
}
|
||||
else
|
||||
{
|
||||
codecs.Add(codec.Name, codec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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()
|
||||
|
@ -378,19 +416,28 @@ namespace FFMpegCore
|
|||
ParsePartOfCodecs(res, "-codecs", (s) =>
|
||||
{
|
||||
if (Codec.TryParseFromCodecs(s, out var codec))
|
||||
{
|
||||
return codec;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
ParsePartOfCodecs(res, "-encoders", (s) =>
|
||||
{
|
||||
if (Codec.TryParseFromEncodersDecoders(s, out var codec, true))
|
||||
{
|
||||
return codec;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
ParsePartOfCodecs(res, "-decoders", (s) =>
|
||||
{
|
||||
if (Codec.TryParseFromEncodersDecoders(s, out var codec, false))
|
||||
{
|
||||
return codec;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
|
@ -400,14 +447,20 @@ namespace FFMpegCore
|
|||
public static IReadOnlyList<Codec> GetCodecs()
|
||||
{
|
||||
if (!GlobalFFOptions.Current.UseCache)
|
||||
{
|
||||
return GetCodecsInternal().Values.ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
return FFMpegCache.Codecs.Values.ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public static IReadOnlyList<Codec> GetCodecs(CodecType type)
|
||||
{
|
||||
if (!GlobalFFOptions.Current.UseCache)
|
||||
{
|
||||
return GetCodecsInternal().Values.Where(x => x.Type == type).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
return FFMpegCache.Codecs.Values.Where(x => x.Type == type).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
|
@ -424,13 +477,18 @@ namespace FFMpegCore
|
|||
return codec != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FFMpegCache.Codecs.TryGetValue(name, out codec);
|
||||
}
|
||||
}
|
||||
|
||||
public static Codec GetCodec(string name)
|
||||
{
|
||||
if (TryGetCodec(name, out var codec) && codec != null)
|
||||
{
|
||||
return codec;
|
||||
}
|
||||
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{name}\" not supported");
|
||||
}
|
||||
#endregion
|
||||
|
@ -445,11 +503,16 @@ namespace FFMpegCore
|
|||
instance.OutputDataReceived += (e, data) =>
|
||||
{
|
||||
if (ContainerFormat.TryParse(data, out var fmt))
|
||||
{
|
||||
list.Add(fmt);
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -457,7 +520,10 @@ namespace FFMpegCore
|
|||
public static IReadOnlyList<ContainerFormat> GetContainerFormats()
|
||||
{
|
||||
if (!GlobalFFOptions.Current.UseCache)
|
||||
{
|
||||
return GetContainersFormatsInternal();
|
||||
}
|
||||
|
||||
return FFMpegCache.ContainerFormats.Values.ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
|
@ -469,13 +535,18 @@ namespace FFMpegCore
|
|||
return fmt != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FFMpegCache.ContainerFormats.TryGetValue(name, out fmt);
|
||||
}
|
||||
}
|
||||
|
||||
public static ContainerFormat GetContainerFormat(string name)
|
||||
{
|
||||
if (TryGetContainerFormat(name, out var fmt))
|
||||
{
|
||||
return fmt;
|
||||
}
|
||||
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Container format \"{name}\" not supported");
|
||||
}
|
||||
#endregion
|
||||
|
@ -485,8 +556,10 @@ namespace FFMpegCore
|
|||
foreach (var path in pathList)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Drawing;
|
||||
using FFMpegCore.Arguments;
|
||||
using FFMpegCore.Enums;
|
||||
|
||||
|
@ -20,8 +17,6 @@ namespace FFMpegCore
|
|||
public FFMpegArgumentOptions Resize(int width, int height) => WithArgument(new SizeArgument(width, height));
|
||||
public FFMpegArgumentOptions Resize(Size? size) => WithArgument(new SizeArgument(size));
|
||||
|
||||
|
||||
|
||||
public FFMpegArgumentOptions WithBitStreamFilter(Channel channel, Filter filter) => WithArgument(new BitStreamFilterArgument(channel, filter));
|
||||
public FFMpegArgumentOptions WithConstantRateFactor(int crf) => WithArgument(new ConstantRateFactorArgument(crf));
|
||||
public FFMpegArgumentOptions CopyChannel(Channel channel = Channel.Both) => WithArgument(new CopyArgument(channel));
|
||||
|
@ -81,11 +76,10 @@ namespace FFMpegCore
|
|||
public FFMpegArgumentOptions WithAudibleActivationBytes(string activationBytes) => WithArgument(new AudibleEncryptionKeyArgument(activationBytes));
|
||||
public FFMpegArgumentOptions WithTagVersion(int id3v2Version = 3) => WithArgument(new ID3V2VersionArgument(id3v2Version));
|
||||
|
||||
|
||||
public FFMpegArgumentOptions WithArgument(IArgument argument)
|
||||
{
|
||||
Arguments.Add(argument);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
using FFMpegCore.Exceptions;
|
||||
using FFMpegCore.Helpers;
|
||||
using Instances;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
using FFMpegCore.Helpers;
|
||||
using Instances;
|
||||
|
||||
namespace FFMpegCore
|
||||
{
|
||||
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 FFMpegArguments _ffMpegArguments;
|
||||
private Action<double>? _onPercentageProgress;
|
||||
|
@ -110,7 +106,9 @@ namespace FFMpegCore
|
|||
catch (OperationCanceledException)
|
||||
{
|
||||
if (throwOnError)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
|
||||
|
@ -129,7 +127,9 @@ namespace FFMpegCore
|
|||
catch (OperationCanceledException)
|
||||
{
|
||||
if (throwOnError)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
|
||||
|
@ -154,6 +154,7 @@ namespace FFMpegCore
|
|||
instance.Kill();
|
||||
}
|
||||
}
|
||||
|
||||
CancelEvent += OnCancelEvent;
|
||||
|
||||
try
|
||||
|
@ -181,10 +182,15 @@ namespace FFMpegCore
|
|||
private bool HandleCompletion(bool throwOnError, int exitCode, IReadOnlyList<string> errorData)
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
_onPercentageProgress?.Invoke(100.0);
|
||||
if (_totalTimespan.HasValue) _onTimeProgress?.Invoke(_totalTimespan.Value);
|
||||
if (_totalTimespan.HasValue)
|
||||
{
|
||||
_onTimeProgress?.Invoke(_totalTimespan.Value);
|
||||
}
|
||||
|
||||
return exitCode == 0;
|
||||
}
|
||||
|
@ -207,16 +213,18 @@ namespace FFMpegCore
|
|||
FFMpegHelper.RootExceptionCheck();
|
||||
FFMpegHelper.VerifyFFMpegExists(ffOptions);
|
||||
|
||||
string? arguments = _ffMpegArguments.Text;
|
||||
var arguments = _ffMpegArguments.Text;
|
||||
|
||||
//If local loglevel is null, set the global.
|
||||
if (_logLevel == null)
|
||||
{
|
||||
_logLevel = ffOptions.LogLevel;
|
||||
}
|
||||
|
||||
//If neither local nor global loglevel is null, set the argument.
|
||||
if (_logLevel != null)
|
||||
{
|
||||
string normalizedLogLevel = _logLevel.ToString()
|
||||
var normalizedLogLevel = _logLevel.ToString()
|
||||
.ToLower();
|
||||
arguments += $" -v {normalizedLogLevel}";
|
||||
}
|
||||
|
@ -233,10 +241,14 @@ namespace FFMpegCore
|
|||
cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
if (_onOutput != null)
|
||||
{
|
||||
processArguments.OutputDataReceived += OutputData;
|
||||
}
|
||||
|
||||
if (_onError != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null))
|
||||
{
|
||||
processArguments.ErrorDataReceived += ErrorData;
|
||||
}
|
||||
|
||||
return processArguments;
|
||||
}
|
||||
|
@ -246,12 +258,19 @@ namespace FFMpegCore
|
|||
_onError?.Invoke(msg);
|
||||
|
||||
var match = ProgressRegex.Match(msg);
|
||||
if (!match.Success) return;
|
||||
if (!match.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
|
||||
_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);
|
||||
_onPercentageProgress(percentage);
|
||||
}
|
||||
|
@ -262,4 +281,4 @@ namespace FFMpegCore
|
|||
_onOutput?.Invoke(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using FFMpegCore.Arguments;
|
||||
using FFMpegCore.Arguments;
|
||||
using FFMpegCore.Builders.MetaData;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
|
@ -13,7 +6,7 @@ namespace FFMpegCore
|
|||
{
|
||||
public sealed class FFMpegArguments : FFMpegArgumentsBase
|
||||
{
|
||||
private readonly FFMpegGlobalArguments _globalArguments = new FFMpegGlobalArguments();
|
||||
private readonly FFMpegGlobalArguments _globalArguments = new();
|
||||
|
||||
private FFMpegArguments() { }
|
||||
|
||||
|
@ -33,7 +26,6 @@ namespace FFMpegCore
|
|||
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 FFMpegArguments WithGlobalOptions(Action<FFMpegGlobalArguments> configureOptions)
|
||||
{
|
||||
configureOptions(_globalArguments);
|
||||
|
@ -50,7 +42,6 @@ namespace FFMpegCore
|
|||
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);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Maps the metadata of the given stream
|
||||
/// </summary>
|
||||
|
@ -83,7 +74,9 @@ namespace FFMpegCore
|
|||
internal void Pre()
|
||||
{
|
||||
foreach (var argument in Arguments.OfType<IInputOutputArgument>())
|
||||
{
|
||||
argument.Pre();
|
||||
}
|
||||
}
|
||||
internal async Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
@ -93,7 +86,9 @@ namespace FFMpegCore
|
|||
internal void Post()
|
||||
{
|
||||
foreach (var argument in Arguments.OfType<IInputOutputArgument>())
|
||||
{
|
||||
argument.Post();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using FFMpegCore.Arguments;
|
||||
using FFMpegCore.Arguments;
|
||||
|
||||
namespace FFMpegCore
|
||||
{
|
||||
public abstract class FFMpegArgumentsBase
|
||||
{
|
||||
internal readonly List<IArgument> Arguments = new List<IArgument>();
|
||||
internal readonly List<IArgument> Arguments = new();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
using FFMpegCore.Enums;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
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, Codec>? _codecs;
|
||||
private static Dictionary<string, ContainerFormat>? _containers;
|
||||
|
@ -16,39 +14,54 @@ namespace FFMpegCore
|
|||
get
|
||||
{
|
||||
if (_pixelFormats == null) //First check not thread safe
|
||||
{
|
||||
lock (_syncObject)
|
||||
{
|
||||
if (_pixelFormats == null)//Second check thread safe
|
||||
{
|
||||
_pixelFormats = FFMpeg.GetPixelFormatsInternal().ToDictionary(x => x.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _pixelFormats;
|
||||
}
|
||||
|
||||
}
|
||||
public static IReadOnlyDictionary<string, Codec> Codecs
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_codecs == null) //First check not thread safe
|
||||
{
|
||||
lock (_syncObject)
|
||||
{
|
||||
if (_codecs == null)//Second check thread safe
|
||||
{
|
||||
_codecs = FFMpeg.GetCodecsInternal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _codecs;
|
||||
}
|
||||
|
||||
}
|
||||
public static IReadOnlyDictionary<string, ContainerFormat> ContainerFormats
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_containers == null) //First check not thread safe
|
||||
{
|
||||
lock (_syncObject)
|
||||
{
|
||||
if (_containers == null)//Second check thread safe
|
||||
{
|
||||
_containers = FFMpeg.GetContainersFormatsInternal().ToDictionary(x => x.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _containers;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,13 @@ namespace FFMpegCore
|
|||
public sealed class FFMpegGlobalArguments : FFMpegArgumentsBase
|
||||
{
|
||||
internal FFMpegGlobalArguments() { }
|
||||
|
||||
|
||||
public FFMpegGlobalArguments WithVerbosityLevel(VerbosityLevel verbosityLevel = VerbosityLevel.Error) => WithOption(new VerbosityLevelArgument(verbosityLevel));
|
||||
|
||||
|
||||
private FFMpegGlobalArguments WithOption(IArgument argument)
|
||||
{
|
||||
Arguments.Add(argument);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Pipes
|
||||
namespace FFMpegCore.Pipes
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for Audio sample
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Pipes
|
||||
namespace FFMpegCore.Pipes
|
||||
{
|
||||
public interface IPipeSink
|
||||
{
|
||||
|
|
|
@ -1,8 +1,4 @@
|
|||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FFMpegCore.Pipes
|
||||
namespace FFMpegCore.Pipes
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for ffmpeg pipe source data IO
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue