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/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/FFMpeg/FFMpeg.cs b/FFMpegCore/FFMpeg/FFMpeg.cs
index d227a2e..4e7d47a 100644
--- a/FFMpegCore/FFMpeg/FFMpeg.cs
+++ b/FFMpegCore/FFMpeg/FFMpeg.cs
@@ -247,7 +247,10 @@ 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
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 75f0df1..15ad171 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;
@@ -15,38 +16,33 @@ public static class FFProbe
{
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);
var processArguments = PrepareStreamAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
var result = processArguments.StartAndWaitForExit();
- if (result.ExitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
+ ThrowIfExitCodeNotZero(result);
return ParseOutput(result);
}
+
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);
var instance = PrepareFrameAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
var result = instance.StartAndWaitForExit();
- if (result.ExitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
+ ThrowIfExitCodeNotZero(result);
return ParseFramesOutput(result);
}
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);
var instance = PreparePacketAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
var result = instance.StartAndWaitForExit();
- if (result.ExitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
+ ThrowIfExitCodeNotZero(result);
return ParsePacketsOutput(result);
}
@@ -55,8 +51,7 @@ public static IMediaAnalysis Analyse(Uri uri, FFOptions? ffOptions = null)
{
var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current);
var result = instance.StartAndWaitForExit();
- if (result.ExitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
+ ThrowIfExitCodeNotZero(result);
return ParseOutput(result);
}
@@ -78,64 +73,59 @@ public static IMediaAnalysis Analyse(Stream stream, FFOptions? ffOptions = null)
pipeArgument.Post();
}
var result = task.ConfigureAwait(false).GetAwaiter().GetResult();
- if (result.ExitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
+ ThrowIfExitCodeNotZero(result);
return ParseOutput(result);
}
- public static async Task AnalyseAsync(string filePath, 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);
var instance = PrepareStreamAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
- var result = await instance.StartAndWaitForExitAsync().ConfigureAwait(false);
- if (result.ExitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
+ var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false);
+ ThrowIfExitCodeNotZero(result);
return ParseOutput(result);
}
- public static async Task GetFramesAsync(string filePath, 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);
var instance = PrepareFrameAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
- var result = await instance.StartAndWaitForExitAsync().ConfigureAwait(false);
+ var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false);
return ParseFramesOutput(result);
}
- public static async Task GetPacketsAsync(string filePath, 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);
var instance = PreparePacketAnalysisInstance(filePath, ffOptions ?? GlobalFFOptions.Current);
- var result = await instance.StartAndWaitForExitAsync().ConfigureAwait(false);
+ var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false);
return ParsePacketsOutput(result);
}
- public static async Task AnalyseAsync(Uri uri, FFOptions? ffOptions = null)
+ public static async Task AnalyseAsync(Uri uri, FFOptions? ffOptions = null, CancellationToken cancellationToken = default)
{
var instance = PrepareStreamAnalysisInstance(uri.AbsoluteUri, ffOptions ?? GlobalFFOptions.Current);
- var result = await instance.StartAndWaitForExitAsync().ConfigureAwait(false);
- if (result.ExitCode != 0)
- throw new FFMpegException(FFMpegExceptionType.Process, $"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", null, string.Join("\n", result.ErrorData));
+ var result = await instance.StartAndWaitForExitAsync(cancellationToken).ConfigureAwait(false);
+ ThrowIfExitCodeNotZero(result);
return ParseOutput(result);
}
- public static async Task AnalyseAsync(Stream stream, 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);
var instance = PrepareStreamAnalysisInstance(pipeArgument.PipePath, ffOptions ?? GlobalFFOptions.Current);
pipeArgument.Pre();
- var task = instance.StartAndWaitForExitAsync();
+ var task = instance.StartAndWaitForExitAsync(cancellationToken);
try
{
- await pipeArgument.During().ConfigureAwait(false);
+ await pipeArgument.During(cancellationToken).ConfigureAwait(false);
}
catch(IOException)
{
@@ -145,8 +135,7 @@ public static async Task AnalyseAsync(Stream stream, FFOptions?
pipeArgument.Post();
}
var result = await task.ConfigureAwait(false);
- if (result.ExitCode != 0)
- throw new FFProbeProcessException($"ffprobe exited with non-zero exit-code ({result.ExitCode} - {string.Join("\n", result.ErrorData)})", result.ErrorData);
+ ThrowIfExitCodeNotZero(result);
pipeArgument.Post();
return ParseOutput(result);
@@ -174,7 +163,7 @@ private static FFProbeFrames ParseFramesOutput(IProcessResult instance)
NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowReadingFromString | System.Text.Json.Serialization.JsonNumberHandling.WriteAsString
}) ;
- return ffprobeAnalysis;
+ return ffprobeAnalysis!;
}
private static FFProbePackets ParsePacketsOutput(IProcessResult instance)
@@ -186,9 +175,25 @@ private static FFProbePackets ParsePacketsOutput(IProcessResult 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 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);
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/MediaAnalysis.cs b/FFMpegCore/FFProbe/MediaAnalysis.cs
index a2db068..b5f9c34 100644
--- a/FFMpegCore/FFProbe/MediaAnalysis.cs
+++ b/FFMpegCore/FFProbe/MediaAnalysis.cs
@@ -25,7 +25,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(),
};
}
@@ -68,7 +68,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 +89,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 +104,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 +188,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/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);
}