Skip to main content

selene_core/library/track/
core_impls.rs

1use std::sync::Arc;
2
3use blake3::Hash;
4use lofty::ogg::VorbisComments;
5use lunar_lib::{
6    database::{DatabaseEntry, DatabaseError, TransactionError},
7    iterator_ext::IteratorExtensions,
8};
9
10use crate::{
11    config::{ExportConfig, MultiValueStrategy},
12    database::LibraryDb,
13    library::{
14        album::Album,
15        artist::Artist,
16        loudnorm::LoudnormAnalysis,
17        track::{
18            ResolvedTrack, Track, TrackId,
19            lyric_data::LyricData,
20            track_meta::{TrackAlbumInfo, TrackMeta},
21        },
22    },
23    lyrics::LyricFormat,
24    media_container::MediaContainer,
25};
26
27// Core
28impl Track {
29    #[must_use]
30    pub fn new(hash: Hash, container: MediaContainer, metadata: TrackMeta) -> Self {
31        Self {
32            id: TrackId::new(hash),
33            container,
34            metadata,
35            loudnorm_analysis: None,
36        }
37    }
38}
39
40// Accessors
41impl Track {
42    #[must_use]
43    pub fn id(&self) -> TrackId {
44        self.id
45    }
46
47    #[must_use]
48    pub fn container(&self) -> &MediaContainer {
49        &self.container
50    }
51
52    #[must_use]
53    pub fn loudnorm_analysis(&self) -> Option<&LoudnormAnalysis> {
54        self.loudnorm_analysis.as_ref()
55    }
56
57    #[must_use]
58    pub fn is_single(&self) -> bool {
59        self.metadata.album.is_none()
60    }
61
62    #[must_use]
63    pub fn metadata(&self) -> &TrackMeta {
64        &self.metadata
65    }
66
67    #[must_use]
68    pub fn metadata_mut(&mut self) -> &mut TrackMeta {
69        &mut self.metadata
70    }
71
72    pub fn album(&self, db: &LibraryDb) -> Result<Option<TrackAlbumInfo>, DatabaseError> {
73        if let Some(album_id) = self.metadata.album {
74            Ok(Some({
75                let album = Album::db_get(album_id, db)?.expect("Dangling album reference");
76
77                let reference = album
78                    .track_refs()
79                    .iter()
80                    .find(|t| t.id == self.id)
81                    .expect("Track not found in album");
82
83                let track_num = reference.track_num;
84                let disc_num = reference.disc_num;
85
86                TrackAlbumInfo {
87                    album,
88                    track_num,
89                    disc_num,
90                }
91            }))
92        } else {
93            Ok(None)
94        }
95    }
96}
97
98impl ResolvedTrack {
99    pub fn metadata_key_values(
100        &self,
101        export_settings: &ExportConfig,
102    ) -> Result<VorbisComments, TransactionError> {
103        let mut tags = VorbisComments::new();
104
105        if let Some((al, ar, tn, dn)) = self.album_info() {
106            tags.insert(String::from("ALBUM"), al.name().to_owned());
107
108            apply_artists(
109                &mut tags,
110                ar,
111                &export_settings.multi_value_strategy,
112                "ALBUMARTIST",
113            );
114
115            if let Some(track_total) = al.track_total {
116                tags.insert(String::from("TRACKTOTAL"), track_total.to_string());
117            }
118            if let Some(disc_total) = al.disc_total {
119                tags.insert(String::from("DISCTOTAL"), disc_total.to_string());
120            }
121            if let Some(track_num) = tn {
122                tags.insert(String::from("TRACKNUMBER"), track_num.to_string());
123            }
124            if let Some(disc_num) = dn {
125                tags.insert(String::from("DISCNUMBER"), disc_num.to_string());
126            }
127        } else if export_settings.singles_as_albums {
128            tags.insert(
129                String::from("ALBUM"),
130                self.track.metadata().safe_title().to_owned(),
131            );
132
133            apply_artists(
134                &mut tags,
135                self.artists(),
136                &export_settings.multi_value_strategy,
137                "ALBUMARTIST",
138            );
139            tags.insert(String::from("TRACKTOTAL"), String::from("1"));
140            tags.insert(String::from("DISCTOTAL"), String::from("1"));
141            tags.insert(String::from("TRACKNUMBER"), String::from("1"));
142            tags.insert(String::from("DISCNUMBER"), String::from("1"));
143        }
144
145        apply_artists(
146            &mut tags,
147            &self.artists,
148            &export_settings.multi_value_strategy,
149            "ARTIST",
150        );
151
152        if let Some(date) = self.metadata.date {
153            tags.insert(
154                String::from("DATE"),
155                date.format("%Y-%m-%dT%H:%M:%S").to_string(),
156            );
157        }
158
159        for genre in &self.metadata.genre {
160            tags.push(String::from("GENRE"), genre.to_owned());
161        }
162
163        if let Some(title) = &self.metadata.title {
164            tags.insert(String::from("TITLE"), title.to_owned())
165        }
166
167        if let Some(lyric_data) = &self.metadata.lyric_data {
168            match lyric_data {
169                LyricData::Instrumental => {
170                    tags.insert(String::from("INSTRUMENTAL"), "1".to_owned());
171                }
172                LyricData::Plain(lyrics) => {
173                    tags.insert(String::from("UNSYNCEDLYRICS"), lyrics.to_string());
174                }
175                LyricData::Synced(lyrics) => {
176                    tags.insert(
177                        String::from("SYNCEDLYRICS"),
178                        lyrics.to_lyrics(LyricFormat::Lrc { a2: false }),
179                    );
180                }
181            }
182        }
183
184        if let Some(loudnorm_analysis) = self.loudnorm_analysis() {
185            tags.insert(
186                String::from("REPLAYGAIN_TRACK_GAIN"),
187                format!("{} dB", loudnorm_analysis.calculated_gain_db()),
188            );
189            tags.insert(
190                String::from("REPLAYGAIN_TRACK_PEAK"),
191                format!("{}", loudnorm_analysis.calculated_replay_gain_peak()),
192            );
193        }
194
195        self.metadata.other.iter().for_each(|(k, v)| {
196            tags.push(k.to_uppercase(), v.to_owned());
197        });
198
199        Ok(tags)
200    }
201}
202
203fn apply_artists(
204    tags: &mut VorbisComments,
205    artists: &[Arc<Artist>],
206    strategy: &MultiValueStrategy,
207    tag: &'static str,
208) {
209    match strategy {
210        MultiValueStrategy::MultipleTags => {
211            for artist in artists {
212                tags.push(tag.to_owned(), artist.name().to_owned());
213            }
214        }
215        MultiValueStrategy::JoinBy(sep) => {
216            tags.insert(
217                tag.to_owned(),
218                artists.iter().map(|a| a.name()).to_vec().join(&sep),
219            );
220        }
221        MultiValueStrategy::First => {
222            if let Some(artist) = artists.first() {
223                tags.insert(tag.to_owned(), artist.name().to_owned());
224            }
225        }
226    }
227}