vapor-parser 0.1.0

A parser for MPEG files
Documentation
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 {
                        // TODO: Detect encoding (UTF-8, UTF-16)
                        let formatted_data =
                            data.chars().filter(|c| *c != '\u{0}').collect::<String>();

                        // Remove 01 FF FE bytes
                        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;
    }
}