feat(setup): fetch ffmpeg on first run instead of bundling it
Ship a smaller installer that no longer carries ffmpeg.exe/ffprobe.exe (the bulk of its size). On first run they are downloaded from a pinned upstream archive (gyan 8.1.2), verified against a pinned SHA-256, and unpacked with the OS tar.exe into the managed userData/bin dir -- the same place as the self-updating yt-dlp, so they survive app updates and portable re-extraction. A hard onboarding gate blocks the app until they are present, with a "locate existing ffmpeg" folder-picker fallback for offline machines and a Repair action in Settings > About. The updater's streaming/checksum/redirect/idle-timeout download loop is extracted to lib/verifiedDownload.streamVerifiedFile and shared by both the app-installer download and the ffmpeg fetch; the updater's public behaviour and error strings are unchanged (its boundary tests still pass). No new npm dependency: extraction uses the System32 bsdtar resolved by absolute path (audit F3). Note: this commit also carries pre-existing, in-progress aria2c/network and updater-token work that was already uncommitted in the working tree and is entangled with the above in shared files (updater.ts, ipc.ts, preload/index.ts, mockApi.ts, shared/ipc.ts, plus the settings/network files and aria2c.exe). It was not cleanly separable by path, so it is included here rather than split out. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+41
-13
@@ -42,15 +42,20 @@ export function getAppIconImage(): NativeImage {
|
||||
}
|
||||
|
||||
/**
|
||||
* AeroFetch keeps its OWN writable copy of yt-dlp.exe under userData, separate
|
||||
* from the read-only bundled seed in resources/bin. The managed copy is what
|
||||
* actually gets spawned and self-updated (`--update-to`), so an app reinstall or
|
||||
* portable re-extraction — which only ever replace the bundled seed — can never
|
||||
* roll a freshly-updated yt-dlp back to a stale version. See ensureManagedYtdlp
|
||||
* in ytdlp.ts, which seeds this from getBundledYtdlpPath() on first run.
|
||||
* AeroFetch keeps its OWN writable copies of the tool binaries under userData/bin,
|
||||
* separate from anything in the read-only bundle (resources/bin). This managed dir is
|
||||
* what actually gets spawned, and it survives an app reinstall or portable re-extraction
|
||||
* (which only ever touch the bundle) — so a self-updated yt-dlp is never rolled back to
|
||||
* a stale bundled version, and the first-run-downloaded ffmpeg/ffprobe (which the
|
||||
* installer no longer ships at all) persist across updates.
|
||||
*
|
||||
* ffmpeg/ffprobe/aria2c stay in getBinDir(): they're not self-updating and
|
||||
* yt-dlp finds them via `--ffmpeg-location <binDir>`.
|
||||
* - yt-dlp.exe — seeded from the bundled copy, then self-updates (`--update-to`).
|
||||
* See ensureManagedYtdlp in ytdlp.ts.
|
||||
* - ffmpeg.exe / ffprobe.exe — fetched from upstream on first run (ffmpegSetup.ts);
|
||||
* in dev, ensureManagedFfmpeg seeds them from resources/bin so `npm run dev` needs
|
||||
* no download. yt-dlp finds them here via `--ffmpeg-location <getFfmpegDir()>`.
|
||||
*
|
||||
* aria2c stays in getBinDir(): it's optional, bundled, and passed by absolute path.
|
||||
*/
|
||||
function getManagedBinDir(): string {
|
||||
return join(app.getPath('userData'), 'bin')
|
||||
@@ -66,17 +71,40 @@ export function getYtdlpPath(): string {
|
||||
return join(getManagedBinDir(), 'yt-dlp.exe')
|
||||
}
|
||||
|
||||
/**
|
||||
* The managed ffmpeg.exe under userData/bin. The installer no longer bundles ffmpeg
|
||||
* (it's the bulk of the download); ffmpegSetup.ts fetches it on first run, and in dev
|
||||
* ensureManagedFfmpeg seeds it from getBundledFfmpegPath(). yt-dlp locates it (and
|
||||
* ffprobe) via `--ffmpeg-location getFfmpegDir()`.
|
||||
*/
|
||||
export function getFfmpegPath(): string {
|
||||
return join(getBinDir(), 'ffmpeg.exe')
|
||||
return join(getManagedBinDir(), 'ffmpeg.exe')
|
||||
}
|
||||
|
||||
/**
|
||||
* yt-dlp finds ffprobe via --ffmpeg-location (the bin dir), so the app never
|
||||
* spawns it directly — but it must be present, or duration-aware post-processing
|
||||
* (SponsorBlock-remove, --force-keyframes-at-cuts, --split-chapters) fails. This
|
||||
* accessor exists so startDownload can assert its presence up front.
|
||||
* The managed ffprobe.exe under userData/bin. yt-dlp finds it via --ffmpeg-location
|
||||
* (so the app never spawns it directly), but it must be present, or duration-aware
|
||||
* post-processing (SponsorBlock-remove, --force-keyframes-at-cuts, --split-chapters)
|
||||
* fails. startDownload / the setup gate assert its presence up front.
|
||||
*/
|
||||
export function getFfprobePath(): string {
|
||||
return join(getManagedBinDir(), 'ffprobe.exe')
|
||||
}
|
||||
|
||||
/** Directory holding the managed ffmpeg/ffprobe, handed to yt-dlp as --ffmpeg-location. */
|
||||
export function getFfmpegDir(): string {
|
||||
return getManagedBinDir()
|
||||
}
|
||||
|
||||
/**
|
||||
* Dev-only read seeds for ffmpeg/ffprobe in resources/bin, copied into the managed dir
|
||||
* by ensureManagedFfmpeg when present. Absent in a shipped installer (excluded from the
|
||||
* bundle in electron-builder.yml), where first-run download provides them instead.
|
||||
*/
|
||||
export function getBundledFfmpegPath(): string {
|
||||
return join(getBinDir(), 'ffmpeg.exe')
|
||||
}
|
||||
export function getBundledFfprobePath(): string {
|
||||
return join(getBinDir(), 'ffprobe.exe')
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user