use crate::error::DiscError;
use crate::types::{CdToc, CdTrack, CdTrackType, DiscType, Msf};
#[derive(Debug, Default)]
pub struct CdrdaoToc;
impl CdrdaoToc {
pub fn format(toc: &CdToc, bin_filename: &str) -> String {
let mut out = String::new();
let any_xa = toc.tracks.iter().any(|t| {
matches!(
t.track_type,
CdTrackType::Mode2Form1 | CdTrackType::Mode2Form2 | CdTrackType::Mode2FormMix
)
});
let all_audio = toc.tracks.iter().all(|t| t.track_type.is_audio());
if any_xa {
out.push_str("CD_ROM_XA\n\n\n");
} else if all_audio {
out.push_str("CD_DA\n\n\n");
} else {
out.push_str("CD_ROM\n\n\n");
}
let mut cumulative_bytes: u64 = 0;
for (i, track) in toc.tracks.iter().enumerate() {
if i > 0 {
out.push('\n');
}
out.push_str(&format!("// Track {}\n", track.track_num));
out.push_str(&format!(
"TRACK {}\n",
track.track_type.chd_metadata_type_str()
));
out.push_str("NO COPY\n");
if track.track_type.is_audio() {
out.push_str("NO PRE_EMPHASIS\n");
out.push_str("TWO_CHANNEL_AUDIO\n");
}
if track.pregap_frames > 0 {
let pg_msf = Msf::from_lba(track.pregap_frames);
let pg_type_str = if track.track_type.is_audio() {
"AUDIO"
} else {
track.track_type.chd_metadata_type_str()
};
out.push_str(&format!("ZERO {} {}\n", pg_type_str, pg_msf));
}
let frames_msf = Msf::from_lba(track.frames);
let track_bytes = track.frames as u64 * track.track_type.sector_size() as u64;
if cumulative_bytes > 0 {
out.push_str(&format!(
"DATAFILE \"{}\" #{} {} // length in bytes: {}\n",
bin_filename, cumulative_bytes, frames_msf, track_bytes
));
} else {
out.push_str(&format!(
"DATAFILE \"{}\" {} // length in bytes: {}\n",
bin_filename, frames_msf, track_bytes
));
}
if track.pregap_frames > 0 {
let start_msf = Msf::from_lba(track.pregap_frames);
out.push_str(&format!("START {}\n", start_msf));
}
cumulative_bytes += track_bytes;
out.push('\n');
}
out
}
pub fn parse(content: &str) -> Result<CdToc, DiscError> {
let mut tracks = Vec::new();
let mut current_track_num: u32 = 0;
let mut current_track_type: Option<CdTrackType> = None;
let mut current_pregap: u32 = 0;
let mut current_frames: Option<u32> = None;
let mut current_file: Option<String> = None;
let mut current_offset: u64 = 0;
let flush_track = |tracks: &mut Vec<CdTrack>,
num: u32,
t_type: Option<CdTrackType>,
pregap: u32,
frames: Option<u32>,
file: Option<String>,
offset: u64|
-> Result<(), DiscError> {
if let (Some(t), Some(f)) = (t_type, frames) {
let mut track = CdTrack::new(num, t, f);
track.pregap_frames = pregap;
track.file_name = file;
track.file_offset = offset;
tracks.push(track);
}
Ok(())
};
for raw_line in content.lines() {
let line = raw_line.trim();
if line.is_empty() || line.starts_with("//") {
continue;
}
let tokens: Vec<&str> = line.split_whitespace().collect();
if tokens.is_empty() {
continue;
}
match tokens[0] {
"TRACK" => {
flush_track(
&mut tracks,
current_track_num,
current_track_type,
current_pregap,
current_frames,
current_file.take(),
current_offset,
)?;
current_track_num += 1;
if tokens.len() < 2 {
return Err(DiscError::InvalidToc("missing track type".to_string()));
}
current_track_type = Some(CdTrackType::parse_cue_or_metadata(tokens[1])?);
current_pregap = 0;
current_frames = None;
current_offset = 0;
}
"ZERO" => {
if tokens.len() >= 3 {
let msf = Msf::parse(tokens[2])?;
current_pregap += msf.to_lba();
}
}
"DATAFILE" | "FILE" | "AUDIOFILE" if tokens.len() >= 2 => {
let mut file_token = tokens[1];
if file_token.starts_with('"') && file_token.ends_with('"') {
file_token = &file_token[1..file_token.len() - 1];
}
crate::types::validate_descriptor_path(file_token)?;
current_file = Some(file_token.to_string());
let mut msf_idx = 2;
if tokens.len() > 2 && tokens[2].starts_with('#') {
let off_str = &tokens[2][1..];
current_offset = off_str.parse::<u64>().unwrap_or(0);
msf_idx = 3;
}
if tokens.len() > msf_idx {
if let Ok(msf) = Msf::parse(tokens[msf_idx]) {
current_frames = Some(msf.to_lba());
}
}
}
_ => {}
}
}
flush_track(
&mut tracks,
current_track_num,
current_track_type,
current_pregap,
current_frames,
current_file,
current_offset,
)?;
if tracks.is_empty() {
return Err(DiscError::InvalidToc("no tracks in TOC".to_string()));
}
Ok(CdToc {
disc_type: DiscType::Cd,
tracks,
})
}
}