vapor_parser/id3v2/
mod.rs

1mod tags;
2use crate::{error::Error, error::Result, utils::read_synchsafe_bytes, utils::ID3V2_HEADER_SIZE};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
6pub struct ID3V2Flags {
7    unsynchronisation: bool,
8    extended_header: bool,
9    experimental: bool,
10    footer: bool,
11}
12
13#[derive(Debug, Clone)]
14pub struct ID3V2TagHeader {
15    pub version: u8,
16    pub revision: u8,
17    pub flags: ID3V2Flags,
18    pub tag_size: usize,
19}
20
21#[derive(Debug, Clone)]
22pub struct ID3V2Tag {
23    pub header: ID3V2TagHeader,
24    pub frames: HashMap<String, String>,
25}
26
27pub fn is_id3v2_tag(buffer: &[u8]) -> bool {
28    if buffer.len() < 3 {
29        return false;
30    }
31    return &buffer[0..3] == b"ID3";
32}
33
34pub fn decode_header(buffer: &[u8; ID3V2_HEADER_SIZE]) -> Result<ID3V2TagHeader> {
35    if buffer.len() < ID3V2_HEADER_SIZE {
36        return Err(Error::Id3v2BufferTooShort);
37    }
38
39    if !is_id3v2_tag(buffer) {
40        return Err(Error::Id3v2InvalidTag);
41    }
42
43    let version = buffer[3];
44
45    if version != 3 && version != 4 {
46        return Err(Error::Id3v2UnsupportedVersion);
47    }
48
49    let revision = buffer[4];
50
51    if revision == 0xFF {
52        return Err(Error::Id3v2InvalidRevision);
53    }
54
55    let flags = buffer[5];
56
57    let unsynchronisation = flags & 0b1000_0000 != 0;
58    let extended_header = flags & 0b0100_0000 != 0;
59    let experimental = flags & 0b0010_0000 != 0;
60    let footer = flags & 0b0001_0000 != 0;
61
62    if flags & 0b0000_1111 != 0 {
63        return Err(Error::Id3v2InvalidTag);
64    }
65
66    let tag_size = read_synchsafe_bytes(&buffer[6..10]).into();
67    let footer_size: u64 = if footer { 10 } else { 0 };
68
69    return Ok(ID3V2TagHeader {
70        version,
71        revision,
72        flags: ID3V2Flags {
73            unsynchronisation,
74            extended_header,
75            experimental,
76            footer,
77        },
78        tag_size,
79    });
80}
81
82pub fn is_valid_key_character(c: char) -> bool {
83    return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
84}
85
86pub async fn decode_frames(buffer: &[u8]) -> Result<HashMap<String, String>> {
87    let mut potential_key = String::new();
88    let mut current_key = String::new();
89    let mut data = String::new();
90    let mut frames = HashMap::new();
91
92    let mut index = 0;
93    for _ in buffer.iter() {
94        if is_valid_key_character(char::from(buffer[index])) {
95            potential_key.push(char::from(buffer[index]));
96
97            if index < buffer.len() - 3 {
98                for i in 1..4 {
99                    if is_valid_key_character(char::from(buffer[index + i])) {
100                        potential_key.push(char::from(buffer[index + i]));
101                    } else {
102                        data.push_str(potential_key.as_str());
103                        data.push(char::from(buffer[index + i]));
104                        potential_key.clear();
105                        break;
106                    }
107                }
108
109                if potential_key.len() == 4 && tags::TAGS.contains(&potential_key.as_str()) {
110                    if current_key.len() > 0 {
111                        // TODO: Detect encoding (UTF-8, UTF-16)
112                        let formatted_data =
113                            data.chars().filter(|c| *c != '\u{0}').collect::<String>();
114
115                        // Remove 01 FF FE bytes
116                        let formatted_data = formatted_data
117                            .chars()
118                            .filter(|c| *c != '\u{1}' && *c != '\u{FF}' && *c != '\u{FE}')
119                            .collect::<String>();
120
121                        frames.insert(current_key.clone(), formatted_data.clone());
122                        data.clear();
123                    }
124
125                    current_key = potential_key.clone();
126                    potential_key.clear();
127
128                    index += 3;
129                } else {
130                    data.push_str(potential_key.as_str());
131                    potential_key.clear();
132                }
133            }
134        } else {
135            data.push(char::from(buffer[index]));
136            potential_key.clear();
137        }
138
139        index += 1;
140
141        if index >= buffer.len() {
142            break;
143        }
144    }
145
146    return Ok(frames);
147}
148
149impl ID3V2Tag {
150    pub fn new(header: ID3V2TagHeader, frames: HashMap<String, String>) -> Self {
151        return ID3V2Tag { header, frames };
152    }
153
154    pub fn title(&self) -> Option<&String> {
155        return self.frames.get("TIT2");
156    }
157
158    pub fn artist(&self) -> Option<&String> {
159        return self.frames.get("TPE1");
160    }
161
162    pub fn album(&self) -> Option<&String> {
163        return self.frames.get("TALB");
164    }
165
166    pub fn year(&self) -> Option<&String> {
167        return self.frames.get("TYER");
168    }
169
170    pub fn size(&self) -> usize {
171        return self.header.tag_size;
172    }
173}