// http://id3.org/id3v2.3.0 use std::convert::TryFrom; use std::io::{self, Read}; use std::iter; use std::num::TryFromIntError; use crate::encoding::{from_latin_1, from_ucs_2_bom}; use crate::error::IntoIOError; 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), TALB(String), TRCK(String), } struct ArrayIter2 { arr: [T; 2], cursor: u8, } impl ArrayIter2 { fn new(arr: [T; 2]) -> Self { Self { arr, cursor: 0 } } } impl Iterator for ArrayIter2 { type Item = T; fn next(&mut self) -> Option { if (self.cursor as usize) < self.arr.len() { let i = self.cursor; self.cursor += 1; Some(self.arr[i as usize].clone()) } else { None } } } 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) -> 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; } 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()) } } } fn encode_string(s: &str) -> io::Result> { let as_latin1 = || iter::once(Ok(0)) .chain(s.chars().map(|c| u8::try_from(u32::from(c)))) .collect::, _>>(); let as_ucs2 = || { let nibbles = s.chars() .map(|c| u16::try_from(u32::from(c))) .collect::, _>>()?; Ok(iter::once(1) .chain(nibbles.iter().flat_map(|&n| ArrayIter2::new([(n >> 8) as u8, n as u8]))) .collect::>()) }; as_latin1() .or_else(|_| as_ucs2()) .map_err(|e: TryFromIntError| e.ioerr()) } pub fn interpret(&self) -> io::Result> { let type_t = |typ: fn(String) -> Frame| self.interpret_encoded_string().map(typ).map(Some); if self.id == "TIT2" { type_t(Frame::TIT2) } else if self.id == "TYER" { type_t(Frame::TYER) } else if self.id == "TPE1" { type_t(Frame::TPE1) } else if self.id == "TALB" { type_t(Frame::TALB) } else if self.id == "TRCK" { type_t(Frame::TRCK) } else { Ok(None) } } pub fn map_string String>(self, f: F) -> io::Result> { let type_t = |id: &str, body: String| -> io::Result { Ok(Self { id: id.to_string(), flags: 0, body: Self::encode_string(&body)?, }) }; match self.interpret()? { Some(Frame::TIT2(s)) => Ok(Some(type_t("TIT2", f(s))?)), Some(Frame::TYER(s)) => Ok(Some(type_t("TYER", f(s))?)), Some(Frame::TPE1(s)) => Ok(Some(type_t("TPE1", f(s))?)), Some(Frame::TALB(s)) => Ok(Some(type_t("TALB", f(s))?)), Some(Frame::TRCK(s)) => Ok(Some(type_t("TRCK", f(s))?)), None => Ok(Some(self)), } } } 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 }) } }