diff options
Diffstat (limited to 'src/id3v2.rs')
-rw-r--r-- | src/id3v2.rs | 95 |
1 files changed, 76 insertions, 19 deletions
diff --git a/src/id3v2.rs b/src/id3v2.rs index a8dccb9..934bdf3 100644 --- a/src/id3v2.rs +++ b/src/id3v2.rs @@ -1,9 +1,11 @@ // 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::options::EncodingOptions; use crate::util::read_big_endian; fn parse_id3v2_header(bytes: &[u8]) -> Option<(u16, u8, usize)> { @@ -46,6 +48,30 @@ pub enum Frame { TRCK(String), } +struct ArrayIter2<T> { + arr: [T; 2], + cursor: u8, +} + +impl<T> ArrayIter2<T> { + fn new(arr: [T; 2]) -> Self { + Self { arr, cursor: 0 } + } +} + +impl<T: Clone> Iterator for ArrayIter2<T> { + type Item = T; + fn next(&mut self) -> Option<T> { + 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<Option<(Self, usize)>, String> { if data.len() < 10 { @@ -69,16 +95,12 @@ impl RawFrame { Ok(Some((RawFrame { id, flags, body }, 10 + size))) } - fn interpret_encoded_string(&self, encopts: &EncodingOptions) -> io::Result<String> { + fn interpret_encoded_string(&self) -> io::Result<String> { 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()) - } + from_latin_1(&self.body[1..i]).ok_or("Invalid Latin-1 string field".ioerr()) } 1 => { // UCS-2 @@ -93,21 +115,56 @@ impl RawFrame { } } - pub fn interpret(&self, encopts: &EncodingOptions) -> io::Result<Option<Frame>> { - 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 if self.id == "TALB" { - self.interpret_encoded_string(encopts).map(Frame::TALB).map(Some) - } else if self.id == "TRCK" { - self.interpret_encoded_string(encopts).map(Frame::TRCK).map(Some) - } else { + fn encode_string(s: &str) -> io::Result<Vec<u8>> { + let as_latin1 = || iter::once(Ok(0)) + .chain(s.chars().map(|c| u8::try_from(u32::from(c)))) + .collect::<Result<Vec<u8>, _>>(); + + let as_ucs2 = || { + let nibbles = s.chars() + .map(|c| u16::try_from(u32::from(c))) + .collect::<Result<Vec<u16>, _>>()?; + Ok(iter::once(1) + .chain(nibbles.iter().flat_map(|&n| ArrayIter2::new([(n >> 8) as u8, n as u8]))) + .collect::<Vec<u8>>()) + }; + + as_latin1() + .or_else(|_| as_ucs2()) + .map_err(|e: TryFromIntError| e.ioerr()) + } + + pub fn interpret(&self) -> io::Result<Option<Frame>> { + 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<F: Fn(String) -> String>(self, f: F) -> io::Result<Option<Self>> { + let type_t = |id: &str, body: String| -> io::Result<Self> { + 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 { |