From 05e42668cbbe39a22c7a5cf3072f0307b90e91b9 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Tue, 11 Aug 2026 20:04:44 +0800 Subject: [PATCH] build(ffmpeg): static GPL FFmpeg 8.0 via project script + FFMPEG_DIR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - tooling/ffmpeg/build-ffmpeg.sh builds release/8.0 static+PIC into .cache/ffmpeg: GPL/version3, every free-license external codec lib probed via pkg-config (enabled when present), per-OS hardware acceleration (VideoToolbox/AudioToolbox, VAAPI/VDPAU/libdrm, D3D11VA/DXVA2/MediaFoundation, nvenc when ffnvcodec exists) - tooling/install-deps.sh installs those libraries on Homebrew / MSYS2 UCRT64 / Debian-Ubuntu / Fedora / Arch; nothing in the build sudo's - ffmpeg-next's own build feature is unusable (every crate-version to FFmpeg-release pairing is broken upstream: 9.0.0->FF9 AVCodec fields, 8.1.0->FF8.1 new enum variants, 8.0.0->FF8 FF_PROFILE rename), so ffmpeg-next 9 + FFmpeg 8.x headers via FFMPEG_DIR it is - new links-crate oakffmpeg-link emits the static FFmpeg's transitive link flags from its .pc files (cargo only propagates them from links crates, and rustc prunes the flags unless the rlib is referenced — hence the force_link statics) - docs/build.md updated for the Rust workspace flow --- Cargo.lock | 8 ++- crates/oakaudio/Cargo.toml | 4 +- crates/oakaudio/src/lib.rs | 6 ++ crates/oakcodec/Cargo.toml | 15 ++++- crates/oakcodec/src/lib.rs | 6 ++ crates/oakffmpeg-link/Cargo.toml | 13 ++++ crates/oakffmpeg-link/build.rs | 105 +++++++++++++++++++++++++++++++ crates/oakffmpeg-link/src/lib.rs | 24 +++++++ docs/build.md | 30 +++++++++ tooling/install-deps.sh | 93 +++++++++++++++++++++++++++ 10 files changed, 301 insertions(+), 3 deletions(-) create mode 100644 crates/oakffmpeg-link/Cargo.toml create mode 100644 crates/oakffmpeg-link/build.rs create mode 100644 crates/oakffmpeg-link/src/lib.rs create mode 100755 tooling/install-deps.sh diff --git a/Cargo.lock b/Cargo.lock index 9515aa7df..13ea8d49d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2329,7 +2329,7 @@ dependencies = [ "log", "presser", "thiserror 1.0.69", - "windows 0.57.0", + "windows 0.58.0", ] [[package]] @@ -3928,6 +3928,7 @@ dependencies = [ "oakcodec", "oakcommon", "oakcore-rs", + "oakffmpeg-link", ] [[package]] @@ -3936,6 +3937,7 @@ version = "0.1.0" dependencies = [ "ffmpeg-next", "oakcore-rs", + "oakffmpeg-link", ] [[package]] @@ -3972,6 +3974,10 @@ dependencies = [ "serde_json", ] +[[package]] +name = "oakffmpeg-link" +version = "0.1.0" + [[package]] name = "oaknode" version = "0.1.0" diff --git a/crates/oakaudio/Cargo.toml b/crates/oakaudio/Cargo.toml index 53ea850d9..ebeacdb96 100644 --- a/crates/oakaudio/Cargo.toml +++ b/crates/oakaudio/Cargo.toml @@ -9,10 +9,12 @@ license = "GPL-3.0-or-later" crate-type = ["staticlib", "rlib"] [dependencies] +oakffmpeg-link = { path = "../oakffmpeg-link" } oakcore-rs = { path = "../oakcore" } oakcommon = { path = "../oakcommon" } oakcodec = { path = "../oakcodec" } # Real resample/channel-convert/time-stretch filter graph. The C++ # ffmpeg_bridge library existed only to absorb FFmpeg API churn; the Rust -# crate calls ffmpeg-next directly (same as oakcodec). +# crate calls ffmpeg-next directly (same as oakcodec). The oakffmpeg-link +# dependency above emits the transitive link flags of the static FFmpeg. ffmpeg-next = "9" diff --git a/crates/oakaudio/src/lib.rs b/crates/oakaudio/src/lib.rs index 3deac3498..4f44837d2 100644 --- a/crates/oakaudio/src/lib.rs +++ b/crates/oakaudio/src/lib.rs @@ -43,3 +43,9 @@ pub mod processor; pub mod synchronizer; pub mod waveform; pub mod waveformsync; + +// Keep the oakffmpeg-link rlib referenced so its build script's native +// link flags (the static FFmpeg's transitive dependencies) reach the +// final link — rustc prunes the flags of an unreferenced rlib. +#[used] +static FORCE_FFMPEG_LINK: fn() = oakffmpeg_link::force_link; diff --git a/crates/oakcodec/Cargo.toml b/crates/oakcodec/Cargo.toml index 8dc84d866..81d6f80a4 100644 --- a/crates/oakcodec/Cargo.toml +++ b/crates/oakcodec/Cargo.toml @@ -15,7 +15,20 @@ crate-type = ["staticlib", "rlib"] test-stubs = [] [dependencies] +oakffmpeg-link = { path = "../oakffmpeg-link" } oakcore-rs = { path = "../oakcore" } # Real media decode/encode. The C++ ffmpeg_bridge library existed only to # absorb FFmpeg API churn; the Rust crate calls ffmpeg-next directly. -ffmpeg-next = "9" +# +# ffmpeg-next 9.x pairs with FFmpeg 8.x headers, which most distros do +# not ship yet — build a matching GPL FFmpeg (all free-license codecs + +# per-OS hardware acceleration, static+PIC) with +# tooling/ffmpeg/build-ffmpeg.sh and point cargo at it via FFMPEG_DIR +# (see docs/build.md). The `static` feature makes the linker prefer the +# archives. +# +# ffmpeg-next's own `build` feature is NOT usable: it clones +# release/, and every such pairing is broken upstream +# (9.0.0 -> FFmpeg 9.0 removed AVCodec fields; 8.1.0 -> FFmpeg 8.1 added +# enum variants; 8.0.0 -> FFmpeg 8.0 renamed FF_PROFILE_*). +ffmpeg-next = { version = "9.0", features = ["static"] } diff --git a/crates/oakcodec/src/lib.rs b/crates/oakcodec/src/lib.rs index d377e444f..b7803b713 100644 --- a/crates/oakcodec/src/lib.rs +++ b/crates/oakcodec/src/lib.rs @@ -52,3 +52,9 @@ pub mod timecodemetadata; #[cfg(test)] mod realmedia_tests; + +// Keep the oakffmpeg-link rlib referenced so its build script's native +// link flags (the static FFmpeg's transitive dependencies) reach the +// final link — rustc prunes the flags of an unreferenced rlib. +#[used] +static FORCE_FFMPEG_LINK: fn() = oakffmpeg_link::force_link; diff --git a/crates/oakffmpeg-link/Cargo.toml b/crates/oakffmpeg-link/Cargo.toml new file mode 100644 index 000000000..ee60e7e8a --- /dev/null +++ b/crates/oakffmpeg-link/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "oakffmpeg-link" +version = "0.1.0" +edition = "2021" +description = "Oak Video Editor internal link helper: transitive dependencies of a static FFmpeg (Rust)" +license = "GPL-3.0-or-later" +# A dedicated `links` crate so the emitted link directives propagate to +# every dependent's final link (cargo only propagates rustc-link-* from +# `links` crates). ffmpeg-sys-next already claims links = "ffmpeg". +links = "oak_ffmpeg_static_deps" + +[lib] +path = "src/lib.rs" diff --git a/crates/oakffmpeg-link/build.rs b/crates/oakffmpeg-link/build.rs new file mode 100644 index 000000000..50b306387 --- /dev/null +++ b/crates/oakffmpeg-link/build.rs @@ -0,0 +1,105 @@ +// Oak Video Editor - Non-Linear Video Editor +// Copyright (C) 2026 Oak Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Build-time link configuration for the `oakffmpeg-link` crate. +//! +//! ffmpeg-sys-next only emits the link directives for FFmpeg's own +//! libraries. When FFmpeg is linked STATICALLY (the project FFmpeg built +//! by tooling/ffmpeg/build-ffmpeg.sh, pointed at via `FFMPEG_DIR`), its +//! archives reference every external codec library they were configured +//! with (x264, x265, dav1d, ..., plus system frameworks on macOS), and +//! the final link fails with undefined symbols unless those transitive +//! dependencies are linked too. FFmpeg's own `.pc` files carry the exact +//! list in `Libs.private`, so this script asks pkg-config for the static +//! link line and forwards it to cargo. +//! +//! No-op when `FFMPEG_DIR` is unset (shared system FFmpeg carries its +//! own transitive dependencies). + +use std::path::PathBuf; +use std::process::Command; + +fn main() { + let Ok(dir) = std::env::var("FFMPEG_DIR") else { + return; + }; + let pc_dir = PathBuf::from(&dir).join("lib").join("pkgconfig"); + if !pc_dir.exists() { + return; + } + println!("cargo:rerun-if-env-changed=FFMPEG_DIR"); + + let pkg_path = std::env::var("PKG_CONFIG_PATH").unwrap_or_default(); + let output = Command::new("pkg-config") + .arg("--static") + .arg("--libs") + .args([ + "libavformat", + "libavcodec", + "libavfilter", + "libavdevice", + "libavutil", + "libswscale", + "libswresample", + ]) + .env( + "PKG_CONFIG_PATH", + format!("{}{}{}", pc_dir.display(), if pkg_path.is_empty() { "" } else { ":" }, pkg_path), + ) + .output() + .expect("pkg-config is required when FFMPEG_DIR is set"); + if !output.status.success() { + panic!( + "pkg-config --static --libs failed for the FFMPEG_DIR install: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + + for token in String::from_utf8_lossy(&output.stdout).split_whitespace() { + if let Some(path) = token.strip_prefix("-L") { + println!("cargo:rustc-link-search=native={path}"); + } else if let Some(lib) = token.strip_prefix("-l") { + // System libs (m, z, bz2, iconv, ...) and externals alike; the + // linker picks .a or .dylib per -L search order. + println!("cargo:rustc-link-lib={lib}"); + } else if token == "-framework" { + // Handled on the next token (see below). + } else if let Some(framework) = token.strip_prefix("-framework=") { + println!("cargo:rustc-link-lib=framework={framework}"); + } + // Bare tokens after "-framework" are handled by the stateful pass + // below; pkg-config prints "-framework Foo" as two tokens. + } + // Second pass for the two-token "-framework Foo" form. + let stdout = String::from_utf8_lossy(&output.stdout).into_owned(); + let tokens: Vec<&str> = stdout.split_whitespace().collect(); + for pair in tokens.windows(2) { + if pair[0] == "-framework" { + println!("cargo:rustc-link-lib=framework={}", pair[1]); + } + } + + // Some externals (Homebrew lame, snappy) reach the FFmpeg archives via + // the configure-time --extra-ldflags instead of Requires.private, so + // their -L never shows up above; add the Homebrew lib dir as a + // fallback search path. + #[cfg(target_os = "macos")] + for prefix in ["/opt/homebrew/lib", "/usr/local/lib"] { + if std::path::Path::new(prefix).exists() { + println!("cargo:rustc-link-search=native={prefix}"); + } + } +} diff --git a/crates/oakffmpeg-link/src/lib.rs b/crates/oakffmpeg-link/src/lib.rs new file mode 100644 index 000000000..04f13ec8e --- /dev/null +++ b/crates/oakffmpeg-link/src/lib.rs @@ -0,0 +1,24 @@ +// Oak Video Editor - Non-Linear Video Editor +// Copyright (C) 2026 Oak Team +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Empty crate whose build script emits the transitive link flags of a +//! static FFmpeg (see build.rs). Depend on it from every crate that uses +//! `ffmpeg-next` so the final binary/test links. + +/// Referenced by dependents (`#[used] static … = force_link`) so this +/// rlib is never pruned as unused — the build script's native link flags +/// only reach the final link while the rlib is linked. +pub fn force_link() {} diff --git a/docs/build.md b/docs/build.md index d81d11398..56cdf7c68 100644 --- a/docs/build.md +++ b/docs/build.md @@ -2,6 +2,36 @@ This document describes how to build Oak Video Editor from source on Windows, Linux, and macOS. +> **Note (2026):** Oak is now a Rust workspace; `cargo build` at the +> repository root produces the app, the CLI, the worker and +> `liboakengine`. The CMake instructions below are kept for historical +> reference only. Rust dependencies are pulled from crates.io; the only +> native libraries still needed are FFmpeg (see the next section), +> OpenColorIO (optional; `ocio-sys` builds a stub without it) and a +> C/C++ toolchain for the FFI shims. +> +> ### FFmpeg for the Rust build +> +> `ffmpeg-next` 9.x pairs with FFmpeg 8.x headers, which most distros do +> not ship yet. Build a project-owned FFmpeg — GPL parts enabled, every +> free-license external codec library that is installed, per-OS hardware +> acceleration, static+PIC — with the project scripts: +> +> ```sh +> tooling/install-deps.sh # Homebrew / MSYS2 UCRT64 / Debian / Fedora / Arch +> tooling/ffmpeg/build-ffmpeg.sh # clones release/8.0, installs into .cache/ffmpeg +> export FFMPEG_DIR="$(pwd)/.cache/ffmpeg" +> cargo build +> ``` +> +> External libraries are probed with `pkg-config` and silently skipped +> when missing. Unset `FFMPEG_DIR` to fall back to the system +> `pkg-config` FFmpeg (must be 8.x for ffmpeg-next to compile). +> ffmpeg-next's `build` cargo feature is deliberately not used: it +> clones release/, and every such pairing is broken +> upstream (9.0.0 → FFmpeg 9.0 removed AVCodec fields; 8.1.0 → FFmpeg +> 8.1 added enum variants; 8.0.0 → FFmpeg 8.0 renamed FF_PROFILE_*). + ## Prerequisites - CMake 3.20+ diff --git a/tooling/install-deps.sh b/tooling/install-deps.sh new file mode 100755 index 000000000..048d73235 --- /dev/null +++ b/tooling/install-deps.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# Oak Video Editor - Non-Linear Video Editor +# Copyright (C) 2026 Oak Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Installs the system dependencies of the Oak Rust workspace: +# the free-license external codec/filter libraries FFmpeg is configured +# with (see crates/oakcodec/Cargo.toml), plus the build tools. FFmpeg +# itself is built from source by cargo (ffmpeg-next `build` feature) and +# is NOT installed here. +# +# Supported: Homebrew (macOS), MSYS2 UCRT64 (Windows), Debian/Ubuntu, +# Fedora, Arch. Run it yourself — nothing in the build invokes it +# automatically (it needs sudo on Linux). +# +# Usage: tooling/install-deps.sh + +set -euo pipefail + +run() { echo "+ $*"; "$@"; } + +if [[ "$OSTYPE" == msys* || "$OSTYPE" == cygwin* || -n "${MSYSTEM:-}" ]]; then + if [ "${MSYSTEM:-}" != "UCRT64" ]; then + echo "Please run this from the MSYS2 UCRT64 shell (MSYSTEM=$MSYSTEM)." >&2 + exit 1 + fi + run pacman -S --needed --noconfirm \ + mingw-w64-ucrt-x86_64-toolchain mingw-w64-ucrt-x86_64-pkgconf \ + mingw-w64-ucrt-x86_64-nasm \ + mingw-w64-ucrt-x86_64-x264 mingw-w64-ucrt-x86_64-x265 \ + mingw-w64-ucrt-x86_64-dav1d mingw-w64-ucrt-x86_64-libvpx \ + mingw-w64-ucrt-x86_64-openh264 mingw-w64-ucrt-x86_64-openjpeg2 \ + mingw-w64-ucrt-x86_64-libtheora mingw-w64-ucrt-x86_64-libwebp \ + mingw-w64-ucrt-x86_64-lame mingw-w64-ucrt-x86_64-opus \ + mingw-w64-ucrt-x86_64-libvorbis mingw-w64-ucrt-x86_64-speex \ + mingw-w64-ucrt-x86_64-snappy mingw-w64-ucrt-x86_64-libass \ + mingw-w64-ucrt-x86_64-freetype mingw-w64-ucrt-x86_64-fribidi \ + mingw-w64-ucrt-x86_64-fontconfig mingw-w64-ucrt-x86_64-gnutls + exit 0 +fi + +case "$(uname -s)" in + Darwin) + run brew install pkg-config nasm \ + x264 x265 dav1d libvpx openh264 openjpeg libtheora libwebp \ + lame opus libvorbis speex snappy libass freetype fribidi \ + fontconfig gnutls + ;; + Linux) + if command -v apt-get >/dev/null; then + run sudo apt-get update + run sudo apt-get install -y build-essential pkg-config nasm \ + libx264-dev libx265-dev libdav1d-dev libvpx-dev \ + libopenh264-dev libopenjp2-7-dev libtheora-dev libwebp-dev \ + libmp3lame-dev libopus-dev libvorbis-dev libspeex-dev \ + libsnappy-dev libass-dev libfreetype-dev libfribidi-dev \ + libfontconfig-dev libgnutls28-dev + elif command -v dnf >/dev/null; then + run sudo dnf install -y gcc gcc-c++ pkgconf-pkg-config nasm \ + x264-devel x265-devel dav1d-devel libvpx-devel \ + openh264-devel openjpeg2-devel libtheora-devel libwebp-devel \ + lame-devel opus-devel libvorbis-devel speex-devel \ + snappy-devel libass-devel freetype-devel fribidi-devel \ + fontconfig-devel gnutls-devel + elif command -v pacman >/dev/null; then + run sudo pacman -S --needed --noconfirm base-devel pkgconf nasm \ + x264 x265 dav1d libvpx openh264 openjpeg2 libtheora libwebp \ + lame opus libvorbis speex snappy libass freetype2 fribidi \ + fontconfig gnutls + else + echo "Unsupported Linux distribution (need apt-get, dnf or pacman)." >&2 + exit 1 + fi + ;; + *) + echo "Unsupported platform: $(uname -s)" >&2 + exit 1 + ;; +esac + +echo "Done. 'cargo build' now compiles FFmpeg 8.1 from source with these libraries."