ez-ffmpeg 0.13.1

A safe and ergonomic Rust interface for FFmpeg integration, designed for ease of use.
Documentation
# CI for ez-ffmpeg.
#
# Every job runs in a two-lane FFmpeg matrix, because the crate supports
# linking either major and the two lanes are provisioned differently:
#
#   * ffmpeg 8.1 lane — `--features ffmpeg-sys-next/build`: the sys crate's
#     build script git-clones FFmpeg `release/8.1` (the branch is hardcoded to
#     the crate's own major.minor) and compiles/statically links it. This is
#     the same mechanism the pre-8 CI used, which then produced FFmpeg 7.1 —
#     after the dependency bump the SAME flag silently produces 8.1, which is
#     exactly why the 7.x lane below must NOT use it.
#
#   * ffmpeg 7.1 lane — explicit from-source build of FFmpeg `release/7.1`
#     (shared libs, default components, no external deps) installed under
#     $HOME/ffmpeg71 and linked via pkg-config. This proves the bump keeps
#     FFmpeg 7.x users working: ffmpeg-sys-next 8.1 probes the installed
#     headers and only enables cfg flags up to ffmpeg_7_1.
#
# Ubuntu's apt FFmpeg (6.1 on 24.04) matches neither lane, so it is never
# installed; the apt packages below are just the FFmpeg build toolchain
# (nasm/yasm/clang/pkg-config/zlib). First build per lane compiles FFmpeg
# (~10-20 min); caches make subsequent runs fast.
#
# GPU-dependent code: the `opengl` frame filter needs a real GPU/display and
# its tests fail headless ("Failed to create Surfman connection"), so the
# `test` job omits that feature; the `build` job still compiles it (via
# `--all-features`) so it stays type-checked. The `wgpu` feature runs in the
# test job: its GPU oracles self-skip without an adapter while its CPU-only
# gates (offline naga shader validation, layout pins, builders) run for real.
# The macOS-only `videotoolbox` scheduler tests are
# `#[cfg(target_os = "macos")]`, so they do not run on Linux.
#
# The `build` job (all-features build + a default-features build + clippy) and
# the `test` job run per FFmpeg lane. Two lanes run once, not per FFmpeg:
#
#   * `docs` — compiles the crate under `--cfg docsrs` (`DOCS_RS=1`), which stubs
#     ez-ffmpeg's own FFI call sites; the gate catches docsrs-stub compile gaps.
#     ffmpeg-sys-next still needs a real FFmpeg for its bindings, so this job builds
#     one via `ffmpeg-sys-next/build` (not a literal docs.rs run — docs.rs has no
#     network to clone FFmpeg — but it exercises the same `--cfg docsrs` paths).
#   * `asan` — AddressSanitizer over the lifecycle/teardown integration tests,
#     guarding the use-after-free fixes. FFmpeg is statically linked via
#     ffmpeg-sys-next/build; ASan's interceptors see its alloc/free, so a UAF where
#     instrumented Rust touches FFmpeg-freed memory is caught (one entirely inside
#     un-instrumented FFmpeg C is not reliably covered — those regressions are
#     Rust-side here).
#
# All jobs are enforced gates.

name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1

