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 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 for disc in self.discs.iter_mut() {
176 disc.artist = None;
177 }
178 self.artist = disc_artist.into_iter().next().unwrap();
179 } else {
180 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 self.album_type = all_discs_type.clone()
200 }
201 for disc in self.discs.iter_mut() {
203 disc.disc_type = None;
204 }
205 } else {
206 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 #[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 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 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 }
345
346 if detailed {
347 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 } else {
357 flac.blocks
359 .retain(|block| !matches!(block.data, MetadataBlockData::Picture(_)));
360 }
361
362 flac.save::<String>(None)?;
364 }
366 }
367 Ok(())
368 }
369
370 #[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 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 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 pub album_id: Uuid,
465 pub title: String,
467 #[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 pub artist: String,
476 #[serde(default)]
478 #[serde(skip_serializing_if = "is_artists_empty")]
479 pub artists: Option<HashMap<String, String>>,
480 #[serde(rename = "date")]
482 pub release_date: AnniDate,
483 #[serde(rename = "type")]
485 pub album_type: TrackType,
486 pub catalog: String,
488 #[serde(default)]
490 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 #[serde(skip_serializing_if = "Option::is_none")]
558 title: Option<String>,
559 pub catalog: String,
561 #[serde(skip_serializing_if = "Option::is_none")]
563 pub artist: Option<String>,
564 #[serde(skip_serializing_if = "is_artists_empty")]
566 pub artists: Option<HashMap<String, String>>,
567 #[serde(rename = "type")]
569 #[serde(skip_serializing_if = "Option::is_none")]
570 pub disc_type: Option<TrackType>,
571 #[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 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 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 let track_artist = self
723 .iter()
724 .map(|disc| disc.artist().to_string())
725 .collect::<HashSet<_>>();
726 if track_artist.len() == 1 {
727 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 }
735
736 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 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 pub title: String,
769 #[serde(skip_serializing_if = "Option::is_none")]
771 pub artist: Option<String>,
772 #[serde(skip_serializing_if = "is_artists_empty")]
774 pub artists: Option<HashMap<String, String>>,
775 #[serde(rename = "type")]
777 #[serde(skip_serializing_if = "Option::is_none")]
778 pub track_type: Option<TrackType>,
779 #[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 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 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}