diff --git a/FFMpegCore.Downloader/FFMpegCore.Downloader.csproj b/FFMpegCore.Downloader/FFMpegCore.Downloader.csproj
new file mode 100644
index 0000000..720cbf1
--- /dev/null
+++ b/FFMpegCore.Downloader/FFMpegCore.Downloader.csproj
@@ -0,0 +1,23 @@
+
+
+
+ netstandard2.1
+ enable
+
+
+
+ true
+ FFMpeg downloader extension for FFMpegCore
+ 5.0.0
+ ../nupkg
+
+
+ ffmpeg ffprobe convert video audio mediafile resize analyze download
+ Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev, Kerry Cao
+
+
+
+
+
+
+
diff --git a/FFMpegCore.Downloader/FFMpegDownloader.cs b/FFMpegCore.Downloader/FFMpegDownloader.cs
new file mode 100644
index 0000000..36f27c5
--- /dev/null
+++ b/FFMpegCore.Downloader/FFMpegDownloader.cs
@@ -0,0 +1,272 @@
+using System.IO.Compression;
+using System.Net;
+using System.Runtime.InteropServices;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace FFMpegCore.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,
+ 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
+ ///
+ /// 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 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> DownloadFFMpegSuite(
+ FFMpegVersions version = FFMpegVersions.Latest,
+ FFMpegBinaries binaries = FFMpegBinaries.FFMpeg | FFMpegBinaries.FFProbe,
+ PlatformOverride? platformOverride = null)
+ {
+ var versionInfo = await GetVersionInfo(version);
+ var downloadInfo = versionInfo.BinaryInfo?.GetCompatibleDownloadInfo(platformOverride) ??
+ throw new FFMpegDownloaderException("Failed to get compatible download info");
+
+ var successList = new List();
+
+ // if ffmpeg is selected
+ if (binaries.HasFlag(FFMpegBinaries.FFMpeg) && downloadInfo.FFMpeg is not null)
+ {
+ var zipStream = DownloadFileAsSteam(new Uri(downloadInfo.FFMpeg));
+ successList.AddRange(ExtractZipAndSave(zipStream));
+ }
+
+ // if ffprobe is selected
+ if (binaries.HasFlag(FFMpegBinaries.FFProbe) && downloadInfo.FFProbe is not null)
+ {
+ var zipStream = DownloadFileAsSteam(new Uri(downloadInfo.FFProbe));
+ successList.AddRange(ExtractZipAndSave(zipStream));
+ }
+
+ // if ffplay is selected
+ if (binaries.HasFlag(FFMpegBinaries.FFPlay) && downloadInfo.FFPlay is not null)
+ {
+ var zipStream = DownloadFileAsSteam(new Uri(downloadInfo.FFPlay));
+ successList.AddRange(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.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.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj
index b78af1b..d54d269 100644
--- a/FFMpegCore.Test/FFMpegCore.Test.csproj
+++ b/FFMpegCore.Test/FFMpegCore.Test.csproj
@@ -24,6 +24,7 @@
+
diff --git a/FFMpegCore.sln b/FFMpegCore.sln
index 7ab0929..f7f42d2 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.Downloader", "FFMpegCore.Downloader\FFMpegCore.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