Skip to content

fix(linux): give the capture helper's ffmpeg libraries their own directory name - #300

Open
EtienneLescot wants to merge 1 commit into
mainfrom
fix/helper-ffmpeg-path-collision
Open

fix(linux): give the capture helper's ffmpeg libraries their own directory name#300
EtienneLescot wants to merge 1 commit into
mainfrom
fix/helper-ffmpeg-path-collision

Conversation

@EtienneLescot

@EtienneLescotEtienneLescot commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #298, which added a beforePack guard that detects this collision. This removes it.

Three artifacts claimed electron/native/bin/linux-x64/ffmpeg, not two:

ClaimantWants it to be
scripts/fetch-ffmpeg.mjsa file — the static ffmpeg executable (PINNED["linux-x64"].exe)
scripts/build-linux-pipewire-helper.mjsa directory — the helper's unrenamed libav*/libsw*.so, the name its $ORIGIN/ffmpeg RUNPATH is compiled against
electron/media/audioPeaks.tsa file — it spawns it to decode waveform peaks

Whoever ran last won, and every loser failed obliquely:

Error: EEXIST: file already exists, mkdir '.../electron/native/bin/linux-x64/ffmpeg'
at stageFfmpeg (scripts/build-linux-pipewire-helper.mjs:127:5)
spawnSync .../electron/native/bin/linux-x64/ffmpeg EACCES

Neither message names the real problem. CI never sees it because build:linux runs only fetch:ffmpeg:sdk on a fresh checkout — and that flag exists, per its own comment, to dodge this very collision.

Which side moved, and why

The helper's directory, to helper-ffmpeg/. A bare ffmpeg means the executable to every other reader of this tree — Windows has ffmpeg.exe at the same level, and audioPeaks.ts already carries an isExecutableFile() guard and a test written after this directory fooled existsSync. A directory of shared objects under that name is a lie about its contents.

Changed: the RUNPATH in build.rs, stageFfmpeg's target, the payload guard in before-pack.cjs (its file-instead-of-directory branch loses the explanation of a collision that can no longer happen), and the comments in electron-builder.json5, fetch-ffmpeg.mjs and both audioPeaks files.

linux.extraResources' filter: ["linux-*/**"] already matches at any depth, so packaging needs no change — only its comment.

Two things found while checking the consumers

