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..bd18fb3
--- /dev/null
+++ b/FFMpegCore.Extensions.Downloader/FFMpegCore.Extensions.Downloader.csproj
@@ -0,0 +1,18 @@
+
+
+
+ true
+ FFMpeg downloader extension for FFMpegCore
+ 5.0.0
+ ../nupkg
+
+
+ ffmpeg ffprobe convert video audio mediafile resize analyze download
+ 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.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..47bb74b 100644
--- a/FFMpegCore.Test/FFMpegCore.Test.csproj
+++ b/FFMpegCore.Test/FFMpegCore.Test.csproj
@@ -24,6 +24,7 @@
+
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 010ec44..7946552 100644
--- a/FFMpegCore.Test/VideoTest.cs
+++ b/FFMpegCore.Test/VideoTest.cs
@@ -93,7 +93,7 @@ public class VideoTest
}
[SupportedOSPlatform("windows")]
- [WindowsOnlyTestMethod]
+ [OsSpecificTestMethod(OsPlatforms.Windows)]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
[DataRow(PixelFormat.Format24bppRgb)]
[DataRow(PixelFormat.Format32bppArgb)]
@@ -125,7 +125,7 @@ public class VideoTest
}
[SupportedOSPlatform("windows")]
- [WindowsOnlyTestMethod]
+ [OsSpecificTestMethod(OsPlatforms.Windows)]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly()
{
@@ -157,7 +157,7 @@ public class VideoTest
}
[SupportedOSPlatform("windows")]
- [WindowsOnlyTestMethod]
+ [OsSpecificTestMethod(OsPlatforms.Windows)]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_ToMP4_Args_Pipe_DifferentImageSizes_WindowsOnly_Async()
{
@@ -189,7 +189,7 @@ public class VideoTest
}
[SupportedOSPlatform("windows")]
- [WindowsOnlyTestMethod]
+ [OsSpecificTestMethod(OsPlatforms.Windows)]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly()
{
@@ -222,7 +222,7 @@ public class VideoTest
}
[SupportedOSPlatform("windows")]
- [WindowsOnlyTestMethod]
+ [OsSpecificTestMethod(OsPlatforms.Windows)]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public async Task Video_ToMP4_Args_Pipe_DifferentPixelFormats_WindowsOnly_Async()
{
@@ -397,7 +397,7 @@ public class VideoTest
}
[SupportedOSPlatform("windows")]
- [WindowsOnlyTestMethod]
+ [OsSpecificTestMethod(OsPlatforms.Windows)]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
[DataRow(PixelFormat.Format24bppRgb)]
[DataRow(PixelFormat.Format32bppArgb)]
@@ -446,7 +446,7 @@ public class VideoTest
}
[SupportedOSPlatform("windows")]
- [WindowsOnlyTestMethod]
+ [OsSpecificTestMethod(OsPlatforms.Windows)]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
[DataRow(SKColorType.Rgb565)]
[DataRow(SKColorType.Bgra8888)]
@@ -483,7 +483,7 @@ public class VideoTest
}
[SupportedOSPlatform("windows")]
- [WindowsOnlyTestMethod]
+ [OsSpecificTestMethod(OsPlatforms.Windows)]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
[DataRow(PixelFormat.Format24bppRgb)]
[DataRow(PixelFormat.Format32bppArgb)]
@@ -516,7 +516,7 @@ public class VideoTest
}
[SupportedOSPlatform("windows")]
- [WindowsOnlyTestMethod]
+ [OsSpecificTestMethod(OsPlatforms.Windows)]
[Timeout(BaseTimeoutMilliseconds, CooperativeCancellation = true)]
public void Video_Snapshot_InMemory_SystemDrawingCommon()
{
@@ -840,7 +840,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/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/).