diff --git a/FFMpegCore.Extensions.Downloader/Enums/FFMpegBinaries.cs b/FFMpegCore.Extensions.Downloader/Enums/FFMpegBinaries.cs
new file mode 100644
index 0000000..d92116a
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/Enums/FFMpegBinaries.cs
@@ -0,0 +1,9 @@
+namespace FFMpegCore.Extensions.Downloader.Enums;
+
+[Flags]
+public enum FFMpegBinaries : ushort
+{
+ FFMpeg,
+ FFProbe,
+ FFPlay
+}
diff --git a/FFMpegCore.Extensions.Downloader/Enums/FFMpegVersions.cs b/FFMpegCore.Extensions.Downloader/Enums/FFMpegVersions.cs
new file mode 100644
index 0000000..7a9f476
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/Enums/FFMpegVersions.cs
@@ -0,0 +1,29 @@
+using System.ComponentModel;
+
+namespace FFMpegCore.Extensions.Downloader.Enums;
+
+public enum FFMpegVersions : ushort
+{
+ [Description("https://ffbinaries.com/api/v1/version/latest")]
+ Latest,
+ [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..c7d2ead
--- /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 string Detail { get; set; } = "";
+
+ 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..e2f5616
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/Extensions/EnumExtensions.cs
@@ -0,0 +1,21 @@
+using System.ComponentModel;
+
+namespace FFMpegCore.Extensions.Downloader.Extensions;
+
+internal static class EnumExtensions
+{
+ public static string GetDescription(this Enum enumValue)
+ {
+ var field = enumValue.GetType().GetField(enumValue.ToString());
+ if (field == null)
+ return enumValue.ToString();
+
+ var attributes = field.GetCustomAttributes(typeof(DescriptionAttribute), false);
+ if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute)
+ {
+ return attribute.Description;
+ }
+
+ return enumValue.ToString();
+ }
+}
diff --git a/FFMpegCore.Extensions.Downloader/FFMpegDownloader.cs b/FFMpegCore.Extensions.Downloader/FFMpegDownloader.cs
index 139921c..adc9c74 100644
--- a/FFMpegCore.Extensions.Downloader/FFMpegDownloader.cs
+++ b/FFMpegCore.Extensions.Downloader/FFMpegDownloader.cs
@@ -1,51 +1,11 @@
-using System.IO.Compression;
-using System.Net;
-using System.Runtime.InteropServices;
-using System.Text.Json;
-using System.Text.Json.Serialization;
+using FFMpegCore.Extensions.Downloader.Enums;
+using FFMpegCore.Extensions.Downloader.Exceptions;
+using FFMpegCore.Extensions.Downloader.Services;
namespace FFMpegCore.Extensions.Downloader;
-///
-/// Downloads the latest FFMpeg suite binaries from ffbinaries.com.
-///
public class FFMpegDownloader
{
- [Flags]
- public enum FFMpegBinaries : ushort
- {
- FFMpeg,
- FFProbe,
- FFPlay
- }
-
- public enum FFMpegVersions : ushort
- {
- Latest,
- V6_1,
- V5_1,
- V4_4_1,
- V4_2_1,
- V4_2,
- V4_1,
- V4_0,
- V3_4,
- V3_3,
- V3_2
- }
-
- public enum PlatformOverride : short
- {
- Windows64,
- Windows32,
- Linux64,
- Linux32,
- LinuxArmhf,
- LinuxArmel,
- LinuxArm64,
- Osx64
- }
-
///
/// Download the latest FFMpeg suite binaries for current platform
///
@@ -56,221 +16,39 @@ public class FFMpegDownloader
public static async Task> DownloadFFMpegSuite(
FFMpegVersions version = FFMpegVersions.Latest,
FFMpegBinaries binaries = FFMpegBinaries.FFMpeg | FFMpegBinaries.FFProbe,
- PlatformOverride? platformOverride = null)
+ SupportedPlatforms? platformOverride = null)
{
- var versionInfo = await GetVersionInfo(version);
+ // get all available versions
+ var versionInfo = await FFbinariesService.GetVersionInfo(version);
+
+ // get the download info for the current platform
var downloadInfo = versionInfo.BinaryInfo?.GetCompatibleDownloadInfo(platformOverride) ??
throw new FFMpegDownloaderException("Failed to get compatible download info");
-
+
var successList = new List();
-
- // if ffmpeg is selected
+
+ // download ffmpeg if selected
if (binaries.HasFlag(FFMpegBinaries.FFMpeg) && downloadInfo.FFMpeg is not null)
{
- var zipStream = DownloadFileAsSteam(new Uri(downloadInfo.FFMpeg));
- successList.AddRange(ExtractZipAndSave(zipStream));
+ var zipStream = FFbinariesService.DownloadFileAsSteam(new Uri(downloadInfo.FFMpeg));
+ successList.AddRange(FFbinariesService.ExtractZipAndSave(zipStream));
}
- // if ffprobe is selected
+ // download ffprobe if selected
if (binaries.HasFlag(FFMpegBinaries.FFProbe) && downloadInfo.FFProbe is not null)
{
- var zipStream = DownloadFileAsSteam(new Uri(downloadInfo.FFProbe));
- successList.AddRange(ExtractZipAndSave(zipStream));
+ var zipStream = FFbinariesService.DownloadFileAsSteam(new Uri(downloadInfo.FFProbe));
+ successList.AddRange(FFbinariesService.ExtractZipAndSave(zipStream));
}
- // if ffplay is selected
+ // download ffplay if selected
if (binaries.HasFlag(FFMpegBinaries.FFPlay) && downloadInfo.FFPlay is not null)
{
- var zipStream = DownloadFileAsSteam(new Uri(downloadInfo.FFPlay));
- successList.AddRange(ExtractZipAndSave(zipStream));
+ var zipStream = FFbinariesService.DownloadFileAsSteam(new Uri(downloadInfo.FFPlay));
+ successList.AddRange(FFbinariesService.ExtractZipAndSave(zipStream));
}
return successList;
}
-
- ///
- /// Download file from uri
- ///
- /// uri of the file
- ///
- private static MemoryStream DownloadFileAsSteam(Uri address)
- {
- var client = new WebClient();
- var fileStream = new MemoryStream(client.DownloadData(address));
- fileStream.Position = 0;
-
- return fileStream;
- }
-
- ///
- /// Extracts the binaries from the zip stream and saves them to the current binary folder
- ///
- /// steam of the zip file
- ///
- private static IEnumerable ExtractZipAndSave(Stream zipStream)
- {
- using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read);
- List files = new();
- foreach (var entry in archive.Entries)
- {
- if (entry.Name is "ffmpeg" or "ffmpeg.exe" or "ffprobe.exe" or "ffprobe" or "ffplay.exe" or "ffplay")
- {
- entry.ExtractToFile(Path.Combine(GlobalFFOptions.Current.BinaryFolder, entry.Name), true);
- files.Add(Path.Combine(GlobalFFOptions.Current.BinaryFolder, entry.Name));
- }
- }
-
- return files;
- }
-
- #region FFbinaries api
-
- private class DownloadInfo
- {
- [JsonPropertyName("ffmpeg")] public string? FFMpeg { get; set; }
-
- [JsonPropertyName("ffprobe")] public string? FFProbe { get; set; }
-
- [JsonPropertyName("ffplay")] public string? FFPlay { get; set; }
- }
-
- private class BinaryInfo
- {
- [JsonPropertyName("windows-64")] public DownloadInfo? Windows64 { get; set; }
-
- [JsonPropertyName("windows-32")] public DownloadInfo? Windows32 { get; set; }
-
- [JsonPropertyName("linux-32")] public DownloadInfo? Linux32 { get; set; }
-
- [JsonPropertyName("linux-64")] public DownloadInfo? Linux64 { get; set; }
-
- [JsonPropertyName("linux-armhf")] public DownloadInfo? LinuxArmhf { get; set; }
-
- [JsonPropertyName("linux-armel")] public DownloadInfo? LinuxArmel { get; set; }
-
- [JsonPropertyName("linux-arm64")] public DownloadInfo? LinuxArm64 { get; set; }
-
- [JsonPropertyName("osx-64")] public DownloadInfo? Osx64 { get; set; }
-
- ///
- /// Automatically get the compatible download info for current os and architecture
- ///
- ///
- ///
- ///
- ///
- public DownloadInfo? GetCompatibleDownloadInfo(PlatformOverride? platformOverride = null)
- {
- if (platformOverride is not null)
- {
- return platformOverride switch
- {
- PlatformOverride.Windows64 => Windows64,
- PlatformOverride.Windows32 => Windows32,
- PlatformOverride.Linux64 => Linux64,
- PlatformOverride.Linux32 => Linux32,
- PlatformOverride.LinuxArmhf => LinuxArmhf,
- PlatformOverride.LinuxArmel => LinuxArmel,
- PlatformOverride.LinuxArm64 => LinuxArm64,
- PlatformOverride.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))
- {
- return Osx64;
- }
-
- throw new PlatformNotSupportedException("Unsupported OS or Architecture");
- }
- }
-
- private class VersionInfo
- {
- [JsonPropertyName("version")] public string? Version { get; set; }
-
- [JsonPropertyName("permalink")] public string? Permalink { get; set; }
-
- [JsonPropertyName("bin")] public BinaryInfo? BinaryInfo { get; set; }
- }
-
- private static readonly Dictionary _FFBinariesAPIs = new()
- {
- { FFMpegVersions.Latest, "https://ffbinaries.com/api/v1/version/latest" },
- { FFMpegVersions.V6_1, "https://ffbinaries.com/api/v1/version/6.1" },
- { FFMpegVersions.V5_1, "https://ffbinaries.com/api/v1/version/5.1" },
- { FFMpegVersions.V4_4_1, "https://ffbinaries.com/api/v1/version/4.4.1" },
- { FFMpegVersions.V4_2_1, "https://ffbinaries.com/api/v1/version/4.2.1" },
- { FFMpegVersions.V4_2, "https://ffbinaries.com/api/v1/version/4.2" },
- { FFMpegVersions.V4_1, "https://ffbinaries.com/api/v1/version/4.1" },
- { FFMpegVersions.V4_0, "https://ffbinaries.com/api/v1/version/4.0" },
- { FFMpegVersions.V3_4, "https://ffbinaries.com/api/v1/version/3.4" },
- { FFMpegVersions.V3_3, "https://ffbinaries.com/api/v1/version/3.3" },
- { FFMpegVersions.V3_2, "https://ffbinaries.com/api/v1/version/3.2" }
- };
-
- ///
- /// Get version info from ffbinaries.com
- ///
- /// use to explicitly state the version of ffmpeg you want
- ///
- ///
- private static async Task GetVersionInfo(FFMpegVersions version)
- {
- if (!_FFBinariesAPIs.TryGetValue(version, out var versionUri))
- {
- throw new FFMpegDownloaderException($"Invalid version selected: {version}", "contact dev");
- }
-
- HttpClient client = new();
- 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);
- }
-
- #endregion
}
-///
-/// Custom exception for FFMpegDownloader
-///
-public class FFMpegDownloaderException : Exception
-{
- public FFMpegDownloaderException(string message) : base(message)
- {
- }
-
- public FFMpegDownloaderException(string message, string detail) : base(message)
- {
- Detail = detail;
- }
-
- public string Detail { get; set; } = "";
-}
diff --git a/FFMpegCore.Extensions.Downloader/Models/BinaryInfo.cs b/FFMpegCore.Extensions.Downloader/Models/BinaryInfo.cs
new file mode 100644
index 0000000..e855b2d
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/Models/BinaryInfo.cs
@@ -0,0 +1,82 @@
+using System.Runtime.InteropServices;
+using System.Text.Json.Serialization;
+using FFMpegCore.Extensions.Downloader.Enums;
+
+namespace FFMpegCore.Extensions.Downloader.Models;
+
+internal record BinaryInfo
+{
+ [JsonPropertyName("windows-64")]
+ public DownloadInfo? Windows64 { get; set; }
+
+ [JsonPropertyName("windows-32")]
+ public DownloadInfo? Windows32 { get; set; }
+
+ [JsonPropertyName("linux-32")]
+ public DownloadInfo? Linux32 { get; set; }
+
+ [JsonPropertyName("linux-64")]
+ public DownloadInfo? Linux64 { get; set; }
+
+ [JsonPropertyName("linux-armhf")]
+ public DownloadInfo? LinuxArmhf { get; set; }
+
+ [JsonPropertyName("linux-armel")]
+ public DownloadInfo? LinuxArmel { get; set; }
+
+ [JsonPropertyName("linux-arm64")]
+ public DownloadInfo? LinuxArm64 { get; set; }
+
+ [JsonPropertyName("osx-64")]
+ public DownloadInfo? Osx64 { get; set; }
+
+ ///
+ /// Automatically get the compatible download info for current os and architecture
+ ///
+ ///
+ ///
+ ///
+ ///
+ public DownloadInfo? 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))
+ {
+ return Osx64;
+ }
+
+ throw new PlatformNotSupportedException("Unsupported OS or Architecture");
+ }
+}
diff --git a/FFMpegCore.Extensions.Downloader/Models/DownloadInfo.cs b/FFMpegCore.Extensions.Downloader/Models/DownloadInfo.cs
new file mode 100644
index 0000000..2e3e692
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/Models/DownloadInfo.cs
@@ -0,0 +1,15 @@
+using System.Text.Json.Serialization;
+
+namespace FFMpegCore.Extensions.Downloader.Models;
+
+internal record DownloadInfo
+{
+ [JsonPropertyName("ffmpeg")]
+ public string? FFMpeg { get; set; }
+
+ [JsonPropertyName("ffprobe")]
+ public string? FFProbe { get; set; }
+
+ [JsonPropertyName("ffplay")]
+ public string? FFPlay { get; set; }
+}
diff --git a/FFMpegCore.Extensions.Downloader/Models/VersionInfo.cs b/FFMpegCore.Extensions.Downloader/Models/VersionInfo.cs
new file mode 100644
index 0000000..490f431
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/Models/VersionInfo.cs
@@ -0,0 +1,15 @@
+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.Downloader/Services/FFbinariesService.cs b/FFMpegCore.Extensions.Downloader/Services/FFbinariesService.cs
new file mode 100644
index 0000000..155d925
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/Services/FFbinariesService.cs
@@ -0,0 +1,75 @@
+using System.IO.Compression;
+using System.Net;
+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.Services;
+
+///
+/// Service to interact with ffbinaries.com API
+///
+internal class FFbinariesService
+{
+ ///
+ /// Get version info from ffbinaries.com
+ ///
+ /// use to explicitly state the version of ffmpeg you want
+ ///
+ ///
+ internal static async Task GetVersionInfo(FFMpegVersions version)
+ {
+ var versionUri = version.GetDescription();
+
+ HttpClient client = new();
+ 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);
+ }
+
+ ///
+ /// Download file from uri
+ ///
+ /// uri of the file
+ ///
+ internal static MemoryStream DownloadFileAsSteam(Uri address)
+ {
+ var client = new WebClient();
+ var fileStream = new MemoryStream(client.DownloadData(address));
+ fileStream.Position = 0;
+
+ return fileStream;
+ }
+
+ ///
+ /// Extracts the binaries from the zip stream and saves them to the current binary folder
+ ///
+ /// steam of the zip file
+ ///
+ internal static IEnumerable ExtractZipAndSave(Stream zipStream)
+ {
+ using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read);
+ List files = new();
+ foreach (var entry in archive.Entries)
+ {
+ if (entry.Name is "ffmpeg" or "ffmpeg.exe" or "ffprobe.exe" or "ffprobe" or "ffplay.exe" or "ffplay")
+ {
+ entry.ExtractToFile(Path.Combine(GlobalFFOptions.Current.BinaryFolder, entry.Name), true);
+ files.Add(Path.Combine(GlobalFFOptions.Current.BinaryFolder, entry.Name));
+ }
+ }
+
+ return files;
+ }
+}
diff --git a/FFMpegCore.Test/DownloaderTests.cs b/FFMpegCore.Test/DownloaderTests.cs
index 5a89a59..1a86b4c 100644
--- a/FFMpegCore.Test/DownloaderTests.cs
+++ b/FFMpegCore.Test/DownloaderTests.cs
@@ -1,4 +1,5 @@
using FFMpegCore.Extensions.Downloader;
+using FFMpegCore.Extensions.Downloader.Enums;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FFMpegCore.Test;
@@ -9,14 +10,14 @@ public class DownloaderTests
[TestMethod]
public void GetAllLatestSuiteTest()
{
- var binaries = FFMpegDownloader.DownloadFFMpegSuite(binaries: FFMpegDownloader.FFMpegBinaries.FFMpeg).Result;
- Assert.IsTrue(binaries.Count == 1); // many platforms have only ffmpeg and ffprobe
+ var binaries = FFMpegDownloader.DownloadFFMpegSuite().Result;
+ Assert.IsTrue(binaries.Count == 2); // many platforms have only ffmpeg and ffprobe
}
[TestMethod]
public void GetSpecificVersionTest()
{
- var binaries = FFMpegDownloader.DownloadFFMpegSuite(FFMpegDownloader.FFMpegVersions.V4_0, binaries: FFMpegDownloader.FFMpegBinaries.FFMpeg).Result;
+ var binaries = FFMpegDownloader.DownloadFFMpegSuite(FFMpegVersions.V4_0, binaries: FFMpegBinaries.FFMpeg).Result;
Assert.IsTrue(binaries.Count == 1);
}
}