Remove the ffmpeg-static npm runtime dependency and download checksum-verified pinned FFmpeg release assets directly

Hop-State: A_06FNNTFDVDZGB2NFDAARAT0
Hop-Proposal: R_06FNNTEE6NDAD98WA9S7VA0
Hop-Task: T_06FNNS0FK2T1TH9QVHG8MSR
Hop-Attempt: AT_06FNNS0FK2MH777VNTJ1530
This commit is contained in:
2026-07-13 02:58:28 -07:00
committed by Hop
parent 73c4eb1857
commit f34266b704
4 changed files with 63 additions and 254 deletions
+4 -4
View File
@@ -13,11 +13,11 @@ Navidrome is downloaded from its official GitHub release during the desktop buil
## FFmpeg
Resonant desktop builds bundle a platform-specific FFmpeg executable for media transcoding. The binary is supplied by `ffmpeg-static` 5.3.0 and reports FFmpeg 6.0 on Apple Silicon.
Resonant desktop builds bundle a platform-specific FFmpeg executable for media transcoding. The binary and its matching build notices come from the pinned `ffmpeg-static` `b6.1.1` release. The exact FFmpeg build varies by platform and is recorded in the notice bundled with the app (the Apple Silicon binary reports FFmpeg 6.0).
- Project: https://ffmpeg.org/
- Source: https://github.com/FFmpeg/FFmpeg/tree/n6.0
- Binary packaging: https://github.com/eugeneware/ffmpeg-static/tree/5.3.0
- Source project: https://github.com/FFmpeg/FFmpeg
- Binary release: https://github.com/eugeneware/ffmpeg-static/releases/tag/b6.1.1
- License: GNU General Public License v3.0 or later
The exact upstream binary build information and license copied by `ffmpeg-static` are included in every packaged Resonant app.
The preparation script downloads and SHA-256 verifies the binary, license, and build information published with that release. The matching notices are included in every packaged Resonant app.
-226
View File
@@ -25,7 +25,6 @@
"@types/spark-md5": "^3.0.5",
"@vitejs/plugin-react": "latest",
"esbuild": "latest",
"ffmpeg-static": "5.3.0",
"jsdom": "latest",
"typescript": "latest",
"vite": "^8.1.4",
@@ -280,22 +279,6 @@
"node": ">=20.19.0"
}
},
"node_modules/@derhuerst/http-basic": {
"version": "8.2.4",
"resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz",
"integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==",
"dev": true,
"license": "MIT",
"dependencies": {
"caseless": "^0.12.0",
"concat-stream": "^2.0.0",
"http-response-object": "^3.0.1",
"parse-cache-control": "^1.0.1"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@emnapi/core": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
@@ -1969,19 +1952,6 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
@@ -2037,20 +2007,6 @@
"require-from-string": "^2.0.2"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true,
"license": "MIT"
},
"node_modules/caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/chai": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
@@ -2061,22 +2017,6 @@
"node": ">=18"
}
},
"node_modules/concat-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
"dev": true,
"engines": [
"node >= 6.0"
],
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.0.2",
"typedarray": "^0.0.6"
}
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -2126,24 +2066,6 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/decimal.js": {
"version": "10.6.0",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
@@ -2192,16 +2114,6 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/env-paths": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/es-module-lexer": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz",
@@ -2289,23 +2201,6 @@
}
}
},
"node_modules/ffmpeg-static": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.3.0.tgz",
"integrity": "sha512-H+K6sW6TiIX6VGend0KQwthe+kaceeH/luE8dIZyOP35ik7ahYojDuqlTV1bOrtEwl01sy2HFNGQfi5IDJvotg==",
"dev": true,
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@derhuerst/http-basic": "^8.2.0",
"env-paths": "^2.2.0",
"https-proxy-agent": "^5.0.0",
"progress": "^2.0.3"
},
"engines": {
"node": ">=16"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2334,37 +2229,6 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
}
},
"node_modules/http-response-object": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz",
"integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "^10.0.3"
}
},
"node_modules/http-response-object/node_modules/@types/node": {
"version": "10.17.60",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
"integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==",
"dev": true,
"license": "MIT"
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/indent-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
@@ -2375,13 +2239,6 @@
"node": ">=8"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true,
"license": "ISC"
},
"node_modules/is-potential-custom-element-name": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -2756,13 +2613,6 @@
"node": ">=4"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
@@ -2796,12 +2646,6 @@
"node": ">=12.20.0"
}
},
"node_modules/parse-cache-control": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz",
"integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==",
"dev": true
},
"node_modules/parse5": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
@@ -2887,16 +2731,6 @@
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
}
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -2936,21 +2770,6 @@
"license": "MIT",
"peer": true
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dev": true,
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/redent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
@@ -3009,27 +2828,6 @@
"@rolldown/binding-win32-x64-msvc": "1.1.5"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
@@ -3086,16 +2884,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -3214,13 +3002,6 @@
"license": "0BSD",
"optional": true
},
"node_modules/typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
"dev": true,
"license": "MIT"
},
"node_modules/typescript": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz",
@@ -3273,13 +3054,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT"
},
"node_modules/vite": {
"version": "8.1.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz",
-1
View File
@@ -32,7 +32,6 @@
"@types/spark-md5": "^3.0.5",
"@vitejs/plugin-react": "latest",
"esbuild": "latest",
"ffmpeg-static": "5.3.0",
"jsdom": "latest",
"typescript": "latest",
"vite": "^8.1.4",
+59 -23
View File
@@ -4,10 +4,11 @@ import { chmod, copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from "node:f
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import ffmpegPath from "ffmpeg-static";
const VERSION = "0.61.2";
const RELEASE_API = `https://api.github.com/repos/navidrome/navidrome/releases/tags/v${VERSION}`;
const FFMPEG_RELEASE = "b6.1.1";
const FFMPEG_RELEASE_API = `https://api.github.com/repos/eugeneware/ffmpeg-static/releases/tags/${FFMPEG_RELEASE}`;
const targetTriple = execFileSync("rustc", ["--print", "host-tuple"], { encoding: "utf8" }).trim();
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -19,9 +20,18 @@ const archives = {
"x86_64-pc-windows-msvc": `navidrome_${VERSION}_windows_amd64.zip`,
};
const ffmpegPlatforms = {
"aarch64-apple-darwin": "darwin-arm64",
"x86_64-apple-darwin": "darwin-x64",
"aarch64-unknown-linux-gnu": "linux-arm64",
"x86_64-unknown-linux-gnu": "linux-x64",
"x86_64-pc-windows-msvc": "win32-x64",
};
const archiveName = archives[targetTriple];
if (!archiveName) {
throw new Error(`Navidrome ${VERSION} is not configured for Rust target ${targetTriple}.`);
const ffmpegPlatform = ffmpegPlatforms[targetTriple];
if (!archiveName || !ffmpegPlatform) {
throw new Error(`Resonant's media sidecars are not configured for Rust target ${targetTriple}.`);
}
const extension = targetTriple.includes("windows") ? ".exe" : "";
@@ -38,6 +48,8 @@ await mkdir(resourceDirectory, { recursive: true });
let binaryReady = false;
let licenseReady = false;
let ffmpegReady = false;
let ffmpegLicenseReady = false;
let ffmpegReadmeReady = false;
try {
await readFile(outputPath);
binaryReady = true;
@@ -54,18 +66,52 @@ try {
await readFile(ffmpegOutputPath);
ffmpegReady = true;
} catch {
// Copy the pinned npm-provided binary below when missing.
// Download the pinned release asset below when missing.
}
if (!ffmpegPath) throw new Error(`ffmpeg-static does not provide a binary for ${process.platform}-${process.arch}.`);
if (!ffmpegReady) {
await copyFile(ffmpegPath, ffmpegOutputPath);
if (!extension) await chmod(ffmpegOutputPath, 0o755);
try {
await readFile(ffmpegLicensePath);
ffmpegLicenseReady = true;
} catch {
// Download the matching binary license below when missing.
}
try {
await readFile(ffmpegReadmePath);
ffmpegReadmeReady = true;
} catch {
// Download the matching binary build information below when missing.
}
await copyFile(join(dirname(ffmpegPath), "ffmpeg.LICENSE"), ffmpegLicensePath);
await copyFile(join(dirname(ffmpegPath), "ffmpeg.README"), ffmpegReadmePath);
const headers = { Accept: "application/vnd.github+json", "User-Agent": "Resonant-sidecar-preparer" };
const verifiedReleaseAsset = async (release, assetName) => {
const asset = release.assets.find((candidate) => candidate.name === assetName);
if (!asset) throw new Error(`The pinned release does not contain ${assetName}.`);
const response = await fetch(asset.browser_download_url, { headers });
if (!response.ok) throw new Error(`Could not download ${assetName} (${response.status}).`);
const bytes = Buffer.from(await response.arrayBuffer());
const actualDigest = createHash("sha256").update(bytes).digest("hex");
const expectedDigest = asset.digest?.replace(/^sha256:/, "");
if (!expectedDigest || actualDigest !== expectedDigest) {
throw new Error(`Checksum verification failed for ${assetName}.`);
}
return bytes;
};
if (!ffmpegReady || !ffmpegLicenseReady || !ffmpegReadmeReady) {
const releaseResponse = await fetch(FFMPEG_RELEASE_API, { headers });
if (!releaseResponse.ok) throw new Error(`Could not read FFmpeg ${FFMPEG_RELEASE} release metadata (${releaseResponse.status}).`);
const release = await releaseResponse.json();
if (!ffmpegReady) {
await writeFile(ffmpegOutputPath, await verifiedReleaseAsset(release, `ffmpeg-${ffmpegPlatform}`));
if (!extension) await chmod(ffmpegOutputPath, 0o755);
}
if (!ffmpegLicenseReady) {
await writeFile(ffmpegLicensePath, await verifiedReleaseAsset(release, `${ffmpegPlatform}.LICENSE`));
}
if (!ffmpegReadmeReady) {
await writeFile(ffmpegReadmePath, await verifiedReleaseAsset(release, `${ffmpegPlatform}.README`));
}
}
if (!licenseReady) {
const licenseResponse = await fetch(`https://raw.githubusercontent.com/navidrome/navidrome/v${VERSION}/LICENSE`, { headers });
if (!licenseResponse.ok) throw new Error(`Could not download the Navidrome license (${licenseResponse.status}).`);
@@ -79,17 +125,7 @@ if (binaryReady) {
const releaseResponse = await fetch(RELEASE_API, { headers });
if (!releaseResponse.ok) throw new Error(`Could not read the Navidrome release metadata (${releaseResponse.status}).`);
const release = await releaseResponse.json();
const asset = release.assets.find((candidate) => candidate.name === archiveName);
if (!asset) throw new Error(`The official Navidrome release does not contain ${archiveName}.`);
const archiveResponse = await fetch(asset.browser_download_url, { headers });
if (!archiveResponse.ok) throw new Error(`Could not download ${archiveName} (${archiveResponse.status}).`);
const archive = Buffer.from(await archiveResponse.arrayBuffer());
const actualDigest = createHash("sha256").update(archive).digest("hex");
const expectedDigest = asset.digest?.replace(/^sha256:/, "");
if (!expectedDigest || actualDigest !== expectedDigest) {
throw new Error(`Checksum verification failed for ${archiveName}.`);
}
const archive = await verifiedReleaseAsset(release, archiveName);
const workingDirectory = await mkdtemp(join(tmpdir(), "resonant-navidrome-"));
try {
@@ -103,4 +139,4 @@ try {
await rm(workingDirectory, { recursive: true, force: true });
}
process.stdout.write(`Downloaded and verified Navidrome ${VERSION}; FFmpeg is ready for ${targetTriple}.\n`);
process.stdout.write(`Downloaded and verified Navidrome ${VERSION}; FFmpeg ${FFMPEG_RELEASE} is ready for ${targetTriple}.\n`);