chore(release): refresh cross-platform FFmpeg payloads

This commit is contained in:
NimBold
2026-07-15 00:11:04 +03:30
parent e8487ee71b
commit 0eee47b97f
5 changed files with 61 additions and 32 deletions
+8 -7
View File
@@ -11,7 +11,7 @@ Targets:
## Distribution policy
Firelink does not use an Apple Developer account. macOS releases are unsigned and not notarized. Users must explicitly approve the downloaded app through Finder or macOS Privacy & Security. Release copy must never describe these builds as signed, notarized, or Gatekeeper-approved.
Firelink does not use an Apple Developer account. macOS releases are ad-hoc signed but not notarized or Gatekeeper-approved. Users may still need to explicitly approve the downloaded app through Finder or macOS Privacy & Security. Release copy must not describe these builds as Developer ID signed, notarized, or Gatekeeper-approved.
Windows releases are currently unsigned. SmartScreen may warn until code signing is added.
@@ -58,10 +58,12 @@ node scripts/verify-binaries.js --search-root "$APP" --target aarch64-apple-darw
node scripts/smoke-packaged-app.js --executable "$APP/Contents/MacOS/firelink"
```
GitHub release publication is intentionally manual. Tag pushes build and upload
artifacts, but the `publish` job only runs from a `workflow_dispatch` on a `v*`
tag when both release-certification inputs are checked after Windows, Linux,
and macOS clean-machine QA.
GitHub release publication follows `.github/workflows/release.yml`. A `v*` tag
push builds, verifies, and publishes the GitHub release after the platform jobs
pass. A `workflow_dispatch` on a `v*` tag also publishes when its
`publish_release` input is enabled. The current workflow has no separate
release-certification inputs; clean-machine QA remains a release-owner gate
before pushing the tag.
## Automated release builds
@@ -74,7 +76,6 @@ git push origin v<version>
GitHub Actions builds all targets on native runners, verifies engines inside
final package contents, performs packaged launch smoke where supported, and
uploads artifacts. Publish the GitHub Release with a manual `workflow_dispatch`
run on the same tag after clean-machine QA is complete.
publishes the GitHub Release after the build matrix passes.
No target may silently skip missing engines, failed extraction, checksum mismatch, or missing package output.
+4 -4
View File
@@ -14,8 +14,8 @@
},
"ffmpeg": {
"version": "8.1.2-22-g94138f6973",
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-07-06-14-19/ffmpeg-n8.1.2-22-g94138f6973-win64-gpl-8.1.zip",
"sha256": "a758e2836a8f33c5f21fc44270cb00392acc6d0085dd0ba14fe14ae75935813d"
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-07-14-13-19/ffmpeg-n8.1.2-22-g94138f6973-win64-gpl-8.1.zip",
"sha256": "fb627ebbf9b16a3142fa48b8acd27cd9196d57aee1f719e1233416e95da7e877"
},
"aria2c": {
"version": "1.37.0",
@@ -36,8 +36,8 @@
},
"ffmpeg": {
"version": "8.1.2-22-g94138f6973",
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-07-06-14-19/ffmpeg-n8.1.2-22-g94138f6973-linux64-gpl-8.1.tar.xz",
"sha256": "df99ffb3803ee56dc68954f43f950ea9f33685a3595a5da8a3e73ef4bef37e3c"
"url": "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-07-14-13-19/ffmpeg-n8.1.2-22-g94138f6973-linux64-gpl-8.1.tar.xz",
"sha256": "96cf2585a4b2874044320cbc5d4078af57e266f0c86cca9888507c5faab3aee8"
},
"aria2c": {
"version": "1.37.0",
+3 -2
View File
@@ -16,10 +16,11 @@
"sha256": "111b2f5ed760f1e1a2ec06117c4e8094fcde336ba16122dda1c5e7209bf1862d"
},
"ffmpeg": {
"version": "N-125450-gfad2e0bc50",
"version": "N-125610-g312c830916",
"source": "https://ffmpeg.org/",
"build": "GPLv3 build identified by binary as https://www.martin-riedl.de",
"sha256": "be2c39e5c9ef923f60da6cb62f5a209ed98b4da8a732d9f06de4355d5ea99e58"
"url": "https://ffmpeg.martin-riedl.de/download/macos/arm64/1784052810_N-125610-g312c830916/ffmpeg.zip",
"sha256": "f65b4ba782b25e9f4374765d421793c6613f692d8e50b2888079657793619034"
},
"deno": {
"version": "2.9.2",
+46 -19
View File
@@ -83,22 +83,38 @@ async function latestMartinRiedlMacArm64Release() {
async function latestMartinRiedlMacArm64Snapshot() {
const html = await fetchText('https://ffmpeg.martin-riedl.de/');
const snapshotSection = html.split('Download Snapshot Build')[1]?.split('Download Release Build')[0] || '';
const card = snapshotSection.match(/<h3>macOS \(Apple Silicon\/arm64\)<\/h3>[\s\S]*?<\/div>/)?.[0] || '';
const match =
snapshotSection.match(/macOS \(Apple Silicon\/arm64\)[\s\S]*?<b>Release:\s*<\/b>\s*([A-Za-z0-9.-]+)/) ||
snapshotSection.match(/macOS \(Apple Silicon\/arm64\)[\s\S]*?Release:\s*([A-Za-z0-9.-]+)/);
return match?.[1];
card.match(/<b>Release:\s*<\/b>\s*([A-Za-z0-9.-]+)/) ||
card.match(/Release:\s*([A-Za-z0-9.-]+)/);
const url = card.match(/href="([^"]+\/ffmpeg\.zip)"/)?.[1];
return match?.[1]
? { version: match[1], url: url ? new URL(url, 'https://ffmpeg.martin-riedl.de').href : undefined }
: undefined;
}
async function latestBtbnFfmpegN81Build() {
const releases = await fetchJson('https://api.github.com/repos/BtbN/FFmpeg-Builds/releases?per_page=10');
for (const release of releases) {
if (release.tag_name === 'latest') continue;
const versions = (release.assets || [])
.map(asset => asset.name.match(/^ffmpeg-n(8\.1\.\d+-\d+-g[0-9a-f]+)-(?:win64-gpl-8\.1\.zip|linux64-gpl-8\.1\.tar\.xz)$/)?.[1])
const assets = (release.assets || [])
.map(asset => {
const match = asset.name.match(/^ffmpeg-n(8\.1\.\d+-\d+-g[0-9a-f]+)-(win64|linux64)-gpl-8\.1\.(?:zip|tar\.xz)$/);
if (!match) return undefined;
return {
target: match[2] === 'win64' ? 'windows' : 'linux',
version: match[1],
url: asset.browser_download_url,
};
})
.filter(Boolean);
const unique = [...new Set(versions)];
if (unique.length === 1 && versions.length >= 2) {
return unique[0];
const unique = [...new Set(assets.map(asset => asset.version))];
const byTarget = Object.fromEntries(assets.map(asset => [asset.target, asset]));
if (unique.length === 1 && byTarget.windows && byTarget.linux) {
return {
version: unique[0],
urls: { windows: byTarget.windows.url, linux: byTarget.linux.url },
};
}
}
return undefined;
@@ -131,22 +147,26 @@ function packagedEngineVersions(engineLock) {
const rows = [];
for (const [target, targetLock] of Object.entries(engineLock.targets || {})) {
for (const [engine, meta] of Object.entries(targetLock.engines || {})) {
rows.push({ target, engine, version: meta.version });
rows.push({ target, engine, version: meta.version, url: meta.url });
}
}
return rows;
}
function checkRows(rows, latestByEngine, latestByTargetEngine = {}) {
function checkRows(rows, latestByEngine, latestByTargetEngine = {}, latestUrlsByTargetEngine = {}) {
let outdated = 0;
for (const row of rows) {
const latest = latestByTargetEngine[`${row.target}:${row.engine}`] || latestByEngine[row.engine];
if (!latest) continue;
const current = normalizeVersion(row.version);
const wanted = normalizeVersion(latest);
const status = compareVersions(current, wanted) < 0 ? 'outdated' : 'current';
if (status === 'outdated') outdated += 1;
const latestUrl = latestUrlsByTargetEngine[`${row.target}:${row.engine}`];
const versionOutdated = compareVersions(current, wanted) < 0;
const sourceOutdated = Boolean(latestUrl && row.url && row.url !== latestUrl);
const status = versionOutdated ? 'outdated' : sourceOutdated ? 'source-outdated' : 'current';
if (status !== 'current') outdated += 1;
console.log(` ${row.target} ${row.engine}: ${current} -> ${wanted} ${status}`);
if (sourceOutdated) console.log(` source: ${row.url} -> ${latestUrl}`);
}
return outdated;
}
@@ -184,9 +204,14 @@ async function main() {
ffmpeg,
};
const latestByTargetEngine = {
'x86_64-pc-windows-msvc:ffmpeg': btbnFfmpegN81Build || ffmpeg,
'x86_64-unknown-linux-gnu:ffmpeg': btbnFfmpegN81Build || ffmpeg,
'aarch64-apple-darwin:ffmpeg': martinRiedlMacArm64Snapshot || martinRiedlMacArm64Ffmpeg,
'x86_64-pc-windows-msvc:ffmpeg': btbnFfmpegN81Build?.version || ffmpeg,
'x86_64-unknown-linux-gnu:ffmpeg': btbnFfmpegN81Build?.version || ffmpeg,
'aarch64-apple-darwin:ffmpeg': martinRiedlMacArm64Snapshot?.version || martinRiedlMacArm64Ffmpeg,
};
const latestUrlsByTargetEngine = {
'x86_64-pc-windows-msvc:ffmpeg': btbnFfmpegN81Build?.urls.windows,
'x86_64-unknown-linux-gnu:ffmpeg': btbnFfmpegN81Build?.urls.linux,
'aarch64-apple-darwin:ffmpeg': martinRiedlMacArm64Snapshot?.url,
};
console.log('\nlatest engines:');
@@ -194,21 +219,23 @@ async function main() {
console.log(` ${engine}: ${normalizeVersion(version)}`);
}
console.log('\nlatest engine provider builds:');
console.log(` BtbN FFmpeg n8.1 Windows/Linux: ${normalizeVersion(btbnFfmpegN81Build || ffmpeg)}`);
console.log(` Martin Riedl FFmpeg macOS arm64 snapshot: ${normalizeVersion(martinRiedlMacArm64Snapshot || martinRiedlMacArm64Ffmpeg)}`);
console.log(` BtbN FFmpeg n8.1 Windows/Linux: ${normalizeVersion(btbnFfmpegN81Build?.version || ffmpeg)}`);
console.log(` Martin Riedl FFmpeg macOS arm64 snapshot: ${normalizeVersion(martinRiedlMacArm64Snapshot?.version || martinRiedlMacArm64Ffmpeg)}`);
console.log('\nengine source lock:');
outdatedCount += checkRows(
sourceEngineVersions(parseJsonFile('engine-sources.lock.json')),
latestByEngine,
latestByTargetEngine
latestByTargetEngine,
latestUrlsByTargetEngine
);
console.log('\npackaged engine lock:');
outdatedCount += checkRows(
packagedEngineVersions(parseJsonFile('engines.lock.json')),
latestByEngine,
latestByTargetEngine
latestByTargetEngine,
latestUrlsByTargetEngine
);
if (outdatedCount > 0) {
Binary file not shown.