use anyhow::anyhow; use log::warn; use std::io::Read; use std::path::Path; use std::process::Command; use anyhow::Result; use crate::common::get_static_dir; use super::Previewable; pub struct VideoPath<'a>(pub &'a Path); impl<'a> Previewable for VideoPath<'a> { fn get_thumbnail(&self) -> Result>> { let duration_cmd = Command::new("ffprobe") .args(["-v", "error"]) .args(["-show_entries", "format=duration"]) .args(["-of", "default=noprint_wrappers=1:nokey=1"]) .arg(self.0) .output()?; 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 play_button = get_static_dir("assets").map(|d| d.join("play.png")).ok(); let outfile = tempfile::Builder::new().suffix(".webp").tempfile()?; let thumbnail_cmd = Command::new("ffmpeg") .args(["-i", &self.0.to_string_lossy()]) .args(if let Some(play_button) = play_button { vec![ "-i".to_string(), play_button.to_string_lossy().to_string(), "-filter_complex".to_string(), "[0:v][1:v]overlay=(W-w)/2:(H-h)/2".to_string(), ] } else { warn!("Could not find play button to overlay for video thumbnail!"); vec![] }) .args(["-vframes", "1"]) .args(["-ss", &(duration / 2.0).to_string()]) .arg(&*outfile.path().to_string_lossy()) .arg("-y") .output()?; 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)) } }