librespot_metadata/audio/
file.rs1use std::{
2 collections::HashMap,
3 fmt::Debug,
4 ops::{Deref, DerefMut},
5};
6
7use librespot_core::FileId;
8
9use crate::util::impl_deref_wrapped;
10use librespot_protocol as protocol;
11use protocol::metadata::AudioFile as AudioFileMessage;
12
13use librespot_protocol::metadata::audio_file::Format;
14use protobuf::Enum;
15
16#[allow(non_camel_case_types)]
17#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
18pub enum AudioFileFormat {
19 OGG_VORBIS_96, OGG_VORBIS_160, OGG_VORBIS_320, MP3_256, MP3_320, MP3_160, MP3_96, MP3_160_ENC, AAC_24, AAC_48, FLAC_FLAC, XHE_AAC_24, XHE_AAC_16, XHE_AAC_12, FLAC_FLAC_24BIT, AAC_160, AAC_320, MP4_128, OTHER5, }
40
41impl TryFrom<i32> for AudioFileFormat {
42 type Error = i32;
43
44 fn try_from(value: i32) -> Result<Self, Self::Error> {
45 Ok(match value {
46 10 => AudioFileFormat::AAC_160,
47 11 => AudioFileFormat::AAC_320,
48 12 => AudioFileFormat::MP4_128,
49 13 => AudioFileFormat::OTHER5,
50 _ => Format::from_i32(value).ok_or(value)?.into(),
51 })
52 }
53}
54
55impl From<Format> for AudioFileFormat {
56 fn from(value: Format) -> Self {
57 match value {
58 Format::OGG_VORBIS_96 => AudioFileFormat::OGG_VORBIS_96,
59 Format::OGG_VORBIS_160 => AudioFileFormat::OGG_VORBIS_160,
60 Format::OGG_VORBIS_320 => AudioFileFormat::OGG_VORBIS_320,
61 Format::MP3_256 => AudioFileFormat::MP3_256,
62 Format::MP3_320 => AudioFileFormat::MP3_320,
63 Format::MP3_160 => AudioFileFormat::MP3_160,
64 Format::MP3_96 => AudioFileFormat::MP3_96,
65 Format::MP3_160_ENC => AudioFileFormat::MP3_160_ENC,
66 Format::AAC_24 => AudioFileFormat::AAC_24,
67 Format::AAC_48 => AudioFileFormat::AAC_48,
68 Format::FLAC_FLAC => AudioFileFormat::FLAC_FLAC,
69 Format::XHE_AAC_24 => AudioFileFormat::XHE_AAC_24,
70 Format::XHE_AAC_16 => AudioFileFormat::XHE_AAC_16,
71 Format::XHE_AAC_12 => AudioFileFormat::XHE_AAC_12,
72 Format::FLAC_FLAC_24BIT => AudioFileFormat::FLAC_FLAC_24BIT,
73 }
74 }
75}
76
77#[derive(Debug, Clone, Default)]
78pub struct AudioFiles(pub HashMap<AudioFileFormat, FileId>);
79
80impl_deref_wrapped!(AudioFiles, HashMap<AudioFileFormat, FileId>);
81
82impl AudioFiles {
83 pub fn is_ogg_vorbis(format: AudioFileFormat) -> bool {
84 matches!(
85 format,
86 AudioFileFormat::OGG_VORBIS_320
87 | AudioFileFormat::OGG_VORBIS_160
88 | AudioFileFormat::OGG_VORBIS_96
89 )
90 }
91
92 pub fn is_mp3(format: AudioFileFormat) -> bool {
93 matches!(
94 format,
95 AudioFileFormat::MP3_320
96 | AudioFileFormat::MP3_256
97 | AudioFileFormat::MP3_160
98 | AudioFileFormat::MP3_96
99 | AudioFileFormat::MP3_160_ENC
100 )
101 }
102
103 pub fn is_flac(format: AudioFileFormat) -> bool {
104 matches!(format, AudioFileFormat::FLAC_FLAC)
105 }
106
107 pub fn mime_type(format: AudioFileFormat) -> Option<&'static str> {
108 if Self::is_ogg_vorbis(format) {
109 Some("audio/ogg")
110 } else if Self::is_mp3(format) {
111 Some("audio/mpeg")
112 } else if Self::is_flac(format) {
113 Some("audio/flac")
114 } else {
115 None
116 }
117 }
118}
119
120impl From<&[AudioFileMessage]> for AudioFiles {
121 fn from(files: &[AudioFileMessage]) -> Self {
122 let audio_files: HashMap<AudioFileFormat, FileId> = files
123 .iter()
124 .filter_map(|file| {
125 let file_id = FileId::from(file.file_id());
126 let format = file
127 .format
128 .ok_or(format!("Ignoring file <{file_id}> with unspecified format",))
129 .and_then(|format| match format.enum_value() {
130 Ok(f) => Ok((f.into(), file_id)),
131 Err(unknown) => Err(format!(
132 "Ignoring file <{file_id}> with unknown format {unknown}",
133 )),
134 });
135
136 if let Err(ref why) = format {
137 trace!("{why}");
138 }
139
140 format.ok()
141 })
142 .collect();
143
144 AudioFiles(audio_files)
145 }
146}