Skip to main content

selene_core/library/track/
core_impls.rs

1use std::io;
2
3use lofty::tag::{Accessor, ItemKey, Tag, TagType};
4use lunar_lib::database::{DatabaseError, EntryIdExt};
5
6use crate::{
7    config::ExportConfig,
8    database::LibraryDb,
9    library::{
10        loudnorm::LoudnormAnalysis,
11        metadata::LoftyTagRefAccessors,
12        track::{
13            ResolvedTrack, Track, TrackId,
14            lyric_data::LyricData,
15            track_meta::{TrackAlbumInfo, TrackMeta},
16        },
17    },
18    lyrics::LyricFormat,
19    media_container::{ContainerFormat, MediaContainer},
20    utils::hash_file,
21};
22
23// Core
24impl Track {
25    #[must_use]
26    pub fn new(container: MediaContainer, metadata: TrackMeta) -> io::Result<Self> {
27        Ok(Self {
28            id: TrackId::new(hash_file(container.path())?),
29            container,
30            metadata,
31            loudnorm_analysis: None,
32        })
33    }
34}
35
36// Accessors
37impl Track {
38    #[must_use]
39    pub fn id(&self) -> TrackId {
40        self.id
41    }
42
43    #[must_use]
44    pub fn container(&self) -> &MediaContainer {
45        &self.container
46    }
47
48    #[must_use]
49    pub fn loudnorm_analysis(&self) -> Option<&LoudnormAnalysis> {
50        self.loudnorm_analysis.as_ref()
51    }
52
53    #[must_use]
54    pub fn is_single(&self) -> bool {
55        self.metadata.album.is_none()
56    }
57
58    #[must_use]
59    pub fn metadata(&self) -> &TrackMeta {
60        &self.metadata
61    }
62
63    #[must_use]
64    pub fn metadata_mut(&mut self) -> &mut TrackMeta {
65        &mut self.metadata
66    }
67
68    pub fn album(&self, db: &LibraryDb) -> Result<Option<TrackAlbumInfo>, DatabaseError> {
69        if let Some(album_id) = self.metadata.album {
70            Ok(Some({
71                let album = album_id.db_get(db)?.expect("Dangling album reference");
72
73                let reference = album
74                    .track_refs()
75                    .iter()
76                    .find(|t| t.id == self.id)
77                    .expect("Track not found in album");
78
79                let track_num = reference.track_num;
80                let disc_num = reference.disc_num;
81
82                TrackAlbumInfo {
83                    album,
84                    track_num,
85                    disc_num,
86                }
87            }))
88        } else {
89            Ok(None)
90        }
91    }
92}
93
94impl ResolvedTrack {
95    #[must_use] 
96    pub fn metadata_key_values(&self, export_settings: &ExportConfig) -> Tag {
97        let mut tags = match self.container().format {
98            ContainerFormat::Flac | ContainerFormat::Ogg => Tag::new(TagType::VorbisComments),
99            ContainerFormat::Ape => Tag::new(TagType::Ape),
100            ContainerFormat::Mpa | ContainerFormat::Wav | ContainerFormat::Aiff => {
101                Tag::new(TagType::Id3v2)
102            }
103        };
104
105        if let Some((al, ar, tn, dn)) = self.album_info() {
106            tags.insert_text(ItemKey::AlbumTitle, al.name().to_owned());
107
108            tags.set_artists(ar.iter().map(|a| &**a), ItemKey::AlbumArtist);
109
110            if let Some(track_total) = al.track_total {
111                tags.set_track_total(track_total);
112            }
113            if let Some(disc_total) = al.disc_total {
114                tags.set_disk_total(disc_total);
115            }
116            if let Some(track_num) = tn {
117                tags.set_track(track_num);
118            }
119            if let Some(disc_num) = dn {
120                tags.set_disk_total(disc_num);
121            }
122        } else if export_settings.singles_as_albums {
123            tags.insert_text(
124                ItemKey::AlbumTitle,
125                self.track.metadata().safe_title().to_owned(),
126            );
127
128            tags.set_artists(self.artists().iter().map(|a| &**a), ItemKey::AlbumArtist);
129
130            tags.set_track_total(1);
131            tags.set_disk_total(1);
132            tags.set_track(1);
133            tags.set_disk_total(1);
134        }
135
136        tags.set_artists(self.artists().iter().map(|a| &**a), ItemKey::TrackArtist);
137
138        if let Some(date) = self.metadata.date {
139            tags.insert_text(
140                ItemKey::RecordingDate,
141                date.format("%Y-%m-%dT%H:%M:%S").to_string(),
142            );
143        }
144
145        for genre in &self.metadata.genre {
146            tags.set_genre(genre.to_owned());
147        }
148
149        if let Some(title) = &self.metadata.title {
150            Accessor::set_title(&mut tags, title.to_owned());
151        }
152
153        if let Some(lyric_data) = &self.metadata.lyric_data {
154            match lyric_data {
155                LyricData::Instrumental => (),
156                LyricData::Plain(lyrics) => {
157                    tags.insert_text(ItemKey::UnsyncLyrics, lyrics.to_string());
158                }
159                LyricData::Synced(lyrics) => {
160                    tags.insert_text(
161                        ItemKey::Lyrics,
162                        lyrics.to_lyrics(LyricFormat::Lrc { a2: false }),
163                    );
164                }
165            }
166        }
167
168        if let Some(loudnorm_analysis) = self.loudnorm_analysis() {
169            tags.insert_text(
170                ItemKey::ReplayGainTrackGain,
171                format!("{} dB", loudnorm_analysis.calculated_gain_db()),
172            );
173            tags.insert_text(
174                ItemKey::ReplayGainTrackPeak,
175                format!("{}", loudnorm_analysis.calculated_replay_gain_peak()),
176            );
177        }
178
179        self.metadata.other.iter().for_each(|(k, v)| {
180            if let Some(item_key) = ItemKey::from_key(TagType::VorbisComments, k) {
181                tags.insert_text(item_key, v.to_owned());
182            }
183        });
184
185        tags
186    }
187}