mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2025-12-16 11:05:44 +00:00
Compare commits
No commits in common. "46512524279735e94254f32f844e8a32f0ddf87b" and "6f1a8d77d75646a344850744717cd170e0fe8cfc" have entirely different histories.
4651252427
...
6f1a8d77d7
173 changed files with 7460 additions and 8579 deletions
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest]
|
||||
os: [windows-latest, ubuntu-latest, macos-13]
|
||||
timeout-minutes: 7
|
||||
steps:
|
||||
|
||||
|
|
@ -30,15 +30,14 @@ jobs:
|
|||
with:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Lint with dotnet
|
||||
- name: Lint with dotnet
|
||||
run: dotnet format FFMpegCore.sln --severity warn --verify-no-changes
|
||||
|
||||
- name: Setup FFmpeg
|
||||
uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae # 1.1.0
|
||||
- name: Prepare FFMpeg
|
||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||
with:
|
||||
version: ${{ matrix.os != 'macos-latest' && '7.1' || '711' }}
|
||||
token: ${{ github.token }}
|
||||
ffmpeg-version: 6.0.1
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Test with dotnet
|
||||
run: dotnet test FFMpegCore.sln --collect "XPlat Code Coverage" --logger GitHubActions
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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\FFMpegCore.csproj"/>
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ var outputStream = new MemoryStream();
|
|||
}
|
||||
|
||||
{
|
||||
FFMpeg.JoinImageSequence(@"..\joined_video.mp4", 1, @"..\1.png", @"..\2.png", @"..\3.png");
|
||||
FFMpeg.JoinImageSequence(@"..\joined_video.mp4", frameRate: 1, @"..\1.png", @"..\2.png", @"..\3.png");
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -90,11 +90,7 @@ var inputImagePath = "/path/to/input/image";
|
|||
skiaSharpImage.AddAudio(inputAudioPath, outputPath);
|
||||
}
|
||||
|
||||
IVideoFrame GetNextFrame()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IVideoFrame GetNextFrame() => throw new NotImplementedException();
|
||||
{
|
||||
IEnumerable<IVideoFrame> CreateFrames(int count)
|
||||
{
|
||||
|
|
@ -104,8 +100,7 @@ IVideoFrame GetNextFrame()
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
namespace FFMpegCore.Extensions.Downloader.Enums;
|
||||
|
||||
[Flags]
|
||||
public enum FFMpegBinaries : ushort
|
||||
{
|
||||
FFMpeg = 1,
|
||||
FFProbe = 2,
|
||||
FFPlay = 4
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
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
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
namespace FFMpegCore.Extensions.Downloader.Enums;
|
||||
|
||||
public enum SupportedPlatforms : ushort
|
||||
{
|
||||
Windows64,
|
||||
Windows32,
|
||||
Linux64,
|
||||
Linux32,
|
||||
LinuxArmhf,
|
||||
LinuxArmel,
|
||||
LinuxArm64,
|
||||
Osx64
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<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>
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
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");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
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; }
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
using SkiaSharp;
|
||||
|
||||
namespace FFMpegCore.Extensions.SkiaSharp;
|
||||
|
||||
public static class BitmapExtensions
|
||||
namespace FFMpegCore.Extensions.SkiaSharp
|
||||
{
|
||||
public static class BitmapExtensions
|
||||
{
|
||||
public static bool AddAudio(this SKBitmap poster, string audio, string output)
|
||||
{
|
||||
var destination = $"{Environment.TickCount}.png";
|
||||
|
|
@ -24,4 +24,5 @@ public static class BitmapExtensions
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,24 @@
|
|||
using FFMpegCore.Pipes;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FFMpegCore.Extensions.SkiaSharp;
|
||||
|
||||
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
||||
namespace FFMpegCore.Extensions.SkiaSharp
|
||||
{
|
||||
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)
|
||||
{
|
||||
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
|
||||
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)
|
||||
{
|
||||
var data = Source.Bytes;
|
||||
|
|
@ -36,6 +31,11 @@ public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
|||
await stream.WriteAsync(data, 0, data.Length, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Source.Dispose();
|
||||
}
|
||||
|
||||
private static string ConvertStreamFormat(SKColorType fmt)
|
||||
{
|
||||
// TODO: Add support for additional formats
|
||||
|
|
@ -55,4 +55,5 @@ public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
|||
throw new NotSupportedException($"Not supported pixel format {fmt}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,23 +3,21 @@
|
|||
<PropertyGroup>
|
||||
<IsPackable>true</IsPackable>
|
||||
<Description>Image extension for FFMpegCore using SkiaSharp</Description>
|
||||
<PackageVersion>5.0.3</PackageVersion>
|
||||
<PackageVersion>5.0.2</PackageVersion>
|
||||
<PackageOutputPath>../nupkg</PackageOutputPath>
|
||||
<PackageReleaseNotes>
|
||||
- Updated dependencies
|
||||
</PackageReleaseNotes>
|
||||
<PackageTags>ffmpeg ffprobe convert video audio image mediafile resize analyze muxing skia skiasharp</PackageTags>
|
||||
<PackageReleaseNotes>Bump dependencies</PackageReleaseNotes>
|
||||
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze muxing skiasharp</PackageTags>
|
||||
<Authors>Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev, Dimitri Vranken</Authors>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp" Version="3.119.1"/>
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.119.1"/>
|
||||
<PackageReference Include="SkiaSharp" Version="3.116.1" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.116.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj"/>
|
||||
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
using FFMpegCore.Pipes;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FFMpegCore.Extensions.SkiaSharp;
|
||||
|
||||
public static class FFMpegImage
|
||||
namespace FFMpegCore.Extensions.SkiaSharp
|
||||
{
|
||||
public static class FFMpegImage
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
|
|
@ -30,7 +30,6 @@ public static class FFMpegImage
|
|||
using var bitmap = SKBitmap.Decode(ms);
|
||||
return bitmap.Copy();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
|
|
@ -40,8 +39,7 @@ public static class FFMpegImage
|
|||
/// <param name="streamIndex">Selected video stream index.</param>
|
||||
/// <param name="inputFileIndex">Input file index</param>
|
||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||
public static async Task<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 (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||
|
|
@ -55,4 +53,5 @@ public static class FFMpegImage
|
|||
ms.Position = 0;
|
||||
return SKBitmap.Decode(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using System.Drawing;
|
||||
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common;
|
||||
|
||||
public static class BitmapExtensions
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common
|
||||
{
|
||||
public static class BitmapExtensions
|
||||
{
|
||||
public static bool AddAudio(this Image poster, string audio, string output)
|
||||
{
|
||||
var destination = $"{Environment.TickCount}.png";
|
||||
|
|
@ -20,4 +20,5 @@ public static class BitmapExtensions
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,29 +3,24 @@ using System.Drawing.Imaging;
|
|||
using System.Runtime.InteropServices;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common;
|
||||
|
||||
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common
|
||||
{
|
||||
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)
|
||||
{
|
||||
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
|
||||
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)
|
||||
{
|
||||
var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
|
||||
|
|
@ -58,6 +53,11 @@ public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Source.Dispose();
|
||||
}
|
||||
|
||||
private static string ConvertStreamFormat(PixelFormat fmt)
|
||||
{
|
||||
switch (fmt)
|
||||
|
|
@ -83,4 +83,5 @@ public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
|||
throw new NotSupportedException($"Not supported pixel format {fmt}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,16 @@
|
|||
<PropertyGroup>
|
||||
<IsPackable>true</IsPackable>
|
||||
<Description>Image extension for FFMpegCore using System.Common.Drawing</Description>
|
||||
<PackageVersion>5.0.3</PackageVersion>
|
||||
<PackageVersion>5.0.2</PackageVersion>
|
||||
<PackageOutputPath>../nupkg</PackageOutputPath>
|
||||
<PackageReleaseNotes>
|
||||
- Updated dependencies
|
||||
</PackageReleaseNotes>
|
||||
<PackageTags>ffmpeg ffprobe convert video audio image mediafile resize analyze muxing</PackageTags>
|
||||
<PackageReleaseNotes>Bump dependencies</PackageReleaseNotes>
|
||||
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze muxing</PackageTags>
|
||||
<Authors>Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev</Authors>
|
||||
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.10"/>
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System.Drawing;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common;
|
||||
|
||||
public static class FFMpegImage
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common
|
||||
{
|
||||
public static class FFMpegImage
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
|
|
@ -39,8 +39,7 @@ public static class FFMpegImage
|
|||
/// <param name="streamIndex">Selected video stream index.</param>
|
||||
/// <param name="inputFileIndex">Input file index</param>
|
||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||
public static async Task<Bitmap> SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null,
|
||||
int inputFileIndex = 0)
|
||||
public static async Task<Bitmap> SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
||||
{
|
||||
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
|
||||
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||
|
|
@ -54,4 +53,5 @@ public static class FFMpegImage
|
|||
ms.Position = 0;
|
||||
return new Bitmap(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
using System.Drawing;
|
||||
using FFMpegCore.Arguments;
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Pipes;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class ArgumentBuilderTest
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class ArgumentBuilderTest
|
||||
{
|
||||
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" };
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -257,16 +256,14 @@ public class ArgumentBuilderTest
|
|||
[TestMethod]
|
||||
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);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +338,7 @@ public class ArgumentBuilderTest
|
|||
.OutputToFile("output.mp4", false, opt => opt
|
||||
.WithVideoFilters(filterOptions => filterOptions
|
||||
.HardBurnSubtitle(SubtitleHardBurnOptions
|
||||
.Create("sample.srt")
|
||||
.Create(subtitlePath: "sample.srt")
|
||||
.SetCharacterEncoding("UTF-8")
|
||||
.SetOriginalSize(1366, 768)
|
||||
.SetSubtitleIndex(0)
|
||||
|
|
@ -350,8 +347,7 @@ public class ArgumentBuilderTest
|
|||
.WithParameter("PrimaryColour", "&HAA00FF00")))))
|
||||
.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);
|
||||
}
|
||||
|
||||
|
|
@ -363,7 +359,7 @@ public class ArgumentBuilderTest
|
|||
.OutputToFile("output.mp4", false, opt => opt
|
||||
.WithVideoFilters(filterOptions => filterOptions
|
||||
.HardBurnSubtitle(SubtitleHardBurnOptions
|
||||
.Create(@"sample( \ : [ ] , ' ).srt"))))
|
||||
.Create(subtitlePath: @"sample( \ : [ ] , ' ).srt"))))
|
||||
.Arguments;
|
||||
|
||||
Assert.AreEqual(@"-i ""input.mp4"" -vf ""subtitles='sample( \\ \: \[ \] \, '\\\'' ).srt'"" ""output.mp4""",
|
||||
|
|
@ -498,7 +494,7 @@ public class ArgumentBuilderTest
|
|||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.aaxc", false, x => x.WithAudibleEncryptionKeys("123", "456"))
|
||||
.MapMetaData()
|
||||
.OutputToFile("output.m4b", true, x => x.WithTagVersion().DisableChannel(Channel.Video).CopyChannel(Channel.Audio))
|
||||
.OutputToFile("output.m4b", true, x => x.WithTagVersion(3).DisableChannel(Channel.Video).CopyChannel(Channel.Audio))
|
||||
.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);
|
||||
|
|
@ -582,11 +578,13 @@ public class ArgumentBuilderTest
|
|||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4")
|
||||
.MultiOutput(args => args
|
||||
.OutputToFile("output.mp4", true, args => args.CopyChannel())
|
||||
.OutputToFile("output.ts", false, args => args.CopyChannel().ForceFormat("mpegts"))
|
||||
.OutputToFile("output.mp4", overwrite: true, args => args.CopyChannel())
|
||||
.OutputToFile("output.ts", overwrite: false, args => args.CopyChannel().ForceFormat("mpegts"))
|
||||
.OutputToUrl("http://server/path", options => options.ForceFormat("webm")))
|
||||
.Arguments;
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -594,10 +592,10 @@ public class ArgumentBuilderTest
|
|||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4")
|
||||
.MultiOutput(args => args
|
||||
.OutputToFile("sd.mp4", true, args => args.Resize(1200, 720))
|
||||
.OutputToFile("hd.mp4", false, args => args.Resize(1920, 1080)))
|
||||
.OutputToFile("sd.mp4", overwrite: true, args => args.Resize(1200, 720))
|
||||
.OutputToFile("hd.mp4", overwrite: false, args => args.Resize(1920, 1080)))
|
||||
.Arguments;
|
||||
Assert.AreEqual("""
|
||||
Assert.AreEqual($"""
|
||||
-i "input.mp4" -s 1200x720 "sd.mp4" -y -s 1920x1080 "hd.mp4"
|
||||
""", str);
|
||||
}
|
||||
|
|
@ -607,14 +605,13 @@ public class ArgumentBuilderTest
|
|||
{
|
||||
var str = FFMpegArguments.FromFileInput("input.mp4")
|
||||
.OutputToTee(args => args
|
||||
.OutputToFile("output.mp4", false, args => args.WithFastStart())
|
||||
.OutputToFile("output.mp4", overwrite: false, args => args.WithFastStart())
|
||||
.OutputToUrl("http://server/path", options => options.ForceFormat("mpegts").SelectStream(0, channel: Channel.Video)))
|
||||
.Arguments;
|
||||
Assert.AreEqual("""
|
||||
Assert.AreEqual($"""
|
||||
-i "input.mp4" -f tee "[movflags=faststart]output.mp4|[f=mpegts:select=\'0:v:0\']http://server/path"
|
||||
""", str);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Builder_BuildString_MultiInput()
|
||||
{
|
||||
|
|
@ -623,24 +620,26 @@ public class ArgumentBuilderTest
|
|||
var ffmpegArgs = $"-filter_complex \"{mixFilter}\" -map \"[final]\"";
|
||||
var str = FFMpegArguments
|
||||
.FromFileInput(_multiFiles)
|
||||
.OutputToFile("output.mp3", true, options => options
|
||||
.OutputToFile("output.mp3", overwrite: true, options => options
|
||||
.WithCustomArgument(ffmpegArgs)
|
||||
.WithAudioCodec(AudioCodec.LibMp3Lame) // Set the audio codec to MP3
|
||||
.WithAudioBitrate(128) // Set the bitrate to 128kbps
|
||||
.WithAudioSamplingRate() // Set the sample rate to 48kHz
|
||||
.WithAudioSamplingRate(48000) // Set the sample rate to 48kHz
|
||||
.WithoutMetadata() // Remove metadata
|
||||
.WithCustomArgument("-ac 2 -write_xing 0 -id3v2_version 0")) // Force 2 Channels
|
||||
.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]
|
||||
public void Pre_VerifyExists_AllFilesExist()
|
||||
{
|
||||
// Arrange
|
||||
var filePaths = new List<string> { Path.GetTempFileName(), Path.GetTempFileName(), Path.GetTempFileName() };
|
||||
var filePaths = new List<string>
|
||||
{
|
||||
Path.GetTempFileName(),
|
||||
Path.GetTempFileName(),
|
||||
Path.GetTempFileName()
|
||||
};
|
||||
var argument = new MultiInputArgument(true, filePaths);
|
||||
try
|
||||
{
|
||||
|
|
@ -661,12 +660,17 @@ public class ArgumentBuilderTest
|
|||
public void Pre_VerifyExists_SomeFilesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var filePaths = new List<string> { Path.GetTempFileName(), "file2.mp4", "file3.mp4" };
|
||||
var filePaths = new List<string>
|
||||
{
|
||||
Path.GetTempFileName(),
|
||||
"file2.mp4",
|
||||
"file3.mp4"
|
||||
};
|
||||
var argument = new MultiInputArgument(true, filePaths);
|
||||
try
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.ThrowsExactly<FileNotFoundException>(() => argument.Pre());
|
||||
Assert.ThrowsException<FileNotFoundException>(() => argument.Pre());
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -679,44 +683,15 @@ public class ArgumentBuilderTest
|
|||
public void Pre_VerifyExists_NoFilesExist()
|
||||
{
|
||||
// Arrange
|
||||
var filePaths = new List<string> { "file1.mp4", "file2.mp4", "file3.mp4" };
|
||||
var filePaths = new List<string>
|
||||
{
|
||||
"file1.mp4",
|
||||
"file2.mp4",
|
||||
"file3.mp4"
|
||||
};
|
||||
var argument = new MultiInputArgument(true, filePaths);
|
||||
// Act & Assert
|
||||
Assert.ThrowsExactly<FileNotFoundException>(() => argument.Pre());
|
||||
Assert.ThrowsException<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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
|
||||
|
|
@ -3,12 +3,13 @@ using FFMpegCore.Exceptions;
|
|||
using FFMpegCore.Extend;
|
||||
using FFMpegCore.Pipes;
|
||||
using FFMpegCore.Test.Resources;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class AudioTest
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class AudioTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void Audio_Remove()
|
||||
{
|
||||
|
|
@ -17,8 +18,8 @@ public class AudioTest
|
|||
FFMpeg.Mute(TestResources.Mp4Video, outputFile);
|
||||
var analysis = FFProbe.Analyse(outputFile);
|
||||
|
||||
Assert.IsNotEmpty(analysis.VideoStreams);
|
||||
Assert.IsEmpty(analysis.AudioStreams);
|
||||
Assert.IsTrue(analysis.VideoStreams.Any());
|
||||
Assert.IsTrue(!analysis.AudioStreams.Any());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -29,10 +30,9 @@ public class AudioTest
|
|||
FFMpeg.ExtractAudio(TestResources.Mp4Video, outputFile);
|
||||
var analysis = FFProbe.Analyse(outputFile);
|
||||
|
||||
Assert.IsNotEmpty(analysis.AudioStreams);
|
||||
Assert.IsEmpty(analysis.VideoStreams);
|
||||
Assert.IsTrue(!analysis.VideoStreams.Any());
|
||||
Assert.IsTrue(analysis.AudioStreams.Any());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Audio_FromRaw()
|
||||
{
|
||||
|
|
@ -65,19 +65,27 @@ public class AudioTest
|
|||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
FFMpeg.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile);
|
||||
var analysis = FFProbe.Analyse(TestResources.Mp3Audio);
|
||||
Assert.IsGreaterThan(0, analysis.Duration.TotalSeconds);
|
||||
Assert.IsTrue(analysis.Duration.TotalSeconds > 0);
|
||||
Assert.IsTrue(File.Exists(outputFile));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToAAC_Args_Pipe()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };
|
||||
var samples = new List<IAudioSample>
|
||||
{
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
};
|
||||
|
||||
var audioSamplesSource = new RawAudioPipeSource(samples) { Channels = 2, Format = "s8", SampleRate = 8000 };
|
||||
var audioSamplesSource = new RawAudioPipeSource(samples)
|
||||
{
|
||||
Channels = 2,
|
||||
Format = "s8",
|
||||
SampleRate = 8000,
|
||||
};
|
||||
|
||||
var success = FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
|
|
@ -87,15 +95,23 @@ public class AudioTest
|
|||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToLibVorbis_Args_Pipe()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };
|
||||
var samples = new List<IAudioSample>
|
||||
{
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
};
|
||||
|
||||
var audioSamplesSource = new RawAudioPipeSource(samples) { Channels = 2, Format = "s8", SampleRate = 8000 };
|
||||
var audioSamplesSource = new RawAudioPipeSource(samples)
|
||||
{
|
||||
Channels = 2,
|
||||
Format = "s8",
|
||||
SampleRate = 8000,
|
||||
};
|
||||
|
||||
var success = FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
|
|
@ -105,15 +121,23 @@ public class AudioTest
|
|||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Audio_ToAAC_Args_Pipe_Async()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };
|
||||
var samples = new List<IAudioSample>
|
||||
{
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
};
|
||||
|
||||
var audioSamplesSource = new RawAudioPipeSource(samples) { Channels = 2, Format = "s8", SampleRate = 8000 };
|
||||
var audioSamplesSource = new RawAudioPipeSource(samples)
|
||||
{
|
||||
Channels = 2,
|
||||
Format = "s8",
|
||||
SampleRate = 8000,
|
||||
};
|
||||
|
||||
var success = await FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
|
|
@ -123,13 +147,16 @@ public class AudioTest
|
|||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToAAC_Args_Pipe_ValidDefaultConfiguration()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };
|
||||
var samples = new List<IAudioSample>
|
||||
{
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
};
|
||||
|
||||
var audioSamplesSource = new RawAudioPipeSource(samples);
|
||||
|
||||
|
|
@ -141,53 +168,58 @@ public class AudioTest
|
|||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToAAC_Args_Pipe_InvalidChannels()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) { Channels = 0 };
|
||||
var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>())
|
||||
{
|
||||
Channels = 0,
|
||||
};
|
||||
|
||||
Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
|
||||
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToAAC_Args_Pipe_InvalidFormat()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) { Format = "s8le" };
|
||||
var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>())
|
||||
{
|
||||
Format = "s8le",
|
||||
};
|
||||
|
||||
Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
|
||||
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToAAC_Args_Pipe_InvalidSampleRate()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>()) { SampleRate = 0 };
|
||||
var audioSamplesSource = new RawAudioPipeSource(new List<IAudioSample>())
|
||||
{
|
||||
SampleRate = 0,
|
||||
};
|
||||
|
||||
Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
|
||||
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_Pan_ToMono()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
|
@ -201,12 +233,11 @@ public class AudioTest
|
|||
var mediaAnalysis = FFProbe.Analyse(outputFile);
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.HasCount(1, mediaAnalysis.AudioStreams);
|
||||
Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count);
|
||||
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_Pan_ToMonoNoDefinitions()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
|
@ -220,38 +251,35 @@ public class AudioTest
|
|||
var mediaAnalysis = FFProbe.Analyse(outputFile);
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.HasCount(1, mediaAnalysis.AudioStreams);
|
||||
Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count);
|
||||
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_Pan_ToMonoChannelsToOutputDefinitionsMismatch()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
Assert.ThrowsExactly<ArgumentException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
var ex = Assert.ThrowsException<ArgumentException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.Pan(1, "c0=c0", "c1=c1")))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_Pan_ToMonoChannelsLayoutToOutputDefinitionsMismatch()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.Pan("mono", "c0=c0", "c1=c1")))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_DynamicNormalizer_WithDefaultValues()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
|
@ -265,8 +293,7 @@ public class AudioTest
|
|||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_DynamicNormalizer_WithNonDefaultValues()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
|
@ -274,14 +301,14 @@ public class AudioTest
|
|||
var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.DynamicNormalizer(250, 7, 0.9, 2, 1, false, true, true, 0.5)))
|
||||
.WithAudioFilters(
|
||||
filter => filter.DynamicNormalizer(250, 7, 0.9, 2, 1, false, true, true, 0.5)))
|
||||
.ProcessSynchronously();
|
||||
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[DataTestMethod, Timeout(10000)]
|
||||
[DataRow(2)]
|
||||
[DataRow(32)]
|
||||
[DataRow(8)]
|
||||
|
|
@ -289,11 +316,13 @@ public class AudioTest
|
|||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => FFMpegArguments
|
||||
var ex = Assert.ThrowsException<ArgumentOutOfRangeException>(() => FFMpegArguments
|
||||
.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.DynamicNormalizer(filterWindow: filterWindow)))
|
||||
.WithAudioFilters(
|
||||
filter => filter.DynamicNormalizer(filterWindow: filterWindow)))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +1,34 @@
|
|||
namespace FFMpegCore.Test;
|
||||
using System.Reflection;
|
||||
using FFMpegCore.Arguments;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
[TestClass]
|
||||
public class FFMpegArgumentProcessorTest
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
private static FFMpegArgumentProcessor CreateArgumentProcessor()
|
||||
[TestClass]
|
||||
public class FFMpegArgumentProcessorTest
|
||||
{
|
||||
return FFMpegArguments
|
||||
[TestCleanup]
|
||||
public void TestInitialize()
|
||||
|
||||
{
|
||||
// After testing reset global configuration to null, to be not wrong for other test relying on configuration
|
||||
typeof(GlobalFFOptions).GetField("_current", BindingFlags.NonPublic | BindingFlags.Static)!.SetValue(GlobalFFOptions.Current, null);
|
||||
}
|
||||
|
||||
private static FFMpegArgumentProcessor CreateArgumentProcessor() => FFMpegArguments
|
||||
.FromFileInput("")
|
||||
.OutputToFile("");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ZZZ_Processor_GlobalOptions_GetUsed()
|
||||
public void Processor_GlobalOptions_GetUsed()
|
||||
{
|
||||
var globalWorkingDir = "Whatever";
|
||||
var processor = CreateArgumentProcessor();
|
||||
|
||||
try
|
||||
{
|
||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir });
|
||||
|
||||
var options = processor.GetConfiguredOptions(null);
|
||||
|
||||
Assert.AreEqual(globalWorkingDir, options.WorkingDirectory);
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlobalFFOptions.Configure(new FFOptions());
|
||||
}
|
||||
var processor = CreateArgumentProcessor();
|
||||
var options2 = processor.GetConfiguredOptions(null);
|
||||
options2.WorkingDirectory.Should().Be(globalWorkingDir);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -39,60 +40,64 @@ public class FFMpegArgumentProcessorTest
|
|||
processor.Configure(options => options.WorkingDirectory = sessionWorkingDir);
|
||||
var options = processor.GetConfiguredOptions(null);
|
||||
|
||||
Assert.AreEqual(sessionWorkingDir, options.WorkingDirectory);
|
||||
options.WorkingDirectory.Should().Be(sessionWorkingDir);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ZZZ_Processor_Options_CanBeOverridden_And_Configured()
|
||||
public void Processor_Options_CanBeOverridden_And_Configured()
|
||||
{
|
||||
var globalConfig = "Whatever";
|
||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig });
|
||||
|
||||
try
|
||||
{
|
||||
var processor = CreateArgumentProcessor();
|
||||
|
||||
var sessionTempDir = "./CurrentRunWorkingDir";
|
||||
processor.Configure(options => options.TemporaryFilesFolder = sessionTempDir);
|
||||
|
||||
var overrideOptions = new FFOptions { WorkingDirectory = "override" };
|
||||
|
||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig });
|
||||
var overrideOptions = new FFOptions() { WorkingDirectory = "override" };
|
||||
var options = processor.GetConfiguredOptions(overrideOptions);
|
||||
|
||||
Assert.AreEqual(options.WorkingDirectory, overrideOptions.WorkingDirectory);
|
||||
Assert.AreEqual(options.TemporaryFilesFolder, overrideOptions.TemporaryFilesFolder);
|
||||
Assert.AreEqual(options.BinaryFolder, overrideOptions.BinaryFolder);
|
||||
|
||||
Assert.AreEqual(sessionTempDir, options.TemporaryFilesFolder);
|
||||
Assert.AreNotEqual(globalConfig, options.BinaryFolder);
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlobalFFOptions.Configure(new FFOptions());
|
||||
}
|
||||
options.Should().BeEquivalentTo(overrideOptions);
|
||||
options.TemporaryFilesFolder.Should().BeEquivalentTo(sessionTempDir);
|
||||
options.BinaryFolder.Should().NotBeEquivalentTo(globalConfig);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ZZZ_Options_Global_And_Session_Options_Can_Differ()
|
||||
public void Options_Global_And_Session_Options_Can_Differ()
|
||||
{
|
||||
var globalWorkingDir = "Whatever";
|
||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir });
|
||||
|
||||
try
|
||||
{
|
||||
var processor1 = CreateArgumentProcessor();
|
||||
var sessionWorkingDir = "./CurrentRunWorkingDir";
|
||||
processor1.Configure(options => options.WorkingDirectory = sessionWorkingDir);
|
||||
var options1 = processor1.GetConfiguredOptions(null);
|
||||
Assert.AreEqual(sessionWorkingDir, options1.WorkingDirectory);
|
||||
options1.WorkingDirectory.Should().Be(sessionWorkingDir);
|
||||
|
||||
var processor2 = CreateArgumentProcessor();
|
||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir });
|
||||
var options2 = processor2.GetConfiguredOptions(null);
|
||||
Assert.AreEqual(globalWorkingDir, options2.WorkingDirectory);
|
||||
options2.WorkingDirectory.Should().Be(globalWorkingDir);
|
||||
}
|
||||
finally
|
||||
|
||||
[TestMethod]
|
||||
public void Concat_Escape()
|
||||
{
|
||||
GlobalFFOptions.Configure(new FFOptions());
|
||||
var arg = new DemuxConcatArgument(new[] { @"Heaven's River\05 - Investigation.m4b" });
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
<IsPackable>false</IsPackable>
|
||||
<Nullable>disable</Nullable>
|
||||
<LangVersion>default</LangVersion>
|
||||
<OrderTestsByNameInClass>true</OrderTestsByNameInClass>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
@ -13,20 +12,21 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FluentAssertions" Version="8.0.1" />
|
||||
<PackageReference Include="GitHubActionsTestLogger" Version="2.4.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0"/>
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="4.0.1"/>
|
||||
<PackageReference Include="MSTest.TestFramework" Version="4.0.1"/>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.8.0" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.8.0" />
|
||||
<PackageReference Include="SkiaSharp" Version="3.116.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FFMpegCore.Extensions.Downloader\FFMpegCore.Extensions.Downloader.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\FFMpegCore.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\FFMpegCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
using Newtonsoft.Json;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class FFMpegOptionsTest
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class FFMpegOptionsTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void Options_Initialized()
|
||||
{
|
||||
|
|
@ -14,7 +15,7 @@ public class FFMpegOptionsTest
|
|||
[TestMethod]
|
||||
public void Options_Defaults_Configured()
|
||||
{
|
||||
Assert.AreEqual("", new FFOptions().BinaryFolder);
|
||||
Assert.AreEqual(new FFOptions().BinaryFolder, $"");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -27,16 +28,21 @@ public class FFMpegOptionsTest
|
|||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ZZZ_Options_Set_Programmatically()
|
||||
public void Options_Set_Programmatically()
|
||||
{
|
||||
var original = GlobalFFOptions.Current;
|
||||
try
|
||||
{
|
||||
GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "Whatever" });
|
||||
Assert.AreEqual("Whatever", GlobalFFOptions.Current.BinaryFolder);
|
||||
Assert.AreEqual(
|
||||
GlobalFFOptions.Current.BinaryFolder,
|
||||
"Whatever"
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlobalFFOptions.Configure(new FFOptions());
|
||||
GlobalFFOptions.Configure(original);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
using FFMpegCore.Test.Resources;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class FFProbeTests
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
[TestClass]
|
||||
public class FFProbeTests
|
||||
{
|
||||
[TestMethod]
|
||||
public async Task Audio_FromStream_Duration()
|
||||
{
|
||||
var fileAnalysis = await FFProbe.AnalyseAsync(TestResources.WebmVideo, cancellationToken: TestContext.CancellationToken);
|
||||
var fileAnalysis = await FFProbe.AnalyseAsync(TestResources.WebmVideo);
|
||||
await using var inputStream = File.OpenRead(TestResources.WebmVideo);
|
||||
var streamAnalysis = await FFProbe.AnalyseAsync(inputStream, cancellationToken: TestContext.CancellationToken);
|
||||
var streamAnalysis = await FFProbe.AnalyseAsync(inputStream);
|
||||
Assert.IsTrue(fileAnalysis.Duration == streamAnalysis.Duration);
|
||||
}
|
||||
|
||||
|
|
@ -21,7 +20,7 @@ public class FFProbeTests
|
|||
{
|
||||
var frameAnalysis = FFProbe.GetFrames(TestResources.WebmVideo);
|
||||
|
||||
Assert.HasCount(90, frameAnalysis.Frames);
|
||||
Assert.AreEqual(90, frameAnalysis.Frames.Count);
|
||||
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.Width == 640));
|
||||
|
|
@ -31,9 +30,9 @@ public class FFProbeTests
|
|||
[TestMethod]
|
||||
public async Task FrameAnalysis_Async()
|
||||
{
|
||||
var frameAnalysis = await FFProbe.GetFramesAsync(TestResources.WebmVideo, cancellationToken: TestContext.CancellationToken);
|
||||
var frameAnalysis = await FFProbe.GetFramesAsync(TestResources.WebmVideo);
|
||||
|
||||
Assert.HasCount(90, frameAnalysis.Frames);
|
||||
Assert.AreEqual(90, frameAnalysis.Frames.Count);
|
||||
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.Width == 640));
|
||||
|
|
@ -43,11 +42,11 @@ public class FFProbeTests
|
|||
[TestMethod]
|
||||
public async Task PacketAnalysis_Async()
|
||||
{
|
||||
var packetAnalysis = await FFProbe.GetPacketsAsync(TestResources.WebmVideo, cancellationToken: TestContext.CancellationToken);
|
||||
var packetAnalysis = await FFProbe.GetPacketsAsync(TestResources.WebmVideo);
|
||||
var packets = packetAnalysis.Packets;
|
||||
Assert.HasCount(96, packets);
|
||||
Assert.AreEqual(96, packets.Count);
|
||||
Assert.IsTrue(packets.All(f => f.CodecType == "video"));
|
||||
Assert.StartsWith("K_", packets[0].Flags);
|
||||
Assert.IsTrue(packets[0].Flags.StartsWith("K_"));
|
||||
Assert.AreEqual(1362, packets.Last().Size);
|
||||
}
|
||||
|
||||
|
|
@ -56,9 +55,9 @@ public class FFProbeTests
|
|||
{
|
||||
var packets = FFProbe.GetPackets(TestResources.WebmVideo).Packets;
|
||||
|
||||
Assert.HasCount(96, packets);
|
||||
Assert.AreEqual(96, packets.Count);
|
||||
Assert.IsTrue(packets.All(f => f.CodecType == "video"));
|
||||
Assert.StartsWith("K_", packets[0].Flags);
|
||||
Assert.IsTrue(packets[0].Flags.StartsWith("K_"));
|
||||
Assert.AreEqual(1362, packets.Last().Size);
|
||||
}
|
||||
|
||||
|
|
@ -67,7 +66,7 @@ public class FFProbeTests
|
|||
{
|
||||
var packets = FFProbe.GetPackets(TestResources.Mp4Video).Packets;
|
||||
|
||||
Assert.HasCount(216, packets);
|
||||
Assert.AreEqual(216, packets.Count);
|
||||
var actual = packets.Select(f => f.CodecType).Distinct().ToList();
|
||||
var expected = new List<string> { "audio", "video" };
|
||||
CollectionAssert.AreEquivalent(expected, actual);
|
||||
|
|
@ -76,13 +75,12 @@ public class FFProbeTests
|
|||
Assert.AreEqual(141, packets.Count(t => t.CodecType == "audio"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataTestMethod]
|
||||
[DataRow("0:00:03.008000", 0, 0, 0, 3, 8)]
|
||||
[DataRow("05:12:59.177", 0, 5, 12, 59, 177)]
|
||||
[DataRow("149:07:50.911750", 6, 5, 7, 50, 911)]
|
||||
[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 };
|
||||
|
||||
|
|
@ -95,12 +93,10 @@ public class FFProbeTests
|
|||
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()
|
||||
{
|
||||
var fileAnalysis = await FFProbe.AnalyseAsync(new Uri("https://github.com/rosenbjerg/FFMpegCore/raw/master/FFMpegCore.Test/Resources/input_3sec.webm"),
|
||||
cancellationToken: TestContext.CancellationToken);
|
||||
var fileAnalysis = await FFProbe.AnalyseAsync(new Uri("https://github.com/rosenbjerg/FFMpegCore/raw/master/FFMpegCore.Test/Resources/input_3sec.webm"));
|
||||
Assert.IsNotNull(fileAnalysis);
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +105,7 @@ public class FFProbeTests
|
|||
{
|
||||
var info = FFProbe.Analyse(TestResources.Mp4Video);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
Assert.IsEmpty(info.Chapters);
|
||||
Assert.AreEqual(0, info.Chapters.Count);
|
||||
|
||||
Assert.AreEqual("5.1", info.PrimaryAudioStream!.ChannelLayout);
|
||||
Assert.AreEqual(6, info.PrimaryAudioStream.Channels);
|
||||
|
|
@ -144,11 +140,9 @@ public class FFProbeTests
|
|||
public void Probe_Rotation()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.Mp4Video);
|
||||
Assert.IsNotNull(info.PrimaryVideoStream);
|
||||
Assert.AreEqual(0, info.PrimaryVideoStream.Rotation);
|
||||
|
||||
info = FFProbe.Analyse(TestResources.Mp4VideoRotation);
|
||||
Assert.IsNotNull(info.PrimaryVideoStream);
|
||||
Assert.AreEqual(90, info.PrimaryVideoStream.Rotation);
|
||||
}
|
||||
|
||||
|
|
@ -156,25 +150,20 @@ public class FFProbeTests
|
|||
public void Probe_Rotation_Negative_Value()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.Mp4VideoRotationNegative);
|
||||
Assert.IsNotNull(info.PrimaryVideoStream);
|
||||
Assert.AreEqual(-90, info.PrimaryVideoStream.Rotation);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Async_Success()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video, cancellationToken: TestContext.CancellationToken);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
Assert.IsNotNull(info.PrimaryVideoStream);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth);
|
||||
// This video's audio stream is AAC, which is lossy, so bit depth is meaningless.
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Probe_Success_FromStream()
|
||||
{
|
||||
using var stream = File.OpenRead(TestResources.WebmVideo);
|
||||
|
|
@ -184,17 +173,15 @@ public class FFProbeTests
|
|||
Assert.IsNull(info.PrimaryAudioStream);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_FromStream_Async()
|
||||
{
|
||||
await using var stream = File.OpenRead(TestResources.WebmVideo);
|
||||
var info = await FFProbe.AnalyseAsync(stream, cancellationToken: TestContext.CancellationToken);
|
||||
var info = await FFProbe.AnalyseAsync(stream);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Probe_HDR()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.HdrVideo);
|
||||
|
|
@ -206,75 +193,66 @@ public class FFProbeTests
|
|||
Assert.AreEqual("bt2020", info.PrimaryVideoStream.ColorPrimaries);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_Subtitle_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.SrtSubtitle, cancellationToken: TestContext.CancellationToken);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.SrtSubtitle);
|
||||
Assert.IsNotNull(info.PrimarySubtitleStream);
|
||||
Assert.HasCount(1, info.SubtitleStreams);
|
||||
Assert.IsEmpty(info.AudioStreams);
|
||||
Assert.IsEmpty(info.VideoStreams);
|
||||
Assert.AreEqual(1, info.SubtitleStreams.Count);
|
||||
Assert.AreEqual(0, info.AudioStreams.Count);
|
||||
Assert.AreEqual(0, info.VideoStreams.Count);
|
||||
// BitDepth is meaningless for subtitles
|
||||
Assert.IsNull(info.SubtitleStreams[0].BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_Disposition_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video, cancellationToken: TestContext.CancellationToken);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream.Disposition);
|
||||
Assert.IsTrue(info.PrimaryAudioStream.Disposition["default"]);
|
||||
Assert.IsFalse(info.PrimaryAudioStream.Disposition["forced"]);
|
||||
Assert.AreEqual(true, info.PrimaryAudioStream.Disposition["default"]);
|
||||
Assert.AreEqual(false, info.PrimaryAudioStream.Disposition["forced"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_Mp3AudioBitDepthNull_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp3Audio, cancellationToken: TestContext.CancellationToken);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp3Audio);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
// mp3 is lossy, so bit depth is meaningless.
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_VocAudioBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.AiffAudio, cancellationToken: TestContext.CancellationToken);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.AiffAudio);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(16, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_MkvVideoBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.MkvVideo, cancellationToken: TestContext.CancellationToken);
|
||||
Assert.IsNotNull(info.PrimaryVideoStream);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth);
|
||||
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.MkvVideo);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth);
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_24BitWavBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav24Bit, cancellationToken: TestContext.CancellationToken);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav24Bit);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(24, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_32BitWavBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav32Bit, cancellationToken: TestContext.CancellationToken);
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav32Bit);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(32, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
|
@ -285,4 +263,5 @@ public class FFProbeTests
|
|||
var info = FFProbe.Analyse(TestResources.Mp4Video, customArguments: "-headers \"Hello: World\"");
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using FFMpegCore.Builders.MetaData;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class MetaDataBuilderTests
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class MetaDataBuilderTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestMetaDataBuilderIntegrity()
|
||||
{
|
||||
|
|
@ -19,8 +20,10 @@ public class MetaDataBuilderTests
|
|||
Genres = new[] { "Synthwave", "Classics" },
|
||||
Tracks = new[]
|
||||
{
|
||||
new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 01" }, 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 01" },
|
||||
new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 02" },
|
||||
new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 03" },
|
||||
new { Duration = TimeSpan.FromSeconds(10), Title = "Chapter 04" },
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -63,4 +66,5 @@ public class MetaDataBuilderTests
|
|||
Assert.IsTrue(Regex.IsMatch(text0, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 1"), "map_metadata index is calculated incorrectly.");
|
||||
Assert.IsTrue(Regex.IsMatch(text1, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 2"), "map_metadata index is calculated incorrectly.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
using FFMpegCore.Exceptions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class PixelFormatTests
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class PixelFormatTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void PixelFormats_Enumerate()
|
||||
{
|
||||
var formats = FFMpeg.GetPixelFormats();
|
||||
Assert.IsNotEmpty(formats);
|
||||
Assert.IsTrue(formats.Count > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
|
|
@ -34,6 +35,7 @@ public class PixelFormatTests
|
|||
[TestMethod]
|
||||
public void PixelFormats_GetNotExisting()
|
||||
{
|
||||
Assert.ThrowsExactly<FFMpegException>(() => FFMpeg.GetPixelFormat("yuv420pppUnknown"));
|
||||
Assert.ThrowsException<FFMpegException>(() => FFMpeg.GetPixelFormat("yuv420pppUnknown"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
namespace FFMpegCore.Test.Resources;
|
||||
|
||||
public static class TestResources
|
||||
namespace FFMpegCore.Test.Resources
|
||||
{
|
||||
public static class TestResources
|
||||
{
|
||||
public static readonly string Mp4Video = "./Resources/input_3sec.mp4";
|
||||
public static readonly string Mp4VideoRotation = "./Resources/input_3sec_rotation_90deg.mp4";
|
||||
public static readonly string Mp4VideoRotationNegative = "./Resources/input_3sec_rotation_negative_90deg.mp4";
|
||||
|
|
@ -18,4 +18,5 @@ public static class TestResources
|
|||
public static readonly string MkvVideo = "./Resources/sampleMKV.mkv";
|
||||
public static readonly string Wav24Bit = "./Resources/24_bit_fixed.WAV";
|
||||
public static readonly string Wav32Bit = "./Resources/32_bit_float.WAV";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
namespace FFMpegCore.Test;
|
||||
|
||||
public class TemporaryFile : IDisposable
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
public class TemporaryFile : IDisposable
|
||||
{
|
||||
private readonly string _path;
|
||||
|
||||
public TemporaryFile(string filename)
|
||||
|
|
@ -9,6 +9,7 @@ public class TemporaryFile : IDisposable
|
|||
_path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}-{filename}");
|
||||
}
|
||||
|
||||
public static implicit operator string(TemporaryFile temporaryFile) => temporaryFile._path;
|
||||
public void Dispose()
|
||||
{
|
||||
if (File.Exists(_path))
|
||||
|
|
@ -16,9 +17,5 @@ public class TemporaryFile : IDisposable
|
|||
File.Delete(_path);
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator string(TemporaryFile temporaryFile)
|
||||
{
|
||||
return temporaryFile._path;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,13 @@
|
|||
using System.Drawing.Imaging;
|
||||
using System.Numerics;
|
||||
using System.Runtime.Versioning;
|
||||
using FFMpegCore.Extensions.System.Drawing.Common;
|
||||
using FFMpegCore.Pipes;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FFMpegCore.Test.Utilities;
|
||||
|
||||
internal static class BitmapSource
|
||||
namespace FFMpegCore.Test.Utilities
|
||||
{
|
||||
internal static class BitmapSource
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static IEnumerable<IVideoFrame> CreateBitmaps(int count, PixelFormat fmt, int w, int h)
|
||||
{
|
||||
|
|
@ -34,7 +33,7 @@ internal static class BitmapSource
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fmt, int w, int h, float scaleNoise, float offset)
|
||||
public static Extensions.System.Drawing.Common.BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fmt, int w, int h, float scaleNoise, float offset)
|
||||
{
|
||||
var bitmap = new Bitmap(w, h, fmt);
|
||||
|
||||
|
|
@ -44,7 +43,7 @@ internal static class BitmapSource
|
|||
bitmap.SetPixel(x, y, color);
|
||||
}
|
||||
|
||||
return new BitmapVideoFrameWrapper(bitmap);
|
||||
return new Extensions.System.Drawing.Common.BitmapVideoFrameWrapper(bitmap);
|
||||
}
|
||||
|
||||
public static Extensions.SkiaSharp.BitmapVideoFrameWrapper CreateVideoFrame(int index, SKColorType fmt, int w, int h, float scaleNoise, float offset)
|
||||
|
|
@ -58,8 +57,7 @@ internal static class BitmapSource
|
|||
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;
|
||||
|
||||
|
|
@ -74,7 +72,7 @@ internal static class BitmapSource
|
|||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -230,23 +228,28 @@ internal static class BitmapSource
|
|||
{
|
||||
var h = hash & 15;
|
||||
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);
|
||||
}
|
||||
|
||||
private static readonly int[] perm =
|
||||
{
|
||||
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247,
|
||||
120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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
|
||||
private static readonly int[] perm = {
|
||||
151,160,137,91,90,15,
|
||||
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
|
||||
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
|
||||
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,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, 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,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,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,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, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
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) }
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
23
FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs
Normal file
23
FFMpegCore.Test/Utilities/WindowsOnlyDataTestMethod.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test.Utilities;
|
||||
|
||||
public class WindowsOnlyDataTestMethod : DataTestMethodAttribute
|
||||
{
|
||||
public override TestResult[] Execute(ITestMethod testMethod)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var message = $"Test not executed on other platforms than Windows";
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return base.Execute(testMethod);
|
||||
}
|
||||
}
|
||||
23
FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs
Normal file
23
FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test.Utilities;
|
||||
|
||||
public class WindowsOnlyTestMethod : TestMethodAttribute
|
||||
{
|
||||
public override TestResult[] Execute(ITestMethod testMethod)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var message = $"Test not executed on other platforms than Windows";
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return base.Execute(testMethod);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,24 +5,19 @@ using System.Text;
|
|||
using FFMpegCore.Arguments;
|
||||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
using FFMpegCore.Extensions.System.Drawing.Common;
|
||||
using FFMpegCore.Pipes;
|
||||
using FFMpegCore.Test.Resources;
|
||||
using FFMpegCore.Test.Utilities;
|
||||
using SkiaSharp;
|
||||
using PixelFormat = System.Drawing.Imaging.PixelFormat;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class VideoTest
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestClass]
|
||||
public class VideoTest
|
||||
{
|
||||
private const int BaseTimeoutMilliseconds = 15_000;
|
||||
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToOGV()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}");
|
||||
|
|
@ -34,8 +29,7 @@ public class VideoTest
|
|||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToMP4()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
|
@ -47,8 +41,7 @@ public class VideoTest
|
|||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToMP4_YUV444p()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
|
@ -61,11 +54,10 @@ public class VideoTest
|
|||
.ProcessSynchronously();
|
||||
Assert.IsTrue(success);
|
||||
var analysis = FFProbe.Analyse(outputFile);
|
||||
Assert.AreEqual("yuv444p", analysis.VideoStreams.First().PixelFormat);
|
||||
Assert.IsTrue(analysis.VideoStreams.First().PixelFormat == "yuv444p");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToMP4_Args()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
|
@ -78,11 +70,10 @@ public class VideoTest
|
|||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToH265_MKV_Args()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mkv");
|
||||
using var outputFile = new TemporaryFile($"out.mkv");
|
||||
|
||||
var success = FFMpegArguments
|
||||
.FromFileInput(TestResources.WebmVideo)
|
||||
|
|
@ -93,23 +84,15 @@ public class VideoTest
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[OsSpecificTestMethod(OsPlatforms.Windows)]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[DataRow(PixelFormat.Format24bppRgb)]
|
||||
[DataRow(PixelFormat.Format32bppArgb)]
|
||||
public void Video_ToMP4_Args_Pipe_WindowsOnly(PixelFormat pixelFormat)
|
||||
{
|
||||
Video_ToMP4_Args_Pipe_Internal(pixelFormat);
|
||||
}
|
||||
[WindowsOnlyDataTestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||
public void Video_ToMP4_Args_Pipe_WindowsOnly(System.Drawing.Imaging.PixelFormat pixelFormat) => Video_ToMP4_Args_Pipe_Internal(pixelFormat);
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[DataRow(SKColorType.Rgb565)]
|
||||
[DataRow(SKColorType.Bgra8888)]
|
||||
public void Video_ToMP4_Args_Pipe(SKColorType pixelFormat)
|
||||
{
|
||||
Video_ToMP4_Args_Pipe_Internal(pixelFormat);
|
||||
}
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
[DataRow(SkiaSharp.SKColorType.Rgb565)]
|
||||
[DataRow(SkiaSharp.SKColorType.Bgra8888)]
|
||||
public void Video_ToMP4_Args_Pipe(SkiaSharp.SKColorType pixelFormat) => Video_ToMP4_Args_Pipe_Internal(pixelFormat);
|
||||
|
||||
private static void Video_ToMP4_Args_Pipe_Internal(dynamic pixelFormat)
|
||||
{
|
||||
|
|
@ -125,19 +108,11 @@ public class VideoTest
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[OsSpecificTestMethod(OsPlatforms.Windows)]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
public void Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly()
|
||||
{
|
||||
Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal(PixelFormat.Format24bppRgb);
|
||||
}
|
||||
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly() => Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal(System.Drawing.Imaging.PixelFormat.Format24bppRgb);
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
public void Video_ToMP4_Args_Pipe_DifferentImageSizes()
|
||||
{
|
||||
Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal(SKColorType.Rgb565);
|
||||
}
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToMP4_Args_Pipe_DifferentImageSizes() => Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal(SkiaSharp.SKColorType.Rgb565);
|
||||
|
||||
private static void Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal(dynamic pixelFormat)
|
||||
{
|
||||
|
|
@ -145,11 +120,12 @@ public class VideoTest
|
|||
|
||||
var frames = new List<IVideoFrame>
|
||||
{
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormat, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormat, 256, 256, 1, 0)
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormat, 255, 255, 1, 0),
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormat, 256, 256, 1, 0)
|
||||
};
|
||||
|
||||
var videoFramesSource = new RawVideoPipeSource(frames);
|
||||
Assert.ThrowsExactly<FFMpegStreamFormatException>(() => FFMpegArguments
|
||||
var ex = Assert.ThrowsException<FFMpegStreamFormatException>(() => FFMpegArguments
|
||||
.FromPipeInput(videoFramesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithVideoCodec(VideoCodec.LibX264))
|
||||
|
|
@ -157,19 +133,11 @@ public class VideoTest
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[OsSpecificTestMethod(OsPlatforms.Windows)]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly_Async()
|
||||
{
|
||||
await Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal_Async(PixelFormat.Format24bppRgb);
|
||||
}
|
||||
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly_Async() => await Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal_Async(System.Drawing.Imaging.PixelFormat.Format24bppRgb);
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async()
|
||||
{
|
||||
await Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal_Async(SKColorType.Rgb565);
|
||||
}
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async() => await Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal_Async(SkiaSharp.SKColorType.Rgb565);
|
||||
|
||||
private static async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Internal_Async(dynamic pixelFormat)
|
||||
{
|
||||
|
|
@ -177,11 +145,12 @@ public class VideoTest
|
|||
|
||||
var frames = new List<IVideoFrame>
|
||||
{
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormat, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormat, 256, 256, 1, 0)
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormat, 255, 255, 1, 0),
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormat, 256, 256, 1, 0)
|
||||
};
|
||||
|
||||
var videoFramesSource = new RawVideoPipeSource(frames);
|
||||
await Assert.ThrowsExactlyAsync<FFMpegStreamFormatException>(() => FFMpegArguments
|
||||
var ex = await Assert.ThrowsExceptionAsync<FFMpegStreamFormatException>(() => FFMpegArguments
|
||||
.FromPipeInput(videoFramesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithVideoCodec(VideoCodec.LibX264))
|
||||
|
|
@ -189,20 +158,12 @@ public class VideoTest
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[OsSpecificTestMethod(OsPlatforms.Windows)]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
public void Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly()
|
||||
{
|
||||
Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal(PixelFormat.Format24bppRgb,
|
||||
PixelFormat.Format32bppRgb);
|
||||
}
|
||||
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly() =>
|
||||
Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal(System.Drawing.Imaging.PixelFormat.Format24bppRgb, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
public void Video_ToMP4_Args_Pipe_DifferentPixelFormats()
|
||||
{
|
||||
Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal(SKColorType.Rgb565, SKColorType.Bgra8888);
|
||||
}
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToMP4_Args_Pipe_DifferentPixelFormats() => Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal(SkiaSharp.SKColorType.Rgb565, SkiaSharp.SKColorType.Bgra8888);
|
||||
|
||||
private static void Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal(dynamic pixelFormatFrame1, dynamic pixelFormatFrame2)
|
||||
{
|
||||
|
|
@ -210,11 +171,12 @@ public class VideoTest
|
|||
|
||||
var frames = new List<IVideoFrame>
|
||||
{
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormatFrame1, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormatFrame2, 255, 255, 1, 0)
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormatFrame1, 255, 255, 1, 0),
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormatFrame2, 255, 255, 1, 0)
|
||||
};
|
||||
|
||||
var videoFramesSource = new RawVideoPipeSource(frames);
|
||||
Assert.ThrowsExactly<FFMpegStreamFormatException>(() => FFMpegArguments
|
||||
var ex = Assert.ThrowsException<FFMpegStreamFormatException>(() => FFMpegArguments
|
||||
.FromPipeInput(videoFramesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithVideoCodec(VideoCodec.LibX264))
|
||||
|
|
@ -222,20 +184,12 @@ public class VideoTest
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[OsSpecificTestMethod(OsPlatforms.Windows)]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly_Async()
|
||||
{
|
||||
await Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal_Async(PixelFormat.Format24bppRgb,
|
||||
PixelFormat.Format32bppRgb);
|
||||
}
|
||||
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly_Async() =>
|
||||
await Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal_Async(System.Drawing.Imaging.PixelFormat.Format24bppRgb, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
|
||||
|
||||
[TestMethod]
|
||||
[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);
|
||||
}
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Async() => await Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal_Async(SkiaSharp.SKColorType.Rgb565, SkiaSharp.SKColorType.Bgra8888);
|
||||
|
||||
private static async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Internal_Async(dynamic pixelFormatFrame1, dynamic pixelFormatFrame2)
|
||||
{
|
||||
|
|
@ -243,19 +197,19 @@ public class VideoTest
|
|||
|
||||
var frames = new List<IVideoFrame>
|
||||
{
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormatFrame1, 255, 255, 1, 0), BitmapSource.CreateVideoFrame(0, pixelFormatFrame2, 255, 255, 1, 0)
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormatFrame1, 255, 255, 1, 0),
|
||||
BitmapSource.CreateVideoFrame(0, pixelFormatFrame2, 255, 255, 1, 0)
|
||||
};
|
||||
|
||||
var videoFramesSource = new RawVideoPipeSource(frames);
|
||||
await Assert.ThrowsExactlyAsync<FFMpegStreamFormatException>(() => FFMpegArguments
|
||||
var ex = await Assert.ThrowsExceptionAsync<FFMpegStreamFormatException>(() => FFMpegArguments
|
||||
.FromPipeInput(videoFramesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithVideoCodec(VideoCodec.LibX264))
|
||||
.ProcessAsynchronously());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToMP4_Args_StreamPipe()
|
||||
{
|
||||
using var input = File.OpenRead(TestResources.WebmVideo);
|
||||
|
|
@ -269,11 +223,10 @@ public class VideoTest
|
|||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_ToMP4_Args_StreamOutputPipe_Async_Failure()
|
||||
{
|
||||
await Assert.ThrowsExactlyAsync<FFMpegException>(async () =>
|
||||
await Assert.ThrowsExceptionAsync<FFMpegException>(async () =>
|
||||
{
|
||||
await using var ms = new MemoryStream();
|
||||
var pipeSource = new StreamPipeSink(ms);
|
||||
|
|
@ -284,8 +237,7 @@ public class VideoTest
|
|||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_StreamFile_OutputToMemoryStream()
|
||||
{
|
||||
var output = new MemoryStream();
|
||||
|
|
@ -302,11 +254,10 @@ public class VideoTest
|
|||
Console.WriteLine(result.Duration);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToMP4_Args_StreamOutputPipe_Failure()
|
||||
{
|
||||
Assert.ThrowsExactly<FFMpegException>(() =>
|
||||
Assert.ThrowsException<FFMpegException>(() =>
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
FFMpegArguments
|
||||
|
|
@ -317,8 +268,7 @@ public class VideoTest
|
|||
});
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_ToMP4_Args_StreamOutputPipe_Async()
|
||||
{
|
||||
await using var ms = new MemoryStream();
|
||||
|
|
@ -331,8 +281,7 @@ public class VideoTest
|
|||
.ProcessAsynchronously();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task TestDuplicateRun()
|
||||
{
|
||||
FFMpegArguments
|
||||
|
|
@ -348,8 +297,7 @@ public class VideoTest
|
|||
File.Delete("temporary.mp4");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void TranscodeToMemoryStream_Success()
|
||||
{
|
||||
using var output = new MemoryStream();
|
||||
|
|
@ -367,8 +315,7 @@ public class VideoTest
|
|||
Assert.AreEqual(inputAnalysis.Duration.TotalSeconds, outputAnalysis.Duration.TotalSeconds, 0.3);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToTS()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.MpegTs.Extension}");
|
||||
|
|
@ -380,8 +327,7 @@ public class VideoTest
|
|||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_ToTS_Args()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.MpegTs.Extension}");
|
||||
|
|
@ -397,23 +343,15 @@ public class VideoTest
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[OsSpecificTestMethod(OsPlatforms.Windows)]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[DataRow(PixelFormat.Format24bppRgb)]
|
||||
[DataRow(PixelFormat.Format32bppArgb)]
|
||||
public async Task Video_ToTS_Args_Pipe_WindowsOnly(PixelFormat pixelFormat)
|
||||
{
|
||||
await Video_ToTS_Args_Pipe_Internal(pixelFormat);
|
||||
}
|
||||
[WindowsOnlyDataTestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||
public async Task Video_ToTS_Args_Pipe_WindowsOnly(System.Drawing.Imaging.PixelFormat pixelFormat) => await Video_ToTS_Args_Pipe_Internal(pixelFormat);
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[DataRow(SKColorType.Rgb565)]
|
||||
[DataRow(SKColorType.Bgra8888)]
|
||||
public async Task Video_ToTS_Args_Pipe(SKColorType pixelFormat)
|
||||
{
|
||||
await Video_ToTS_Args_Pipe_Internal(pixelFormat);
|
||||
}
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
[DataRow(SkiaSharp.SKColorType.Rgb565)]
|
||||
[DataRow(SkiaSharp.SKColorType.Bgra8888)]
|
||||
public async Task Video_ToTS_Args_Pipe(SkiaSharp.SKColorType pixelFormat) => await Video_ToTS_Args_Pipe_Internal(pixelFormat);
|
||||
|
||||
private static async Task Video_ToTS_Args_Pipe_Internal(dynamic pixelFormat)
|
||||
{
|
||||
|
|
@ -431,8 +369,7 @@ public class VideoTest
|
|||
Assert.AreEqual(VideoType.Ts.Name, analysis.Format.FormatName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_ToOGV_Resize()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}");
|
||||
|
|
@ -446,11 +383,10 @@ public class VideoTest
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[OsSpecificTestMethod(OsPlatforms.Windows)]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[DataRow(SKColorType.Rgb565)]
|
||||
[DataRow(SKColorType.Bgra8888)]
|
||||
public void RawVideoPipeSource_Ogv_Scale(SKColorType pixelFormat)
|
||||
[WindowsOnlyDataTestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
[DataRow(SkiaSharp.SKColorType.Rgb565)]
|
||||
[DataRow(SkiaSharp.SKColorType.Bgra8888)]
|
||||
public void RawVideoPipeSource_Ogv_Scale(SkiaSharp.SKColorType pixelFormat)
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Ogv.Extension}");
|
||||
var videoFramesSource = new RawVideoPipeSource(BitmapSource.CreateBitmaps(128, pixelFormat, 256, 256));
|
||||
|
|
@ -467,8 +403,7 @@ public class VideoTest
|
|||
Assert.AreEqual((int)VideoSize.Ed, analysis.PrimaryVideoStream!.Width);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Scale_Mp4_Multithreaded()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
|
@ -483,24 +418,16 @@ public class VideoTest
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[OsSpecificTestMethod(OsPlatforms.Windows)]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[DataRow(PixelFormat.Format24bppRgb)]
|
||||
[DataRow(PixelFormat.Format32bppArgb)]
|
||||
[WindowsOnlyDataTestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format24bppRgb)]
|
||||
[DataRow(System.Drawing.Imaging.PixelFormat.Format32bppArgb)]
|
||||
// [DataRow(PixelFormat.Format48bppRgb)]
|
||||
public void Video_ToMP4_Resize_Args_Pipe(PixelFormat pixelFormat)
|
||||
{
|
||||
Video_ToMP4_Resize_Args_Pipe_Internal(pixelFormat);
|
||||
}
|
||||
public void Video_ToMP4_Resize_Args_Pipe(System.Drawing.Imaging.PixelFormat pixelFormat) => Video_ToMP4_Resize_Args_Pipe_Internal(pixelFormat);
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[DataRow(SKColorType.Rgb565)]
|
||||
[DataRow(SKColorType.Bgra8888)]
|
||||
public void Video_ToMP4_Resize_Args_Pipe(SKColorType pixelFormat)
|
||||
{
|
||||
Video_ToMP4_Resize_Args_Pipe_Internal(pixelFormat);
|
||||
}
|
||||
[DataTestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
[DataRow(SkiaSharp.SKColorType.Rgb565)]
|
||||
[DataRow(SkiaSharp.SKColorType.Bgra8888)]
|
||||
public void Video_ToMP4_Resize_Args_Pipe(SkiaSharp.SKColorType pixelFormat) => Video_ToMP4_Resize_Args_Pipe_Internal(pixelFormat);
|
||||
|
||||
private static void Video_ToMP4_Resize_Args_Pipe_Internal(dynamic pixelFormat)
|
||||
{
|
||||
|
|
@ -516,11 +443,10 @@ public class VideoTest
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[OsSpecificTestMethod(OsPlatforms.Windows)]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_Snapshot_InMemory_SystemDrawingCommon()
|
||||
{
|
||||
using var bitmap = FFMpegImage.Snapshot(TestResources.Mp4Video);
|
||||
using var bitmap = Extensions.System.Drawing.Common.FFMpegImage.Snapshot(TestResources.Mp4Video);
|
||||
|
||||
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
||||
Assert.AreEqual(input.PrimaryVideoStream!.Width, bitmap.Width);
|
||||
|
|
@ -528,8 +454,7 @@ public class VideoTest
|
|||
Assert.AreEqual(bitmap.RawFormat, ImageFormat.Png);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_Snapshot_InMemory_SkiaSharp()
|
||||
{
|
||||
using var bitmap = Extensions.SkiaSharp.FFMpegImage.Snapshot(TestResources.Mp4Video);
|
||||
|
|
@ -541,9 +466,8 @@ public class VideoTest
|
|||
// e.g. Bgra8888 on Windows and Rgba8888 on macOS.
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
public void Video_Snapshot_Png_PersistSnapshot()
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_Snapshot_PersistSnapshot()
|
||||
{
|
||||
using var outputPath = new TemporaryFile("out.png");
|
||||
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
||||
|
|
@ -556,69 +480,7 @@ public class VideoTest
|
|||
Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
|
||||
}
|
||||
|
||||
[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)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_Snapshot_Rotated_PersistSnapshot()
|
||||
{
|
||||
using var outputPath = new TemporaryFile("out.png");
|
||||
|
|
@ -633,8 +495,7 @@ public class VideoTest
|
|||
Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_GifSnapshot_PersistSnapshot()
|
||||
{
|
||||
using var outputPath = new TemporaryFile("out.gif");
|
||||
|
|
@ -648,15 +509,14 @@ public class VideoTest
|
|||
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_GifSnapshot_PersistSnapshot_SizeSupplied()
|
||||
{
|
||||
using var outputPath = new TemporaryFile("out.gif");
|
||||
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
||||
var desiredGifSize = new Size(320, 240);
|
||||
|
||||
FFMpeg.GifSnapshot(TestResources.Mp4Video, outputPath, desiredGifSize, TimeSpan.FromSeconds(0));
|
||||
FFMpeg.GifSnapshot(TestResources.Mp4Video, outputPath, desiredGifSize, captureTime: TimeSpan.FromSeconds(0));
|
||||
|
||||
var analysis = FFProbe.Analyse(outputPath);
|
||||
Assert.AreNotEqual(input.PrimaryVideoStream!.Width, desiredGifSize.Width);
|
||||
|
|
@ -664,8 +524,7 @@ public class VideoTest
|
|||
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_GifSnapshot_PersistSnapshotAsync()
|
||||
{
|
||||
using var outputPath = new TemporaryFile("out.gif");
|
||||
|
|
@ -679,15 +538,14 @@ public class VideoTest
|
|||
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_GifSnapshot_PersistSnapshotAsync_SizeSupplied()
|
||||
{
|
||||
using var outputPath = new TemporaryFile("out.gif");
|
||||
var input = FFProbe.Analyse(TestResources.Mp4Video);
|
||||
var desiredGifSize = new Size(320, 240);
|
||||
|
||||
await FFMpeg.GifSnapshotAsync(TestResources.Mp4Video, outputPath, desiredGifSize, TimeSpan.FromSeconds(0));
|
||||
await FFMpeg.GifSnapshotAsync(TestResources.Mp4Video, outputPath, desiredGifSize, captureTime: TimeSpan.FromSeconds(0));
|
||||
|
||||
var analysis = FFProbe.Analyse(outputPath);
|
||||
Assert.AreNotEqual(input.PrimaryVideoStream!.Width, desiredGifSize.Width);
|
||||
|
|
@ -695,8 +553,7 @@ public class VideoTest
|
|||
Assert.AreEqual("gif", analysis.PrimaryVideoStream!.CodecName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_Join()
|
||||
{
|
||||
using var inputCopy = new TemporaryFile("copy-input.mp4");
|
||||
|
|
@ -718,8 +575,7 @@ public class VideoTest
|
|||
Assert.AreEqual(input.PrimaryVideoStream.Width, result.PrimaryVideoStream.Width);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(2 * BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(2 * BaseTimeoutMilliseconds)]
|
||||
public void Video_Join_Image_Sequence()
|
||||
{
|
||||
var imageSet = new List<string>();
|
||||
|
|
@ -735,7 +591,7 @@ public class VideoTest
|
|||
var imageAnalysis = FFProbe.Analyse(imageSet.First());
|
||||
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
var success = FFMpeg.JoinImageSequence(outputFile, 10, imageSet.ToArray());
|
||||
var success = FFMpeg.JoinImageSequence(outputFile, frameRate: 10, images: imageSet.ToArray());
|
||||
Assert.IsTrue(success);
|
||||
var result = FFProbe.Analyse(outputFile);
|
||||
|
||||
|
|
@ -744,18 +600,16 @@ public class VideoTest
|
|||
Assert.AreEqual(imageAnalysis.PrimaryVideoStream!.Height, result.PrimaryVideoStream.Height);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_With_Only_Audio_Should_Extract_Metadata()
|
||||
{
|
||||
var video = FFProbe.Analyse(TestResources.Mp4WithoutVideo);
|
||||
Assert.IsNull(video.PrimaryVideoStream);
|
||||
Assert.AreEqual(null, video.PrimaryVideoStream);
|
||||
Assert.AreEqual("aac", video.PrimaryAudioStream!.CodecName);
|
||||
Assert.AreEqual(10, video.Duration.TotalSeconds, 0.5);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_Duration()
|
||||
{
|
||||
var video = FFProbe.Analyse(TestResources.Mp4Video);
|
||||
|
|
@ -775,8 +629,7 @@ public class VideoTest
|
|||
Assert.AreEqual(video.Duration.Seconds - 2, outputVideo.Duration.Seconds);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_UpdatesProgress()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
|
@ -817,13 +670,13 @@ public class VideoTest
|
|||
Assert.AreNotEqual(analysis.Duration, timeDone);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_OutputsData()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
var dataReceived = false;
|
||||
|
||||
GlobalFFOptions.Configure(opt => opt.Encoding = Encoding.UTF8);
|
||||
var success = FFMpegArguments
|
||||
.FromFileInput(TestResources.Mp4Video)
|
||||
.WithGlobalOptions(options => options
|
||||
|
|
@ -831,7 +684,6 @@ public class VideoTest
|
|||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithDuration(TimeSpan.FromSeconds(2)))
|
||||
.NotifyOnError(_ => dataReceived = true)
|
||||
.Configure(opt => opt.Encoding = Encoding.UTF8)
|
||||
.ProcessSynchronously();
|
||||
|
||||
Assert.IsTrue(dataReceived);
|
||||
|
|
@ -840,19 +692,11 @@ public class VideoTest
|
|||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[OsSpecificTestMethod(OsPlatforms.Windows)]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
public void Video_TranscodeInMemory_WindowsOnly()
|
||||
{
|
||||
Video_TranscodeInMemory_Internal(PixelFormat.Format24bppRgb);
|
||||
}
|
||||
[WindowsOnlyTestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_TranscodeInMemory_WindowsOnly() => Video_TranscodeInMemory_Internal(System.Drawing.Imaging.PixelFormat.Format24bppRgb);
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
public void Video_TranscodeInMemory()
|
||||
{
|
||||
Video_TranscodeInMemory_Internal(SKColorType.Rgb565);
|
||||
}
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_TranscodeInMemory() => Video_TranscodeInMemory_Internal(SkiaSharp.SKColorType.Rgb565);
|
||||
|
||||
private static void Video_TranscodeInMemory_Internal(dynamic pixelFormat)
|
||||
{
|
||||
|
|
@ -869,12 +713,11 @@ public class VideoTest
|
|||
|
||||
resStream.Position = 0;
|
||||
var vi = FFProbe.Analyse(resStream);
|
||||
Assert.AreEqual(128, vi.PrimaryVideoStream!.Width);
|
||||
Assert.AreEqual(128, vi.PrimaryVideoStream.Height);
|
||||
Assert.AreEqual(vi.PrimaryVideoStream!.Width, 128);
|
||||
Assert.AreEqual(vi.PrimaryVideoStream.Height, 128);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(2 * BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(2 * BaseTimeoutMilliseconds)]
|
||||
public void Video_TranscodeToMemory()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
|
@ -888,12 +731,11 @@ public class VideoTest
|
|||
|
||||
memoryStream.Position = 0;
|
||||
var vi = FFProbe.Analyse(memoryStream);
|
||||
Assert.AreEqual(640, vi.PrimaryVideoStream!.Width);
|
||||
Assert.AreEqual(360, vi.PrimaryVideoStream.Height);
|
||||
Assert.AreEqual(vi.PrimaryVideoStream!.Width, 640);
|
||||
Assert.AreEqual(vi.PrimaryVideoStream.Height, 360);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_Cancel_Async()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
|
@ -909,7 +751,7 @@ public class VideoTest
|
|||
.CancellableThrough(out var cancel)
|
||||
.ProcessAsynchronously(false);
|
||||
|
||||
await Task.Delay(300, TestContext.CancellationToken);
|
||||
await Task.Delay(300);
|
||||
cancel();
|
||||
|
||||
var result = await task;
|
||||
|
|
@ -917,8 +759,7 @@ public class VideoTest
|
|||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_Cancel()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
|
@ -932,15 +773,14 @@ public class VideoTest
|
|||
.WithSpeedPreset(Speed.VeryFast))
|
||||
.CancellableThrough(out var cancel);
|
||||
|
||||
Task.Delay(300, TestContext.CancellationToken).ContinueWith(_ => cancel(), TestContext.CancellationToken);
|
||||
Task.Delay(300).ContinueWith((_) => cancel());
|
||||
|
||||
var result = task.ProcessSynchronously(false);
|
||||
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_Cancel_Async_With_Timeout()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
|
@ -956,12 +796,12 @@ public class VideoTest
|
|||
.CancellableThrough(out var cancel, 10000)
|
||||
.ProcessAsynchronously(false);
|
||||
|
||||
await Task.Delay(300, TestContext.CancellationToken);
|
||||
await Task.Delay(300);
|
||||
cancel();
|
||||
|
||||
await task;
|
||||
|
||||
var outputInfo = await FFProbe.AnalyseAsync(outputFile, cancellationToken: TestContext.CancellationToken);
|
||||
var outputInfo = await FFProbe.AnalyseAsync(outputFile);
|
||||
|
||||
Assert.IsNotNull(outputInfo);
|
||||
Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width);
|
||||
|
|
@ -970,8 +810,7 @@ public class VideoTest
|
|||
Assert.AreEqual("aac", outputInfo.PrimaryAudioStream!.CodecName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_Cancel_CancellationToken_Async()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
|
@ -996,8 +835,7 @@ public class VideoTest
|
|||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_Cancel_CancellationToken_Async_Throws()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
|
@ -1017,11 +855,10 @@ public class VideoTest
|
|||
|
||||
cts.CancelAfter(300);
|
||||
|
||||
await Assert.ThrowsExactlyAsync<OperationCanceledException>(() => task);
|
||||
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => task);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public void Video_Cancel_CancellationToken_Throws()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
|
@ -1040,11 +877,10 @@ public class VideoTest
|
|||
|
||||
cts.CancelAfter(300);
|
||||
|
||||
Assert.ThrowsExactly<OperationCanceledException>(() => task.ProcessSynchronously());
|
||||
Assert.ThrowsException<OperationCanceledException>(() => task.ProcessSynchronously());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
|
||||
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
|
||||
public async Task Video_Cancel_CancellationToken_Async_With_Timeout()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
|
@ -1066,7 +902,7 @@ public class VideoTest
|
|||
|
||||
await task;
|
||||
|
||||
var outputInfo = await FFProbe.AnalyseAsync(outputFile, cancellationToken: TestContext.CancellationToken);
|
||||
var outputInfo = await FFProbe.AnalyseAsync(outputFile);
|
||||
|
||||
Assert.IsNotNull(outputInfo);
|
||||
Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width);
|
||||
|
|
@ -1074,4 +910,5 @@ public class VideoTest
|
|||
Assert.AreEqual("h264", outputInfo.PrimaryVideoStream.CodecName);
|
||||
Assert.AreEqual("aac", outputInfo.PrimaryAudioStream!.CodecName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"BinaryFolder": ""
|
||||
"RootDirectory": ""
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.7.34003.232
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31005.135
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore", "FFMpegCore\FFMpegCore.csproj", "{19DE2EC2-9955-4712-8096-C22EF6713E4F}"
|
||||
EndProject
|
||||
|
|
@ -13,8 +13,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Extensions.Syste
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Extensions.SkiaSharp", "FFMpegCore.Extensions.SkiaSharp\FFMpegCore.Extensions.SkiaSharp.csproj", "{5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFMpegCore.Extensions.Downloader", "FFMpegCore.Extensions.Downloader\FFMpegCore.Extensions.Downloader.csproj", "{5FA30158-CAB0-44FD-AD98-C31F5E3D5A56}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -41,10 +39,6 @@ Global
|
|||
{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.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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
namespace FFMpegCore.Extend;
|
||||
|
||||
internal static class KeyValuePairExtensions
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
internal static class KeyValuePairExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Concat the two members of a <see cref="KeyValuePair{TKey,TValue}" />
|
||||
/// </summary>
|
||||
|
|
@ -18,4 +18,5 @@ internal static class KeyValuePairExtensions
|
|||
|
||||
return $"{key}={value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Extend;
|
||||
|
||||
public class PcmAudioSampleWrapper : IAudioSample
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
public class PcmAudioSampleWrapper : IAudioSample
|
||||
{
|
||||
//This could actually be short or int, but copies would be inefficient.
|
||||
//Handling bytes lets the user decide on the conversion, and abstract the library
|
||||
//from handling shorts, unsigned shorts, integers, unsigned integers and floats.
|
||||
|
|
@ -23,4 +23,5 @@ public class PcmAudioSampleWrapper : IAudioSample
|
|||
{
|
||||
await stream.WriteAsync(_sample, 0, _sample.Length, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using System.Text;
|
||||
|
||||
namespace FFMpegCore.Extend;
|
||||
|
||||
internal static class StringExtensions
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
internal static class StringExtensions
|
||||
{
|
||||
private static Dictionary<char, string> CharactersSubstitution { get; } = new()
|
||||
{
|
||||
{ '\\', @"\\" },
|
||||
|
|
@ -65,4 +65,5 @@ internal static class StringExtensions
|
|||
|
||||
return parsedString.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
namespace FFMpegCore.Extend;
|
||||
|
||||
public static class UriExtensions
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
public static class UriExtensions
|
||||
{
|
||||
public static bool SaveStream(this Uri uri, string output)
|
||||
{
|
||||
return FFMpeg.SaveM3U8Stream(uri, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class AudibleEncryptionKeyArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class AudibleEncryptionKeyArgument : IArgument
|
||||
{
|
||||
private readonly bool _aaxcMode;
|
||||
|
||||
private readonly string? _activationBytes;
|
||||
private readonly string? _key;
|
||||
private readonly string? _iv;
|
||||
|
||||
private readonly string? _key;
|
||||
private readonly string? _activationBytes;
|
||||
|
||||
public AudibleEncryptionKeyArgument(string activationBytes)
|
||||
{
|
||||
|
|
@ -23,4 +23,5 @@ public class AudibleEncryptionKeyArgument : IArgument
|
|||
}
|
||||
|
||||
public string Text => _aaxcMode ? $"-audible_key {_key} -audible_iv {_iv}" : $"-activation_bytes {_activationBytes}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents parameter of audio codec and it's quality
|
||||
/// </summary>
|
||||
public class AudioBitrateArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of audio codec and it's quality
|
||||
/// </summary>
|
||||
public class AudioBitrateArgument : IArgument
|
||||
{
|
||||
public readonly int Bitrate;
|
||||
public AudioBitrateArgument(AudioQuality value) : this((int)value) { }
|
||||
|
||||
public AudioBitrateArgument(int bitrate)
|
||||
{
|
||||
Bitrate = bitrate;
|
||||
}
|
||||
|
||||
public string Text => $"-b:a {Bitrate}k";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents parameter of audio codec and it's quality
|
||||
/// </summary>
|
||||
public class AudioCodecArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of audio codec and it's quality
|
||||
/// </summary>
|
||||
public class AudioCodecArgument : IArgument
|
||||
{
|
||||
public readonly string AudioCodec;
|
||||
|
||||
public AudioCodecArgument(Codec audioCodec)
|
||||
|
|
@ -25,5 +25,6 @@ public class AudioCodecArgument : IArgument
|
|||
AudioCodec = audioCodec;
|
||||
}
|
||||
|
||||
public string Text => $"-c:a {AudioCodec.ToLowerInvariant()}";
|
||||
public string Text => $"-c:a {AudioCodec.ToString().ToLowerInvariant()}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class AudioFiltersArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class AudioFiltersArgument : IArgument
|
||||
{
|
||||
public readonly AudioFilterOptions Options;
|
||||
|
||||
public AudioFiltersArgument(AudioFilterOptions options)
|
||||
|
|
@ -30,68 +30,42 @@ public class AudioFiltersArgument : IArgument
|
|||
|
||||
return $"-af \"{string.Join(", ", arguments)}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface IAudioFilterArgument
|
||||
{
|
||||
public interface IAudioFilterArgument
|
||||
{
|
||||
string Key { get; }
|
||||
string Value { get; }
|
||||
}
|
||||
}
|
||||
|
||||
public class AudioFilterOptions
|
||||
{
|
||||
public class AudioFilterOptions
|
||||
{
|
||||
public List<IAudioFilterArgument> Arguments { get; } = new();
|
||||
|
||||
public AudioFilterOptions Pan(string channelLayout, params string[] outputDefinitions)
|
||||
{
|
||||
return WithArgument(new PanArgument(channelLayout, outputDefinitions));
|
||||
}
|
||||
|
||||
public AudioFilterOptions Pan(int channels, params string[] outputDefinitions)
|
||||
{
|
||||
return WithArgument(new PanArgument(channels, outputDefinitions));
|
||||
}
|
||||
|
||||
public AudioFilterOptions Pan(string channelLayout, params string[] outputDefinitions) => WithArgument(new PanArgument(channelLayout, outputDefinitions));
|
||||
public AudioFilterOptions Pan(int channels, params string[] outputDefinitions) => WithArgument(new PanArgument(channels, outputDefinitions));
|
||||
public AudioFilterOptions DynamicNormalizer(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)
|
||||
{
|
||||
return WithArgument(new DynamicNormalizerArgument(frameLength, filterWindow,
|
||||
double compressorFactor = 0.0) => WithArgument(new DynamicNormalizerArgument(frameLength, filterWindow,
|
||||
targetPeak, gainFactor, targetRms, channelCoupling, enableDcBiasCorrection, enableAlternativeBoundary,
|
||||
compressorFactor));
|
||||
}
|
||||
|
||||
public AudioFilterOptions HighPass(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707,
|
||||
double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto",
|
||||
int? blocksize = null)
|
||||
{
|
||||
return WithArgument(new HighPassFilterArgument(frequency, poles, width_type, width, mix, channels, normalize, transform, precision, blocksize));
|
||||
}
|
||||
|
||||
int? blocksize = null) => WithArgument(new HighPassFilterArgument(frequency, poles, width_type, width, mix, channels, normalize, transform, precision, blocksize));
|
||||
public AudioFilterOptions LowPass(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707,
|
||||
double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto",
|
||||
int? blocksize = null)
|
||||
{
|
||||
return WithArgument(new LowPassFilterArgument(frequency, poles, width_type, width, mix, channels, normalize, transform, precision, blocksize));
|
||||
}
|
||||
|
||||
int? blocksize = null) => WithArgument(new LowPassFilterArgument(frequency, poles, width_type, width, mix, channels, normalize, transform, precision, blocksize));
|
||||
public AudioFilterOptions AudioGate(double level_in = 1, string mode = "downward", double range = 0.06125, double threshold = 0.125,
|
||||
int ratio = 2, double attack = 20, double release = 250, int makeup = 1, double knee = 2.828427125, string detection = "rms",
|
||||
string link = "average")
|
||||
{
|
||||
return WithArgument(new AudioGateArgument(level_in, mode, range, threshold, ratio, attack, release, makeup, knee, detection, link));
|
||||
}
|
||||
|
||||
string link = "average") => WithArgument(new AudioGateArgument(level_in, mode, range, threshold, ratio, attack, release, makeup, knee, detection, link));
|
||||
public AudioFilterOptions SilenceDetect(string noise_type = "db", double noise = 60, double duration = 2,
|
||||
bool mono = false)
|
||||
{
|
||||
return WithArgument(new SilenceDetectArgument(noise_type, noise, duration, mono));
|
||||
}
|
||||
bool mono = false) => WithArgument(new SilenceDetectArgument(noise_type, noise, duration, mono));
|
||||
|
||||
private AudioFilterOptions WithArgument(IAudioFilterArgument argument)
|
||||
{
|
||||
Arguments.Add(argument);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +1,25 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class AudioGateArgument : IAudioFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class AudioGateArgument : IAudioFilterArgument
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
/// <summary>
|
||||
/// Audio Gate. <see href="https://ffmpeg.org/ffmpeg-filters.html#agate" />
|
||||
/// Audio Gate. <see href="https://ffmpeg.org/ffmpeg-filters.html#agate"/>
|
||||
/// </summary>
|
||||
/// <param name="levelIn">Set input level before filtering. Default is 1. Allowed range is from 0.015625 to 64.</param>
|
||||
/// <param name="mode">
|
||||
/// Set the mode of operation. Can be upward or downward. Default is downward. If set to upward mode, higher parts of signal
|
||||
/// will be amplified, expanding dynamic range in upward direction. Otherwise, in case of downward lower parts of signal will be reduced.
|
||||
/// </param>
|
||||
/// <param name="range">
|
||||
/// Set the level of gain reduction when the signal is below the threshold. Default is 0.06125. Allowed range is from 0 to
|
||||
/// 1. Setting this to 0 disables reduction and then filter behaves like expander.
|
||||
/// </param>
|
||||
/// <param name="mode">Set the mode of operation. Can be upward or downward. Default is downward. If set to upward mode, higher parts of signal will be amplified, expanding dynamic range in upward direction. Otherwise, in case of downward lower parts of signal will be reduced.</param>
|
||||
/// <param name="range">Set the level of gain reduction when the signal is below the threshold. Default is 0.06125. Allowed range is from 0 to 1. Setting this to 0 disables reduction and then filter behaves like expander.</param>
|
||||
/// <param name="threshold">If a signal rises above this level the gain reduction is released. Default is 0.125. Allowed range is from 0 to 1.</param>
|
||||
/// <param name="ratio">Set a ratio by which the signal is reduced. Default is 2. Allowed range is from 1 to 9000.</param>
|
||||
/// <param name="attack">
|
||||
/// Amount of milliseconds the signal has to rise above the threshold before gain reduction stops. Default is 20
|
||||
/// milliseconds. Allowed range is from 0.01 to 9000.
|
||||
/// </param>
|
||||
/// <param name="release">
|
||||
/// Amount of milliseconds the signal has to fall below the threshold before the reduction is increased again. Default is
|
||||
/// 250 milliseconds. Allowed range is from 0.01 to 9000.
|
||||
/// </param>
|
||||
/// <param name="attack">Amount of milliseconds the signal has to rise above the threshold before gain reduction stops. Default is 20 milliseconds. Allowed range is from 0.01 to 9000.</param>
|
||||
/// <param name="release">Amount of milliseconds the signal has to fall below the threshold before the reduction is increased again. Default is 250 milliseconds. Allowed range is from 0.01 to 9000.</param>
|
||||
/// <param name="makeup">Set amount of amplification of signal after processing. Default is 1. Allowed range is from 1 to 64.</param>
|
||||
/// <param name="knee">
|
||||
/// Curve the sharp knee around the threshold to enter gain reduction more softly. Default is 2.828427125. Allowed range is
|
||||
/// from 1 to 8.
|
||||
/// </param>
|
||||
/// <param name="knee">Curve the sharp knee around the threshold to enter gain reduction more softly. Default is 2.828427125. Allowed range is from 1 to 8.</param>
|
||||
/// <param name="detection">Choose if exact signal should be taken for detection or an RMS like one. Default is rms. Can be peak or rms.</param>
|
||||
/// <param name="link">
|
||||
/// Choose if the average level between all channels or the louder channel affects the reduction. Default is average. Can be
|
||||
/// average or maximum.
|
||||
/// </param>
|
||||
/// <param name="link">Choose if the average level between all channels or the louder channel affects the reduction. Default is average. Can be average or maximum.</param>
|
||||
public AudioGateArgument(double levelIn = 1, string mode = "downward", double range = 0.06125, double threshold = 0.125, int ratio = 2,
|
||||
double attack = 20, double release = 250, int makeup = 1, double knee = 2.828427125, string detection = "rms", string link = "average")
|
||||
{
|
||||
|
|
@ -112,4 +94,5 @@ public class AudioGateArgument : IAudioFilterArgument
|
|||
public string Key { get; } = "agate";
|
||||
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Audio sampling rate argument. Defaults to 48000 (Hz)
|
||||
/// </summary>
|
||||
public class AudioSamplingRateArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Audio sampling rate argument. Defaults to 48000 (Hz)
|
||||
/// </summary>
|
||||
public class AudioSamplingRateArgument : IArgument
|
||||
{
|
||||
public readonly int SamplingRate;
|
||||
|
||||
public AudioSamplingRateArgument(int samplingRate = 48000)
|
||||
{
|
||||
SamplingRate = samplingRate;
|
||||
}
|
||||
|
||||
public string Text => $"-ar {SamplingRate}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents parameter of bitstream filter
|
||||
/// </summary>
|
||||
public class BitStreamFilterArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of bitstream filter
|
||||
/// </summary>
|
||||
public class BitStreamFilterArgument : IArgument
|
||||
{
|
||||
public readonly Channel Channel;
|
||||
public readonly Filter Filter;
|
||||
|
||||
|
|
@ -22,4 +22,5 @@ public class BitStreamFilterArgument : IArgument
|
|||
Channel.Video => $"-bsf:v {Filter.ToString().ToLowerInvariant()}",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class BlackDetectArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class BlackDetectArgument : IVideoFilterArgument
|
||||
{
|
||||
public string Key => "blackdetect";
|
||||
|
||||
public string Value { get; }
|
||||
|
||||
public BlackDetectArgument(double minimumDuration = 2.0, double pictureBlackRatioThreshold = 0.98, double pixelBlackThreshold = 0.1)
|
||||
{
|
||||
Value = $"d={minimumDuration}:pic_th={pictureBlackRatioThreshold}:pix_th={pixelBlackThreshold}";
|
||||
}
|
||||
|
||||
public string Key => "blackdetect";
|
||||
|
||||
public string Value { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
internal class BlackFrameArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
internal class BlackFrameArgument : IVideoFilterArgument
|
||||
{
|
||||
public string Key => "blackframe";
|
||||
|
||||
public string Value { get; }
|
||||
|
||||
public BlackFrameArgument(int amount = 98, int threshold = 32)
|
||||
{
|
||||
Value = $"amount={amount}:threshold={threshold}";
|
||||
}
|
||||
|
||||
public string Key => "blackframe";
|
||||
|
||||
public string Value { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,22 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents parameter of concat argument
|
||||
/// Used for creating video from multiple images or videos
|
||||
/// </summary>
|
||||
public class ConcatArgument : IInputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly IEnumerable<string> Values;
|
||||
|
||||
/// <summary>
|
||||
/// Represents parameter of concat argument
|
||||
/// Used for creating video from multiple images or videos
|
||||
/// </summary>
|
||||
public class ConcatArgument : IInputArgument
|
||||
{
|
||||
public readonly IEnumerable<string> Values;
|
||||
public ConcatArgument(IEnumerable<string> values)
|
||||
{
|
||||
Values = values;
|
||||
}
|
||||
|
||||
public void Pre() { }
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() { }
|
||||
|
||||
public string Text => $"-i \"concat:{string.Join(@"|", Values)}\"";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Constant Rate Factor (CRF) argument
|
||||
/// </summary>
|
||||
public class ConstantRateFactorArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant Rate Factor (CRF) argument
|
||||
/// </summary>
|
||||
public class ConstantRateFactorArgument : IArgument
|
||||
{
|
||||
public readonly int Crf;
|
||||
|
||||
public ConstantRateFactorArgument(int crf)
|
||||
|
|
@ -18,4 +18,5 @@ public class ConstantRateFactorArgument : IArgument
|
|||
}
|
||||
|
||||
public string Text => $"-crf {Crf}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents parameter of copy parameter
|
||||
/// Defines if channel (audio, video or both) should be copied to output file
|
||||
/// </summary>
|
||||
public class CopyArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents parameter of copy parameter
|
||||
/// Defines if channel (audio, video or both) should be copied to output file
|
||||
/// </summary>
|
||||
public class CopyArgument : IArgument
|
||||
{
|
||||
public readonly Channel Channel;
|
||||
|
||||
public CopyArgument(Channel channel = Channel.Both)
|
||||
{
|
||||
Channel = channel;
|
||||
|
|
@ -20,4 +19,5 @@ public class CopyArgument : IArgument
|
|||
Channel.Both => "-c:a copy -c:v copy",
|
||||
_ => $"-c{Channel.StreamType()} copy"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a copy codec parameter
|
||||
/// </summary>
|
||||
public class CopyCodecArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public string Text => "-codec copy";
|
||||
/// <summary>
|
||||
/// Represents a copy codec parameter
|
||||
/// </summary>
|
||||
public class CopyCodecArgument : IArgument
|
||||
{
|
||||
public string Text => $"-codec copy";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
using System.Drawing;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class CropArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly int Left;
|
||||
public class CropArgument : IArgument
|
||||
{
|
||||
public readonly Size? Size;
|
||||
public readonly int Top;
|
||||
public readonly int Left;
|
||||
|
||||
public CropArgument(Size? size, int top, int left)
|
||||
{
|
||||
|
|
@ -18,4 +18,5 @@ public class CropArgument : IArgument
|
|||
public CropArgument(int width, int height, int top, int left) : this(new Size(width, height), top, left) { }
|
||||
|
||||
public string Text => Size == null ? string.Empty : $"-vf crop={Size.Value.Width}:{Size.Value.Height}:{Left}:{Top}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class CustomArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class CustomArgument : IArgument
|
||||
{
|
||||
public readonly string Argument;
|
||||
|
||||
public CustomArgument(string argument)
|
||||
|
|
@ -10,4 +10,5 @@ public class CustomArgument : IArgument
|
|||
}
|
||||
|
||||
public string Text => Argument ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,31 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents parameter of concat argument
|
||||
/// Used for creating video from multiple images or videos
|
||||
/// </summary>
|
||||
public class DemuxConcatArgument : IInputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"concat_{Guid.NewGuid()}.txt");
|
||||
/// <summary>
|
||||
/// Represents parameter of concat argument
|
||||
/// Used for creating video from multiple images or videos
|
||||
/// </summary>
|
||||
public class DemuxConcatArgument : IInputArgument
|
||||
{
|
||||
public readonly IEnumerable<string> Values;
|
||||
|
||||
public DemuxConcatArgument(IEnumerable<string> values)
|
||||
{
|
||||
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>
|
||||
/// Thanks slhck
|
||||
/// https://superuser.com/a/787651/1089628
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
private string Escape(string value)
|
||||
{
|
||||
return value.Replace("'", @"'\''");
|
||||
private string Escape(string value) => value.Replace("'", @"'\''");
|
||||
|
||||
private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"concat_{Guid.NewGuid()}.txt");
|
||||
|
||||
public void Pre() => File.WriteAllLines(_tempFileName, Values);
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() => File.Delete(_tempFileName);
|
||||
|
||||
public string Text => $"-f concat -safe 0 -i \"{_tempFileName}\"";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents cpu speed parameter
|
||||
/// </summary>
|
||||
public class DisableChannelArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents cpu speed parameter
|
||||
/// </summary>
|
||||
public class DisableChannelArgument : IArgument
|
||||
{
|
||||
public readonly Channel Channel;
|
||||
|
||||
public DisableChannelArgument(Channel channel)
|
||||
|
|
@ -26,4 +26,5 @@ public class DisableChannelArgument : IArgument
|
|||
Channel.Audio => "-an",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Drawtext video filter argument
|
||||
/// </summary>
|
||||
public class DrawTextArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Drawtext video filter argument
|
||||
/// </summary>
|
||||
public class DrawTextArgument : IVideoFilterArgument
|
||||
{
|
||||
public readonly DrawTextOptions Options;
|
||||
|
||||
public DrawTextArgument(DrawTextOptions options)
|
||||
|
|
@ -14,33 +14,25 @@ public class DrawTextArgument : IVideoFilterArgument
|
|||
|
||||
public string Key { get; } = "drawtext";
|
||||
public string Value => Options.TextInternal;
|
||||
}
|
||||
|
||||
public class DrawTextOptions
|
||||
{
|
||||
public readonly string Font;
|
||||
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 class DrawTextOptions
|
||||
{
|
||||
public readonly string Text;
|
||||
public readonly string Font;
|
||||
public readonly List<(string key, string value)> Parameters;
|
||||
|
||||
public static DrawTextOptions Create(string text, string font)
|
||||
{
|
||||
return new DrawTextOptions(text, font, new List<(string, string)>());
|
||||
}
|
||||
|
||||
public static DrawTextOptions Create(string text, string font, params (string key, string value)[] 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)
|
||||
{
|
||||
return $"{pair.key}={EncloseIfContainsSpace(pair.value)}";
|
||||
|
|
@ -51,9 +43,17 @@ public class DrawTextOptions
|
|||
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)
|
||||
{
|
||||
Parameters.Add((key, value));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents duration parameter
|
||||
/// </summary>
|
||||
public class DurationArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents duration parameter
|
||||
/// </summary>
|
||||
public class DurationArgument : IArgument
|
||||
{
|
||||
public readonly TimeSpan? Duration;
|
||||
|
||||
public DurationArgument(TimeSpan? duration)
|
||||
{
|
||||
Duration = duration;
|
||||
}
|
||||
|
||||
public string Text => !Duration.HasValue ? string.Empty : $"-t {Duration.Value}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class DynamicNormalizerArgument : IAudioFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class DynamicNormalizerArgument : IAudioFilterArgument
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
/// <summary>
|
||||
/// Dynamic Audio Normalizer. <see href="https://ffmpeg.org/ffmpeg-filters.html#dynaudnorm" />
|
||||
/// Dynamic Audio Normalizer. <see href="https://ffmpeg.org/ffmpeg-filters.html#dynaudnorm"/>
|
||||
/// </summary>
|
||||
/// <param name="frameLength">Set the frame length in milliseconds. Must be between 10 to 8000. The default value is 500</param>
|
||||
/// <param name="filterWindow">Set the Gaussian filter window size. In range from 3 to 301, must be odd number. The default value is 31</param>
|
||||
|
|
@ -18,8 +18,7 @@ public class DynamicNormalizerArgument : IAudioFilterArgument
|
|||
/// <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="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)
|
||||
{
|
||||
|
|
@ -70,4 +69,5 @@ public class DynamicNormalizerArgument : IAudioFilterArgument
|
|||
public string Key { get; } = "dynaudnorm";
|
||||
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
using FFMpegCore.Extend;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents seek parameter
|
||||
/// </summary>
|
||||
public class EndSeekArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents seek parameter
|
||||
/// </summary>
|
||||
public class EndSeekArgument : IArgument
|
||||
{
|
||||
public readonly TimeSpan? SeekTo;
|
||||
|
||||
public EndSeekArgument(TimeSpan? seekTo)
|
||||
|
|
@ -15,4 +15,5 @@ public class EndSeekArgument : IArgument
|
|||
}
|
||||
|
||||
public string Text => SeekTo.HasValue ? $"-to {SeekTo.Value.ToLongString()}" : string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Faststart argument - for moving moov atom to the start of file
|
||||
/// </summary>
|
||||
public class FaststartArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Faststart argument - for moving moov atom to the start of file
|
||||
/// </summary>
|
||||
public class FaststartArgument : IArgument
|
||||
{
|
||||
public string Text => "-movflags faststart";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents force format parameter
|
||||
/// </summary>
|
||||
public class ForceFormatArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents force format parameter
|
||||
/// </summary>
|
||||
public class ForceFormatArgument : IArgument
|
||||
{
|
||||
private readonly string _format;
|
||||
|
||||
public ForceFormatArgument(string format)
|
||||
{
|
||||
_format = format;
|
||||
|
|
@ -20,4 +19,5 @@ public class ForceFormatArgument : IArgument
|
|||
}
|
||||
|
||||
public string Text => $"-f {_format}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class ForcePixelFormat : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class ForcePixelFormat : IArgument
|
||||
{
|
||||
public string PixelFormat { get; }
|
||||
public string Text => $"-pix_fmt {PixelFormat}";
|
||||
|
||||
public ForcePixelFormat(string format)
|
||||
{
|
||||
PixelFormat = format;
|
||||
}
|
||||
|
||||
public ForcePixelFormat(PixelFormat format) : this(format.Name) { }
|
||||
public string PixelFormat { get; }
|
||||
public string Text => $"-pix_fmt {PixelFormat}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents frame output count parameter
|
||||
/// </summary>
|
||||
public class FrameOutputCountArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents frame output count parameter
|
||||
/// </summary>
|
||||
public class FrameOutputCountArgument : IArgument
|
||||
{
|
||||
public readonly int Frames;
|
||||
|
||||
public FrameOutputCountArgument(int frames)
|
||||
{
|
||||
Frames = frames;
|
||||
}
|
||||
|
||||
public string Text => $"-vframes {Frames}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents frame rate parameter
|
||||
/// </summary>
|
||||
public class FrameRateArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents frame rate parameter
|
||||
/// </summary>
|
||||
public class FrameRateArgument : IArgument
|
||||
{
|
||||
public readonly double Framerate;
|
||||
|
||||
public FrameRateArgument(double framerate)
|
||||
|
|
@ -14,5 +12,6 @@ public class FrameRateArgument : IArgument
|
|||
Framerate = framerate;
|
||||
}
|
||||
|
||||
public string Text => $"-r {Framerate.ToString(CultureInfo.InvariantCulture)}";
|
||||
public string Text => $"-r {Framerate.ToString(System.Globalization.CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
using System.Drawing;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class GifPaletteArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class GifPaletteArgument : IArgument
|
||||
{
|
||||
private readonly int _streamIndex;
|
||||
|
||||
private readonly int _fps;
|
||||
|
||||
private readonly Size? _size;
|
||||
private readonly int _streamIndex;
|
||||
|
||||
public GifPaletteArgument(int streamIndex, int fps, Size? size)
|
||||
{
|
||||
|
|
@ -18,6 +19,6 @@ public class GifPaletteArgument : IArgument
|
|||
|
||||
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\"";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class HardwareAccelerationArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class HardwareAccelerationArgument : IArgument
|
||||
{
|
||||
public HardwareAccelerationDevice HardwareAccelerationDevice { get; }
|
||||
|
||||
public HardwareAccelerationArgument(HardwareAccelerationDevice hardwareAccelerationDevice)
|
||||
{
|
||||
HardwareAccelerationDevice = hardwareAccelerationDevice;
|
||||
}
|
||||
|
||||
public HardwareAccelerationDevice HardwareAccelerationDevice { get; }
|
||||
|
||||
public string Text => $"-hwaccel {HardwareAccelerationDevice.ToString().ToLower()}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,27 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class HighPassFilterArgument : IAudioFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class HighPassFilterArgument : IAudioFilterArgument
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
private readonly List<string> _precision = new()
|
||||
{
|
||||
"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"
|
||||
};
|
||||
|
||||
private readonly List<string> _widthTypes = new() { "h", "q", "o", "s", "k" };
|
||||
private readonly List<string> _transformTypes = new() { "di", "dii", "tdi", "tdii", "latt", "svf", "zdf" };
|
||||
private readonly List<string> _precision = new() { "auto", "s16", "s32", "f32", "f64" };
|
||||
/// <summary>
|
||||
/// HighPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#highpass" />
|
||||
/// HighPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#highpass"/>
|
||||
/// </summary>
|
||||
/// <param name="frequency">Set frequency in Hz. Default is 3000.</param>
|
||||
/// <param name="poles">Set number of poles. Default is 2.</param>
|
||||
/// <param name="width_type">Set method to specify band-width of filter, possible values are: h, q, o, s, k</param>
|
||||
/// <param name="width">
|
||||
/// Specify the band-width of a filter in width_type units. Applies only to double-pole filter. The default is 0.707q and
|
||||
/// gives a Butterworth response.
|
||||
/// </param>
|
||||
/// <param name="width">Specify the band-width of a filter in width_type units. Applies only to double-pole filter. The default is 0.707q and gives a Butterworth response.</param>
|
||||
/// <param name="mix">How much to use filtered signal in output. Default is 1. Range is between 0 and 1.</param>
|
||||
/// <param name="channels">Specify which channels to filter, by default all available are filtered.</param>
|
||||
/// <param name="normalize">Normalize biquad coefficients, by default is disabled. Enabling it will normalize magnitude response at DC to 0dB.</param>
|
||||
/// <param name="transform">Set transform type of IIR filter, possible values are: di, dii, tdi, tdii, latt, svf, zdf</param>
|
||||
/// <param name="precision">Set precison of filtering, possible values are: auto, s16, s32, f32, f64.</param>
|
||||
/// <param name="block_size">
|
||||
/// Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse
|
||||
/// response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just
|
||||
/// produce nasty artifacts.
|
||||
/// </param>
|
||||
public HighPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "",
|
||||
bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
|
||||
/// <param name="block_size">Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just produce nasty artifacts.</param>
|
||||
public HighPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
|
||||
{
|
||||
if (frequency < 0)
|
||||
{
|
||||
|
|
@ -70,7 +35,7 @@ public class HighPassFilterArgument : IAudioFilterArgument
|
|||
|
||||
if (!_widthTypes.Contains(width_type))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes);
|
||||
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes.ToString());
|
||||
}
|
||||
|
||||
if (mix < 0 || mix > 1)
|
||||
|
|
@ -80,7 +45,7 @@ public class HighPassFilterArgument : IAudioFilterArgument
|
|||
|
||||
if (!_precision.Contains(precision))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision);
|
||||
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision.ToString());
|
||||
}
|
||||
|
||||
_arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
|
|
@ -109,4 +74,5 @@ public class HighPassFilterArgument : IAudioFilterArgument
|
|||
public string Key { get; } = "highpass";
|
||||
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public interface IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public interface IArgument
|
||||
{
|
||||
/// <summary>
|
||||
/// The textual representation of the argument
|
||||
/// </summary>
|
||||
string Text { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class ID3V2VersionArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class ID3V2VersionArgument : IArgument
|
||||
{
|
||||
private readonly int _version;
|
||||
|
||||
public ID3V2VersionArgument(int version)
|
||||
|
|
@ -10,4 +10,5 @@ public class ID3V2VersionArgument : IArgument
|
|||
}
|
||||
|
||||
public string Text => $"-id3v2_version {_version}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public interface IDynamicArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public interface IDynamicArgument
|
||||
{
|
||||
/// <summary>
|
||||
/// Same as <see cref="IArgument.Text" />, but this receives the arguments generated before as parameter
|
||||
/// Same as <see cref="IArgument.Text"/>, but this receives the arguments generated before as parameter
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
/// <returns></returns>
|
||||
//public string GetText(StringBuilder context);
|
||||
string GetText(IEnumerable<IArgument> context);
|
||||
public string GetText(IEnumerable<IArgument> context);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public interface IInputArgument : IInputOutputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public interface IInputArgument : IInputOutputArgument
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public interface IInputOutputArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public interface IInputOutputArgument : IArgument
|
||||
{
|
||||
void Pre();
|
||||
Task During(CancellationToken cancellationToken = default);
|
||||
void Post();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public interface IOutputArgument : IInputOutputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public interface IOutputArgument : IInputOutputArgument
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents input parameter
|
||||
/// </summary>
|
||||
public class InputArgument : IInputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly string FilePath;
|
||||
/// <summary>
|
||||
/// Represents input parameter
|
||||
/// </summary>
|
||||
public class InputArgument : IInputArgument
|
||||
{
|
||||
public readonly bool VerifyExists;
|
||||
public readonly string FilePath;
|
||||
|
||||
public InputArgument(bool verifyExists, string filePaths)
|
||||
{
|
||||
|
|
@ -24,12 +24,9 @@ public class InputArgument : IInputArgument
|
|||
}
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() { }
|
||||
|
||||
public string Text => $"-i \"{FilePath}\"";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an input device parameter
|
||||
/// </summary>
|
||||
public class InputDeviceArgument : IInputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an input device parameter
|
||||
/// </summary>
|
||||
public class InputDeviceArgument : IInputArgument
|
||||
{
|
||||
private readonly string Device;
|
||||
|
||||
public InputDeviceArgument(string device)
|
||||
|
|
@ -12,14 +12,12 @@ public class InputDeviceArgument : IInputArgument
|
|||
Device = device;
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
|
||||
public void Pre() { }
|
||||
|
||||
public void Post() { }
|
||||
|
||||
public string Text => $"-i {Device}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
using System.IO.Pipes;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents input parameter for a named pipe
|
||||
/// </summary>
|
||||
public class InputPipeArgument : PipeArgument, IInputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents input parameter for a named pipe
|
||||
/// </summary>
|
||||
public class InputPipeArgument : PipeArgument, IInputArgument
|
||||
{
|
||||
public readonly IPipeSource Writer;
|
||||
|
||||
public InputPipeArgument(IPipeSource writer) : base(PipeDirection.Out)
|
||||
|
|
@ -27,4 +27,5 @@ public class InputPipeArgument : PipeArgument, IInputArgument
|
|||
|
||||
await Writer.WriteAsync(Pipe, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents loop parameter
|
||||
/// </summary>
|
||||
public class LoopArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents loop parameter
|
||||
/// </summary>
|
||||
public class LoopArgument : IArgument
|
||||
{
|
||||
public readonly int Times;
|
||||
|
||||
public LoopArgument(int times)
|
||||
{
|
||||
Times = times;
|
||||
}
|
||||
|
||||
public string Text => $"-loop {Times}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,27 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class LowPassFilterArgument : IAudioFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class LowPassFilterArgument : IAudioFilterArgument
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
private readonly List<string> _precision = new()
|
||||
{
|
||||
"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"
|
||||
};
|
||||
|
||||
private readonly List<string> _widthTypes = new() { "h", "q", "o", "s", "k" };
|
||||
private readonly List<string> _transformTypes = new() { "di", "dii", "tdi", "tdii", "latt", "svf", "zdf" };
|
||||
private readonly List<string> _precision = new() { "auto", "s16", "s32", "f32", "f64" };
|
||||
/// <summary>
|
||||
/// LowPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#lowpass" />
|
||||
/// LowPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#lowpass"/>
|
||||
/// </summary>
|
||||
/// <param name="frequency">Set frequency in Hz. Default is 3000.</param>
|
||||
/// <param name="poles">Set number of poles. Default is 2.</param>
|
||||
/// <param name="width_type">Set method to specify band-width of filter, possible values are: h, q, o, s, k</param>
|
||||
/// <param name="width">
|
||||
/// Specify the band-width of a filter in width_type units. Applies only to double-pole filter. The default is 0.707q and
|
||||
/// gives a Butterworth response.
|
||||
/// </param>
|
||||
/// <param name="width">Specify the band-width of a filter in width_type units. Applies only to double-pole filter. The default is 0.707q and gives a Butterworth response.</param>
|
||||
/// <param name="mix">How much to use filtered signal in output. Default is 1. Range is between 0 and 1.</param>
|
||||
/// <param name="channels">Specify which channels to filter, by default all available are filtered.</param>
|
||||
/// <param name="normalize">Normalize biquad coefficients, by default is disabled. Enabling it will normalize magnitude response at DC to 0dB.</param>
|
||||
/// <param name="transform">Set transform type of IIR filter, possible values are: di, dii, tdi, tdii, latt, svf, zdf</param>
|
||||
/// <param name="precision">Set precison of filtering, possible values are: auto, s16, s32, f32, f64.</param>
|
||||
/// <param name="block_size">
|
||||
/// Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse
|
||||
/// response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just
|
||||
/// produce nasty artifacts.
|
||||
/// </param>
|
||||
public LowPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "",
|
||||
bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
|
||||
/// <param name="block_size">Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just produce nasty artifacts.</param>
|
||||
public LowPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
|
||||
{
|
||||
if (frequency < 0)
|
||||
{
|
||||
|
|
@ -70,7 +35,7 @@ public class LowPassFilterArgument : IAudioFilterArgument
|
|||
|
||||
if (!_widthTypes.Contains(width_type))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes);
|
||||
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes.ToString());
|
||||
}
|
||||
|
||||
if (mix < 0 || mix > 1)
|
||||
|
|
@ -80,7 +45,7 @@ public class LowPassFilterArgument : IAudioFilterArgument
|
|||
|
||||
if (!_precision.Contains(precision))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision);
|
||||
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision.ToString());
|
||||
}
|
||||
|
||||
_arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
|
|
@ -109,4 +74,5 @@ public class LowPassFilterArgument : IAudioFilterArgument
|
|||
public string Key { get; } = "lowpass";
|
||||
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class MapMetadataArgument : IInputArgument, IDynamicArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class MapMetadataArgument : IInputArgument, IDynamicArgument
|
||||
{
|
||||
private readonly int? _inputIndex;
|
||||
|
||||
public string Text => GetText(null);
|
||||
|
||||
/// <summary>
|
||||
/// Null means it takes the last input used before this argument
|
||||
/// </summary>
|
||||
|
|
@ -35,8 +37,6 @@ public class MapMetadataArgument : IInputArgument, IDynamicArgument
|
|||
return $"-map_metadata {index}";
|
||||
}
|
||||
|
||||
public string Text => GetText(null);
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
|
|
@ -49,4 +49,5 @@ public class MapMetadataArgument : IInputArgument, IDynamicArgument
|
|||
public void Pre()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents choice of stream by the stream specifier
|
||||
/// </summary>
|
||||
public class MapStreamArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly Channel _channel;
|
||||
/// <summary>
|
||||
/// Represents choice of stream by the stream specifier
|
||||
/// </summary>
|
||||
public class MapStreamArgument : IArgument
|
||||
{
|
||||
private readonly int _inputFileIndex;
|
||||
private readonly bool _negativeMap;
|
||||
private readonly int _streamIndex;
|
||||
private readonly Channel _channel;
|
||||
private readonly bool _negativeMap;
|
||||
|
||||
public MapStreamArgument(int streamIndex, int inputFileIndex, Channel channel = Channel.All, bool negativeMap = false)
|
||||
{
|
||||
|
|
@ -27,4 +27,5 @@ public class MapStreamArgument : IArgument
|
|||
}
|
||||
|
||||
public string Text => $"-map {(_negativeMap ? "-" : "")}{_inputFileIndex}{_channel.StreamType()}:{_streamIndex}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class MetaDataArgument : IInputArgument, IDynamicArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class MetaDataArgument : IInputArgument, IDynamicArgument
|
||||
{
|
||||
private readonly string _metaDataContent;
|
||||
private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"metadata_{Guid.NewGuid()}.txt");
|
||||
|
||||
|
|
@ -10,6 +10,14 @@ public class MetaDataArgument : IInputArgument, IDynamicArgument
|
|||
_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)
|
||||
{
|
||||
arguments ??= Enumerable.Empty<IArgument>();
|
||||
|
|
@ -21,21 +29,5 @@ public class MetaDataArgument : IInputArgument, IDynamicArgument
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents input parameters for multiple files
|
||||
/// </summary>
|
||||
public class MultiInputArgument : IInputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly IEnumerable<string> FilePaths;
|
||||
/// <summary>
|
||||
/// Represents input parameters for multiple files
|
||||
/// </summary>
|
||||
public class MultiInputArgument : IInputArgument
|
||||
{
|
||||
public readonly bool VerifyExists;
|
||||
public readonly IEnumerable<string> FilePaths;
|
||||
|
||||
public MultiInputArgument(bool verifyExists, IEnumerable<string> filePaths)
|
||||
{
|
||||
|
|
@ -36,15 +36,12 @@ public class MultiInputArgument : IInputArgument
|
|||
}
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() { }
|
||||
|
||||
/// <summary>
|
||||
/// Generates a combined input argument text for all file paths
|
||||
/// </summary>
|
||||
public string Text => string.Join(" ", FilePaths.Select(filePath => $"-i \"{filePath}\""));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents output parameter
|
||||
/// </summary>
|
||||
public class OutputArgument : IOutputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly bool Overwrite;
|
||||
/// <summary>
|
||||
/// Represents output parameter
|
||||
/// </summary>
|
||||
public class OutputArgument : IOutputArgument
|
||||
{
|
||||
public readonly string Path;
|
||||
public readonly bool Overwrite;
|
||||
|
||||
public OutputArgument(string path, bool overwrite = true)
|
||||
{
|
||||
|
|
@ -16,10 +16,6 @@ public class OutputArgument : IOutputArgument
|
|||
Overwrite = overwrite;
|
||||
}
|
||||
|
||||
public OutputArgument(FileInfo value) : this(value.FullName) { }
|
||||
|
||||
public OutputArgument(Uri value) : this(value.AbsolutePath) { }
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
if (!Overwrite && File.Exists(Path))
|
||||
|
|
@ -27,15 +23,15 @@ public class OutputArgument : IOutputArgument
|
|||
throw new FFMpegException(FFMpegExceptionType.File, "Output file already exists and overwrite is disabled");
|
||||
}
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
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)}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System.IO.Pipes;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class OutputPipeArgument : PipeArgument, IOutputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class OutputPipeArgument : PipeArgument, IOutputArgument
|
||||
{
|
||||
public readonly IPipeSink Reader;
|
||||
|
||||
public OutputPipeArgument(IPipeSink reader) : base(PipeDirection.In)
|
||||
|
|
@ -24,4 +24,5 @@ public class OutputPipeArgument : PipeArgument, IOutputArgument
|
|||
|
||||
await Reader.ReadAsync(Pipe, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
internal class OutputTeeArgument : IOutputArgument
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
internal class OutputTeeArgument : IOutputArgument
|
||||
{
|
||||
private readonly FFMpegMultiOutputOptions _options;
|
||||
|
||||
public OutputTeeArgument(FFMpegMultiOutputOptions options)
|
||||
|
|
@ -16,10 +17,7 @@ internal class OutputTeeArgument : IOutputArgument
|
|||
|
||||
public string Text => $"-f tee \"{string.Join("|", _options.Outputs.Select(MapOptions))}\"";
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
|
||||
public void Post()
|
||||
{
|
||||
|
|
@ -48,12 +46,12 @@ internal class OutputTeeArgument : IOutputArgument
|
|||
{
|
||||
return map.Text.Replace("-map ", "select=\\'") + "\\'";
|
||||
}
|
||||
|
||||
if (argument is BitStreamFilterArgument bitstreamFilter)
|
||||
else if (argument is BitStreamFilterArgument bitstreamFilter)
|
||||
{
|
||||
return bitstreamFilter.Text.Replace("-bsf:", "bsfs/").Replace(' ', '=');
|
||||
}
|
||||
|
||||
return argument.Text.TrimStart('-').Replace(' ', '=');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents outputting to url using supported protocols
|
||||
/// See http://ffmpeg.org/ffmpeg-protocols.html
|
||||
/// </summary>
|
||||
public class OutputUrlArgument : IOutputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents outputting to url using supported protocols
|
||||
/// See http://ffmpeg.org/ffmpeg-protocols.html
|
||||
/// </summary>
|
||||
public class OutputUrlArgument : IOutputArgument
|
||||
{
|
||||
public readonly string Url;
|
||||
|
||||
public OutputUrlArgument(string url)
|
||||
|
|
@ -15,12 +15,10 @@ public class OutputUrlArgument : IOutputArgument
|
|||
|
||||
public void Post() { }
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
|
||||
public void Pre() { }
|
||||
|
||||
public string Text => Url;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents overwrite parameter
|
||||
/// If output file should be overwritten if exists
|
||||
/// </summary>
|
||||
public class OverwriteArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents overwrite parameter
|
||||
/// If output file should be overwritten if exists
|
||||
/// </summary>
|
||||
public class OverwriteArgument : IArgument
|
||||
{
|
||||
public string Text => "-y";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using FFMpegCore.Extend;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class PadArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class PadArgument : IVideoFilterArgument
|
||||
{
|
||||
private readonly PadOptions _options;
|
||||
|
||||
public PadArgument(PadOptions options)
|
||||
|
|
@ -13,12 +13,31 @@ public class PadArgument : IVideoFilterArgument
|
|||
|
||||
public string Key => "pad";
|
||||
public string Value => _options.TextInternal;
|
||||
}
|
||||
|
||||
public class PadOptions
|
||||
{
|
||||
}
|
||||
|
||||
public class PadOptions
|
||||
{
|
||||
public readonly Dictionary<string, string> Parameters = new();
|
||||
|
||||
internal string TextInternal => string.Join(":", Parameters.Select(parameter => parameter.FormatArgumentPair(true)));
|
||||
|
||||
public static PadOptions Create(string? width, string? height)
|
||||
{
|
||||
return new PadOptions(width, height);
|
||||
}
|
||||
|
||||
public static PadOptions Create(string aspectRatio)
|
||||
{
|
||||
return new PadOptions(aspectRatio);
|
||||
}
|
||||
|
||||
public PadOptions WithParameter(string key, string value)
|
||||
{
|
||||
Parameters.Add(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
private PadOptions(string? width, string? height)
|
||||
{
|
||||
if (width == null && height == null)
|
||||
|
|
@ -41,22 +60,5 @@ public class PadOptions
|
|||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Mix channels with specific gain levels.
|
||||
/// </summary>
|
||||
public class PanArgument : IAudioFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly string[] _outputDefinitions;
|
||||
/// <summary>
|
||||
/// Mix channels with specific gain levels.
|
||||
/// </summary>
|
||||
public class PanArgument : IAudioFilterArgument
|
||||
{
|
||||
public readonly string ChannelLayout;
|
||||
private readonly string[] _outputDefinitions;
|
||||
|
||||
/// <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"/>
|
||||
/// </summary>
|
||||
/// <param name="channelLayout">
|
||||
/// Represent the output channel layout. Like "stereo", "mono", "2.1", "5.1"
|
||||
|
|
@ -30,7 +30,7 @@ public class PanArgument : IAudioFilterArgument
|
|||
}
|
||||
|
||||
/// <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"/>
|
||||
/// </summary>
|
||||
/// <param name="channels">Number of channels in output file</param>
|
||||
/// <param name="outputDefinitions">
|
||||
|
|
@ -57,4 +57,5 @@ public class PanArgument : IAudioFilterArgument
|
|||
|
||||
public string Value =>
|
||||
string.Join("|", Enumerable.Empty<string>().Append(ChannelLayout).Concat(_outputDefinitions));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,28 +2,23 @@
|
|||
using System.IO.Pipes;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public abstract class PipeArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly PipeDirection _direction;
|
||||
private readonly object _pipeLock = new();
|
||||
|
||||
protected PipeArgument(PipeDirection direction)
|
||||
public abstract class PipeArgument
|
||||
{
|
||||
PipeName = PipeHelpers.GetUniquePipeName();
|
||||
_direction = direction;
|
||||
}
|
||||
|
||||
private string PipeName { get; }
|
||||
public string PipePath => PipeHelpers.GetPipePath(PipeName);
|
||||
|
||||
protected NamedPipeServerStream Pipe { get; private set; } = null!;
|
||||
public abstract string Text { get; }
|
||||
private readonly PipeDirection _direction;
|
||||
|
||||
protected PipeArgument(PipeDirection direction)
|
||||
{
|
||||
PipeName = PipeHelpers.GetUnqiuePipeName();
|
||||
_direction = direction;
|
||||
}
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
lock (_pipeLock)
|
||||
{
|
||||
if (Pipe != null)
|
||||
{
|
||||
|
|
@ -32,17 +27,13 @@ public abstract class PipeArgument
|
|||
|
||||
Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
|
||||
}
|
||||
}
|
||||
|
||||
public void Post()
|
||||
{
|
||||
Debug.WriteLine($"Disposing NamedPipeServerStream on {GetType().Name}");
|
||||
lock (_pipeLock)
|
||||
{
|
||||
Pipe?.Dispose();
|
||||
Pipe = null!;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
|
@ -57,15 +48,14 @@ public abstract class PipeArgument
|
|||
finally
|
||||
{
|
||||
Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}");
|
||||
lock (_pipeLock)
|
||||
{
|
||||
if (Pipe is { IsConnected: true })
|
||||
{
|
||||
Pipe.Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task ProcessDataAsync(CancellationToken token);
|
||||
public abstract string Text { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Remove metadata argument
|
||||
/// </summary>
|
||||
public class RemoveMetadataArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Remove metadata argument
|
||||
/// </summary>
|
||||
public class RemoveMetadataArgument : IArgument
|
||||
{
|
||||
public string Text => "-map_metadata -1";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
using System.Drawing;
|
||||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents scale parameter
|
||||
/// </summary>
|
||||
public class ScaleArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents scale parameter
|
||||
/// </summary>
|
||||
public class ScaleArgument : IVideoFilterArgument
|
||||
{
|
||||
public readonly Size? Size;
|
||||
|
||||
public ScaleArgument(Size? size)
|
||||
{
|
||||
Size = size;
|
||||
|
|
@ -19,9 +18,10 @@ public class ScaleArgument : IVideoFilterArgument
|
|||
|
||||
public ScaleArgument(VideoSize videosize)
|
||||
{
|
||||
Size = videosize == VideoSize.Original ? null : new Size(-1, (int)videosize);
|
||||
Size = videosize == VideoSize.Original ? null : (Size?)new Size(-1, (int)videosize);
|
||||
}
|
||||
|
||||
public string Key { get; } = "scale";
|
||||
public string Value => Size == null ? string.Empty : $"{Size.Value.Width}:{Size.Value.Height}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
using FFMpegCore.Extend;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents seek parameter
|
||||
/// </summary>
|
||||
public class SeekArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents seek parameter
|
||||
/// </summary>
|
||||
public class SeekArgument : IArgument
|
||||
{
|
||||
public readonly TimeSpan? SeekTo;
|
||||
|
||||
public SeekArgument(TimeSpan? seekTo)
|
||||
|
|
@ -15,4 +15,5 @@ public class SeekArgument : IArgument
|
|||
}
|
||||
|
||||
public string Text => SeekTo.HasValue ? $"-ss {SeekTo.Value.ToLongString()}" : string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class SetMirroringArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public class SetMirroringArgument : IVideoFilterArgument
|
||||
{
|
||||
public SetMirroringArgument(Mirroring mirroring)
|
||||
{
|
||||
Mirroring = mirroring;
|
||||
|
|
@ -20,4 +20,5 @@ public class SetMirroringArgument : IVideoFilterArgument
|
|||
Mirroring.Vertical => "vflip",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(Mirroring))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents shortest parameter
|
||||
/// </summary>
|
||||
public class ShortestArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents shortest parameter
|
||||
/// </summary>
|
||||
public class ShortestArgument : IArgument
|
||||
{
|
||||
public readonly bool Shortest;
|
||||
|
||||
public ShortestArgument(bool shortest)
|
||||
|
|
@ -13,4 +13,5 @@ public class ShortestArgument : IArgument
|
|||
}
|
||||
|
||||
public string Text => Shortest ? "-shortest" : string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue