* 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:
Malte Rosenbjerg 2023-02-05 00:29:37 +01:00 committed by GitHub
parent 9efd5c5d4f
commit 278ab4c7b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
144 changed files with 2083 additions and 1203 deletions

275
.editorconfig Normal file
View file

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

View file

@ -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

View file

@ -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
View 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>

View file

@ -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>

View file

@ -1,11 +1,8 @@
using System; using System.Drawing;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using FFMpegCore; using FFMpegCore;
using FFMpegCore.Enums; using FFMpegCore.Enums;
using FFMpegCore.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 @@
{ {
// 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 @@ await FFMpegArguments
} }
{ {
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")
);
} }
{ {
@ -84,16 +77,18 @@ await FFMpegArguments
var inputImagePath = "/path/to/input/image"; 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
} }
@ -131,4 +126,4 @@ await FFMpegArguments
.Configure(options => options.WorkingDirectory = "./CurrentRunWorkingDir") .Configure(options => options.WorkingDirectory = "./CurrentRunWorkingDir")
.Configure(options => options.TemporaryFilesFolder = "./CurrentRunTmpFolder") .Configure(options => options.TemporaryFilesFolder = "./CurrentRunTmpFolder")
.ProcessAsynchronously(); .ProcessAsynchronously();
} }

View file

@ -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,8 +14,11 @@ public static bool AddAudio(this Image poster, string audio, string output)
} }
finally finally
{ {
if (File.Exists(destination)) File.Delete(destination); if (File.Exists(destination))
{
File.Delete(destination);
}
} }
} }
} }
} }

View file

@ -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
{ {

View file

@ -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>

View 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);
}
}
}

View file

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

View file

@ -4,25 +4,20 @@
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
{ {
[TestClass] [TestClass]
public class AudioTest public class AudioTest
{ {
[TestMethod] [TestMethod]
public void Audio_Remove() public void Audio_Remove()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
FFMpeg.Mute(TestResources.Mp4Video, outputFile); FFMpeg.Mute(TestResources.Mp4Video, outputFile);
var analysis = FFProbe.Analyse(outputFile); var analysis = FFProbe.Analyse(outputFile);
Assert.IsTrue(analysis.VideoStreams.Any()); Assert.IsTrue(analysis.VideoStreams.Any());
Assert.IsTrue(!analysis.AudioStreams.Any()); Assert.IsTrue(!analysis.AudioStreams.Any());
} }
@ -31,10 +26,10 @@ public void Audio_Remove()
public void Audio_Save() public void Audio_Save()
{ {
using var outputFile = new TemporaryFile("out.mp3"); using var outputFile = new TemporaryFile("out.mp3");
FFMpeg.ExtractAudio(TestResources.Mp4Video, outputFile); FFMpeg.ExtractAudio(TestResources.Mp4Video, outputFile);
var analysis = FFProbe.Analyse(outputFile); var analysis = FFProbe.Analyse(outputFile);
Assert.IsTrue(!analysis.VideoStreams.Any()); Assert.IsTrue(!analysis.VideoStreams.Any());
Assert.IsTrue(analysis.AudioStreams.Any()); Assert.IsTrue(analysis.AudioStreams.Any());
} }
@ -48,17 +43,17 @@ await FFMpegArguments
.OutputToPipe(new StreamPipeSink(memoryStream), options => options.ForceFormat("mp3")) .OutputToPipe(new StreamPipeSink(memoryStream), options => options.ForceFormat("mp3"))
.ProcessAsynchronously(); .ProcessAsynchronously();
} }
[TestMethod] [TestMethod]
public void Audio_Add() public void Audio_Add()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
var success = FFMpeg.ReplaceAudio(TestResources.Mp4WithoutAudio, TestResources.Mp3Audio, outputFile); var success = FFMpeg.ReplaceAudio(TestResources.Mp4WithoutAudio, TestResources.Mp3Audio, outputFile);
var videoAnalysis = FFProbe.Analyse(TestResources.Mp4WithoutAudio); var videoAnalysis = FFProbe.Analyse(TestResources.Mp4WithoutAudio);
var audioAnalysis = FFProbe.Analyse(TestResources.Mp3Audio); var audioAnalysis = FFProbe.Analyse(TestResources.Mp3Audio);
var outputAnalysis = FFProbe.Analyse(outputFile); var outputAnalysis = FFProbe.Analyse(outputFile);
Assert.IsTrue(success); Assert.IsTrue(success);
Assert.AreEqual(Math.Max(videoAnalysis.Duration.TotalSeconds, audioAnalysis.Duration.TotalSeconds), outputAnalysis.Duration.TotalSeconds, 0.15); Assert.AreEqual(Math.Max(videoAnalysis.Duration.TotalSeconds, audioAnalysis.Duration.TotalSeconds), outputAnalysis.Duration.TotalSeconds, 0.15);
Assert.IsTrue(File.Exists(outputFile)); Assert.IsTrue(File.Exists(outputFile));
@ -239,7 +234,7 @@ public void Audio_Pan_ToMono()
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 @@ public void Audio_Pan_ToMonoNoDefinitions()
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)]
@ -330,4 +325,4 @@ public void Audio_DynamicNormalizer_FilterWindow(int filterWindow)
.ProcessSynchronously()); .ProcessSynchronously());
} }
} }
} }

View file

