diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 17f6732..923d4b6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,15 +15,23 @@ jobs: - platform: macos-latest args: '--target aarch64-apple-darwin' label: macOS (Apple Silicon) + rustTarget: aarch64-apple-darwin + ffmpegAsset: ffmpeg-darwin-arm64 - platform: macos-latest args: '--target x86_64-apple-darwin' label: macOS (Intel) + rustTarget: x86_64-apple-darwin + ffmpegAsset: ffmpeg-darwin-x64 - platform: windows-latest args: '' label: Windows + rustTarget: x86_64-pc-windows-msvc + ffmpegAsset: ffmpeg-win32-x64 - platform: ubuntu-24.04 args: '' label: Linux + rustTarget: x86_64-unknown-linux-gnu + ffmpegAsset: ffmpeg-linux-x64 name: Build — ${{ matrix.label }} runs-on: ${{ matrix.platform }} @@ -74,6 +82,22 @@ jobs: - name: Run tests run: npm test -- --run + # Download the FFmpeg binary for this target and place it where Tauri's + # externalBin expects it (bin/ffmpeg-[.exe]). Bundled into the app + # so recordings need no system ffmpeg — and never committed to git. + - name: Download FFmpeg sidecar + shell: bash + run: | + set -euo pipefail + mkdir -p src-tauri/bin + EXT="" + [ "${{ runner.os }}" = "Windows" ] && EXT=".exe" + DEST="src-tauri/bin/ffmpeg-${{ matrix.rustTarget }}${EXT}" + curl -fL --retry 3 -o "$DEST" \ + "https://github.com/eugeneware/ffmpeg-static/releases/download/b6.0/${{ matrix.ffmpegAsset }}" + chmod +x "$DEST" || true + ls -la src-tauri/bin + - name: Build Tauri app uses: tauri-apps/tauri-action@v0 env: diff --git a/RELEASING-DESKTOP.md b/RELEASING-DESKTOP.md index 2852bc7..a568112 100644 --- a/RELEASING-DESKTOP.md +++ b/RELEASING-DESKTOP.md @@ -56,7 +56,7 @@ platform artifact. Store it as a GitHub Actions secret: | `TAURI_SIGNING_PRIVATE_KEY` | Full contents of `~/.tauri/gwt-updater.key`| | `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Empty (the key was generated without one)| -4. The `release.yml` workflow already reads these via: +2. The `release.yml` workflow already reads these via: ```yaml env: @@ -99,26 +99,36 @@ missing, the signing secrets weren't set. ## Bundling FFmpeg -Screen recording with audio requires a platform-specific FFmpeg binary bundled -inside the app. Place each binary at `src-tauri/bin/ffmpeg-`: +Screen recording with audio bundles a static FFmpeg binary inside the app (via +Tauri `externalBin: ["bin/ffmpeg"]`), so recordings need **no system ffmpeg**. The +binaries are **not committed to git** (they're ~45 MB each) — they're downloaded +at build time. -| Platform | Triple | Extension | -|-------------------|----------------------------------|-----------| -| macOS Apple Silicon | `aarch64-apple-darwin` | (none) | -| macOS Intel | `x86_64-apple-darwin` | (none) | -| Windows x64 | `x86_64-pc-windows-msvc` | `.exe` | -| Linux x64 | `x86_64-unknown-linux-gnu` | (none) | +**In CI:** `release.yml` downloads the correct binary for each target (from +[`eugeneware/ffmpeg-static`](https://github.com/eugeneware/ffmpeg-static)) into +`src-tauri/bin/ffmpeg-` before `tauri build`. Nothing to do. -Download static FFmpeg builds from: -- macOS/Linux: or -- Windows: (essentials build) - -Or run the helper script: +**Local release build:** run this once (downloads the binary for your host): ```bash npm run download:ffmpeg ``` +It writes `src-tauri/bin/ffmpeg-` (`.exe` on Windows) for your platform: + +| Platform | Triple | Extension | +|---------------------|----------------------------|-----------| +| macOS Apple Silicon | `aarch64-apple-darwin` | (none) | +| macOS Intel | `x86_64-apple-darwin` | (none) | +| Windows x64 | `x86_64-pc-windows-msvc` | `.exe` | +| Linux x64 | `x86_64-unknown-linux-gnu` | (none) | + +> `tauri dev` doesn't need the sidecar (it falls back to system ffmpeg); only +> `tauri build` (bundling) requires it. +> +> **License note:** `ffmpeg-static` ships a GPL build of FFmpeg. Bundling it makes +> the distributed app GPL-encumbered — fine while GoodWebTools stays open source. + Verify everything is in place before building: ```bash diff --git a/scripts/download-ffmpeg-binaries.mjs b/scripts/download-ffmpeg-binaries.mjs index 170a059..f88cdb7 100644 --- a/scripts/download-ffmpeg-binaries.mjs +++ b/scripts/download-ffmpeg-binaries.mjs @@ -2,95 +2,60 @@ /** * download-ffmpeg-binaries.mjs * - * Downloads pre-built static FFmpeg binaries into src-tauri/bin/ so they - * can be bundled with the Tauri app as sidecar resources. + * Downloads a static FFmpeg binary for the CURRENT host into src-tauri/bin/ so + * `tauri build` can bundle it as a sidecar (externalBin "bin/ffmpeg"). Needed for + * local release builds; CI does the same per target in release.yml. * - * Sources (static builds, no runtime deps): - * macOS → evermeet.cx (aarch64 + x86_64) - * Windows → gyan.dev (x86_64) - * Linux → johnvansickle (x86_64) + * Source: eugeneware/ffmpeg-static (raw single-file binaries, no extraction). * - * Run: node scripts/download-ffmpeg-binaries.mjs - * Or: npm run download:ffmpeg + * Run: npm run download:ffmpeg */ -import { createWriteStream, existsSync, mkdirSync } from 'node:fs'; -import { pipeline } from 'node:stream/promises'; +import { existsSync, mkdirSync, writeFileSync, chmodSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { createGunzip } from 'node:zlib'; const __dirname = dirname(fileURLToPath(import.meta.url)); const BIN_DIR = join(__dirname, '..', 'src-tauri', 'bin'); - -const BINARIES = [ - { - platform: 'darwin-aarch64', - url: 'https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip', - filename: 'ffmpeg-aarch64-apple-darwin', - }, - { - platform: 'darwin-x86_64', - url: 'https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip', - filename: 'ffmpeg-x86_64-apple-darwin', - }, - { - platform: 'windows-x86_64', - url: 'https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip', - filename: 'ffmpeg-x86_64-pc-windows-msvc.exe', - }, - { - platform: 'linux-x86_64', - url: 'https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz', - filename: 'ffmpeg-x86_64-unknown-linux-gnu', - }, -]; - -if (!existsSync(BIN_DIR)) { - mkdirSync(BIN_DIR, { recursive: true }); +const FFMPEG_STATIC_TAG = 'b6.0'; + +// host platform/arch → { rust target triple, ffmpeg-static asset name } +const TARGETS = { + 'darwin-arm64': { triple: 'aarch64-apple-darwin', asset: 'ffmpeg-darwin-arm64' }, + 'darwin-x64': { triple: 'x86_64-apple-darwin', asset: 'ffmpeg-darwin-x64' }, + 'win32-x64': { triple: 'x86_64-pc-windows-msvc', asset: 'ffmpeg-win32-x64' }, + 'linux-x64': { triple: 'x86_64-unknown-linux-gnu', asset: 'ffmpeg-linux-x64' }, +}; + +const key = `${process.platform}-${process.arch}`; +const target = TARGETS[key]; +if (!target) { + console.error(`No FFmpeg binary configured for ${key}.`); + console.error(`Supported: ${Object.keys(TARGETS).join(', ')}`); + process.exit(1); } -const host = process.platform; -const arch = process.arch; +const ext = process.platform === 'win32' ? '.exe' : ''; +const outPath = join(BIN_DIR, `ffmpeg-${target.triple}${ext}`); -// Determine which binary to download for the current build machine -let target; -if (host === 'darwin') { - target = arch === 'arm64' ? 'darwin-aarch64' : 'darwin-x86_64'; -} else if (host === 'win32') { - target = 'windows-x86_64'; -} else { - target = 'linux-x86_64'; +if (existsSync(outPath)) { + console.log(`✓ Already present: ${outPath}`); + process.exit(0); } -const entry = BINARIES.find(b => b.platform === target); -if (!entry) { - console.error(`No FFmpeg binary configured for platform: ${host}/${arch}`); - process.exit(1); -} +const url = `https://github.com/eugeneware/ffmpeg-static/releases/download/${FFMPEG_STATIC_TAG}/${target.asset}`; -const outPath = join(BIN_DIR, entry.filename); +mkdirSync(BIN_DIR, { recursive: true }); +console.log(`Downloading FFmpeg (${key}) …`); +console.log(` ${url}`); -if (existsSync(outPath)) { - console.log(`✓ FFmpeg binary already exists: ${outPath}`); - process.exit(0); +const res = await fetch(url, { redirect: 'follow' }); +if (!res.ok) { + console.error(`Download failed: HTTP ${res.status} ${res.statusText}`); + process.exit(1); } +const buf = Buffer.from(await res.arrayBuffer()); +writeFileSync(outPath, buf); +if (process.platform !== 'win32') chmodSync(outPath, 0o755); -console.log(`Downloading FFmpeg for ${target}…`); -console.log(` URL: ${entry.url}`); -console.log(` → ${outPath}`); -console.log(''); -console.log('NOTE: Automatic extraction from zip/tar is not implemented here.'); -console.log('Please download FFmpeg manually from one of these sources:'); -console.log(' macOS: https://evermeet.cx/ffmpeg/'); -console.log(' Windows: https://www.gyan.dev/ffmpeg/builds/'); -console.log(' Linux: https://johnvansickle.com/ffmpeg/'); -console.log(''); -console.log(`Then place the binary at: ${outPath}`); -console.log(''); -console.log('Alternatively, install system FFmpeg:'); -console.log(' macOS: brew install ffmpeg'); -console.log(' Ubuntu: sudo apt install ffmpeg'); -console.log(' Windows: winget install ffmpeg'); -console.log(''); -console.log('The app falls back to system FFmpeg if bundled binary is not found.'); +console.log(`✓ Saved ${(buf.length / 1_000_000).toFixed(1)} MB → ${outPath}`); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index d46670d..65d10ca 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -76,9 +76,10 @@ } }, "bundle": { - "active": false, + "active": true, "targets": "all", "resources": [], + "externalBin": ["bin/ffmpeg"], "category": "Utility", "shortDescription": "Privacy-first client-side tools", "longDescription": "GoodWebTools Desktop brings browser-based tools to your desktop with system-wide capture, global hotkeys, and native file access."