Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/ui/Features/Options/Settings/SettingsViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
using Nikse.SubtitleEdit.Features.Video.BurnIn;
using Nikse.SubtitleEdit.Logic;
using Nikse.SubtitleEdit.Logic.Config;
using Nikse.SubtitleEdit.Logic.Download;
using Nikse.SubtitleEdit.Logic.Media;
using Nikse.SubtitleEdit.Logic.VideoPlayers.LibMpvDynamic;
using System;
Expand Down Expand Up @@ -689,7 +690,7 @@ public SettingsViewModel(IWindowService windowService, IFolderHelper folderHelpe
LibMpvPath = string.Empty;
IsLibMpvDownloadVisible = OperatingSystem.IsWindows();
IsLibVlcDownloadVisible = OperatingSystem.IsWindows();
IsFfmpegLibsDownloadVisible = OperatingSystem.IsWindows();
IsFfmpegLibsDownloadVisible = FfmpegLibsDownloadService.IsDownloadSupportedOnCurrentPlatform();

MpvPreviewFontName = FontNames.First();
MpvPreviewSelectedBorderType = MpvPreviewBorderTypes.First();
Expand Down
12 changes: 11 additions & 1 deletion src/ui/Logic/Download/FfmpegLibsDownloadService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ public async Task DownloadFfmpegLibs(string destinationFileName, IProgress<float
await VerifySha256Async(destinationFileName, WindowsX64Sha256, cancellationToken);
}

internal static bool IsDownloadSupported(bool isWindows, Architecture processArchitecture)
{
return isWindows && processArchitecture == Architecture.X64;
}

internal static bool IsDownloadSupportedOnCurrentPlatform()
{
return IsDownloadSupported(OperatingSystem.IsWindows(), RuntimeInformation.ProcessArchitecture);
}

internal static async Task VerifySha256Async(string filePath, string expectedSha256, CancellationToken cancellationToken)
{
var actual = await Sha256Util.ComputeSha256Async(filePath, cancellationToken);
Expand Down Expand Up @@ -64,7 +74,7 @@ private static void TryDeleteFile(string filePath)

private static string GetUrl()
{
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.X64)
if (IsDownloadSupportedOnCurrentPlatform())
{
return WindowsX64Url;
}
Expand Down
30 changes: 30 additions & 0 deletions src/ui/Logic/VideoPlayers/Ffmpeg/FfmpegLibraries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ public static class FfmpegLibraries
/// <summary>libavcodec major the bindings were generated for (63 for FFmpeg 9), as used in the library file names.</summary>
public static int AvCodecMajor => ffmpeg.LIBAVCODEC_VERSION_MAJOR;

internal static int VersionMajor(uint version) => (int)(version >> 16);

internal static bool RequiredLibraryVersionsMatch(
uint avcodec,
uint avformat,
uint avutil,
uint swscale,
uint swresample)
{
return VersionMajor(avcodec) == ffmpeg.LIBAVCODEC_VERSION_MAJOR &&
VersionMajor(avformat) == ffmpeg.LIBAVFORMAT_VERSION_MAJOR &&
VersionMajor(avutil) == ffmpeg.LIBAVUTIL_VERSION_MAJOR &&
VersionMajor(swscale) == ffmpeg.LIBSWSCALE_VERSION_MAJOR &&
VersionMajor(swresample) == ffmpeg.LIBSWRESAMPLE_VERSION_MAJOR;
}

/// <summary>
/// Set this path (directory only) to override the default search paths - the same idea as
/// <c>LibVlcDynamicPlayer.LibVlcPath</c>.
Expand Down Expand Up @@ -163,6 +179,20 @@ private static bool TryInitialize()
// An empty RootPath leaves the lookup to the system loader (PATH / LD_LIBRARY_PATH /
// dyld), which is the normal case on Linux where FFmpeg is a distro package.
ffmpeg.RootPath = _resolvedPath;

// Probe every library the player actually uses. av_version_info() belongs to avutil,
// so probing only that function can report the player as available even when codec,
// demux, scaling or resampling libraries are missing.
var avcodec = ffmpeg.avcodec_version();
var avformat = ffmpeg.avformat_version();
var avutil = ffmpeg.avutil_version();
var swscale = ffmpeg.swscale_version();
var swresample = ffmpeg.swresample_version();
if (!RequiredLibraryVersionsMatch(avcodec, avformat, avutil, swscale, swresample))
{
return false;
}

var version = ffmpeg.av_version_info();
if (string.IsNullOrEmpty(version))
{
Expand Down
14 changes: 14 additions & 0 deletions tests/UI/Logic/Download/FfmpegLibsDownloadServiceTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Nikse.SubtitleEdit.Logic.Download;
using Nikse.SubtitleEdit.Logic.VideoPlayers.Ffmpeg;
using System.Runtime.InteropServices;

namespace UITests.Logic.Download;

Expand All @@ -16,6 +17,19 @@ public void WindowsArchive_IsPinnedToReviewedAutobuildAndDigest()
Assert.Matches("^[0-9a-f]{64}$", FfmpegLibsDownloadService.WindowsX64Sha256);
}

[Theory]
[InlineData(true, Architecture.X64, true)]
[InlineData(true, Architecture.Arm64, false)]
[InlineData(true, Architecture.X86, false)]
[InlineData(false, Architecture.X64, false)]
public void IsDownloadSupported_MatchesPublishedWindowsX64Capability(
bool isWindows,
Architecture architecture,
bool expected)
{
Assert.Equal(expected, FfmpegLibsDownloadService.IsDownloadSupported(isWindows, architecture));
}

[Fact]
public async Task VerifySha256Async_MatchingDigest_KeepsFile()
{
Expand Down
25 changes: 25 additions & 0 deletions tests/UI/Logic/FfmpegPlayerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,31 @@ public void AvCodecFileName_CarriesTheBindingsMajorVersion()
Assert.Contains(FfmpegLibraries.AvCodecMajor.ToString(), FfmpegLibraries.AvCodecFileName);
}

[Fact]
public void RequiredLibraryVersionsMatch_RequiresEveryBindingMajor()
{
static uint V(int major) => (uint)major << 16;

var codec = V(ffmpeg.LIBAVCODEC_VERSION_MAJOR);
var format = V(ffmpeg.LIBAVFORMAT_VERSION_MAJOR);
var util = V(ffmpeg.LIBAVUTIL_VERSION_MAJOR);
var scale = V(ffmpeg.LIBSWSCALE_VERSION_MAJOR);
var resample = V(ffmpeg.LIBSWRESAMPLE_VERSION_MAJOR);

Assert.True(FfmpegLibraries.RequiredLibraryVersionsMatch(codec, format, util, scale, resample));
Assert.False(FfmpegLibraries.RequiredLibraryVersionsMatch(codec + (1u << 16), format, util, scale, resample));
Assert.False(FfmpegLibraries.RequiredLibraryVersionsMatch(codec, format + (1u << 16), util, scale, resample));
Assert.False(FfmpegLibraries.RequiredLibraryVersionsMatch(codec, format, util + (1u << 16), scale, resample));
Assert.False(FfmpegLibraries.RequiredLibraryVersionsMatch(codec, format, util, scale + (1u << 16), resample));
Assert.False(FfmpegLibraries.RequiredLibraryVersionsMatch(codec, format, util, scale, resample + (1u << 16)));
}

[Fact]
public void VersionMajor_UsesFfmpegVersionEncoding()
{
Assert.Equal(63, FfmpegLibraries.VersionMajor((63u << 16) | (12u << 8) | 100u));
}

[Theory]
[InlineData(12.5, 60.0, 12.5)]
[InlineData(75.0, 60.0, 60.0)] // past the end: clamped to the duration
Expand Down
Loading