The --sdk-only rationale is now partly obsolete. It justified itself with this collision. What survives is a size argument: the static CLI is ~110 MB and linux.extraResources has no exclusion for it (unlike Windows' "!win32-*/ffmpeg.exe"). Behaviour is unchanged here — whether the CLI should ship on Linux is a separate call with a real price tag.

assertLgpl's note says "nothing in the app spawns ffmpeg any more". That is false.audioPeaks.ts:301 resolves and spawns it, ~6× faster than the renderer's own decode. It degrades gracefully — getAudioPeaks returns null and the renderer falls back to its browser pipelines — so on Linux, where the CLI does not ship, that fallback is always the one taken. Slower on first decode, cached afterwards, not broken. Comment corrected; the shipping decision left alone.

Related issue

No issue — follow-up to #298.

Type of change

  • Bug fix

Release impact

  • No release note needed

Desktop impact

  • Linux
  • Installer / packaging

Screenshots / video

n/a

Testing

Ubuntu 24.04 x86_64. Both orders now work from a clean electron/native/bin/linux-x64/ — each of them used to be a hard failure:

OrderBeforeAfter
fetch:ffmpegbuild:native:linuxEEXIST: mkdir .../ffmpegboth succeed, helper probe {"event":"ready","pipewireVersion":"1.0.5"}
build:native:linuxfetch:ffmpegspawnSync … EACCESboth succeed, LGPL verified

Resulting layout, with the two artifacts side by side:

ffmpeg ELF 64-bit LSB pie executable
helper-ffmpeg/ libavcodec.so.62 libavformat.so.62 libavutil.so.60
libswresample.so.6 libswscale.so.9
openscreen-pipewire-helper
compositor_view.node (+ the 5 osff_*-renamed .so, unchanged)

The RUNPATH actually moved and actually resolves — not just the staging path:

$ readelf -d openscreen-pipewire-helper | grep -i rpath
Library rpath: [$ORIGIN/helper-ffmpeg:/…/crates/thirdparty/ffmpeg-linux64-lgpl-shared/lib]
$ ldd openscreen-pipewire-helper | grep libav
libavcodec.so.62 => …/electron/native/bin/linux-x64/helper-ffmpeg/libavcodec.so.62
… 0 "not found"

npm run build:native:compositor:linux still passes its own check (Verified: no unprefixed ffmpeg imports remain in the addon) — the addon reads $ORIGIN, so helper-ffmpeg/ is invisible to it.

node scripts/before-pack.cjs on the renamed layout: exit 0 when complete, and still exit 1 with the right message when helper-ffmpeg/ is emptied.

npm run test: 1678 passed, 140 files, 0 failed. npx tsc --noEmit clean, biome check clean. audioPeaks.test.ts is 13/13 — that file previously failed in a worktree with the EACCES above, because the path it resolves held the helper's libraries instead of a binary.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved Linux audio and PipeWire helper packaging by separating FFmpeg shared libraries from the standalone FFmpeg executable.
    • Prevented path collisions that could interfere with executable detection and runtime library loading.
    • Updated validation and packaging guidance to reflect the new helper library location.
  • Documentation

    • Clarified Linux FFmpeg staging, runtime search paths, and waveform decoding fallback behavior.

…ctory name
Three artifacts claimed electron/native/bin/linux-x64/ffmpeg:
- fetch-ffmpeg.mjs vendors the static ffmpeg EXECUTABLE there
(PINNED["linux-x64"].exe)
- build-linux-pipewire-helper.mjs mkdir'd it as a DIRECTORY for the helper's
unrenamed libav*/libsw*.so, the name its $ORIGIN/ffmpeg RUNPATH is
compiled against
- audioPeaks.ts resolves that same path expecting a binary to spawn
Whoever ran last won, and every loser failed obliquely: `EEXIST: mkdir
.../linux-x64/ffmpeg` from the helper build, `spawn … EACCES` from
audioPeaks when it found a directory where a binary belonged. CI never saw
it because build:linux runs only fetch:ffmpeg:sdk on a fresh checkout — a
flag that exists, per its own comment, to dodge this very collision.
The helper's directory moves to helper-ffmpeg/ rather than the executable
moving, because a bare `ffmpeg` means the executable to every other reader
of this tree (win32 has ffmpeg.exe at the same level), and a directory of
shared objects under that name is a lie about its contents.
RUNPATH follows in build.rs. The staleness/payload guard in before-pack.cjs
follows too, and its file-instead-of-directory branch loses the explanation
of a collision that can no longer happen.
Also corrects the --sdk-only comment, which justified itself with that
collision, and the claim in assertLgpl's note that nothing in the app
spawns the ffmpeg CLI any more — audioPeaks does, falling back to the
browser decode paths when it is absent, which on Linux is always. Whether
the CLI should ship on Linux at all is a separate question (~110 MB) and
this change does not touch it.
@coderabbitai

coderabbitaiBot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e75d6d6-052e-4761-9784-ced0386cb28a

📥 Commits

Reviewing files that changed from the base of the PR and between df4e00a and 2cc1c11.

📒 Files selected for processing (7)
  • electron-builder.json5
  • electron/media/audioPeaks.test.ts
  • electron/media/audioPeaks.ts
  • electron/native/pipewire-capture/build.rs
  • scripts/before-pack.cjs
  • scripts/build-linux-pipewire-helper.mjs
  • scripts/fetch-ffmpeg.mjs

📝 Walkthrough

Walkthrough

Linux FFmpeg shared libraries now use helper-ffmpeg/, separate from the standalone ffmpeg executable. Staging, PipeWire runtime lookup, packaging validation, and candidate-resolution documentation now use the updated layout.

Changes

Linux FFmpeg packaging path

Layer / File(s)Summary
FFmpeg helper staging
scripts/fetch-ffmpeg.mjs, scripts/build-linux-pipewire-helper.mjs
Linux helper libraries are staged under helper-ffmpeg/. Documentation describes the separate standalone executable and updated RUNPATH.
Helper runtime lookup
electron/native/pipewire-capture/build.rs
The PipeWire helper searches for runtime libraries under $ORIGIN/helper-ffmpeg.
Packaging validation and candidate handling
scripts/before-pack.cjs, electron-builder.json5, electron/media/audioPeaks.ts, electron/media/audioPeaks.test.ts
Packaging checks and FFmpeg candidate documentation distinguish the helper library directory from the standalone executable. The directory-candidate test documentation reflects the resolved collision.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description check✅ PassedThe description includes all required sections and provides clear scope, impact, related issue context, and detailed testing results.
Title check✅ PassedThe title clearly and concisely describes the primary Linux FFmpeg library directory change.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/helper-ffmpeg-path-collision

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@EtienneLescot