mod tags;
use crate::{error::Error, error::Result, utils::read_synchsafe_bytes, utils::ID3V2_HEADER_SIZE};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct ID3V2Flags {
unsynchronisation: bool,
extended_header: bool,
experimental: bool,
footer: bool,
}
#[derive(Debug, Clone)]
pub struct ID3V2TagHeader {
pub version: u8,
pub revision: u8,
pub flags: ID3V2Flags,
pub tag_size: usize,
}
#[derive(Debug, Clone)]
pub struct ID3V2Tag {
pub header: ID3V2TagHeader,
pub frames: HashMap<String, String>,
}
pub fn is_id3v2_tag(buffer: &[u8]) -> bool {
if buffer.len() < 3 {
return false;
}
return &buffer[0..3] == b"ID3";
}
pub fn decode_header(buffer: &[u8; ID3V2_HEADER_SIZE]) -> Result<ID3V2TagHeader> {
if buffer.len() < ID3V2_HEADER_SIZE {
return Err(Error::Id3v2BufferTooShort);
}
if !is_id3v2_tag(buffer) {
return Err(Error::Id3v2InvalidTag);
}
let version = buffer[3];
if version != 3 && version != 4 {
return Err(Error::Id3v2UnsupportedVersion);
}
let revision = buffer[4];
if revision == 0xFF {
return Err(Error::Id3v2InvalidRevision);
}
let flags = buffer[5];
let unsynchronisation = flags & 0b1000_0000 != 0;
let extended_header = flags & 0b0100_0000 != 0;
let experimental = flags & 0b0010_0000 != 0;
let footer = flags & 0b0001_0000 != 0;
if flags & 0b0000_1111 != 0 {
return Err(Error::Id3v2InvalidTag);
}
let tag_size = read_synchsafe_bytes(&buffer[6..10]).into();
let footer_size: u64 = if footer { 10 } else { 0 };
return Ok(ID3V2TagHeader {
version,
revision,
flags: ID3V2Flags {
unsynchronisation,
extended_header,
experimental,
footer,
},
tag_size,
});
}
pub fn is_valid_key_character(c: char) -> bool {
return (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
}
pub async fn decode_frames(buffer: &[u8]) -> Result<HashMap<String, String>> {
let mut potential_key = String::new();
let mut current_key = String::new();
let mut data = String::new();
let mut frames = HashMap::new();
let mut index = 0;
for _ in buffer.iter() {
if is_valid_key_character(char::from(buffer[index])) {
potential_key.push(char::from(buffer[index]));
if index < buffer.len() - 3 {
for i in 1..4 {
if is_valid_key_character(char::from(buffer[index + i])) {
potential_key.push(char::from(buffer[index + i]));
} else {
data.push_str(potential_key.as_str());
data.push(char::from(buffer[index + i]));
potential_key.clear();
break;
}
}
if potential_key.len() == 4 && tags::TAGS.contains(&potential_key.as_str()) {
if current_key.len() > 0 {
let formatted_data =
data.chars().filter(|c| *c != '\u{0}').collect::<String>();
let formatted_data = formatted_data
.chars()
.filter(|c| *c != '\u{1}' && *c != '\u{FF}' && *c != '\u{FE}')
.collect::<String>();
frames.insert(current_key.clone(), formatted_data.clone());
data.clear();
}
current_key = potential_key.clone();
potential_key.clear();
index += 3;
} else {
data.push_str(potential_key.as_str());
potential_key.clear();
}
}
} else {
data.push(char::from(buffer[index]));
potential_key.clear();
}
index += 1;
if index >= buffer.len() {
break;
}
}
return Ok(frames);
}
impl ID3V2Tag {
pub fn new(header: ID3V2TagHeader, frames: HashMap<String, String>) -> Self {
return ID3V2Tag { header, frames };
}
pub fn title(&self) -> Option<&String> {
return self.frames.get("TIT2");
}
pub fn artist(&self) -> Option<&String> {
return self.frames.get("TPE1");
}
pub fn album(&self) -> Option<&String> {
return self.frames.get("TALB");
}
pub fn year(&self) -> Option<&String> {
return self.frames.get("TYER");
}
pub fn size(&self) -> usize {
return self.header.tag_size;
}
}