use std::path::{Path, PathBuf};
use std::process::Command;
use super::engine::CacheError;
const THUMBNAIL_SIZE: u32 = 224;
const THUMBNAIL_NAME_LEN: usize = 16;
pub(crate) fn generate_image_thumbnail(src: &Path, dest: &Path) -> Result<(), CacheError> {
ensure_parent(dest)?;
let img = image::open(src).map_err(|e| CacheError::ThumbnailGenerationFailed(e.to_string()))?;
let thumb = img.resize(
THUMBNAIL_SIZE,
THUMBNAIL_SIZE,
image::imageops::FilterType::Lanczos3,
);
thumb
.save(dest)
.map_err(|e| CacheError::ThumbnailGenerationFailed(e.to_string()))?;
Ok(())
}
pub(crate) fn generate_video_thumbnail(
src: &Path,
dest: &Path,
ffmpeg_path: &Path,
) -> Result<(), CacheError> {
ensure_parent(dest)?;
if run_ffmpeg(ffmpeg_path, src, dest, "00:00:05").is_ok() && dest.exists() {
return Ok(());
}
if run_ffmpeg(ffmpeg_path, src, dest, "00:00:00").is_ok() && dest.exists() {
return Ok(());
}
Err(CacheError::ThumbnailGenerationFailed(format!(
"ffmpeg failed to extract frame from {}",
src.display()
)))
}
fn run_ffmpeg(
ffmpeg_path: &Path,
src: &Path,
dest: &Path,
timestamp: &str,
) -> Result<(), CacheError> {
let output = Command::new(ffmpeg_path)
.args([
"-ss",
timestamp,
"-i",
src.to_str().unwrap_or(""),
"-vframes",
"1",
"-vf",
&format!(
"scale={THUMBNAIL_SIZE}:{THUMBNAIL_SIZE}:force_original_aspect_ratio=decrease"
),
"-y", dest.to_str().unwrap_or(""),
])
.output()
.map_err(|e| CacheError::ThumbnailGenerationFailed(e.to_string()))?;
if output.status.success() {
Ok(())
} else {
Err(CacheError::ThumbnailGenerationFailed(
String::from_utf8_lossy(&output.stderr).to_string(),
))
}
}
pub(crate) fn thumbnail_dest(thumbnail_dir: &Path, src: &Path) -> Result<PathBuf, CacheError> {
let canonical = src.canonicalize().map_err(|e| CacheError::io(src, e))?;
Ok(thumbnail_dest_for_canonical(
thumbnail_dir,
&canonical.to_string_lossy(),
))
}
pub(crate) fn thumbnail_dest_for_canonical(thumbnail_dir: &Path, canonical: &str) -> PathBuf {
let hash = blake3::hash(canonical.as_bytes());
let hex = hash.to_hex();
thumbnail_dir.join(format!("{}.jpg", &hex.as_str()[..THUMBNAIL_NAME_LEN]))
}
fn ensure_parent(path: &Path) -> Result<(), CacheError> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| CacheError::io(parent, e))?;
}
Ok(())
}