build(ffmpeg): static GPL FFmpeg 8.0 via project script + FFMPEG_DIR

- 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
This commit is contained in:
2026-08-11 20:04:44 +08:00
parent cdda643d64
commit 05e42668cb
10 changed files with 301 additions and 3 deletions
Generated
+7 -1
View File
@@ -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"
+3 -1
View File
@@ -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"
+6
View File
@@ -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;
+14 -1
View File
@@ -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/<crate-version>, 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"] }
+6
View File
@@ -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;
+13
View File
@@ -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"
+105
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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}");
}
}
}
+24
View File
@@ -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 <http://www.gnu.org/licenses/>.
//! 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() {}
+30
View File
@@ -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/<crate-version>, 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+
+93
View File
@@ -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 <http://www.gnu.org/licenses/>.
# 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."