@ -1,7 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Reflection;
using FluentAssertions;
using System.Reflection;
using FFMpegCore.Arguments; using FFMpegCore.Arguments;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FFMpegCore.Test namespace FFMpegCore.Test
{ {
@ -13,14 +13,13 @@ public void TestInitialize()
{ {
// 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 @@ public void Processor_SessionOptions_GetUsed()
options.WorkingDirectory.Should().Be(sessionWorkingDir); options.WorkingDirectory.Should().Be(sessionWorkingDir);
} }
[TestMethod] [TestMethod]
public void Processor_Options_CanBeOverridden_And_Configured() public void Processor_Options_CanBeOverridden_And_Configured()
{ {
var globalConfig = "Whatever"; var globalConfig = "Whatever";
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig }); GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig });
var processor = CreateArgumentProcessor(); var processor = CreateArgumentProcessor();
var sessionTempDir = "./CurrentRunWorkingDir"; var sessionTempDir = "./CurrentRunWorkingDir";
@ -65,14 +62,9 @@ public void Processor_Options_CanBeOverridden_And_Configured()
options.BinaryFolder.Should().NotBeEquivalentTo(globalConfig); options.BinaryFolder.Should().NotBeEquivalentTo(globalConfig);
} }
[TestMethod] [TestMethod]
public void Options_Global_And_Session_Options_Can_Differ() public void Options_Global_And_Session_Options_Can_Differ()
{ {
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 @@ FFMpegArgumentProcessor CreateArgumentProcessor() => FFMpegArguments
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 @@ public void Audible_Aaxc_Test()
arg.Text.Should().Be($"-audible_key 123 -audible_iv 456"); arg.Text.Should().Be($"-audible_key 123 -audible_iv 456");
} }
[TestMethod] [TestMethod]
public void Audible_Aax_Test() public void Audible_Aax_Test()
{ {
@ -110,4 +100,4 @@ public void Audible_Aax_Test()
arg.Text.Should().Be($"-activation_bytes 62689101"); arg.Text.Should().Be($"-activation_bytes 62689101");
} }
} }
} }

View file

@ -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>

View file

