librespot_metadata/audio/
file.rs

1use 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,   // 0
20    OGG_VORBIS_160,  // 1
21    OGG_VORBIS_320,  // 2
22    MP3_256,         // 3
23    MP3_320,         // 4
24    MP3_160,         // 5
25    MP3_96,          // 6
26    MP3_160_ENC,     // 7
27    AAC_24,          // 8
28    AAC_48,          // 9
29    FLAC_FLAC,       // 16
30    XHE_AAC_24,      // 18
31    XHE_AAC_16,      // 19
32    XHE_AAC_12,      // 20
33    FLAC_FLAC_24BIT, // 22
34    // not defined in protobuf, but sometimes send
35    AAC_160, // 10
36    AAC_320, // 11
37    MP4_128, // 12
38    OTHER5,  // 13
39}
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}