diff --git a/listenarr.infrastructure/Ffmpeg/Installation/FfprobePlatformDefaults.cs b/listenarr.infrastructure/Ffmpeg/Installation/FfprobePlatformDefaults.cs
index 2eb4b55b7..9fd776926 100644
--- a/listenarr.infrastructure/Ffmpeg/Installation/FfprobePlatformDefaults.cs
+++ b/listenarr.infrastructure/Ffmpeg/Installation/FfprobePlatformDefaults.cs
@@ -25,20 +25,49 @@ internal static class FfprobePlatformDefaults
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
- if (RuntimeInformation.OSArchitecture == Architecture.Arm64)
- {
- return "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz";
- }
-
- return "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz";
+ return GetDownloadUrl(OSPlatform.Linux, RuntimeInformation.OSArchitecture);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
- return "https://evermeet.cx/ffmpeg/ffmpeg-6.0.zip";
+ return GetDownloadUrl(OSPlatform.OSX, RuntimeInformation.OSArchitecture);
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return GetDownloadUrl(OSPlatform.Windows, RuntimeInformation.OSArchitecture);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Resolves the archive URL for an explicit platform, so the mapping for every platform can
+ /// be asserted from a test host running on any one of them.
+ ///
+ ///
+ /// Two constraints apply to anything returned here. The archive has to contain ffprobe:
+ /// the Linux and Windows builds ship a bundle carrying both binaries, but evermeet
+ /// publishes one binary per archive, so macOS needs the ffprobe archive and not the ffmpeg
+ /// one. The URL also has to end in a suffix
+ /// recognises, which rules out endpoints that redirect to the current release without
+ /// naming a file.
+ ///
+ internal static string? GetDownloadUrl(OSPlatform platform, Architecture architecture)
+ {
+ if (platform == OSPlatform.Linux)
+ {
+ return architecture == Architecture.Arm64
+ ? "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-arm64-static.tar.xz"
+ : "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz";
+ }
+
+ if (platform == OSPlatform.OSX)
+ {
+ return "https://evermeet.cx/ffmpeg/ffprobe-6.0.zip";
+ }
+
+ if (platform == OSPlatform.Windows)
{
return "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip";
}
diff --git a/tests/Features/Infrastructure/Ffmpeg/Installation/FfprobePlatformDefaultsTests.cs b/tests/Features/Infrastructure/Ffmpeg/Installation/FfprobePlatformDefaultsTests.cs
new file mode 100644
index 000000000..e7d13db46
--- /dev/null
+++ b/tests/Features/Infrastructure/Ffmpeg/Installation/FfprobePlatformDefaultsTests.cs
@@ -0,0 +1,113 @@
+/*
+ * Listenarr - Audiobook Management System
+ * Copyright (C) 2024-2026 Listenarr Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+using System.Runtime.InteropServices;
+
+// Aliased because `Listenarr.Tests.Features.Architecture` is an enclosing namespace here, and a
+// namespace member wins over the type of the same name.
+using Arch = System.Runtime.InteropServices.Architecture;
+
+namespace Listenarr.Tests.Features.Infrastructure.Ffmpeg.Installation
+{
+ [Trait("Name", "FfprobePlatformDefaultsTests")]
+ [Trait("Category", "FfmpegService")]
+ public class FfprobePlatformDefaultsTests
+ {
+ // Suffixes FfprobeArchiveExtractor dispatches on. A URL outside this set downloads fine and
+ // then extracts nothing, which is the failure mode that hid the macOS bug: the install log
+ // shows a successful download and no binary ever appears.
+ private static readonly string[] ExtractableSuffixes = [".zip", ".tar.xz", ".tar.gz", ".tgz"];
+
+ public static TheoryData AllPlatforms() => new()
+ {
+ { OSPlatform.Linux, Arch.X64 },
+ { OSPlatform.Linux, Arch.Arm64 },
+ { OSPlatform.OSX, Arch.X64 },
+ { OSPlatform.Windows, Arch.X64 },
+ };
+
+ [Theory]
+ [MemberData(nameof(AllPlatforms))]
+ [Trait("Method", "GetDownloadUrl")]
+ public void GetDownloadUrl_ReturnsAnArchiveTheExtractorCanOpen(
+ OSPlatform platform,
+ Arch architecture)
+ {
+ var url = FfprobePlatformDefaults.GetDownloadUrl(platform, architecture);
+
+ Assert.NotNull(url);
+ Assert.Contains(
+ ExtractableSuffixes,
+ suffix => url!.EndsWith(suffix, StringComparison.OrdinalIgnoreCase));
+ }
+
+ [Fact]
+ [Trait("Method", "GetDownloadUrl")]
+ public void GetDownloadUrl_ForMacOs_PointsAtTheFfprobeArchive()
+ {
+ // evermeet ships a separate archive per binary, so the ffmpeg archive contains no
+ // ffprobe at all and the install can never succeed on macOS.
+ var url = FfprobePlatformDefaults.GetDownloadUrl(OSPlatform.OSX, Arch.X64);
+
+ Assert.NotNull(url);
+ var fileName = url!.Split('/')[^1];
+ Assert.Contains("ffprobe", fileName, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Theory]
+ [MemberData(nameof(AllPlatforms))]
+ [Trait("Method", "GetDownloadUrl")]
+ public void GetDownloadUrl_IsServedOverHttps(OSPlatform platform, Arch architecture)
+ {
+ var url = FfprobePlatformDefaults.GetDownloadUrl(platform, architecture);
+
+ Assert.NotNull(url);
+ Assert.StartsWith("https://", url, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ [Trait("Method", "GetDownloadUrl")]
+ public void GetDownloadUrl_ForLinux_DistinguishesArm64FromX64()
+ {
+ var x64 = FfprobePlatformDefaults.GetDownloadUrl(OSPlatform.Linux, Arch.X64);
+ var arm64 = FfprobePlatformDefaults.GetDownloadUrl(OSPlatform.Linux, Arch.Arm64);
+
+ Assert.NotEqual(x64, arm64);
+ Assert.Contains("arm64", arm64!, StringComparison.OrdinalIgnoreCase);
+ }
+
+ [Fact]
+ [Trait("Method", "GetDownloadUrl")]
+ public void GetDownloadUrl_ForAnUnsupportedPlatform_ReturnsNull()
+ {
+ Assert.Null(FfprobePlatformDefaults.GetDownloadUrl(OSPlatform.FreeBSD, Arch.X64));
+ }
+
+ [Fact]
+ [Trait("Method", "GetDownloadUrl")]
+ public void GetDownloadUrl_WithoutArguments_MatchesTheHostPlatform()
+ {
+ var expected = FfprobePlatformDefaults.GetDownloadUrl(
+ OperatingSystem.IsWindows() ? OSPlatform.Windows
+ : OperatingSystem.IsMacOS() ? OSPlatform.OSX
+ : OSPlatform.Linux,
+ RuntimeInformation.OSArchitecture);
+
+ Assert.Equal(expected, FfprobePlatformDefaults.GetDownloadUrl());
+ }
+ }
+}