use std::{
fs,
path::{Path, PathBuf},
process::Command,
};
use blake3::{Hash, hash};
use serde::{Deserialize, Serialize};
use crate::{
base_dirs,
errors::FfmpegError,
ffmpeg::{command_mutators::FfmpegPresets, ffmpeg, output_ffmpeg},
};
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
pub struct CoverArt {
hash: Hash,
path: PathBuf,
}
impl CoverArt {
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, FfmpegError> {
let path = path.as_ref();
Ok(Self {
hash: hash_video_stream(path)?,
path: path.to_path_buf(),
})
}
#[must_use]
pub fn hash(&self) -> Hash {
self.hash
}
#[must_use]
pub fn source(&self) -> &Path {
self.path.as_path()
}
}
pub fn has_video_stream(path: impl AsRef<Path>) -> bool {
let path = path.as_ref();
let output = Command::new("ffprobe")
.args([
"-v",
"error",
"-select_streams",
"v",
"-show_entries",
"stream=index",
"-of",
"csv=p=0",
path.to_str().unwrap(),
])
.output();
let Ok(output) = output else { return false };
!output.stdout.is_empty()
}
pub fn get_or_cache_cover_art(source: &CoverArt, size: usize) -> Result<PathBuf, FfmpegError> {
let cover_dir = base_dirs().cache_dir().join("covers");
fs::create_dir_all(&cover_dir)?;
let scale = format!("scale={size}:{size}:flags=lanczos");
let output_file = cover_dir.join(format!("{id}_{size}_{size}.webp", id = source.hash()));
let mut command = Command::new("ffmpeg");
command.args(["-v", "error", "-i"]);
command.arg(source.source());
command.args(["-map", "0:v:0", "-frames:v", "1", "-filter-v"]);
command.arg(scale);
command.args([
"-c:v",
"libwebp",
"-qscale:v",
"80",
"-compression_level",
"6",
"-y",
]);
command.arg(output_file.as_path());
let output = command.output()?;
if !output.status.success() {
let err = String::from_utf8_lossy(&output.stderr);
return Err(FfmpegError::FfmpegWrapperError(err.into()));
}
Ok(output_file)
}
pub fn hash_video_stream(path: impl AsRef<Path>) -> Result<Hash, FfmpegError> {
let path = path.as_ref();
let mut command = ffmpeg();
command.input_file(path);
command.args(["-map", "0:v:0", "-c", "copy", "-f", "rawvideo", "-"]);
let result = output_ffmpeg(command)?;
Ok(hash(result.as_bytes()))
}