From 8421f2c03d6f905b58b5447a6e0469519c7f8fa6 Mon Sep 17 00:00:00 2001 From: tomsmeding Date: Sun, 5 Jan 2020 20:44:27 +0100 Subject: Initial --- src/id3v2.rs | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 src/id3v2.rs (limited to 'src/id3v2.rs') diff --git a/src/id3v2.rs b/src/id3v2.rs new file mode 100644 index 0000000..c16c573 --- /dev/null +++ b/src/id3v2.rs @@ -0,0 +1,146 @@ +// 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 }) + } +} -- cgit v1.2.3