Compare commits

..

54 commits

Author SHA1 Message Date
Malte Rosenbjerg
b063d37464
Merge branch 'main' into Add_Segment_muxer_output 2024-12-04 21:00:15 +01:00
Malte Rosenbjerg
71e36847a9
Merge pull request #505 from AddyMills/main
Add Multiple Input files
2024-12-04 20:59:10 +01:00
Malte Rosenbjerg
10bc004888
Merge pull request #546 from brett-baker/main
Add Copy option to Audio Codec.  Add Crop option to Arguments
2024-12-04 20:56:58 +01:00
Malte Rosenbjerg
69535d6fdc dotnet format 2024-12-04 21:54:06 +02:00
Malte Rosenbjerg
0a3be1e78c
Merge pull request #510 from Hagfjall/hagfjall/fix-509-snapshot-rotation
fix: Snapshots from rotated videos should have correct width/height
2024-12-04 20:53:22 +01:00
Malte Rosenbjerg
5d654af5f8 dotnet format 2024-12-04 21:51:43 +02:00
Malte Rosenbjerg
5bddf383bb
Merge pull request #523 from BenediktBertsch/main
Feat: add av1 support for smaller snapshots and videos
2024-12-04 20:50:26 +01:00
Malte Rosenbjerg
a4b9a885d3
Merge pull request #527 from alahane-techtel/main
>24hr Duration handling added in FFMpegArgumentProcessor
2024-12-04 20:49:59 +01:00
Malte Rosenbjerg
4eb515de56
Merge pull request #542 from Kaaybi/add-video-stream-level-to-ffprobe-analysis
Add video-stream level to FFProbe analysis
2024-12-04 20:49:32 +01:00
Malte Rosenbjerg
9b6626f54b
Merge branch 'main' into hagfjall/fix-509-snapshot-rotation 2024-12-04 20:47:41 +01:00
Malte Rosenbjerg
045068c70e
Merge branch 'main' into main 2024-12-04 20:46:04 +01:00
Malte Rosenbjerg
4a6abef172
Merge branch 'main' into add-video-stream-level-to-ffprobe-analysis 2024-12-04 20:45:03 +01:00
Malte Rosenbjerg
e8ef5804e8
Merge branch 'main' into main 2024-12-04 20:44:31 +01:00
Malte Rosenbjerg
40a3609d5d
Merge branch 'main' into main 2024-12-04 20:43:51 +01:00
Malte Rosenbjerg
a41ec3adae
Merge branch 'main' into main 2024-12-04 20:43:12 +01:00
Malte Rosenbjerg
71981ad3a0
Merge pull request #543 from Kaaybi/bump-system-text-json-v8.0.4
Bump system.text.json
2024-12-04 20:42:55 +01:00
Malte Rosenbjerg
a3a3144aac Bump codecov/codecov-action and set CODECOV_TOKEN 2024-12-04 21:38:41 +02:00
Malte Rosenbjerg
cf8f7cc674 Call MoveNext before accessing Current 2024-12-04 21:29:37 +02:00
Malte Rosenbjerg
0cd4e076ff Bump test packages 2024-12-04 20:55:31 +02:00
Malte Rosenbjerg
d4dcf86a36 Use macos-13 runner 2024-12-04 20:51:12 +02:00
Malte Rosenbjerg
dc165f9eae Bump workflow actions 2024-12-04 20:48:59 +02:00
Malte Rosenbjerg
63fe3a6e3d Set ProduceReferenceAssembly to true 2024-12-04 20:46:29 +02:00
Malte Rosenbjerg
79ea2a9797 Bump System.Text.Json to 9.0.0 2024-12-04 20:44:05 +02:00
Malte Rosenbjerg
410e940b7e Use .NET 8 for Examples and Test 2024-12-04 20:43:47 +02:00
Malte Rosenbjerg
cf513aac0f Use .NET 8 in workflows 2024-12-04 20:43:32 +02:00
Malte Rosenbjerg
8636817fe1
Merge branch 'main' into hagfjall/fix-509-snapshot-rotation 2024-12-04 19:34:42 +01:00
Malte Rosenbjerg
b1d908971c
Merge branch 'main' into bump-system-text-json-v8.0.4 2024-12-04 19:32:13 +01:00
Malte Rosenbjerg
9942e54762
Merge branch 'main' into main 2024-12-04 19:29:59 +01:00
Malte Rosenbjerg
cf9111881d
Merge branch 'main' into add-video-stream-level-to-ffprobe-analysis 2024-12-04 19:29:31 +01:00
Malte Rosenbjerg
cefc8efe95
Merge pull request #498 from Tomiscout/main
Add HDR color properties support in FFProbe analysis
2024-12-04 19:28:52 +01:00
AddyMills
8309951519 Reapply "Update JSON Reference"
This reverts commit aabd5cc3a8.
2024-11-16 20:09:24 -06:00
AddyMills
aabd5cc3a8 Revert "Update JSON Reference"
This reverts commit f3be9f2b77.
2024-11-16 20:07:57 -06:00
AddyMills
f3be9f2b77 Update JSON Reference 2024-11-16 19:50:29 -06:00
Brett Baker
ff42378834 null check on title to not fail Media Analysis
If the title is missing from the analysis metadata, the analysis fails.  Added null check and default value of "TitleValueNotSet".
2024-11-13 08:45:00 -05:00
Brett Baker
72eddfca6d Add CropArgument
Add the option to crop the video to a given size.
2024-11-02 07:34:21 -04:00
Brett Baker
c4232b7ca0 Added Copy option to Audio 2024-11-01 09:13:29 -04:00
Kaaybi
d5a2e5cbe6 chore: bump system.text.json to v8.0.4 2024-09-18 12:13:25 +02:00
Kaaybi
f86d999035 feat: add video-stream level to ffprobe analysis 2024-09-18 12:08:16 +02:00
AddyMills
49c2941474 Remove JSON vulnerability Reference 2024-08-31 20:30:37 -06:00
Ashish
8d7d37a308 removed unnecessary tests 2024-07-01 19:26:00 +10:00
Ashish
745fe2a8cc removed unnecessary usings 2024-07-01 19:24:06 +10:00
Ashish
06b9667991 >24hr Duration handling added in FFMpegArgumentProcessor 2024-07-01 19:18:34 +10:00
Benedikt Bertsch
9007883d76
Feat: add av1 support for smaller snapshots and videos 2024-06-08 14:58:18 +02:00
Fredrik Hagfjäll
fe4b79e24c fix linting 2024-03-30 09:31:03 +01:00
Fredrik Hagfjäll
7697e2767d made test resize the output 2024-03-30 09:29:25 +01:00
Fredrik Hagfjäll
82cc9715dc fix: Snapshots from rotated videos should have correct width/height
Some video-files have their metadata as -90 instead of 90. This PR handles correct aspect-ratio and rotation when taking snapshots.

