use crate::ParserError;
#[derive(Default, Debug)]
pub struct RiffMeta {
pub ages: Option<String>,
pub comment: Option<String>,
pub comments: Option<String>,
pub directory: Option<String>,
pub sound_scheme_title: Option<String>,
pub archival_location: Option<String>,
pub artist: Option<String>,
pub first_language: Option<String>,
pub second_language: Option<String>,
pub third_language: Option<String>,
pub fourth_language: Option<String>,
pub fifth_language: Option<String>,
pub sixth_language: Option<String>,
pub seventh_language: Option<String>,
pub eighth_language: Option<String>,
pub ninth_language: Option<String>,
pub base_url: Option<String>,
pub default_audio_stream: Option<String>,
pub costume_designer: Option<String>,
pub commissioned: Option<String>,
pub cinematographer: Option<String>,
pub country: Option<String>,
pub copyright: Option<String>,
pub date_created: Option<String>,
pub cropped: Option<String>,
pub dimensions: Option<String>,
pub date_time_original: Option<String>,
pub dots_per_inch: Option<String>,
pub distributed_by: Option<String>,
pub edited_by: Option<String>,
pub encoded_by: Option<String>,
pub engineer: Option<String>,
pub genre: Option<String>,
pub keywords: Option<String>,
pub lightness: Option<String>,
pub logo_url: Option<String>,
pub logo_icon_url: Option<String>,
pub more_info_banner_image: Option<String>,
pub more_info_banner_url: Option<String>,
pub medium: Option<String>,
pub more_info_text: Option<String>,
pub more_info_url: Option<String>,
pub music_by: Option<String>,
pub title: Option<String>,
pub production_designer: Option<String>,
pub num_colors: Option<String>,
pub product: Option<String>,
pub produced_by: Option<String>,
pub ripped_by: Option<String>,
pub rating: Option<String>,
pub subject: Option<String>,
pub software: Option<String>,
pub secondary_genre: Option<String>,
pub sharpness: Option<String>,
pub time_code: Option<String>,
pub source: Option<String>,
pub source_form: Option<String>,
pub production_studio: Option<String>,
pub starring: Option<String>,
pub technician: Option<String>,
pub track_number: Option<String>,
pub watermark_url: Option<String>,
pub written_by: Option<String>,
pub language: Option<String>,
pub location: Option<String>,
pub part: Option<String>,
pub number_of_parts: Option<String>,
pub rate: Option<String>,
pub statistics: Option<String>,
pub tape_name: Option<String>,
pub end_timecode: Option<String>,
pub start_timecode: Option<String>,
pub length: Option<String>,
pub organization: Option<String>,
pub url: Option<String>,
pub version: Option<String>,
pub vegas_version_major: Option<String>,
pub vegas_version_minor: Option<String>,
pub year: Option<String>,
}
use std::fmt;
impl fmt::Display for RiffMeta {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
macro_rules! show {
($field:expr, $name:expr) => {
if let Some(value) = &$field {
writeln!(f, "{}: {}", $name, value)?;
}
};
}
show!(self.title, "Title");
show!(self.artist, "Artist");
show!(self.genre, "Genre");
show!(self.year, "Year");
Ok(())
}
}
pub fn find_info_chunks<'a>(chunks: &'a [RiffChunk<'a>]) -> Vec<&'a [u8]> {
chunks
.iter()
.filter_map(|chunk| {
if &chunk.id == b"LIST" && chunk.data.len() >= 4 {
if &chunk.data[..4] == b"INFO" {
return Some(&chunk.data[4..]);
}
}
None
})
.collect()
}
impl RiffMeta {
pub fn parse(data: &[u8]) -> Result<Self, ParserError> {
let chunks = parse_riff_chunks(data)?;
let info_chunks = find_info_chunks(&chunks);
let mut meta = RiffMeta::default();
for info in info_chunks {
let mut i = 0;
while i + 8 <= info.len() {
let tag = &info[i..i + 4];
i += 4;
let len = u32::from_le_bytes(
info[i..i + 4]
.try_into()
.map_err(|_| ParserError { message: "Invalid entry length".into() })?,
) as usize;
i += 4;
if i + len > info.len() {
return Err(ParserError {
message: "Entry out of bounds".into(),
});
}
let raw = &info[i..i + len];
i += len;
if len % 2 == 1 {
i += 1;
}
let value = String::from_utf8_lossy(raw)
.trim_end_matches('\0')
.to_string();
match tag {
b"INAM" | b"TITL" => meta.title = Some(value),
b"IART" => meta.artist = Some(value),
b"GENR" | b"IGNR" => meta.genre = Some(value),
b"YEAR" => meta.year = Some(value),
_ => {}
}
}
}
Ok(meta)
}
}
#[derive(Debug)]
pub struct RiffChunk<'a> {
pub id: [u8; 4],
pub data: &'a [u8],
}
pub fn parse_riff_chunks<'a>(data: &'a [u8]) -> Result<Vec<RiffChunk<'a>>, ParserError> {
let mut chunks = Vec::new();
let mut i = 0;
while i + 8 <= data.len() {
let id: [u8; 4] = data[i..i + 4]
.try_into()
.map_err(|_| ParserError { message: "Invalid chunk id".into() })?;
let size = u32::from_le_bytes(
data[i + 4..i + 8]
.try_into()
.map_err(|_| ParserError { message: "Invalid chunk size".into() })?,
) as usize;
i += 8;
if i + size > data.len() {
return Err(ParserError {
message: "Chunk out of bounds".into(),
});
}
let chunk_data = &data[i..i + size];
chunks.push(RiffChunk { id, data: chunk_data });
if &id == b"RIFF" || &id == b"LIST" {
if size >= 4 {
let sub_chunks = parse_riff_chunks(&chunk_data[4..])?;
chunks.extend(sub_chunks);
}
}
i += size;
if size % 2 == 1 {
i += 1;
}
}
Ok(chunks)
}