selene_core/library/track/
core_impls.rs1use 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
23impl 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
36impl 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}