selene-core 0.8.2

selene-core is the backend for Selene, a local-first music player
Documentation
use std::io;

use lofty::tag::{Accessor, ItemKey, Tag, TagType};
use lunar_lib::database::{DatabaseError, EntryIdExt};

use crate::{
    config::ExportConfig,
    database::LibraryDb,
    library::{
        loudnorm::LoudnormAnalysis,
        metadata::LoftyTagRefAccessors,
        track::{
            ResolvedTrack, Track, TrackId,
            lyric_data::LyricData,
            track_meta::{TrackAlbumInfo, TrackMeta},
        },
    },
    lyrics::LyricFormat,
    media_container::{ContainerFormat, MediaContainer},
    utils::hash_file,
};

// Core
impl Track {
    #[must_use]
    pub fn new(container: MediaContainer, metadata: TrackMeta) -> io::Result<Self> {
        Ok(Self {
            id: TrackId::new(hash_file(container.path())?),
            container,
            metadata,
            loudnorm_analysis: None,
        })
    }
}

// Accessors
impl Track {
    #[must_use]
    pub fn id(&self) -> TrackId {
        self.id
    }

    #[must_use]
    pub fn container(&self) -> &MediaContainer {
        &self.container
    }

    #[must_use]
    pub fn loudnorm_analysis(&self) -> Option<&LoudnormAnalysis> {
        self.loudnorm_analysis.as_ref()
    }

    #[must_use]
    pub fn is_single(&self) -> bool {
        self.metadata.album.is_none()
    }

    #[must_use]
    pub fn metadata(&self) -> &TrackMeta {
        &self.metadata
    }

    #[must_use]
    pub fn metadata_mut(&mut self) -> &mut TrackMeta {
        &mut self.metadata
    }

    pub fn album(&self, db: &LibraryDb) -> Result<Option<TrackAlbumInfo>, DatabaseError> {
        if let Some(album_id) = self.metadata.album {
            Ok(Some({
                let album = album_id.db_get(db)?.expect("Dangling album reference");

                let reference = album
                    .track_refs()
                    .iter()
                    .find(|t| t.id == self.id)
                    .expect("Track not found in album");

                let track_num = reference.track_num;
                let disc_num = reference.disc_num;

                TrackAlbumInfo {
                    album,
                    track_num,
                    disc_num,
                }
            }))
        } else {
            Ok(None)
        }
    }
}

impl ResolvedTrack {
    #[must_use] 
    pub fn metadata_key_values(&self, export_settings: &ExportConfig) -> Tag {
        let mut tags = match self.container().format {
            ContainerFormat::Flac | ContainerFormat::Ogg => Tag::new(TagType::VorbisComments),
            ContainerFormat::Ape => Tag::new(TagType::Ape),
            ContainerFormat::Mpa | ContainerFormat::Wav | ContainerFormat::Aiff => {
                Tag::new(TagType::Id3v2)
            }
        };

        if let Some((al, ar, tn, dn)) = self.album_info() {
            tags.insert_text(ItemKey::AlbumTitle, al.name().to_owned());

            tags.set_artists(ar.iter().map(|a| &**a), ItemKey::AlbumArtist);

            if let Some(track_total) = al.track_total {
                tags.set_track_total(track_total);
            }
            if let Some(disc_total) = al.disc_total {
                tags.set_disk_total(disc_total);
            }
            if let Some(track_num) = tn {
                tags.set_track(track_num);
            }
            if let Some(disc_num) = dn {
                tags.set_disk_total(disc_num);
            }
        } else if export_settings.singles_as_albums {
            tags.insert_text(
                ItemKey::AlbumTitle,
                self.track.metadata().safe_title().to_owned(),
            );

            tags.set_artists(self.artists().iter().map(|a| &**a), ItemKey::AlbumArtist);

            tags.set_track_total(1);
            tags.set_disk_total(1);
            tags.set_track(1);
            tags.set_disk_total(1);
        }

        tags.set_artists(self.artists().iter().map(|a| &**a), ItemKey::TrackArtist);

        if let Some(date) = self.metadata.date {
            tags.insert_text(
                ItemKey::RecordingDate,
                date.format("%Y-%m-%dT%H:%M:%S").to_string(),
            );
        }

        for genre in &self.metadata.genre {
            tags.set_genre(genre.to_owned());
        }

        if let Some(title) = &self.metadata.title {
            Accessor::set_title(&mut tags, title.to_owned());
        }

        if let Some(lyric_data) = &self.metadata.lyric_data {
            match lyric_data {
                LyricData::Instrumental => (),
                LyricData::Plain(lyrics) => {
                    tags.insert_text(ItemKey::UnsyncLyrics, lyrics.to_string());
                }
                LyricData::Synced(lyrics) => {
                    tags.insert_text(
                        ItemKey::Lyrics,
                        lyrics.to_lyrics(LyricFormat::Lrc { a2: false }),
                    );
                }
            }
        }

        if let Some(loudnorm_analysis) = self.loudnorm_analysis() {
            tags.insert_text(
                ItemKey::ReplayGainTrackGain,
                format!("{} dB", loudnorm_analysis.calculated_gain_db()),
            );
            tags.insert_text(
                ItemKey::ReplayGainTrackPeak,
                format!("{}", loudnorm_analysis.calculated_replay_gain_peak()),
            );
        }

        self.metadata.other.iter().for_each(|(k, v)| {
            if let Some(item_key) = ItemKey::from_key(TagType::VorbisComments, k) {
                tags.insert_text(item_key, v.to_owned());
            }
        });

        tags
    }
}