jobs:
  build:
    name: Build & Clippy (FFmpeg ${{ matrix.ffmpeg }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - ffmpeg: "8.1"
            ffmpeg-features: "--features ffmpeg-sys-next/build"
          - ffmpeg: "7.1"
            ffmpeg-features: ""
    steps:
      - uses: actions/checkout@v4

      - name: Install FFmpeg build toolchain
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            nasm yasm clang pkg-config make git zlib1g-dev
          echo "nasm: $(nasm --version | head -1)"

      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy

      - name: Cache FFmpeg 7.1 install
        if: matrix.ffmpeg == '7.1'
        uses: actions/cache@v4
        with:
          path: ~/ffmpeg71
          key: ${{ runner.os }}-ffmpeg71-shared-v1

      - name: Build FFmpeg 7.1 from source (if not cached)
        if: matrix.ffmpeg == '7.1'
        run: |
          if [ ! -f "$HOME/ffmpeg71/lib/pkgconfig/libavcodec.pc" ]; then
            git clone --depth=1 -b release/7.1 https://github.com/FFmpeg/FFmpeg ffmpeg-71-src
            cd ffmpeg-71-src
            ./configure --prefix="$HOME/ffmpeg71" \
                        --disable-programs --disable-doc \
                        --disable-static --enable-shared
            make -j"$(nproc)"
            make install
          fi

      - name: Point pkg-config at FFmpeg 7.1
        if: matrix.ffmpeg == '7.1'
        run: |
          echo "PKG_CONFIG_PATH=$HOME/ffmpeg71/lib/pkgconfig" >> "$GITHUB_ENV"
          echo "LD_LIBRARY_PATH=$HOME/ffmpeg71/lib" >> "$GITHUB_ENV"

      - name: Cache cargo registry and target
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-ffmpeg${{ matrix.ffmpeg }}-${{ hashFiles('**/Cargo.toml') }}

      - name: Build
        run: cargo build --all-features ${{ matrix.ffmpeg-features }} --verbose

      # Compiles every #[cfg(test)] tree under --all-features WITHOUT running:
      # the opengl/wgpu suites need a GPU/display to RUN, but their tests carry
      # compile-time signature pins (e.g. the opengl typed-error fn-pointer
      # bindings) that only a test build type-checks. `--no-run` is headless-safe.
      - name: Build tests (all features, no run)
        run: cargo test --lib --all-features ${{ matrix.ffmpeg-features }} --no-run

      # The crate's default feature set is empty (`default = []`), yet every job
      # here builds `--all-features`; this proves the no-feature build and its cfg
      # gates still compile. The ffmpeg-features flag only controls how FFmpeg is
      # provisioned, so "default features" still carries it.
      - name: Build (default features)
        run: cargo build ${{ matrix.ffmpeg-features }} --verbose

      # Safety-hygiene lints surface here. Not `-D warnings` yet: the crate has a
      # pre-existing unused-import / undocumented-unsafe backlog to clear first.
      - name: Clippy
        run: cargo clippy --all-features ${{ matrix.ffmpeg-features }} --lib

  test:
    name: Test (FFmpeg ${{ matrix.ffmpeg }})
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        include:
          - ffmpeg: "8.1"
            ffmpeg-features: "--features ffmpeg-sys-next/build"
          - ffmpeg: "7.1"
            ffmpeg-features: ""
    # Runs the full library suite MINUS the opengl feature, whose tests need a
    # GPU/display (surfman connection creation fails headless). The wgpu
    # feature IS included: its GPU oracles self-skip when no adapter exists,
    # while its CPU-only gates — offline naga shader validation, uniform
    # layout pins, builder checks — run for real. Media assets are committed
    # as test.mp4.
    steps:
      - uses: actions/checkout@v4

      - name: Install FFmpeg build toolchain
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            nasm yasm clang pkg-config make git zlib1g-dev

      - uses: dtolnay/rust-toolchain@stable

      - name: Cache FFmpeg 7.1 install
        if: matrix.ffmpeg == '7.1'
        uses: actions/cache@v4
        with:
          path: ~/ffmpeg71
          key: ${{ runner.os }}-ffmpeg71-shared-v1

      - name: Build FFmpeg 7.1 from source (if not cached)
        if: matrix.ffmpeg == '7.1'
        run: |
          if [ ! -f "$HOME/ffmpeg71/lib/pkgconfig/libavcodec.pc" ]; then
            git clone --depth=1 -b release/7.1 https://github.com/FFmpeg/FFmpeg ffmpeg-71-src
            cd ffmpeg-71-src
            ./configure --prefix="$HOME/ffmpeg71" \
                        --disable-programs --disable-doc \
                        --disable-static --enable-shared
            make -j"$(nproc)"
            make install
          fi

      - name: Point pkg-config at FFmpeg 7.1
        if: matrix.ffmpeg == '7.1'
        run: |
          echo "PKG_CONFIG_PATH=$HOME/ffmpeg71/lib/pkgconfig" >> "$GITHUB_ENV"
          echo "LD_LIBRARY_PATH=$HOME/ffmpeg71/lib" >> "$GITHUB_ENV"

      - name: Cache cargo registry and target
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-ffmpeg${{ matrix.ffmpeg }}-${{ hashFiles('**/Cargo.toml') }}

      - name: Test
        run: cargo test --lib --features async,rtmp,flv,subtitle,wgpu ${{ matrix.ffmpeg-features }}

  docs:
    name: Docs (docsrs cfg compile)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install FFmpeg build toolchain
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            nasm yasm clang pkg-config make git zlib1g-dev

      - uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo registry and target
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-docs-${{ hashFiles('**/Cargo.toml') }}

      # `DOCS_RS=1` makes ez-ffmpeg's build.rs pass `--cfg docsrs`, compiling its FFI
      # call sites as stubs; this gate catches a docsrs-stub compile gap (a fn that
      # takes a `#[cfg(not(docsrs))]` type but is itself ungated). ffmpeg-sys-next's
      # build.rs does NOT honor DOCS_RS, so it still needs a real FFmpeg for its
      # bindings; `ffmpeg-sys-next/build` compiles one from source (the runner has
      # the network docs.rs lacks), keeping the job self-contained. The features
      # otherwise match `[package.metadata.docs.rs]`. Not `-D warnings`: docs.rs does
      # not, and the crate has a pre-existing intra-doc-link backlog (a separate
      # task); this lane is the stub-compile gate, not doc-link cleanup.
      - name: cargo doc (docsrs cfg)
        env:
          DOCS_RS: "1"
        run: cargo doc --no-deps --features ffmpeg-sys-next/build,async,opengl,rtmp,flv,subtitle,wgpu

  asan:
    name: ASAN (UAF, FFmpeg 8.1)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install FFmpeg build toolchain
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            nasm yasm clang pkg-config make git zlib1g-dev

      - uses: dtolnay/rust-toolchain@nightly

      - name: Cache cargo registry and target
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
          key: ${{ runner.os }}-cargo-asan-${{ hashFiles('**/Cargo.toml') }}

      # `-Zsanitizer=address` instruments the target crates' Rust code. The explicit
      # `--target` scopes the sanitizer to those crates and off the host build
      # scripts / proc-macros; std is reused un-instrumented from the sysroot (so no
      # `-Zbuild-std`), which suffices because the UAF regressions here are detected
      # on the Rust side. `detect_leaks=0` because FFmpeg's one-time global
      # allocations are not leaks. GPU features are omitted here (the test job
      # runs wgpu's self-skipping suite; opengl needs a display either way) —
      # the lifecycle/teardown suites under ASAN do not need them.
      - name: ASAN UAF tests
        env:
          RUSTFLAGS: "-Zsanitizer=address"
          RUSTDOCFLAGS: "-Zsanitizer=address"
          ASAN_OPTIONS: "detect_leaks=0"
        run: |
          cargo +nightly test --target x86_64-unknown-linux-gnu \
            --features ffmpeg-sys-next/build,async,rtmp,flv,subtitle \
            --test bsf --test mux_teardown --test lifecycle \
            --test start_failure_join --test start_panic_unwind \
            --test shortest --test frame_pipeline_hardening