selene-core 0.8.1

selene-core is the backend for Selene, a local-first music player
Documentation
use std::{ops::Deref, str::FromStr, sync::Arc};

use crate::{
    database::LibraryDb,
    library::{
        album::Album, artist::Artist, loudnorm::LoudnormAnalysis, track::track_meta::TrackMeta,
    },
    media_container::MediaContainer,
};
use blake3::Hash;
use lunar_lib::database::EntryId;
use serde::{Deserialize, Serialize};

pub(crate) mod internal;

pub mod core_impls;
pub mod frontend_impls;
pub mod trait_impls;

pub mod cover_art;
pub mod lyric_data;
pub mod track_meta;

pub const UNKNOWN_TITLE: &str = "UNKNOWN TITLE";

/// Track root. Defines file info, container info, metadata info
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Track {
    id: TrackId,
    pub(crate) container: MediaContainer,

    pub(crate) metadata: TrackMeta,

    pub(crate) loudnorm_analysis: Option<LoudnormAnalysis>,
}

#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TrackId(Hash);

impl EntryId for TrackId {
    type Entry = Track;
    type IdDb = LibraryDb;
}

impl Deref for TrackId {
    type Target = [u8; 32];

    fn deref(&self) -> &Self::Target {
        self.0.as_bytes()
    }
}

impl FromStr for TrackId {
    type Err = <Hash as FromStr>::Err;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self(Hash::from_str(s)?))
    }
}

impl TrackId {
    pub(crate) fn new(id: Hash) -> Self {
        Self(id)
    }

    #[must_use]
    pub fn to_hash(&self) -> Hash {
        self.0
    }

    #[must_use]
    pub fn to_selene_id(&self) -> String {
        format!("track:{}", self.0)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedTrack {
    pub(crate) track: Arc<Track>,

    pub(crate) album: Option<Arc<Album>>,
    pub(crate) album_artists: Option<Vec<Arc<Artist>>>,

    pub(crate) artists: Vec<Arc<Artist>>,
}

impl ResolvedTrack {
    #[must_use] 
    pub fn album_info(&self) -> Option<(&Arc<Album>, &[Arc<Artist>], Option<u32>, Option<u32>)> {
        let album = self.album.as_ref()?;
        let artists = self
            .album_artists
            .as_ref()
            .expect("Resolved track album artists must be some if album is some");

        let (track_num, disc_num) = album
            .tracks
            .iter()
            .find_map(|t| (t.id == self.id).then_some((t.track_num, t.disc_num)))
            .unwrap();

        Some((album, artists, track_num, disc_num))
    }

    #[must_use] 
    pub fn artists(&self) -> &[Arc<Artist>] {
        &self.artists
    }
}

impl Deref for ResolvedTrack {
    type Target = Track;

    fn deref(&self) -> &Self::Target {
        &self.track
    }
}