19 Commits

Author SHA1 Message Date
Jamie Hardt
e502913f4f Updated readme 2020-12-10 18:11:48 -08:00
Jamie Hardt
4890483dcd Cue lists implemented 2020-12-10 18:09:28 -08:00
Jamie Hardt
8519854596 Cue lists in progress
Need to clean up zero-terminated strings in adtl
2020-12-10 14:48:25 -08:00
Jamie Hardt
c947904d0f Cue point implementation 2020-12-10 12:26:49 -08:00
Jamie Hardt
2323d61ee9 Added test media from izotope RX Editor 2020-12-09 20:35:06 -08:00
Jamie Hardt
60af2f43ff Update README.md 2020-12-06 12:16:49 -08:00
Jamie Hardt
eb431f865f Update README.md 2020-12-06 11:47:53 -08:00
Jamie Hardt
8b049e4245 Chanegd reader interface
broadcast_extension returns an Option<> now
2020-12-04 22:22:40 -08:00
Jamie Hardt
dfe90fba4a Documentation 2020-12-04 22:11:25 -08:00
Jamie Hardt
b2d4bff28b Update lib.rs 2020-12-04 20:39:58 -08:00
Jamie Hardt
79173b0576 Update README.md 2020-12-04 20:39:04 -08:00
Jamie Hardt
e15e36d65d Update README.md 2020-12-04 20:35:39 -08:00
Jamie Hardt
1dc5f153b0 Update README.md 2020-12-04 20:34:08 -08:00
Jamie Hardt
7a9f9c8bb3 Update README.md
Roadmap updates to "Features"
2020-12-04 20:33:30 -08:00
Jamie Hardt
0dd7ee4d18 Merge branch 'master' of https://github.com/iluvcapra/bwavfile 2020-12-03 23:57:43 -08:00
Jamie Hardt
a213e748db Sampler resources 2020-12-03 23:47:41 -08:00
Jamie Hardt
d221629b3e More notes on things to do 2020-12-02 22:39:02 -08:00
Jamie Hardt
2fcb211a67 Docs 2020-12-02 22:28:33 -08:00
Jamie Hardt
dfe513b596 iXML and axml cleanup impl 2020-12-02 22:27:14 -08:00
11 changed files with 493 additions and 57 deletions

2
Cargo.lock generated
View File

@@ -2,7 +2,7 @@
# It is not intended for manual editing. # It is not intended for manual editing.
[[package]] [[package]]
name = "bwavfile" name = "bwavfile"
version = "0.1.6" version = "0.1.7"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"encoding", "encoding",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bwavfile" name = "bwavfile"
version = "0.1.6" version = "0.1.7"
authors = ["Jamie Hardt <jamiehardt@me.com>"] authors = ["Jamie Hardt <jamiehardt@me.com>"]
edition = "2018" edition = "2018"
license = "MIT" license = "MIT"

View File

@@ -6,22 +6,22 @@
# bwavfile # bwavfile
Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support Rust Wave File Reader/Writer with Broadcast-WAV, MBWF and RF64 Support
This is currently a work-in-progress!
### Features ### Features
- Read standard WAV, Broadcast-Wave, and 64-bit RF64 and BW64 wave files with one interface for This is currently a work-in-progress! However many features presently work:
- [x] Read standard WAV, Broadcast-Wave, and 64-bit RF64 and BW64 wave files with one interface for
all types with transparent format detection. all types with transparent format detection.
- Unified format definition interface for standard and extended-format wave files. - [ ] Wave/RF64 file writing with transparent promotion from WAV to RF64.
- Read channel/speaker map metadata. - [x] Unified format definition interface for standard and extended-format wave files.
- Read standard EBU Broadcast-Wave metadata and decode to fields, including timestamp and SMPTE UMID. - [x] Read channel/speaker map metadata.
- Validate the compatibility of a given wave file for certain regimes. - [x] Read standard EBU Broadcast-Wave metadata and decode to fields, including timestamp and SMPTE UMID.
- Metadata support for ambisonic B-format. - [x] iXML and ADM XML metadata.
- [ ] Broadcast-WAV Level and Quality metadata.
### In Progress - [x] Cue list metadata.
- [ ] Sampler and instrument metadata.
- Wave/RF64 file writing. - [x] Validate the compatibility of a given wave file for certain regimes.
- iXML and ADM XML metadata reading/writing. - [x] Metadata support for ambisonic B-format.
## Use Examples ## Use Examples
@@ -46,6 +46,21 @@ This is currently a work-in-progress!
assert_eq!(read, 1); assert_eq!(read, 1);
``` ```
### Accessing Channel Descriptions
```rust
use bwavfile::{WaveReader, ChannelMask};
let mut f = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap();
let chans = f.channels().unwrap();
assert_eq!(chans[0].index, 0);
assert_eq!(chans[0].speaker, ChannelMask::FrontLeft);
assert_eq!(chans[3].index, 3);
assert_eq!(chans[3].speaker, ChannelMask::LowFrequency);
assert_eq!(chans[4].speaker, ChannelMask::BackLeft);
```
## Note on Testing ## Note on Testing
All of the media for the integration tests is committed to the respository All of the media for the integration tests is committed to the respository

