diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 91fbab6..0da1b83 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,28 +1,24 @@
name: CI
on:
- push:
- branches:
- - master
- paths:
- - .github/workflows/ci.yml
- - FFMpegCore/**
- - FFMpegCore.Test/**
pull_request:
branches:
- main
- release
paths:
- - .github/workflows/ci.yml
- - FFMpegCore/**
- - FFMpegCore.Test/**
+ - .github/workflows/ci.yml
+ - Directory.Build.props
+ - FFMpegCore/**
+ - FFMpegCore.Test/**
+ - FFMpegCore.Extensions.SkiaSharp/**
+ - FFMpegCore.Extensions.System.Drawing.Common/**
jobs:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
- os: [windows-latest, ubuntu-latest, macos-13]
+ os: [windows-latest, ubuntu-latest, macos-latest]
timeout-minutes: 7
steps:
@@ -34,21 +30,24 @@ jobs:
with:
dotnet-version: '8.0.x'
- - name: Lint with dotnet
+ - if: matrix.os == 'ubuntu-latest'
+ name: Lint with dotnet
run: dotnet format FFMpegCore.sln --severity warn --verify-no-changes
- - name: Prepare FFMpeg
- uses: FedericoCarboni/setup-ffmpeg@v3
+ - name: Setup FFmpeg
+ uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae # 1.1.0
with:
- token: ${{ secrets.GITHUB_TOKEN }}
+ version: ${{ matrix.os != 'macos-latest' && '7.1' || '711' }}
+ token: ${{ github.token }}
- name: Test with dotnet
run: dotnet test FFMpegCore.sln --collect "XPlat Code Coverage" --logger GitHubActions
- if: matrix.os == 'windows-latest'
name: Upload coverage reports to Codecov
- uses: codecov/codecov-action@v5
+ uses: codecov/codecov-action@v4
with:
fail_ci_if_error: true
directory: FFMpegCore.Test/TestResults
token: ${{ secrets.CODECOV_TOKEN }}
+ os: windows
diff --git a/Directory.Build.props b/Directory.Build.props
index 2944c6b..628195a 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -13,5 +13,14 @@
https://github.com/rosenbjerg/FFMpegCore
MIT
en
+
+ true
+ true
+ snupkg
+ true
+
+
+ true
+
\ No newline at end of file
diff --git a/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj b/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj
index 3b16e06..3609ddf 100644
--- a/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj
+++ b/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj
@@ -3,18 +3,17 @@
true
Image extension for FFMpegCore using SkiaSharp
- 5.0.0
+ 5.0.2
../nupkg
-
-
+ Bump dependencies
ffmpeg ffprobe convert video audio mediafile resize analyze muxing skiasharp
Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev, Dimitri Vranken
true
-
-
+
+
diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj
index ebd7c9a..3a45aa5 100644
--- a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj
+++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj
@@ -3,17 +3,16 @@
true
Image extension for FFMpegCore using System.Common.Drawing
- 5.0.0
+ 5.0.2
../nupkg
-
-
+ Bump dependencies
ffmpeg ffprobe convert video audio mediafile resize analyze muxing
Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev
true
-
+
diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs
index cf455c8..ce39c9d 100644
--- a/FFMpegCore.Test/ArgumentBuilderTest.cs
+++ b/FFMpegCore.Test/ArgumentBuilderTest.cs
@@ -9,6 +9,7 @@ namespace FFMpegCore.Test
public class ArgumentBuilderTest
{
private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4" };
+ private readonly string[] _multiFiles = { "1.mp3", "2.mp3", "3.mp3", "4.mp3" };
[TestMethod]
public void Builder_BuildString_IO_1()
@@ -611,5 +612,86 @@ namespace FFMpegCore.Test
-i "input.mp4" -f tee "[movflags=faststart]output.mp4|[f=mpegts:select=\'0:v:0\']http://server/path"
""", str);
}
+ [TestMethod]
+ public void Builder_BuildString_MultiInput()
+ {
+ var audioStreams = string.Join("", _multiFiles.Select((item, index) => $"[{index}:0]"));
+ var mixFilter = $"{audioStreams}amix=inputs={_multiFiles.Length}:duration=longest:dropout_transition=1:normalize=0[final]";
+ var ffmpegArgs = $"-filter_complex \"{mixFilter}\" -map \"[final]\"";
+ var str = FFMpegArguments
+ .FromFileInput(_multiFiles)
+ .OutputToFile("output.mp3", overwrite: true, options => options
+ .WithCustomArgument(ffmpegArgs)
+ .WithAudioCodec(AudioCodec.LibMp3Lame) // Set the audio codec to MP3
+ .WithAudioBitrate(128) // Set the bitrate to 128kbps
+ .WithAudioSamplingRate(48000) // Set the sample rate to 48kHz
+ .WithoutMetadata() // Remove metadata
+ .WithCustomArgument("-ac 2 -write_xing 0 -id3v2_version 0")) // Force 2 Channels
+ .Arguments;
+ Assert.AreEqual($"-i \"1.mp3\" -i \"2.mp3\" -i \"3.mp3\" -i \"4.mp3\" -filter_complex \"[0:0][1:0][2:0][3:0]amix=inputs=4:duration=longest:dropout_transition=1:normalize=0[final]\" -map \"[final]\" -c:a libmp3lame -b:a 128k -ar 48000 -map_metadata -1 -ac 2 -write_xing 0 -id3v2_version 0 \"output.mp3\" -y", str);
+ }
+ [TestMethod]
+ public void Pre_VerifyExists_AllFilesExist()
+ {
+ // Arrange
+ var filePaths = new List
+ {
+ Path.GetTempFileName(),
+ Path.GetTempFileName(),
+ Path.GetTempFileName()
+ };
+ var argument = new MultiInputArgument(true, filePaths);
+ try
+ {
+ // Act & Assert
+ argument.Pre(); // No exception should be thrown
+ }
+ finally
+ {
+ // Cleanup
+ foreach (var filePath in filePaths)
+ {
+ File.Delete(filePath);
+ }
+ }
+ }
+
+ [TestMethod]
+ public void Pre_VerifyExists_SomeFilesNotExist()
+ {
+ // Arrange
+ var filePaths = new List
+ {
+ Path.GetTempFileName(),
+ "file2.mp4",
+ "file3.mp4"
+ };
+ var argument = new MultiInputArgument(true, filePaths);
+ try
+ {
+ // Act & Assert
+ Assert.ThrowsException(() => argument.Pre());
+ }
+ finally
+ {
+ // Cleanup
+ File.Delete(filePaths[0]);
+ }
+ }
+
+ [TestMethod]
+ public void Pre_VerifyExists_NoFilesExist()
+ {
+ // Arrange
+ var filePaths = new List
+ {
+ "file1.mp4",
+ "file2.mp4",
+ "file3.mp4"
+ };
+ var argument = new MultiInputArgument(true, filePaths);
+ // Act & Assert
+ Assert.ThrowsException(() => argument.Pre());
+ }
}
}
diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj
index 84b863f..8413f84 100644
--- a/FFMpegCore.Test/FFMpegCore.Test.csproj
+++ b/FFMpegCore.Test/FFMpegCore.Test.csproj
@@ -8,19 +8,19 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
+
+
+
+
diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs
index 8da9c19..ce23660 100644
--- a/FFMpegCore.Test/VideoTest.cs
+++ b/FFMpegCore.Test/VideoTest.cs
@@ -467,7 +467,7 @@ namespace FFMpegCore.Test
}
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
- public void Video_Snapshot_PersistSnapshot()
+ public void Video_Snapshot_Png_PersistSnapshot()
{
using var outputPath = new TemporaryFile("out.png");
var input = FFProbe.Analyse(TestResources.Mp4Video);
@@ -480,6 +480,63 @@ namespace FFMpegCore.Test
Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
}
+ [TestMethod, Timeout(BaseTimeoutMilliseconds)]
+ public void Video_Snapshot_Jpg_PersistSnapshot()
+ {
+ using var outputPath = new TemporaryFile("out.jpg");
+ var input = FFProbe.Analyse(TestResources.Mp4Video);
+
+ FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
+
+ var analysis = FFProbe.Analyse(outputPath);
+ Assert.AreEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
+ Assert.AreEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
+ Assert.AreEqual("mjpeg", analysis.PrimaryVideoStream!.CodecName);
+ }
+
+ [TestMethod, Timeout(BaseTimeoutMilliseconds)]
+ public void Video_Snapshot_Bmp_PersistSnapshot()
+ {
+ using var outputPath = new TemporaryFile("out.bmp");
+ var input = FFProbe.Analyse(TestResources.Mp4Video);
+
+ FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
+
+ var analysis = FFProbe.Analyse(outputPath);
+ Assert.AreEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
+ Assert.AreEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
+ Assert.AreEqual("bmp", analysis.PrimaryVideoStream!.CodecName);
+ }
+
+ [TestMethod, Timeout(BaseTimeoutMilliseconds)]
+ public void Video_Snapshot_Webp_PersistSnapshot()
+ {
+ using var outputPath = new TemporaryFile("out.webp");
+ var input = FFProbe.Analyse(TestResources.Mp4Video);
+
+ FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
+
+ var analysis = FFProbe.Analyse(outputPath);
+ Assert.AreEqual(input.PrimaryVideoStream!.Width, analysis.PrimaryVideoStream!.Width);
+ Assert.AreEqual(input.PrimaryVideoStream.Height, analysis.PrimaryVideoStream!.Height);
+ Assert.AreEqual("webp", analysis.PrimaryVideoStream!.CodecName);
+ }
+
+ [TestMethod, Timeout(BaseTimeoutMilliseconds)]
+ public void Video_Snapshot_Exception_PersistSnapshot()
+ {
+ using var outputPath = new TemporaryFile("out.asd");
+
+ try
+ {
+ FFMpeg.Snapshot(TestResources.Mp4Video, outputPath);
+ }
+ catch (Exception ex)
+ {
+ Assert.IsTrue(ex is ArgumentException);
+ }
+ }
+
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
public void Video_Snapshot_Rotated_PersistSnapshot()
{
diff --git a/FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs b/FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs
index d44d18d..c7b6b56 100644
--- a/FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs
+++ b/FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs
@@ -8,6 +8,6 @@
///
///
//public string GetText(StringBuilder context);
- public string GetText(IEnumerable context);
+ string GetText(IEnumerable context);
}
}
diff --git a/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs b/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs
new file mode 100644
index 0000000..288c761
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs
@@ -0,0 +1,47 @@
+namespace FFMpegCore.Arguments
+{
+ ///
+ /// Represents input parameters for multiple files
+ ///
+ public class MultiInputArgument : IInputArgument
+ {
+ public readonly bool VerifyExists;
+ public readonly IEnumerable FilePaths;
+
+ public MultiInputArgument(bool verifyExists, IEnumerable filePaths)
+ {
+ VerifyExists = verifyExists;
+ FilePaths = filePaths;
+ }
+
+ public MultiInputArgument(IEnumerable filePaths, bool verifyExists) : this(verifyExists, filePaths) { }
+
+ public void Pre()
+ {
+ if (VerifyExists)
+ {
+ var missingFiles = new List();
+ foreach (var filePath in FilePaths)
+ {
+ if (!File.Exists(filePath))
+ {
+ missingFiles.Add(filePath);
+ }
+ }
+
+ 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() { }
+
+ ///
+ /// Generates a combined input argument text for all file paths
+ ///
+ public string Text => string.Join(" ", FilePaths.Select(filePath => $"-i \"{filePath}\""));
+ }
+}
diff --git a/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs b/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs
index f7a9e4a..4a43c9d 100644
--- a/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs
+++ b/FFMpegCore/FFMpeg/Arguments/VideoFiltersArgument.cs
@@ -36,8 +36,8 @@ namespace FFMpegCore.Arguments
public interface IVideoFilterArgument
{
- public string Key { get; }
- public string Value { get; }
+ string Key { get; }
+ string Value { get; }
}
public class VideoFilterOptions
diff --git a/FFMpegCore/FFMpeg/Enums/Enums.cs b/FFMpegCore/FFMpeg/Enums/Enums.cs
index 1f00203..d777ecb 100644
--- a/FFMpegCore/FFMpeg/Enums/Enums.cs
+++ b/FFMpegCore/FFMpeg/Enums/Enums.cs
@@ -15,9 +15,33 @@
public static Codec LibX265 => FFMpeg.GetCodec("libx265");
public static Codec LibVpx => FFMpeg.GetCodec("libvpx");
public static Codec LibTheora => FFMpeg.GetCodec("libtheora");
- public static Codec Png => FFMpeg.GetCodec("png");
public static Codec MpegTs => FFMpeg.GetCodec("mpegts");
public static Codec LibaomAv1 => FFMpeg.GetCodec("libaom-av1");
+
+ public static class Image
+ {
+ public static Codec Png => FFMpeg.GetCodec("png");
+ public static Codec Jpg => FFMpeg.GetCodec("mjpeg");
+ public static Codec Bmp => FFMpeg.GetCodec("bmp");
+ public static Codec Webp => FFMpeg.GetCodec("webp");
+
+ public static Codec GetByExtension(string path)
+ {
+ var ext = Path.GetExtension(path);
+ switch (ext)
+ {
+ case FileExtension.Image.Png:
+ return Png;
+ case FileExtension.Image.Jpg:
+ return Jpg;
+ case FileExtension.Image.Bmp:
+ return Bmp;
+ case FileExtension.Image.Webp:
+ return Webp;
+ default: throw new NotSupportedException($"Unsupported image extension: {ext}");
+ }
+ }
+ }
}
public static class AudioCodec
diff --git a/FFMpegCore/FFMpeg/Enums/FileExtension.cs b/FFMpegCore/FFMpeg/Enums/FileExtension.cs
index f3067ba..386ef33 100644
--- a/FFMpegCore/FFMpeg/Enums/FileExtension.cs
+++ b/FFMpegCore/FFMpeg/Enums/FileExtension.cs
@@ -10,7 +10,10 @@
"libxvpx" => WebM,
"libxtheora" => Ogv,
"mpegts" => Ts,
- "png" => Png,
+ "png" => Image.Png,
+ "jpg" => Image.Jpg,
+ "bmp" => Image.Bmp,
+ "webp" => Image.Webp,
_ => throw new Exception("The extension for this video type is not defined.")
};
}
@@ -18,8 +21,16 @@
public static readonly string Ts = VideoType.MpegTs.Extension;
public static readonly string Ogv = VideoType.Ogv.Extension;
public static readonly string WebM = VideoType.WebM.Extension;
- public static readonly string Png = ".png";
public static readonly string Mp3 = ".mp3";
public static readonly string Gif = ".gif";
+
+ public static class Image
+ {
+ public const string Png = ".png";
+ public const string Jpg = ".jpg";
+ public const string Bmp = ".bmp";
+ public const string Webp = ".webp";
+ public static readonly List All = [Png, Jpg, Bmp, Webp];
+ }
}
}
diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs
index 820d9fb..cc9f94a 100644
--- a/FFMpegCore/FFMpeg/FFMpeg.cs
+++ b/FFMpegCore/FFMpeg/FFMpeg.cs
@@ -20,16 +20,11 @@ namespace FFMpegCore
/// Bitmap with the requested snapshot.
public static bool Snapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
{
- if (Path.GetExtension(output) != FileExtension.Png)
- {
- output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Png);
- }
+ CheckSnapshotOutputExtension(output, FileExtension.Image.All);
var source = FFProbe.Analyse(input);
- var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
- return arguments
- .OutputToFile(output, true, outputOptions)
+ return SnapshotProcess(input, output, source, size, captureTime, streamIndex, inputFileIndex)
.ProcessSynchronously();
}
///
@@ -44,47 +39,55 @@ namespace FFMpegCore
/// Bitmap with the requested snapshot.
public static async Task SnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
{
- if (Path.GetExtension(output) != FileExtension.Png)
- {
- output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Png);
- }
+ CheckSnapshotOutputExtension(output, FileExtension.Image.All);
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
- var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, source, size, captureTime, streamIndex, inputFileIndex);
- return await arguments
- .OutputToFile(output, true, outputOptions)
+ return await SnapshotProcess(input, output, source, size, captureTime, streamIndex, inputFileIndex)
.ProcessAsynchronously();
}
public static bool GifSnapshot(string input, string output, Size? size = null, TimeSpan? captureTime = null, TimeSpan? duration = null, int? streamIndex = null)
{
- if (Path.GetExtension(output)?.ToLower() != FileExtension.Gif)
- {
- output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Gif);
- }
+ CheckSnapshotOutputExtension(output, [FileExtension.Gif]);
var source = FFProbe.Analyse(input);
- var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildGifSnapshotArguments(input, source, size, captureTime, duration, streamIndex);
- return arguments
- .OutputToFile(output, true, outputOptions)
+ return GifSnapshotProcess(input, output, source, size, captureTime, duration, streamIndex)
.ProcessSynchronously();
}
public static async Task GifSnapshotAsync(string input, string output, Size? size = null, TimeSpan? captureTime = null, TimeSpan? duration = null, int? streamIndex = null)
{
- if (Path.GetExtension(output)?.ToLower() != FileExtension.Gif)
- {
- output = Path.Combine(Path.GetDirectoryName(output), Path.GetFileNameWithoutExtension(output) + FileExtension.Gif);
- }
+ CheckSnapshotOutputExtension(output, [FileExtension.Gif]);
var source = await FFProbe.AnalyseAsync(input).ConfigureAwait(false);
+
+ return await GifSnapshotProcess(input, output, source, size, captureTime, duration, streamIndex)
+ .ProcessAsynchronously();
+ }
+
+ private static FFMpegArgumentProcessor SnapshotProcess(string input, string output, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, int? streamIndex = null, int inputFileIndex = 0)
+ {
+ var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildSnapshotArguments(input, output, source, size, captureTime, streamIndex, inputFileIndex);
+
+ return arguments.OutputToFile(output, true, outputOptions);
+ }
+
+ private static FFMpegArgumentProcessor GifSnapshotProcess(string input, string output, IMediaAnalysis source, Size? size = null, TimeSpan? captureTime = null, TimeSpan? duration = null, int? streamIndex = null)
+ {
var (arguments, outputOptions) = SnapshotArgumentBuilder.BuildGifSnapshotArguments(input, source, size, captureTime, duration, streamIndex);
- return await arguments
- .OutputToFile(output, true, outputOptions)
- .ProcessAsynchronously();
+ return arguments.OutputToFile(output, true, outputOptions);
+ }
+
+ private static void CheckSnapshotOutputExtension(string output, List extensions)
+ {
+ if (!extensions.Contains(Path.GetExtension(output).ToLower()))
+ {
+ throw new ArgumentException(
+ $"Invalid snapshot output extension: {output}, needed: {string.Join(",", FileExtension.Image.All)}");
+ }
}
///
diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs
index cfc6d9d..ddb1f72 100644
--- a/FFMpegCore/FFMpeg/FFMpegArguments.cs
+++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs
@@ -21,6 +21,7 @@ namespace FFMpegCore
public static FFMpegArguments FromConcatInput(IEnumerable filePaths, Action? addArguments = null) => new FFMpegArguments().WithInput(new ConcatArgument(filePaths), addArguments);
public static FFMpegArguments FromDemuxConcatInput(IEnumerable filePaths, Action? addArguments = null) => new FFMpegArguments().WithInput(new DemuxConcatArgument(filePaths), addArguments);
public static FFMpegArguments FromFileInput(string filePath, bool verifyExists = true, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(verifyExists, filePath), addArguments);
+ public static FFMpegArguments FromFileInput(IEnumerable filePath, bool verifyExists = true, Action? addArguments = null) => new FFMpegArguments().WithInput(new MultiInputArgument(verifyExists, filePath), addArguments);
public static FFMpegArguments FromFileInput(FileInfo fileInfo, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(fileInfo.FullName, false), addArguments);
public static FFMpegArguments FromUrlInput(Uri uri, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments);
public static FFMpegArguments FromDeviceInput(string device, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputDeviceArgument(device), addArguments);
@@ -35,6 +36,7 @@ namespace FFMpegCore
public FFMpegArguments AddConcatInput(IEnumerable filePaths, Action? addArguments = null) => WithInput(new ConcatArgument(filePaths), addArguments);
public FFMpegArguments AddDemuxConcatInput(IEnumerable filePaths, Action? addArguments = null) => WithInput(new DemuxConcatArgument(filePaths), addArguments);
public FFMpegArguments AddFileInput(string filePath, bool verifyExists = true, Action? addArguments = null) => WithInput(new InputArgument(verifyExists, filePath), addArguments);
+ public FFMpegArguments AddFileInput(IEnumerable filePath, bool verifyExists = true, Action? addArguments = null) => WithInput(new MultiInputArgument(verifyExists, filePath), addArguments);
public FFMpegArguments AddFileInput(FileInfo fileInfo, Action? addArguments = null) => WithInput(new InputArgument(fileInfo.FullName, false), addArguments);
public FFMpegArguments AddUrlInput(Uri uri, Action? addArguments = null) => WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments);
public FFMpegArguments AddDeviceInput(string device, Action? addArguments = null) => WithInput(new InputDeviceArgument(device), addArguments);
diff --git a/FFMpegCore/FFMpeg/SnapshotArgumentBuilder.cs b/FFMpegCore/FFMpeg/SnapshotArgumentBuilder.cs
index 7d83183..3204bcc 100644
--- a/FFMpegCore/FFMpeg/SnapshotArgumentBuilder.cs
+++ b/FFMpegCore/FFMpeg/SnapshotArgumentBuilder.cs
@@ -9,6 +9,30 @@ public static class SnapshotArgumentBuilder
{
public static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments(
string input,
+ string output,
+ IMediaAnalysis source,
+ Size? size = null,
+ TimeSpan? captureTime = null,
+ int? streamIndex = null,
+ int inputFileIndex = 0)
+ {
+ return BuildSnapshotArguments(input, VideoCodec.Image.GetByExtension(output), source, size, captureTime, streamIndex, inputFileIndex);
+ }
+
+ public static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments(
+ string input,
+ IMediaAnalysis source,
+ Size? size = null,
+ TimeSpan? captureTime = null,
+ int? streamIndex = null,
+ int inputFileIndex = 0)
+ {
+ return BuildSnapshotArguments(input, VideoCodec.Image.Png, source, size, captureTime, streamIndex, inputFileIndex);
+ }
+
+ private static (FFMpegArguments, Action outputOptions) BuildSnapshotArguments(
+ string input,
+ Codec codec,
IMediaAnalysis source,
Size? size = null,
TimeSpan? captureTime = null,
@@ -26,7 +50,7 @@ public static class SnapshotArgumentBuilder
.Seek(captureTime)),
options => options
.SelectStream((int)streamIndex, inputFileIndex)
- .WithVideoCodec(VideoCodec.Png)
+ .WithVideoCodec(codec)
.WithFrameOutputCount(1)
.Resize(size));
}
diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj
index 2c02502..c324d62 100644
--- a/FFMpegCore/FFMpegCore.csproj
+++ b/FFMpegCore/FFMpegCore.csproj
@@ -3,10 +3,15 @@
true
A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications
- 5.1.0
+ 5.2.0
../nupkg
-
-
+ - **Instances and Packages Updates**: Updates to various instances and packages by rosenbjerg.
+- **Audio and Video Enhancements**: Additions include a Copy option to Audio Codec and a Crop option to Arguments by brett-baker; video-stream level added to FFProbe analysis by Kaaybi; AV1 support for smaller snapshots and videos by BenediktBertsch; multiple input files support by AddyMills; HDR color properties support added to FFProbe analysis by Tomiscout.
+- **System.Text.Json Bump**: Update by Kaaybi.
+- **FFMpeg Processors and Utilities**: Modification for handling durations over 24 hours in `FFMpegArgumentProcessor` by alahane-techtel; fix for snapshots with correct width/height from rotated videos by Hagfjall.
+- **Feature Additions and Fixes**: Support for multiple outputs and tee muxer by duggaraju; custom ffprob arguments by vfrz; fix for null reference exception with tags container by rosenbjerg; Chapter Modell change by vortex852456; codec copy added to the SaveM3U8Stream method by rpaschoal.
+- **Closed and Non-merged Contributions**: Notable closed contributions include JSON source generators usage by onionware-github; Snapshot overload by 3UR; FromRawInput method by pedoc; runtime ffmpeg suite installation by yuqian5; and support for scale_npp by vicwilliam.
+- **Miscellaneous Fixes**: Minor readme corrections by NaBian; fix for ffmpeg path issue by devedse.
ffmpeg ffprobe convert video audio mediafile resize analyze muxing
Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev
README.md
@@ -17,8 +22,8 @@
-
-
+
+
diff --git a/README.md b/README.md
index 33f7ddf..4b4a2b2 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# [FFMpegCore](https://www.nuget.org/packages/FFMpegCore/)
-[](https://www.nuget.org/packages/FFMpegCore/)
+[](https://www.nuget.org/packages/FFMpegCore/)
[](https://github.com/rosenbjerg/FFMpegCore/issues)
[](https://github.com/rosenbjerg/FFMpegCore/stargazers)
[](https://github.com/rosenbjerg/FFMpegCore/blob/master/LICENSE)