Skip to content

Repository files navigation

ext-ffmpeg — a native PHP extension for media manipulation, built on FFmpeg

ext-ffmpeg

A native PHP extension that binds the FFmpeg libraries (libavformat, libavcodec, libavfilter, libavutil, libswscale, libswresample) directly — no shell_exec, no CLI string-building, no FFI. Written in pure C against the Zend API.

Language-level media access for PHP: open and probe media, build filter graphs, generate streams from nothing, and transcode or remux — with a real object model and typed exceptions instead of parsing stderr.

📚 Docs & guides: https://ext-ffmpeg.com

Attribution. This project is built on FFmpeg and would not exist without it. It is an independent binding and is not affiliated with, endorsed by, or an official part of the FFmpeg project. FFmpeg is a trademark of Fabrice Bellard. If ext-ffmpeg is useful to you, please also support FFmpeg.

Licensing is not finalized. Released builds statically bundle FFmpeg, so the distributed binary is GPL by construction; a license must be chosen and a LICENSE added before any public release. See ADR 0005 — Licensing & trademark and ADR 0006 — Linking & distribution.

What it looks like

use FFmpeg\Media;
use FFmpeg\MediaEncoder;
use FFmpeg\Codec\{VideoCodec, AudioCodec};
use FFmpeg\Filter\Scale;

$media = Media::open('input.mov');                 // eager probe

echo $media->duration, "s\n";                      // typed, readonly properties
$v = $media->videoStream();
echo "{$v->width}x{$v->height} {$v->codec}\n";

(new MediaEncoder())
    ->addVideo($v->apply(new Scale(1280, 720)), VideoCodec::H264, crf: 20, preset: 'medium')
    ->addAudio($media->audioStream(), AudioCodec::AAC, bitrate: 128_000)
    ->save('output.mp4');

Every FFmpeg failure surfaces as a typed FFmpeg\Exception\* carrying ->operation and ->avError — never a bare false. More recipes: https://ext-ffmpeg.com/guide/examples/.

Status

Pre-release, and further along than "early". Implemented and tested today:

  • Core object modelMedia::open, typed stream views (VideoStream/AudioStream), the FFmpeg\Exception\* hierarchy, and MediaEncoder (addVideo/addAudio/save) with re-encode, stream-copy/remux, and per-stream encode options (crf/preset/ bitrate). Native lifecycle is tied to PHP's GC.
  • Filter system — an immutable stream-node DAG lowered into one AVFilterGraph at save(): single- and multi-input filters (overlay, amix), multi-output fan-out (split/asplit), audio graphs, per-filter output-metadata derivation, and source filters via VideoStream::generate() / AudioStream::generate().
  • Typed filter catalog — a maintainer-time code generator introspects the bundled FFmpeg and emits 475 typed filter classes + 395 enums into a companion Composer package (see Packages).
  • A documentation website at https://ext-ffmpeg.com with per-filter pages and a live ffmpeg.wasm playground.

Still ahead: subtitles / multi-track muxing, per-frame PHP callables inside filter expressions, and finalizing licensing before going public. See docs/PRD.md and docs/filter-system-design.md.

Installation

Pre-release. Building from source works today; PIE and prebuilt binaries are the intended distribution and aren't published yet. Full guide: https://ext-ffmpeg.com/guide/installation/.

Requirements: PHP ≥ 8.4 and FFmpeg ≥ 7.0 shared libraries (libavformat, libavcodec, libavutil, libswscale, libswresample) — brew install ffmpeg or apt install libavformat-dev libavcodec-dev libavutil-dev libswscale-dev libswresample-dev.

From source (today):

git clone https://github.com/artisan-build/ext-ffmpeg
cd ext-ffmpeg/ext
phpize && ./configure && make && sudo make install
# ./configure --with-ffmpeg=<prefix> if FFmpeg isn't on your pkg-config path

Then add extension=ffmpeg to your php.ini and verify with php -m | grep ffmpeg.

Then add the companion packages for the typed filters and IDE support:

composer require artisan-build/ext-ffmpeg-filters       # the typed filter classes (autoloaded)
composer require --dev artisan-build/ext-ffmpeg-stubs   # IDE stubs for the core .so classes

PIE (once published): pie install artisan-build/ext-ffmpeg — the repo is already PIE-ready (root composer.json, build-path: ext).

Packages & distribution

The extension ships as a hybrid two-package model, both version-locked in lockstep with the extension and the bundled FFmpeg (see packages/):

Package What How it's used
ext-ffmpeg-filters The 475 concrete, typed filter classes (Scale, Overlay, Volume, …) + enums, generated from FFmpeg metadata composer require — real classes, PSR-4 autoloaded, instantiated at runtime
ext-ffmpeg-stubs Declaration-only IDE stubs for the core .so classes (Media, streams, MediaEncoder, the Filter base + axis interfaces, codec enums, exceptions) require-dev — indexed by your editor, never autoloaded; synced from the canonical ext/src/**/*.stub.php

The catalog lives in exactly one place (the filters package), so there's no duplicate declaration. See docs/filter-system-design.md.

Repository layout

