Skip to main content

riff/
lib.rs

1//! # riff
2//! 
3//! `riff` provides utility methods for reading and writing RIFF-formatted files,
4//! such as Microsoft Wave, AVI or DLS files.
5
6use std::fmt;
7use std::io::Read;
8use std::io::Write;
9use std::io::Seek;
10use std::io::SeekFrom;
11use std::convert::TryInto;
12
13/// A chunk id, also known as FourCC
14#[derive(PartialEq, Eq, Clone, Copy, Hash)]
15pub struct ChunkId {
16  /// The raw bytes of the id
17  pub value: [u8; 4]
18}
19
20/// The `RIFF` id
21pub static RIFF_ID: ChunkId = ChunkId { value: [0x52, 0x49, 0x46, 0x46] };
22
23/// The `LIST` id
24pub static LIST_ID: ChunkId = ChunkId { value: [0x4C, 0x49, 0x53, 0x54] };
25
26/// The `seqt` id
27pub static SEQT_ID: ChunkId = ChunkId { value: [0x73, 0x65, 0x71, 0x74] };
28
29impl ChunkId {
30  /// Returns the value of the id as a string.
31  /// 
32  /// # Examples
33  /// ```
34  /// assert_eq!(riff::RIFF_ID.as_str(), "RIFF");
35  /// ```
36  /// 
37  /// # Panics
38  /// This function panics when the value does not represent a valid UTF-8 string.
39  pub fn as_str(&self) -> &str {
40    std::str::from_utf8(&self.value).unwrap()
41  }
42
43  /// Creates a new ChunkId from a string.
44  /// 
45  /// # Examples
46  /// ```
47  /// # use std::error::Error;
48  /// #
49  /// # fn try_main() -> Result<(), Box<Error>> {
50  /// let chunk_id = riff::ChunkId::new("RIFF")?;
51  /// #   Ok(())
52  /// # }
53  /// #
54  /// # fn main() {
55  /// #     try_main().unwrap();
56  /// # }
57  /// ```
58  /// 
59  /// # Errors
60  /// The function fails when the string's length in bytes is not exactly 4.
61  pub fn new(s: &str) -> Result<ChunkId, &str> {
62    let bytes = s.as_bytes();
63    if bytes.len() != 4 {
64      Err("Invalid length")
65    } else {
66      let mut a: [u8; 4] = Default::default();
67      a.copy_from_slice(&bytes[..]);
68      Ok(ChunkId { value: a })
69    }
70  }
71}
72
73impl fmt::Display for ChunkId {
74  fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result {
75        write!(f, "'{}'", self.as_str())
76  }
77}
78
79impl fmt::Debug for ChunkId {
80  fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result {
81        write!(f, "{}", self)
82  }
83}
84
85#[derive(PartialEq, Debug)]
86pub enum ChunkContents {
87  Data(ChunkId, Vec<u8>),
88  Children(ChunkId, ChunkId, Vec<ChunkContents>),
89  ChildrenNoType(ChunkId, Vec<ChunkContents>)
90}
91
92impl ChunkContents {
93  pub fn write<T>(&self, writer: &mut T) -> std::io::Result<u64>
94      where T: Seek + Write {
95    match &self {
96      &ChunkContents::Data(id, data) => {
97        if data.len() as u64 > u32::MAX as u64 {
98          use std::io::{Error, ErrorKind};
99          return Err(Error::new(ErrorKind::InvalidData, "Data too big"));
100        }
101
102        let len = data.len() as u32;
103        writer.write_all(&id.value)?;
104        writer.write_all(&len.to_le_bytes())?;
105        writer.write_all(&data)?;
106        if len % 2 != 0 {
107          let single_byte: [u8; 1] = [0];
108          writer.write_all(&single_byte)?;
109        }
110        Ok((8 + len + (len % 2)).into())
111      }
112      &ChunkContents::Children(id, chunk_type, children) => {
113        writer.write_all(&id.value)?;
114        let len_pos = writer.seek(SeekFrom::Current(0))?;
115        let zeros: [u8; 4] = [0, 0, 0, 0];
116        writer.write_all(&zeros)?;
117        writer.write_all(&chunk_type.value)?;
118        let mut total_len: u64 = 4;
119        for child in children {
120          total_len = total_len + child.write(writer)?;
121        }
122
123        if total_len > u32::MAX as u64 {
124          use std::io::{Error, ErrorKind};
125          return Err(Error::new(ErrorKind::InvalidData, "Data too big"));
126        }
127
128        let end_pos = writer.seek(SeekFrom::Current(0))?;
129        writer.seek(SeekFrom::Start(len_pos))?;
130        writer.write_all(&(total_len as u32).to_le_bytes())?;
131        writer.seek(SeekFrom::Start(end_pos))?;
132
133        Ok((8 + total_len + (total_len % 2)).into())
134      }
135      &ChunkContents::ChildrenNoType(id, children) => {
136        writer.write_all(&id.value)?;
137        let len_pos = writer.seek(SeekFrom::Current(0))?;
138        let zeros: [u8; 4] = [0, 0, 0, 0];
139        writer.write_all(&zeros)?;
140        let mut total_len: u64 = 0;
141        for child in children {
142          total_len = total_len + child.write(writer)?;
143        }
144
145        if total_len > u32::MAX as u64 {
146          use std::io::{Error, ErrorKind};
147          return Err(Error::new(ErrorKind::InvalidData, "Data too big"));
148        }
149
150        let end_pos = writer.seek(SeekFrom::Current(0))?;
151        writer.seek(SeekFrom::Start(len_pos))?;
152        writer.write_all(&(total_len as u32).to_le_bytes())?;
153        writer.seek(SeekFrom::Start(end_pos))?;
154
155        Ok((8 + total_len + (total_len % 2)).into())
156      }
157    }
158  }
159}
160
161/// A chunk, also known as a form
162#[derive(PartialEq, Eq, Debug)]
163pub struct Chunk {
164  pos: u64,
165  id: ChunkId,
166  len: u32,
167}
168
169/// An iterator over the children of a `Chunk`
170pub struct Iter<'a, T>
171    where T: Seek + Read {
172  end: u64,
173  cur: u64,
174  stream: &'a mut T
175}
176
177impl<'a, T> Iterator for Iter<'a, T>
178    where T: Seek + Read {
179  type Item = std::io::Result<Chunk>;
180
181  fn next(&mut self) -> Option<Self::Item> {
182    if self.cur >= self.end {
183      return None
184    }
185
186    let chunk = match Chunk::read(&mut self.stream, self.cur) {
187        Ok(chunk) => chunk,
188        Err(err) => return Some(Err(err)),
189    };
190    let len = chunk.len() as u64;
191    self.cur = self.cur + len + 8 + (len % 2);
192    Some(Ok(chunk))
193  }
194}
195
196impl Chunk {
197  /// Returns the `ChunkId` of this chunk.
198  pub fn id(&self) -> ChunkId {
199    self.id.clone()
200  }
201
202  /// Returns the number of bytes in this chunk.
203  pub fn len(&self) -> u32 {
204    self.len
205  }
206
207  /// Returns the offset of this chunk from the start of the stream.
208  pub fn offset(&self) -> u64 {
209    self.pos
210  }
211
212  /// Reads the chunk type of this chunk.
213  /// 
214  /// Generally only valid for `RIFF` and `LIST` chunks.
215  pub fn read_type<T>(&self, stream: &mut T) -> std::io::Result<ChunkId>
216      where T: Read + Seek {
217    stream.seek(SeekFrom::Start(self.pos + 8))?;
218
219    let mut fourcc : [u8; 4] = [0; 4];
220    stream.read_exact(&mut fourcc)?;
221
222    Ok(ChunkId { value: fourcc })
223  }
224
225  /// Reads a chunk from the specified position in the stream.
226  pub fn read<T>(stream: &mut T, pos: u64) -> std::io::Result<Chunk>
227      where T: Read + Seek {
228    stream.seek(SeekFrom::Start(pos))?;
229
230    let mut fourcc : [u8; 4] = [0; 4];
231    stream.read_exact(&mut fourcc)?;
232
233    let mut len : [u8; 4] = [0; 4];
234    stream.read_exact(&mut len)?;
235
236    Ok(Chunk {
237      pos: pos,
238      id: ChunkId { value: fourcc },
239      len: u32::from_le_bytes(len)
240    })
241  }
242  
243  /// Reads the entirety of the contents of a chunk.
244  pub fn read_contents<T>(&self, stream: &mut T) -> std::io::Result<Vec<u8>>
245      where T: Read + Seek {
246    stream.seek(SeekFrom::Start(self.pos + 8))?;
247
248    let mut data: Vec<u8> = vec![0; self.len.try_into().unwrap()];
249    stream.read_exact(&mut data)?;
250
251    Ok(data)
252  }
253
254  /// Returns an iterator over the children of the chunk.
255  /// 
256  /// If the chunk has children but is noncompliant, e.g. it has
257  /// no type identifier (like `seqt` chunks), use `iter_no_type` instead.
258  pub fn iter<'a, T>(&self, stream: &'a mut T) -> Iter<'a, T>
259      where T: Seek + Read {
260        Iter {
261      cur: self.pos + 12,
262      end: self.pos + 4 + (self.len as u64),
263      stream: stream
264    }
265  }
266
267  /// Returns an iterator over the chilren of the chunk. Only valid for
268  /// noncompliant chunks that have children but no chunk type identifier
269  /// (like `seqt` chunks).
270  pub fn iter_no_type<'a, T>(&self, stream: &'a mut T) -> Iter<'a, T>
271      where T: Seek + Read {
272        Iter {
273      cur: self.pos + 8,
274      end: self.pos + 4 + (self.len as u64),
275      stream: stream
276    }
277  }
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283
284    #[test]
285    fn chunkid_from_str() {
286        assert_eq!(ChunkId::new("RIFF").unwrap(), RIFF_ID);
287        assert_eq!(ChunkId::new("LIST").unwrap(), LIST_ID);
288        assert_eq!(ChunkId::new("seqt").unwrap(), SEQT_ID);
289
290        assert_eq!(ChunkId::new("123 ").unwrap(),
291          ChunkId { value: [0x31, 0x32, 0x33, 0x20] });
292
293        assert_eq!(ChunkId::new("123"), Err("Invalid length"));
294        assert_eq!(ChunkId::new("12345"), Err("Invalid length"));
295    }
296
297    #[test]
298    fn chunkid_to_str() {
299      assert_eq!(RIFF_ID.as_str(), "RIFF");
300      assert_eq!(LIST_ID.as_str(), "LIST");
301      assert_eq!(SEQT_ID.as_str(), "seqt");
302      assert_eq!(ChunkId::new("123 ").unwrap().as_str(), "123 ");
303    }
304
305    #[test]
306    fn chunkid_format() {
307      assert_eq!(format!("{}", RIFF_ID), "'RIFF'");
308      assert_eq!(format!("{}", LIST_ID), "'LIST'");
309      assert_eq!(format!("{}", SEQT_ID), "'seqt'");
310
311      assert_eq!(format!("{:?}", RIFF_ID), "'RIFF'");
312      assert_eq!(format!("{:?}", LIST_ID), "'LIST'");
313      assert_eq!(format!("{:?}", SEQT_ID), "'seqt'");
314    }
315}