mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2025-12-14 18:15:44 +00:00
V.5.0.0 (#391)
* Move NotifyOnProgress processing to ErrorData
* added HighPass filter, LowPass filter, Audiogate and Silencedetection
* re-added corresponding AudioFilterOptions
* Update LICENSE
* Update ci.yml
* Fix argument
* Bump action versions
* Ignore Uri_Duration until root cause found
* Use action that can install specific ffmpeg version
* Remove ignore
* Revert "Remove ignore"
This reverts commit d85a4b81ab.
* Bump dependencies
* Use setup-dotnet@v2 since v3 seems to install .NET 7
* Init
* WIP
* Add Directory.Build.props
* Add SupportedOSPlatform attribute on tests using SDC
* Fix using temporarily
* Add IgnoreIf attribute to only run SDC tests on Windows
* Cleanup pipelines
* Cleanup
* Cleanup using directives
* More cleanup
* Simplify attribute
* Fix attribute
* Add missing test file
* Added blackdetect and blackframe arguments
* Added log levels
* Add missing using directive after rebase
* fix extension is not png lost path
* Apply fix to methods in new location
* Add Uri support for Frame Analysis,
skipped Stream support as this cannot support MP4's with moov atom in the end of the file, and input pipes do not support seek.
* Add select multiple streams
* Add other stream types to Channel (V,s,d,t)
* Add negative mapping to select stream (deselect)
* Update test
* Add pad video filter
* Update PipeHelpers.cs
* Fix GetPipePath() for MacOS
* Add SampleAspectRatio property to VideoStream
* Update year
* Always use Path.GetTempPath() on linux and macos
* Use FedericoCarboni/setup-ffmpeg@v2
* Include macos in ci matrix
* AddDeviceInput similar to AddFileInput and FromDeviceInput
* fixed hwaccel parameter not working in 5.0
* a hack to unconditionally kill ffmpeg when parent .NET process exits
* Remove PInvoke.Kernel32
* Remove unneeded cast
* Update test
* Added ability to retrieve bit depth from media streams for lossless encodings (#359)
* Added ability to retrieve bit depth from media streams for lossless encodings
* Shortened sample AIFF file used in tests
* 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
* fix: Switch source for rotation property from 'tags/rotate' to 'side_data_list/rotation' (incl. test case) (#388)
* Init (#389)
* build master branch after merge or push (#390)
* Update ci.yml
* Add codecov
* Remove codecov for now
* Add coverlet.collector and codecov
---------
Co-authored-by: keg247 <44041557+keg247@users.noreply.github.com>
Co-authored-by: Wilbert Bongers <msdnwilbert@muziekweb.nl>
Co-authored-by: Artemii Gazizianov <107502822+ArtemiiimetrA@users.noreply.github.com>
Co-authored-by: Thodoris Koskinopoulos <me@koskit.me>
Co-authored-by: 赵宁 <602726286@qq.com>
Co-authored-by: jeroenvanderschoot <jeroen.vanderschoot@kinetiq.tv>
Co-authored-by: Sky Z <qe201020335@sina.com>
Co-authored-by: Gleb Moskalenko <gleb.moskalenko.general@gmail.com>
Co-authored-by: ep1kt3t0s <86835785+ep1kt3t0s@users.noreply.github.com>
Co-authored-by: Victor Nova <lostfreeman@gmail.com>
Co-authored-by: Tom Bogle <tom_bogle@sil.org>
Co-authored-by: pklaes <10601494+pklaes@users.noreply.github.com>
This commit is contained in:
parent
9efd5c5d4f
commit
278ab4c7b5
144 changed files with 2083 additions and 1203 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
|
||||||
35
.github/workflows/ci.yml
vendored
35
.github/workflows/ci.yml
vendored
|
|
@ -5,9 +5,9 @@ on:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/ci.yml
|
- .github/workflows/ci.yml
|
||||||
- FFMpegCore/**
|
- FFMpegCore/**
|
||||||
- FFMpegCore.Test/**
|
- FFMpegCore.Test/**
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
|
@ -22,16 +22,31 @@ jobs:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest, ubuntu-latest]
|
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||||
timeout-minutes: 6
|
timeout-minutes: 7
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Prepare .NET
|
- name: Prepare .NET
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '7.0.x'
|
||||||
|
|
||||||
|
- name: Lint with dotnet
|
||||||
|
run: dotnet format FFMpegCore.sln --severity warn --verify-no-changes
|
||||||
|
|
||||||
- name: Prepare FFMpeg
|
- name: Prepare FFMpeg
|
||||||
uses: FedericoCarboni/setup-ffmpeg@v1
|
uses: FedericoCarboni/setup-ffmpeg@v2
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Test with dotnet
|
- name: Test with dotnet
|
||||||
run: dotnet test --logger GitHubActions
|
run: dotnet test FFMpegCore.sln --collect "XPlat Code Coverage" --logger GitHubActions
|
||||||
|
|
||||||
|
- name: Upload coverage reports to Codecov
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
directory: FFMpegCore.Test/TestResults
|
||||||
|
fail_ci_if_error: true
|
||||||
21
.github/workflows/release.yml
vendored
21
.github/workflows/release.yml
vendored
|
|
@ -8,13 +8,16 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
- name: Prepare .NET
|
|
||||||
uses: actions/setup-dotnet@v1
|
- name: Prepare .NET
|
||||||
with:
|
uses: actions/setup-dotnet@v3
|
||||||
dotnet-version: '6.0.x'
|
with:
|
||||||
- name: Build solution
|
dotnet-version: '7.0.x'
|
||||||
run: dotnet build --output build -c Release
|
|
||||||
- name: Publish NuGet package
|
- name: Build solution
|
||||||
run: dotnet nuget push "build/*.nupkg" --source nuget.org --api-key ${{ secrets.NUGET_TOKEN }}
|
run: dotnet pack FFMpegCore.sln --output build -c Release
|
||||||
|
|
||||||
|
- name: Publish NuGet package
|
||||||
|
run: dotnet nuget push build/*.nupkg --source nuget.org --api-key ${{ secrets.NUGET_TOKEN }}
|
||||||
|
|
||||||
|
|
|
||||||
17
Directory.Build.props
Normal file
17
Directory.Build.props
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<NeutralLanguage>en</NeutralLanguage>
|
||||||
|
<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>
|
||||||
|
<PackageProjectUrl>https://github.com/rosenbjerg/FFMpegCore</PackageProjectUrl>
|
||||||
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
<NeutralLanguage>en</NeutralLanguage>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FFMpegCore.Extensions.System.Drawing.Common\FFMpegCore.Extensions.System.Drawing.Common.csproj" />
|
||||||
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj" />
|
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
using System;
|
using System.Drawing;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
|
||||||
using FFMpegCore;
|
using FFMpegCore;
|
||||||
using FFMpegCore.Enums;
|
using FFMpegCore.Enums;
|
||||||
|
using FFMpegCore.Extensions.System.Drawing.Common;
|
||||||
using FFMpegCore.Pipes;
|
using FFMpegCore.Pipes;
|
||||||
using FFMpegCore.Extend;
|
|
||||||
|
|
||||||
var inputPath = "/path/to/input";
|
var inputPath = "/path/to/input";
|
||||||
var outputPath = "/path/to/output";
|
var outputPath = "/path/to/output";
|
||||||
|
|
@ -34,7 +31,7 @@ var outputPath = "/path/to/output";
|
||||||
|
|
||||||
{
|
{
|
||||||
// process the snapshot in-memory and use the Bitmap directly
|
// process the snapshot in-memory and use the Bitmap directly
|
||||||
var bitmap = FFMpeg.Snapshot(inputPath, new Size(200, 400), TimeSpan.FromMinutes(1));
|
var bitmap = FFMpegImage.Snapshot(inputPath, new Size(200, 400), TimeSpan.FromMinutes(1));
|
||||||
|
|
||||||
// or persists the image on the drive
|
// or persists the image on the drive
|
||||||
FFMpeg.Snapshot(inputPath, outputPath, new Size(200, 400), TimeSpan.FromMinutes(1));
|
FFMpeg.Snapshot(inputPath, outputPath, new Size(200, 400), TimeSpan.FromMinutes(1));
|
||||||
|
|
@ -61,11 +58,7 @@ var outputStream = new MemoryStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
FFMpeg.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1,
|
FFMpeg.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1, @"..\1.png", @"..\2.png", @"..\3.png");
|
||||||
ImageInfo.FromPath(@"..\1.png"),
|
|
||||||
ImageInfo.FromPath(@"..\2.png"),
|
|
||||||
ImageInfo.FromPath(@"..\3.png")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -85,15 +78,17 @@ var inputImagePath = "/path/to/input/image";
|
||||||
{
|
{
|
||||||
FFMpeg.PosterWithAudio(inputPath, inputAudioPath, outputPath);
|
FFMpeg.PosterWithAudio(inputPath, inputAudioPath, outputPath);
|
||||||
// or
|
// or
|
||||||
var image = Image.FromFile(inputImagePath);
|
#pragma warning disable CA1416
|
||||||
|
using var image = Image.FromFile(inputImagePath);
|
||||||
image.AddAudio(inputAudioPath, outputPath);
|
image.AddAudio(inputAudioPath, outputPath);
|
||||||
|
#pragma warning restore CA1416
|
||||||
}
|
}
|
||||||
|
|
||||||
IVideoFrame GetNextFrame() => throw new NotImplementedException();
|
IVideoFrame GetNextFrame() => throw new NotImplementedException();
|
||||||
{
|
{
|
||||||
IEnumerable<IVideoFrame> CreateFrames(int count)
|
IEnumerable<IVideoFrame> CreateFrames(int count)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
yield return GetNextFrame(); //method of generating new frames
|
yield return GetNextFrame(); //method of generating new frames
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
using System;
|
using System.Drawing;
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Extend
|
namespace FFMpegCore.Extensions.System.Drawing.Common
|
||||||
{
|
{
|
||||||
public static class BitmapExtensions
|
public static class BitmapExtensions
|
||||||
{
|
{
|
||||||
|
|
@ -16,7 +14,10 @@ namespace FFMpegCore.Extend
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (File.Exists(destination)) File.Delete(destination);
|
if (File.Exists(destination))
|
||||||
|
{
|
||||||
|
File.Delete(destination);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
using System;
|
using System.Drawing;
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FFMpegCore.Pipes;
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
namespace FFMpegCore.Extend
|
namespace FFMpegCore.Extensions.System.Drawing.Common
|
||||||
{
|
{
|
||||||
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<IsPackable>true</IsPackable>
|
||||||
|
<Description>Image extension for FFMpegCore using System.Common.Drawing</Description>
|
||||||
|
<PackageVersion>5.0.0</PackageVersion>
|
||||||
|
<PackageReleaseNotes>
|
||||||
|
</PackageReleaseNotes>
|
||||||
|
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze muxing</PackageTags>
|
||||||
|
<Authors>Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev</Authors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
56
FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs
Normal file
56
FFMpegCore.Extensions.System.Drawing.Common/FFMpegImage.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
using System.Drawing;
|
||||||
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Extensions.System.Drawing.Common
|
||||||
|
{
|
||||||
|
public static class FFMpegImage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Source video file.</param>
|
||||||
|
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||||
|
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||||
|
/// <param name="streamIndex">Selected video stream index.</param>
|
||||||
|
/// <param name="inputFileIndex">Input file index</param>
|
||||||
|
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||||
|
public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
||||||
|
{
|
||||||
|
var source = FFProbe.Analyse(input);
|
||||||
|
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
|
||||||
|
arguments
|
||||||
|
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
||||||
|
.ForceFormat("rawvideo")))
|
||||||
|
.ProcessSynchronously();
|
||||||
|
|
||||||
|
ms.Position = 0;
|
||||||
|
using var bitmap = new Bitmap(ms);
|
||||||
|
return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat);
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">Source video file.</param>
|
||||||
|
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||||
|
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||||
|
/// <param name="streamIndex">Selected video stream index.</param>
|
||||||
|
/// <param name="inputFileIndex">Input file index</param>
|
||||||
|
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||||
|
public static async Task<Bitmap> SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
||||||
|
{
|
||||||
|
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
|
||||||
|
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
|
||||||
|
await arguments
|
||||||
|
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
||||||
|
.ForceFormat("rawvideo")))
|
||||||
|
.ProcessAsynchronously();
|
||||||
|
|
||||||
|
ms.Position = 0;
|
||||||
|
return new Bitmap(ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using FFMpegCore.Arguments;
|
||||||
using System;
|
|
||||||
using FFMpegCore.Arguments;
|
|
||||||
using FFMpegCore.Enums;
|
using FFMpegCore.Enums;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
|
|
@ -10,7 +9,6 @@ namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4" };
|
private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4" };
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_IO_1()
|
public void Builder_BuildString_IO_1()
|
||||||
{
|
{
|
||||||
|
|
@ -53,7 +51,6 @@ namespace FFMpegCore.Test
|
||||||
Assert.AreEqual("-hide_banner -loglevel error -i \"input.mp4\" \"output.mp4\"", str);
|
Assert.AreEqual("-hide_banner -loglevel error -i \"input.mp4\" \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_AudioCodec_Fluent()
|
public void Builder_BuildString_AudioCodec_Fluent()
|
||||||
{
|
{
|
||||||
|
|
@ -75,7 +72,7 @@ namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
var str = FFMpegArguments.FromFileInput("input.mp4")
|
var str = FFMpegArguments.FromFileInput("input.mp4")
|
||||||
.OutputToFile("output.mp4", false, opt => opt.WithHardwareAcceleration()).Arguments;
|
.OutputToFile("output.mp4", false, opt => opt.WithHardwareAcceleration()).Arguments;
|
||||||
Assert.AreEqual("-i \"input.mp4\" -hwaccel \"output.mp4\"", str);
|
Assert.AreEqual("-i \"input.mp4\" -hwaccel auto \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
@ -114,7 +111,7 @@ namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
var str = FFMpegArguments.FromFileInput("input.mp4")
|
var str = FFMpegArguments.FromFileInput("input.mp4")
|
||||||
.OutputToFile("output.mp4", false, opt => opt.CopyChannel()).Arguments;
|
.OutputToFile("output.mp4", false, opt => opt.CopyChannel()).Arguments;
|
||||||
Assert.AreEqual("-i \"input.mp4\" -c copy \"output.mp4\"", str);
|
Assert.AreEqual("-i \"input.mp4\" -c:a copy -c:v copy \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
|
@ -400,7 +397,6 @@ namespace FFMpegCore.Test
|
||||||
Assert.AreEqual("-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\" -y", str);
|
Assert.AreEqual("-i \"input.mp4\" -c:v libx264 -pix_fmt yuv420p \"output.mp4\" -y", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_Duration()
|
public void Builder_BuildString_Duration()
|
||||||
{
|
{
|
||||||
|
|
@ -421,7 +417,6 @@ namespace FFMpegCore.Test
|
||||||
Assert.AreEqual("-i \"input.mp4\" -acodec copy \"output.mp4\"", str);
|
Assert.AreEqual("-i \"input.mp4\" -acodec copy \"output.mp4\"", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Builder_BuildString_ForcePixelFormat()
|
public void Builder_BuildString_ForcePixelFormat()
|
||||||
{
|
{
|
||||||
|
|
@ -495,5 +490,45 @@ namespace FFMpegCore.Test
|
||||||
|
|
||||||
Assert.AreEqual("-audible_key 123 -audible_iv 456 -i \"input.aaxc\" -map_metadata 0 -id3v2_version 3 -vn -c:a copy \"output.m4b\" -y", str);
|
Assert.AreEqual("-audible_key 123 -audible_iv 456 -i \"input.aaxc\" -map_metadata 0 -id3v2_version 3 -vn -c:a copy \"output.m4b\" -y", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Builder_BuildString_PadFilter()
|
||||||
|
{
|
||||||
|
var str = FFMpegArguments
|
||||||
|
.FromFileInput("input.mp4")
|
||||||
|
.OutputToFile("output.mp4", false, opt => opt
|
||||||
|
.WithVideoFilters(filterOptions => filterOptions
|
||||||
|
.Pad(PadOptions
|
||||||
|
.Create("max(iw,ih)", "ow")
|
||||||
|
.WithParameter("x", "(ow-iw)/2")
|
||||||
|
.WithParameter("y", "(oh-ih)/2")
|
||||||
|
.WithParameter("color", "violet")
|
||||||
|
.WithParameter("eval", "frame"))))
|
||||||
|
.Arguments;
|
||||||
|
|
||||||
|
Assert.AreEqual(
|
||||||
|
"-i \"input.mp4\" -vf \"pad=width=max(iw\\,ih):height=ow:x=(ow-iw)/2:y=(oh-ih)/2:color=violet:eval=frame\" \"output.mp4\"",
|
||||||
|
str);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Builder_BuildString_PadFilter_Alt()
|
||||||
|
{
|
||||||
|
var str = FFMpegArguments
|
||||||
|
.FromFileInput("input.mp4")
|
||||||
|
.OutputToFile("output.mp4", false, opt => opt
|
||||||
|
.WithVideoFilters(filterOptions => filterOptions
|
||||||
|
.Pad(PadOptions
|
||||||
|
.Create("4/3")
|
||||||
|
.WithParameter("x", "(ow-iw)/2")
|
||||||
|
.WithParameter("y", "(oh-ih)/2")
|
||||||
|
.WithParameter("color", "violet")
|
||||||
|
.WithParameter("eval", "frame"))))
|
||||||
|
.Arguments;
|
||||||
|
|
||||||
|
Assert.AreEqual(
|
||||||
|
"-i \"input.mp4\" -vf \"pad=aspect=4/3:x=(ow-iw)/2:y=(oh-ih)/2:color=violet:eval=frame\" \"output.mp4\"",
|
||||||
|
str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,11 +4,6 @@ using FFMpegCore.Extend;
|
||||||
using FFMpegCore.Pipes;
|
using FFMpegCore.Pipes;
|
||||||
using FFMpegCore.Test.Resources;
|
using FFMpegCore.Test.Resources;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
|
|
@ -239,7 +234,7 @@ namespace FFMpegCore.Test
|
||||||
|
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count);
|
Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count);
|
||||||
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream.ChannelLayout);
|
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
|
|
@ -257,7 +252,7 @@ namespace FFMpegCore.Test
|
||||||
|
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count);
|
Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count);
|
||||||
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream.ChannelLayout);
|
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using System.Reflection;
|
||||||
using FluentAssertions;
|
|
||||||
using System.Reflection;
|
|
||||||
using FFMpegCore.Arguments;
|
using FFMpegCore.Arguments;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
|
|
@ -13,14 +13,13 @@ namespace FFMpegCore.Test
|
||||||
|
|
||||||
{
|
{
|
||||||
// After testing reset global configuration to null, to be not wrong for other test relying on configuration
|
// After testing reset global configuration to null, to be not wrong for other test relying on configuration
|
||||||
typeof(GlobalFFOptions).GetField("_current", BindingFlags.NonPublic | BindingFlags.Static).SetValue(GlobalFFOptions.Current, null);
|
typeof(GlobalFFOptions).GetField("_current", BindingFlags.NonPublic | BindingFlags.Static)!.SetValue(GlobalFFOptions.Current, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FFMpegArgumentProcessor CreateArgumentProcessor() => FFMpegArguments
|
private static FFMpegArgumentProcessor CreateArgumentProcessor() => FFMpegArguments
|
||||||
.FromFileInput("")
|
.FromFileInput("")
|
||||||
.OutputToFile("");
|
.OutputToFile("");
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Processor_GlobalOptions_GetUsed()
|
public void Processor_GlobalOptions_GetUsed()
|
||||||
{
|
{
|
||||||
|
|
@ -44,14 +43,12 @@ namespace FFMpegCore.Test
|
||||||
options.WorkingDirectory.Should().Be(sessionWorkingDir);
|
options.WorkingDirectory.Should().Be(sessionWorkingDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Processor_Options_CanBeOverridden_And_Configured()
|
public void Processor_Options_CanBeOverridden_And_Configured()
|
||||||
{
|
{
|
||||||
var globalConfig = "Whatever";
|
var globalConfig = "Whatever";
|
||||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig });
|
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig });
|
||||||
|
|
||||||
|
|
||||||
var processor = CreateArgumentProcessor();
|
var processor = CreateArgumentProcessor();
|
||||||
|
|
||||||
var sessionTempDir = "./CurrentRunWorkingDir";
|
var sessionTempDir = "./CurrentRunWorkingDir";
|
||||||
|
|
@ -65,14 +62,9 @@ namespace FFMpegCore.Test
|
||||||
options.BinaryFolder.Should().NotBeEquivalentTo(globalConfig);
|
options.BinaryFolder.Should().NotBeEquivalentTo(globalConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Options_Global_And_Session_Options_Can_Differ()
|
public void Options_Global_And_Session_Options_Can_Differ()
|
||||||
{
|
{
|
||||||
FFMpegArgumentProcessor CreateArgumentProcessor() => FFMpegArguments
|
|
||||||
.FromFileInput("")
|
|
||||||
.OutputToFile("");
|
|
||||||
|
|
||||||
var globalWorkingDir = "Whatever";
|
var globalWorkingDir = "Whatever";
|
||||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir });
|
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir });
|
||||||
|
|
||||||
|
|
@ -82,7 +74,6 @@ namespace FFMpegCore.Test
|
||||||
var options1 = processor1.GetConfiguredOptions(null);
|
var options1 = processor1.GetConfiguredOptions(null);
|
||||||
options1.WorkingDirectory.Should().Be(sessionWorkingDir);
|
options1.WorkingDirectory.Should().Be(sessionWorkingDir);
|
||||||
|
|
||||||
|
|
||||||
var processor2 = CreateArgumentProcessor();
|
var processor2 = CreateArgumentProcessor();
|
||||||
var options2 = processor2.GetConfiguredOptions(null);
|
var options2 = processor2.GetConfiguredOptions(null);
|
||||||
options2.WorkingDirectory.Should().Be(globalWorkingDir);
|
options2.WorkingDirectory.Should().Be(globalWorkingDir);
|
||||||
|
|
@ -102,7 +93,6 @@ namespace FFMpegCore.Test
|
||||||
arg.Text.Should().Be($"-audible_key 123 -audible_iv 456");
|
arg.Text.Should().Be($"-audible_key 123 -audible_iv 456");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void Audible_Aax_Test()
|
public void Audible_Aax_Test()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,41 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
|
|
||||||
<Nullable>disable</Nullable>
|
<Nullable>disable</Nullable>
|
||||||
|
|
||||||
<LangVersion>default</LangVersion>
|
<LangVersion>default</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="ffmpeg.config.json" />
|
<PackageReference Include="coverlet.collector" Version="3.2.0">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="FluentAssertions" Version="6.9.0" />
|
||||||
|
<PackageReference Include="GitHubActionsTestLogger" Version="2.0.1">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||||
|
<PackageReference Include="MSTest.TestAdapter" Version="3.0.2" />
|
||||||
|
<PackageReference Include="MSTest.TestFramework" Version="3.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FFMpegCore.Extensions.System.Drawing.Common\FFMpegCore.Extensions.System.Drawing.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="ffmpeg.config.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<None Update="Resources\24_bit_fixed.WAV">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Resources\32_bit_float.WAV">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<None Update="Resources\input.webm">
|
<None Update="Resources\input.webm">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|
@ -21,6 +46,9 @@
|
||||||
<None Update="Resources\input_3sec.webm">
|
<None Update="Resources\input_3sec.webm">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="Resources\input_3sec_rotation_90deg.mp4">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
<None Update="Resources\input_audio_only_10sec.mp4">
|
<None Update="Resources\input_audio_only_10sec.mp4">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|
@ -28,69 +56,53 @@
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\audio.raw">
|
<None Update="Resources\audio.raw">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Content Include="ffmpeg.config.json">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</None>
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="FluentAssertions" Version="6.3.0" />
|
|
||||||
<PackageReference Include="GitHubActionsTestLogger" Version="1.2.0" />
|
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
|
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="Resources\audio.mp3">
|
<None Update="Resources\audio.mp3">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\audio_only.mp4">
|
<None Update="Resources\audio_only.mp4">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\cover.png">
|
<None Update="Resources\cover.png">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\images\a.png">
|
<None Update="Resources\images\a.png">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\images\b.png">
|
<None Update="Resources\images\b.png">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\images\c.png">
|
<None Update="Resources\images\c.png">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\images\d.png">
|
<None Update="Resources\images\d.png">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\images\e.png">
|
<None Update="Resources\images\e.png">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\images\f.png">
|
<None Update="Resources\images\f.png">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\input.mp4">
|
<None Update="Resources\input.mp4">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\mute.mp4">
|
<None Update="Resources\mute.mp4">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="Resources\sample.srt">
|
<None Update="Resources\sample.srt">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
<None Update="Resources\sample3aiff.aiff">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<ItemGroup>
|
</None>
|
||||||
<Folder Include="Properties\" />
|
<None Update="Resources\sampleMKV.mkv">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="Resources\sampleVOC.voc">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
using System;
|
using FFMpegCore.Test.Resources;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FFMpegCore.Test.Resources;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
|
|
@ -55,7 +50,6 @@ namespace FFMpegCore.Test
|
||||||
Assert.AreEqual(1362, packets.Last().Size);
|
Assert.AreEqual(1362, packets.Last().Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void PacketAnalysis_Sync()
|
public void PacketAnalysis_Sync()
|
||||||
{
|
{
|
||||||
|
|
@ -74,9 +68,9 @@ namespace FFMpegCore.Test
|
||||||
|
|
||||||
Assert.AreEqual(216, packets.Count);
|
Assert.AreEqual(216, packets.Count);
|
||||||
var actual = packets.Select(f => f.CodecType).Distinct().ToList();
|
var actual = packets.Select(f => f.CodecType).Distinct().ToList();
|
||||||
var expected = new List<string> {"audio", "video"};
|
var expected = new List<string> { "audio", "video" };
|
||||||
CollectionAssert.AreEquivalent(expected, actual);
|
CollectionAssert.AreEquivalent(expected, actual);
|
||||||
Assert.IsTrue(packets.Where(t=>t.CodecType == "audio").All(f => f.Flags == "K_"));
|
Assert.IsTrue(packets.Where(t => t.CodecType == "audio").All(f => f.Flags == "K_"));
|
||||||
Assert.AreEqual(75, packets.Count(t => t.CodecType == "video"));
|
Assert.AreEqual(75, packets.Count(t => t.CodecType == "video"));
|
||||||
Assert.AreEqual(141, packets.Count(t => t.CodecType == "audio"));
|
Assert.AreEqual(141, packets.Count(t => t.CodecType == "audio"));
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +84,7 @@ namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
var ffprobeStream = new FFProbeStream { Duration = duration };
|
var ffprobeStream = new FFProbeStream { Duration = duration };
|
||||||
|
|
||||||
var parsedDuration = MediaAnalysisUtils.ParseDuration(ffprobeStream);
|
var parsedDuration = MediaAnalysisUtils.ParseDuration(ffprobeStream.Duration);
|
||||||
|
|
||||||
Assert.AreEqual(expectedDays, parsedDuration.Days);
|
Assert.AreEqual(expectedDays, parsedDuration.Days);
|
||||||
Assert.AreEqual(expectedHours, parsedDuration.Hours);
|
Assert.AreEqual(expectedHours, parsedDuration.Hours);
|
||||||
|
|
@ -99,7 +93,7 @@ namespace FFMpegCore.Test
|
||||||
Assert.AreEqual(expectedMilliseconds, parsedDuration.Milliseconds);
|
Assert.AreEqual(expectedMilliseconds, parsedDuration.Milliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod, Ignore("Consistently fails on GitHub Workflow ubuntu agents")]
|
||||||
public async Task Uri_Duration()
|
public async Task Uri_Duration()
|
||||||
{
|
{
|
||||||
var fileAnalysis = await FFProbe.AnalyseAsync(new Uri("https://github.com/rosenbjerg/FFMpegCore/raw/master/FFMpegCore.Test/Resources/input_3sec.webm"));
|
var fileAnalysis = await FFProbe.AnalyseAsync(new Uri("https://github.com/rosenbjerg/FFMpegCore/raw/master/FFMpegCore.Test/Resources/input_3sec.webm"));
|
||||||
|
|
@ -125,6 +119,8 @@ namespace FFMpegCore.Test
|
||||||
Assert.AreEqual(1471810, info.PrimaryVideoStream!.BitRate);
|
Assert.AreEqual(1471810, info.PrimaryVideoStream!.BitRate);
|
||||||
Assert.AreEqual(16, info.PrimaryVideoStream.DisplayAspectRatio.Width);
|
Assert.AreEqual(16, info.PrimaryVideoStream.DisplayAspectRatio.Width);
|
||||||
Assert.AreEqual(9, info.PrimaryVideoStream.DisplayAspectRatio.Height);
|
Assert.AreEqual(9, info.PrimaryVideoStream.DisplayAspectRatio.Height);
|
||||||
|
Assert.AreEqual(1, info.PrimaryVideoStream.SampleAspectRatio.Width);
|
||||||
|
Assert.AreEqual(1, info.PrimaryVideoStream.SampleAspectRatio.Height);
|
||||||
Assert.AreEqual("yuv420p", info.PrimaryVideoStream.PixelFormat);
|
Assert.AreEqual("yuv420p", info.PrimaryVideoStream.PixelFormat);
|
||||||
Assert.AreEqual(1280, info.PrimaryVideoStream.Width);
|
Assert.AreEqual(1280, info.PrimaryVideoStream.Width);
|
||||||
Assert.AreEqual(720, info.PrimaryVideoStream.Height);
|
Assert.AreEqual(720, info.PrimaryVideoStream.Height);
|
||||||
|
|
@ -138,11 +134,24 @@ namespace FFMpegCore.Test
|
||||||
Assert.AreEqual("0x31637661", info.PrimaryVideoStream.CodecTag);
|
Assert.AreEqual("0x31637661", info.PrimaryVideoStream.CodecTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Probe_Rotation()
|
||||||
|
{
|
||||||
|
var info = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
|
Assert.AreEqual(0, info.PrimaryVideoStream.Rotation);
|
||||||
|
|
||||||
|
info = FFProbe.Analyse(TestResources.Mp4VideoRotation);
|
||||||
|
Assert.AreEqual(90, info.PrimaryVideoStream.Rotation);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public async Task Probe_Async_Success()
|
public async Task Probe_Async_Success()
|
||||||
{
|
{
|
||||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video);
|
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video);
|
||||||
Assert.AreEqual(3, info.Duration.Seconds);
|
Assert.AreEqual(3, info.Duration.Seconds);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
|
|
@ -151,6 +160,8 @@ namespace FFMpegCore.Test
|
||||||
using var stream = File.OpenRead(TestResources.WebmVideo);
|
using var stream = File.OpenRead(TestResources.WebmVideo);
|
||||||
var info = FFProbe.Analyse(stream);
|
var info = FFProbe.Analyse(stream);
|
||||||
Assert.AreEqual(3, info.Duration.Seconds);
|
Assert.AreEqual(3, info.Duration.Seconds);
|
||||||
|
// This video has no audio stream.
|
||||||
|
Assert.IsNull(info.PrimaryAudioStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
|
|
@ -169,6 +180,8 @@ namespace FFMpegCore.Test
|
||||||
Assert.AreEqual(1, info.SubtitleStreams.Count);
|
Assert.AreEqual(1, info.SubtitleStreams.Count);
|
||||||
Assert.AreEqual(0, info.AudioStreams.Count);
|
Assert.AreEqual(0, info.AudioStreams.Count);
|
||||||
Assert.AreEqual(0, info.VideoStreams.Count);
|
Assert.AreEqual(0, info.VideoStreams.Count);
|
||||||
|
// BitDepth is meaningless for subtitles
|
||||||
|
Assert.IsNull(info.SubtitleStreams[0].BitDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
|
|
@ -180,5 +193,47 @@ namespace FFMpegCore.Test
|
||||||
Assert.AreEqual(true, info.PrimaryAudioStream.Disposition["default"]);
|
Assert.AreEqual(true, info.PrimaryAudioStream.Disposition["default"]);
|
||||||
Assert.AreEqual(false, info.PrimaryAudioStream.Disposition["forced"]);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
[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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,15 +1,7 @@
|
||||||
using FFMpegCore.Builders.MetaData;
|
using System.Text.RegularExpressions;
|
||||||
|
using FFMpegCore.Builders.MetaData;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
[TestClass]
|
[TestClass]
|
||||||
|
|
@ -71,8 +63,6 @@ namespace FFMpegCore.Test
|
||||||
.AddMetaData("WhatEver3")
|
.AddMetaData("WhatEver3")
|
||||||
.Text;
|
.Text;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Assert.IsTrue(Regex.IsMatch(text0, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 1"), "map_metadata index is calculated incorrectly.");
|
Assert.IsTrue(Regex.IsMatch(text0, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 1"), "map_metadata index is calculated incorrectly.");
|
||||||
Assert.IsTrue(Regex.IsMatch(text1, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 2"), "map_metadata index is calculated incorrectly.");
|
Assert.IsTrue(Regex.IsMatch(text1, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 2"), "map_metadata index is calculated incorrectly.");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
FFMpegCore.Test/Resources/24_bit_fixed.WAV
Normal file
BIN
FFMpegCore.Test/Resources/24_bit_fixed.WAV
Normal file
Binary file not shown.
BIN
FFMpegCore.Test/Resources/32_bit_float.WAV
Normal file
BIN
FFMpegCore.Test/Resources/32_bit_float.WAV
Normal file
Binary file not shown.
|
|
@ -13,6 +13,7 @@
|
||||||
public static class TestResources
|
public static class TestResources
|
||||||
{
|
{
|
||||||
public static readonly string Mp4Video = "./Resources/input_3sec.mp4";
|
public static readonly string Mp4Video = "./Resources/input_3sec.mp4";
|
||||||
|
public static readonly string Mp4VideoRotation = "./Resources/input_3sec_rotation_90deg.mp4";
|
||||||
public static readonly string WebmVideo = "./Resources/input_3sec.webm";
|
public static readonly string WebmVideo = "./Resources/input_3sec.webm";
|
||||||
public static readonly string Mp4WithoutVideo = "./Resources/input_audio_only_10sec.mp4";
|
public static readonly string Mp4WithoutVideo = "./Resources/input_audio_only_10sec.mp4";
|
||||||
public static readonly string Mp4WithoutAudio = "./Resources/input_video_only_3sec.mp4";
|
public static readonly string Mp4WithoutAudio = "./Resources/input_video_only_3sec.mp4";
|
||||||
|
|
@ -21,5 +22,9 @@
|
||||||
public static readonly string PngImage = "./Resources/cover.png";
|
public static readonly string PngImage = "./Resources/cover.png";
|
||||||
public static readonly string ImageCollection = "./Resources/images";
|
public static readonly string ImageCollection = "./Resources/images";
|
||||||
public static readonly string SrtSubtitle = "./Resources/sample.srt";
|
public static readonly string SrtSubtitle = "./Resources/sample.srt";
|
||||||
|
public static readonly string AiffAudio = "./Resources/sample3aiff.aiff";
|
||||||
|
public static readonly string MkvVideo = "./Resources/sampleMKV.mkv";
|
||||||
|
public static readonly string Wav24Bit = "./Resources/24_bit_fixed.WAV";
|
||||||
|
public static readonly string Wav32Bit = "./Resources/32_bit_float.WAV";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
FFMpegCore.Test/Resources/input_3sec_rotation_90deg.mp4
Normal file
BIN
FFMpegCore.Test/Resources/input_3sec_rotation_90deg.mp4
Normal file
Binary file not shown.
BIN
FFMpegCore.Test/Resources/sample3aiff.aiff
Normal file
BIN
FFMpegCore.Test/Resources/sample3aiff.aiff
Normal file
Binary file not shown.
BIN
FFMpegCore.Test/Resources/sampleMKV.mkv
Normal file
BIN
FFMpegCore.Test/Resources/sampleMKV.mkv
Normal file
Binary file not shown.
|
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Test
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
|
||||||
{
|
{
|
||||||
public class TemporaryFile : IDisposable
|
public class TemporaryFile : IDisposable
|
||||||
{
|
{
|
||||||
|
|
@ -16,7 +13,9 @@ namespace FFMpegCore.Test
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (File.Exists(_path))
|
if (File.Exists(_path))
|
||||||
|
{
|
||||||
File.Delete(_path);
|
File.Delete(_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
using FFMpegCore.Extend;
|
using System.Drawing;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
using FFMpegCore.Extensions.System.Drawing.Common;
|
||||||
using FFMpegCore.Pipes;
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test.Utilities
|
||||||
{
|
{
|
||||||
static class BitmapSource
|
[SupportedOSPlatform("windows")]
|
||||||
|
internal static class BitmapSource
|
||||||
{
|
{
|
||||||
public static IEnumerable<IVideoFrame> CreateBitmaps(int count, PixelFormat fmt, int w, int h)
|
public static IEnumerable<IVideoFrame> CreateBitmaps(int count, PixelFormat fmt, int w, int h)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < count; i++)
|
for (var i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
using (var frame = CreateVideoFrame(i, fmt, w, h, 0.025f, 0.025f * w * 0.03f))
|
using (var frame = CreateVideoFrame(i, fmt, w, h, 0.025f, 0.025f * w * 0.03f))
|
||||||
{
|
{
|
||||||
|
|
@ -27,8 +27,9 @@ namespace FFMpegCore.Test
|
||||||
|
|
||||||
offset = offset * index;
|
offset = offset * index;
|
||||||
|
|
||||||
for (int y = 0; y < h; y++)
|
for (var y = 0; y < h; y++)
|
||||||
for (int x = 0; x < w; x++)
|
{
|
||||||
|
for (var x = 0; x < w; x++)
|
||||||
{
|
{
|
||||||
var xf = x / (float)w;
|
var xf = x / (float)w;
|
||||||
var yf = y / (float)h;
|
var yf = y / (float)h;
|
||||||
|
|
@ -41,6 +42,7 @@ namespace FFMpegCore.Test
|
||||||
|
|
||||||
bitmap.SetPixel(x, y, color);
|
bitmap.SetPixel(x, y, color);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new BitmapVideoFrameWrapper(bitmap);
|
return new BitmapVideoFrameWrapper(bitmap);
|
||||||
}
|
}
|
||||||
|
|
@ -53,7 +55,7 @@ namespace FFMpegCore.Test
|
||||||
// Based on the original implementation by Ken Perlin
|
// Based on the original implementation by Ken Perlin
|
||||||
// http://mrl.nyu.edu/~perlin/noise/
|
// http://mrl.nyu.edu/~perlin/noise/
|
||||||
//
|
//
|
||||||
static class Perlin
|
private static class Perlin
|
||||||
{
|
{
|
||||||
#region Noise functions
|
#region Noise functions
|
||||||
|
|
||||||
|
|
@ -126,6 +128,7 @@ namespace FFMpegCore.Test
|
||||||
x *= 2.0f;
|
x *= 2.0f;
|
||||||
w *= 0.5f;
|
w *= 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,6 +142,7 @@ namespace FFMpegCore.Test
|
||||||
coord *= 2.0f;
|
coord *= 2.0f;
|
||||||
w *= 0.5f;
|
w *= 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,6 +161,7 @@ namespace FFMpegCore.Test
|
||||||
coord *= 2.0f;
|
coord *= 2.0f;
|
||||||
w *= 0.5f;
|
w *= 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,27 +174,27 @@ namespace FFMpegCore.Test
|
||||||
|
|
||||||
#region Private functions
|
#region Private functions
|
||||||
|
|
||||||
static float Fade(float t)
|
private static float Fade(float t)
|
||||||
{
|
{
|
||||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
static float Lerp(float t, float a, float b)
|
private static float Lerp(float t, float a, float b)
|
||||||
{
|
{
|
||||||
return a + t * (b - a);
|
return a + t * (b - a);
|
||||||
}
|
}
|
||||||
|
|
||||||
static float Grad(int hash, float x)
|
private static float Grad(int hash, float x)
|
||||||
{
|
{
|
||||||
return (hash & 1) == 0 ? x : -x;
|
return (hash & 1) == 0 ? x : -x;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float Grad(int hash, float x, float y)
|
private static float Grad(int hash, float x, float y)
|
||||||
{
|
{
|
||||||
return ((hash & 1) == 0 ? x : -x) + ((hash & 2) == 0 ? y : -y);
|
return ((hash & 1) == 0 ? x : -x) + ((hash & 2) == 0 ? y : -y);
|
||||||
}
|
}
|
||||||
|
|
||||||
static float Grad(int hash, float x, float y, float z)
|
private static float Grad(int hash, float x, float y, float z)
|
||||||
{
|
{
|
||||||
var h = hash & 15;
|
var h = hash & 15;
|
||||||
var u = h < 8 ? x : y;
|
var u = h < 8 ? x : y;
|
||||||
|
|
@ -197,7 +202,7 @@ namespace FFMpegCore.Test
|
||||||
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
|
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int[] perm = {
|
private static readonly int[] perm = {
|
||||||
151,160,137,91,90,15,
|
151,160,137,91,90,15,
|
||||||
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
|
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
|
||||||
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
|
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
|
||||||
|
|
|
||||||
23
FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs
Normal file
23
FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Test.Utilities;
|
||||||
|
|
||||||
|
public class WindowsOnlyDataTestMethod : DataTestMethodAttribute
|
||||||
|
{
|
||||||
|
public override TestResult[] Execute(ITestMethod testMethod)
|
||||||
|
{
|
||||||
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
var message = $"Test not executed on other platforms than Windows";
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Execute(testMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs
Normal file
23
FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Test.Utilities;
|
||||||
|
|
||||||
|
public class WindowsOnlyTestMethod : TestMethodAttribute
|
||||||
|
{
|
||||||
|
public override TestResult[] Execute(ITestMethod testMethod)
|
||||||
|
{
|
||||||
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
var message = $"Test not executed on other platforms than Windows";
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.Execute(testMethod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,14 @@
|
||||||
using FFMpegCore.Enums;
|
using System.Drawing.Imaging;
|
||||||
using FFMpegCore.Test.Resources;
|
using System.Runtime.Versioning;
|
||||||
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.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FFMpegCore.Arguments;
|
using FFMpegCore.Arguments;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
using FFMpegCore.Exceptions;
|
using FFMpegCore.Exceptions;
|
||||||
|
using FFMpegCore.Extensions.System.Drawing.Common;
|
||||||
using FFMpegCore.Pipes;
|
using FFMpegCore.Pipes;
|
||||||
using System.Threading;
|
using FFMpegCore.Test.Resources;
|
||||||
|
using FFMpegCore.Test.Utilities;
|
||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
namespace FFMpegCore.Test
|
namespace FFMpegCore.Test
|
||||||
{
|
{
|
||||||
|
|
@ -85,7 +81,8 @@ namespace FFMpegCore.Test
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataTestMethod, Timeout(10000)]
|
[SupportedOSPlatform("windows")]
|
||||||
|
[WindowsOnlyDataTestMethod, Timeout(10000)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||||
public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||||
|
|
@ -101,7 +98,8 @@ namespace FFMpegCore.Test
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[SupportedOSPlatform("windows")]
|
||||||
|
[WindowsOnlyTestMethod, Timeout(10000)]
|
||||||
public void Video_ToMP4_Args_Pipe_DifferentImageSizes()
|
public void Video_ToMP4_Args_Pipe_DifferentImageSizes()
|
||||||
{
|
{
|
||||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||||
|
|
@ -120,8 +118,8 @@ namespace FFMpegCore.Test
|
||||||
.ProcessSynchronously());
|
.ProcessSynchronously());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
[TestMethod, Timeout(10000)]
|
[WindowsOnlyTestMethod, Timeout(10000)]
|
||||||
public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async()
|
public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async()
|
||||||
{
|
{
|
||||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||||
|
|
@ -140,7 +138,8 @@ namespace FFMpegCore.Test
|
||||||
.ProcessAsynchronously());
|
.ProcessAsynchronously());
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[SupportedOSPlatform("windows")]
|
||||||
|
[WindowsOnlyTestMethod, Timeout(10000)]
|
||||||
public void Video_ToMP4_Args_Pipe_DifferentPixelFormats()
|
public void Video_ToMP4_Args_Pipe_DifferentPixelFormats()
|
||||||
{
|
{
|
||||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||||
|
|
@ -159,8 +158,8 @@ namespace FFMpegCore.Test
|
||||||
.ProcessSynchronously());
|
.ProcessSynchronously());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
[TestMethod, Timeout(10000)]
|
[WindowsOnlyTestMethod, Timeout(10000)]
|
||||||
public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Async()
|
public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Async()
|
||||||
{
|
{
|
||||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||||
|
|
@ -207,7 +206,6 @@ namespace FFMpegCore.Test
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public void Video_StreamFile_OutputToMemoryStream()
|
public void Video_StreamFile_OutputToMemoryStream()
|
||||||
{
|
{
|
||||||
|
|
@ -239,7 +237,6 @@ namespace FFMpegCore.Test
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public async Task Video_ToMP4_Args_StreamOutputPipe_Async()
|
public async Task Video_ToMP4_Args_StreamOutputPipe_Async()
|
||||||
{
|
{
|
||||||
|
|
@ -314,7 +311,8 @@ namespace FFMpegCore.Test
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataTestMethod, Timeout(10000)]
|
[SupportedOSPlatform("windows")]
|
||||||
|
[WindowsOnlyDataTestMethod, Timeout(10000)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||||
public async Task Video_ToTS_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
public async Task Video_ToTS_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||||
|
|
@ -346,10 +344,11 @@ namespace FFMpegCore.Test
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataTestMethod, Timeout(10000)]
|
[SupportedOSPlatform("windows")]
|
||||||
|
[WindowsOnlyDataTestMethod, Timeout(10000)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format48bppRgb)]
|
||||||
public void RawVideoPipeSource_Ogv_Scale(System.Drawing.Imaging.PixelFormat pixelFormat)
|
public void RawVideoPipeSource_Ogv_Scale(System.Drawing.Imaging.PixelFormat pixelFormat)
|
||||||
{
|
{
|
||||||
using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}");
|
using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}");
|
||||||
|
|
@ -381,7 +380,8 @@ namespace FFMpegCore.Test
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
[DataTestMethod, Timeout(10000)]
|
[SupportedOSPlatform("windows")]
|
||||||
|
[WindowsOnlyDataTestMethod, Timeout(10000)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
// [DataRow(PixelFormat.Format48bppRgb)]
|
||||||
|
|
@ -398,12 +398,13 @@ namespace FFMpegCore.Test
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[SupportedOSPlatform("windows")]
|
||||||
|
[WindowsOnlyTestMethod, Timeout(10000)]
|
||||||
public void Video_Snapshot_InMemory()
|
public void Video_Snapshot_InMemory()
|
||||||
{
|
{
|
||||||
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
using var bitmap = FFMpegImage.Snapshot(TestResources.Mp4Video);
|
||||||
using var bitmap = FFMpeg.Snapshot(TestResources.Mp4Video);
|
|
||||||
|
|
||||||
|
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width);
|
Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width);
|
||||||
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
||||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||||
|
|
@ -417,10 +418,10 @@ namespace FFMpegCore.Test
|
||||||
|
|
||||||
FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
|
FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
|
||||||
|
|
||||||
using var bitmap = Image.FromFile(outputPath);
|
var analysis = FFProbe.Analyse(outputPath);
|
||||||
Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width);
|
Assert.AreEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
|
||||||
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height);
|
Assert.AreEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
|
||||||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
|
|
@ -445,28 +446,29 @@ namespace FFMpegCore.Test
|
||||||
Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width);
|
Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(20000)]
|
||||||
public void Video_Join_Image_Sequence()
|
public void Video_Join_Image_Sequence()
|
||||||
{
|
{
|
||||||
var imageSet = new List<ImageInfo>();
|
var imageSet = new List<string>();
|
||||||
Directory.EnumerateFiles(TestResources.ImageCollection)
|
Directory.EnumerateFiles(TestResources.ImageCollection, "*.png")
|
||||||
.Where(file => file.ToLower().EndsWith(".png"))
|
|
||||||
.ToList()
|
.ToList()
|
||||||
.ForEach(file =>
|
.ForEach(file =>
|
||||||
{
|
{
|
||||||
for (var i = 0; i < 15; i++)
|
for (var i = 0; i < 5; i++)
|
||||||
{
|
{
|
||||||
imageSet.Add(new ImageInfo(file));
|
imageSet.Add(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
var imageAnalysis = FFProbe.Analyse(imageSet.First());
|
||||||
|
|
||||||
using var outputFile = new TemporaryFile("out.mp4");
|
var outputFile = new TemporaryFile("out.mp4");
|
||||||
var success = FFMpeg.JoinImageSequence(outputFile, images: imageSet.ToArray());
|
var success = FFMpeg.JoinImageSequence(outputFile, frameRate: 10, images: imageSet.ToArray());
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
var result = FFProbe.Analyse(outputFile);
|
var result = FFProbe.Analyse(outputFile);
|
||||||
Assert.AreEqual(3, result.Duration.Seconds);
|
|
||||||
Assert.AreEqual(imageSet.First().Width, result.PrimaryVideoStream!.Width);
|
Assert.AreEqual(1, result.Duration.Seconds);
|
||||||
Assert.AreEqual(imageSet.First().Height, result.PrimaryVideoStream.Height);
|
Assert.AreEqual(imageAnalysis.PrimaryVideoStream!.Width, result.PrimaryVideoStream!.Width);
|
||||||
|
Assert.AreEqual(imageAnalysis.PrimaryVideoStream!.Height, result.PrimaryVideoStream.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
|
|
@ -505,14 +507,28 @@ namespace FFMpegCore.Test
|
||||||
|
|
||||||
var percentageDone = 0.0;
|
var percentageDone = 0.0;
|
||||||
var timeDone = TimeSpan.Zero;
|
var timeDone = TimeSpan.Zero;
|
||||||
void OnPercentageProgess(double percentage) => percentageDone = percentage;
|
|
||||||
void OnTimeProgess(TimeSpan time) => timeDone = time;
|
|
||||||
|
|
||||||
var analysis = FFProbe.Analyse(TestResources.Mp4Video);
|
var analysis = FFProbe.Analyse(TestResources.Mp4Video);
|
||||||
|
|
||||||
|
void OnPercentageProgess(double percentage)
|
||||||
|
{
|
||||||
|
if (percentage < 100)
|
||||||
|
{
|
||||||
|
percentageDone = percentage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnTimeProgess(TimeSpan time)
|
||||||
|
{
|
||||||
|
if (time < analysis.Duration)
|
||||||
|
{
|
||||||
|
timeDone = time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var success = FFMpegArguments
|
var success = FFMpegArguments
|
||||||
.FromFileInput(TestResources.Mp4Video)
|
.FromFileInput(TestResources.Mp4Video)
|
||||||
.OutputToFile(outputFile, false, opt => opt
|
.OutputToFile(outputFile, false, opt => opt
|
||||||
.WithDuration(TimeSpan.FromSeconds(2)))
|
.WithDuration(analysis.Duration))
|
||||||
.NotifyOnProgress(OnPercentageProgess, analysis.Duration)
|
.NotifyOnProgress(OnPercentageProgess, analysis.Duration)
|
||||||
.NotifyOnProgress(OnTimeProgess)
|
.NotifyOnProgress(OnTimeProgess)
|
||||||
.ProcessSynchronously();
|
.ProcessSynchronously();
|
||||||
|
|
@ -520,7 +536,9 @@ namespace FFMpegCore.Test
|
||||||
Assert.IsTrue(success);
|
Assert.IsTrue(success);
|
||||||
Assert.IsTrue(File.Exists(outputFile));
|
Assert.IsTrue(File.Exists(outputFile));
|
||||||
Assert.AreNotEqual(0.0, percentageDone);
|
Assert.AreNotEqual(0.0, percentageDone);
|
||||||
|
Assert.AreNotEqual(100.0, percentageDone);
|
||||||
Assert.AreNotEqual(TimeSpan.Zero, timeDone);
|
Assert.AreNotEqual(TimeSpan.Zero, timeDone);
|
||||||
|
Assert.AreNotEqual(analysis.Duration, timeDone);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
|
|
@ -544,7 +562,8 @@ namespace FFMpegCore.Test
|
||||||
Assert.IsTrue(File.Exists(outputFile));
|
Assert.IsTrue(File.Exists(outputFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[SupportedOSPlatform("windows")]
|
||||||
|
[WindowsOnlyTestMethod, Timeout(10000)]
|
||||||
public void Video_TranscodeInMemory()
|
public void Video_TranscodeInMemory()
|
||||||
{
|
{
|
||||||
using var resStream = new MemoryStream();
|
using var resStream = new MemoryStream();
|
||||||
|
|
@ -564,6 +583,24 @@ namespace FFMpegCore.Test
|
||||||
Assert.AreEqual(vi.PrimaryVideoStream.Height, 128);
|
Assert.AreEqual(vi.PrimaryVideoStream.Height, 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestMethod, Timeout(20000)]
|
||||||
|
public void Video_TranscodeToMemory()
|
||||||
|
{
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
|
||||||
|
FFMpegArguments
|
||||||
|
.FromFileInput(TestResources.WebmVideo)
|
||||||
|
.OutputToPipe(new StreamPipeSink(memoryStream), opt => opt
|
||||||
|
.WithVideoCodec("vp9")
|
||||||
|
.ForceFormat("webm"))
|
||||||
|
.ProcessSynchronously();
|
||||||
|
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
var vi = FFProbe.Analyse(memoryStream);
|
||||||
|
Assert.AreEqual(vi.PrimaryVideoStream!.Width, 640);
|
||||||
|
Assert.AreEqual(vi.PrimaryVideoStream.Height, 360);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod, Timeout(10000)]
|
[TestMethod, Timeout(10000)]
|
||||||
public async Task Video_Cancel_Async()
|
public async Task Video_Cancel_Async()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Test", "FFMpegCo
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Examples", "FFMpegCore.Examples\FFMpegCore.Examples.csproj", "{3125CF91-FFBD-4E4E-8930-247116AFE772}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Examples", "FFMpegCore.Examples\FFMpegCore.Examples.csproj", "{3125CF91-FFBD-4E4E-8930-247116AFE772}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFMpegCore.Extensions.System.Drawing.Common", "FFMpegCore.Extensions.System.Drawing.Common\FFMpegCore.Extensions.System.Drawing.Common.csproj", "{9C1A4930-9369-4A18-AD98-929A2A510D80}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -27,6 +29,10 @@ Global
|
||||||
{3125CF91-FFBD-4E4E-8930-247116AFE772}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3125CF91-FFBD-4E4E-8930-247116AFE772}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{3125CF91-FFBD-4E4E-8930-247116AFE772}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3125CF91-FFBD-4E4E-8930-247116AFE772}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3125CF91-FFBD-4E4E-8930-247116AFE772}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3125CF91-FFBD-4E4E-8930-247116AFE772}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{9C1A4930-9369-4A18-AD98-929A2A510D80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{9C1A4930-9369-4A18-AD98-929A2A510D80}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{9C1A4930-9369-4A18-AD98-929A2A510D80}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{9C1A4930-9369-4A18-AD98-929A2A510D80}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System.Collections.Generic;
|
namespace FFMpegCore.Extend
|
||||||
|
|
||||||
namespace FFMpegCore.Extend
|
|
||||||
{
|
{
|
||||||
internal static class KeyValuePairExtensions
|
internal static class KeyValuePairExtensions
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using System.IO;
|
using FFMpegCore.Pipes;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FFMpegCore.Pipes;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Extend
|
namespace FFMpegCore.Extend
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
using System;
|
using System.Text;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Extend
|
namespace FFMpegCore.Extend
|
||||||
{
|
{
|
||||||
internal static class StringExtensions
|
internal static class StringExtensions
|
||||||
{
|
{
|
||||||
private static Dictionary<char, string> CharactersSubstitution { get; } = new Dictionary<char, string>
|
private static Dictionary<char, string> CharactersSubstitution { get; } = new()
|
||||||
{
|
{
|
||||||
{ '\\', @"\\" },
|
{ '\\', @"\\" },
|
||||||
{ ':', @"\:" },
|
{ ':', @"\:" },
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Extend
|
||||||
|
|
||||||
namespace FFMpegCore.Extend
|
|
||||||
{
|
{
|
||||||
public static class UriExtensions
|
public static class UriExtensions
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,10 @@
|
||||||
{
|
{
|
||||||
private readonly bool _aaxcMode;
|
private readonly bool _aaxcMode;
|
||||||
|
|
||||||
private readonly string _key;
|
private readonly string? _key;
|
||||||
private readonly string _iv;
|
private readonly string? _iv;
|
||||||
|
|
||||||
private readonly string _activationBytes;
|
|
||||||
|
|
||||||
|
private readonly string? _activationBytes;
|
||||||
|
|
||||||
public AudibleEncryptionKeyArgument(string activationBytes)
|
public AudibleEncryptionKeyArgument(string activationBytes)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ namespace FFMpegCore.Arguments
|
||||||
Bitrate = bitrate;
|
Bitrate = bitrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public string Text => $"-b:a {Bitrate}k";
|
public string Text => $"-b:a {Bitrate}k";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,7 +13,9 @@ namespace FFMpegCore.Arguments
|
||||||
public AudioCodecArgument(Codec audioCodec)
|
public AudioCodecArgument(Codec audioCodec)
|
||||||
{
|
{
|
||||||
if (audioCodec.Type != CodecType.Audio)
|
if (audioCodec.Type != CodecType.Audio)
|
||||||
|
{
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{audioCodec.Name}\" is not an audio codec");
|
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{audioCodec.Name}\" is not an audio codec");
|
||||||
|
}
|
||||||
|
|
||||||
AudioCodec = audioCodec.Name;
|
AudioCodec = audioCodec.Name;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using FFMpegCore.Exceptions;
|
||||||
using System.Linq;
|
|
||||||
using FFMpegCore.Exceptions;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
|
|
@ -18,7 +16,9 @@ namespace FFMpegCore.Arguments
|
||||||
private string GetText()
|
private string GetText()
|
||||||
{
|
{
|
||||||
if (!Options.Arguments.Any())
|
if (!Options.Arguments.Any())
|
||||||
|
{
|
||||||
throw new FFMpegArgumentException("No audio-filter arguments provided");
|
throw new FFMpegArgumentException("No audio-filter arguments provided");
|
||||||
|
}
|
||||||
|
|
||||||
var arguments = Options.Arguments
|
var arguments = Options.Arguments
|
||||||
.Where(arg => !string.IsNullOrEmpty(arg.Value))
|
.Where(arg => !string.IsNullOrEmpty(arg.Value))
|
||||||
|
|
@ -34,13 +34,13 @@ namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
public interface IAudioFilterArgument
|
public interface IAudioFilterArgument
|
||||||
{
|
{
|
||||||
public string Key { get; }
|
string Key { get; }
|
||||||
public string Value { get; }
|
string Value { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AudioFilterOptions
|
public class AudioFilterOptions
|
||||||
{
|
{
|
||||||
public List<IAudioFilterArgument> Arguments { get; } = new List<IAudioFilterArgument>();
|
public List<IAudioFilterArgument> Arguments { get; } = new();
|
||||||
|
|
||||||
public AudioFilterOptions Pan(string channelLayout, params string[] outputDefinitions) => WithArgument(new PanArgument(channelLayout, outputDefinitions));
|
public AudioFilterOptions Pan(string channelLayout, params string[] outputDefinitions) => WithArgument(new PanArgument(channelLayout, outputDefinitions));
|
||||||
public AudioFilterOptions Pan(int channels, params string[] outputDefinitions) => WithArgument(new PanArgument(channels, outputDefinitions));
|
public AudioFilterOptions Pan(int channels, params string[] outputDefinitions) => WithArgument(new PanArgument(channels, outputDefinitions));
|
||||||
|
|
@ -50,6 +50,17 @@ namespace FFMpegCore.Arguments
|
||||||
double compressorFactor = 0.0) => WithArgument(new DynamicNormalizerArgument(frameLength, filterWindow,
|
double compressorFactor = 0.0) => WithArgument(new DynamicNormalizerArgument(frameLength, filterWindow,
|
||||||
targetPeak, gainFactor, targetRms, channelCoupling, enableDcBiasCorrection, enableAlternativeBoundary,
|
targetPeak, gainFactor, targetRms, channelCoupling, enableDcBiasCorrection, enableAlternativeBoundary,
|
||||||
compressorFactor));
|
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",
|
||||||
|
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",
|
||||||
|
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",
|
||||||
|
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,
|
||||||
|
bool mono = false) => WithArgument(new SilenceDetectArgument(noise_type, noise, duration, mono));
|
||||||
|
|
||||||
private AudioFilterOptions WithArgument(IAudioFilterArgument argument)
|
private AudioFilterOptions WithArgument(IAudioFilterArgument argument)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
98
FFMpegCore/FFMpeg/Arguments/AudioGateArgument.cs
Normal file
98
FFMpegCore/FFMpeg/Arguments/AudioGateArgument.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
public class AudioGateArgument : IAudioFilterArgument
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, string> _arguments = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Audio Gate. <see href="https://ffmpeg.org/ffmpeg-filters.html#agate"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <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>
|
||||||
|
/// <param name="ratio">Set a ratio by which the signal is reduced. Default is 2. Allowed range is from 1 to 9000.</param>
|
||||||
|
/// <param name="attack">Amount of milliseconds the signal has to rise above the threshold before gain reduction stops. Default is 20 milliseconds. Allowed range is from 0.01 to 9000.</param>
|
||||||
|
/// <param name="release">Amount of milliseconds the signal has to fall below the threshold before the reduction is increased again. Default is 250 milliseconds. Allowed range is from 0.01 to 9000.</param>
|
||||||
|
/// <param name="makeup">Set amount of amplification of signal after processing. Default is 1. Allowed range is from 1 to 64.</param>
|
||||||
|
/// <param name="knee">Curve the sharp knee around the threshold to enter gain reduction more softly. Default is 2.828427125. Allowed range is from 1 to 8.</param>
|
||||||
|
/// <param name="detection">Choose if exact signal should be taken for detection or an RMS like one. Default is rms. Can be peak or rms.</param>
|
||||||
|
/// <param name="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 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 (levelIn is < 0.015625 or > 64)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(levelIn), "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 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("knee", knee.ToString("0.00", CultureInfo.InvariantCulture));
|
||||||
|
_arguments.Add("detection", detection);
|
||||||
|
_arguments.Add("link", link);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key { get; } = "agate";
|
||||||
|
|
||||||
|
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
14
FFMpegCore/FFMpeg/Arguments/BlackDetectArgument.cs
Normal file
14
FFMpegCore/FFMpeg/Arguments/BlackDetectArgument.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
public class BlackDetectArgument : IVideoFilterArgument
|
||||||
|
{
|
||||||
|
public string Key => "blackdetect";
|
||||||
|
|
||||||
|
public string Value { get; }
|
||||||
|
|
||||||
|
public BlackDetectArgument(double minimumDuration = 2.0, double pictureBlackRatioThreshold = 0.98, double pixelBlackThreshold = 0.1)
|
||||||
|
{
|
||||||
|
Value = $"d={minimumDuration}:pic_th={pictureBlackRatioThreshold}:pix_th={pixelBlackThreshold}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
FFMpegCore/FFMpeg/Arguments/BlackFrameArgument.cs
Normal file
14
FFMpegCore/FFMpeg/Arguments/BlackFrameArgument.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
internal class BlackFrameArgument : IVideoFilterArgument
|
||||||
|
{
|
||||||
|
public string Key => "blackframe";
|
||||||
|
|
||||||
|
public string Value { get; }
|
||||||
|
|
||||||
|
public BlackFrameArgument(int amount = 98, int threshold = 32)
|
||||||
|
{
|
||||||
|
Value = $"amount={amount}:threshold={threshold}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
using System.Collections.Generic;
|
namespace FFMpegCore.Arguments
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constant Rate Factor (CRF) argument
|
/// Constant Rate Factor (CRF) argument
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,8 @@ namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
public string Text => Channel switch
|
public string Text => Channel switch
|
||||||
{
|
{
|
||||||
Channel.Audio => "-c:a copy",
|
Channel.Both => "-c:a copy -c:v copy",
|
||||||
Channel.Video => "-c:v copy",
|
_ => $"-c{Channel.StreamType()} copy"
|
||||||
_ => "-c copy"
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Arguments
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents parameter of concat argument
|
/// Represents parameter of concat argument
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@ namespace FFMpegCore.Arguments
|
||||||
public DisableChannelArgument(Channel channel)
|
public DisableChannelArgument(Channel channel)
|
||||||
{
|
{
|
||||||
if (channel == Channel.Both)
|
if (channel == Channel.Both)
|
||||||
|
{
|
||||||
throw new FFMpegException(FFMpegExceptionType.Conversion, "Cannot disable both channels");
|
throw new FFMpegException(FFMpegExceptionType.Conversion, "Cannot disable both channels");
|
||||||
|
}
|
||||||
|
|
||||||
Channel = channel;
|
Channel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using System.Collections.Generic;
|
namespace FFMpegCore.Arguments
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Drawtext video filter argument
|
/// Drawtext video filter argument
|
||||||
|
|
@ -34,7 +31,7 @@ namespace FFMpegCore.Arguments
|
||||||
return new DrawTextOptions(text, font, parameters);
|
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)
|
private static string FormatArgumentPair((string key, string value) pair)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents duration parameter
|
/// Represents duration parameter
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
using System;
|
using System.Globalization;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
public class DynamicNormalizerArgument : IAudioFilterArgument
|
public class DynamicNormalizerArgument : IAudioFilterArgument
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, string> _arguments = new Dictionary<string, string>();
|
private readonly Dictionary<string, string> _arguments = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dynamic Audio Normalizer. <see href="https://ffmpeg.org/ffmpeg-filters.html#dynaudnorm"/>
|
/// Dynamic Audio Normalizer. <see href="https://ffmpeg.org/ffmpeg-filters.html#dynaudnorm"/>
|
||||||
|
|
@ -23,13 +20,40 @@ namespace FFMpegCore.Arguments
|
||||||
/// <param name="compressorFactor">Set the compress factor. In range from 0.0 to 30.0. Default is 0.0 (disabled).</param>
|
/// <param name="compressorFactor">Set the compress factor. In range from 0.0 to 30.0. Default is 0.0 (disabled).</param>
|
||||||
public DynamicNormalizerArgument(int frameLength = 500, int filterWindow = 31, double targetPeak = 0.95, double gainFactor = 10.0, double targetRms = 0.0, bool channelCoupling = true, bool enableDcBiasCorrection = false, bool enableAlternativeBoundary = false, double compressorFactor = 0.0)
|
public DynamicNormalizerArgument(int frameLength = 500, int filterWindow = 31, double targetPeak = 0.95, double gainFactor = 10.0, double targetRms = 0.0, bool channelCoupling = true, bool enableDcBiasCorrection = false, bool enableAlternativeBoundary = false, double compressorFactor = 0.0)
|
||||||
{
|
{
|
||||||
if (frameLength < 10 || frameLength > 8000) throw new ArgumentOutOfRangeException(nameof(frameLength),"Frame length must be between 10 to 8000");
|
if (frameLength < 10 || frameLength > 8000)
|
||||||
if (filterWindow < 3 || filterWindow > 31) throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be between 3 to 31");
|
{
|
||||||
if (filterWindow % 2 == 0) throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be an odd number");
|
throw new ArgumentOutOfRangeException(nameof(frameLength), "Frame length must be between 10 to 8000");
|
||||||
if (targetPeak <= 0 || targetPeak > 1) throw new ArgumentOutOfRangeException(nameof(targetPeak));
|
}
|
||||||
if (gainFactor < 1 || gainFactor > 100) throw new ArgumentOutOfRangeException(nameof(gainFactor), "Gain factor must be between 1.0 to 100.0");
|
|
||||||
if (targetRms < 0 || targetRms > 1) throw new ArgumentOutOfRangeException(nameof(targetRms), "Target RMS must be between 0.0 and 1.0");
|
if (filterWindow < 3 || filterWindow > 31)
|
||||||
if (compressorFactor < 0 || compressorFactor > 30) throw new ArgumentOutOfRangeException(nameof(compressorFactor), "Compressor factor must be between 0.0 and 30.0");
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be between 3 to 31");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterWindow % 2 == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be an odd number");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetPeak <= 0 || targetPeak > 1)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(targetPeak));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gainFactor < 1 || gainFactor > 100)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(gainFactor), "Gain factor must be between 1.0 to 100.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetRms < 0 || targetRms > 1)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(targetRms), "Target RMS must be between 0.0 and 1.0");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compressorFactor < 0 || compressorFactor > 30)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(compressorFactor), "Compressor factor must be between 0.0 and 30.0");
|
||||||
|
}
|
||||||
|
|
||||||
_arguments.Add("f", frameLength.ToString());
|
_arguments.Add("f", frameLength.ToString());
|
||||||
_arguments.Add("g", filterWindow.ToString());
|
_arguments.Add("g", filterWindow.ToString());
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ namespace FFMpegCore.Arguments
|
||||||
HardwareAccelerationDevice = hardwareAccelerationDevice;
|
HardwareAccelerationDevice = hardwareAccelerationDevice;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Text => HardwareAccelerationDevice != HardwareAccelerationDevice.Auto
|
public string Text => $"-hwaccel {HardwareAccelerationDevice.ToString().ToLower()}";
|
||||||
? $"-hwaccel {HardwareAccelerationDevice.ToString().ToLower()}"
|
|
||||||
: "-hwaccel";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
78
FFMpegCore/FFMpeg/Arguments/HighPassFilterArgument.cs
Normal file
78
FFMpegCore/FFMpeg/Arguments/HighPassFilterArgument.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
public class HighPassFilterArgument : IAudioFilterArgument
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
/// <param name="frequency">Set frequency in Hz. Default is 3000.</param>
|
||||||
|
/// <param name="poles">Set number of poles. Default is 2.</param>
|
||||||
|
/// <param name="width_type">Set method to specify band-width of filter, possible values are: h, q, o, s, k</param>
|
||||||
|
/// <param name="width">Specify the band-width of a filter in width_type units. Applies only to double-pole filter. The default is 0.707q and gives a Butterworth response.</param>
|
||||||
|
/// <param name="mix">How much to use filtered signal in output. Default is 1. Range is between 0 and 1.</param>
|
||||||
|
/// <param name="channels">Specify which channels to filter, by default all available are filtered.</param>
|
||||||
|
/// <param name="normalize">Normalize biquad coefficients, by default is disabled. Enabling it will normalize magnitude response at DC to 0dB.</param>
|
||||||
|
/// <param name="transform">Set transform type of IIR filter, possible values are: di, dii, tdi, tdii, latt, svf, zdf</param>
|
||||||
|
/// <param name="precision">Set precison of filtering, possible values are: auto, s16, s32, f32, f64.</param>
|
||||||
|
/// <param name="block_size">Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just produce nasty artifacts.</param>
|
||||||
|
public HighPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
|
||||||
|
{
|
||||||
|
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());
|
||||||
|
_arguments.Add("t", width_type);
|
||||||
|
_arguments.Add("w", width.ToString("0.00", CultureInfo.InvariantCulture));
|
||||||
|
_arguments.Add("m", mix.ToString("0.00", CultureInfo.InvariantCulture));
|
||||||
|
if (channels != "")
|
||||||
|
{
|
||||||
|
_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)
|
||||||
|
{
|
||||||
|
_arguments.Add("b", block_size.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key { get; } = "highpass";
|
||||||
|
|
||||||
|
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using System.Collections.Generic;
|
namespace FFMpegCore.Arguments
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
public interface IDynamicArgument
|
public interface IDynamicArgument
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using System.Threading;
|
namespace FFMpegCore.Arguments
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
public interface IInputOutputArgument : IArgument
|
public interface IInputOutputArgument : IArgument
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
using System.IO;
|
namespace FFMpegCore.Arguments
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents input parameter
|
/// Represents input parameter
|
||||||
|
|
@ -23,7 +19,9 @@ namespace FFMpegCore.Arguments
|
||||||
public void Pre()
|
public void Pre()
|
||||||
{
|
{
|
||||||
if (VerifyExists && !File.Exists(FilePath))
|
if (VerifyExists && !File.Exists(FilePath))
|
||||||
|
{
|
||||||
throw new FileNotFoundException("Input file not found", FilePath);
|
throw new FileNotFoundException("Input file not found", FilePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using System.Threading;
|
namespace FFMpegCore.Arguments
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an input device parameter
|
/// Represents an input device parameter
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
using System.IO.Pipes;
|
||||||
using System.IO.Pipes;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FFMpegCore.Pipes;
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
namespace FFMpegCore.Arguments
|
||||||
|
|
@ -24,7 +21,10 @@ namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||||
if (!Pipe.IsConnected)
|
if (!Pipe.IsConnected)
|
||||||
|
{
|
||||||
throw new OperationCanceledException();
|
throw new OperationCanceledException();
|
||||||
|
}
|
||||||
|
|
||||||
await Writer.WriteAsync(Pipe, token).ConfigureAwait(false);
|
await Writer.WriteAsync(Pipe, token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
78
FFMpegCore/FFMpeg/Arguments/LowPassFilterArgument.cs
Normal file
78
FFMpegCore/FFMpeg/Arguments/LowPassFilterArgument.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
public class LowPassFilterArgument : IAudioFilterArgument
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
/// <param name="frequency">Set frequency in Hz. Default is 3000.</param>
|
||||||
|
/// <param name="poles">Set number of poles. Default is 2.</param>
|
||||||
|
/// <param name="width_type">Set method to specify band-width of filter, possible values are: h, q, o, s, k</param>
|
||||||
|
/// <param name="width">Specify the band-width of a filter in width_type units. Applies only to double-pole filter. The default is 0.707q and gives a Butterworth response.</param>
|
||||||
|
/// <param name="mix">How much to use filtered signal in output. Default is 1. Range is between 0 and 1.</param>
|
||||||
|
/// <param name="channels">Specify which channels to filter, by default all available are filtered.</param>
|
||||||
|
/// <param name="normalize">Normalize biquad coefficients, by default is disabled. Enabling it will normalize magnitude response at DC to 0dB.</param>
|
||||||
|
/// <param name="transform">Set transform type of IIR filter, possible values are: di, dii, tdi, tdii, latt, svf, zdf</param>
|
||||||
|
/// <param name="precision">Set precison of filtering, possible values are: auto, s16, s32, f32, f64.</param>
|
||||||
|
/// <param name="block_size">Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just produce nasty artifacts.</param>
|
||||||
|
public LowPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
|
||||||
|
{
|
||||||
|
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());
|
||||||
|
_arguments.Add("t", width_type);
|
||||||
|
_arguments.Add("w", width.ToString("0.00", CultureInfo.InvariantCulture));
|
||||||
|
_arguments.Add("m", mix.ToString("0.00", CultureInfo.InvariantCulture));
|
||||||
|
if (channels != "")
|
||||||
|
{
|
||||||
|
_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)
|
||||||
|
{
|
||||||
|
_arguments.Add("b", block_size.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key { get; } = "lowpass";
|
||||||
|
|
||||||
|
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,4 @@
|
||||||
using FFMpegCore.Extend;
|
namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
public class MapMetadataArgument : IInputArgument, IDynamicArgument
|
public class MapMetadataArgument : IInputArgument, IDynamicArgument
|
||||||
{
|
{
|
||||||
|
|
@ -58,7 +49,5 @@ namespace FFMpegCore.Arguments
|
||||||
public void Pre()
|
public void Pre()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,31 @@
|
||||||
namespace FFMpegCore.Arguments
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents choice of video stream
|
/// Represents choice of stream by the stream specifier
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MapStreamArgument : IArgument
|
public class MapStreamArgument : IArgument
|
||||||
{
|
{
|
||||||
private readonly int _inputFileIndex;
|
private readonly int _inputFileIndex;
|
||||||
private readonly int _streamIndex;
|
private readonly int _streamIndex;
|
||||||
|
private readonly Channel _channel;
|
||||||
|
private readonly bool _negativeMap;
|
||||||
|
|
||||||
public MapStreamArgument(int streamIndex, int inputFileIndex)
|
public MapStreamArgument(int streamIndex, int inputFileIndex, Channel channel = Channel.All, bool negativeMap = false)
|
||||||
{
|
{
|
||||||
|
if (channel == Channel.Both)
|
||||||
|
{
|
||||||
|
// "Both" is not valid in this case and probably means all stream types
|
||||||
|
channel = Channel.All;
|
||||||
|
}
|
||||||
|
|
||||||
_inputFileIndex = inputFileIndex;
|
_inputFileIndex = inputFileIndex;
|
||||||
_streamIndex = streamIndex;
|
_streamIndex = streamIndex;
|
||||||
|
_channel = channel;
|
||||||
|
_negativeMap = negativeMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Text => $"-map {_inputFileIndex}:{_streamIndex}";
|
public string Text => $"-map {(_negativeMap ? "-" : "")}{_inputFileIndex}{_channel.StreamType()}:{_streamIndex}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,4 @@
|
||||||
using FFMpegCore.Extend;
|
namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
public class MetaDataArgument : IInputArgument, IDynamicArgument
|
public class MetaDataArgument : IInputArgument, IDynamicArgument
|
||||||
{
|
{
|
||||||
|
|
@ -24,7 +14,6 @@ namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||||
|
|
||||||
|
|
||||||
public void Pre() => File.WriteAllText(_tempFileName, _metaDataContent);
|
public void Pre() => File.WriteAllText(_tempFileName, _metaDataContent);
|
||||||
|
|
||||||
public void Post() => File.Delete(_tempFileName);
|
public void Post() => File.Delete(_tempFileName);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
using System;
|
using FFMpegCore.Exceptions;
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FFMpegCore.Exceptions;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
|
|
@ -23,7 +19,9 @@ namespace FFMpegCore.Arguments
|
||||||
public void Pre()
|
public void Pre()
|
||||||
{
|
{
|
||||||
if (!Overwrite && File.Exists(Path))
|
if (!Overwrite && File.Exists(Path))
|
||||||
|
{
|
||||||
throw new FFMpegException(FFMpegExceptionType.File, "Output file already exists and overwrite is disabled");
|
throw new FFMpegException(FFMpegExceptionType.File, "Output file already exists and overwrite is disabled");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||||
public void Post()
|
public void Post()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FFMpegCore.Pipes;
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
namespace FFMpegCore.Arguments
|
||||||
|
|
@ -20,7 +18,10 @@ namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||||
if (!Pipe.IsConnected)
|
if (!Pipe.IsConnected)
|
||||||
|
{
|
||||||
throw new TaskCanceledException();
|
throw new TaskCanceledException();
|
||||||
|
}
|
||||||
|
|
||||||
await Reader.ReadAsync(Pipe, token).ConfigureAwait(false);
|
await Reader.ReadAsync(Pipe, token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using System.Threading;
|
namespace FFMpegCore.Arguments
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents outputting to url using supported protocols
|
/// Represents outputting to url using supported protocols
|
||||||
|
|
|
||||||
64
FFMpegCore/FFMpeg/Arguments/PadArgument.cs
Normal file
64
FFMpegCore/FFMpeg/Arguments/PadArgument.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
using FFMpegCore.Extend;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
public class PadArgument : IVideoFilterArgument
|
||||||
|
{
|
||||||
|
private readonly PadOptions _options;
|
||||||
|
|
||||||
|
public PadArgument(PadOptions options)
|
||||||
|
{
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key => "pad";
|
||||||
|
public string Value => _options.TextInternal;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PadOptions
|
||||||
|
{
|
||||||
|
public readonly Dictionary<string, string> Parameters = new();
|
||||||
|
|
||||||
|
internal string TextInternal => string.Join(":", Parameters.Select(parameter => parameter.FormatArgumentPair(true)));
|
||||||
|
|
||||||
|
public static PadOptions Create(string? width, string? height)
|
||||||
|
{
|
||||||
|
return new PadOptions(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PadOptions Create(string aspectRatio)
|
||||||
|
{
|
||||||
|
return new PadOptions(aspectRatio);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PadOptions WithParameter(string key, string value)
|
||||||
|
{
|
||||||
|
Parameters.Add(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PadOptions(string? width, string? height)
|
||||||
|
{
|
||||||
|
if (width == null && height == null)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PadOptions(string aspectRatio)
|
||||||
|
{
|
||||||
|
Parameters.Add("aspect", aspectRatio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Arguments
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mix channels with specific gain levels.
|
/// Mix channels with specific gain levels.
|
||||||
|
|
@ -24,7 +21,7 @@ namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(channelLayout))
|
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;
|
ChannelLayout = channelLayout;
|
||||||
|
|
@ -41,10 +38,15 @@ namespace FFMpegCore.Arguments
|
||||||
/// </param>
|
/// </param>
|
||||||
public PanArgument(int channels, params string[] outputDefinitions)
|
public PanArgument(int channels, params string[] outputDefinitions)
|
||||||
{
|
{
|
||||||
if (channels <= 0) throw new ArgumentOutOfRangeException(nameof(channels));
|
if (channels <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(channels));
|
||||||
|
}
|
||||||
|
|
||||||
if (outputDefinitions.Length > channels)
|
if (outputDefinitions.Length > channels)
|
||||||
|
{
|
||||||
throw new ArgumentException("The number of output definitions must be equal or lower than number of channels", nameof(outputDefinitions));
|
throw new ArgumentException("The number of output definitions must be equal or lower than number of channels", nameof(outputDefinitions));
|
||||||
|
}
|
||||||
|
|
||||||
ChannelLayout = $"{channels}c";
|
ChannelLayout = $"{channels}c";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
using System;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FFMpegCore.Pipes;
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
namespace FFMpegCore.Arguments
|
||||||
|
|
@ -24,7 +21,9 @@ namespace FFMpegCore.Arguments
|
||||||
public void Pre()
|
public void Pre()
|
||||||
{
|
{
|
||||||
if (Pipe != null)
|
if (Pipe != null)
|
||||||
|
{
|
||||||
throw new InvalidOperationException("Pipe already has been opened");
|
throw new InvalidOperationException("Pipe already has been opened");
|
||||||
|
}
|
||||||
|
|
||||||
Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +49,9 @@ namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}");
|
Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}");
|
||||||
if (Pipe is { IsConnected: true })
|
if (Pipe is { IsConnected: true })
|
||||||
|
{
|
||||||
Pipe.Disconnect();
|
Pipe.Disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents seek parameter
|
/// Represents seek parameter
|
||||||
|
|
@ -14,15 +12,18 @@ namespace FFMpegCore.Arguments
|
||||||
SeekTo = seekTo;
|
SeekTo = seekTo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Text {
|
public string Text
|
||||||
get {
|
{
|
||||||
if(SeekTo.HasValue)
|
get
|
||||||
|
{
|
||||||
|
if (SeekTo.HasValue)
|
||||||
{
|
{
|
||||||
int hours = SeekTo.Value.Hours;
|
var hours = SeekTo.Value.Hours;
|
||||||
if(SeekTo.Value.Days > 0)
|
if (SeekTo.Value.Days > 0)
|
||||||
{
|
{
|
||||||
hours += SeekTo.Value.Days * 24;
|
hours += SeekTo.Value.Days * 24;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $"-ss {hours.ToString("00")}:{SeekTo.Value.Minutes.ToString("00")}:{SeekTo.Value.Seconds.ToString("00")}.{SeekTo.Value.Milliseconds.ToString("000")}";
|
return $"-ss {hours.ToString("00")}:{SeekTo.Value.Minutes.ToString("00")}:{SeekTo.Value.Seconds.ToString("00")}.{SeekTo.Value.Milliseconds.ToString("000")}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using FFMpegCore.Enums;
|
using FFMpegCore.Enums;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
|
|
@ -14,17 +13,12 @@ namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
public string Key => string.Empty;
|
public string Key => string.Empty;
|
||||||
|
|
||||||
public string Value
|
public string Value =>
|
||||||
{
|
Mirroring switch
|
||||||
get
|
|
||||||
{
|
{
|
||||||
return Mirroring switch
|
Mirroring.Horizontal => "hflip",
|
||||||
{
|
Mirroring.Vertical => "vflip",
|
||||||
Mirroring.Horizontal => "hflip",
|
_ => throw new ArgumentOutOfRangeException(nameof(Mirroring))
|
||||||
Mirroring.Vertical => "vflip",
|
};
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(Mirroring))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
FFMpegCore/FFMpeg/Arguments/SilenceDetectArgument.cs
Normal file
39
FFMpegCore/FFMpeg/Arguments/SilenceDetectArgument.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace FFMpegCore.Arguments
|
||||||
|
{
|
||||||
|
public class SilenceDetectArgument : IAudioFilterArgument
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, string> _arguments = new();
|
||||||
|
/// <summary>
|
||||||
|
/// Silence Detection. <see href="https://ffmpeg.org/ffmpeg-filters.html#silencedetect"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="noise_type">Set noise type to db (decibel) or ar (amplitude ratio). Default is dB</param>
|
||||||
|
/// <param name="noise">Set noise tolerance. Can be specified in dB (in case "dB" is appended to the specified value) or amplitude ratio. Default is -60dB, or 0.001.</param>
|
||||||
|
/// <param name="duration">Set silence duration until notification (default is 2 seconds). See (ffmpeg-utils)the Time duration section in the ffmpeg-utils(1) manual for the accepted syntax.</param>
|
||||||
|
/// <param name="mono">Process each channel separately, instead of combined. By default is disabled.</param>
|
||||||
|
|
||||||
|
public SilenceDetectArgument(string noise_type = "db", double noise = 60, double duration = 2, bool mono = false)
|
||||||
|
{
|
||||||
|
if (noise_type == "db")
|
||||||
|
{
|
||||||
|
_arguments.Add("n", $"{noise.ToString("0.0", CultureInfo.InvariantCulture)}dB");
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
_arguments.Add("d", duration.ToString("0.00", CultureInfo.InvariantCulture));
|
||||||
|
_arguments.Add("m", (mono ? 1 : 0).ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key { get; } = "silencedetect";
|
||||||
|
|
||||||
|
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
using FFMpegCore.Extend;
|
using System.Drawing;
|
||||||
using System.Collections.Generic;
|
using FFMpegCore.Extend;
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
|
|
@ -23,7 +21,7 @@ namespace FFMpegCore.Arguments
|
||||||
{
|
{
|
||||||
private readonly string _subtitle;
|
private readonly string _subtitle;
|
||||||
|
|
||||||
public readonly Dictionary<string, string> Parameters = new Dictionary<string, string>();
|
public readonly Dictionary<string, string> Parameters = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="SubtitleHardBurnOptions"/> using a provided subtitle file or a video file
|
/// Create a new <see cref="SubtitleHardBurnOptions"/> using a provided subtitle file or a video file
|
||||||
|
|
@ -110,7 +108,7 @@ namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
public class StyleOptions
|
public class StyleOptions
|
||||||
{
|
{
|
||||||
public readonly Dictionary<string, string> Parameters = new Dictionary<string, string>();
|
public readonly Dictionary<string, string> Parameters = new();
|
||||||
|
|
||||||
public static StyleOptions Create()
|
public static StyleOptions Create()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents threads parameter
|
/// Represents threads parameter
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
namespace FFMpegCore.Arguments
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Variable Bitrate Argument (VBR) argument
|
/// Variable Bitrate Argument (VBR) argument
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,9 @@ namespace FFMpegCore.Arguments
|
||||||
public VideoCodecArgument(Codec value)
|
public VideoCodecArgument(Codec value)
|
||||||
{
|
{
|
||||||
if (value.Type != CodecType.Video)
|
if (value.Type != CodecType.Video)
|
||||||
|
{
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{value.Name}\" is not a video codec");
|
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{value.Name}\" is not a video codec");
|
||||||
|
}
|
||||||
|
|
||||||
Codec = value.Name;
|
Codec = value.Name;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Drawing;
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using FFMpegCore.Enums;
|
using FFMpegCore.Enums;
|
||||||
using FFMpegCore.Exceptions;
|
using FFMpegCore.Exceptions;
|
||||||
|
|
||||||
|
|
@ -20,7 +18,9 @@ namespace FFMpegCore.Arguments
|
||||||
private string GetText()
|
private string GetText()
|
||||||
{
|
{
|
||||||
if (!Options.Arguments.Any())
|
if (!Options.Arguments.Any())
|
||||||
|
{
|
||||||
throw new FFMpegArgumentException("No video-filter arguments provided");
|
throw new FFMpegArgumentException("No video-filter arguments provided");
|
||||||
|
}
|
||||||
|
|
||||||
var arguments = Options.Arguments
|
var arguments = Options.Arguments
|
||||||
.Where(arg => !string.IsNullOrEmpty(arg.Value))
|
.Where(arg => !string.IsNullOrEmpty(arg.Value))
|
||||||
|
|
@ -42,7 +42,7 @@ namespace FFMpegCore.Arguments
|
||||||
|
|
||||||
public class VideoFilterOptions
|
public class VideoFilterOptions
|
||||||
{
|
{
|
||||||
public List<IVideoFilterArgument> Arguments { get; } = new List<IVideoFilterArgument>();
|
public List<IVideoFilterArgument> Arguments { get; } = new();
|
||||||
|
|
||||||
public VideoFilterOptions Scale(VideoSize videoSize) => WithArgument(new ScaleArgument(videoSize));
|
public VideoFilterOptions Scale(VideoSize videoSize) => WithArgument(new ScaleArgument(videoSize));
|
||||||
public VideoFilterOptions Scale(int width, int height) => WithArgument(new ScaleArgument(width, height));
|
public VideoFilterOptions Scale(int width, int height) => WithArgument(new ScaleArgument(width, height));
|
||||||
|
|
@ -51,6 +51,9 @@ namespace FFMpegCore.Arguments
|
||||||
public VideoFilterOptions Mirror(Mirroring mirroring) => WithArgument(new SetMirroringArgument(mirroring));
|
public VideoFilterOptions Mirror(Mirroring mirroring) => WithArgument(new SetMirroringArgument(mirroring));
|
||||||
public VideoFilterOptions DrawText(DrawTextOptions drawTextOptions) => WithArgument(new DrawTextArgument(drawTextOptions));
|
public VideoFilterOptions DrawText(DrawTextOptions drawTextOptions) => WithArgument(new DrawTextArgument(drawTextOptions));
|
||||||
public VideoFilterOptions HardBurnSubtitle(SubtitleHardBurnOptions subtitleHardBurnOptions) => WithArgument(new SubtitleHardBurnArgument(subtitleHardBurnOptions));
|
public VideoFilterOptions HardBurnSubtitle(SubtitleHardBurnOptions subtitleHardBurnOptions) => WithArgument(new SubtitleHardBurnArgument(subtitleHardBurnOptions));
|
||||||
|
public VideoFilterOptions BlackDetect(double minimumDuration = 2.0, double pictureBlackRatioThreshold = 0.98, double pixelBlackThreshold = 0.1) => WithArgument(new BlackDetectArgument(minimumDuration, pictureBlackRatioThreshold, pixelBlackThreshold));
|
||||||
|
public VideoFilterOptions BlackFrame(int amount = 98, int threshold = 32) => WithArgument(new BlackFrameArgument(amount, threshold));
|
||||||
|
public VideoFilterOptions Pad(PadOptions padOptions) => WithArgument(new PadArgument(padOptions));
|
||||||
|
|
||||||
private VideoFilterOptions WithArgument(IVideoFilterArgument argument)
|
private VideoFilterOptions WithArgument(IVideoFilterArgument argument)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Builders.MetaData
|
||||||
|
|
||||||
namespace FFMpegCore.Builders.MetaData
|
|
||||||
{
|
{
|
||||||
public class ChapterData
|
public class ChapterData
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System.Collections.Generic;
|
namespace FFMpegCore.Builders.MetaData
|
||||||
|
|
||||||
namespace FFMpegCore.Builders.MetaData
|
|
||||||
{
|
{
|
||||||
|
|
||||||
public interface IReadOnlyMetaData
|
public interface IReadOnlyMetaData
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
using System.Collections.Generic;
|
namespace FFMpegCore.Builders.MetaData
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Builders.MetaData
|
|
||||||
{
|
{
|
||||||
public class MetaData : IReadOnlyMetaData
|
public class MetaData : IReadOnlyMetaData
|
||||||
{
|
{
|
||||||
public Dictionary<string, string> Entries { get; private set; }
|
public Dictionary<string, string> Entries { get; private set; }
|
||||||
public List<ChapterData> Chapters { get; private set; }
|
public List<ChapterData> Chapters { get; private set; }
|
||||||
|
|
||||||
IReadOnlyList<ChapterData> IReadOnlyMetaData.Chapters => this.Chapters;
|
IReadOnlyList<ChapterData> IReadOnlyMetaData.Chapters => Chapters;
|
||||||
IReadOnlyDictionary<string, string> IReadOnlyMetaData.Entries => this.Entries;
|
IReadOnlyDictionary<string, string> IReadOnlyMetaData.Entries => Entries;
|
||||||
|
|
||||||
public MetaData()
|
public MetaData()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,14 @@
|
||||||
using System;
|
namespace FFMpegCore.Builders.MetaData
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Builders.MetaData
|
|
||||||
{
|
{
|
||||||
public class MetaDataBuilder
|
public class MetaDataBuilder
|
||||||
{
|
{
|
||||||
private MetaData _metaData = new MetaData();
|
private readonly MetaData _metaData = new();
|
||||||
|
|
||||||
public MetaDataBuilder WithEntry(string key, string entry)
|
public MetaDataBuilder WithEntry(string key, string entry)
|
||||||
{
|
{
|
||||||
if (_metaData.Entries.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value))
|
if (_metaData.Entries.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value))
|
||||||
{
|
{
|
||||||
entry = String.Concat(value, "; ", entry);
|
entry = string.Concat(value, "; ", entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
_metaData.Entries[key] = entry;
|
_metaData.Entries[key] = entry;
|
||||||
|
|
@ -20,10 +16,10 @@ namespace FFMpegCore.Builders.MetaData
|
||||||
}
|
}
|
||||||
|
|
||||||
public MetaDataBuilder WithEntry(string key, params string[] values)
|
public MetaDataBuilder WithEntry(string key, params string[] values)
|
||||||
=> this.WithEntry(key, String.Join("; ", values));
|
=> WithEntry(key, string.Join("; ", values));
|
||||||
|
|
||||||
public MetaDataBuilder WithEntry(string key, IEnumerable<string> values)
|
public MetaDataBuilder WithEntry(string key, IEnumerable<string> values)
|
||||||
=> this.WithEntry(key, String.Join("; ", values));
|
=> WithEntry(key, string.Join("; ", values));
|
||||||
|
|
||||||
public MetaDataBuilder AddChapter(ChapterData chapterData)
|
public MetaDataBuilder AddChapter(ChapterData chapterData)
|
||||||
{
|
{
|
||||||
|
|
@ -33,7 +29,7 @@ namespace FFMpegCore.Builders.MetaData
|
||||||
|
|
||||||
public MetaDataBuilder AddChapters<T>(IEnumerable<T> values, Func<T, (TimeSpan duration, string title)> chapterGetter)
|
public MetaDataBuilder AddChapters<T>(IEnumerable<T> values, Func<T, (TimeSpan duration, string title)> chapterGetter)
|
||||||
{
|
{
|
||||||
foreach (T value in values)
|
foreach (var value in values)
|
||||||
{
|
{
|
||||||
var (duration, title) = chapterGetter(value);
|
var (duration, title) = chapterGetter(value);
|
||||||
AddChapter(duration, title);
|
AddChapter(duration, title);
|
||||||
|
|
@ -46,13 +42,13 @@ namespace FFMpegCore.Builders.MetaData
|
||||||
{
|
{
|
||||||
var start = _metaData.Chapters.LastOrDefault()?.End ?? TimeSpan.Zero;
|
var start = _metaData.Chapters.LastOrDefault()?.End ?? TimeSpan.Zero;
|
||||||
var end = start + duration;
|
var end = start + duration;
|
||||||
title = String.IsNullOrEmpty(title) ? $"Chapter {_metaData.Chapters.Count + 1}" : title;
|
title = string.IsNullOrEmpty(title) ? $"Chapter {_metaData.Chapters.Count + 1}" : title;
|
||||||
|
|
||||||
_metaData.Chapters.Add(new ChapterData
|
_metaData.Chapters.Add(new ChapterData
|
||||||
(
|
(
|
||||||
start: start,
|
start: start,
|
||||||
end: end,
|
end: end,
|
||||||
title: title ?? String.Empty
|
title: title ?? string.Empty
|
||||||
));
|
));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
|
@ -102,8 +98,6 @@ namespace FFMpegCore.Builders.MetaData
|
||||||
//encoder=Lavf58.47.100
|
//encoder=Lavf58.47.100
|
||||||
public MetaDataBuilder WithEncoder(string value) => WithEntry("encoder", value);
|
public MetaDataBuilder WithEncoder(string value) => WithEntry("encoder", value);
|
||||||
|
|
||||||
|
public ReadOnlyMetaData Build() => new(_metaData);
|
||||||
|
|
||||||
public ReadOnlyMetaData Build() => new ReadOnlyMetaData(_metaData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
using System.Linq;
|
using System.Text;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Builders.MetaData
|
namespace FFMpegCore.Builders.MetaData
|
||||||
{
|
{
|
||||||
public class MetaDataSerializer
|
public class MetaDataSerializer
|
||||||
{
|
{
|
||||||
public static readonly MetaDataSerializer Instance = new MetaDataSerializer();
|
public static readonly MetaDataSerializer Instance = new();
|
||||||
|
|
||||||
public string Serialize(IReadOnlyMetaData metaData)
|
public string Serialize(IReadOnlyMetaData metaData)
|
||||||
{
|
{
|
||||||
|
|
@ -17,7 +16,7 @@ namespace FFMpegCore.Builders.MetaData
|
||||||
sb.AppendLine($"{value.Key}={value.Value}");
|
sb.AppendLine($"{value.Key}={value.Value}");
|
||||||
}
|
}
|
||||||
|
|
||||||
int chapterNumber = 0;
|
var chapterNumber = 0;
|
||||||
foreach (var chapter in metaData.Chapters ?? Enumerable.Empty<ChapterData>())
|
foreach (var chapter in metaData.Chapters ?? Enumerable.Empty<ChapterData>())
|
||||||
{
|
{
|
||||||
chapterNumber++;
|
chapterNumber++;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
using System.Collections.Generic;
|
namespace FFMpegCore.Builders.MetaData
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Builders.MetaData
|
|
||||||
{
|
{
|
||||||
public class ReadOnlyMetaData : IReadOnlyMetaData
|
public class ReadOnlyMetaData : IReadOnlyMetaData
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using FFMpegCore.Exceptions;
|
using System.Text.RegularExpressions;
|
||||||
using System;
|
using FFMpegCore.Exceptions;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Enums
|
namespace FFMpegCore.Enums
|
||||||
{
|
{
|
||||||
|
|
@ -13,8 +12,8 @@ namespace FFMpegCore.Enums
|
||||||
|
|
||||||
public class Codec
|
public class Codec
|
||||||
{
|
{
|
||||||
private static readonly Regex _codecsFormatRegex = new Regex(@"([D\.])([E\.])([VASD\.])([I\.])([L\.])([S\.])\s+([a-z0-9_-]+)\s+(.+)");
|
private static readonly Regex _codecsFormatRegex = new(@"([D\.])([E\.])([VASD\.])([I\.])([L\.])([S\.])\s+([a-z0-9_-]+)\s+(.+)");
|
||||||
private static readonly Regex _decodersEncodersFormatRegex = new Regex(@"([VASD\.])([F\.])([S\.])([X\.])([B\.])([D\.])\s+([a-z0-9_-]+)\s+(.+)");
|
private static readonly Regex _decodersEncodersFormatRegex = new(@"([VASD\.])([F\.])([S\.])([X\.])([B\.])([D\.])\s+([a-z0-9_-]+)\s+(.+)");
|
||||||
|
|
||||||
public class FeatureLevel
|
public class FeatureLevel
|
||||||
{
|
{
|
||||||
|
|
@ -73,7 +72,7 @@ namespace FFMpegCore.Enums
|
||||||
_ => CodecType.Unknown
|
_ => CodecType.Unknown
|
||||||
};
|
};
|
||||||
|
|
||||||
if(type == CodecType.Unknown)
|
if (type == CodecType.Unknown)
|
||||||
{
|
{
|
||||||
codec = null!;
|
codec = null!;
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -133,7 +132,9 @@ namespace FFMpegCore.Enums
|
||||||
internal void Merge(Codec other)
|
internal void Merge(Codec other)
|
||||||
{
|
{
|
||||||
if (Name != other.Name)
|
if (Name != other.Name)
|
||||||
|
{
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation, "different codecs enable to merge");
|
throw new FFMpegException(FFMpegExceptionType.Operation, "different codecs enable to merge");
|
||||||
|
}
|
||||||
|
|
||||||
Type |= other.Type;
|
Type |= other.Type;
|
||||||
DecodingSupported |= other.DecodingSupported;
|
DecodingSupported |= other.DecodingSupported;
|
||||||
|
|
@ -146,7 +147,9 @@ namespace FFMpegCore.Enums
|
||||||
DecoderFeatureLevel.Merge(other.DecoderFeatureLevel);
|
DecoderFeatureLevel.Merge(other.DecoderFeatureLevel);
|
||||||
|
|
||||||
if (Description != other.Description)
|
if (Description != other.Description)
|
||||||
|
{
|
||||||
Description += "\r\n" + other.Description;
|
Description += "\r\n" + other.Description;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ namespace FFMpegCore.Enums
|
||||||
{
|
{
|
||||||
public class ContainerFormat
|
public class ContainerFormat
|
||||||
{
|
{
|
||||||
private static readonly Regex FormatRegex = new Regex(@"([D ])([E ])\s+([a-z0-9_]+)\s+(.+)");
|
private static readonly Regex FormatRegex = new(@"([D ])([E ])\s+([a-z0-9_]+)\s+(.+)");
|
||||||
|
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
public bool DemuxingSupported { get; private set; }
|
public bool DemuxingSupported { get; private set; }
|
||||||
|
|
@ -16,7 +16,10 @@ namespace FFMpegCore.Enums
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (GlobalFFOptions.Current.ExtensionOverrides.ContainsKey(Name))
|
if (GlobalFFOptions.Current.ExtensionOverrides.ContainsKey(Name))
|
||||||
|
{
|
||||||
return GlobalFFOptions.Current.ExtensionOverrides[Name];
|
return GlobalFFOptions.Current.ExtensionOverrides[Name];
|
||||||
|
}
|
||||||
|
|
||||||
return "." + Name;
|
return "." + Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,42 @@
|
||||||
Aac_AdtstoAsc
|
Aac_AdtstoAsc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// https://ffmpeg.org/ffmpeg.html#Stream-specifiers-1
|
||||||
|
/// ’v’ or ’V’ for video, ’a’ for audio, ’s’ for subtitle, ’d’ for data, and ’t’ for attachments
|
||||||
|
/// ’V’ only matches video streams which are not attached pictures, video thumbnails or cover arts.
|
||||||
|
/// Both for audio + video
|
||||||
|
/// All for all types
|
||||||
|
/// </summary>
|
||||||
public enum Channel
|
public enum Channel
|
||||||
{
|
{
|
||||||
Audio,
|
Audio,
|
||||||
Video,
|
Video,
|
||||||
Both
|
Both,
|
||||||
|
VideoNoAttachedPic,
|
||||||
|
Subtitle,
|
||||||
|
Data,
|
||||||
|
Attachments,
|
||||||
|
All
|
||||||
|
}
|
||||||
|
internal static class ChannelMethods
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// <see cref="Channel.Both"/> is left as empty because it cannot be in a single stream specifier
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The stream_type used in stream specifiers</returns>
|
||||||
|
public static string StreamType(this Channel channel)
|
||||||
|
{
|
||||||
|
return channel switch
|
||||||
|
{
|
||||||
|
Channel.Audio => ":a",
|
||||||
|
Channel.Video => ":v",
|
||||||
|
Channel.VideoNoAttachedPic => ":V",
|
||||||
|
Channel.Subtitle => ":s",
|
||||||
|
Channel.Data => ":d",
|
||||||
|
Channel.Attachments => ":t",
|
||||||
|
_ => string.Empty
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
15
FFMpegCore/FFMpeg/Enums/FFMpegLogLevel.cs
Normal file
15
FFMpegCore/FFMpeg/Enums/FFMpegLogLevel.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace FFMpegCore.Enums
|
||||||
|
{
|
||||||
|
public enum FFMpegLogLevel
|
||||||
|
{
|
||||||
|
Quiet = 0,
|
||||||
|
Panic = 1,
|
||||||
|
Fatal = 2,
|
||||||
|
Error = 3,
|
||||||
|
Warning = 4,
|
||||||
|
Info = 5,
|
||||||
|
Verbose = 6,
|
||||||
|
Debug = 7,
|
||||||
|
Trace = 8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Enums
|
||||||
|
|
||||||
namespace FFMpegCore.Enums
|
|
||||||
{
|
{
|
||||||
public static class FileExtension
|
public static class FileExtension
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
DXVA2,
|
DXVA2,
|
||||||
QSV,
|
QSV,
|
||||||
CUVID,
|
CUVID,
|
||||||
|
CUDA,
|
||||||
VDPAU,
|
VDPAU,
|
||||||
VAAPI,
|
VAAPI,
|
||||||
LibMFX
|
LibMFX
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ namespace FFMpegCore.Enums
|
||||||
{
|
{
|
||||||
public class PixelFormat
|
public class PixelFormat
|
||||||
{
|
{
|
||||||
private static readonly Regex _formatRegex = new Regex(@"([I\.])([O\.])([H\.])([P\.])([B\.])\s+(\S+)\s+([0-9]+)\s+([0-9]+)");
|
private static readonly Regex _formatRegex = new(@"([I\.])([O\.])([H\.])([P\.])([B\.])\s+(\S+)\s+([0-9]+)\s+([0-9]+)");
|
||||||
|
|
||||||
public bool InputConversionSupported { get; private set; }
|
public bool InputConversionSupported { get; private set; }
|
||||||
public bool OutputConversionSupported { get; private set; }
|
public bool OutputConversionSupported { get; private set; }
|
||||||
|
|
@ -41,10 +41,16 @@ namespace FFMpegCore.Enums
|
||||||
fmt.IsPaletted = match.Groups[4].Value != ".";
|
fmt.IsPaletted = match.Groups[4].Value != ".";
|
||||||
fmt.IsBitstream = match.Groups[5].Value != ".";
|
fmt.IsBitstream = match.Groups[5].Value != ".";
|
||||||
if (!int.TryParse(match.Groups[7].Value, out var nbComponents))
|
if (!int.TryParse(match.Groups[7].Value, out var nbComponents))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Components = nbComponents;
|
fmt.Components = nbComponents;
|
||||||
if (!int.TryParse(match.Groups[8].Value, out var bpp))
|
if (!int.TryParse(match.Groups[8].Value, out var bpp))
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
fmt.BitsPerPixel = bpp;
|
fmt.BitsPerPixel = bpp;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace FFMpegCore.Exceptions
|
||||||
|
|
||||||
namespace FFMpegCore.Exceptions
|
|
||||||
{
|
{
|
||||||
public enum FFMpegExceptionType
|
public enum FFMpegExceptionType
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
using FFMpegCore.Enums;
|
using System.Drawing;
|
||||||
|
using FFMpegCore.Enums;
|
||||||
using FFMpegCore.Exceptions;
|
using FFMpegCore.Exceptions;
|
||||||
using FFMpegCore.Helpers;
|
using FFMpegCore.Helpers;
|
||||||
using FFMpegCore.Pipes;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Instances;
|
using Instances;
|
||||||
|
|
||||||
namespace FFMpegCore
|
namespace FFMpegCore
|
||||||
|
|
@ -27,10 +21,12 @@ namespace FFMpegCore
|
||||||
public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
||||||
{
|
{
|
||||||
if (Path.GetExtension(output) != FileExtension.Png)
|
if (Path.GetExtension(output) != FileExtension.Png)
|
||||||
output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png;
|
{
|
||||||
|
output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Png);
|
||||||
|
}
|
||||||
|
|
||||||
var source = FFProbe.Analyse(input);
|
var source = FFProbe.Analyse(input);
|
||||||
var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||||
|
|
||||||
return arguments
|
return arguments
|
||||||
.OutputToFile(output, true, outputOptions)
|
.OutputToFile(output, true, outputOptions)
|
||||||
|
|
@ -49,10 +45,12 @@ namespace FFMpegCore
|
||||||
public static async Task<bool> SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
public static async Task<bool> SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
||||||
{
|
{
|
||||||
if (Path.GetExtension(output) != FileExtension.Png)
|
if (Path.GetExtension(output) != FileExtension.Png)
|
||||||
output = Path.GetFileNameWithoutExtension(output) + FileExtension.Png;
|
{
|
||||||
|
output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Png);
|
||||||
|
}
|
||||||
|
|
||||||
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
|
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
|
||||||
var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||||
|
|
||||||
return await arguments
|
return await arguments
|
||||||
.OutputToFile(output, true, outputOptions)
|
.OutputToFile(output, true, outputOptions)
|
||||||
|
|
@ -60,110 +58,79 @@ namespace FFMpegCore
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
/// Converts an image sequence to a video.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="input">Source video file.</param>
|
/// <param name="output">Output video file.</param>
|
||||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
/// <param name="frameRate">FPS</param>
|
||||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
/// <param name="images">Image sequence collection</param>
|
||||||
/// <param name="streamIndex">Selected video stream index.</param>
|
/// <returns>Output video information.</returns>
|
||||||
/// <param name="inputFileIndex">Input file index</param>
|
public static bool JoinImageSequence(string output, double frameRate = 30, params string[] images)
|
||||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
|
||||||
public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
|
||||||
{
|
{
|
||||||
var source = FFProbe.Analyse(input);
|
int? width = null, height = null;
|
||||||
var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
var tempFolderName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, Guid.NewGuid().ToString());
|
||||||
using var ms = new MemoryStream();
|
var temporaryImageFiles = images.Select((imagePath, index) =>
|
||||||
|
|
||||||
arguments
|
|
||||||
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
|
||||||
.ForceFormat("rawvideo")))
|
|
||||||
.ProcessSynchronously();
|
|
||||||
|
|
||||||
ms.Position = 0;
|
|
||||||
using var bitmap = new Bitmap(ms);
|
|
||||||
return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat);
|
|
||||||
}
|
|
||||||
/// <summary>
|
|
||||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="input">Source video file.</param>
|
|
||||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
|
||||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
|
||||||
/// <param name="streamIndex">Selected video stream index.</param>
|
|
||||||
/// <param name="inputFileIndex">Input file index</param>
|
|
||||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
|
||||||
public static async Task<Bitmap> SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
|
||||||
{
|
|
||||||
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
|
|
||||||
var (arguments, outputOptions) = BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
|
|
||||||
await arguments
|
|
||||||
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
|
||||||
.ForceFormat("rawvideo")))
|
|
||||||
.ProcessAsynchronously();
|
|
||||||
|
|
||||||
ms.Position = 0;
|
|
||||||
return new Bitmap(ms);
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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 analysis = FFProbe.Analyse(imagePath);
|
||||||
{
|
FFMpegHelper.ConversionSizeExceptionCheck(analysis.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Height);
|
||||||
var ratio = (double)wantedSize.Value.Height / currentSize.Height;
|
width ??= analysis.PrimaryVideoStream.Width;
|
||||||
return new Size((int)(currentSize.Width * ratio), (int)(currentSize.Height * ratio));
|
height ??= analysis.PrimaryVideoStream.Height;
|
||||||
}
|
|
||||||
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;
|
var destinationPath = Path.Combine(tempFolderName, $"{index.ToString().PadLeft(9, '0')}{Path.GetExtension(imagePath)}");
|
||||||
|
Directory.CreateDirectory(tempFolderName);
|
||||||
|
File.Copy(imagePath, destinationPath);
|
||||||
|
return destinationPath;
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return FFMpegArguments
|
||||||
|
.FromFileInput(Path.Combine(tempFolderName, "%09d.png"), false)
|
||||||
|
.OutputToFile(output, true, options => options
|
||||||
|
.ForcePixelFormat("yuv420p")
|
||||||
|
.Resize(width!.Value, height!.Value)
|
||||||
|
.WithFramerate(frameRate))
|
||||||
|
.ProcessSynchronously();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Cleanup(temporaryImageFiles);
|
||||||
|
Directory.Delete(tempFolderName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a poster image to an audio file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="image">Source image file.</param>
|
||||||
|
/// <param name="audio">Source audio file.</param>
|
||||||
|
/// <param name="output">Output video file.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool PosterWithAudio(string image, string audio, string output)
|
||||||
|
{
|
||||||
|
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
||||||
|
var analysis = FFProbe.Analyse(image);
|
||||||
|
FFMpegHelper.ConversionSizeExceptionCheck(analysis.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Height);
|
||||||
|
|
||||||
|
return FFMpegArguments
|
||||||
|
.FromFileInput(image, false, options => options
|
||||||
|
.Loop(1)
|
||||||
|
.ForceFormat("image2"))
|
||||||
|
.AddFileInput(audio)
|
||||||
|
.OutputToFile(output, true, options => options
|
||||||
|
.ForcePixelFormat("yuv420p")
|
||||||
|
.WithVideoCodec(VideoCodec.LibX264)
|
||||||
|
.WithConstantRateFactor(21)
|
||||||
|
.WithAudioBitrate(AudioQuality.Normal)
|
||||||
|
.UsingShortest())
|
||||||
|
.ProcessSynchronously();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Convert a video do a different format.
|
/// Convert a video do a different format.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="source">Input video source.</param>
|
/// <param name="input">Input video source.</param>
|
||||||
/// <param name="output">Output information.</param>
|
/// <param name="output">Output information.</param>
|
||||||
/// <param name="type">Target conversion video type.</param>
|
/// <param name="format">Target conversion video format.</param>
|
||||||
/// <param name="speed">Conversion target speed/quality (faster speed = lower quality).</param>
|
/// <param name="speed">Conversion target speed/quality (faster speed = lower quality).</param>
|
||||||
/// <param name="size">Video size.</param>
|
/// <param name="size">Video size.</param>
|
||||||
/// <param name="audioQuality">Conversion target audio quality.</param>
|
/// <param name="audioQuality">Conversion target audio quality.</param>
|
||||||
|
|
@ -186,7 +153,9 @@ namespace FFMpegCore
|
||||||
var outputSize = new Size((int)(source.PrimaryVideoStream!.Width / scale), (int)(source.PrimaryVideoStream.Height / scale));
|
var outputSize = new Size((int)(source.PrimaryVideoStream!.Width / scale), (int)(source.PrimaryVideoStream.Height / scale));
|
||||||
|
|
||||||
if (outputSize.Width % 2 != 0)
|
if (outputSize.Width % 2 != 0)
|
||||||
|
{
|
||||||
outputSize.Width += 1;
|
outputSize.Width += 1;
|
||||||
|
}
|
||||||
|
|
||||||
return format.Name switch
|
return format.Name switch
|
||||||
{
|
{
|
||||||
|
|
@ -237,35 +206,6 @@ namespace FFMpegCore
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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 imageFile = Image.FromFile(image))
|
|
||||||
{
|
|
||||||
FFMpegHelper.ConversionSizeExceptionCheck(imageFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
return FFMpegArguments
|
|
||||||
.FromFileInput(image, false, options => options
|
|
||||||
.Loop(1)
|
|
||||||
.ForceFormat("image2"))
|
|
||||||
.AddFileInput(audio)
|
|
||||||
.OutputToFile(output, true, options => options
|
|
||||||
.ForcePixelFormat("yuv420p")
|
|
||||||
.WithVideoCodec(VideoCodec.LibX264)
|
|
||||||
.WithConstantRateFactor(21)
|
|
||||||
.WithAudioBitrate(AudioQuality.Normal)
|
|
||||||
.UsingShortest())
|
|
||||||
.ProcessSynchronously();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Joins a list of video files.
|
/// Joins a list of video files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -299,44 +239,6 @@ namespace FFMpegCore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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);
|
|
||||||
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>
|
/// <summary>
|
||||||
/// Records M3U8 streams to the specified output.
|
/// Records M3U8 streams to the specified output.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -348,7 +250,9 @@ namespace FFMpegCore
|
||||||
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
|
||||||
|
|
||||||
if (uri.Scheme != "http" && uri.Scheme != "https")
|
if (uri.Scheme != "http" && uri.Scheme != "https")
|
||||||
|
{
|
||||||
throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream.");
|
throw new ArgumentException($"Uri: {uri.AbsoluteUri}, does not point to a valid http(s) stream.");
|
||||||
|
}
|
||||||
|
|
||||||
return FFMpegArguments
|
return FFMpegArguments
|
||||||
.FromUrlInput(uri)
|
.FromUrlInput(uri)
|
||||||
|
|
@ -428,12 +332,16 @@ namespace FFMpegCore
|
||||||
processArguments.OutputDataReceived += (e, data) =>
|
processArguments.OutputDataReceived += (e, data) =>
|
||||||
{
|
{
|
||||||
if (PixelFormat.TryParse(data, out var format))
|
if (PixelFormat.TryParse(data, out var format))
|
||||||
|
{
|
||||||
list.Add(format);
|
list.Add(format);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = processArguments.StartAndWaitForExit();
|
var result = processArguments.StartAndWaitForExit();
|
||||||
if (result.ExitCode != 0)
|
if (result.ExitCode != 0)
|
||||||
|
{
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
|
||||||
|
}
|
||||||
|
|
||||||
return list.AsReadOnly();
|
return list.AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
@ -441,25 +349,33 @@ namespace FFMpegCore
|
||||||
public static IReadOnlyList<PixelFormat> GetPixelFormats()
|
public static IReadOnlyList<PixelFormat> GetPixelFormats()
|
||||||
{
|
{
|
||||||
if (!GlobalFFOptions.Current.UseCache)
|
if (!GlobalFFOptions.Current.UseCache)
|
||||||
|
{
|
||||||
return GetPixelFormatsInternal();
|
return GetPixelFormatsInternal();
|
||||||
|
}
|
||||||
|
|
||||||
return FFMpegCache.PixelFormats.Values.ToList().AsReadOnly();
|
return FFMpegCache.PixelFormats.Values.ToList().AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryGetPixelFormat(string name, out PixelFormat fmt)
|
public static bool TryGetPixelFormat(string name, out PixelFormat format)
|
||||||
{
|
{
|
||||||
if (!GlobalFFOptions.Current.UseCache)
|
if (!GlobalFFOptions.Current.UseCache)
|
||||||
{
|
{
|
||||||
fmt = GetPixelFormatsInternal().FirstOrDefault(x => x.Name == name.ToLowerInvariant().Trim());
|
format = GetPixelFormatsInternal().FirstOrDefault(x => x.Name == name.ToLowerInvariant().Trim());
|
||||||
return fmt != null;
|
return format != null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return FFMpegCache.PixelFormats.TryGetValue(name, out fmt);
|
{
|
||||||
|
return FFMpegCache.PixelFormats.TryGetValue(name, out format);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PixelFormat GetPixelFormat(string name)
|
public static PixelFormat GetPixelFormat(string name)
|
||||||
{
|
{
|
||||||
if (TryGetPixelFormat(name, out var fmt))
|
if (TryGetPixelFormat(name, out var fmt))
|
||||||
|
{
|
||||||
return fmt;
|
return fmt;
|
||||||
|
}
|
||||||
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Pixel format \"{name}\" not supported");
|
throw new FFMpegException(FFMpegExceptionType.Operation, $"Pixel format \"{name}\" not supported");
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -474,15 +390,24 @@ namespace FFMpegCore
|
||||||
processArguments.OutputDataReceived += (e, data) =>
|
processArguments.OutputDataReceived += (e, data) =>
|
||||||
{
|
{
|
||||||
var codec = parser(data);
|
var codec = parser(data);
|
||||||
if(codec != null)
|
if (codec != null)
|
||||||
|
{
|
||||||
if (codecs.TryGetValue(codec.Name, out var parentCodec))
|
if (codecs.TryGetValue(codec.Name, out var parentCodec))
|
||||||
|
{
|
||||||
parentCodec.Merge(codec);
|
parentCodec.Merge(codec);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
codecs.Add(codec.Name, codec);
|
codecs.Add(codec.Name, codec);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = processArguments.StartAndWaitForExit();
|
var result = processArguments.StartAndWaitForExit();
|
||||||
if (result.ExitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
|
if (result.ExitCode != 0)
|
||||||
|
{
|
||||||
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static Dictionary<string, Codec> GetCodecsInternal()
|
internal static Dictionary<string, Codec> GetCodecsInternal()
|
||||||
|
|
@ -491,19 +416,28 @@ namespace FFMpegCore
|
||||||
ParsePartOfCodecs(res, "-codecs", (s) =>
|
ParsePartOfCodecs(res, "-codecs", (s) =>
|
||||||
{
|
{
|
||||||
if (Codec.TryParseFromCodecs(s, out var codec))
|
if (Codec.TryParseFromCodecs(s, out var codec))
|
||||||
|
{
|
||||||
return codec;
|
return codec;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
ParsePartOfCodecs(res, "-encoders", (s) =>
|
ParsePartOfCodecs(res, "-encoders", (s) =>
|
||||||
{
|
{
|
||||||
if (Codec.TryParseFromEncodersDecoders(s, out var codec, true))
|
if (Codec.TryParseFromEncodersDecoders(s, out var codec, true))
|
||||||
|
{
|
||||||
return codec;
|
return codec;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
ParsePartOfCodecs(res, "-decoders", (s) =>
|
ParsePartOfCodecs(res, "-decoders", (s) =>
|
||||||
{
|
{
|
||||||
if (Codec.TryParseFromEncodersDecoders(s, out var codec, false))
|
if (Codec.TryParseFromEncodersDecoders(s, out var codec, false))
|
||||||
|
{
|
||||||
return codec;
|
return codec;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -513,15 +447,21 @@ namespace FFMpegCore
|
||||||
public static IReadOnlyList<Codec> GetCodecs()
|
public static IReadOnlyList<Codec> GetCodecs()
|
||||||
{
|
{
|
||||||
if (!GlobalFFOptions.Current.UseCache)
|
if (!GlobalFFOptions.Current.UseCache)
|
||||||
|
{
|
||||||
return GetCodecsInternal().Values.ToList().AsReadOnly();
|
return GetCodecsInternal().Values.ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
return FFMpegCache.Codecs.Values.ToList().AsReadOnly();
|
return FFMpegCache.Codecs.Values.ToList().AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyList<Codec> GetCodecs(CodecType type)
|
public static IReadOnlyList<Codec> GetCodecs(CodecType type)
|
||||||
{
|
{
|
||||||
if (!GlobalFFOptions.Current.UseCache)
|
if (!GlobalFFOptions.Current.UseCache)
|
||||||
|
{
|
||||||
return GetCodecsInternal().Values.Where(x => x.Type == type).ToList().AsReadOnly();
|
return GetCodecsInternal().Values.Where(x => x.Type == type).ToList().AsReadOnly();
|
||||||
return FFMpegCache.Codecs.Values.Where(x=>x.Type == type).ToList().AsReadOnly();
|
}
|
||||||
|
|
||||||
|
return FFMpegCache.Codecs.Values.Where(x => x.Type == type).ToList().AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyList<Codec> GetVideoCodecs() => GetCodecs(CodecType.Video);
|
public static IReadOnlyList<Codec> GetVideoCodecs() => GetCodecs(CodecType.Video);
|
||||||
|
|
@ -537,13 +477,18 @@ namespace FFMpegCore
|
||||||
return codec != null;
|
return codec != null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
return FFMpegCache.Codecs.TryGetValue(name, out codec);
|
return FFMpegCache.Codecs.TryGetValue(name, out codec);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Codec GetCodec(string name)
|
public static Codec GetCodec(string name)
|
||||||
{
|
{
|
||||||
if (TryGetCodec(name, out var codec) && codec != null)
|
if (TryGetCodec(name, out var codec) && codec != null)
|
||||||
|
{
|
||||||
return codec;
|
return codec;
|
||||||
|
}
|
||||||
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{name}\" not supported");
|
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{name}\" not supported");
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -558,11 +503,16 @@ namespace FFMpegCore
|
||||||
instance.OutputDataReceived += (e, data) =>
|
instance.OutputDataReceived += (e, data) =>
|
||||||
{
|
{
|
||||||
if (ContainerFormat.TryParse(data, out var fmt))
|
if (ContainerFormat.TryParse(data, out var fmt))
|
||||||
|
{
|
||||||
list.Add(fmt);
|
list.Add(fmt);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = instance.StartAndWaitForExit();
|
var result = instance.StartAndWaitForExit();
|
||||||
if (result.ExitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
|
if (result.ExitCode != 0)
|
||||||
|
{
|
||||||
|
throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
|
||||||
|
}
|
||||||
|
|
||||||
return list.AsReadOnly();
|
return list.AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
@ -570,7 +520,10 @@ namespace FFMpegCore
|
||||||
public static IReadOnlyList<ContainerFormat> GetContainerFormats()
|
public static IReadOnlyList<ContainerFormat> GetContainerFormats()
|
||||||
{
|
{
|
||||||
if (!GlobalFFOptions.Current.UseCache)
|
if (!GlobalFFOptions.Current.UseCache)
|
||||||
|
{
|
||||||
return GetContainersFormatsInternal();
|
return GetContainersFormatsInternal();
|
||||||
|
}
|
||||||
|
|
||||||
return FFMpegCache.ContainerFormats.Values.ToList().AsReadOnly();
|
return FFMpegCache.ContainerFormats.Values.ToList().AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -582,13 +535,18 @@ namespace FFMpegCore
|
||||||
return fmt != null;
|
return fmt != null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
return FFMpegCache.ContainerFormats.TryGetValue(name, out fmt);
|
return FFMpegCache.ContainerFormats.TryGetValue(name, out fmt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ContainerFormat GetContainerFormat(string name)
|
public static ContainerFormat GetContainerFormat(string name)
|
||||||
{
|
{
|
||||||
if (TryGetContainerFormat(name, out var fmt))
|
if (TryGetContainerFormat(name, out var fmt))
|
||||||
|
{
|
||||||
return fmt;
|
return fmt;
|
||||||
|
}
|
||||||
|
|
||||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Container format \"{name}\" not supported");
|
throw new FFMpegException(FFMpegExceptionType.Operation, $"Container format \"{name}\" not supported");
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
@ -598,7 +556,9 @@ namespace FFMpegCore
|
||||||
foreach (var path in pathList)
|
foreach (var path in pathList)
|
||||||
{
|
{
|
||||||
if (File.Exists(path))
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
File.Delete(path);
|
File.Delete(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
using System.Drawing;
|
||||||
using System.Drawing;
|
|
||||||
|
|
||||||
using FFMpegCore.Arguments;
|
using FFMpegCore.Arguments;
|
||||||
using FFMpegCore.Enums;
|
using FFMpegCore.Enums;
|
||||||
|
|
||||||
|
|
@ -19,8 +17,6 @@ namespace FFMpegCore
|
||||||
public FFMpegArgumentOptions Resize(int width, int height) => WithArgument(new SizeArgument(width, height));
|
public FFMpegArgumentOptions Resize(int width, int height) => WithArgument(new SizeArgument(width, height));
|
||||||
public FFMpegArgumentOptions Resize(Size? size) => WithArgument(new SizeArgument(size));
|
public FFMpegArgumentOptions Resize(Size? size) => WithArgument(new SizeArgument(size));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public FFMpegArgumentOptions WithBitStreamFilter(Channel channel, Filter filter) => WithArgument(new BitStreamFilterArgument(channel, filter));
|
public FFMpegArgumentOptions WithBitStreamFilter(Channel channel, Filter filter) => WithArgument(new BitStreamFilterArgument(channel, filter));
|
||||||
public FFMpegArgumentOptions WithConstantRateFactor(int crf) => WithArgument(new ConstantRateFactorArgument(crf));
|
public FFMpegArgumentOptions WithConstantRateFactor(int crf) => WithArgument(new ConstantRateFactorArgument(crf));
|
||||||
public FFMpegArgumentOptions CopyChannel(Channel channel = Channel.Both) => WithArgument(new CopyArgument(channel));
|
public FFMpegArgumentOptions CopyChannel(Channel channel = Channel.Both) => WithArgument(new CopyArgument(channel));
|
||||||
|
|
@ -60,7 +56,16 @@ namespace FFMpegCore
|
||||||
public FFMpegArgumentOptions Seek(TimeSpan? seekTo) => WithArgument(new SeekArgument(seekTo));
|
public FFMpegArgumentOptions Seek(TimeSpan? seekTo) => WithArgument(new SeekArgument(seekTo));
|
||||||
public FFMpegArgumentOptions Loop(int times) => WithArgument(new LoopArgument(times));
|
public FFMpegArgumentOptions Loop(int times) => WithArgument(new LoopArgument(times));
|
||||||
public FFMpegArgumentOptions OverwriteExisting() => WithArgument(new OverwriteArgument());
|
public FFMpegArgumentOptions OverwriteExisting() => WithArgument(new OverwriteArgument());
|
||||||
public FFMpegArgumentOptions SelectStream(int streamIndex, int inputFileIndex = 0) => WithArgument(new MapStreamArgument(streamIndex, inputFileIndex));
|
public FFMpegArgumentOptions SelectStream(int streamIndex, int inputFileIndex = 0,
|
||||||
|
Channel channel = Channel.All) => WithArgument(new MapStreamArgument(streamIndex, inputFileIndex, channel));
|
||||||
|
public FFMpegArgumentOptions SelectStreams(IEnumerable<int> streamIndices, int inputFileIndex = 0,
|
||||||
|
Channel channel = Channel.All) => streamIndices.Aggregate(this,
|
||||||
|
(options, streamIndex) => options.SelectStream(streamIndex, inputFileIndex, channel));
|
||||||
|
public FFMpegArgumentOptions DeselectStream(int streamIndex, int inputFileIndex = 0,
|
||||||
|
Channel channel = Channel.All) => WithArgument(new MapStreamArgument(streamIndex, inputFileIndex, channel, true));
|
||||||
|
public FFMpegArgumentOptions DeselectStreams(IEnumerable<int> streamIndices, int inputFileIndex = 0,
|
||||||
|
Channel channel = Channel.All) => streamIndices.Aggregate(this,
|
||||||
|
(options, streamIndex) => options.DeselectStream(streamIndex, inputFileIndex, channel));
|
||||||
|
|
||||||
public FFMpegArgumentOptions ForceFormat(ContainerFormat format) => WithArgument(new ForceFormatArgument(format));
|
public FFMpegArgumentOptions ForceFormat(ContainerFormat format) => WithArgument(new ForceFormatArgument(format));
|
||||||
public FFMpegArgumentOptions ForceFormat(string format) => WithArgument(new ForceFormatArgument(format));
|
public FFMpegArgumentOptions ForceFormat(string format) => WithArgument(new ForceFormatArgument(format));
|
||||||
|
|
@ -71,7 +76,6 @@ namespace FFMpegCore
|
||||||
public FFMpegArgumentOptions WithAudibleActivationBytes(string activationBytes) => WithArgument(new AudibleEncryptionKeyArgument(activationBytes));
|
public FFMpegArgumentOptions WithAudibleActivationBytes(string activationBytes) => WithArgument(new AudibleEncryptionKeyArgument(activationBytes));
|
||||||
public FFMpegArgumentOptions WithTagVersion(int id3v2Version = 3) => WithArgument(new ID3V2VersionArgument(id3v2Version));
|
public FFMpegArgumentOptions WithTagVersion(int id3v2Version = 3) => WithArgument(new ID3V2VersionArgument(id3v2Version));
|
||||||
|
|
||||||
|
|
||||||
public FFMpegArgumentOptions WithArgument(IArgument argument)
|
public FFMpegArgumentOptions WithArgument(IArgument argument)
|
||||||
{
|
{
|
||||||
Arguments.Add(argument);
|
Arguments.Add(argument);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
using System;
|
using System.Diagnostics;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using FFMpegCore.Enums;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using FFMpegCore.Exceptions;
|
using FFMpegCore.Exceptions;
|
||||||
using FFMpegCore.Helpers;
|
using FFMpegCore.Helpers;
|
||||||
using Instances;
|
using Instances;
|
||||||
|
|
@ -13,7 +10,7 @@ namespace FFMpegCore
|
||||||
{
|
{
|
||||||
public class FFMpegArgumentProcessor
|
public class FFMpegArgumentProcessor
|
||||||
{
|
{
|
||||||
private static readonly Regex ProgressRegex = new Regex(@"time=(\d\d:\d\d:\d\d.\d\d?)", RegexOptions.Compiled);
|
private static readonly Regex ProgressRegex = new(@"time=(\d\d:\d\d:\d\d.\d\d?)", RegexOptions.Compiled);
|
||||||
private readonly List<Action<FFOptions>> _configurations;
|
private readonly List<Action<FFOptions>> _configurations;
|
||||||
private readonly FFMpegArguments _ffMpegArguments;
|
private readonly FFMpegArguments _ffMpegArguments;
|
||||||
private Action<double>? _onPercentageProgress;
|
private Action<double>? _onPercentageProgress;
|
||||||
|
|
@ -21,6 +18,7 @@ namespace FFMpegCore
|
||||||
private Action<string>? _onOutput;
|
private Action<string>? _onOutput;
|
||||||
private Action<string>? _onError;
|
private Action<string>? _onError;
|
||||||
private TimeSpan? _totalTimespan;
|
private TimeSpan? _totalTimespan;
|
||||||
|
private FFMpegLogLevel? _logLevel;
|
||||||
|
|
||||||
internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments)
|
internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments)
|
||||||
{
|
{
|
||||||
|
|
@ -83,12 +81,23 @@ namespace FFMpegCore
|
||||||
_configurations.Add(configureOptions);
|
_configurations.Add(configureOptions);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the log level of this process. Overides the <see cref="FFMpegLogLevel"/>
|
||||||
|
/// that is set in the <see cref="FFOptions"/> for this specific process.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logLevel">The log level of the ffmpeg execution.</param>
|
||||||
|
public FFMpegArgumentProcessor WithLogLevel(FFMpegLogLevel logLevel)
|
||||||
|
{
|
||||||
|
_logLevel = logLevel;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
|
public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
|
||||||
{
|
{
|
||||||
var options = GetConfiguredOptions(ffMpegOptions);
|
var options = GetConfiguredOptions(ffMpegOptions);
|
||||||
var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource);
|
var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource);
|
||||||
|
|
||||||
|
|
||||||
IProcessResult? processResult = null;
|
IProcessResult? processResult = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -97,7 +106,9 @@ namespace FFMpegCore
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
if (throwOnError)
|
if (throwOnError)
|
||||||
|
{
|
||||||
throw;
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
|
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
|
||||||
|
|
@ -116,7 +127,9 @@ namespace FFMpegCore
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
if (throwOnError)
|
if (throwOnError)
|
||||||
|
{
|
||||||
throw;
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
|
return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty<string>());
|
||||||
|
|
@ -141,6 +154,7 @@ namespace FFMpegCore
|
||||||
instance.Kill();
|
instance.Kill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CancelEvent += OnCancelEvent;
|
CancelEvent += OnCancelEvent;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -168,10 +182,15 @@ namespace FFMpegCore
|
||||||
private bool HandleCompletion(bool throwOnError, int exitCode, IReadOnlyList<string> errorData)
|
private bool HandleCompletion(bool throwOnError, int exitCode, IReadOnlyList<string> errorData)
|
||||||
{
|
{
|
||||||
if (throwOnError && exitCode != 0)
|
if (throwOnError && exitCode != 0)
|
||||||
|
{
|
||||||
throw new FFMpegException(FFMpegExceptionType.Process, $"ffmpeg exited with non-zero exit-code ({exitCode} - {string.Join("\n", errorData)})", null, string.Join("\n", errorData));
|
throw new FFMpegException(FFMpegExceptionType.Process, $"ffmpeg exited with non-zero exit-code ({exitCode} - {string.Join("\n", errorData)})", null, string.Join("\n", errorData));
|
||||||
|
}
|
||||||
|
|
||||||
_onPercentageProgress?.Invoke(100.0);
|
_onPercentageProgress?.Invoke(100.0);
|
||||||
if (_totalTimespan.HasValue) _onTimeProgress?.Invoke(_totalTimespan.Value);
|
if (_totalTimespan.HasValue)
|
||||||
|
{
|
||||||
|
_onTimeProgress?.Invoke(_totalTimespan.Value);
|
||||||
|
}
|
||||||
|
|
||||||
return exitCode == 0;
|
return exitCode == 0;
|
||||||
}
|
}
|
||||||
|
|
@ -193,10 +212,27 @@ namespace FFMpegCore
|
||||||
{
|
{
|
||||||
FFMpegHelper.RootExceptionCheck();
|
FFMpegHelper.RootExceptionCheck();
|
||||||
FFMpegHelper.VerifyFFMpegExists(ffOptions);
|
FFMpegHelper.VerifyFFMpegExists(ffOptions);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var normalizedLogLevel = _logLevel.ToString()
|
||||||
|
.ToLower();
|
||||||
|
arguments += $" -v {normalizedLogLevel}";
|
||||||
|
}
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo
|
var startInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = GlobalFFOptions.GetFFMpegBinaryPath(ffOptions),
|
FileName = GlobalFFOptions.GetFFMpegBinaryPath(ffOptions),
|
||||||
Arguments = _ffMpegArguments.Text,
|
Arguments = arguments,
|
||||||
StandardOutputEncoding = ffOptions.Encoding,
|
StandardOutputEncoding = ffOptions.Encoding,
|
||||||
StandardErrorEncoding = ffOptions.Encoding,
|
StandardErrorEncoding = ffOptions.Encoding,
|
||||||
WorkingDirectory = ffOptions.WorkingDirectory
|
WorkingDirectory = ffOptions.WorkingDirectory
|
||||||
|
|
@ -204,11 +240,15 @@ namespace FFMpegCore
|
||||||
var processArguments = new ProcessArguments(startInfo);
|
var processArguments = new ProcessArguments(startInfo);
|
||||||
cancellationTokenSource = new CancellationTokenSource();
|
cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
if (_onOutput != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null))
|
if (_onOutput != null)
|
||||||
|
{
|
||||||
processArguments.OutputDataReceived += OutputData;
|
processArguments.OutputDataReceived += OutputData;
|
||||||
|
}
|
||||||
|
|
||||||
if (_onError != null)
|
if (_onError != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null))
|
||||||
|
{
|
||||||
processArguments.ErrorDataReceived += ErrorData;
|
processArguments.ErrorDataReceived += ErrorData;
|
||||||
|
}
|
||||||
|
|
||||||
return processArguments;
|
return processArguments;
|
||||||
}
|
}
|
||||||
|
|
@ -216,22 +256,29 @@ namespace FFMpegCore
|
||||||
private void ErrorData(object sender, string msg)
|
private void ErrorData(object sender, string msg)
|
||||||
{
|
{
|
||||||
_onError?.Invoke(msg);
|
_onError?.Invoke(msg);
|
||||||
|
|
||||||
|
var match = ProgressRegex.Match(msg);
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
|
||||||
|
_onTimeProgress?.Invoke(processed);
|
||||||
|
|
||||||
|
if (_onPercentageProgress == null || _totalTimespan == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var percentage = Math.Round(processed.TotalSeconds / _totalTimespan.Value.TotalSeconds * 100, 2);
|
||||||
|
_onPercentageProgress(percentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OutputData(object sender, string msg)
|
private void OutputData(object sender, string msg)
|
||||||
{
|
{
|
||||||
Debug.WriteLine(msg);
|
Debug.WriteLine(msg);
|
||||||
_onOutput?.Invoke(msg);
|
_onOutput?.Invoke(msg);
|
||||||
|
|
||||||
var match = ProgressRegex.Match(msg);
|
|
||||||
if (!match.Success) return;
|
|
||||||
|
|
||||||
var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
|
|
||||||
_onTimeProgress?.Invoke(processed);
|
|
||||||
|
|
||||||
if (_onPercentageProgress == null || _totalTimespan == null) return;
|
|
||||||
var percentage = Math.Round(processed.TotalSeconds / _totalTimespan.Value.TotalSeconds * 100, 2);
|
|
||||||
_onPercentageProgress(percentage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,4 @@
|
||||||
using System;
|
using FFMpegCore.Arguments;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using FFMpegCore.Arguments;
|
|
||||||
using FFMpegCore.Builders.MetaData;
|
using FFMpegCore.Builders.MetaData;
|
||||||
using FFMpegCore.Pipes;
|
using FFMpegCore.Pipes;
|
||||||
|
|
||||||
|
|
@ -14,7 +6,7 @@ namespace FFMpegCore
|
||||||
{
|
{
|
||||||
public sealed class FFMpegArguments : FFMpegArgumentsBase
|
public sealed class FFMpegArguments : FFMpegArgumentsBase
|
||||||
{
|
{
|
||||||
private readonly FFMpegGlobalArguments _globalArguments = new FFMpegGlobalArguments();
|
private readonly FFMpegGlobalArguments _globalArguments = new();
|
||||||
|
|
||||||
private FFMpegArguments() { }
|
private FFMpegArguments() { }
|
||||||
|
|
||||||
|
|
@ -34,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 FromDeviceInput(string device, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new InputDeviceArgument(device), addArguments);
|
||||||
public static FFMpegArguments FromPipeInput(IPipeSource sourcePipe, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new InputPipeArgument(sourcePipe), addArguments);
|
public static FFMpegArguments FromPipeInput(IPipeSource sourcePipe, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new InputPipeArgument(sourcePipe), addArguments);
|
||||||
|
|
||||||
|
|
||||||
public FFMpegArguments WithGlobalOptions(Action<FFMpegGlobalArguments> configureOptions)
|
public FFMpegArguments WithGlobalOptions(Action<FFMpegGlobalArguments> configureOptions)
|
||||||
{
|
{
|
||||||
configureOptions(_globalArguments);
|
configureOptions(_globalArguments);
|
||||||
|
|
@ -46,11 +37,11 @@ namespace FFMpegCore
|
||||||
public FFMpegArguments AddFileInput(string filePath, bool verifyExists = true, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputArgument(verifyExists, filePath), addArguments);
|
public FFMpegArguments AddFileInput(string filePath, bool verifyExists = true, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputArgument(verifyExists, filePath), addArguments);
|
||||||
public FFMpegArguments AddFileInput(FileInfo fileInfo, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputArgument(fileInfo.FullName, false), addArguments);
|
public FFMpegArguments AddFileInput(FileInfo fileInfo, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputArgument(fileInfo.FullName, false), addArguments);
|
||||||
public FFMpegArguments AddUrlInput(Uri uri, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments);
|
public FFMpegArguments AddUrlInput(Uri uri, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments);
|
||||||
|
public FFMpegArguments AddDeviceInput(string device, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputDeviceArgument(device), addArguments);
|
||||||
public FFMpegArguments AddPipeInput(IPipeSource sourcePipe, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputPipeArgument(sourcePipe), addArguments);
|
public FFMpegArguments AddPipeInput(IPipeSource sourcePipe, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputPipeArgument(sourcePipe), addArguments);
|
||||||
public FFMpegArguments AddMetaData(string content, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new MetaDataArgument(content), addArguments);
|
public FFMpegArguments AddMetaData(string content, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new MetaDataArgument(content), addArguments);
|
||||||
public FFMpegArguments AddMetaData(IReadOnlyMetaData metaData, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new MetaDataArgument(MetaDataSerializer.Instance.Serialize(metaData)), addArguments);
|
public FFMpegArguments AddMetaData(IReadOnlyMetaData metaData, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new MetaDataArgument(MetaDataSerializer.Instance.Serialize(metaData)), addArguments);
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps the metadata of the given stream
|
/// Maps the metadata of the given stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -83,7 +74,9 @@ namespace FFMpegCore
|
||||||
internal void Pre()
|
internal void Pre()
|
||||||
{
|
{
|
||||||
foreach (var argument in Arguments.OfType<IInputOutputArgument>())
|
foreach (var argument in Arguments.OfType<IInputOutputArgument>())
|
||||||
|
{
|
||||||
argument.Pre();
|
argument.Pre();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
internal async Task During(CancellationToken cancellationToken = default)
|
internal async Task During(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
|
@ -93,7 +86,9 @@ namespace FFMpegCore
|
||||||
internal void Post()
|
internal void Post()
|
||||||
{
|
{
|
||||||
foreach (var argument in Arguments.OfType<IInputOutputArgument>())
|
foreach (var argument in Arguments.OfType<IInputOutputArgument>())
|
||||||
|
{
|
||||||
argument.Post();
|
argument.Post();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using FFMpegCore.Arguments;
|
||||||
using FFMpegCore.Arguments;
|
|
||||||
|
|
||||||
namespace FFMpegCore
|
namespace FFMpegCore
|
||||||
{
|
{
|
||||||
public abstract class FFMpegArgumentsBase
|
public abstract class FFMpegArgumentsBase
|
||||||
{
|
{
|
||||||
internal readonly List<IArgument> Arguments = new List<IArgument>();
|
internal readonly List<IArgument> Arguments = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
using FFMpegCore.Enums;
|
using FFMpegCore.Enums;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace FFMpegCore
|
namespace FFMpegCore
|
||||||
{
|
{
|
||||||
static class FFMpegCache
|
internal static class FFMpegCache
|
||||||
{
|
{
|
||||||
private static readonly object _syncObject = new object();
|
private static readonly object _syncObject = new();
|
||||||
private static Dictionary<string, PixelFormat>? _pixelFormats;
|
private static Dictionary<string, PixelFormat>? _pixelFormats;
|
||||||
private static Dictionary<string, Codec>? _codecs;
|
private static Dictionary<string, Codec>? _codecs;
|
||||||
private static Dictionary<string, ContainerFormat>? _containers;
|
private static Dictionary<string, ContainerFormat>? _containers;
|
||||||
|
|
@ -16,39 +14,54 @@ namespace FFMpegCore
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_pixelFormats == null) //First check not thread safe
|
if (_pixelFormats == null) //First check not thread safe
|
||||||
|
{
|
||||||
lock (_syncObject)
|
lock (_syncObject)
|
||||||
|
{
|
||||||
if (_pixelFormats == null)//Second check thread safe
|
if (_pixelFormats == null)//Second check thread safe
|
||||||
|
{
|
||||||
_pixelFormats = FFMpeg.GetPixelFormatsInternal().ToDictionary(x => x.Name);
|
_pixelFormats = FFMpeg.GetPixelFormatsInternal().ToDictionary(x => x.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return _pixelFormats;
|
return _pixelFormats;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public static IReadOnlyDictionary<string, Codec> Codecs
|
public static IReadOnlyDictionary<string, Codec> Codecs
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_codecs == null) //First check not thread safe
|
if (_codecs == null) //First check not thread safe
|
||||||
|
{
|
||||||
lock (_syncObject)
|
lock (_syncObject)
|
||||||
|
{
|
||||||
if (_codecs == null)//Second check thread safe
|
if (_codecs == null)//Second check thread safe
|
||||||
|
{
|
||||||
_codecs = FFMpeg.GetCodecsInternal();
|
_codecs = FFMpeg.GetCodecsInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return _codecs;
|
return _codecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public static IReadOnlyDictionary<string, ContainerFormat> ContainerFormats
|
public static IReadOnlyDictionary<string, ContainerFormat> ContainerFormats
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_containers == null) //First check not thread safe
|
if (_containers == null) //First check not thread safe
|
||||||
|
{
|
||||||
lock (_syncObject)
|
lock (_syncObject)
|
||||||
|
{
|
||||||
if (_containers == null)//Second check thread safe
|
if (_containers == null)//Second check thread safe
|
||||||
|
{
|
||||||
_containers = FFMpeg.GetContainersFormatsInternal().ToDictionary(x => x.Name);
|
_containers = FFMpeg.GetContainersFormatsInternal().ToDictionary(x => x.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return _containers;
|
return _containers;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,5 @@ namespace FFMpegCore
|
||||||
Arguments.Add(argument);
|
Arguments.Add(argument);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
using System.IO;
|
namespace FFMpegCore.Pipes
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Pipes
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for Audio sample
|
/// Interface for Audio sample
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
using System.IO;
|
namespace FFMpegCore.Pipes
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Pipes
|
|
||||||
{
|
{
|
||||||
public interface IPipeSink
|
public interface IPipeSink
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
using System.IO;
|
namespace FFMpegCore.Pipes
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Pipes
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for ffmpeg pipe source data IO
|
/// Interface for ffmpeg pipe source data IO
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
using System.IO;
|
namespace FFMpegCore.Pipes
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Pipes
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for Video frame
|
/// Interface for Video frame
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
using System;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Pipes
|
namespace FFMpegCore.Pipes
|
||||||
{
|
{
|
||||||
static class PipeHelpers
|
internal static class PipeHelpers
|
||||||
{
|
{
|
||||||
public static string GetUnqiuePipeName() => $"FFMpegCore_{Guid.NewGuid()}";
|
public static string GetUnqiuePipeName() => $"FFMpegCore_{Guid.NewGuid().ToString("N").Substring(0, 5)}";
|
||||||
|
|
||||||
public static string GetPipePath(string pipeName)
|
public static string GetPipePath(string pipeName)
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
return $@"\\.\pipe\{pipeName}";
|
return $@"\\.\pipe\{pipeName}";
|
||||||
else
|
}
|
||||||
return $"unix:/tmp/CoreFxPipe_{pipeName}";
|
|
||||||
|
return $"unix:{Path.Combine(Path.GetTempPath(), $"CoreFxPipe_{pipeName}")}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
using System.Collections.Generic;
|
namespace FFMpegCore.Pipes
|
||||||
using System.IO;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FFMpegCore.Pipes
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implementation of <see cref="IPipeSource"/> for a raw audio stream that is gathered from <see cref="IEnumerator{IAudioFrame}"/>.
|
/// Implementation of <see cref="IPipeSource"/> for a raw audio stream that is gathered from <see cref="IEnumerator{IAudioFrame}"/>.
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue