vapor_parser/id3v2/
mod.rs1mod 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 let formatted_data =
113 data.chars().filter(|c| *c != '\u{0}').collect::<String>();
114
115 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}