ffcine 0.1.0

GPU video finishing: decode, grade (LUTs, tone mapping, scaling) and encode with frames kept in VRAM (facade over the ffcine-* crates)
//! ffcine: GPU video finishing. Decode, grade (3D LUTs, tone and gamut mapping, crop/scale, user
//! shaders) and encode, with frames kept in VRAM end to end.
//!
//! The types here ([`Pipeline`], [`Grade`], [`Lut`], [`ColorSpace`], [`EncoderSettings`]) are
//! backend-neutral; [`backend::Ffmpeg`] runs them on ffmpeg's libav* and libplacebo/Vulkan.
//!
//! ```no_run
//! use ffcine::{Backend, Grade, Lut, LutStage, LutType, Pipeline, backend};
//!
//! let ff = backend::Ffmpeg::new()?;
//! println!("{}", ff.probe("in.mov".as_ref())?);
//! let grade = Grade {
//!     lut: Some(LutStage { lut: Lut::from_cube_file("grade.cube")?, kind: LutType::Normalized }),
//!     ..Grade::default()
//! };
//! Pipeline::new("in.mov", "out.mkv").grade(grade).run(&ff)?;
//! # Ok::<(), ffcine::Error>(())
//! ```

//!
//! This crate is a facade: each part lives in its own crate (`docs/design.md#crates`) and is
//! re-exported here under the module names it always had.

pub use ffcine_assembly as assembly;
pub use ffcine_audio as audio;
pub use ffcine_core::{ColorSpace, Error, Lut, LutType, MediaInfo, Rational, Result};
pub use ffcine_core::{camera, color, geom, lut, probe};
pub use ffcine_delivery as delivery;
pub use ffcine_filters as filters;
pub use ffcine_filters::{Denoise, Grain, Spatial, Temporal};
pub use ffcine_hooks as hooks;
pub use ffcine_import as import;
pub use ffcine_ingest as ingest;
pub use ffcine_reframe as reframe;
pub use ffcine_render::{
    Backend, Codec, ColorMapTuning, ColorOverride, Crop, Deband, Decode, DeliveryProfile, Device,
    Dither, DitherMethod, EncoderSettings, GamutMapping, Grade, Hook, Image, Layer, LayerSource,
    LutStage, Mask, Overlay, OverlayPart, ParamTrack, Pipeline, PixelFormat, RateControl, RunStats,
    Scaler, Scaling, Size, Stage, ToneMapping, Warp,
};
pub use ffcine_render::{backend, encode, grade, layer, overlay, pipeline, profile, shaders};
pub use ffcine_stabilize as stabilize;
pub use ffcine_text as text;
pub use ffcine_timeline as timeline;
pub use ffcine_triage as triage;

/// Stabilizes a pipeline's shot in its grade pass: GPU detect over the pipeline's range (cached
/// unless `cache` is false), smoothing and zoom per `params`, and the warps set on the first
/// grade stage (one is added if there is none). The range's rate is the pipeline's output rate,
/// else the source's (probed on `backend`); with no `frames` it runs to the end of the source.
///
/// This is what `ffcine grade --stabilize` and a timeline clip's `ffcine.fx.stabilize/1`
/// ([`stabilize::fx::params`]) do.
#[cfg(feature = "nvof")]
pub fn stabilize_pipeline(
    backend: &dyn Backend,
    pipeline: &mut Pipeline,
    params: &stabilize::StabilizeParams,
    cache: bool,
) -> Result<(stabilize::Stabilization, bool)> {
    let start = pipeline.start.as_secs_f64();
    let (rate, frames) = match (pipeline.frame_rate, pipeline.frames) {
        (Some(r), Some(n)) => (r, n),
        (r, n) => {
            let info = backend.probe(&pipeline.input)?;
            let rate = r
                .or_else(|| info.video().and_then(|(_, v)| v.frame_rate))
                .ok_or_else(|| Error::Pipeline("stabilize: source has no frame rate".into()))?;
            let n = n.unwrap_or_else(|| {
                let left = info.duration.map_or(0.0, |d| d.as_secs_f64()) - start;
                #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
                let n = (left.max(0.0) * rate.as_f64()).round() as u64;
                n
            });
            (rate, n)
        }
    };
    let range = stabilize::SourceRange {
        start,
        frames: usize::try_from(frames).unwrap_or(usize::MAX),
        rate,
    };
    let detect = stabilize::DetectParams::default();
    let (det, cached) = if cache {
        stabilize::detect_cached(&pipeline.input, &range, &detect)?
    } else {
        (stabilize::detect(&pipeline.input, &range, &detect)?, false)
    };
    let st = stabilize::stabilize(&det, params);
    let warp = Some(Warp {
        transforms: st.transforms.clone(),
    });
    if let Some(Stage::Grade(g)) = pipeline.stages.first_mut() {
        g.warp = warp;
    } else {
        pipeline.stages.insert(
            0,
            Stage::Grade(Grade {
                warp,
                ..Grade::default()
            }),
        );
    }
    Ok((st, cached))
}