From 64fe35ff090f3000aaa770ca0a15e98405e21199 Mon Sep 17 00:00:00 2001
From: m4bard <304653687+m4bard@users.noreply.github.com>
Date: Thu, 30 Jul 2026 00:44:42 -0500
Subject: [PATCH] fix(ffmpeg): point macOS at evermeet's ffprobe archive, not
its ffmpeg one
The bundled ffprobe install can never succeed on macOS. FfprobePlatformDefaults
returns evermeet's ffmpeg archive, and evermeet ships one binary per archive, so
the download succeeds and FfprobeArchiveExtractor then finds no ffprobe entry to
install. Metadata import is broken on every Mac as a result.
evermeet publishes ffprobe as its own archive at the same version, so this points
macOS at ffprobe-6.0.zip instead of ffmpeg-6.0.zip. Same version, same host, same
extraction path; only the artifact changes.
Verified against the live archives: ffmpeg-6.0.zip holds a single entry, ffmpeg,
with no ffprobe anywhere in it, while ffprobe-6.0.zip holds a single ffprobe that
file reports as a Mach-O 64-bit x86_64 executable. x86_64 is what is wanted here,
since the project publishes osx-x64 only and is therefore always an x64 process on
macOS, natively on Intel and under Rosetta on Apple Silicon.
Adds an internal GetDownloadUrl(OSPlatform, Architecture) overload so the mapping
for every platform can be asserted from a test host on any one of them, and tests
covering the macOS regression plus two invariants that hold everywhere: the macOS
filename has to name ffprobe, and every URL has to end in a suffix the extractor
dispatches on. The second one matters because evermeet's getrelease endpoint
redirects to the current release and ends in /zip, so it looks like the tidier
target and would extract nothing.
macOS stays on 6.0 and GetChecksum() still returns null. Both are deliberate: a
version bump needs evidence I cannot produce without a Mac, and pinning with
checksums is the wider half of #777 and applies to all three platforms.
Refs #777
---
.../Installation/FfprobePlatformDefaults.cs | 43 +++++--
.../FfprobePlatformDefaultsTests.cs | 113 ++++++++++++++++++
2 files changed, 149 insertions(+), 7 deletions(-)
create mode 100644 tests/Features/Infrastructure/Ffmpeg/Installation/FfprobePlatformDefaultsTests.cs
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());
+ }
+ }
+}