├── .gitignore ├── test_assets ├── minimal.riff ├── minimal_2.riff ├── test.riff └── test_2.riff ├── Cargo.toml ├── LICENSE ├── .travis.yml ├── README.md ├── tests └── integration_tests.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | *.bak 5 | -------------------------------------------------------------------------------- /test_assets/minimal.riff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frabert/riff/HEAD/test_assets/minimal.riff -------------------------------------------------------------------------------- /test_assets/minimal_2.riff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frabert/riff/HEAD/test_assets/minimal_2.riff -------------------------------------------------------------------------------- /test_assets/test.riff: -------------------------------------------------------------------------------- 1 | RIFFdsmplLIST>tst1testhey this is a testtesthey this is another testseqttest 2 | final test -------------------------------------------------------------------------------- /test_assets/test_2.riff: -------------------------------------------------------------------------------- 1 | RIFFfsmplLIST@tst1testhey this is a testtesthey this is another test!seqttest 2 | final test -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "riff" 3 | version = "2.0.0" 4 | authors = ["Francesco Bertolaccini "] 5 | description = "This crate provides utilities for reading and writing RIFF formatted files" 6 | repository = "https://github.com/frabert/riff" 7 | readme = "README.md" 8 | license = "MIT" 9 | edition = "2018" 10 | 11 | [badges] 12 | travis-ci = { repository = "frabert/riff" } 13 | codecov = { repository = "frabert/riff" } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Francesco Bertolaccini 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | sudo: required 4 | 5 | rust: 6 | - stable 7 | - beta 8 | - nightly 9 | 10 | env: 11 | - RUSTFLAGS="-C link-dead-code" 12 | 13 | matrix: 14 | allow_failures: 15 | - rust: nightly 16 | 17 | addons: 18 | apt: 19 | packages: 20 | - libcurl4-openssl-dev 21 | - libelf-dev 22 | - libdw-dev 23 | - cmake 24 | - gcc 25 | - binutils-dev 26 | - libiberty-dev 27 | 28 | after_success: | 29 | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && 30 | tar xzf master.tar.gz && 31 | cd kcov-master && 32 | mkdir build && 33 | cd build && 34 | cmake .. && 35 | make && 36 | make install DESTDIR=../../kcov-build && 37 | cd ../.. && 38 | rm -rf kcov-master && 39 | for file in target/debug/riff-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && 40 | for file in target/debug/integration_tests-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && 41 | bash <(curl -s https://codecov.io/bash) && 42 | echo "Uploaded code coverage" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # riff 2 | 3 | ## Crate for doing IO on RIFF-formatted files 4 | 5 | This crate provides utility methods for reading and writing formats such as 6 | Microsoft Wave, Audio Video Interleave or Downloadable Sounds. 7 | 8 | ### Examples 9 | 10 | Reading chunks: 11 | 12 | ````rust 13 | let mut file = File::open("somefile.wav")?; 14 | let chunk = riff::Chunk::read(&mut file, 0)?; 15 | 16 | for child in chunk.iter(&mut file) { 17 | println!(child.id()); 18 | } 19 | ```` 20 | 21 | Writing chunks: 22 | 23 | ````rust 24 | // Some ids to use while creating chunks 25 | let smpl_id: ChunkId = ChunkId { value: [0x73, 0x6D, 0x70, 0x6C] }; 26 | let test_id: ChunkId = ChunkId { value: [0x74, 0x65, 0x73, 0x74] }; 27 | let tst1_id: ChunkId = ChunkId { value: [0x74, 0x73, 0x74, 0x31] }; 28 | let tst2_id: ChunkId = ChunkId { value: [0x74, 0x73, 0x74, 0x32] }; 29 | 30 | let str1 = "hey this is a test".as_bytes().to_vec(); 31 | let str2 = "hey this is another test".as_bytes().to_vec(); 32 | let str3 = "final test".as_bytes().to_vec(); 33 | 34 | let contents = ChunkContents::Children( 35 | riff::RIFF_ID, 36 | smpl_id, 37 | vec![ 38 | ChunkContents::Children( 39 | riff::LIST_ID, 40 | tst1_id, 41 | vec![ 42 | ChunkContents::Data(test_id, str1), 43 | ChunkContents::Data(test_id, str2) 44 | ]), 45 | ChunkContents::Children( 46 | riff::LIST_ID, 47 | tst2_id, 48 | vec![ 49 | ChunkContents::Data(test_id, str3) 50 | ] 51 | ) 52 | ]); 53 | 54 | let mut file = File::create("somefile.riff")?; 55 | contents.write(&mut file)?; 56 | ```` 57 | -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | extern crate riff; 2 | 3 | use riff::Chunk; 4 | use riff::ChunkContents; 5 | use riff::ChunkId; 6 | 7 | use std::fs::File; 8 | 9 | static SMPL_ID: ChunkId = ChunkId { 10 | value: [0x73, 0x6D, 0x70, 0x6C], 11 | }; 12 | static TEST_ID: ChunkId = ChunkId { 13 | value: [0x74, 0x65, 0x73, 0x74], 14 | }; 15 | static TST1_ID: ChunkId = ChunkId { 16 | value: [0x74, 0x73, 0x74, 0x31], 17 | }; 18 | static TST2_ID: ChunkId = ChunkId { 19 | value: [0x74, 0x73, 0x74, 0x32], 20 | }; 21 | 22 | fn read_items(iter: &mut riff::Iter) -> Vec 23 | where 24 | T: std::io::Read + std::io::Seek, 25 | { 26 | let mut vec: Vec = Vec::new(); 27 | for item in iter { 28 | match item { 29 | Ok(chunk) => vec.push(chunk), 30 | _ => (), 31 | } 32 | } 33 | vec 34 | } 35 | 36 | #[test] 37 | fn read_minimal() { 38 | let mut file = File::open("test_assets/minimal.riff").unwrap(); 39 | let chunk = riff::Chunk::read(&mut file, 0).unwrap(); 40 | 41 | assert_eq!(chunk.id(), riff::RIFF_ID); 42 | assert_eq!(chunk.read_type(&mut file).unwrap(), SMPL_ID); 43 | 44 | let items = read_items(&mut chunk.iter(&mut file)); 45 | 46 | assert_eq!(items.len(), 1); 47 | 48 | let item = &items[0]; 49 | 50 | assert_eq!(item.id(), TEST_ID); 51 | assert_eq!(item.read_contents(&mut file).unwrap(), vec![0xFF]); 52 | } 53 | 54 | #[test] 55 | fn read_minimal2() { 56 | let mut file = File::open("test_assets/minimal_2.riff").unwrap(); 57 | let chunk = riff::Chunk::read(&mut file, 0).unwrap(); 58 | 59 | assert_eq!(chunk.id(), riff::RIFF_ID); 60 | assert_eq!(chunk.read_type(&mut file).unwrap(), SMPL_ID); 61 | 62 | let items = read_items(&mut chunk.iter(&mut file)); 63 | 64 | assert_eq!(items.len(), 2); 65 | 66 | let item1 = &items[0]; 67 | let item2 = &items[1]; 68 | 69 | assert_eq!(item1.id(), TST1_ID); 70 | assert_eq!(item1.read_contents(&mut file).unwrap(), vec![0xFF]); 71 | 72 | assert_eq!(item2.id(), TST2_ID); 73 | assert_eq!(item2.read_contents(&mut file).unwrap(), vec![0xEE]); 74 | } 75 | 76 | #[test] 77 | fn read_test_1() { 78 | let mut file = File::open("test_assets/test.riff").unwrap(); 79 | let str1 = "hey this is a test".as_bytes().to_vec(); 80 | let str2 = "hey this is another test".as_bytes().to_vec(); 81 | let str3 = "final test".as_bytes().to_vec(); 82 | 83 | let smpl = Chunk::read(&mut file, 0).unwrap(); 84 | assert_eq!(smpl.id(), riff::RIFF_ID); 85 | assert_eq!(smpl.read_type(&mut file).unwrap(), SMPL_ID); 86 | 87 | let smpl_items = read_items(&mut smpl.iter(&mut file)); 88 | assert_eq!(smpl_items.len(), 2); 89 | 90 | let tst1 = &smpl_items[0]; 91 | assert_eq!(tst1.id(), riff::LIST_ID); 92 | assert_eq!(tst1.read_type(&mut file).unwrap(), TST1_ID); 93 | let tst1_items = read_items(&mut tst1.iter(&mut file)); 94 | assert_eq!(tst1_items.len(), 2); 95 | 96 | let test_1 = &tst1_items[0]; 97 | let test_2 = &tst1_items[1]; 98 | 99 | assert_eq!(test_1.id(), TEST_ID); 100 | assert_eq!(test_1.read_contents(&mut file).unwrap(), str1); 101 | 102 | assert_eq!(test_2.id(), TEST_ID); 103 | assert_eq!(test_2.read_contents(&mut file).unwrap(), str2); 104 | 105 | let tst2 = &smpl_items[1]; 106 | assert_eq!(tst2.id(), riff::SEQT_ID); 107 | 108 | let tst2_items = read_items(&mut tst2.iter_no_type(&mut file)); 109 | assert_eq!(tst2_items.len(), 1); 110 | 111 | let test_3 = &tst2_items[0]; 112 | assert_eq!(test_3.id(), TEST_ID); 113 | assert_eq!(test_3.read_contents(&mut file).unwrap(), str3); 114 | } 115 | 116 | #[test] 117 | fn read_test_2() { 118 | let mut file = File::open("test_assets/test_2.riff").unwrap(); 119 | let str1 = "hey this is a test".as_bytes().to_vec(); 120 | let str2 = "hey this is another test!".as_bytes().to_vec(); 121 | let str3 = "final test".as_bytes().to_vec(); 122 | 123 | let smpl = Chunk::read(&mut file, 0).unwrap(); 124 | assert_eq!(smpl.id(), riff::RIFF_ID); 125 | assert_eq!(smpl.read_type(&mut file).unwrap(), SMPL_ID); 126 | 127 | let smpl_items = read_items(&mut smpl.iter(&mut file)); 128 | assert_eq!(smpl_items.len(), 2); 129 | 130 | let tst1 = &smpl_items[0]; 131 | assert_eq!(tst1.id(), riff::LIST_ID); 132 | assert_eq!(tst1.read_type(&mut file).unwrap(), TST1_ID); 133 | let tst1_items = read_items(&mut tst1.iter(&mut file)); 134 | assert_eq!(tst1_items.len(), 2); 135 | 136 | let test_1 = &tst1_items[0]; 137 | let test_2 = &tst1_items[1]; 138 | 139 | assert_eq!(test_1.id(), TEST_ID); 140 | assert_eq!(test_1.read_contents(&mut file).unwrap(), str1); 141 | 142 | assert_eq!(test_2.id(), TEST_ID); 143 | assert_eq!(test_2.read_contents(&mut file).unwrap(), str2); 144 | 145 | let tst2 = &smpl_items[1]; 146 | assert_eq!(tst2.id(), riff::SEQT_ID); 147 | 148 | let tst2_items = read_items(&mut tst2.iter_no_type(&mut file)); 149 | assert_eq!(tst2_items.len(), 1); 150 | 151 | let test_3 = &tst2_items[0]; 152 | assert_eq!(test_3.id(), TEST_ID); 153 | assert_eq!(test_3.read_contents(&mut file).unwrap(), str3); 154 | } 155 | 156 | fn read_chunk(chunk: &Chunk, file: &mut T) -> ChunkContents 157 | where 158 | T: std::io::Seek + std::io::Read, 159 | { 160 | let id = chunk.id(); 161 | if id == riff::RIFF_ID || id == riff::LIST_ID { 162 | let chunk_type = chunk.read_type(file).unwrap(); 163 | let children = read_items(&mut chunk.iter(file)); 164 | let mut children_contents: Vec = Vec::new(); 165 | 166 | for child in children { 167 | children_contents.push(read_chunk(&child, file)); 168 | } 169 | 170 | ChunkContents::Children(id, chunk_type, children_contents) 171 | } else if id == riff::SEQT_ID { 172 | let children = read_items(&mut chunk.iter_no_type(file)); 173 | let mut children_contents: Vec = Vec::new(); 174 | 175 | for child in children { 176 | children_contents.push(read_chunk(&child, file)); 177 | } 178 | 179 | ChunkContents::ChildrenNoType(id, children_contents) 180 | } else { 181 | let contents = chunk.read_contents(file).unwrap(); 182 | ChunkContents::Data(id, contents) 183 | } 184 | } 185 | 186 | #[test] 187 | fn write_test_1() { 188 | let buf: Vec = vec![0; 1024]; 189 | let chunk = { 190 | let mut file_read = File::open("test_assets/test.riff").unwrap(); 191 | let chunk = Chunk::read(&mut file_read, 0).unwrap(); 192 | read_chunk(&chunk, &mut file_read) 193 | }; 194 | let mut cursor = std::io::Cursor::new(buf); 195 | chunk.write(&mut cursor).unwrap(); 196 | 197 | let reread_chunk = { 198 | let chunk = Chunk::read(&mut cursor, 0).unwrap(); 199 | read_chunk(&chunk, &mut cursor) 200 | }; 201 | 202 | assert_eq!(chunk, reread_chunk); 203 | } 204 | 205 | #[test] 206 | fn write_test_2() { 207 | let buf: Vec = vec![0; 1024]; 208 | let chunk = { 209 | let mut file_read = File::open("test_assets/test_2.riff").unwrap(); 210 | let chunk = Chunk::read(&mut file_read, 0).unwrap(); 211 | read_chunk(&chunk, &mut file_read) 212 | }; 213 | let mut cursor = std::io::Cursor::new(buf); 214 | chunk.write(&mut cursor).unwrap(); 215 | 216 | let reread_chunk = { 217 | let chunk = Chunk::read(&mut cursor, 0).unwrap(); 218 | read_chunk(&chunk, &mut cursor) 219 | }; 220 | 221 | assert_eq!(chunk, reread_chunk); 222 | } 223 | -------------------------------------------------------------------------------- /src/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 | 6 | use std::fmt; 7 | use std::io::Read; 8 | use std::io::Write; 9 | use std::io::Seek; 10 | use std::io::SeekFrom; 11 | use std::convert::TryInto; 12 | 13 | /// A chunk id, also known as FourCC 14 | #[derive(PartialEq, Eq, Clone, Copy, Hash)] 15 | pub struct ChunkId { 16 | /// The raw bytes of the id 17 | pub value: [u8; 4] 18 | } 19 | 20 | /// The `RIFF` id 21 | pub static RIFF_ID: ChunkId = ChunkId { value: [0x52, 0x49, 0x46, 0x46] }; 22 | 23 | /// The `LIST` id 24 | pub static LIST_ID: ChunkId = ChunkId { value: [0x4C, 0x49, 0x53, 0x54] }; 25 | 26 | /// The `seqt` id 27 | pub static SEQT_ID: ChunkId = ChunkId { value: [0x73, 0x65, 0x71, 0x74] }; 28 | 29 | impl 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> { 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 { 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 | 73 | impl fmt::Display for ChunkId { 74 | fn fmt(&self, f: &mut std::fmt::Formatter) -> fmt::Result { 75 | write!(f, "'{}'", self.as_str()) 76 | } 77 | } 78 | 79 | impl 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)] 86 | pub enum ChunkContents { 87 | Data(ChunkId, Vec), 88 | Children(ChunkId, ChunkId, Vec), 89 | ChildrenNoType(ChunkId, Vec) 90 | } 91 | 92 | impl ChunkContents { 93 | pub fn write(&self, writer: &mut T) -> std::io::Result 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)] 163 | pub struct Chunk { 164 | pos: u64, 165 | id: ChunkId, 166 | len: u32, 167 | } 168 | 169 | /// An iterator over the children of a `Chunk` 170 | pub struct Iter<'a, T> 171 | where T: Seek + Read { 172 | end: u64, 173 | cur: u64, 174 | stream: &'a mut T 175 | } 176 | 177 | impl<'a, T> Iterator for Iter<'a, T> 178 | where T: Seek + Read { 179 | type Item = std::io::Result; 180 | 181 | fn next(&mut self) -> Option { 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 | 196 | impl 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(&self, stream: &mut T) -> std::io::Result 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(stream: &mut T, pos: u64) -> std::io::Result 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(&self, stream: &mut T) -> std::io::Result> 245 | where T: Read + Seek { 246 | stream.seek(SeekFrom::Start(self.pos + 8))?; 247 | 248 | let mut data: Vec = 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)] 281 | mod 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 | } 316 | --------------------------------------------------------------------------------