summaryrefslogtreecommitdiff
path: root/src/id3v2.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/id3v2.rs')
-rw-r--r--src/id3v2.rs95
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 {