mirror of
https://github.com/rosenbjerg/FFMpegCore.git
synced 2024-11-10 08:34:12 +01:00
support added for linux, macos
win32, win64, lnx32, lnx64, lnx-armhf, lnx-armel, lnx-arm64, osx64
This commit is contained in:
parent
d44c77c958
commit
da7d1fafed
2 changed files with 209 additions and 137 deletions
|
@ -1,5 +1,4 @@
|
||||||
using System.Runtime.InteropServices;
|
using FFMpegCore.Helpers;
|
||||||
using FFMpegCore.Helpers;
|
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
namespace FFMpegCore.Test;
|
namespace FFMpegCore.Test;
|
||||||
|
@ -8,58 +7,18 @@ namespace FFMpegCore.Test;
|
||||||
public class DownloaderTests
|
public class DownloaderTests
|
||||||
{
|
{
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void GetLatestSuiteTest()
|
public void GetAllLatestSuiteTest()
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
var binaries = FFMpegDownloader.DownloadFFMpegSuite(binaries: FFMpegDownloader.FFMpegBinaries.FFProbe |
|
||||||
{
|
FFMpegDownloader.FFMpegBinaries.FFMpeg |
|
||||||
var fileNames = FFMpegDownloader.AutoDownloadFFMpegSuite();
|
FFMpegDownloader.FFMpegBinaries.FFPlay).Result;
|
||||||
Assert.IsTrue(fileNames.Count == 3);
|
Assert.IsTrue(binaries.Count >= 2); // many platforms have only ffmpeg and ffprobe
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.Inconclusive("This test is only for Windows");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void GetLatestFFMpegTest()
|
public void GetSpecificVersionTest()
|
||||||
{
|
{
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
var binaries = FFMpegDownloader.DownloadFFMpegSuite(FFMpegDownloader.FFMpegVersions.V4_0).Result;
|
||||||
{
|
Assert.IsTrue(binaries.Count == 2);
|
||||||
var fileNames = FFMpegDownloader.AutoDownloadFFMpeg();
|
|
||||||
Assert.IsTrue(fileNames.Count == 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.Inconclusive("This test is only for Windows");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void GetLatestFFProbeTest()
|
|
||||||
{
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
||||||
{
|
|
||||||
var fileNames = FFMpegDownloader.AutoDownloadFFProbe();
|
|
||||||
Assert.IsTrue(fileNames.Count == 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.Inconclusive("This test is only for Windows");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void GetLatestFFPlayTest()
|
|
||||||
{
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
||||||
{
|
|
||||||
var fileNames = FFMpegDownloader.AutoDownloadFFPlay();
|
|
||||||
Assert.IsTrue(fileNames.Count == 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Assert.Inconclusive("This test is only for Windows");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +1,38 @@
|
||||||
using System.ComponentModel;
|
using System.IO.Compression;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.IO;
|
using System.Runtime.InteropServices;
|
||||||
using System.IO.Compression;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace FFMpegCore.Helpers;
|
namespace FFMpegCore.Helpers;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Downloads the latest FFMpeg suite binaries from GitHub. Only supported for windows at the moment.
|
/// Downloads the latest FFMpeg suite binaries from ffbinaries.com.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FFMpegDownloader // this class is built to be easily modified to support other platforms
|
public class FFMpegDownloader
|
||||||
{
|
{
|
||||||
private static Dictionary<FFMpegVersions, string> Windows64FFMpegDownloadUrls = new()
|
[Flags]
|
||||||
|
public enum FFMpegBinaries : ushort
|
||||||
{
|
{
|
||||||
{ FFMpegVersions.V4_4_1, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v4.4.1/ffmpeg-4.4.1-win-64.zip"},
|
/// <summary>
|
||||||
{ FFMpegVersions.V4_2_1, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v4.2.1/ffmpeg-4.2.1-win-64.zip"},
|
/// FFMpeg binary
|
||||||
{ FFMpegVersions.V4_2, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v4.2/ffmpeg-4.2-win-64.zip"},
|
/// </summary>
|
||||||
{ FFMpegVersions.V4_1, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v4.1/ffmpeg-4.1-win-64.zip"},
|
FFMpeg,
|
||||||
{ FFMpegVersions.V4_0, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v4.0/ffmpeg-4.0.1-win-64.zip"},
|
|
||||||
{ FFMpegVersions.V3_4, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v3.4/ffmpeg-3.4-win-64.zip"},
|
/// <summary>
|
||||||
{ FFMpegVersions.V3_3, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v3.3/ffmpeg-3.3.4-win-64.zip"},
|
/// FFProbe binary
|
||||||
{ FFMpegVersions.V3_2, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v3.2/ffmpeg-3.2-win-64.zip"},
|
/// </summary>
|
||||||
};
|
FFProbe,
|
||||||
|
|
||||||
private static Dictionary<FFMpegVersions, string> Windows32FFMpegDownloadUrls = new()
|
/// <summary>
|
||||||
{
|
/// FFPlay binary
|
||||||
{ FFMpegVersions.V4_4_1, "https://example.com/" },
|
/// </summary>
|
||||||
{ FFMpegVersions.V4_2_1, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v4.2.1/ffmpeg-4.2.1-win-32.zip"},
|
FFPlay
|
||||||
{ FFMpegVersions.V4_2, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v4.2/ffmpeg-4.2-win-32.zip"},
|
}
|
||||||
{ FFMpegVersions.V4_1, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v4.1/ffmpeg-4.1-win-32.zip"},
|
|
||||||
{ FFMpegVersions.V4_0, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v4.0/ffmpeg-4.0.1-win-32.zip"},
|
public enum FFMpegVersions : ushort
|
||||||
{ FFMpegVersions.V3_4, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v3.4/ffmpeg-3.4-win-32.zip"},
|
|
||||||
{ FFMpegVersions.V3_3, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v3.3/ffmpeg-3.3.4-win-32.zip"},
|
|
||||||
{ FFMpegVersions.V3_2, "https://github.com/ffbinaries/ffbinaries-prebuilt/releases/download/v3.2/ffmpeg-3.2-win-32.zip"},
|
|
||||||
};
|
|
||||||
|
|
||||||
public enum FFMpegVersions
|
|
||||||
{
|
{
|
||||||
|
Latest,
|
||||||
V4_4_1,
|
V4_4_1,
|
||||||
V4_2_1,
|
V4_2_1,
|
||||||
V4_2,
|
V4_2,
|
||||||
|
@ -48,70 +43,71 @@ public enum FFMpegVersions
|
||||||
V3_2
|
V3_2
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<string> AutoDownloadFFMpegSuite(FFMpegVersions version = FFMpegVersions.V4_4_1)
|
/// <summary>
|
||||||
|
/// Download the latest FFMpeg suite binaries for current platform
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="version">used to explicitly state the version of binary you want to download</param>
|
||||||
|
/// <param name="binaries">used to explicitly state the binary you want to download</param>
|
||||||
|
/// <returns>a list of the binaries that have been successfully downloaded</returns>
|
||||||
|
public static async Task<List<string>> DownloadFFMpegSuite(FFMpegVersions version = FFMpegVersions.Latest,
|
||||||
|
FFMpegBinaries binaries = FFMpegBinaries.FFMpeg | FFMpegBinaries.FFProbe)
|
||||||
{
|
{
|
||||||
var files = AutoDownloadFFMpeg(version);
|
var versionInfo = await GetVersionInfo(version);
|
||||||
files.AddRange(AutoDownloadFFProbe(version));
|
var downloadInfo = versionInfo.BinaryInfo?.GetCompatibleDownloadInfo() ??
|
||||||
files.AddRange(AutoDownloadFFPlay(version));
|
throw new FFMpegDownloaderException("Failed to get compatible download info");
|
||||||
|
|
||||||
return files;
|
var successList = new List<string>();
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<string> AutoDownloadFFMpeg(FFMpegVersions version = FFMpegVersions.V4_4_1)
|
/// <summary>
|
||||||
{
|
/// Download file from uri
|
||||||
var url = Environment.Is64BitProcess
|
/// </summary>
|
||||||
? new Uri(Windows64FFMpegDownloadUrls[version])
|
/// <param name="address">uri of the file</param>
|
||||||
: new Uri(Windows32FFMpegDownloadUrls[version]);
|
/// <returns></returns>
|
||||||
|
private static MemoryStream DownloadFileAsSteam(Uri address)
|
||||||
HasValidUri(url);
|
|
||||||
|
|
||||||
Stream zipStream = DownloadZip(url);
|
|
||||||
|
|
||||||
return ExtractAndSave(zipStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<string> AutoDownloadFFProbe(FFMpegVersions version = FFMpegVersions.V4_4_1)
|
|
||||||
{
|
|
||||||
var url = Environment.Is64BitProcess
|
|
||||||
? new Uri(Windows64FFMpegDownloadUrls[version].Replace("ffmpeg", "ffprobe"))
|
|
||||||
: new Uri(Windows32FFMpegDownloadUrls[version].Replace("ffmpeg", "ffprobe"));
|
|
||||||
|
|
||||||
HasValidUri(url);
|
|
||||||
|
|
||||||
Stream zipStream = DownloadZip(url);
|
|
||||||
|
|
||||||
return ExtractAndSave(zipStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<string> AutoDownloadFFPlay(FFMpegVersions version = FFMpegVersions.V4_4_1)
|
|
||||||
{
|
|
||||||
var url = Environment.Is64BitProcess
|
|
||||||
? new Uri(Windows64FFMpegDownloadUrls[version].Replace("ffmpeg", "ffplay"))
|
|
||||||
: new Uri(Windows32FFMpegDownloadUrls[version].Replace("ffmpeg", "ffplay"));
|
|
||||||
|
|
||||||
HasValidUri(url);
|
|
||||||
|
|
||||||
Stream zipStream = DownloadZip(url);
|
|
||||||
|
|
||||||
return ExtractAndSave(zipStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MemoryStream DownloadZip(Uri address)
|
|
||||||
{
|
{
|
||||||
var client = new WebClient();
|
var client = new WebClient();
|
||||||
var zipStream = new MemoryStream(client.DownloadData(address));
|
var fileStream = new MemoryStream(client.DownloadData(address));
|
||||||
zipStream.Position = 0;
|
fileStream.Position = 0;
|
||||||
|
|
||||||
return zipStream;
|
return fileStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<string> ExtractAndSave(Stream zipStream)
|
/// <summary>
|
||||||
|
/// Extracts the binaries from the zip stream and saves them to the current binary folder
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="zipStream">steam of the zip file</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static List<string> ExtractZipAndSave(Stream zipStream)
|
||||||
{
|
{
|
||||||
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read);
|
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read);
|
||||||
List<string> files = new();
|
List<string> files = new();
|
||||||
foreach (var entry in archive.Entries)
|
foreach (var entry in archive.Entries)
|
||||||
{
|
{
|
||||||
if (entry.Name is "ffmpeg.exe" or "ffmpeg" or "ffprobe.exe")
|
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);
|
entry.ExtractToFile(Path.Combine(GlobalFFOptions.Current.BinaryFolder, entry.Name), true);
|
||||||
files.Add(Path.Combine(GlobalFFOptions.Current.BinaryFolder, entry.Name));
|
files.Add(Path.Combine(GlobalFFOptions.Current.BinaryFolder, entry.Name));
|
||||||
|
@ -121,11 +117,128 @@ private static List<string> ExtractAndSave(Stream zipStream)
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void HasValidUri(Uri uri)
|
#region FFbinaries api
|
||||||
|
|
||||||
|
private class DownloadInfo
|
||||||
{
|
{
|
||||||
if (uri.ToString() == "https://example.com/" || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
[JsonPropertyName("ffmpeg")] public string? FFMpeg { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("ffprobe")] public string? FFProbe { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("ffplay")] public string? FFPlay { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BinaryInfo
|
||||||
|
{
|
||||||
|
[JsonPropertyName("windows-64")] public DownloadInfo? Windows64 { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("windows-32")] public DownloadInfo? Windows32 { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("linux-32")] public DownloadInfo? Linux32 { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("linux-64")] public DownloadInfo? Linux64 { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("linux-armhf")] public DownloadInfo? LinuxArmhf { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("linux-armel")] public DownloadInfo? LinuxArmel { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("linux-arm64")] public DownloadInfo? LinuxArm64 { get; }
|
||||||
|
|
||||||
|
[JsonPropertyName("osx-64")] public DownloadInfo? Osx64 { get; }
|
||||||
|
|
||||||
|
public DownloadInfo? GetCompatibleDownloadInfo()
|
||||||
{
|
{
|
||||||
throw new PlatformNotSupportedException("The requested version of FFMpeg component is not available for your OS.");
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
return RuntimeInformation.OSArchitecture == Architecture.X64 ? Windows64 : Windows32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
return RuntimeInformation.OSArchitecture switch
|
||||||
|
{
|
||||||
|
Architecture.X64 => Linux64,
|
||||||
|
Architecture.Arm => LinuxArmhf,
|
||||||
|
Architecture.Arm64 => LinuxArm64,
|
||||||
|
_ => throw new PlatformNotSupportedException("Unsupported Linux architecture")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
|
{
|
||||||
|
return Osx64;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PlatformNotSupportedException("Unsupported OS");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<FFMpegVersions, string> 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" }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get version info from ffbinaries.com
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="version">use to explicitly state the version of ffmpeg you want</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="FFMpegDownloaderException"></exception>
|
||||||
|
private static async Task<VersionInfo> 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<VersionInfo>(jsonString);
|
||||||
|
|
||||||
|
return versionInfo ??
|
||||||
|
throw new FFMpegDownloaderException($"Failed to deserialize version info from {versionUri}", jsonString);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Custom exception for FFMpegDownloader
|
||||||
|
/// </summary>
|
||||||
|
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; } = "";
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue