diff --git a/packages/scanner/src/error.rs b/packages/scanner/src/error.rs index dafbd940cb..ed462b4fd9 100644 --- a/packages/scanner/src/error.rs +++ b/packages/scanner/src/error.rs @@ -26,3 +26,16 @@ impl fmt::Display for MetadataError { write!(f, "MetadataError: {}", self.message) } } + +#[derive(Debug)] +pub struct ThumbnailError { + pub message: String, +} + +impl Error for ThumbnailError {} + +impl fmt::Display for ThumbnailError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ThumbnailError: {}", self.message) + } +} diff --git a/packages/scanner/src/metadata.rs b/packages/scanner/src/metadata.rs index d39ed283c6..db4bbf3e62 100644 --- a/packages/scanner/src/metadata.rs +++ b/packages/scanner/src/metadata.rs @@ -1,7 +1,12 @@ use id3::TagLike; use metaflac; +use neon::meta; -use crate::{error::MetadataError, thumbnails::generate_thumbnail}; +use crate::{ + error::MetadataError, + thumbnails::ThumbnailGenerator, + thumbnails::{FlacThumbnailGenerator, Mp3ThumbnailGenerator}, +}; pub struct AudioMetadata { pub artist: Option, @@ -56,7 +61,7 @@ impl MetadataExtractor for Mp3MetadataExtractor { metadata.position = tag.track(); metadata.disc = tag.disc(); metadata.year = tag.year().map(|s| s as u32); - metadata.thumbnail = generate_thumbnail(&path, thumbnails_dir); + metadata.thumbnail = Mp3ThumbnailGenerator::generate_thumbnail(&path, thumbnails_dir); Ok(metadata) } @@ -106,7 +111,7 @@ impl MetadataExtractor for FlacMetadataExtractor { metadata.year = Self::extract_numeric_metadata(&tag, "DATE"); let thumbnail_content = tag.pictures().next().map(|p| p.data.clone()).unwrap(); - //TODO: add thumbnail generation + metadata.thumbnail = FlacThumbnailGenerator::generate_thumbnail(&path, thumbnails_dir); Ok(metadata) } diff --git a/packages/scanner/src/thumbnails.rs b/packages/scanner/src/thumbnails.rs index bd7951c30d..95b133346f 100644 --- a/packages/scanner/src/thumbnails.rs +++ b/packages/scanner/src/thumbnails.rs @@ -1,20 +1,34 @@ use id3::Tag; use image::{imageops::resize, imageops::FilterType, io::Reader as ImageReader, ImageFormat}; use md5; -use std::io::Cursor; +use metaflac; +use std::io::{self, Cursor}; use std::path::{Path, PathBuf}; -fn hash_thumb_filename(path: &str) -> String { - let filename = Path::new(path).file_name().unwrap(); +use crate::error::ThumbnailError; + +pub trait ThumbnailGenerator { + fn generate_thumbnail(filename: &str, thumbnails_dir: &str) -> Option; +} + +fn hash_thumb_filename(path: &str) -> Result { + let filename = Path::new(path).file_name().ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("Invalid path: {}", path), + ) + })?; let hash = md5::compute(filename.to_string_lossy().as_bytes()); - format!("{:x}.webp", hash) + Ok(format!("{:x}.webp", hash)) } -pub fn create_thumbnails_dir(thumbnails_dir: &str) { +pub fn create_thumbnails_dir(thumbnails_dir: &str) -> io::Result<()> { let thumbnails_dir_path = Path::new(thumbnails_dir); if !thumbnails_dir_path.exists() { - std::fs::create_dir(thumbnails_dir_path).unwrap(); + std::fs::create_dir(thumbnails_dir_path) + } else { + Ok(()) } } @@ -24,36 +38,85 @@ fn url_path_from_path(path: &str) -> String { format!("file://{}", path) } -pub fn generate_thumbnail(filename: &str, thumbnails_dir: &str) -> Option { - let mut thumbnail_path = PathBuf::from(thumbnails_dir); +pub struct Mp3ThumbnailGenerator; +impl ThumbnailGenerator for Mp3ThumbnailGenerator { + fn generate_thumbnail(filename: &str, thumbnails_dir: &str) -> Option { + let mut thumbnail_path = PathBuf::from(thumbnails_dir); - thumbnail_path.push(hash_thumb_filename(filename)); + let thumbnail_filename = hash_thumb_filename(filename); - let thumbnail_path_str = thumbnail_path.to_str().unwrap(); + let thumbnail_filename = match thumbnail_filename { + Ok(filename) => filename, + Err(e) => return None, + }; + thumbnail_path.push(thumbnail_filename); - if Path::new(thumbnail_path_str).exists() { - return Some(url_path_from_path(thumbnail_path_str)); - } + let thumbnail_path_str = thumbnail_path.to_str()?; - let tag = Tag::read_from_path(filename).unwrap(); - let thumbnail = tag - .pictures() - .find(|p| p.picture_type == id3::frame::PictureType::CoverFront) - .map(|p| p.data.clone()); - - if let Some(thumbnail) = thumbnail { - let img = ImageReader::new(Cursor::new(&thumbnail)) - .with_guessed_format() - .unwrap() - .decode() - .unwrap(); - - let img = resize(&img, 192, 192, FilterType::Lanczos3); - img.save_with_format(thumbnail_path_str, ImageFormat::WebP) - .unwrap(); - } else { - return None; + if thumbnail_path.exists() { + return Some(url_path_from_path(thumbnail_path_str)); + } + + let tag = Tag::read_from_path(filename).unwrap(); + let thumbnail = tag + .pictures() + .find(|p| p.picture_type == id3::frame::PictureType::CoverFront) + .map(|p| p.data.clone()); + + if let Some(thumbnail) = thumbnail { + let img = ImageReader::new(Cursor::new(&thumbnail)) + .with_guessed_format() + .unwrap() + .decode() + .unwrap(); + + let img = resize(&img, 192, 192, FilterType::Lanczos3); + img.save_with_format(thumbnail_path_str, ImageFormat::WebP) + .unwrap(); + } else { + return None; + } + + Some(url_path_from_path(thumbnail_path_str)) } +} + +pub struct FlacThumbnailGenerator; +impl ThumbnailGenerator for FlacThumbnailGenerator { + fn generate_thumbnail(filename: &str, thumbnails_dir: &str) -> Option { + let mut thumbnail_path = PathBuf::from(thumbnails_dir); + + let thumbnail_filename = hash_thumb_filename(filename); + + let thumbnail_filename = match thumbnail_filename { + Ok(filename) => filename, + Err(e) => return None, + }; + thumbnail_path.push(thumbnail_filename); + + let thumbnail_path_str = thumbnail_path.to_str()?; - Some(url_path_from_path(thumbnail_path_str)) + if thumbnail_path.exists() { + return Some(url_path_from_path(thumbnail_path_str)); + } + + let tag = metaflac::Tag::read_from_path(filename).unwrap(); + let thumbnail = tag.pictures().next().map(|p| p.data.clone()); + + if let Some(thumbnail) = thumbnail { + let img = ImageReader::new(Cursor::new(&thumbnail)) + .with_guessed_format() + .unwrap() + .decode() + .unwrap(); + + let img = resize(&img, 192, 192, FilterType::Lanczos3); + img.save_with_format(thumbnail_path_str, ImageFormat::WebP) + .unwrap(); + } else { + return None; + } + + Some(url_path_from_path(thumbnail_path_str)) + } }