@ -1,6 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.IO;
namespace FFMpegCore.Test namespace FFMpegCore.Test
{ {
@ -23,7 +22,7 @@ public void Options_Defaults_Configured()
public void Options_Loaded_From_File() public void Options_Loaded_From_File()
{ {
Assert.AreEqual( Assert.AreEqual(
GlobalFFOptions.Current.BinaryFolder, GlobalFFOptions.Current.BinaryFolder,
JsonConvert.DeserializeObject<FFOptions>(File.ReadAllText("ffmpeg.config.json")).BinaryFolder JsonConvert.DeserializeObject<FFOptions>(File.ReadAllText("ffmpeg.config.json")).BinaryFolder
); );
} }
@ -31,7 +30,7 @@ public void Options_Loaded_From_File()
[TestMethod] [TestMethod]
public void Options_Set_Programmatically() public void Options_Set_Programmatically()
{ {
var original = GlobalFFOptions.Current; var original = GlobalFFOptions.Current;
try try
{ {
GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "Whatever" }); GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "Whatever" });

View file

@ -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
@ -24,7 +19,7 @@ public async Task Audio_FromStream_Duration()
public void FrameAnalysis_Sync() public void FrameAnalysis_Sync()
{ {
var frameAnalysis = FFProbe.GetFrames(TestResources.WebmVideo); var frameAnalysis = FFProbe.GetFrames(TestResources.WebmVideo);
Assert.AreEqual(90, frameAnalysis.Frames.Count); Assert.AreEqual(90, frameAnalysis.Frames.Count);
Assert.IsTrue(frameAnalysis.Frames.All(f => f.PixelFormat == "yuv420p")); Assert.IsTrue(frameAnalysis.Frames.All(f => f.PixelFormat == "yuv420p"));
Assert.IsTrue(frameAnalysis.Frames.All(f => f.Height == 360)); Assert.IsTrue(frameAnalysis.Frames.All(f => f.Height == 360));
@ -36,7 +31,7 @@ public void FrameAnalysis_Sync()
public async Task FrameAnalysis_Async() public async Task FrameAnalysis_Async()
{ {
var frameAnalysis = await FFProbe.GetFramesAsync(TestResources.WebmVideo); var frameAnalysis = await FFProbe.GetFramesAsync(TestResources.WebmVideo);
Assert.AreEqual(90, frameAnalysis.Frames.Count); Assert.AreEqual(90, frameAnalysis.Frames.Count);
Assert.IsTrue(frameAnalysis.Frames.All(f => f.PixelFormat == "yuv420p")); Assert.IsTrue(frameAnalysis.Frames.All(f => f.PixelFormat == "yuv420p"));
Assert.IsTrue(frameAnalysis.Frames.All(f => f.Height == 360)); Assert.IsTrue(frameAnalysis.Frames.All(f => f.Height == 360));
@ -55,12 +50,11 @@ public async Task PacketAnalysis_Async()
Assert.AreEqual(1362, packets.Last().Size); Assert.AreEqual(1362, packets.Last().Size);
} }
[TestMethod] [TestMethod]
public void PacketAnalysis_Sync() public void PacketAnalysis_Sync()
{ {
var packets = FFProbe.GetPackets(TestResources.WebmVideo).Packets; var packets = FFProbe.GetPackets(TestResources.WebmVideo).Packets;
Assert.AreEqual(96, packets.Count); Assert.AreEqual(96, packets.Count);
Assert.IsTrue(packets.All(f => f.CodecType == "video")); Assert.IsTrue(packets.All(f => f.CodecType == "video"));
Assert.AreEqual("K_", packets[0].Flags); Assert.AreEqual("K_", packets[0].Flags);
@ -74,9 +68,9 @@ public void PacketAnalysisAudioVideo_Sync()
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 @@ public void MediaAnalysis_ParseDuration(string duration, int expectedDays, int e
{ {
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,19 +93,19 @@ public void MediaAnalysis_ParseDuration(string duration, int expectedDays, int e
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"));
Assert.IsNotNull(fileAnalysis); Assert.IsNotNull(fileAnalysis);
} }
[TestMethod] [TestMethod]
public void Probe_Success() public void Probe_Success()
{ {
var info = FFProbe.Analyse(TestResources.Mp4Video); var info = FFProbe.Analyse(TestResources.Mp4Video);
Assert.AreEqual(3, info.Duration.Seconds); Assert.AreEqual(3, info.Duration.Seconds);
Assert.AreEqual("5.1", info.PrimaryAudioStream!.ChannelLayout); Assert.AreEqual("5.1", info.PrimaryAudioStream!.ChannelLayout);
Assert.AreEqual(6, info.PrimaryAudioStream.Channels); Assert.AreEqual(6, info.PrimaryAudioStream.Channels);
Assert.AreEqual("AAC (Advanced Audio Coding)", info.PrimaryAudioStream.CodecLongName); Assert.AreEqual("AAC (Advanced Audio Coding)", info.PrimaryAudioStream.CodecLongName);
@ -121,10 +115,12 @@ public void Probe_Success()
Assert.AreEqual(48000, info.PrimaryAudioStream.SampleRateHz); Assert.AreEqual(48000, info.PrimaryAudioStream.SampleRateHz);
Assert.AreEqual("mp4a", info.PrimaryAudioStream.CodecTagString); Assert.AreEqual("mp4a", info.PrimaryAudioStream.CodecTagString);
Assert.AreEqual("0x6134706d", info.PrimaryAudioStream.CodecTag); Assert.AreEqual("0x6134706d", info.PrimaryAudioStream.CodecTag);
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);
@ -137,12 +133,25 @@ public void Probe_Success()
Assert.AreEqual("avc1", info.PrimaryVideoStream.CodecTagString); Assert.AreEqual("avc1", info.PrimaryVideoStream.CodecTagString);
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 @@ public void Probe_Success_FromStream()
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 @@ public async Task Probe_Success_Subtitle_Async()
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 @@ public async Task Probe_Success_Disposition_Async()
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);
}
} }
} }

View file

@ -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 @@ public void TestMapMetadata()
.AddMetaData("WhatEver3") .AddMetaData("WhatEver3")
.Text; .Text;
Assert.IsTrue(Regex.IsMatch(text0, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 1"), "map_metadata index is calculated incorrectly."); Assert.IsTrue(Regex.IsMatch(text0, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 1"), "map_metadata index is calculated incorrectly.");
Assert.IsTrue(Regex.IsMatch(text1, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 2"), "map_metadata index is calculated incorrectly."); Assert.IsTrue(Regex.IsMatch(text1, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 2"), "map_metadata index is calculated incorrectly.");
} }

View file

@ -24,7 +24,7 @@ public void PixelFormats_TryGetNotExisting()
{ {
Assert.IsFalse(FFMpeg.TryGetPixelFormat("yuv420pppUnknown", out _)); Assert.IsFalse(FFMpeg.TryGetPixelFormat("yuv420pppUnknown", out _));
} }
[TestMethod] [TestMethod]
public void PixelFormats_GetExisting() public void PixelFormats_GetExisting()
{ {

Binary file not shown.

Binary file not shown.

View file

@ -13,6 +13,7 @@ public enum ImageType
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 class TestResources
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";
} }
} }

Binary file not shown.

Binary file not shown.

View file

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

View file

