use anyhow::anyhow; use anyhow::Result; use std::collections::HashMap; use std::io::Read; use std::path::Path; use std::process::Command; use tracing::{debug, trace}; use super::Previewable; pub struct VideoPath<'a>(pub &'a Path); impl<'a> Previewable for VideoPath<'a> { fn get_thumbnail(&self, options: HashMap) -> Result>> { let mut ffprobe = Command::new("ffprobe"); let command = ffprobe .args(["-threads", "1"]) .args(["-v", "error"]) .args(["-show_entries", "format=duration"]) .args(["-of", "default=noprint_wrappers=1:nokey=1"]) .arg(self.0); trace!("Running `{:?}`", command); let now = std::time::Instant::now(); let duration_cmd = command.output()?; debug!("Ran `{:?}`, took {}s", command, now.elapsed().as_secs_f32()); if !duration_cmd.status.success() { return Err(anyhow!( "Failed to retrieve file duration: {:?}", String::from_utf8_lossy(&duration_cmd.stderr) )); } let duration = String::from_utf8_lossy(&duration_cmd.stdout) .trim() .parse::()?; let position = { if let Some(str_position) = options.get("position") { str_position.parse()? } else { 90.0f64 } }; let outfile = tempfile::Builder::new().suffix(".webp").tempfile()?; let mut ffmpeg = Command::new("ffmpeg"); let command = ffmpeg .args(["-threads", "1"]) .args(["-discard", "nokey"]) .args(["-noaccurate_seek"]) .args(["-ss", &(position.min(duration / 2.0)).to_string()]) .args(["-i", &self.0.to_string_lossy()]) .args(["-vframes", "1"]) .args(["-vsync", "passthrough"]) .arg(&*outfile.path().to_string_lossy()) .arg("-y"); trace!("Running `{:?}`", command); let now = std::time::Instant::now(); let thumbnail_cmd = command.output()?; debug!("Ran `{:?}`, took {}s", command, now.elapsed().as_secs_f32()); if !thumbnail_cmd.status.success() { return Err(anyhow!( "Failed to render thumbnail: {:?}", String::from_utf8_lossy(&thumbnail_cmd.stderr) )); } let mut buffer = Vec::new(); outfile.as_file().read_to_end(&mut buffer)?; Ok(Some(buffer)) } }