#![no_std]
extern crate alloc;
use alloc::{borrow::ToOwned, string::String, vec::Vec};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
pub struct CDText<'data> {
_length: usize,
data: &'data [u8],
}
#[derive(Debug, FromPrimitive, PartialEq, Clone, Copy)]
pub enum CDTextPackType {
Title = 0x80,
Performers = 0x81,
Songwriters = 0x82,
Composers = 0x83,
Arrangers = 0x84,
Message = 0x85,
DiscID = 0x86,
Genre = 0x87,
TOC = 0x88,
AdditionalTOC = 0x89,
ClosedInfo = 0x8d,
Code = 0x8e,
BlockSizeInfo = 0x8f,
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum CDTextTrackNumber {
WholeAlbum,
Track(u8),
}
#[derive(Debug, Clone)]
pub struct CDTextPack {
pub pack_type: CDTextPackType,
pub track_number: CDTextTrackNumber,
pub seq_counter: u8,
pub character_position: u8,
pub block_number: u8,
pub is_double_byte_characters: bool,
pub payload: [u8; 12],
pub crc: u16,
}
#[derive(Debug, Clone)]
pub enum CDTextEntryDataType {
String(String),
Data(Vec<u8>),
}
#[derive(Debug, Clone)]
pub struct CDTextEntry {
pub track_number: CDTextTrackNumber,
pub entry_type: CDTextPackType,
pub data: CDTextEntryDataType,
}
impl<'data> CDText<'data> {
pub fn from_data_with_length(data: &'data [u8]) -> Self {
Self {
_length: (((data[0] as usize) << 8) | (data[1] as usize)) - 2,
data: &data[4..],
}
}
pub fn from_data(data: &'data [u8]) -> Self {
Self {
_length: data.len(),
data,
}
}
fn parse_pack(&self, subdata: &[u8]) -> Option<CDTextPack> {
debug_assert!(subdata.len() == 18);
let pack_type = CDTextPackType::from_u8(subdata[0])?;
let track_number = match subdata[1] {
0 => CDTextTrackNumber::WholeAlbum,
n => CDTextTrackNumber::Track(n),
};
let seq_counter = subdata[2];
let character_position = subdata[3] & 0b1111;
let block_nr = (subdata[3] >> 4) & 0b111;
let is_double_byte_chars = ((subdata[3] >> 7) & 1) != 0;
let payload = &subdata[4..16];
let crc = u16::from_be_bytes(subdata[16..18].try_into().unwrap());
Some(CDTextPack {
pack_type,
track_number,
seq_counter,
character_position,
block_number: block_nr,
is_double_byte_characters: is_double_byte_chars,
payload: payload.try_into().unwrap(),
crc,
})
}
pub fn iter_pack_chunks(&self) -> impl Iterator<Item = Option<CDTextPack>> {
self.data.chunks(18).map(|x| self.parse_pack(x))
}
pub fn parse(&self) -> Vec<CDTextEntry> {
let mut payload_buffer: Vec<u8> = Vec::with_capacity(16);
let mut prev_pack = self.iter_pack_chunks().next().unwrap().unwrap();
let mut parsed_data: Vec<CDTextEntry> = Vec::new();
for pack in self.iter_pack_chunks().skip(1) {
let pack = pack.as_ref().unwrap();
let index = 12u8.saturating_sub(pack.character_position) as usize;
match pack.pack_type {
CDTextPackType::Arrangers
| CDTextPackType::Composers
| CDTextPackType::Title
| CDTextPackType::Performers
| CDTextPackType::Songwriters => {
let mut track_number = prev_pack.track_number;
let mut before = &prev_pack.payload[..index];
let after = &prev_pack.payload[index..];
let is_terminal = before.ends_with(&[0]);
if before.iter().filter(|&x| *x == 0).count() == 2 {
let position = before.iter().position(|&x| x == 0).unwrap();
payload_buffer.extend_from_slice(&before[..position]);
if !payload_buffer.is_empty() {
parsed_data.push(CDTextEntry {
track_number,
entry_type: prev_pack.pack_type,
data: CDTextEntryDataType::String(
str::from_utf8(&payload_buffer).unwrap().to_owned(),
),
});
} else {
parsed_data.push(CDTextEntry {
track_number,
entry_type: prev_pack.pack_type,
data: CDTextEntryDataType::String(
str::from_utf8(&payload_buffer).unwrap().to_owned(),
),
});
}
payload_buffer.clear();
before = &before[before.iter().position(|&x| x == 0).unwrap_or(0) + 1..];
if let CDTextTrackNumber::Track(nr) = track_number {
track_number = CDTextTrackNumber::Track(nr + 1);
} else if let CDTextTrackNumber::WholeAlbum = track_number {
track_number = CDTextTrackNumber::Track(1);
}
}
payload_buffer.extend_from_slice(if is_terminal {
let len = before.iter().rev().position(|x| *x != 0);
if let Some(ix) = len {
&before[..before.len() - ix]
} else {
before
}
} else {
before
});
if is_terminal {
parsed_data.push(CDTextEntry {
track_number,
entry_type: prev_pack.pack_type,
data: CDTextEntryDataType::String(
str::from_utf8(&payload_buffer).unwrap().trim_end_matches(|x| x as u32 == 0).to_owned(),
),
});
payload_buffer.clear();
}
payload_buffer.extend_from_slice(after);
}
_ => {
break;
},
};
prev_pack = pack.clone();
}
payload_buffer.extend_from_slice(&prev_pack.payload[..prev_pack.payload.iter().position(|&x| x == 0).unwrap()]);
parsed_data.push(CDTextEntry {
track_number: prev_pack.track_number,
entry_type: prev_pack.pack_type,
data: CDTextEntryDataType::String(
str::from_utf8(&payload_buffer).unwrap().to_owned(),
),
});
parsed_data
}
}