Skip to content

ffprobe is installed from unpinned, unverified URLs, and the platforms disagree #791

Description

@m4bard

The setup

FfprobePlatformDefaults.GetDownloadUrl picks one of three third-party URLs:

  • Linux: johnvansickle.com/ffmpeg/releases/ffmpeg-release-{amd64,arm64}-static.tar.xz
  • Windows: gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip
  • macOS: evermeet.cx/ffmpeg/ffprobe-6.0.zip

Two of those are rolling pointers, so the URL stays the same while the file behind it changes. GetChecksum() returns null, which leaves expected empty on the default path, so the mismatch branch in FfmpegService never fires. Whatever the URL serves that day gets extracted and run.

What that produces right now

I run a cross-platform harness that fetches ffprobe on every RID Listenarr ships and compares the fields FfprobeMetadataMapper reads against a committed golden. Latest run, today:

platform current source result
linux-x64 johnvansickle matches golden
linux-arm64 johnvansickle matches golden
osx-x64 (under Rosetta) evermeet no ffprobe in the archive (#777)
win-x64 gyan disagrees with golden

The Windows URL now serves ffprobe 8.1.2. On the same input files it reports:

sample.mp3  format.duration   1.071020  ->  1.000000
sample.mp3  format.bit_rate   34277     ->  36712

Both of those are mapped: format.duration becomes metadata.Duration and format.bit_rate becomes metadata.BitRate. The m4a and m4b samples also drop vendor_id from stream.tags, which I do not think reaches a field, but it shows the tag dictionary is not stable across builds either.

So a book imported on Windows and the same book imported on Linux get different durations and bit rates recorded. I measured that today. I cannot tell you when it started, because the URL keeps no history and my previous run's logs have expired.

Most of the fix already exists

FfmpegConfig.Provider accepts github:<owner>/<repo>. When it is set, FfprobeGithubAssetDiscoverer finds a checksum asset in the release, FfprobeChecksumParser pulls the line for the downloaded file, and FfmpegService compares it and aborts the install on mismatch. That is the behaviour you want and it is already wired up. The default path just never reaches it.

One gap if you point that at a multi-platform release repo: asset selection filters on the Arch hint as a plain substring and then takes the first archive it sees, with no notion of operating system. It happens to work with jellyfin-ffmpeg because their asset names fold platform and arch into one token (portable_linux64-gpl, portable_macarm64-gpl), so the hint can carry both. A source that names them separately would need an OS-aware selector.

Prior art in the family

Lidarr has the same shape of problem with fpcalc and solved it by not downloading at runtime at all. The binaries are committed per RID under src/Runtimes/<rid>/fpcalc, eleven of them, and src/Runtimes/README records where they came from: a pinned release tag, chromaprint v1.5.1. FpcalcCheck then reports a health warning and disables fingerprinting when the binary is missing or too old, rather than failing quietly.

Two things there are worth stealing. The source is a pinned GitHub release rather than a vendor's "latest" URL, and a health check makes the degraded state visible.

Options

A. Pin and checksum what you have. Smallest change: replace the rolling URLs with versioned ones and return real hashes from GetChecksum(). Fixes reproducibility and drops the unverified download. Does not fix macOS, since evermeet ships no ffprobe in the ffmpeg archive (that is #786, separately).

B. Flip the default to a durable GitHub-release source. Reuses the github: path that already verifies and aborts. Needs the OS-aware asset selection above. jellyfin-ffmpeg reproduces the golden on all five cells I test, including macOS, so on the evidence I have it is a behaviour-safe swap.

C. Ship it per RID, like Lidarr. No network at first boot and nothing to verify at runtime, at the cost of repo or release size.

A is the cheap one. I would pick B. C is the most robust of the three and much the largest change.

There is a smaller point buried in B. evermeet publishes x86_64 only, so Macs run ffprobe under Rosetta today, while jellyfin ships a native portable_macarm64-gpl build alongside portable_mac64-gpl. Listenarr has no osx-arm64 RID, so that is not actionable on its own, but it is a reason to prefer a source that has the build waiting.

Offer

The harness is at https://github.com/m4bard/listenarr-testdata and the matrix is reproducible. If you tell me which option you want I will open the PR. If none of them, that is a useful answer too and I will leave the area alone.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions