Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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-<triple>[.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:
Expand Down
38 changes: 24 additions & 14 deletions RELEASING-DESKTOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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-<triple>`:
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-<triple>` before `tauri build`. Nothing to do.

Download static FFmpeg builds from:
- macOS/Linux: <https://evermeet.cx/ffmpeg/> or <https://johnvansickle.com/ffmpeg/>
- Windows: <https://www.gyan.dev/ffmpeg/builds/> (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-<triple>` (`.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
Expand Down
113 changes: 39 additions & 74 deletions scripts/download-ffmpeg-binaries.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
3 changes: 2 additions & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
Loading