This repo doubles as the multi-repo dev workspace. The extension source lives in ext/; the two large upstreams are cloned in as gitignored siblings so all three can be built together.

ext-ffmpeg/              ← workspace root (orchestration Makefile + manifest.toml)
├── ext/                 ← the extension: config.m4, ffmpeg.c (thin MINIT), src/<concern>/, tests/
│   └── src/             ← one concern per dir: media/ stream/ codec/ encoder/ exception/ filter/
├── packages/            ← co-located Composer packages (ext-ffmpeg-filters, ext-ffmpeg-stubs)
├── codegen/             ← typed filter-catalog generator (introspect.c → filters.json → generate.php)
├── site/                ← the docs website (Astro + Starlight) → ext-ffmpeg.com
├── docs/                ← PRD, ADRs (decisions/), contributing guides, design notes, lessons/
├── scripts/             ← bootstrap + helper scripts
├── art/                 ← brand assets
├── php-src/             ← clone of ProjektGopher/php-src   (gitignored fork)
└── ffmpeg/              ← clone of ProjektGopher/FFmpeg     (gitignored fork)

php-src and ffmpeg are forks under the ProjektGopher org, each with the real project as an upstream remote, pinned in manifest.toml (php PHP-8.5, ffmpeg n7.1.1). We build against our own from-source FFmpeg (Homebrew's 8.0 dropped the text-rendering libs) and our own debug + ASAN/UBSAN php-src (turns segfaults into stack traces). Local patches (e.g. a future custom AVFilter) live on a patch branch and rebase onto upstream tags.

Dev workspace

make bootstrap     # clone the php-src + ffmpeg forks at pinned refs (manifest.toml)
make ffmpeg        # build FFmpeg from source into ffmpeg/_install (text-rendering libs on)
make php           # build + install php-src (DEBUG) into php-src/_install (the dev workhorse)
make ext           # phpize-build the extension (in ext/) against the dev php + dev ffmpeg
make smoke         # probe the fixture and exercise an exception
make test          # run the .phpt suite against the dev php

The dev PHP is a --enable-debug build: Zend's debug memory manager does red-zone bounds checks and reports leaks at shutdown. make php-asan layers ASAN/UBSAN on top — run that php with USE_ZEND_ALLOC=0 ASAN_OPTIONS=detect_leaks=0 (LSan is unavailable on macOS/arm64).

Other targets (make help for the full list):

make stubs               # regenerate src/**/*_arginfo.h from the .stub.php files (commit them)
make clangd              # unified compile_commands.json (needs `bear`) for cross-repo go-to-def
make clean               # remove extension build artifacts

# filter catalog (maintainer)
make filter-introspect   # dump bundled FFmpeg's filter metadata → codegen/filters.json
make filter-catalog      # generate the typed classes + doc facts from filters.json
make filter-catalog-test # unit + golden-snapshot tests for the generator (no extension needed)
make filter-catalog-check# drift check: regenerate and assert no git diff
make stubs-package       # sync core .stub.php → packages/ext-ffmpeg-stubs (declaration-only)

# website
make site-setup          # install deps + park a local Herd domain (https://ext-ffmpeg.test)
make site-dev            # Astro dev server (HMR)
make site                # build the static site → site/dist
make deploy              # build + deploy to Cloudflare Pages (maintainers)

make stubs and the catalog generator run with the dev php (no Homebrew/Herd php needed); generated *_arginfo.h and the catalog are committed, so make ext never needs the codegen toolchain. See docs/contributing/building.md and codegen/README.md.

Why pure C (not PHP-CPP / FFI / Rust)

  • PHP-CPP added an ABI dependency (libphpcpp.so) that itself needed patching for PHP 8.5, plus extern "C" friction. The original PoC used it; dropped.
  • FFI isn't universally available and has per-call overhead unsuitable for frame-by-frame work.
  • Rust (ext-php-rs) is viable, but C aligns with php-src, FFmpeg, and OpenCV/MediaPipe (all C-API), and with the PHP Foundation audience.

See ADR 0002 — Foundation, naming & scope.

Documentation

  • https://ext-ffmpeg.com — the canonical usage docs (guide, per-class reference, filter catalog, live playground).
  • docs/PRD.md — the long-range product vision.
  • docs/filter-system-design.md — the filter wave's design, implementation log, and gaps.
  • docs/decisions/ — the ADRs (no private-struct access, naming/scope, v1 API, project structure, licensing/trademark, linking/distribution).
  • docs/contributing/ — how to build, the repo structure, adding a class, authoring a filter page, and the website.
  • docs/ffmpeg-notes.md — language-agnostic FFmpeg gotchas.
  • docs/lessons/ — short engineering postmortems (a segfault, a stale make dep, an arginfo type-degradation).
  • docs/AVEXPR_DEEP_DIVE.md, docs/CALLABLE_EVALUATION.md — per-frame callable research.
  • Upstream FFmpeg patch proposals live on the site under /upstream.

Built on FFmpeg. An Artisan Build project.

About

Native PHP extension binding the FFmpeg libraries (pure C / Zend API). An Artisan Build project.

Resources

Stars

44 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages