Skip to content

Commit

Permalink
Generate FLAC thumbnails
Browse files Browse the repository at this point in the history
  • Loading branch information
nukeop committed Nov 10, 2023
1 parent 00144d4 commit d3dd1fb
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 35 deletions.
13 changes: 13 additions & 0 deletions packages/scanner/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
11 changes: 8 additions & 3 deletions packages/scanner/src/metadata.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
127 changes: 95 additions & 32 deletions packages/scanner/src/thumbnails.rs
Original file line number Diff line number Diff line change
@@ -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<String>;
}

fn hash_thumb_filename(path: &str) -> Result<String, io::Error> {
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(())
}
}

Expand All @@ -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<String> {
let mut thumbnail_path = PathBuf::from(thumbnails_dir);
pub struct Mp3ThumbnailGenerator;
impl ThumbnailGenerator for Mp3ThumbnailGenerator {
fn generate_thumbnail(filename: &str, thumbnails_dir: &str) -> Option<String> {
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<String> {
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))
}
}

0 comments on commit d3dd1fb

Please sign in to comment.