Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to install ffmpeg suite at runtime added with FFMpegDownloader #442

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
9db4ba7
features to auto download ffmpeg binaries added
yuqian5 Apr 12, 2023
7c333a7
Merge pull request #1 from rosenbjerg/main
yuqian5 Apr 12, 2023
a4bb69a
changes made to support multiple version downloads for windows
yuqian5 Apr 12, 2023
d44c77c
Merge remote-tracking branch 'origin/main'
yuqian5 Apr 12, 2023
657ee5f
Comments added
May 5, 2023
d68ce8e
Methods renamed to remove auto prefix
May 5, 2023
debb868
README.md updated to include ffmpegdownloader usage
May 5, 2023
a647f44
refactoring and reformatting
May 5, 2023
463fb9b
Reformatted to fix lint error
May 5, 2023
cf7fd2f
Merge branch 'main' into main
yuqian5 May 7, 2023
97fcd3b
Merge branch 'main' into main
yuqian5 Jun 4, 2023
da7d1fa
support added for linux, macos
yuqian5 Sep 6, 2023
ae92e93
Merge remote-tracking branch 'origin/main'
yuqian5 Sep 6, 2023
df8d97e
allow os and architecture overrider
yuqian5 Sep 6, 2023
937db76
format fix for lint
yuqian5 Sep 6, 2023
dfc486d
ffmpeg downloader moved to seperate package
Sep 6, 2023
d978d7d
ffmpeg downloader moved to separate package
Sep 6, 2023
2b66b82
Merge remote-tracking branch 'origin/main'
yuqian5 Sep 6, 2023
ae5ae97
FFMpegCore.Downloader renamed to FFMpegCore.Extensions.Downloader
yuqian5 Sep 6, 2023
882cdf8
Merge branch 'main' into main
rosenbjerg Oct 5, 2023
bba4a9f
Merge branch 'main' into main
rosenbjerg Oct 5, 2023
5bedbdb
Update README.md
yuqian5 Oct 10, 2023
22fa002
Debug for CI failure
yuqian5 Oct 10, 2023
6524b1c
Update DownloaderTests.cs
yuqian5 Oct 10, 2023
105a26b
Merge branch 'rosenbjerg:main' into main
yuqian5 Jan 4, 2024
09ae944
version 5.1 and 6.1 added
yuqian5 Jan 4, 2024
ac3794e
Merge branch 'main' into main
rosenbjerg Dec 4, 2024
a91e309
Merge branch 'main' into main
rosenbjerg Dec 4, 2024
c44170b
Merge branch 'main' into main
rosenbjerg Dec 4, 2024
995690c
Merge branch 'rosenbjerg:main' into main
yuqian5 Jan 28, 2025
7be5496
major refactoring
yuqian5 Jan 28, 2025
8e32d68
format cleanup to avoid lint error
yuqian5 Jan 28, 2025
91a2cea
Test update to try and resolve failed tests
yuqian5 Jan 28, 2025
33098ad
linter fix
yuqian5 Jan 28, 2025
88c7f7b
debug
yuqian5 Jan 28, 2025
9f6249c
re-enable DownloaderTests.cs
yuqian5 Jan 28, 2025
644c41d
GetSpecificVersionTest assertion fix
yuqian5 Jan 29, 2025
1c03587
obsolete webclient replaced with httpclient
yuqian5 Jan 29, 2025
483c526
linter fix
yuqian5 Jan 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions FFMpegCore.Extensions.Downloader/Enums/FFMpegBinaries.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace FFMpegCore.Extensions.Downloader.Enums;

[Flags]
public enum FFMpegBinaries : ushort
{
FFMpeg,
FFProbe,
FFPlay
}
39 changes: 39 additions & 0 deletions FFMpegCore.Extensions.Downloader/Enums/FFMpegVersions.cs
Original file line number Diff line number Diff line change
@@ -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
}
13 changes: 13 additions & 0 deletions FFMpegCore.Extensions.Downloader/Enums/SupportedPlatforms.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace FFMpegCore.Extensions.Downloader.Enums;

public enum SupportedPlatforms : ushort
{
Windows64,
Windows32,
Linux64,
Linux32,
LinuxArmhf,
LinuxArmel,
LinuxArm64,
Osx64
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
22 changes: 22 additions & 0 deletions FFMpegCore.Extensions.Downloader/Extensions/EnumExtensions.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup>
<IsPackable>true</IsPackable>
<Description>FFMpeg downloader extension for FFMpegCore</Description>
<PackageVersion>5.0.0</PackageVersion>
<PackageOutputPath>../nupkg</PackageOutputPath>
<PackageReleaseNotes>
</PackageReleaseNotes>
<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>

</Project>
53 changes: 53 additions & 0 deletions FFMpegCore.Extensions.Downloader/FFMpegDownloader.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <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 binaries you want to download (ffmpeg, ffprobe, ffplay)</param>
/// <param name="platformOverride">used to explicitly state the os and architecture 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,
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<string>();

// 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;
}
}
74 changes: 74 additions & 0 deletions FFMpegCore.Extensions.Downloader/Models/BinaryInfo.cs
Original file line number Diff line number Diff line change
@@ -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");
}
}
12 changes: 12 additions & 0 deletions FFMpegCore.Extensions.Downloader/Models/DownloadInfo.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
12 changes: 12 additions & 0 deletions FFMpegCore.Extensions.Downloader/Models/VersionInfo.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
71 changes: 71 additions & 0 deletions FFMpegCore.Extensions.Downloader/Services/FFbinariesService.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <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>
yuqian5 marked this conversation as resolved.
Show resolved Hide resolved
/// Download file from uri
/// </summary>
/// <param name="address">uri of the file</param>
/// <returns></returns>
internal static async Task<Stream> DownloadFileAsSteam(Uri address)
{
var client = new HttpClient();
return await client.GetStreamAsync(address);
}

/// <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;
}
}
23 changes: 23 additions & 0 deletions FFMpegCore.Test/DownloaderTests.cs
Original file line number Diff line number Diff line change
@@ -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
}
}
1 change: 1 addition & 0 deletions FFMpegCore.Test/FFMpegCore.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\FFMpegCore.Extensions.Downloader\FFMpegCore.Extensions.Downloader.csproj" />
<ProjectReference Include="..\FFMpegCore.Extensions.SkiaSharp\FFMpegCore.Extensions.SkiaSharp.csproj" />
<ProjectReference Include="..\FFMpegCore.Extensions.System.Drawing.Common\FFMpegCore.Extensions.System.Drawing.Common.csproj" />
<ProjectReference Include="..\FFMpegCore\FFMpegCore.csproj" />
Expand Down
Loading
Loading