@ -1,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 @@ public static BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fm
offset = offset * index; offset = offset * index;
for (int y = 0; y < h; y++) for (var y = 0; y < h; y++)
for (int x = 0; x < w; x++) {
for (var x = 0; x < w; x++)
{ {
var xf = x / (float)w; var xf = x / (float)w;
var yf = y / (float)h; var yf = y / (float)h;
@ -41,6 +42,7 @@ public static BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fm
bitmap.SetPixel(x, y, color); bitmap.SetPixel(x, y, color);
} }
}
return new BitmapVideoFrameWrapper(bitmap); return new BitmapVideoFrameWrapper(bitmap);
} }
@ -53,7 +55,7 @@ public static BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fm
// Based on the original implementation by Ken Perlin // Based on the original implementation by Ken Perlin
// http://mrl.nyu.edu/~perlin/noise/ // http://mrl.nyu.edu/~perlin/noise/
// //
static class Perlin private static class Perlin
{ {
#region Noise functions #region Noise functions
@ -126,6 +128,7 @@ public static float Fbm(float x, int octave)
x *= 2.0f; x *= 2.0f;
w *= 0.5f; w *= 0.5f;
} }
return f; return f;
} }
@ -139,6 +142,7 @@ public static float Fbm(Vector2 coord, int octave)
coord *= 2.0f; coord *= 2.0f;
w *= 0.5f; w *= 0.5f;
} }
return f; return f;
} }
@ -157,6 +161,7 @@ public static float Fbm(Vector3 coord, int octave)
coord *= 2.0f; coord *= 2.0f;
w *= 0.5f; w *= 0.5f;
} }
return f; return f;
} }
@ -169,27 +174,27 @@ public static float Fbm(float x, float y, float z, int octave)
#region Private functions #region Private functions
static float Fade(float t) private static float Fade(float t)
{ {
return t * t * t * (t * (t * 6 - 15) + 10); return t * t * t * (t * (t * 6 - 15) + 10);
} }
static float Lerp(float t, float a, float b) private static float Lerp(float t, float a, float b)
{ {
return a + t * (b - a); return a + t * (b - a);
} }
static float Grad(int hash, float x) private static float Grad(int hash, float x)
{ {
return (hash & 1) == 0 ? x : -x; return (hash & 1) == 0 ? x : -x;
} }
static float Grad(int hash, float x, float y) private static float Grad(int hash, float x, float y)
{ {
return ((hash & 1) == 0 ? x : -x) + ((hash & 2) == 0 ? y : -y); return ((hash & 1) == 0 ? x : -x) + ((hash & 2) == 0 ? y : -y);
} }
static float Grad(int hash, float x, float y, float z) private static float Grad(int hash, float x, float y, float z)
{ {
var h = hash & 15; var h = hash & 15;
var u = h < 8 ? x : y; var u = h < 8 ? x : y;
@ -197,7 +202,7 @@ static float Grad(int hash, float x, float y, float z)
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
} }
static int[] perm = { private static readonly int[] perm = {
151,160,137,91,90,15, 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,

View file

@ -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);
}
}

View 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);
}
}

View file

