lumen-engine-ffmpeg 0.2.0

FFmpeg integration for media decode, encode, muxing, and GPU interop in Lumen.
Documentation
use std::{env, path::PathBuf, time::Instant};

use lumen_ffmpeg::{
    DecodeMode, InputContext, MuxedEncoder, VideoCodec, VideoDecoder, VideoDecoderConfig,
    VideoEncoderConfig,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = env::args().skip(1);
    let input_path = args
        .next()
        .map(PathBuf::from)
        .ok_or("usage: transcode_file <input> <output> [max_frames] [encoder]")?;
    let output_path = args
        .next()
        .map(PathBuf::from)
        .ok_or("missing output path")?;
    let max_frames = args.next().and_then(|value| value.parse::<usize>().ok());
    let encoder_name = args.next();

    let started = Instant::now();
    let mut input = InputContext::open(input_path.to_string_lossy().to_string())?;
    let stream = input.best_video_stream()?;
    let mut decoder = VideoDecoder::open(
        &input,
        VideoDecoderConfig {
            stream_index: stream.stream_index,
            mode: DecodeMode::Cpu,
        },
    )?;
    let fps = stream
        .avg_frame_rate
        .as_f64()
        .filter(|fps| *fps > 0.0)
        .unwrap_or(60.0)
        .round()
        .clamp(1.0, u32::MAX as f64) as u32;
    let mut config =
        VideoEncoderConfig::cpu_rgba(stream.width, stream.height, fps, VideoCodec::H264);
    if let Some(encoder_name) = encoder_name {
        config.encoder_name = Some(encoder_name);
    }
    let mut encoder = MuxedEncoder::create(output_path.to_string_lossy().to_string(), config)?;

    let mut frames = 0_usize;
    'decode: while let Some(packet) = input.read_packet()? {
        decoder.send_packet(&packet)?;
        while let Some(frame) = decoder.receive_cpu_frame()? {
            encoder.write_video_frame(&frame)?;
            frames = frames.saturating_add(1);
            if max_frames.is_some_and(|limit| frames >= limit) {
                break 'decode;
            }
        }
    }

    if max_frames.is_none_or(|limit| frames < limit) {
        decoder.send_eof()?;
        while let Some(frame) = decoder.receive_cpu_frame()? {
            encoder.write_video_frame(&frame)?;
            frames = frames.saturating_add(1);
            if max_frames.is_some_and(|limit| frames >= limit) {
                break;
            }
        }
    }

    encoder.finish()?;
    println!("input={}", input_path.display());
    println!("output={}", output_path.display());
    println!("frames={frames}");
    println!("elapsed_ms={}", started.elapsed().as_millis());
    println!(
        "fps={:.2}",
        frames as f64 / started.elapsed().as_secs_f64().max(1e-9)
    );
    Ok(())
}