The sample video provided in the PR is my own recording. A bit too big maybe? I couldn't find any command to set the metadata to `-90` on existing video. Would be happy to get it working on already existing test-data to reduce size.

Fixes #509
2024-03-30 09:10:13 +01:00
AddyMills
94db493d19 Add IEnumerable tests for inputs 2024-03-03 18:56:06 -06:00
AddyMills
31b117d186 Remove whitespace 2024-03-03 13:09:52 -06:00
AddyMills
bb341e6dd7 Update one more instance of string[] to IEnumerable 2024-03-03 13:02:50 -06:00
AddyMills
622db9600c Add MultiInput Test 2024-03-03 12:49:34 -06:00
AddyMills
d836501681 Change string array to IEnumerable 2024-03-03 11:42:52 -06:00
AddyMills
8f6d1aa8e9 Update MultiInput to IEnumerable 2024-03-01 07:13:55 -06:00
AddyMills
8a764eccc7 Add MultiInputArgument 2024-03-01 07:08:16 -06:00
Tomas Gužauskas
ef08fd93a9 Add HDR color properties support in FFProbe analysis
Add hdr video test
2024-01-30 00:52:25 +02:00
25 changed files with 277 additions and 27 deletions

View file

@ -22,23 +22,23 @@ 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:
- 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 +47,8 @@ jobs:
- if: matrix.os == 'windows-latest'
name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v5
with:
fail_ci_if_error: true
directory: FFMpegCore.Test/TestResults
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

View file

@ -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

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

View file

@ -9,6 +9,7 @@
</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>

View file

@ -9,6 +9,7 @@
</PackageReleaseNotes>
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze muxing</PackageTags>
<Authors>Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev</Authors>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
</PropertyGroup>
<ItemGroup>

View file

@ -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<string>
{
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<string>
{
Path.GetTempFileName(),
"file2.mp4",
"file3.mp4"
};
var argument = new MultiInputArgument(true, filePaths);
try
{
// Act & Assert
Assert.ThrowsException<FileNotFoundException>(() => argument.Pre());
}
finally
{
// Cleanup
File.Delete(filePaths[0]);
}
}
[TestMethod]
public void Pre_VerifyExists_NoFilesExist()
{
// Arrange
var filePaths = new List<string>
{
"file1.mp4",
"file2.mp4",
"file3.mp4"
};
var argument = new MultiInputArgument(true, filePaths);
// Act & Assert
Assert.ThrowsException<FileNotFoundException>(() => argument.Pre());
}
}
}

View file

@ -1,25 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>disable</Nullable>
<LangVersion>default</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3">
<PackageReference Include="FluentAssertions" Version="7.0.0" />
<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="17.7.2" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.6.4" />
<PackageReference Include="MSTest.TestFramework" Version="3.6.4" />
<PackageReference Include="SkiaSharp" Version="2.88.6" />
</ItemGroup>
@ -51,9 +51,15 @@
<None Update="Resources\input_3sec_rotation_90deg.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\input_3sec_rotation_negative_90deg.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\input_audio_only_10sec.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\input_hdr.mov">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\input_video_only_3sec.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View file

@ -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()
{

View file

@ -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";

Binary file not shown.

View file

@ -493,6 +493,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()
{

View file

@ -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}";
}
}

View file