@ -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
{ {
@ -23,7 +19,7 @@ public class VideoTest
public void Video_ToOGV() public void Video_ToOGV()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}");
var success = FFMpegArguments var success = FFMpegArguments
.FromFileInput(TestResources.WebmVideo) .FromFileInput(TestResources.WebmVideo)
.OutputToFile(outputFile, false) .OutputToFile(outputFile, false)
@ -35,7 +31,7 @@ public void Video_ToOGV()
public void Video_ToMP4() public void Video_ToMP4()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var success = FFMpegArguments var success = FFMpegArguments
.FromFileInput(TestResources.WebmVideo) .FromFileInput(TestResources.WebmVideo)
.OutputToFile(outputFile, false) .OutputToFile(outputFile, false)
@ -47,7 +43,7 @@ public void Video_ToMP4()
public void Video_ToMP4_YUV444p() public void Video_ToMP4_YUV444p()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var success = FFMpegArguments var success = FFMpegArguments
.FromFileInput(TestResources.WebmVideo) .FromFileInput(TestResources.WebmVideo)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
@ -63,7 +59,7 @@ public void Video_ToMP4_YUV444p()
public void Video_ToMP4_Args() public void Video_ToMP4_Args()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var success = FFMpegArguments var success = FFMpegArguments
.FromFileInput(TestResources.WebmVideo) .FromFileInput(TestResources.WebmVideo)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
@ -76,7 +72,7 @@ public void Video_ToMP4_Args()
public void Video_ToH265_MKV_Args() public void Video_ToH265_MKV_Args()
{ {
using var outputFile = new TemporaryFile($"out.mkv"); using var outputFile = new TemporaryFile($"out.mkv");
var success = FFMpegArguments var success = FFMpegArguments
.FromFileInput(TestResources.WebmVideo) .FromFileInput(TestResources.WebmVideo)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
@ -85,7 +81,8 @@ public void Video_ToH265_MKV_Args()
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,12 +98,13 @@ public void Video_ToMP4_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat
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}");
var frames = new List<IVideoFrame> var frames = new List<IVideoFrame>
{ {
BitmapSource.CreateVideoFrame(0, System.Drawing.Imaging.PixelFormat.Format24bppRgb, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, System.Drawing.Imaging.PixelFormat.Format24bppRgb, 255, 255, 1, 0),
BitmapSource.CreateVideoFrame(0, System.Drawing.Imaging.PixelFormat.Format24bppRgb, 256, 256, 1, 0) BitmapSource.CreateVideoFrame(0, System.Drawing.Imaging.PixelFormat.Format24bppRgb, 256, 256, 1, 0)
@ -120,8 +118,8 @@ public void Video_ToMP4_Args_Pipe_DifferentImageSizes()
.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 @@ public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async()
.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 @@ public void Video_ToMP4_Args_Pipe_DifferentPixelFormats()
.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}");
@ -184,7 +183,7 @@ public void Video_ToMP4_Args_StreamPipe()
{ {
using var input = File.OpenRead(TestResources.WebmVideo); using var input = File.OpenRead(TestResources.WebmVideo);
using var output = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var output = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var success = FFMpegArguments var success = FFMpegArguments
.FromPipeInput(new StreamPipeSource(input)) .FromPipeInput(new StreamPipeSource(input))
.OutputToFile(output, false, opt => opt .OutputToFile(output, false, opt => opt
@ -207,7 +206,6 @@ await FFMpegArguments
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
public void Video_StreamFile_OutputToMemoryStream() public void Video_StreamFile_OutputToMemoryStream()
{ {
@ -239,7 +237,6 @@ public void Video_ToMP4_Args_StreamOutputPipe_Failure()
}); });
} }
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
public async Task Video_ToMP4_Args_StreamOutputPipe_Async() public async Task Video_ToMP4_Args_StreamOutputPipe_Async()
{ {
@ -260,12 +257,12 @@ public async Task TestDuplicateRun()
.FromFileInput(TestResources.Mp4Video) .FromFileInput(TestResources.Mp4Video)
.OutputToFile("temporary.mp4") .OutputToFile("temporary.mp4")
.ProcessSynchronously(); .ProcessSynchronously();
await FFMpegArguments await FFMpegArguments
.FromFileInput(TestResources.Mp4Video) .FromFileInput(TestResources.Mp4Video)
.OutputToFile("temporary.mp4") .OutputToFile("temporary.mp4")
.ProcessAsynchronously(); .ProcessAsynchronously();
File.Delete("temporary.mp4"); File.Delete("temporary.mp4");
} }
@ -291,7 +288,7 @@ public void TranscodeToMemoryStream_Success()
public void Video_ToTS() public void Video_ToTS()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.MpegTs.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.MpegTs.Extension}");
var success = FFMpegArguments var success = FFMpegArguments
.FromFileInput(TestResources.Mp4Video) .FromFileInput(TestResources.Mp4Video)
.OutputToFile(outputFile, false) .OutputToFile(outputFile, false)
@ -303,7 +300,7 @@ public void Video_ToTS()
public void Video_ToTS_Args() public void Video_ToTS_Args()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.MpegTs.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.MpegTs.Extension}");
var success = FFMpegArguments var success = FFMpegArguments
.FromFileInput(TestResources.Mp4Video) .FromFileInput(TestResources.Mp4Video)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
@ -314,14 +311,15 @@ public void Video_ToTS_Args()
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)
{ {
using var output = new TemporaryFile($"out{VideoType.Ts.Extension}"); using var output = new TemporaryFile($"out{VideoType.Ts.Extension}");
var input = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256)); var input = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256));
var success = await FFMpegArguments var success = await FFMpegArguments
.FromPipeInput(input) .FromPipeInput(input)
.OutputToFile(output, false, opt => opt .OutputToFile(output, false, opt => opt
@ -346,15 +344,16 @@ public async Task Video_ToOGV_Resize()
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}");
var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256)); var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256));
FFMpegArguments FFMpegArguments
.FromPipeInput(videoFramesSource) .FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
@ -371,7 +370,7 @@ public void RawVideoPipeSource_Ogv_Scale(System.Drawing.Imaging.PixelFormat pixe
public void Scale_Mp4_Multithreaded() public void Scale_Mp4_Multithreaded()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var success = FFMpegArguments var success = FFMpegArguments
.FromFileInput(TestResources.Mp4Video) .FromFileInput(TestResources.Mp4Video)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
@ -381,7 +380,8 @@ public void Scale_Mp4_Multithreaded()
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)]
@ -389,7 +389,7 @@ public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixe
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256)); var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256));
var success = FFMpegArguments var success = FFMpegArguments
.FromPipeInput(videoFramesSource) .FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
@ -398,12 +398,13 @@ public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixe
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[TestMethod, Timeout(10000)] [SupportedOSPlatform("windows")]
[WindowsOnlyTestMethod, Timeout(10000)]
public void Video_Snapshot_InMemory() public void Video_Snapshot_InMemory()
{ {
using var bitmap = FFMpegImage.Snapshot(TestResources.Mp4Video);
var input = FFProbe.Analyse(TestResources.Mp4Video); var input = FFProbe.Analyse(TestResources.Mp4Video);
using var bitmap = FFMpeg.Snapshot(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 @@ public void Video_Snapshot_PersistSnapshot()
FFMpeg.Snapshot(TestResources.Mp4Video, outputPath); FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
using var bitmap = Image.FromFile(outputPath); var analysis = FFProbe.Analyse(outputPath);
Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width); Assert.AreEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
Assert.AreEqual(input.PrimaryVideoStream.Height, bitmap.Height); Assert.AreEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png); Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
} }
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
@ -428,13 +429,13 @@ public void Video_Join()
{ {
var inputCopy = new TemporaryFile("copy-input.mp4"); var inputCopy = new TemporaryFile("copy-input.mp4");
File.Copy(TestResources.Mp4Video, inputCopy); File.Copy(TestResources.Mp4Video, inputCopy);
var outputPath = new TemporaryFile("out.mp4"); var outputPath = new TemporaryFile("out.mp4");
var input = FFProbe.Analyse(TestResources.Mp4Video); var input = FFProbe.Analyse(TestResources.Mp4Video);
var success = FFMpeg.Join(outputPath, TestResources.Mp4Video, inputCopy); var success = FFMpeg.Join(outputPath, TestResources.Mp4Video, inputCopy);
Assert.IsTrue(success); Assert.IsTrue(success);
Assert.IsTrue(File.Exists(outputPath)); Assert.IsTrue(File.Exists(outputPath));
var expectedDuration = input.Duration * 2; var expectedDuration = input.Duration * 2;
var result = FFProbe.Analyse(outputPath); var result = FFProbe.Analyse(outputPath);
Assert.AreEqual(expectedDuration.Days, result.Duration.Days); Assert.AreEqual(expectedDuration.Days, result.Duration.Days);
@ -445,28 +446,29 @@ public void Video_Join()
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 @@ public void Video_UpdatesProgress()
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 @@ public void Video_UpdatesProgress()
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)]
@ -528,7 +546,7 @@ public void Video_OutputsData()
{ {
var outputFile = new TemporaryFile("out.mp4"); var outputFile = new TemporaryFile("out.mp4");
var dataReceived = false; var dataReceived = false;
GlobalFFOptions.Configure(opt => opt.Encoding = Encoding.UTF8); GlobalFFOptions.Configure(opt => opt.Encoding = Encoding.UTF8);
var success = FFMpegArguments var success = FFMpegArguments
.FromFileInput(TestResources.Mp4Video) .FromFileInput(TestResources.Mp4Video)
@ -544,7 +562,8 @@ public void Video_OutputsData()
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 @@ public void Video_TranscodeInMemory()
Assert.AreEqual(vi.PrimaryVideoStream.Height, 128); Assert.AreEqual(vi.PrimaryVideoStream.Height, 128);
} }
[TestMethod, Timeout(20000)]
public void Video_TranscodeToMemory()
{
using var memoryStream = new MemoryStream();
FFMpegArguments
.FromFileInput(TestResources.WebmVideo)
.OutputToPipe(new StreamPipeSink(memoryStream), opt => opt
.WithVideoCodec("vp9")
.ForceFormat("webm"))
.ProcessSynchronously();
memoryStream.Position = 0;
var vi = FFProbe.Analyse(memoryStream);
Assert.AreEqual(vi.PrimaryVideoStream!.Width, 640);
Assert.AreEqual(vi.PrimaryVideoStream.Height, 360);
}
[TestMethod, Timeout(10000)] [TestMethod, Timeout(10000)]
public async Task Video_Cancel_Async() public async Task Video_Cancel_Async()
{ {

View file

@ -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

View file

@ -1,3 +1,3 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("FFMpegCore.Test")] [assembly: InternalsVisibleTo("FFMpegCore.Test")]

View file

@ -1,6 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Extend
namespace FFMpegCore.Extend
{ {
internal static class KeyValuePairExtensions internal static class KeyValuePairExtensions
{ {
@ -21,4 +19,4 @@ public static string FormatArgumentPair(this KeyValuePair<string, string> pair,
return $"{key}={value}"; return $"{key}={value}";
} }
} }
} }

