selene-core 0.5.4

selene-core is the backend for Selene, a local-first music player
Documentation
use std::{
    fs,
    io::{self, Cursor},
    path::PathBuf,
};

use blake3::Hash;
use lofty::{file::TaggedFileExt, picture::PictureType};
use serde::{Deserialize, Serialize};

use crate::{
    cache_dir,
    library::image_art::{CacheableArt, ImageArt, cache_image},
};

#[derive(Debug, Deserialize, Serialize, Clone)]
pub enum CoverArt {
    Embedded { hash: Hash, source: PathBuf },
    File(ImageArt),
}

impl CoverArt {
    pub fn from_file(file: impl Into<PathBuf>) -> Result<Self, image::ImageError> {
        let image = Self::File(ImageArt::from_file(file)?);
        Ok(image)
    }
}

impl CacheableArt for CoverArt {
    fn cache_file(&self, x: u32, y: u32) -> io::Result<PathBuf> {
        match self {
            CoverArt::File(image_art) => image_art.cache_file(x, y),

            // TODO: Optimize speed of embedded cover art caching. Most likely with a dedicated loading thread
            CoverArt::Embedded { hash, source } => {
                let cover_dir = cache_dir().join("covers");
                let cache_file = cover_dir.join(format!("{id}_{x}_{x}.jpeg", id = hash));
                if cache_file.exists() {
                    return Ok(cache_file);
                }

                let mut file = fs::File::open(source)?;
                let mut tagged_file = lofty::read_from(&mut file)
                    .expect("Embedded art already has its metadata read");

                let tags = tagged_file.primary_tag_mut().unwrap();

                let picture_pos = tags
                    .pictures()
                    .iter()
                    .position(|p| p.pic_type() == PictureType::CoverFront)
                    .unwrap_or(0);

                let picture = tags.remove_picture(picture_pos);
                let format = match picture.mime_type().unwrap() {
                    lofty::picture::MimeType::Png => image::ImageFormat::Png,
                    lofty::picture::MimeType::Jpeg => image::ImageFormat::Jpeg,
                    lofty::picture::MimeType::Tiff => image::ImageFormat::Tiff,
                    lofty::picture::MimeType::Bmp => image::ImageFormat::Bmp,
                    lofty::picture::MimeType::Gif => image::ImageFormat::Gif,
                    _ => unreachable!("Embedded cover art is pre-validated on extract"),
                };

                let reader = Cursor::new(picture.into_data());
                let image = image::ImageReader::with_format(reader, format)
                    .decode()
                    .unwrap();

                cache_image(image, x, y, &cache_file)?;

                Ok(cache_file)
            }
        }
    }
}