258
src/cue.rs Normal file
View File

@@ -0,0 +1,258 @@
use super::fourcc::{FourCC,ReadFourCC, LABL_SIG, NOTE_SIG, LTXT_SIG};
use super::list_form::collect_list_form;
use byteorder::{ReadBytesExt, LittleEndian};
use encoding::{DecoderTrap};
use encoding::{Encoding};
use encoding::all::ASCII;
use std::io::{Cursor, Error, Read};
#[derive(Copy,Clone, Debug)]
struct RawCue {
cue_point_id : u32,
frame : u32,
chunk_id : FourCC,
chunk_start : u32,
block_start : u32,
frame_offset : u32
}
impl RawCue {
fn read_from(data : &[u8]) -> Result<Vec<Self>,Error> {
let mut rdr = Cursor::new(data);
let count = rdr.read_u32::<LittleEndian>()?;
let mut retval : Vec<Self> = vec![];
for _ in 0..count {
retval.push( Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?,
frame : rdr.read_u32::<LittleEndian>()?,
chunk_id : rdr.read_fourcc()?,
chunk_start : rdr.read_u32::<LittleEndian>()?,
block_start : rdr.read_u32::<LittleEndian>()?,
frame_offset : rdr.read_u32::<LittleEndian>()?
})
}
Ok( retval )
}
}
#[derive(Clone, Debug)]
struct RawLabel {
cue_point_id : u32,
text : Vec<u8>
}
impl RawLabel {
fn read_from(data : &[u8]) -> Result<Self, Error> {
let mut rdr = Cursor::new(data);
let length = data.len();
Ok( Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?,
text : {
let mut buf = vec![0u8; (length - 4) as usize ];
rdr.read_exact(&mut buf)?;
buf
}
})
}
}
#[derive(Clone, Debug)]
struct RawNote {
cue_point_id : u32,
text : Vec<u8>
}
impl RawNote {
fn read_from(data : &[u8]) -> Result<Self, Error> {
let mut rdr = Cursor::new(data);
let length = data.len();
Ok( Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?,
text : {
let mut buf = vec![0u8; (length - 4) as usize ];
rdr.read_exact(&mut buf)?;
buf
}
})
}
}
#[derive(Clone, Debug)]
struct RawLtxt {
cue_point_id : u32,
frame_length : u32,
purpose : FourCC,
country : u16,
language : u16,
dialect : u16,
code_page : u16,
text: Option<Vec<u8>>
}
impl RawLtxt {
fn read_from(data : &[u8]) -> Result<Self, Error> {
let mut rdr = Cursor::new(data);
let length = data.len();
Ok( Self {
cue_point_id : rdr.read_u32::<LittleEndian>()?,
frame_length : rdr.read_u32::<LittleEndian>()?,
purpose : rdr.read_fourcc()?,
country : rdr.read_u16::<LittleEndian>()?,
language : rdr.read_u16::<LittleEndian>()?,
dialect : rdr.read_u16::<LittleEndian>()?,
code_page : rdr.read_u16::<LittleEndian>()?,
text : {
if length - 20 > 0 {
let mut buf = vec![0u8; (length - 20) as usize];
rdr.read_exact(&mut buf)?;
Some( buf )
} else {
None
}
}
})
}
}
#[derive(Clone, Debug)]
enum RawAdtlMember {
Label(RawLabel),
Note(RawNote),
LabeledText(RawLtxt),
Unrecognized(FourCC)
}
impl RawAdtlMember {
fn collect_from(chunk : &[u8]) -> Result<Vec<RawAdtlMember>,Error> {
let chunks = collect_list_form(chunk)?;
let mut retval : Vec<RawAdtlMember> = vec![];
for chunk in chunks.iter() {
retval.push(
match chunk.signature {
LABL_SIG => RawAdtlMember::Label( RawLabel::read_from(&chunk.contents)? ),
NOTE_SIG => RawAdtlMember::Note( RawNote::read_from(&chunk.contents)? ),
LTXT_SIG => RawAdtlMember::LabeledText( RawLtxt::read_from(&chunk.contents)? ),
x => RawAdtlMember::Unrecognized(x)
}
)
}
Ok( retval )
}
}
trait AdtlMemberSearch {
fn labels_for_cue_point(&self, id: u32) -> Vec<&RawLabel>;
fn notes_for_cue_point(&self, id : u32) -> Vec<&RawNote>;
fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt>;
}
impl AdtlMemberSearch for Vec<RawAdtlMember> {
fn labels_for_cue_point(&self, id: u32) -> Vec<&RawLabel> {
self.iter().filter_map(|item| {
match item {
RawAdtlMember::Label(x) if x.cue_point_id == id => Some(x),
_ => None
}
})
.collect()
}
fn notes_for_cue_point(&self, id: u32) -> Vec<&RawNote> {
self.iter().filter_map(|item| {
match item {
RawAdtlMember::Note(x) if x.cue_point_id == id => Some(x),
_ => None
}
})
.collect()
}
fn ltxt_for_cue_point(&self, id: u32) -> Vec<&RawLtxt> {
self.iter().filter_map(|item| {
match item {
RawAdtlMember::LabeledText(x) if x.cue_point_id == id => Some(x),
_ => None
}
})
.collect()
}
}
/// A cue point recorded in the `cue` and `adtl` metadata.
///
///
pub struct Cue {
/// Unique numeric identifier for this cue
pub ident : u32,
/// The time of this marker
pub frame : u32,
/// The length of this marker, if it is a range
pub length : Option<u32>,
/// The text "label"/name of this marker if provided
pub label : Option<String>,
/// The text "note"/comment of this marker if provided
pub note : Option<String>
}
fn convert_to_cue_string(buffer : &[u8]) -> String {
let trimmed : Vec<u8> = buffer.iter().take_while(|c| **c != 0 as u8).cloned().collect();
ASCII.decode(&trimmed, DecoderTrap::Ignore).expect("Error decoding text")
}
impl Cue {
pub fn collect_from(cue_chunk : &[u8], adtl_chunk : Option<&[u8]>) -> Result<Vec<Cue>, Error> {
let raw_cues = RawCue::read_from(cue_chunk)?;
let raw_adtl : Vec<RawAdtlMember>;
if let Some(adtl) = adtl_chunk {
raw_adtl = RawAdtlMember::collect_from(adtl)?;
} else {
raw_adtl = vec![];
}
Ok(
raw_cues.iter()
.map(|i| {
Cue {
ident : i.cue_point_id,
frame : i.frame,
length: {
raw_adtl.ltxt_for_cue_point(i.cue_point_id).first()
.filter(|x| x.purpose == FourCC::make(b"rgn "))
.map(|x| x.frame_length)
},
label: {
raw_adtl.labels_for_cue_point(i.cue_point_id).iter()
.map(|s| convert_to_cue_string(&s.text))
.next()
},
note : {
raw_adtl.notes_for_cue_point(i.cue_point_id).iter()
//.filter_map(|x| str::from_utf8(&x.text).ok())
.map(|s| convert_to_cue_string(&s.text))
.next()
}
}
}).collect()
)
}
}