View file

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

View file

@ -1,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()
{ {
{ '\\', @"\\" }, { '\\', @"\\" },
{ ':', @"\:" }, { ':', @"\:" },
@ -68,4 +66,4 @@ public static string Replace(this string str, Dictionary<char, string> replaceLi
return parsedString.ToString(); return parsedString.ToString();
} }
} }
} }

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Extend
namespace FFMpegCore.Extend
{ {
public static class UriExtensions public static class UriExtensions
{ {
@ -9,4 +7,4 @@ public static bool SaveStream(this Uri uri, string output)
return FFMpeg.SaveM3U8Stream(uri, output); return FFMpeg.SaveM3U8Stream(uri, output);
} }
} }
} }

View file

@ -3,12 +3,11 @@
public class AudibleEncryptionKeyArgument : IArgument public class AudibleEncryptionKeyArgument : IArgument
{ {
private readonly bool _aaxcMode; private readonly bool _aaxcMode;
private readonly string _key;
private readonly string _iv;
private readonly string _activationBytes; private readonly string? _key;
private readonly string? _iv;
private readonly string? _activationBytes;
public AudibleEncryptionKeyArgument(string activationBytes) public AudibleEncryptionKeyArgument(string activationBytes)
{ {

View file

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

View file

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

View file

@ -1,6 +1,4 @@
using System.Collections.Generic; using FFMpegCore.Exceptions;
using System.Linq;
using FFMpegCore.Exceptions;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
@ -18,7 +16,9 @@ public AudioFiltersArgument(AudioFilterOptions options)
private string GetText() private string GetText()
{ {
if (!Options.Arguments.Any()) if (!Options.Arguments.Any())
{
throw new FFMpegArgumentException("No audio-filter arguments provided"); throw new FFMpegArgumentException("No audio-filter arguments provided");
}
var arguments = Options.Arguments var arguments = Options.Arguments
.Where(arg => !string.IsNullOrEmpty(arg.Value)) .Where(arg => !string.IsNullOrEmpty(arg.Value))
@ -34,13 +34,13 @@ private string GetText()
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 @@ public AudioFilterOptions DynamicNormalizer(int frameLength = 500, int filterWin
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)
{ {
@ -57,4 +68,4 @@ private AudioFilterOptions WithArgument(IAudioFilterArgument argument)
return this; return this;
} }
} }
} }

View 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}"));
}
}

View file

@ -13,4 +13,4 @@ public AudioSamplingRateArgument(int samplingRate = 48000)
public string Text => $"-ar {SamplingRate}"; public string Text => $"-ar {SamplingRate}";
} }
} }

View 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}";
}
}
}

View 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}";
}
}
}

View file

