anni_repo/models/
album.rs

1use crate::prelude::*;
2use indexmap::IndexSet;
3use serde::{Deserialize, Serialize};
4use std::borrow::Cow;
5use std::collections::{HashMap, HashSet};
6use std::ops::{Deref, DerefMut};
7use std::str::FromStr;
8use uuid::Uuid;
9
10pub const UNKNOWN_ARTIST: &'static str = "[Unknown Artist]";
11pub const VARIOUS_ARTISTS: &'static str = "Various Artists";
12
13#[derive(Debug)]
14pub struct TrackIdentifier {
15    pub album_id: Uuid,
16    pub disc_id: u32,
17    pub track_id: u32,
18}
19
20#[derive(Serialize, Deserialize, Debug, Clone)]
21#[serde(deny_unknown_fields)]
22pub struct Album {
23    #[serde(rename = "album")]
24    pub(crate) info: AlbumInfo,
25    pub(crate) discs: Vec<Disc>,
26}
27
28impl Album {
29    pub fn new(info: AlbumInfo, discs: Vec<Disc>) -> Self {
30        let mut album = Album { info, discs };
31        album.format();
32        album
33    }
34
35    pub(crate) fn resolve_tags(
36        &mut self,
37        tags: &HashMap<String, HashMap<TagType, Tag>>,
38    ) -> RepoResult<()> {
39        for tag in self.info.tags.iter_mut() {
40            tag.resolve(tags)?;
41        }
42
43        for disc in self.discs.iter_mut() {
44            disc.resolve_tags(tags)?;
45        }
46
47        Ok(())
48    }
49}
50
51impl FromStr for Album {
52    type Err = Error;
53
54    fn from_str(toml_str: &str) -> Result<Self, Self::Err> {
55        let album: Album = toml::from_str(toml_str).map_err(|e| Error::TomlParseError {
56            target: "Album",
57            input: toml_str.to_string(),
58            err: e,
59        })?;
60
61        Ok(album)
62    }
63}
64
65impl Deref for Album {
66    type Target = AlbumInfo;
67
68    fn deref(&self) -> &Self::Target {
69        &self.info
70    }
71}
72
73impl DerefMut for Album {
74    fn deref_mut(&mut self) -> &mut Self::Target {
75        &mut self.info
76    }
77}
78
79impl Album {
80    pub fn album_id(&self) -> Uuid {
81        self.info.album_id
82    }
83
84    /// Only album title uses edition parameter.
85    pub fn full_title(&self) -> Cow<str> {
86        if let Some(edition) = &self.info.edition {
87            Cow::Owned(format!("{}【{edition}】", self.info.title))
88        } else {
89            Cow::Borrowed(&self.info.title)
90        }
91    }
92
93    pub fn title_raw(&self) -> &str {
94        self.info.title.as_ref()
95    }
96
97    pub fn edition(&self) -> Option<&str> {
98        self.info.edition.as_deref()
99    }
100
101    pub fn artist(&self) -> &str {
102        self.info.artist.as_ref()
103    }
104
105    pub fn release_date(&self) -> &AnniDate {
106        &self.info.release_date
107    }
108
109    pub fn track_type(&self) -> &TrackType {
110        &self.info.album_type
111    }
112
113    pub fn catalog(&self) -> &str {
114        self.info.catalog.as_ref()
115    }
116
117    pub fn tags<'me, 'tag>(&'me self) -> Vec<&'me TagRef<'tag>>
118    where
119        'tag: 'me,
120    {
121        let mut tags = Vec::new();
122        tags.append(&mut self.info.tags.iter().collect::<Vec<_>>());
123        for disc in self.discs.iter() {
124            if !disc.tags.is_empty() {
125                tags.append(&mut disc.tags.iter().collect::<Vec<_>>());
126            }
127            for track in disc.tracks.iter() {
128                if !track.tags.is_empty() {
129                    tags.append(&mut track.tags.iter().collect::<Vec<_>>());
130                }
131            }
132        }
133        tags.into_iter()
134            .map(|t| &t.0)
135            .collect::<IndexSet<_>>()
136            .into_iter()
137            .collect()
138    }
139
140    pub fn album_tags(&self) -> Vec<&TagRef> {
141        self.info.tags.iter().map(|t| &t.0).collect()
142    }
143
144    pub fn discs_len(&self) -> usize {
145        self.discs.len()
146    }
147
148    pub fn iter(&self) -> impl Iterator<Item = DiscRef> {
149        self.discs.iter().map(move |disc| DiscRef {
150            album: &self.info,
151            disc,
152        })
153    }
154
155    pub fn iter_mut(&mut self) -> impl Iterator<Item = DiscRefMut> {
156        let album = &self.info;
157        self.discs.iter_mut().map(move |disc| DiscRefMut {
158            album,
159            disc: &mut disc.info,
160            tracks: &mut disc.tracks,
161        })
162    }
163
164    pub fn format(&mut self) {
165        self.iter_mut().for_each(|mut disc| disc.format());
166
167        let disc_artist = self
168            .iter()
169            .map(|disc| disc.artist().to_string())
170            .collect::<HashSet<_>>();
171        if disc_artist.len() == 1
172            && (self.artist == UNKNOWN_ARTIST || &self.artist == disc_artist.iter().next().unwrap())
173        {
174            // all artists of the discs are the same, set all artists of discs to None
175            for disc in self.discs.iter_mut() {
176                disc.artist = None;
177            }
178            self.artist = disc_artist.into_iter().next().unwrap();
179        } else {
180            // not the same, set part of them to None
181            let album_artist = self.artist.to_string();
182            for disc in self.discs.iter_mut() {
183                if disc.artist.as_deref() == Some(&album_artist) {
184                    disc.artist = None;
185                }
186            }
187        }
188
189        let album_type = self.album_type.clone();
190        let all_discs_type = self
191            .discs
192            .iter()
193            .map(|disc| disc.disc_type.as_ref().unwrap_or(&album_type))
194            .collect::<HashSet<_>>();
195        if all_discs_type.len() == 1 {
196            let all_discs_type = all_discs_type.into_iter().next().unwrap();
197            if &album_type != all_discs_type {
198                // not the same, set album type
199                self.album_type = all_discs_type.clone()
200            }
201            // all discs have the same type, set all discs' type to None
202            for disc in self.discs.iter_mut() {
203                disc.disc_type = None;
204            }
205        } else {
206            // not the same, set part of them to None
207            for disc in self.discs.iter_mut() {
208                if disc.disc_type.as_ref() == Some(&album_type) {
209                    disc.disc_type = None;
210                }
211            }
212        }
213    }
214
215    pub fn format_to_string(&mut self) -> String {
216        self.format();
217        toml::to_string_pretty(&self).unwrap()
218    }
219
220    /// Apply album metadata to a directory formatted with strict album format.
221    ///
222    /// This function applies both metadata and cover.
223    ///
224    /// The argument `detailed` determines whether to write metadata(such as title and artist) and cover to flac files.
225    #[cfg(feature = "apply")]
226    pub fn apply_strict<P>(
227        &self,
228        directory: P,
229        detailed: bool,
230    ) -> Result<(), crate::error::AlbumApplyError>
231    where
232        P: AsRef<std::path::Path>,
233    {
234        use crate::error::AlbumApplyError;
235        use anni_common::fs;
236        use anni_flac::{
237            blocks::{BlockPicture, PictureType, UserComment, UserCommentExt},
238            FlacHeader, MetadataBlock, MetadataBlockData,
239        };
240
241        // check disc name
242        let mut discs = fs::read_dir(directory.as_ref())?
243            .filter_map(|entry| entry.ok())
244            .filter_map(|entry| {
245                entry
246                    .metadata()
247                    .ok()
248                    .and_then(|meta| if meta.is_dir() { Some(entry) } else { None })
249            })
250            .filter_map(|entry| {
251                entry
252                    .path()
253                    .file_name()
254                    .and_then(|f| f.to_str().map(|s| s.to_string()))
255            })
256            .collect::<Vec<_>>();
257        alphanumeric_sort::sort_str_slice(&mut discs);
258
259        if self.discs_len() != discs.len() {
260            return Err(AlbumApplyError::DiscMismatch {
261                path: directory.as_ref().to_path_buf(),
262                expected: self.discs_len(),
263                actual: discs.len(),
264            });
265        }
266
267        let album_cover_path = directory.as_ref().join("cover.jpg");
268        if !album_cover_path.exists() {
269            return Err(AlbumApplyError::MissingCover(album_cover_path));
270        }
271
272        for (index, disc_id) in discs.iter().enumerate() {
273            let disc_path = directory.as_ref().join(disc_id);
274            if disc_id != &(index + 1).to_string() {
275                return Err(AlbumApplyError::InvalidDiscFolder(disc_path));
276            }
277
278            let disc_cover_path = disc_path.join("cover.jpg");
279            if !disc_cover_path.exists() {
280                return Err(AlbumApplyError::MissingCover(disc_cover_path));
281            }
282        }
283
284        let disc_total = discs.len();
285
286        for ((disc_id, disc), disc_name) in self.iter().enumerate().zip(discs) {
287            let disc_num = disc_id + 1;
288            let disc_dir = directory.as_ref().join(disc_name);
289
290            let mut files = fs::get_ext_files(&disc_dir, "flac", false)?;
291            alphanumeric_sort::sort_path_slice(&mut files);
292            let tracks = disc.iter();
293            let track_total = disc.tracks_len();
294
295            if files.len() != track_total {
296                return Err(AlbumApplyError::TrackMismatch {
297                    path: disc_dir,
298                    expected: track_total,
299                    actual: files.len(),
300                });
301            }
302
303            for (track_num, (file, track)) in files.iter().zip(tracks).enumerate() {
304                let track_num = track_num + 1;
305
306                let mut flac = FlacHeader::from_file(file)?;
307                let comments = flac.comments();
308                let meta = format!(
309                    r#"TITLE={title}
310    ALBUM={album}
311    ARTIST={artist}
312    DATE={release_date}
313    TRACKNUMBER={track_number}
314    TRACKTOTAL={track_total}
315    DISCNUMBER={disc_number}
316    DISCTOTAL={disc_total}
317    "#,
318                    title = track.title(),
319                    album = disc.title(),
320                    artist = track.artist(),
321                    release_date = self.release_date(),
322                    track_number = track_num,
323                    disc_number = disc_num,
324                );
325
326                // let mut modified = false;
327                // no comment block exist, or comments is not correct
328                // TODO: the comparison is not accurate if `detailed` = false
329                if comments.is_none() || comments.unwrap().to_string() != meta {
330                    let comments = flac.comments_mut();
331                    comments.clear();
332
333                    if detailed {
334                        comments.push(UserComment::title(track.title()));
335                        comments.push(UserComment::album(disc.title()));
336                        comments.push(UserComment::artist(track.artist()));
337                        comments.push(UserComment::date(self.release_date()));
338                    }
339                    comments.push(UserComment::track_number(track_num));
340                    comments.push(UserComment::track_total(track_total));
341                    comments.push(UserComment::disc_number(disc_num));
342                    comments.push(UserComment::disc_total(disc_total));
343                    // modified = true;
344                }
345
346                if detailed {
347                    // TODO: do not modify flac file if embed cover is the same as the one in folder
348                    let cover_path = file.with_file_name("cover.jpg");
349                    let picture =
350                        BlockPicture::new(cover_path, PictureType::CoverFront, String::new())?;
351                    flac.blocks
352                        .retain(|block| !matches!(block.data, MetadataBlockData::Picture(_)));
353                    flac.blocks
354                        .push(MetadataBlock::new(MetadataBlockData::Picture(picture)));
355                    // modified = true;
356                } else {
357                    // remove cover block
358                    flac.blocks
359                        .retain(|block| !matches!(block.data, MetadataBlockData::Picture(_)));
360                }
361
362                // if modified {
363                flac.save::<String>(None)?;
364                // }
365            }
366        }
367        Ok(())
368    }
369
370    /// Apply album metadata to a directory formatted with **convention album format**.
371    ///
372    /// This function applies metadata only. Cover is not checked
373    #[cfg(feature = "apply")]
374    pub fn apply_convention<P>(&self, directory: P) -> Result<(), crate::error::AlbumApplyError>
375    where
376        P: AsRef<std::path::Path>,
377    {
378        use crate::error::AlbumApplyError;
379        use anni_common::fs;
380        use anni_flac::{
381            blocks::{UserComment, UserCommentExt},
382            FlacHeader,
383        };
384
385        let disc_total = self.discs_len();
386
387        for (disc_num, disc) in self.iter().enumerate() {
388            let disc_num = disc_num + 1;
389            let disc_dir = if disc_total > 1 {
390                directory.as_ref().join(format!(
391                    "[{catalog}] {title} [Disc {disc_num}]",
392                    catalog = disc.catalog(),
393                    title = disc.title(),
394                    disc_num = disc_num,
395                ))
396            } else {
397                directory.as_ref().to_owned()
398            };
399
400            if !disc_dir.exists() {
401                return Err(AlbumApplyError::InvalidDiscFolder(disc_dir));
402            }
403
404            let files = fs::get_ext_files(&disc_dir, "flac", false)?;
405            let tracks = disc.iter();
406            let track_total = disc.tracks_len();
407            if files.len() != track_total {
408                return Err(AlbumApplyError::TrackMismatch {
409                    path: disc_dir,
410                    expected: track_total,
411                    actual: files.len(),
412                });
413            }
414
415            for (track_num, (file, track)) in files.iter().zip(tracks).enumerate() {
416                let track_num = track_num + 1;
417
418                let mut flac = FlacHeader::from_file(file)?;
419                let comments = flac.comments();
420                // TODO: read anni convention config here
421                let meta = format!(
422                    r#"TITLE={title}
423ALBUM={album}
424ARTIST={artist}
425DATE={release_date}
426TRACKNUMBER={track_number}
427TRACKTOTAL={track_total}
428DISCNUMBER={disc_number}
429DISCTOTAL={disc_total}
430"#,
431                    title = track.title(),
432                    album = disc.title(),
433                    artist = track.artist(),
434                    release_date = self.release_date(),
435                    track_number = track_num,
436                    track_total = track_total,
437                    disc_number = disc_num,
438                    disc_total = disc_total,
439                );
440                // no comment block exist, or comments is not correct
441                if comments.is_none() || comments.unwrap().to_string() != meta {
442                    let comments = flac.comments_mut();
443                    comments.clear();
444                    comments.push(UserComment::title(track.title()));
445                    comments.push(UserComment::album(disc.title()));
446                    comments.push(UserComment::artist(track.artist()));
447                    comments.push(UserComment::date(self.release_date()));
448                    comments.push(UserComment::track_number(track_num));
449                    comments.push(UserComment::track_total(track_total));
450                    comments.push(UserComment::disc_number(disc_num));
451                    comments.push(UserComment::disc_total(disc_total));
452                    flac.save::<String>(None)?;
453                }
454            }
455        }
456        Ok(())
457    }
458}
459
460#[derive(Serialize, Deserialize, Debug, Clone)]
461#[serde(deny_unknown_fields)]
462pub struct AlbumInfo {
463    /// Album ID(uuid)
464    pub album_id: Uuid,
465    /// Album title
466    pub title: String,
467    /// Album edition
468    ///
469    /// If this field is not None and is not empty, the full title of Album should be {title}【{edition}】
470    #[serde(default)]
471    #[serde(skip_serializing_if = "Option::is_none")]
472    #[serde(deserialize_with = "anni_common::decode::non_empty_str")]
473    pub edition: Option<String>,
474    /// Album artist
475    pub artist: String,
476    /// Album artists
477    #[serde(default)]
478    #[serde(skip_serializing_if = "is_artists_empty")]
479    pub artists: Option<HashMap<String, String>>,
480    /// Album release date
481    #[serde(rename = "date")]
482    pub release_date: AnniDate,
483    /// Album track type
484    #[serde(rename = "type")]
485    pub album_type: TrackType,
486    /// Album catalog
487    pub catalog: String,
488    /// Album tags
489    #[serde(default)]
490    // TODO: use IndexSet
491    pub tags: Vec<TagString>,
492}
493
494impl Default for AlbumInfo {
495    fn default() -> Self {
496        Self {
497            album_id: Uuid::new_v4(),
498            title: "UnknownTitle".to_string(),
499            edition: None,
500            artist: UNKNOWN_ARTIST.to_string(),
501            artists: HashMap::new().into(),
502            release_date: AnniDate::new(2021, 1, 1),
503            album_type: TrackType::Normal,
504            catalog: "@TEMP".to_string(),
505            tags: Default::default(),
506        }
507    }
508}
509
510#[derive(Serialize, Deserialize, Debug, Clone)]
511#[serde(deny_unknown_fields)]
512pub struct Disc {
513    #[serde(flatten)]
514    info: DiscInfo,
515    tracks: Vec<Track>,
516}
517
518impl Disc {
519    pub fn new(info: DiscInfo, tracks: Vec<Track>) -> Self {
520        Self { info, tracks }
521    }
522
523    pub(crate) fn resolve_tags(
524        &mut self,
525        tags: &HashMap<String, HashMap<TagType, Tag>>,
526    ) -> RepoResult<()> {
527        for tag in self.tags.iter_mut() {
528            tag.resolve(tags)?;
529        }
530        for track in self.tracks.iter_mut() {
531            track.resolve_tags(tags)?;
532        }
533
534        Ok(())
535    }
536}
537
538impl Deref for Disc {
539    type Target = DiscInfo;
540
541    fn deref(&self) -> &Self::Target {
542        &self.info
543    }
544}
545
546impl DerefMut for Disc {
547    fn deref_mut(&mut self) -> &mut Self::Target {
548        &mut self.info
549    }
550}
551
552#[derive(Serialize, Deserialize, Debug, Clone)]
553#[cfg_attr(test, derive(PartialEq, Eq))]
554#[serde(deny_unknown_fields)]
555pub struct DiscInfo {
556    /// Disc title
557    #[serde(skip_serializing_if = "Option::is_none")]
558    title: Option<String>,
559    /// Disc catalog
560    pub catalog: String,
561    /// Disc artist
562    #[serde(skip_serializing_if = "Option::is_none")]
563    pub artist: Option<String>,
564    /// Disc artists
565    #[serde(skip_serializing_if = "is_artists_empty")]
566    pub artists: Option<HashMap<String, String>>,
567    /// Disc type
568    #[serde(rename = "type")]
569    #[serde(skip_serializing_if = "Option::is_none")]
570    pub disc_type: Option<TrackType>,
571    /// Disc tags
572    #[serde(default)]
573    #[serde(skip_serializing_if = "Vec::is_empty")]
574    pub tags: Vec<TagString>,
575}
576
577impl DiscInfo {
578    pub fn new(
579        catalog: String,
580        title: Option<String>,
581        artist: Option<String>,
582        artists: Option<HashMap<String, String>>,
583        disc_type: Option<TrackType>,
584        tags: Vec<TagString>,
585    ) -> Self {
586        DiscInfo {
587            title,
588            artist,
589            artists,
590            catalog,
591            tags,
592            disc_type,
593        }
594    }
595}
596
597#[derive(Clone)]
598pub struct DiscRef<'album> {
599    pub(crate) album: &'album AlbumInfo,
600    pub(crate) disc: &'album Disc,
601}
602
603impl<'album> DiscRef<'album> {
604    pub fn title(&self) -> &str {
605        self.disc
606            .title
607            .as_deref()
608            .unwrap_or(self.album.title.as_str())
609    }
610
611    /// Get raw disc title without inherit
612    pub fn title_raw(&self) -> Option<&str> {
613        self.disc.title.as_deref()
614    }
615
616    pub fn artist(&self) -> &str {
617        self.disc
618            .artist
619            .as_deref()
620            .unwrap_or(self.album.artist.as_str())
621    }
622
623    /// Get raw disc artist without inherit
624    pub fn artist_raw(&self) -> Option<&str> {
625        self.disc.artist.as_deref()
626    }
627
628    pub fn artists(&self) -> Option<&HashMap<String, String>> {
629        self.disc.artists.as_ref()
630    }
631
632    pub fn catalog(&self) -> &str {
633        self.disc.catalog.as_ref()
634    }
635
636    pub fn track_type(&self) -> &TrackType {
637        self.disc
638            .disc_type
639            .as_ref()
640            .unwrap_or(&self.album.album_type)
641    }
642
643    pub fn tags_iter(&self) -> impl Iterator<Item = &TagRef> {
644        self.disc.tags.iter().map(|t| &t.0)
645    }
646
647    pub fn tracks_len(&self) -> usize {
648        self.disc.tracks.len()
649    }
650
651    pub fn raw(&self) -> &'album Disc {
652        self.disc
653    }
654
655    pub fn iter<'disc>(&'disc self) -> impl Iterator<Item = TrackRef<'album, 'disc>> {
656        self.disc.tracks.iter().map(move |track| TrackRef {
657            album: self.album,
658            disc: self.disc,
659            track,
660        })
661    }
662}
663
664pub struct DiscRefMut<'album> {
665    pub(crate) album: &'album AlbumInfo,
666    pub(crate) disc: &'album mut DiscInfo,
667    pub(crate) tracks: &'album mut Vec<Track>,
668}
669
670impl<'album> DiscRefMut<'album> {
671    pub fn title(&self) -> &str {
672        self.disc
673            .title
674            .as_deref()
675            .unwrap_or(self.album.title.as_str())
676    }
677
678    pub fn artist(&self) -> &str {
679        self.disc
680            .artist
681            .as_deref()
682            .unwrap_or(self.album.artist.as_str())
683    }
684
685    pub fn catalog(&self) -> &str {
686        self.disc.catalog.as_ref()
687    }
688
689    pub fn track_type(&self) -> &TrackType {
690        self.disc
691            .disc_type
692            .as_ref()
693            .unwrap_or(&self.album.album_type)
694    }
695
696    pub fn tags_iter(&self) -> impl Iterator<Item = &TagRef> {
697        self.disc.tags.iter().map(|t| &t.0)
698    }
699
700    pub fn tracks_len(&self) -> usize {
701        self.tracks.len()
702    }
703
704    pub fn iter<'disc>(&'disc self) -> impl Iterator<Item = TrackRef<'album, 'disc>> {
705        self.tracks.iter().map(move |track| TrackRef {
706            album: self.album,
707            disc: self.disc,
708            track,
709        })
710    }
711
712    pub fn iter_mut(&mut self) -> impl Iterator<Item = TrackRefMut> {
713        let album = self.album;
714        let disc = &self.disc;
715        self.tracks
716            .iter_mut()
717            .map(move |track| TrackRefMut { album, disc, track })
718    }
719
720    pub fn format(&mut self) {
721        // format artists
722        let track_artist = self
723            .iter()
724            .map(|disc| disc.artist().to_string())
725            .collect::<HashSet<_>>();
726        if track_artist.len() == 1 {
727            // all track artists are the same, set all of them to None
728            for mut track in self.iter_mut() {
729                track.artist = None;
730            }
731            self.disc.artist = Some(track_artist.into_iter().next().unwrap());
732        } else {
733            // not the same, ignore extraction
734        }
735
736        // format type
737        // if all type of the tracks are the same, set the disc type to the same
738        // or, re-use disc type to format part of tracks
739        let disc_type = self.track_type().clone();
740        let all_tracks_type = self
741            .iter()
742            .map(|track| track.track_type())
743            .collect::<HashSet<_>>();
744        if all_tracks_type.len() == 1 {
745            let all_tracks_type = all_tracks_type.into_iter().next().unwrap();
746            if &disc_type != all_tracks_type {
747                self.disc.disc_type = Some(all_tracks_type.clone());
748            }
749
750            // set all tracks type to None
751            for mut track in self.iter_mut() {
752                track.track_type = None;
753            }
754        } else {
755            for mut track in self.iter_mut() {
756                if track.track_type() == &disc_type {
757                    track.track_type = None;
758                }
759            }
760        }
761    }
762}
763
764#[derive(Serialize, Deserialize, Debug, Clone)]
765#[serde(deny_unknown_fields)]
766pub struct Track {
767    /// Track title
768    pub title: String,
769    /// Track artist
770    #[serde(skip_serializing_if = "Option::is_none")]
771    pub artist: Option<String>,
772    /// Track artists
773    #[serde(skip_serializing_if = "is_artists_empty")]
774    pub artists: Option<HashMap<String, String>>,
775    /// Track type
776    #[serde(rename = "type")]
777    #[serde(skip_serializing_if = "Option::is_none")]
778    pub track_type: Option<TrackType>,
779    /// Track tags
780    #[serde(default)]
781    #[serde(skip_serializing_if = "Vec::is_empty")]
782    pub tags: Vec<TagString>,
783}
784
785impl Track {
786    pub fn new(
787        title: String,
788        artist: Option<String>,
789        artists: Option<HashMap<String, String>>,
790        track_type: Option<TrackType>,
791        tags: Vec<TagString>,
792    ) -> Self {
793        Track {
794            title,
795            artist,
796            artists,
797            track_type,
798            tags,
799        }
800    }
801
802    pub fn empty() -> Self {
803        Track::new(String::new(), None, None, None, Default::default())
804    }
805
806    pub(crate) fn resolve_tags(
807        &mut self,
808        tags: &HashMap<String, HashMap<TagType, Tag>>,
809    ) -> RepoResult<()> {
810        for tag in self.tags.iter_mut() {
811            tag.resolve(tags)?;
812        }
813
814        Ok(())
815    }
816}
817
818#[derive(Clone)]
819pub struct TrackRef<'album, 'disc> {
820    pub(crate) album: &'album AlbumInfo,
821    pub(crate) disc: &'disc DiscInfo,
822    pub(crate) track: &'disc Track,
823}
824
825impl<'album, 'disc> TrackRef<'album, 'disc>
826where
827    'album: 'disc,
828{
829    pub fn title(&self) -> &'disc str {
830        self.track.title.as_ref()
831    }
832
833    pub fn artist(&self) -> &'disc str {
834        self.track.artist.as_deref().unwrap_or_else(|| {
835            self.disc
836                .artist
837                .as_deref()
838                .unwrap_or(self.album.artist.as_str())
839        })
840    }
841
842    pub fn artists(&self) -> Option<&'disc HashMap<String, String>> {
843        self.track
844            .artists
845            .as_ref()
846            .or_else(|| self.disc.artists.as_ref().or(self.album.artists.as_ref()))
847    }
848
849    pub fn track_type(&self) -> &'disc TrackType {
850        self.track.track_type.as_ref().unwrap_or_else(|| {
851            self.disc
852                .disc_type
853                .as_ref()
854                .unwrap_or(&self.album.album_type)
855        })
856    }
857
858    pub fn tags_iter<'me, 'tag>(&'me self) -> impl Iterator<Item = &'me TagRef<'tag>>
859    where
860        'tag: 'me,
861    {
862        self.track.tags.iter().map(|t| &t.0)
863    }
864
865    pub fn raw(&self) -> &'disc Track {
866        self.track
867    }
868}
869
870pub struct TrackRefMut<'album, 'disc> {
871    pub(crate) album: &'album AlbumInfo,
872    pub(crate) disc: &'disc DiscInfo,
873    pub(crate) track: &'disc mut Track,
874}
875
876impl Deref for TrackRefMut<'_, '_> {
877    type Target = Track;
878
879    fn deref(&self) -> &Self::Target {
880        self.track
881    }
882}
883
884impl DerefMut for TrackRefMut<'_, '_> {
885    fn deref_mut(&mut self) -> &mut Self::Target {
886        self.track
887    }
888}
889
890impl<'album, 'disc> TrackRefMut<'album, 'disc>
891where
892    'album: 'disc,
893{
894    fn inner(&'album self) -> TrackRef<'album, 'disc> {
895        TrackRef {
896            album: self.album,
897            disc: self.disc,
898            track: self.track,
899        }
900    }
901
902    pub fn title(&self) -> &str {
903        self.inner().title()
904    }
905
906    pub fn artist(&self) -> &str {
907        self.inner().artist()
908    }
909
910    pub fn artists(&self) -> Option<&HashMap<String, String>> {
911        self.inner().artists()
912    }
913
914    pub fn track_type(&self) -> &TrackType {
915        self.inner().track_type()
916    }
917
918    pub fn tags_iter<'me, 'tag>(&'me self) -> impl Iterator<Item = &'me TagRef<'tag>>
919    where
920        'tag: 'me,
921    {
922        self.track.tags.iter().map(|t| &t.0)
923    }
924
925    pub fn set_artist(&mut self, artist: Option<String>) {
926        if let Some(artist) = artist {
927            let artist_str = artist.as_str();
928            let current_artist_str = self.track.artist.as_deref().unwrap_or_else(|| {
929                self.disc
930                    .artist
931                    .as_deref()
932                    .unwrap_or(self.album.artist.as_str())
933            });
934
935            if artist_str == current_artist_str {
936                self.track.artist = None;
937            } else {
938                self.track.artist = Some(artist);
939            }
940        } else {
941            self.track.artist = None;
942        }
943    }
944
945    pub fn set_track_type(&mut self, track_type: Option<TrackType>) {
946        self.track.track_type = track_type;
947    }
948}
949
950#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
951#[serde(rename_all = "lowercase")]
952pub enum TrackType {
953    Normal,
954    Instrumental,
955    Absolute,
956    Drama,
957    Radio,
958    Vocal,
959}
960
961impl AsRef<str> for TrackType {
962    fn as_ref(&self) -> &str {
963        match &self {
964            TrackType::Normal => "normal",
965            TrackType::Instrumental => "instrumental",
966            TrackType::Absolute => "absolute",
967            TrackType::Drama => "drama",
968            TrackType::Radio => "radio",
969            TrackType::Vocal => "vocal",
970        }
971    }
972}
973
974impl FromStr for TrackType {
975    type Err = Error;
976
977    fn from_str(s: &str) -> Result<Self, Self::Err> {
978        match s {
979            "normal" => Ok(TrackType::Normal),
980            "instrumental" => Ok(TrackType::Instrumental),
981            "absolute" => Ok(TrackType::Absolute),
982            "drama" => Ok(TrackType::Drama),
983            "radio" => Ok(TrackType::Radio),
984            "vocal" => Ok(TrackType::Vocal),
985            _ => Err(Error::InvalidTrackType(s.to_string())),
986        }
987    }
988}
989
990impl TrackType {
991    pub fn guess(title: &str) -> Option<TrackType> {
992        let title_lowercase = title.to_lowercase();
993        if title_lowercase.contains("off vocal")
994            || title_lowercase.contains("instrumental")
995            || title_lowercase.contains("カラオケ")
996            || title_lowercase.contains("offvocal")
997        {
998            Some(TrackType::Instrumental)
999        } else if title_lowercase.contains("drama") || title_lowercase.contains("ドラマ") {
1000            Some(TrackType::Drama)
1001        } else if title_lowercase.contains("radio") || title_lowercase.contains("ラジオ") {
1002            Some(TrackType::Radio)
1003        } else {
1004            None
1005        }
1006    }
1007}
1008
1009pub(crate) fn is_artists_empty(artists: &Option<HashMap<String, String>>) -> bool {
1010    match artists {
1011        Some(artists) => artists.is_empty(),
1012        None => true,
1013    }
1014}
1015
1016#[cfg(feature = "flac")]
1017impl From<anni_flac::FlacHeader> for Track {
1018    fn from(stream: anni_flac::FlacHeader) -> Self {
1019        use regex::Regex;
1020
1021        match stream.comments() {
1022            Some(comment) => {
1023                let map = comment.to_map();
1024                let title = map
1025                    .get("TITLE")
1026                    .map(|v| v.value().to_owned())
1027                    .or_else(|| {
1028                        // use filename as default track name
1029                        let reg = Regex::new(r#"^\d{2,3}(?:\s?[.-]\s?|\s)(.+)$"#).unwrap();
1030                        let input = stream.path.file_stem().and_then(|s| s.to_str())?;
1031                        let filename = reg
1032                            .captures(input)
1033                            .and_then(|c| c.get(1))
1034                            .map(|r| r.as_str().to_string())
1035                            .unwrap_or_else(|| input.to_string());
1036                        Some(filename)
1037                    })
1038                    .unwrap_or_default();
1039                // auto audio type for instrumental, drama and radio
1040                let track_type = TrackType::guess(&title);
1041                Track::new(
1042                    title,
1043                    map.get("ARTIST").map(|v| v.value().to_string()),
1044                    None,
1045                    track_type,
1046                    Default::default(),
1047                )
1048            }
1049            None => Track::empty(),
1050        }
1051    }
1052}