# avio

[![Crates.io](https://img.shields.io/crates/v/avio.svg)](https://crates.io/crates/avio)
[![Docs.rs](https://docs.rs/avio/badge.svg)](https://docs.rs/avio)
[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](#license)

`avio` is the unified facade for the `ff-*` crate family. Depend on a single crate and opt in
to only the capabilities you need via feature flags.

> **Project status (as of 2026-03-26):** This crate is in an early phase. The high-level API is designed and reviewed by hand; AI is used as an accelerator to implement FFmpeg bindings efficiently. Code contributions are not expected at this time — questions, bug reports, and feature requests are welcome. See the [main repository](https://github.com/itsakeyfut/avio) for full context.

## Installation

```toml
[dependencies]
# Default: probe + decode + encode
avio = "0.13"

# Add filtering
avio = { version = "0.13", features = ["filter"] }

# Full stack (implies filter + pipeline)
avio = { version = "0.13", features = ["stream"] }

# Async decode/encode (requires tokio runtime)
avio = { version = "0.13", features = ["tokio"] }
```

## Feature Flags

| Feature    | Enables                                        | Default |
|------------|------------------------------------------------|---------|
| `probe`    | `ff-probe` — read-only media metadata          | yes     |
| `decode`   | `ff-decode` — video and audio decoding         | yes     |
| `encode`   | `ff-encode` — video and audio encoding         | yes     |
| `filter`   | `ff-filter` — filter graph operations          | no      |
| `pipeline` | `ff-pipeline` — decode → filter → encode       | no      |
| `stream`   | `ff-stream` — HLS / DASH streaming output      | no      |
| `tokio`    | Async wrappers for decode and encode           | no      |

## Quick Start

### Probe

```rust
use avio::open;

let info = open("video.mp4")?;
if let Some(video) = info.primary_video() {
    println!("{}x{} @ {:.2} fps ({:?})", video.width(), video.height(), video.fps(), video.codec());
}
```

### Decode

```rust
use avio::{VideoDecoder, AudioDecoder, PixelFormat, SampleFormat};

// Video — request RGB24 output (FFmpeg converts internally)
let mut vdec = VideoDecoder::open("video.mp4")
    .output_format(PixelFormat::Rgb24)
    .build()?;
while let Some(frame) = vdec.decode_one()? {
    // frame.data() contains raw pixel bytes
}

// Audio — resample to f32 at 44.1 kHz
let mut adec = AudioDecoder::open("audio.flac")
    .output_format(SampleFormat::F32)
    .output_sample_rate(44_100)
    .build()?;
while let Some(frame) = adec.decode_one()? {
    // frame.to_f32_interleaved() returns interleaved f32 samples
}
```

### Image Sequences

A `%`-pattern path (e.g. `frame%04d.png`) automatically selects the `image2`
demuxer for decode and muxer for encode:

```rust
use avio::{VideoDecoder, VideoEncoder, VideoCodec, HardwareAccel};

// Decode numbered PNGs → video
let mut decoder = VideoDecoder::open("frames/frame%04d.png")
    .hardware_accel(HardwareAccel::None)
    .frame_rate(25)
    .build()?;

// Encode video → numbered PNGs
let mut encoder = VideoEncoder::create("out/frame%04d.png")
    .video(1920, 1080, 25.0)
    .video_codec(VideoCodec::Png)
    .build()?;
```

### Encode

```rust
use avio::{VideoEncoder, VideoCodec, AudioCodec, BitrateMode};

let mut encoder = VideoEncoder::create("output.mp4")
    .video(1920, 1080, 30.0)
    .video_codec(VideoCodec::H264)
    .bitrate_mode(BitrateMode::Crf(23))
    .audio(48_000, 2)
    .audio_codec(AudioCodec::Aac)
    .build()?;

for frame in &video_frames {
    encoder.push_video(frame)?;
}
encoder.finish()?;
```

### Per-Codec Options

`VideoCodecOptions` and `AudioCodecOptions` provide typed, per-codec
configuration applied before the codec is opened:

```rust
use avio::{
    VideoEncoder, VideoCodec, VideoCodecOptions,
    H264Options, H264Profile, H264Preset,
    BitrateMode,
};

let opts = VideoCodecOptions::H264(H264Options {
    profile: H264Profile::High,
    level: Some(41),
    bframes: 2,
    gop_size: 250,
    refs: 3,
    preset: Some(H264Preset::Fast),
    tune: None,
});

let mut encoder = VideoEncoder::create("output.mp4")
    .video(1920, 1080, 30.0)
    .video_codec(VideoCodec::H264)
    .bitrate_mode(BitrateMode::Crf(23))
    .codec_options(opts)
    .build()?;
```

Available option structs: `H264Options`, `H265Options`, `Av1Options`,
`SvtAv1Options`, `Vp9Options`, `ProResOptions`, `DnxhdOptions`,
`OpusOptions`, `AacOptions`, `Mp3Options`, `FlacOptions`.

### Professional Formats

```rust
use avio::{
    VideoEncoder, VideoCodec, VideoCodecOptions,
    ProResOptions, ProResProfile, PixelFormat,
};

let mut encoder = VideoEncoder::create("output.mov")
    .video(1920, 1080, 25.0)
    .video_codec(VideoCodec::ProRes)
    .pixel_format(PixelFormat::Yuv422p10le)
    .codec_options(VideoCodecOptions::ProRes(ProResOptions {
        profile: ProResProfile::Hq,
        vendor: None,
    }))
    .build()?;
```

### HDR Metadata

```rust
use avio::{
    VideoEncoder, VideoCodec, VideoCodecOptions,
    H265Options, H265Profile, PixelFormat, BitrateMode,
    Hdr10Metadata, MasteringDisplay, ColorTransfer,
};

// HDR10 — PQ transfer + MaxCLL/MaxFALL side data
let mut encoder = VideoEncoder::create("output.mkv")
    .video(3840, 2160, 24.0)
    .video_codec(VideoCodec::H265)
    .bitrate_mode(BitrateMode::Crf(22))
    .pixel_format(PixelFormat::Yuv420p10le)
    .codec_options(VideoCodecOptions::H265(H265Options {
        profile: H265Profile::Main10,
        ..H265Options::default()
    }))
    .hdr10_metadata(Hdr10Metadata {
        max_cll: 1000, max_fall: 400,
        mastering_display: MasteringDisplay {
            red_x: 17000, red_y: 8500,
            green_x: 13250, green_y: 34500,
            blue_x: 7500, blue_y: 3000,
            white_x: 15635, white_y: 16450,
            min_luminance: 50,
            max_luminance: 10_000_000,
        },
    })
    .build()?;

// HLG — broadcast HDR without MaxCLL/MaxFALL
use avio::{ColorSpace, ColorPrimaries};
let mut encoder = VideoEncoder::create("output.mkv")
    .video(1920, 1080, 50.0)
    .video_codec(VideoCodec::H265)
    .pixel_format(PixelFormat::Yuv420p10le)
    .color_transfer(ColorTransfer::Hlg)
    .color_space(ColorSpace::Bt2020)
    .color_primaries(ColorPrimaries::Bt2020)
    .build()?;
```

### Async (tokio feature)

```rust
use avio::{AsyncVideoDecoder, AsyncVideoEncoder, VideoEncoder, VideoCodec};
use futures::StreamExt;

let mut encoder = AsyncVideoEncoder::from_builder(
    VideoEncoder::create("output.mp4")
        .video(1920, 1080, 30.0)
        .video_codec(VideoCodec::H264),
)?;

let stream = AsyncVideoDecoder::open("input.mp4").await?.into_stream();
tokio::pin!(stream);
while let Some(Ok(frame)) = stream.next().await {
    encoder.push(frame).await?;
}
encoder.finish().await?;
```

### Pipeline (pipeline feature)

```rust
use avio::{Pipeline, EncoderConfig, VideoCodec, AudioCodec, BitrateMode};

Pipeline::builder()
    .input("input.mp4")
    .output("output.mp4", EncoderConfig::builder()
        .video_codec(VideoCodec::H264)
        .audio_codec(AudioCodec::Aac)
        .bitrate_mode(BitrateMode::Crf(23))
        .build())
    .build()?
    .run()?;
```

## Crate Family

| Crate         | Purpose                                        |
|---------------|------------------------------------------------|
| `ff-sys`      | Raw bindgen FFI — internal use only            |
| `ff-common`   | Shared buffer-pooling abstractions             |
| `ff-format`   | Pure-Rust type definitions (no FFmpeg linkage) |
| `ff-probe`    | Read-only media metadata extraction            |
| `ff-decode`   | Video and audio decoding                       |
| `ff-encode`   | Video and audio encoding                       |
| `ff-filter`   | Filter graph operations                        |
| `ff-pipeline` | Decode → filter → encode pipeline              |
| `ff-stream`   | HLS / DASH adaptive streaming output           |
| `avio`        | Unified facade (this crate)                    |

## MSRV

Rust 1.93.0 (edition 2024).

## License

MIT OR Apache-2.0
