1use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
15#[serde(rename_all = "snake_case")]
16pub enum PictureType {
17 Other,
18 FileIcon, OtherFileIcon,
20 #[default]
21 CoverFront,
22 CoverBack,
23 LeafletPage,
24 Media, LeadArtist,
26 Artist,
27 Conductor,
28 Band,
29 Composer,
30 Lyricist,
31 RecordingLocation,
32 DuringRecording,
33 DuringPerformance,
34 VideoScreenCapture,
35 BrightColouredFish, Illustration,
37 BandLogo,
38 PublisherLogo,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Picture {
44 pub mime_type: String,
46 pub picture_type: PictureType,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub description: Option<String>,
51 #[serde(with = "serde_bytes")]
53 pub data: Vec<u8>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct Comment {
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub language: Option<String>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub description: Option<String>,
69 pub text: String,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct Lyrics {
76 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub language: Option<String>,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub description: Option<String>,
82 pub text: String,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
88#[serde(rename_all = "snake_case")]
89pub enum SyncedLyricsContentType {
90 Other,
91 #[default]
92 Lyrics,
93 TextTranscription,
94 PartName, Events, Chord, Trivia, WebpageUrl,
99 ImageUrl,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct SyncedLyricsLine {
105 pub timestamp_ms: u64,
107 pub text: String,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct SyncedLyrics {
114 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub language: Option<String>,
117 #[serde(default)]
119 pub content_type: SyncedLyricsContentType,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub description: Option<String>,
123 pub lines: Vec<SyncedLyricsLine>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct UserText {
130 pub description: String,
132 pub value: String,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct UserUrl {
139 pub description: String,
141 pub url: String,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct Popularimeter {
148 #[serde(default, skip_serializing_if = "Option::is_none")]
150 pub email: Option<String>,
151 pub rating: u8,
153 #[serde(default, skip_serializing_if = "Option::is_none")]
155 pub play_count: Option<u64>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct WaveformData {
165 pub peaks_per_second: u32,
167 pub peaks: Vec<f32>,
169 #[serde(default = "default_one")]
171 pub channels: u8,
172}
173
174fn default_one() -> u8 {
175 1
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
180#[serde(rename_all = "snake_case")]
181pub enum SectionType {
182 Intro,
183 Verse,
184 PreChorus,
185 Chorus,
186 PostChorus,
187 Bridge,
188 Breakdown,
189 Drop,
190 Buildup,
191 Solo,
192 Instrumental,
193 Outro,
194 Silence,
195 Other,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct SectionMarker {
201 pub timestamp_ms: u64,
203 pub section_type: SectionType,
205 #[serde(default, skip_serializing_if = "Option::is_none")]
207 pub label: Option<String>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct BpmChange {
213 pub timestamp_ms: u64,
215 pub bpm: f32,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct KeyChange {
222 pub timestamp_ms: u64,
224 pub key: String,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct LoudnessPoint {
231 pub timestamp_ms: u64,
233 pub lufs: f32,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct CreatorNote {
240 #[serde(default, skip_serializing_if = "Option::is_none")]
242 pub timestamp_ms: Option<u64>,
243 pub text: String,
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct CollaborationCredit {
250 pub role: String,
252 pub name: String,
254 #[serde(default, skip_serializing_if = "Option::is_none")]
256 pub timestamp_ms: Option<u64>,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct RemixChainEntry {
262 pub title: String,
264 pub artist: String,
266 #[serde(default, skip_serializing_if = "Option::is_none")]
268 pub year: Option<u32>,
269 #[serde(default, skip_serializing_if = "Option::is_none")]
271 pub isrc: Option<String>,
272 pub relationship: String,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct AnimatedCover {
279 pub mime_type: String,
281 #[serde(with = "serde_bytes")]
283 pub data: Vec<u8>,
284 #[serde(default, skip_serializing_if = "Option::is_none")]
286 pub duration_ms: Option<u32>,
287 #[serde(default, skip_serializing_if = "Option::is_none")]
289 pub loop_count: Option<u32>,
290}
291
292#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
294#[serde(rename_all = "snake_case")]
295pub enum CoverVariantType {
296 Standard,
297 Explicit,
298 Clean,
299 Remix,
300 Deluxe,
301 Limited,
302 Vinyl,
303 Cassette,
304 Digital,
305 Other,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct CoverVariant {
311 pub variant_type: CoverVariantType,
313 pub mime_type: String,
315 #[serde(with = "serde_bytes")]
317 pub data: Vec<u8>,
318 #[serde(default, skip_serializing_if = "Option::is_none")]
320 pub description: Option<String>,
321}
322
323#[derive(Debug, Clone, Default, Serialize, Deserialize)]
328pub struct FloMetadata {
329 #[serde(default, skip_serializing_if = "Option::is_none")]
332 pub title: Option<String>,
333
334 #[serde(default, skip_serializing_if = "Option::is_none")]
336 pub subtitle: Option<String>,
337
338 #[serde(default, skip_serializing_if = "Option::is_none")]
340 pub content_group: Option<String>,
341
342 #[serde(default, skip_serializing_if = "Option::is_none")]
344 pub album: Option<String>,
345
346 #[serde(default, skip_serializing_if = "Option::is_none")]
348 pub original_album: Option<String>,
349
350 #[serde(default, skip_serializing_if = "Option::is_none")]
352 pub set_subtitle: Option<String>,
353
354 #[serde(default, skip_serializing_if = "Option::is_none")]
356 pub track_number: Option<u32>,
357
358 #[serde(default, skip_serializing_if = "Option::is_none")]
360 pub track_total: Option<u32>,
361
362 #[serde(default, skip_serializing_if = "Option::is_none")]
364 pub disc_number: Option<u32>,
365
366 #[serde(default, skip_serializing_if = "Option::is_none")]
368 pub disc_total: Option<u32>,
369
370 #[serde(default, skip_serializing_if = "Option::is_none")]
372 pub isrc: Option<String>,
373
374 #[serde(default, skip_serializing_if = "Option::is_none")]
377 pub artist: Option<String>,
378
379 #[serde(default, skip_serializing_if = "Option::is_none")]
381 pub album_artist: Option<String>,
382
383 #[serde(default, skip_serializing_if = "Option::is_none")]
385 pub conductor: Option<String>,
386
387 #[serde(default, skip_serializing_if = "Option::is_none")]
389 pub remixer: Option<String>,
390
391 #[serde(default, skip_serializing_if = "Option::is_none")]
393 pub original_artist: Option<String>,
394
395 #[serde(default, skip_serializing_if = "Option::is_none")]
397 pub composer: Option<String>,
398
399 #[serde(default, skip_serializing_if = "Option::is_none")]
401 pub lyricist: Option<String>,
402
403 #[serde(default, skip_serializing_if = "Option::is_none")]
405 pub original_lyricist: Option<String>,
406
407 #[serde(default, skip_serializing_if = "Option::is_none")]
409 pub encoded_by: Option<String>,
410
411 #[serde(default, skip_serializing_if = "Option::is_none")]
413 pub involved_people: Option<Vec<(String, String)>>,
414
415 #[serde(default, skip_serializing_if = "Option::is_none")]
417 pub musician_credits: Option<Vec<(String, String)>>,
418
419 #[serde(default, skip_serializing_if = "Option::is_none")]
422 pub genre: Option<String>,
423
424 #[serde(default, skip_serializing_if = "Option::is_none")]
426 pub mood: Option<String>,
427
428 #[serde(default, skip_serializing_if = "Option::is_none")]
430 pub bpm: Option<u32>,
431
432 #[serde(default, skip_serializing_if = "Option::is_none")]
434 pub key: Option<String>,
435
436 #[serde(default, skip_serializing_if = "Option::is_none")]
438 pub language: Option<String>,
439
440 #[serde(default, skip_serializing_if = "Option::is_none")]
442 pub length_ms: Option<u64>,
443
444 #[serde(default, skip_serializing_if = "Option::is_none")]
447 pub year: Option<u32>,
448
449 #[serde(default, skip_serializing_if = "Option::is_none")]
451 pub recording_time: Option<String>,
452
453 #[serde(default, skip_serializing_if = "Option::is_none")]
455 pub release_time: Option<String>,
456
457 #[serde(default, skip_serializing_if = "Option::is_none")]
459 pub original_release_time: Option<String>,
460
461 #[serde(default, skip_serializing_if = "Option::is_none")]
463 pub encoding_time: Option<String>,
464
465 #[serde(default, skip_serializing_if = "Option::is_none")]
467 pub tagging_time: Option<String>,
468
469 #[serde(default, skip_serializing_if = "Option::is_none")]
472 pub copyright: Option<String>,
473
474 #[serde(default, skip_serializing_if = "Option::is_none")]
476 pub produced_notice: Option<String>,
477
478 #[serde(default, skip_serializing_if = "Option::is_none")]
480 pub publisher: Option<String>,
481
482 #[serde(default, skip_serializing_if = "Option::is_none")]
484 pub file_owner: Option<String>,
485
486 #[serde(default, skip_serializing_if = "Option::is_none")]
488 pub radio_station: Option<String>,
489
490 #[serde(default, skip_serializing_if = "Option::is_none")]
492 pub radio_station_owner: Option<String>,
493
494 #[serde(default, skip_serializing_if = "Option::is_none")]
497 pub album_sort: Option<String>,
498
499 #[serde(default, skip_serializing_if = "Option::is_none")]
501 pub artist_sort: Option<String>,
502
503 #[serde(default, skip_serializing_if = "Option::is_none")]
505 pub title_sort: Option<String>,
506
507 #[serde(default, skip_serializing_if = "Option::is_none")]
510 pub original_filename: Option<String>,
511
512 #[serde(default, skip_serializing_if = "Option::is_none")]
514 pub playlist_delay: Option<u32>,
515
516 #[serde(default, skip_serializing_if = "Option::is_none")]
518 pub encoder_settings: Option<String>,
519
520 #[serde(default, skip_serializing_if = "Option::is_none")]
523 pub url_commercial: Option<String>,
524
525 #[serde(default, skip_serializing_if = "Option::is_none")]
527 pub url_copyright: Option<String>,
528
529 #[serde(default, skip_serializing_if = "Option::is_none")]
531 pub url_audio_file: Option<String>,
532
533 #[serde(default, skip_serializing_if = "Option::is_none")]
535 pub url_artist: Option<String>,
536
537 #[serde(default, skip_serializing_if = "Option::is_none")]
539 pub url_audio_source: Option<String>,
540
541 #[serde(default, skip_serializing_if = "Option::is_none")]
543 pub url_radio_station: Option<String>,
544
545 #[serde(default, skip_serializing_if = "Option::is_none")]
547 pub url_payment: Option<String>,
548
549 #[serde(default, skip_serializing_if = "Option::is_none")]
551 pub url_publisher: Option<String>,
552
553 #[serde(default, skip_serializing_if = "Vec::is_empty")]
555 pub user_urls: Vec<UserUrl>,
556
557 #[serde(default, skip_serializing_if = "Vec::is_empty")]
560 pub comments: Vec<Comment>,
561
562 #[serde(default, skip_serializing_if = "Vec::is_empty")]
564 pub lyrics: Vec<Lyrics>,
565
566 #[serde(default, skip_serializing_if = "Vec::is_empty")]
568 pub synced_lyrics: Vec<SyncedLyrics>,
569
570 #[serde(default, skip_serializing_if = "Vec::is_empty")]
572 pub pictures: Vec<Picture>,
573
574 #[serde(default, skip_serializing_if = "Vec::is_empty")]
576 pub user_text: Vec<UserText>,
577
578 #[serde(default, skip_serializing_if = "Option::is_none")]
580 pub play_count: Option<u64>,
581
582 #[serde(default, skip_serializing_if = "Option::is_none")]
584 pub popularimeter: Option<Popularimeter>,
585
586 #[serde(default, skip_serializing_if = "Option::is_none")]
589 pub waveform_data: Option<WaveformData>,
590
591 #[serde(default, skip_serializing_if = "Option::is_none")]
593 #[serde(with = "serde_bytes_option")]
594 pub spectrum_fingerprint: Option<Vec<u8>>,
595
596 #[serde(default, skip_serializing_if = "Vec::is_empty")]
599 pub bpm_map: Vec<BpmChange>,
600
601 #[serde(default, skip_serializing_if = "Vec::is_empty")]
603 pub key_changes: Vec<KeyChange>,
604
605 #[serde(default, skip_serializing_if = "Vec::is_empty")]
607 pub loudness_profile: Vec<LoudnessPoint>,
608
609 #[serde(default, skip_serializing_if = "Option::is_none")]
611 pub integrated_loudness_lufs: Option<f32>,
612
613 #[serde(default, skip_serializing_if = "Option::is_none")]
615 pub loudness_range_lu: Option<f32>,
616
617 #[serde(default, skip_serializing_if = "Option::is_none")]
619 pub true_peak_dbtp: Option<f32>,
620
621 #[serde(default, skip_serializing_if = "Vec::is_empty")]
623 pub section_markers: Vec<SectionMarker>,
624
625 #[serde(default, skip_serializing_if = "Vec::is_empty")]
628 pub creator_notes: Vec<CreatorNote>,
629
630 #[serde(default, skip_serializing_if = "Vec::is_empty")]
632 pub collaboration_credits: Vec<CollaborationCredit>,
633
634 #[serde(default, skip_serializing_if = "Vec::is_empty")]
636 pub remix_chain: Vec<RemixChainEntry>,
637
638 #[serde(default, skip_serializing_if = "Option::is_none")]
641 pub animated_cover: Option<AnimatedCover>,
642
643 #[serde(default, skip_serializing_if = "Vec::is_empty")]
645 pub cover_variants: Vec<CoverVariant>,
646
647 #[serde(default, skip_serializing_if = "Option::is_none")]
649 pub artist_signature: Option<Picture>,
650
651 #[serde(default, skip_serializing_if = "Option::is_none")]
654 pub flo_encoder_version: Option<String>,
655
656 #[serde(default, skip_serializing_if = "Option::is_none")]
658 pub source_format: Option<String>,
659
660 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
662 pub custom: HashMap<String, String>,
663}
664
665mod serde_bytes_option {
667 use serde::{Deserialize, Deserializer, Serialize, Serializer};
668
669 pub fn serialize<S>(data: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error>
670 where
671 S: Serializer,
672 {
673 match data {
674 Some(bytes) => serde_bytes::serialize(bytes, serializer),
675 None => Option::<&[u8]>::None.serialize(serializer),
676 }
677 }
678
679 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error>
680 where
681 D: Deserializer<'de>,
682 {
683 let opt: Option<serde_bytes::ByteBuf> = Option::deserialize(deserializer)?;
684 Ok(opt.map(|b| b.into_vec()))
685 }
686}
687
688impl FloMetadata {
689 pub fn new() -> Self {
691 Self::default()
692 }
693
694 pub fn with_basic(
696 title: Option<String>,
697 artist: Option<String>,
698 album: Option<String>,
699 ) -> Self {
700 Self {
701 title,
702 artist,
703 album,
704 ..Default::default()
705 }
706 }
707
708 pub fn to_msgpack(&self) -> Result<Vec<u8>, rmp_serde::encode::Error> {
710 rmp_serde::to_vec_named(self)
711 }
712
713 pub fn from_msgpack(data: &[u8]) -> Result<Self, rmp_serde::decode::Error> {
715 rmp_serde::from_slice(data)
716 }
717
718 pub fn is_empty(&self) -> bool {
720 self.title.is_none()
721 && self.artist.is_none()
722 && self.album.is_none()
723 && self.pictures.is_empty()
724 && self.comments.is_empty()
725 && self.lyrics.is_empty()
726 && self.synced_lyrics.is_empty()
727 }
728
729 pub fn add_picture(&mut self, mime_type: &str, picture_type: PictureType, data: Vec<u8>) {
733 self.pictures.push(Picture {
734 mime_type: mime_type.to_string(),
735 picture_type,
736 description: None,
737 data,
738 });
739 }
740
741 pub fn front_cover(&self) -> Option<&Picture> {
743 self.pictures
744 .iter()
745 .find(|p| p.picture_type == PictureType::CoverFront)
746 }
747
748 pub fn any_picture(&self) -> Option<&Picture> {
750 self.pictures.first()
751 }
752
753 pub fn add_comment(&mut self, text: &str, language: Option<&str>) {
757 self.comments.push(Comment {
758 language: language.map(|s| s.to_string()),
759 description: None,
760 text: text.to_string(),
761 });
762 }
763
764 pub fn add_lyrics(&mut self, text: &str, language: Option<&str>) {
766 self.lyrics.push(Lyrics {
767 language: language.map(|s| s.to_string()),
768 description: None,
769 text: text.to_string(),
770 });
771 }
772
773 pub fn add_synced_lyrics_line(
775 &mut self,
776 timestamp_ms: u64,
777 text: &str,
778 language: Option<&str>,
779 ) {
780 let lang = language.map(|s| s.to_string());
781 if let Some(synced) = self.synced_lyrics.iter_mut().find(|s| s.language == lang) {
782 synced.lines.push(SyncedLyricsLine {
783 timestamp_ms,
784 text: text.to_string(),
785 });
786 } else {
787 self.synced_lyrics.push(SyncedLyrics {
788 language: lang,
789 content_type: SyncedLyricsContentType::Lyrics,
790 description: None,
791 lines: vec![SyncedLyricsLine {
792 timestamp_ms,
793 text: text.to_string(),
794 }],
795 });
796 }
797 }
798
799 pub fn set_custom(&mut self, key: &str, value: &str) {
803 self.custom.insert(key.to_string(), value.to_string());
804 }
805
806 pub fn get_custom(&self, key: &str) -> Option<&str> {
808 self.custom.get(key).map(|s| s.as_str())
809 }
810
811 pub fn add_section(
815 &mut self,
816 timestamp_ms: u64,
817 section_type: SectionType,
818 label: Option<&str>,
819 ) {
820 self.section_markers.push(SectionMarker {
821 timestamp_ms,
822 section_type,
823 label: label.map(|s| s.to_string()),
824 });
825 }
826
827 pub fn add_bpm_change(&mut self, timestamp_ms: u64, bpm: f32) {
829 self.bpm_map.push(BpmChange { timestamp_ms, bpm });
830 }
831
832 pub fn add_key_change(&mut self, timestamp_ms: u64, key: &str) {
834 self.key_changes.push(KeyChange {
835 timestamp_ms,
836 key: key.to_string(),
837 });
838 }
839
840 pub fn add_creator_note(&mut self, text: &str, timestamp_ms: Option<u64>) {
842 self.creator_notes.push(CreatorNote {
843 timestamp_ms,
844 text: text.to_string(),
845 });
846 }
847
848 pub fn add_collaboration(&mut self, role: &str, name: &str, timestamp_ms: Option<u64>) {
850 self.collaboration_credits.push(CollaborationCredit {
851 role: role.to_string(),
852 name: name.to_string(),
853 timestamp_ms,
854 });
855 }
856}