Support CDDA alignment; Update XML tag names

This commit is contained in:
jaby 2024-07-28 23:12:10 -05:00
parent 10554c7a6e
commit bd9a477cf4
3 changed files with 44 additions and 27 deletions

View File

@ -8,11 +8,22 @@ pub enum LZ4State {
Compress, Compress,
} }
pub struct CDAudioFile {
pub file_path: PathBuf,
pub align: bool,
}
impl CDAudioFile {
pub fn new(file_path: PathBuf, align: bool) -> CDAudioFile {
CDAudioFile{file_path, align}
}
}
pub struct Configuration { pub struct Configuration {
pub publisher: Option<String>, pub publisher: Option<String>,
pub license_path: Option<PathBuf>, pub license_path: Option<PathBuf>,
pub root: Directory, pub root: Directory,
pub cd_audio_files: Vec<PathBuf>, pub cd_audio_files: Vec<CDAudioFile>,
pub lead_out_sectors: Option<usize>, pub lead_out_sectors: Option<usize>,
} }

View File

@ -1,10 +1,10 @@
use attribute_names::LBA_SOURCE; use attribute_names::LBA_SOURCE;
use cdtypes::types::time::Time; use cdtypes::types::time::Time;
use std::path::PathBuf; use std::path::PathBuf;
use tool_helper::{format_if_error, path_with_env_from, print_warning, string_with_env_from}; use tool_helper::{format_if_error, path_with_env_from, string_with_env_from};
use crate::config_reader::Directory; use crate::config_reader::Directory;
use super::{CommonProperties, Configuration, Error, File, FileKind, LZ4State}; use super::{CDAudioFile, CommonProperties, Configuration, Error, File, FileKind, LZ4State};
mod attribute_names { mod attribute_names {
pub const NAME: &'static str = "name"; pub const NAME: &'static str = "name";
@ -14,39 +14,45 @@ mod attribute_names {
pub const LZ4_STATE: &'static str = "lz4"; pub const LZ4_STATE: &'static str = "lz4";
} }
mod tag_names {
pub const ROOT: &'static str = "PSXCD";
pub const TRACK: &'static str = "Filesystem";
pub const INTERLEAVED: &'static str = "InterleavedFile";
pub const CDDA: &'static str = "AudioTrack";
}
pub fn parse(xml: String) -> Result<Configuration, Error> { pub fn parse(xml: String) -> Result<Configuration, Error> {
let mut config = Configuration::new(); let mut config = Configuration::new();
let parser = format_if_error!(roxmltree::Document::parse(xml.as_str()), "Parsing XML file failed with: {error_text}")?; let parser = format_if_error!(roxmltree::Document::parse(xml.as_str()), "Parsing XML file failed with: {error_text}")?;
let children = parser.root().children(); let children = parser.root().children();
for node in children { for node in children {
if node.is_element() && node.tag_name().name() == "ISO_Project" { if node.is_element() && node.tag_name().name() == tag_names::ROOT {
parse_iso_project(node, &mut config)?; parse_root(node, &mut config)?;
} }
} }
Ok(config) Ok(config)
} }
fn parse_iso_project(iso_project: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> { fn parse_root(root: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
let mut parsed_track = false; let mut parsed_track = false;
for node in iso_project.children() { for node in root.children() {
if node.is_element() { if node.is_element() {
match node.tag_name().name() { match node.tag_name().name() {
"Description" => parse_description(node, config), "Description" => parse_description(node, config),
"Track" => { tag_names::TRACK => {
if !parsed_track { if !parsed_track {
parsed_track = true; parsed_track = true;
parse_track(node, config) parse_track(node, config)
} }
else { else {
print_warning("Found more than 1 track. Multi-Tracks are not supported yet. Track will be ignored".to_owned()); Err(Error::from_text(format!("Found more than 1 filesystem. Multi filesystem discs are not supported")))
Ok(())
} }
}, },
"CD_Audio" => parse_cd_audio(node, config), tag_names::CDDA => parse_cd_audio(node, config),
_ => Ok(()) _ => Ok(())
}?; }?;
} }
@ -109,8 +115,8 @@ fn parse_track(track: roxmltree::Node, config: &mut Configuration) -> Result<(),
Ok(File{common, path, kind: FileKind::Overlay(PathBuf::from(lba_source))}) Ok(File{common, path, kind: FileKind::Overlay(PathBuf::from(lba_source))})
} }
fn parse_xa_audio(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> { fn parse_interleaved(file: roxmltree::Node, is_hidden: bool) -> Result<File, Error> {
// v Never compress XA-Audio // v Never compress interleaved files
let common = read_common_properties(&file, is_hidden, Some(LZ4State::None))?; let common = read_common_properties(&file, is_hidden, Some(LZ4State::None))?;
let channel = { let channel = {
let mut channel = Vec::new(); let mut channel = Vec::new();
@ -130,11 +136,11 @@ fn parse_track(track: roxmltree::Node, config: &mut Configuration) -> Result<(),
for node in cur_node.children() { for node in cur_node.children() {
if node.is_element() { if node.is_element() {
match node.tag_name().name() { match node.tag_name().name() {
"File" => root.add_file(parse_regular_file(node, is_hidden)?), "File" => root.add_file(parse_regular_file(node, is_hidden)?),
"Main" => root.add_file(parse_main_file(node)?), "Main" => root.add_file(parse_main_file(node)?),
"Overlay" => root.add_file(parse_overlay_file(node, is_hidden)?), "Overlay" => root.add_file(parse_overlay_file(node, is_hidden)?),
"XA-Audio" => root.add_file(parse_xa_audio(node, is_hidden)?), tag_names::INTERLEAVED => root.add_file(parse_interleaved(node, is_hidden)?),
"Directory" => { "Directory" => {
is_hidden |= parse_boolean_attribute(&node, attribute_names::HIDDEN)?; is_hidden |= parse_boolean_attribute(&node, attribute_names::HIDDEN)?;
let mut new_dir = Directory::new(node.attribute(attribute_names::NAME).unwrap_or_default(), is_hidden); let mut new_dir = Directory::new(node.attribute(attribute_names::NAME).unwrap_or_default(), is_hidden);
@ -158,7 +164,7 @@ fn parse_track(track: roxmltree::Node, config: &mut Configuration) -> Result<(),
fn parse_split<'a>(who: &'a str, str_opt: Option<&'a str>) -> Result<u8, Error> { fn parse_split<'a>(who: &'a str, str_opt: Option<&'a str>) -> Result<u8, Error> {
match str_opt.unwrap_or("0").parse::<u8>() { match str_opt.unwrap_or("0").parse::<u8>() {
Ok(num) => Ok(num), Ok(num) => Ok(num),
Err(error) => Err(Error::from_text(format!("Failed converting \"{}\" option for \"Track\" attribute \"{}\": {}", who, ATTRIBUTE_NAME, error))) Err(error) => Err(Error::from_text(format!("Failed converting \"{}\" option for \"{}\" attribute \"{}\": {}", who, tag_names::TRACK, ATTRIBUTE_NAME, error)))
} }
} }
@ -185,7 +191,7 @@ fn parse_track(track: roxmltree::Node, config: &mut Configuration) -> Result<(),
} }
fn parse_cd_audio(cdda: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> { fn parse_cd_audio(cdda: roxmltree::Node, config: &mut Configuration) -> Result<(), Error> {
config.cd_audio_files.push(path_from_node(&cdda, "CD Audio file")?); config.cd_audio_files.push(CDAudioFile::new(path_from_node(&cdda, "CD Audio file")?, parse_boolean_attribute(&cdda, "align")?));
Ok(()) Ok(())
} }

View File

@ -7,7 +7,7 @@ pub mod types;
use crate::types::RawData; use crate::types::RawData;
use cdtypes::types::sector::AudioSample; use cdtypes::types::sector::AudioSample;
use config_reader::LZ4State; use config_reader::{CDAudioFile, LZ4State};
use encoder::{cd::{self, calculate_lbas, calculate_length_for}, SizeInfo}; use encoder::{cd::{self, calculate_lbas, calculate_length_for}, SizeInfo};
use tool_helper::{format_if_error, Output, read_file}; use tool_helper::{format_if_error, Output, read_file};
use types::{layout::Layout, CDDATrack, CDDesc, Directory, File, FileType, FileSystemMap, Properties, SharedPtr}; use types::{layout::Layout, CDDATrack, CDDesc, Directory, File, FileType, FileSystemMap, Properties, SharedPtr};
@ -204,7 +204,7 @@ fn parse_configuration(config: config_reader::Configuration) -> Result<(CDDesc,
Ok(()) Ok(())
} }
fn parse_cd_da(cd_da_tracks: &mut Vec<CDDATrack>, cd_da_files: Vec<PathBuf>) -> Result<(), Error> { fn parse_cd_da(cd_da_tracks: &mut Vec<CDDATrack>, cd_da_files: Vec<CDAudioFile>) -> Result<(), Error> {
fn convert_into_16<R:std::io::Read, S:hound::Sample>(samples: hound::WavSamples<R,S>, file_path: &PathBuf) -> Result<Vec<AudioSample>, Error> { fn convert_into_16<R:std::io::Read, S:hound::Sample>(samples: hound::WavSamples<R,S>, file_path: &PathBuf) -> Result<Vec<AudioSample>, Error> {
let mut sample_buffer = 0; let mut sample_buffer = 0;
let mut sample_id = 0; let mut sample_id = 0;
@ -233,18 +233,18 @@ fn parse_configuration(config: config_reader::Configuration) -> Result<(CDDesc,
} }
for cd_da_file in cd_da_files { for cd_da_file in cd_da_files {
let mut audio_io = hound::WavReader::open(&cd_da_file)?; let mut audio_io = hound::WavReader::open(&cd_da_file.file_path)?;
let header = audio_io.spec(); let header = audio_io.spec();
if header.sample_format != hound::SampleFormat::Int { if header.sample_format != hound::SampleFormat::Int {
return Err(Error::from_text(format!("{}: Only integer WAV format (PCM) is supported for Audio tracks", cd_da_file.display()))); return Err(Error::from_text(format!("{}: Only integer WAV format (PCM) is supported for Audio tracks", cd_da_file.file_path.display())));
} }
if header.sample_rate != 44_100 { if header.sample_rate != 44_100 {
return Err(Error::from_text(format!("{}: Only a sampling rate of 44.1kHz is supported for Audio tracks", cd_da_file.display()))); return Err(Error::from_text(format!("{}: Only a sampling rate of 44.1kHz is supported for Audio tracks", cd_da_file.file_path.display())));
} }
cd_da_tracks.push(CDDATrack::new(convert_into_16(audio_io.samples::<i16>(), &cd_da_file)?, false)); cd_da_tracks.push(CDDATrack::new(convert_into_16(audio_io.samples::<i16>(), &cd_da_file.file_path)?, cd_da_file.align));
} }
Ok(()) Ok(())
} }