Compare commits

...

131 commits

Author SHA1 Message Date
Malte Rosenbjerg
4651252427
Merge pull request #582 from rosenbjerg/main
Some checks failed
NuGet release / release (push) Has been cancelled
V.5.3.0
2025-10-17 20:49:31 +02:00
Malte Rosenbjerg
fc5e8a66e3 Prep for release 2025-10-17 20:36:58 +02:00
Malte Rosenbjerg
46fb46381d
Merge pull request #581 from rosenbjerg/include-more-guid-chars-in-pipe-path
Include more guid chars in pipe path
2025-10-17 20:34:45 +02:00
Malte Rosenbjerg
77d13e8143
Update FFMpegCore.Test/ArgumentBuilderTest.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-17 19:54:15 +02:00
Malte Rosenbjerg
1b0051b234 Create variable for holding macOS max pipe path length to avoid magic number 2025-10-17 19:53:38 +02:00
Malte Rosenbjerg
f0b5859afd Add test verifying that full pipe path is less than maximum pipe path length on macos 2025-10-17 19:47:41 +02:00
Malte Rosenbjerg
c0b5e8e52f Fix typo 2025-10-17 19:47:08 +02:00
Malte Rosenbjerg
94174a28db Include 16 guid chars in pipe name 2025-10-17 19:47:01 +02:00
Malte Rosenbjerg
cf775ae7a9
Merge pull request #571 from techtel-pstevens/main
Fixed race condition on Named pipe dispose/disconnect
2025-10-17 15:43:57 +02:00
Malte Rosenbjerg
767a3f59ab
Merge branch 'main' into main 2025-10-17 15:37:38 +02:00
Malte Rosenbjerg
1722b5496d
Merge pull request #442 from yuqian5/main
Ability to install ffmpeg suite at runtime added with FFMpegDownloader
2025-10-17 15:34:00 +02:00
Malte Rosenbjerg
bbdbe5592a Merge branch 'main' of https://github.com/yuqian5/FFMpegCore into pr/442 2025-10-17 15:22:42 +02:00
Malte Rosenbjerg
8b8701ef44 Make Detail prop on FFMpegDownloaderException a readonly field 2025-10-17 15:22:35 +02:00
Malte Rosenbjerg
abf2ab5ee7 Ensure sub tempfolder is deleted after use 2025-10-17 15:22:17 +02:00
Malte Rosenbjerg
81bf155c38
Update README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-17 15:20:51 +02:00
Malte Rosenbjerg
0a0e6c4985
Update FFMpegCore.Test/Utilities/OsSpecificTestMethod.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-17 15:20:44 +02:00
Malte Rosenbjerg
7794276536
Update FFMpegCore.Extensions.Downloader/Enums/FFMpegBinaries.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-17 15:19:12 +02:00
Malte Rosenbjerg
8720e19b91 Fix usage of temp subfolder 2025-10-17 15:17:27 +02:00
Malte Rosenbjerg
54e28cea23 Use subfolder in temp folder for testing download of binaries 2025-10-17 15:15:02 +02:00
Malte Rosenbjerg
2a25bff836 Remove WindowsOnlyTestMethod and use OsSpecificTestMethod 2025-10-17 15:14:42 +02:00
Malte Rosenbjerg
a71a55741f Use OsSpecificTestMethod on GetAllLatestSuiteTest 2025-10-17 15:08:35 +02:00
Malte Rosenbjerg
bfcb1b9544 Minor refactor to use yield return 2025-10-17 15:08:13 +02:00
Malte Rosenbjerg
1a49b4eec3 Rename Latest enum member 2025-10-17 15:08:00 +02:00
Malte Rosenbjerg
3404d63655 Rename download function 2025-10-17 15:07:45 +02:00
Malte Rosenbjerg
8f2d3d7125 Remove specification of .NET Standard 2.1 2025-10-17 15:07:10 +02:00
Malte Rosenbjerg
ad27076c1f Refactor for conciseness 2025-10-17 15:01:11 +02:00
Malte Rosenbjerg
d7fec62d2e Only run downloader tests on windows and linux since macos CI runs on arm which builds are not available for 2025-10-17 15:01:00 +02:00
Malte Rosenbjerg
3be446880a Only run downloader tests on windows and linux since macos CI runs on arm which builds are not available for 2025-10-17 14:45:11 +02:00
Malte Rosenbjerg
07253b4b49 Update nuget package authors 2025-10-17 10:35:05 +02:00
Malte Rosenbjerg
af47c9ae94 Throw exception if binary folder not specified 2025-10-17 10:34:49 +02:00
Malte Rosenbjerg
b1eb8da6a9 Delete downloaded binaries after assert 2025-10-17 10:34:27 +02:00
Malte Rosenbjerg
2852692250 Fix typo 2025-10-17 10:33:29 +02:00
Malte Rosenbjerg
f919a05d43 Make download tests run async 2025-10-17 10:24:26 +02:00
Malte Rosenbjerg
bb90cb1ea1 Merge branch 'main' into pr/442 2025-10-17 10:21:42 +02:00
Malte Rosenbjerg
ea8c493f8a Merge branch 'main' into pr/571 2025-10-16 14:46:05 +02:00
Malte Rosenbjerg
bd55ec2a51
Merge pull request #579 from rosenbjerg/cleanup-tests
Cleanup tests
2025-10-16 14:45:53 +02:00
Malte Rosenbjerg
cd258991bd Add prefix to tests modifying GlobalOptions 2025-10-16 14:42:04 +02:00
Malte Rosenbjerg
5082e6503e Set OrderTestsByNameInClass to true 2025-10-16 14:41:47 +02:00
Malte Rosenbjerg
48ccd3e291 Update test config.json file 2025-10-16 14:41:36 +02:00
Malte Rosenbjerg
b8de2fc545 Parallelize on MethodLevel 2025-10-16 14:41:26 +02:00
Malte Rosenbjerg
91bbfa850e Avoid modifying global options if not necessary 2025-10-16 14:36:25 +02:00
Malte Rosenbjerg
7c71a70a0c Revert "Improve test readability"
This reverts commit 85e7170fd9.
2025-10-16 14:26:53 +02:00
Malte Rosenbjerg
0c467e3a05 Improve resetting 2025-10-16 14:14:47 +02:00
Malte Rosenbjerg
65e8ba85e3 Move tests 2025-10-16 14:09:59 +02:00
Malte Rosenbjerg
85e7170fd9 Improve test readability 2025-10-16 14:04:10 +02:00
Malte Rosenbjerg
2d149d4c9a Initialize lock inline 2025-10-16 13:52:17 +02:00
Malte Rosenbjerg
0387e47396 Merge branch 'main' into pr/571 2025-10-16 13:50:27 +02:00
Malte Rosenbjerg
70668ce623
Merge pull request #578 from rosenbjerg/clean-up-unit-tests
Clean up unit tests
2025-10-16 13:50:09 +02:00
Malte Rosenbjerg
012a5ca74a Improve reset of GlobalOptions 2025-10-16 13:46:08 +02:00
Malte Rosenbjerg
9765b78d48 Add missing reset of GlobalOptions and reorder 2025-10-16 13:41:06 +02:00
Malte Rosenbjerg
671829b501 Run code cleanup 2025-10-16 13:32:25 +02:00
Malte Rosenbjerg
4aa580e5f4 Remove FluentAssertions and update usages 2025-10-16 13:31:32 +02:00
Malte Rosenbjerg
262c3f1b4f Wrap remaining Pipe access in lock 2025-10-16 13:10:33 +02:00
Malte Rosenbjerg
29f40b88af Use private readonly object for locking 2025-10-16 13:09:52 +02:00
Malte Rosenbjerg
6fc811bc39 Move comment 2025-10-16 13:04:58 +02:00
Malte Rosenbjerg
5f2147e207 Fix formatting 2025-10-16 13:03:26 +02:00
Malte Rosenbjerg
066ca894ba Merge branch 'main' into pr/571 2025-10-16 12:58:20 +02:00
Malte Rosenbjerg
fb6d6f80f0 Bump package versions 2025-10-16 12:57:02 +02:00
Malte Rosenbjerg
c99dfc9a8c
Merge pull request #577 from rosenbjerg/update-nuget-dependencies
Update nuget dependencies
2025-10-16 12:56:19 +02:00
Malte Rosenbjerg
122c7b0f87 Add codecov badge 2025-10-16 12:52:09 +02:00
Malte Rosenbjerg
0d78315a4a Fix formatting 2025-10-16 12:46:37 +02:00
Malte Rosenbjerg
c6a38fabdc Fix newline between properties formatting 2025-10-16 12:44:01 +02:00
Malte Rosenbjerg
c49f4c79ca Pass TestContext.CancellationToken to async invocations 2025-10-16 12:40:25 +02:00
Malte Rosenbjerg
ce87dc8a36 Run full code cleanup 2025-10-16 12:38:57 +02:00
Malte Rosenbjerg
d3d810ecd7 Update tests to new syntax 2025-10-16 12:35:45 +02:00
Malte Rosenbjerg
31e30c1eb7 Update nuget packages 2025-10-16 12:35:42 +02:00
Malte Rosenbjerg
9a5b9ca19d
Merge branch 'main' into main 2025-10-16 10:10:19 +02:00
Malte Rosenbjerg
53b3e0fbd0
Merge pull request #566 from GorobVictor/snapshot-formats
FEAT: added more extensions for snapshot(jpg, bmp, webp)
2025-10-16 10:08:25 +02:00
Malte Rosenbjerg
bfefa9560e
Merge branch 'main' into snapshot-formats 2025-10-16 10:00:56 +02:00
Malte Rosenbjerg
cae2e04a54
Merge branch 'main' into main 2025-10-16 10:00:07 +02:00
Malte Rosenbjerg
37b4119e13
Merge pull request #576 from rosenbjerg/migrate-to-macos-latest-test-runner-instead-of-macos-13
CI: Install ffmpeg using brew if running on arm64 macos
2025-10-16 09:59:48 +02:00
Malte Rosenbjerg
7e135a78d3 Use install step for installing ffmpeg 2025-10-16 09:55:50 +02:00
Malte Rosenbjerg
91c1629215 Add name labels to ffmpeg install steps 2025-10-16 09:53:09 +02:00
Malte Rosenbjerg
7036ad6df2 Switch on matrix.os instead of runner.os 2025-10-16 09:47:53 +02:00
Malte Rosenbjerg
5ba8122d00 Fix specified ffmpeg version 2025-10-16 09:41:34 +02:00
Malte Rosenbjerg
665c9f9213 Use full reference 2025-10-16 09:39:51 +02:00
Malte Rosenbjerg
fb10b78e35 Include github token to avoid rate limiting 2025-10-16 09:37:42 +02:00
Malte Rosenbjerg
99181e9f65 Include github token to avoid rate limiting 2025-10-16 09:37:27 +02:00
Malte Rosenbjerg
af6587d8fe Reference workflow action by reference 2025-10-16 09:37:04 +02:00
Malte Rosenbjerg
9c636d4059 Migrate to AnimMouse/setup-ffmpeg@v1 2025-10-16 09:32:23 +02:00
Malte Rosenbjerg
5d8d346598 Only lint on ubuntu-latest to not waste time 2025-10-16 09:32:11 +02:00
Malte Rosenbjerg
918ca9a9ab Remove accessibility modifiers on interface properties (IDE0040) 2025-10-16 09:22:09 +02:00
Malte Rosenbjerg
abb9f15eeb Use brew if running on arm64 macos 2025-10-16 09:19:05 +02:00
Malte Rosenbjerg
193ed43f1d
Merge branch 'main' into snapshot-formats 2025-09-09 08:23:41 +02:00
Malte Rosenbjerg
50fcddab9b
Merge branch 'main' into main 2025-09-09 08:22:17 +02:00
Malte Rosenbjerg
25d7ae8374
Merge pull request #572 from WeihanLi/patch-1
docs: update nuget badge
2025-09-09 08:21:05 +02:00
Weihan Li
bbfcfaefac
docs: update nuget badge 2025-07-21 17:08:22 +08:00
Ashish
67808c2e4a
Merge pull request #1 from alahane-techtel/alahane-techtel-patch-1/NamedPipeCleanupRaceCondition
Fixed race condition on Named pipe dispose/disconnect
2025-07-07 17:04:39 +10:00
Ashish
d608026d17
Fixed race condition on Named pipe dispose/disconnect 2025-07-04 23:12:05 +10:00
Victor Horobchuk
3d21599c5d FIX: for dotnet format 2025-04-17 13:17:52 +03:00
Victor Horobchuk
4025b82fbf FIX: small moments 2025-04-17 13:02:27 +03:00
Victor Horobchuk
aa1051b268 FEAT: added more extensions for snapshot(jpg, bmp, webp) 2025-04-17 12:29:06 +03:00
Kerry Cao
483c526772 linter fix 2025-01-28 21:44:34 -05:00
Kerry Cao
1c03587cd8 obsolete webclient replaced with httpclient 2025-01-28 21:42:12 -05:00
Kerry Cao
644c41df90 GetSpecificVersionTest assertion fix 2025-01-28 21:41:44 -05:00
Kerry Cao
9f6249cb5f re-enable DownloaderTests.cs 2025-01-27 22:43:20 -05:00
Kerry Cao
88c7f7bf7c debug 2025-01-27 22:39:35 -05:00
Kerry Cao
33098ad4de linter fix 2025-01-27 22:37:03 -05:00
Kerry Cao
91a2ceaceb Test update to try and resolve failed tests 2025-01-27 22:34:57 -05:00
Kerry Cao
8e32d68877 format cleanup to avoid lint error 2025-01-27 22:30:22 -05:00
Kerry Cao
7be5496305 major refactoring 2025-01-27 22:22:47 -05:00
K
995690c0f3
Merge branch 'rosenbjerg:main' into main 2025-01-27 21:42:14 -05:00
Malte Rosenbjerg
c44170ba8e
Merge branch 'main' into main 2024-12-04 23:08:27 +01:00
Malte Rosenbjerg
a91e309da5
Merge branch 'main' into main 2024-12-04 20:53:07 +01:00
Malte Rosenbjerg
ac3794e753
Merge branch 'main' into main 2024-12-04 20:49:20 +01:00
K
09ae9447c8
version 5.1 and 6.1 added 2024-01-04 16:59:25 -07:00
K
105a26bf5e
Merge branch 'rosenbjerg:main' into main 2024-01-04 15:43:12 -07:00
K
6524b1cd4d
Update DownloaderTests.cs 2023-10-10 15:59:11 -06:00
K
22fa002394
Debug for CI failure 2023-10-10 15:50:59 -06:00
K
5bedbdb107
Update README.md 2023-10-10 15:45:14 -06:00
Malte Rosenbjerg
bba4a9f39b
Merge branch 'main' into main 2023-10-05 12:17:18 +02:00
Malte Rosenbjerg
882cdf849e
Merge branch 'main' into main 2023-10-05 09:21:55 +02:00
Kerry Cao
ae5ae973fa FFMpegCore.Downloader renamed to FFMpegCore.Extensions.Downloader 2023-09-06 14:07:08 -06:00
Kerry Cao
2b66b82d8d Merge remote-tracking branch 'origin/main' 2023-09-06 13:45:32 -06:00
Kerry Cao
d978d7d9b4 ffmpeg downloader moved to separate package 2023-09-06 13:45:22 -06:00
Kerry Cao
dfc486db1d ffmpeg downloader moved to seperate package 2023-09-06 13:42:22 -06:00
Kerry Cao
937db76e00 format fix for lint 2023-09-06 13:22:13 -06:00
Kerry Cao
df8d97ebbb allow os and architecture overrider 2023-09-06 13:19:47 -06:00
Kerry Cao
ae92e9370c Merge remote-tracking branch 'origin/main'
# Conflicts:
#	FFMpegCore.Test/DownloaderTests.cs
#	FFMpegCore/Helpers/FFMpegDownloader.cs
2023-09-06 01:26:56 -06:00
Kerry Cao
da7d1fafed support added for linux, macos
win32, win64, lnx32, lnx64, lnx-armhf, lnx-armel, lnx-arm64, osx64
2023-09-06 01:25:51 -06:00
KCYQ
97fcd3b95e
Merge branch 'main' into main 2023-06-04 16:48:47 +08:00
KCYQ
cf7fd2fdf2
Merge branch 'main' into main 2023-05-06 20:58:04 -06:00
Kerry Cao
463fb9bf6b Reformatted to fix lint error 2023-05-05 01:54:50 -06:00
Kerry Cao
a647f44029 refactoring and reformatting 2023-05-05 01:47:08 -06:00
Kerry Cao
debb868b38 README.md updated to include ffmpegdownloader usage 2023-05-05 01:34:21 -06:00
Kerry Cao
d68ce8e953 Methods renamed to remove auto prefix 2023-05-05 01:30:52 -06:00
Kerry Cao
657ee5ff5d Comments added 2023-05-05 01:21:53 -06:00
Kerry Cao
d44c77c958 Merge remote-tracking branch 'origin/main' 2023-04-11 22:43:04 -06:00
Kerry Cao
a4bb69a8a8 changes made to support multiple version downloads for windows 2023-04-11 22:42:51 -06:00
KCYQ
7c333a723a
Merge pull request #1 from rosenbjerg/main
Merge with upstream
2023-04-11 21:57:40 -06:00
Kerry Cao
9db4ba75b4 features to auto download ffmpeg binaries added 2023-04-11 21:55:30 -06:00
173 changed files with 8688 additions and 7569 deletions

View file

@ -18,7 +18,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [windows-latest, ubuntu-latest, macos-13] os: [windows-latest, ubuntu-latest, macos-latest]
timeout-minutes: 7 timeout-minutes: 7
steps: steps:
@ -30,14 +30,15 @@ jobs:
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
- name: Lint with dotnet - if: matrix.os == 'ubuntu-latest'
name: Lint with dotnet
run: dotnet format FFMpegCore.sln --severity warn --verify-no-changes run: dotnet format FFMpegCore.sln --severity warn --verify-no-changes
- name: Prepare FFMpeg - name: Setup FFmpeg
uses: FedericoCarboni/setup-ffmpeg@v3 uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae # 1.1.0
with: with:
ffmpeg-version: 6.0.1 version: ${{ matrix.os != 'macos-latest' && '7.1' || '711' }}
github-token: ${{ secrets.GITHUB_TOKEN }} token: ${{ github.token }}
- name: Test with dotnet - name: Test with dotnet
run: dotnet test FFMpegCore.sln --collect "XPlat Code Coverage" --logger GitHubActions run: dotnet test FFMpegCore.sln --collect "XPlat Code Coverage" --logger GitHubActions

View file

@ -61,7 +61,7 @@ var outputStream = new MemoryStream();
} }
{ {
FFMpeg.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1, @"..\1.png", @"..\2.png", @"..\3.png"); FFMpeg.JoinImageSequence(@"..\joined_video.mp4", 1, @"..\1.png", @"..\2.png", @"..\3.png");
} }
{ {
@ -90,7 +90,11 @@ var inputImagePath = "/path/to/input/image";
skiaSharpImage.AddAudio(inputAudioPath, outputPath); skiaSharpImage.AddAudio(inputAudioPath, outputPath);
} }
IVideoFrame GetNextFrame() => throw new NotImplementedException(); IVideoFrame GetNextFrame()
{
throw new NotImplementedException();
}
{ {
IEnumerable<IVideoFrame> CreateFrames(int count) IEnumerable<IVideoFrame> CreateFrames(int count)
{ {
@ -100,7 +104,8 @@ IVideoFrame GetNextFrame() => throw new NotImplementedException();
} }
} }
var videoFramesSource = new RawVideoPipeSource(CreateFrames(64)) //pass IEnumerable<IVideoFrame> or IEnumerator<IVideoFrame> to constructor of RawVideoPipeSource var videoFramesSource =
new RawVideoPipeSource(CreateFrames(64)) //pass IEnumerable<IVideoFrame> or IEnumerator<IVideoFrame> to constructor of RawVideoPipeSource
{ {
FrameRate = 30 //set source frame rate FrameRate = 30 //set source frame rate
}; };

View file

@ -0,0 +1,9 @@
namespace FFMpegCore.Extensions.Downloader.Enums;
[Flags]
public enum FFMpegBinaries : ushort
{
FFMpeg = 1,
FFProbe = 2,
FFPlay = 4
}

View file

@ -0,0 +1,39 @@
using System.ComponentModel;
namespace FFMpegCore.Extensions.Downloader.Enums;
public enum FFMpegVersions : ushort
{
[Description("https://ffbinaries.com/api/v1/version/latest")]
LatestAvailable,
[Description("https://ffbinaries.com/api/v1/version/6.1")]
V6_1,
[Description("https://ffbinaries.com/api/v1/version/5.1")]
V5_1,
[Description("https://ffbinaries.com/api/v1/version/4.4.1")]
V4_4_1,
[Description("https://ffbinaries.com/api/v1/version/4.2.1")]
V4_2_1,
[Description("https://ffbinaries.com/api/v1/version/4.2")]
V4_2,
[Description("https://ffbinaries.com/api/v1/version/4.1")]
V4_1,
[Description("https://ffbinaries.com/api/v1/version/4.0")]
V4_0,
[Description("https://ffbinaries.com/api/v1/version/3.4")]
V3_4,
[Description("https://ffbinaries.com/api/v1/version/3.3")]
V3_3,
[Description("https://ffbinaries.com/api/v1/version/3.2")]
V3_2
}

View file

@ -0,0 +1,13 @@
namespace FFMpegCore.Extensions.Downloader.Enums;
public enum SupportedPlatforms : ushort
{
Windows64,
Windows32,
Linux64,
Linux32,
LinuxArmhf,
LinuxArmel,
LinuxArm64,
Osx64
}

View file

@ -0,0 +1,18 @@
namespace FFMpegCore.Extensions.Downloader.Exceptions;
/// <summary>
/// Custom exception for FFMpegDownloader
/// </summary>
public class FFMpegDownloaderException : Exception
{
public readonly string Detail = "";
public FFMpegDownloaderException(string message) : base(message)
{
}
public FFMpegDownloaderException(string message, string detail) : base(message)
{
Detail = detail;
}
}

