diff --git a/src/ui/Features/Options/Settings/SettingsViewModel.cs b/src/ui/Features/Options/Settings/SettingsViewModel.cs index 4bc8761ad1..70206be5e7 100644 --- a/src/ui/Features/Options/Settings/SettingsViewModel.cs +++ b/src/ui/Features/Options/Settings/SettingsViewModel.cs @@ -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; @@ -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(); diff --git a/src/ui/Logic/Download/FfmpegLibsDownloadService.cs b/src/ui/Logic/Download/FfmpegLibsDownloadService.cs index 8c813955d7..70e27a102b 100644 --- a/src/ui/Logic/Download/FfmpegLibsDownloadService.cs +++ b/src/ui/Logic/Download/FfmpegLibsDownloadService.cs @@ -34,6 +34,16 @@ public async Task DownloadFfmpegLibs(string destinationFileName, IProgresslibavcodec major the bindings were generated for (63 for FFmpeg 9), as used in the library file names. 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; + } + /// /// Set this path (directory only) to override the default search paths - the same idea as /// LibVlcDynamicPlayer.LibVlcPath. @@ -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)) { diff --git a/tests/UI/Logic/Download/FfmpegLibsDownloadServiceTests.cs b/tests/UI/Logic/Download/FfmpegLibsDownloadServiceTests.cs index b3e294f3a8..641e1a3be2 100644 --- a/tests/UI/Logic/Download/FfmpegLibsDownloadServiceTests.cs +++ b/tests/UI/Logic/Download/FfmpegLibsDownloadServiceTests.cs @@ -1,5 +1,6 @@ using Nikse.SubtitleEdit.Logic.Download; using Nikse.SubtitleEdit.Logic.VideoPlayers.Ffmpeg; +using System.Runtime.InteropServices; namespace UITests.Logic.Download; @@ -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() { diff --git a/tests/UI/Logic/FfmpegPlayerTests.cs b/tests/UI/Logic/FfmpegPlayerTests.cs index e19394646b..692a79d50a 100644 --- a/tests/UI/Logic/FfmpegPlayerTests.cs +++ b/tests/UI/Logic/FfmpegPlayerTests.cs @@ -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