@ -0,0 +1,47 @@
namespace FFMpegCore.Arguments
{
/// <summary>
/// Represents input parameters for multiple files
/// </summary>
public class MultiInputArgument : IInputArgument
{
public readonly bool VerifyExists;
public readonly IEnumerable<string> FilePaths;
public MultiInputArgument(bool verifyExists, IEnumerable<string> filePaths)
{
VerifyExists = verifyExists;
FilePaths = filePaths;
}
public MultiInputArgument(IEnumerable<string> filePaths, bool verifyExists) : this(verifyExists, filePaths) { }
public void Pre()
{
if (VerifyExists)
{
var missingFiles = new List<string>();
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() { }
/// <summary>
/// Generates a combined input argument text for all file paths
/// </summary>
public string Text => string.Join(" ", FilePaths.Select(filePath => $"-i \"{filePath}\""));
}
}

View file

@ -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

View file

@ -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));

View file

@ -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)

View file

@ -21,6 +21,7 @@ private string GetText()
public static FFMpegArguments FromConcatInput(IEnumerable<string> filePaths, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new ConcatArgument(filePaths), addArguments);
public static FFMpegArguments FromDemuxConcatInput(IEnumerable<string> filePaths, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new DemuxConcatArgument(filePaths), addArguments);
public static FFMpegArguments FromFileInput(string filePath, bool verifyExists = true, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(verifyExists, filePath), addArguments);
public static FFMpegArguments FromFileInput(IEnumerable<string> filePath, bool verifyExists = true, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new MultiInputArgument(verifyExists, filePath), addArguments);
public static FFMpegArguments FromFileInput(FileInfo fileInfo, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(fileInfo.FullName, false), addArguments);
public static FFMpegArguments FromUrlInput(Uri uri, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments);
public static FFMpegArguments FromDeviceInput(string device, Action<FFMpegArgumentOptions>? addArguments = null) => new FFMpegArguments().WithInput(new InputDeviceArgument(device), addArguments);
@ -35,6 +36,7 @@ public FFMpegArguments WithGlobalOptions(Action<FFMpegGlobalArguments> configure
public FFMpegArguments AddConcatInput(IEnumerable<string> filePaths, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new ConcatArgument(filePaths), addArguments);
public FFMpegArguments AddDemuxConcatInput(IEnumerable<string> filePaths, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new DemuxConcatArgument(filePaths), addArguments);
public FFMpegArguments AddFileInput(string filePath, bool verifyExists = true, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputArgument(verifyExists, filePath), addArguments);
public FFMpegArguments AddFileInput(IEnumerable<string> filePath, bool verifyExists = true, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new MultiInputArgument(verifyExists, filePath), addArguments);
public FFMpegArguments AddFileInput(FileInfo fileInfo, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputArgument(fileInfo.FullName, false), addArguments);
public FFMpegArguments AddUrlInput(Uri uri, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments);
public FFMpegArguments AddDeviceInput(string device, Action<FFMpegArgumentOptions>? addArguments = null) => WithInput(new InputDeviceArgument(device), addArguments);

View file

@ -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);
}

View file

@ -64,7 +64,7 @@ public static (FFMpegArguments, Action<FFMpegArgumentOptions> 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<FFMpegArgumentOptions> outputOptions) Bui
return null;
}
private static bool IsRotated(int rotation)
{
var absRotation = Math.Abs(rotation);
return absRotation == 90 || absRotation == 180;
}
}

View file

@ -3,7 +3,7 @@
<PropertyGroup>
<IsPackable>true</IsPackable>
<Description>A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications</Description>
<PackageVersion>5.1.0</PackageVersion>
<PackageVersion>5.1.1</PackageVersion>
<PackageOutputPath>../nupkg</PackageOutputPath>
<PackageReleaseNotes>
</PackageReleaseNotes>
@ -18,7 +18,9 @@
<ItemGroup>
<PackageReference Include="Instances" Version="3.0.0" />
<PackageReference Include="System.Text.Json" Version="8.0.4" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
</ItemGroup>
</Project>

View file

@ -22,10 +22,20 @@ public class FFOptions : ICloneable
public string TemporaryFilesFolder { get; set; } = Path.GetTempPath();
[JsonIgnore]
/// <summary>
/// Encoding web name used to persist encoding <see cref="Encoding"/>
/// </summary>
public string EncodingWebName { get; set; } = Encoding.Default.WebName;
/// <summary>
/// Encoding used for parsing stdout/stderr on ffmpeg and ffprobe processes
/// </summary>
public Encoding Encoding { get; set; } = Encoding.Default;
[JsonIgnore]
public Encoding Encoding
{
get => Encoding.GetEncoding(EncodingWebName);
set => EncodingWebName = value?.WebName ?? Encoding.Default.WebName;
}
/// <summary>
/// The log level to use when calling of the ffmpeg executable.

View file

@ -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<Dictionary<string, JsonValue>> 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

View file

@ -30,9 +30,12 @@ private MediaFormat ParseFormat(Format analysisFormat)
};
}
private string GetValue(string tagName, Dictionary<string, string>? 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),

View file

@ -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);
}