diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6946920..5afb8d4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,10 +4,18 @@ on:
push:
branches:
- master
+ paths:
+ - .github/workflows/ci.yml
+ - FFMpegCore/**
+ - FFMpegCore.Test/**
pull_request:
branches:
- master
- release
+ paths:
+ - .github/workflows/ci.yml
+ - FFMpegCore/**
+ - FFMpegCore.Test/**
jobs:
ci:
@@ -22,7 +30,7 @@ jobs:
- name: Prepare .NET
uses: actions/setup-dotnet@v1
with:
- dotnet-version: '5.0.x'
+ dotnet-version: '6.0.x'
- name: Prepare FFMpeg
uses: FedericoCarboni/setup-ffmpeg@v1
- name: Test with dotnet
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5ef0a4c..7cf1425 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,7 +12,7 @@ jobs:
- name: Prepare .NET
uses: actions/setup-dotnet@v1
with:
- dotnet-version: '5.0.x'
+ dotnet-version: '6.0.x'
- name: Build solution
run: dotnet build --output build -c Release
- name: Publish NuGet package
diff --git a/FFMpegCore.Examples/FFMpegCore.Examples.csproj b/FFMpegCore.Examples/FFMpegCore.Examples.csproj
index f9daae7..68e7b5c 100644
--- a/FFMpegCore.Examples/FFMpegCore.Examples.csproj
+++ b/FFMpegCore.Examples/FFMpegCore.Examples.csproj
@@ -2,7 +2,7 @@
Exe
- net5.0
+ net6.0
diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs
index 54620a5..9cf7e39 100644
--- a/FFMpegCore.Test/ArgumentBuilderTest.cs
+++ b/FFMpegCore.Test/ArgumentBuilderTest.cs
@@ -334,7 +334,7 @@ public void Builder_BuildString_SubtitleHardBurnFilter()
.HardBurnSubtitle(SubtitleHardBurnOptions
.Create(subtitlePath: "sample.srt")
.SetCharacterEncoding("UTF-8")
- .SetOriginalSize(1366,768)
+ .SetOriginalSize(1366, 768)
.SetSubtitleIndex(0)
.WithStyle(StyleOptions.Create()
.WithParameter("FontName", "DejaVu Serif")
@@ -479,10 +479,21 @@ public void Builder_BuildString_DynamicAudioNormalizerWithValuesFormat()
{
var str = FFMpegArguments.FromFileInput("input.mp4")
.OutputToFile("output.mp4", false,
- opt => opt.WithAudioFilters(filterOptions => filterOptions.DynamicNormalizer(125, 13, 0.9215, 5.124, 0.5458,false,true,true, 0.3333333)))
+ opt => opt.WithAudioFilters(filterOptions => filterOptions.DynamicNormalizer(125, 13, 0.9215, 5.124, 0.5458, false, true, true, 0.3333333)))
.Arguments;
Assert.AreEqual("-i \"input.mp4\" -af \"dynaudnorm=f=125:g=13:p=0.92:m=5.1:r=0.5:n=0:c=1:b=1:s=0.3\" \"output.mp4\"", str);
}
+
+ [TestMethod]
+ public void Builder_BuildString_Audible_AAXC_Decryption()
+ {
+ var str = FFMpegArguments.FromFileInput("input.aaxc", false, x => x.WithAudibleEncryptionKeys("123", "456"))
+ .MapMetaData()
+ .OutputToFile("output.m4b", true, x => x.WithTagVersion(3).DisableChannel(Channel.Video).CopyChannel(Channel.Audio))
+ .Arguments;
+
+ Assert.AreEqual("-audible_key 123 -audible_iv 456 -i \"input.aaxc\" -map_metadata 0 -id3v2_version 3 -vn -c:a copy \"output.m4b\" -y", str);
+ }
}
}
\ No newline at end of file
diff --git a/FFMpegCore.Test/FFMpegArgumentProcessorTest.cs b/FFMpegCore.Test/FFMpegArgumentProcessorTest.cs
index 8443d0d..6e30999 100644
--- a/FFMpegCore.Test/FFMpegArgumentProcessorTest.cs
+++ b/FFMpegCore.Test/FFMpegArgumentProcessorTest.cs
@@ -94,5 +94,20 @@ public void Concat_Escape()
var arg = new DemuxConcatArgument(new[] { @"Heaven's River\05 - Investigation.m4b" });
arg.Values.Should().BeEquivalentTo(new[] { @"file 'Heaven'\''s River\05 - Investigation.m4b'" });
}
+
+ [TestMethod]
+ public void Audible_Aaxc_Test()
+ {
+ var arg = new AudibleEncryptionKeyArgument("123", "456");
+ arg.Text.Should().Be($"-audible_key 123 -audible_iv 456");
+ }
+
+
+ [TestMethod]
+ public void Audible_Aax_Test()
+ {
+ var arg = new AudibleEncryptionKeyArgument("62689101");
+ arg.Text.Should().Be($"-activation_bytes 62689101");
+ }
}
}
\ No newline at end of file
diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj
index 5d49065..d281c3d 100644
--- a/FFMpegCore.Test/FFMpegCore.Test.csproj
+++ b/FFMpegCore.Test/FFMpegCore.Test.csproj
@@ -1,7 +1,7 @@
- net5.0
+ net6.0
false
diff --git a/FFMpegCore.Test/FFProbeTests.cs b/FFMpegCore.Test/FFProbeTests.cs
index c2e6e5a..a4d836d 100644
--- a/FFMpegCore.Test/FFProbeTests.cs
+++ b/FFMpegCore.Test/FFProbeTests.cs
@@ -11,13 +11,6 @@ namespace FFMpegCore.Test
[TestClass]
public class FFProbeTests
{
- [TestMethod]
- public void Probe_TooLongOutput()
- {
- Assert.ThrowsException(() => FFProbe.Analyse(TestResources.Mp4Video, 5));
- }
-
-
[TestMethod]
public async Task Audio_FromStream_Duration()
{
diff --git a/FFMpegCore.Test/MetaDataBuilderTests.cs b/FFMpegCore.Test/MetaDataBuilderTests.cs
index 5f0a144..747fd9e 100644
--- a/FFMpegCore.Test/MetaDataBuilderTests.cs
+++ b/FFMpegCore.Test/MetaDataBuilderTests.cs
@@ -4,8 +4,10 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace FFMpegCore.Test
@@ -50,5 +52,29 @@ public void TestMetaDataBuilderIntegrity()
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
+
+ var text0 = FFMpegArguments.FromFileInput("whaterver0")
+ .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.");
+ }
}
}
diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs
index 0f806d6..262bd75 100644
--- a/FFMpegCore.Test/VideoTest.cs
+++ b/FFMpegCore.Test/VideoTest.cs
@@ -113,13 +113,11 @@ public void Video_ToMP4_Args_Pipe_DifferentImageSizes()
};
var videoFramesSource = new RawVideoPipeSource(frames);
- var ex = Assert.ThrowsException(() => FFMpegArguments
+ var ex = Assert.ThrowsException(() => FFMpegArguments
.FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264))
.ProcessSynchronously());
-
- Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException));
}
@@ -135,13 +133,11 @@ public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_Async()
};
var videoFramesSource = new RawVideoPipeSource(frames);
- var ex = await Assert.ThrowsExceptionAsync(() => FFMpegArguments
+ var ex = await Assert.ThrowsExceptionAsync(() => FFMpegArguments
.FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264))
.ProcessAsynchronously());
-
- Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException));
}
[TestMethod, Timeout(10000)]
@@ -156,13 +152,11 @@ public void Video_ToMP4_Args_Pipe_DifferentPixelFormats()
};
var videoFramesSource = new RawVideoPipeSource(frames);
- var ex = Assert.ThrowsException(() => FFMpegArguments
+ var ex = Assert.ThrowsException(() => FFMpegArguments
.FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264))
.ProcessSynchronously());
-
- Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException));
}
@@ -178,13 +172,11 @@ public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_Async()
};
var videoFramesSource = new RawVideoPipeSource(frames);
- var ex = await Assert.ThrowsExceptionAsync(() => FFMpegArguments
+ var ex = await Assert.ThrowsExceptionAsync(() => FFMpegArguments
.FromPipeInput(videoFramesSource)
.OutputToFile(outputFile, false, opt => opt
.WithVideoCodec(VideoCodec.LibX264))
.ProcessAsynchronously());
-
- Assert.IsInstanceOfType(ex.GetBaseException(), typeof(FFMpegStreamFormatException));
}
[TestMethod, Timeout(10000)]
@@ -468,7 +460,7 @@ public void Video_Join_Image_Sequence()
}
});
- var outputFile = new TemporaryFile("out.mp4");
+ using var outputFile = new TemporaryFile("out.mp4");
var success = FFMpeg.JoinImageSequence(outputFile, images: imageSet.ToArray());
Assert.IsTrue(success);
var result = FFProbe.Analyse(outputFile);
@@ -544,7 +536,7 @@ public void Video_OutputsData()
.WithVerbosityLevel(VerbosityLevel.Info))
.OutputToFile(outputFile, false, opt => opt
.WithDuration(TimeSpan.FromSeconds(2)))
- .NotifyOnOutput((_, _) => dataReceived = true)
+ .NotifyOnError(_ => dataReceived = true)
.ProcessSynchronously();
Assert.IsTrue(dataReceived);
@@ -596,6 +588,27 @@ public async Task Video_Cancel_Async()
Assert.IsFalse(result);
}
+ [TestMethod, Timeout(10000)]
+ public void Video_Cancel()
+ {
+ var outputFile = new TemporaryFile("out.mp4");
+ var task = FFMpegArguments
+ .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args
+ .WithCustomArgument("-re")
+ .ForceFormat("lavfi"))
+ .OutputToFile(outputFile, false, opt => opt
+ .WithAudioCodec(AudioCodec.Aac)
+ .WithVideoCodec(VideoCodec.LibX264)
+ .WithSpeedPreset(Speed.VeryFast))
+ .CancellableThrough(out var cancel);
+
+ Task.Delay(300).ContinueWith((_) => cancel());
+
+ var result = task.ProcessSynchronously(false);
+
+ Assert.IsFalse(result);
+ }
+
[TestMethod, Timeout(10000)]
public async Task Video_Cancel_Async_With_Timeout()
{
@@ -615,11 +628,10 @@ public async Task Video_Cancel_Async_With_Timeout()
await Task.Delay(300);
cancel();
- var result = await task;
+ await task;
var outputInfo = await FFProbe.AnalyseAsync(outputFile);
- Assert.IsTrue(result);
Assert.IsNotNull(outputInfo);
Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width);
Assert.AreEqual(240, outputInfo.PrimaryVideoStream.Height);
@@ -645,14 +657,58 @@ public async Task Video_Cancel_CancellationToken_Async()
.CancellableThrough(cts.Token)
.ProcessAsynchronously(false);
- await Task.Delay(300);
- cts.Cancel();
+ cts.CancelAfter(300);
var result = await task;
Assert.IsFalse(result);
}
+ [TestMethod, Timeout(10000)]
+ public async Task Video_Cancel_CancellationToken_Async_Throws()
+ {
+ var outputFile = new TemporaryFile("out.mp4");
+
+ var cts = new CancellationTokenSource();
+
+ var task = FFMpegArguments
+ .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args
+ .WithCustomArgument("-re")
+ .ForceFormat("lavfi"))
+ .OutputToFile(outputFile, false, opt => opt
+ .WithAudioCodec(AudioCodec.Aac)
+ .WithVideoCodec(VideoCodec.LibX264)
+ .WithSpeedPreset(Speed.VeryFast))
+ .CancellableThrough(cts.Token)
+ .ProcessAsynchronously();
+
+ cts.CancelAfter(300);
+
+ await Assert.ThrowsExceptionAsync(() => task);
+ }
+
+ [TestMethod, Timeout(10000)]
+ public void Video_Cancel_CancellationToken_Throws()
+ {
+ var outputFile = new TemporaryFile("out.mp4");
+
+ var cts = new CancellationTokenSource();
+
+ var task = FFMpegArguments
+ .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args
+ .WithCustomArgument("-re")
+ .ForceFormat("lavfi"))
+ .OutputToFile(outputFile, false, opt => opt
+ .WithAudioCodec(AudioCodec.Aac)
+ .WithVideoCodec(VideoCodec.LibX264)
+ .WithSpeedPreset(Speed.VeryFast))
+ .CancellableThrough(cts.Token);
+
+ cts.CancelAfter(300);
+
+ Assert.ThrowsException(() => task.ProcessSynchronously());
+ }
+
[TestMethod, Timeout(10000)]
public async Task Video_Cancel_CancellationToken_Async_With_Timeout()
{
@@ -671,14 +727,12 @@ public async Task Video_Cancel_CancellationToken_Async_With_Timeout()
.CancellableThrough(cts.Token, 8000)
.ProcessAsynchronously(false);
- await Task.Delay(300);
- cts.Cancel();
+ cts.CancelAfter(300);
- var result = await task;
+ await task;
var outputInfo = await FFProbe.AnalyseAsync(outputFile);
- Assert.IsTrue(result);
Assert.IsNotNull(outputInfo);
Assert.AreEqual(320, outputInfo.PrimaryVideoStream!.Width);
Assert.AreEqual(240, outputInfo.PrimaryVideoStream.Height);
diff --git a/FFMpegCore/Extend/StringExtensions.cs b/FFMpegCore/Extend/StringExtensions.cs
index 29c8d42..7b02089 100644
--- a/FFMpegCore/Extend/StringExtensions.cs
+++ b/FFMpegCore/Extend/StringExtensions.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Text;
namespace FFMpegCore.Extend
diff --git a/FFMpegCore/FFMpeg/Arguments/AudibleEncryptionKeyArgument.cs b/FFMpegCore/FFMpeg/Arguments/AudibleEncryptionKeyArgument.cs
new file mode 100644
index 0000000..0f514dc
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/AudibleEncryptionKeyArgument.cs
@@ -0,0 +1,28 @@
+namespace FFMpegCore.Arguments
+{
+ public class AudibleEncryptionKeyArgument : IArgument
+ {
+ 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}";
+ }
+}
diff --git a/FFMpegCore/FFMpeg/Arguments/ID3V2VersionArgument.cs b/FFMpegCore/FFMpeg/Arguments/ID3V2VersionArgument.cs
new file mode 100644
index 0000000..e18d93b
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/ID3V2VersionArgument.cs
@@ -0,0 +1,14 @@
+namespace FFMpegCore.Arguments
+{
+ public class ID3V2VersionArgument : IArgument
+ {
+ private readonly int _version;
+
+ public ID3V2VersionArgument(int version)
+ {
+ _version = version;
+ }
+
+ public string Text => $"-id3v2_version {_version}";
+ }
+}
diff --git a/FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs b/FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs
new file mode 100644
index 0000000..36a504e
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/IDynamicArgument.cs
@@ -0,0 +1,16 @@
+using System.Collections.Generic;
+using System.Text;
+
+namespace FFMpegCore.Arguments
+{
+ public interface IDynamicArgument
+ {
+ ///
+ /// Same as , but this receives the arguments generated before as parameter
+ ///
+ ///
+ ///
+ //public string GetText(StringBuilder context);
+ public string GetText(IEnumerable context);
+ }
+}
\ No newline at end of file
diff --git a/FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs b/FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs
index 199d324..d5ba44c 100644
--- a/FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs
+++ b/FFMpegCore/FFMpeg/Arguments/InputPipeArgument.cs
@@ -1,4 +1,5 @@
-using System.IO.Pipes;
+using System;
+using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Pipes;
@@ -23,7 +24,7 @@ protected override async Task ProcessDataAsync(CancellationToken token)
{
await Pipe.WaitForConnectionAsync(token).ConfigureAwait(false);
if (!Pipe.IsConnected)
- throw new TaskCanceledException();
+ throw new OperationCanceledException();
await Writer.WriteAsync(Pipe, token).ConfigureAwait(false);
}
}
diff --git a/FFMpegCore/FFMpeg/Arguments/MapMetadataArgument.cs b/FFMpegCore/FFMpeg/Arguments/MapMetadataArgument.cs
new file mode 100644
index 0000000..afec731
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/MapMetadataArgument.cs
@@ -0,0 +1,64 @@
+using FFMpegCore.Extend;
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace FFMpegCore.Arguments
+{
+ public class MapMetadataArgument : IInputArgument, IDynamicArgument
+ {
+ private readonly int? _inputIndex;
+
+ public string Text => GetText(null);
+
+ ///
+ /// Null means it takes the last input used before this argument
+ ///
+ ///
+ public MapMetadataArgument(int? inputIndex = null)
+ {
+ _inputIndex = inputIndex;
+ }
+
+ public string GetText(IEnumerable? arguments)
+ {
+ arguments ??= Enumerable.Empty();
+
+ var index = 0;
+ if (_inputIndex is null)
+ {
+ index = arguments
+ .TakeWhile(x => x != this)
+ .OfType()
+ .Count();
+
+ index = Math.Max(index - 1, 0);
+ }
+ else
+ {
+ index = _inputIndex.Value;
+ }
+
+ return $"-map_metadata {index}";
+ }
+
+ public Task During(CancellationToken cancellationToken = default)
+ {
+ return Task.CompletedTask;
+ }
+
+ public void Post()
+ {
+ }
+
+ public void Pre()
+ {
+ }
+
+
+ }
+}
diff --git a/FFMpegCore/FFMpeg/Arguments/MetaDataArgument.cs b/FFMpegCore/FFMpeg/Arguments/MetaDataArgument.cs
index 7e9ffc6..89bb1fe 100644
--- a/FFMpegCore/FFMpeg/Arguments/MetaDataArgument.cs
+++ b/FFMpegCore/FFMpeg/Arguments/MetaDataArgument.cs
@@ -1,11 +1,16 @@
-using System;
+using FFMpegCore.Extend;
+
+using System;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FFMpegCore.Arguments
{
- public class MetaDataArgument : IInputArgument
+ public class MetaDataArgument : IInputArgument, IDynamicArgument
{
private readonly string _metaDataContent;
private readonly string _tempFileName = Path.Combine(GlobalFFOptions.Current.TemporaryFilesFolder, $"metadata_{Guid.NewGuid()}.txt");
@@ -15,7 +20,7 @@ public MetaDataArgument(string metaDataContent)
_metaDataContent = metaDataContent;
}
- public string Text => $"-i \"{_tempFileName}\" -map_metadata 1";
+ public string Text => GetText(null);
public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
@@ -23,5 +28,17 @@ public MetaDataArgument(string metaDataContent)
public void Pre() => File.WriteAllText(_tempFileName, _metaDataContent);
public void Post() => File.Delete(_tempFileName);
+
+ public string GetText(IEnumerable? arguments)
+ {
+ arguments ??= Enumerable.Empty();
+
+ var index = arguments
+ .TakeWhile(x => x != this)
+ .OfType()
+ .Count();
+
+ return $"-i \"{_tempFileName}\" -map_metadata {index}";
+ }
}
}
diff --git a/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs b/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs
index c25df04..ddaab82 100644
--- a/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs
+++ b/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs
@@ -42,14 +42,15 @@ public async Task During(CancellationToken cancellationToken = default)
{
await ProcessDataAsync(cancellationToken).ConfigureAwait(false);
}
- catch (TaskCanceledException)
+ catch (OperationCanceledException)
{
Debug.WriteLine($"ProcessDataAsync on {GetType().Name} cancelled");
}
finally
{
Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}");
- Pipe?.Disconnect();
+ if (Pipe is { IsConnected: true })
+ Pipe.Disconnect();
}
}
diff --git a/FFMpegCore/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs
index 6f0ede3..9e9e0ce 100644
--- a/FFMpegCore/FFMpeg/FFMpeg.cs
+++ b/FFMpegCore/FFMpeg/FFMpeg.cs
@@ -8,6 +8,7 @@
using System.IO;
using System.Linq;
using System.Threading.Tasks;
+using Instances;
namespace FFMpegCore
{
@@ -246,13 +247,18 @@ public static bool Convert(
public static bool PosterWithAudio(string image, string audio, string output)
{
FFMpegHelper.ExtensionExceptionCheck(output, FileExtension.Mp4);
- FFMpegHelper.ConversionSizeExceptionCheck(Image.FromFile(image));
+ using (var imageFile = Image.FromFile(image))
+ {
+ FFMpegHelper.ConversionSizeExceptionCheck(imageFile);
+ }
return FFMpegArguments
.FromFileInput(image, false, options => options
- .Loop(1))
+ .Loop(1)
+ .ForceFormat("image2"))
.AddFileInput(audio)
.OutputToFile(output, true, options => options
+ .ForcePixelFormat("yuv420p")
.WithVideoCodec(VideoCodec.LibX264)
.WithConstantRateFactor(21)
.WithAudioBitrate(AudioQuality.Normal)
@@ -319,6 +325,7 @@ public static bool JoinImageSequence(string output, double frameRate = 30, param
return FFMpegArguments
.FromFileInput(Path.Combine(tempFolderName, "%09d.png"), false)
.OutputToFile(output, true, options => options
+ .ForcePixelFormat("yuv420p")
.Resize(firstImage.Width, firstImage.Height)
.WithFramerate(frameRate))
.ProcessSynchronously();
@@ -417,15 +424,16 @@ internal static IReadOnlyList GetPixelFormatsInternal()
FFMpegHelper.RootExceptionCheck();
var list = new List();
- using var instance = new Instances.Instance(GlobalFFOptions.GetFFMpegBinaryPath(), "-pix_fmts");
- instance.DataReceived += (e, args) =>
+ var processArguments = new ProcessArguments(GlobalFFOptions.GetFFMpegBinaryPath(), "-pix_fmts");
+ processArguments.OutputDataReceived += (e, data) =>
{
- if (PixelFormat.TryParse(args.Data, out var format))
+ if (PixelFormat.TryParse(data, out var format))
list.Add(format);
};
- var exitCode = instance.BlockUntilFinished();
- if (exitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", instance.OutputData));
+ var result = processArguments.StartAndWaitForExit();
+ if (result.ExitCode != 0)
+ throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
return list.AsReadOnly();
}
@@ -462,10 +470,10 @@ private static void ParsePartOfCodecs(Dictionary codecs, string a
{
FFMpegHelper.RootExceptionCheck();
- using var instance = new Instances.Instance(GlobalFFOptions.GetFFMpegBinaryPath(), arguments);
- instance.DataReceived += (e, args) =>
+ var processArguments = new ProcessArguments(GlobalFFOptions.GetFFMpegBinaryPath(), arguments);
+ processArguments.OutputDataReceived += (e, data) =>
{
- var codec = parser(args.Data);
+ var codec = parser(data);
if(codec != null)
if (codecs.TryGetValue(codec.Name, out var parentCodec))
parentCodec.Merge(codec);
@@ -473,8 +481,8 @@ private static void ParsePartOfCodecs(Dictionary codecs, string a
codecs.Add(codec.Name, codec);
};
- var exitCode = instance.BlockUntilFinished();
- if (exitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", instance.OutputData));
+ var result = processArguments.StartAndWaitForExit();
+ if (result.ExitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
}
internal static Dictionary GetCodecsInternal()
@@ -546,15 +554,15 @@ internal static IReadOnlyList GetContainersFormatsInternal()
FFMpegHelper.RootExceptionCheck();
var list = new List();
- using var instance = new Instances.Instance(GlobalFFOptions.GetFFMpegBinaryPath(), "-formats");
- instance.DataReceived += (e, args) =>
+ var instance = new ProcessArguments(GlobalFFOptions.GetFFMpegBinaryPath(), "-formats");
+ instance.OutputDataReceived += (e, data) =>
{
- if (ContainerFormat.TryParse(args.Data, out var fmt))
+ if (ContainerFormat.TryParse(data, out var fmt))
list.Add(fmt);
};
- var exitCode = instance.BlockUntilFinished();
- if (exitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", instance.OutputData));
+ var result = instance.StartAndWaitForExit();
+ if (result.ExitCode != 0) throw new FFMpegException(FFMpegExceptionType.Process, string.Join("\r\n", result.OutputData));
return list.AsReadOnly();
}
diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs
index ca6628a..7b3da7a 100644
--- a/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs
+++ b/FFMpegCore/FFMpeg/FFMpegArgumentOptions.cs
@@ -1,5 +1,6 @@
using System;
using System.Drawing;
+
using FFMpegCore.Arguments;
using FFMpegCore.Enums;
@@ -66,6 +67,11 @@ public FFMpegArgumentOptions WithAudioFilters(Action audioFi
public FFMpegArgumentOptions ForcePixelFormat(string pixelFormat) => WithArgument(new ForcePixelFormat(pixelFormat));
public FFMpegArgumentOptions ForcePixelFormat(PixelFormat pixelFormat) => WithArgument(new ForcePixelFormat(pixelFormat));
+ public FFMpegArgumentOptions WithAudibleEncryptionKeys(string key, string iv) => WithArgument(new AudibleEncryptionKeyArgument(key, iv));
+ public FFMpegArgumentOptions WithAudibleActivationBytes(string activationBytes) => WithArgument(new AudibleEncryptionKeyArgument(activationBytes));
+ public FFMpegArgumentOptions WithTagVersion(int id3v2Version = 3) => WithArgument(new ID3V2VersionArgument(id3v2Version));
+
+
public FFMpegArgumentOptions WithArgument(IArgument argument)
{
Arguments.Add(argument);
diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs
index fdbdcc8..43ace4d 100644
--- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs
+++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs
@@ -18,7 +18,8 @@ public class FFMpegArgumentProcessor
private readonly FFMpegArguments _ffMpegArguments;
private Action? _onPercentageProgress;
private Action? _onTimeProgress;
- private Action? _onOutput;
+ private Action? _onOutput;
+ private Action? _onError;
private TimeSpan? _totalTimespan;
internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments)
@@ -57,11 +58,16 @@ public FFMpegArgumentProcessor NotifyOnProgress(Action onTimeProgress)
/// Register action that will be invoked during the ffmpeg processing, when a line is output
///
///
- public FFMpegArgumentProcessor NotifyOnOutput(Action onOutput)
+ public FFMpegArgumentProcessor NotifyOnOutput(Action onOutput)
{
_onOutput = onOutput;
return this;
}
+ public FFMpegArgumentProcessor NotifyOnError(Action onError)
+ {
+ _onError = onError;
+ return this;
+ }
public FFMpegArgumentProcessor CancellableThrough(out Action cancel, int timeout = 0)
{
cancel = () => CancelEvent?.Invoke(this, timeout);
@@ -80,85 +86,83 @@ public FFMpegArgumentProcessor Configure(Action configureOptions)
public bool ProcessSynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
{
var options = GetConfiguredOptions(ffMpegOptions);
- using var instance = PrepareInstance(options, out var cancellationTokenSource);
+ var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource);
- void OnCancelEvent(object sender, int timeout)
- {
- instance.SendInput("q");
-
- if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
- {
- cancellationTokenSource.Cancel();
- instance.Started = false;
- }
- }
- CancelEvent += OnCancelEvent;
- instance.Exited += delegate { cancellationTokenSource.Cancel(); };
-
- var errorCode = -1;
+
+ IProcessResult? processResult = null;
try
{
- errorCode = Process(instance, cancellationTokenSource).ConfigureAwait(false).GetAwaiter().GetResult();
+ processResult = Process(processArguments, cancellationTokenSource).ConfigureAwait(false).GetAwaiter().GetResult();
}
- catch (Exception e)
+ catch (OperationCanceledException)
{
- if (!HandleException(throwOnError, e, instance.ErrorData)) return false;
- }
- finally
- {
- CancelEvent -= OnCancelEvent;
+ if (throwOnError)
+ throw;
}
- return HandleCompletion(throwOnError, errorCode, instance.ErrorData);
+ return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty());
}
public async Task ProcessAsynchronously(bool throwOnError = true, FFOptions? ffMpegOptions = null)
{
var options = GetConfiguredOptions(ffMpegOptions);
- using var instance = PrepareInstance(options, out var cancellationTokenSource);
+ var processArguments = PrepareProcessArguments(options, out var cancellationTokenSource);
+
+ IProcessResult? processResult = null;
+ try
+ {
+ processResult = await Process(processArguments, cancellationTokenSource).ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ if (throwOnError)
+ throw;
+ }
+
+ return HandleCompletion(throwOnError, processResult?.ExitCode ?? -1, processResult?.ErrorData ?? Array.Empty());
+ }
+ private async Task Process(ProcessArguments processArguments, CancellationTokenSource cancellationTokenSource)
+ {
+ IProcessResult processResult = null!;
+
+ _ffMpegArguments.Pre();
+
+ using var instance = processArguments.Start();
+ var cancelled = false;
void OnCancelEvent(object sender, int timeout)
{
+ cancelled = true;
instance.SendInput("q");
if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
{
cancellationTokenSource.Cancel();
- instance.Started = false;
+ instance.Kill();
}
}
CancelEvent += OnCancelEvent;
- var errorCode = -1;
try
{
- errorCode = await Process(instance, cancellationTokenSource).ConfigureAwait(false);
- }
- catch (Exception e)
- {
- if (!HandleException(throwOnError, e, instance.ErrorData)) return false;
+ await Task.WhenAll(instance.WaitForExitAsync().ContinueWith(t =>
+ {
+ processResult = t.Result;
+ cancellationTokenSource.Cancel();
+ _ffMpegArguments.Post();
+ }), _ffMpegArguments.During(cancellationTokenSource.Token)).ConfigureAwait(false);
+
+ if (cancelled)
+ {
+ throw new OperationCanceledException("ffmpeg processing was cancelled");
+ }
+
+ return processResult;
}
finally
{
CancelEvent -= OnCancelEvent;
}
-
- return HandleCompletion(throwOnError, errorCode, instance.ErrorData);
- }
-
- private async Task Process(Instance instance, CancellationTokenSource cancellationTokenSource)
- {
- var errorCode = -1;
-
- _ffMpegArguments.Pre();
- await Task.WhenAll(instance.FinishedRunning().ContinueWith(t =>
- {
- errorCode = t.Result;
- cancellationTokenSource.Cancel();
- _ffMpegArguments.Post();
- }), _ffMpegArguments.During(cancellationTokenSource.Token)).ConfigureAwait(false);
-
- return errorCode;
}
private bool HandleCompletion(bool throwOnError, int exitCode, IReadOnlyList errorData)
@@ -184,7 +188,7 @@ internal FFOptions GetConfiguredOptions(FFOptions? ffOptions)
return options;
}
- private Instance PrepareInstance(FFOptions ffOptions,
+ private ProcessArguments PrepareProcessArguments(FFOptions ffOptions,
out CancellationTokenSource cancellationTokenSource)
{
FFMpegHelper.RootExceptionCheck();
@@ -197,30 +201,29 @@ private Instance PrepareInstance(FFOptions ffOptions,
StandardErrorEncoding = ffOptions.Encoding,
WorkingDirectory = ffOptions.WorkingDirectory
};
- var instance = new Instance(startInfo);
+ var processArguments = new ProcessArguments(startInfo);
cancellationTokenSource = new CancellationTokenSource();
if (_onOutput != null || _onTimeProgress != null || (_onPercentageProgress != null && _totalTimespan != null))
- instance.DataReceived += OutputData;
+ processArguments.OutputDataReceived += OutputData;
+
+ if (_onError != null)
+ processArguments.ErrorDataReceived += ErrorData;
- return instance;
+ return processArguments;
}
-
- private static bool HandleException(bool throwOnError, Exception e, IReadOnlyList errorData)
+ private void ErrorData(object sender, string msg)
{
- if (!throwOnError)
- return false;
-
- throw new FFMpegException(FFMpegExceptionType.Process, "Exception thrown during processing", e, string.Join("\n", errorData));
+ _onError?.Invoke(msg);
}
- private void OutputData(object sender, (DataType Type, string Data) msg)
+ private void OutputData(object sender, string msg)
{
- Debug.WriteLine(msg.Data);
- _onOutput?.Invoke(msg.Data, msg.Type);
+ Debug.WriteLine(msg);
+ _onOutput?.Invoke(msg);
- var match = ProgressRegex.Match(msg.Data);
+ var match = ProgressRegex.Match(msg);
if (!match.Success) return;
var processed = TimeSpan.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs
index 6c9784d..c100c85 100644
--- a/FFMpegCore/FFMpeg/FFMpegArguments.cs
+++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs
@@ -2,8 +2,10 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
+
using FFMpegCore.Arguments;
using FFMpegCore.Builders.MetaData;
using FFMpegCore.Pipes;
@@ -13,10 +15,16 @@ namespace FFMpegCore
public sealed class FFMpegArguments : FFMpegArgumentsBase
{
private readonly FFMpegGlobalArguments _globalArguments = new FFMpegGlobalArguments();
-
+
private FFMpegArguments() { }
- public string Text => string.Join(" ", _globalArguments.Arguments.Concat(Arguments).Select(arg => arg.Text));
+ public string Text => GetText();
+
+ private string GetText()
+ {
+ var allArguments = _globalArguments.Arguments.Concat(Arguments).ToArray();
+ return string.Join(" ", allArguments.Select(arg => arg is IDynamicArgument dynArg ? dynArg.GetText(allArguments) : arg.Text));
+ }
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);
@@ -26,7 +34,7 @@ private FFMpegArguments() { }
public static FFMpegArguments FromDeviceInput(string device, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputDeviceArgument(device), addArguments);
public static FFMpegArguments FromPipeInput(IPipeSource sourcePipe, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputPipeArgument(sourcePipe), addArguments);
-
+
public FFMpegArguments WithGlobalOptions(Action configureOptions)
{
configureOptions(_globalArguments);
@@ -42,6 +50,13 @@ public FFMpegArguments WithGlobalOptions(Action configure
public FFMpegArguments AddMetaData(string content, Action? addArguments = null) => WithInput(new MetaDataArgument(content), addArguments);
public FFMpegArguments AddMetaData(IReadOnlyMetaData metaData, Action? addArguments = null) => WithInput(new MetaDataArgument(MetaDataSerializer.Instance.Serialize(metaData)), addArguments);
+
+ ///
+ /// Maps the metadata of the given stream
+ ///
+ /// null means, the previous input will be used
+ public FFMpegArguments MapMetaData(int? inputIndex = null, Action? addArguments = null) => WithInput(new MapMetadataArgument(inputIndex), addArguments);
+
private FFMpegArguments WithInput(IInputArgument inputArgument, Action? addArguments)
{
var arguments = new FFMpegArgumentOptions();
@@ -81,4 +96,4 @@ internal void Post()
argument.Post();
}
}
-}
\ No newline at end of file
+}
diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj
index afabd90..a0464c7 100644
--- a/FFMpegCore/FFMpegCore.csproj
+++ b/FFMpegCore/FFMpegCore.csproj
@@ -32,9 +32,9 @@
-
+
-
+
diff --git a/FFMpegCore/FFProbe/AudioStream.cs b/FFMpegCore/FFProbe/AudioStream.cs
index d6f4b33..50c5572 100644
--- a/FFMpegCore/FFProbe/AudioStream.cs
+++ b/FFMpegCore/FFProbe/AudioStream.cs
@@ -2,9 +2,9 @@
{
public class AudioStream : MediaStream
{
- public int Channels { get; internal set; }
- public string ChannelLayout { get; internal set; } = null!;
- public int SampleRateHz { get; internal set; }
- public string Profile { get; internal set; } = null!;
+ public int Channels { get; set; }
+ public string ChannelLayout { get; set; } = null!;
+ public int SampleRateHz { get; set; }
+ public string Profile { get; set; } = null!;
}
}
\ No newline at end of file
diff --git a/FFMpegCore/FFProbe/FFProbe.cs b/FFMpegCore/FFProbe/FFProbe.cs
index 36f050c..21cf3af 100644
--- a/FFMpegCore/FFProbe/FFProbe.cs
+++ b/FFMpegCore/FFProbe/FFProbe.cs
@@ -2,6 +2,7 @@
using System.Diagnostics;
using System.IO;
using System.Text.Json;
+using System.Threading;
using System.Threading.Tasks;
using FFMpegCore.Arguments;
using FFMpegCore.Exceptions;
@@ -13,61 +14,55 @@ namespace FFMpegCore
{
public static class FFProbe
{
- public static IMediaAnalysis Analyse(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
+ public static IMediaAnalysis Analyse(string filePath, FFOptions? ffOptions = null)
{
- if (!File.Exists(filePath))
- throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
+ ThrowIfInputFileDoesNotExist(filePath);
- using var instance = PrepareStreamAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
- var exitCode = instance.BlockUntilFinished();
- if (exitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
+ var processArguments = PrepareStreamAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
+ var result = processArguments.StartAndWaitForExit();
+ ThrowIfExitCodeNotZero(result);
- return ParseOutput(instance);
+ return ParseOutput(result);
}
- public static FFProbeFrames GetFrames(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
+
+ public static FFProbeFrames GetFrames(string filePath, FFOptions? ffOptions = null)
{
- if (!File.Exists(filePath))
- throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
+ ThrowIfInputFileDoesNotExist(filePath);
- using var instance = PrepareFrameAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
- var exitCode = instance.BlockUntilFinished();
- if (exitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
+ var instance = PrepareFrameAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
+ var result = instance.StartAndWaitForExit();
+ ThrowIfExitCodeNotZero(result);
- return ParseFramesOutput(instance);
+ return ParseFramesOutput(result);
}
- public static FFProbePackets GetPackets(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
+ public static FFProbePackets GetPackets(string filePath, FFOptions? ffOptions = null)
{
- if (!File.Exists(filePath))
- throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
+ ThrowIfInputFileDoesNotExist(filePath);
- using var instance = PreparePacketAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
- var exitCode = instance.BlockUntilFinished();
- if (exitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
+ var instance = PreparePacketAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
+ var result = instance.StartAndWaitForExit();
+ ThrowIfExitCodeNotZero(result);
- return ParsePacketsOutput(instance);
+ return ParsePacketsOutput(result);
}
- public static IMediaAnalysis Analyse(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
+ public static IMediaAnalysis Analyse(Uri uri, FFOptions? ffOptions = null)
{
- using var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
- var exitCode = instance.BlockUntilFinished();
- if (exitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
+ var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current);
+ var result = instance.StartAndWaitForExit();
+ ThrowIfExitCodeNotZero(result);
- return ParseOutput(instance);
+ return ParseOutput(result);
}
- public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
+ public static IMediaAnalysis Analyse(Stream stream, FFOptions? ffOptions = null)
{
var streamPipeSource = new StreamPipeSource(stream);
var pipeArgument = new InputPipeArgument(streamPipeSource);
- using var instance = PrepareStreamAnalysisInstance(pipeArgument.PipePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
+ var instance = PrepareStreamAnalysisInstance(pipeArgument.PipePath, ffOptions ?? GlobalFFOptions.Current);
pipeArgument.Pre();
- var task = instance.FinishedRunning();
+ var task = instance.StartAndWaitForExitAsync();
try
{
pipeArgument.During().ConfigureAwait(false).GetAwaiter().GetResult();
@@ -77,65 +72,60 @@ public static IMediaAnalysis Analyse(Stream stream, int outputCapacity = int.Max
{
pipeArgument.Post();
}
- var exitCode = task.ConfigureAwait(false).GetAwaiter().GetResult();
- if (exitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
+ var result = task.ConfigureAwait(false).GetAwaiter().GetResult();
+ ThrowIfExitCodeNotZero(result);
- return ParseOutput(instance);
+ return ParseOutput(result);
}
- public static async Task AnalyseAsync(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
+
+ public static async Task AnalyseAsync(string filePath, FFOptions? ffOptions = null, CancellationToken cancellationToken = default)
{
- if (!File.Exists(filePath))
- throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
+ ThrowIfInputFileDoesNotExist(filePath);
- using var instance = PrepareStreamAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
- var exitCode = await instance.FinishedRunning().ConfigureAwait(false);
- if (exitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
+ var instance = PrepareStreamAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
+ var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false);
+ ThrowIfExitCodeNotZero(result);
- return ParseOutput(instance);
+ return ParseOutput(result);
}
- public static async Task GetFramesAsync(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
+ public static async Task GetFramesAsync(string filePath, FFOptions? ffOptions = null, CancellationToken cancellationToken = default)
{
- if (!File.Exists(filePath))
- throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
+ ThrowIfInputFileDoesNotExist(filePath);
- using var instance = PrepareFrameAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
- await instance.FinishedRunning().ConfigureAwait(false);
- return ParseFramesOutput(instance);
+ var instance = PrepareFrameAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
+ var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false);
+ return ParseFramesOutput(result);
}
- public static async Task GetPacketsAsync(string filePath, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
+ public static async Task GetPacketsAsync(string filePath, FFOptions? ffOptions = null, CancellationToken cancellationToken = default)
{
- if (!File.Exists(filePath))
- throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
+ ThrowIfInputFileDoesNotExist(filePath);
- using var instance = PreparePacketAnalysisInstance(filePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
- await instance.FinishedRunning().ConfigureAwait(false);
- return ParsePacketsOutput(instance);
+ var instance = PreparePacketAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
+ var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false);
+ return ParsePacketsOutput(result);
}
- public static async Task AnalyseAsync(Uri uri, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
+ public static async Task AnalyseAsync(Uri uri, FFOptions? ffOptions = null, CancellationToken cancellationToken = default)
{
- using var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
- var exitCode = await instance.FinishedRunning().ConfigureAwait(false);
- if (exitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", null, string.Join("\n", instance.ErrorData));
+ var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current);
+ var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false);
+ ThrowIfExitCodeNotZero(result);
- return ParseOutput(instance);
+ return ParseOutput(result);
}
- public static async Task AnalyseAsync(Stream stream, int outputCapacity = int.MaxValue, FFOptions? ffOptions = null)
+ public static async Task AnalyseAsync(Stream stream, FFOptions? ffOptions = null, CancellationToken cancellationToken = default)
{
var streamPipeSource = new StreamPipeSource(stream);
var pipeArgument = new InputPipeArgument(streamPipeSource);
- using var instance = PrepareStreamAnalysisInstance(pipeArgument.PipePath, outputCapacity, ffOptions ?? GlobalFFOptions.Current);
+ var instance = PrepareStreamAnalysisInstance(pipeArgument.PipePath, ffOptions ?? GlobalFFOptions.Current);
pipeArgument.Pre();
- var task = instance.FinishedRunning();
+ var task = instance.StartAndWaitForExitAsync(cancellationToken);
try
{
- await pipeArgument.During().ConfigureAwait(false);
+ await pipeArgument.During(cancellationToken).ConfigureAwait(false);
}
catch(IOException)
{
@@ -144,15 +134,14 @@ public static async Task AnalyseAsync(Stream stream, int outputC
{
pipeArgument.Post();
}
- var exitCode = await task.ConfigureAwait(false);
- if (exitCode != 0)
- throw new FFProbeProcessException($"ffprobe exited with non-zero exit-code ({exitCode} - {string.Join("\n", instance.ErrorData)})", instance.ErrorData);
+ var result = await task.ConfigureAwait(false);
+ ThrowIfExitCodeNotZero(result);
pipeArgument.Post();
- return ParseOutput(instance);
+ return ParseOutput(result);
}
- private static IMediaAnalysis ParseOutput(Instance instance)
+ private static IMediaAnalysis ParseOutput(IProcessResult instance)
{
var json = string.Join(string.Empty, instance.OutputData);
var ffprobeAnalysis = JsonSerializer.Deserialize(json, new JsonSerializerOptions
@@ -163,9 +152,10 @@ private static IMediaAnalysis ParseOutput(Instance instance)
if (ffprobeAnalysis?.Format == null)
throw new FormatNullException();
+ ffprobeAnalysis.ErrorData = instance.ErrorData;
return new MediaAnalysis(ffprobeAnalysis);
}
- private static FFProbeFrames ParseFramesOutput(Instance instance)
+ private static FFProbeFrames ParseFramesOutput(IProcessResult instance)
{
var json = string.Join(string.Empty, instance.OutputData);
var ffprobeAnalysis = JsonSerializer.Deserialize(json, new JsonSerializerOptions
@@ -174,10 +164,10 @@ private static FFProbeFrames ParseFramesOutput(Instance instance)
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString | System.Text.Json.Serialization.JsonNumberHandling.WriteAsString
}) ;
- return ffprobeAnalysis;
+ return ffprobeAnalysis!;
}
- private static FFProbePackets ParsePacketsOutput(Instance instance)
+ private static FFProbePackets ParsePacketsOutput(IProcessResult instance)
{
var json = string.Join(string.Empty, instance.OutputData);
var ffprobeAnalysis = JsonSerializer.Deserialize(json, new JsonSerializerOptions
@@ -186,29 +176,44 @@ private static FFProbePackets ParsePacketsOutput(Instance instance)
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString | System.Text.Json.Serialization.JsonNumberHandling.WriteAsString
}) ;
- return ffprobeAnalysis;
+ return ffprobeAnalysis!;
}
+ private static void ThrowIfInputFileDoesNotExist(string filePath)
+ {
+ if (!File.Exists(filePath))
+ {
+ throw new FFMpegException(FFMpegExceptionType.File, $"No file found at '{filePath}'");
+ }
+ }
- private static Instance PrepareStreamAnalysisInstance(string filePath, int outputCapacity, FFOptions ffOptions)
- => PrepareInstance($"-loglevel error -print_format json -show_format -sexagesimal -show_streams \"{filePath}\"", outputCapacity, ffOptions);
- private static Instance PrepareFrameAnalysisInstance(string filePath, int outputCapacity, FFOptions ffOptions)
- => PrepareInstance($"-loglevel error -print_format json -show_frames -v quiet -sexagesimal \"{filePath}\"", outputCapacity, ffOptions);
- private static Instance PreparePacketAnalysisInstance(string filePath, int outputCapacity, FFOptions ffOptions)
- => PrepareInstance($"-loglevel error -print_format json -show_packets -v quiet -sexagesimal \"{filePath}\"", outputCapacity, ffOptions);
+ private static void ThrowIfExitCodeNotZero(IProcessResult result)
+ {
+ if (result.ExitCode != 0)
+ {
+ var message = $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})";
+ throw new FFMpegException(FFMpegExceptionType.Process, message, null, string.Join("\n", result.ErrorData));
+ }
+ }
+
+ private static ProcessArguments PrepareStreamAnalysisInstance(string filePath, FFOptions ffOptions)
+ => PrepareInstance($"-loglevel error -print_format json -show_format -sexagesimal -show_streams \"{filePath}\"", ffOptions);
+ private static ProcessArguments PrepareFrameAnalysisInstance(string filePath, FFOptions ffOptions)
+ => PrepareInstance($"-loglevel error -print_format json -show_frames -v quiet -sexagesimal \"{filePath}\"", ffOptions);
+ private static ProcessArguments PreparePacketAnalysisInstance(string filePath, FFOptions ffOptions)
+ => PrepareInstance($"-loglevel error -print_format json -show_packets -v quiet -sexagesimal \"{filePath}\"", ffOptions);
- private static Instance PrepareInstance(string arguments, int outputCapacity, FFOptions ffOptions)
+ private static ProcessArguments PrepareInstance(string arguments, FFOptions ffOptions)
{
FFProbeHelper.RootExceptionCheck();
FFProbeHelper.VerifyFFProbeExists(ffOptions);
- var startInfo = new ProcessStartInfo(GlobalFFOptions.GetFFProbeBinaryPath(), arguments)
+ var startInfo = new ProcessStartInfo(GlobalFFOptions.GetFFProbeBinaryPath(ffOptions), arguments)
{
StandardOutputEncoding = ffOptions.Encoding,
StandardErrorEncoding = ffOptions.Encoding,
WorkingDirectory = ffOptions.WorkingDirectory
};
- var instance = new Instance(startInfo) { DataBufferCapacity = outputCapacity };
- return instance;
+ return new ProcessArguments(startInfo);
}
}
}
diff --git a/FFMpegCore/FFProbe/FFProbeAnalysis.cs b/FFMpegCore/FFProbe/FFProbeAnalysis.cs
index 2177307..cbbb9fd 100644
--- a/FFMpegCore/FFProbe/FFProbeAnalysis.cs
+++ b/FFMpegCore/FFProbe/FFProbeAnalysis.cs
@@ -10,6 +10,9 @@ public class FFProbeAnalysis
[JsonPropertyName("format")]
public Format Format { get; set; } = null!;
+
+ [JsonIgnore]
+ public IReadOnlyList ErrorData { get; set; }
}
public class FFProbeStream : ITagsContainer, IDispositionContainer
diff --git a/FFMpegCore/FFProbe/FrameAnalysis.cs b/FFMpegCore/FFProbe/FrameAnalysis.cs
index a22cd24..08e5037 100644
--- a/FFMpegCore/FFProbe/FrameAnalysis.cs
+++ b/FFMpegCore/FFProbe/FrameAnalysis.cs
@@ -6,7 +6,7 @@ namespace FFMpegCore
public class FFProbeFrameAnalysis
{
[JsonPropertyName("media_type")]
- public string MediaType { get; set; }
+ public string MediaType { get; set; } = null!;
[JsonPropertyName("stream_index")]
public int StreamIndex { get; set; }
@@ -18,25 +18,25 @@ public class FFProbeFrameAnalysis
public long PacketPts { get; set; }
[JsonPropertyName("pkt_pts_time")]
- public string PacketPtsTime { get; set; }
+ public string PacketPtsTime { get; set; } = null!;
[JsonPropertyName("pkt_dts")]
public long PacketDts { get; set; }
[JsonPropertyName("pkt_dts_time")]
- public string PacketDtsTime { get; set; }
+ public string PacketDtsTime { get; set; } = null!;
[JsonPropertyName("best_effort_timestamp")]
public long BestEffortTimestamp { get; set; }
[JsonPropertyName("best_effort_timestamp_time")]
- public string BestEffortTimestampTime { get; set; }
+ public string BestEffortTimestampTime { get; set; } = null!;
[JsonPropertyName("pkt_duration")]
public int PacketDuration { get; set; }
[JsonPropertyName("pkt_duration_time")]
- public string PacketDurationTime { get; set; }
+ public string PacketDurationTime { get; set; } = null!;
[JsonPropertyName("pkt_pos")]
public long PacketPos { get; set; }
@@ -51,10 +51,10 @@ public class FFProbeFrameAnalysis
public long Height { get; set; }
[JsonPropertyName("pix_fmt")]
- public string PixelFormat { get; set; }
+ public string PixelFormat { get; set; } = null!;
[JsonPropertyName("pict_type")]
- public string PictureType { get; set; }
+ public string PictureType { get; set; } = null!;
[JsonPropertyName("coded_picture_number")]
public long CodedPictureNumber { get; set; }
@@ -72,12 +72,12 @@ public class FFProbeFrameAnalysis
public int RepeatPicture { get; set; }
[JsonPropertyName("chroma_location")]
- public string ChromaLocation { get; set; }
+ public string ChromaLocation { get; set; } = null!;
}
public class FFProbeFrames
{
[JsonPropertyName("frames")]
- public List Frames { get; set; }
+ public List Frames { get; set; } = null!;
}
}
diff --git a/FFMpegCore/FFProbe/IMediaAnalysis.cs b/FFMpegCore/FFProbe/IMediaAnalysis.cs
index 7be3b20..5884f74 100644
--- a/FFMpegCore/FFProbe/IMediaAnalysis.cs
+++ b/FFMpegCore/FFProbe/IMediaAnalysis.cs
@@ -13,5 +13,6 @@ public interface IMediaAnalysis
List VideoStreams { get; }
List AudioStreams { get; }
List SubtitleStreams { get; }
+ IReadOnlyList ErrorData { get; }
}
}
diff --git a/FFMpegCore/FFProbe/MediaAnalysis.cs b/FFMpegCore/FFProbe/MediaAnalysis.cs
index a2db068..e1fbd1d 100644
--- a/FFMpegCore/FFProbe/MediaAnalysis.cs
+++ b/FFMpegCore/FFProbe/MediaAnalysis.cs
@@ -13,6 +13,7 @@ internal MediaAnalysis(FFProbeAnalysis analysis)
VideoStreams = analysis.Streams.Where(stream => stream.CodecType == "video").Select(ParseVideoStream).ToList();
AudioStreams = analysis.Streams.Where(stream => stream.CodecType == "audio").Select(ParseAudioStream).ToList();
SubtitleStreams = analysis.Streams.Where(stream => stream.CodecType == "subtitle").Select(ParseSubtitleStream).ToList();
+ ErrorData = analysis.ErrorData ?? new List().AsReadOnly();
}
private MediaFormat ParseFormat(Format analysisFormat)
@@ -25,7 +26,7 @@ private MediaFormat ParseFormat(Format analysisFormat)
StreamCount = analysisFormat.NbStreams,
ProbeScore = analysisFormat.ProbeScore,
BitRate = long.Parse(analysisFormat.BitRate ?? "0"),
- Tags = analysisFormat.Tags,
+ Tags = analysisFormat.Tags.ToCaseInsensitive(),
};
}
@@ -45,7 +46,8 @@ private MediaFormat ParseFormat(Format analysisFormat)
public List VideoStreams { get; }
public List AudioStreams { get; }
public List SubtitleStreams { get; }
-
+ public IReadOnlyList ErrorData { get; }
+
private VideoStream ParseVideoStream(FFProbeStream stream)
{
return new VideoStream
@@ -68,7 +70,7 @@ private VideoStream ParseVideoStream(FFProbeStream stream)
Rotation = (int)float.Parse(stream.GetRotate() ?? "0"),
Language = stream.GetLanguage(),
Disposition = MediaAnalysisUtils.FormatDisposition(stream.Disposition),
- Tags = stream.Tags,
+ Tags = stream.Tags.ToCaseInsensitive(),
};
}
@@ -89,7 +91,7 @@ private AudioStream ParseAudioStream(FFProbeStream stream)
Profile = stream.Profile,
Language = stream.GetLanguage(),
Disposition = MediaAnalysisUtils.FormatDisposition(stream.Disposition),
- Tags = stream.Tags,
+ Tags = stream.Tags.ToCaseInsensitive(),
};
}
@@ -104,15 +106,20 @@ private SubtitleStream ParseSubtitleStream(FFProbeStream stream)
Duration = MediaAnalysisUtils.ParseDuration(stream),
Language = stream.GetLanguage(),
Disposition = MediaAnalysisUtils.FormatDisposition(stream.Disposition),
- Tags = stream.Tags,
+ Tags = stream.Tags.ToCaseInsensitive(),
};
}
+
}
public static class MediaAnalysisUtils
{
private static readonly Regex DurationRegex = new Regex(@"^(\d+):(\d{1,2}):(\d{1,2})\.(\d{1,3})", RegexOptions.Compiled);
+ internal static Dictionary? ToCaseInsensitive(this Dictionary? dictionary)
+ {
+ return dictionary?.ToDictionary(tag => tag.Key, tag => tag.Value, StringComparer.OrdinalIgnoreCase) ?? new Dictionary();
+ }
public static double DivideRatio((double, double) ratio) => ratio.Item1 / ratio.Item2;
public static (int, int) ParseRatioInt(string input, char separator)
@@ -183,7 +190,7 @@ public static TimeSpan ParseDuration(FFProbeStream ffProbeStream)
return null;
}
- var result = new Dictionary(disposition.Count);
+ var result = new Dictionary(disposition.Count, StringComparer.Ordinal);
foreach (var pair in disposition)
{
diff --git a/FFMpegCore/FFProbe/MediaStream.cs b/FFMpegCore/FFProbe/MediaStream.cs
index 68bc78f..ffab04b 100644
--- a/FFMpegCore/FFProbe/MediaStream.cs
+++ b/FFMpegCore/FFProbe/MediaStream.cs
@@ -5,18 +5,18 @@
namespace FFMpegCore
{
- public class MediaStream
+ public abstract class MediaStream
{
- public int Index { get; internal set; }
- public string CodecName { get; internal set; } = null!;
- public string CodecLongName { get; internal set; } = null!;
+ public int Index { get; set; }
+ public string CodecName { get; set; } = null!;
+ public string CodecLongName { get; set; } = null!;
public string CodecTagString { get; set; } = null!;
public string CodecTag { get; set; } = null!;
- public long BitRate { get; internal set; }
- public TimeSpan Duration { get; internal set; }
- public string? Language { get; internal set; }
- public Dictionary? Disposition { get; internal set; }
- public Dictionary? Tags { get; internal set; }
+ public long BitRate { get; set; }
+ public TimeSpan Duration { get; set; }
+ public string? Language { get; set; }
+ public Dictionary? Disposition { get; set; }
+ public Dictionary? Tags { get; set; }
public Codec GetCodecInfo() => FFMpeg.GetCodec(CodecName);
}
diff --git a/FFMpegCore/FFProbe/PacketAnalysis.cs b/FFMpegCore/FFProbe/PacketAnalysis.cs
index d4da0f5..babe403 100644
--- a/FFMpegCore/FFProbe/PacketAnalysis.cs
+++ b/FFMpegCore/FFProbe/PacketAnalysis.cs
@@ -6,7 +6,7 @@ namespace FFMpegCore
public class FFProbePacketAnalysis
{
[JsonPropertyName("codec_type")]
- public string CodecType { get; set; }
+ public string CodecType { get; set; } = null!;
[JsonPropertyName("stream_index")]
public int StreamIndex { get; set; }
@@ -15,19 +15,19 @@ public class FFProbePacketAnalysis
public long Pts { get; set; }
[JsonPropertyName("pts_time")]
- public string PtsTime { get; set; }
+ public string PtsTime { get; set; } = null!;
[JsonPropertyName("dts")]
public long Dts { get; set; }
[JsonPropertyName("dts_time")]
- public string DtsTime { get; set; }
+ public string DtsTime { get; set; } = null!;
[JsonPropertyName("duration")]
public int Duration { get; set; }
[JsonPropertyName("duration_time")]
- public string DurationTime { get; set; }
+ public string DurationTime { get; set; } = null!;
[JsonPropertyName("size")]
public int Size { get; set; }
@@ -36,12 +36,12 @@ public class FFProbePacketAnalysis
public long Pos { get; set; }
[JsonPropertyName("flags")]
- public string Flags { get; set; }
+ public string Flags { get; set; } = null!;
}
public class FFProbePackets
{
[JsonPropertyName("packets")]
- public List Packets { get; set; }
+ public List Packets { get; set; } = null!;
}
}
diff --git a/FFMpegCore/FFProbe/ProcessArgumentsExtensions.cs b/FFMpegCore/FFProbe/ProcessArgumentsExtensions.cs
new file mode 100644
index 0000000..1647e9b
--- /dev/null
+++ b/FFMpegCore/FFProbe/ProcessArgumentsExtensions.cs
@@ -0,0 +1,20 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Instances;
+
+namespace FFMpegCore
+{
+ public static class ProcessArgumentsExtensions
+ {
+ public static IProcessResult StartAndWaitForExit(this ProcessArguments processArguments)
+ {
+ using var instance = processArguments.Start();
+ return instance.WaitForExit();
+ }
+ public static async Task StartAndWaitForExitAsync(this ProcessArguments processArguments, CancellationToken cancellationToken = default)
+ {
+ using var instance = processArguments.Start();
+ return await instance.WaitForExitAsync(cancellationToken);
+ }
+ }
+}
\ No newline at end of file
diff --git a/FFMpegCore/FFProbe/VideoStream.cs b/FFMpegCore/FFProbe/VideoStream.cs
index 0bcfc09..a07cdd9 100644
--- a/FFMpegCore/FFProbe/VideoStream.cs
+++ b/FFMpegCore/FFProbe/VideoStream.cs
@@ -4,15 +4,16 @@ namespace FFMpegCore
{
public class VideoStream : MediaStream
{
- public double AvgFrameRate { get; internal set; }
- public int BitsPerRawSample { get; internal set; }
- public (int Width, int Height) DisplayAspectRatio { get; internal set; }
- public string Profile { get; internal set; } = null!;
- public int Width { get; internal set; }
- public int Height { get; internal set; }
- public double FrameRate { get; internal set; }
- public string PixelFormat { get; internal set; } = null!;
+ public double AvgFrameRate { get; set; }
+ public int BitsPerRawSample { get; set; }
+ public (int Width, int Height) DisplayAspectRatio { get; set; }
+ public string Profile { get; set; } = null!;
+ public int Width { get; set; }
+ public int Height { get; set; }
+ public double FrameRate { get; set; }
+ public string PixelFormat { get; set; } = null!;
public int Rotation { get; set; }
+ public double AverageFrameRate { get; set; }
public PixelFormat GetPixelFormatInfo() => FFMpeg.GetPixelFormat(PixelFormat);
}
diff --git a/FFMpegCore/Helpers/FFMpegHelper.cs b/FFMpegCore/Helpers/FFMpegHelper.cs
index 12e52c3..cb3b4cf 100644
--- a/FFMpegCore/Helpers/FFMpegHelper.cs
+++ b/FFMpegCore/Helpers/FFMpegHelper.cs
@@ -38,8 +38,8 @@ public static void RootExceptionCheck()
public static void VerifyFFMpegExists(FFOptions ffMpegOptions)
{
if (_ffmpegVerified) return;
- var (exitCode, _) = Instance.Finish(GlobalFFOptions.GetFFMpegBinaryPath(ffMpegOptions), "-version");
- _ffmpegVerified = exitCode == 0;
+ var result = Instance.Finish(GlobalFFOptions.GetFFMpegBinaryPath(ffMpegOptions), "-version");
+ _ffmpegVerified = result.ExitCode == 0;
if (!_ffmpegVerified)
throw new FFMpegException(FFMpegExceptionType.Operation, "ffmpeg was not found on your system");
}
diff --git a/FFMpegCore/Helpers/FFProbeHelper.cs b/FFMpegCore/Helpers/FFProbeHelper.cs
index 4989542..f5b3472 100644
--- a/FFMpegCore/Helpers/FFProbeHelper.cs
+++ b/FFMpegCore/Helpers/FFProbeHelper.cs
@@ -27,8 +27,8 @@ public static void RootExceptionCheck()
public static void VerifyFFProbeExists(FFOptions ffMpegOptions)
{
if (_ffprobeVerified) return;
- var (exitCode, _) = Instance.Finish(GlobalFFOptions.GetFFProbeBinaryPath(ffMpegOptions), "-version");
- _ffprobeVerified = exitCode == 0;
+ var result = Instance.Finish(GlobalFFOptions.GetFFProbeBinaryPath(ffMpegOptions), "-version");
+ _ffprobeVerified = result.ExitCode == 0;
if (!_ffprobeVerified)
throw new FFProbeException("ffprobe was not found on your system");
}
diff --git a/README.md b/README.md
index 2c55520..7ed60ae 100644
--- a/README.md
+++ b/README.md
@@ -3,14 +3,14 @@
[![GitHub issues](https://img.shields.io/github/issues/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/issues)
[![GitHub stars](https://img.shields.io/github/stars/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/stargazers)
[![GitHub](https://img.shields.io/github/license/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/blob/master/LICENSE)
-[![CI](https://github.com/rosenbjerg/FFMpegCore/workflows/CI/badge.svg)](https://github.com/rosenbjerg/FFMpegCore/actions?query=workflow%3ACI)
+[![CI](https://github.com/rosenbjerg/FFMpegCore/workflows/CI/badge.svg)](https://github.com/rosenbjerg/FFMpegCore/actions/workflows/ci.yml)
+[![GitHub code contributors](https://img.shields.io/github/contributors/rosenbjerg/FFMpegCore)](https://github.com/rosenbjerg/FFMpegCore/graphs/contributors)
A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications. Supports both synchronous and asynchronous calls
# API
## FFProbe
-
Use FFProbe to analyze media files:
```csharp
@@ -21,12 +21,12 @@ or
var mediaInfo = FFProbe.Analyse(inputPath);
```
-
## FFMpeg
Use FFMpeg to convert your media files.
Easily build your FFMpeg arguments using the fluent argument builder:
Convert input file to h264/aac scaled to 720p w/ faststart, for web playback
+
```csharp
FFMpegArguments
.FromFileInput(inputPath)
@@ -192,7 +192,7 @@ await FFMpegArguments
.Configure(options => options.WorkingDirectory = "./CurrentRunWorkingDir")
.Configure(options => options.TemporaryFilesFolder = "./CurrentRunTmpFolder")
.ProcessAsynchronously();
- ```
+```
### Option 2