// http://id3.org/id3v2.3.0 use std::io::{self, Read}; use crate::encoding::{from_latin_1, from_ucs_2_bom}; use crate::error::IntoIOError; use crate::options::EncodingOptions; use crate::util::read_big_endian; fn parse_id3v2_header(bytes: &[u8]) -> Option<(u16, u8, usize)> { if bytes.len() == 10 && bytes[0] == b'I' && bytes[1] == b'D' && bytes[2] == b'3' && bytes[3] != 0xff && bytes[4] != 0xff && bytes[6..10].iter().all(|b| (b & 0x80) == 0) { Some(( read_big_endian(&bytes[3..5], 8) as u16, bytes[5], read_big_endian(&bytes[6..10], 7) )) } else { None } } #[derive(Debug)] pub struct ID3v2 { header_size: usize, pub frames: Vec, } #[derive(Debug)] pub struct RawFrame { id: String, flags: u16, body: Vec, } #[derive(Debug)] pub enum Frame { TIT2(String), TYER(String), TPE1(String), } impl RawFrame { fn parse(data: &[u8]) -> Result, String> { if data.len() < 10 { return Err(String::from("Frame buffer too short")); } if data[0..4].iter().all(|&b| b == 0) { return Ok(None) } if !data[0..4].iter().all(|&b| (b'A' <= b && b <= b'Z') || (b'0' <= b && b <= b'9')) { return Err(format!("Invalid frame type {:?}", &data[0..4])); } let id = String::from_utf8(data[0..4].to_vec()).unwrap(); let size = read_big_endian(&data[4..8], 8); let flags = read_big_endian(&data[8..10], 8) as u16; let body = data[10..10+size].to_vec(); Ok(Some((RawFrame { id, flags, body }, 10 + size))) } fn interpret_encoded_string(&self, encopts: &EncodingOptions) -> io::Result { match self.body.get(0).ok_or("String field too small".ioerr())? { 0 => { // Latin-1 let mut i = self.body.len(); while i > 0 && self.body[i-1] == 0 { i -= 1; } if encopts.latin1_as_utf8 { String::from_utf8(self.body[1..i].to_vec()).map_err(|e| e.ioerr()) } else { from_latin_1(&self.body[1..i]).ok_or("Invalid Latin-1 string field".ioerr()) } } 1 => { // UCS-2 let mut i = self.body.len(); while i > 1 && self.body[i-2] == 0 && self.body[i-1] == 0 { i -= 2; } from_ucs_2_bom(&self.body[1..i]).ok_or("Invalid UCS-2 string field".ioerr()) } enc => { Err(format!("Unknown string encoding {}", enc).ioerr()) } } } pub fn interpret(&self, encopts: &EncodingOptions) -> io::Result> { if self.id == "TIT2" { self.interpret_encoded_string(encopts).map(Frame::TIT2).map(Some) } else if self.id == "TYER" { self.interpret_encoded_string(encopts).map(Frame::TYER).map(Some) } else if self.id == "TPE1" { self.interpret_encoded_string(encopts).map(Frame::TPE1).map(Some) } else { Ok(None) } } } impl ID3v2 { pub fn from_stream(stream: &mut R) -> io::Result { let mut header = [0u8; 10]; stream.read_exact(&mut header)?; let (id3version, flags, header_size) = parse_id3v2_header(&header).ok_or("Invalid ID3 header".ioerr())?; if id3version != 0x0300 { return Err(format!("ID3 header version {}.{} not supported", id3version / 256, id3version % 256).ioerr()) } if flags != 0 { return Err(format!("No ID3 header flags supported ({:x})", flags).ioerr()); } let body = { let mut body = Vec::new(); body.resize(header_size, 0u8); stream.read_exact(&mut body)?; body }; let mut frames = Vec::new(); let mut cursor = 0; while cursor < body.len() { match RawFrame::parse(&body[cursor..]).map_err(|e| e.ioerr())? { Some((frame, consumed)) => { frames.push(frame); cursor += consumed; } None => { break; } } } Ok(ID3v2 { frames, header_size }) } }