@ -1,8 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Arguments
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
@ -20,7 +16,7 @@ public ConcatArgument(IEnumerable<string> values)
public void Pre() { } public void Pre() { }
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
public void Post() { } public void Post() { }
public string Text => $"-i \"concat:{string.Join(@"|", Values)}\""; public string Text => $"-i \"concat:{string.Join(@"|", Values)}\"";
} }
} }

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Arguments
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Constant Rate Factor (CRF) argument /// Constant Rate Factor (CRF) argument
@ -21,4 +19,4 @@ public ConstantRateFactorArgument(int crf)
public string Text => $"-crf {Crf}"; public string Text => $"-crf {Crf}";
} }
} }

View file

@ -16,9 +16,8 @@ public CopyArgument(Channel channel = Channel.Both)
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"
}; };
} }
} }

View file

@ -11,4 +11,4 @@ public CustomArgument(string argument)
public string Text => Argument ?? string.Empty; public string Text => Argument ?? string.Empty;
} }
} }

View file

@ -1,11 +1,4 @@
using System; namespace FFMpegCore.Arguments
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Represents parameter of concat argument /// Represents parameter of concat argument
@ -26,7 +19,7 @@ public DemuxConcatArgument(IEnumerable<string> values)
/// <param name="value"></param> /// <param name="value"></param>
/// <returns></returns> /// <returns></returns>
private string Escape(string value) => value.Replace("'", @"'\''"); private string Escape(string value) => value.Replace("'", @"'\''");
private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"concat_{Guid.NewGuid()}.txt"); private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"concat_{Guid.NewGuid()}.txt");
public void Pre() => File.WriteAllLines(_tempFileName, Values); public void Pre() => File.WriteAllLines(_tempFileName, Values);

View file

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

View file

@ -1,7 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Arguments
using System.Linq;
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Drawtext video filter argument /// Drawtext video filter argument
@ -9,16 +6,16 @@ namespace FFMpegCore.Arguments
public class DrawTextArgument : IVideoFilterArgument public class DrawTextArgument : IVideoFilterArgument
{ {
public readonly DrawTextOptions Options; public readonly DrawTextOptions Options;
public DrawTextArgument(DrawTextOptions options) public DrawTextArgument(DrawTextOptions options)
{ {
Options = options; Options = options;
} }
public string Key { get; } = "drawtext"; public string Key { get; } = "drawtext";
public string Value => Options.TextInternal; public string Value => Options.TextInternal;
} }
public class DrawTextOptions public class DrawTextOptions
{ {
public readonly string Text; public readonly string Text;
@ -34,7 +31,7 @@ public static DrawTextOptions Create(string text, string font, params (string ke
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)
{ {
@ -59,4 +56,4 @@ public DrawTextOptions WithParameter(string key, string value)
return this; return this;
} }
} }
} }

View file

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

View file

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

View file

@ -7,4 +7,4 @@ public class FaststartArgument : IArgument
{ {
public string Text => "-movflags faststart"; public string Text => "-movflags faststart";
} }
} }

View file

@ -11,8 +11,6 @@ public HardwareAccelerationArgument(HardwareAccelerationDevice hardwareAccelerat
HardwareAccelerationDevice = hardwareAccelerationDevice; HardwareAccelerationDevice = hardwareAccelerationDevice;
} }
public string Text => HardwareAccelerationDevice != HardwareAccelerationDevice.Auto public string Text => $"-hwaccel {HardwareAccelerationDevice.ToString().ToLower()}";
? $"-hwaccel {HardwareAccelerationDevice.ToString().ToLower()}"
: "-hwaccel";
} }
} }

View 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}"));
}
}

View file

@ -7,4 +7,4 @@ public interface IArgument
/// </summary> /// </summary>
string Text { get; } string Text { get; }
} }
} }

View file

@ -1,7 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Arguments
using System.Text;
namespace FFMpegCore.Arguments
{ {
public interface IDynamicArgument public interface IDynamicArgument
{ {
@ -13,4 +10,4 @@ public interface IDynamicArgument
//public string GetText(StringBuilder context); //public string GetText(StringBuilder context);
public string GetText(IEnumerable<IArgument> context); public string GetText(IEnumerable<IArgument> context);
} }
} }

View file

@ -3,4 +3,4 @@
public interface IInputArgument : IInputOutputArgument public interface IInputArgument : IInputOutputArgument
{ {
} }
} }

View file

@ -1,7 +1,4 @@
using System.Threading; namespace FFMpegCore.Arguments
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{ {
public interface IInputOutputArgument : IArgument public interface IInputOutputArgument : IArgument
{ {
@ -9,4 +6,4 @@ public interface IInputOutputArgument : IArgument
Task During(CancellationToken cancellationToken = default); Task During(CancellationToken cancellationToken = default);
void Post(); void Post();
} }
} }

View file

@ -3,4 +3,4 @@
public interface IOutputArgument : IInputOutputArgument public interface IOutputArgument : IInputOutputArgument
{ {
} }
} }

View file

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

View file

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

View file

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

View file

@ -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}"));
}
}

View file

@ -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 @@ public void Post()
public void Pre() public void Pre()
{ {
} }
} }
} }

View file

@ -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}";
} }
} }

View file

@ -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 @@ public MetaDataArgument(string metaDataContent)
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
public void Pre() => File.WriteAllText(_tempFileName, _metaDataContent); public void Pre() => File.WriteAllText(_tempFileName, _metaDataContent);
public void Post() => File.Delete(_tempFileName); public void Post() => File.Delete(_tempFileName);

View file

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

View file

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

View file

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

View file

@ -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);
}
}
}

View file

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

View file

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

View file