View File

@@ -105,6 +105,14 @@ pub const BEXT_SIG: FourCC = FourCC::make(b"bext");
pub const JUNK_SIG: FourCC = FourCC::make(b"JUNK"); pub const JUNK_SIG: FourCC = FourCC::make(b"JUNK");
pub const FLLR_SIG: FourCC = FourCC::make(b"FLLR"); pub const FLLR_SIG: FourCC = FourCC::make(b"FLLR");
pub const LIST_SIG: FourCC = FourCC::make(b"LIST");
pub const CUE__SIG: FourCC = FourCC::make(b"cue ");
pub const ADTL_SIG: FourCC = FourCC::make(b"adtl");
pub const LABL_SIG: FourCC = FourCC::make(b"labl");
pub const NOTE_SIG: FourCC = FourCC::make(b"note");
pub const LTXT_SIG: FourCC = FourCC::make(b"ltxt");
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

View File

@@ -15,6 +15,7 @@ production.
Apps we test against: Apps we test against:
- Avid Pro Tools - Avid Pro Tools
- iZotope RX Audio Editor
- FFMpeg - FFMpeg
- Audacity - Audacity
@@ -67,6 +68,8 @@ Things that are _not_ necessarily in the scope of this package:
### Other resources ### Other resources
- [RFC 3261][rfc3261] (June 1998) "WAVE and AVI Codec Registries" - [RFC 3261][rfc3261] (June 1998) "WAVE and AVI Codec Registries"
- [Sampler Metadata](http://www.piclist.com/techref/io/serial/midi/wave.html)
- [Cue list, label and other metadata](https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#smpl)
- [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html) - [Peter Kabal, McGill University](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html)
- [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf) - [Multimedia Programming Interface and Data Specifications 1.0](http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf)
(August 1991), IBM Corporation and Microsoft Corporation (August 1991), IBM Corporation and Microsoft Corporation
@@ -111,7 +114,10 @@ mod parser;
mod raw_chunk_reader; mod raw_chunk_reader;
mod audio_frame_reader; mod audio_frame_reader;
mod list_form;
mod chunks; mod chunks;
mod cue;
mod bext; mod bext;
mod fmt; mod fmt;
@@ -125,3 +131,4 @@ pub use bext::Bext;
pub use fmt::{WaveFmt, WaveFmtExtended, ChannelDescriptor, ChannelMask}; pub use fmt::{WaveFmt, WaveFmtExtended, ChannelDescriptor, ChannelMask};
pub use common_format::CommonFormat; pub use common_format::CommonFormat;
pub use audio_frame_reader::AudioFrameReader; pub use audio_frame_reader::AudioFrameReader;
pub use cue::Cue;

40
src/list_form.rs Normal file
View File

@@ -0,0 +1,40 @@
use super::fourcc::{FourCC, ReadFourCC};
use byteorder::{ReadBytesExt, LittleEndian};
use std::io::{Cursor, Error, Read};
pub struct ListFormItem {
pub signature : FourCC,
pub contents : Vec<u8>
}
/// A helper that will accept a LIST chunk as a [u8]
/// and give you back each segment
///
pub fn collect_list_form(list_contents :& [u8]) -> Result<Vec<ListFormItem>, Error> {
let mut cursor = Cursor::new(list_contents);
let mut remain = list_contents.len();
let _ = cursor.read_fourcc()?; // skip signature
remain -= 4;
let mut retval : Vec<ListFormItem> = vec![];
while remain > 0 {
let this_sig = cursor.read_fourcc()?;
let this_size = cursor.read_u32::<LittleEndian>()? as usize;
remain -= 8;
let mut content_buf = vec![0u8; this_size];
cursor.read_exact(&mut content_buf)?;
remain -= this_size;
retval.push( ListFormItem { signature : this_sig, contents : content_buf } );
if this_size % 2 == 1 {
cursor.read_u8()?;
//panic!("Got this far!");
remain -= 1;
}
}
Ok( retval )
}

View File

@@ -28,7 +28,7 @@ impl<'a,R: Read + Seek> RawChunkReader<'a, R> {
impl<'a, R:Read + Seek> Read for RawChunkReader<'_, R> { impl<'a, R:Read + Seek> Read for RawChunkReader<'_, R> {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> { fn read(&mut self, buf: &mut [u8]) -> Result<usize, Error> {
if self.position >= self.length { if self.position >= self.length {
Err(Error::new(ErrorKind::UnexpectedEof, "RawChunkReader encountered end-of-file")) Ok(0)
} else { } else {
self.reader.seek(Start(self.start + self.position))?; self.reader.seek(Start(self.start + self.position))?;
let to_read = min(self.length - self.position, buf.len() as u64); let to_read = min(self.length - self.position, buf.len() as u64);

View File

@@ -2,15 +2,16 @@
use std::fs::File; use std::fs::File;
use super::parser::Parser; use super::parser::Parser;
use super::fourcc::{FourCC, FMT__SIG,DATA_SIG, BEXT_SIG, JUNK_SIG, FLLR_SIG}; use super::fourcc::{FourCC, ReadFourCC, FMT__SIG,DATA_SIG, BEXT_SIG, LIST_SIG, JUNK_SIG, FLLR_SIG, CUE__SIG, ADTL_SIG};
use super::errors::Error as ParserError; use super::errors::Error as ParserError;
use super::raw_chunk_reader::RawChunkReader; use super::raw_chunk_reader::RawChunkReader;
use super::fmt::{WaveFmt, ChannelDescriptor, ChannelMask}; use super::fmt::{WaveFmt, ChannelDescriptor, ChannelMask};
use super::bext::Bext; use super::bext::Bext;
use super::audio_frame_reader::AudioFrameReader; use super::audio_frame_reader::AudioFrameReader;
use super::chunks::ReadBWaveChunks; use super::chunks::ReadBWaveChunks;
use super::cue::Cue;
use std::io::Cursor;
use std::io::{Read, Seek}; use std::io::{Read, Seek};
@@ -118,18 +119,26 @@ impl<R: Read + Seek> WaveReader<R> {
Ok( data_length / (format.block_alignment as u64) ) Ok( data_length / (format.block_alignment as u64) )
} }
/**
* Sample and frame format of this wave file. /// Sample and frame format of this wave file.
*/ ///
pub fn format(&mut self) -> Result<WaveFmt, ParserError> { pub fn format(&mut self) -> Result<WaveFmt, ParserError> {
self.chunk_reader(FMT__SIG, 0)?.read_wave_fmt() self.chunk_reader(FMT__SIG, 0)?.read_wave_fmt()
} }
/** ///
* The Broadcast-WAV metadata record for this file. /// The Broadcast-WAV metadata record for this file, if present.
*/ ///
pub fn broadcast_extension(&mut self) -> Result<Bext, ParserError> { pub fn broadcast_extension(&mut self) -> Result<Option<Bext>, ParserError> {
self.chunk_reader(BEXT_SIG, 0)?.read_bext() let mut bext_buff : Vec<u8> = vec![ ];
let result = self.read_chunk(BEXT_SIG, 0, &mut bext_buff)?;
if result > 0 {
let mut bext_cursor = Cursor::new(bext_buff);
Ok( Some( bext_cursor.read_bext()? ) )
} else {
Ok( None)
}
} }
/// Describe the channels in this file /// Describe the channels in this file
@@ -137,8 +146,9 @@ impl<R: Read + Seek> WaveReader<R> {
/// Returns a vector of channel descriptors, one for each channel /// Returns a vector of channel descriptors, one for each channel
/// ///
/// ```rust /// ```rust
/// # use bwavfile::WaveReader; /// use bwavfile::WaveReader;
/// # use bwavfile::ChannelMask; /// use bwavfile::ChannelMask;
///
/// let mut f = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap(); /// let mut f = WaveReader::open("tests/media/pt_24bit_51.wav").unwrap();
/// ///
/// let chans = f.channels().unwrap(); /// let chans = f.channels().unwrap();
@@ -163,38 +173,72 @@ impl<R: Read + Seek> WaveReader<R> {
.collect() ) .collect() )
} }
/// Read cue points.
///
/// ```rust
/// use bwavfile::WaveReader;
/// use bwavfile::Cue;
///
/// let mut f = WaveReader::open("tests/media/izotope_test.wav").unwrap();
/// let cue_points = f.cue_points().unwrap();
///
/// assert_eq!(cue_points.len(), 3);
/// assert_eq!(cue_points[0].ident, 1);
/// assert_eq!(cue_points[0].frame, 12532);
/// assert_eq!(cue_points[0].length, None);
/// assert_eq!(cue_points[0].label, Some(String::from("Marker 1")));
/// assert_eq!(cue_points[0].note, Some(String::from("Marker 1 Comment")));
///
/// assert_eq!(cue_points[1].ident, 2);
/// assert_eq!(cue_points[1].frame, 20997);
/// assert_eq!(cue_points[1].length, None);
/// assert_eq!(cue_points[1].label, Some(String::from("Marker 2")));
/// assert_eq!(cue_points[1].note, Some(String::from("Marker 2 Comment")));
///
/// assert_eq!(cue_points[2].ident, 3);
/// assert_eq!(cue_points[2].frame, 26711);
/// assert_eq!(cue_points[2].length, Some(6465));
/// assert_eq!(cue_points[2].label, Some(String::from("Timed Region")));
/// assert_eq!(cue_points[2].note, Some(String::from("Region Comment")));
///
/// ```
pub fn cue_points(&mut self) -> Result<Vec<Cue>,ParserError> {
let mut cue_buffer : Vec<u8> = vec![];
let mut adtl_buffer : Vec<u8> = vec![];
let cue_read = self.read_chunk(CUE__SIG, 0, &mut cue_buffer)?;
let adtl_read = self.read_list(ADTL_SIG, &mut adtl_buffer)?;
match (cue_read, adtl_read) {
(0,_) => Ok( vec![] ),
(_,0) => Ok( Cue::collect_from(&cue_buffer, None)? ),
(_,_) => Ok( Cue::collect_from(&cue_buffer, Some(&adtl_buffer) )? )
}
}
/// Read iXML data. /// Read iXML data.
/// ///
/// The iXML data will be appended to `buffer`.
/// If there are no iXML metadata present in the file, /// If there are no iXML metadata present in the file,
/// Err(Error::ChunkMissing { "iXML" } will be returned. /// Ok(0) will be returned.
pub fn ixml(&mut self, buffer: &mut Vec<u8>) -> Result<usize, ParserError> { pub fn read_ixml(&mut self, buffer: &mut Vec<u8>) -> Result<usize, ParserError> {
let ixml_sig: FourCC = FourCC::make(b"iXML"); let ixml_fourcc = FourCC::make(b"iXML");
let mut chunk = self.chunk_reader(ixml_sig, 0)?; self.read_chunk(ixml_fourcc, 0, buffer)
match chunk.read_to_end(buffer) {
Ok(read) => Ok(read),
Err(error) => Err(error.into())
}
} }
/// Read AXML data. /// Read AXML data.
/// ///
/// By convention this will generally be ADM metadata. /// The axml data will be appended to `buffer`. By convention this will
/// generally be ADM metadata.
/// ///
/// If there are no iXML metadata present in the file, /// If there are no axml metadata present in the file,
/// Err(Error::ChunkMissing { "axml" } will be returned. /// Ok(0) will be returned
pub fn axml(&mut self, buffer: &mut Vec<u8>) -> Result<usize, ParserError> { pub fn read_axml(&mut self, buffer: &mut Vec<u8>) -> Result<usize, ParserError> {
let axml_sig: FourCC = FourCC::make(b"axml"); let axml_fourcc = FourCC::make(b"axml");
let mut chunk = self.chunk_reader(axml_sig, 0)?; self.read_chunk(axml_fourcc, 0, buffer)
match chunk.read_to_end(buffer) {
Ok(read) => Ok(read),
Err(error) => Err(error.into())
}
} }
/** /**
* Validate file is readable. * Validate file is readable.
* *
@@ -220,6 +264,8 @@ impl<R: Read + Seek> WaveReader<R> {
* `Ok(())` if the source is `validate_readable()` AND * `Ok(())` if the source is `validate_readable()` AND
* *
* - Contains _only_ a `fmt` chunk and `data` chunk, with no other chunks present * - Contains _only_ a `fmt` chunk and `data` chunk, with no other chunks present
* - `fmt` chunk is exactly 16 bytes long and begins _exactly_ at file offset 12
* - `data` content begins _exactly_ at file offset 36
* - is not an RF64/BW64 * - is not an RF64/BW64
* *
* Some clients require a WAVE file to only contain format and data without any other * Some clients require a WAVE file to only contain format and data without any other
@@ -248,7 +294,7 @@ impl<R: Read + Seek> WaveReader<R> {
.into_chunk_list()?.iter().map(|c| c.signature ).collect(); .into_chunk_list()?.iter().map(|c| c.signature ).collect();
if chunk_fourccs == vec![FMT__SIG, DATA_SIG] { if chunk_fourccs == vec![FMT__SIG, DATA_SIG] {
Ok(()) Ok(()) /* FIXME: finish implementation */
} else { } else {
Err( ParserError::NotMinimalWaveFile ) Err( ParserError::NotMinimalWaveFile )
} }
@@ -332,21 +378,78 @@ impl<R: Read + Seek> WaveReader<R> {
} }
} }
impl<R:Read+Seek> WaveReader<R> { /* Private Implementation */ impl<R:Read+Seek> WaveReader<R> {
// Private implementation
//
// As time passes thi get smore obnoxious because I haven't implemented recursive chunk
// parsing in the raw parser and I'm working around it
fn chunk_reader(&mut self, signature: FourCC, at_index: u32) -> Result<RawChunkReader<R>, ParserError> { fn chunk_reader(&mut self, signature: FourCC, at_index: u32) -> Result<RawChunkReader<R>, ParserError> {
let (start, length) = self.get_chunk_extent_at_index(signature, at_index)?; let (start, length) = self.get_chunk_extent_at_index(signature, at_index)?;
Ok( RawChunkReader::new(&mut self.inner, start, length) ) Ok( RawChunkReader::new(&mut self.inner, start, length) )
} }
fn get_chunk_extent_at_index(&mut self, fourcc: FourCC, index: u32) -> Result<(u64,u64), ParserError> { fn read_list(&mut self, ident: FourCC, buffer: &mut Vec<u8>) -> Result<usize, ParserError> {
if let Some(index) = self.get_list_form(ident)? {
self.read_chunk(LIST_SIG, index, buffer)
} else {
Ok( 0 )
}
}
fn read_chunk(&mut self, ident: FourCC, at: u32, buffer: &mut Vec<u8>) -> Result<usize, ParserError> {
let result = self.chunk_reader(ident, at);
match result {
Ok(mut chunk) => {
match chunk.read_to_end(buffer) {
Ok(read) => Ok(read),
Err(err) => Err(err.into())
}
},
Err(ParserError::ChunkMissing { signature : _} ) => Ok(0),
Err( any ) => Err(any.into())
}
}
/// Extent of every chunk with the given fourcc
fn get_chunks_extents(&mut self, fourcc: FourCC) -> Result<Vec<(u64,u64)>, ParserError> {
let p = Parser::make(&mut self.inner)?.into_chunk_list()?; let p = Parser::make(&mut self.inner)?.into_chunk_list()?;
if let Some(chunk) = p.iter().filter(|item| item.signature == fourcc).nth(index as usize) { Ok( p.iter().filter(|item| item.signature == fourcc)
Ok ((chunk.start, chunk.length)) .map(|item| (item.start, item.length)).collect() )
}
/// Index of first LIST for with the given FORM fourcc
fn get_list_form(&mut self, fourcc: FourCC) -> Result<Option<u32>, ParserError> {
for (n, (start, length)) in self.get_chunks_extents(LIST_SIG)?.iter().enumerate() {
let mut reader = RawChunkReader::new(&mut self.inner, *start, *length);
let this_fourcc = reader.read_fourcc()?;
if this_fourcc == fourcc {
return Ok( Some( n as u32 ) );
}
}
Ok( None )
}
fn get_chunk_extent_at_index(&mut self, fourcc: FourCC, index: u32) -> Result<(u64,u64), ParserError> {
if let Some((start, length)) = self.get_chunks_extents(fourcc)?.iter().nth(index as usize) {
Ok ((*start, *length))
} else { } else {
Err( ParserError::ChunkMissing { signature : fourcc }) Err( ParserError::ChunkMissing { signature : fourcc } )
} }
} }
} }
#[test]
fn test_list_form() {
let mut f = WaveReader::open("tests/media/izotope_test.wav").unwrap();
let mut buf : Vec<u8> = vec![];
f.read_list(ADTL_SIG, &mut buf).unwrap();
assert_ne!(buf.len(), 0);
}

5
tests/Untitled.txt Normal file
View File

@@ -0,0 +1,5 @@
Marker file version: 1
Time format: Time
Marker 1 00:00:00.28417234 Marker 1 Comment
Marker 2 00:00:00.47612245 Marker 2 Comment
Timed Region 00:00:00.60569161 00:00:00.75226757 Region Comment

Binary file not shown.