diff --git a/FFMpegCore.Extensions.Downloader/Enums/FFMpegBinaries.cs b/FFMpegCore.Extensions.Downloader/Enums/FFMpegBinaries.cs
new file mode 100644
index 00000000..d92116a4
--- /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 00000000..a2e5f097
--- /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")]
+ 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 00000000..0378f3e0
--- /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 00000000..c7d2ead9
--- /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 00000000..7930c130
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/Extensions/EnumExtensions.cs
@@ -0,0 +1,22 @@
+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();
+ }
+
+ if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute)
+ {
+ return attribute.Description;
+ }
+
+ return enumValue.ToString();
+ }
+}
diff --git a/FFMpegCore.Extensions.Downloader/FFMpegCore.Extensions.Downloader.csproj b/FFMpegCore.Extensions.Downloader/FFMpegCore.Extensions.Downloader.csproj
new file mode 100644
index 00000000..0c6f791e
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/FFMpegCore.Extensions.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.Extensions.Downloader/FFMpegDownloader.cs b/FFMpegCore.Extensions.Downloader/FFMpegDownloader.cs
new file mode 100644
index 00000000..0def62ba
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/FFMpegDownloader.cs
@@ -0,0 +1,53 @@
+using FFMpegCore.Extensions.Downloader.Enums;
+using FFMpegCore.Extensions.Downloader.Exceptions;
+using FFMpegCore.Extensions.Downloader.Services;
+
+namespace FFMpegCore.Extensions.Downloader;
+
+public 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 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,
+ SupportedPlatforms? platformOverride = null)
+ {
+ // 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();
+
+ // download ffmpeg if selected
+ if (binaries.HasFlag(FFMpegBinaries.FFMpeg) && downloadInfo.FFMpeg is not null)
+ {
+ await using var zipStream = await FFbinariesService.DownloadFileAsSteam(new Uri(downloadInfo.FFMpeg));
+ successList.AddRange(FFbinariesService.ExtractZipAndSave(zipStream));
+ }
+
+ // download ffprobe if selected
+ if (binaries.HasFlag(FFMpegBinaries.FFProbe) && downloadInfo.FFProbe is not null)
+ {
+ await using var zipStream = await FFbinariesService.DownloadFileAsSteam(new Uri(downloadInfo.FFProbe));
+ successList.AddRange(FFbinariesService.ExtractZipAndSave(zipStream));
+ }
+
+ // download ffplay if selected
+ if (binaries.HasFlag(FFMpegBinaries.FFPlay) && downloadInfo.FFPlay is not null)
+ {
+ await using var zipStream = await FFbinariesService.DownloadFileAsSteam(new Uri(downloadInfo.FFPlay));
+ successList.AddRange(FFbinariesService.ExtractZipAndSave(zipStream));
+ }
+
+ return successList;
+ }
+}
diff --git a/FFMpegCore.Extensions.Downloader/Models/BinaryInfo.cs b/FFMpegCore.Extensions.Downloader/Models/BinaryInfo.cs
new file mode 100644
index 00000000..e91fcee2
--- /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 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 00000000..5b7c0fdc
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/Models/DownloadInfo.cs
@@ -0,0 +1,12 @@
+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 00000000..ef24f629
--- /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.Downloader/Services/FFbinariesService.cs b/FFMpegCore.Extensions.Downloader/Services/FFbinariesService.cs
new file mode 100644
index 00000000..1b7edc27
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/Services/FFbinariesService.cs
@@ -0,0 +1,71 @@
+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.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 async Task DownloadFileAsSteam(Uri address)
+ {
+ var client = new HttpClient();
+ return await client.GetStreamAsync(address);
+ }
+
+ ///
+ /// 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
new file mode 100644
index 00000000..f2eb5633
--- /dev/null
+++ b/FFMpegCore.Test/DownloaderTests.cs
@@ -0,0 +1,23 @@
+using FFMpegCore.Extensions.Downloader;
+using FFMpegCore.Extensions.Downloader.Enums;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace FFMpegCore.Test;
+
+[TestClass]
+public class DownloaderTests
+{
+ [TestMethod]
+ public void GetSpecificVersionTest()
+ {
+ var binaries = FFMpegDownloader.DownloadFFMpegSuite(FFMpegVersions.V6_1).Result;
+ Assert.IsTrue(binaries.Count == 2);
+ }
+
+ [TestMethod]
+ public void GetAllLatestSuiteTest()
+ {
+ var binaries = FFMpegDownloader.DownloadFFMpegSuite().Result;
+ Assert.IsTrue(binaries.Count == 2); // many platforms have only ffmpeg and ffprobe
+ }
+}
diff --git a/FFMpegCore.Test/FFMpegCore.Test.csproj b/FFMpegCore.Test/FFMpegCore.Test.csproj
index 77914234..9fd30b24 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 7ab09297..b99a44ea 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/README.md b/README.md
index 33f7ddfb..716d85fa 100644
--- a/README.md
+++ b/README.md
@@ -162,7 +162,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/).
### Windows (using choco)