use std::sync::Arc;
use blake3::Hash;
use lofty::ogg::VorbisComments;
use lunar_lib::{
database::{DatabaseEntry, DatabaseError, TransactionError},
iterator_ext::IteratorExtensions,
};
use crate::{
config::{ExportConfig, MultiValueStrategy},
database::LibraryDb,
library::{
album::Album,
artist::Artist,
loudnorm::LoudnormAnalysis,
track::{
ResolvedTrack, Track, TrackId,
lyric_data::LyricData,
track_meta::{TrackAlbumInfo, TrackMeta},
},
},
lyrics::LyricFormat,
media_container::MediaContainer,
};
impl Track {
#[must_use]
pub fn new(hash: Hash, container: MediaContainer, metadata: TrackMeta) -> Self {
Self {
id: TrackId::new(hash),
container,
metadata,
loudnorm_analysis: None,
}
}
}
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::db_get(album_id, 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 {
pub fn metadata_key_values(
&self,
export_settings: &ExportConfig,
) -> Result<VorbisComments, TransactionError> {
let mut tags = VorbisComments::new();
if let Some((al, ar, tn, dn)) = self.album_info() {
tags.insert(String::from("ALBUM"), al.name().to_owned());
apply_artists(
&mut tags,
ar,
&export_settings.multi_value_strategy,
"ALBUMARTIST",
);
if let Some(track_total) = al.track_total {
tags.insert(String::from("TRACKTOTAL"), track_total.to_string());
}
if let Some(disc_total) = al.disc_total {
tags.insert(String::from("DISCTOTAL"), disc_total.to_string());
}
if let Some(track_num) = tn {
tags.insert(String::from("TRACKNUMBER"), track_num.to_string());
}
if let Some(disc_num) = dn {
tags.insert(String::from("DISCNUMBER"), disc_num.to_string());
}
} else if export_settings.singles_as_albums {
tags.insert(
String::from("ALBUM"),
self.track.metadata().safe_title().to_owned(),
);
apply_artists(
&mut tags,
self.artists(),
&export_settings.multi_value_strategy,
"ALBUMARTIST",
);
tags.insert(String::from("TRACKTOTAL"), String::from("1"));
tags.insert(String::from("DISCTOTAL"), String::from("1"));
tags.insert(String::from("TRACKNUMBER"), String::from("1"));
tags.insert(String::from("DISCNUMBER"), String::from("1"));
}
apply_artists(
&mut tags,
&self.artists,
&export_settings.multi_value_strategy,
"ARTIST",
);
if let Some(date) = self.metadata.date {
tags.insert(
String::from("DATE"),
date.format("%Y-%m-%dT%H:%M:%S").to_string(),
);
}
for genre in &self.metadata.genre {
tags.push(String::from("GENRE"), genre.to_owned());
}
if let Some(title) = &self.metadata.title {
tags.insert(String::from("TITLE"), title.to_owned())
}
if let Some(lyric_data) = &self.metadata.lyric_data {
match lyric_data {
LyricData::Instrumental => {
tags.insert(String::from("INSTRUMENTAL"), "1".to_owned());
}
LyricData::Plain(lyrics) => {
tags.insert(String::from("UNSYNCEDLYRICS"), lyrics.to_string());
}
LyricData::Synced(lyrics) => {
tags.insert(
String::from("SYNCEDLYRICS"),
lyrics.to_lyrics(LyricFormat::Lrc { a2: false }),
);
}
}
}
if let Some(loudnorm_analysis) = self.loudnorm_analysis() {
tags.insert(
String::from("REPLAYGAIN_TRACK_GAIN"),
format!("{} dB", loudnorm_analysis.calculated_gain_db()),
);
tags.insert(
String::from("REPLAYGAIN_TRACK_PEAK"),
format!("{}", loudnorm_analysis.calculated_replay_gain_peak()),
);
}
self.metadata.other.iter().for_each(|(k, v)| {
tags.push(k.to_uppercase(), v.to_owned());
});
Ok(tags)
}
}
fn apply_artists(
tags: &mut VorbisComments,
artists: &[Arc<Artist>],
strategy: &MultiValueStrategy,
tag: &'static str,
) {
match strategy {
MultiValueStrategy::MultipleTags => {
for artist in artists {
tags.push(tag.to_owned(), artist.name().to_owned());
}
}
MultiValueStrategy::JoinBy(sep) => {
tags.insert(
tag.to_owned(),
artists.iter().map(|a| a.name()).to_vec().join(&sep),
);
}
MultiValueStrategy::First => {
if let Some(artist) = artists.first() {
tags.insert(tag.to_owned(), artist.name().to_owned());
}
}
}
}