View file

@ -0,0 +1,31 @@
using System.ComponentModel;
namespace FFMpegCore.Extensions.Downloader.Extensions;
public static class EnumExtensions
{
internal static string GetDescription(this Enum enumValue)
{
var field = enumValue.GetType().GetField(enumValue.ToString());
if (field == null)
{
return enumValue.ToString();
}
if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute)
{
return attribute.Description;
}
return enumValue.ToString();
}
public static TEnum[] GetFlags<TEnum>(this TEnum input) where TEnum : Enum
{
return Enum.GetValues(input.GetType())
.Cast<Enum>()
.Where(input.HasFlag)
.Cast<TEnum>()
.ToArray();
}
}

View file

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>true</IsPackable>
<Description>FFMpeg downloader extension for FFMpegCore</Description>
<PackageVersion>5.0.0</PackageVersion>
<PackageOutputPath>../nupkg</PackageOutputPath>
<PackageReleaseNotes>
- Updated dependencies
</PackageReleaseNotes>
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze download install</PackageTags>
<Authors>Kerry Cao, Malte Rosenbjerg</Authors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj"/>
</ItemGroup>
</Project>

View file

@ -0,0 +1,83 @@
using System.IO.Compression;
using System.Text.Json;
using FFMpegCore.Extensions.Downloader.Enums;
using FFMpegCore.Extensions.Downloader.Exceptions;
using FFMpegCore.Extensions.Downloader.Extensions;
using FFMpegCore.Extensions.Downloader.Models;
namespace FFMpegCore.Extensions.Downloader;
public static class FFMpegDownloader
{
/// <summary>
/// Download the latest FFMpeg suite binaries for current platform
/// </summary>
/// <param name="version">used to explicitly state the version of binary you want to download</param>
/// <param name="binaries">used to explicitly state the binaries you want to download (ffmpeg, ffprobe, ffplay)</param>
/// <param name="options">used for specifying binary folder to download binaries into. If not provided, GlobalFFOptions are used</param>
/// <param name="platformOverride">used to explicitly state the os and architecture you want to download</param>
/// <returns>a list of the binaries that have been successfully downloaded</returns>
public static async Task<List<string>> DownloadBinaries(
FFMpegVersions version = FFMpegVersions.LatestAvailable,
FFMpegBinaries binaries = FFMpegBinaries.FFMpeg | FFMpegBinaries.FFProbe,
FFOptions? options = null,
SupportedPlatforms? platformOverride = null)
{
using var httpClient = new HttpClient();
var versionInfo = await httpClient.GetVersionInfo(version);
var binariesDictionary = versionInfo.BinaryInfo?.GetCompatibleDownloadInfo(platformOverride) ??
throw new FFMpegDownloaderException("Failed to get compatible download info");
var successList = new List<string>();
var relevantOptions = options ?? GlobalFFOptions.Current;
if (string.IsNullOrEmpty(relevantOptions.BinaryFolder))
{
throw new FFMpegDownloaderException("Binary folder not specified");
}
var binaryFlags = binaries.GetFlags();
foreach (var binaryFlag in binaryFlags)
{
if (binariesDictionary.TryGetValue(binaryFlag.ToString().ToLowerInvariant(), out var binaryUrl))
{
using var zipStream = await httpClient.GetStreamAsync(new Uri(binaryUrl));
var extracted = ExtractZipAndSave(zipStream, relevantOptions.BinaryFolder);
successList.AddRange(extracted);
}
}
return successList;
}
private static async Task<VersionInfo> GetVersionInfo(this HttpClient client, FFMpegVersions version)
{
var versionUri = version.GetDescription();
var response = await client.GetAsync(versionUri);
if (!response.IsSuccessStatusCode)
{
throw new FFMpegDownloaderException($"Failed to get version info from {versionUri}", "network error");
}
var jsonString = await response.Content.ReadAsStringAsync();
var versionInfo = JsonSerializer.Deserialize<VersionInfo>(jsonString);
return versionInfo ??
throw new FFMpegDownloaderException($"Failed to deserialize version info from {versionUri}", jsonString);
}
private static IEnumerable<string> ExtractZipAndSave(Stream zipStream, string binaryFolder)
{
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read);
foreach (var entry in archive.Entries)
{
if (entry.Name is "ffmpeg" or "ffmpeg.exe" or "ffprobe.exe" or "ffprobe" or "ffplay.exe" or "ffplay")
{
var filePath = Path.Combine(binaryFolder, entry.Name);
entry.ExtractToFile(filePath, true);
yield return filePath;
}
}
}
}

View file

@ -0,0 +1,74 @@
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using FFMpegCore.Extensions.Downloader.Enums;
namespace FFMpegCore.Extensions.Downloader.Models;
internal class BinaryInfo
{
[JsonPropertyName("windows-64")] public Dictionary<string, string>? Windows64 { get; set; }
[JsonPropertyName("windows-32")] public Dictionary<string, string>? Windows32 { get; set; }
[JsonPropertyName("linux-32")] public Dictionary<string, string>? Linux32 { get; set; }
[JsonPropertyName("linux-64")] public Dictionary<string, string>? Linux64 { get; set; }
[JsonPropertyName("linux-armhf")] public Dictionary<string, string>? LinuxArmhf { get; set; }
[JsonPropertyName("linux-armel")] public Dictionary<string, string>? LinuxArmel { get; set; }
[JsonPropertyName("linux-arm64")] public Dictionary<string, string>? LinuxArm64 { get; set; }
[JsonPropertyName("osx-64")] public Dictionary<string, string>? Osx64 { get; set; }
/// <summary>
/// Automatically get the compatible download info for current os and architecture
/// </summary>
/// <param name="platformOverride"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="PlatformNotSupportedException"></exception>
public Dictionary<string, string>? GetCompatibleDownloadInfo(SupportedPlatforms? platformOverride = null)
{
if (platformOverride is not null)
{
return platformOverride switch
{
SupportedPlatforms.Windows64 => Windows64,
SupportedPlatforms.Windows32 => Windows32,
SupportedPlatforms.Linux64 => Linux64,
SupportedPlatforms.Linux32 => Linux32,
SupportedPlatforms.LinuxArmhf => LinuxArmhf,
SupportedPlatforms.LinuxArmel => LinuxArmel,
SupportedPlatforms.LinuxArm64 => LinuxArm64,
SupportedPlatforms.Osx64 => Osx64,
_ => throw new ArgumentOutOfRangeException(nameof(platformOverride), platformOverride, null)
};
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return RuntimeInformation.OSArchitecture == Architecture.X64 ? Windows64 : Windows32;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return RuntimeInformation.OSArchitecture switch
{
Architecture.X86 => Linux32,
Architecture.X64 => Linux64,
Architecture.Arm => LinuxArmhf,
Architecture.Arm64 => LinuxArm64,
_ => LinuxArmel
};
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && RuntimeInformation.OSArchitecture == Architecture.X64)
{
return Osx64;
}
throw new PlatformNotSupportedException("Unsupported OS or Architecture");
}
}

View file

@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
namespace FFMpegCore.Extensions.Downloader.Models;
internal record VersionInfo
{
[JsonPropertyName("version")] public string? Version { get; set; }
[JsonPropertyName("permalink")] public string? Permalink { get; set; }
[JsonPropertyName("bin")] public BinaryInfo? BinaryInfo { get; set; }
}

View file

@ -1,7 +1,7 @@
using SkiaSharp; using SkiaSharp;
namespace FFMpegCore.Extensions.SkiaSharp namespace FFMpegCore.Extensions.SkiaSharp;
{
public static class BitmapExtensions public static class BitmapExtensions
{ {
public static bool AddAudio(this SKBitmap poster, string audio, string output) public static bool AddAudio(this SKBitmap poster, string audio, string output)
@ -25,4 +25,3 @@ namespace FFMpegCore.Extensions.SkiaSharp
} }
} }
} }
}

View file

@ -1,24 +1,29 @@
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
using SkiaSharp; using SkiaSharp;
namespace FFMpegCore.Extensions.SkiaSharp namespace FFMpegCore.Extensions.SkiaSharp;
{
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
{ {
public int Width => Source.Width;
public int Height => Source.Height;
public string Format { get; private set; }
public SKBitmap Source { get; private set; }
public BitmapVideoFrameWrapper(SKBitmap bitmap) public BitmapVideoFrameWrapper(SKBitmap bitmap)
{ {
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap)); Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
Format = ConvertStreamFormat(bitmap.ColorType); Format = ConvertStreamFormat(bitmap.ColorType);
} }
public SKBitmap Source { get; }
public void Dispose()
{
Source.Dispose();
}
public int Width => Source.Width;
public int Height => Source.Height;
public string Format { get; }
public void Serialize(Stream stream) public void Serialize(Stream stream)
{ {
var data = Source.Bytes; var data = Source.Bytes;
@ -31,11 +36,6 @@ namespace FFMpegCore.Extensions.SkiaSharp
await stream.WriteAsync(data, 0, data.Length, token).ConfigureAwait(false); await stream.WriteAsync(data, 0, data.Length, token).ConfigureAwait(false);
} }
public void Dispose()
{
Source.Dispose();
}
private static string ConvertStreamFormat(SKColorType fmt) private static string ConvertStreamFormat(SKColorType fmt)
{ {
// TODO: Add support for additional formats // TODO: Add support for additional formats
@ -56,4 +56,3 @@ namespace FFMpegCore.Extensions.SkiaSharp
} }
} }
} }
}

View file

@ -3,17 +3,19 @@
<PropertyGroup> <PropertyGroup>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<Description>Image extension for FFMpegCore using SkiaSharp</Description> <Description>Image extension for FFMpegCore using SkiaSharp</Description>
<PackageVersion>5.0.2</PackageVersion> <PackageVersion>5.0.3</PackageVersion>
<PackageOutputPath>../nupkg</PackageOutputPath> <PackageOutputPath>../nupkg</PackageOutputPath>
<PackageReleaseNotes>Bump dependencies</PackageReleaseNotes> <PackageReleaseNotes>
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze muxing skiasharp</PackageTags> - Updated dependencies
</PackageReleaseNotes>
<PackageTags>ffmpeg ffprobe convert video audio image mediafile resize analyze muxing skia skiasharp</PackageTags>
<Authors>Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev, Dimitri Vranken</Authors> <Authors>Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev, Dimitri Vranken</Authors>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="SkiaSharp" Version="3.116.1" /> <PackageReference Include="SkiaSharp" Version="3.119.1"/>
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.116.1" /> <PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.1"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -2,8 +2,8 @@
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
using SkiaSharp; using SkiaSharp;
namespace FFMpegCore.Extensions.SkiaSharp namespace FFMpegCore.Extensions.SkiaSharp;
{
public static class FFMpegImage public static class FFMpegImage
{ {
/// <summary> /// <summary>
@ -30,6 +30,7 @@ namespace FFMpegCore.Extensions.SkiaSharp
using var bitmap = SKBitmap.Decode(ms); using var bitmap = SKBitmap.Decode(ms);
return bitmap.Copy(); return bitmap.Copy();
} }
/// <summary> /// <summary>
/// Saves a 'png' thumbnail to an in-memory bitmap /// Saves a 'png' thumbnail to an in-memory bitmap
/// </summary> /// </summary>
@ -39,7 +40,8 @@ namespace FFMpegCore.Extensions.SkiaSharp
/// <param name="streamIndex">Selected video stream index.</param> /// <param name="streamIndex">Selected video stream index.</param>
/// <param name="inputFileIndex">Input file index</param> /// <param name="inputFileIndex">Input file index</param>
/// <returns>Bitmap with the requested snapshot.</returns> /// <returns>Bitmap with the requested snapshot.</returns>
public static async Task<SKBitmap> SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0) public static async Task<SKBitmap> SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null,
int inputFileIndex = 0)
{ {
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false); var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
@ -54,4 +56,3 @@ namespace FFMpegCore.Extensions.SkiaSharp
return SKBitmap.Decode(ms); return SKBitmap.Decode(ms);
} }
} }
}

View file

@ -1,7 +1,7 @@
using System.Drawing; using System.Drawing;
namespace FFMpegCore.Extensions.System.Drawing.Common namespace FFMpegCore.Extensions.System.Drawing.Common;
{
public static class BitmapExtensions public static class BitmapExtensions
{ {
public static bool AddAudio(this Image poster, string audio, string output) public static bool AddAudio(this Image poster, string audio, string output)
@ -21,4 +21,3 @@ namespace FFMpegCore.Extensions.System.Drawing.Common
} }
} }
} }
}

View file

@ -3,24 +3,29 @@ using System.Drawing.Imaging;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore.Extensions.System.Drawing.Common namespace FFMpegCore.Extensions.System.Drawing.Common;
{
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
{ {
public int Width => Source.Width;
public int Height => Source.Height;
public string Format { get; private set; }
public Bitmap Source { get; private set; }
public BitmapVideoFrameWrapper(Bitmap bitmap) public BitmapVideoFrameWrapper(Bitmap bitmap)
{ {
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap)); Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
Format = ConvertStreamFormat(bitmap.PixelFormat); Format = ConvertStreamFormat(bitmap.PixelFormat);
} }
public Bitmap Source { get; }
public void Dispose()
{
Source.Dispose();
}
public int Width => Source.Width;
public int Height => Source.Height;
public string Format { get; }
public void Serialize(Stream stream) public void Serialize(Stream stream)
{ {
var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat); var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
@ -53,11 +58,6 @@ namespace FFMpegCore.Extensions.System.Drawing.Common
} }
} }
public void Dispose()
{
Source.Dispose();
}
private static string ConvertStreamFormat(PixelFormat fmt) private static string ConvertStreamFormat(PixelFormat fmt)
{ {
switch (fmt) switch (fmt)
@ -84,4 +84,3 @@ namespace FFMpegCore.Extensions.System.Drawing.Common
} }
} }
} }
}

View file

@ -3,16 +3,18 @@
<PropertyGroup> <PropertyGroup>
<IsPackable>true</IsPackable> <IsPackable>true</IsPackable>
<Description>Image extension for FFMpegCore using System.Common.Drawing</Description> <Description>Image extension for FFMpegCore using System.Common.Drawing</Description>
<PackageVersion>5.0.2</PackageVersion> <PackageVersion>5.0.3</PackageVersion>
<PackageOutputPath>../nupkg</PackageOutputPath> <PackageOutputPath>../nupkg</PackageOutputPath>
<PackageReleaseNotes>Bump dependencies</PackageReleaseNotes> <PackageReleaseNotes>
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze muxing</PackageTags> - Updated dependencies
</PackageReleaseNotes>
<PackageTags>ffmpeg ffprobe convert video audio image mediafile resize analyze muxing</PackageTags>
<Authors>Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev</Authors> <Authors>Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev</Authors>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly> <ProduceReferenceAssembly>true</ProduceReferenceAssembly>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="9.0.2" /> <PackageReference Include="System.Drawing.Common" Version="9.0.10"/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -1,8 +1,8 @@
using System.Drawing; using System.Drawing;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore.Extensions.System.Drawing.Common namespace FFMpegCore.Extensions.System.Drawing.Common;
{
public static class FFMpegImage public static class FFMpegImage
{ {
/// <summary> /// <summary>
@ -39,7 +39,8 @@ namespace FFMpegCore.Extensions.System.Drawing.Common
/// <param name="streamIndex">Selected video stream index.</param> /// <param name="streamIndex">Selected video stream index.</param>
/// <param name="inputFileIndex">Input file index</param> /// <param name="inputFileIndex">Input file index</param>
/// <returns>Bitmap with the requested snapshot.</returns> /// <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) 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 source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex); var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
@ -54,4 +55,3 @@ namespace FFMpegCore.Extensions.System.Drawing.Common
return new Bitmap(ms); return new Bitmap(ms);
} }
} }
}

View file

