1#![no_std]
2
3extern crate alloc;
4
5use alloc::{borrow::ToOwned, string::String, vec::Vec};
6use num_derive::FromPrimitive;
7use num_traits::FromPrimitive;
8
9pub struct CDText<'data> {
11 _length: usize,
12 data: &'data [u8],
13}
14
15#[derive(Debug, FromPrimitive, PartialEq, Clone, Copy)]
17pub enum CDTextPackType {
18 Title = 0x80,
19 Performers = 0x81,
20 Songwriters = 0x82,
21 Composers = 0x83,
22 Arrangers = 0x84,
23 Message = 0x85,
24 DiscID = 0x86,
25 Genre = 0x87,
26 TOC = 0x88,
27 AdditionalTOC = 0x89,
28 ClosedInfo = 0x8d,
29 Code = 0x8e,
30 BlockSizeInfo = 0x8f,
31}
32
33#[derive(Debug, PartialEq, Clone, Copy)]
36pub enum CDTextTrackNumber {
37 WholeAlbum,
38 Track(u8),
39}
40
41#[derive(Debug, Clone)]
43pub struct CDTextPack {
44 pub pack_type: CDTextPackType,
45 pub track_number: CDTextTrackNumber,
46 pub seq_counter: u8,
47 pub character_position: u8,
48 pub block_number: u8,
49 pub is_double_byte_characters: bool,
50
51 pub payload: [u8; 12],
52 pub crc: u16,
53}
54
55#[derive(Debug, Clone)]
57pub enum CDTextEntryDataType {
58 String(String),
59 Data(Vec<u8>),
60}
61
62#[derive(Debug, Clone)]
64pub struct CDTextEntry {
65 pub track_number: CDTextTrackNumber,
66 pub entry_type: CDTextPackType,
67 pub data: CDTextEntryDataType,
68}
69
70impl<'data> CDText<'data> {
71 pub fn from_data_with_length(data: &'data [u8]) -> Self {
74 Self {
75 _length: (((data[0] as usize) << 8) | (data[1] as usize)) - 2,
76 data: &data[4..],
77 }
78 }
79
80 pub fn from_data(data: &'data [u8]) -> Self {
82 Self {
83 _length: data.len(),
84 data,
85 }
86 }
87
88 fn parse_pack(&self, subdata: &[u8]) -> Option<CDTextPack> {
91 debug_assert!(subdata.len() == 18);
92
93 let pack_type = CDTextPackType::from_u8(subdata[0])?;
95
96 let track_number = match subdata[1] {
98 0 => CDTextTrackNumber::WholeAlbum,
100 n => CDTextTrackNumber::Track(n),
101 };
102
103 let seq_counter = subdata[2];
105
106 let character_position = subdata[3] & 0b1111;
108
109 let block_nr = (subdata[3] >> 4) & 0b111;
111
112 let is_double_byte_chars = ((subdata[3] >> 7) & 1) != 0;
114
115 let payload = &subdata[4..16];
116
117 let crc = u16::from_be_bytes(subdata[16..18].try_into().unwrap());
118
119 Some(CDTextPack {
120 pack_type,
121 track_number,
122 seq_counter,
123 character_position,
124 block_number: block_nr,
125 is_double_byte_characters: is_double_byte_chars,
126 payload: payload.try_into().unwrap(),
127 crc,
128 })
129 }
130
131 pub fn iter_pack_chunks(&self) -> impl Iterator<Item = Option<CDTextPack>> {
133 self.data.chunks(18).map(|x| self.parse_pack(x))
136 }
137
138 pub fn parse(&self) -> Vec<CDTextEntry> {
140 let mut payload_buffer: Vec<u8> = Vec::with_capacity(16);
141 let mut prev_pack = self.iter_pack_chunks().next().unwrap().unwrap();
142
143 let mut parsed_data: Vec<CDTextEntry> = Vec::new();
144
145 for pack in self.iter_pack_chunks().skip(1) {
146 let pack = pack.as_ref().unwrap();
147
148 let index = 12u8.saturating_sub(pack.character_position) as usize;
155
156 match pack.pack_type {
157 CDTextPackType::Arrangers
158 | CDTextPackType::Composers
159 | CDTextPackType::Title
160 | CDTextPackType::Performers
161 | CDTextPackType::Songwriters => {
162 let mut track_number = prev_pack.track_number;
163 let mut before = &prev_pack.payload[..index];
164 let after = &prev_pack.payload[index..];
165
166 let is_terminal = before.ends_with(&[0]);
167
168 if before.iter().filter(|&x| *x == 0).count() == 2 {
172 let position = before.iter().position(|&x| x == 0).unwrap();
175 payload_buffer.extend_from_slice(&before[..position]);
176
177 if !payload_buffer.is_empty() {
178 parsed_data.push(CDTextEntry {
181 track_number,
182 entry_type: prev_pack.pack_type,
183 data: CDTextEntryDataType::String(
184 str::from_utf8(&payload_buffer).unwrap().to_owned(),
185 ),
186 });
187 } else {
188 parsed_data.push(CDTextEntry {
189 track_number,
190 entry_type: prev_pack.pack_type,
191 data: CDTextEntryDataType::String(
192 str::from_utf8(&payload_buffer).unwrap().to_owned(),
193 ),
194 });
195 }
196
197 payload_buffer.clear();
198
199 before = &before[before.iter().position(|&x| x == 0).unwrap_or(0) + 1..];
200
201 if let CDTextTrackNumber::Track(nr) = track_number {
202 track_number = CDTextTrackNumber::Track(nr + 1);
203 } else if let CDTextTrackNumber::WholeAlbum = track_number {
204 track_number = CDTextTrackNumber::Track(1);
205 }
206 }
207
208 payload_buffer.extend_from_slice(if is_terminal {
209 let len = before.iter().rev().position(|x| *x != 0);
210
211 if let Some(ix) = len {
212 &before[..before.len() - ix]
213 } else {
214 before
215 }
216 } else {
217 before
218 });
219
220 if is_terminal {
228 parsed_data.push(CDTextEntry {
229 track_number,
230 entry_type: prev_pack.pack_type,
231 data: CDTextEntryDataType::String(
232 str::from_utf8(&payload_buffer).unwrap().trim_end_matches(|x| x as u32 == 0).to_owned(),
233 ),
234 });
235
236 payload_buffer.clear();
237 }
238
239 payload_buffer.extend_from_slice(after);
240 }
241 _ => {
242 break;
243 },
244 };
245
246 prev_pack = pack.clone();
247 }
248
249 payload_buffer.extend_from_slice(&prev_pack.payload[..prev_pack.payload.iter().position(|&x| x == 0).unwrap()]);
252
253 parsed_data.push(CDTextEntry {
254 track_number: prev_pack.track_number,
255 entry_type: prev_pack.pack_type,
256 data: CDTextEntryDataType::String(
257 str::from_utf8(&payload_buffer).unwrap().to_owned(),
258 ),
259 });
260
261 parsed_data
268 }
269}