# 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.