@ -1,14 +1,15 @@
using System.Drawing; using System.Drawing;
using FFMpegCore.Arguments; using FFMpegCore.Arguments;
using FFMpegCore.Enums; using FFMpegCore.Enums;
using Microsoft.VisualStudio.TestTools.UnitTesting; using FFMpegCore.Pipes;
namespace FFMpegCore.Test;
namespace FFMpegCore.Test
{
[TestClass] [TestClass]
public class ArgumentBuilderTest 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" };
private readonly int _macOsMaxPipePathLength = 104;
private readonly string[] _multiFiles = { "1.mp3", "2.mp3", "3.mp3", "4.mp3" }; private readonly string[] _multiFiles = { "1.mp3", "2.mp3", "3.mp3", "4.mp3" };
[TestMethod] [TestMethod]
@ -256,14 +257,16 @@ namespace FFMpegCore.Test
[TestMethod] [TestMethod]
public void Builder_BuildString_Seek() public void Builder_BuildString_Seek()
{ {
var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))).OutputToFile("output.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))).Arguments; var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10)))
.OutputToFile("output.mp4", false, opt => opt.Seek(TimeSpan.FromSeconds(10))).Arguments;
Assert.AreEqual("-ss 00:00:10.000 -i \"input.mp4\" -ss 00:00:10.000 \"output.mp4\"", str); Assert.AreEqual("-ss 00:00:10.000 -i \"input.mp4\" -ss 00:00:10.000 \"output.mp4\"", str);
} }
[TestMethod] [TestMethod]
public void Builder_BuildString_EndSeek() public void Builder_BuildString_EndSeek()
{ {
var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.EndSeek(TimeSpan.FromSeconds(10))).OutputToFile("output.mp4", false, opt => opt.EndSeek(TimeSpan.FromSeconds(10))).Arguments; var str = FFMpegArguments.FromFileInput("input.mp4", false, opt => opt.EndSeek(TimeSpan.FromSeconds(10)))
.OutputToFile("output.mp4", false, opt => opt.EndSeek(TimeSpan.FromSeconds(10))).Arguments;
Assert.AreEqual("-to 00:00:10.000 -i \"input.mp4\" -to 00:00:10.000 \"output.mp4\"", str); Assert.AreEqual("-to 00:00:10.000 -i \"input.mp4\" -to 00:00:10.000 \"output.mp4\"", str);
} }
@ -338,7 +341,7 @@ namespace FFMpegCore.Test
.OutputToFile("output.mp4", false, opt => opt .OutputToFile("output.mp4", false, opt => opt
.WithVideoFilters(filterOptions => filterOptions .WithVideoFilters(filterOptions => filterOptions
.HardBurnSubtitle(SubtitleHardBurnOptions .HardBurnSubtitle(SubtitleHardBurnOptions
.Create(subtitlePath: "sample.srt") .Create("sample.srt")
.SetCharacterEncoding("UTF-8") .SetCharacterEncoding("UTF-8")
.SetOriginalSize(1366, 768) .SetOriginalSize(1366, 768)
.SetSubtitleIndex(0) .SetSubtitleIndex(0)
@ -347,7 +350,8 @@ namespace FFMpegCore.Test
.WithParameter("PrimaryColour", "&HAA00FF00"))))) .WithParameter("PrimaryColour", "&HAA00FF00")))))
.Arguments; .Arguments;
Assert.AreEqual("-i \"input.mp4\" -vf \"subtitles='sample.srt':charenc=UTF-8:original_size=1366x768:stream_index=0:force_style='FontName=DejaVu Serif\\,PrimaryColour=&HAA00FF00'\" \"output.mp4\"", Assert.AreEqual(
"-i \"input.mp4\" -vf \"subtitles='sample.srt':charenc=UTF-8:original_size=1366x768:stream_index=0:force_style='FontName=DejaVu Serif\\,PrimaryColour=&HAA00FF00'\" \"output.mp4\"",
str); str);
} }
@ -359,7 +363,7 @@ namespace FFMpegCore.Test
.OutputToFile("output.mp4", false, opt => opt .OutputToFile("output.mp4", false, opt => opt
.WithVideoFilters(filterOptions => filterOptions .WithVideoFilters(filterOptions => filterOptions
.HardBurnSubtitle(SubtitleHardBurnOptions .HardBurnSubtitle(SubtitleHardBurnOptions
.Create(subtitlePath: @"sample( \ : [ ] , ' ).srt")))) .Create(@"sample( \ : [ ] , ' ).srt"))))
.Arguments; .Arguments;
Assert.AreEqual(@"-i ""input.mp4"" -vf ""subtitles='sample( \\ \: \[ \] \, '\\\'' ).srt'"" ""output.mp4""", Assert.AreEqual(@"-i ""input.mp4"" -vf ""subtitles='sample( \\ \: \[ \] \, '\\\'' ).srt'"" ""output.mp4""",
@ -494,7 +498,7 @@ namespace FFMpegCore.Test
{ {
var str = FFMpegArguments.FromFileInput("input.aaxc", false, x => x.WithAudibleEncryptionKeys("123", "456")) var str = FFMpegArguments.FromFileInput("input.aaxc", false, x => x.WithAudibleEncryptionKeys("123", "456"))
.MapMetaData() .MapMetaData()
.OutputToFile("output.m4b", true, x => x.WithTagVersion(3).DisableChannel(Channel.Video).CopyChannel(Channel.Audio)) .OutputToFile("output.m4b", true, x => x.WithTagVersion().DisableChannel(Channel.Video).CopyChannel(Channel.Audio))
.Arguments; .Arguments;
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);
@ -578,13 +582,11 @@ namespace FFMpegCore.Test
{ {
var str = FFMpegArguments.FromFileInput("input.mp4") var str = FFMpegArguments.FromFileInput("input.mp4")
.MultiOutput(args => args .MultiOutput(args => args
.OutputToFile("output.mp4", overwrite: true, args => args.CopyChannel()) .OutputToFile("output.mp4", true, args => args.CopyChannel())
.OutputToFile("output.ts", overwrite: false, args => args.CopyChannel().ForceFormat("mpegts")) .OutputToFile("output.ts", false, args => args.CopyChannel().ForceFormat("mpegts"))
.OutputToUrl("http://server/path", options => options.ForceFormat("webm"))) .OutputToUrl("http://server/path", options => options.ForceFormat("webm")))
.Arguments; .Arguments;
Assert.AreEqual($""" Assert.AreEqual("""-i "input.mp4" -c:a copy -c:v copy "output.mp4" -y -c:a copy -c:v copy -f mpegts "output.ts" -f webm http://server/path""", str);
-i "input.mp4" -c:a copy -c:v copy "output.mp4" -y -c:a copy -c:v copy -f mpegts "output.ts" -f webm http://server/path
""", str);
} }
[TestMethod] [TestMethod]
@ -592,10 +594,10 @@ namespace FFMpegCore.Test
{ {
var str = FFMpegArguments.FromFileInput("input.mp4") var str = FFMpegArguments.FromFileInput("input.mp4")
.MultiOutput(args => args .MultiOutput(args => args
.OutputToFile("sd.mp4", overwrite: true, args => args.Resize(1200, 720)) .OutputToFile("sd.mp4", true, args => args.Resize(1200, 720))
.OutputToFile("hd.mp4", overwrite: false, args => args.Resize(1920, 1080))) .OutputToFile("hd.mp4", false, args => args.Resize(1920, 1080)))
.Arguments; .Arguments;
Assert.AreEqual($""" Assert.AreEqual("""
-i "input.mp4" -s 1200x720 "sd.mp4" -y -s 1920x1080 "hd.mp4" -i "input.mp4" -s 1200x720 "sd.mp4" -y -s 1920x1080 "hd.mp4"
""", str); """, str);
} }
@ -605,13 +607,14 @@ namespace FFMpegCore.Test
{ {
var str = FFMpegArguments.FromFileInput("input.mp4") var str = FFMpegArguments.FromFileInput("input.mp4")
.OutputToTee(args => args .OutputToTee(args => args
.OutputToFile("output.mp4", overwrite: false, args => args.WithFastStart()) .OutputToFile("output.mp4", false, args => args.WithFastStart())
.OutputToUrl("http://server/path", options => options.ForceFormat("mpegts").SelectStream(0, channel: Channel.Video))) .OutputToUrl("http://server/path", options => options.ForceFormat("mpegts").SelectStream(0, channel: Channel.Video)))
.Arguments; .Arguments;
Assert.AreEqual($""" Assert.AreEqual("""
-i "input.mp4" -f tee "[movflags=faststart]output.mp4|[f=mpegts:select=\'0:v:0\']http://server/path" -i "input.mp4" -f tee "[movflags=faststart]output.mp4|[f=mpegts:select=\'0:v:0\']http://server/path"
""", str); """, str);
} }
[TestMethod] [TestMethod]
public void Builder_BuildString_MultiInput() public void Builder_BuildString_MultiInput()
{ {
@ -620,26 +623,24 @@ namespace FFMpegCore.Test
var ffmpegArgs = $"-filter_complex \"{mixFilter}\" -map \"[final]\""; var ffmpegArgs = $"-filter_complex \"{mixFilter}\" -map \"[final]\"";
var str = FFMpegArguments var str = FFMpegArguments
.FromFileInput(_multiFiles) .FromFileInput(_multiFiles)
.OutputToFile("output.mp3", overwrite: true, options => options .OutputToFile("output.mp3", true, options => options
.WithCustomArgument(ffmpegArgs) .WithCustomArgument(ffmpegArgs)
.WithAudioCodec(AudioCodec.LibMp3Lame) // Set the audio codec to MP3 .WithAudioCodec(AudioCodec.LibMp3Lame) // Set the audio codec to MP3
.WithAudioBitrate(128) // Set the bitrate to 128kbps .WithAudioBitrate(128) // Set the bitrate to 128kbps
.WithAudioSamplingRate(48000) // Set the sample rate to 48kHz .WithAudioSamplingRate() // Set the sample rate to 48kHz
.WithoutMetadata() // Remove metadata .WithoutMetadata() // Remove metadata
.WithCustomArgument("-ac 2 -write_xing 0 -id3v2_version 0")) // Force 2 Channels .WithCustomArgument("-ac 2 -write_xing 0 -id3v2_version 0")) // Force 2 Channels
.Arguments; .Arguments;
Assert.AreEqual($"-i \"1.mp3\" -i \"2.mp3\" -i \"3.mp3\" -i \"4.mp3\" -filter_complex \"[0:0][1:0][2:0][3:0]amix=inputs=4:duration=longest:dropout_transition=1:normalize=0[final]\" -map \"[final]\" -c:a libmp3lame -b:a 128k -ar 48000 -map_metadata -1 -ac 2 -write_xing 0 -id3v2_version 0 \"output.mp3\" -y", str); Assert.AreEqual(
"-i \"1.mp3\" -i \"2.mp3\" -i \"3.mp3\" -i \"4.mp3\" -filter_complex \"[0:0][1:0][2:0][3:0]amix=inputs=4:duration=longest:dropout_transition=1:normalize=0[final]\" -map \"[final]\" -c:a libmp3lame -b:a 128k -ar 48000 -map_metadata -1 -ac 2 -write_xing 0 -id3v2_version 0 \"output.mp3\" -y",
str);
} }
[TestMethod] [TestMethod]
public void Pre_VerifyExists_AllFilesExist() public void Pre_VerifyExists_AllFilesExist()
{ {
// Arrange // Arrange
var filePaths = new List<string> var filePaths = new List<string> { Path.GetTempFileName(), Path.GetTempFileName(), Path.GetTempFileName() };
{
Path.GetTempFileName(),
Path.GetTempFileName(),
Path.GetTempFileName()
};
var argument = new MultiInputArgument(true, filePaths); var argument = new MultiInputArgument(true, filePaths);
try try
{ {
@ -660,17 +661,12 @@ namespace FFMpegCore.Test
public void Pre_VerifyExists_SomeFilesNotExist() public void Pre_VerifyExists_SomeFilesNotExist()
{ {
// Arrange // Arrange
var filePaths = new List<string> var filePaths = new List<string> { Path.GetTempFileName(), "file2.mp4", "file3.mp4" };
{
Path.GetTempFileName(),
"file2.mp4",
"file3.mp4"
};
var argument = new MultiInputArgument(true, filePaths); var argument = new MultiInputArgument(true, filePaths);
try try
{ {
// Act & Assert // Act & Assert
Assert.ThrowsException<FileNotFoundException>(() => argument.Pre()); Assert.ThrowsExactly<FileNotFoundException>(() => argument.Pre());
} }
finally finally
{ {
@ -683,15 +679,44 @@ namespace FFMpegCore.Test
public void Pre_VerifyExists_NoFilesExist() public void Pre_VerifyExists_NoFilesExist()
{ {
// Arrange // Arrange
var filePaths = new List<string> var filePaths = new List<string> { "file1.mp4", "file2.mp4", "file3.mp4" };
{
"file1.mp4",
"file2.mp4",
"file3.mp4"
};
var argument = new MultiInputArgument(true, filePaths); var argument = new MultiInputArgument(true, filePaths);
// Act & Assert // Act & Assert
Assert.ThrowsException<FileNotFoundException>(() => argument.Pre()); Assert.ThrowsExactly<FileNotFoundException>(() => argument.Pre());
} }
[TestMethod]
public void Concat_Escape()
{
var arg = new DemuxConcatArgument([@"Heaven's River\05 - Investigation.m4b"]);
CollectionAssert.AreEquivalent(new[] { @"file 'Heaven'\''s River\05 - Investigation.m4b'" }, arg.Values.ToArray());
}
[TestMethod]
public void Audible_Aaxc_Test()
{
var arg = new AudibleEncryptionKeyArgument("123", "456");
Assert.AreEqual("-audible_key 123 -audible_iv 456", arg.Text);
}
[TestMethod]
public void Audible_Aax_Test()
{
var arg = new AudibleEncryptionKeyArgument("62689101");
Assert.AreEqual("-activation_bytes 62689101", arg.Text);
}
[TestMethod]
public void InputPipe_MaxLength_ShorterThanMacOSMax()
{
var pipePath = new InputPipeArgument(new StreamPipeSource(Stream.Null)).PipePath;
Assert.IsLessThan(104, pipePath.Length);
}
[TestMethod]
public void OutputPipe_MaxLength_ShorterThanMacOSMax()
{
var pipePath = new OutputPipeArgument(new StreamPipeSink(Stream.Null)).PipePath;
Assert.IsLessThan(_macOsMaxPipePathLength, pipePath.Length);
} }
} }

View file

@ -0,0 +1 @@
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]

View file

@ -3,10 +3,9 @@ using FFMpegCore.Exceptions;
using FFMpegCore.Extend; using FFMpegCore.Extend;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
using FFMpegCore.Test.Resources; using FFMpegCore.Test.Resources;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FFMpegCore.Test namespace FFMpegCore.Test;
{
[TestClass] [TestClass]
public class AudioTest public class AudioTest
{ {
@ -18,8 +17,8 @@ namespace FFMpegCore.Test
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.IsNotEmpty(analysis.VideoStreams);
Assert.IsTrue(!analysis.AudioStreams.Any()); Assert.IsEmpty(analysis.AudioStreams);
} }
[TestMethod] [TestMethod]
@ -30,9 +29,10 @@ namespace FFMpegCore.Test
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.IsNotEmpty(analysis.AudioStreams);
Assert.IsTrue(analysis.AudioStreams.Any()); Assert.IsEmpty(analysis.VideoStreams);
} }
[TestMethod] [TestMethod]
public async Task Audio_FromRaw() public async Task Audio_FromRaw()
{ {
@ -65,27 +65,19 @@ namespace FFMpegCore.Test
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
FFMpeg.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile); FFMpeg.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile);
var analysis = FFProbe.Analyse(TestResources.Mp3Audio); var analysis = FFProbe.Analyse(TestResources.Mp3Audio);
Assert.IsTrue(analysis.Duration.TotalSeconds > 0); Assert.IsGreaterThan(0, analysis.Duration.TotalSeconds);
Assert.IsTrue(File.Exists(outputFile)); Assert.IsTrue(File.Exists(outputFile));
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_ToAAC_Args_Pipe() public void Audio_ToAAC_Args_Pipe()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var samples = new List<IAudioSample> var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };
{
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
};
var audioSamplesSource = new RawAudioPipeSource(samples) var audioSamplesSource = new RawAudioPipeSource(samples) { Channels = 2, Format = "s8", SampleRate = 8000 };
{
Channels = 2,
Format = "s8",
SampleRate = 8000,
};
var success = FFMpegArguments var success = FFMpegArguments
.FromPipeInput(audioSamplesSource) .FromPipeInput(audioSamplesSource)
@ -95,23 +87,15 @@ namespace FFMpegCore.Test
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_ToLibVorbis_Args_Pipe() public void Audio_ToLibVorbis_Args_Pipe()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var samples = new List<IAudioSample> var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };
{
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
};
var audioSamplesSource = new RawAudioPipeSource(samples) var audioSamplesSource = new RawAudioPipeSource(samples) { Channels = 2, Format = "s8", SampleRate = 8000 };
{
Channels = 2,
Format = "s8",
SampleRate = 8000,
};
var success = FFMpegArguments var success = FFMpegArguments
.FromPipeInput(audioSamplesSource) .FromPipeInput(audioSamplesSource)
@ -121,23 +105,15 @@ namespace FFMpegCore.Test
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public async Task Audio_ToAAC_Args_Pipe_Async() public async Task Audio_ToAAC_Args_Pipe_Async()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var samples = new List<IAudioSample> var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };
{
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
};
var audioSamplesSource = new RawAudioPipeSource(samples) var audioSamplesSource = new RawAudioPipeSource(samples) { Channels = 2, Format = "s8", SampleRate = 8000 };
{
Channels = 2,
Format = "s8",
SampleRate = 8000,
};
var success = await FFMpegArguments var success = await FFMpegArguments
.FromPipeInput(audioSamplesSource) .FromPipeInput(audioSamplesSource)
@ -147,16 +123,13 @@ namespace FFMpegCore.Test
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_ToAAC_Args_Pipe_ValidDefaultConfiguration() public void Audio_ToAAC_Args_Pipe_ValidDefaultConfiguration()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var samples = new List<IAudioSample> var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };
{
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
};
var audioSamplesSource = new RawAudioPipeSource(samples); var audioSamplesSource = new RawAudioPipeSource(samples);
@ -168,58 +141,53 @@ namespace FFMpegCore.Test
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_ToAAC_Args_Pipe_InvalidChannels() public void Audio_ToAAC_Args_Pipe_InvalidChannels()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) { Channels = 0 };
{
Channels = 0,
};
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
.FromPipeInput(audioSamplesSource) .FromPipeInput(audioSamplesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithAudioCodec(AudioCodec.Aac)) .WithAudioCodec(AudioCodec.Aac))
.ProcessSynchronously()); .ProcessSynchronously());
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_ToAAC_Args_Pipe_InvalidFormat() public void Audio_ToAAC_Args_Pipe_InvalidFormat()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) { Format = "s8le" };
{
Format = "s8le",
};
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
.FromPipeInput(audioSamplesSource) .FromPipeInput(audioSamplesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithAudioCodec(AudioCodec.Aac)) .WithAudioCodec(AudioCodec.Aac))
.ProcessSynchronously()); .ProcessSynchronously());
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_ToAAC_Args_Pipe_InvalidSampleRate() public void Audio_ToAAC_Args_Pipe_InvalidSampleRate()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) { SampleRate = 0 };
{
SampleRate = 0,
};
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
.FromPipeInput(audioSamplesSource) .FromPipeInput(audioSamplesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithAudioCodec(AudioCodec.Aac)) .WithAudioCodec(AudioCodec.Aac))
.ProcessSynchronously()); .ProcessSynchronously());
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_Pan_ToMono() public void Audio_Pan_ToMono()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
@ -233,11 +201,12 @@ namespace FFMpegCore.Test
var mediaAnalysis = FFProbe.Analyse(outputFile); var mediaAnalysis = FFProbe.Analyse(outputFile);
Assert.IsTrue(success); Assert.IsTrue(success);
Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count); Assert.HasCount(1, mediaAnalysis.AudioStreams);
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout); Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_Pan_ToMonoNoDefinitions() public void Audio_Pan_ToMonoNoDefinitions()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
@ -251,35 +220,38 @@ namespace FFMpegCore.Test
var mediaAnalysis = FFProbe.Analyse(outputFile); var mediaAnalysis = FFProbe.Analyse(outputFile);
Assert.IsTrue(success); Assert.IsTrue(success);
Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count); Assert.HasCount(1, mediaAnalysis.AudioStreams);
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout); Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_Pan_ToMonoChannelsToOutputDefinitionsMismatch() public void Audio_Pan_ToMonoChannelsToOutputDefinitionsMismatch()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var ex = Assert.ThrowsException<ArgumentException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio) Assert.ThrowsExactly<ArgumentException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
.OutputToFile(outputFile, true, .OutputToFile(outputFile, true,
argumentOptions => argumentOptions argumentOptions => argumentOptions
.WithAudioFilters(filter => filter.Pan(1, "c0=c0", "c1=c1"))) .WithAudioFilters(filter => filter.Pan(1, "c0=c0", "c1=c1")))
.ProcessSynchronously()); .ProcessSynchronously());
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_Pan_ToMonoChannelsLayoutToOutputDefinitionsMismatch() public void Audio_Pan_ToMonoChannelsLayoutToOutputDefinitionsMismatch()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio) Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
.OutputToFile(outputFile, true, .OutputToFile(outputFile, true,
argumentOptions => argumentOptions argumentOptions => argumentOptions
.WithAudioFilters(filter => filter.Pan("mono", "c0=c0", "c1=c1"))) .WithAudioFilters(filter => filter.Pan("mono", "c0=c0", "c1=c1")))
.ProcessSynchronously()); .ProcessSynchronously());
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_DynamicNormalizer_WithDefaultValues() public void Audio_DynamicNormalizer_WithDefaultValues()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
@ -293,7 +265,8 @@ namespace FFMpegCore.Test
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Audio_DynamicNormalizer_WithNonDefaultValues() public void Audio_DynamicNormalizer_WithNonDefaultValues()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
@ -301,14 +274,14 @@ namespace FFMpegCore.Test
var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio) var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
.OutputToFile(outputFile, true, .OutputToFile(outputFile, true,
argumentOptions => argumentOptions argumentOptions => argumentOptions
.WithAudioFilters( .WithAudioFilters(filter => filter.DynamicNormalizer(250, 7, 0.9, 2, 1, false, true, true, 0.5)))
filter => filter.DynamicNormalizer(250, 7, 0.9, 2, 1, false, true, true, 0.5)))
.ProcessSynchronously(); .ProcessSynchronously();
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[DataTestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
[DataRow(2)] [DataRow(2)]
[DataRow(32)] [DataRow(32)]
[DataRow(8)] [DataRow(8)]
@ -316,13 +289,11 @@ namespace FFMpegCore.Test
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
var ex = Assert.ThrowsException<ArgumentOutOfRangeException>(() => FFMpegArguments Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => FFMpegArguments
.FromFileInput(TestResources.Mp3Audio) .FromFileInput(TestResources.Mp3Audio)
.OutputToFile(outputFile, true, .OutputToFile(outputFile, true,
argumentOptions => argumentOptions argumentOptions => argumentOptions
.WithAudioFilters( .WithAudioFilters(filter => filter.DynamicNormalizer(filterWindow: filterWindow)))
filter => filter.DynamicNormalizer(filterWindow: filterWindow)))
.ProcessSynchronously()); .ProcessSynchronously());
} }
} }
}

View file

@ -0,0 +1,53 @@
using FFMpegCore.Extensions.Downloader;
using FFMpegCore.Extensions.Downloader.Enums;
using FFMpegCore.Test.Utilities;
namespace FFMpegCore.Test;
[TestClass]
public class DownloaderTests
{
private FFOptions _ffOptions;
[TestInitialize]
public void InitializeTestFolder()
{
var tempDownloadFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempDownloadFolder);
_ffOptions = new FFOptions { BinaryFolder = tempDownloadFolder };
}
[TestCleanup]
public void DeleteTestFolder()
{
Directory.Delete(_ffOptions.BinaryFolder, true);
}
[OsSpecificTestMethod(OsPlatforms.Windows | OsPlatforms.Linux)]
public async Task GetSpecificVersionTest()
{
var binaries = await FFMpegDownloader.DownloadBinaries(FFMpegVersions.V6_1, options: _ffOptions);
try
{
Assert.HasCount(2, binaries);
}
finally
{
binaries.ForEach(File.Delete);
}
}
[OsSpecificTestMethod(OsPlatforms.Windows | OsPlatforms.Linux)]
public async Task GetAllLatestSuiteTest()
{
var binaries = await FFMpegDownloader.DownloadBinaries(options: _ffOptions);
try
{
Assert.HasCount(2, binaries);
}
finally
{
binaries.ForEach(File.Delete);
}
}
}

View file

@ -1,34 +1,33 @@
using System.Reflection; namespace FFMpegCore.Test;
using FFMpegCore.Arguments;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FFMpegCore.Test
{
[TestClass] [TestClass]
public class FFMpegArgumentProcessorTest public class FFMpegArgumentProcessorTest
{ {
[TestCleanup] private static FFMpegArgumentProcessor CreateArgumentProcessor()
public void TestInitialize()
{ {
// After testing reset global configuration to null, to be not wrong for other test relying on configuration return FFMpegArguments
typeof(GlobalFFOptions).GetField("_current", BindingFlags.NonPublic | BindingFlags.Static)!.SetValue(GlobalFFOptions.Current, null);
}
private static FFMpegArgumentProcessor CreateArgumentProcessor() => FFMpegArguments
.FromFileInput("") .FromFileInput("")
.OutputToFile(""); .OutputToFile("");
}
[TestMethod] [TestMethod]
public void Processor_GlobalOptions_GetUsed() public void ZZZ_Processor_GlobalOptions_GetUsed()
{ {
var globalWorkingDir = "Whatever"; var globalWorkingDir = "Whatever";
var processor = CreateArgumentProcessor();
try
{
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir }); GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir });
var processor = CreateArgumentProcessor(); var options = processor.GetConfiguredOptions(null);
var options2 = processor.GetConfiguredOptions(null);
options2.WorkingDirectory.Should().Be(globalWorkingDir); Assert.AreEqual(globalWorkingDir, options.WorkingDirectory);
}
finally
{
GlobalFFOptions.Configure(new FFOptions());
}
} }
[TestMethod] [TestMethod]
@ -40,64 +39,60 @@ namespace FFMpegCore.Test
processor.Configure(options => options.WorkingDirectory = sessionWorkingDir); processor.Configure(options => options.WorkingDirectory = sessionWorkingDir);
var options = processor.GetConfiguredOptions(null); var options = processor.GetConfiguredOptions(null);
options.WorkingDirectory.Should().Be(sessionWorkingDir); Assert.AreEqual(sessionWorkingDir, options.WorkingDirectory);
} }
[TestMethod] [TestMethod]
public void Processor_Options_CanBeOverridden_And_Configured() public void ZZZ_Processor_Options_CanBeOverridden_And_Configured()
{ {
var globalConfig = "Whatever"; var globalConfig = "Whatever";
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig });
try
{
var processor = CreateArgumentProcessor(); var processor = CreateArgumentProcessor();
var sessionTempDir = "./CurrentRunWorkingDir"; var sessionTempDir = "./CurrentRunWorkingDir";
processor.Configure(options => options.TemporaryFilesFolder = sessionTempDir); processor.Configure(options => options.TemporaryFilesFolder = sessionTempDir);
var overrideOptions = new FFOptions() { WorkingDirectory = "override" }; var overrideOptions = new FFOptions { WorkingDirectory = "override" };
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig });
var options = processor.GetConfiguredOptions(overrideOptions); var options = processor.GetConfiguredOptions(overrideOptions);
options.Should().BeEquivalentTo(overrideOptions); Assert.AreEqual(options.WorkingDirectory, overrideOptions.WorkingDirectory);
options.TemporaryFilesFolder.Should().BeEquivalentTo(sessionTempDir); Assert.AreEqual(options.TemporaryFilesFolder, overrideOptions.TemporaryFilesFolder);
options.BinaryFolder.Should().NotBeEquivalentTo(globalConfig); Assert.AreEqual(options.BinaryFolder, overrideOptions.BinaryFolder);
Assert.AreEqual(sessionTempDir, options.TemporaryFilesFolder);
Assert.AreNotEqual(globalConfig, options.BinaryFolder);
}
finally
{
GlobalFFOptions.Configure(new FFOptions());
}
} }
[TestMethod] [TestMethod]
public void Options_Global_And_Session_Options_Can_Differ() public void ZZZ_Options_Global_And_Session_Options_Can_Differ()
{ {
var globalWorkingDir = "Whatever"; var globalWorkingDir = "Whatever";
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir });
try
{
var processor1 = CreateArgumentProcessor(); var processor1 = CreateArgumentProcessor();
var sessionWorkingDir = "./CurrentRunWorkingDir"; var sessionWorkingDir = "./CurrentRunWorkingDir";
processor1.Configure(options => options.WorkingDirectory = sessionWorkingDir); processor1.Configure(options => options.WorkingDirectory = sessionWorkingDir);
var options1 = processor1.GetConfiguredOptions(null); var options1 = processor1.GetConfiguredOptions(null);
options1.WorkingDirectory.Should().Be(sessionWorkingDir); Assert.AreEqual(sessionWorkingDir, options1.WorkingDirectory);
var processor2 = CreateArgumentProcessor(); var processor2 = CreateArgumentProcessor();
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir });
var options2 = processor2.GetConfiguredOptions(null); var options2 = processor2.GetConfiguredOptions(null);
options2.WorkingDirectory.Should().Be(globalWorkingDir); Assert.AreEqual(globalWorkingDir, options2.WorkingDirectory);
} }
finally
[TestMethod]
public void Concat_Escape()
{ {
var arg = new DemuxConcatArgument(new[] { @"Heaven's River\05 - Investigation.m4b" }); GlobalFFOptions.Configure(new FFOptions());
arg.Values.Should().BeEquivalentTo(new[] { @"file 'Heaven'\''s River\05 - Investigation.m4b'" });
}
[TestMethod]
public void Audible_Aaxc_Test()
{
var arg = new AudibleEncryptionKeyArgument("123", "456");
arg.Text.Should().Be($"-audible_key 123 -audible_iv 456");
}
[TestMethod]
public void Audible_Aax_Test()
{
var arg = new AudibleEncryptionKeyArgument("62689101");
arg.Text.Should().Be($"-activation_bytes 62689101");
} }
} }
} }

View file

@ -5,6 +5,7 @@
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<Nullable>disable</Nullable> <Nullable>disable</Nullable>
<LangVersion>default</LangVersion> <LangVersion>default</LangVersion>
<OrderTestsByNameInClass>true</OrderTestsByNameInClass>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -12,18 +13,17 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="FluentAssertions" Version="8.0.1" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.4.1"> <PackageReference Include="GitHubActionsTestLogger" Version="2.4.1">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0"/>
<PackageReference Include="MSTest.TestAdapter" Version="3.8.0" /> <PackageReference Include="MSTest.TestAdapter" Version="4.0.1"/>
<PackageReference Include="MSTest.TestFramework" Version="3.8.0" /> <PackageReference Include="MSTest.TestFramework" Version="4.0.1"/>
<PackageReference Include="SkiaSharp" Version="3.116.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FFMpegCore.Extensions.Downloader\FFMpegCore.Extensions.Downloader.csproj"/>
<ProjectReference Include="..\FFMpegCore.Extensions.SkiaSharp\FFMpegCore.Extensions.SkiaSharp.csproj"/> <ProjectReference Include="..\FFMpegCore.Extensions.SkiaSharp\FFMpegCore.Extensions.SkiaSharp.csproj"/>
<ProjectReference Include="..\FFMpegCore.Extensions.System.Drawing.Common\FFMpegCore.Extensions.System.Drawing.Common.csproj"/> <ProjectReference Include="..\FFMpegCore.Extensions.System.Drawing.Common\FFMpegCore.Extensions.System.Drawing.Common.csproj"/>
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj"/> <ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj"/>

View file

@ -1,8 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json;
using Newtonsoft.Json;
namespace FFMpegCore.Test;
namespace FFMpegCore.Test
{
[TestClass] [TestClass]
public class FFMpegOptionsTest public class FFMpegOptionsTest
{ {
@ -15,7 +14,7 @@ namespace FFMpegCore.Test
[TestMethod] [TestMethod]
public void Options_Defaults_Configured() public void Options_Defaults_Configured()
{ {
Assert.AreEqual(new FFOptions().BinaryFolder, $""); Assert.AreEqual("", new FFOptions().BinaryFolder);
} }
[TestMethod] [TestMethod]
@ -28,21 +27,16 @@ namespace FFMpegCore.Test
} }
[TestMethod] [TestMethod]
public void Options_Set_Programmatically() public void ZZZ_Options_Set_Programmatically()
{ {
var original = GlobalFFOptions.Current;
try try
{ {
GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "Whatever" }); GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "Whatever" });
Assert.AreEqual( Assert.AreEqual("Whatever", GlobalFFOptions.Current.BinaryFolder);
GlobalFFOptions.Current.BinaryFolder,
"Whatever"
);
} }
finally finally
{ {
GlobalFFOptions.Configure(original); GlobalFFOptions.Configure(new FFOptions());
}
} }
} }
} }

View file

@ -1,17 +1,18 @@
using FFMpegCore.Test.Resources; using FFMpegCore.Test.Resources;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FFMpegCore.Test namespace FFMpegCore.Test;
{
[TestClass] [TestClass]
public class FFProbeTests public class FFProbeTests
{ {
public TestContext TestContext { get; set; }
[TestMethod] [TestMethod]
public async Task Audio_FromStream_Duration() public async Task Audio_FromStream_Duration()
{ {
var fileAnalysis = await FFProbe.AnalyseAsync(TestResources.WebmVideo); var fileAnalysis = await FFProbe.AnalyseAsync(TestResources.WebmVideo, cancellationToken: TestContext.CancellationToken);
await using var inputStream = File.OpenRead(TestResources.WebmVideo); await using var inputStream = File.OpenRead(TestResources.WebmVideo);
var streamAnalysis = await FFProbe.AnalyseAsync(inputStream); var streamAnalysis = await FFProbe.AnalyseAsync(inputStream, cancellationToken: TestContext.CancellationToken);
Assert.IsTrue(fileAnalysis.Duration == streamAnalysis.Duration); Assert.IsTrue(fileAnalysis.Duration == streamAnalysis.Duration);
} }
@ -20,7 +21,7 @@ namespace FFMpegCore.Test
{ {
var frameAnalysis = FFProbe.GetFrames(TestResources.WebmVideo); var frameAnalysis = FFProbe.GetFrames(TestResources.WebmVideo);
Assert.AreEqual(90, frameAnalysis.Frames.Count); Assert.HasCount(90, frameAnalysis.Frames);
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));
Assert.IsTrue(frameAnalysis.Frames.All(f => f.Width == 640)); Assert.IsTrue(frameAnalysis.Frames.All(f => f.Width == 640));
@ -30,9 +31,9 @@ namespace FFMpegCore.Test
[TestMethod] [TestMethod]
public async Task FrameAnalysis_Async() public async Task FrameAnalysis_Async()
{ {
var frameAnalysis = await FFProbe.GetFramesAsync(TestResources.WebmVideo); var frameAnalysis = await FFProbe.GetFramesAsync(TestResources.WebmVideo, cancellationToken: TestContext.CancellationToken);
Assert.AreEqual(90, frameAnalysis.Frames.Count); Assert.HasCount(90, frameAnalysis.Frames);
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));
Assert.IsTrue(frameAnalysis.Frames.All(f => f.Width == 640)); Assert.IsTrue(frameAnalysis.Frames.All(f => f.Width == 640));
@ -42,11 +43,11 @@ namespace FFMpegCore.Test
[TestMethod] [TestMethod]
public async Task PacketAnalysis_Async() public async Task PacketAnalysis_Async()
{ {
var packetAnalysis = await FFProbe.GetPacketsAsync(TestResources.WebmVideo); var packetAnalysis = await FFProbe.GetPacketsAsync(TestResources.WebmVideo, cancellationToken: TestContext.CancellationToken);
var packets = packetAnalysis.Packets; var packets = packetAnalysis.Packets;
Assert.AreEqual(96, packets.Count); Assert.HasCount(96, packets);
Assert.IsTrue(packets.All(f => f.CodecType == "video")); Assert.IsTrue(packets.All(f => f.CodecType == "video"));
Assert.IsTrue(packets[0].Flags.StartsWith("K_")); Assert.StartsWith("K_", packets[0].Flags);
Assert.AreEqual(1362, packets.Last().Size); Assert.AreEqual(1362, packets.Last().Size);
} }
@ -55,9 +56,9 @@ namespace FFMpegCore.Test
{ {
var packets = FFProbe.GetPackets(TestResources.WebmVideo).Packets; var packets = FFProbe.GetPackets(TestResources.WebmVideo).Packets;
Assert.AreEqual(96, packets.Count); Assert.HasCount(96, packets);
Assert.IsTrue(packets.All(f => f.CodecType == "video")); Assert.IsTrue(packets.All(f => f.CodecType == "video"));
Assert.IsTrue(packets[0].Flags.StartsWith("K_")); Assert.StartsWith("K_", packets[0].Flags);
Assert.AreEqual(1362, packets.Last().Size); Assert.AreEqual(1362, packets.Last().Size);
} }
@ -66,7 +67,7 @@ namespace FFMpegCore.Test
{ {
var packets = FFProbe.GetPackets(TestResources.Mp4Video).Packets; var packets = FFProbe.GetPackets(TestResources.Mp4Video).Packets;
Assert.AreEqual(216, packets.Count); Assert.HasCount(216, packets);
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);
@ -75,12 +76,13 @@ namespace FFMpegCore.Test
Assert.AreEqual(141, packets.Count(t => t.CodecType == "audio")); Assert.AreEqual(141, packets.Count(t => t.CodecType == "audio"));
} }
[DataTestMethod] [TestMethod]
[DataRow("0:00:03.008000", 0, 0, 0, 3, 8)] [DataRow("0:00:03.008000", 0, 0, 0, 3, 8)]
[DataRow("05:12:59.177", 0, 5, 12, 59, 177)] [DataRow("05:12:59.177", 0, 5, 12, 59, 177)]
[DataRow("149:07:50.911750", 6, 5, 7, 50, 911)] [DataRow("149:07:50.911750", 6, 5, 7, 50, 911)]
[DataRow("00:00:00.83", 0, 0, 0, 0, 830)] [DataRow("00:00:00.83", 0, 0, 0, 0, 830)]
public void MediaAnalysis_ParseDuration(string duration, int expectedDays, int expectedHours, int expectedMinutes, int expectedSeconds, int expectedMilliseconds) public void MediaAnalysis_ParseDuration(string duration, int expectedDays, int expectedHours, int expectedMinutes, int expectedSeconds,
int expectedMilliseconds)
{ {
var ffprobeStream = new FFProbeStream { Duration = duration }; var ffprobeStream = new FFProbeStream { Duration = duration };
@ -93,10 +95,12 @@ namespace FFMpegCore.Test
Assert.AreEqual(expectedMilliseconds, parsedDuration.Milliseconds); Assert.AreEqual(expectedMilliseconds, parsedDuration.Milliseconds);
} }
[TestMethod, Ignore("Consistently fails on GitHub Workflow ubuntu agents")] [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"),
cancellationToken: TestContext.CancellationToken);
Assert.IsNotNull(fileAnalysis); Assert.IsNotNull(fileAnalysis);
} }
@ -105,7 +109,7 @@ namespace FFMpegCore.Test
{ {
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(0, info.Chapters.Count); Assert.IsEmpty(info.Chapters);
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);
@ -140,9 +144,11 @@ namespace FFMpegCore.Test
public void Probe_Rotation() public void Probe_Rotation()
{ {
var info = FFProbe.Analyse(TestResources.Mp4Video); var info = FFProbe.Analyse(TestResources.Mp4Video);
Assert.IsNotNull(info.PrimaryVideoStream);
Assert.AreEqual(0, info.PrimaryVideoStream.Rotation); Assert.AreEqual(0, info.PrimaryVideoStream.Rotation);
info = FFProbe.Analyse(TestResources.Mp4VideoRotation); info = FFProbe.Analyse(TestResources.Mp4VideoRotation);
Assert.IsNotNull(info.PrimaryVideoStream);
Assert.AreEqual(90, info.PrimaryVideoStream.Rotation); Assert.AreEqual(90, info.PrimaryVideoStream.Rotation);
} }
@ -150,20 +156,25 @@ namespace FFMpegCore.Test
public void Probe_Rotation_Negative_Value() public void Probe_Rotation_Negative_Value()
{ {
var info = FFProbe.Analyse(TestResources.Mp4VideoRotationNegative); var info = FFProbe.Analyse(TestResources.Mp4VideoRotationNegative);
Assert.IsNotNull(info.PrimaryVideoStream);
Assert.AreEqual(-90, info.PrimaryVideoStream.Rotation); Assert.AreEqual(-90, info.PrimaryVideoStream.Rotation);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
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, cancellationToken: TestContext.CancellationToken);
Assert.AreEqual(3, info.Duration.Seconds); Assert.AreEqual(3, info.Duration.Seconds);
Assert.IsNotNull(info.PrimaryVideoStream);
Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth); Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth);
// This video's audio stream is AAC, which is lossy, so bit depth is meaningless. // This video's audio stream is AAC, which is lossy, so bit depth is meaningless.
Assert.IsNotNull(info.PrimaryAudioStream);
Assert.IsNull(info.PrimaryAudioStream.BitDepth); Assert.IsNull(info.PrimaryAudioStream.BitDepth);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Probe_Success_FromStream() public void Probe_Success_FromStream()
{ {
using var stream = File.OpenRead(TestResources.WebmVideo); using var stream = File.OpenRead(TestResources.WebmVideo);
@ -173,15 +184,17 @@ namespace FFMpegCore.Test
Assert.IsNull(info.PrimaryAudioStream); Assert.IsNull(info.PrimaryAudioStream);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public async Task Probe_Success_FromStream_Async() public async Task Probe_Success_FromStream_Async()
{ {
await using var stream = File.OpenRead(TestResources.WebmVideo); await using var stream = File.OpenRead(TestResources.WebmVideo);
var info = await FFProbe.AnalyseAsync(stream); var info = await FFProbe.AnalyseAsync(stream, cancellationToken: TestContext.CancellationToken);
Assert.AreEqual(3, info.Duration.Seconds); Assert.AreEqual(3, info.Duration.Seconds);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public void Probe_HDR() public void Probe_HDR()
{ {
var info = FFProbe.Analyse(TestResources.HdrVideo); var info = FFProbe.Analyse(TestResources.HdrVideo);
@ -193,66 +206,75 @@ namespace FFMpegCore.Test
Assert.AreEqual("bt2020", info.PrimaryVideoStream.ColorPrimaries); Assert.AreEqual("bt2020", info.PrimaryVideoStream.ColorPrimaries);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public async Task Probe_Success_Subtitle_Async() public async Task Probe_Success_Subtitle_Async()
{ {
var info = await FFProbe.AnalyseAsync(TestResources.SrtSubtitle); var info = await FFProbe.AnalyseAsync(TestResources.SrtSubtitle, cancellationToken: TestContext.CancellationToken);
Assert.IsNotNull(info.PrimarySubtitleStream); Assert.IsNotNull(info.PrimarySubtitleStream);
Assert.AreEqual(1, info.SubtitleStreams.Count); Assert.HasCount(1, info.SubtitleStreams);
Assert.AreEqual(0, info.AudioStreams.Count); Assert.IsEmpty(info.AudioStreams);
Assert.AreEqual(0, info.VideoStreams.Count); Assert.IsEmpty(info.VideoStreams);
// BitDepth is meaningless for subtitles // BitDepth is meaningless for subtitles
Assert.IsNull(info.SubtitleStreams[0].BitDepth); Assert.IsNull(info.SubtitleStreams[0].BitDepth);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public async Task Probe_Success_Disposition_Async() public async Task Probe_Success_Disposition_Async()
{ {
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video); var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video, cancellationToken: TestContext.CancellationToken);
Assert.IsNotNull(info.PrimaryAudioStream); Assert.IsNotNull(info.PrimaryAudioStream);
Assert.IsNotNull(info.PrimaryAudioStream.Disposition); Assert.IsNotNull(info.PrimaryAudioStream.Disposition);
Assert.AreEqual(true, info.PrimaryAudioStream.Disposition["default"]); Assert.IsTrue(info.PrimaryAudioStream.Disposition["default"]);
Assert.AreEqual(false, info.PrimaryAudioStream.Disposition["forced"]); Assert.IsFalse(info.PrimaryAudioStream.Disposition["forced"]);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public async Task Probe_Success_Mp3AudioBitDepthNull_Async() public async Task Probe_Success_Mp3AudioBitDepthNull_Async()
{ {
var info = await FFProbe.AnalyseAsync(TestResources.Mp3Audio); var info = await FFProbe.AnalyseAsync(TestResources.Mp3Audio, cancellationToken: TestContext.CancellationToken);
Assert.IsNotNull(info.PrimaryAudioStream); Assert.IsNotNull(info.PrimaryAudioStream);
// mp3 is lossy, so bit depth is meaningless. // mp3 is lossy, so bit depth is meaningless.
Assert.IsNull(info.PrimaryAudioStream.BitDepth); Assert.IsNull(info.PrimaryAudioStream.BitDepth);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public async Task Probe_Success_VocAudioBitDepth_Async() public async Task Probe_Success_VocAudioBitDepth_Async()
{ {
var info = await FFProbe.AnalyseAsync(TestResources.AiffAudio); var info = await FFProbe.AnalyseAsync(TestResources.AiffAudio, cancellationToken: TestContext.CancellationToken);
Assert.IsNotNull(info.PrimaryAudioStream); Assert.IsNotNull(info.PrimaryAudioStream);
Assert.AreEqual(16, info.PrimaryAudioStream.BitDepth); Assert.AreEqual(16, info.PrimaryAudioStream.BitDepth);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public async Task Probe_Success_MkvVideoBitDepth_Async() public async Task Probe_Success_MkvVideoBitDepth_Async()
{ {
var info = await FFProbe.AnalyseAsync(TestResources.MkvVideo); var info = await FFProbe.AnalyseAsync(TestResources.MkvVideo, cancellationToken: TestContext.CancellationToken);
Assert.IsNotNull(info.PrimaryAudioStream); Assert.IsNotNull(info.PrimaryVideoStream);
Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth); Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth);
Assert.IsNotNull(info.PrimaryAudioStream);
Assert.IsNull(info.PrimaryAudioStream.BitDepth); Assert.IsNull(info.PrimaryAudioStream.BitDepth);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public async Task Probe_Success_24BitWavBitDepth_Async() public async Task Probe_Success_24BitWavBitDepth_Async()
{ {
var info = await FFProbe.AnalyseAsync(TestResources.Wav24Bit); var info = await FFProbe.AnalyseAsync(TestResources.Wav24Bit, cancellationToken: TestContext.CancellationToken);
Assert.IsNotNull(info.PrimaryAudioStream); Assert.IsNotNull(info.PrimaryAudioStream);
Assert.AreEqual(24, info.PrimaryAudioStream.BitDepth); Assert.AreEqual(24, info.PrimaryAudioStream.BitDepth);
} }
[TestMethod, Timeout(10000)] [TestMethod]
[Timeout(10000, CooperativeCancellation = true)]
public async Task Probe_Success_32BitWavBitDepth_Async() public async Task Probe_Success_32BitWavBitDepth_Async()
{ {
var info = await FFProbe.AnalyseAsync(TestResources.Wav32Bit); var info = await FFProbe.AnalyseAsync(TestResources.Wav32Bit, cancellationToken: TestContext.CancellationToken);
Assert.IsNotNull(info.PrimaryAudioStream); Assert.IsNotNull(info.PrimaryAudioStream);
Assert.AreEqual(32, info.PrimaryAudioStream.BitDepth); Assert.AreEqual(32, info.PrimaryAudioStream.BitDepth);
} }
@ -264,4 +286,3 @@ namespace FFMpegCore.Test
Assert.AreEqual(3, info.Duration.Seconds); Assert.AreEqual(3, info.Duration.Seconds);
} }
} }
}

View file

@ -1,9 +1,8 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FFMpegCore.Builders.MetaData; using FFMpegCore.Builders.MetaData;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FFMpegCore.Test namespace FFMpegCore.Test;
{
[TestClass] [TestClass]
public class MetaDataBuilderTests public class MetaDataBuilderTests
{ {
@ -20,10 +19,8 @@ namespace FFMpegCore.Test
Genres = new[] { "Synthwave", "Classics" }, Genres = new[] { "Synthwave", "Classics" },
Tracks = new[] Tracks = new[]
{ {
new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 01" }, new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 01" }, new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 02" },
new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 02" }, new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 03" }, new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 04" }
new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 03" },
new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 04" },
} }
}; };
@ -67,4 +64,3 @@ namespace FFMpegCore.Test
Assert.IsTrue(Regex.IsMatch(text1, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 2"), "map_metadata index is calculated incorrectly."); Assert.IsTrue(Regex.IsMatch(text1, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 2"), "map_metadata index is calculated incorrectly.");
} }
} }
}

View file

@ -1,8 +1,7 @@
using FFMpegCore.Exceptions; using FFMpegCore.Exceptions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FFMpegCore.Test namespace FFMpegCore.Test;
{
[TestClass] [TestClass]
public class PixelFormatTests public class PixelFormatTests
{ {
@ -10,7 +9,7 @@ namespace FFMpegCore.Test
public void PixelFormats_Enumerate() public void PixelFormats_Enumerate()
{ {
var formats = FFMpeg.GetPixelFormats(); var formats = FFMpeg.GetPixelFormats();
Assert.IsTrue(formats.Count > 0); Assert.IsNotEmpty(formats);
} }
[TestMethod] [TestMethod]
@ -35,7 +34,6 @@ namespace FFMpegCore.Test
[TestMethod] [TestMethod]
public void PixelFormats_GetNotExisting() public void PixelFormats_GetNotExisting()
{ {
Assert.ThrowsException<FFMpegException>(() => FFMpeg.GetPixelFormat("yuv420pppUnknown")); Assert.ThrowsExactly<FFMpegException>(() => FFMpeg.GetPixelFormat("yuv420pppUnknown"));
}
} }
} }

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Test.Resources namespace FFMpegCore.Test.Resources;
{
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";
@ -19,4 +19,3 @@
public static readonly string Wav24Bit = "./Resources/24_bit_fixed.WAV"; public static readonly string Wav24Bit = "./Resources/24_bit_fixed.WAV";
public static readonly string Wav32Bit = "./Resources/32_bit_float.WAV"; public static readonly string Wav32Bit = "./Resources/32_bit_float.WAV";
} }
}

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Test namespace FFMpegCore.Test;
{
public class TemporaryFile : IDisposable public class TemporaryFile : IDisposable
{ {
private readonly string _path; private readonly string _path;
@ -9,7 +9,6 @@
_path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}-{filename}"); _path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}-{filename}");
} }
public static implicit operator string(TemporaryFile temporaryFile) => temporaryFile._path;
public void Dispose() public void Dispose()
{ {
if (File.Exists(_path)) if (File.Exists(_path))
@ -17,5 +16,9 @@
File.Delete(_path); File.Delete(_path);
} }
} }
public static implicit operator string(TemporaryFile temporaryFile)
{
return temporaryFile._path;
} }
} }

View file

@ -2,11 +2,12 @@
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.Numerics; using System.Numerics;
using System.Runtime.Versioning; using System.Runtime.Versioning;
using FFMpegCore.Extensions.System.Drawing.Common;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
using SkiaSharp; using SkiaSharp;
namespace FFMpegCore.Test.Utilities namespace FFMpegCore.Test.Utilities;
{
internal static class BitmapSource internal static class BitmapSource
{ {
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
@ -33,7 +34,7 @@ namespace FFMpegCore.Test.Utilities
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
public static Extensions.System.Drawing.Common.BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fmt, int w, int h, float scaleNoise, float offset) public static BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fmt, int w, int h, float scaleNoise, float offset)
{ {
var bitmap = new Bitmap(w, h, fmt); var bitmap = new Bitmap(w, h, fmt);
@ -43,7 +44,7 @@ namespace FFMpegCore.Test.Utilities
bitmap.SetPixel(x, y, color); bitmap.SetPixel(x, y, color);
} }
return new Extensions.System.Drawing.Common.BitmapVideoFrameWrapper(bitmap); return new BitmapVideoFrameWrapper(bitmap);
} }
public static Extensions.SkiaSharp.BitmapVideoFrameWrapper CreateVideoFrame(int index, SKColorType fmt, int w, int h, float scaleNoise, float offset) public static Extensions.SkiaSharp.BitmapVideoFrameWrapper CreateVideoFrame(int index, SKColorType fmt, int w, int h, float scaleNoise, float offset)
@ -57,7 +58,8 @@ namespace FFMpegCore.Test.Utilities
return new Extensions.SkiaSharp.BitmapVideoFrameWrapper(bitmap); return new Extensions.SkiaSharp.BitmapVideoFrameWrapper(bitmap);
} }
private static IEnumerable<(int x, int y, byte red, byte green, byte blue)> GenerateVideoFramePixels(int index, int w, int h, float scaleNoise, float offset) private static IEnumerable<(int x, int y, byte red, byte green, byte blue)> GenerateVideoFramePixels(int index, int w, int h, float scaleNoise,
float offset)
{ {
offset = offset * index; offset = offset * index;
@ -72,7 +74,7 @@ namespace FFMpegCore.Test.Utilities
var value = (byte)((Perlin.Noise(nx, ny) + 1.0f) / 2.0f * 255); var value = (byte)((Perlin.Noise(nx, ny) + 1.0f) / 2.0f * 255);
yield return ((x, y, (byte)(value * xf), (byte)(value * yf), value)); yield return (x, y, (byte)(value * xf), (byte)(value * yf), value);
} }
} }
} }
@ -228,28 +230,23 @@ namespace FFMpegCore.Test.Utilities
{ {
var h = hash & 15; var h = hash & 15;
var u = h < 8 ? x : y; var u = h < 8 ? x : y;
var v = h < 4 ? y : (h == 12 || h == 14 ? x : z); var v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
} }
private static readonly int[] perm = { private static readonly int[] perm =
151,160,137,91,90,15, {
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, 78, 66, 215, 61, 156, 180, 151
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180,
151
}; };
#endregion #endregion
} }
} }
}

View file

@ -0,0 +1,42 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using FFMpegCore.Extensions.Downloader.Extensions;
namespace FFMpegCore.Test.Utilities;
[Flags]
internal enum OsPlatforms : ushort
{
Windows = 1,
Linux = 2,
MacOS = 4
}
internal class OsSpecificTestMethod : TestMethodAttribute
{
private readonly IEnumerable<OSPlatform> _supportedOsPlatforms;
public OsSpecificTestMethod(OsPlatforms supportedOsPlatforms, [CallerFilePath] string callerFilePath = "",
[CallerLineNumber] int callerLineNumber = -1) : base(callerFilePath, callerLineNumber)
{
_supportedOsPlatforms = supportedOsPlatforms.GetFlags()
.Select(flag => OSPlatform.Create(flag.ToString().ToUpperInvariant()))
.ToArray();
}
public override async Task<TestResult[]> ExecuteAsync(ITestMethod testMethod)
{
if (_supportedOsPlatforms.Any(RuntimeInformation.IsOSPlatform))
{
return await base.ExecuteAsync(testMethod);
}
var message = $"Test only executed on specific platforms: {string.Join(", ", _supportedOsPlatforms.Select(platform => platform.ToString()))}";
{
return
[
new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) }
];
}
}
}

View file

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

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

@ -5,19 +5,24 @@ using System.Text;
using FFMpegCore.Arguments; using FFMpegCore.Arguments;
using FFMpegCore.Enums; using FFMpegCore.Enums;
using FFMpegCore.Exceptions; using FFMpegCore.Exceptions;
using FFMpegCore.Extensions.System.Drawing.Common;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
using FFMpegCore.Test.Resources; using FFMpegCore.Test.Resources;
using FFMpegCore.Test.Utilities; using FFMpegCore.Test.Utilities;
using Microsoft.VisualStudio.TestTools.UnitTesting; using SkiaSharp;
using PixelFormat = System.Drawing.Imaging.PixelFormat;
namespace FFMpegCore.Test;
namespace FFMpegCore.Test
{
[TestClass] [TestClass]
public class VideoTest public class VideoTest
{ {
private const int BaseTimeoutMilliseconds = 15_000; private const int BaseTimeoutMilliseconds = 15_000;
[TestMethod, Timeout(BaseTimeoutMilliseconds)] public TestContext TestContext { get; set; }
[TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
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}");
@ -29,7 +34,8 @@ namespace FFMpegCore.Test
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
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}");
@ -41,7 +47,8 @@ namespace FFMpegCore.Test
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
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}");
@ -54,10 +61,11 @@ namespace FFMpegCore.Test
.ProcessSynchronously(); .ProcessSynchronously();
Assert.IsTrue(success); Assert.IsTrue(success);
var analysis = FFProbe.Analyse(outputFile); var analysis = FFProbe.Analyse(outputFile);
Assert.IsTrue(analysis.VideoStreams.First().PixelFormat == "yuv444p"); Assert.AreEqual("yuv444p", analysis.VideoStreams.First().PixelFormat);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
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}");
@ -70,10 +78,11 @@ namespace FFMpegCore.Test
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
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)
@ -84,15 +93,23 @@ namespace FFMpegCore.Test
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[WindowsOnlyDataTestMethod, Timeout(BaseTimeoutMilliseconds)] [OsSpecificTestMethod(OsPlatforms.Windows)]
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] [DataRow(PixelFormat.Format24bppRgb)]
public void Video_ToMP4_Args_Pipe_WindowsOnly(System.Drawing.Imaging.PixelFormat pixelFormat) => Video_ToMP4_Args_Pipe_Internal(pixelFormat); [DataRow(PixelFormat.Format32bppArgb)]
public void Video_ToMP4_Args_Pipe_WindowsOnly(PixelFormat pixelFormat)
{
Video_ToMP4_Args_Pipe_Internal(pixelFormat);
}
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[DataRow(SkiaSharp.SKColorType.Rgb565)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
[DataRow(SkiaSharp.SKColorType.Bgra8888)] [DataRow(SKColorType.Rgb565)]
public void Video_ToMP4_Args_Pipe(SkiaSharp.SKColorType pixelFormat) => Video_ToMP4_Args_Pipe_Internal(pixelFormat); [DataRow(SKColorType.Bgra8888)]
public void Video_ToMP4_Args_Pipe(SKColorType pixelFormat)
{
Video_ToMP4_Args_Pipe_Internal(pixelFormat);
}
private static void Video_ToMP4_Args_Pipe_Internal(dynamic pixelFormat) private static void Video_ToMP4_Args_Pipe_Internal(dynamic pixelFormat)
{ {
@ -108,11 +125,19 @@ namespace FFMpegCore.Test
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)] [OsSpecificTestMethod(OsPlatforms.Windows)]
public void Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly() => Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal(System.Drawing.Imaging.PixelFormat.Format24bppRgb); [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly()
{
Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal(PixelFormat.Format24bppRgb);
}
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
public void Video_ToMP4_Args_Pipe_DifferentImageSizes() => Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal(SkiaSharp.SKColorType.Rgb565); [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_ToMP4_Args_Pipe_DifferentImageSizes()
{
Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal(SKColorType.Rgb565);
}
private static void Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal(dynamic pixelFormat) private static void Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal(dynamic pixelFormat)
{ {
@ -120,12 +145,11 @@ namespace FFMpegCore.Test
var frames = new List<IVideoFrame> var frames = new List<IVideoFrame>
{ {
BitmapSource.CreateVideoFrame(0, pixelFormat, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormat, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormat, 256, 256, 1, 0)
BitmapSource.CreateVideoFrame(0, pixelFormat, 256, 256, 1, 0)
}; };
var videoFramesSource = new RawVideoPipeSource(frames); var videoFramesSource = new RawVideoPipeSource(frames);
var ex = Assert.ThrowsException<FFMpegStreamFormatException>(() => FFMpegArguments Assert.ThrowsExactly<FFMpegStreamFormatException>(() => FFMpegArguments
.FromPipeInput(videoFramesSource) .FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264)) .WithVideoCodec(VideoCodec.LibX264))
@ -133,11 +157,19 @@ namespace FFMpegCore.Test
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)] [OsSpecificTestMethod(OsPlatforms.Windows)]
public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly_Async() => await Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal_Async(System.Drawing.Imaging.PixelFormat.Format24bppRgb); [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly_Async()
{
await Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal_Async(PixelFormat.Format24bppRgb);
}
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async() => await Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal_Async(SkiaSharp.SKColorType.Rgb565); [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async()
{
await Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal_Async(SKColorType.Rgb565);
}
private static async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal_Async(dynamic pixelFormat) private static async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal_Async(dynamic pixelFormat)
{ {
@ -145,12 +177,11 @@ namespace FFMpegCore.Test
var frames = new List<IVideoFrame> var frames = new List<IVideoFrame>
{ {
BitmapSource.CreateVideoFrame(0, pixelFormat, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormat, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormat, 256, 256, 1, 0)
BitmapSource.CreateVideoFrame(0, pixelFormat, 256, 256, 1, 0)
}; };
var videoFramesSource = new RawVideoPipeSource(frames); var videoFramesSource = new RawVideoPipeSource(frames);
var ex = await Assert.ThrowsExceptionAsync<FFMpegStreamFormatException>(() => FFMpegArguments await Assert.ThrowsExactlyAsync<FFMpegStreamFormatException>(() => FFMpegArguments
.FromPipeInput(videoFramesSource) .FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264)) .WithVideoCodec(VideoCodec.LibX264))
@ -158,12 +189,20 @@ namespace FFMpegCore.Test
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)] [OsSpecificTestMethod(OsPlatforms.Windows)]
public void Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly() => [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal(System.Drawing.Imaging.PixelFormat.Format24bppRgb, System.Drawing.Imaging.PixelFormat.Format32bppRgb); public void Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly()
{
Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal(PixelFormat.Format24bppRgb,
PixelFormat.Format32bppRgb);
}
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
public void Video_ToMP4_Args_Pipe_DifferentPixelFormats() => Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal(SkiaSharp.SKColorType.Rgb565, SkiaSharp.SKColorType.Bgra8888); [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_ToMP4_Args_Pipe_DifferentPixelFormats()
{
Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal(SKColorType.Rgb565, SKColorType.Bgra8888);
}
private static void Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal(dynamic pixelFormatFrame1, dynamic pixelFormatFrame2) private static void Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal(dynamic pixelFormatFrame1, dynamic pixelFormatFrame2)
{ {
@ -171,12 +210,11 @@ namespace FFMpegCore.Test
var frames = new List<IVideoFrame> var frames = new List<IVideoFrame>
{ {
BitmapSource.CreateVideoFrame(0, pixelFormatFrame1, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormatFrame1, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormatFrame2, 255, 255, 1, 0)
BitmapSource.CreateVideoFrame(0, pixelFormatFrame2, 255, 255, 1, 0)
}; };
var videoFramesSource = new RawVideoPipeSource(frames); var videoFramesSource = new RawVideoPipeSource(frames);
var ex = Assert.ThrowsException<FFMpegStreamFormatException>(() => FFMpegArguments Assert.ThrowsExactly<FFMpegStreamFormatException>(() => FFMpegArguments
.FromPipeInput(videoFramesSource) .FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264)) .WithVideoCodec(VideoCodec.LibX264))
@ -184,12 +222,20 @@ namespace FFMpegCore.Test
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)] [OsSpecificTestMethod(OsPlatforms.Windows)]
public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly_Async() => [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
await Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal_Async(System.Drawing.Imaging.PixelFormat.Format24bppRgb, System.Drawing.Imaging.PixelFormat.Format32bppRgb); public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly_Async()
{
await Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal_Async(PixelFormat.Format24bppRgb,
PixelFormat.Format32bppRgb);
}
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Async() => await Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal_Async(SkiaSharp.SKColorType.Rgb565, SkiaSharp.SKColorType.Bgra8888); [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Async()
{
await Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal_Async(SKColorType.Rgb565, SKColorType.Bgra8888);
}
private static async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal_Async(dynamic pixelFormatFrame1, dynamic pixelFormatFrame2) private static async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal_Async(dynamic pixelFormatFrame1, dynamic pixelFormatFrame2)
{ {
@ -197,19 +243,19 @@ namespace FFMpegCore.Test
var frames = new List<IVideoFrame> var frames = new List<IVideoFrame>
{ {
BitmapSource.CreateVideoFrame(0, pixelFormatFrame1, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormatFrame1, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormatFrame2, 255, 255, 1, 0)
BitmapSource.CreateVideoFrame(0, pixelFormatFrame2, 255, 255, 1, 0)
}; };
var videoFramesSource = new RawVideoPipeSource(frames); var videoFramesSource = new RawVideoPipeSource(frames);
var ex = await Assert.ThrowsExceptionAsync<FFMpegStreamFormatException>(() => FFMpegArguments await Assert.ThrowsExactlyAsync<FFMpegStreamFormatException>(() => FFMpegArguments
.FromPipeInput(videoFramesSource) .FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264)) .WithVideoCodec(VideoCodec.LibX264))
.ProcessAsynchronously()); .ProcessAsynchronously());
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_ToMP4_Args_StreamPipe() public void Video_ToMP4_Args_StreamPipe()
{ {
using var input = File.OpenRead(TestResources.WebmVideo); using var input = File.OpenRead(TestResources.WebmVideo);
@ -223,10 +269,11 @@ namespace FFMpegCore.Test
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_ToMP4_Args_StreamOutputPipe_Async_Failure() public async Task Video_ToMP4_Args_StreamOutputPipe_Async_Failure()
{ {
await Assert.ThrowsExceptionAsync<FFMpegException>(async () => await Assert.ThrowsExactlyAsync<FFMpegException>(async () =>
{ {
await using var ms = new MemoryStream(); await using var ms = new MemoryStream();
var pipeSource = new StreamPipeSink(ms); var pipeSource = new StreamPipeSink(ms);
@ -237,7 +284,8 @@ namespace FFMpegCore.Test
}); });
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_StreamFile_OutputToMemoryStream() public void Video_StreamFile_OutputToMemoryStream()
{ {
var output = new MemoryStream(); var output = new MemoryStream();
@ -254,10 +302,11 @@ namespace FFMpegCore.Test
Console.WriteLine(result.Duration); Console.WriteLine(result.Duration);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_ToMP4_Args_StreamOutputPipe_Failure() public void Video_ToMP4_Args_StreamOutputPipe_Failure()
{ {
Assert.ThrowsException<FFMpegException>(() => Assert.ThrowsExactly<FFMpegException>(() =>
{ {
using var ms = new MemoryStream(); using var ms = new MemoryStream();
FFMpegArguments FFMpegArguments
@ -268,7 +317,8 @@ namespace FFMpegCore.Test
}); });
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_ToMP4_Args_StreamOutputPipe_Async() public async Task Video_ToMP4_Args_StreamOutputPipe_Async()
{ {
await using var ms = new MemoryStream(); await using var ms = new MemoryStream();
@ -281,7 +331,8 @@ namespace FFMpegCore.Test
.ProcessAsynchronously(); .ProcessAsynchronously();
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task TestDuplicateRun() public async Task TestDuplicateRun()
{ {
FFMpegArguments FFMpegArguments
@ -297,7 +348,8 @@ namespace FFMpegCore.Test
File.Delete("temporary.mp4"); File.Delete("temporary.mp4");
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void TranscodeToMemoryStream_Success() public void TranscodeToMemoryStream_Success()
{ {
using var output = new MemoryStream(); using var output = new MemoryStream();
@ -315,7 +367,8 @@ namespace FFMpegCore.Test
Assert.AreEqual(inputAnalysis.Duration.TotalSeconds, outputAnalysis.Duration.TotalSeconds, 0.3); Assert.AreEqual(inputAnalysis.Duration.TotalSeconds, outputAnalysis.Duration.TotalSeconds, 0.3);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
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}");
@ -327,7 +380,8 @@ namespace FFMpegCore.Test
Assert.IsTrue(success); Assert.IsTrue(success);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
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}");
@ -343,15 +397,23 @@ namespace FFMpegCore.Test
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[WindowsOnlyDataTestMethod, Timeout(BaseTimeoutMilliseconds)] [OsSpecificTestMethod(OsPlatforms.Windows)]
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] [DataRow(PixelFormat.Format24bppRgb)]
public async Task Video_ToTS_Args_Pipe_WindowsOnly(System.Drawing.Imaging.PixelFormat pixelFormat) => await Video_ToTS_Args_Pipe_Internal(pixelFormat); [DataRow(PixelFormat.Format32bppArgb)]
public async Task Video_ToTS_Args_Pipe_WindowsOnly(PixelFormat pixelFormat)
{
await Video_ToTS_Args_Pipe_Internal(pixelFormat);
}
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[DataRow(SkiaSharp.SKColorType.Rgb565)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
[DataRow(SkiaSharp.SKColorType.Bgra8888)] [DataRow(SKColorType.Rgb565)]
public async Task Video_ToTS_Args_Pipe(SkiaSharp.SKColorType pixelFormat) => await Video_ToTS_Args_Pipe_Internal(pixelFormat); [DataRow(SKColorType.Bgra8888)]
public async Task Video_ToTS_Args_Pipe(SKColorType pixelFormat)
{
await Video_ToTS_Args_Pipe_Internal(pixelFormat);
}
private static async Task Video_ToTS_Args_Pipe_Internal(dynamic pixelFormat) private static async Task Video_ToTS_Args_Pipe_Internal(dynamic pixelFormat)
{ {
@ -369,7 +431,8 @@ namespace FFMpegCore.Test
Assert.AreEqual(VideoType.Ts.Name, analysis.Format.FormatName); Assert.AreEqual(VideoType.Ts.Name, analysis.Format.FormatName);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_ToOGV_Resize() public async Task Video_ToOGV_Resize()
{ {
using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}"); using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}");
@ -383,10 +446,11 @@ namespace FFMpegCore.Test
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[WindowsOnlyDataTestMethod, Timeout(BaseTimeoutMilliseconds)] [OsSpecificTestMethod(OsPlatforms.Windows)]
[DataRow(SkiaSharp.SKColorType.Rgb565)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
[DataRow(SkiaSharp.SKColorType.Bgra8888)] [DataRow(SKColorType.Rgb565)]
public void RawVideoPipeSource_Ogv_Scale(SkiaSharp.SKColorType pixelFormat) [DataRow(SKColorType.Bgra8888)]
public void RawVideoPipeSource_Ogv_Scale(SKColorType 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));
@ -403,7 +467,8 @@ namespace FFMpegCore.Test
Assert.AreEqual((int)VideoSize.Ed, analysis.PrimaryVideoStream!.Width); Assert.AreEqual((int)VideoSize.Ed, analysis.PrimaryVideoStream!.Width);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
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}");
@ -418,16 +483,24 @@ namespace FFMpegCore.Test
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[WindowsOnlyDataTestMethod, Timeout(BaseTimeoutMilliseconds)] [OsSpecificTestMethod(OsPlatforms.Windows)]
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)] [DataRow(PixelFormat.Format24bppRgb)]
[DataRow(PixelFormat.Format32bppArgb)]
// [DataRow(PixelFormat.Format48bppRgb)] // [DataRow(PixelFormat.Format48bppRgb)]
public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat) => Video_ToMP4_Resize_Args_Pipe_Internal(pixelFormat); public void Video_ToMP4_Resize_Args_Pipe(PixelFormat pixelFormat)
{
Video_ToMP4_Resize_Args_Pipe_Internal(pixelFormat);
}
[DataTestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[DataRow(SkiaSharp.SKColorType.Rgb565)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
[DataRow(SkiaSharp.SKColorType.Bgra8888)] [DataRow(SKColorType.Rgb565)]
public void Video_ToMP4_Resize_Args_Pipe(SkiaSharp.SKColorType pixelFormat) => Video_ToMP4_Resize_Args_Pipe_Internal(pixelFormat); [DataRow(SKColorType.Bgra8888)]
public void Video_ToMP4_Resize_Args_Pipe(SKColorType pixelFormat)
{
Video_ToMP4_Resize_Args_Pipe_Internal(pixelFormat);
}
private static void Video_ToMP4_Resize_Args_Pipe_Internal(dynamic pixelFormat) private static void Video_ToMP4_Resize_Args_Pipe_Internal(dynamic pixelFormat)
{ {
@ -443,10 +516,11 @@ namespace FFMpegCore.Test
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)] [OsSpecificTestMethod(OsPlatforms.Windows)]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Snapshot_InMemory_SystemDrawingCommon() public void Video_Snapshot_InMemory_SystemDrawingCommon()
{ {
using var bitmap = Extensions.System.Drawing.Common.FFMpegImage.Snapshot(TestResources.Mp4Video); using var bitmap = FFMpegImage.Snapshot(TestResources.Mp4Video);
var input = FFProbe.Analyse(TestResources.Mp4Video); var input = FFProbe.Analyse(TestResources.Mp4Video);
Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width); Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width);
@ -454,7 +528,8 @@ namespace FFMpegCore.Test
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png); Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Snapshot_InMemory_SkiaSharp() public void Video_Snapshot_InMemory_SkiaSharp()
{ {
using var bitmap = Extensions.SkiaSharp.FFMpegImage.Snapshot(TestResources.Mp4Video); using var bitmap = Extensions.SkiaSharp.FFMpegImage.Snapshot(TestResources.Mp4Video);
@ -466,8 +541,9 @@ namespace FFMpegCore.Test
// e.g. Bgra8888 on Windows and Rgba8888 on macOS. // e.g. Bgra8888 on Windows and Rgba8888 on macOS.
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
public void Video_Snapshot_PersistSnapshot() [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Snapshot_Png_PersistSnapshot()
{ {
using var outputPath = new TemporaryFile("out.png"); using var outputPath = new TemporaryFile("out.png");
var input = FFProbe.Analyse(TestResources.Mp4Video); var input = FFProbe.Analyse(TestResources.Mp4Video);
@ -480,7 +556,69 @@ namespace FFMpegCore.Test
Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName); Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Snapshot_Jpg_PersistSnapshot()
{
using var outputPath = new TemporaryFile("out.jpg");
var input = FFProbe.Analyse(TestResources.Mp4Video);
FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
var analysis = FFProbe.Analyse(outputPath);
Assert.AreEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
Assert.AreEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
Assert.AreEqual("mjpeg", analysis.PrimaryVideoStream!.CodecName);
}
[TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Snapshot_Bmp_PersistSnapshot()
{
using var outputPath = new TemporaryFile("out.bmp");
var input = FFProbe.Analyse(TestResources.Mp4Video);
FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
var analysis = FFProbe.Analyse(outputPath);
Assert.AreEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
Assert.AreEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
Assert.AreEqual("bmp", analysis.PrimaryVideoStream!.CodecName);
}
[TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Snapshot_Webp_PersistSnapshot()
{
using var outputPath = new TemporaryFile("out.webp");
var input = FFProbe.Analyse(TestResources.Mp4Video);
FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
var analysis = FFProbe.Analyse(outputPath);
Assert.AreEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
Assert.AreEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
Assert.AreEqual("webp", analysis.PrimaryVideoStream!.CodecName);
}
[TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Snapshot_Exception_PersistSnapshot()
{
using var outputPath = new TemporaryFile("out.asd");
try
{
FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
}
catch (Exception ex)
{
Assert.IsTrue(ex is ArgumentException);
}
}
[TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Snapshot_Rotated_PersistSnapshot() public void Video_Snapshot_Rotated_PersistSnapshot()
{ {
using var outputPath = new TemporaryFile("out.png"); using var outputPath = new TemporaryFile("out.png");
@ -495,7 +633,8 @@ namespace FFMpegCore.Test
Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName); Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_GifSnapshot_PersistSnapshot() public void Video_GifSnapshot_PersistSnapshot()
{ {
using var outputPath = new TemporaryFile("out.gif"); using var outputPath = new TemporaryFile("out.gif");
@ -509,14 +648,15 @@ namespace FFMpegCore.Test
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName); Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_GifSnapshot_PersistSnapshot_SizeSupplied() public void Video_GifSnapshot_PersistSnapshot_SizeSupplied()
{ {
using var outputPath = new TemporaryFile("out.gif"); using var outputPath = new TemporaryFile("out.gif");
var input = FFProbe.Analyse(TestResources.Mp4Video); var input = FFProbe.Analyse(TestResources.Mp4Video);
var desiredGifSize = new Size(320, 240); var desiredGifSize = new Size(320, 240);
FFMpeg.GifSnapshot(TestResources.Mp4Video, outputPath, desiredGifSize, captureTime: TimeSpan.FromSeconds(0)); FFMpeg.GifSnapshot(TestResources.Mp4Video, outputPath, desiredGifSize, TimeSpan.FromSeconds(0));
var analysis = FFProbe.Analyse(outputPath); var analysis = FFProbe.Analyse(outputPath);
Assert.AreNotEqual(input.PrimaryVideoStream!.Width, desiredGifSize.Width); Assert.AreNotEqual(input.PrimaryVideoStream!.Width, desiredGifSize.Width);
@ -524,7 +664,8 @@ namespace FFMpegCore.Test
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName); Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_GifSnapshot_PersistSnapshotAsync() public async Task Video_GifSnapshot_PersistSnapshotAsync()
{ {
using var outputPath = new TemporaryFile("out.gif"); using var outputPath = new TemporaryFile("out.gif");
@ -538,14 +679,15 @@ namespace FFMpegCore.Test
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName); Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_GifSnapshot_PersistSnapshotAsync_SizeSupplied() public async Task Video_GifSnapshot_PersistSnapshotAsync_SizeSupplied()
{ {
using var outputPath = new TemporaryFile("out.gif"); using var outputPath = new TemporaryFile("out.gif");
var input = FFProbe.Analyse(TestResources.Mp4Video); var input = FFProbe.Analyse(TestResources.Mp4Video);
var desiredGifSize = new Size(320, 240); var desiredGifSize = new Size(320, 240);
await FFMpeg.GifSnapshotAsync(TestResources.Mp4Video, outputPath, desiredGifSize, captureTime: TimeSpan.FromSeconds(0)); await FFMpeg.GifSnapshotAsync(TestResources.Mp4Video, outputPath, desiredGifSize, TimeSpan.FromSeconds(0));
var analysis = FFProbe.Analyse(outputPath); var analysis = FFProbe.Analyse(outputPath);
Assert.AreNotEqual(input.PrimaryVideoStream!.Width, desiredGifSize.Width); Assert.AreNotEqual(input.PrimaryVideoStream!.Width, desiredGifSize.Width);
@ -553,7 +695,8 @@ namespace FFMpegCore.Test
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName); Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Join() public void Video_Join()
{ {
using var inputCopy = new TemporaryFile("copy-input.mp4"); using var inputCopy = new TemporaryFile("copy-input.mp4");
@ -575,7 +718,8 @@ namespace FFMpegCore.Test
Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width); Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width);
} }
[TestMethod, Timeout(2 * BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(2 * BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Join_Image_Sequence() public void Video_Join_Image_Sequence()
{ {
var imageSet = new List<string>(); var imageSet = new List<string>();
@ -591,7 +735,7 @@ namespace FFMpegCore.Test
var imageAnalysis = FFProbe.Analyse(imageSet.First()); var imageAnalysis = FFProbe.Analyse(imageSet.First());
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
var success = FFMpeg.JoinImageSequence(outputFile, frameRate: 10, images: imageSet.ToArray()); var success = FFMpeg.JoinImageSequence(outputFile, 10, imageSet.ToArray());
Assert.IsTrue(success); Assert.IsTrue(success);
var result = FFProbe.Analyse(outputFile); var result = FFProbe.Analyse(outputFile);
@ -600,16 +744,18 @@ namespace FFMpegCore.Test
Assert.AreEqual(imageAnalysis.PrimaryVideoStream!.Height, result.PrimaryVideoStream.Height); Assert.AreEqual(imageAnalysis.PrimaryVideoStream!.Height, result.PrimaryVideoStream.Height);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_With_Only_Audio_Should_Extract_Metadata() public void Video_With_Only_Audio_Should_Extract_Metadata()
{ {
var video = FFProbe.Analyse(TestResources.Mp4WithoutVideo); var video = FFProbe.Analyse(TestResources.Mp4WithoutVideo);
Assert.AreEqual(null, video.PrimaryVideoStream); Assert.IsNull(video.PrimaryVideoStream);
Assert.AreEqual("aac", video.PrimaryAudioStream!.CodecName); Assert.AreEqual("aac", video.PrimaryAudioStream!.CodecName);
Assert.AreEqual(10, video.Duration.TotalSeconds, 0.5); Assert.AreEqual(10, video.Duration.TotalSeconds, 0.5);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Duration() public void Video_Duration()
{ {
var video = FFProbe.Analyse(TestResources.Mp4Video); var video = FFProbe.Analyse(TestResources.Mp4Video);
@ -629,7 +775,8 @@ namespace FFMpegCore.Test
Assert.AreEqual(video.Duration.Seconds - 2, outputVideo.Duration.Seconds); Assert.AreEqual(video.Duration.Seconds - 2, outputVideo.Duration.Seconds);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_UpdatesProgress() public void Video_UpdatesProgress()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
@ -670,13 +817,13 @@ namespace FFMpegCore.Test
Assert.AreNotEqual(analysis.Duration, timeDone); Assert.AreNotEqual(analysis.Duration, timeDone);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_OutputsData() public void Video_OutputsData()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
var dataReceived = false; var dataReceived = false;
GlobalFFOptions.Configure(opt => opt.Encoding = Encoding.UTF8);
var success = FFMpegArguments var success = FFMpegArguments
.FromFileInput(TestResources.Mp4Video) .FromFileInput(TestResources.Mp4Video)
.WithGlobalOptions(options => options .WithGlobalOptions(options => options
@ -684,6 +831,7 @@ namespace FFMpegCore.Test
.OutputToFile(outputFile, false, opt => opt .OutputToFile(outputFile, false, opt => opt
.WithDuration(TimeSpan.FromSeconds(2))) .WithDuration(TimeSpan.FromSeconds(2)))
.NotifyOnError(_ => dataReceived = true) .NotifyOnError(_ => dataReceived = true)
.Configure(opt => opt.Encoding = Encoding.UTF8)
.ProcessSynchronously(); .ProcessSynchronously();
Assert.IsTrue(dataReceived); Assert.IsTrue(dataReceived);
@ -692,11 +840,19 @@ namespace FFMpegCore.Test
} }
[SupportedOSPlatform("windows")] [SupportedOSPlatform("windows")]
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)] [OsSpecificTestMethod(OsPlatforms.Windows)]
public void Video_TranscodeInMemory_WindowsOnly() => Video_TranscodeInMemory_Internal(System.Drawing.Imaging.PixelFormat.Format24bppRgb); [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_TranscodeInMemory_WindowsOnly()
{
Video_TranscodeInMemory_Internal(PixelFormat.Format24bppRgb);
}
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
public void Video_TranscodeInMemory() => Video_TranscodeInMemory_Internal(SkiaSharp.SKColorType.Rgb565); [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_TranscodeInMemory()
{
Video_TranscodeInMemory_Internal(SKColorType.Rgb565);
}
private static void Video_TranscodeInMemory_Internal(dynamic pixelFormat) private static void Video_TranscodeInMemory_Internal(dynamic pixelFormat)
{ {
@ -713,11 +869,12 @@ namespace FFMpegCore.Test
resStream.Position = 0; resStream.Position = 0;
var vi = FFProbe.Analyse(resStream); var vi = FFProbe.Analyse(resStream);
Assert.AreEqual(vi.PrimaryVideoStream!.Width, 128); Assert.AreEqual(128, vi.PrimaryVideoStream!.Width);
Assert.AreEqual(vi.PrimaryVideoStream.Height, 128); Assert.AreEqual(128, vi.PrimaryVideoStream.Height);
} }
[TestMethod, Timeout(2 * BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(2 * BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_TranscodeToMemory() public void Video_TranscodeToMemory()
{ {
using var memoryStream = new MemoryStream(); using var memoryStream = new MemoryStream();
@ -731,11 +888,12 @@ namespace FFMpegCore.Test
memoryStream.Position = 0; memoryStream.Position = 0;
var vi = FFProbe.Analyse(memoryStream); var vi = FFProbe.Analyse(memoryStream);
Assert.AreEqual(vi.PrimaryVideoStream!.Width, 640); Assert.AreEqual(640, vi.PrimaryVideoStream!.Width);
Assert.AreEqual(vi.PrimaryVideoStream.Height, 360); Assert.AreEqual(360, vi.PrimaryVideoStream.Height);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_Cancel_Async() public async Task Video_Cancel_Async()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
@ -751,7 +909,7 @@ namespace FFMpegCore.Test
.CancellableThrough(out var cancel) .CancellableThrough(out var cancel)
.ProcessAsynchronously(false); .ProcessAsynchronously(false);
await Task.Delay(300); await Task.Delay(300, TestContext.CancellationToken);
cancel(); cancel();
var result = await task; var result = await task;
@ -759,7 +917,8 @@ namespace FFMpegCore.Test
Assert.IsFalse(result); Assert.IsFalse(result);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Cancel() public void Video_Cancel()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
@ -773,14 +932,15 @@ namespace FFMpegCore.Test
.WithSpeedPreset(Speed.VeryFast)) .WithSpeedPreset(Speed.VeryFast))
.CancellableThrough(out var cancel); .CancellableThrough(out var cancel);
Task.Delay(300).ContinueWith((_) => cancel()); Task.Delay(300, TestContext.CancellationToken).ContinueWith(_ => cancel(), TestContext.CancellationToken);
var result = task.ProcessSynchronously(false); var result = task.ProcessSynchronously(false);
Assert.IsFalse(result); Assert.IsFalse(result);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_Cancel_Async_With_Timeout() public async Task Video_Cancel_Async_With_Timeout()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
@ -796,12 +956,12 @@ namespace FFMpegCore.Test
.CancellableThrough(out var cancel, 10000) .CancellableThrough(out var cancel, 10000)
.ProcessAsynchronously(false); .ProcessAsynchronously(false);
await Task.Delay(300); await Task.Delay(300, TestContext.CancellationToken);
cancel(); cancel();
await task; await task;
var outputInfo = await FFProbe.AnalyseAsync(outputFile); var outputInfo = await FFProbe.AnalyseAsync(outputFile, cancellationToken: TestContext.CancellationToken);
Assert.IsNotNull(outputInfo); Assert.IsNotNull(outputInfo);
Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width); Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width);
@ -810,7 +970,8 @@ namespace FFMpegCore.Test
Assert.AreEqual("aac", outputInfo.PrimaryAudioStream!.CodecName); Assert.AreEqual("aac", outputInfo.PrimaryAudioStream!.CodecName);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_Cancel_CancellationToken_Async() public async Task Video_Cancel_CancellationToken_Async()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
@ -835,7 +996,8 @@ namespace FFMpegCore.Test
Assert.IsFalse(result); Assert.IsFalse(result);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_Cancel_CancellationToken_Async_Throws() public async Task Video_Cancel_CancellationToken_Async_Throws()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
@ -855,10 +1017,11 @@ namespace FFMpegCore.Test
cts.CancelAfter(300); cts.CancelAfter(300);
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => task); await Assert.ThrowsExactlyAsync<OperationCanceledException>(() => task);
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Cancel_CancellationToken_Throws() public void Video_Cancel_CancellationToken_Throws()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
@ -877,10 +1040,11 @@ namespace FFMpegCore.Test
cts.CancelAfter(300); cts.CancelAfter(300);
Assert.ThrowsException<OperationCanceledException>(() => task.ProcessSynchronously()); Assert.ThrowsExactly<OperationCanceledException>(() => task.ProcessSynchronously());
} }
[TestMethod, Timeout(BaseTimeoutMilliseconds)] [TestMethod]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_Cancel_CancellationToken_Async_With_Timeout() public async Task Video_Cancel_CancellationToken_Async_With_Timeout()
{ {
using var outputFile = new TemporaryFile("out.mp4"); using var outputFile = new TemporaryFile("out.mp4");
@ -902,7 +1066,7 @@ namespace FFMpegCore.Test
await task; await task;
var outputInfo = await FFProbe.AnalyseAsync(outputFile); var outputInfo = await FFProbe.AnalyseAsync(outputFile, cancellationToken: TestContext.CancellationToken);
Assert.IsNotNull(outputInfo); Assert.IsNotNull(outputInfo);
Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width); Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width);
@ -911,4 +1075,3 @@ namespace FFMpegCore.Test
Assert.AreEqual("aac", outputInfo.PrimaryAudioStream!.CodecName); Assert.AreEqual("aac", outputInfo.PrimaryAudioStream!.CodecName);
} }
} }
}

View file

@ -1,3 +1,3 @@
{ {
"RootDirectory": "" "BinaryFolder": ""
} }

View file

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 17
VisualStudioVersion = 16.0.31005.135 VisualStudioVersion = 17.7.34003.232
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore", "FFMpegCore\FFMpegCore.csproj", "{19DE2EC2-9955-4712-8096-C22EF6713E4F}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore", "FFMpegCore\FFMpegCore.csproj", "{19DE2EC2-9955-4712-8096-C22EF6713E4F}"
EndProject EndProject
@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Extensions.Syste
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Extensions.SkiaSharp", "FFMpegCore.Extensions.SkiaSharp\FFMpegCore.Extensions.SkiaSharp.csproj", "{5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Extensions.SkiaSharp", "FFMpegCore.Extensions.SkiaSharp\FFMpegCore.Extensions.SkiaSharp.csproj", "{5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFMpegCore.Extensions.Downloader", "FFMpegCore.Extensions.Downloader\FFMpegCore.Extensions.Downloader.csproj", "{5FA30158-CAB0-44FD-AD98-C31F5E3D5A56}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -39,6 +41,10 @@ Global
{5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Release|Any CPU.Build.0 = Release|Any CPU {5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Release|Any CPU.Build.0 = Release|Any CPU
{5FA30158-CAB0-44FD-AD98-C31F5E3D5A56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FA30158-CAB0-44FD-AD98-C31F5E3D5A56}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FA30158-CAB0-44FD-AD98-C31F5E3D5A56}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FA30158-CAB0-44FD-AD98-C31F5E3D5A56}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Extend namespace FFMpegCore.Extend;
{
internal static class KeyValuePairExtensions internal static class KeyValuePairExtensions
{ {
/// <summary> /// <summary>
@ -19,4 +19,3 @@
return $"{key}={value}"; return $"{key}={value}";
} }
} }
}

View file

@ -1,7 +1,7 @@
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore.Extend namespace FFMpegCore.Extend;
{
public class PcmAudioSampleWrapper : IAudioSample public class PcmAudioSampleWrapper : IAudioSample
{ {
//This could actually be short or int, but copies would be inefficient. //This could actually be short or int, but copies would be inefficient.
@ -24,4 +24,3 @@ namespace FFMpegCore.Extend
await stream.WriteAsync(_sample, 0, _sample.Length, token).ConfigureAwait(false); await stream.WriteAsync(_sample, 0, _sample.Length, token).ConfigureAwait(false);
} }
} }
}

View file

@ -1,7 +1,7 @@
using System.Text; using System.Text;
namespace FFMpegCore.Extend namespace FFMpegCore.Extend;
{
internal static class StringExtensions internal static class StringExtensions
{ {
private static Dictionary<char, string> CharactersSubstitution { get; } = new() private static Dictionary<char, string> CharactersSubstitution { get; } = new()
@ -66,4 +66,3 @@ namespace FFMpegCore.Extend
return parsedString.ToString(); return parsedString.ToString();
} }
} }
}

View file

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

View file

@ -1,13 +1,13 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class AudibleEncryptionKeyArgument : IArgument public class AudibleEncryptionKeyArgument : IArgument
{ {
private readonly bool _aaxcMode; private readonly bool _aaxcMode;
private readonly string? _key; private readonly string? _activationBytes;
private readonly string? _iv; private readonly string? _iv;
private readonly string? _activationBytes; private readonly string? _key;
public AudibleEncryptionKeyArgument(string activationBytes) public AudibleEncryptionKeyArgument(string activationBytes)
{ {
@ -24,4 +24,3 @@
public string Text => _aaxcMode ? $"-audible_key {_key} -audible_iv {_iv}" : $"-activation_bytes {_activationBytes}"; public string Text => _aaxcMode ? $"-audible_key {_key} -audible_iv {_iv}" : $"-activation_bytes {_activationBytes}";
} }
}

View file

@ -1,7 +1,7 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents parameter of audio codec and it's quality /// Represents parameter of audio codec and it's quality
/// </summary> /// </summary>
@ -9,6 +9,7 @@ namespace FFMpegCore.Arguments
{ {
public readonly int Bitrate; public readonly int Bitrate;
public AudioBitrateArgument(AudioQuality value) : this((int)value) { } public AudioBitrateArgument(AudioQuality value) : this((int)value) { }
public AudioBitrateArgument(int bitrate) public AudioBitrateArgument(int bitrate)
{ {
Bitrate = bitrate; Bitrate = bitrate;
@ -16,4 +17,3 @@ namespace FFMpegCore.Arguments
public string Text => $"-b:a {Bitrate}k"; public string Text => $"-b:a {Bitrate}k";
} }
}

View file

@ -1,8 +1,8 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
using FFMpegCore.Exceptions; using FFMpegCore.Exceptions;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents parameter of audio codec and it's quality /// Represents parameter of audio codec and it's quality
/// </summary> /// </summary>
@ -25,6 +25,5 @@ namespace FFMpegCore.Arguments
AudioCodec = audioCodec; AudioCodec = audioCodec;
} }
public string Text => $"-c:a {AudioCodec.ToString().ToLowerInvariant()}"; public string Text => $"-c:a {AudioCodec.ToLowerInvariant()}";
}
} }

View file

@ -1,7 +1,7 @@
using FFMpegCore.Exceptions; using FFMpegCore.Exceptions;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class AudioFiltersArgument : IArgument public class AudioFiltersArgument : IArgument
{ {
public readonly AudioFilterOptions Options; public readonly AudioFilterOptions Options;
@ -42,25 +42,52 @@ namespace FFMpegCore.Arguments
{ {
public List<IAudioFilterArgument> Arguments { get; } = new(); 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)
public AudioFilterOptions Pan(int channels, params string[] outputDefinitions) => WithArgument(new PanArgument(channels, outputDefinitions)); {
return WithArgument(new PanArgument(channelLayout, outputDefinitions));
}
public AudioFilterOptions Pan(int channels, params string[] outputDefinitions)
{
return WithArgument(new PanArgument(channels, outputDefinitions));
}
public AudioFilterOptions DynamicNormalizer(int frameLength = 500, int filterWindow = 31, double targetPeak = 0.95, public AudioFilterOptions DynamicNormalizer(int frameLength = 500, int filterWindow = 31, double targetPeak = 0.95,
double gainFactor = 10.0, double targetRms = 0.0, bool channelCoupling = true, double gainFactor = 10.0, double targetRms = 0.0, bool channelCoupling = true,
bool enableDcBiasCorrection = false, bool enableAlternativeBoundary = false, bool enableDcBiasCorrection = false, bool enableAlternativeBoundary = false,
double compressorFactor = 0.0) => WithArgument(new DynamicNormalizerArgument(frameLength, filterWindow, double compressorFactor = 0.0)
{
return 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, 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", 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)); int? blocksize = null)
{
return 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, public AudioFilterOptions LowPass(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707,
double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto", double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto",
int? blocksize = null) => WithArgument(new LowPassFilterArgument(frequency, poles, width_type, width, mix, channels, normalize, transform, precision, blocksize)); int? blocksize = null)
{
return 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, public AudioFilterOptions AudioGate(double level_in = 1, string mode = "downward", double range = 0.06125, double threshold = 0.125,
int ratio = 2, double attack = 20, double release = 250, int makeup = 1, double knee = 2.828427125, string detection = "rms", int ratio = 2, double attack = 20, double release = 250, int makeup = 1, double knee = 2.828427125, string detection = "rms",
string link = "average") => WithArgument(new AudioGateArgument(level_in, mode, range, threshold, ratio, attack, release, makeup, knee, detection, link)); string link = "average")
{
return WithArgument(new AudioGateArgument(level_in, mode, range, threshold, ratio, attack, release, makeup, knee, detection, link));
}
public AudioFilterOptions SilenceDetect(string noise_type = "db", double noise = 60, double duration = 2, public AudioFilterOptions SilenceDetect(string noise_type = "db", double noise = 60, double duration = 2,
bool mono = false) => WithArgument(new SilenceDetectArgument(noise_type, noise, duration, mono)); bool mono = false)
{
return WithArgument(new SilenceDetectArgument(noise_type, noise, duration, mono));
}
private AudioFilterOptions WithArgument(IAudioFilterArgument argument) private AudioFilterOptions WithArgument(IAudioFilterArgument argument)
{ {
@ -68,4 +95,3 @@ namespace FFMpegCore.Arguments
return this; return this;
} }
} }
}

View file

@ -1,7 +1,7 @@
using System.Globalization; using System.Globalization;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class AudioGateArgument : IAudioFilterArgument public class AudioGateArgument : IAudioFilterArgument
{ {
private readonly Dictionary<string, string> _arguments = new(); private readonly Dictionary<string, string> _arguments = new();
@ -10,16 +10,34 @@ namespace FFMpegCore.Arguments
/// Audio Gate. <see href="https://ffmpeg.org/ffmpeg-filters.html#agate" /> /// Audio Gate. <see href="https://ffmpeg.org/ffmpeg-filters.html#agate" />
/// </summary> /// </summary>
/// <param name="levelIn">Set input level before filtering. Default is 1. Allowed range is from 0.015625 to 64.</param> /// <param name="levelIn">Set input level before filtering. Default is 1. Allowed range is from 0.015625 to 64.</param>
/// <param name="mode">Set the mode of operation. Can be upward or downward. Default is downward. If set to upward mode, higher parts of signal will be amplified, expanding dynamic range in upward direction. Otherwise, in case of downward lower parts of signal will be reduced.</param> /// <param name="mode">
/// <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> /// 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="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="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="attack">
/// <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> /// 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="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="knee">
/// Curve the sharp knee around the threshold to enter gain reduction more softly. Default is 2.828427125. Allowed range is
/// from 1 to 8.
/// </param>
/// <param name="detection">Choose if exact signal should be taken for detection or an RMS like one. Default is rms. Can be peak or rms.</param> /// <param name="detection">Choose if exact signal should be taken for detection or an RMS like one. Default is rms. Can be peak or rms.</param>
/// <param name="link">Choose if the average level between all channels or the louder channel affects the reduction. Default is average. Can be average or maximum.</param> /// <param name="link">
/// Choose if the average level between all channels or the louder channel affects the reduction. Default is average. Can be
/// average or maximum.
/// </param>
public AudioGateArgument(double levelIn = 1, string mode = "downward", double range = 0.06125, double threshold = 0.125, int ratio = 2, 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") double attack = 20, double release = 250, int makeup = 1, double knee = 2.828427125, string detection = "rms", string link = "average")
{ {
@ -95,4 +113,3 @@ namespace FFMpegCore.Arguments
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

@ -1,11 +1,12 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Audio sampling rate argument. Defaults to 48000 (Hz) /// Audio sampling rate argument. Defaults to 48000 (Hz)
/// </summary> /// </summary>
public class AudioSamplingRateArgument : IArgument public class AudioSamplingRateArgument : IArgument
{ {
public readonly int SamplingRate; public readonly int SamplingRate;
public AudioSamplingRateArgument(int samplingRate = 48000) public AudioSamplingRateArgument(int samplingRate = 48000)
{ {
SamplingRate = samplingRate; SamplingRate = samplingRate;
@ -13,4 +14,3 @@
public string Text => $"-ar {SamplingRate}"; public string Text => $"-ar {SamplingRate}";
} }
}

View file

@ -1,7 +1,7 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents parameter of bitstream filter /// Represents parameter of bitstream filter
/// </summary> /// </summary>
@ -23,4 +23,3 @@ namespace FFMpegCore.Arguments
_ => string.Empty _ => string.Empty
}; };
} }
}

View file

@ -1,14 +1,13 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class BlackDetectArgument : IVideoFilterArgument 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) public BlackDetectArgument(double minimumDuration = 2.0, double pictureBlackRatioThreshold = 0.98, double pixelBlackThreshold = 0.1)
{ {
Value = $"d={minimumDuration}:pic_th={pictureBlackRatioThreshold}:pix_th={pixelBlackThreshold}"; Value = $"d={minimumDuration}:pic_th={pictureBlackRatioThreshold}:pix_th={pixelBlackThreshold}";
} }
}
public string Key => "blackdetect";
public string Value { get; }
} }

View file

@ -1,14 +1,13 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
internal class BlackFrameArgument : IVideoFilterArgument internal class BlackFrameArgument : IVideoFilterArgument
{ {
public string Key => "blackframe";
public string Value { get; }
public BlackFrameArgument(int amount = 98, int threshold = 32) public BlackFrameArgument(int amount = 98, int threshold = 32)
{ {
Value = $"amount={amount}:threshold={threshold}"; Value = $"amount={amount}:threshold={threshold}";
} }
}
public string Key => "blackframe";
public string Value { get; }
} }

View file

@ -1,5 +1,4 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents parameter of concat argument /// Represents parameter of concat argument
@ -8,15 +7,20 @@
public class ConcatArgument : IInputArgument public class ConcatArgument : IInputArgument
{ {
public readonly IEnumerable<string> Values; public readonly IEnumerable<string> Values;
public ConcatArgument(IEnumerable<string> values) public ConcatArgument(IEnumerable<string> values)
{ {
Values = values; Values = values;
} }
public void Pre() { } public void Pre() { }
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
public Task During(CancellationToken cancellationToken = default)
{
return 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,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Constant Rate Factor (CRF) argument /// Constant Rate Factor (CRF) argument
/// </summary> /// </summary>
@ -19,4 +19,3 @@
public string Text => $"-crf {Crf}"; public string Text => $"-crf {Crf}";
} }
}

View file

@ -1,7 +1,7 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents parameter of copy parameter /// Represents parameter of copy parameter
/// Defines if channel (audio, video or both) should be copied to output file /// Defines if channel (audio, video or both) should be copied to output file
@ -9,6 +9,7 @@ namespace FFMpegCore.Arguments
public class CopyArgument : IArgument public class CopyArgument : IArgument
{ {
public readonly Channel Channel; public readonly Channel Channel;
public CopyArgument(Channel channel = Channel.Both) public CopyArgument(Channel channel = Channel.Both)
{ {
Channel = channel; Channel = channel;
@ -20,4 +21,3 @@ namespace FFMpegCore.Arguments
_ => $"-c{Channel.StreamType()} copy" _ => $"-c{Channel.StreamType()} copy"
}; };
} }
}

View file

@ -1,10 +1,9 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents a copy codec parameter /// Represents a copy codec parameter
/// </summary> /// </summary>
public class CopyCodecArgument : IArgument public class CopyCodecArgument : IArgument
{ {
public string Text => $"-codec copy"; public string Text => "-codec copy";
}
} }

View file

@ -1,12 +1,12 @@
using System.Drawing; using System.Drawing;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class CropArgument : IArgument public class CropArgument : IArgument
{ {
public readonly int Left;
public readonly Size? Size; public readonly Size? Size;
public readonly int Top; public readonly int Top;
public readonly int Left;
public CropArgument(Size? size, int top, int left) public CropArgument(Size? size, int top, int left)
{ {
@ -19,4 +19,3 @@ namespace FFMpegCore.Arguments
public string Text => Size == null ? string.Empty : $"-vf crop={Size.Value.Width}:{Size.Value.Height}:{Left}:{Top}"; public string Text => Size == null ? string.Empty : $"-vf crop={Size.Value.Width}:{Size.Value.Height}:{Left}:{Top}";
} }
}

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class CustomArgument : IArgument public class CustomArgument : IArgument
{ {
public readonly string Argument; public readonly string Argument;
@ -11,4 +11,3 @@
public string Text => Argument ?? string.Empty; public string Text => Argument ?? string.Empty;
} }
}

View file

@ -1,31 +1,44 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents parameter of concat argument /// Represents parameter of concat argument
/// Used for creating video from multiple images or videos /// Used for creating video from multiple images or videos
/// </summary> /// </summary>
public class DemuxConcatArgument : IInputArgument public class DemuxConcatArgument : IInputArgument
{ {
private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"concat_{Guid.NewGuid()}.txt");
public readonly IEnumerable<string> Values; public readonly IEnumerable<string> Values;
public DemuxConcatArgument(IEnumerable<string> values) public DemuxConcatArgument(IEnumerable<string> values)
{ {
Values = values.Select(value => $"file '{Escape(value)}'"); Values = values.Select(value => $"file '{Escape(value)}'");
} }
public void Pre()
{
File.WriteAllLines(_tempFileName, Values);
}
public Task During(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public void Post()
{
File.Delete(_tempFileName);
}
public string Text => $"-f concat -safe 0 -i \"{_tempFileName}\"";
/// <summary> /// <summary>
/// Thanks slhck /// Thanks slhck
/// https://superuser.com/a/787651/1089628 /// https://superuser.com/a/787651/1089628
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="value"></param>
/// <returns></returns> /// <returns></returns>
private string Escape(string value) => value.Replace("'", @"'\''"); private string Escape(string value)
{
private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"concat_{Guid.NewGuid()}.txt"); return value.Replace("'", @"'\''");
public void Pre() => File.WriteAllLines(_tempFileName, Values);
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
public void Post() => File.Delete(_tempFileName);
public string Text => $"-f concat -safe 0 -i \"{_tempFileName}\"";
} }
} }

View file

@ -1,8 +1,8 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
using FFMpegCore.Exceptions; using FFMpegCore.Exceptions;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents cpu speed parameter /// Represents cpu speed parameter
/// </summary> /// </summary>
@ -27,4 +27,3 @@ namespace FFMpegCore.Arguments
_ => string.Empty _ => string.Empty
}; };
} }
}

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Drawtext video filter argument /// Drawtext video filter argument
/// </summary> /// </summary>
@ -18,21 +18,29 @@
public class DrawTextOptions public class DrawTextOptions
{ {
public readonly string Text;
public readonly string Font; public readonly string Font;
public readonly List<(string key, string value)> Parameters; public readonly List<(string key, string value)> Parameters;
public readonly string Text;
private DrawTextOptions(string text, string font, IEnumerable<(string, string)> parameters)
{
Text = text;
Font = font;
Parameters = parameters.ToList();
}
internal string TextInternal => string.Join(":", new[] { ("text", Text), ("fontfile", Font) }.Concat(Parameters).Select(FormatArgumentPair));
public static DrawTextOptions Create(string text, string font) public static DrawTextOptions Create(string text, string font)
{ {
return new DrawTextOptions(text, font, new List<(string, string)>()); return new DrawTextOptions(text, font, new List<(string, string)>());
} }
public static DrawTextOptions Create(string text, string font, params (string key, string value)[] parameters) public static DrawTextOptions Create(string text, string font, params (string key, string value)[] parameters)
{ {
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));
private static string FormatArgumentPair((string key, string value) pair) private static string FormatArgumentPair((string key, string value) pair)
{ {
return $"{pair.key}={EncloseIfContainsSpace(pair.value)}"; return $"{pair.key}={EncloseIfContainsSpace(pair.value)}";
@ -43,17 +51,9 @@
return input.Contains(" ") ? $"'{input}'" : input; return input.Contains(" ") ? $"'{input}'" : input;
} }
private DrawTextOptions(string text, string font, IEnumerable<(string, string)> parameters)
{
Text = text;
Font = font;
Parameters = parameters.ToList();
}
public DrawTextOptions WithParameter(string key, string value) public DrawTextOptions WithParameter(string key, string value)
{ {
Parameters.Add((key, value)); Parameters.Add((key, value));
return this; return this;
} }
} }
}

View file

@ -1,11 +1,12 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents duration parameter /// Represents duration parameter
/// </summary> /// </summary>
public class DurationArgument : IArgument public class DurationArgument : IArgument
{ {
public readonly TimeSpan? Duration; public readonly TimeSpan? Duration;
public DurationArgument(TimeSpan? duration) public DurationArgument(TimeSpan? duration)
{ {
Duration = duration; Duration = duration;
@ -13,4 +14,3 @@
public string Text => !Duration.HasValue ? string.Empty : $"-t {Duration.Value}"; public string Text => !Duration.HasValue ? string.Empty : $"-t {Duration.Value}";
} }
}

View file

@ -1,7 +1,7 @@
using System.Globalization; using System.Globalization;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class DynamicNormalizerArgument : IAudioFilterArgument public class DynamicNormalizerArgument : IAudioFilterArgument
{ {
private readonly Dictionary<string, string> _arguments = new(); private readonly Dictionary<string, string> _arguments = new();
@ -18,7 +18,8 @@ namespace FFMpegCore.Arguments
/// <param name="enableDcBiasCorrection">Enable DC bias correction. By default is disabled.</param> /// <param name="enableDcBiasCorrection">Enable DC bias correction. By default is disabled.</param>
/// <param name="enableAlternativeBoundary">Enable alternative boundary mode. By default is disabled.</param> /// <param name="enableAlternativeBoundary">Enable alternative boundary mode. By default is disabled.</param>
/// <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) if (frameLength < 10 || frameLength > 8000)
{ {
@ -70,4 +71,3 @@ namespace FFMpegCore.Arguments
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

@ -1,7 +1,7 @@
using FFMpegCore.Extend; using FFMpegCore.Extend;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents seek parameter /// Represents seek parameter
/// </summary> /// </summary>
@ -16,4 +16,3 @@ namespace FFMpegCore.Arguments
public string Text => SeekTo.HasValue ? $"-to {SeekTo.Value.ToLongString()}" : string.Empty; public string Text => SeekTo.HasValue ? $"-to {SeekTo.Value.ToLongString()}" : string.Empty;
} }
}

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Faststart argument - for moving moov atom to the start of file /// Faststart argument - for moving moov atom to the start of file
/// </summary> /// </summary>
@ -7,4 +7,3 @@
{ {
public string Text => "-movflags faststart"; public string Text => "-movflags faststart";
} }
}

View file

@ -1,13 +1,14 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents force format parameter /// Represents force format parameter
/// </summary> /// </summary>
public class ForceFormatArgument : IArgument public class ForceFormatArgument : IArgument
{ {
private readonly string _format; private readonly string _format;
public ForceFormatArgument(string format) public ForceFormatArgument(string format)
{ {
_format = format; _format = format;
@ -20,4 +21,3 @@ namespace FFMpegCore.Arguments
public string Text => $"-f {_format}"; public string Text => $"-f {_format}";
} }
}

View file

@ -1,17 +1,15 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class ForcePixelFormat : IArgument public class ForcePixelFormat : IArgument
{ {
public string PixelFormat { get; }
public string Text => $"-pix_fmt {PixelFormat}";
public ForcePixelFormat(string format) public ForcePixelFormat(string format)
{ {
PixelFormat = format; PixelFormat = format;
} }
public ForcePixelFormat(PixelFormat format) : this(format.Name) { } public ForcePixelFormat(PixelFormat format) : this(format.Name) { }
} public string PixelFormat { get; }
public string Text => $"-pix_fmt {PixelFormat}";
} }

View file

@ -1,11 +1,12 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents frame output count parameter /// Represents frame output count parameter
/// </summary> /// </summary>
public class FrameOutputCountArgument : IArgument public class FrameOutputCountArgument : IArgument
{ {
public readonly int Frames; public readonly int Frames;
public FrameOutputCountArgument(int frames) public FrameOutputCountArgument(int frames)
{ {
Frames = frames; Frames = frames;
@ -13,4 +14,3 @@
public string Text => $"-vframes {Frames}"; public string Text => $"-vframes {Frames}";
} }
}

View file

@ -1,5 +1,7 @@
namespace FFMpegCore.Arguments using System.Globalization;
{
namespace FFMpegCore.Arguments;
/// <summary> /// <summary>
/// Represents frame rate parameter /// Represents frame rate parameter
/// </summary> /// </summary>
@ -12,6 +14,5 @@
Framerate = framerate; Framerate = framerate;
} }
public string Text => $"-r {Framerate.ToString(System.Globalization.CultureInfo.InvariantCulture)}"; public string Text => $"-r {Framerate.ToString(CultureInfo.InvariantCulture)}";
}
} }

View file

@ -1,14 +1,13 @@
using System.Drawing; using System.Drawing;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class GifPaletteArgument : IArgument public class GifPaletteArgument : IArgument
{ {
private readonly int _streamIndex;
private readonly int _fps; private readonly int _fps;
private readonly Size? _size; private readonly Size? _size;
private readonly int _streamIndex;
public GifPaletteArgument(int streamIndex, int fps, Size? size) public GifPaletteArgument(int streamIndex, int fps, Size? size)
{ {
@ -19,6 +18,6 @@ namespace FFMpegCore.Arguments
private string ScaleText => _size.HasValue ? $"scale=w={_size.Value.Width}:h={_size.Value.Height}," : string.Empty; private string ScaleText => _size.HasValue ? $"scale=w={_size.Value.Width}:h={_size.Value.Height}," : string.Empty;
public string Text => $"-filter_complex \"[{_streamIndex}:v] fps={_fps},{ScaleText}split [a][b];[a] palettegen=max_colors=32 [p];[b][p] paletteuse=dither=bayer\""; public string Text =>
} $"-filter_complex \"[{_streamIndex}:v] fps={_fps},{ScaleText}split [a][b];[a] palettegen=max_colors=32 [p];[b][p] paletteuse=dither=bayer\"";
} }

View file

@ -1,16 +1,15 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class HardwareAccelerationArgument : IArgument public class HardwareAccelerationArgument : IArgument
{ {
public HardwareAccelerationDevice HardwareAccelerationDevice { get; }
public HardwareAccelerationArgument(HardwareAccelerationDevice hardwareAccelerationDevice) public HardwareAccelerationArgument(HardwareAccelerationDevice hardwareAccelerationDevice)
{ {
HardwareAccelerationDevice = hardwareAccelerationDevice; HardwareAccelerationDevice = hardwareAccelerationDevice;
} }
public HardwareAccelerationDevice HardwareAccelerationDevice { get; }
public string Text => $"-hwaccel {HardwareAccelerationDevice.ToString().ToLower()}"; public string Text => $"-hwaccel {HardwareAccelerationDevice.ToString().ToLower()}";
} }
}

View file

@ -1,27 +1,62 @@
using System.Globalization; using System.Globalization;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class HighPassFilterArgument : IAudioFilterArgument public class HighPassFilterArgument : IAudioFilterArgument
{ {
private readonly Dictionary<string, string> _arguments = new(); 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()
private readonly List<string> _precision = new() { "auto", "s16", "s32", "f32", "f64" }; {
"auto",
"s16",
"s32",
"f32",
"f64"
};
private readonly List<string> _transformTypes = new()
{
"di",
"dii",
"tdi",
"tdii",
"latt",
"svf",
"zdf"
};
private readonly List<string> _widthTypes = new()
{
"h",
"q",
"o",
"s",
"k"
};
/// <summary> /// <summary>
/// HighPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#highpass" /> /// HighPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#highpass" />
/// </summary> /// </summary>
/// <param name="frequency">Set frequency in Hz. Default is 3000.</param> /// <param name="frequency">Set frequency in Hz. Default is 3000.</param>
/// <param name="poles">Set number of poles. Default is 2.</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_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="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="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="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="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="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="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> /// <param name="block_size">
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) /// 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) if (frequency < 0)
{ {
@ -35,7 +70,7 @@ namespace FFMpegCore.Arguments
if (!_widthTypes.Contains(width_type)) if (!_widthTypes.Contains(width_type))
{ {
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes.ToString()); throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes);
} }
if (mix < 0 || mix > 1) if (mix < 0 || mix > 1)
@ -45,7 +80,7 @@ namespace FFMpegCore.Arguments
if (!_precision.Contains(precision)) if (!_precision.Contains(precision))
{ {
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision.ToString()); throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision);
} }
_arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture)); _arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture));
@ -75,4 +110,3 @@ namespace FFMpegCore.Arguments
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

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public interface IArgument public interface IArgument
{ {
/// <summary> /// <summary>
@ -7,4 +7,3 @@
/// </summary> /// </summary>
string Text { get; } string Text { get; }
} }
}

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class ID3V2VersionArgument : IArgument public class ID3V2VersionArgument : IArgument
{ {
private readonly int _version; private readonly int _version;
@ -11,4 +11,3 @@
public string Text => $"-id3v2_version {_version}"; public string Text => $"-id3v2_version {_version}";
} }
}

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public interface IDynamicArgument public interface IDynamicArgument
{ {
/// <summary> /// <summary>
@ -8,6 +8,5 @@
/// <param name="context"></param> /// <param name="context"></param>
/// <returns></returns> /// <returns></returns>
//public string GetText(StringBuilder context); //public string GetText(StringBuilder context);
public string GetText(IEnumerable<IArgument> context); string GetText(IEnumerable<IArgument> context);
}
} }

View file

@ -1,6 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public interface IInputArgument : IInputOutputArgument public interface IInputArgument : IInputOutputArgument
{ {
} }
}

View file

@ -1,9 +1,8 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public interface IInputOutputArgument : IArgument public interface IInputOutputArgument : IArgument
{ {
void Pre(); void Pre();
Task During(CancellationToken cancellationToken = default); Task During(CancellationToken cancellationToken = default);
void Post(); void Post();
} }
}

View file

@ -1,6 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public interface IOutputArgument : IInputOutputArgument public interface IOutputArgument : IInputOutputArgument
{ {
} }
}

View file

@ -1,12 +1,12 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents input parameter /// Represents input parameter
/// </summary> /// </summary>
public class InputArgument : IInputArgument public class InputArgument : IInputArgument
{ {
public readonly bool VerifyExists;
public readonly string FilePath; public readonly string FilePath;
public readonly bool VerifyExists;
public InputArgument(bool verifyExists, string filePaths) public InputArgument(bool verifyExists, string filePaths)
{ {
@ -24,9 +24,12 @@
} }
} }
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task During(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public void Post() { } public void Post() { }
public string Text => $"-i \"{FilePath}\""; public string Text => $"-i \"{FilePath}\"";
} }
}

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents an input device parameter /// Represents an input device parameter
/// </summary> /// </summary>
@ -12,7 +12,10 @@
Device = device; Device = device;
} }
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task During(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public void Pre() { } public void Pre() { }
@ -20,4 +23,3 @@
public string Text => $"-i {Device}"; public string Text => $"-i {Device}";
} }
}

View file

@ -1,8 +1,8 @@
using System.IO.Pipes; using System.IO.Pipes;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents input parameter for a named pipe /// Represents input parameter for a named pipe
/// </summary> /// </summary>
@ -28,4 +28,3 @@ namespace FFMpegCore.Arguments
await Writer.WriteAsync(Pipe, token).ConfigureAwait(false); await Writer.WriteAsync(Pipe, token).ConfigureAwait(false);
} }
} }
}

View file

@ -1,11 +1,12 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents loop parameter /// Represents loop parameter
/// </summary> /// </summary>
public class LoopArgument : IArgument public class LoopArgument : IArgument
{ {
public readonly int Times; public readonly int Times;
public LoopArgument(int times) public LoopArgument(int times)
{ {
Times = times; Times = times;
@ -13,4 +14,3 @@
public string Text => $"-loop {Times}"; public string Text => $"-loop {Times}";
} }
}

View file

@ -1,27 +1,62 @@
using System.Globalization; using System.Globalization;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class LowPassFilterArgument : IAudioFilterArgument public class LowPassFilterArgument : IAudioFilterArgument
{ {
private readonly Dictionary<string, string> _arguments = new(); 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()
private readonly List<string> _precision = new() { "auto", "s16", "s32", "f32", "f64" }; {
"auto",
"s16",
"s32",
"f32",
"f64"
};
private readonly List<string> _transformTypes = new()
{
"di",
"dii",
"tdi",
"tdii",
"latt",
"svf",
"zdf"
};
private readonly List<string> _widthTypes = new()
{
"h",
"q",
"o",
"s",
"k"
};
/// <summary> /// <summary>
/// LowPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#lowpass" /> /// LowPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#lowpass" />
/// </summary> /// </summary>
/// <param name="frequency">Set frequency in Hz. Default is 3000.</param> /// <param name="frequency">Set frequency in Hz. Default is 3000.</param>
/// <param name="poles">Set number of poles. Default is 2.</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_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="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="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="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="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="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="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> /// <param name="block_size">
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) /// 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) if (frequency < 0)
{ {
@ -35,7 +70,7 @@ namespace FFMpegCore.Arguments
if (!_widthTypes.Contains(width_type)) if (!_widthTypes.Contains(width_type))
{ {
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes.ToString()); throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes);
} }
if (mix < 0 || mix > 1) if (mix < 0 || mix > 1)
@ -45,7 +80,7 @@ namespace FFMpegCore.Arguments
if (!_precision.Contains(precision)) if (!_precision.Contains(precision))
{ {
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision.ToString()); throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision);
} }
_arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture)); _arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture));
@ -75,4 +110,3 @@ namespace FFMpegCore.Arguments
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

@ -1,11 +1,9 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class MapMetadataArgument : IInputArgument, IDynamicArgument public class MapMetadataArgument : IInputArgument, IDynamicArgument
{ {
private readonly int? _inputIndex; private readonly int? _inputIndex;
public string Text => GetText(null);
/// <summary> /// <summary>
/// Null means it takes the last input used before this argument /// Null means it takes the last input used before this argument
/// </summary> /// </summary>
@ -37,6 +35,8 @@
return $"-map_metadata {index}"; return $"-map_metadata {index}";
} }
public string Text => GetText(null);
public Task During(CancellationToken cancellationToken = default) public Task During(CancellationToken cancellationToken = default)
{ {
return Task.CompletedTask; return Task.CompletedTask;
@ -50,4 +50,3 @@
{ {
} }
} }
}

View file

@ -1,16 +1,16 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents choice of stream by the stream specifier /// 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 _streamIndex;
private readonly Channel _channel; private readonly Channel _channel;
private readonly int _inputFileIndex;
private readonly bool _negativeMap; private readonly bool _negativeMap;
private readonly int _streamIndex;
public MapStreamArgument(int streamIndex, int inputFileIndex, Channel channel = Channel.All, bool negativeMap = false) public MapStreamArgument(int streamIndex, int inputFileIndex, Channel channel = Channel.All, bool negativeMap = false)
{ {
@ -28,4 +28,3 @@ namespace FFMpegCore.Arguments
public string Text => $"-map {(_negativeMap ? "-" : "")}{_inputFileIndex}{_channel.StreamType()}:{_streamIndex}"; public string Text => $"-map {(_negativeMap ? "-" : "")}{_inputFileIndex}{_channel.StreamType()}:{_streamIndex}";
} }
}

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class MetaDataArgument : IInputArgument, IDynamicArgument public class MetaDataArgument : IInputArgument, IDynamicArgument
{ {
private readonly string _metaDataContent; private readonly string _metaDataContent;
@ -10,14 +10,6 @@
_metaDataContent = metaDataContent; _metaDataContent = metaDataContent;
} }
public string Text => GetText(null);
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
public void Pre() => File.WriteAllText(_tempFileName, _metaDataContent);
public void Post() => File.Delete(_tempFileName);
public string GetText(IEnumerable<IArgument>? arguments) public string GetText(IEnumerable<IArgument>? arguments)
{ {
arguments ??= Enumerable.Empty<IArgument>(); arguments ??= Enumerable.Empty<IArgument>();
@ -29,5 +21,21 @@
return $"-i \"{_tempFileName}\" -map_metadata {index}"; return $"-i \"{_tempFileName}\" -map_metadata {index}";
} }
public string Text => GetText(null);
public Task During(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public void Pre()
{
File.WriteAllText(_tempFileName, _metaDataContent);
}
public void Post()
{
File.Delete(_tempFileName);
} }
} }

View file

@ -1,12 +1,12 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents input parameters for multiple files /// Represents input parameters for multiple files
/// </summary> /// </summary>
public class MultiInputArgument : IInputArgument public class MultiInputArgument : IInputArgument
{ {
public readonly bool VerifyExists;
public readonly IEnumerable<string> FilePaths; public readonly IEnumerable<string> FilePaths;
public readonly bool VerifyExists;
public MultiInputArgument(bool verifyExists, IEnumerable<string> filePaths) public MultiInputArgument(bool verifyExists, IEnumerable<string> filePaths)
{ {
@ -36,7 +36,11 @@
} }
} }
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task During(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public void Post() { } public void Post() { }
/// <summary> /// <summary>
@ -44,4 +48,3 @@
/// </summary> /// </summary>
public string Text => string.Join(" ", FilePaths.Select(filePath => $"-i \"{filePath}\"")); public string Text => string.Join(" ", FilePaths.Select(filePath => $"-i \"{filePath}\""));
} }
}

View file

@ -1,14 +1,14 @@
using FFMpegCore.Exceptions; using FFMpegCore.Exceptions;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents output parameter /// Represents output parameter
/// </summary> /// </summary>
public class OutputArgument : IOutputArgument public class OutputArgument : IOutputArgument
{ {
public readonly string Path;
public readonly bool Overwrite; public readonly bool Overwrite;
public readonly string Path;
public OutputArgument(string path, bool overwrite = true) public OutputArgument(string path, bool overwrite = true)
{ {
@ -16,6 +16,10 @@ namespace FFMpegCore.Arguments
Overwrite = overwrite; Overwrite = overwrite;
} }
public OutputArgument(FileInfo value) : this(value.FullName) { }
public OutputArgument(Uri value) : this(value.AbsolutePath) { }
public void Pre() public void Pre()
{ {
if (!Overwrite && File.Exists(Path)) if (!Overwrite && File.Exists(Path))
@ -23,15 +27,15 @@ namespace FFMpegCore.Arguments
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)
{
return Task.CompletedTask;
}
public void Post() public void Post()
{ {
} }
public OutputArgument(FileInfo value) : this(value.FullName) { }
public OutputArgument(Uri value) : this(value.AbsolutePath) { }
public string Text => $"\"{Path}\"{(Overwrite ? " -y" : string.Empty)}"; public string Text => $"\"{Path}\"{(Overwrite ? " -y" : string.Empty)}";
} }
}

View file

@ -1,8 +1,8 @@
using System.IO.Pipes; using System.IO.Pipes;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class OutputPipeArgument : PipeArgument, IOutputArgument public class OutputPipeArgument : PipeArgument, IOutputArgument
{ {
public readonly IPipeSink Reader; public readonly IPipeSink Reader;
@ -25,4 +25,3 @@ namespace FFMpegCore.Arguments
await Reader.ReadAsync(Pipe, token).ConfigureAwait(false); await Reader.ReadAsync(Pipe, token).ConfigureAwait(false);
} }
} }
}

View file

@ -1,6 +1,5 @@
 namespace FFMpegCore.Arguments;
namespace FFMpegCore.Arguments
{
internal class OutputTeeArgument : IOutputArgument internal class OutputTeeArgument : IOutputArgument
{ {
private readonly FFMpegMultiOutputOptions _options; private readonly FFMpegMultiOutputOptions _options;
@ -17,7 +16,10 @@ namespace FFMpegCore.Arguments
public string Text => $"-f tee \"{string.Join("|", _options.Outputs.Select(MapOptions))}\""; public string Text => $"-f tee \"{string.Join("|", _options.Outputs.Select(MapOptions))}\"";
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task During(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public void Post() public void Post()
{ {
@ -46,7 +48,8 @@ namespace FFMpegCore.Arguments
{ {
return map.Text.Replace("-map ", "select=\\'") + "\\'"; return map.Text.Replace("-map ", "select=\\'") + "\\'";
} }
else if (argument is BitStreamFilterArgument bitstreamFilter)
if (argument is BitStreamFilterArgument bitstreamFilter)
{ {
return bitstreamFilter.Text.Replace("-bsf:", "bsfs/").Replace(' ', '='); return bitstreamFilter.Text.Replace("-bsf:", "bsfs/").Replace(' ', '=');
} }
@ -54,4 +57,3 @@ namespace FFMpegCore.Arguments
return argument.Text.TrimStart('-').Replace(' ', '='); return argument.Text.TrimStart('-').Replace(' ', '=');
} }
} }
}

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents outputting to url using supported protocols /// Represents outputting to url using supported protocols
/// See http://ffmpeg.org/ffmpeg-protocols.html /// See http://ffmpeg.org/ffmpeg-protocols.html
@ -15,10 +15,12 @@
public void Post() { } public void Post() { }
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask; public Task During(CancellationToken cancellationToken = default)
{
return Task.CompletedTask;
}
public void Pre() { } public void Pre() { }
public string Text => Url; public string Text => Url;
} }
}

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents overwrite parameter /// Represents overwrite parameter
/// If output file should be overwritten if exists /// If output file should be overwritten if exists
@ -8,4 +8,3 @@
{ {
public string Text => "-y"; public string Text => "-y";
} }
}

View file

@ -1,7 +1,7 @@
using FFMpegCore.Extend; using FFMpegCore.Extend;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class PadArgument : IVideoFilterArgument public class PadArgument : IVideoFilterArgument
{ {
private readonly PadOptions _options; private readonly PadOptions _options;
@ -13,31 +13,12 @@ namespace FFMpegCore.Arguments
public string Key => "pad"; public string Key => "pad";
public string Value => _options.TextInternal; public string Value => _options.TextInternal;
} }
public class PadOptions public class PadOptions
{ {
public readonly Dictionary<string, string> Parameters = new(); 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) private PadOptions(string? width, string? height)
{ {
if (width == null && height == null) if (width == null && height == null)
@ -60,5 +41,22 @@ namespace FFMpegCore.Arguments
{ {
Parameters.Add("aspect", aspectRatio); Parameters.Add("aspect", aspectRatio);
} }
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;
} }
} }

View file

@ -1,12 +1,12 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Mix channels with specific gain levels. /// Mix channels with specific gain levels.
/// </summary> /// </summary>
public class PanArgument : IAudioFilterArgument public class PanArgument : IAudioFilterArgument
{ {
public readonly string ChannelLayout;
private readonly string[] _outputDefinitions; private readonly string[] _outputDefinitions;
public readonly string ChannelLayout;
/// <summary> /// <summary>
/// Mix channels with specific gain levels <see href="https://ffmpeg.org/ffmpeg-filters.html#toc-pan-1" /> /// Mix channels with specific gain levels <see href="https://ffmpeg.org/ffmpeg-filters.html#toc-pan-1" />
@ -58,4 +58,3 @@
public string Value => public string Value =>
string.Join("|", Enumerable.Empty<string>().Append(ChannelLayout).Concat(_outputDefinitions)); string.Join("|", Enumerable.Empty<string>().Append(ChannelLayout).Concat(_outputDefinitions));
} }
}

View file

@ -2,23 +2,28 @@
using System.IO.Pipes; using System.IO.Pipes;
using FFMpegCore.Pipes; using FFMpegCore.Pipes;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public abstract class PipeArgument public abstract class PipeArgument
{ {
private readonly PipeDirection _direction;
private readonly object _pipeLock = new();
protected PipeArgument(PipeDirection direction)
{
PipeName = PipeHelpers.GetUniquePipeName();
_direction = direction;
}
private string PipeName { get; } private string PipeName { get; }
public string PipePath => PipeHelpers.GetPipePath(PipeName); public string PipePath => PipeHelpers.GetPipePath(PipeName);
protected NamedPipeServerStream Pipe { get; private set; } = null!; protected NamedPipeServerStream Pipe { get; private set; } = null!;
private readonly PipeDirection _direction; public abstract string Text { get; }
protected PipeArgument(PipeDirection direction)
{
PipeName = PipeHelpers.GetUnqiuePipeName();
_direction = direction;
}
public void Pre() public void Pre()
{
lock (_pipeLock)
{ {
if (Pipe != null) if (Pipe != null)
{ {
@ -27,13 +32,17 @@ namespace FFMpegCore.Arguments
Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
} }
}
public void Post() public void Post()
{ {
Debug.WriteLine($"Disposing NamedPipeServerStream on {GetType().Name}"); Debug.WriteLine($"Disposing NamedPipeServerStream on {GetType().Name}");
lock (_pipeLock)
{
Pipe?.Dispose(); Pipe?.Dispose();
Pipe = null!; Pipe = null!;
} }
}
public async Task During(CancellationToken cancellationToken = default) public async Task During(CancellationToken cancellationToken = default)
{ {
@ -48,14 +57,15 @@ namespace FFMpegCore.Arguments
finally finally
{ {
Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}"); Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}");
lock (_pipeLock)
{
if (Pipe is { IsConnected: true }) if (Pipe is { IsConnected: true })
{ {
Pipe.Disconnect(); Pipe.Disconnect();
} }
} }
} }
}
protected abstract Task ProcessDataAsync(CancellationToken token); protected abstract Task ProcessDataAsync(CancellationToken token);
public abstract string Text { get; }
}
} }

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Remove metadata argument /// Remove metadata argument
/// </summary> /// </summary>
@ -7,4 +7,3 @@
{ {
public string Text => "-map_metadata -1"; public string Text => "-map_metadata -1";
} }
}

View file

@ -1,14 +1,15 @@
using System.Drawing; using System.Drawing;
using FFMpegCore.Enums; using FFMpegCore.Enums;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents scale parameter /// Represents scale parameter
/// </summary> /// </summary>
public class ScaleArgument : IVideoFilterArgument public class ScaleArgument : IVideoFilterArgument
{ {
public readonly Size? Size; public readonly Size? Size;
public ScaleArgument(Size? size) public ScaleArgument(Size? size)
{ {
Size = size; Size = size;
@ -18,10 +19,9 @@ namespace FFMpegCore.Arguments
public ScaleArgument(VideoSize videosize) public ScaleArgument(VideoSize videosize)
{ {
Size = videosize == VideoSize.Original ? null : (Size?)new Size(-1, (int)videosize); Size = videosize == VideoSize.Original ? null : new Size(-1, (int)videosize);
} }
public string Key { get; } = "scale"; public string Key { get; } = "scale";
public string Value => Size == null ? string.Empty : $"{Size.Value.Width}:{Size.Value.Height}"; public string Value => Size == null ? string.Empty : $"{Size.Value.Width}:{Size.Value.Height}";
} }
}

View file

@ -1,7 +1,7 @@
using FFMpegCore.Extend; using FFMpegCore.Extend;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents seek parameter /// Represents seek parameter
/// </summary> /// </summary>
@ -16,4 +16,3 @@ namespace FFMpegCore.Arguments
public string Text => SeekTo.HasValue ? $"-ss {SeekTo.Value.ToLongString()}" : string.Empty; public string Text => SeekTo.HasValue ? $"-ss {SeekTo.Value.ToLongString()}" : string.Empty;
} }
}

View file

@ -1,7 +1,7 @@
using FFMpegCore.Enums; using FFMpegCore.Enums;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class SetMirroringArgument : IVideoFilterArgument public class SetMirroringArgument : IVideoFilterArgument
{ {
public SetMirroringArgument(Mirroring mirroring) public SetMirroringArgument(Mirroring mirroring)
@ -21,4 +21,3 @@ namespace FFMpegCore.Arguments
_ => throw new ArgumentOutOfRangeException(nameof(Mirroring)) _ => throw new ArgumentOutOfRangeException(nameof(Mirroring))
}; };
} }
}

View file

@ -1,5 +1,5 @@
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
/// <summary> /// <summary>
/// Represents shortest parameter /// Represents shortest parameter
/// </summary> /// </summary>
@ -14,4 +14,3 @@
public string Text => Shortest ? "-shortest" : string.Empty; public string Text => Shortest ? "-shortest" : string.Empty;
} }
}

View file

@ -1,18 +1,24 @@
using System.Globalization; using System.Globalization;
namespace FFMpegCore.Arguments namespace FFMpegCore.Arguments;
{
public class SilenceDetectArgument : IAudioFilterArgument public class SilenceDetectArgument : IAudioFilterArgument
{ {
private readonly Dictionary<string, string> _arguments = new(); private readonly Dictionary<string, string> _arguments = new();
/// <summary> /// <summary>
/// Silence Detection. <see href="https://ffmpeg.org/ffmpeg-filters.html#silencedetect" /> /// Silence Detection. <see href="https://ffmpeg.org/ffmpeg-filters.html#silencedetect" />
/// </summary> /// </summary>
/// <param name="noise_type">Set noise type to db (decibel) or ar (amplitude ratio). Default is dB</param> /// <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="noise">
/// <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> /// 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> /// <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) public SilenceDetectArgument(string noise_type = "db", double noise = 60, double duration = 2, bool mono = false)
{ {
if (noise_type == "db") if (noise_type == "db")
@ -36,4 +42,3 @@ namespace FFMpegCore.Arguments
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}")); public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
} }
}

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