#![doc = include_str!("../README.md")]
mod writer;
use std::fs::File;
use std::io::ErrorKind;
use std::io::{Error, Result};
use memmap2::{Mmap, MmapOptions};
pub type FourCC = [u8; 4];
const RIFF: FourCC = [0x52, 0x49, 0x46, 0x46];
const LIST: FourCC = [0x4c, 0x49, 0x53, 0x54];
#[derive(Debug, Clone)]
pub enum Entry<T> {
List(List<T>),
Chunk(Chunk<T>),
}
impl<T: Data> Entry<T> {
fn bytes_len(&self) -> usize {
match self {
Entry::List(l) => l.bytes_len(),
Entry::Chunk(c) => c.bytes_len(),
}
}
}
impl Entry<DataRef> {
pub fn to_owned(self, map: &[u8]) -> Entry<DataOwned> {
match self {
Entry::List(l) => Entry::List(l.to_owned(map)),
Entry::Chunk(c) => Entry::Chunk(c.to_owned(map)),
}
}
}
#[derive(Debug, Clone)]
pub struct List<T> {
pub fourcc: FourCC,
pub list_type: FourCC,
pub children: Vec<Entry<T>>,
}
impl<T> List<T> where T: Data {
pub fn bytes_len(&self) -> usize {
12 + self.children.iter().map(Entry::bytes_len).sum::<usize>()
}
}
impl List<DataRef> {
fn to_owned(self, map: &[u8]) -> List<DataOwned> {
List {
fourcc: self.fourcc,
list_type: self.list_type,
children: self.children.into_iter().map(|c| c.to_owned(map)).collect(),
}
}
}
pub trait Data {
fn size(&self) -> usize;
}
#[derive(Debug, Clone)]
pub struct DataRef {
pub offset: usize,
pub size: usize,
}
impl Data for DataRef {
fn size(&self) -> usize {
self.size
}
}
impl DataRef {
fn to_owned(&self, map: &[u8]) -> DataOwned {
DataOwned(map[self.offset..][..self.size].into())
}
}
#[derive(Debug, Clone)]
pub struct DataOwned(pub Vec<u8>);
impl Data for DataOwned {
fn size(&self) -> usize {
self.0.len()
}
}
#[derive(Debug, Clone)]
pub struct Chunk<T> {
pub id: FourCC,
pub data: T,
pub chunk_size: usize,
}
impl<T: Data> Chunk<T> {
fn bytes_len(&self) -> usize {
let s = self.data.size() + 8;
s + s % 2
}
}
impl Chunk<DataRef> {
pub fn bytes<'a>(&self, map: &'a [u8]) -> &'a[u8] {
&map[self.data.offset..][..self.data.size]
}
pub fn to_owned(&self, map: &[u8]) -> Chunk<DataOwned> {
Chunk {
id: self.id,
chunk_size: self.chunk_size,
data: self.data.to_owned(map),
}
}
}
pub struct RiffFile {
mmap: Mmap,
file_type: FourCC,
data_size: usize,
}
type WithEnd<T> = (T, usize);
impl RiffFile {
pub fn open(filename: &str) -> Result<Self> {
let file = File::open(&filename)?;
Self::open_with_file_handle(&file)
}
pub fn open_with_file_handle(file: &File) -> Result<Self> {
let metadata = file.metadata()?;
let file_size = metadata.len() as usize;
let mmap = unsafe { MmapOptions::new().map(&*file)? };
let header = &mmap[0..12];
let four_cc = parse_fourcc(&header[0..4]);
if four_cc != RIFF {
return Err(Error::new(ErrorKind::Other, "Incorrect RIFF header"));
}
let declared_size = parse_size(&header[4..8]) as usize;
if file_size != declared_size as usize + 8 {
}
let file_type: FourCC = parse_fourcc(&header[8..12]);
Ok(Self {
mmap,
file_type,
data_size: declared_size,
})
}
pub fn file_type(&self) -> &FourCC {
&self.file_type
}
pub fn file_size(&self) -> usize {
self.data_size
}
pub fn bytes(&self) -> &[u8] {
&self.mmap[..]
}
pub fn read_file(&self) -> Result<Entry<DataRef>> {
self.read_entry(0).map(|(e, _)| e)
}
fn read_entry(&self, offset: usize) -> Result<WithEnd<Entry<DataRef>>> {
let header = &self.mmap[offset..offset + 8];
let pos = offset + 8_usize;
let four_cc = parse_fourcc(&header[0..4]);
let size = parse_size(&header[4..8]) as usize;
if four_cc == LIST || four_cc == RIFF {
self.read_list(four_cc, pos, size)
} else {
self.read_chunk(four_cc, pos, size)
}
}
fn read_list(&self, fourcc: FourCC, offset: usize, list_size: usize) -> Result<WithEnd<Entry<DataRef>>> {
let header = &self.mmap[offset..offset + 4];
let data_offset = offset + 4_usize;
let list_type = parse_fourcc(&header[0..4]);
let data_size = list_size - 4;
let mut children = vec![];
let mut pos = data_offset;
let end = data_offset + data_size;
while pos < end {
let (entry, eend) = self.read_entry(pos)?;
pos = eend;
children.push(entry);
}
Ok((
Entry::List(List::<DataRef> {
fourcc,
list_type,
children,
}),
end,
))
}
fn read_chunk(&self, chunk_id: FourCC, offset: usize, chunk_size: usize) -> Result<WithEnd<Entry<DataRef>>> {
let data_size = chunk_size + chunk_size % 2;
Ok((
Entry::Chunk(Chunk::<DataRef> {
data: DataRef { offset, size: data_size},
id: chunk_id,
chunk_size,
}),
offset + data_size,
))
}
}
fn parse_fourcc(header: &[u8]) -> FourCC {
let mut four_cc: FourCC = Default::default();
four_cc.copy_from_slice(header);
four_cc
}
fn parse_size(array: &[u8]) -> u32 {
(array[0] as u32)
+ ((array[1] as u32) << 8)
+ ((array[2] as u32) << 16)
+ ((array[3] as u32) << 24)
}