@ -7,4 +7,4 @@ public class RemoveMetadataArgument : IArgument
{ {
public string Text => "-map_metadata -1"; public string Text => "-map_metadata -1";
} }
} }

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Arguments
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Represents seek parameter /// Represents seek parameter
@ -14,15 +12,18 @@ public SeekArgument(TimeSpan? seekTo)
SeekTo = seekTo; SeekTo = seekTo;
} }
public string Text { public string Text
get { {
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

View file

@ -1,5 +1,4 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
using System;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments
{ {
@ -14,17 +13,12 @@ public SetMirroringArgument(Mirroring mirroring)
public string Key => string.Empty; public string Key => string.Empty;
public string Value public string Value =>
{ Mirroring switch
get
{ {
return Mirroring switch Mirroring.Horizontal => "hflip",
{ Mirroring.Vertical => "vflip",
Mirroring.Horizontal => "hflip", _ => throw new ArgumentOutOfRangeException(nameof(Mirroring))
Mirroring.Vertical => "vflip", };
_ => throw new ArgumentOutOfRangeException(nameof(Mirroring))
};
}
}
} }
} }

View 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}"));
}
}

View file

@ -6,7 +6,7 @@
public class StartNumberArgument : IArgument public class StartNumberArgument : IArgument
{ {
public readonly int StartNumber; public readonly int StartNumber;
public StartNumberArgument(int startNumber) public StartNumberArgument(int startNumber)
{ {
StartNumber = startNumber; StartNumber = startNumber;

View file

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

View file

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

View file

@ -20,4 +20,4 @@ public TransposeArgument(Transposition transposition)
public string Key { get; } = "transpose"; public string Key { get; } = "transpose";
public string Value => ((int)Transposition).ToString(); public string Value => ((int)Transposition).ToString();
} }
} }

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Arguments
namespace FFMpegCore.Arguments
{ {
/// <summary> /// <summary>
/// Variable Bitrate Argument (VBR) argument /// Variable Bitrate Argument (VBR) argument
@ -21,4 +19,4 @@ public VariableBitRateArgument(int vbr)
public string Text => $"-vbr {Vbr}"; public string Text => $"-vbr {Vbr}";
} }
} }

View file

@ -22,4 +22,4 @@ public enum VerbosityLevel
Debug = 48, Debug = 48,
Trace = 56 Trace = 56
} }
} }

View file

@ -14,4 +14,4 @@ public VideoBitrateArgument(int bitrate)
public string Text => $"-b:v {Bitrate}k"; public string Text => $"-b:v {Bitrate}k";
} }
} }

View file

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

View file

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

View file

@ -1,6 +1,4 @@
using System; namespace FFMpegCore.Builders.MetaData
namespace FFMpegCore.Builders.MetaData
{ {
public class ChapterData public class ChapterData
{ {
@ -15,4 +13,4 @@ public ChapterData(string title, TimeSpan start, TimeSpan end)
End = end; End = end;
} }
} }
} }

View file

@ -1,6 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Builders.MetaData
namespace FFMpegCore.Builders.MetaData
{ {
public interface IReadOnlyMetaData public interface IReadOnlyMetaData
@ -8,4 +6,4 @@ public interface IReadOnlyMetaData
IReadOnlyList<ChapterData> Chapters { get; } IReadOnlyList<ChapterData> Chapters { get; }
IReadOnlyDictionary<string, string> Entries { get; } IReadOnlyDictionary<string, string> Entries { get; }
} }
} }

View file

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

View file

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

View file

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

View file

@ -1,7 +1,4 @@
using System.Collections.Generic; namespace FFMpegCore.Builders.MetaData
using System.Linq;
namespace FFMpegCore.Builders.MetaData
{ {
public class ReadOnlyMetaData : IReadOnlyMetaData public class ReadOnlyMetaData : IReadOnlyMetaData
{ {
@ -22,4 +19,4 @@ public ReadOnlyMetaData(MetaData metaData)
.AsReadOnly(); .AsReadOnly();
} }
} }
} }

View file

@ -9,4 +9,4 @@ public enum AudioQuality
BelowNormal = 96, BelowNormal = 96,
Low = 64 Low = 64
} }
} }

View file

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

View file

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

View file

@ -26,7 +26,7 @@ public static class AudioCodec
public static Codec LibFdk_Aac => FFMpeg.GetCodec("libfdk_aac"); public static Codec LibFdk_Aac => FFMpeg.GetCodec("libfdk_aac");
public static Codec Ac3 => FFMpeg.GetCodec("ac3"); public static Codec Ac3 => FFMpeg.GetCodec("ac3");
public static Codec Eac3 => FFMpeg.GetCodec("eac3"); public static Codec Eac3 => FFMpeg.GetCodec("eac3");
public static Codec LibMp3Lame => FFMpeg.GetCodec("libmp3lame"); public static Codec LibMp3Lame => FFMpeg.GetCodec("libmp3lame");
} }
public static class VideoType public static class VideoType
@ -46,10 +46,42 @@ public enum Filter
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
};
}
}
}

View 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
}
}

View file

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

View file

@ -7,8 +7,9 @@ public enum HardwareAccelerationDevice
DXVA2, DXVA2,
QSV, QSV,
CUVID, CUVID,
CUDA,
VDPAU, VDPAU,
VAAPI, VAAPI,
LibMFX LibMFX
} }
} }

View file

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

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