Skip to main content

CHANGELOG.md

# Changelog

## 0.3.0 - 2026-05-20

Initial public release. Native Elixir bindings for FFmpeg 8 via the
`rsmpeg` Rust crate, packaged as a Rustler NIF - in-process calls
instead of shelling out to the `ffmpeg` / `ffprobe` CLIs.

### Operations

- `Exmpeg.version/0` - linked FFmpeg sub-library versions and configure
  flags.
- `Exmpeg.probe/1` - container + per-stream metadata (`ffprobe`).
- `Exmpeg.remux/3` - stream copy between containers with optional
  `start_s` / `duration_s` window, `:drop_audio` / `:drop_video` /
  `:drop_subtitles`, and a `:tags` keyword/map for container metadata.
- `Exmpeg.extract_frame/3` - single-frame thumbnail at a timestamp,
  written as `.jpg` / `.png` / `.bmp` / `.webp`, with optional
  aspect-preserving resize.
- `Exmpeg.extract_audio/3` - decoded audio to `.wav`, `.mp3`,
  `.m4a` / `.aac`, `.opus` / `.ogg`, or `.flac`, with optional
  sample-rate / channel-count / bitrate selection. Sample rate snaps to
  the encoder's supported list when needed (e.g. libopus
  8/12/16/24/48 kHz).
- `Exmpeg.concat/3` - join multiple inputs sharing the same stream
  layout into a single output without re-encoding.
- `Exmpeg.transcode/3` - per-stream re-encode with codec / bitrate /
  scale / fps / sample-rate selection driven by an `AVFilterGraph`, plus
  a raw `:video_filter` filter-graph spec. Streams marked `"copy"` are
  stream-copied.

### Inputs and progress

- **Memory inputs** - every read-side operation accepts
  `{:memory, binary}` in place of a filesystem path, backed by a custom
  `AVIOContextCustom` with read + seek callbacks so demuxers that seek
  (`mp4` `moov`, `matroska` cues) work without a temp file.
- **Progress callbacks** - `remux/3`, `extract_audio/3`, `concat/3`, and
  `transcode/3` accept `progress: pid()` and send throttled
  `{:exmpeg_progress, %{...}}` messages plus a final tick after
  `write_trailer`.

### Packaging

- Precompiled NIFs for `aarch64-apple-darwin`,
  `x86_64-unknown-linux-gnu`, and `aarch64-unknown-linux-gnu`; other
  targets build from source with `EXMPEG_BUILD=1`.
- The precompiled tarballs bundle an **LGPL-only** FFmpeg 8 (libmp3lame,
  libopus, libvpx; no `--enable-gpl`), so they redistribute cleanly
  under this package's MIT license. H.264 / H.265 software encoding
  (`libx264` / `libx265`, GPL) is not in the precompiled binaries and
  returns `:unsupported`; build from source against a GPL-enabled
  FFmpeg 8 to use them. H.264/H.265 decoding is unaffected.

### Safety

- The Rust crate is built on rsmpeg's safe wrappers with
  `#![deny(unsafe_code)]` at the root. All `unsafe` is confined to
  `ffi_helpers.rs`, each block carrying a `SAFETY:` comment.
- Every NIF entry point is wrapped in `run_with_panic_protection`, so a
  Rust panic surfaces as `{:error, %{type: "nif_panic"}}` instead of
  crashing the BEAM.
- Size options (`:width` / `:height` / `:sample_rate`) are bounded at
  the API boundary so an absurd value cannot trigger an out-of-memory
  allocation inside the NIF.
- Disk writes are atomic: operations write a `<stem>.partial.<ext>`
  sibling and rename onto the destination only after the muxer trailer
  is written; a mid-encode failure removes the partial.