diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 94a858ff..3f3677ba 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,42 +3,48 @@ name: CI
on:
push:
branches:
- - master
+ - main
paths:
- .github/workflows/ci.yml
+ - Directory.Build.props
- FFMpegCore/**
- FFMpegCore.Test/**
+ - FFMpegCore.Extensions.SkiaSharp/**
+ - FFMpegCore.Extensions.System.Drawing.Common/**
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-latest]
+ os: [windows-latest, ubuntu-latest, macos-13]
timeout-minutes: 7
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Prepare .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: '7.0.x'
+ dotnet-version: '8.0.x'
- name: Lint with dotnet
run: dotnet format FFMpegCore.sln --severity warn --verify-no-changes
- name: Prepare FFMpeg
- uses: FedericoCarboni/setup-ffmpeg@v2
+ uses: FedericoCarboni/setup-ffmpeg@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -47,7 +53,9 @@ jobs:
- if: matrix.os == 'windows-latest'
name: Upload coverage reports to Codecov
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v4
with:
+ fail_ci_if_error: true
directory: FFMpegCore.Test/TestResults
- fail_ci_if_error: true
\ No newline at end of file
+ token: ${{ secrets.CODECOV_TOKEN }}
+ os: windows
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 00a1ea7d..f832def4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -8,12 +8,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Prepare .NET
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: '7.0.x'
+ dotnet-version: '8.0.x'
- name: Build solution
run: dotnet pack FFMpegCore.sln -c Release
diff --git a/Directory.Build.props b/Directory.Build.props
index 2944c6bf..628195a6 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.Examples/FFMpegCore.Examples.csproj b/FFMpegCore.Examples/FFMpegCore.Examples.csproj
index f4a2b9bb..1aa7d463 100644
--- a/FFMpegCore.Examples/FFMpegCore.Examples.csproj
+++ b/FFMpegCore.Examples/FFMpegCore.Examples.csproj
@@ -2,7 +2,7 @@
Exe
- net6.0
+ net8.0
false
diff --git a/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj b/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj
index 87710cc1..9867a9ca 100644
--- a/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj
+++ b/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj
@@ -3,17 +3,18 @@
true
Image extension for FFMpegCore using SkiaSharp
- 5.0.0
+ 5.0.1
../nupkg
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 13cdc1ab..885779a8 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,16 +3,17 @@
true
Image extension for FFMpegCore using System.Common.Drawing
- 5.0.0
+ 5.0.1
../nupkg
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 cf455c82..ce39c9d8 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 @@ public void Builder_BuildString_TeeOutput()
-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 7c4b8884..77914234 100644
--- a/FFMpegCore.Test/FFMpegCore.Test.csproj
+++ b/FFMpegCore.Test/FFMpegCore.Test.csproj
@@ -1,26 +1,26 @@
- net6.0
+ net8.0
false
disable
default
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
+
+
+
+
@@ -51,9 +51,15 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs
index a5070785..6ebad0f3 100644
--- a/FFMpegCore.Test/FFProbeTests.cs
+++ b/FFMpegCore.Test/FFProbeTests.cs
@@ -123,6 +123,7 @@ public void Probe_Success()
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);
@@ -145,6 +146,13 @@ public void Probe_Rotation()
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)]
public async Task Probe_Async_Success()
{
@@ -173,6 +181,18 @@ public async Task Probe_Success_FromStream_Async()
Assert.AreEqual(3, info.Duration.Seconds);
}
+ [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);
+ }
+
[TestMethod, Timeout(10000)]
public async Task Probe_Success_Subtitle_Async()
{
diff --git a/FFMpegCore.Test/Resources/TestResources.cs b/FFMpegCore.Test/Resources/TestResources.cs
index b958b80b..97e78924 100644
--- a/FFMpegCore.Test/Resources/TestResources.cs
+++ b/FFMpegCore.Test/Resources/TestResources.cs
@@ -4,7 +4,9 @@ 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";
diff --git a/FFMpegCore.Test/Resources/input_3sec_rotation_negative_90deg.mp4 b/FFMpegCore.Test/Resources/input_3sec_rotation_negative_90deg.mp4
new file mode 100644
index 00000000..9a135f08
Binary files /dev/null and b/FFMpegCore.Test/Resources/input_3sec_rotation_negative_90deg.mp4 differ
diff --git a/FFMpegCore.Test/Resources/input_hdr.mov b/FFMpegCore.Test/Resources/input_hdr.mov
new file mode 100644
index 00000000..3fdeea11
Binary files /dev/null and b/FFMpegCore.Test/Resources/input_hdr.mov differ
diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs
index 5071a483..8da9c19a 100644
--- a/FFMpegCore.Test/VideoTest.cs
+++ b/FFMpegCore.Test/VideoTest.cs
@@ -480,6 +480,21 @@ public void Video_Snapshot_PersistSnapshot()
Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
}
+ [TestMethod, Timeout(BaseTimeoutMilliseconds)]
+ public void Video_Snapshot_Rotated_PersistSnapshot()
+ {
+ using var outputPath = new TemporaryFile("out.png");
+
+ var size = new Size(360, 0); // half the size of original video, keeping height 0 for keeping aspect ratio
+ FFMpeg.Snapshot(TestResources.Mp4VideoRotationNegative, outputPath, size);
+
+ var analysis = FFProbe.Analyse(outputPath);
+ Assert.AreEqual(size.Width, analysis.PrimaryVideoStream!.Width);
+ Assert.AreEqual(1280 / 2, analysis.PrimaryVideoStream!.Height);
+ Assert.AreEqual(0, analysis.PrimaryVideoStream!.Rotation);
+ Assert.AreEqual("png", analysis.PrimaryVideoStream!.CodecName);
+ }
+
[TestMethod, Timeout(BaseTimeoutMilliseconds)]
public void Video_GifSnapshot_PersistSnapshot()
{
diff --git a/FFMpegCore/FFMpeg/Arguments/CropArgument.cs b/FFMpegCore/FFMpeg/Arguments/CropArgument.cs
new file mode 100644
index 00000000..4fe10d64
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/CropArgument.cs
@@ -0,0 +1,22 @@
+using System.Drawing;
+
+namespace FFMpegCore.Arguments
+{
+ public class CropArgument : IArgument
+ {
+ 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}";
+ }
+}
diff --git a/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs b/FFMpegCore/FFMpeg/Arguments/MultiInputArgument.cs
new file mode 100644
index 00000000..288c7615
--- /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/Enums/Enums.cs b/FFMpegCore/FFMpeg/Enums/Enums.cs
index 4974b448..1f002037 100644
--- a/FFMpegCore/FFMpeg/Enums/Enums.cs
+++ b/FFMpegCore/FFMpeg/Enums/Enums.cs
@@ -17,6 +17,7 @@ public static class VideoCodec
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 AudioCodec
@@ -27,6 +28,8 @@ public static class AudioCodec
public static Codec Ac3 => FFMpeg.GetCodec("ac3");
public static Codec Eac3 => FFMpeg.GetCodec("eac3");
public static Codec LibMp3Lame => FFMpeg.GetCodec("libmp3lame");
+ public static Codec Copy => new Codec("copy", CodecType.Audio);
+
}
public static class VideoType
diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs
index 6a6586c4..941c1c27 100644
--- a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs
+++ b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs
@@ -16,7 +16,10 @@ internal FFMpegArgumentOptions() { }
public FFMpegArgumentOptions WithVariableBitrate(int vbr) => WithArgument(new VariableBitRateArgument(vbr));
public FFMpegArgumentOptions Resize(int width, int height) => WithArgument(new SizeArgument(width, height));
public FFMpegArgumentOptions Resize(Size? size) => WithArgument(new SizeArgument(size));
-
+ public FFMpegArgumentOptions Crop(Size? size, int left, int top) => WithArgument(new CropArgument(size, top, left));
+ public FFMpegArgumentOptions Crop(int width, int height, int left, int top) => WithArgument(new CropArgument(new Size(width, height), top, left));
+ public FFMpegArgumentOptions Crop(Size? size) => WithArgument(new CropArgument(size, 0, 0));
+ public FFMpegArgumentOptions Crop(int width, int height) => WithArgument(new CropArgument(new Size(width, height), 0, 0));
public FFMpegArgumentOptions WithBitStreamFilter(Channel channel, Filter filter) => WithArgument(new BitStreamFilterArgument(channel, filter));
public FFMpegArgumentOptions WithConstantRateFactor(int crf) => WithArgument(new ConstantRateFactorArgument(crf));
public FFMpegArgumentOptions CopyChannel(Channel channel = Channel.Both) => WithArgument(new CopyArgument(channel));
diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs
index 27237d8c..3a587049 100644
--- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs
+++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs
@@ -1,5 +1,4 @@
using System.Diagnostics;
-using System.Globalization;
using System.Text.RegularExpressions;
using FFMpegCore.Enums;
using FFMpegCore.Exceptions;
@@ -263,7 +262,7 @@ private void ErrorData(object sender, string msg)
return;
}
- var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
+ var processed = MediaAnalysisUtils.ParseDuration(match.Groups[1].Value);
_onTimeProgress?.Invoke(processed);
if (_onPercentageProgress == null || _totalTimespan == null)
diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs
index cfc6d9de..ddb1f722 100644
--- a/FFMpegCore/FFMpeg/FFMpegArguments.cs
+++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs
@@ -21,6 +21,7 @@ private string GetText()
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 @@ public FFMpegArguments WithGlobalOptions(Action configure
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/Pipes/RawAudioPipeSource.cs b/FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs
index 653e2520..cfe31ef3 100644
--- a/FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs
+++ b/FFMpegCore/FFMpeg/Pipes/RawAudioPipeSource.cs
@@ -27,7 +27,7 @@ public string GetStreamArguments()
public async Task WriteAsync(Stream outputStream, CancellationToken cancellationToken)
{
- if (_sampleEnumerator.Current != null)
+ if (_sampleEnumerator.MoveNext() && _sampleEnumerator.Current != null)
{
await _sampleEnumerator.Current.SerializeAsync(outputStream, cancellationToken).ConfigureAwait(false);
}
diff --git a/FFMpegCore/FFMpeg/SnapshotArgumentBuilder.cs b/FFMpegCore/FFMpeg/SnapshotArgumentBuilder.cs
index 0d9b414b..7d831834 100644
--- a/FFMpegCore/FFMpeg/SnapshotArgumentBuilder.cs
+++ b/FFMpegCore/FFMpeg/SnapshotArgumentBuilder.cs
@@ -64,7 +64,7 @@ public static (FFMpegArguments, Action outputOptions) Bui
}
var currentSize = new Size(source.PrimaryVideoStream.Width, source.PrimaryVideoStream.Height);
- if (source.PrimaryVideoStream.Rotation == 90 || source.PrimaryVideoStream.Rotation == 180)
+ if (IsRotated(source.PrimaryVideoStream.Rotation))
{
currentSize = new Size(source.PrimaryVideoStream.Height, source.PrimaryVideoStream.Width);
}
@@ -88,4 +88,10 @@ public static (FFMpegArguments, Action outputOptions) Bui
return null;
}
+
+ private static bool IsRotated(int rotation)
+ {
+ var absRotation = Math.Abs(rotation);
+ return absRotation == 90 || absRotation == 180;
+ }
}
diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj
index ed3b71c8..2563fb5e 100644
--- a/FFMpegCore/FFMpegCore.csproj
+++ b/FFMpegCore/FFMpegCore.csproj
@@ -3,7 +3,7 @@
true
A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications
- 5.1.0
+ 5.1.1
../nupkg
@@ -18,7 +18,9 @@
-
+
+
+
diff --git a/FFMpegCore/FFOptions.cs b/FFMpegCore/FFOptions.cs
index 3194874f..2d4e4c98 100644
--- a/FFMpegCore/FFOptions.cs
+++ b/FFMpegCore/FFOptions.cs
@@ -1,4 +1,5 @@
using System.Text;
+using System.Text.Json.Serialization;
using FFMpegCore.Enums;
namespace FFMpegCore
@@ -20,10 +21,20 @@ public class FFOptions : ICloneable
///
public string TemporaryFilesFolder { get; set; } = Path.GetTempPath();
+ ///
+ /// Encoding web name used to persist encoding
+ ///
+ public string EncodingWebName { get; set; } = Encoding.Default.WebName;
+
///
/// Encoding used for parsing stdout/stderr on ffmpeg and ffprobe processes
///
- public Encoding Encoding { get; set; } = Encoding.Default;
+ [JsonIgnore]
+ public Encoding Encoding
+ {
+ get => Encoding.GetEncoding(EncodingWebName);
+ set => EncodingWebName = value?.WebName ?? Encoding.Default.WebName;
+ }
///
/// The log level to use when calling of the ffmpeg executable.
diff --git a/FFMpegCore/FFProbe/FFProbeAnalysis.cs b/FFMpegCore/FFProbe/FFProbeAnalysis.cs
index e88f4618..9f8e880c 100644
--- a/FFMpegCore/FFProbe/FFProbeAnalysis.cs
+++ b/FFMpegCore/FFProbe/FFProbeAnalysis.cs
@@ -83,6 +83,9 @@ public class FFProbeStream : ITagsContainer, IDispositionContainer
[JsonPropertyName("pix_fmt")]
public string PixelFormat { get; set; } = null!;
+ [JsonPropertyName("level")]
+ public int Level { get; set; }
+
[JsonPropertyName("sample_rate")]
public string SampleRate { get; set; } = null!;
@@ -94,6 +97,18 @@ public class FFProbeStream : ITagsContainer, IDispositionContainer
[JsonPropertyName("side_data_list")]
public List> SideData { get; set; } = null!;
+
+ [JsonPropertyName("color_range")]
+ public string ColorRange { get; set; } = null!;
+
+ [JsonPropertyName("color_space")]
+ public string ColorSpace { get; set; } = null!;
+
+ [JsonPropertyName("color_transfer")]
+ public string ColorTransfer { get; set; } = null!;
+
+ [JsonPropertyName("color_primaries")]
+ public string ColorPrimaries { get; set; } = null!;
}
public class Format : ITagsContainer
diff --git a/FFMpegCore/FFProbe/MediaAnalysis.cs b/FFMpegCore/FFProbe/MediaAnalysis.cs
index 887baeba..bf16af6b 100644
--- a/FFMpegCore/FFProbe/MediaAnalysis.cs
+++ b/FFMpegCore/FFProbe/MediaAnalysis.cs
@@ -30,9 +30,12 @@ private MediaFormat ParseFormat(Format analysisFormat)
};
}
+ private string GetValue(string tagName, Dictionary? tags, string defaultValue) =>
+ tags == null ? defaultValue : tags.TryGetValue(tagName, out var value) ? value : defaultValue;
+
private ChapterData ParseChapter(Chapter analysisChapter)
{
- var title = analysisChapter.Tags.FirstOrDefault(t => t.Key == "title").Value;
+ var title = GetValue("title", analysisChapter.Tags, "TitleValueNotSet");
var start = MediaAnalysisUtils.ParseDuration(analysisChapter.StartTime);
var end = MediaAnalysisUtils.ParseDuration(analysisChapter.EndTime);
@@ -87,6 +90,11 @@ private VideoStream ParseVideoStream(FFProbeStream stream)
Width = stream.Width ?? 0,
Profile = stream.Profile,
PixelFormat = stream.PixelFormat,
+ Level = stream.Level,
+ ColorRange = stream.ColorRange,
+ ColorSpace = stream.ColorSpace,
+ ColorTransfer = stream.ColorTransfer,
+ ColorPrimaries = stream.ColorPrimaries,
Rotation = MediaAnalysisUtils.ParseRotation(stream),
Language = stream.GetLanguage(),
Disposition = MediaAnalysisUtils.FormatDisposition(stream.Disposition),
diff --git a/FFMpegCore/FFProbe/VideoStream.cs b/FFMpegCore/FFProbe/VideoStream.cs
index b41bb627..377b2d0c 100644
--- a/FFMpegCore/FFProbe/VideoStream.cs
+++ b/FFMpegCore/FFProbe/VideoStream.cs
@@ -13,8 +13,13 @@ public class VideoStream : MediaStream
public int Height { get; set; }
public double FrameRate { get; set; }
public string PixelFormat { get; set; } = null!;
+ public int Level { get; set; }
public int Rotation { get; set; }
public double AverageFrameRate { get; set; }
+ public string ColorRange { get; set; } = null!;
+ public string ColorSpace { get; set; } = null!;
+ public string ColorTransfer { get; set; } = null!;
+ public string ColorPrimaries { get; set; } = null!;
public PixelFormat GetPixelFormatInfo() => FFMpeg.GetPixelFormat(PixelFormat);
}