mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2025-12-16 19:15:44 +00:00
Compare commits
No commits in common. "85e7170fd97d9aac1fc0c278d5264100c5c389bb" and "67808c2e4a2d46337eafb0169f3ffdc10b8b4129" have entirely different histories.
85e7170fd9
...
67808c2e4a
160 changed files with 7451 additions and 8171 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
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
<AssemblyVersion>5.0.0.0</AssemblyVersion>
|
||||
<LangVersion>default</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
<AssemblyVersion>5.0.0.0</AssemblyVersion>
|
||||
<LangVersion>default</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<RepositoryType>GitHub</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/rosenbjerg/FFMpegCore</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/rosenbjerg/FFMpegCore</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
<RepositoryType>GitHub</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/rosenbjerg/FFMpegCore</RepositoryUrl>
|
||||
<PackageProjectUrl>https://github.com/rosenbjerg/FFMpegCore</PackageProjectUrl>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
</PropertyGroup>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<IncludeSymbols>true</IncludeSymbols>
|
||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
|
||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -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,11 +100,10 @@ IVideoFrame GetNextFrame()
|
|||
}
|
||||
}
|
||||
|
||||
var videoFramesSource =
|
||||
new RawVideoPipeSource(CreateFrames(64)) //pass IEnumerable<IVideoFrame> or IEnumerator<IVideoFrame> to constructor of RawVideoPipeSource
|
||||
{
|
||||
FrameRate = 30 //set source frame rate
|
||||
};
|
||||
var videoFramesSource = new RawVideoPipeSource(CreateFrames(64)) //pass IEnumerable<IVideoFrame> or IEnumerator<IVideoFrame> to constructor of RawVideoPipeSource
|
||||
{
|
||||
FrameRate = 30 //set source frame rate
|
||||
};
|
||||
await FFMpegArguments
|
||||
.FromPipeInput(videoFramesSource)
|
||||
.OutputToFile(outputPath, false, options => options
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
using SkiaSharp;
|
||||
|
||||
namespace FFMpegCore.Extensions.SkiaSharp;
|
||||
|
||||
public static class BitmapExtensions
|
||||
namespace FFMpegCore.Extensions.SkiaSharp
|
||||
{
|
||||
public static bool AddAudio(this SKBitmap poster, string audio, string output)
|
||||
public static class BitmapExtensions
|
||||
{
|
||||
var destination = $"{Environment.TickCount}.png";
|
||||
using (var fileStream = File.OpenWrite(destination))
|
||||
public static bool AddAudio(this SKBitmap poster, string audio, string output)
|
||||
{
|
||||
poster.Encode(fileStream, SKEncodedImageFormat.Png, default); // PNG does not respect the quality parameter
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return FFMpeg.PosterWithAudio(destination, audio, output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(destination))
|
||||
var destination = $"{Environment.TickCount}.png";
|
||||
using (var fileStream = File.OpenWrite(destination))
|
||||
{
|
||||
File.Delete(destination);
|
||||
poster.Encode(fileStream, SKEncodedImageFormat.Png, default); // PNG does not respect the quality parameter
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return FFMpeg.PosterWithAudio(destination, audio, output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(destination))
|
||||
{
|
||||
File.Delete(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,58 +1,59 @@
|
|||
using FFMpegCore.Pipes;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FFMpegCore.Extensions.SkiaSharp;
|
||||
|
||||
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
||||
namespace FFMpegCore.Extensions.SkiaSharp
|
||||
{
|
||||
public BitmapVideoFrameWrapper(SKBitmap bitmap)
|
||||
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
||||
{
|
||||
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
|
||||
Format = ConvertStreamFormat(bitmap.ColorType);
|
||||
}
|
||||
public int Width => Source.Width;
|
||||
|
||||
public SKBitmap Source { get; }
|
||||
public int Height => Source.Height;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Source.Dispose();
|
||||
}
|
||||
public string Format { get; private set; }
|
||||
|
||||
public int Width => Source.Width;
|
||||
public SKBitmap Source { get; private set; }
|
||||
|
||||
public int Height => Source.Height;
|
||||
|
||||
public string Format { get; }
|
||||
|
||||
public void Serialize(Stream stream)
|
||||
{
|
||||
var data = Source.Bytes;
|
||||
stream.Write(data, 0, data.Length);
|
||||
}
|
||||
|
||||
public async Task SerializeAsync(Stream stream, CancellationToken token)
|
||||
{
|
||||
var data = Source.Bytes;
|
||||
await stream.WriteAsync(data, 0, data.Length, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string ConvertStreamFormat(SKColorType fmt)
|
||||
{
|
||||
// TODO: Add support for additional formats
|
||||
switch (fmt)
|
||||
public BitmapVideoFrameWrapper(SKBitmap bitmap)
|
||||
{
|
||||
case SKColorType.Gray8:
|
||||
return "gray8";
|
||||
case SKColorType.Bgra8888:
|
||||
return "bgra";
|
||||
case SKColorType.Rgb888x:
|
||||
return "rgb";
|
||||
case SKColorType.Rgba8888:
|
||||
return "rgba";
|
||||
case SKColorType.Rgb565:
|
||||
return "rgb565";
|
||||
default:
|
||||
throw new NotSupportedException($"Not supported pixel format {fmt}");
|
||||
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
|
||||
Format = ConvertStreamFormat(bitmap.ColorType);
|
||||
}
|
||||
|
||||
public void Serialize(Stream stream)
|
||||
{
|
||||
var data = Source.Bytes;
|
||||
stream.Write(data, 0, data.Length);
|
||||
}
|
||||
|
||||
public async Task SerializeAsync(Stream stream, CancellationToken token)
|
||||
{
|
||||
var data = Source.Bytes;
|
||||
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
|
||||
switch (fmt)
|
||||
{
|
||||
case SKColorType.Gray8:
|
||||
return "gray8";
|
||||
case SKColorType.Bgra8888:
|
||||
return "bgra";
|
||||
case SKColorType.Rgb888x:
|
||||
return "rgb";
|
||||
case SKColorType.Rgba8888:
|
||||
return "rgba";
|
||||
case SKColorType.Rgb565:
|
||||
return "rgb565";
|
||||
default:
|
||||
throw new NotSupportedException($"Not supported pixel format {fmt}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>true</IsPackable>
|
||||
<Description>Image extension for FFMpegCore using SkiaSharp</Description>
|
||||
<PackageVersion>5.0.3</PackageVersion>
|
||||
<PackageOutputPath>../nupkg</PackageOutputPath>
|
||||
<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>
|
||||
<PropertyGroup>
|
||||
<IsPackable>true</IsPackable>
|
||||
<Description>Image extension for FFMpegCore using SkiaSharp</Description>
|
||||
<PackageVersion>5.0.2</PackageVersion>
|
||||
<PackageOutputPath>../nupkg</PackageOutputPath>
|
||||
<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"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SkiaSharp" Version="3.116.1" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="3.116.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -2,57 +2,56 @@
|
|||
using FFMpegCore.Pipes;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FFMpegCore.Extensions.SkiaSharp;
|
||||
|
||||
public static class FFMpegImage
|
||||
namespace FFMpegCore.Extensions.SkiaSharp
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
/// <param name="input">Source video file.</param>
|
||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||
/// <param name="streamIndex">Selected video stream index.</param>
|
||||
/// <param name="inputFileIndex">Input file index</param>
|
||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||
public static SKBitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
||||
public static class FFMpegImage
|
||||
{
|
||||
var source = FFProbe.Analyse(input);
|
||||
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||
using var ms = new MemoryStream();
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
/// <param name="input">Source video file.</param>
|
||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||
/// <param name="streamIndex">Selected video stream index.</param>
|
||||
/// <param name="inputFileIndex">Input file index</param>
|
||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||
public static SKBitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
||||
{
|
||||
var source = FFProbe.Analyse(input);
|
||||
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||
using var ms = new MemoryStream();
|
||||
|
||||
arguments
|
||||
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
||||
.ForceFormat("rawvideo")))
|
||||
.ProcessSynchronously();
|
||||
arguments
|
||||
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
||||
.ForceFormat("rawvideo")))
|
||||
.ProcessSynchronously();
|
||||
|
||||
ms.Position = 0;
|
||||
using var bitmap = SKBitmap.Decode(ms);
|
||||
return bitmap.Copy();
|
||||
}
|
||||
ms.Position = 0;
|
||||
using var bitmap = SKBitmap.Decode(ms);
|
||||
return bitmap.Copy();
|
||||
}
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
/// <param name="input">Source video file.</param>
|
||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||
/// <param name="streamIndex">Selected video stream index.</param>
|
||||
/// <param name="inputFileIndex">Input file index</param>
|
||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||
public static async Task<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);
|
||||
using var ms = new MemoryStream();
|
||||
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
/// <param name="input">Source video file.</param>
|
||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||
/// <param name="streamIndex">Selected video stream index.</param>
|
||||
/// <param name="inputFileIndex">Input file index</param>
|
||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||
public static async Task<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);
|
||||
using var ms = new MemoryStream();
|
||||
await arguments
|
||||
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
||||
.ForceFormat("rawvideo")))
|
||||
.ProcessAsynchronously();
|
||||
|
||||
await arguments
|
||||
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
||||
.ForceFormat("rawvideo")))
|
||||
.ProcessAsynchronously();
|
||||
|
||||
ms.Position = 0;
|
||||
return SKBitmap.Decode(ms);
|
||||
ms.Position = 0;
|
||||
return SKBitmap.Decode(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,23 @@
|
|||
using System.Drawing;
|
||||
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common;
|
||||
|
||||
public static class BitmapExtensions
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common
|
||||
{
|
||||
public static bool AddAudio(this Image poster, string audio, string output)
|
||||
public static class BitmapExtensions
|
||||
{
|
||||
var destination = $"{Environment.TickCount}.png";
|
||||
poster.Save(destination);
|
||||
try
|
||||
public static bool AddAudio(this Image poster, string audio, string output)
|
||||
{
|
||||
return FFMpeg.PosterWithAudio(destination, audio, output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(destination))
|
||||
var destination = $"{Environment.TickCount}.png";
|
||||
poster.Save(destination);
|
||||
try
|
||||
{
|
||||
File.Delete(destination);
|
||||
return FFMpeg.PosterWithAudio(destination, audio, output);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(destination))
|
||||
{
|
||||
File.Delete(destination);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,84 +3,85 @@ 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 BitmapVideoFrameWrapper(Bitmap bitmap)
|
||||
public class BitmapVideoFrameWrapper : IVideoFrame, IDisposable
|
||||
{
|
||||
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
|
||||
Format = ConvertStreamFormat(bitmap.PixelFormat);
|
||||
}
|
||||
public int Width => Source.Width;
|
||||
|
||||
public Bitmap Source { get; }
|
||||
public int Height => Source.Height;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Source.Dispose();
|
||||
}
|
||||
public string Format { get; private set; }
|
||||
|
||||
public int Width => Source.Width;
|
||||
public Bitmap Source { get; private set; }
|
||||
|
||||
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);
|
||||
|
||||
try
|
||||
public BitmapVideoFrameWrapper(Bitmap bitmap)
|
||||
{
|
||||
var buffer = new byte[data.Stride * data.Height];
|
||||
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
|
||||
stream.Write(buffer, 0, buffer.Length);
|
||||
Source = bitmap ?? throw new ArgumentNullException(nameof(bitmap));
|
||||
Format = ConvertStreamFormat(bitmap.PixelFormat);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Source.UnlockBits(data);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SerializeAsync(Stream stream, CancellationToken token)
|
||||
{
|
||||
var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
|
||||
public void Serialize(Stream stream)
|
||||
{
|
||||
var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
|
||||
|
||||
try
|
||||
{
|
||||
var buffer = new byte[data.Stride * data.Height];
|
||||
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
|
||||
await stream.WriteAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var buffer = new byte[data.Stride * data.Height];
|
||||
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
|
||||
stream.Write(buffer, 0, buffer.Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Source.UnlockBits(data);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Source.UnlockBits(data);
|
||||
}
|
||||
}
|
||||
|
||||
private static string ConvertStreamFormat(PixelFormat fmt)
|
||||
{
|
||||
switch (fmt)
|
||||
public async Task SerializeAsync(Stream stream, CancellationToken token)
|
||||
{
|
||||
case PixelFormat.Format16bppGrayScale:
|
||||
return "gray16le";
|
||||
case PixelFormat.Format16bppRgb555:
|
||||
return "bgr555le";
|
||||
case PixelFormat.Format16bppRgb565:
|
||||
return "bgr565le";
|
||||
case PixelFormat.Format24bppRgb:
|
||||
return "bgr24";
|
||||
case PixelFormat.Format32bppArgb:
|
||||
return "bgra";
|
||||
case PixelFormat.Format32bppPArgb:
|
||||
//This is not really same as argb32
|
||||
return "argb";
|
||||
case PixelFormat.Format32bppRgb:
|
||||
return "rgba";
|
||||
case PixelFormat.Format48bppRgb:
|
||||
return "rgb48le";
|
||||
default:
|
||||
throw new NotSupportedException($"Not supported pixel format {fmt}");
|
||||
var data = Source.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadOnly, Source.PixelFormat);
|
||||
|
||||
try
|
||||
{
|
||||
var buffer = new byte[data.Stride * data.Height];
|
||||
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
|
||||
await stream.WriteAsync(buffer, 0, buffer.Length, token).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Source.UnlockBits(data);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Source.Dispose();
|
||||
}
|
||||
|
||||
private static string ConvertStreamFormat(PixelFormat fmt)
|
||||
{
|
||||
switch (fmt)
|
||||
{
|
||||
case PixelFormat.Format16bppGrayScale:
|
||||
return "gray16le";
|
||||
case PixelFormat.Format16bppRgb555:
|
||||
return "bgr555le";
|
||||
case PixelFormat.Format16bppRgb565:
|
||||
return "bgr565le";
|
||||
case PixelFormat.Format24bppRgb:
|
||||
return "bgr24";
|
||||
case PixelFormat.Format32bppArgb:
|
||||
return "bgra";
|
||||
case PixelFormat.Format32bppPArgb:
|
||||
//This is not really same as argb32
|
||||
return "argb";
|
||||
case PixelFormat.Format32bppRgb:
|
||||
return "rgba";
|
||||
case PixelFormat.Format48bppRgb:
|
||||
return "rgb48le";
|
||||
default:
|
||||
throw new NotSupportedException($"Not supported pixel format {fmt}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<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>Bump dependencies</PackageReleaseNotes>
|
||||
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze muxing</PackageTags>
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.10"/>
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,57 +1,57 @@
|
|||
using System.Drawing;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common;
|
||||
|
||||
public static class FFMpegImage
|
||||
namespace FFMpegCore.Extensions.System.Drawing.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
/// <param name="input">Source video file.</param>
|
||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||
/// <param name="streamIndex">Selected video stream index.</param>
|
||||
/// <param name="inputFileIndex">Input file index</param>
|
||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||
public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
||||
public static class FFMpegImage
|
||||
{
|
||||
var source = FFProbe.Analyse(input);
|
||||
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||
using var ms = new MemoryStream();
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
/// <param name="input">Source video file.</param>
|
||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||
/// <param name="streamIndex">Selected video stream index.</param>
|
||||
/// <param name="inputFileIndex">Input file index</param>
|
||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||
public static Bitmap Snapshot(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
||||
{
|
||||
var source = FFProbe.Analyse(input);
|
||||
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||
using var ms = new MemoryStream();
|
||||
|
||||
arguments
|
||||
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
||||
.ForceFormat("rawvideo")))
|
||||
.ProcessSynchronously();
|
||||
arguments
|
||||
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
||||
.ForceFormat("rawvideo")))
|
||||
.ProcessSynchronously();
|
||||
|
||||
ms.Position = 0;
|
||||
using var bitmap = new Bitmap(ms);
|
||||
return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat);
|
||||
}
|
||||
ms.Position = 0;
|
||||
using var bitmap = new Bitmap(ms);
|
||||
return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
/// <param name="input">Source video file.</param>
|
||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||
/// <param name="streamIndex">Selected video stream index.</param>
|
||||
/// <param name="inputFileIndex">Input file index</param>
|
||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||
public static async Task<Bitmap> SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null,
|
||||
int inputFileIndex = 0)
|
||||
{
|
||||
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
|
||||
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||
using var ms = new MemoryStream();
|
||||
/// <summary>
|
||||
/// Saves a 'png' thumbnail to an in-memory bitmap
|
||||
/// </summary>
|
||||
/// <param name="input">Source video file.</param>
|
||||
/// <param name="captureTime">Seek position where the thumbnail should be taken.</param>
|
||||
/// <param name="size">Thumbnail size. If width or height equal 0, the other will be computed automatically.</param>
|
||||
/// <param name="streamIndex">Selected video stream index.</param>
|
||||
/// <param name="inputFileIndex">Input file index</param>
|
||||
/// <returns>Bitmap with the requested snapshot.</returns>
|
||||
public static async Task<Bitmap> SnapshotAsync(string input, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
|
||||
{
|
||||
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
|
||||
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
|
||||
using var ms = new MemoryStream();
|
||||
|
||||
await arguments
|
||||
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
||||
.ForceFormat("rawvideo")))
|
||||
.ProcessAsynchronously();
|
||||
await arguments
|
||||
.OutputToPipe(new StreamPipeSink(ms), options => outputOptions(options
|
||||
.ForceFormat("rawvideo")))
|
||||
.ProcessAsynchronously();
|
||||
|
||||
ms.Position = 0;
|
||||
return new Bitmap(ms);
|
||||
ms.Position = 0;
|
||||
return new Bitmap(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1 +0,0 @@
|
|||
[assembly: Parallelize]
|
||||
|
|
@ -3,297 +3,326 @@ 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
|
||||
{
|
||||
[TestMethod]
|
||||
public void Audio_Remove()
|
||||
[TestClass]
|
||||
public class AudioTest
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
[TestMethod]
|
||||
public void Audio_Remove()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
||||
FFMpeg.Mute(TestResources.Mp4Video, outputFile);
|
||||
var analysis = FFProbe.Analyse(outputFile);
|
||||
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]
|
||||
public void Audio_Save()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp3");
|
||||
[TestMethod]
|
||||
public void Audio_Save()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp3");
|
||||
|
||||
FFMpeg.ExtractAudio(TestResources.Mp4Video, outputFile);
|
||||
var analysis = FFProbe.Analyse(outputFile);
|
||||
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()
|
||||
{
|
||||
await using var file = File.Open(TestResources.RawAudio, FileMode.Open);
|
||||
var memoryStream = new MemoryStream();
|
||||
await FFMpegArguments
|
||||
.FromPipeInput(new StreamPipeSource(file), options => options.ForceFormat("s16le"))
|
||||
.OutputToPipe(new StreamPipeSink(memoryStream), options => options.ForceFormat("mp3"))
|
||||
.ProcessAsynchronously();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task Audio_FromRaw()
|
||||
{
|
||||
await using var file = File.Open(TestResources.RawAudio, FileMode.Open);
|
||||
var memoryStream = new MemoryStream();
|
||||
await FFMpegArguments
|
||||
.FromPipeInput(new StreamPipeSource(file), options => options.ForceFormat("s16le"))
|
||||
.OutputToPipe(new StreamPipeSink(memoryStream), options => options.ForceFormat("mp3"))
|
||||
.ProcessAsynchronously();
|
||||
}
|
||||
[TestMethod]
|
||||
public void Audio_Add()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
|
||||
[TestMethod]
|
||||
public void Audio_Add()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
var success = FFMpeg.ReplaceAudio(TestResources.Mp4WithoutAudio, TestResources.Mp3Audio, outputFile);
|
||||
var videoAnalysis = FFProbe.Analyse(TestResources.Mp4WithoutAudio);
|
||||
var audioAnalysis = FFProbe.Analyse(TestResources.Mp3Audio);
|
||||
var outputAnalysis = FFProbe.Analyse(outputFile);
|
||||
|
||||
var success = FFMpeg.ReplaceAudio(TestResources.Mp4WithoutAudio, TestResources.Mp3Audio, outputFile);
|
||||
var videoAnalysis = FFProbe.Analyse(TestResources.Mp4WithoutAudio);
|
||||
var audioAnalysis = FFProbe.Analyse(TestResources.Mp3Audio);
|
||||
var outputAnalysis = FFProbe.Analyse(outputFile);
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(Math.Max(videoAnalysis.Duration.TotalSeconds, audioAnalysis.Duration.TotalSeconds), outputAnalysis.Duration.TotalSeconds, 0.15);
|
||||
Assert.IsTrue(File.Exists(outputFile));
|
||||
}
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(Math.Max(videoAnalysis.Duration.TotalSeconds, audioAnalysis.Duration.TotalSeconds), outputAnalysis.Duration.TotalSeconds, 0.15);
|
||||
Assert.IsTrue(File.Exists(outputFile));
|
||||
}
|
||||
[TestMethod]
|
||||
public void Image_AddAudio()
|
||||
{
|
||||
using var outputFile = new TemporaryFile("out.mp4");
|
||||
FFMpeg.PosterWithAudio(TestResources.PngImage, TestResources.Mp3Audio, outputFile);
|
||||
var analysis = FFProbe.Analyse(TestResources.Mp3Audio);
|
||||
Assert.IsTrue(analysis.Duration.TotalSeconds > 0);
|
||||
Assert.IsTrue(File.Exists(outputFile));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Image_AddAudio()
|
||||
{
|
||||
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(File.Exists(outputFile));
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToAAC_Args_Pipe()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public void Audio_ToAAC_Args_Pipe()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
var samples = new List<IAudioSample>
|
||||
{
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
};
|
||||
|
||||
var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([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)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously();
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
var success = FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously();
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToLibVorbis_Args_Pipe()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public void Audio_ToLibVorbis_Args_Pipe()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
var samples = new List<IAudioSample>
|
||||
{
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
};
|
||||
|
||||
var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([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)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.LibVorbis))
|
||||
.ProcessSynchronously();
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
var success = FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.LibVorbis))
|
||||
.ProcessSynchronously();
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Audio_ToAAC_Args_Pipe_Async()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public async Task Audio_ToAAC_Args_Pipe_Async()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
var samples = new List<IAudioSample>
|
||||
{
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
};
|
||||
|
||||
var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([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)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessAsynchronously();
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
var success = await FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessAsynchronously();
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToAAC_Args_Pipe_ValidDefaultConfiguration()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public void Audio_ToAAC_Args_Pipe_ValidDefaultConfiguration()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
var samples = new List<IAudioSample>
|
||||
{
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
new PcmAudioSampleWrapper(new byte[] { 0, 0 }),
|
||||
};
|
||||
|
||||
var samples = new List<IAudioSample> { new PcmAudioSampleWrapper([0, 0]), new PcmAudioSampleWrapper([0, 0]) };
|
||||
var audioSamplesSource = new RawAudioPipeSource(samples);
|
||||
|
||||
var audioSamplesSource = new RawAudioPipeSource(samples);
|
||||
var success = FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously();
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
var success = FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously();
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToAAC_Args_Pipe_InvalidChannels()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
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 };
|
||||
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
|
||||
Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToAAC_Args_Pipe_InvalidFormat()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
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" };
|
||||
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
|
||||
Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_ToAAC_Args_Pipe_InvalidSampleRate()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
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 };
|
||||
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
|
||||
Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments
|
||||
.FromPipeInput(audioSamplesSource)
|
||||
.OutputToFile(outputFile, false, opt => opt
|
||||
.WithAudioCodec(AudioCodec.Aac))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_Pan_ToMono()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public void Audio_Pan_ToMono()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.Pan(1, "c0 < 0.9 * c0 + 0.1 * c1")))
|
||||
.ProcessSynchronously();
|
||||
|
||||
var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.Pan(1, "c0 < 0.9 * c0 + 0.1 * c1")))
|
||||
.ProcessSynchronously();
|
||||
var mediaAnalysis = FFProbe.Analyse(outputFile);
|
||||
|
||||
var mediaAnalysis = FFProbe.Analyse(outputFile);
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count);
|
||||
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
|
||||
}
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.HasCount(1, mediaAnalysis.AudioStreams);
|
||||
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_Pan_ToMonoNoDefinitions()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public void Audio_Pan_ToMonoNoDefinitions()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.Pan(1)))
|
||||
.ProcessSynchronously();
|
||||
|
||||
var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.Pan(1)))
|
||||
.ProcessSynchronously();
|
||||
var mediaAnalysis = FFProbe.Analyse(outputFile);
|
||||
|
||||
var mediaAnalysis = FFProbe.Analyse(outputFile);
|
||||
Assert.IsTrue(success);
|
||||
Assert.AreEqual(1, mediaAnalysis.AudioStreams.Count);
|
||||
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
|
||||
}
|
||||
|
||||
Assert.IsTrue(success);
|
||||
Assert.HasCount(1, mediaAnalysis.AudioStreams);
|
||||
Assert.AreEqual("mono", mediaAnalysis.PrimaryAudioStream!.ChannelLayout);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_Pan_ToMonoChannelsToOutputDefinitionsMismatch()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public void Audio_Pan_ToMonoChannelsToOutputDefinitionsMismatch()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
var ex = Assert.ThrowsException<ArgumentException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.Pan(1, "c0=c0", "c1=c1")))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
|
||||
Assert.ThrowsExactly<ArgumentException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.Pan(1, "c0=c0", "c1=c1")))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_Pan_ToMonoChannelsLayoutToOutputDefinitionsMismatch()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public void Audio_Pan_ToMonoChannelsLayoutToOutputDefinitionsMismatch()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
var ex = Assert.ThrowsException<FFMpegException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.Pan("mono", "c0=c0", "c1=c1")))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
|
||||
Assert.ThrowsExactly<FFMpegException>(() => FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.Pan("mono", "c0=c0", "c1=c1")))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_DynamicNormalizer_WithDefaultValues()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public void Audio_DynamicNormalizer_WithDefaultValues()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.DynamicNormalizer()))
|
||||
.ProcessSynchronously();
|
||||
|
||||
var success = FFMpegArguments.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.DynamicNormalizer()))
|
||||
.ProcessSynchronously();
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Audio_DynamicNormalizer_WithNonDefaultValues()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public void Audio_DynamicNormalizer_WithNonDefaultValues()
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
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)))
|
||||
.ProcessSynchronously();
|
||||
|
||||
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)))
|
||||
.ProcessSynchronously();
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
|
||||
Assert.IsTrue(success);
|
||||
}
|
||||
[DataTestMethod, Timeout(10000)]
|
||||
[DataRow(2)]
|
||||
[DataRow(32)]
|
||||
[DataRow(8)]
|
||||
public void Audio_DynamicNormalizer_FilterWindow(int filterWindow)
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
[DataRow(2)]
|
||||
[DataRow(32)]
|
||||
[DataRow(8)]
|
||||
public void Audio_DynamicNormalizer_FilterWindow(int filterWindow)
|
||||
{
|
||||
using var outputFile = new TemporaryFile($"out{VideoType.Mp4.Extension}");
|
||||
|
||||
Assert.ThrowsExactly<ArgumentOutOfRangeException>(() => FFMpegArguments
|
||||
.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(filter => filter.DynamicNormalizer(filterWindow: filterWindow)))
|
||||
.ProcessSynchronously());
|
||||
var ex = Assert.ThrowsException<ArgumentOutOfRangeException>(() => FFMpegArguments
|
||||
.FromFileInput(TestResources.Mp3Audio)
|
||||
.OutputToFile(outputFile, true,
|
||||
argumentOptions => argumentOptions
|
||||
.WithAudioFilters(
|
||||
filter => filter.DynamicNormalizer(filterWindow: filterWindow)))
|
||||
.ProcessSynchronously());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,121 +1,103 @@
|
|||
using FFMpegCore.Arguments;
|
||||
using System.Reflection;
|
||||
using FFMpegCore.Arguments;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class FFMpegArgumentProcessorTest
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
private static FFMpegArgumentProcessor CreateArgumentProcessor()
|
||||
[TestClass]
|
||||
public class FFMpegArgumentProcessorTest
|
||||
{
|
||||
return FFMpegArguments
|
||||
.FromFileInput("")
|
||||
.OutputToFile("");
|
||||
}
|
||||
[TestCleanup]
|
||||
public void TestInitialize()
|
||||
|
||||
[TestMethod]
|
||||
public void Processor_GlobalOptions_GetUsed()
|
||||
{
|
||||
var globalWorkingDir = "Whatever1";
|
||||
var processor = CreateArgumentProcessor();
|
||||
|
||||
try
|
||||
{
|
||||
// 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 Processor_GlobalOptions_GetUsed()
|
||||
{
|
||||
var globalWorkingDir = "Whatever";
|
||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir });
|
||||
|
||||
var processor = CreateArgumentProcessor();
|
||||
var options2 = processor.GetConfiguredOptions(null);
|
||||
options2.WorkingDirectory.Should().Be(globalWorkingDir);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Processor_SessionOptions_GetUsed()
|
||||
{
|
||||
var sessionWorkingDir = "./CurrentRunWorkingDir";
|
||||
|
||||
var processor = CreateArgumentProcessor();
|
||||
processor.Configure(options => options.WorkingDirectory = sessionWorkingDir);
|
||||
var options = processor.GetConfiguredOptions(null);
|
||||
|
||||
Assert.AreEqual(globalWorkingDir, options.WorkingDirectory);
|
||||
options.WorkingDirectory.Should().Be(sessionWorkingDir);
|
||||
}
|
||||
finally
|
||||
|
||||
[TestMethod]
|
||||
public void Processor_Options_CanBeOverridden_And_Configured()
|
||||
{
|
||||
GlobalFFOptions.Configure(new FFOptions());
|
||||
}
|
||||
}
|
||||
var globalConfig = "Whatever";
|
||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig });
|
||||
|
||||
[TestMethod]
|
||||
public void Processor_SessionOptions_GetUsed()
|
||||
{
|
||||
var sessionWorkingDir = "./CurrentRunWorkingDir";
|
||||
|
||||
var processor = CreateArgumentProcessor();
|
||||
processor.Configure(options => options.WorkingDirectory = sessionWorkingDir);
|
||||
var options = processor.GetConfiguredOptions(null);
|
||||
|
||||
Assert.AreEqual(sessionWorkingDir, options.WorkingDirectory);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Processor_Options_CanBeOverridden_And_Configured()
|
||||
{
|
||||
var globalConfig = "Whatever2";
|
||||
|
||||
try
|
||||
{
|
||||
var processor = CreateArgumentProcessor();
|
||||
|
||||
var sessionTempDir = "./CurrentRunWorkingDir";
|
||||
processor.Configure(options => options.TemporaryFilesFolder = sessionTempDir);
|
||||
|
||||
var overrideOptions = new FFOptions { WorkingDirectory = "override" };
|
||||
var overrideOptions = new FFOptions() { WorkingDirectory = "override" };
|
||||
var options = processor.GetConfiguredOptions(overrideOptions);
|
||||
|
||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalConfig, TemporaryFilesFolder = globalConfig, BinaryFolder = globalConfig });
|
||||
var configuredOptions = processor.GetConfiguredOptions(overrideOptions);
|
||||
|
||||
Assert.AreEqual(configuredOptions.WorkingDirectory, overrideOptions.WorkingDirectory);
|
||||
Assert.AreEqual(configuredOptions.TemporaryFilesFolder, overrideOptions.TemporaryFilesFolder);
|
||||
Assert.AreEqual(configuredOptions.BinaryFolder, overrideOptions.BinaryFolder);
|
||||
|
||||
Assert.AreEqual(sessionTempDir, configuredOptions.TemporaryFilesFolder);
|
||||
Assert.AreNotEqual(globalConfig, configuredOptions.BinaryFolder);
|
||||
options.Should().BeEquivalentTo(overrideOptions);
|
||||
options.TemporaryFilesFolder.Should().BeEquivalentTo(sessionTempDir);
|
||||
options.BinaryFolder.Should().NotBeEquivalentTo(globalConfig);
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlobalFFOptions.Configure(new FFOptions());
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Options_Global_And_Session_Options_Can_Differ()
|
||||
{
|
||||
var globalWorkingDir = "Whatever3";
|
||||
|
||||
try
|
||||
[TestMethod]
|
||||
public void Options_Global_And_Session_Options_Can_Differ()
|
||||
{
|
||||
var globalWorkingDir = "Whatever";
|
||||
GlobalFFOptions.Configure(new FFOptions { WorkingDirectory = globalWorkingDir });
|
||||
|
||||
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 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");
|
||||
arg.Text.Should().Be($"-audible_key 123 -audible_iv 456");
|
||||
}
|
||||
|
||||
[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 Audible_Aax_Test()
|
||||
{
|
||||
var arg = new AudibleEncryptionKeyArgument("62689101");
|
||||
arg.Text.Should().Be($"-activation_bytes 62689101");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,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="SkiaSharp" Version="3.119.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.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,46 +1,48 @@
|
|||
using Newtonsoft.Json;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class FFMpegOptionsTest
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestMethod]
|
||||
public void Options_Initialized()
|
||||
[TestClass]
|
||||
public class FFMpegOptionsTest
|
||||
{
|
||||
Assert.IsNotNull(GlobalFFOptions.Current);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Options_Defaults_Configured()
|
||||
{
|
||||
Assert.AreEqual("", new FFOptions().BinaryFolder);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Options_Loaded_From_File()
|
||||
{
|
||||
Assert.AreEqual(
|
||||
GlobalFFOptions.Current.BinaryFolder,
|
||||
JsonConvert.DeserializeObject<FFOptions>(File.ReadAllText("ffmpeg.config.json")).BinaryFolder
|
||||
);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Options_Set_Programmatically()
|
||||
{
|
||||
var original = GlobalFFOptions.Current;
|
||||
try
|
||||
[TestMethod]
|
||||
public void Options_Initialized()
|
||||
{
|
||||
Assert.IsNotNull(GlobalFFOptions.Current);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Options_Defaults_Configured()
|
||||
{
|
||||
Assert.AreEqual(new FFOptions().BinaryFolder, $"");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Options_Loaded_From_File()
|
||||
{
|
||||
GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "Whatever" });
|
||||
Assert.AreEqual(
|
||||
"Whatever",
|
||||
GlobalFFOptions.Current.BinaryFolder
|
||||
GlobalFFOptions.Current.BinaryFolder,
|
||||
JsonConvert.DeserializeObject<FFOptions>(File.ReadAllText("ffmpeg.config.json")).BinaryFolder
|
||||
);
|
||||
}
|
||||
finally
|
||||
|
||||
[TestMethod]
|
||||
public void Options_Set_Programmatically()
|
||||
{
|
||||
GlobalFFOptions.Configure(original);
|
||||
var original = GlobalFFOptions.Current;
|
||||
try
|
||||
{
|
||||
GlobalFFOptions.Configure(new FFOptions { BinaryFolder = "Whatever" });
|
||||
Assert.AreEqual(
|
||||
GlobalFFOptions.Current.BinaryFolder,
|
||||
"Whatever"
|
||||
);
|
||||
}
|
||||
finally
|
||||
{
|
||||
GlobalFFOptions.Configure(original);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,288 +1,267 @@
|
|||
using FFMpegCore.Test.Resources;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class FFProbeTests
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
[TestMethod]
|
||||
public async Task Audio_FromStream_Duration()
|
||||
[TestClass]
|
||||
public class FFProbeTests
|
||||
{
|
||||
var fileAnalysis = await FFProbe.AnalyseAsync(TestResources.WebmVideo, cancellationToken: TestContext.CancellationToken);
|
||||
await using var inputStream = File.OpenRead(TestResources.WebmVideo);
|
||||
var streamAnalysis = await FFProbe.AnalyseAsync(inputStream, cancellationToken: TestContext.CancellationToken);
|
||||
Assert.IsTrue(fileAnalysis.Duration == streamAnalysis.Duration);
|
||||
}
|
||||
[TestMethod]
|
||||
public async Task Audio_FromStream_Duration()
|
||||
{
|
||||
var fileAnalysis = await FFProbe.AnalyseAsync(TestResources.WebmVideo);
|
||||
await using var inputStream = File.OpenRead(TestResources.WebmVideo);
|
||||
var streamAnalysis = await FFProbe.AnalyseAsync(inputStream);
|
||||
Assert.IsTrue(fileAnalysis.Duration == streamAnalysis.Duration);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FrameAnalysis_Sync()
|
||||
{
|
||||
var frameAnalysis = FFProbe.GetFrames(TestResources.WebmVideo);
|
||||
[TestMethod]
|
||||
public void FrameAnalysis_Sync()
|
||||
{
|
||||
var frameAnalysis = FFProbe.GetFrames(TestResources.WebmVideo);
|
||||
|
||||
Assert.HasCount(90, frameAnalysis.Frames);
|
||||
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));
|
||||
Assert.IsTrue(frameAnalysis.Frames.All(f => f.MediaType == "video"));
|
||||
}
|
||||
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));
|
||||
Assert.IsTrue(frameAnalysis.Frames.All(f => f.MediaType == "video"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task FrameAnalysis_Async()
|
||||
{
|
||||
var frameAnalysis = await FFProbe.GetFramesAsync(TestResources.WebmVideo, cancellationToken: TestContext.CancellationToken);
|
||||
[TestMethod]
|
||||
public async Task FrameAnalysis_Async()
|
||||
{
|
||||
var frameAnalysis = await FFProbe.GetFramesAsync(TestResources.WebmVideo);
|
||||
|
||||
Assert.HasCount(90, frameAnalysis.Frames);
|
||||
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));
|
||||
Assert.IsTrue(frameAnalysis.Frames.All(f => f.MediaType == "video"));
|
||||
}
|
||||
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));
|
||||
Assert.IsTrue(frameAnalysis.Frames.All(f => f.MediaType == "video"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task PacketAnalysis_Async()
|
||||
{
|
||||
var packetAnalysis = await FFProbe.GetPacketsAsync(TestResources.WebmVideo, cancellationToken: TestContext.CancellationToken);
|
||||
var packets = packetAnalysis.Packets;
|
||||
Assert.HasCount(96, packets);
|
||||
Assert.IsTrue(packets.All(f => f.CodecType == "video"));
|
||||
Assert.StartsWith("K_", packets[0].Flags);
|
||||
Assert.AreEqual(1362, packets.Last().Size);
|
||||
}
|
||||
[TestMethod]
|
||||
public async Task PacketAnalysis_Async()
|
||||
{
|
||||
var packetAnalysis = await FFProbe.GetPacketsAsync(TestResources.WebmVideo);
|
||||
var packets = packetAnalysis.Packets;
|
||||
Assert.AreEqual(96, packets.Count);
|
||||
Assert.IsTrue(packets.All(f => f.CodecType == "video"));
|
||||
Assert.IsTrue(packets[0].Flags.StartsWith("K_"));
|
||||
Assert.AreEqual(1362, packets.Last().Size);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PacketAnalysis_Sync()
|
||||
{
|
||||
var packets = FFProbe.GetPackets(TestResources.WebmVideo).Packets;
|
||||
[TestMethod]
|
||||
public void PacketAnalysis_Sync()
|
||||
{
|
||||
var packets = FFProbe.GetPackets(TestResources.WebmVideo).Packets;
|
||||
|
||||
Assert.HasCount(96, packets);
|
||||
Assert.IsTrue(packets.All(f => f.CodecType == "video"));
|
||||
Assert.StartsWith("K_", packets[0].Flags);
|
||||
Assert.AreEqual(1362, packets.Last().Size);
|
||||
}
|
||||
Assert.AreEqual(96, packets.Count);
|
||||
Assert.IsTrue(packets.All(f => f.CodecType == "video"));
|
||||
Assert.IsTrue(packets[0].Flags.StartsWith("K_"));
|
||||
Assert.AreEqual(1362, packets.Last().Size);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PacketAnalysisAudioVideo_Sync()
|
||||
{
|
||||
var packets = FFProbe.GetPackets(TestResources.Mp4Video).Packets;
|
||||
[TestMethod]
|
||||
public void PacketAnalysisAudioVideo_Sync()
|
||||
{
|
||||
var packets = FFProbe.GetPackets(TestResources.Mp4Video).Packets;
|
||||
|
||||
Assert.HasCount(216, packets);
|
||||
var actual = packets.Select(f => f.CodecType).Distinct().ToList();
|
||||
var expected = new List<string> { "audio", "video" };
|
||||
CollectionAssert.AreEquivalent(expected, actual);
|
||||
Assert.IsTrue(packets.Where(t => t.CodecType == "audio").All(f => f.Flags.StartsWith("K_")));
|
||||
Assert.AreEqual(75, packets.Count(t => t.CodecType == "video"));
|
||||
Assert.AreEqual(141, packets.Count(t => t.CodecType == "audio"));
|
||||
}
|
||||
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);
|
||||
Assert.IsTrue(packets.Where(t => t.CodecType == "audio").All(f => f.Flags.StartsWith("K_")));
|
||||
Assert.AreEqual(75, packets.Count(t => t.CodecType == "video"));
|
||||
Assert.AreEqual(141, packets.Count(t => t.CodecType == "audio"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[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)
|
||||
{
|
||||
var ffprobeStream = new FFProbeStream { Duration = duration };
|
||||
[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)
|
||||
{
|
||||
var ffprobeStream = new FFProbeStream { Duration = duration };
|
||||
|
||||
var parsedDuration = MediaAnalysisUtils.ParseDuration(ffprobeStream.Duration);
|
||||
var parsedDuration = MediaAnalysisUtils.ParseDuration(ffprobeStream.Duration);
|
||||
|
||||
Assert.AreEqual(expectedDays, parsedDuration.Days);
|
||||
Assert.AreEqual(expectedHours, parsedDuration.Hours);
|
||||
Assert.AreEqual(expectedMinutes, parsedDuration.Minutes);
|
||||
Assert.AreEqual(expectedSeconds, parsedDuration.Seconds);
|
||||
Assert.AreEqual(expectedMilliseconds, parsedDuration.Milliseconds);
|
||||
}
|
||||
Assert.AreEqual(expectedDays, parsedDuration.Days);
|
||||
Assert.AreEqual(expectedHours, parsedDuration.Hours);
|
||||
Assert.AreEqual(expectedMinutes, parsedDuration.Minutes);
|
||||
Assert.AreEqual(expectedSeconds, parsedDuration.Seconds);
|
||||
Assert.AreEqual(expectedMilliseconds, parsedDuration.Milliseconds);
|
||||
}
|
||||
|
||||
[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);
|
||||
Assert.IsNotNull(fileAnalysis);
|
||||
}
|
||||
[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"));
|
||||
Assert.IsNotNull(fileAnalysis);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Probe_Success()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.Mp4Video);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
Assert.IsEmpty(info.Chapters);
|
||||
[TestMethod]
|
||||
public void Probe_Success()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.Mp4Video);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
Assert.AreEqual(0, info.Chapters.Count);
|
||||
|
||||
Assert.AreEqual("5.1", info.PrimaryAudioStream!.ChannelLayout);
|
||||
Assert.AreEqual(6, info.PrimaryAudioStream.Channels);
|
||||
Assert.AreEqual("AAC (Advanced Audio Coding)", info.PrimaryAudioStream.CodecLongName);
|
||||
Assert.AreEqual("aac", info.PrimaryAudioStream.CodecName);
|
||||
Assert.AreEqual("LC", info.PrimaryAudioStream.Profile);
|
||||
Assert.AreEqual(377351, info.PrimaryAudioStream.BitRate);
|
||||
Assert.AreEqual(48000, info.PrimaryAudioStream.SampleRateHz);
|
||||
Assert.AreEqual("mp4a", info.PrimaryAudioStream.CodecTagString);
|
||||
Assert.AreEqual("0x6134706d", info.PrimaryAudioStream.CodecTag);
|
||||
Assert.AreEqual("5.1", info.PrimaryAudioStream!.ChannelLayout);
|
||||
Assert.AreEqual(6, info.PrimaryAudioStream.Channels);
|
||||
Assert.AreEqual("AAC (Advanced Audio Coding)", info.PrimaryAudioStream.CodecLongName);
|
||||
Assert.AreEqual("aac", info.PrimaryAudioStream.CodecName);
|
||||
Assert.AreEqual("LC", info.PrimaryAudioStream.Profile);
|
||||
Assert.AreEqual(377351, info.PrimaryAudioStream.BitRate);
|
||||
Assert.AreEqual(48000, info.PrimaryAudioStream.SampleRateHz);
|
||||
Assert.AreEqual("mp4a", info.PrimaryAudioStream.CodecTagString);
|
||||
Assert.AreEqual("0x6134706d", info.PrimaryAudioStream.CodecTag);
|
||||
|
||||
Assert.AreEqual(1471810, info.PrimaryVideoStream!.BitRate);
|
||||
Assert.AreEqual(16, info.PrimaryVideoStream.DisplayAspectRatio.Width);
|
||||
Assert.AreEqual(9, info.PrimaryVideoStream.DisplayAspectRatio.Height);
|
||||
Assert.AreEqual(1, info.PrimaryVideoStream.SampleAspectRatio.Width);
|
||||
Assert.AreEqual(1, info.PrimaryVideoStream.SampleAspectRatio.Height);
|
||||
Assert.AreEqual("yuv420p", info.PrimaryVideoStream.PixelFormat);
|
||||
Assert.AreEqual(31, info.PrimaryVideoStream.Level);
|
||||
Assert.AreEqual(1280, info.PrimaryVideoStream.Width);
|
||||
Assert.AreEqual(720, info.PrimaryVideoStream.Height);
|
||||
Assert.AreEqual(25, info.PrimaryVideoStream.AvgFrameRate);
|
||||
Assert.AreEqual(25, info.PrimaryVideoStream.FrameRate);
|
||||
Assert.AreEqual("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", info.PrimaryVideoStream.CodecLongName);
|
||||
Assert.AreEqual("h264", info.PrimaryVideoStream.CodecName);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitsPerRawSample);
|
||||
Assert.AreEqual("Main", info.PrimaryVideoStream.Profile);
|
||||
Assert.AreEqual("avc1", info.PrimaryVideoStream.CodecTagString);
|
||||
Assert.AreEqual("0x31637661", info.PrimaryVideoStream.CodecTag);
|
||||
}
|
||||
Assert.AreEqual(1471810, info.PrimaryVideoStream!.BitRate);
|
||||
Assert.AreEqual(16, info.PrimaryVideoStream.DisplayAspectRatio.Width);
|
||||
Assert.AreEqual(9, info.PrimaryVideoStream.DisplayAspectRatio.Height);
|
||||
Assert.AreEqual(1, info.PrimaryVideoStream.SampleAspectRatio.Width);
|
||||
Assert.AreEqual(1, info.PrimaryVideoStream.SampleAspectRatio.Height);
|
||||
Assert.AreEqual("yuv420p", info.PrimaryVideoStream.PixelFormat);
|
||||
Assert.AreEqual(31, info.PrimaryVideoStream.Level);
|
||||
Assert.AreEqual(1280, info.PrimaryVideoStream.Width);
|
||||
Assert.AreEqual(720, info.PrimaryVideoStream.Height);
|
||||
Assert.AreEqual(25, info.PrimaryVideoStream.AvgFrameRate);
|
||||
Assert.AreEqual(25, info.PrimaryVideoStream.FrameRate);
|
||||
Assert.AreEqual("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10", info.PrimaryVideoStream.CodecLongName);
|
||||
Assert.AreEqual("h264", info.PrimaryVideoStream.CodecName);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitsPerRawSample);
|
||||
Assert.AreEqual("Main", info.PrimaryVideoStream.Profile);
|
||||
Assert.AreEqual("avc1", info.PrimaryVideoStream.CodecTagString);
|
||||
Assert.AreEqual("0x31637661", info.PrimaryVideoStream.CodecTag);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Probe_Rotation()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.Mp4Video);
|
||||
Assert.IsNotNull(info.PrimaryVideoStream);
|
||||
Assert.AreEqual(0, info.PrimaryVideoStream.Rotation);
|
||||
[TestMethod]
|
||||
public void Probe_Rotation()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.Mp4Video);
|
||||
Assert.AreEqual(0, info.PrimaryVideoStream.Rotation);
|
||||
|
||||
info = FFProbe.Analyse(TestResources.Mp4VideoRotation);
|
||||
Assert.IsNotNull(info.PrimaryVideoStream);
|
||||
Assert.AreEqual(90, info.PrimaryVideoStream.Rotation);
|
||||
}
|
||||
info = FFProbe.Analyse(TestResources.Mp4VideoRotation);
|
||||
Assert.AreEqual(90, info.PrimaryVideoStream.Rotation);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Probe_Rotation_Negative_Value()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.Mp4VideoRotationNegative);
|
||||
Assert.IsNotNull(info.PrimaryVideoStream);
|
||||
Assert.AreEqual(-90, info.PrimaryVideoStream.Rotation);
|
||||
}
|
||||
[TestMethod]
|
||||
public void Probe_Rotation_Negative_Value()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.Mp4VideoRotationNegative);
|
||||
Assert.AreEqual(-90, info.PrimaryVideoStream.Rotation);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public async Task Probe_Async_Success()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video, cancellationToken: TestContext.CancellationToken);
|
||||
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)]
|
||||
public async Task Probe_Async_Success()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth);
|
||||
// This video's audio stream is AAC, which is lossy, so bit depth is meaningless.
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public void Probe_Success_FromStream()
|
||||
{
|
||||
using var stream = File.OpenRead(TestResources.WebmVideo);
|
||||
var info = FFProbe.Analyse(stream);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
// This video has no audio stream.
|
||||
Assert.IsNull(info.PrimaryAudioStream);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Probe_Success_FromStream()
|
||||
{
|
||||
using var stream = File.OpenRead(TestResources.WebmVideo);
|
||||
var info = FFProbe.Analyse(stream);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
// This video has no audio stream.
|
||||
Assert.IsNull(info.PrimaryAudioStream);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public async Task Probe_Success_FromStream_Async()
|
||||
{
|
||||
await using var stream = File.OpenRead(TestResources.WebmVideo);
|
||||
var info = await FFProbe.AnalyseAsync(stream, cancellationToken: TestContext.CancellationToken);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_FromStream_Async()
|
||||
{
|
||||
await using var stream = File.OpenRead(TestResources.WebmVideo);
|
||||
var info = await FFProbe.AnalyseAsync(stream);
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public void Probe_HDR()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.HdrVideo);
|
||||
[TestMethod, Timeout(10000)]
|
||||
public void Probe_HDR()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.HdrVideo);
|
||||
|
||||
Assert.IsNotNull(info.PrimaryVideoStream);
|
||||
Assert.AreEqual("tv", info.PrimaryVideoStream.ColorRange);
|
||||
Assert.AreEqual("bt2020nc", info.PrimaryVideoStream.ColorSpace);
|
||||
Assert.AreEqual("arib-std-b67", info.PrimaryVideoStream.ColorTransfer);
|
||||
Assert.AreEqual("bt2020", info.PrimaryVideoStream.ColorPrimaries);
|
||||
}
|
||||
Assert.IsNotNull(info.PrimaryVideoStream);
|
||||
Assert.AreEqual("tv", info.PrimaryVideoStream.ColorRange);
|
||||
Assert.AreEqual("bt2020nc", info.PrimaryVideoStream.ColorSpace);
|
||||
Assert.AreEqual("arib-std-b67", info.PrimaryVideoStream.ColorTransfer);
|
||||
Assert.AreEqual("bt2020", info.PrimaryVideoStream.ColorPrimaries);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public async Task Probe_Success_Subtitle_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.SrtSubtitle, cancellationToken: TestContext.CancellationToken);
|
||||
Assert.IsNotNull(info.PrimarySubtitleStream);
|
||||
Assert.HasCount(1, info.SubtitleStreams);
|
||||
Assert.IsEmpty(info.AudioStreams);
|
||||
Assert.IsEmpty(info.VideoStreams);
|
||||
// BitDepth is meaningless for subtitles
|
||||
Assert.IsNull(info.SubtitleStreams[0].BitDepth);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_Subtitle_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.SrtSubtitle);
|
||||
Assert.IsNotNull(info.PrimarySubtitleStream);
|
||||
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)]
|
||||
public async Task Probe_Success_Disposition_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video, cancellationToken: TestContext.CancellationToken);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream.Disposition);
|
||||
Assert.IsTrue(info.PrimaryAudioStream.Disposition["default"]);
|
||||
Assert.IsFalse(info.PrimaryAudioStream.Disposition["forced"]);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_Disposition_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp4Video);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream.Disposition);
|
||||
Assert.AreEqual(true, info.PrimaryAudioStream.Disposition["default"]);
|
||||
Assert.AreEqual(false, info.PrimaryAudioStream.Disposition["forced"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public async Task Probe_Success_Mp3AudioBitDepthNull_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp3Audio, cancellationToken: TestContext.CancellationToken);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
// mp3 is lossy, so bit depth is meaningless.
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_Mp3AudioBitDepthNull_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Mp3Audio);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
// mp3 is lossy, so bit depth is meaningless.
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public async Task Probe_Success_VocAudioBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.AiffAudio, cancellationToken: TestContext.CancellationToken);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(16, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_VocAudioBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.AiffAudio);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(16, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
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);
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_MkvVideoBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.MkvVideo);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(8, info.PrimaryVideoStream.BitDepth);
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.IsNull(info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_24BitWavBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav24Bit);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(24, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public async Task Probe_Success_24BitWavBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav24Bit, cancellationToken: TestContext.CancellationToken);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(24, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
[TestMethod, Timeout(10000)]
|
||||
public async Task Probe_Success_32BitWavBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav32Bit);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(32, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(10000, CooperativeCancellation = true)]
|
||||
public async Task Probe_Success_32BitWavBitDepth_Async()
|
||||
{
|
||||
var info = await FFProbe.AnalyseAsync(TestResources.Wav32Bit, cancellationToken: TestContext.CancellationToken);
|
||||
Assert.IsNotNull(info.PrimaryAudioStream);
|
||||
Assert.AreEqual(32, info.PrimaryAudioStream.BitDepth);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Probe_Success_Custom_Arguments()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.Mp4Video, customArguments: "-headers \"Hello: World\"");
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
[TestMethod]
|
||||
public void Probe_Success_Custom_Arguments()
|
||||
{
|
||||
var info = FFProbe.Analyse(TestResources.Mp4Video, customArguments: "-headers \"Hello: World\"");
|
||||
Assert.AreEqual(3, info.Duration.Seconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +1,70 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using FFMpegCore.Builders.MetaData;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class MetaDataBuilderTests
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestMetaDataBuilderIntegrity()
|
||||
[TestClass]
|
||||
public class MetaDataBuilderTests
|
||||
{
|
||||
var source = new
|
||||
[TestMethod]
|
||||
public void TestMetaDataBuilderIntegrity()
|
||||
{
|
||||
Album = "Kanon und Gigue",
|
||||
Artist = "Pachelbel",
|
||||
Title = "Kanon und Gigue in D-Dur",
|
||||
Copyright = "Copyright Lol",
|
||||
Composer = "Pachelbel",
|
||||
Genres = new[] { "Synthwave", "Classics" },
|
||||
Tracks = new[]
|
||||
var source = 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" }
|
||||
}
|
||||
};
|
||||
Album = "Kanon und Gigue",
|
||||
Artist = "Pachelbel",
|
||||
Title = "Kanon und Gigue in D-Dur",
|
||||
Copyright = "Copyright Lol",
|
||||
Composer = "Pachelbel",
|
||||
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" },
|
||||
}
|
||||
};
|
||||
|
||||
var builder = new MetaDataBuilder()
|
||||
.WithTitle(source.Title)
|
||||
.WithArtists(source.Artist)
|
||||
.WithComposers(source.Composer)
|
||||
.WithAlbumArtists(source.Artist)
|
||||
.WithGenres(source.Genres)
|
||||
.WithCopyright(source.Copyright)
|
||||
.AddChapters(source.Tracks, x => (x.Duration, x.Title));
|
||||
var builder = new MetaDataBuilder()
|
||||
.WithTitle(source.Title)
|
||||
.WithArtists(source.Artist)
|
||||
.WithComposers(source.Composer)
|
||||
.WithAlbumArtists(source.Artist)
|
||||
.WithGenres(source.Genres)
|
||||
.WithCopyright(source.Copyright)
|
||||
.AddChapters(source.Tracks, x => (x.Duration, x.Title));
|
||||
|
||||
var metadata = builder.Build();
|
||||
var serialized = MetaDataSerializer.Instance.Serialize(metadata);
|
||||
var metadata = builder.Build();
|
||||
var serialized = MetaDataSerializer.Instance.Serialize(metadata);
|
||||
|
||||
Assert.IsTrue(serialized.StartsWith(";FFMETADATA1", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.IsTrue(serialized.Contains("genre=Synthwave; Classics", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.IsTrue(serialized.Contains("title=Chapter 01", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.IsTrue(serialized.Contains("album_artist=Pachelbel", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
Assert.IsTrue(serialized.StartsWith(";FFMETADATA1", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.IsTrue(serialized.Contains("genre=Synthwave; Classics", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.IsTrue(serialized.Contains("title=Chapter 01", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.IsTrue(serialized.Contains("album_artist=Pachelbel", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestMapMetadata()
|
||||
{
|
||||
//-i "whaterver0" // index: 0
|
||||
//-f concat -safe 0
|
||||
//-i "\AppData\Local\Temp\concat_b511f2bf-c4af-4f71-b9bd-24d706bf4861.txt" // index: 1
|
||||
//-i "\AppData\Local\Temp\metadata_210d3259-3d5c-43c8-9786-54b5c414fa70.txt" // index: 2
|
||||
//-map_metadata 2
|
||||
[TestMethod]
|
||||
public void TestMapMetadata()
|
||||
{
|
||||
//-i "whaterver0" // index: 0
|
||||
//-f concat -safe 0
|
||||
//-i "\AppData\Local\Temp\concat_b511f2bf-c4af-4f71-b9bd-24d706bf4861.txt" // index: 1
|
||||
//-i "\AppData\Local\Temp\metadata_210d3259-3d5c-43c8-9786-54b5c414fa70.txt" // index: 2
|
||||
//-map_metadata 2
|
||||
|
||||
var text0 = FFMpegArguments.FromFileInput("whaterver0")
|
||||
.AddMetaData("WhatEver3")
|
||||
.Text;
|
||||
var text0 = FFMpegArguments.FromFileInput("whaterver0")
|
||||
.AddMetaData("WhatEver3")
|
||||
.Text;
|
||||
|
||||
var text1 = FFMpegArguments.FromFileInput("whaterver0")
|
||||
.AddDemuxConcatInput(new[] { "whaterver", "whaterver1" })
|
||||
.AddMetaData("WhatEver3")
|
||||
.Text;
|
||||
var text1 = FFMpegArguments.FromFileInput("whaterver0")
|
||||
.AddDemuxConcatInput(new[] { "whaterver", "whaterver1" })
|
||||
.AddMetaData("WhatEver3")
|
||||
.Text;
|
||||
|
||||
Assert.IsTrue(Regex.IsMatch(text0, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 1"), "map_metadata index is calculated incorrectly.");
|
||||
Assert.IsTrue(Regex.IsMatch(text1, "metadata_[0-9a-f-]+\\.txt\" -map_metadata 2"), "map_metadata index is calculated incorrectly.");
|
||||
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,39 +1,41 @@
|
|||
using FFMpegCore.Exceptions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test;
|
||||
|
||||
[TestClass]
|
||||
public class PixelFormatTests
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
[TestMethod]
|
||||
public void PixelFormats_Enumerate()
|
||||
[TestClass]
|
||||
public class PixelFormatTests
|
||||
{
|
||||
var formats = FFMpeg.GetPixelFormats();
|
||||
Assert.IsNotEmpty(formats);
|
||||
}
|
||||
[TestMethod]
|
||||
public void PixelFormats_Enumerate()
|
||||
{
|
||||
var formats = FFMpeg.GetPixelFormats();
|
||||
Assert.IsTrue(formats.Count > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PixelFormats_TryGetExisting()
|
||||
{
|
||||
Assert.IsTrue(FFMpeg.TryGetPixelFormat("yuv420p", out _));
|
||||
}
|
||||
[TestMethod]
|
||||
public void PixelFormats_TryGetExisting()
|
||||
{
|
||||
Assert.IsTrue(FFMpeg.TryGetPixelFormat("yuv420p", out _));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PixelFormats_TryGetNotExisting()
|
||||
{
|
||||
Assert.IsFalse(FFMpeg.TryGetPixelFormat("yuv420pppUnknown", out _));
|
||||
}
|
||||
[TestMethod]
|
||||
public void PixelFormats_TryGetNotExisting()
|
||||
{
|
||||
Assert.IsFalse(FFMpeg.TryGetPixelFormat("yuv420pppUnknown", out _));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PixelFormats_GetExisting()
|
||||
{
|
||||
var fmt = FFMpeg.GetPixelFormat("yuv420p");
|
||||
Assert.IsTrue(fmt.Components == 3 && fmt.BitsPerPixel == 12);
|
||||
}
|
||||
[TestMethod]
|
||||
public void PixelFormats_GetExisting()
|
||||
{
|
||||
var fmt = FFMpeg.GetPixelFormat("yuv420p");
|
||||
Assert.IsTrue(fmt.Components == 3 && fmt.BitsPerPixel == 12);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PixelFormats_GetNotExisting()
|
||||
{
|
||||
Assert.ThrowsExactly<FFMpegException>(() => FFMpeg.GetPixelFormat("yuv420pppUnknown"));
|
||||
[TestMethod]
|
||||
public void PixelFormats_GetNotExisting()
|
||||
{
|
||||
Assert.ThrowsException<FFMpegException>(() => FFMpeg.GetPixelFormat("yuv420pppUnknown"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
namespace FFMpegCore.Test.Resources;
|
||||
|
||||
public static class TestResources
|
||||
namespace FFMpegCore.Test.Resources
|
||||
{
|
||||
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";
|
||||
public static readonly string WebmVideo = "./Resources/input_3sec.webm";
|
||||
public static readonly string HdrVideo = "./Resources/input_hdr.mov";
|
||||
public static readonly string Mp4WithoutVideo = "./Resources/input_audio_only_10sec.mp4";
|
||||
public static readonly string Mp4WithoutAudio = "./Resources/input_video_only_3sec.mp4";
|
||||
public static readonly string RawAudio = "./Resources/audio.raw";
|
||||
public static readonly string Mp3Audio = "./Resources/audio.mp3";
|
||||
public static readonly string PngImage = "./Resources/cover.png";
|
||||
public static readonly string ImageCollection = "./Resources/images";
|
||||
public static readonly string SrtSubtitle = "./Resources/sample.srt";
|
||||
public static readonly string AiffAudio = "./Resources/sample3aiff.aiff";
|
||||
public static readonly string MkvVideo = "./Resources/sampleMKV.mkv";
|
||||
public static readonly string Wav24Bit = "./Resources/24_bit_fixed.WAV";
|
||||
public static readonly string Wav32Bit = "./Resources/32_bit_float.WAV";
|
||||
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";
|
||||
public static readonly string WebmVideo = "./Resources/input_3sec.webm";
|
||||
public static readonly string HdrVideo = "./Resources/input_hdr.mov";
|
||||
public static readonly string Mp4WithoutVideo = "./Resources/input_audio_only_10sec.mp4";
|
||||
public static readonly string Mp4WithoutAudio = "./Resources/input_video_only_3sec.mp4";
|
||||
public static readonly string RawAudio = "./Resources/audio.raw";
|
||||
public static readonly string Mp3Audio = "./Resources/audio.mp3";
|
||||
public static readonly string PngImage = "./Resources/cover.png";
|
||||
public static readonly string ImageCollection = "./Resources/images";
|
||||
public static readonly string SrtSubtitle = "./Resources/sample.srt";
|
||||
public static readonly string AiffAudio = "./Resources/sample3aiff.aiff";
|
||||
public static readonly string MkvVideo = "./Resources/sampleMKV.mkv";
|
||||
public static readonly string Wav24Bit = "./Resources/24_bit_fixed.WAV";
|
||||
public static readonly string Wav32Bit = "./Resources/32_bit_float.WAV";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,21 @@
|
|||
namespace FFMpegCore.Test;
|
||||
|
||||
public class TemporaryFile : IDisposable
|
||||
namespace FFMpegCore.Test
|
||||
{
|
||||
private readonly string _path;
|
||||
|
||||
public TemporaryFile(string filename)
|
||||
public class TemporaryFile : IDisposable
|
||||
{
|
||||
_path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}-{filename}");
|
||||
}
|
||||
private readonly string _path;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (File.Exists(_path))
|
||||
public TemporaryFile(string filename)
|
||||
{
|
||||
File.Delete(_path);
|
||||
_path = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}-{filename}");
|
||||
}
|
||||
|
||||
public static implicit operator string(TemporaryFile temporaryFile) => temporaryFile._path;
|
||||
public void Dispose()
|
||||
{
|
||||
if (File.Exists(_path))
|
||||
{
|
||||
File.Delete(_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator string(TemporaryFile temporaryFile)
|
||||
{
|
||||
return temporaryFile._path;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,251 +2,254 @@
|
|||
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
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static IEnumerable<IVideoFrame> CreateBitmaps(int count, PixelFormat fmt, int w, int h)
|
||||
internal static class BitmapSource
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static IEnumerable<IVideoFrame> CreateBitmaps(int count, PixelFormat fmt, int w, int h)
|
||||
{
|
||||
using (var frame = CreateVideoFrame(i, fmt, w, h, 0.025f, 0.025f * w * 0.03f))
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
yield return frame;
|
||||
using (var frame = CreateVideoFrame(i, fmt, w, h, 0.025f, 0.025f * w * 0.03f))
|
||||
{
|
||||
yield return frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<IVideoFrame> CreateBitmaps(int count, SKColorType fmt, int w, int h)
|
||||
{
|
||||
for (var i = 0; i < count; i++)
|
||||
public static IEnumerable<IVideoFrame> CreateBitmaps(int count, SKColorType fmt, int w, int h)
|
||||
{
|
||||
using (var frame = CreateVideoFrame(i, fmt, w, h, 0.025f, 0.025f * w * 0.03f))
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
yield return frame;
|
||||
using (var frame = CreateVideoFrame(i, fmt, w, h, 0.025f, 0.025f * w * 0.03f))
|
||||
{
|
||||
yield return frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fmt, int w, int h, float scaleNoise, float offset)
|
||||
{
|
||||
var bitmap = new Bitmap(w, h, fmt);
|
||||
|
||||
foreach (var (x, y, red, green, blue) in GenerateVideoFramePixels(index, w, h, scaleNoise, offset))
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static Extensions.System.Drawing.Common.BitmapVideoFrameWrapper CreateVideoFrame(int index, PixelFormat fmt, int w, int h, float scaleNoise, float offset)
|
||||
{
|
||||
var color = Color.FromArgb(red, blue, green);
|
||||
bitmap.SetPixel(x, y, color);
|
||||
}
|
||||
var bitmap = new Bitmap(w, h, fmt);
|
||||
|
||||
return new BitmapVideoFrameWrapper(bitmap);
|
||||
}
|
||||
|
||||
public static Extensions.SkiaSharp.BitmapVideoFrameWrapper CreateVideoFrame(int index, SKColorType fmt, int w, int h, float scaleNoise, float offset)
|
||||
{
|
||||
var bitmap = new SKBitmap(w, h, fmt, SKAlphaType.Opaque);
|
||||
|
||||
bitmap.Pixels = GenerateVideoFramePixels(index, w, h, scaleNoise, offset)
|
||||
.Select(args => new SKColor(args.red, args.blue, args.green))
|
||||
.ToArray();
|
||||
|
||||
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)
|
||||
{
|
||||
offset = offset * index;
|
||||
|
||||
for (var y = 0; y < h; y++)
|
||||
{
|
||||
for (var x = 0; x < w; x++)
|
||||
foreach (var (x, y, red, green, blue) in GenerateVideoFramePixels(index, w, h, scaleNoise, offset))
|
||||
{
|
||||
var xf = x / (float)w;
|
||||
var yf = y / (float)h;
|
||||
var nx = x * scaleNoise + offset;
|
||||
var ny = y * scaleNoise + offset;
|
||||
|
||||
var value = (byte)((Perlin.Noise(nx, ny) + 1.0f) / 2.0f * 255);
|
||||
|
||||
yield return (x, y, (byte)(value * xf), (byte)(value * yf), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Perlin noise generator for Unity
|
||||
// Keijiro Takahashi, 2013, 2015
|
||||
// https://github.com/keijiro/PerlinNoise
|
||||
//
|
||||
// Based on the original implementation by Ken Perlin
|
||||
// http://mrl.nyu.edu/~perlin/noise/
|
||||
//
|
||||
private static class Perlin
|
||||
{
|
||||
#region Noise functions
|
||||
|
||||
public static float Noise(float x)
|
||||
{
|
||||
var X = (int)MathF.Floor(x) & 0xff;
|
||||
x -= MathF.Floor(x);
|
||||
var u = Fade(x);
|
||||
return Lerp(u, Grad(perm[X], x), Grad(perm[X + 1], x - 1)) * 2;
|
||||
}
|
||||
|
||||
public static float Noise(float x, float y)
|
||||
{
|
||||
var X = (int)MathF.Floor(x) & 0xff;
|
||||
var Y = (int)MathF.Floor(y) & 0xff;
|
||||
x -= MathF.Floor(x);
|
||||
y -= MathF.Floor(y);
|
||||
var u = Fade(x);
|
||||
var v = Fade(y);
|
||||
var A = (perm[X] + Y) & 0xff;
|
||||
var B = (perm[X + 1] + Y) & 0xff;
|
||||
return Lerp(v, Lerp(u, Grad(perm[A], x, y), Grad(perm[B], x - 1, y)),
|
||||
Lerp(u, Grad(perm[A + 1], x, y - 1), Grad(perm[B + 1], x - 1, y - 1)));
|
||||
}
|
||||
|
||||
public static float Noise(Vector2 coord)
|
||||
{
|
||||
return Noise(coord.X, coord.Y);
|
||||
}
|
||||
|
||||
public static float Noise(float x, float y, float z)
|
||||
{
|
||||
var X = (int)MathF.Floor(x) & 0xff;
|
||||
var Y = (int)MathF.Floor(y) & 0xff;
|
||||
var Z = (int)MathF.Floor(z) & 0xff;
|
||||
x -= MathF.Floor(x);
|
||||
y -= MathF.Floor(y);
|
||||
z -= MathF.Floor(z);
|
||||
var u = Fade(x);
|
||||
var v = Fade(y);
|
||||
var w = Fade(z);
|
||||
var A = (perm[X] + Y) & 0xff;
|
||||
var B = (perm[X + 1] + Y) & 0xff;
|
||||
var AA = (perm[A] + Z) & 0xff;
|
||||
var BA = (perm[B] + Z) & 0xff;
|
||||
var AB = (perm[A + 1] + Z) & 0xff;
|
||||
var BB = (perm[B + 1] + Z) & 0xff;
|
||||
return Lerp(w, Lerp(v, Lerp(u, Grad(perm[AA], x, y, z), Grad(perm[BA], x - 1, y, z)),
|
||||
Lerp(u, Grad(perm[AB], x, y - 1, z), Grad(perm[BB], x - 1, y - 1, z))),
|
||||
Lerp(v, Lerp(u, Grad(perm[AA + 1], x, y, z - 1), Grad(perm[BA + 1], x - 1, y, z - 1)),
|
||||
Lerp(u, Grad(perm[AB + 1], x, y - 1, z - 1), Grad(perm[BB + 1], x - 1, y - 1, z - 1))));
|
||||
}
|
||||
|
||||
public static float Noise(Vector3 coord)
|
||||
{
|
||||
return Noise(coord.X, coord.Y, coord.Z);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region fBm functions
|
||||
|
||||
public static float Fbm(float x, int octave)
|
||||
{
|
||||
var f = 0.0f;
|
||||
var w = 0.5f;
|
||||
for (var i = 0; i < octave; i++)
|
||||
{
|
||||
f += w * Noise(x);
|
||||
x *= 2.0f;
|
||||
w *= 0.5f;
|
||||
var color = Color.FromArgb(red, blue, green);
|
||||
bitmap.SetPixel(x, y, color);
|
||||
}
|
||||
|
||||
return f;
|
||||
return new Extensions.System.Drawing.Common.BitmapVideoFrameWrapper(bitmap);
|
||||
}
|
||||
|
||||
public static float Fbm(Vector2 coord, int octave)
|
||||
public static Extensions.SkiaSharp.BitmapVideoFrameWrapper CreateVideoFrame(int index, SKColorType fmt, int w, int h, float scaleNoise, float offset)
|
||||
{
|
||||
var f = 0.0f;
|
||||
var w = 0.5f;
|
||||
for (var i = 0; i < octave; i++)
|
||||
var bitmap = new SKBitmap(w, h, fmt, SKAlphaType.Opaque);
|
||||
|
||||
bitmap.Pixels = GenerateVideoFramePixels(index, w, h, scaleNoise, offset)
|
||||
.Select(args => new SKColor(args.red, args.blue, args.green))
|
||||
.ToArray();
|
||||
|
||||
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)
|
||||
{
|
||||
offset = offset * index;
|
||||
|
||||
for (var y = 0; y < h; y++)
|
||||
{
|
||||
f += w * Noise(coord);
|
||||
coord *= 2.0f;
|
||||
w *= 0.5f;
|
||||
for (var x = 0; x < w; x++)
|
||||
{
|
||||
var xf = x / (float)w;
|
||||
var yf = y / (float)h;
|
||||
var nx = x * scaleNoise + offset;
|
||||
var ny = y * scaleNoise + offset;
|
||||
|
||||
var value = (byte)((Perlin.Noise(nx, ny) + 1.0f) / 2.0f * 255);
|
||||
|
||||
yield return ((x, y, (byte)(value * xf), (byte)(value * yf), value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Perlin noise generator for Unity
|
||||
// Keijiro Takahashi, 2013, 2015
|
||||
// https://github.com/keijiro/PerlinNoise
|
||||
//
|
||||
// Based on the original implementation by Ken Perlin
|
||||
// http://mrl.nyu.edu/~perlin/noise/
|
||||
//
|
||||
private static class Perlin
|
||||
{
|
||||
#region Noise functions
|
||||
|
||||
public static float Noise(float x)
|
||||
{
|
||||
var X = (int)MathF.Floor(x) & 0xff;
|
||||
x -= MathF.Floor(x);
|
||||
var u = Fade(x);
|
||||
return Lerp(u, Grad(perm[X], x), Grad(perm[X + 1], x - 1)) * 2;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
public static float Fbm(float x, float y, int octave)
|
||||
{
|
||||
return Fbm(new Vector2(x, y), octave);
|
||||
}
|
||||
|
||||
public static float Fbm(Vector3 coord, int octave)
|
||||
{
|
||||
var f = 0.0f;
|
||||
var w = 0.5f;
|
||||
for (var i = 0; i < octave; i++)
|
||||
public static float Noise(float x, float y)
|
||||
{
|
||||
f += w * Noise(coord);
|
||||
coord *= 2.0f;
|
||||
w *= 0.5f;
|
||||
var X = (int)MathF.Floor(x) & 0xff;
|
||||
var Y = (int)MathF.Floor(y) & 0xff;
|
||||
x -= MathF.Floor(x);
|
||||
y -= MathF.Floor(y);
|
||||
var u = Fade(x);
|
||||
var v = Fade(y);
|
||||
var A = (perm[X] + Y) & 0xff;
|
||||
var B = (perm[X + 1] + Y) & 0xff;
|
||||
return Lerp(v, Lerp(u, Grad(perm[A], x, y), Grad(perm[B], x - 1, y)),
|
||||
Lerp(u, Grad(perm[A + 1], x, y - 1), Grad(perm[B + 1], x - 1, y - 1)));
|
||||
}
|
||||
|
||||
return f;
|
||||
public static float Noise(Vector2 coord)
|
||||
{
|
||||
return Noise(coord.X, coord.Y);
|
||||
}
|
||||
|
||||
public static float Noise(float x, float y, float z)
|
||||
{
|
||||
var X = (int)MathF.Floor(x) & 0xff;
|
||||
var Y = (int)MathF.Floor(y) & 0xff;
|
||||
var Z = (int)MathF.Floor(z) & 0xff;
|
||||
x -= MathF.Floor(x);
|
||||
y -= MathF.Floor(y);
|
||||
z -= MathF.Floor(z);
|
||||
var u = Fade(x);
|
||||
var v = Fade(y);
|
||||
var w = Fade(z);
|
||||
var A = (perm[X] + Y) & 0xff;
|
||||
var B = (perm[X + 1] + Y) & 0xff;
|
||||
var AA = (perm[A] + Z) & 0xff;
|
||||
var BA = (perm[B] + Z) & 0xff;
|
||||
var AB = (perm[A + 1] + Z) & 0xff;
|
||||
var BB = (perm[B + 1] + Z) & 0xff;
|
||||
return Lerp(w, Lerp(v, Lerp(u, Grad(perm[AA], x, y, z), Grad(perm[BA], x - 1, y, z)),
|
||||
Lerp(u, Grad(perm[AB], x, y - 1, z), Grad(perm[BB], x - 1, y - 1, z))),
|
||||
Lerp(v, Lerp(u, Grad(perm[AA + 1], x, y, z - 1), Grad(perm[BA + 1], x - 1, y, z - 1)),
|
||||
Lerp(u, Grad(perm[AB + 1], x, y - 1, z - 1), Grad(perm[BB + 1], x - 1, y - 1, z - 1))));
|
||||
}
|
||||
|
||||
public static float Noise(Vector3 coord)
|
||||
{
|
||||
return Noise(coord.X, coord.Y, coord.Z);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region fBm functions
|
||||
|
||||
public static float Fbm(float x, int octave)
|
||||
{
|
||||
var f = 0.0f;
|
||||
var w = 0.5f;
|
||||
for (var i = 0; i < octave; i++)
|
||||
{
|
||||
f += w * Noise(x);
|
||||
x *= 2.0f;
|
||||
w *= 0.5f;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
public static float Fbm(Vector2 coord, int octave)
|
||||
{
|
||||
var f = 0.0f;
|
||||
var w = 0.5f;
|
||||
for (var i = 0; i < octave; i++)
|
||||
{
|
||||
f += w * Noise(coord);
|
||||
coord *= 2.0f;
|
||||
w *= 0.5f;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
public static float Fbm(float x, float y, int octave)
|
||||
{
|
||||
return Fbm(new Vector2(x, y), octave);
|
||||
}
|
||||
|
||||
public static float Fbm(Vector3 coord, int octave)
|
||||
{
|
||||
var f = 0.0f;
|
||||
var w = 0.5f;
|
||||
for (var i = 0; i < octave; i++)
|
||||
{
|
||||
f += w * Noise(coord);
|
||||
coord *= 2.0f;
|
||||
w *= 0.5f;
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
public static float Fbm(float x, float y, float z, int octave)
|
||||
{
|
||||
return Fbm(new Vector3(x, y, z), octave);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private functions
|
||||
|
||||
private static float Fade(float t)
|
||||
{
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
private static float Lerp(float t, float a, float b)
|
||||
{
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
private static float Grad(int hash, float x)
|
||||
{
|
||||
return (hash & 1) == 0 ? x : -x;
|
||||
}
|
||||
|
||||
private static float Grad(int hash, float x, float y)
|
||||
{
|
||||
return ((hash & 1) == 0 ? x : -x) + ((hash & 2) == 0 ? y : -y);
|
||||
}
|
||||
|
||||
private static float Grad(int hash, float x, float y, float z)
|
||||
{
|
||||
var h = hash & 15;
|
||||
var u = h < 8 ? x : y;
|
||||
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
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static float Fbm(float x, float y, float z, int octave)
|
||||
{
|
||||
return Fbm(new Vector3(x, y, z), octave);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private functions
|
||||
|
||||
private static float Fade(float t)
|
||||
{
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
private static float Lerp(float t, float a, float b)
|
||||
{
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
private static float Grad(int hash, float x)
|
||||
{
|
||||
return (hash & 1) == 0 ? x : -x;
|
||||
}
|
||||
|
||||
private static float Grad(int hash, float x, float y)
|
||||
{
|
||||
return ((hash & 1) == 0 ? x : -x) + ((hash & 2) == 0 ? y : -y);
|
||||
}
|
||||
|
||||
private static float Grad(int hash, float x, float y, float z)
|
||||
{
|
||||
var h = hash & 15;
|
||||
var u = h < 8 ? x : y;
|
||||
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
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,23 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FFMpegCore.Test.Utilities;
|
||||
|
||||
public class WindowsOnlyTestMethod : TestMethodAttribute
|
||||
{
|
||||
public WindowsOnlyTestMethod([CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1)
|
||||
: base(callerFilePath, callerLineNumber)
|
||||
{
|
||||
}
|
||||
|
||||
public override async Task<TestResult[]> ExecuteAsync(ITestMethod testMethod)
|
||||
public override TestResult[] Execute(ITestMethod testMethod)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
var message = "Test not executed on other platforms than Windows";
|
||||
var message = $"Test not executed on other platforms than Windows";
|
||||
{
|
||||
return
|
||||
[
|
||||
return new[]
|
||||
{
|
||||
new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) }
|
||||
];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return await base.ExecuteAsync(testMethod);
|
||||
return base.Execute(testMethod);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,21 +1,22 @@
|
|||
namespace FFMpegCore.Extend;
|
||||
|
||||
internal static class KeyValuePairExtensions
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
/// <summary>
|
||||
/// Concat the two members of a <see cref="KeyValuePair{TKey,TValue}" />
|
||||
/// </summary>
|
||||
/// <param name="pair">Input object</param>
|
||||
/// <param name="enclose">
|
||||
/// If true encloses the value part between quotes if contains an space character. If false use the
|
||||
/// value unmodified
|
||||
/// </param>
|
||||
/// <returns>The formatted string</returns>
|
||||
public static string FormatArgumentPair(this KeyValuePair<string, string> pair, bool enclose)
|
||||
internal static class KeyValuePairExtensions
|
||||
{
|
||||
var key = pair.Key;
|
||||
var value = enclose ? StringExtensions.EncloseIfContainsSpace(pair.Value) : pair.Value;
|
||||
/// <summary>
|
||||
/// Concat the two members of a <see cref="KeyValuePair{TKey,TValue}" />
|
||||
/// </summary>
|
||||
/// <param name="pair">Input object</param>
|
||||
/// <param name="enclose">
|
||||
/// If true encloses the value part between quotes if contains an space character. If false use the
|
||||
/// value unmodified
|
||||
/// </param>
|
||||
/// <returns>The formatted string</returns>
|
||||
public static string FormatArgumentPair(this KeyValuePair<string, string> pair, bool enclose)
|
||||
{
|
||||
var key = pair.Key;
|
||||
var value = enclose ? StringExtensions.EncloseIfContainsSpace(pair.Value) : pair.Value;
|
||||
|
||||
return $"{key}={value}";
|
||||
return $"{key}={value}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Extend;
|
||||
|
||||
public class PcmAudioSampleWrapper : IAudioSample
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
//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.
|
||||
private readonly byte[] _sample;
|
||||
|
||||
public PcmAudioSampleWrapper(byte[] sample)
|
||||
public class PcmAudioSampleWrapper : IAudioSample
|
||||
{
|
||||
_sample = sample;
|
||||
}
|
||||
//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.
|
||||
private readonly byte[] _sample;
|
||||
|
||||
public void Serialize(Stream stream)
|
||||
{
|
||||
stream.Write(_sample, 0, _sample.Length);
|
||||
}
|
||||
public PcmAudioSampleWrapper(byte[] sample)
|
||||
{
|
||||
_sample = sample;
|
||||
}
|
||||
|
||||
public async Task SerializeAsync(Stream stream, CancellationToken token)
|
||||
{
|
||||
await stream.WriteAsync(_sample, 0, _sample.Length, token).ConfigureAwait(false);
|
||||
public void Serialize(Stream stream)
|
||||
{
|
||||
stream.Write(_sample, 0, _sample.Length);
|
||||
}
|
||||
|
||||
public async Task SerializeAsync(Stream stream, CancellationToken token)
|
||||
{
|
||||
await stream.WriteAsync(_sample, 0, _sample.Length, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,68 +1,69 @@
|
|||
using System.Text;
|
||||
|
||||
namespace FFMpegCore.Extend;
|
||||
|
||||
internal static class StringExtensions
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
private static Dictionary<char, string> CharactersSubstitution { get; } = new()
|
||||
internal static class StringExtensions
|
||||
{
|
||||
{ '\\', @"\\" },
|
||||
{ ':', @"\:" },
|
||||
{ '[', @"\[" },
|
||||
{ ']', @"\]" },
|
||||
{ '\'', @"'\\\''" }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Enclose string between quotes if contains an space character
|
||||
/// </summary>
|
||||
/// <param name="input">The input</param>
|
||||
/// <returns>The enclosed string</returns>
|
||||
public static string EncloseIfContainsSpace(string input)
|
||||
{
|
||||
return input.Contains(" ") ? $"'{input}'" : input;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enclose an string in quotes
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static string EncloseInQuotes(string input)
|
||||
{
|
||||
return $"'{input}'";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scape several characters in subtitle path used by FFmpeg
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is needed because internally FFmpeg use Libav Filters
|
||||
/// and the info send to it must be in an specific format
|
||||
/// </remarks>
|
||||
/// <param name="source"></param>
|
||||
/// <returns>Scaped path</returns>
|
||||
public static string ToFFmpegLibavfilterPath(string source)
|
||||
{
|
||||
return source.Replace(CharactersSubstitution);
|
||||
}
|
||||
|
||||
public static string Replace(this string str, Dictionary<char, string> replaceList)
|
||||
{
|
||||
var parsedString = new StringBuilder();
|
||||
|
||||
foreach (var l in str)
|
||||
private static Dictionary<char, string> CharactersSubstitution { get; } = new()
|
||||
{
|
||||
if (replaceList.ContainsKey(l))
|
||||
{
|
||||
parsedString.Append(replaceList[l]);
|
||||
}
|
||||
else
|
||||
{
|
||||
parsedString.Append(l);
|
||||
}
|
||||
{ '\\', @"\\" },
|
||||
{ ':', @"\:" },
|
||||
{ '[', @"\[" },
|
||||
{ ']', @"\]" },
|
||||
{ '\'', @"'\\\''" }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Enclose string between quotes if contains an space character
|
||||
/// </summary>
|
||||
/// <param name="input">The input</param>
|
||||
/// <returns>The enclosed string</returns>
|
||||
public static string EncloseIfContainsSpace(string input)
|
||||
{
|
||||
return input.Contains(" ") ? $"'{input}'" : input;
|
||||
}
|
||||
|
||||
return parsedString.ToString();
|
||||
/// <summary>
|
||||
/// Enclose an string in quotes
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
public static string EncloseInQuotes(string input)
|
||||
{
|
||||
return $"'{input}'";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scape several characters in subtitle path used by FFmpeg
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is needed because internally FFmpeg use Libav Filters
|
||||
/// and the info send to it must be in an specific format
|
||||
/// </remarks>
|
||||
/// <param name="source"></param>
|
||||
/// <returns>Scaped path</returns>
|
||||
public static string ToFFmpegLibavfilterPath(string source)
|
||||
{
|
||||
return source.Replace(CharactersSubstitution);
|
||||
}
|
||||
|
||||
public static string Replace(this string str, Dictionary<char, string> replaceList)
|
||||
{
|
||||
var parsedString = new StringBuilder();
|
||||
|
||||
foreach (var l in str)
|
||||
{
|
||||
if (replaceList.ContainsKey(l))
|
||||
{
|
||||
parsedString.Append(replaceList[l]);
|
||||
}
|
||||
else
|
||||
{
|
||||
parsedString.Append(l);
|
||||
}
|
||||
}
|
||||
|
||||
return parsedString.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
namespace FFMpegCore.Extend;
|
||||
|
||||
public static class UriExtensions
|
||||
namespace FFMpegCore.Extend
|
||||
{
|
||||
public static bool SaveStream(this Uri uri, string output)
|
||||
public static class UriExtensions
|
||||
{
|
||||
return FFMpeg.SaveM3U8Stream(uri, output);
|
||||
public static bool SaveStream(this Uri uri, string output)
|
||||
{
|
||||
return FFMpeg.SaveM3U8Stream(uri, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class AudibleEncryptionKeyArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly bool _aaxcMode;
|
||||
|
||||
private readonly string? _activationBytes;
|
||||
private readonly string? _iv;
|
||||
|
||||
private readonly string? _key;
|
||||
|
||||
public AudibleEncryptionKeyArgument(string activationBytes)
|
||||
public class AudibleEncryptionKeyArgument : IArgument
|
||||
{
|
||||
_activationBytes = activationBytes;
|
||||
private readonly bool _aaxcMode;
|
||||
|
||||
private readonly string? _key;
|
||||
private readonly string? _iv;
|
||||
|
||||
private readonly string? _activationBytes;
|
||||
|
||||
public AudibleEncryptionKeyArgument(string activationBytes)
|
||||
{
|
||||
_activationBytes = activationBytes;
|
||||
}
|
||||
|
||||
public AudibleEncryptionKeyArgument(string key, string iv)
|
||||
{
|
||||
_aaxcMode = true;
|
||||
|
||||
_key = key;
|
||||
_iv = iv;
|
||||
}
|
||||
|
||||
public string Text => _aaxcMode ? $"-audible_key {_key} -audible_iv {_iv}" : $"-activation_bytes {_activationBytes}";
|
||||
}
|
||||
|
||||
public AudibleEncryptionKeyArgument(string key, string iv)
|
||||
{
|
||||
_aaxcMode = true;
|
||||
|
||||
_key = key;
|
||||
_iv = iv;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
public readonly int Bitrate;
|
||||
public AudioBitrateArgument(AudioQuality value) : this((int)value) { }
|
||||
|
||||
public AudioBitrateArgument(int bitrate)
|
||||
/// <summary>
|
||||
/// Represents parameter of audio codec and it's quality
|
||||
/// </summary>
|
||||
public class AudioBitrateArgument : IArgument
|
||||
{
|
||||
Bitrate = bitrate;
|
||||
}
|
||||
public readonly int Bitrate;
|
||||
public AudioBitrateArgument(AudioQuality value) : this((int)value) { }
|
||||
public AudioBitrateArgument(int bitrate)
|
||||
{
|
||||
Bitrate = bitrate;
|
||||
}
|
||||
|
||||
public string Text => $"-b:a {Bitrate}k";
|
||||
public string Text => $"-b:a {Bitrate}k";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
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
|
||||
{
|
||||
public readonly string AudioCodec;
|
||||
|
||||
public AudioCodecArgument(Codec audioCodec)
|
||||
/// <summary>
|
||||
/// Represents parameter of audio codec and it's quality
|
||||
/// </summary>
|
||||
public class AudioCodecArgument : IArgument
|
||||
{
|
||||
if (audioCodec.Type != CodecType.Audio)
|
||||
public readonly string AudioCodec;
|
||||
|
||||
public AudioCodecArgument(Codec audioCodec)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{audioCodec.Name}\" is not an audio codec");
|
||||
if (audioCodec.Type != CodecType.Audio)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{audioCodec.Name}\" is not an audio codec");
|
||||
}
|
||||
|
||||
AudioCodec = audioCodec.Name;
|
||||
}
|
||||
|
||||
AudioCodec = audioCodec.Name;
|
||||
}
|
||||
public AudioCodecArgument(string audioCodec)
|
||||
{
|
||||
AudioCodec = audioCodec;
|
||||
}
|
||||
|
||||
public AudioCodecArgument(string audioCodec)
|
||||
{
|
||||
AudioCodec = audioCodec;
|
||||
public string Text => $"-c:a {AudioCodec.ToString().ToLowerInvariant()}";
|
||||
}
|
||||
|
||||
public string Text => $"-c:a {AudioCodec.ToLowerInvariant()}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,97 +1,71 @@
|
|||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class AudioFiltersArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly AudioFilterOptions Options;
|
||||
|
||||
public AudioFiltersArgument(AudioFilterOptions options)
|
||||
public class AudioFiltersArgument : IArgument
|
||||
{
|
||||
Options = options;
|
||||
}
|
||||
public readonly AudioFilterOptions Options;
|
||||
|
||||
public string Text => GetText();
|
||||
|
||||
private string GetText()
|
||||
{
|
||||
if (!Options.Arguments.Any())
|
||||
public AudioFiltersArgument(AudioFilterOptions options)
|
||||
{
|
||||
throw new FFMpegArgumentException("No audio-filter arguments provided");
|
||||
Options = options;
|
||||
}
|
||||
|
||||
var arguments = Options.Arguments
|
||||
.Where(arg => !string.IsNullOrEmpty(arg.Value))
|
||||
.Select(arg =>
|
||||
public string Text => GetText();
|
||||
|
||||
private string GetText()
|
||||
{
|
||||
if (!Options.Arguments.Any())
|
||||
{
|
||||
var escapedValue = arg.Value.Replace(",", "\\,");
|
||||
return string.IsNullOrEmpty(arg.Key) ? escapedValue : $"{arg.Key}={escapedValue}";
|
||||
});
|
||||
throw new FFMpegArgumentException("No audio-filter arguments provided");
|
||||
}
|
||||
|
||||
return $"-af \"{string.Join(", ", arguments)}\"";
|
||||
}
|
||||
}
|
||||
var arguments = Options.Arguments
|
||||
.Where(arg => !string.IsNullOrEmpty(arg.Value))
|
||||
.Select(arg =>
|
||||
{
|
||||
var escapedValue = arg.Value.Replace(",", "\\,");
|
||||
return string.IsNullOrEmpty(arg.Key) ? escapedValue : $"{arg.Key}={escapedValue}";
|
||||
});
|
||||
|
||||
public interface IAudioFilterArgument
|
||||
{
|
||||
string Key { get; }
|
||||
string Value { get; }
|
||||
}
|
||||
|
||||
public class AudioFilterOptions
|
||||
{
|
||||
public List<IAudioFilterArgument> Arguments { get; } = new();
|
||||
|
||||
public AudioFilterOptions Pan(string channelLayout, params string[] outputDefinitions)
|
||||
{
|
||||
return WithArgument(new PanArgument(channelLayout, outputDefinitions));
|
||||
return $"-af \"{string.Join(", ", arguments)}\"";
|
||||
}
|
||||
}
|
||||
|
||||
public AudioFilterOptions Pan(int channels, params string[] outputDefinitions)
|
||||
public interface IAudioFilterArgument
|
||||
{
|
||||
return WithArgument(new PanArgument(channels, outputDefinitions));
|
||||
string Key { get; }
|
||||
string Value { get; }
|
||||
}
|
||||
|
||||
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)
|
||||
public class AudioFilterOptions
|
||||
{
|
||||
return WithArgument(new DynamicNormalizerArgument(frameLength, filterWindow,
|
||||
public List<IAudioFilterArgument> Arguments { get; } = new();
|
||||
|
||||
public AudioFilterOptions Pan(string channelLayout, params string[] outputDefinitions) => WithArgument(new PanArgument(channelLayout, outputDefinitions));
|
||||
public AudioFilterOptions Pan(int channels, params string[] outputDefinitions) => WithArgument(new PanArgument(channels, outputDefinitions));
|
||||
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) => 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) => WithArgument(new HighPassFilterArgument(frequency, poles, width_type, width, mix, channels, normalize, transform, precision, blocksize));
|
||||
public AudioFilterOptions LowPass(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707,
|
||||
double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto",
|
||||
int? blocksize = null) => WithArgument(new LowPassFilterArgument(frequency, poles, width_type, width, mix, channels, normalize, transform, precision, blocksize));
|
||||
public AudioFilterOptions AudioGate(double level_in = 1, string mode = "downward", double range = 0.06125, double threshold = 0.125,
|
||||
int ratio = 2, double attack = 20, double release = 250, int makeup = 1, double knee = 2.828427125, string detection = "rms",
|
||||
string link = "average") => WithArgument(new AudioGateArgument(level_in, mode, range, threshold, ratio, attack, release, makeup, knee, detection, link));
|
||||
public AudioFilterOptions SilenceDetect(string noise_type = "db", double noise = 60, double duration = 2,
|
||||
bool mono = false) => WithArgument(new SilenceDetectArgument(noise_type, noise, duration, mono));
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
private AudioFilterOptions WithArgument(IAudioFilterArgument argument)
|
||||
{
|
||||
Arguments.Add(argument);
|
||||
return this;
|
||||
private AudioFilterOptions WithArgument(IAudioFilterArgument argument)
|
||||
{
|
||||
Arguments.Add(argument);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,115 +1,98 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class AudioGateArgument : IAudioFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
/// <summary>
|
||||
/// Audio Gate. <see href="https://ffmpeg.org/ffmpeg-filters.html#agate" />
|
||||
/// </summary>
|
||||
/// <param name="levelIn">Set input level before filtering. Default is 1. Allowed range is from 0.015625 to 64.</param>
|
||||
/// <param name="mode">
|
||||
/// Set the mode of operation. Can be upward or downward. Default is downward. If set to upward mode, higher parts of signal
|
||||
/// will be amplified, expanding dynamic range in upward direction. Otherwise, in case of downward lower parts of signal will be reduced.
|
||||
/// </param>
|
||||
/// <param name="range">
|
||||
/// Set the level of gain reduction when the signal is below the threshold. Default is 0.06125. Allowed range is from 0 to
|
||||
/// 1. Setting this to 0 disables reduction and then filter behaves like expander.
|
||||
/// </param>
|
||||
/// <param name="threshold">If a signal rises above this level the gain reduction is released. Default is 0.125. Allowed range is from 0 to 1.</param>
|
||||
/// <param name="ratio">Set a ratio by which the signal is reduced. Default is 2. Allowed range is from 1 to 9000.</param>
|
||||
/// <param name="attack">
|
||||
/// Amount of milliseconds the signal has to rise above the threshold before gain reduction stops. Default is 20
|
||||
/// milliseconds. Allowed range is from 0.01 to 9000.
|
||||
/// </param>
|
||||
/// <param name="release">
|
||||
/// Amount of milliseconds the signal has to fall below the threshold before the reduction is increased again. Default is
|
||||
/// 250 milliseconds. Allowed range is from 0.01 to 9000.
|
||||
/// </param>
|
||||
/// <param name="makeup">Set amount of amplification of signal after processing. Default is 1. Allowed range is from 1 to 64.</param>
|
||||
/// <param name="knee">
|
||||
/// Curve the sharp knee around the threshold to enter gain reduction more softly. Default is 2.828427125. Allowed range is
|
||||
/// from 1 to 8.
|
||||
/// </param>
|
||||
/// <param name="detection">Choose if exact signal should be taken for detection or an RMS like one. Default is rms. Can be peak or rms.</param>
|
||||
/// <param name="link">
|
||||
/// Choose if the average level between all channels or the louder channel affects the reduction. Default is average. Can be
|
||||
/// average or maximum.
|
||||
/// </param>
|
||||
public AudioGateArgument(double levelIn = 1, string mode = "downward", double range = 0.06125, double threshold = 0.125, int ratio = 2,
|
||||
double attack = 20, double release = 250, int makeup = 1, double knee = 2.828427125, string detection = "rms", string link = "average")
|
||||
public class AudioGateArgument : IAudioFilterArgument
|
||||
{
|
||||
if (levelIn is < 0.015625 or > 64)
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
/// <summary>
|
||||
/// Audio Gate. <see href="https://ffmpeg.org/ffmpeg-filters.html#agate"/>
|
||||
/// </summary>
|
||||
/// <param name="levelIn">Set input level before filtering. Default is 1. Allowed range is from 0.015625 to 64.</param>
|
||||
/// <param name="mode">Set the mode of operation. Can be upward or downward. Default is downward. If set to upward mode, higher parts of signal will be amplified, expanding dynamic range in upward direction. Otherwise, in case of downward lower parts of signal will be reduced.</param>
|
||||
/// <param name="range">Set the level of gain reduction when the signal is below the threshold. Default is 0.06125. Allowed range is from 0 to 1. Setting this to 0 disables reduction and then filter behaves like expander.</param>
|
||||
/// <param name="threshold">If a signal rises above this level the gain reduction is released. Default is 0.125. Allowed range is from 0 to 1.</param>
|
||||
/// <param name="ratio">Set a ratio by which the signal is reduced. Default is 2. Allowed range is from 1 to 9000.</param>
|
||||
/// <param name="attack">Amount of milliseconds the signal has to rise above the threshold before gain reduction stops. Default is 20 milliseconds. Allowed range is from 0.01 to 9000.</param>
|
||||
/// <param name="release">Amount of milliseconds the signal has to fall below the threshold before the reduction is increased again. Default is 250 milliseconds. Allowed range is from 0.01 to 9000.</param>
|
||||
/// <param name="makeup">Set amount of amplification of signal after processing. Default is 1. Allowed range is from 1 to 64.</param>
|
||||
/// <param name="knee">Curve the sharp knee around the threshold to enter gain reduction more softly. Default is 2.828427125. Allowed range is from 1 to 8.</param>
|
||||
/// <param name="detection">Choose if exact signal should be taken for detection or an RMS like one. Default is rms. Can be peak or rms.</param>
|
||||
/// <param name="link">Choose if the average level between all channels or the louder channel affects the reduction. Default is average. Can be average or maximum.</param>
|
||||
public AudioGateArgument(double levelIn = 1, string mode = "downward", double range = 0.06125, double threshold = 0.125, int ratio = 2,
|
||||
double attack = 20, double release = 250, int makeup = 1, double knee = 2.828427125, string detection = "rms", string link = "average")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(levelIn), "Level in must be between 0.015625 to 64");
|
||||
if (levelIn is < 0.015625 or > 64)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(levelIn), "Level in must be between 0.015625 to 64");
|
||||
}
|
||||
|
||||
if (mode != "upward" && mode != "downward")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(mode), "Mode must be either upward or downward");
|
||||
}
|
||||
|
||||
if (range is <= 0 or > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(range));
|
||||
}
|
||||
|
||||
if (threshold is < 0 or > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(threshold), "Threshold must be between 0 and 1");
|
||||
}
|
||||
|
||||
if (ratio is < 1 or > 9000)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(ratio), "Ratio must be between 1 and 9000");
|
||||
}
|
||||
|
||||
if (attack is < 0.01 or > 9000)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(attack), "Attack must be between 0.01 and 9000");
|
||||
}
|
||||
|
||||
if (release is < 0.01 or > 9000)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(release), "Release must be between 0.01 and 9000");
|
||||
}
|
||||
|
||||
if (makeup is < 1 or > 64)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(makeup), "Makeup Gain must be between 1 and 64");
|
||||
}
|
||||
|
||||
if (knee is < 1 or > 64)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(makeup), "Knee must be between 1 and 8");
|
||||
}
|
||||
|
||||
if (detection != "peak" && detection != "rms")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(detection), "Detection must be either peak or rms");
|
||||
}
|
||||
|
||||
if (link != "average" && link != "maximum")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(link), "Link must be either average or maximum");
|
||||
}
|
||||
|
||||
_arguments.Add("level_in", levelIn.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("mode", mode);
|
||||
_arguments.Add("range", range.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("threshold", threshold.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("ratio", ratio.ToString());
|
||||
_arguments.Add("attack", attack.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("release", release.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("makeup", makeup.ToString());
|
||||
_arguments.Add("knee", knee.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("detection", detection);
|
||||
_arguments.Add("link", link);
|
||||
}
|
||||
|
||||
if (mode != "upward" && mode != "downward")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(mode), "Mode must be either upward or downward");
|
||||
}
|
||||
public string Key { get; } = "agate";
|
||||
|
||||
if (range is <= 0 or > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(range));
|
||||
}
|
||||
|
||||
if (threshold is < 0 or > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(threshold), "Threshold must be between 0 and 1");
|
||||
}
|
||||
|
||||
if (ratio is < 1 or > 9000)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(ratio), "Ratio must be between 1 and 9000");
|
||||
}
|
||||
|
||||
if (attack is < 0.01 or > 9000)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(attack), "Attack must be between 0.01 and 9000");
|
||||
}
|
||||
|
||||
if (release is < 0.01 or > 9000)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(release), "Release must be between 0.01 and 9000");
|
||||
}
|
||||
|
||||
if (makeup is < 1 or > 64)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(makeup), "Makeup Gain must be between 1 and 64");
|
||||
}
|
||||
|
||||
if (knee is < 1 or > 64)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(makeup), "Knee must be between 1 and 8");
|
||||
}
|
||||
|
||||
if (detection != "peak" && detection != "rms")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(detection), "Detection must be either peak or rms");
|
||||
}
|
||||
|
||||
if (link != "average" && link != "maximum")
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(link), "Link must be either average or maximum");
|
||||
}
|
||||
|
||||
_arguments.Add("level_in", levelIn.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("mode", mode);
|
||||
_arguments.Add("range", range.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("threshold", threshold.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("ratio", ratio.ToString());
|
||||
_arguments.Add("attack", attack.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("release", release.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("makeup", makeup.ToString());
|
||||
_arguments.Add("knee", knee.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("detection", detection);
|
||||
_arguments.Add("link", link);
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
public readonly int SamplingRate;
|
||||
|
||||
public AudioSamplingRateArgument(int samplingRate = 48000)
|
||||
/// <summary>
|
||||
/// Audio sampling rate argument. Defaults to 48000 (Hz)
|
||||
/// </summary>
|
||||
public class AudioSamplingRateArgument : IArgument
|
||||
{
|
||||
SamplingRate = samplingRate;
|
||||
}
|
||||
public readonly int SamplingRate;
|
||||
public AudioSamplingRateArgument(int samplingRate = 48000)
|
||||
{
|
||||
SamplingRate = samplingRate;
|
||||
}
|
||||
|
||||
public string Text => $"-ar {SamplingRate}";
|
||||
public string Text => $"-ar {SamplingRate}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents parameter of bitstream filter
|
||||
/// </summary>
|
||||
public class BitStreamFilterArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly Channel Channel;
|
||||
public readonly Filter Filter;
|
||||
|
||||
public BitStreamFilterArgument(Channel channel, Filter filter)
|
||||
/// <summary>
|
||||
/// Represents parameter of bitstream filter
|
||||
/// </summary>
|
||||
public class BitStreamFilterArgument : IArgument
|
||||
{
|
||||
Channel = channel;
|
||||
Filter = filter;
|
||||
public readonly Channel Channel;
|
||||
public readonly Filter Filter;
|
||||
|
||||
public BitStreamFilterArgument(Channel channel, Filter filter)
|
||||
{
|
||||
Channel = channel;
|
||||
Filter = filter;
|
||||
}
|
||||
|
||||
public string Text => Channel switch
|
||||
{
|
||||
Channel.Audio => $"-bsf:a {Filter.ToString().ToLowerInvariant()}",
|
||||
Channel.Video => $"-bsf:v {Filter.ToString().ToLowerInvariant()}",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
public string Text => Channel switch
|
||||
{
|
||||
Channel.Audio => $"-bsf:a {Filter.ToString().ToLowerInvariant()}",
|
||||
Channel.Video => $"-bsf:v {Filter.ToString().ToLowerInvariant()}",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class BlackDetectArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public BlackDetectArgument(double minimumDuration = 2.0, double pictureBlackRatioThreshold = 0.98, double pixelBlackThreshold = 0.1)
|
||||
public class BlackDetectArgument : IVideoFilterArgument
|
||||
{
|
||||
Value = $"d={minimumDuration}:pic_th={pictureBlackRatioThreshold}:pix_th={pixelBlackThreshold}";
|
||||
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
|
||||
{
|
||||
public BlackFrameArgument(int amount = 98, int threshold = 32)
|
||||
internal class BlackFrameArgument : IVideoFilterArgument
|
||||
{
|
||||
Value = $"amount={amount}:threshold={threshold}";
|
||||
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;
|
||||
|
||||
public ConcatArgument(IEnumerable<string> values)
|
||||
/// <summary>
|
||||
/// Represents parameter of concat argument
|
||||
/// Used for creating video from multiple images or videos
|
||||
/// </summary>
|
||||
public class ConcatArgument : IInputArgument
|
||||
{
|
||||
Values = values;
|
||||
public readonly IEnumerable<string> Values;
|
||||
public ConcatArgument(IEnumerable<string> values)
|
||||
{
|
||||
Values = values;
|
||||
}
|
||||
|
||||
public void Pre() { }
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() { }
|
||||
|
||||
public string Text => $"-i \"concat:{string.Join(@"|", Values)}\"";
|
||||
}
|
||||
|
||||
public void Pre() { }
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Post() { }
|
||||
|
||||
public string Text => $"-i \"concat:{string.Join(@"|", Values)}\"";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Constant Rate Factor (CRF) argument
|
||||
/// </summary>
|
||||
public class ConstantRateFactorArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly int Crf;
|
||||
|
||||
public ConstantRateFactorArgument(int crf)
|
||||
/// <summary>
|
||||
/// Constant Rate Factor (CRF) argument
|
||||
/// </summary>
|
||||
public class ConstantRateFactorArgument : IArgument
|
||||
{
|
||||
if (crf < 0 || crf > 63)
|
||||
public readonly int Crf;
|
||||
|
||||
public ConstantRateFactorArgument(int crf)
|
||||
{
|
||||
throw new ArgumentException("Argument is outside range (0 - 63)", nameof(crf));
|
||||
if (crf < 0 || crf > 63)
|
||||
{
|
||||
throw new ArgumentException("Argument is outside range (0 - 63)", nameof(crf));
|
||||
}
|
||||
|
||||
Crf = crf;
|
||||
}
|
||||
|
||||
Crf = crf;
|
||||
public string Text => $"-crf {Crf}";
|
||||
}
|
||||
|
||||
public string Text => $"-crf {Crf}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
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
|
||||
{
|
||||
public readonly Channel Channel;
|
||||
|
||||
public CopyArgument(Channel channel = Channel.Both)
|
||||
/// <summary>
|
||||
/// Represents parameter of copy parameter
|
||||
/// Defines if channel (audio, video or both) should be copied to output file
|
||||
/// </summary>
|
||||
public class CopyArgument : IArgument
|
||||
{
|
||||
Channel = channel;
|
||||
public readonly Channel Channel;
|
||||
public CopyArgument(Channel channel = Channel.Both)
|
||||
{
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
public string Text => Channel switch
|
||||
{
|
||||
Channel.Both => "-c:a copy -c:v copy",
|
||||
_ => $"-c{Channel.StreamType()} copy"
|
||||
};
|
||||
}
|
||||
|
||||
public string Text => Channel switch
|
||||
{
|
||||
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,21 +1,22 @@
|
|||
using System.Drawing;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class CropArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly int Left;
|
||||
public readonly Size? Size;
|
||||
public readonly int Top;
|
||||
|
||||
public CropArgument(Size? size, int top, int left)
|
||||
public class CropArgument : IArgument
|
||||
{
|
||||
Size = size;
|
||||
Top = top;
|
||||
Left = left;
|
||||
public readonly Size? Size;
|
||||
public readonly int Top;
|
||||
public readonly int Left;
|
||||
|
||||
public CropArgument(Size? size, int top, int left)
|
||||
{
|
||||
Size = size;
|
||||
Top = top;
|
||||
Left = left;
|
||||
}
|
||||
|
||||
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}";
|
||||
}
|
||||
|
||||
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,13 +1,14 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class CustomArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly string Argument;
|
||||
|
||||
public CustomArgument(string argument)
|
||||
public class CustomArgument : IArgument
|
||||
{
|
||||
Argument = argument;
|
||||
}
|
||||
public readonly string Argument;
|
||||
|
||||
public string Text => Argument ?? string.Empty;
|
||||
public CustomArgument(string argument)
|
||||
{
|
||||
Argument = argument;
|
||||
}
|
||||
|
||||
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");
|
||||
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
|
||||
/// Represents parameter of concat argument
|
||||
/// Used for creating video from multiple images or videos
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
private string Escape(string value)
|
||||
public class DemuxConcatArgument : IInputArgument
|
||||
{
|
||||
return value.Replace("'", @"'\''");
|
||||
public readonly IEnumerable<string> Values;
|
||||
public DemuxConcatArgument(IEnumerable<string> values)
|
||||
{
|
||||
Values = values.Select(value => $"file '{Escape(value)}'");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Thanks slhck
|
||||
/// https://superuser.com/a/787651/1089628
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
private string Escape(string value) => value.Replace("'", @"'\''");
|
||||
|
||||
private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"concat_{Guid.NewGuid()}.txt");
|
||||
|
||||
public void Pre() => File.WriteAllLines(_tempFileName, Values);
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() => File.Delete(_tempFileName);
|
||||
|
||||
public string Text => $"-f concat -safe 0 -i \"{_tempFileName}\"";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents cpu speed parameter
|
||||
/// </summary>
|
||||
public class DisableChannelArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly Channel Channel;
|
||||
|
||||
public DisableChannelArgument(Channel channel)
|
||||
/// <summary>
|
||||
/// Represents cpu speed parameter
|
||||
/// </summary>
|
||||
public class DisableChannelArgument : IArgument
|
||||
{
|
||||
if (channel == Channel.Both)
|
||||
public readonly Channel Channel;
|
||||
|
||||
public DisableChannelArgument(Channel channel)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Conversion, "Cannot disable both channels");
|
||||
if (channel == Channel.Both)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Conversion, "Cannot disable both channels");
|
||||
}
|
||||
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
Channel = channel;
|
||||
public string Text => Channel switch
|
||||
{
|
||||
Channel.Video => "-vn",
|
||||
Channel.Audio => "-an",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
public string Text => Channel switch
|
||||
{
|
||||
Channel.Video => "-vn",
|
||||
Channel.Audio => "-an",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,59 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Drawtext video filter argument
|
||||
/// </summary>
|
||||
public class DrawTextArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly DrawTextOptions Options;
|
||||
|
||||
public DrawTextArgument(DrawTextOptions options)
|
||||
/// <summary>
|
||||
/// Drawtext video filter argument
|
||||
/// </summary>
|
||||
public class DrawTextArgument : IVideoFilterArgument
|
||||
{
|
||||
Options = options;
|
||||
public readonly DrawTextOptions Options;
|
||||
|
||||
public DrawTextArgument(DrawTextOptions options)
|
||||
{
|
||||
Options = options;
|
||||
}
|
||||
|
||||
public string Key { get; } = "drawtext";
|
||||
public string Value => Options.TextInternal;
|
||||
}
|
||||
|
||||
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)
|
||||
public class DrawTextOptions
|
||||
{
|
||||
Text = text;
|
||||
Font = font;
|
||||
Parameters = parameters.ToList();
|
||||
}
|
||||
public readonly string Text;
|
||||
public readonly string Font;
|
||||
public readonly List<(string key, string value)> Parameters;
|
||||
|
||||
internal string TextInternal => string.Join(":", new[] { ("text", Text), ("fontfile", Font) }.Concat(Parameters).Select(FormatArgumentPair));
|
||||
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);
|
||||
}
|
||||
|
||||
public static DrawTextOptions Create(string text, string font)
|
||||
{
|
||||
return new DrawTextOptions(text, font, new List<(string, string)>());
|
||||
}
|
||||
internal string TextInternal => string.Join(":", new[] { ("text", Text), ("fontfile", Font) }.Concat(Parameters).Select(FormatArgumentPair));
|
||||
|
||||
public static DrawTextOptions Create(string text, string font, params (string key, string value)[] parameters)
|
||||
{
|
||||
return new DrawTextOptions(text, font, parameters);
|
||||
}
|
||||
private static string FormatArgumentPair((string key, string value) pair)
|
||||
{
|
||||
return $"{pair.key}={EncloseIfContainsSpace(pair.value)}";
|
||||
}
|
||||
|
||||
private static string FormatArgumentPair((string key, string value) pair)
|
||||
{
|
||||
return $"{pair.key}={EncloseIfContainsSpace(pair.value)}";
|
||||
}
|
||||
private static string EncloseIfContainsSpace(string input)
|
||||
{
|
||||
return input.Contains(" ") ? $"'{input}'" : input;
|
||||
}
|
||||
|
||||
private static string EncloseIfContainsSpace(string input)
|
||||
{
|
||||
return input.Contains(" ") ? $"'{input}'" : input;
|
||||
}
|
||||
private DrawTextOptions(string text, string font, IEnumerable<(string, string)> parameters)
|
||||
{
|
||||
Text = text;
|
||||
Font = font;
|
||||
Parameters = parameters.ToList();
|
||||
}
|
||||
|
||||
public DrawTextOptions WithParameter(string key, string value)
|
||||
{
|
||||
Parameters.Add((key, value));
|
||||
return this;
|
||||
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
|
||||
{
|
||||
public readonly TimeSpan? Duration;
|
||||
|
||||
public DurationArgument(TimeSpan? duration)
|
||||
/// <summary>
|
||||
/// Represents duration parameter
|
||||
/// </summary>
|
||||
public class DurationArgument : IArgument
|
||||
{
|
||||
Duration = duration;
|
||||
}
|
||||
public readonly TimeSpan? Duration;
|
||||
public DurationArgument(TimeSpan? duration)
|
||||
{
|
||||
Duration = duration;
|
||||
}
|
||||
|
||||
public string Text => !Duration.HasValue ? string.Empty : $"-t {Duration.Value}";
|
||||
public string Text => !Duration.HasValue ? string.Empty : $"-t {Duration.Value}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,73 +1,73 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class DynamicNormalizerArgument : IAudioFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="targetPeak">Set the target peak value. The default value is 0.95</param>
|
||||
/// <param name="gainFactor">Set the maximum gain factor. In range from 1.0 to 100.0. Default is 10.0.</param>
|
||||
/// <param name="targetRms">Set the target RMS. In range from 0.0 to 1.0. Default to 0.0 (disabled)</param>
|
||||
/// <param name="channelCoupling">Enable channels coupling. By default is enabled.</param>
|
||||
/// <param name="enableDcBiasCorrection">Enable DC bias correction. By default is disabled.</param>
|
||||
/// <param name="enableAlternativeBoundary">Enable alternative boundary mode. By default is disabled.</param>
|
||||
/// <param name="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 class DynamicNormalizerArgument : IAudioFilterArgument
|
||||
{
|
||||
if (frameLength < 10 || frameLength > 8000)
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <param name="targetPeak">Set the target peak value. The default value is 0.95</param>
|
||||
/// <param name="gainFactor">Set the maximum gain factor. In range from 1.0 to 100.0. Default is 10.0.</param>
|
||||
/// <param name="targetRms">Set the target RMS. In range from 0.0 to 1.0. Default to 0.0 (disabled)</param>
|
||||
/// <param name="channelCoupling">Enable channels coupling. By default is enabled.</param>
|
||||
/// <param name="enableDcBiasCorrection">Enable DC bias correction. By default is disabled.</param>
|
||||
/// <param name="enableAlternativeBoundary">Enable alternative boundary mode. By default is disabled.</param>
|
||||
/// <param name="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)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(frameLength), "Frame length must be between 10 to 8000");
|
||||
if (frameLength < 10 || frameLength > 8000)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(frameLength), "Frame length must be between 10 to 8000");
|
||||
}
|
||||
|
||||
if (filterWindow < 3 || filterWindow > 31)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be between 3 to 31");
|
||||
}
|
||||
|
||||
if (filterWindow % 2 == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be an odd number");
|
||||
}
|
||||
|
||||
if (targetPeak <= 0 || targetPeak > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(targetPeak));
|
||||
}
|
||||
|
||||
if (gainFactor < 1 || gainFactor > 100)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(gainFactor), "Gain factor must be between 1.0 to 100.0");
|
||||
}
|
||||
|
||||
if (targetRms < 0 || targetRms > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(targetRms), "Target RMS must be between 0.0 and 1.0");
|
||||
}
|
||||
|
||||
if (compressorFactor < 0 || compressorFactor > 30)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(compressorFactor), "Compressor factor must be between 0.0 and 30.0");
|
||||
}
|
||||
|
||||
_arguments.Add("f", frameLength.ToString());
|
||||
_arguments.Add("g", filterWindow.ToString());
|
||||
_arguments.Add("p", targetPeak.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("m", gainFactor.ToString("0.0", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("r", targetRms.ToString("0.0", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("n", (channelCoupling ? 1 : 0).ToString());
|
||||
_arguments.Add("c", (enableDcBiasCorrection ? 1 : 0).ToString());
|
||||
_arguments.Add("b", (enableAlternativeBoundary ? 1 : 0).ToString());
|
||||
_arguments.Add("s", compressorFactor.ToString("0.0", CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
if (filterWindow < 3 || filterWindow > 31)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be between 3 to 31");
|
||||
}
|
||||
public string Key { get; } = "dynaudnorm";
|
||||
|
||||
if (filterWindow % 2 == 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(filterWindow), "Gaussian filter window size must be an odd number");
|
||||
}
|
||||
|
||||
if (targetPeak <= 0 || targetPeak > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(targetPeak));
|
||||
}
|
||||
|
||||
if (gainFactor < 1 || gainFactor > 100)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(gainFactor), "Gain factor must be between 1.0 to 100.0");
|
||||
}
|
||||
|
||||
if (targetRms < 0 || targetRms > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(targetRms), "Target RMS must be between 0.0 and 1.0");
|
||||
}
|
||||
|
||||
if (compressorFactor < 0 || compressorFactor > 30)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(compressorFactor), "Compressor factor must be between 0.0 and 30.0");
|
||||
}
|
||||
|
||||
_arguments.Add("f", frameLength.ToString());
|
||||
_arguments.Add("g", filterWindow.ToString());
|
||||
_arguments.Add("p", targetPeak.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("m", gainFactor.ToString("0.0", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("r", targetRms.ToString("0.0", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("n", (channelCoupling ? 1 : 0).ToString());
|
||||
_arguments.Add("c", (enableDcBiasCorrection ? 1 : 0).ToString());
|
||||
_arguments.Add("b", (enableAlternativeBoundary ? 1 : 0).ToString());
|
||||
_arguments.Add("s", compressorFactor.ToString("0.0", CultureInfo.InvariantCulture));
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
|
||||
public string Key { get; } = "dynaudnorm";
|
||||
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
using FFMpegCore.Extend;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents seek parameter
|
||||
/// </summary>
|
||||
public class EndSeekArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly TimeSpan? SeekTo;
|
||||
|
||||
public EndSeekArgument(TimeSpan? seekTo)
|
||||
/// <summary>
|
||||
/// Represents seek parameter
|
||||
/// </summary>
|
||||
public class EndSeekArgument : IArgument
|
||||
{
|
||||
SeekTo = seekTo;
|
||||
}
|
||||
public readonly TimeSpan? SeekTo;
|
||||
|
||||
public string Text => SeekTo.HasValue ? $"-to {SeekTo.Value.ToLongString()}" : string.Empty;
|
||||
public EndSeekArgument(TimeSpan? seekTo)
|
||||
{
|
||||
SeekTo = seekTo;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
public string Text => "-movflags faststart";
|
||||
/// <summary>
|
||||
/// Faststart argument - for moving moov atom to the start of file
|
||||
/// </summary>
|
||||
public class FaststartArgument : IArgument
|
||||
{
|
||||
public string Text => "-movflags faststart";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents force format parameter
|
||||
/// </summary>
|
||||
public class ForceFormatArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly string _format;
|
||||
|
||||
public ForceFormatArgument(string format)
|
||||
/// <summary>
|
||||
/// Represents force format parameter
|
||||
/// </summary>
|
||||
public class ForceFormatArgument : IArgument
|
||||
{
|
||||
_format = format;
|
||||
}
|
||||
private readonly string _format;
|
||||
public ForceFormatArgument(string format)
|
||||
{
|
||||
_format = format;
|
||||
}
|
||||
|
||||
public ForceFormatArgument(ContainerFormat format)
|
||||
{
|
||||
_format = format.Name;
|
||||
}
|
||||
public ForceFormatArgument(ContainerFormat format)
|
||||
{
|
||||
_format = format.Name;
|
||||
}
|
||||
|
||||
public string Text => $"-f {_format}";
|
||||
public string Text => $"-f {_format}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class ForcePixelFormat : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public ForcePixelFormat(string format)
|
||||
public class ForcePixelFormat : IArgument
|
||||
{
|
||||
PixelFormat = format;
|
||||
}
|
||||
public string PixelFormat { get; }
|
||||
public string Text => $"-pix_fmt {PixelFormat}";
|
||||
|
||||
public ForcePixelFormat(PixelFormat format) : this(format.Name) { }
|
||||
public string PixelFormat { get; }
|
||||
public string Text => $"-pix_fmt {PixelFormat}";
|
||||
public ForcePixelFormat(string format)
|
||||
{
|
||||
PixelFormat = format;
|
||||
}
|
||||
|
||||
public ForcePixelFormat(PixelFormat format) : this(format.Name) { }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents frame output count parameter
|
||||
/// </summary>
|
||||
public class FrameOutputCountArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly int Frames;
|
||||
|
||||
public FrameOutputCountArgument(int frames)
|
||||
/// <summary>
|
||||
/// Represents frame output count parameter
|
||||
/// </summary>
|
||||
public class FrameOutputCountArgument : IArgument
|
||||
{
|
||||
Frames = frames;
|
||||
}
|
||||
public readonly int Frames;
|
||||
public FrameOutputCountArgument(int frames)
|
||||
{
|
||||
Frames = frames;
|
||||
}
|
||||
|
||||
public string Text => $"-vframes {Frames}";
|
||||
public string Text => $"-vframes {Frames}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,17 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents frame rate parameter
|
||||
/// </summary>
|
||||
public class FrameRateArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly double Framerate;
|
||||
|
||||
public FrameRateArgument(double framerate)
|
||||
/// <summary>
|
||||
/// Represents frame rate parameter
|
||||
/// </summary>
|
||||
public class FrameRateArgument : IArgument
|
||||
{
|
||||
Framerate = framerate;
|
||||
}
|
||||
public readonly double Framerate;
|
||||
|
||||
public string Text => $"-r {Framerate.ToString(CultureInfo.InvariantCulture)}";
|
||||
public FrameRateArgument(double framerate)
|
||||
{
|
||||
Framerate = framerate;
|
||||
}
|
||||
|
||||
public string Text => $"-r {Framerate.ToString(System.Globalization.CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
using System.Drawing;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class GifPaletteArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly int _fps;
|
||||
|
||||
private readonly Size? _size;
|
||||
private readonly int _streamIndex;
|
||||
|
||||
public GifPaletteArgument(int streamIndex, int fps, Size? size)
|
||||
public class GifPaletteArgument : IArgument
|
||||
{
|
||||
_streamIndex = streamIndex;
|
||||
_fps = fps;
|
||||
_size = size;
|
||||
private readonly int _streamIndex;
|
||||
|
||||
private readonly int _fps;
|
||||
|
||||
private readonly Size? _size;
|
||||
|
||||
public GifPaletteArgument(int streamIndex, int fps, Size? size)
|
||||
{
|
||||
_streamIndex = streamIndex;
|
||||
_fps = fps;
|
||||
_size = size;
|
||||
}
|
||||
|
||||
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\"";
|
||||
}
|
||||
|
||||
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\"";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class HardwareAccelerationArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public HardwareAccelerationArgument(HardwareAccelerationDevice hardwareAccelerationDevice)
|
||||
public class HardwareAccelerationArgument : IArgument
|
||||
{
|
||||
HardwareAccelerationDevice = hardwareAccelerationDevice;
|
||||
public HardwareAccelerationDevice HardwareAccelerationDevice { get; }
|
||||
|
||||
public HardwareAccelerationArgument(HardwareAccelerationDevice hardwareAccelerationDevice)
|
||||
{
|
||||
HardwareAccelerationDevice = hardwareAccelerationDevice;
|
||||
}
|
||||
|
||||
public string Text => $"-hwaccel {HardwareAccelerationDevice.ToString().ToLower()}";
|
||||
}
|
||||
|
||||
public HardwareAccelerationDevice HardwareAccelerationDevice { get; }
|
||||
|
||||
public string Text => $"-hwaccel {HardwareAccelerationDevice.ToString().ToLower()}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,112 +1,78 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class HighPassFilterArgument : IAudioFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
private readonly List<string> _precision = new()
|
||||
public class HighPassFilterArgument : IAudioFilterArgument
|
||||
{
|
||||
"auto",
|
||||
"s16",
|
||||
"s32",
|
||||
"f32",
|
||||
"f64"
|
||||
};
|
||||
|
||||
private readonly List<string> _transformTypes = new()
|
||||
{
|
||||
"di",
|
||||
"dii",
|
||||
"tdi",
|
||||
"tdii",
|
||||
"latt",
|
||||
"svf",
|
||||
"zdf"
|
||||
};
|
||||
|
||||
private readonly List<string> _widthTypes = new()
|
||||
{
|
||||
"h",
|
||||
"q",
|
||||
"o",
|
||||
"s",
|
||||
"k"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// HighPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#highpass" />
|
||||
/// </summary>
|
||||
/// <param name="frequency">Set frequency in Hz. Default is 3000.</param>
|
||||
/// <param name="poles">Set number of poles. Default is 2.</param>
|
||||
/// <param name="width_type">Set method to specify band-width of filter, possible values are: h, q, o, s, k</param>
|
||||
/// <param name="width">
|
||||
/// Specify the band-width of a filter in width_type units. Applies only to double-pole filter. The default is 0.707q and
|
||||
/// gives a Butterworth response.
|
||||
/// </param>
|
||||
/// <param name="mix">How much to use filtered signal in output. Default is 1. Range is between 0 and 1.</param>
|
||||
/// <param name="channels">Specify which channels to filter, by default all available are filtered.</param>
|
||||
/// <param name="normalize">Normalize biquad coefficients, by default is disabled. Enabling it will normalize magnitude response at DC to 0dB.</param>
|
||||
/// <param name="transform">Set transform type of IIR filter, possible values are: di, dii, tdi, tdii, latt, svf, zdf</param>
|
||||
/// <param name="precision">Set precison of filtering, possible values are: auto, s16, s32, f32, f64.</param>
|
||||
/// <param name="block_size">
|
||||
/// Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse
|
||||
/// response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just
|
||||
/// produce nasty artifacts.
|
||||
/// </param>
|
||||
public HighPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "",
|
||||
bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
|
||||
{
|
||||
if (frequency < 0)
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
private readonly List<string> _widthTypes = new() { "h", "q", "o", "s", "k" };
|
||||
private readonly List<string> _transformTypes = new() { "di", "dii", "tdi", "tdii", "latt", "svf", "zdf" };
|
||||
private readonly List<string> _precision = new() { "auto", "s16", "s32", "f32", "f64" };
|
||||
/// <summary>
|
||||
/// HighPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#highpass"/>
|
||||
/// </summary>
|
||||
/// <param name="frequency">Set frequency in Hz. Default is 3000.</param>
|
||||
/// <param name="poles">Set number of poles. Default is 2.</param>
|
||||
/// <param name="width_type">Set method to specify band-width of filter, possible values are: h, q, o, s, k</param>
|
||||
/// <param name="width">Specify the band-width of a filter in width_type units. Applies only to double-pole filter. The default is 0.707q and gives a Butterworth response.</param>
|
||||
/// <param name="mix">How much to use filtered signal in output. Default is 1. Range is between 0 and 1.</param>
|
||||
/// <param name="channels">Specify which channels to filter, by default all available are filtered.</param>
|
||||
/// <param name="normalize">Normalize biquad coefficients, by default is disabled. Enabling it will normalize magnitude response at DC to 0dB.</param>
|
||||
/// <param name="transform">Set transform type of IIR filter, possible values are: di, dii, tdi, tdii, latt, svf, zdf</param>
|
||||
/// <param name="precision">Set precison of filtering, possible values are: auto, s16, s32, f32, f64.</param>
|
||||
/// <param name="block_size">Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just produce nasty artifacts.</param>
|
||||
public HighPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number");
|
||||
if (frequency < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number");
|
||||
}
|
||||
|
||||
if (poles < 1 || poles > 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(poles), "Poles must be either 1 or 2");
|
||||
}
|
||||
|
||||
if (!_widthTypes.Contains(width_type))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes.ToString());
|
||||
}
|
||||
|
||||
if (mix < 0 || mix > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(mix), "Mix must be between 0 and 1");
|
||||
}
|
||||
|
||||
if (!_precision.Contains(precision))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision.ToString());
|
||||
}
|
||||
|
||||
_arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("p", poles.ToString());
|
||||
_arguments.Add("t", width_type);
|
||||
_arguments.Add("w", width.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("m", mix.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
if (channels != "")
|
||||
{
|
||||
_arguments.Add("c", channels);
|
||||
}
|
||||
|
||||
_arguments.Add("n", (normalize ? 1 : 0).ToString());
|
||||
if (transform != "" && _transformTypes.Contains(transform))
|
||||
{
|
||||
_arguments.Add("a", transform);
|
||||
}
|
||||
|
||||
_arguments.Add("r", precision);
|
||||
if (block_size != null && block_size >= 0)
|
||||
{
|
||||
_arguments.Add("b", block_size.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (poles < 1 || poles > 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(poles), "Poles must be either 1 or 2");
|
||||
}
|
||||
public string Key { get; } = "highpass";
|
||||
|
||||
if (!_widthTypes.Contains(width_type))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes);
|
||||
}
|
||||
|
||||
if (mix < 0 || mix > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(mix), "Mix must be between 0 and 1");
|
||||
}
|
||||
|
||||
if (!_precision.Contains(precision))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision);
|
||||
}
|
||||
|
||||
_arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("p", poles.ToString());
|
||||
_arguments.Add("t", width_type);
|
||||
_arguments.Add("w", width.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("m", mix.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
if (channels != "")
|
||||
{
|
||||
_arguments.Add("c", channels);
|
||||
}
|
||||
|
||||
_arguments.Add("n", (normalize ? 1 : 0).ToString());
|
||||
if (transform != "" && _transformTypes.Contains(transform))
|
||||
{
|
||||
_arguments.Add("a", transform);
|
||||
}
|
||||
|
||||
_arguments.Add("r", precision);
|
||||
if (block_size != null && block_size >= 0)
|
||||
{
|
||||
_arguments.Add("b", block_size.ToString());
|
||||
}
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// The textual representation of the argument
|
||||
/// </summary>
|
||||
string Text { get; }
|
||||
public interface IArgument
|
||||
{
|
||||
/// <summary>
|
||||
/// The textual representation of the argument
|
||||
/// </summary>
|
||||
string Text { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class ID3V2VersionArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly int _version;
|
||||
|
||||
public ID3V2VersionArgument(int version)
|
||||
public class ID3V2VersionArgument : IArgument
|
||||
{
|
||||
_version = version;
|
||||
}
|
||||
private readonly int _version;
|
||||
|
||||
public string Text => $"-id3v2_version {_version}";
|
||||
public ID3V2VersionArgument(int version)
|
||||
{
|
||||
_version = version;
|
||||
}
|
||||
|
||||
public string Text => $"-id3v2_version {_version}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public interface IDynamicArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
/// <summary>
|
||||
/// 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 interface IDynamicArgument
|
||||
{
|
||||
/// <summary>
|
||||
/// 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);
|
||||
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
|
||||
{
|
||||
void Pre();
|
||||
Task During(CancellationToken cancellationToken = default);
|
||||
void Post();
|
||||
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,35 +1,32 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents input parameter
|
||||
/// </summary>
|
||||
public class InputArgument : IInputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly string FilePath;
|
||||
public readonly bool VerifyExists;
|
||||
|
||||
public InputArgument(bool verifyExists, string filePaths)
|
||||
/// <summary>
|
||||
/// Represents input parameter
|
||||
/// </summary>
|
||||
public class InputArgument : IInputArgument
|
||||
{
|
||||
VerifyExists = verifyExists;
|
||||
FilePath = filePaths;
|
||||
}
|
||||
public readonly bool VerifyExists;
|
||||
public readonly string FilePath;
|
||||
|
||||
public InputArgument(string path, bool verifyExists) : this(verifyExists, path) { }
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
if (VerifyExists && !File.Exists(FilePath))
|
||||
public InputArgument(bool verifyExists, string filePaths)
|
||||
{
|
||||
throw new FileNotFoundException("Input file not found", FilePath);
|
||||
VerifyExists = verifyExists;
|
||||
FilePath = filePaths;
|
||||
}
|
||||
|
||||
public InputArgument(string path, bool verifyExists) : this(verifyExists, path) { }
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
if (VerifyExists && !File.Exists(FilePath))
|
||||
{
|
||||
throw new FileNotFoundException("Input file not found", FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post() { }
|
||||
|
||||
public string Text => $"-i \"{FilePath}\"";
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Post() { }
|
||||
|
||||
public string Text => $"-i \"{FilePath}\"";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,23 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an input device parameter
|
||||
/// </summary>
|
||||
public class InputDeviceArgument : IInputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly string Device;
|
||||
|
||||
public InputDeviceArgument(string device)
|
||||
/// <summary>
|
||||
/// Represents an input device parameter
|
||||
/// </summary>
|
||||
public class InputDeviceArgument : IInputArgument
|
||||
{
|
||||
Device = device;
|
||||
private readonly string Device;
|
||||
|
||||
public InputDeviceArgument(string device)
|
||||
{
|
||||
Device = device;
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
|
||||
public void Pre() { }
|
||||
|
||||
public void Post() { }
|
||||
|
||||
public string Text => $"-i {Device}";
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Pre() { }
|
||||
|
||||
public void Post() { }
|
||||
|
||||
public string Text => $"-i {Device}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,31 @@
|
|||
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
|
||||
{
|
||||
public readonly IPipeSource Writer;
|
||||
|
||||
public InputPipeArgument(IPipeSource writer) : base(PipeDirection.Out)
|
||||
/// <summary>
|
||||
/// Represents input parameter for a named pipe
|
||||
/// </summary>
|
||||
public class InputPipeArgument : PipeArgument, IInputArgument
|
||||
{
|
||||
Writer = writer;
|
||||
}
|
||||
public readonly IPipeSource Writer;
|
||||
|
||||
public override string Text => $"{Writer.GetStreamArguments()} -i \"{PipePath}\"";
|
||||
|
||||
protected override async Task ProcessDataAsync(CancellationToken token)
|
||||
{
|
||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||
if (!Pipe.IsConnected)
|
||||
public InputPipeArgument(IPipeSource writer) : base(PipeDirection.Out)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
Writer = writer;
|
||||
}
|
||||
|
||||
await Writer.WriteAsync(Pipe, token).ConfigureAwait(false);
|
||||
public override string Text => $"{Writer.GetStreamArguments()} -i \"{PipePath}\"";
|
||||
|
||||
protected override async Task ProcessDataAsync(CancellationToken token)
|
||||
{
|
||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||
if (!Pipe.IsConnected)
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
public readonly int Times;
|
||||
|
||||
public LoopArgument(int times)
|
||||
/// <summary>
|
||||
/// Represents loop parameter
|
||||
/// </summary>
|
||||
public class LoopArgument : IArgument
|
||||
{
|
||||
Times = times;
|
||||
}
|
||||
public readonly int Times;
|
||||
public LoopArgument(int times)
|
||||
{
|
||||
Times = times;
|
||||
}
|
||||
|
||||
public string Text => $"-loop {Times}";
|
||||
public string Text => $"-loop {Times}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,112 +1,78 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class LowPassFilterArgument : IAudioFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
private readonly List<string> _precision = new()
|
||||
public class LowPassFilterArgument : IAudioFilterArgument
|
||||
{
|
||||
"auto",
|
||||
"s16",
|
||||
"s32",
|
||||
"f32",
|
||||
"f64"
|
||||
};
|
||||
|
||||
private readonly List<string> _transformTypes = new()
|
||||
{
|
||||
"di",
|
||||
"dii",
|
||||
"tdi",
|
||||
"tdii",
|
||||
"latt",
|
||||
"svf",
|
||||
"zdf"
|
||||
};
|
||||
|
||||
private readonly List<string> _widthTypes = new()
|
||||
{
|
||||
"h",
|
||||
"q",
|
||||
"o",
|
||||
"s",
|
||||
"k"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// LowPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#lowpass" />
|
||||
/// </summary>
|
||||
/// <param name="frequency">Set frequency in Hz. Default is 3000.</param>
|
||||
/// <param name="poles">Set number of poles. Default is 2.</param>
|
||||
/// <param name="width_type">Set method to specify band-width of filter, possible values are: h, q, o, s, k</param>
|
||||
/// <param name="width">
|
||||
/// Specify the band-width of a filter in width_type units. Applies only to double-pole filter. The default is 0.707q and
|
||||
/// gives a Butterworth response.
|
||||
/// </param>
|
||||
/// <param name="mix">How much to use filtered signal in output. Default is 1. Range is between 0 and 1.</param>
|
||||
/// <param name="channels">Specify which channels to filter, by default all available are filtered.</param>
|
||||
/// <param name="normalize">Normalize biquad coefficients, by default is disabled. Enabling it will normalize magnitude response at DC to 0dB.</param>
|
||||
/// <param name="transform">Set transform type of IIR filter, possible values are: di, dii, tdi, tdii, latt, svf, zdf</param>
|
||||
/// <param name="precision">Set precison of filtering, possible values are: auto, s16, s32, f32, f64.</param>
|
||||
/// <param name="block_size">
|
||||
/// Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse
|
||||
/// response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just
|
||||
/// produce nasty artifacts.
|
||||
/// </param>
|
||||
public LowPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "",
|
||||
bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
|
||||
{
|
||||
if (frequency < 0)
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
private readonly List<string> _widthTypes = new() { "h", "q", "o", "s", "k" };
|
||||
private readonly List<string> _transformTypes = new() { "di", "dii", "tdi", "tdii", "latt", "svf", "zdf" };
|
||||
private readonly List<string> _precision = new() { "auto", "s16", "s32", "f32", "f64" };
|
||||
/// <summary>
|
||||
/// LowPass Filter. <see href="https://ffmpeg.org/ffmpeg-filters.html#lowpass"/>
|
||||
/// </summary>
|
||||
/// <param name="frequency">Set frequency in Hz. Default is 3000.</param>
|
||||
/// <param name="poles">Set number of poles. Default is 2.</param>
|
||||
/// <param name="width_type">Set method to specify band-width of filter, possible values are: h, q, o, s, k</param>
|
||||
/// <param name="width">Specify the band-width of a filter in width_type units. Applies only to double-pole filter. The default is 0.707q and gives a Butterworth response.</param>
|
||||
/// <param name="mix">How much to use filtered signal in output. Default is 1. Range is between 0 and 1.</param>
|
||||
/// <param name="channels">Specify which channels to filter, by default all available are filtered.</param>
|
||||
/// <param name="normalize">Normalize biquad coefficients, by default is disabled. Enabling it will normalize magnitude response at DC to 0dB.</param>
|
||||
/// <param name="transform">Set transform type of IIR filter, possible values are: di, dii, tdi, tdii, latt, svf, zdf</param>
|
||||
/// <param name="precision">Set precison of filtering, possible values are: auto, s16, s32, f32, f64.</param>
|
||||
/// <param name="block_size">Set block size used for reverse IIR processing. If this value is set to high enough value (higher than impulse response length truncated when reaches near zero values) filtering will become linear phase otherwise if not big enough it will just produce nasty artifacts.</param>
|
||||
public LowPassFilterArgument(double frequency = 3000, int poles = 2, string width_type = "q", double width = 0.707, double mix = 1, string channels = "", bool normalize = false, string transform = "", string precision = "auto", int? block_size = null)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number");
|
||||
if (frequency < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(frequency), "Frequency must be a positive number");
|
||||
}
|
||||
|
||||
if (poles < 1 || poles > 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(poles), "Poles must be either 1 or 2");
|
||||
}
|
||||
|
||||
if (!_widthTypes.Contains(width_type))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes.ToString());
|
||||
}
|
||||
|
||||
if (mix < 0 || mix > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(mix), "Mix must be between 0 and 1");
|
||||
}
|
||||
|
||||
if (!_precision.Contains(precision))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision.ToString());
|
||||
}
|
||||
|
||||
_arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("p", poles.ToString());
|
||||
_arguments.Add("t", width_type);
|
||||
_arguments.Add("w", width.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("m", mix.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
if (channels != "")
|
||||
{
|
||||
_arguments.Add("c", channels);
|
||||
}
|
||||
|
||||
_arguments.Add("n", (normalize ? 1 : 0).ToString());
|
||||
if (transform != "" && _transformTypes.Contains(transform))
|
||||
{
|
||||
_arguments.Add("a", transform);
|
||||
}
|
||||
|
||||
_arguments.Add("r", precision);
|
||||
if (block_size != null && block_size >= 0)
|
||||
{
|
||||
_arguments.Add("b", block_size.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
if (poles < 1 || poles > 2)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(poles), "Poles must be either 1 or 2");
|
||||
}
|
||||
public string Key { get; } = "lowpass";
|
||||
|
||||
if (!_widthTypes.Contains(width_type))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(width_type), "Width type must be either " + _widthTypes);
|
||||
}
|
||||
|
||||
if (mix < 0 || mix > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(mix), "Mix must be between 0 and 1");
|
||||
}
|
||||
|
||||
if (!_precision.Contains(precision))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(precision), "Precision must be either " + _precision);
|
||||
}
|
||||
|
||||
_arguments.Add("f", frequency.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("p", poles.ToString());
|
||||
_arguments.Add("t", width_type);
|
||||
_arguments.Add("w", width.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("m", mix.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
if (channels != "")
|
||||
{
|
||||
_arguments.Add("c", channels);
|
||||
}
|
||||
|
||||
_arguments.Add("n", (normalize ? 1 : 0).ToString());
|
||||
if (transform != "" && _transformTypes.Contains(transform))
|
||||
{
|
||||
_arguments.Add("a", transform);
|
||||
}
|
||||
|
||||
_arguments.Add("r", precision);
|
||||
if (block_size != null && block_size >= 0)
|
||||
{
|
||||
_arguments.Add("b", block_size.ToString());
|
||||
}
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
|
||||
public string Key { get; } = "lowpass";
|
||||
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,52 +1,53 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class MapMetadataArgument : IInputArgument, IDynamicArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly int? _inputIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Null means it takes the last input used before this argument
|
||||
/// </summary>
|
||||
/// <param name="inputIndex"></param>
|
||||
public MapMetadataArgument(int? inputIndex = null)
|
||||
public class MapMetadataArgument : IInputArgument, IDynamicArgument
|
||||
{
|
||||
_inputIndex = inputIndex;
|
||||
}
|
||||
private readonly int? _inputIndex;
|
||||
|
||||
public string GetText(IEnumerable<IArgument>? arguments)
|
||||
{
|
||||
arguments ??= Enumerable.Empty<IArgument>();
|
||||
public string Text => GetText(null);
|
||||
|
||||
var index = 0;
|
||||
if (_inputIndex is null)
|
||||
/// <summary>
|
||||
/// Null means it takes the last input used before this argument
|
||||
/// </summary>
|
||||
/// <param name="inputIndex"></param>
|
||||
public MapMetadataArgument(int? inputIndex = null)
|
||||
{
|
||||
index = arguments
|
||||
.TakeWhile(x => x != this)
|
||||
.OfType<IInputArgument>()
|
||||
.Count();
|
||||
|
||||
index = Math.Max(index - 1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
index = _inputIndex.Value;
|
||||
_inputIndex = inputIndex;
|
||||
}
|
||||
|
||||
return $"-map_metadata {index}";
|
||||
}
|
||||
public string GetText(IEnumerable<IArgument>? arguments)
|
||||
{
|
||||
arguments ??= Enumerable.Empty<IArgument>();
|
||||
|
||||
public string Text => GetText(null);
|
||||
var index = 0;
|
||||
if (_inputIndex is null)
|
||||
{
|
||||
index = arguments
|
||||
.TakeWhile(x => x != this)
|
||||
.OfType<IInputArgument>()
|
||||
.Count();
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
index = Math.Max(index - 1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
index = _inputIndex.Value;
|
||||
}
|
||||
|
||||
public void Post()
|
||||
{
|
||||
}
|
||||
return $"-map_metadata {index}";
|
||||
}
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Post()
|
||||
{
|
||||
}
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,31 @@
|
|||
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;
|
||||
private readonly int _inputFileIndex;
|
||||
private readonly bool _negativeMap;
|
||||
private readonly int _streamIndex;
|
||||
|
||||
public MapStreamArgument(int streamIndex, int inputFileIndex, Channel channel = Channel.All, bool negativeMap = false)
|
||||
/// <summary>
|
||||
/// Represents choice of stream by the stream specifier
|
||||
/// </summary>
|
||||
public class MapStreamArgument : IArgument
|
||||
{
|
||||
if (channel == Channel.Both)
|
||||
private readonly int _inputFileIndex;
|
||||
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)
|
||||
{
|
||||
// "Both" is not valid in this case and probably means all stream types
|
||||
channel = Channel.All;
|
||||
if (channel == Channel.Both)
|
||||
{
|
||||
// "Both" is not valid in this case and probably means all stream types
|
||||
channel = Channel.All;
|
||||
}
|
||||
|
||||
_inputFileIndex = inputFileIndex;
|
||||
_streamIndex = streamIndex;
|
||||
_channel = channel;
|
||||
_negativeMap = negativeMap;
|
||||
}
|
||||
|
||||
_inputFileIndex = inputFileIndex;
|
||||
_streamIndex = streamIndex;
|
||||
_channel = channel;
|
||||
_negativeMap = negativeMap;
|
||||
public string Text => $"-map {(_negativeMap ? "-" : "")}{_inputFileIndex}{_channel.StreamType()}:{_streamIndex}";
|
||||
}
|
||||
|
||||
public string Text => $"-map {(_negativeMap ? "-" : "")}{_inputFileIndex}{_channel.StreamType()}:{_streamIndex}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +1,33 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class MetaDataArgument : IInputArgument, IDynamicArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly string _metaDataContent;
|
||||
private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"metadata_{Guid.NewGuid()}.txt");
|
||||
|
||||
public MetaDataArgument(string metaDataContent)
|
||||
public class MetaDataArgument : IInputArgument, IDynamicArgument
|
||||
{
|
||||
_metaDataContent = metaDataContent;
|
||||
}
|
||||
private readonly string _metaDataContent;
|
||||
private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"metadata_{Guid.NewGuid()}.txt");
|
||||
|
||||
public string GetText(IEnumerable<IArgument>? arguments)
|
||||
{
|
||||
arguments ??= Enumerable.Empty<IArgument>();
|
||||
public MetaDataArgument(string metaDataContent)
|
||||
{
|
||||
_metaDataContent = metaDataContent;
|
||||
}
|
||||
|
||||
var index = arguments
|
||||
.TakeWhile(x => x != this)
|
||||
.OfType<IInputArgument>()
|
||||
.Count();
|
||||
public string Text => GetText(null);
|
||||
|
||||
return $"-i \"{_tempFileName}\" -map_metadata {index}";
|
||||
}
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
|
||||
public string Text => GetText(null);
|
||||
public void Pre() => File.WriteAllText(_tempFileName, _metaDataContent);
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public void Post() => File.Delete(_tempFileName);
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
File.WriteAllText(_tempFileName, _metaDataContent);
|
||||
}
|
||||
public string GetText(IEnumerable<IArgument>? arguments)
|
||||
{
|
||||
arguments ??= Enumerable.Empty<IArgument>();
|
||||
|
||||
public void Post()
|
||||
{
|
||||
File.Delete(_tempFileName);
|
||||
var index = arguments
|
||||
.TakeWhile(x => x != this)
|
||||
.OfType<IInputArgument>()
|
||||
.Count();
|
||||
|
||||
return $"-i \"{_tempFileName}\" -map_metadata {index}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,47 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents input parameters for multiple files
|
||||
/// </summary>
|
||||
public class MultiInputArgument : IInputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly IEnumerable<string> FilePaths;
|
||||
public readonly bool VerifyExists;
|
||||
|
||||
public MultiInputArgument(bool verifyExists, IEnumerable<string> filePaths)
|
||||
/// <summary>
|
||||
/// Represents input parameters for multiple files
|
||||
/// </summary>
|
||||
public class MultiInputArgument : IInputArgument
|
||||
{
|
||||
VerifyExists = verifyExists;
|
||||
FilePaths = filePaths;
|
||||
}
|
||||
public readonly bool VerifyExists;
|
||||
public readonly IEnumerable<string> FilePaths;
|
||||
|
||||
public MultiInputArgument(IEnumerable<string> filePaths, bool verifyExists) : this(verifyExists, filePaths) { }
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
if (VerifyExists)
|
||||
public MultiInputArgument(bool verifyExists, IEnumerable<string> filePaths)
|
||||
{
|
||||
var missingFiles = new List<string>();
|
||||
foreach (var filePath in FilePaths)
|
||||
VerifyExists = verifyExists;
|
||||
FilePaths = filePaths;
|
||||
}
|
||||
|
||||
public MultiInputArgument(IEnumerable<string> filePaths, bool verifyExists) : this(verifyExists, filePaths) { }
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
if (VerifyExists)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
var missingFiles = new List<string>();
|
||||
foreach (var filePath in FilePaths)
|
||||
{
|
||||
missingFiles.Add(filePath);
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
missingFiles.Add(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingFiles.Any())
|
||||
{
|
||||
throw new FileNotFoundException($"The following input files were not found: {string.Join(", ", missingFiles)}");
|
||||
}
|
||||
}
|
||||
|
||||
if (missingFiles.Any())
|
||||
{
|
||||
throw new FileNotFoundException($"The following input files were not found: {string.Join(", ", missingFiles)}");
|
||||
}
|
||||
}
|
||||
|
||||
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}\""));
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return 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,41 +1,37 @@
|
|||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents output parameter
|
||||
/// </summary>
|
||||
public class OutputArgument : IOutputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly bool Overwrite;
|
||||
public readonly string Path;
|
||||
|
||||
public OutputArgument(string path, bool overwrite = true)
|
||||
/// <summary>
|
||||
/// Represents output parameter
|
||||
/// </summary>
|
||||
public class OutputArgument : IOutputArgument
|
||||
{
|
||||
Path = path;
|
||||
Overwrite = overwrite;
|
||||
}
|
||||
public readonly string Path;
|
||||
public readonly bool Overwrite;
|
||||
|
||||
public OutputArgument(FileInfo value) : this(value.FullName) { }
|
||||
|
||||
public OutputArgument(Uri value) : this(value.AbsolutePath) { }
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
if (!Overwrite && File.Exists(Path))
|
||||
public OutputArgument(string path, bool overwrite = true)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.File, "Output file already exists and overwrite is disabled");
|
||||
Path = path;
|
||||
Overwrite = overwrite;
|
||||
}
|
||||
}
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public void Pre()
|
||||
{
|
||||
if (!Overwrite && File.Exists(Path))
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.File, "Output file already exists and overwrite is disabled");
|
||||
}
|
||||
}
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
public void Post()
|
||||
{
|
||||
}
|
||||
|
||||
public void Post()
|
||||
{
|
||||
}
|
||||
public OutputArgument(FileInfo value) : this(value.FullName) { }
|
||||
|
||||
public string Text => $"\"{Path}\"{(Overwrite ? " -y" : string.Empty)}";
|
||||
public OutputArgument(Uri value) : this(value.AbsolutePath) { }
|
||||
|
||||
public string Text => $"\"{Path}\"{(Overwrite ? " -y" : string.Empty)}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,28 @@
|
|||
using System.IO.Pipes;
|
||||
using FFMpegCore.Pipes;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class OutputPipeArgument : PipeArgument, IOutputArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly IPipeSink Reader;
|
||||
|
||||
public OutputPipeArgument(IPipeSink reader) : base(PipeDirection.In)
|
||||
public class OutputPipeArgument : PipeArgument, IOutputArgument
|
||||
{
|
||||
Reader = reader;
|
||||
}
|
||||
public readonly IPipeSink Reader;
|
||||
|
||||
public override string Text => $"\"{PipePath}\" -y";
|
||||
|
||||
protected override async Task ProcessDataAsync(CancellationToken token)
|
||||
{
|
||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||
if (!Pipe.IsConnected)
|
||||
public OutputPipeArgument(IPipeSink reader) : base(PipeDirection.In)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
Reader = reader;
|
||||
}
|
||||
|
||||
await Reader.ReadAsync(Pipe, token).ConfigureAwait(false);
|
||||
public override string Text => $"\"{PipePath}\" -y";
|
||||
|
||||
protected override async Task ProcessDataAsync(CancellationToken token)
|
||||
{
|
||||
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
|
||||
if (!Pipe.IsConnected)
|
||||
{
|
||||
throw new TaskCanceledException();
|
||||
}
|
||||
|
||||
await Reader.ReadAsync(Pipe, token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +1,57 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
internal class OutputTeeArgument : IOutputArgument
|
||||
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly FFMpegMultiOutputOptions _options;
|
||||
|
||||
public OutputTeeArgument(FFMpegMultiOutputOptions options)
|
||||
internal class OutputTeeArgument : IOutputArgument
|
||||
{
|
||||
if (options.Outputs.Count == 0)
|
||||
private readonly FFMpegMultiOutputOptions _options;
|
||||
|
||||
public OutputTeeArgument(FFMpegMultiOutputOptions options)
|
||||
{
|
||||
throw new ArgumentException("Atleast one output must be specified.", nameof(options));
|
||||
if (options.Outputs.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("Atleast one output must be specified.", nameof(options));
|
||||
}
|
||||
|
||||
_options = options;
|
||||
}
|
||||
|
||||
_options = options;
|
||||
}
|
||||
public string Text => $"-f tee \"{string.Join("|", _options.Outputs.Select(MapOptions))}\"";
|
||||
|
||||
public string Text => $"-f tee \"{string.Join("|", _options.Outputs.Select(MapOptions))}\"";
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Post()
|
||||
{
|
||||
}
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
}
|
||||
|
||||
private static string MapOptions(FFMpegArgumentOptions option)
|
||||
{
|
||||
var optionPrefix = string.Empty;
|
||||
if (option.Arguments.Count > 1)
|
||||
public void Post()
|
||||
{
|
||||
var options = option.Arguments.Take(option.Arguments.Count - 1);
|
||||
optionPrefix = $"[{string.Join(":", options.Select(MapArgument))}]";
|
||||
}
|
||||
|
||||
var output = option.Arguments.OfType<IOutputArgument>().Single();
|
||||
return $"{optionPrefix}{output.Text.Trim('"')}";
|
||||
}
|
||||
|
||||
private static string MapArgument(IArgument argument)
|
||||
{
|
||||
if (argument is MapStreamArgument map)
|
||||
public void Pre()
|
||||
{
|
||||
return map.Text.Replace("-map ", "select=\\'") + "\\'";
|
||||
}
|
||||
|
||||
if (argument is BitStreamFilterArgument bitstreamFilter)
|
||||
private static string MapOptions(FFMpegArgumentOptions option)
|
||||
{
|
||||
return bitstreamFilter.Text.Replace("-bsf:", "bsfs/").Replace(' ', '=');
|
||||
var optionPrefix = string.Empty;
|
||||
if (option.Arguments.Count > 1)
|
||||
{
|
||||
var options = option.Arguments.Take(option.Arguments.Count - 1);
|
||||
optionPrefix = $"[{string.Join(":", options.Select(MapArgument))}]";
|
||||
}
|
||||
|
||||
var output = option.Arguments.OfType<IOutputArgument>().Single();
|
||||
return $"{optionPrefix}{output.Text.Trim('"')}";
|
||||
}
|
||||
|
||||
return argument.Text.TrimStart('-').Replace(' ', '=');
|
||||
private static string MapArgument(IArgument argument)
|
||||
{
|
||||
if (argument is MapStreamArgument map)
|
||||
{
|
||||
return map.Text.Replace("-map ", "select=\\'") + "\\'";
|
||||
}
|
||||
else if (argument is BitStreamFilterArgument bitstreamFilter)
|
||||
{
|
||||
return bitstreamFilter.Text.Replace("-bsf:", "bsfs/").Replace(' ', '=');
|
||||
}
|
||||
|
||||
return argument.Text.TrimStart('-').Replace(' ', '=');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,24 @@
|
|||
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
|
||||
{
|
||||
public readonly string Url;
|
||||
|
||||
public OutputUrlArgument(string url)
|
||||
/// <summary>
|
||||
/// Represents outputting to url using supported protocols
|
||||
/// See http://ffmpeg.org/ffmpeg-protocols.html
|
||||
/// </summary>
|
||||
public class OutputUrlArgument : IOutputArgument
|
||||
{
|
||||
Url = url;
|
||||
public readonly string Url;
|
||||
|
||||
public OutputUrlArgument(string url)
|
||||
{
|
||||
Url = url;
|
||||
}
|
||||
|
||||
public void Post() { }
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
|
||||
|
||||
public void Pre() { }
|
||||
|
||||
public string Text => Url;
|
||||
}
|
||||
|
||||
public void Post() { }
|
||||
|
||||
public Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return 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
|
||||
{
|
||||
public string Text => "-y";
|
||||
/// <summary>
|
||||
/// Represents overwrite parameter
|
||||
/// If output file should be overwritten if exists
|
||||
/// </summary>
|
||||
public class OverwriteArgument : IArgument
|
||||
{
|
||||
public string Text => "-y";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,62 +1,64 @@
|
|||
using FFMpegCore.Extend;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class PadArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly PadOptions _options;
|
||||
|
||||
public PadArgument(PadOptions options)
|
||||
public class PadArgument : IVideoFilterArgument
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
private readonly PadOptions _options;
|
||||
|
||||
public string Key => "pad";
|
||||
public string Value => _options.TextInternal;
|
||||
}
|
||||
|
||||
public class PadOptions
|
||||
{
|
||||
public readonly Dictionary<string, string> Parameters = new();
|
||||
|
||||
private PadOptions(string? width, string? height)
|
||||
{
|
||||
if (width == null && height == null)
|
||||
public PadArgument(PadOptions options)
|
||||
{
|
||||
throw new Exception("At least one of the parameters must be not null");
|
||||
_options = options;
|
||||
}
|
||||
|
||||
if (width != null)
|
||||
public string Key => "pad";
|
||||
public string Value => _options.TextInternal;
|
||||
|
||||
}
|
||||
|
||||
public class PadOptions
|
||||
{
|
||||
public readonly Dictionary<string, string> Parameters = new();
|
||||
|
||||
internal string TextInternal => string.Join(":", Parameters.Select(parameter => parameter.FormatArgumentPair(true)));
|
||||
|
||||
public static PadOptions Create(string? width, string? height)
|
||||
{
|
||||
Parameters.Add("width", width);
|
||||
return new PadOptions(width, height);
|
||||
}
|
||||
|
||||
if (height != null)
|
||||
public static PadOptions Create(string aspectRatio)
|
||||
{
|
||||
Parameters.Add("height", height);
|
||||
return new PadOptions(aspectRatio);
|
||||
}
|
||||
}
|
||||
|
||||
private PadOptions(string aspectRatio)
|
||||
{
|
||||
Parameters.Add("aspect", aspectRatio);
|
||||
}
|
||||
public PadOptions WithParameter(string key, string value)
|
||||
{
|
||||
Parameters.Add(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal string TextInternal => string.Join(":", Parameters.Select(parameter => parameter.FormatArgumentPair(true)));
|
||||
private PadOptions(string? width, string? height)
|
||||
{
|
||||
if (width == null && height == null)
|
||||
{
|
||||
throw new Exception("At least one of the parameters must be not null");
|
||||
}
|
||||
|
||||
public static PadOptions Create(string? width, string? height)
|
||||
{
|
||||
return new PadOptions(width, height);
|
||||
}
|
||||
if (width != null)
|
||||
{
|
||||
Parameters.Add("width", width);
|
||||
}
|
||||
|
||||
public static PadOptions Create(string aspectRatio)
|
||||
{
|
||||
return new PadOptions(aspectRatio);
|
||||
}
|
||||
if (height != null)
|
||||
{
|
||||
Parameters.Add("height", height);
|
||||
}
|
||||
}
|
||||
|
||||
public PadOptions WithParameter(string key, string value)
|
||||
{
|
||||
Parameters.Add(key, value);
|
||||
return this;
|
||||
private PadOptions(string aspectRatio)
|
||||
{
|
||||
Parameters.Add("aspect", aspectRatio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,61 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Mix channels with specific gain levels.
|
||||
/// </summary>
|
||||
public class PanArgument : IAudioFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly string[] _outputDefinitions;
|
||||
public readonly string ChannelLayout;
|
||||
|
||||
/// <summary>
|
||||
/// Mix channels with specific gain levels <see href="https://ffmpeg.org/ffmpeg-filters.html#toc-pan-1" />
|
||||
/// Mix channels with specific gain levels.
|
||||
/// </summary>
|
||||
/// <param name="channelLayout">
|
||||
/// Represent the output channel layout. Like "stereo", "mono", "2.1", "5.1"
|
||||
/// </param>
|
||||
/// <param name="outputDefinitions">
|
||||
/// Output channel specification, of the form: "out_name=[gain*]in_name[(+-)[gain*]in_name...]"
|
||||
/// </param>
|
||||
public PanArgument(string channelLayout, params string[] outputDefinitions)
|
||||
public class PanArgument : IAudioFilterArgument
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(channelLayout))
|
||||
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"/>
|
||||
/// </summary>
|
||||
/// <param name="channelLayout">
|
||||
/// Represent the output channel layout. Like "stereo", "mono", "2.1", "5.1"
|
||||
/// </param>
|
||||
/// <param name="outputDefinitions">
|
||||
/// Output channel specification, of the form: "out_name=[gain*]in_name[(+-)[gain*]in_name...]"
|
||||
/// </param>
|
||||
public PanArgument(string channelLayout, params string[] outputDefinitions)
|
||||
{
|
||||
throw new ArgumentException("The channel layout must be set", nameof(channelLayout));
|
||||
if (string.IsNullOrWhiteSpace(channelLayout))
|
||||
{
|
||||
throw new ArgumentException("The channel layout must be set", nameof(channelLayout));
|
||||
}
|
||||
|
||||
ChannelLayout = channelLayout;
|
||||
|
||||
_outputDefinitions = outputDefinitions;
|
||||
}
|
||||
|
||||
ChannelLayout = channelLayout;
|
||||
/// <summary>
|
||||
/// 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">
|
||||
/// Output channel specification, of the form: "out_name=[gain*]in_name[(+-)[gain*]in_name...]"
|
||||
/// </param>
|
||||
public PanArgument(int channels, params string[] outputDefinitions)
|
||||
{
|
||||
if (channels <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(channels));
|
||||
}
|
||||
|
||||
_outputDefinitions = outputDefinitions;
|
||||
if (outputDefinitions.Length > channels)
|
||||
{
|
||||
throw new ArgumentException("The number of output definitions must be equal or lower than number of channels", nameof(outputDefinitions));
|
||||
}
|
||||
|
||||
ChannelLayout = $"{channels}c";
|
||||
|
||||
_outputDefinitions = outputDefinitions;
|
||||
}
|
||||
|
||||
public string Key { get; } = "pan";
|
||||
|
||||
public string Value =>
|
||||
string.Join("|", Enumerable.Empty<string>().Append(ChannelLayout).Concat(_outputDefinitions));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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">
|
||||
/// Output channel specification, of the form: "out_name=[gain*]in_name[(+-)[gain*]in_name...]"
|
||||
/// </param>
|
||||
public PanArgument(int channels, params string[] outputDefinitions)
|
||||
{
|
||||
if (channels <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(channels));
|
||||
}
|
||||
|
||||
if (outputDefinitions.Length > channels)
|
||||
{
|
||||
throw new ArgumentException("The number of output definitions must be equal or lower than number of channels", nameof(outputDefinitions));
|
||||
}
|
||||
|
||||
ChannelLayout = $"{channels}c";
|
||||
|
||||
_outputDefinitions = outputDefinitions;
|
||||
}
|
||||
|
||||
public string Key { get; } = "pan";
|
||||
|
||||
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.GetUnqiuePipeName();
|
||||
_direction = direction;
|
||||
}
|
||||
private string PipeName { get; }
|
||||
public string PipePath => PipeHelpers.GetPipePath(PipeName);
|
||||
|
||||
private string PipeName { get; }
|
||||
public string PipePath => PipeHelpers.GetPipePath(PipeName);
|
||||
protected NamedPipeServerStream Pipe { get; private set; } = null!;
|
||||
private readonly PipeDirection _direction;
|
||||
|
||||
protected NamedPipeServerStream Pipe { get; private set; } = null!;
|
||||
public abstract string Text { get; }
|
||||
protected PipeArgument(PipeDirection direction)
|
||||
{
|
||||
PipeName = PipeHelpers.GetUnqiuePipeName();
|
||||
_direction = direction;
|
||||
}
|
||||
|
||||
public void Pre()
|
||||
{
|
||||
lock (_pipeLock)
|
||||
public void Pre()
|
||||
{
|
||||
if (Pipe != null)
|
||||
{
|
||||
|
|
@ -32,40 +27,45 @@ 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)
|
||||
public void Post()
|
||||
{
|
||||
Pipe?.Dispose();
|
||||
Pipe = null!;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessDataAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Debug.WriteLine($"ProcessDataAsync on {GetType().Name} cancelled");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}");
|
||||
lock (_pipeLock)
|
||||
Debug.WriteLine($"Disposing NamedPipeServerStream on {GetType().Name}");
|
||||
lock(Pipe)
|
||||
{
|
||||
if (Pipe is { IsConnected: true })
|
||||
|
||||
Pipe?.Dispose();
|
||||
Pipe = null!;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task During(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ProcessDataAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Debug.WriteLine($"ProcessDataAsync on {GetType().Name} cancelled");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}");
|
||||
lock (Pipe ?? new object())
|
||||
//if Pipe is null, then the lock doesnt matter,
|
||||
//Because the next code will not execute anyways.
|
||||
//so we can use a new object
|
||||
{
|
||||
Pipe.Disconnect();
|
||||
if (Pipe is { IsConnected: true })
|
||||
{
|
||||
Pipe.Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task ProcessDataAsync(CancellationToken token);
|
||||
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
|
||||
{
|
||||
public string Text => "-map_metadata -1";
|
||||
/// <summary>
|
||||
/// Remove metadata argument
|
||||
/// </summary>
|
||||
public class RemoveMetadataArgument : IArgument
|
||||
{
|
||||
public string Text => "-map_metadata -1";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
using System.Drawing;
|
||||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents scale parameter
|
||||
/// </summary>
|
||||
public class ScaleArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly Size? Size;
|
||||
|
||||
public ScaleArgument(Size? size)
|
||||
/// <summary>
|
||||
/// Represents scale parameter
|
||||
/// </summary>
|
||||
public class ScaleArgument : IVideoFilterArgument
|
||||
{
|
||||
Size = size;
|
||||
public readonly Size? Size;
|
||||
public ScaleArgument(Size? size)
|
||||
{
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public ScaleArgument(int width, int height) : this(new Size(width, height)) { }
|
||||
|
||||
public ScaleArgument(VideoSize 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}";
|
||||
}
|
||||
|
||||
public ScaleArgument(int width, int height) : this(new Size(width, height)) { }
|
||||
|
||||
public ScaleArgument(VideoSize videosize)
|
||||
{
|
||||
Size = videosize == VideoSize.Original ? null : new Size(-1, (int)videosize);
|
||||
}
|
||||
|
||||
public string Key { get; } = "scale";
|
||||
public string Value => Size == null ? string.Empty : $"{Size.Value.Width}:{Size.Value.Height}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
using FFMpegCore.Extend;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents seek parameter
|
||||
/// </summary>
|
||||
public class SeekArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly TimeSpan? SeekTo;
|
||||
|
||||
public SeekArgument(TimeSpan? seekTo)
|
||||
/// <summary>
|
||||
/// Represents seek parameter
|
||||
/// </summary>
|
||||
public class SeekArgument : IArgument
|
||||
{
|
||||
SeekTo = seekTo;
|
||||
}
|
||||
public readonly TimeSpan? SeekTo;
|
||||
|
||||
public string Text => SeekTo.HasValue ? $"-ss {SeekTo.Value.ToLongString()}" : string.Empty;
|
||||
public SeekArgument(TimeSpan? seekTo)
|
||||
{
|
||||
SeekTo = seekTo;
|
||||
}
|
||||
|
||||
public string Text => SeekTo.HasValue ? $"-ss {SeekTo.Value.ToLongString()}" : string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,24 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class SetMirroringArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public SetMirroringArgument(Mirroring mirroring)
|
||||
public class SetMirroringArgument : IVideoFilterArgument
|
||||
{
|
||||
Mirroring = mirroring;
|
||||
}
|
||||
|
||||
public Mirroring Mirroring { get; set; }
|
||||
|
||||
public string Key => string.Empty;
|
||||
|
||||
public string Value =>
|
||||
Mirroring switch
|
||||
public SetMirroringArgument(Mirroring mirroring)
|
||||
{
|
||||
Mirroring.Horizontal => "hflip",
|
||||
Mirroring.Vertical => "vflip",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(Mirroring))
|
||||
};
|
||||
Mirroring = mirroring;
|
||||
}
|
||||
|
||||
public Mirroring Mirroring { get; set; }
|
||||
|
||||
public string Key => string.Empty;
|
||||
|
||||
public string Value =>
|
||||
Mirroring switch
|
||||
{
|
||||
Mirroring.Horizontal => "hflip",
|
||||
Mirroring.Vertical => "vflip",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(Mirroring))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents shortest parameter
|
||||
/// </summary>
|
||||
public class ShortestArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly bool Shortest;
|
||||
|
||||
public ShortestArgument(bool shortest)
|
||||
/// <summary>
|
||||
/// Represents shortest parameter
|
||||
/// </summary>
|
||||
public class ShortestArgument : IArgument
|
||||
{
|
||||
Shortest = shortest;
|
||||
}
|
||||
public readonly bool Shortest;
|
||||
|
||||
public string Text => Shortest ? "-shortest" : string.Empty;
|
||||
public ShortestArgument(bool shortest)
|
||||
{
|
||||
Shortest = shortest;
|
||||
}
|
||||
|
||||
public string Text => Shortest ? "-shortest" : string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,39 @@
|
|||
using System.Globalization;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class SilenceDetectArgument : IAudioFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
|
||||
/// <summary>
|
||||
/// Silence Detection. <see href="https://ffmpeg.org/ffmpeg-filters.html#silencedetect" />
|
||||
/// </summary>
|
||||
/// <param name="noise_type">Set noise type to db (decibel) or ar (amplitude ratio). Default is dB</param>
|
||||
/// <param name="noise">
|
||||
/// Set noise tolerance. Can be specified in dB (in case "dB" is appended to the specified value) or amplitude ratio.
|
||||
/// Default is -60dB, or 0.001.
|
||||
/// </param>
|
||||
/// <param name="duration">
|
||||
/// Set silence duration until notification (default is 2 seconds). See (ffmpeg-utils)the Time duration section in the
|
||||
/// ffmpeg-utils(1) manual for the accepted syntax.
|
||||
/// </param>
|
||||
/// <param name="mono">Process each channel separately, instead of combined. By default is disabled.</param>
|
||||
public SilenceDetectArgument(string noise_type = "db", double noise = 60, double duration = 2, bool mono = false)
|
||||
public class SilenceDetectArgument : IAudioFilterArgument
|
||||
{
|
||||
if (noise_type == "db")
|
||||
private readonly Dictionary<string, string> _arguments = new();
|
||||
/// <summary>
|
||||
/// Silence Detection. <see href="https://ffmpeg.org/ffmpeg-filters.html#silencedetect"/>
|
||||
/// </summary>
|
||||
/// <param name="noise_type">Set noise type to db (decibel) or ar (amplitude ratio). Default is dB</param>
|
||||
/// <param name="noise">Set noise tolerance. Can be specified in dB (in case "dB" is appended to the specified value) or amplitude ratio. Default is -60dB, or 0.001.</param>
|
||||
/// <param name="duration">Set silence duration until notification (default is 2 seconds). See (ffmpeg-utils)the Time duration section in the ffmpeg-utils(1) manual for the accepted syntax.</param>
|
||||
/// <param name="mono">Process each channel separately, instead of combined. By default is disabled.</param>
|
||||
|
||||
public SilenceDetectArgument(string noise_type = "db", double noise = 60, double duration = 2, bool mono = false)
|
||||
{
|
||||
_arguments.Add("n", $"{noise.ToString("0.0", CultureInfo.InvariantCulture)}dB");
|
||||
}
|
||||
else if (noise_type == "ar")
|
||||
{
|
||||
_arguments.Add("n", noise.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(noise_type), "Noise type must be either db or ar");
|
||||
if (noise_type == "db")
|
||||
{
|
||||
_arguments.Add("n", $"{noise.ToString("0.0", CultureInfo.InvariantCulture)}dB");
|
||||
}
|
||||
else if (noise_type == "ar")
|
||||
{
|
||||
_arguments.Add("n", noise.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(noise_type), "Noise type must be either db or ar");
|
||||
}
|
||||
|
||||
_arguments.Add("d", duration.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("m", (mono ? 1 : 0).ToString());
|
||||
}
|
||||
|
||||
_arguments.Add("d", duration.ToString("0.00", CultureInfo.InvariantCulture));
|
||||
_arguments.Add("m", (mono ? 1 : 0).ToString());
|
||||
public string Key { get; } = "silencedetect";
|
||||
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
|
||||
public string Key { get; } = "silencedetect";
|
||||
|
||||
public string Value => string.Join(":", _arguments.Select(pair => $"{pair.Key}={pair.Value}"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
using System.Drawing;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents size parameter
|
||||
/// </summary>
|
||||
public class SizeArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly Size? Size;
|
||||
|
||||
public SizeArgument(Size? size)
|
||||
/// <summary>
|
||||
/// Represents size parameter
|
||||
/// </summary>
|
||||
public class SizeArgument : IArgument
|
||||
{
|
||||
Size = size;
|
||||
public readonly Size? Size;
|
||||
public SizeArgument(Size? size)
|
||||
{
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public SizeArgument(int width, int height) : this(new Size(width, height)) { }
|
||||
|
||||
public string Text => Size == null ? string.Empty : $"-s {Size.Value.Width}x{Size.Value.Height}";
|
||||
}
|
||||
|
||||
public SizeArgument(int width, int height) : this(new Size(width, height)) { }
|
||||
|
||||
public string Text => Size == null ? string.Empty : $"-s {Size.Value.Width}x{Size.Value.Height}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents speed parameter
|
||||
/// </summary>
|
||||
public class SpeedPresetArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly Speed Speed;
|
||||
|
||||
public SpeedPresetArgument(Speed speed)
|
||||
/// <summary>
|
||||
/// Represents speed parameter
|
||||
/// </summary>
|
||||
public class SpeedPresetArgument : IArgument
|
||||
{
|
||||
Speed = speed;
|
||||
}
|
||||
public readonly Speed Speed;
|
||||
|
||||
public string Text => $"-preset {Speed.ToString().ToLowerInvariant()}";
|
||||
public SpeedPresetArgument(Speed speed)
|
||||
{
|
||||
Speed = speed;
|
||||
}
|
||||
|
||||
public string Text => $"-preset {Speed.ToString().ToLowerInvariant()}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents start number parameter
|
||||
/// </summary>
|
||||
public class StartNumberArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly int StartNumber;
|
||||
|
||||
public StartNumberArgument(int startNumber)
|
||||
/// <summary>
|
||||
/// Represents start number parameter
|
||||
/// </summary>
|
||||
public class StartNumberArgument : IArgument
|
||||
{
|
||||
StartNumber = startNumber;
|
||||
}
|
||||
public readonly int StartNumber;
|
||||
|
||||
public string Text => $"-start_number {StartNumber}";
|
||||
public StartNumberArgument(int startNumber)
|
||||
{
|
||||
StartNumber = startNumber;
|
||||
}
|
||||
|
||||
public string Text => $"-start_number {StartNumber}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,131 +1,132 @@
|
|||
using System.Drawing;
|
||||
using FFMpegCore.Extend;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class SubtitleHardBurnArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly SubtitleHardBurnOptions _subtitleHardBurnOptions;
|
||||
|
||||
public SubtitleHardBurnArgument(SubtitleHardBurnOptions subtitleHardBurnOptions)
|
||||
public class SubtitleHardBurnArgument : IVideoFilterArgument
|
||||
{
|
||||
_subtitleHardBurnOptions = subtitleHardBurnOptions;
|
||||
private readonly SubtitleHardBurnOptions _subtitleHardBurnOptions;
|
||||
|
||||
public SubtitleHardBurnArgument(SubtitleHardBurnOptions subtitleHardBurnOptions)
|
||||
{
|
||||
_subtitleHardBurnOptions = subtitleHardBurnOptions;
|
||||
}
|
||||
|
||||
public string Key => "subtitles";
|
||||
|
||||
public string Value => _subtitleHardBurnOptions.TextInternal;
|
||||
}
|
||||
|
||||
public string Key => "subtitles";
|
||||
|
||||
public string Value => _subtitleHardBurnOptions.TextInternal;
|
||||
}
|
||||
|
||||
public class SubtitleHardBurnOptions
|
||||
{
|
||||
private readonly string _subtitle;
|
||||
|
||||
public readonly Dictionary<string, string> Parameters = new();
|
||||
|
||||
private SubtitleHardBurnOptions(string subtitle)
|
||||
public class SubtitleHardBurnOptions
|
||||
{
|
||||
_subtitle = subtitle;
|
||||
private readonly string _subtitle;
|
||||
|
||||
public readonly Dictionary<string, string> Parameters = new();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SubtitleHardBurnOptions"/> using a provided subtitle file or a video file
|
||||
/// containing one.
|
||||
/// </summary>
|
||||
/// <param name="subtitlePath"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>Only support .srt and .ass files, and subrip and ssa subtitle streams</remarks>
|
||||
public static SubtitleHardBurnOptions Create(string subtitlePath)
|
||||
{
|
||||
return new SubtitleHardBurnOptions(subtitlePath);
|
||||
}
|
||||
|
||||
private SubtitleHardBurnOptions(string subtitle)
|
||||
{
|
||||
_subtitle = subtitle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the size of the original video, the video for which the ASS file was composed.
|
||||
/// </summary>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
/// <returns></returns>
|
||||
public SubtitleHardBurnOptions SetOriginalSize(int width, int height)
|
||||
{
|
||||
return WithParameter("original_size", $"{width}x{height}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the size of the original video, the video for which the ASS file was composed.
|
||||
/// </summary>
|
||||
/// <param name="size"></param>
|
||||
/// <returns></returns>
|
||||
public SubtitleHardBurnOptions SetOriginalSize(Size size)
|
||||
{
|
||||
return SetOriginalSize(size.Width, size.Height);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set subtitles stream index.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Used when the provided subtitle is an stream of a video file (ex. .mkv) with multiple subtitles.
|
||||
/// Represent the index of the subtitle not the stream, them the first subtitle index is 0 and second is 1
|
||||
/// </remarks>
|
||||
public SubtitleHardBurnOptions SetSubtitleIndex(int index)
|
||||
{
|
||||
return WithParameter("stream_index", index.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set subtitles input character encoding. Only useful if not UTF-8
|
||||
/// </summary>
|
||||
/// <param name="encode">Charset encoding</param>
|
||||
/// <returns></returns>
|
||||
public SubtitleHardBurnOptions SetCharacterEncoding(string encode)
|
||||
{
|
||||
return WithParameter("charenc", encode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override default style or script info parameters of the subtitles
|
||||
/// </summary>
|
||||
/// <param name="styleOptions"></param>
|
||||
/// <returns></returns>
|
||||
public SubtitleHardBurnOptions WithStyle(StyleOptions styleOptions)
|
||||
{
|
||||
return WithParameter("force_style", styleOptions.TextInternal);
|
||||
}
|
||||
|
||||
public SubtitleHardBurnOptions WithParameter(string key, string value)
|
||||
{
|
||||
Parameters.Add(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal string TextInternal => string
|
||||
.Join(":", new[] { StringExtensions.EncloseInQuotes(StringExtensions.ToFFmpegLibavfilterPath(_subtitle)) }
|
||||
.Concat(Parameters.Select(parameter => parameter.FormatArgumentPair(enclose: true))));
|
||||
}
|
||||
|
||||
internal string TextInternal => string
|
||||
.Join(":", new[] { StringExtensions.EncloseInQuotes(StringExtensions.ToFFmpegLibavfilterPath(_subtitle)) }
|
||||
.Concat(Parameters.Select(parameter => parameter.FormatArgumentPair(true))));
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SubtitleHardBurnOptions" /> using a provided subtitle file or a video file
|
||||
/// containing one.
|
||||
/// </summary>
|
||||
/// <param name="subtitlePath"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>Only support .srt and .ass files, and subrip and ssa subtitle streams</remarks>
|
||||
public static SubtitleHardBurnOptions Create(string subtitlePath)
|
||||
public class StyleOptions
|
||||
{
|
||||
return new SubtitleHardBurnOptions(subtitlePath);
|
||||
}
|
||||
public readonly Dictionary<string, string> Parameters = new();
|
||||
|
||||
/// <summary>
|
||||
/// Specify the size of the original video, the video for which the ASS file was composed.
|
||||
/// </summary>
|
||||
/// <param name="width"></param>
|
||||
/// <param name="height"></param>
|
||||
/// <returns></returns>
|
||||
public SubtitleHardBurnOptions SetOriginalSize(int width, int height)
|
||||
{
|
||||
return WithParameter("original_size", $"{width}x{height}");
|
||||
}
|
||||
public static StyleOptions Create()
|
||||
{
|
||||
return new StyleOptions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify the size of the original video, the video for which the ASS file was composed.
|
||||
/// </summary>
|
||||
/// <param name="size"></param>
|
||||
/// <returns></returns>
|
||||
public SubtitleHardBurnOptions SetOriginalSize(Size size)
|
||||
{
|
||||
return SetOriginalSize(size.Width, size.Height);
|
||||
}
|
||||
/// <summary>
|
||||
/// Used to override default style or script info parameters of the subtitles. It accepts ASS style format
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public StyleOptions WithParameter(string key, string value)
|
||||
{
|
||||
Parameters.Add(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set subtitles stream index.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Used when the provided subtitle is an stream of a video file (ex. .mkv) with multiple subtitles.
|
||||
/// Represent the index of the subtitle not the stream, them the first subtitle index is 0 and second is 1
|
||||
/// </remarks>
|
||||
public SubtitleHardBurnOptions SetSubtitleIndex(int index)
|
||||
{
|
||||
return WithParameter("stream_index", index.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set subtitles input character encoding. Only useful if not UTF-8
|
||||
/// </summary>
|
||||
/// <param name="encode">Charset encoding</param>
|
||||
/// <returns></returns>
|
||||
public SubtitleHardBurnOptions SetCharacterEncoding(string encode)
|
||||
{
|
||||
return WithParameter("charenc", encode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override default style or script info parameters of the subtitles
|
||||
/// </summary>
|
||||
/// <param name="styleOptions"></param>
|
||||
/// <returns></returns>
|
||||
public SubtitleHardBurnOptions WithStyle(StyleOptions styleOptions)
|
||||
{
|
||||
return WithParameter("force_style", styleOptions.TextInternal);
|
||||
}
|
||||
|
||||
public SubtitleHardBurnOptions WithParameter(string key, string value)
|
||||
{
|
||||
Parameters.Add(key, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class StyleOptions
|
||||
{
|
||||
public readonly Dictionary<string, string> Parameters = new();
|
||||
|
||||
internal string TextInternal => string.Join(",", Parameters.Select(parameter => parameter.FormatArgumentPair(false)));
|
||||
|
||||
public static StyleOptions Create()
|
||||
{
|
||||
return new StyleOptions();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to override default style or script info parameters of the subtitles. It accepts ASS style format
|
||||
/// </summary>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public StyleOptions WithParameter(string key, string value)
|
||||
{
|
||||
Parameters.Add(key, value);
|
||||
return this;
|
||||
internal string TextInternal => string.Join(",", Parameters.Select(parameter => parameter.FormatArgumentPair(enclose: false)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents threads parameter
|
||||
/// Number of threads used for video encoding
|
||||
/// </summary>
|
||||
public class ThreadsArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly int Threads;
|
||||
|
||||
public ThreadsArgument(int threads)
|
||||
/// <summary>
|
||||
/// Represents threads parameter
|
||||
/// Number of threads used for video encoding
|
||||
/// </summary>
|
||||
public class ThreadsArgument : IArgument
|
||||
{
|
||||
Threads = threads;
|
||||
public readonly int Threads;
|
||||
public ThreadsArgument(int threads)
|
||||
{
|
||||
Threads = threads;
|
||||
}
|
||||
|
||||
public ThreadsArgument(bool isMultiThreaded) : this(isMultiThreaded ? Environment.ProcessorCount : 1) { }
|
||||
|
||||
public string Text => $"-threads {Threads}";
|
||||
}
|
||||
|
||||
public ThreadsArgument(bool isMultiThreaded) : this(isMultiThreaded ? Environment.ProcessorCount : 1) { }
|
||||
|
||||
public string Text => $"-threads {Threads}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
using FFMpegCore.Enums;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Transpose argument.
|
||||
/// 0 = 90CounterCLockwise and Vertical Flip (default)
|
||||
/// 1 = 90Clockwise
|
||||
/// 2 = 90CounterClockwise
|
||||
/// 3 = 90Clockwise and Vertical Flip
|
||||
/// </summary>
|
||||
public class TransposeArgument : IVideoFilterArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly Transposition Transposition;
|
||||
|
||||
public TransposeArgument(Transposition transposition)
|
||||
/// <summary>
|
||||
/// Transpose argument.
|
||||
/// 0 = 90CounterCLockwise and Vertical Flip (default)
|
||||
/// 1 = 90Clockwise
|
||||
/// 2 = 90CounterClockwise
|
||||
/// 3 = 90Clockwise and Vertical Flip
|
||||
/// </summary>
|
||||
public class TransposeArgument : IVideoFilterArgument
|
||||
{
|
||||
Transposition = transposition;
|
||||
}
|
||||
public readonly Transposition Transposition;
|
||||
public TransposeArgument(Transposition transposition)
|
||||
{
|
||||
Transposition = transposition;
|
||||
}
|
||||
|
||||
public string Key { get; } = "transpose";
|
||||
public string Value => ((int)Transposition).ToString();
|
||||
public string Key { get; } = "transpose";
|
||||
public string Value => ((int)Transposition).ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,22 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Variable Bitrate Argument (VBR) argument
|
||||
/// </summary>
|
||||
public class VariableBitRateArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly int Vbr;
|
||||
|
||||
public VariableBitRateArgument(int vbr)
|
||||
/// <summary>
|
||||
/// Variable Bitrate Argument (VBR) argument
|
||||
/// </summary>
|
||||
public class VariableBitRateArgument : IArgument
|
||||
{
|
||||
if (vbr < 0 || vbr > 5)
|
||||
public readonly int Vbr;
|
||||
|
||||
public VariableBitRateArgument(int vbr)
|
||||
{
|
||||
throw new ArgumentException("Argument is outside range (0 - 5)", nameof(vbr));
|
||||
if (vbr < 0 || vbr > 5)
|
||||
{
|
||||
throw new ArgumentException("Argument is outside range (0 - 5)", nameof(vbr));
|
||||
}
|
||||
|
||||
Vbr = vbr;
|
||||
}
|
||||
|
||||
Vbr = vbr;
|
||||
public string Text => $"-vbr {Vbr}";
|
||||
}
|
||||
|
||||
public string Text => $"-vbr {Vbr}";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class VerbosityLevelArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
private readonly VerbosityLevel _verbosityLevel;
|
||||
|
||||
public VerbosityLevelArgument(VerbosityLevel verbosityLevel)
|
||||
public class VerbosityLevelArgument : IArgument
|
||||
{
|
||||
_verbosityLevel = verbosityLevel;
|
||||
private readonly VerbosityLevel _verbosityLevel;
|
||||
|
||||
public VerbosityLevelArgument(VerbosityLevel verbosityLevel)
|
||||
{
|
||||
_verbosityLevel = verbosityLevel;
|
||||
}
|
||||
public string Text => $"{((int)_verbosityLevel < 32 ? "-hide_banner " : "")}-loglevel {_verbosityLevel.ToString().ToLowerInvariant()}";
|
||||
}
|
||||
|
||||
public string Text => $"{((int)_verbosityLevel < 32 ? "-hide_banner " : "")}-loglevel {_verbosityLevel.ToString().ToLowerInvariant()}";
|
||||
}
|
||||
|
||||
public enum VerbosityLevel
|
||||
{
|
||||
Quiet = -8,
|
||||
Fatal = 8,
|
||||
Error = 16,
|
||||
Warning = 24,
|
||||
Info = 32,
|
||||
Verbose = 40,
|
||||
Debug = 48,
|
||||
Trace = 56
|
||||
public enum VerbosityLevel
|
||||
{
|
||||
Quiet = -8,
|
||||
Fatal = 8,
|
||||
Error = 16,
|
||||
Warning = 24,
|
||||
Info = 32,
|
||||
Verbose = 40,
|
||||
Debug = 48,
|
||||
Trace = 56
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents video bitrate parameter
|
||||
/// </summary>
|
||||
public class VideoBitrateArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly int Bitrate;
|
||||
|
||||
public VideoBitrateArgument(int bitrate)
|
||||
/// <summary>
|
||||
/// Represents video bitrate parameter
|
||||
/// </summary>
|
||||
public class VideoBitrateArgument : IArgument
|
||||
{
|
||||
Bitrate = bitrate;
|
||||
}
|
||||
public readonly int Bitrate;
|
||||
|
||||
public string Text => $"-b:v {Bitrate}k";
|
||||
public VideoBitrateArgument(int bitrate)
|
||||
{
|
||||
Bitrate = bitrate;
|
||||
}
|
||||
|
||||
public string Text => $"-b:v {Bitrate}k";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
/// <summary>
|
||||
/// Represents video codec parameter
|
||||
/// </summary>
|
||||
public class VideoCodecArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly string Codec;
|
||||
|
||||
public VideoCodecArgument(string codec)
|
||||
/// <summary>
|
||||
/// Represents video codec parameter
|
||||
/// </summary>
|
||||
public class VideoCodecArgument : IArgument
|
||||
{
|
||||
Codec = codec;
|
||||
}
|
||||
public readonly string Codec;
|
||||
|
||||
public VideoCodecArgument(Codec value)
|
||||
{
|
||||
if (value.Type != CodecType.Video)
|
||||
public VideoCodecArgument(string codec)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{value.Name}\" is not a video codec");
|
||||
Codec = codec;
|
||||
}
|
||||
|
||||
Codec = value.Name;
|
||||
}
|
||||
public VideoCodecArgument(Codec value)
|
||||
{
|
||||
if (value.Type != CodecType.Video)
|
||||
{
|
||||
throw new FFMpegException(FFMpegExceptionType.Operation, $"Codec \"{value.Name}\" is not a video codec");
|
||||
}
|
||||
|
||||
public string Text => $"-c:v {Codec}";
|
||||
Codec = value.Name;
|
||||
}
|
||||
|
||||
public string Text => $"-c:v {Codec}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,101 +2,63 @@
|
|||
using FFMpegCore.Enums;
|
||||
using FFMpegCore.Exceptions;
|
||||
|
||||
namespace FFMpegCore.Arguments;
|
||||
|
||||
public class VideoFiltersArgument : IArgument
|
||||
namespace FFMpegCore.Arguments
|
||||
{
|
||||
public readonly VideoFilterOptions Options;
|
||||
|
||||
public VideoFiltersArgument(VideoFilterOptions options)
|
||||
public class VideoFiltersArgument : IArgument
|
||||
{
|
||||
Options = options;
|
||||
}
|
||||
public readonly VideoFilterOptions Options;
|
||||
|
||||
public string Text => GetText();
|
||||
|
||||
private string GetText()
|
||||
{
|
||||
if (!Options.Arguments.Any())
|
||||
public VideoFiltersArgument(VideoFilterOptions options)
|
||||
{
|
||||
throw new FFMpegArgumentException("No video-filter arguments provided");
|
||||
Options = options;
|
||||
}
|
||||
|
||||
var arguments = Options.Arguments
|
||||
.Where(arg => !string.IsNullOrEmpty(arg.Value))
|
||||
.Select(arg =>
|
||||
public string Text => GetText();
|
||||
|
||||
private string GetText()
|
||||
{
|
||||
if (!Options.Arguments.Any())
|
||||
{
|
||||
var escapedValue = arg.Value.Replace(",", "\\,");
|
||||
return string.IsNullOrEmpty(arg.Key) ? escapedValue : $"{arg.Key}={escapedValue}";
|
||||
});
|
||||
throw new FFMpegArgumentException("No video-filter arguments provided");
|
||||
}
|
||||
|
||||
return $"-vf \"{string.Join(", ", arguments)}\"";
|
||||
}
|
||||
}
|
||||
|
||||
public interface IVideoFilterArgument
|
||||
{
|
||||
string Key { get; }
|
||||
string Value { get; }
|
||||
}
|
||||
|
||||
public class VideoFilterOptions
|
||||
{
|
||||
public List<IVideoFilterArgument> Arguments { get; } = new();
|
||||
|
||||
public VideoFilterOptions Scale(VideoSize videoSize)
|
||||
{
|
||||
return WithArgument(new ScaleArgument(videoSize));
|
||||
}
|
||||
|
||||
public VideoFilterOptions Scale(int width, int height)
|
||||
{
|
||||
return WithArgument(new ScaleArgument(width, height));
|
||||
}
|
||||
|
||||
public VideoFilterOptions Scale(Size size)
|
||||
{
|
||||
return WithArgument(new ScaleArgument(size));
|
||||
}
|
||||
|
||||
public VideoFilterOptions Transpose(Transposition transposition)
|
||||
{
|
||||
return WithArgument(new TransposeArgument(transposition));
|
||||
}
|
||||
|
||||
public VideoFilterOptions Mirror(Mirroring mirroring)
|
||||
{
|
||||
return WithArgument(new SetMirroringArgument(mirroring));
|
||||
}
|
||||
|
||||
public VideoFilterOptions DrawText(DrawTextOptions drawTextOptions)
|
||||
{
|
||||
return WithArgument(new DrawTextArgument(drawTextOptions));
|
||||
}
|
||||
|
||||
public VideoFilterOptions HardBurnSubtitle(SubtitleHardBurnOptions subtitleHardBurnOptions)
|
||||
{
|
||||
return WithArgument(new SubtitleHardBurnArgument(subtitleHardBurnOptions));
|
||||
}
|
||||
|
||||
public VideoFilterOptions BlackDetect(double minimumDuration = 2.0, double pictureBlackRatioThreshold = 0.98, double pixelBlackThreshold = 0.1)
|
||||
{
|
||||
return WithArgument(new BlackDetectArgument(minimumDuration, pictureBlackRatioThreshold, pixelBlackThreshold));
|
||||
}
|
||||
|
||||
public VideoFilterOptions BlackFrame(int amount = 98, int threshold = 32)
|
||||
{
|
||||
return WithArgument(new BlackFrameArgument(amount, threshold));
|
||||
}
|
||||
|
||||
public VideoFilterOptions Pad(PadOptions padOptions)
|
||||
{
|
||||
return WithArgument(new PadArgument(padOptions));
|
||||
}
|
||||
|
||||
private VideoFilterOptions WithArgument(IVideoFilterArgument argument)
|
||||
{
|
||||
Arguments.Add(argument);
|
||||
return this;
|
||||
var arguments = Options.Arguments
|
||||
.Where(arg => !string.IsNullOrEmpty(arg.Value))
|
||||
.Select(arg =>
|
||||
{
|
||||
var escapedValue = arg.Value.Replace(",", "\\,");
|
||||
return string.IsNullOrEmpty(arg.Key) ? escapedValue : $"{arg.Key}={escapedValue}";
|
||||
});
|
||||
|
||||
return $"-vf \"{string.Join(", ", arguments)}\"";
|
||||
}
|
||||
}
|
||||
|
||||
public interface IVideoFilterArgument
|
||||
{
|
||||
public string Key { get; }
|
||||
public string Value { get; }
|
||||
}
|
||||
|
||||
public class VideoFilterOptions
|
||||
{
|
||||
public List<IVideoFilterArgument> Arguments { get; } = new();
|
||||
|
||||
public VideoFilterOptions Scale(VideoSize videoSize) => WithArgument(new ScaleArgument(videoSize));
|
||||
public VideoFilterOptions Scale(int width, int height) => WithArgument(new ScaleArgument(width, height));
|
||||
public VideoFilterOptions Scale(Size size) => WithArgument(new ScaleArgument(size));
|
||||
public VideoFilterOptions Transpose(Transposition transposition) => WithArgument(new TransposeArgument(transposition));
|
||||
public VideoFilterOptions Mirror(Mirroring mirroring) => WithArgument(new SetMirroringArgument(mirroring));
|
||||
public VideoFilterOptions DrawText(DrawTextOptions drawTextOptions) => WithArgument(new DrawTextArgument(drawTextOptions));
|
||||
public VideoFilterOptions HardBurnSubtitle(SubtitleHardBurnOptions subtitleHardBurnOptions) => WithArgument(new SubtitleHardBurnArgument(subtitleHardBurnOptions));
|
||||
public VideoFilterOptions BlackDetect(double minimumDuration = 2.0, double pictureBlackRatioThreshold = 0.98, double pixelBlackThreshold = 0.1) => WithArgument(new BlackDetectArgument(minimumDuration, pictureBlackRatioThreshold, pixelBlackThreshold));
|
||||
public VideoFilterOptions BlackFrame(int amount = 98, int threshold = 32) => WithArgument(new BlackFrameArgument(amount, threshold));
|
||||
public VideoFilterOptions Pad(PadOptions padOptions) => WithArgument(new PadArgument(padOptions));
|
||||
|
||||
private VideoFilterOptions WithArgument(IVideoFilterArgument argument)
|
||||
{
|
||||
Arguments.Add(argument);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue