Compare commits

...

9 commits

Author SHA1 Message Date
Kerry Cao
9f6249cb5f re-enable DownloaderTests.cs 2025-01-27 22:43:20 -05:00
Kerry Cao
88c7f7bf7c debug 2025-01-27 22:39:35 -05:00
Kerry Cao
33098ad4de linter fix 2025-01-27 22:37:03 -05:00
Kerry Cao
91a2ceaceb Test update to try and resolve failed tests 2025-01-27 22:34:57 -05:00
Kerry Cao
8e32d68877 format cleanup to avoid lint error 2025-01-27 22:30:22 -05:00
Kerry Cao
7be5496305 major refactoring 2025-01-27 22:22:47 -05:00
K
995690c0f3
Merge branch 'rosenbjerg:main' into main 2025-01-27 21:42:14 -05:00
Malte Rosenbjerg
1c4333ee4c
Merge pull request #552 from rosenbjerg/bump-dependencies--instances
Some checks failed
CI / ci (macos-13) (push) Has been cancelled
CI / ci (ubuntu-latest) (push) Has been cancelled
CI / ci (windows-latest) (push) Has been cancelled
Bump Instances
2024-12-05 10:05:06 +01:00
Malte Rosenbjerg
855e6ece30 Bump Instances 2024-12-05 10:59:59 +02:00
13 changed files with 300 additions and 250 deletions

View file

@ -0,0 +1,9 @@
namespace FFMpegCore.Extensions.Downloader.Enums;
[Flags]
public enum FFMpegBinaries : ushort
{
FFMpeg,
FFProbe,
FFPlay
}

View file

@ -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
}

View file

@ -0,0 +1,13 @@
namespace FFMpegCore.Extensions.Downloader.Enums;
public enum SupportedPlatforms : ushort
{
Windows64,
Windows32,
Linux64,
Linux32,
LinuxArmhf,
LinuxArmel,
LinuxArm64,
Osx64
}

View file

@ -0,0 +1,18 @@
namespace FFMpegCore.Extensions.Downloader.Exceptions;
/// <summary>
/// Custom exception for FFMpegDownloader
/// </summary>
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;
}
}

View file

@ -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();
}
}

View file

@ -15,7 +15,7 @@
<PackageTags>ffmpeg ffprobe convert video audio mediafile resize analyze download</PackageTags>
<Authors>Malte Rosenbjerg, Vlad Jerca, Max Bagryantsev, Kerry Cao</Authors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj"/>
</ItemGroup>

View file

@ -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;
/// <summary>
/// Downloads the latest FFMpeg suite binaries from ffbinaries.com.
/// </summary>
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
}
/// <summary>
/// Download the latest FFMpeg suite binaries for current platform
/// </summary>
@ -56,221 +16,38 @@ public class FFMpegDownloader
public static async Task<List<string>> 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<string>();
// 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;
}
/// <summary>
/// Download file from uri
/// </summary>
/// <param name="address">uri of the file</param>
/// <returns></returns>
private static MemoryStream DownloadFileAsSteam(Uri address)
{
var client = new WebClient();
var fileStream = new MemoryStream(client.DownloadData(address));
fileStream.Position = 0;
return fileStream;
}
/// <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 IEnumerable<string> ExtractZipAndSave(Stream zipStream)
{
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read);
List<string> 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; }
/// <summary>
/// Automatically get the compatible download info for current os and architecture
/// </summary>
/// <param name="platformOverride"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="PlatformNotSupportedException"></exception>
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<FFMpegVersions, string> _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" }
};
/// <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; } = "";
}

View file

@ -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; }
/// <summary>
/// Automatically get the compatible download info for current os and architecture
/// </summary>
/// <param name="platformOverride"></param>
/// <returns></returns>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="PlatformNotSupportedException"></exception>
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");
}
}

View file

@ -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; }
}

View file

@ -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; }
}

View file

@ -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;
/// <summary>
/// Service to interact with ffbinaries.com API
/// </summary>
internal class FFbinariesService
{
/// <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>
internal static async Task<VersionInfo> 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<VersionInfo>(jsonString);
return versionInfo ??
throw new FFMpegDownloaderException($"Failed to deserialize version info from {versionUri}", jsonString);
}
/// <summary>
/// Download file from uri
/// </summary>
/// <param name="address">uri of the file</param>
/// <returns></returns>
internal static MemoryStream DownloadFileAsSteam(Uri address)
{
var client = new WebClient();
var fileStream = new MemoryStream(client.DownloadData(address));
fileStream.Position = 0;
return fileStream;
}
/// <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>
internal static IEnumerable<string> ExtractZipAndSave(Stream zipStream)
{
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read);
List<string> 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;
}
}

View file

@ -1,4 +1,5 @@
using FFMpegCore.Extensions.Downloader;
using FFMpegCore.Extensions.Downloader.Enums;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FFMpegCore.Test;
@ -7,16 +8,16 @@ namespace FFMpegCore.Test;
public class DownloaderTests
{
[TestMethod]
public void GetAllLatestSuiteTest()
public void GetSpecificVersionTest()
{
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(FFMpegVersions.V6_1).Result;
Assert.IsTrue(binaries.Count == 1);
}
[TestMethod]
public void GetSpecificVersionTest()
public void GetAllLatestSuiteTest()
{
var binaries = FFMpegDownloader.DownloadFFMpegSuite(FFMpegDownloader.FFMpegVersions.V4_0, binaries: FFMpegDownloader.FFMpegBinaries.FFMpeg).Result;
Assert.IsTrue(binaries.Count == 1);
var binaries = FFMpegDownloader.DownloadFFMpegSuite().Result;
Assert.IsTrue(binaries.Count == 2); // many platforms have only ffmpeg and ffprobe
}
}

View file

@ -17,10 +17,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Instances" Version="3.0.0" />
<PackageReference Include="Instances" Version="3.0.1" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
</ItemGroup>
</Project>