diff --git a/FFMpegCore.Extensions.Downloader/Enums/FFMpegBinaries.cs b/FFMpegCore.Extensions.Downloader/Enums/FFMpegBinaries.cs new file mode 100644 index 0000000..c3be69f --- /dev/null +++ b/FFMpegCore.Extensions.Downloader/Enums/FFMpegBinaries.cs @@ -0,0 +1,9 @@ +namespace FFMpegCore.Extensions.Downloader.Enums; + +[Flags] +public enum FFMpegBinaries : ushort +{ + FFMpeg = 1, + FFProbe = 2, + FFPlay = 4 +} diff --git a/FFMpegCore.Extensions.Downloader/Enums/FFMpegVersions.cs b/FFMpegCore.Extensions.Downloader/Enums/FFMpegVersions.cs new file mode 100644 index 0000000..c9f5dd3 --- /dev/null +++ b/FFMpegCore.Extensions.Downloader/Enums/FFMpegVersions.cs @@ -0,0 +1,39 @@ +using System.ComponentModel; + +namespace FFMpegCore.Extensions.Downloader.Enums; + +public enum FFMpegVersions : ushort +{ + [Description("https://ffbinaries.com/api/v1/version/latest")] + LatestAvailable, + + [Description("https://ffbinaries.com/api/v1/version/6.1")] + V6_1, + + [Description("https://ffbinaries.com/api/v1/version/5.1")] + V5_1, + + [Description("https://ffbinaries.com/api/v1/version/4.4.1")] + V4_4_1, + + [Description("https://ffbinaries.com/api/v1/version/4.2.1")] + V4_2_1, + + [Description("https://ffbinaries.com/api/v1/version/4.2")] + V4_2, + + [Description("https://ffbinaries.com/api/v1/version/4.1")] + V4_1, + + [Description("https://ffbinaries.com/api/v1/version/4.0")] + V4_0, + + [Description("https://ffbinaries.com/api/v1/version/3.4")] + V3_4, + + [Description("https://ffbinaries.com/api/v1/version/3.3")] + V3_3, + + [Description("https://ffbinaries.com/api/v1/version/3.2")] + V3_2 +} diff --git a/FFMpegCore.Extensions.Downloader/Enums/SupportedPlatforms.cs b/FFMpegCore.Extensions.Downloader/Enums/SupportedPlatforms.cs new file mode 100644 index 0000000..0378f3e --- /dev/null +++ b/FFMpegCore.Extensions.Downloader/Enums/SupportedPlatforms.cs @@ -0,0 +1,13 @@ +namespace FFMpegCore.Extensions.Downloader.Enums; + +public enum SupportedPlatforms : ushort +{ + Windows64, + Windows32, + Linux64, + Linux32, + LinuxArmhf, + LinuxArmel, + LinuxArm64, + Osx64 +} diff --git a/FFMpegCore.Extensions.Downloader/Exceptions/FFMpegDownloaderException.cs b/FFMpegCore.Extensions.Downloader/Exceptions/FFMpegDownloaderException.cs new file mode 100644 index 0000000..8355c4c --- /dev/null +++ b/FFMpegCore.Extensions.Downloader/Exceptions/FFMpegDownloaderException.cs @@ -0,0 +1,18 @@ +namespace FFMpegCore.Extensions.Downloader.Exceptions; + +/// +/// Custom exception for FFMpegDownloader +/// +public class FFMpegDownloaderException : Exception +{ + public readonly string Detail = ""; + + public FFMpegDownloaderException(string message) : base(message) + { + } + + public FFMpegDownloaderException(string message, string detail) : base(message) + { + Detail = detail; + } +} diff --git a/FFMpegCore.Extensions.Downloader/Extensions/EnumExtensions.cs b/FFMpegCore.Extensions.Downloader/Extensions/EnumExtensions.cs new file mode 100644 index 0000000..3336f11 --- /dev/null +++ b/FFMpegCore.Extensions.Downloader/Extensions/EnumExtensions.cs @@ -0,0 +1,31 @@ +using System.ComponentModel; + +namespace FFMpegCore.Extensions.Downloader.Extensions; + +public static class EnumExtensions +{ + internal static string GetDescription(this Enum enumValue) + { + var field = enumValue.GetType().GetField(enumValue.ToString()); + if (field == null) + { + return enumValue.ToString(); + } + + if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute) + { + return attribute.Description; + } + + return enumValue.ToString(); + } + + public static TEnum[] GetFlags(this TEnum input) where TEnum : Enum + { + return Enum.GetValues(input.GetType()) + .Cast() + .Where(input.HasFlag) + .Cast() + .ToArray(); + } +} diff --git a/FFMpegCore.Extensions.Downloader/FFMpegCore.Extensions.Downloader.csproj b/FFMpegCore.Extensions.Downloader/FFMpegCore.Extensions.Downloader.csproj new file mode 100644 index 0000000..2715c89 --- /dev/null +++ b/FFMpegCore.Extensions.Downloader/FFMpegCore.Extensions.Downloader.csproj @@ -0,0 +1,19 @@ + + + + true + FFMpeg downloader extension for FFMpegCore + 5.0.0 + ../nupkg + + - Updated dependencies + + ffmpeg ffprobe convert video audio mediafile resize analyze download install + Kerry Cao, Malte Rosenbjerg + + + + + + + diff --git a/FFMpegCore.Extensions.Downloader/FFMpegDownloader.cs b/FFMpegCore.Extensions.Downloader/FFMpegDownloader.cs new file mode 100644 index 0000000..f616c31 --- /dev/null +++ b/FFMpegCore.Extensions.Downloader/FFMpegDownloader.cs @@ -0,0 +1,83 @@ +using System.IO.Compression; +using System.Text.Json; +using FFMpegCore.Extensions.Downloader.Enums; +using FFMpegCore.Extensions.Downloader.Exceptions; +using FFMpegCore.Extensions.Downloader.Extensions; +using FFMpegCore.Extensions.Downloader.Models; + +namespace FFMpegCore.Extensions.Downloader; + +public static class FFMpegDownloader +{ + /// + /// Download the latest FFMpeg suite binaries for current platform + /// + /// used to explicitly state the version of binary you want to download + /// used to explicitly state the binaries you want to download (ffmpeg, ffprobe, ffplay) + /// used for specifying binary folder to download binaries into. If not provided, GlobalFFOptions are used + /// used to explicitly state the os and architecture you want to download + /// a list of the binaries that have been successfully downloaded + public static async Task> DownloadBinaries( + FFMpegVersions version = FFMpegVersions.LatestAvailable, + FFMpegBinaries binaries = FFMpegBinaries.FFMpeg | FFMpegBinaries.FFProbe, + FFOptions? options = null, + SupportedPlatforms? platformOverride = null) + { + using var httpClient = new HttpClient(); + + var versionInfo = await httpClient.GetVersionInfo(version); + var binariesDictionary = versionInfo.BinaryInfo?.GetCompatibleDownloadInfo(platformOverride) ?? + throw new FFMpegDownloaderException("Failed to get compatible download info"); + + var successList = new List(); + var relevantOptions = options ?? GlobalFFOptions.Current; + if (string.IsNullOrEmpty(relevantOptions.BinaryFolder)) + { + throw new FFMpegDownloaderException("Binary folder not specified"); + } + + var binaryFlags = binaries.GetFlags(); + foreach (var binaryFlag in binaryFlags) + { + if (binariesDictionary.TryGetValue(binaryFlag.ToString().ToLowerInvariant(), out var binaryUrl)) + { + using var zipStream = await httpClient.GetStreamAsync(new Uri(binaryUrl)); + var extracted = ExtractZipAndSave(zipStream, relevantOptions.BinaryFolder); + successList.AddRange(extracted); + } + } + + return successList; + } + + private static async Task GetVersionInfo(this HttpClient client, FFMpegVersions version) + { + var versionUri = version.GetDescription(); + + var response = await client.GetAsync(versionUri); + if (!response.IsSuccessStatusCode) + { + throw new FFMpegDownloaderException($"Failed to get version info from {versionUri}", "network error"); + } + + var jsonString = await response.Content.ReadAsStringAsync(); + var versionInfo = JsonSerializer.Deserialize(jsonString); + + return versionInfo ?? + throw new FFMpegDownloaderException($"Failed to deserialize version info from {versionUri}", jsonString); + } + + private static IEnumerable ExtractZipAndSave(Stream zipStream, string binaryFolder) + { + using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read); + foreach (var entry in archive.Entries) + { + if (entry.Name is "ffmpeg" or "ffmpeg.exe" or "ffprobe.exe" or "ffprobe" or "ffplay.exe" or "ffplay") + { + var filePath = Path.Combine(binaryFolder, entry.Name); + entry.ExtractToFile(filePath, true); + yield return filePath; + } + } + } +} diff --git a/FFMpegCore.Extensions.Downloader/Models/BinaryInfo.cs b/FFMpegCore.Extensions.Downloader/Models/BinaryInfo.cs new file mode 100644 index 0000000..d02cb14 --- /dev/null +++ b/FFMpegCore.Extensions.Downloader/Models/BinaryInfo.cs @@ -0,0 +1,74 @@ +using System.Runtime.InteropServices; +using System.Text.Json.Serialization; +using FFMpegCore.Extensions.Downloader.Enums; + +namespace FFMpegCore.Extensions.Downloader.Models; + +internal class BinaryInfo +{ + [JsonPropertyName("windows-64")] public Dictionary? Windows64 { get; set; } + + [JsonPropertyName("windows-32")] public Dictionary? Windows32 { get; set; } + + [JsonPropertyName("linux-32")] public Dictionary? Linux32 { get; set; } + + [JsonPropertyName("linux-64")] public Dictionary? Linux64 { get; set; } + + [JsonPropertyName("linux-armhf")] public Dictionary? LinuxArmhf { get; set; } + + [JsonPropertyName("linux-armel")] public Dictionary? LinuxArmel { get; set; } + + [JsonPropertyName("linux-arm64")] public Dictionary? LinuxArm64 { get; set; } + + [JsonPropertyName("osx-64")] public Dictionary? Osx64 { get; set; } + + /// + /// Automatically get the compatible download info for current os and architecture + /// + /// + /// + /// + /// + public Dictionary? GetCompatibleDownloadInfo(SupportedPlatforms? platformOverride = null) + { + if (platformOverride is not null) + { + return platformOverride switch + { + SupportedPlatforms.Windows64 => Windows64, + SupportedPlatforms.Windows32 => Windows32, + SupportedPlatforms.Linux64 => Linux64, + SupportedPlatforms.Linux32 => Linux32, + SupportedPlatforms.LinuxArmhf => LinuxArmhf, + SupportedPlatforms.LinuxArmel => LinuxArmel, + SupportedPlatforms.LinuxArm64 => LinuxArm64, + SupportedPlatforms.Osx64 => Osx64, + _ => throw new ArgumentOutOfRangeException(nameof(platformOverride), platformOverride, null) + }; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return RuntimeInformation.OSArchitecture == Architecture.X64 ? Windows64 : Windows32; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return RuntimeInformation.OSArchitecture switch + { + Architecture.X86 => Linux32, + Architecture.X64 => Linux64, + Architecture.Arm => LinuxArmhf, + Architecture.Arm64 => LinuxArm64, + _ => LinuxArmel + }; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && RuntimeInformation.OSArchitecture == Architecture.X64) + { + return Osx64; + } + + throw new PlatformNotSupportedException("Unsupported OS or Architecture"); + } +} diff --git a/FFMpegCore.Extensions.Downloader/Models/VersionInfo.cs b/FFMpegCore.Extensions.Downloader/Models/VersionInfo.cs new file mode 100644 index 0000000..ef24f62 --- /dev/null +++ b/FFMpegCore.Extensions.Downloader/Models/VersionInfo.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace FFMpegCore.Extensions.Downloader.Models; + +internal record VersionInfo +{ + [JsonPropertyName("version")] public string? Version { get; set; } + + [JsonPropertyName("permalink")] public string? Permalink { get; set; } + + [JsonPropertyName("bin")] public BinaryInfo? BinaryInfo { get; set; } +} diff --git a/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj b/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj index 71f5773..5dccbfe 100644 --- a/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj +++ b/FFMpegCore.Extensions.SkiaSharp/FFMpegCore.Extensions.SkiaSharp.csproj @@ -5,8 +5,10 @@ Image extension for FFMpegCore using SkiaSharp 5.0.3 ../nupkg - Bump dependencies - ffmpeg ffprobe convert video audio mediafile resize analyze muxing skiasharp + + - Updated dependencies + + ffmpeg ffprobe convert video audio image mediafile resize analyze muxing skia skiasharp Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev, Dimitri Vranken true diff --git a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj index d73275e..fb09ab7 100644 --- a/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj +++ b/FFMpegCore.Extensions.System.Drawing.Common/FFMpegCore.Extensions.System.Drawing.Common.csproj @@ -5,8 +5,10 @@ Image extension for FFMpegCore using System.Common.Drawing 5.0.3 ../nupkg - Bump dependencies - ffmpeg ffprobe convert video audio mediafile resize analyze muxing + + - Updated dependencies + + ffmpeg ffprobe convert video audio image mediafile resize analyze muxing Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev true diff --git a/FFMpegCore.Test/ArgumentBuilderTest.cs b/FFMpegCore.Test/ArgumentBuilderTest.cs index 0e6d715..53999c3 100644 --- a/FFMpegCore.Test/ArgumentBuilderTest.cs +++ b/FFMpegCore.Test/ArgumentBuilderTest.cs @@ -1,6 +1,7 @@ using System.Drawing; using FFMpegCore.Arguments; using FFMpegCore.Enums; +using FFMpegCore.Pipes; namespace FFMpegCore.Test; @@ -8,6 +9,7 @@ namespace FFMpegCore.Test; public class ArgumentBuilderTest { private readonly string[] _concatFiles = { "1.mp4", "2.mp4", "3.mp4", "4.mp4" }; + private readonly int _macOsMaxPipePathLength = 104; private readonly string[] _multiFiles = { "1.mp3", "2.mp3", "3.mp3", "4.mp3" }; [TestMethod] @@ -703,4 +705,18 @@ public class ArgumentBuilderTest var arg = new AudibleEncryptionKeyArgument("62689101"); Assert.AreEqual("-activation_bytes 62689101", arg.Text); } + + [TestMethod] + public void InputPipe_MaxLength_ShorterThanMacOSMax() + { + var pipePath = new InputPipeArgument(new StreamPipeSource(Stream.Null)).PipePath; + Assert.IsLessThan(104, pipePath.Length); + } + + [TestMethod] + public void OutputPipe_MaxLength_ShorterThanMacOSMax() + { + var pipePath = new OutputPipeArgument(new StreamPipeSink(Stream.Null)).PipePath; + Assert.IsLessThan(_macOsMaxPipePathLength, pipePath.Length); + } } diff --git a/FFMpegCore.Test/DownloaderTests.cs b/FFMpegCore.Test/DownloaderTests.cs new file mode 100644 index 0000000..d574230 --- /dev/null +++ b/FFMpegCore.Test/DownloaderTests.cs @@ -0,0 +1,53 @@ +using FFMpegCore.Extensions.Downloader; +using FFMpegCore.Extensions.Downloader.Enums; +using FFMpegCore.Test.Utilities; + +namespace FFMpegCore.Test; + +[TestClass] +public class DownloaderTests +{ + private FFOptions _ffOptions; + + [TestInitialize] + public void InitializeTestFolder() + { + var tempDownloadFolder = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDownloadFolder); + _ffOptions = new FFOptions { BinaryFolder = tempDownloadFolder }; + } + + [TestCleanup] + public void DeleteTestFolder() + { + Directory.Delete(_ffOptions.BinaryFolder, true); + } + + [OsSpecificTestMethod(OsPlatforms.Windows | OsPlatforms.Linux)] + public async Task GetSpecificVersionTest() + { + var binaries = await FFMpegDownloader.DownloadBinaries(FFMpegVersions.V6_1, options: _ffOptions); + try + { + Assert.HasCount(2, binaries); + } + finally + { + binaries.ForEach(File.Delete); + } + } + + [OsSpecificTestMethod(OsPlatforms.Windows | OsPlatforms.Linux)] + public async Task GetAllLatestSuiteTest() + { + var binaries = await FFMpegDownloader.DownloadBinaries(options: _ffOptions); + try + { + Assert.HasCount(2, binaries); + } + finally + { + binaries.ForEach(File.Delete); + } + } +} diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj index 9fa8034..16154a4 100644 --- a/FFMpegCore.Test/FFMpegCore.Test.csproj +++ b/FFMpegCore.Test/FFMpegCore.Test.csproj @@ -20,10 +20,10 @@ - + diff --git a/FFMpegCore.Test/Utilities/OsSpecificTestMethod.cs b/FFMpegCore.Test/Utilities/OsSpecificTestMethod.cs new file mode 100644 index 0000000..df7ebd5 --- /dev/null +++ b/FFMpegCore.Test/Utilities/OsSpecificTestMethod.cs @@ -0,0 +1,42 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using FFMpegCore.Extensions.Downloader.Extensions; + +namespace FFMpegCore.Test.Utilities; + +[Flags] +internal enum OsPlatforms : ushort +{ + Windows = 1, + Linux = 2, + MacOS = 4 +} + +internal class OsSpecificTestMethod : TestMethodAttribute +{ + private readonly IEnumerable _supportedOsPlatforms; + + public OsSpecificTestMethod(OsPlatforms supportedOsPlatforms, [CallerFilePath] string callerFilePath = "", + [CallerLineNumber] int callerLineNumber = -1) : base(callerFilePath, callerLineNumber) + { + _supportedOsPlatforms = supportedOsPlatforms.GetFlags() + .Select(flag => OSPlatform.Create(flag.ToString().ToUpperInvariant())) + .ToArray(); + } + + public override async Task ExecuteAsync(ITestMethod testMethod) + { + if (_supportedOsPlatforms.Any(RuntimeInformation.IsOSPlatform)) + { + return await base.ExecuteAsync(testMethod); + } + + var message = $"Test only executed on specific platforms: {string.Join(", ", _supportedOsPlatforms.Select(platform => platform.ToString()))}"; + { + return + [ + new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) } + ]; + } + } +} diff --git a/FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs b/FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs deleted file mode 100644 index 9a87749..0000000 --- a/FFMpegCore.Test/Utilities/WindowsOnlyTestMethod.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace FFMpegCore.Test.Utilities; - -public class WindowsOnlyTestMethod : TestMethodAttribute -{ - public WindowsOnlyTestMethod([CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = -1) - : base(callerFilePath, callerLineNumber) - { - } - - public override async Task ExecuteAsync(ITestMethod testMethod) - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - var message = "Test not executed on other platforms than Windows"; - { - return - [ - new TestResult { Outcome = UnitTestOutcome.Inconclusive, TestFailureException = new AssertInconclusiveException(message) } - ]; - } - } - - return await base.ExecuteAsync(testMethod); - } -} diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs index 0d17cb6..457118d 100644 --- a/FFMpegCore.Test/VideoTest.cs +++ b/FFMpegCore.Test/VideoTest.cs @@ -110,7 +110,7 @@ public class VideoTest } [SupportedOSPlatform("windows")] - [WindowsOnlyTestMethod] + [OsSpecificTestMethod(OsPlatforms.Windows)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] [DataRow(PixelFormat.Format24bppRgb)] [DataRow(PixelFormat.Format32bppArgb)] @@ -142,7 +142,7 @@ public class VideoTest } [SupportedOSPlatform("windows")] - [WindowsOnlyTestMethod] + [OsSpecificTestMethod(OsPlatforms.Windows)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] public void Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly() { @@ -174,7 +174,7 @@ public class VideoTest } [SupportedOSPlatform("windows")] - [WindowsOnlyTestMethod] + [OsSpecificTestMethod(OsPlatforms.Windows)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly_Async() { @@ -206,7 +206,7 @@ public class VideoTest } [SupportedOSPlatform("windows")] - [WindowsOnlyTestMethod] + [OsSpecificTestMethod(OsPlatforms.Windows)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] public void Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly() { @@ -239,7 +239,7 @@ public class VideoTest } [SupportedOSPlatform("windows")] - [WindowsOnlyTestMethod] + [OsSpecificTestMethod(OsPlatforms.Windows)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly_Async() { @@ -414,7 +414,7 @@ public class VideoTest } [SupportedOSPlatform("windows")] - [WindowsOnlyTestMethod] + [OsSpecificTestMethod(OsPlatforms.Windows)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] [DataRow(PixelFormat.Format24bppRgb)] [DataRow(PixelFormat.Format32bppArgb)] @@ -463,7 +463,7 @@ public class VideoTest } [SupportedOSPlatform("windows")] - [WindowsOnlyTestMethod] + [OsSpecificTestMethod(OsPlatforms.Windows)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] [DataRow(SKColorType.Rgb565)] [DataRow(SKColorType.Bgra8888)] @@ -500,7 +500,7 @@ public class VideoTest } [SupportedOSPlatform("windows")] - [WindowsOnlyTestMethod] + [OsSpecificTestMethod(OsPlatforms.Windows)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] [DataRow(PixelFormat.Format24bppRgb)] [DataRow(PixelFormat.Format32bppArgb)] @@ -533,7 +533,7 @@ public class VideoTest } [SupportedOSPlatform("windows")] - [WindowsOnlyTestMethod] + [OsSpecificTestMethod(OsPlatforms.Windows)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] public void Video_Snapshot_InMemory_SystemDrawingCommon() { @@ -857,7 +857,7 @@ public class VideoTest } [SupportedOSPlatform("windows")] - [WindowsOnlyTestMethod] + [OsSpecificTestMethod(OsPlatforms.Windows)] [Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)] public void Video_TranscodeInMemory_WindowsOnly() { diff --git a/FFMpegCore.sln b/FFMpegCore.sln index 7ab0929..b99a44e 100644 --- a/FFMpegCore.sln +++ b/FFMpegCore.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31005.135 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34003.232 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore", "FFMpegCore\FFMpegCore.csproj", "{19DE2EC2-9955-4712-8096-C22EF6713E4F}" EndProject @@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Extensions.Syste EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFMpegCore.Extensions.SkiaSharp", "FFMpegCore.Extensions.SkiaSharp\FFMpegCore.Extensions.SkiaSharp.csproj", "{5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FFMpegCore.Extensions.Downloader", "FFMpegCore.Extensions.Downloader\FFMpegCore.Extensions.Downloader.csproj", "{5FA30158-CAB0-44FD-AD98-C31F5E3D5A56}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -39,6 +41,10 @@ Global {5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Debug|Any CPU.Build.0 = Debug|Any CPU {5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A76F9B7-3681-4551-A9B6-8D3AC5DA1090}.Release|Any CPU.Build.0 = Release|Any CPU + {5FA30158-CAB0-44FD-AD98-C31F5E3D5A56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5FA30158-CAB0-44FD-AD98-C31F5E3D5A56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5FA30158-CAB0-44FD-AD98-C31F5E3D5A56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5FA30158-CAB0-44FD-AD98-C31F5E3D5A56}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs b/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs index 5a809f7..311a518 100644 --- a/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs +++ b/FFMpegCore/FFMpeg/Arguments/PipeArgument.cs @@ -7,10 +7,11 @@ namespace FFMpegCore.Arguments; public abstract class PipeArgument { private readonly PipeDirection _direction; + private readonly object _pipeLock = new(); protected PipeArgument(PipeDirection direction) { - PipeName = PipeHelpers.GetUnqiuePipeName(); + PipeName = PipeHelpers.GetUniquePipeName(); _direction = direction; } @@ -22,19 +23,25 @@ public abstract class PipeArgument public void Pre() { - if (Pipe != null) + lock (_pipeLock) { - throw new InvalidOperationException("Pipe already has been opened"); - } + if (Pipe != null) + { + throw new InvalidOperationException("Pipe already has been opened"); + } - Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + Pipe = new NamedPipeServerStream(PipeName, _direction, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); + } } public void Post() { Debug.WriteLine($"Disposing NamedPipeServerStream on {GetType().Name}"); - Pipe?.Dispose(); - Pipe = null!; + lock (_pipeLock) + { + Pipe?.Dispose(); + Pipe = null!; + } } public async Task During(CancellationToken cancellationToken = default) @@ -50,9 +57,12 @@ public abstract class PipeArgument finally { Debug.WriteLine($"Disconnecting NamedPipeServerStream on {GetType().Name}"); - if (Pipe is { IsConnected: true }) + lock (_pipeLock) { - Pipe.Disconnect(); + if (Pipe is { IsConnected: true }) + { + Pipe.Disconnect(); + } } } } diff --git a/FFMpegCore/FFMpeg/Pipes/PipeHelpers.cs b/FFMpegCore/FFMpeg/Pipes/PipeHelpers.cs index cbd90ac..01f3416 100644 --- a/FFMpegCore/FFMpeg/Pipes/PipeHelpers.cs +++ b/FFMpegCore/FFMpeg/Pipes/PipeHelpers.cs @@ -4,9 +4,9 @@ namespace FFMpegCore.Pipes; internal static class PipeHelpers { - public static string GetUnqiuePipeName() + public static string GetUniquePipeName() { - return $"FFMpegCore_{Guid.NewGuid().ToString("N").Substring(0, 5)}"; + return $"FFMpegCore_{Guid.NewGuid().ToString("N").Substring(0, 16)}"; } public static string GetPipePath(string pipeName) diff --git a/FFMpegCore/FFMpegCore.csproj b/FFMpegCore/FFMpegCore.csproj index c0150ce..671239d 100644 --- a/FFMpegCore/FFMpegCore.csproj +++ b/FFMpegCore/FFMpegCore.csproj @@ -3,15 +3,13 @@ true A .NET Standard FFMpeg/FFProbe wrapper for easily integrating media analysis and conversion into your .NET applications - 5.2.0 + 5.3.0 ../nupkg - - **Instances and Packages Updates**: Updates to various instances and packages by rosenbjerg. - - **Audio and Video Enhancements**: Additions include a Copy option to Audio Codec and a Crop option to Arguments by brett-baker; video-stream level added to FFProbe analysis by Kaaybi; AV1 support for smaller snapshots and videos by BenediktBertsch; multiple input files support by AddyMills; HDR color properties support added to FFProbe analysis by Tomiscout. - - **System.Text.Json Bump**: Update by Kaaybi. - - **FFMpeg Processors and Utilities**: Modification for handling durations over 24 hours in `FFMpegArgumentProcessor` by alahane-techtel; fix for snapshots with correct width/height from rotated videos by Hagfjall. - - **Feature Additions and Fixes**: Support for multiple outputs and tee muxer by duggaraju; custom ffprob arguments by vfrz; fix for null reference exception with tags container by rosenbjerg; Chapter Modell change by vortex852456; codec copy added to the SaveM3U8Stream method by rpaschoal. - - **Closed and Non-merged Contributions**: Notable closed contributions include JSON source generators usage by onionware-github; Snapshot overload by 3UR; FromRawInput method by pedoc; runtime ffmpeg suite installation by yuqian5; and support for scale_npp by vicwilliam. - - **Miscellaneous Fixes**: Minor readme corrections by NaBian; fix for ffmpeg path issue by devedse. + + - **Fixed race condition on Named pipe dispose/disconnect** by techtel-pstevens + - **More extensions for snapshot function(jpg, bmp, webp)** by GorobVictor + - **Include more GUID characters in pipe path** by reima, rosenbjerg + - **Updated dependencies and minor cleanup**: by rosenbjerg ffmpeg ffprobe convert video audio mediafile resize analyze muxing Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev diff --git a/README.md b/README.md index 1394409..dcee337 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,12 @@ If you want to use `System.Drawing.Bitmap`s as `IVideoFrame`s, a `BitmapVideoFra # Binaries -## Installation +## Runtime Auto Installation +You can install a version of ffmpeg suite at runtime using `FFMpegDownloader.DownloadFFMpegSuite();` + +This feature uses the api from [ffbinaries](https://ffbinaries.com/api). + +## Manual Installation If you prefer to manually download them, visit [ffbinaries](https://ffbinaries.com/downloads) or [zeranoe Windows builds](https://ffmpeg.zeranoe.com/builds/).