#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PackType {
Title, Performer, Songwriter, Composer, Arranger, Message, DiscId, Genre, Toc, Toc2, UpcEanIsrc, SizeInfo, Reserved(u8),
}
impl PackType {
#[must_use]
pub fn from_byte(b: u8) -> Self {
match b {
0x80 => Self::Title,
0x81 => Self::Performer,
0x82 => Self::Songwriter,
0x83 => Self::Composer,
0x84 => Self::Arranger,
0x85 => Self::Message,
0x86 => Self::DiscId,
0x87 => Self::Genre,
0x88 => Self::Toc,
0x89 => Self::Toc2,
0x8E => Self::UpcEanIsrc,
0x8F => Self::SizeInfo,
other => Self::Reserved(other),
}
}
#[must_use]
pub fn is_text(self) -> bool {
matches!(
self,
Self::Title
| Self::Performer
| Self::Songwriter
| Self::Composer
| Self::Arranger
| Self::Message
| Self::UpcEanIsrc
)
}
}
#[must_use]
pub fn crc16_ccitt(data: &[u8]) -> u16 {
let mut crc: u16 = 0;
for &b in data {
crc ^= u16::from(b) << 8;
for _ in 0..8 {
crc = if crc & 0x8000 != 0 { (crc << 1) ^ 0x1021 } else { crc << 1 };
}
}
crc
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CdText {
fields: Vec<(PackType, u8, String)>,
}
impl CdText {
#[must_use]
pub fn entries(&self) -> &[(PackType, u8, String)] {
&self.fields
}
fn get(&self, pt: PackType, track: u8) -> Option<&str> {
self.fields.iter().find(|(t, n, _)| *t == pt && *n == track).map(|(_, _, s)| s.as_str())
}
#[must_use]
pub fn album_title(&self) -> Option<&str> {
self.get(PackType::Title, 0)
}
#[must_use]
pub fn track_title(&self, track: u8) -> Option<&str> {
self.get(PackType::Title, track)
}
#[must_use]
pub fn album_performer(&self) -> Option<&str> {
self.get(PackType::Performer, 0)
}
#[must_use]
pub fn track_performer(&self, track: u8) -> Option<&str> {
self.get(PackType::Performer, track)
}
}
#[must_use]
pub fn decode(blob: &[u8]) -> CdText {
use std::collections::BTreeMap;
let mut groups: BTreeMap<u8, (u8, Vec<u8>)> = BTreeMap::new();
let mut order: Vec<u8> = Vec::new();
for chunk in blob.chunks_exact(18) {
let type_byte = chunk[0];
if !PackType::from_byte(type_byte).is_text() {
continue;
}
let bncpi = chunk[3];
let double_byte = bncpi & 0x80 != 0;
let block = (bncpi >> 4) & 0x07;
if double_byte || block != 0 {
continue; }
let track = chunk[1] & 0x7F; let entry = groups.entry(type_byte).or_insert_with(|| {
order.push(type_byte);
(track, Vec::new())
});
entry.1.extend_from_slice(&chunk[4..16]);
}
let mut fields = Vec::new();
for type_byte in order {
let (base, bytes) = &groups[&type_byte];
let pt = PackType::from_byte(type_byte);
let mut track = *base;
let mut cur: Vec<u8> = Vec::new();
let push = |t: &mut u8, cur: &mut Vec<u8>, fields: &mut Vec<(PackType, u8, String)>| {
if !cur.is_empty() {
let s: String = cur.iter().map(|&c| c as char).collect();
fields.push((pt, *t, s));
}
cur.clear();
*t = t.wrapping_add(1);
};
for &b in bytes {
if b == 0 {
push(&mut track, &mut cur, &mut fields);
} else {
cur.push(b);
}
}
if !cur.is_empty() {
let s: String = cur.iter().map(|&c| c as char).collect();
fields.push((pt, track, s));
}
}
CdText { fields }
}