use std::env;
use std::error::Error;
use vtome::geometry::Rect;
use vtome::window::Viewer;
use vtome::{Fit, MonitorSelector, Placement, VideoSource};
fn main() -> Result<(), Box<dyn Error>> {
let Some(path) = env::args().nth(1).or_else(|| setting("FILE")) else {
eprintln!("usage: play <video>");
eprintln!(" make play FILE=clip.mp4 MONITOR=1 AREA=40,40,640,360");
std::process::exit(2);
};
let source = VideoSource::from_file(&path)?;
if let Some(track) = source.info().video() {
println!(
"{path}: {}×{} {}, {} decode",
track.width,
track.height,
source.decoder().encoding(),
if source.decoder().is_hardware() {
"hardware"
} else {
"software"
}
);
}
let selector = match setting("MONITOR") {
Some(value) => match value.parse::<usize>() {
Ok(index) => MonitorSelector::Index(index),
Err(_) => MonitorSelector::Name(value),
},
None => MonitorSelector::Primary,
};
let mut placement = Placement::new(selector)
.fit(Fit::Contain)
.always_on_top(true);
if let Some(area) = setting("AREA") {
let numbers: Vec<f64> = area
.split(',')
.map(|part| part.trim().parse())
.collect::<Result<_, _>>()?;
let [x, y, width, height] = numbers[..] else {
return Err(format!("AREA is x,y,width,height; got {area}").into());
};
placement = placement.area(Rect::new(x, y, width, height));
}
if let Some(inset) = setting("KEYSTONE") {
placement = placement.keystone(inset.parse()?);
}
if let Some(opacity) = setting("OPACITY") {
placement = placement.opacity(opacity.parse()?);
}
let click_through = setting("CLICK_THROUGH").is_some();
let once = setting("ONCE").is_some();
println!(
"{}{}",
if click_through {
"Clicks pass through it."
} else {
"Escape to quit, Space to pause."
},
if once { " Closes when the film ends." } else { "" }
);
let report = Viewer::video(source, placement)
.title(format!("vtome — {path}"))
.click_through(click_through)
.looping(!once)
.show()?;
println!(
"{} frames shown, {} dropped, {} refreshes repeated, {} loop{}, {} decode",
report.presented,
report.dropped,
report.repeated,
report.loops,
if report.loops == 1 { "" } else { "s" },
match report.hardware_decode {
Some(true) => "hardware",
Some(false) => "software",
None => "no",
}
);
Ok(())
}
fn setting(name: &str) -> Option<String> {
env::var(name).ok().filter(|value| !value.is_empty())
}