pub mod args;
#[doc(hidden)]
pub mod container_defaults;
#[doc(hidden)]
pub mod frame_tap;
#[cfg(feature = "ico")]
#[doc(hidden)]
pub mod ico_runner;
#[doc(hidden)]
pub mod image_sink;
#[cfg(feature = "mesh3d")]
#[doc(hidden)]
pub mod mesh3d_render;
#[cfg(feature = "mesh3d")]
#[doc(hidden)]
pub mod mesh3d_runner;
pub mod op;
#[doc(hidden)]
pub mod pdf_runner;
#[doc(hidden)]
pub mod ping;
#[doc(hidden)]
pub mod pixel_xform;
pub mod plan_to_job;
#[doc(hidden)]
pub mod probe;
#[doc(hidden)]
pub mod raster_io;
pub mod route;
#[doc(hidden)]
pub mod suggest;
mod timing {
use std::time::Duration;
pub fn enabled() -> bool {
std::env::var_os("OXIDEAV_CONVERT_TIMING").is_some_and(|v| !v.is_empty() && v != "0")
}
pub fn report(spans: &[(&str, Duration)]) {
for (stage, d) in spans {
eprintln!(
"convert: timing: {stage:<44} {:>9.1} ms",
d.as_secs_f64() * 1000.0
);
}
}
}
pub use op::{AlphaOp, ConvertPlan, Dither, Op, PrintfTemplate, ResizeMode};
pub use route::Route;
use oxideav_core::{Error, RuntimeContext};
use oxideav_pipeline::CodecPreferences;
pub fn run(args: &[String], ctx: &RuntimeContext) -> Result<(), Error> {
run_with_preferences(args, ctx, &CodecPreferences::default())
}
pub fn run_with_preferences(
args: &[String],
ctx: &RuntimeContext,
prefs: &CodecPreferences,
) -> Result<(), Error> {
let plan = args::parse(args)?;
match route::decide_with(&plan, Some(ctx))? {
Route::Help => {
print!("{}", args::usage());
Ok(())
}
Route::Ping => ping::run(&plan, ctx),
Route::Probe => probe::run(&plan, ctx),
Route::PdfSideChannel => pdf_runner::run(&plan, ctx),
#[cfg(feature = "mesh3d")]
Route::Mesh3dToMesh3d => {
if !plan.ops.is_empty() {
eprintln!(
"convert: note: raster ops ignored on 3D-asset conversion (stl/obj/gltf/glb/mtl/usdz have no pixel grid)"
);
}
mesh3d_runner::run(&plan.input, &plan.output, &plan.mesh3d_options)
}
#[cfg(feature = "mesh3d")]
Route::Mesh3dToRaster => mesh3d_render::run(
ctx,
&plan.input,
&plan.output,
&plan.ops,
&plan.mesh3d_options,
),
#[cfg(feature = "ico")]
Route::IcoOutput => ico_runner::run(&plan, ctx),
Route::Pipeline => {
let timing = timing::enabled();
let t0 = std::time::Instant::now();
let planned = plan_to_job::plan_pipeline(&plan, ctx)?;
let t_plan = t0.elapsed();
for note in &planned.notes {
eprintln!("{note}");
}
match planned.tap {
None => {
let t1 = std::time::Instant::now();
let stats = oxideav_pipeline::Executor::new(&planned.job, ctx)
.with_codec_preferences(prefs.clone())
.run()?;
eprintln!(
"convert: {} packet(s) read, {} frame(s) decoded, {} frame(s) written",
stats.packets_read, stats.frames_decoded, stats.frames_written
);
if timing {
timing::report(&[
("plan (incl. input header probe)", t_plan),
("pipeline (demux+decode+convert+encode+mux)", t1.elapsed()),
("total", t0.elapsed()),
]);
}
}
Some(tap) => {
let t1 = std::time::Instant::now();
let (stats, written) = frame_tap::run(&planned.job, ctx, &tap, prefs)?;
eprintln!(
"convert: {} packet(s) read, {} frame(s) decoded, {} file(s) written",
stats.packets_read, stats.frames_decoded, written.files
);
if timing {
let run = t1.elapsed();
let first = written.first_frame.unwrap_or_default();
timing::report(&[
("plan (incl. input header probe)", t_plan),
("first frame (demux+decode+filters+convert)", first),
("encode", written.encode),
("mux + write", written.mux),
("run", run),
("total", t0.elapsed()),
]);
}
}
}
Ok(())
}
}
}