mirror of
https://github.com/iluvcapra/bwavfile.git
synced 2025-12-31 17:00:44 +00:00
280 lines
8.5 KiB
Rust
280 lines
8.5 KiB
Rust
|
|
use std::io;
|
|
use std::io::SeekFrom::{Current, Start};
|
|
use std::io::{Seek, Read};
|
|
use std::collections::HashMap;
|
|
|
|
use byteorder::LittleEndian;
|
|
use byteorder::ReadBytesExt;
|
|
|
|
use super::errors::Error;
|
|
use super::fourcc::{FourCC, ReadFourCC};
|
|
use super::fourcc::{RIFF_SIG, RF64_SIG, BW64_SIG, WAVE_SIG, DS64_SIG, DATA_SIG};
|
|
|
|
// just for your reference...
|
|
// RF64 documentation https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.2088-1-201910-I!!PDF-E.pdf
|
|
|
|
// EBU long files being with RF64, and the ITU recommends using BW64, so we recorgnize both.
|
|
|
|
const RF64_SIZE_MARKER: u32 = 0xFF_FF_FF_FF;
|
|
|
|
#[derive(Debug)]
|
|
pub enum Event {
|
|
StartParse,
|
|
ReadHeader { signature: FourCC, length_field: u32 },
|
|
ReadRF64Header { signature: FourCC },
|
|
ReadDS64 {file_size: u64, long_sizes: HashMap<FourCC,u64> },
|
|
BeginChunk { signature: FourCC, content_start: u64, content_length: u64 },
|
|
Failed { error: Error },
|
|
FinishParse
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum State {
|
|
New,
|
|
ReadyForHeader,
|
|
ReadyForDS64,
|
|
ReadyForChunk { at: u64, remaining: u64 },
|
|
Error,
|
|
Complete
|
|
}
|
|
|
|
pub struct Parser<R: Read + Seek> {
|
|
stream: R,
|
|
state: State,
|
|
ds64state: HashMap<FourCC,u64>
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub struct ChunkIteratorItem {
|
|
pub signature: FourCC,
|
|
pub start: u64,
|
|
pub length: u64
|
|
}
|
|
|
|
impl<R: Read + Seek> Parser<R> {
|
|
|
|
// wraps a stream
|
|
pub fn make(stream: R) -> Result<Self, Error> {
|
|
let newmap: HashMap<FourCC, u64> = HashMap::new();
|
|
let mut the_stream = stream;
|
|
the_stream.seek(Start(0))?;
|
|
return Ok(Parser {
|
|
stream: the_stream,
|
|
state: State::New,
|
|
ds64state: newmap,
|
|
})
|
|
}
|
|
|
|
// pub fn into_inner(self) -> R {
|
|
// self.stream
|
|
// }
|
|
|
|
pub fn into_chunk_iterator(self) -> impl Iterator<Item = Result<ChunkIteratorItem, Error>>{
|
|
self.filter_map({|event|
|
|
if let Event::BeginChunk {signature , content_start, content_length } = event {
|
|
Some(Ok(ChunkIteratorItem {signature, start: content_start, length: content_length }))
|
|
} else if let Event::Failed { error } = event {
|
|
Some(Err(error))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn into_chunk_list(self) -> Result<Vec<ChunkIteratorItem>,Error> {
|
|
let mut error = Ok(());
|
|
|
|
let chunks = self.into_chunk_iterator()
|
|
.scan(&mut error, |err, res| match res {
|
|
Ok(ok) => Some(ok),
|
|
Err(e) => { **err = Err(e); None }
|
|
})
|
|
.collect();
|
|
|
|
error?;
|
|
|
|
Ok( chunks )
|
|
}
|
|
|
|
}
|
|
|
|
impl<R: Read + Seek> Iterator for Parser<R> {
|
|
type Item = Event;
|
|
|
|
fn next(&mut self) -> Option<Event> {
|
|
let (event, next_state) = self.advance();
|
|
self.state = next_state;
|
|
return event;
|
|
}
|
|
}
|
|
|
|
impl<R: Read + Seek> Parser<R> {
|
|
|
|
fn parse_header(&mut self) -> Result<(Event,State),io::Error> {
|
|
let file_sig = self.stream.read_fourcc()?;
|
|
let length = self.stream.read_u32::<LittleEndian>()?;
|
|
let list_sig = self.stream.read_fourcc()?;
|
|
|
|
let event : Event;
|
|
let next_state: State;
|
|
|
|
match (file_sig, length, list_sig) {
|
|
(RIFF_SIG, size, WAVE_SIG) => {
|
|
event = Event::ReadHeader {
|
|
signature: file_sig,
|
|
length_field: size
|
|
};
|
|
|
|
next_state = State::ReadyForChunk {
|
|
at: 12,
|
|
remaining: (length - 4) as u64,
|
|
};
|
|
},
|
|
(RF64_SIG, RF64_SIZE_MARKER, WAVE_SIG) | (BW64_SIG, RF64_SIZE_MARKER, WAVE_SIG) => {
|
|
event = Event::ReadRF64Header {
|
|
signature: file_sig
|
|
};
|
|
|
|
next_state = State::ReadyForDS64;
|
|
},
|
|
_ => {
|
|
event = Event::Failed {
|
|
error: Error::HeaderNotRecognized
|
|
};
|
|
next_state = State::Error;
|
|
}
|
|
}
|
|
|
|
return Ok( (event, next_state) );
|
|
}
|
|
|
|
fn parse_ds64(&mut self) -> Result<(Event, State), Error> {
|
|
let at :u64 = 12;
|
|
|
|
let ds64_sig = self.stream.read_fourcc()?;
|
|
let ds64_size = self.stream.read_u32::<LittleEndian>()? as u64;
|
|
let mut read :u64 = 0;
|
|
|
|
if ds64_sig != DS64_SIG {
|
|
return Err(Error::MissingRequiredDS64);
|
|
|
|
} else {
|
|
let long_file_size = self.stream.read_u64::<LittleEndian>()?;
|
|
let long_data_size = self.stream.read_u64::<LittleEndian>()?;
|
|
let _long_frame_count = self.stream.read_u64::<LittleEndian>(); // dead frame count field
|
|
read += 24;
|
|
|
|
let field_count = self.stream.read_u32::<LittleEndian>()?;
|
|
read += 4;
|
|
|
|
for _ in 0..field_count {
|
|
let this_fourcc = self.stream.read_fourcc()?;
|
|
let this_field_size = self.stream.read_u64::<LittleEndian>()?;
|
|
self.ds64state.insert(this_fourcc, this_field_size);
|
|
read += 12;
|
|
}
|
|
|
|
self.ds64state.insert(DATA_SIG, long_data_size);
|
|
|
|
if read < ds64_size {
|
|
/* for some reason the ds64 chunk returned by Pro Tools is longer than
|
|
it should be but it's all zeroes so... skip.
|
|
|
|
For the record libsndfile seems to do the same thing...
|
|
https://github.com/libsndfile/libsndfile/blob/08d802a3d18fa19c74f38ed910d9e33f80248187/src/rf64.c#L230
|
|
*/
|
|
let _ = self.stream.seek(Current((ds64_size - read) as i64));
|
|
}
|
|
|
|
let event = Event::ReadDS64 {
|
|
file_size: long_file_size,
|
|
long_sizes : self.ds64state.clone(),
|
|
};
|
|
|
|
let state = State::ReadyForChunk {
|
|
at: at + 8 + ds64_size,
|
|
remaining: long_file_size - (4 + 8 + ds64_size),
|
|
};
|
|
|
|
return Ok( (event, state) );
|
|
}
|
|
}
|
|
|
|
fn enter_chunk(&mut self, at :u64, remaining: u64) -> Result<(Event, State), io::Error> {
|
|
|
|
let event;
|
|
let state;
|
|
|
|
if remaining == 0 {
|
|
event = Event::FinishParse;
|
|
state = State::Complete;
|
|
|
|
} else {
|
|
let this_fourcc = self.stream.read_fourcc()?;
|
|
let this_size: u64;
|
|
|
|
if self.ds64state.contains_key(&this_fourcc) {
|
|
this_size = self.ds64state[&this_fourcc];
|
|
let _skip = self.stream.read_u32::<LittleEndian>()? as u64;
|
|
} else {
|
|
this_size = self.stream.read_u32::<LittleEndian>()? as u64;
|
|
}
|
|
|
|
let this_displacement :u64 = if this_size % 2 == 1 { this_size + 1 } else { this_size };
|
|
self.stream.seek(Current(this_displacement as i64))?;
|
|
|
|
event = Event::BeginChunk {
|
|
signature: this_fourcc,
|
|
content_start: at + 8,
|
|
content_length: this_size
|
|
};
|
|
|
|
state = State::ReadyForChunk {
|
|
at: at + 8 + this_displacement,
|
|
remaining: remaining - 8 - this_displacement
|
|
}
|
|
}
|
|
|
|
return Ok( (event, state) );
|
|
}
|
|
|
|
fn handle_state(&mut self) -> Result<(Option<Event>, State), Error> {
|
|
match self.state {
|
|
State::New => {
|
|
return Ok( ( Some(Event::StartParse) , State::ReadyForHeader) );
|
|
},
|
|
State::ReadyForHeader => {
|
|
let (event, state) = self.parse_header()?;
|
|
return Ok( ( Some(event), state ) );
|
|
},
|
|
State::ReadyForDS64 => {
|
|
let (event, state) = self.parse_ds64()?;
|
|
return Ok( ( Some(event), state ) );
|
|
},
|
|
State::ReadyForChunk { at, remaining } => {
|
|
let (event, state) = self.enter_chunk(at, remaining)?;
|
|
return Ok( ( Some(event), state ) );
|
|
},
|
|
State::Error => {
|
|
return Ok( ( Some(Event::FinishParse) , State::Complete ) );
|
|
},
|
|
State::Complete => {
|
|
return Ok( ( None, State::Complete ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
fn advance(&mut self) -> (Option<Event>, State) {
|
|
match self.handle_state() {
|
|
Ok(( event , state) ) => {
|
|
return (event, state);
|
|
},
|
|
Err(error) => {
|
|
return (Some(Event::Failed { error: error.into() } ), State::Error );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|