flac_codec/metadata/mod.rs
1// Copyright 2025 Brian Langenberger
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! For handling a FLAC file's metadata blocks
10//!
11//! Many items are capitalized simply because they were capitalized
12//! in the original FLAC format documentation.
13//!
14//! # Metadata Blocks
15//!
16//! FLAC supports seven different metadata block types
17//!
18//! | Block Type | Purpose |
19//! |-----------:|---------|
20//! | [STREAMINFO](`Streaminfo`) | stream information such as sample rate, channel count, etc. |
21//! | [PADDING](`Padding`) | empty data which can easily be resized as needed |
22//! | [APPLICATION](`Application`) | application-specific data such as foreign RIFF WAVE chunks |
23//! | [SEEKTABLE](`SeekTable`) | to allow for more efficient seeking within a FLAC file |
24//! | [VORBIS_COMMENT](`VorbisComment`) | textual metadata such as track title, artist name, album name, etc. |
25//! | [CUESHEET](`Cuesheet`) | the original disc's layout, for CD images |
26//! | [PICTURE](`Picture`) | embedded image files such as cover art |
27
28use crate::Error;
29use bitstream_io::{
30 BigEndian, BitRead, BitReader, BitWrite, ByteRead, ByteReader, FromBitStream,
31 FromBitStreamUsing, FromBitStreamWith, LittleEndian, SignedBitCount, ToBitStream,
32 ToBitStreamUsing, write::Overflowed,
33};
34use std::fs::File;
35use std::io::BufReader;
36use std::num::NonZero;
37use std::path::Path;
38
39/// Types related to the CUESHEET metadata block
40pub mod cuesheet;
41
42const FLAC_TAG: &[u8; 4] = b"fLaC";
43
44/// A trait for indicating various pieces of FLAC stream metadata
45///
46/// This metadata may be necessary for decoding a FLAC file
47/// to some other container or an output stream.
48pub trait Metadata {
49 /// Returns channel count
50 ///
51 /// From 1 to 8
52 fn channel_count(&self) -> u8;
53
54 /// Returns channel mask
55 ///
56 /// This uses the channel mask defined
57 /// in the Vorbis comment, if found, or defaults
58 /// to FLAC's default channel assignment if not
59 fn channel_mask(&self) -> ChannelMask {
60 ChannelMask::from_channels(self.channel_count())
61 }
62
63 /// Returns sample rate, in Hz
64 fn sample_rate(&self) -> u32;
65
66 /// Returns decoder's bits-per-sample
67 ///
68 /// From 1 to 32
69 fn bits_per_sample(&self) -> u32;
70
71 /// Returns total number of channel-independent samples, if known
72 fn total_samples(&self) -> Option<u64> {
73 None
74 }
75
76 /// Returns MD5 of entire stream, if known
77 ///
78 /// MD5 is always calculated in terms of little-endian,
79 /// signed, byte-aligned values.
80 fn md5(&self) -> Option<&[u8; 16]> {
81 None
82 }
83
84 /// Returns total length of decoded file, in bytes
85 fn decoded_len(&self) -> Option<u64> {
86 self.total_samples().map(|s| {
87 s * u64::from(self.channel_count()) * u64::from(self.bits_per_sample().div_ceil(8))
88 })
89 }
90
91 /// Returns duration of file
92 fn duration(&self) -> Option<std::time::Duration> {
93 const NANOS_PER_SEC: u64 = 1_000_000_000;
94
95 let sample_rate = u64::from(self.sample_rate());
96
97 self.total_samples().map(|s| {
98 std::time::Duration::new(
99 s / sample_rate,
100 u32::try_from(((s % sample_rate) * NANOS_PER_SEC) / sample_rate)
101 .unwrap_or_default(),
102 )
103 })
104 }
105}
106
107/// A FLAC metadata block header
108///
109/// | Bits | Field | Meaning |
110/// |-----:|------:|---------|
111/// | 1 | `last` | final metadata block in file |
112/// | 7 | `block_type` | type of block |
113/// | 24 | `size` | block size, in bytes |
114///
115/// # Example
116/// ```
117/// use bitstream_io::{BitReader, BitRead, BigEndian};
118/// use flac_codec::metadata::{BlockHeader, BlockType};
119///
120/// let data: &[u8] = &[0b1_0000000, 0x00, 0x00, 0x22];
121/// let mut r = BitReader::endian(data, BigEndian);
122/// assert_eq!(
123/// r.parse::<BlockHeader>().unwrap(),
124/// BlockHeader {
125/// last: true, // 0b1
126/// block_type: BlockType::Streaminfo, // 0b0000000
127/// size: 0x00_00_22u16.into(), // 0x00, 0x00, 0x22
128/// },
129/// );
130/// ```
131#[derive(Debug, Eq, PartialEq)]
132pub struct BlockHeader {
133 /// Whether we are the final block
134 pub last: bool,
135 /// Our block type
136 pub block_type: BlockType,
137 /// Our block size, in bytes
138 pub size: BlockSize,
139}
140
141impl BlockHeader {
142 const SIZE: BlockSize = BlockSize((1 + 7 + 24) / 8);
143}
144
145/// A type of FLAC metadata block
146pub trait MetadataBlock:
147 ToBitStream<Error: Into<Error>> + Into<Block> + TryFrom<Block> + Clone
148{
149 /// The metadata block's type
150 const TYPE: BlockType;
151
152 /// Whether the block can occur multiple times in a file
153 const MULTIPLE: bool;
154
155 /// Size of block, in bytes, not including header
156 fn bytes(&self) -> Option<BlockSize> {
157 self.bits::<BlockBits>().ok().map(|b| b.into())
158 }
159
160 /// Size of block, in bytes, including block header
161 fn total_size(&self) -> Option<BlockSize> {
162 self.bytes().and_then(|s| s.checked_add(BlockHeader::SIZE))
163 }
164}
165
166#[derive(Default)]
167struct BlockBits(u32);
168
169impl BlockBits {
170 const MAX: u32 = BlockSize::MAX * 8;
171}
172
173impl From<u8> for BlockBits {
174 fn from(u: u8) -> Self {
175 Self(u.into())
176 }
177}
178
179impl TryFrom<u32> for BlockBits {
180 type Error = (); // the error will be replaced later
181
182 fn try_from(u: u32) -> Result<Self, Self::Error> {
183 (u <= Self::MAX).then_some(Self(u)).ok_or(())
184 }
185}
186
187impl TryFrom<usize> for BlockBits {
188 type Error = (); // the error will be replaced later
189
190 fn try_from(u: usize) -> Result<Self, Self::Error> {
191 u32::try_from(u)
192 .map_err(|_| ())
193 .and_then(|u| (u <= Self::MAX).then_some(Self(u)).ok_or(()))
194 }
195}
196
197impl bitstream_io::write::Counter for BlockBits {
198 fn checked_add_assign(&mut self, Self(b): Self) -> Result<(), Overflowed> {
199 *self = self
200 .0
201 .checked_add(b)
202 .filter(|b| *b <= Self::MAX)
203 .map(Self)
204 .ok_or(Overflowed)?;
205 Ok(())
206 }
207
208 fn checked_mul(self, Self(b): Self) -> Result<Self, Overflowed> {
209 self.0
210 .checked_mul(b)
211 .filter(|b| *b <= Self::MAX)
212 .map(Self)
213 .ok_or(Overflowed)
214 }
215
216 fn byte_aligned(&self) -> bool {
217 self.0.is_multiple_of(8)
218 }
219}
220
221impl From<BlockBits> for BlockSize {
222 fn from(BlockBits(u): BlockBits) -> Self {
223 assert!(u % 8 == 0);
224 Self(u / 8)
225 }
226}
227
228impl BlockHeader {
229 fn new<M: MetadataBlock>(last: bool, block: &M) -> Result<Self, Error> {
230 fn large_block<E: Into<Error>>(err: E) -> Error {
231 match err.into() {
232 Error::Io(_) => Error::ExcessiveBlockSize,
233 e => e,
234 }
235 }
236
237 Ok(Self {
238 last,
239 block_type: M::TYPE,
240 size: block.bits::<BlockBits>().map_err(large_block)?.into(),
241 })
242 }
243}
244
245impl FromBitStream for BlockHeader {
246 type Error = Error;
247
248 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
249 Ok(Self {
250 last: r.read::<1, _>()?,
251 block_type: r.parse()?,
252 size: r.parse()?,
253 })
254 }
255}
256
257impl ToBitStream for BlockHeader {
258 type Error = Error;
259
260 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
261 w.write::<1, _>(self.last)?;
262 w.build(&self.block_type)?;
263 w.build(&self.size)?;
264 Ok(())
265 }
266}
267
268/// A defined FLAC metadata block type
269#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
270pub enum BlockType {
271 /// The STREAMINFO block
272 Streaminfo = 0,
273 /// The PADDING block
274 Padding = 1,
275 /// The APPLICATION block
276 Application = 2,
277 /// The SEEKTABLE block
278 SeekTable = 3,
279 /// The VORBIS_COMMENT block
280 VorbisComment = 4,
281 /// The CUESHEET block
282 Cuesheet = 5,
283 /// The PICTURE block
284 Picture = 6,
285}
286
287impl std::fmt::Display for BlockType {
288 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
289 match self {
290 Self::Streaminfo => "STREAMINFO".fmt(f),
291 Self::Padding => "PADDING".fmt(f),
292 Self::Application => "APPLICATION".fmt(f),
293 Self::SeekTable => "SEEKTABLE".fmt(f),
294 Self::VorbisComment => "VORBIS_COMMENT".fmt(f),
295 Self::Cuesheet => "CUESHEET".fmt(f),
296 Self::Picture => "PICTURE".fmt(f),
297 }
298 }
299}
300
301impl FromBitStream for BlockType {
302 type Error = Error;
303
304 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
305 match r.read::<7, u8>()? {
306 0 => Ok(Self::Streaminfo),
307 1 => Ok(Self::Padding),
308 2 => Ok(Self::Application),
309 3 => Ok(Self::SeekTable),
310 4 => Ok(Self::VorbisComment),
311 5 => Ok(Self::Cuesheet),
312 6 => Ok(Self::Picture),
313 7..=126 => Err(Error::ReservedMetadataBlock),
314 _ => Err(Error::InvalidMetadataBlock),
315 }
316 }
317}
318
319impl ToBitStream for BlockType {
320 type Error = Error;
321
322 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
323 w.write::<7, u8>(match self {
324 Self::Streaminfo => 0,
325 Self::Padding => 1,
326 Self::Application => 2,
327 Self::SeekTable => 3,
328 Self::VorbisComment => 4,
329 Self::Cuesheet => 5,
330 Self::Picture => 6,
331 })
332 .map_err(Error::Io)
333 }
334}
335
336/// A block type for optional FLAC metadata blocks
337///
338/// This is a subset of [`BlockType`] which contains
339/// no STREAMINFO, which is a required block.
340#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
341pub enum OptionalBlockType {
342 /// The PADDING block
343 Padding = 1,
344 /// The APPLICATION block
345 Application = 2,
346 /// The SEEKTABLE block
347 SeekTable = 3,
348 /// The VORBIS_COMMENT block
349 VorbisComment = 4,
350 /// The CUESHEET block
351 Cuesheet = 5,
352 /// The PICTURE block
353 Picture = 6,
354}
355
356impl From<OptionalBlockType> for BlockType {
357 fn from(block: OptionalBlockType) -> Self {
358 match block {
359 OptionalBlockType::Padding => Self::Padding,
360 OptionalBlockType::Application => Self::Application,
361 OptionalBlockType::SeekTable => Self::SeekTable,
362 OptionalBlockType::VorbisComment => Self::VorbisComment,
363 OptionalBlockType::Cuesheet => Self::Cuesheet,
364 OptionalBlockType::Picture => Self::Picture,
365 }
366 }
367}
368
369/// A 24-bit block size value, with safeguards against overflow
370#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
371pub struct BlockSize(u32);
372
373impl BlockSize {
374 /// A value of 0
375 pub const ZERO: BlockSize = BlockSize(0);
376
377 const MAX: u32 = (1 << 24) - 1;
378
379 /// Our current value as a u32
380 fn get(&self) -> u32 {
381 self.0
382 }
383}
384
385impl BlockSize {
386 /// Conditionally add `BlockSize` to ourself
387 pub fn checked_add(self, rhs: Self) -> Option<Self> {
388 self.0
389 .checked_add(rhs.0)
390 .filter(|s| *s <= Self::MAX)
391 .map(Self)
392 }
393
394 /// Conditionally subtract `BlockSize` from ourself
395 pub fn checked_sub(self, rhs: Self) -> Option<Self> {
396 self.0.checked_sub(rhs.0).map(Self)
397 }
398}
399
400impl std::fmt::Display for BlockSize {
401 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
402 self.0.fmt(f)
403 }
404}
405
406impl FromBitStream for BlockSize {
407 type Error = std::io::Error;
408
409 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
410 r.read::<24, _>().map(Self)
411 }
412}
413
414impl ToBitStream for BlockSize {
415 type Error = std::io::Error;
416
417 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
418 w.write::<24, _>(self.0)
419 }
420}
421
422impl From<u8> for BlockSize {
423 fn from(u: u8) -> Self {
424 Self(u.into())
425 }
426}
427
428impl From<u16> for BlockSize {
429 fn from(u: u16) -> Self {
430 Self(u.into())
431 }
432}
433
434impl TryFrom<usize> for BlockSize {
435 type Error = BlockSizeOverflow;
436
437 fn try_from(u: usize) -> Result<Self, Self::Error> {
438 u32::try_from(u)
439 .map_err(|_| BlockSizeOverflow)
440 .and_then(|s| (s <= Self::MAX).then_some(Self(s)).ok_or(BlockSizeOverflow))
441 }
442}
443
444impl TryFrom<u32> for BlockSize {
445 type Error = BlockSizeOverflow;
446
447 fn try_from(u: u32) -> Result<Self, Self::Error> {
448 (u <= Self::MAX).then_some(Self(u)).ok_or(BlockSizeOverflow)
449 }
450}
451
452impl TryFrom<u64> for BlockSize {
453 type Error = BlockSizeOverflow;
454
455 fn try_from(u: u64) -> Result<Self, Self::Error> {
456 u32::try_from(u)
457 .map_err(|_| BlockSizeOverflow)
458 .and_then(|s| (s <= Self::MAX).then_some(Self(s)).ok_or(BlockSizeOverflow))
459 }
460}
461
462impl From<BlockSize> for u32 {
463 #[inline]
464 fn from(size: BlockSize) -> u32 {
465 size.0
466 }
467}
468
469/// An error that occurs when trying to build an overly large `BlockSize`
470#[derive(Copy, Clone, Debug)]
471pub struct BlockSizeOverflow;
472
473impl std::error::Error for BlockSizeOverflow {}
474
475impl std::fmt::Display for BlockSizeOverflow {
476 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
477 "value too large for BlockSize".fmt(f)
478 }
479}
480
481/// An iterator over FLAC metadata blocks
482pub struct BlockIterator<R: std::io::Read> {
483 reader: R,
484 failed: bool,
485 tag_read: bool,
486 streaminfo_read: bool,
487 seektable_read: bool,
488 vorbiscomment_read: bool,
489 png_read: bool,
490 icon_read: bool,
491 finished: bool,
492}
493
494impl<R: std::io::Read> BlockIterator<R> {
495 /// Creates an iterator over something that implements `Read`.
496 /// Because this may perform many small reads,
497 /// performance is greatly improved by buffering reads
498 /// when reading from a raw `File`.
499 pub fn new(reader: R) -> Self {
500 Self {
501 reader,
502 failed: false,
503 tag_read: false,
504 streaminfo_read: false,
505 seektable_read: false,
506 vorbiscomment_read: false,
507 png_read: false,
508 icon_read: false,
509 finished: false,
510 }
511 }
512
513 fn read_block(&mut self) -> Option<Result<Block, Error>> {
514 // like a slighly easier variant of "Take"
515 struct LimitedReader<R> {
516 reader: R,
517 size: usize,
518 }
519
520 impl<R: std::io::Read> std::io::Read for LimitedReader<R> {
521 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
522 let size = self.size.min(buf.len());
523 self.reader.read(&mut buf[0..size]).inspect(|amt_read| {
524 self.size -= amt_read;
525 })
526 }
527 }
528
529 (!self.finished).then(|| {
530 BitReader::endian(&mut self.reader, BigEndian)
531 .parse()
532 .and_then(|header: BlockHeader| {
533 let mut reader = BitReader::endian(
534 LimitedReader {
535 reader: self.reader.by_ref(),
536 size: header.size.get().try_into().unwrap(),
537 },
538 BigEndian,
539 );
540
541 let block = reader.parse_with(&header)?;
542
543 match reader.into_reader().size {
544 0 => {
545 self.finished = header.last;
546 Ok(block)
547 }
548 _ => Err(Error::InvalidMetadataBlockSize),
549 }
550 })
551 })
552 }
553}
554
555impl<R: std::io::Read> Iterator for BlockIterator<R> {
556 type Item = Result<Block, Error>;
557
558 fn next(&mut self) -> Option<Self::Item> {
559 if self.failed {
560 // once we hit an error, stop any further reads
561 None
562 } else if !self.tag_read {
563 // "fLaC" tag must come before anything else
564 let mut tag = [0; 4];
565 match self.reader.read_exact(&mut tag) {
566 Ok(()) => match &tag {
567 FLAC_TAG => {
568 self.tag_read = true;
569 self.next()
570 }
571 _ => {
572 self.failed = true;
573 Some(Err(Error::MissingFlacTag))
574 }
575 },
576 Err(err) => {
577 self.failed = true;
578 Some(Err(Error::Io(err)))
579 }
580 }
581 } else if !self.streaminfo_read {
582 // STREAMINFO block must be first in file
583 match self.read_block() {
584 block @ Some(Ok(Block::Streaminfo(_))) => {
585 self.streaminfo_read = true;
586 block
587 }
588 _ => {
589 self.failed = true;
590 Some(Err(Error::MissingStreaminfo))
591 }
592 }
593 } else {
594 match self.read_block() {
595 Some(Ok(Block::Streaminfo(_))) => Some(Err(Error::MultipleStreaminfo)),
596 seektable @ Some(Ok(Block::SeekTable(_))) => {
597 if !self.seektable_read {
598 self.seektable_read = true;
599 seektable
600 } else {
601 self.failed = true;
602 Some(Err(Error::MultipleSeekTable))
603 }
604 }
605 vorbiscomment @ Some(Ok(Block::VorbisComment(_))) => {
606 if !self.vorbiscomment_read {
607 self.vorbiscomment_read = true;
608 vorbiscomment
609 } else {
610 self.failed = true;
611 Some(Err(Error::MultipleVorbisComment))
612 }
613 }
614 picture @ Some(Ok(Block::Picture(Picture {
615 picture_type: PictureType::Png32x32,
616 ..
617 }))) => {
618 if !self.png_read {
619 self.png_read = true;
620 picture
621 } else {
622 self.failed = true;
623 Some(Err(Error::MultiplePngIcon))
624 }
625 }
626 picture @ Some(Ok(Block::Picture(Picture {
627 picture_type: PictureType::GeneralFileIcon,
628 ..
629 }))) => {
630 if !self.icon_read {
631 self.icon_read = true;
632 picture
633 } else {
634 self.failed = true;
635 Some(Err(Error::MultipleGeneralIcon))
636 }
637 }
638 block @ Some(Err(_)) => {
639 self.failed = true;
640 block
641 }
642 block => block,
643 }
644 }
645 }
646}
647
648/// Returns iterator of blocks from the given reader
649///
650/// The reader should be positioned at the start of the FLAC
651/// file.
652///
653/// Because this may perform many small reads,
654/// using a buffered reader may greatly improve performance
655/// when reading from a raw `File`.
656///
657/// # Example
658///
659/// ```
660/// use flac_codec::{
661/// metadata::{read_blocks, Application, Block},
662/// encode::{FlacSampleWriter, Options},
663/// };
664/// use std::io::{Cursor, Seek};
665///
666/// let mut flac = Cursor::new(vec![]); // a FLAC file in memory
667///
668/// // add some APPLICATION blocks at encode-time
669/// let application_1 = Application {id: 0x1234, data: vec![1, 2, 3, 4]};
670/// let application_2 = Application {id: 0x5678, data: vec![5, 6, 7, 8]};
671///
672/// let options = Options::default()
673/// .application(application_1.clone())
674/// .application(application_2.clone())
675/// .no_padding()
676/// .no_seektable();
677///
678/// let mut writer = FlacSampleWriter::new(
679/// &mut flac, // our wrapped writer
680/// options, // our encoding options
681/// 44100, // sample rate
682/// 16, // bits-per-sample
683/// 1, // channel count
684/// Some(1), // total samples
685/// ).unwrap();
686///
687/// // write a simple FLAC file
688/// writer.write(std::slice::from_ref(&0)).unwrap();
689/// writer.finalize().unwrap();
690///
691/// flac.rewind().unwrap();
692///
693/// // read blocks from encoded file
694/// let blocks = read_blocks(flac)
695/// .skip(1) // skip STREAMINFO block
696/// .collect::<Result<Vec<Block>, _>>()
697/// .unwrap();
698///
699/// // ensure they match our APPLICATION blocks
700/// assert_eq!(blocks, vec![application_1.into(), application_2.into()]);
701/// ```
702pub fn read_blocks<R: std::io::Read>(r: R) -> BlockIterator<R> {
703 BlockIterator::new(r)
704}
705
706/// Returns iterator of blocks from the given path
707///
708/// # Errors
709///
710/// Returns any I/O error from opening the path.
711/// Note that the iterator itself may return any errors
712/// from reading individual blocks.
713pub fn blocks<P: AsRef<Path>>(p: P) -> std::io::Result<BlockIterator<BufReader<File>>> {
714 File::open(p.as_ref()).map(|f| read_blocks(BufReader::new(f)))
715}
716
717/// Returns first instance of the given block from the given reader
718///
719/// The reader should be positioned at the start of the FLAC
720/// file.
721///
722/// Because this may perform many small reads,
723/// using a buffered reader may greatly improve performance
724/// when reading from a raw `File`.
725///
726/// # Example
727///
728/// ```
729/// use flac_codec::{
730/// metadata::{read_block, Streaminfo},
731/// encode::{FlacSampleWriter, Options},
732/// };
733/// use std::io::{Cursor, Seek};
734///
735/// let mut flac: Cursor<Vec<u8>> = Cursor::new(vec![]); // a FLAC file in memory
736///
737/// let mut writer = FlacSampleWriter::new(
738/// &mut flac, // our wrapped writer
739/// Options::default(), // default encoding options
740/// 44100, // sample rate
741/// 16, // bits-per-sample
742/// 1, // channel count
743/// Some(1), // total samples
744/// ).unwrap();
745///
746/// // write a simple FLAC file
747/// writer.write(std::slice::from_ref(&0)).unwrap();
748/// writer.finalize().unwrap();
749///
750/// flac.rewind().unwrap();
751///
752/// // STREAMINFO block must always be present
753/// let streaminfo = match read_block::<_, Streaminfo>(flac) {
754/// Ok(Some(streaminfo)) => streaminfo,
755/// _ => panic!("STREAMINFO not found"),
756/// };
757///
758/// // verify STREAMINFO fields against encoding parameters
759/// assert_eq!(streaminfo.sample_rate, 44100);
760/// assert_eq!(u32::from(streaminfo.bits_per_sample), 16);
761/// assert_eq!(streaminfo.channels.get(), 1);
762/// assert_eq!(streaminfo.total_samples.map(|s| s.get()), Some(1));
763/// ```
764pub fn read_block<R, B>(r: R) -> Result<Option<B>, Error>
765where
766 R: std::io::Read,
767 B: MetadataBlock,
768{
769 read_blocks(r)
770 .find_map(|r| r.map(|b| B::try_from(b).ok()).transpose())
771 .transpose()
772}
773
774/// Returns first instance of the given block from the given path
775///
776/// See the [`read_block`] for additional information.
777///
778/// # Errors
779///
780/// Returns any error from opening the path, or from reading
781/// blocks from the path.
782pub fn block<P, B>(p: P) -> Result<Option<B>, Error>
783where
784 P: AsRef<Path>,
785 B: MetadataBlock,
786{
787 blocks(p).map_err(Error::Io).and_then(|mut blocks| {
788 blocks
789 .find_map(|r| r.map(|b| B::try_from(b).ok()).transpose())
790 .transpose()
791 })
792}
793
794/// Returns FLAC's STREAMINFO metadata block from the given file
795///
796/// # Errors
797///
798/// Returns an error if the STREAMINFO block is not first
799/// or if any I/O error occurs when reading the file.
800pub fn info<P: AsRef<Path>>(p: P) -> Result<Streaminfo, Error> {
801 File::open(p)
802 .map_err(Error::Io)
803 .and_then(|f| read_info(BufReader::new(f)))
804}
805
806/// Returns FLAC's STREAMINFO metadata block from the given reader
807///
808/// The reader is assumed to be rewound to the start of the FLAC file data.
809///
810/// # Errors
811///
812/// Returns an error if the STREAMINFO block is not first
813/// or if any I/O error occurs when reading the file.
814pub fn read_info<R: std::io::Read>(r: R) -> Result<Streaminfo, Error> {
815 let mut r = BitReader::endian(r, BigEndian);
816
817 // FLAC tag must be first thing in stream
818 if &r.read_to::<[u8; 4]>()? != FLAC_TAG {
819 return Err(Error::MissingFlacTag);
820 }
821
822 // STREAMINFO block must be present, and must be first
823 if !matches!(
824 r.parse()?,
825 BlockHeader {
826 block_type: BlockType::Streaminfo,
827 size: Streaminfo::SIZE,
828 last: _,
829 }
830 ) {
831 return Err(Error::MissingStreaminfo);
832 }
833
834 // Finally, parse STREAMINFO block itself
835 r.parse().map_err(Error::Io)
836}
837
838/// Returns iterator of blocks of a given type
839pub fn blocks_of<P, B>(p: P) -> impl Iterator<Item = Result<B, Error>>
840where
841 P: AsRef<Path>,
842 B: MetadataBlock + 'static,
843{
844 match blocks(p) {
845 Ok(iter) => {
846 Box::new(iter.filter_map(|block| block.map(|b| B::try_from(b).ok()).transpose()))
847 as Box<dyn Iterator<Item = Result<B, Error>>>
848 }
849 Err(e) => Box::new(std::iter::once(Err(e.into()))),
850 }
851}
852
853/// Writes iterator of blocks to the given writer.
854///
855/// Because this may perform many small writes,
856/// buffering writes may greatly improve performance
857/// when writing to a raw `File`.
858///
859/// let picture_type = picture.picture_type;
860/// # Errorsprintln!(" type: {} ({})", picture_type as u8, picture_type);
861///
862/// Passes along any I/O errors from the underlying stream.
863/// May also generate an error if any of the blocks are invalid
864/// (e.g. STREAMINFO not being the first block, any block is too large, etc.).
865///
866/// # Example
867///
868/// ```
869/// use flac_codec::metadata::{
870/// write_blocks, read_blocks, Streaminfo, Application, Block,
871/// };
872/// use std::io::{Cursor, Seek};
873///
874/// let mut flac: Cursor<Vec<u8>> = Cursor::new(vec![]); // a FLAC file in memory
875///
876/// // our test blocks
877/// let blocks: Vec<Block> = vec![
878/// Streaminfo {
879/// minimum_block_size: 0,
880/// maximum_block_size: 0,
881/// minimum_frame_size: None,
882/// maximum_frame_size: None,
883/// sample_rate: 44100,
884/// channels: 1u8.try_into().unwrap(),
885/// bits_per_sample: 16u32.try_into().unwrap(),
886/// total_samples: None,
887/// md5: None,
888/// }.into(),
889/// Application {id: 0x1234, data: vec![1, 2, 3, 4]}.into(),
890/// Application {id: 0x5678, data: vec![5, 6, 7, 8]}.into(),
891/// ];
892///
893/// // write our test blocks to a file
894/// write_blocks(&mut flac, blocks.clone()).unwrap();
895///
896/// flac.rewind().unwrap();
897///
898/// // read our blocks back from that file
899/// let read_blocks = read_blocks(flac).collect::<Result<Vec<Block>, _>>().unwrap();
900///
901/// // they should be identical
902/// assert_eq!(blocks, read_blocks);
903/// ```
904pub fn write_blocks<B: AsBlockRef>(
905 mut w: impl std::io::Write,
906 blocks: impl IntoIterator<Item = B>,
907) -> Result<(), Error> {
908 fn iter_last<T>(i: impl Iterator<Item = T>) -> impl Iterator<Item = (bool, T)> {
909 let mut iter = i.peekable();
910
911 std::iter::from_fn(move || {
912 let item = iter.next()?;
913 Some((iter.peek().is_none(), item))
914 })
915 }
916
917 // "FlaC" tag must come before anything else
918 w.write_all(FLAC_TAG).map_err(Error::Io)?;
919
920 let mut w = bitstream_io::BitWriter::endian(w, BigEndian);
921 let mut blocks = iter_last(blocks.into_iter());
922
923 // STREAMINFO block must be present and must be first in file
924 let next = blocks.next();
925 match next.as_ref().map(|(last, b)| (last, b.as_block_ref())) {
926 Some((last, streaminfo @ BlockRef::Streaminfo(_))) => w.build_using(&streaminfo, *last)?,
927 _ => return Err(Error::MissingStreaminfo),
928 }
929
930 // certain other blocks in the file must only occur once at most
931 let mut seektable_read = false;
932 let mut vorbiscomment_read = false;
933 let mut png_read = false;
934 let mut icon_read = false;
935
936 blocks.try_for_each(|(last, block)| match block.as_block_ref() {
937 BlockRef::Streaminfo(_) => Err(Error::MultipleStreaminfo),
938 vorbiscomment @ BlockRef::VorbisComment(_) => match vorbiscomment_read {
939 false => {
940 vorbiscomment_read = true;
941 w.build_using(&vorbiscomment.as_block_ref(), last)
942 }
943 true => Err(Error::MultipleVorbisComment),
944 },
945 seektable @ BlockRef::SeekTable(_) => match seektable_read {
946 false => {
947 seektable_read = true;
948 w.build_using(&seektable.as_block_ref(), last)
949 }
950 true => Err(Error::MultipleSeekTable),
951 },
952 picture @ BlockRef::Picture(Picture {
953 picture_type: PictureType::Png32x32,
954 ..
955 }) => {
956 if !png_read {
957 png_read = true;
958 w.build_using(&picture.as_block_ref(), last)
959 } else {
960 Err(Error::MultiplePngIcon)
961 }
962 }
963 picture @ BlockRef::Picture(Picture {
964 picture_type: PictureType::GeneralFileIcon,
965 ..
966 }) => {
967 if !icon_read {
968 icon_read = true;
969 w.build_using(&picture.as_block_ref(), last)
970 } else {
971 Err(Error::MultipleGeneralIcon)
972 }
973 }
974 block => w.build_using(&block.as_block_ref(), last),
975 })
976}
977
978/// Given a Path, attempts to update FLAC metadata blocks
979///
980/// Returns `true` if the file was completely rebuilt,
981/// or `false` if the original was overwritten.
982///
983/// # Errors
984///
985/// Returns error if unable to read metadata blocks,
986/// unable to write blocks, or if the existing or updated
987/// blocks do not conform to the FLAC file specification.
988pub fn update<P, E>(path: P, f: impl FnOnce(&mut BlockList) -> Result<(), E>) -> Result<bool, E>
989where
990 P: AsRef<Path>,
991 E: From<Error>,
992{
993 use std::fs::OpenOptions;
994
995 update_file(
996 OpenOptions::new()
997 .read(true)
998 .write(true)
999 .truncate(false)
1000 .create(false)
1001 .open(path.as_ref())
1002 .map_err(Error::Io)?,
1003 || std::fs::File::create(path.as_ref()),
1004 f,
1005 )
1006}
1007
1008/// Given open file, attempts to update its metadata blocks
1009///
1010/// The original file should be rewound to the start of the stream.
1011///
1012/// Applies closure `f` to the blocks and attempts to update them.
1013///
1014/// If the updated blocks can be made the same size as the
1015/// original file by adjusting padding, the file will be
1016/// partially overwritten with new contents.
1017///
1018/// If the new blocks are too large (or small) to fit into
1019/// the original file, the original unmodified file is dropped
1020/// and the `rebuilt` closure is called to build a new
1021/// file. The file's contents are then dumped into the new file.
1022///
1023/// Returns `true` if the file was completely rebuilt,
1024/// or `false` if the original was overwritten.
1025///
1026/// # Example 1
1027///
1028/// ```
1029/// use flac_codec::{
1030/// metadata::{update_file, BlockList, Padding, VorbisComment},
1031/// metadata::fields::TITLE,
1032/// encode::{FlacSampleWriter, Options},
1033/// };
1034/// use std::io::{Cursor, Seek};
1035///
1036/// let mut flac = Cursor::new(vec![]); // a FLAC file in memory
1037///
1038/// // include a small amount of padding
1039/// const PADDING_SIZE: u32 = 100;
1040/// let options = Options::default().padding(PADDING_SIZE).unwrap();
1041///
1042/// let mut writer = FlacSampleWriter::new(
1043/// &mut flac, // our wrapped writer
1044/// options, // our encoding options
1045/// 44100, // sample rate
1046/// 16, // bits-per-sample
1047/// 1, // channel count
1048/// Some(1), // total samples
1049/// ).unwrap();
1050///
1051/// // write a simple FLAC file
1052/// writer.write(std::slice::from_ref(&0)).unwrap();
1053/// writer.finalize().unwrap();
1054///
1055/// flac.rewind().unwrap();
1056///
1057/// let mut rebuilt: Vec<u8> = vec![];
1058///
1059/// // update file with new Vorbis Comment
1060/// assert!(matches!(
1061/// update_file::<_, _, flac_codec::Error>(
1062/// // our original file
1063/// &mut flac,
1064/// // a closure to create a new file, if necessary
1065/// || Ok(&mut rebuilt),
1066/// // the closure that performs the metadata update
1067/// |blocklist| {
1068/// blocklist.update::<VorbisComment>(
1069/// // the blocklist itself has a closure
1070/// // that updates a block, creating it if necessary
1071/// // (in this case, we're updating the Vorbis comment)
1072/// |vc| vc.set(TITLE, "Track Title")
1073/// );
1074/// Ok(())
1075/// },
1076/// ),
1077/// Ok(false), // false indicates the original file was updated
1078/// ));
1079///
1080/// flac.rewind().unwrap();
1081///
1082/// // re-read the metadata blocks from the original file
1083/// let blocks = BlockList::read(flac).unwrap();
1084///
1085/// // the original file now has a Vorbis Comment block
1086/// // with the track title that we added
1087/// assert_eq!(
1088/// blocks.get::<VorbisComment>().and_then(|vc| vc.get(TITLE)),
1089/// Some("Track Title"),
1090/// );
1091///
1092/// // the original file's padding block is smaller than before
1093/// // to accomodate our new Vorbis Comment block
1094/// assert!(u32::from(blocks.get::<Padding>().unwrap().size) < PADDING_SIZE);
1095///
1096/// // and the unneeded rebuilt file remains empty
1097/// assert!(rebuilt.is_empty());
1098/// ```
1099///
1100/// # Example 2
1101///
1102/// ```
1103/// use flac_codec::{
1104/// metadata::{update_file, BlockList, VorbisComment},
1105/// metadata::fields::TITLE,
1106/// encode::{FlacSampleWriter, Options},
1107/// };
1108/// use std::io::{Cursor, Seek};
1109///
1110/// let mut flac = Cursor::new(vec![]); // a FLAC file in memory
1111///
1112/// // include no padding in our encoded file
1113/// let options = Options::default().no_padding();
1114///
1115/// let mut writer = FlacSampleWriter::new(
1116/// &mut flac, // our wrapped writer
1117/// options, // our encoding options
1118/// 44100, // sample rate
1119/// 16, // bits-per-sample
1120/// 1, // channel count
1121/// Some(1), // total samples
1122/// ).unwrap();
1123///
1124/// // write a simple FLAC file
1125/// writer.write(std::slice::from_ref(&0)).unwrap();
1126/// writer.finalize().unwrap();
1127///
1128/// flac.rewind().unwrap();
1129///
1130/// let mut rebuilt: Vec<u8> = vec![];
1131///
1132/// // update file with new Vorbis Comment
1133/// assert!(matches!(
1134/// update_file::<_, _, flac_codec::Error>(
1135/// // our original file
1136/// &mut flac,
1137/// // a closure to create a new file, if necessary
1138/// || Ok(&mut rebuilt),
1139/// // the closure that performs the metadata update
1140/// |blocklist| {
1141/// blocklist.update::<VorbisComment>(
1142/// // the blocklist itself has a closure
1143/// // that updates a block, creating it if necessary
1144/// // (in this case, we're updating the Vorbis comment)
1145/// |vc| vc.set(TITLE, "Track Title")
1146/// );
1147/// Ok(())
1148/// },
1149/// ),
1150/// Ok(true), // true indicates the original file was not updated
1151/// ));
1152///
1153/// flac.rewind().unwrap();
1154///
1155/// // re-read the metadata blocks from the original file
1156/// let blocks = BlockList::read(flac).unwrap();
1157///
1158/// // the original file remains unchanged
1159/// // and has no Vorbis Comment block
1160/// assert_eq!(blocks.get::<VorbisComment>(), None);
1161///
1162/// // now read the metadata blocks from the rebuilt file
1163/// let blocks = BlockList::read(rebuilt.as_slice()).unwrap();
1164///
1165/// // the rebuilt file has our Vorbis Comment entry instead
1166/// assert_eq!(
1167/// blocks.get::<VorbisComment>().and_then(|vc| vc.get(TITLE)),
1168/// Some("Track Title"),
1169/// );
1170/// ```
1171pub fn update_file<F, N, E>(
1172 mut original: F,
1173 rebuilt: impl FnOnce() -> std::io::Result<N>,
1174 f: impl FnOnce(&mut BlockList) -> Result<(), E>,
1175) -> Result<bool, E>
1176where
1177 F: std::io::Read + std::io::Seek + std::io::Write,
1178 N: std::io::Write,
1179 E: From<Error>,
1180{
1181 use crate::Counter;
1182 use std::cmp::Ordering;
1183 use std::io::{BufReader, BufWriter, Read, sink};
1184
1185 fn rebuild_file<N, R>(
1186 rebuilt: impl FnOnce() -> std::io::Result<N>,
1187 mut r: R,
1188 blocks: BlockList,
1189 ) -> Result<(), Error>
1190 where
1191 N: std::io::Write,
1192 R: Read,
1193 {
1194 // dump our new blocks and remaining FLAC data to temp file
1195 let mut tmp = Vec::new();
1196 write_blocks(&mut tmp, blocks)?;
1197 std::io::copy(&mut r, &mut tmp).map_err(Error::Io)?;
1198 drop(r);
1199
1200 // fresh original file and rewrite it with tmp file contents
1201 rebuilt()
1202 .and_then(|mut f| f.write_all(tmp.as_slice()))
1203 .map_err(Error::Io)
1204 }
1205
1206 /// Returns Ok if successful
1207 fn grow_padding(blocks: &mut BlockList, more_bytes: u64) -> Result<(), ()> {
1208 // if a block set has more than one PADDING, we'll try the first
1209 // rather than attempt to grow each in turn
1210 //
1211 // this is the most common case
1212 let padding = blocks.get_mut::<Padding>().ok_or(())?;
1213
1214 padding.size = padding
1215 .size
1216 .checked_add(more_bytes.try_into().map_err(|_| ())?)
1217 .ok_or(())?;
1218
1219 Ok(())
1220 }
1221
1222 /// Returns Ok if successful
1223 fn shrink_padding(blocks: &mut BlockList, fewer_bytes: u64) -> Result<(), ()> {
1224 // if a block set has more than one PADDING, we'll try the first
1225 // rather than attempt to shrink each in turn
1226 //
1227 // this is the most common case
1228 let padding = blocks.get_mut::<Padding>().ok_or(())?;
1229
1230 padding.size = padding
1231 .size
1232 .checked_sub(fewer_bytes.try_into().map_err(|_| ())?)
1233 .ok_or(())?;
1234
1235 Ok(())
1236 }
1237
1238 // the starting position in the stream we rewind to
1239 let start = std::io::SeekFrom::Start(original.stream_position().map_err(Error::Io)?);
1240
1241 let mut reader = Counter::new(BufReader::new(&mut original));
1242
1243 let mut blocks = BlockList::read(Read::by_ref(&mut reader))?;
1244
1245 let Counter {
1246 stream: reader,
1247 count: old_size,
1248 } = reader;
1249
1250 f(&mut blocks)?;
1251
1252 let new_size = {
1253 let mut new_size = Counter::new(sink());
1254 write_blocks(&mut new_size, blocks.blocks())?;
1255 new_size.count
1256 };
1257
1258 match new_size.cmp(&old_size) {
1259 Ordering::Less => {
1260 // blocks have shrunk in size, so try to expand
1261 // PADDING block to hold additional bytes
1262 match grow_padding(&mut blocks, old_size - new_size) {
1263 Ok(()) => {
1264 original.seek(start).map_err(Error::Io)?;
1265 write_blocks(BufWriter::new(original), blocks)
1266 .map(|()| false)
1267 .map_err(E::from)
1268 }
1269 Err(()) => rebuild_file(rebuilt, reader, blocks)
1270 .map(|()| true)
1271 .map_err(E::from),
1272 }
1273 }
1274 Ordering::Equal => {
1275 // blocks are the same size, so no need to adjust padding
1276 original.seek(start).map_err(Error::Io)?;
1277 write_blocks(BufWriter::new(original), blocks)
1278 .map(|()| false)
1279 .map_err(E::from)
1280 }
1281 Ordering::Greater => {
1282 // blocks have grown in size, so try to shrink
1283 // PADDING block to hold additional bytes
1284 match shrink_padding(&mut blocks, new_size - old_size) {
1285 Ok(()) => {
1286 original.seek(start).map_err(Error::Io)?;
1287 write_blocks(BufWriter::new(original), blocks)
1288 .map(|()| false)
1289 .map_err(E::from)
1290 }
1291 Err(()) => rebuild_file(rebuilt, reader, blocks)
1292 .map(|()| true)
1293 .map_err(E::from),
1294 }
1295 }
1296 }
1297}
1298
1299/// Any possible FLAC metadata block
1300///
1301/// Each block consists of a [`BlockHeader`] followed by the block's contents.
1302///
1303/// ```text
1304/// ┌──────────┬────────┬┄┄┄┄┄┄┄┄┬┄┄┄┬────────┬┄┄┄┄┄┄┄┄┬┄┄┄╮
1305/// │ FLAC Tag │ Block₀ │ Block₁ ┆ … ┆ Frame₀ │ Frame₁ ┆ … ┆ FLAC File
1306/// └──────────┼────────┼┄┄┄┄┄┄┄┄┴┄┄┄┴────────┴┄┄┄┄┄┄┄┄┴┄┄┄╯
1307/// ╭──────────╯ ╰────────────────────────╮
1308/// ├──────────────┬─────────────────────────────┤
1309/// │ Block Header │ Metadata Block Data │ Metadata Block
1310/// └──────────────┴─────────────────────────────┘
1311/// ```
1312#[derive(Debug, Clone, Eq, PartialEq)]
1313pub enum Block {
1314 /// The STREAMINFO block
1315 Streaminfo(Streaminfo),
1316 /// The PADDING block
1317 Padding(Padding),
1318 /// The APPLICATION block
1319 Application(Application),
1320 /// The SEEKTABLE block
1321 SeekTable(SeekTable),
1322 /// The VORBIS_COMMENT block
1323 VorbisComment(VorbisComment),
1324 /// The CUESHEET block
1325 Cuesheet(Cuesheet),
1326 /// The PICTURE block
1327 Picture(Picture),
1328}
1329
1330impl Block {
1331 /// Our block type
1332 pub fn block_type(&self) -> BlockType {
1333 match self {
1334 Self::Streaminfo(_) => BlockType::Streaminfo,
1335 Self::Padding(_) => BlockType::Padding,
1336 Self::Application(_) => BlockType::Application,
1337 Self::SeekTable(_) => BlockType::SeekTable,
1338 Self::VorbisComment(_) => BlockType::VorbisComment,
1339 Self::Cuesheet(_) => BlockType::Cuesheet,
1340 Self::Picture(_) => BlockType::Picture,
1341 }
1342 }
1343}
1344
1345impl AsBlockRef for Block {
1346 fn as_block_ref(&self) -> BlockRef<'_> {
1347 match self {
1348 Self::Streaminfo(s) => BlockRef::Streaminfo(s),
1349 Self::Padding(p) => BlockRef::Padding(p),
1350 Self::Application(a) => BlockRef::Application(a),
1351 Self::SeekTable(s) => BlockRef::SeekTable(s),
1352 Self::VorbisComment(v) => BlockRef::VorbisComment(v),
1353 Self::Cuesheet(v) => BlockRef::Cuesheet(v),
1354 Self::Picture(p) => BlockRef::Picture(p),
1355 }
1356 }
1357}
1358
1359impl FromBitStreamWith<'_> for Block {
1360 type Context = BlockHeader;
1361 type Error = Error;
1362
1363 // parses from reader without header
1364 fn from_reader<R: BitRead + ?Sized>(
1365 r: &mut R,
1366 header: &BlockHeader,
1367 ) -> Result<Self, Self::Error> {
1368 match header.block_type {
1369 BlockType::Streaminfo => Ok(Block::Streaminfo(r.parse()?)),
1370 BlockType::Padding => Ok(Block::Padding(r.parse_using(header.size)?)),
1371 BlockType::Application => Ok(Block::Application(r.parse_using(header.size)?)),
1372 BlockType::SeekTable => Ok(Block::SeekTable(r.parse_using(header.size)?)),
1373 BlockType::VorbisComment => Ok(Block::VorbisComment(r.parse()?)),
1374 BlockType::Cuesheet => Ok(Block::Cuesheet(r.parse()?)),
1375 BlockType::Picture => Ok(Block::Picture(r.parse()?)),
1376 }
1377 }
1378}
1379
1380impl ToBitStreamUsing for Block {
1381 type Context = bool;
1382 type Error = Error;
1383
1384 // builds to writer with header
1385 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W, is_last: bool) -> Result<(), Error> {
1386 match self {
1387 Self::Streaminfo(streaminfo) => w
1388 .build(&BlockHeader::new(is_last, streaminfo)?)
1389 .and_then(|()| w.build(streaminfo).map_err(Error::Io)),
1390 Self::Padding(padding) => w
1391 .build(&BlockHeader::new(is_last, padding)?)
1392 .and_then(|()| w.build(padding).map_err(Error::Io)),
1393 Self::Application(application) => w
1394 .build(&BlockHeader::new(is_last, application)?)
1395 .and_then(|()| w.build(application).map_err(Error::Io)),
1396 Self::SeekTable(seektable) => w
1397 .build(&BlockHeader::new(is_last, seektable)?)
1398 .and_then(|()| w.build(seektable)),
1399 Self::VorbisComment(vorbis_comment) => w
1400 .build(&BlockHeader::new(is_last, vorbis_comment)?)
1401 .and_then(|()| w.build(vorbis_comment)),
1402 Self::Cuesheet(cuesheet) => w
1403 .build(&BlockHeader::new(is_last, cuesheet)?)
1404 .and_then(|()| w.build(cuesheet)),
1405 Self::Picture(picture) => w
1406 .build(&BlockHeader::new(is_last, picture)?)
1407 .and_then(|()| w.build(picture)),
1408 }
1409 }
1410}
1411
1412/// A shared reference to a metadata block
1413#[derive(Debug, Copy, Clone, Eq, PartialEq)]
1414pub enum BlockRef<'b> {
1415 /// The STREAMINFO block
1416 Streaminfo(&'b Streaminfo),
1417 /// The PADDING block
1418 Padding(&'b Padding),
1419 /// The APPLICATION block
1420 Application(&'b Application),
1421 /// The SEEKTABLE block
1422 SeekTable(&'b SeekTable),
1423 /// The VORBIS_COMMENT block
1424 VorbisComment(&'b VorbisComment),
1425 /// The CUESHEET block
1426 Cuesheet(&'b Cuesheet),
1427 /// The PICTURE block
1428 Picture(&'b Picture),
1429}
1430
1431impl BlockRef<'_> {
1432 /// Our block type
1433 pub fn block_type(&self) -> BlockType {
1434 match self {
1435 Self::Streaminfo(_) => BlockType::Streaminfo,
1436 Self::Padding(_) => BlockType::Padding,
1437 Self::Application(_) => BlockType::Application,
1438 Self::SeekTable(_) => BlockType::SeekTable,
1439 Self::VorbisComment(_) => BlockType::VorbisComment,
1440 Self::Cuesheet(_) => BlockType::Cuesheet,
1441 Self::Picture(_) => BlockType::Picture,
1442 }
1443 }
1444}
1445
1446impl AsBlockRef for BlockRef<'_> {
1447 fn as_block_ref(&self) -> BlockRef<'_> {
1448 *self
1449 }
1450}
1451
1452/// A trait for items which can make cheap [`BlockRef`] values.
1453pub trait AsBlockRef {
1454 /// Returns fresh reference to ourself.
1455 fn as_block_ref(&self) -> BlockRef<'_>;
1456}
1457
1458impl<T: AsBlockRef> AsBlockRef for &T {
1459 fn as_block_ref(&self) -> BlockRef<'_> {
1460 <T as AsBlockRef>::as_block_ref(*self)
1461 }
1462}
1463
1464impl ToBitStreamUsing for BlockRef<'_> {
1465 type Context = bool;
1466 type Error = Error;
1467
1468 // builds to writer with header
1469 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W, is_last: bool) -> Result<(), Error> {
1470 match self {
1471 Self::Streaminfo(streaminfo) => w
1472 .build(&BlockHeader::new(is_last, *streaminfo)?)
1473 .and_then(|()| w.build(*streaminfo).map_err(Error::Io)),
1474 Self::Padding(padding) => w
1475 .build(&BlockHeader::new(is_last, *padding)?)
1476 .and_then(|()| w.build(*padding).map_err(Error::Io)),
1477 Self::Application(application) => w
1478 .build(&BlockHeader::new(is_last, *application)?)
1479 .and_then(|()| w.build(*application).map_err(Error::Io)),
1480 Self::SeekTable(seektable) => w
1481 .build(&BlockHeader::new(is_last, *seektable)?)
1482 .and_then(|()| w.build(*seektable)),
1483 Self::VorbisComment(vorbis_comment) => w
1484 .build(&BlockHeader::new(is_last, *vorbis_comment)?)
1485 .and_then(|()| w.build(*vorbis_comment)),
1486 Self::Cuesheet(cuesheet) => w
1487 .build(&BlockHeader::new(is_last, *cuesheet)?)
1488 .and_then(|()| w.build(*cuesheet)),
1489 Self::Picture(picture) => w
1490 .build(&BlockHeader::new(is_last, *picture)?)
1491 .and_then(|()| w.build(*picture)),
1492 }
1493 }
1494}
1495
1496macro_rules! block {
1497 ($t:ty, $v:ident, $m:literal) => {
1498 impl MetadataBlock for $t {
1499 const TYPE: BlockType = BlockType::$v;
1500 const MULTIPLE: bool = $m;
1501 }
1502
1503 impl From<$t> for Block {
1504 fn from(b: $t) -> Self {
1505 Self::$v(b)
1506 }
1507 }
1508
1509 impl TryFrom<Block> for $t {
1510 type Error = ();
1511
1512 fn try_from(block: Block) -> Result<Self, ()> {
1513 match block {
1514 Block::$v(block) => Ok(block),
1515 _ => Err(()),
1516 }
1517 }
1518 }
1519
1520 impl AsBlockRef for $t {
1521 fn as_block_ref(&self) -> BlockRef<'_> {
1522 BlockRef::$v(self)
1523 }
1524 }
1525 };
1526}
1527
1528macro_rules! optional_block {
1529 ($t:ty, $v:ident) => {
1530 impl OptionalMetadataBlock for $t {
1531 const OPTIONAL_TYPE: OptionalBlockType = OptionalBlockType::$v;
1532 }
1533
1534 impl private::OptionalMetadataBlock for $t {
1535 fn try_from_opt_block(
1536 block: &private::OptionalBlock,
1537 ) -> Result<&Self, &private::OptionalBlock> {
1538 match block {
1539 private::OptionalBlock::$v(block) => Ok(block),
1540 block => Err(block),
1541 }
1542 }
1543
1544 fn try_from_opt_block_mut(
1545 block: &mut private::OptionalBlock,
1546 ) -> Result<&mut Self, &mut private::OptionalBlock> {
1547 match block {
1548 private::OptionalBlock::$v(block) => Ok(block),
1549 block => Err(block),
1550 }
1551 }
1552 }
1553
1554 impl From<$t> for private::OptionalBlock {
1555 fn from(vorbis: $t) -> Self {
1556 private::OptionalBlock::$v(vorbis)
1557 }
1558 }
1559
1560 impl TryFrom<private::OptionalBlock> for $t {
1561 type Error = ();
1562
1563 fn try_from(block: private::OptionalBlock) -> Result<Self, ()> {
1564 match block {
1565 private::OptionalBlock::$v(b) => Ok(b),
1566 _ => Err(()),
1567 }
1568 }
1569 }
1570 };
1571}
1572
1573/// A STREAMINFO metadata block
1574///
1575/// This block contains metadata about the stream's contents.
1576///
1577/// It must *always* be present in a FLAC file,
1578/// must *always* be the first metadata block in the stream,
1579/// and must *not* be present more than once.
1580///
1581/// | Bits | Field | Meaning |
1582/// |-----:|------:|---------|
1583/// | 16 | `minimum_block_size` | minimum block size (in samples) in the stream
1584/// | 16 | `maximum_block_size` | maximum block size (in samples) in the stream
1585/// | 24 | `minimum_frame_size` | minimum frame size (in bytes) in the stream
1586/// | 24 | `maximum_frame_size` | maximum frame size (in bytes) in the stream
1587/// | 20 | `sample_rate` | stream's sample rate, in Hz
1588/// | 3 | `channels` | stream's channel count (+1)
1589/// | 5 | `bits_per_sample` | stream's bits-per-sample (+1)
1590/// | 36 | `total_samples` | stream's total channel-independent samples
1591/// | 16×8 | `md5` | decoded stream's MD5 sum hash
1592///
1593/// # Example
1594/// ```
1595/// use bitstream_io::{BitReader, BitRead, BigEndian, SignedBitCount};
1596/// use flac_codec::metadata::Streaminfo;
1597/// use std::num::NonZero;
1598///
1599/// let data: &[u8] = &[
1600/// 0x10, 0x00,
1601/// 0x10, 0x00,
1602/// 0x00, 0x00, 0x0c,
1603/// 0x00, 0x00, 0x0c,
1604/// 0b00001010, 0b11000100, 0b0100_000_0, 0b1111_0000,
1605/// 0b00000000, 0b00000000, 0b00000000, 0b01010000,
1606/// 0xf5, 0x3f, 0x86, 0x87, 0x6d, 0xcd, 0x77, 0x83,
1607/// 0x22, 0x5c, 0x93, 0xba, 0x8a, 0x93, 0x8c, 0x7d,
1608/// ];
1609///
1610/// let mut r = BitReader::endian(data, BigEndian);
1611/// assert_eq!(
1612/// r.parse::<Streaminfo>().unwrap(),
1613/// Streaminfo {
1614/// minimum_block_size: 0x10_00, // 4096 samples
1615/// maximum_block_size: 0x10_00, // 4096 samples
1616/// minimum_frame_size: NonZero::new(0x00_00_0c), // 12 bytes
1617/// maximum_frame_size: NonZero::new(0x00_00_0c), // 12 bytes
1618/// sample_rate: 0b00001010_11000100_0100, // 44100 Hz
1619/// channels: NonZero::new(0b000 + 1).unwrap(), // 1 channel
1620/// bits_per_sample:
1621/// SignedBitCount::new::<{0b0_1111 + 1}>(), // 16 bps
1622/// total_samples: NonZero::new(
1623/// 0b0000_00000000_00000000_00000000_01010000 // 80 samples
1624/// ),
1625/// md5: Some([
1626/// 0xf5, 0x3f, 0x86, 0x87, 0x6d, 0xcd, 0x77, 0x83,
1627/// 0x22, 0x5c, 0x93, 0xba, 0x8a, 0x93, 0x8c, 0x7d,
1628/// ]),
1629/// },
1630/// );
1631/// ```
1632///
1633/// # Important
1634///
1635/// Changing any of these values to something that differs
1636/// from the values of the file's frame headers will render it
1637/// unplayable, as will moving it anywhere but the first
1638/// metadata block in the file.
1639/// Avoid modifying the position and contents of this block unless you
1640/// know exactly what you are doing.
1641#[derive(Debug, Clone, Eq, PartialEq)]
1642pub struct Streaminfo {
1643 /// The minimum block size (in samples) used in the stream,
1644 /// excluding the last block.
1645 pub minimum_block_size: u16,
1646 /// The maximum block size (in samples) used in the stream,
1647 /// excluding the last block.
1648 pub maximum_block_size: u16,
1649 /// The minimum framesize (in bytes) used in the stream.
1650 ///
1651 /// `None` indicates the value is unknown.
1652 pub minimum_frame_size: Option<NonZero<u32>>,
1653 /// The maximum framesize (in bytes) used in the stream.
1654 ///
1655 /// `None` indicates the value is unknown.
1656 pub maximum_frame_size: Option<NonZero<u32>>,
1657 /// Sample rate in Hz
1658 ///
1659 /// 0 indicates a non-audio stream.
1660 pub sample_rate: u32,
1661 /// Number of channels, from 1 to 8
1662 pub channels: NonZero<u8>,
1663 /// Number of bits-per-sample, from 4 to 32
1664 pub bits_per_sample: SignedBitCount<32>,
1665 /// Total number of interchannel samples in stream.
1666 ///
1667 /// `None` indicates the value is unknown.
1668 pub total_samples: Option<NonZero<u64>>,
1669 /// MD5 hash of unencoded audio data.
1670 ///
1671 /// `None` indicates the value is unknown.
1672 pub md5: Option<[u8; 16]>,
1673}
1674
1675impl Streaminfo {
1676 /// The maximum size of a frame, in bytes (2²⁴ - 1)
1677 pub const MAX_FRAME_SIZE: u32 = (1 << 24) - 1;
1678
1679 /// The maximum sample rate, in Hz (2²⁰ - 1)
1680 pub const MAX_SAMPLE_RATE: u32 = (1 << 20) - 1;
1681
1682 /// The maximum number of channels (8)
1683 pub const MAX_CHANNELS: NonZero<u8> = NonZero::new(8).unwrap();
1684
1685 /// The maximum number of total samples (2³⁶ - 1)
1686 pub const MAX_TOTAL_SAMPLES: NonZero<u64> = NonZero::new((1 << 36) - 1).unwrap();
1687
1688 /// Defined size of STREAMINFO block
1689 const SIZE: BlockSize = BlockSize(0x22);
1690}
1691
1692block!(Streaminfo, Streaminfo, false);
1693
1694impl Metadata for Streaminfo {
1695 fn channel_count(&self) -> u8 {
1696 self.channels.get()
1697 }
1698
1699 fn sample_rate(&self) -> u32 {
1700 self.sample_rate
1701 }
1702
1703 fn bits_per_sample(&self) -> u32 {
1704 self.bits_per_sample.into()
1705 }
1706
1707 fn total_samples(&self) -> Option<u64> {
1708 self.total_samples.map(|s| s.get())
1709 }
1710
1711 fn md5(&self) -> Option<&[u8; 16]> {
1712 self.md5.as_ref()
1713 }
1714}
1715
1716impl FromBitStream for Streaminfo {
1717 type Error = std::io::Error;
1718
1719 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
1720 Ok(Self {
1721 minimum_block_size: r.read_to()?,
1722 maximum_block_size: r.read_to()?,
1723 minimum_frame_size: r.read::<24, _>()?,
1724 maximum_frame_size: r.read::<24, _>()?,
1725 sample_rate: r.read::<20, _>()?,
1726 channels: r.read::<3, _>()?,
1727 bits_per_sample: r
1728 .read_count::<0b11111>()?
1729 .checked_add(1)
1730 .and_then(|c| c.signed_count())
1731 .unwrap(),
1732 total_samples: r.read::<36, _>()?,
1733 md5: r
1734 .read_to()
1735 .map(|md5: [u8; 16]| md5.iter().any(|b| *b != 0).then_some(md5))?,
1736 })
1737 }
1738}
1739
1740impl ToBitStream for Streaminfo {
1741 type Error = std::io::Error;
1742
1743 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
1744 w.write_from(self.minimum_block_size)?;
1745 w.write_from(self.maximum_block_size)?;
1746 w.write::<24, _>(self.minimum_frame_size)?;
1747 w.write::<24, _>(self.maximum_frame_size)?;
1748 w.write::<20, _>(self.sample_rate)?;
1749 w.write::<3, _>(self.channels)?;
1750 w.write_count(
1751 self.bits_per_sample
1752 .checked_sub::<0b11111>(1)
1753 .unwrap()
1754 .count(),
1755 )?;
1756 w.write::<36, _>(self.total_samples)?;
1757 w.write_from(self.md5.unwrap_or([0; 16]))?;
1758 Ok(())
1759 }
1760}
1761
1762/// A PADDING metadata block
1763///
1764/// Padding blocks are empty blocks consisting of all 0 bytes.
1765/// If one wishes to edit the metadata in other blocks,
1766/// adjusting the size of the padding block allows
1767/// us to do so without have to rewrite the entire FLAC file.
1768/// For example, when adding 10 bytes to a comment,
1769/// we can subtract 10 bytes from the padding
1770/// and the total size of all blocks remains unchanged.
1771/// Therefore we can simply overwrite the old comment
1772/// block with the new without affecting the following
1773/// FLAC audio frames.
1774///
1775/// This block may occur multiple times in a FLAC file.
1776///
1777/// # Example
1778///
1779/// ```
1780/// use bitstream_io::{BitReader, BitRead, BigEndian};
1781/// use flac_codec::metadata::{BlockHeader, BlockType, Padding};
1782///
1783/// let data: &[u8] = &[
1784/// 0x81, 0x00, 0x00, 0x0a, // block header
1785/// // padding bytes
1786/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1787/// ];
1788///
1789/// let mut r = BitReader::endian(data, BigEndian);
1790/// let header = r.parse::<BlockHeader>().unwrap();
1791/// assert_eq!(
1792/// &header,
1793/// &BlockHeader {
1794/// last: true,
1795/// block_type: BlockType::Padding,
1796/// size: 0x0au8.into(),
1797/// },
1798/// );
1799///
1800/// assert_eq!(
1801/// r.parse_using::<Padding>(header.size).unwrap(),
1802/// Padding {
1803/// size: 0x0au8.into(),
1804/// },
1805/// );
1806/// ```
1807#[derive(Debug, Clone, Eq, PartialEq, Default)]
1808pub struct Padding {
1809 /// The size of the padding, in bytes
1810 pub size: BlockSize,
1811}
1812
1813block!(Padding, Padding, true);
1814optional_block!(Padding, Padding);
1815
1816impl FromBitStreamUsing for Padding {
1817 type Context = BlockSize;
1818 type Error = Error;
1819
1820 fn from_reader<R: BitRead + ?Sized>(r: &mut R, size: BlockSize) -> Result<Self, Self::Error> {
1821 r.skip(size.get() * 8)?;
1822 Ok(Self { size })
1823 }
1824}
1825
1826impl ToBitStream for Padding {
1827 type Error = std::io::Error;
1828
1829 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
1830 w.pad(self.size.get() * 8)
1831 }
1832}
1833
1834/// An APPLICATION metadata block
1835///
1836/// This block is for handling application-specific binary metadata,
1837/// such as foreign RIFF WAVE tags.
1838///
1839/// This block may occur multiple times in a FLAC file.
1840///
1841/// | Bits | Field | Meaning |
1842/// |-----:|------:|---------|
1843/// | 32 | `id` | registered application ID
1844/// | rest of block | `data` | application-specific data
1845///
1846#[derive(Debug, Clone, Eq, PartialEq)]
1847pub struct Application {
1848 /// A registered application ID
1849 pub id: u32,
1850 /// Application-specific data
1851 pub data: Vec<u8>,
1852}
1853
1854impl Application {
1855 /// Application ID for RIFF chunk storage
1856 pub const RIFF: u32 = 0x72696666;
1857
1858 /// Application ID for AIFF chunk storage
1859 pub const AIFF: u32 = 0x61696666;
1860}
1861
1862block!(Application, Application, true);
1863optional_block!(Application, Application);
1864
1865impl FromBitStreamUsing for Application {
1866 type Context = BlockSize;
1867 type Error = Error;
1868
1869 fn from_reader<R: BitRead + ?Sized>(r: &mut R, size: BlockSize) -> Result<Self, Self::Error> {
1870 Ok(Self {
1871 id: r.read_to()?,
1872 data: r.read_to_vec(
1873 size.get()
1874 .checked_sub(4)
1875 .ok_or(Error::InsufficientApplicationBlock)?
1876 .try_into()
1877 .unwrap(),
1878 )?,
1879 })
1880 }
1881}
1882
1883impl ToBitStream for Application {
1884 type Error = std::io::Error;
1885
1886 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
1887 w.write_from(self.id)?;
1888 w.write_bytes(&self.data)
1889 }
1890}
1891
1892/// A SEEKTABLE metadata block
1893///
1894/// Because FLAC frames do not store their compressed length,
1895/// a seek table is used for random access within a FLAC file.
1896/// By mapping a sample number to a byte offset,
1897/// one can quickly reach different parts of the file
1898/// without decoding the whole thing.
1899///
1900/// Also note that seek point byte offsets are
1901/// relative to the start of the first FLAC frame,
1902/// and *not* relative to the start of the entire file.
1903/// This allows us to change the size of the set
1904/// of metadata blocks without having to recalculate
1905/// the contents of the seek table.
1906///
1907/// Because the byte and sample offsets are
1908/// file-specific, a seek table generated for one file
1909/// should not be transferred to another FLAC file where the
1910/// frames are different sizes and in different positions.
1911///
1912/// This block may occur only once in a FLAC file.
1913///
1914/// Its seekpoints occupy the entire block.
1915///
1916/// # Example
1917/// ```
1918/// use bitstream_io::{BitReader, BitRead, BigEndian};
1919/// use flac_codec::metadata::{BlockHeader, BlockType, SeekTable, SeekPoint};
1920///
1921/// let data: &[u8] = &[
1922/// 0x83, 0x00, 0x00, 0x48, // block header
1923/// // seekpoint 0
1924/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1925/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1926/// 0x00, 0x14,
1927/// // seekpoint 1
1928/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
1929/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c,
1930/// 0x00, 0x14,
1931/// // seekpoint 2
1932/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28,
1933/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22,
1934/// 0x00, 0x14,
1935/// // seekpoint 3
1936/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
1937/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c,
1938/// 0x00, 0x14,
1939/// ];
1940///
1941/// let mut r = BitReader::endian(data, BigEndian);
1942/// let header = r.parse::<BlockHeader>().unwrap();
1943/// assert_eq!(
1944/// &header,
1945/// &BlockHeader {
1946/// last: true,
1947/// block_type: BlockType::SeekTable,
1948/// size: 0x48u8.into(),
1949/// },
1950/// );
1951///
1952/// let seektable = r.parse_using::<SeekTable>(header.size).unwrap();
1953///
1954/// assert_eq!(
1955/// Vec::from(seektable.points),
1956/// vec![
1957/// SeekPoint::Defined {
1958/// sample_offset: 0x00,
1959/// byte_offset: 0x00,
1960/// frame_samples: 0x14,
1961/// },
1962/// SeekPoint::Defined {
1963/// sample_offset: 0x14,
1964/// byte_offset: 0x0c,
1965/// frame_samples: 0x14,
1966/// },
1967/// SeekPoint::Defined {
1968/// sample_offset: 0x28,
1969/// byte_offset: 0x22,
1970/// frame_samples: 0x14,
1971/// },
1972/// SeekPoint::Defined {
1973/// sample_offset: 0x3c,
1974/// byte_offset: 0x3c,
1975/// frame_samples: 0x14,
1976/// },
1977/// ],
1978/// );
1979///
1980/// ```
1981#[derive(Debug, Clone, Eq, PartialEq, Default)]
1982pub struct SeekTable {
1983 /// The seek table's individual seek points
1984 pub points: contiguous::Contiguous<{ Self::MAX_POINTS }, SeekPoint>,
1985}
1986
1987impl SeekTable {
1988 /// The maximum number of seek points that fit into a seek table
1989 pub const MAX_POINTS: usize = (1 << 24) / ((64 + 64 + 16) / 8);
1990}
1991
1992block!(SeekTable, SeekTable, false);
1993optional_block!(SeekTable, SeekTable);
1994
1995impl FromBitStreamUsing for SeekTable {
1996 type Context = BlockSize;
1997 type Error = Error;
1998
1999 fn from_reader<R: BitRead + ?Sized>(r: &mut R, size: BlockSize) -> Result<Self, Self::Error> {
2000 match (size.get() / 18, size.get() % 18) {
2001 (p, 0) => Ok(Self {
2002 points: contiguous::Contiguous::try_collect((0..p).map(|_| r.parse()))
2003 .map_err(|_| Error::InvalidSeekTablePoint)??,
2004 }),
2005 _ => Err(Error::InvalidSeekTableSize),
2006 }
2007 }
2008}
2009
2010impl ToBitStream for SeekTable {
2011 type Error = Error;
2012
2013 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
2014 // ensure non-placeholder seek point offsets increment
2015 let mut last_offset = None;
2016
2017 self.points
2018 .iter()
2019 .try_for_each(|point| match last_offset.as_mut() {
2020 None => {
2021 last_offset = point.sample_offset();
2022 w.build(point).map_err(Error::Io)
2023 }
2024 Some(last_offset) => match point.sample_offset() {
2025 Some(our_offset) => match our_offset > *last_offset {
2026 true => {
2027 *last_offset = our_offset;
2028 w.build(point).map_err(Error::Io)
2029 }
2030 false => Err(Error::InvalidSeekTablePoint),
2031 },
2032 _ => w.build(point).map_err(Error::Io),
2033 },
2034 })
2035 }
2036}
2037
2038/// An individual SEEKTABLE seek point
2039///
2040/// | Bits | Field | Meaning |
2041/// |-----:|------:|---------|
2042/// | 64 | `sample_offset` | sample number of first sample in target frame
2043/// | 64 | `byte_offset` | offset, in bytes, from first frame to target frame's header
2044/// | 16 | `frame_samples` | number of samples in target frame
2045///
2046#[derive(Debug, Clone, Eq, PartialEq)]
2047pub enum SeekPoint {
2048 /// A defined, non-placeholder seek point
2049 Defined {
2050 /// The sample number of the first sample in the target frame
2051 sample_offset: u64,
2052 /// Offset, in bytes, from the first byte of the first frame header
2053 /// to the first byte in the target frame's header
2054 byte_offset: u64,
2055 /// Number of samples in the target frame
2056 frame_samples: u16,
2057 },
2058 /// A placeholder seek point
2059 Placeholder,
2060}
2061
2062impl SeekPoint {
2063 /// Returns our sample offset, if not a placeholder point
2064 pub fn sample_offset(&self) -> Option<u64> {
2065 match self {
2066 Self::Defined { sample_offset, .. } => Some(*sample_offset),
2067 Self::Placeholder => None,
2068 }
2069 }
2070}
2071
2072impl contiguous::Adjacent for SeekPoint {
2073 fn valid_first(&self) -> bool {
2074 true
2075 }
2076
2077 fn is_next(&self, previous: &SeekPoint) -> bool {
2078 // seekpoints must be unique by sample offset
2079 // and sample offsets must be ascending
2080 //
2081 // placeholders can come after non-placeholders or other placeholders,
2082 // but non-placeholders can't come after placeholders
2083 match self {
2084 Self::Defined {
2085 sample_offset: our_offset,
2086 ..
2087 } => match previous {
2088 Self::Defined {
2089 sample_offset: prev_offset,
2090 ..
2091 } => our_offset > prev_offset,
2092 Self::Placeholder => false,
2093 },
2094 Self::Placeholder => true,
2095 }
2096 }
2097}
2098
2099impl FromBitStream for SeekPoint {
2100 type Error = std::io::Error;
2101
2102 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
2103 match r.read_to()? {
2104 u64::MAX => {
2105 let _byte_offset = r.read_to::<u64>()?;
2106 let _frame_samples = r.read_to::<u16>()?;
2107 Ok(Self::Placeholder)
2108 }
2109 sample_offset => Ok(Self::Defined {
2110 sample_offset,
2111 byte_offset: r.read_to()?,
2112 frame_samples: r.read_to()?,
2113 }),
2114 }
2115 }
2116}
2117
2118impl ToBitStream for SeekPoint {
2119 type Error = std::io::Error;
2120
2121 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
2122 match self {
2123 Self::Defined {
2124 sample_offset,
2125 byte_offset,
2126 frame_samples,
2127 } => {
2128 w.write_from(*sample_offset)?;
2129 w.write_from(*byte_offset)?;
2130 w.write_from(*frame_samples)
2131 }
2132 Self::Placeholder => {
2133 w.write_from(u64::MAX)?;
2134 w.write_from::<u64>(0)?;
2135 w.write_from::<u16>(0)
2136 }
2137 }
2138 }
2139}
2140
2141/// A VORBIS_COMMENT metadata block
2142///
2143/// This block contains metadata such as track name,
2144/// artist name, album name, etc. Its contents are
2145/// UTF-8 encoded, `=`-delimited text fields
2146/// with a field name followed by value,
2147/// such as:
2148///
2149/// ```text
2150/// TITLE=Track Title
2151/// ```
2152///
2153/// Field names are case-insensitive and
2154/// may occur multiple times within the same comment
2155/// (a track may have multiple artists and choose to
2156/// store an "ARTIST" field for each one).
2157///
2158/// Commonly-used fields are available in the [`fields`] module.
2159///
2160/// This block may occur only once in a FLAC file.
2161///
2162/// # Byte Order
2163///
2164/// Unlike the rest of a FLAC file, the Vorbis comment's
2165/// length fields are stored in little-endian byte order.
2166///
2167/// | Bits | Field | Meaning |
2168/// |-----:|------:|---------|
2169/// | 32 | vendor string len | length of vendor string, in bytes
2170/// | `vendor string len`×8 | `vendor_string` | vendor string, in UTF-8
2171/// | 32 | field count | number of vendor string fields
2172/// | 32 | field₀ len | length of field₀, in bytes
2173/// | `field₀ len`×8 | `fields₀` | first field value, in UTF-8
2174/// | 32 | field₁ len | length of field₁, in bytes
2175/// | `field₁ len`×8 | `fields₁` | second field value, in UTF-8
2176/// | | | ⋮
2177///
2178/// # Example
2179/// ```
2180/// use bitstream_io::{BitReader, BitRead, LittleEndian};
2181/// use flac_codec::metadata::VorbisComment;
2182/// use flac_codec::metadata::fields::{TITLE, ALBUM, ARTIST};
2183///
2184/// let data: &[u8] = &[
2185/// 0x20, 0x00, 0x00, 0x00, // 32 byte vendor string
2186/// 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63,
2187/// 0x65, 0x20, 0x6c, 0x69, 0x62, 0x46, 0x4c, 0x41,
2188/// 0x43, 0x20, 0x31, 0x2e, 0x34, 0x2e, 0x33, 0x20,
2189/// 0x32, 0x30, 0x32, 0x33, 0x30, 0x36, 0x32, 0x33,
2190/// 0x02, 0x00, 0x00, 0x00, // 2 fields
2191/// 0x0d, 0x00, 0x00, 0x00, // 13 byte field 1
2192/// 0x54, 0x49, 0x54, 0x4c, 0x45, 0x3d, 0x54, 0x65,
2193/// 0x73, 0x74, 0x69, 0x6e, 0x67,
2194/// 0x10, 0x00, 0x00, 0x00, // 16 byte field 2
2195/// 0x41, 0x4c, 0x42, 0x55, 0x4d, 0x3d, 0x54, 0x65,
2196/// 0x73, 0x74, 0x20, 0x41, 0x6c, 0x62, 0x75, 0x6d,
2197/// ];
2198///
2199/// let mut r = BitReader::endian(data, LittleEndian);
2200/// let comment = r.parse::<VorbisComment>().unwrap();
2201///
2202/// assert_eq!(
2203/// &comment,
2204/// &VorbisComment {
2205/// vendor_string: "reference libFLAC 1.4.3 20230623".to_string(),
2206/// fields: vec![
2207/// "TITLE=Testing".to_string(),
2208/// "ALBUM=Test Album".to_string(),
2209/// ],
2210/// },
2211/// );
2212///
2213/// assert_eq!(comment.get(TITLE), Some("Testing"));
2214/// assert_eq!(comment.get(ALBUM), Some("Test Album"));
2215/// assert_eq!(comment.get(ARTIST), None);
2216/// ```
2217#[derive(Debug, Clone, Eq, PartialEq)]
2218pub struct VorbisComment {
2219 /// The vendor string
2220 pub vendor_string: String,
2221 /// The individual metadata comment strings
2222 pub fields: Vec<String>,
2223}
2224
2225impl Default for VorbisComment {
2226 fn default() -> Self {
2227 Self {
2228 vendor_string: concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"))
2229 .to_owned(),
2230 fields: vec![],
2231 }
2232 }
2233}
2234
2235impl VorbisComment {
2236 /// Given a field name, returns first matching value, if any
2237 ///
2238 /// Fields are matched case-insensitively
2239 ///
2240 /// # Example
2241 ///
2242 /// ```
2243 /// use flac_codec::metadata::{VorbisComment, fields::{ARTIST, TITLE}};
2244 ///
2245 /// let comment = VorbisComment {
2246 /// fields: vec![
2247 /// "ARTIST=Artist 1".to_owned(),
2248 /// "ARTIST=Artist 2".to_owned(),
2249 /// ],
2250 /// ..VorbisComment::default()
2251 /// };
2252 ///
2253 /// assert_eq!(comment.get(ARTIST), Some("Artist 1"));
2254 /// assert_eq!(comment.get(TITLE), None);
2255 /// ```
2256 pub fn get(&self, field: &str) -> Option<&str> {
2257 self.all(field).next()
2258 }
2259
2260 /// Replaces any instances of the given field with value
2261 ///
2262 /// Fields are matched case-insensitively
2263 ///
2264 /// # Panics
2265 ///
2266 /// Panics if field contains the `=` character.
2267 ///
2268 /// # Example
2269 ///
2270 /// ```
2271 /// use flac_codec::metadata::{VorbisComment, fields::ARTIST};
2272 ///
2273 /// let mut comment = VorbisComment {
2274 /// fields: vec![
2275 /// "ARTIST=Artist 1".to_owned(),
2276 /// "ARTIST=Artist 2".to_owned(),
2277 /// ],
2278 /// ..VorbisComment::default()
2279 /// };
2280 ///
2281 /// comment.set(ARTIST, "Artist 3");
2282 ///
2283 /// assert_eq!(
2284 /// comment.all(ARTIST).collect::<Vec<_>>(),
2285 /// vec!["Artist 3"],
2286 /// );
2287 /// ```
2288 pub fn set<S>(&mut self, field: &str, value: S)
2289 where
2290 S: std::fmt::Display,
2291 {
2292 self.remove(field);
2293 self.insert(field, value);
2294 }
2295
2296 /// Given a field name, iterates over any matching values
2297 ///
2298 /// Fields are matched case-insensitively
2299 ///
2300 /// # Example
2301 ///
2302 /// ```
2303 /// use flac_codec::metadata::{VorbisComment, fields::ARTIST};
2304 ///
2305 /// let comment = VorbisComment {
2306 /// fields: vec![
2307 /// "ARTIST=Artist 1".to_owned(),
2308 /// "ARTIST=Artist 2".to_owned(),
2309 /// ],
2310 /// ..VorbisComment::default()
2311 /// };
2312 ///
2313 /// assert_eq!(
2314 /// comment.all(ARTIST).collect::<Vec<_>>(),
2315 /// vec!["Artist 1", "Artist 2"],
2316 /// );
2317 /// ```
2318 pub fn all(&self, field: &str) -> impl Iterator<Item = &str> {
2319 assert!(!field.contains('='), "field must not contain '='");
2320
2321 self.fields.iter().filter_map(|f| {
2322 f.split_once('=')
2323 .and_then(|(key, value)| key.eq_ignore_ascii_case(field).then_some(value))
2324 })
2325 }
2326
2327 /// Adds new instance of field with the given value
2328 ///
2329 /// # Panics
2330 ///
2331 /// Panics if field contains the `=` character.
2332 ///
2333 /// # Example
2334 ///
2335 /// ```
2336 /// use flac_codec::metadata::{VorbisComment, fields::ARTIST};
2337 ///
2338 /// let mut comment = VorbisComment {
2339 /// fields: vec![
2340 /// "ARTIST=Artist 1".to_owned(),
2341 /// "ARTIST=Artist 2".to_owned(),
2342 /// ],
2343 /// ..VorbisComment::default()
2344 /// };
2345 ///
2346 /// comment.insert(ARTIST, "Artist 3");
2347 ///
2348 /// assert_eq!(
2349 /// comment.all(ARTIST).collect::<Vec<_>>(),
2350 /// vec!["Artist 1", "Artist 2", "Artist 3"],
2351 /// );
2352 /// ```
2353 pub fn insert<S>(&mut self, field: &str, value: S)
2354 where
2355 S: std::fmt::Display,
2356 {
2357 assert!(!field.contains('='), "field must not contain '='");
2358
2359 self.fields.push(format!("{field}={value}"));
2360 }
2361
2362 /// Removes any matching instances of the given field
2363 ///
2364 /// Fields are matched case-insensitively
2365 ///
2366 /// # Panics
2367 ///
2368 /// Panics if field contains the `=` character.
2369 ///
2370 /// # Example
2371 ///
2372 /// ```
2373 /// use flac_codec::metadata::{VorbisComment, fields::ARTIST};
2374 ///
2375 /// let mut comment = VorbisComment {
2376 /// fields: vec![
2377 /// "ARTIST=Artist 1".to_owned(),
2378 /// "ARTIST=Artist 2".to_owned(),
2379 /// ],
2380 /// ..VorbisComment::default()
2381 /// };
2382 ///
2383 /// comment.remove(ARTIST);
2384 ///
2385 /// assert_eq!(comment.get(ARTIST), None);
2386 /// ```
2387 pub fn remove(&mut self, field: &str) {
2388 assert!(!field.contains('='), "field must not contain '='");
2389
2390 self.fields.retain(|f| match f.split_once('=') {
2391 Some((key, _)) => !key.eq_ignore_ascii_case(field),
2392 None => true,
2393 });
2394 }
2395
2396 /// Replaces any instances of the given field with the given values
2397 ///
2398 /// Fields are matched case-insensitively
2399 ///
2400 /// # Panics
2401 ///
2402 /// Panics if field contains the `=` character
2403 ///
2404 /// # Example
2405 ///
2406 /// ```
2407 /// use flac_codec::metadata::{VorbisComment, fields::ARTIST};
2408 ///
2409 /// let mut comment = VorbisComment {
2410 /// fields: vec![
2411 /// "ARTIST=Artist 1".to_owned(),
2412 /// "ARTIST=Artist 2".to_owned(),
2413 /// ],
2414 /// ..VorbisComment::default()
2415 /// };
2416 ///
2417 /// comment.replace(ARTIST, ["Artist 3", "Artist 4"]);
2418 ///
2419 /// assert_eq!(
2420 /// comment.all(ARTIST).collect::<Vec<_>>(),
2421 /// vec!["Artist 3", "Artist 4"],
2422 /// );
2423 ///
2424 /// // reminder that Option also implements IntoIterator
2425 /// comment.replace(ARTIST, Some("Artist 5"));
2426 ///
2427 /// assert_eq!(
2428 /// comment.all(ARTIST).collect::<Vec<_>>(),
2429 /// vec!["Artist 5"],
2430 /// );
2431 /// ```
2432 pub fn replace<S: std::fmt::Display>(
2433 &mut self,
2434 field: &str,
2435 replacements: impl IntoIterator<Item = S>,
2436 ) {
2437 self.remove(field);
2438 self.fields.extend(
2439 replacements
2440 .into_iter()
2441 .map(|value| format!("{field}={value}")),
2442 );
2443 }
2444
2445 /// Replaces instances of the given field with a closure's result
2446 ///
2447 /// Closure takes the field's current value and returns
2448 /// something `Display`-able such as a `String`.
2449 ///
2450 /// The closure is called for each maching field, if any.
2451 ///
2452 /// Fields are matched case-insensitively.
2453 ///
2454 /// # Panics
2455 ///
2456 /// Panics if field contains the `=` character
2457 ///
2458 /// # Example
2459 ///
2460 /// ```
2461 /// use flac_codec::metadata::{VorbisComment, fields::ARTIST};
2462 ///
2463 /// let mut comment = VorbisComment {
2464 /// fields: vec![
2465 /// "ARTIST=some artist".to_owned(),
2466 /// ],
2467 /// ..VorbisComment::default()
2468 /// };
2469 ///
2470 /// comment.replace_with(ARTIST, |s| s.to_ascii_uppercase());
2471 ///
2472 /// assert_eq!(comment.get(ARTIST), Some("SOME ARTIST"));
2473 /// ```
2474 pub fn replace_with<S: std::fmt::Display>(
2475 &mut self,
2476 field: &str,
2477 mut f: impl FnMut(&str) -> S,
2478 ) {
2479 assert!(!field.contains('='), "field must not contain '='");
2480
2481 self.fields.iter_mut().for_each(|s| {
2482 if let Some((key, value)) = s.split_once('=')
2483 && key.eq_ignore_ascii_case(field)
2484 {
2485 *s = format!("{key}={}", f(value));
2486 }
2487 })
2488 }
2489}
2490
2491block!(VorbisComment, VorbisComment, false);
2492optional_block!(VorbisComment, VorbisComment);
2493
2494impl FromBitStream for VorbisComment {
2495 type Error = Error;
2496
2497 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
2498 fn read_string<R: BitRead + ?Sized>(r: &mut R) -> Result<String, Error> {
2499 let size = r.read_as_to::<LittleEndian, u32>()?.try_into().unwrap();
2500 Ok(String::from_utf8(r.read_to_vec(size)?)?)
2501 }
2502
2503 Ok(Self {
2504 vendor_string: read_string(r)?,
2505 fields: (0..(r.read_as_to::<LittleEndian, u32>()?))
2506 .map(|_| read_string(r))
2507 .collect::<Result<Vec<_>, _>>()?,
2508 })
2509 }
2510}
2511
2512impl ToBitStream for VorbisComment {
2513 type Error = Error;
2514
2515 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
2516 fn write_string<W: BitWrite + ?Sized>(w: &mut W, s: &str) -> Result<(), Error> {
2517 w.write_as_from::<LittleEndian, u32>(
2518 s.len()
2519 .try_into()
2520 .map_err(|_| Error::ExcessiveStringLength)?,
2521 )?;
2522 w.write_bytes(s.as_bytes())?;
2523 Ok(())
2524 }
2525
2526 write_string(w, &self.vendor_string)?;
2527 w.write_as_from::<LittleEndian, u32>(
2528 self.fields
2529 .len()
2530 .try_into()
2531 .map_err(|_| Error::ExcessiveVorbisEntries)?,
2532 )?;
2533 self.fields.iter().try_for_each(|s| write_string(w, s))
2534 }
2535}
2536
2537// As neat as it might be implement IndexMut for VorbisComment,
2538// the trait simply isn't compatible. A "&mut str" is mostly
2539// useless, and I can't return a partial "&mut String"
2540// in order to assing a new string to everything after the
2541// initial "FIELD=" indicator.
2542
2543/// Vorbis comment metadata tag fields
2544///
2545/// Not all of these fields are officially defined in the specification,
2546/// but they are in common use.
2547pub mod fields {
2548 /// Name of current work
2549 pub const TITLE: &str = "TITLE";
2550
2551 /// Name of the artist generally responsible for the current work
2552 pub const ARTIST: &str = "ARTIST";
2553
2554 /// Name of the collection the current work belongs to
2555 pub const ALBUM: &str = "ALBUM";
2556
2557 /// The work's original composer
2558 pub const COMPOSER: &str = "COMPOSER";
2559
2560 /// The performance's conductor
2561 pub const CONDUCTOR: &str = "CONDUCTOR";
2562
2563 /// The current work's performer(s)
2564 pub const PERFORMER: &str = "PERFORMER";
2565
2566 /// The album's publisher
2567 pub const PUBLISHER: &str = "PUBLISHER";
2568
2569 /// The album's catalog number
2570 pub const CATALOG: &str = "CATALOG";
2571
2572 /// Release date of work
2573 pub const DATE: &str = "DATE";
2574
2575 /// Generic comment
2576 pub const COMMENT: &str = "COMMENT";
2577
2578 /// Track number in album
2579 pub const TRACK_NUMBER: &str = "TRACKNUMBER";
2580
2581 /// Total tracks in album
2582 pub const TRACK_TOTAL: &str = "TRACKTOTAL";
2583
2584 /// The channel mask of multi-channel audio streams
2585 pub const CHANNEL_MASK: &str = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK";
2586
2587 /// ReplayGain track gain
2588 pub const RG_TRACK_GAIN: &str = "REPLAYGAIN_TRACK_GAIN";
2589
2590 /// ReplayGain album gain
2591 pub const RG_ALBUM_GAIN: &str = "REPLAYGAIN_ALBUM_GAIN";
2592
2593 /// ReplayGain track peak
2594 pub const RG_TRACK_PEAK: &str = "REPLAYGAIN_TRACK_PEAK";
2595
2596 /// ReplayGain album peak
2597 pub const RG_ALBUM_PEAK: &str = "REPLAYGAIN_ALBUM_PEAK";
2598
2599 /// ReplayGain reference loudness
2600 pub const RG_REFERENCE_LOUDNESS: &str = "REPLAYGAIN_REFERENCE_LOUDNESS";
2601}
2602
2603/// Types for collections which must be contiguous
2604///
2605/// Used by the SEEKTABLE and CUESHEET metadata blocks
2606pub mod contiguous {
2607 /// A trait for types which can be contiguous
2608 pub trait Adjacent {
2609 /// Whether the item is valid as the first in a sequence
2610 fn valid_first(&self) -> bool;
2611
2612 /// Whether the item is immediately following the previous
2613 fn is_next(&self, previous: &Self) -> bool;
2614 }
2615
2616 impl Adjacent for u64 {
2617 fn valid_first(&self) -> bool {
2618 *self == 0
2619 }
2620
2621 fn is_next(&self, previous: &Self) -> bool {
2622 *self > *previous
2623 }
2624 }
2625
2626 impl Adjacent for std::num::NonZero<u8> {
2627 fn valid_first(&self) -> bool {
2628 *self == Self::MIN
2629 }
2630
2631 fn is_next(&self, previous: &Self) -> bool {
2632 previous.checked_add(1).map(|n| n == *self).unwrap_or(false)
2633 }
2634 }
2635
2636 /// A Vec-like type which requires all items to be adjacent
2637 #[derive(Debug, Clone, Eq, PartialEq)]
2638 pub struct Contiguous<const MAX: usize, T: Adjacent> {
2639 items: Vec<T>,
2640 }
2641
2642 impl<const MAX: usize, T: Adjacent> Default for Contiguous<MAX, T> {
2643 fn default() -> Self {
2644 Self { items: Vec::new() }
2645 }
2646 }
2647
2648 impl<const MAX: usize, T: Adjacent> Contiguous<MAX, T> {
2649 /// Constructs new contiguous block with the given capacity
2650 ///
2651 /// See [`Vec::with_capacity`]
2652 pub fn with_capacity(capacity: usize) -> Self {
2653 Self {
2654 items: Vec::with_capacity(capacity.min(MAX)),
2655 }
2656 }
2657
2658 /// Removes all items without adjust total capacity
2659 ///
2660 /// See [`Vec::clear`]
2661 pub fn clear(&mut self) {
2662 self.items.clear()
2663 }
2664
2665 /// Attempts to push item into contiguous set
2666 ///
2667 /// # Errors
2668 ///
2669 /// Returns error if item is not a valid first item
2670 /// in the set or is not contiguous with the
2671 /// existing items.
2672 pub fn try_push(&mut self, item: T) -> Result<(), NonContiguous> {
2673 if self.items.len() < MAX {
2674 if match self.items.last() {
2675 None => item.valid_first(),
2676 Some(last) => item.is_next(last),
2677 } {
2678 self.items.push(item);
2679 Ok(())
2680 } else {
2681 Err(NonContiguous)
2682 }
2683 } else {
2684 Err(NonContiguous)
2685 }
2686 }
2687
2688 /// Attempts to extends set with items from iterator
2689 pub fn try_extend(
2690 &mut self,
2691 iter: impl IntoIterator<Item = T>,
2692 ) -> Result<(), NonContiguous> {
2693 iter.into_iter().try_for_each(|item| self.try_push(item))
2694 }
2695
2696 /// Attempts to collect a contiguous set from a fallible iterator
2697 pub fn try_collect<I, E>(iter: I) -> Result<Result<Self, E>, NonContiguous>
2698 where
2699 I: IntoIterator<Item = Result<T, E>>,
2700 {
2701 let iter = iter.into_iter();
2702 let mut c = Self::with_capacity(iter.size_hint().0);
2703
2704 for item in iter {
2705 match item {
2706 Ok(item) => c.try_push(item)?,
2707 Err(err) => return Ok(Err(err)),
2708 }
2709 }
2710 Ok(Ok(c))
2711 }
2712 }
2713
2714 impl<const MAX: usize, T: Adjacent> std::ops::Deref for Contiguous<MAX, T> {
2715 type Target = [T];
2716
2717 fn deref(&self) -> &[T] {
2718 self.items.as_slice()
2719 }
2720 }
2721
2722 impl<const MAX: usize, T: Adjacent> From<Contiguous<MAX, T>> for Vec<T> {
2723 fn from(contiguous: Contiguous<MAX, T>) -> Self {
2724 contiguous.items
2725 }
2726 }
2727
2728 impl<const MAX: usize, T: Adjacent> From<Contiguous<MAX, T>> for std::collections::VecDeque<T> {
2729 fn from(contiguous: Contiguous<MAX, T>) -> Self {
2730 contiguous.items.into()
2731 }
2732 }
2733
2734 impl<const MAX: usize, T: Adjacent> TryFrom<Vec<T>> for Contiguous<MAX, T> {
2735 type Error = NonContiguous;
2736
2737 fn try_from(items: Vec<T>) -> Result<Self, Self::Error> {
2738 (items.len() <= MAX && is_contiguous(&items))
2739 .then_some(Self { items })
2740 .ok_or(NonContiguous)
2741 }
2742 }
2743
2744 /// Attempted to insert a non-contiguous item into a set
2745 #[derive(Copy, Clone, Debug)]
2746 pub struct NonContiguous;
2747
2748 impl std::error::Error for NonContiguous {}
2749
2750 impl std::fmt::Display for NonContiguous {
2751 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2752 "item is non-contiguous".fmt(f)
2753 }
2754 }
2755
2756 fn is_contiguous<'t, T: Adjacent + 't>(items: impl IntoIterator<Item = &'t T>) -> bool {
2757 and_previous(items).all(|(prev, item)| match prev {
2758 Some(prev) => item.is_next(prev),
2759 None => item.valid_first(),
2760 })
2761 }
2762
2763 fn and_previous<T: Copy>(
2764 iter: impl IntoIterator<Item = T>,
2765 ) -> impl Iterator<Item = (Option<T>, T)> {
2766 let mut previous = None;
2767 iter.into_iter().map(move |i| (previous.replace(i), i))
2768 }
2769}
2770
2771/// A CUESHEET metadata block
2772///
2773/// A cue sheet stores a disc's original layout
2774/// with all its tracks, index points, and disc-specific metadata.
2775///
2776/// This block may occur multiple times in a FLAC file, theoretically.
2777///
2778/// | Bits | Field | Meaning |
2779/// |------:|------:|---------|
2780/// | 128×8 | `catalog_number` | media catalog number, in ASCII
2781/// | 64 | `lead_in_samples` | number of lead-in samples
2782/// | 1 | `is_cdda` | whether cuesheet corresponds to CD-DA
2783/// | 7+258×8 | padding | all 0 bits
2784/// | 8 | track count | number of cuesheet tracks
2785/// | | `tracks` | cuesheet track₀, cuesheet track₁, …
2786///
2787/// Although the structure of this block is not particularly
2788/// complicated, a CUESHEET block must abide by many rules
2789/// in order to be considered valid. Many of these
2790/// rules are encoded into the type system.
2791#[derive(Debug, Clone, Eq, PartialEq)]
2792pub enum Cuesheet {
2793 /// A CD-DA Cuesheet, for audio CDs
2794 CDDA {
2795 /// Media catalog number in ASCII digits
2796 ///
2797 /// For CD-DA, if present, this number must
2798 /// be exactly 13 ASCII digits followed by all
2799 /// 0 bytes.
2800 catalog_number: Option<[cuesheet::Digit; 13]>,
2801
2802 /// Number of lead-in samples
2803 ///
2804 /// For CD-DA, this must be at least 2 seconds
2805 /// (88200 samples), but may be longer.
2806 ///
2807 /// Non-CD-DA cuesheets must always use 0 for
2808 /// lead-in samples, which is why that variant
2809 /// does not have this field.
2810 lead_in_samples: u64,
2811
2812 /// The cue sheet's non-lead-out tracks
2813 ///
2814 /// For CD-DA, 0 ≤ track count ≤ 99
2815 tracks: contiguous::Contiguous<99, cuesheet::TrackCDDA>,
2816
2817 /// The required lead-out-track
2818 ///
2819 /// This has a track number of 170 and
2820 /// indicates the end of the disc.
2821 lead_out: cuesheet::LeadOutCDDA,
2822 },
2823 /// A Non-CD-DA Cuesheet, for non-audio CDs
2824 NonCDDA {
2825 /// Media catalog number in ASCII digits
2826 ///
2827 /// 0 ≤ catalog number digits < 120
2828 catalog_number: Vec<cuesheet::Digit>,
2829
2830 /// The cue sheet's non-lead-out tracks
2831 ///
2832 /// For Non-CD-DA, 0 ≤ track count ≤ 254
2833 tracks: contiguous::Contiguous<254, cuesheet::TrackNonCDDA>,
2834
2835 /// The required lead-out-track
2836 ///
2837 /// This has a track number of 255 and
2838 /// indicates the end of the disc.
2839 lead_out: cuesheet::LeadOutNonCDDA,
2840 },
2841}
2842
2843impl Cuesheet {
2844 /// Default number of lead-in samples
2845 const LEAD_IN: u64 = 44100 * 2;
2846
2847 /// Maximum catalog number length, in digits
2848 const CATALOG_LEN: usize = 128;
2849
2850 /// Media catalog number
2851 pub fn catalog_number(&self) -> impl std::fmt::Display {
2852 use cuesheet::Digit;
2853
2854 enum Digits<'d> {
2855 Digits(&'d [Digit]),
2856 Empty,
2857 }
2858
2859 impl std::fmt::Display for Digits<'_> {
2860 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2861 match self {
2862 Self::Digits(digits) => digits.iter().try_for_each(|d| d.fmt(f)),
2863 Self::Empty => Ok(()),
2864 }
2865 }
2866 }
2867
2868 match self {
2869 Self::CDDA {
2870 catalog_number: Some(catalog_number),
2871 ..
2872 } => Digits::Digits(catalog_number),
2873 Self::CDDA {
2874 catalog_number: None,
2875 ..
2876 } => Digits::Empty,
2877 Self::NonCDDA { catalog_number, .. } => Digits::Digits(catalog_number),
2878 }
2879 }
2880
2881 /// The number of lead-in samples for CD-DA discs
2882 pub fn lead_in_samples(&self) -> Option<u64> {
2883 match self {
2884 Self::CDDA {
2885 lead_in_samples, ..
2886 } => Some(*lead_in_samples),
2887 Self::NonCDDA { .. } => None,
2888 }
2889 }
2890
2891 /// If this is a CD-DA cuesheet
2892 pub fn is_cdda(&self) -> bool {
2893 matches!(self, Self::CDDA { .. })
2894 }
2895
2896 /// Returns total number of tracks in cuesheet
2897 pub fn track_count(&self) -> usize {
2898 match self {
2899 Self::CDDA { tracks, .. } => tracks.len() + 1,
2900 Self::NonCDDA { tracks, .. } => tracks.len() + 1,
2901 }
2902 }
2903
2904 /// Iterates over all tracks in cuesheet
2905 ///
2906 /// Tracks are converted into a unified format suitable for display
2907 pub fn tracks(&self) -> Box<dyn Iterator<Item = cuesheet::TrackGeneric> + '_> {
2908 use cuesheet::{Index, Track};
2909
2910 match self {
2911 Self::CDDA {
2912 tracks, lead_out, ..
2913 } => Box::new(
2914 tracks
2915 .iter()
2916 .map(|track| Track {
2917 offset: track.offset.into(),
2918 number: Some(track.number.get()),
2919 isrc: track.isrc.clone(),
2920 non_audio: track.non_audio,
2921 pre_emphasis: track.pre_emphasis,
2922 index_points: track
2923 .index_points
2924 .iter()
2925 .map(|point| Index {
2926 number: point.number,
2927 offset: point.offset.into(),
2928 })
2929 .collect(),
2930 })
2931 .chain(std::iter::once(Track {
2932 offset: lead_out.offset.into(),
2933 number: None,
2934 isrc: lead_out.isrc.clone(),
2935 non_audio: lead_out.non_audio,
2936 pre_emphasis: lead_out.pre_emphasis,
2937 index_points: vec![],
2938 })),
2939 ),
2940 Self::NonCDDA {
2941 tracks, lead_out, ..
2942 } => Box::new(
2943 tracks
2944 .iter()
2945 .map(|track| Track {
2946 offset: track.offset,
2947 number: Some(track.number.get()),
2948 isrc: track.isrc.clone(),
2949 non_audio: track.non_audio,
2950 pre_emphasis: track.pre_emphasis,
2951 index_points: track
2952 .index_points
2953 .iter()
2954 .map(|point| Index {
2955 number: point.number,
2956 offset: point.offset,
2957 })
2958 .collect(),
2959 })
2960 .chain(std::iter::once(Track {
2961 offset: lead_out.offset,
2962 number: None,
2963 isrc: lead_out.isrc.clone(),
2964 non_audio: lead_out.non_audio,
2965 pre_emphasis: lead_out.pre_emphasis,
2966 index_points: vec![],
2967 })),
2968 ),
2969 }
2970 }
2971
2972 /// Given a filename to use, returns cuesheet data as text
2973 pub fn display(&self, filename: &str) -> impl std::fmt::Display {
2974 struct DisplayCuesheet<'c, 'f> {
2975 cuesheet: &'c Cuesheet,
2976 filename: &'f str,
2977 }
2978
2979 impl std::fmt::Display for DisplayCuesheet<'_, '_> {
2980 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2981 /// A CUESHEET timestamp in MM:SS:FF format
2982 #[derive(Copy, Clone)]
2983 pub struct Timestamp {
2984 minutes: u64,
2985 seconds: u8,
2986 frames: u8,
2987 }
2988
2989 impl Timestamp {
2990 const FRAMES_PER_SECOND: u64 = 75;
2991 const SECONDS_PER_MINUTE: u64 = 60;
2992 const SAMPLES_PER_FRAME: u64 = 44100 / 75;
2993 }
2994
2995 impl From<u64> for Timestamp {
2996 fn from(offset: u64) -> Self {
2997 let total_frames = offset / Self::SAMPLES_PER_FRAME;
2998
2999 Self {
3000 minutes: (total_frames / Self::FRAMES_PER_SECOND)
3001 / Self::SECONDS_PER_MINUTE,
3002 seconds: ((total_frames / Self::FRAMES_PER_SECOND)
3003 % Self::SECONDS_PER_MINUTE)
3004 .try_into()
3005 .unwrap(),
3006 frames: (total_frames % Self::FRAMES_PER_SECOND).try_into().unwrap(),
3007 }
3008 }
3009 }
3010
3011 impl std::fmt::Display for Timestamp {
3012 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
3013 write!(
3014 f,
3015 "{:02}:{:02}:{:02}",
3016 self.minutes, self.seconds, self.frames
3017 )
3018 }
3019 }
3020
3021 writeln!(f, "FILE \"{}\" FLAC", self.filename)?;
3022
3023 match self.cuesheet {
3024 Cuesheet::CDDA { tracks, .. } => {
3025 for track in tracks.iter() {
3026 writeln!(
3027 f,
3028 " TRACK {} {}",
3029 track.number,
3030 if track.non_audio {
3031 "NON_AUDIO"
3032 } else {
3033 "AUDIO"
3034 }
3035 )?;
3036 for index in track.index_points.iter() {
3037 writeln!(
3038 f,
3039 " INDEX {:02} {}",
3040 index.number,
3041 Timestamp::from(u64::from(index.offset + track.offset)),
3042 )?;
3043 }
3044 }
3045 }
3046 Cuesheet::NonCDDA { tracks, .. } => {
3047 for track in tracks.iter() {
3048 writeln!(
3049 f,
3050 " TRACK {} {}",
3051 track.number,
3052 if track.non_audio {
3053 "NON_AUDIO"
3054 } else {
3055 "AUDIO"
3056 }
3057 )?;
3058 for index in track.index_points.iter() {
3059 writeln!(
3060 f,
3061 " INDEX {:02} {}",
3062 index.number,
3063 Timestamp::from(index.offset + track.offset),
3064 )?;
3065 }
3066 }
3067 }
3068 }
3069
3070 Ok(())
3071 }
3072 }
3073
3074 DisplayCuesheet {
3075 cuesheet: self,
3076 filename,
3077 }
3078 }
3079
3080 /// Attempts to parse new `Cuesheet` from cue sheet file
3081 ///
3082 /// `total_samples` should be the total number
3083 /// of channel-independent samples, used to
3084 /// calculate the lead-out track
3085 ///
3086 /// `cuesheet` is the entire cuesheet as a string slice
3087 ///
3088 /// This is a simplistic cuesheet parser sufficient
3089 /// for generating FLAC-compatible CUESHEET metadata blocks.
3090 ///
3091 /// # Example File
3092 ///
3093 /// ```text
3094 /// FILE "cdimage.wav" WAVE
3095 /// TRACK 01 AUDIO
3096 /// INDEX 01 00:00:00
3097 /// TRACK 02 AUDIO
3098 /// INDEX 00 02:57:52
3099 /// INDEX 01 03:00:02
3100 /// TRACK 03 AUDIO
3101 /// INDEX 00 04:46:17
3102 /// INDEX 01 04:48:64
3103 /// TRACK 04 AUDIO
3104 /// INDEX 00 07:09:01
3105 /// INDEX 01 07:11:49
3106 /// TRACK 05 AUDIO
3107 /// INDEX 00 09:11:47
3108 /// INDEX 01 09:13:54
3109 /// TRACK 06 AUDIO
3110 /// INDEX 00 11:10:13
3111 /// INDEX 01 11:12:51
3112 /// TRACK 07 AUDIO
3113 /// INDEX 00 13:03:74
3114 /// INDEX 01 13:07:19
3115 /// ```
3116 ///
3117 /// `INDEX` points are in the format:
3118 ///
3119 /// ```text
3120 /// minutes frames
3121 /// ↓↓ ↓↓
3122 /// MM::SS::FF
3123 /// ↑↑
3124 /// seconds
3125 /// ```
3126 ///
3127 /// There are 75 frames per second, and 60 seconds per minute.
3128 /// Since CD audio has 44100 channel-independent samples per second,
3129 /// the number of channel-independent samples per frame is 588
3130 /// (44100 ÷ 75 = 588).
3131 ///
3132 /// Thus, the sample offset of each `INDEX` point can be calculated like:
3133 ///
3134 /// samples = ((MM × 60 × 75) + (SS × 75) + FF) × 588
3135 ///
3136 /// Note that the `INDEX` points are stored in increasing order,
3137 /// as a standard single file cue sheet.
3138 ///
3139 /// # Example
3140 ///
3141 /// ```
3142 /// use flac_codec::metadata::{Cuesheet, cuesheet::{Track, Index, ISRC}};
3143 ///
3144 /// let file = "FILE \"cdimage.wav\" WAVE
3145 /// TRACK 01 AUDIO
3146 /// INDEX 01 00:00:00
3147 /// TRACK 02 AUDIO
3148 /// INDEX 00 02:57:52
3149 /// INDEX 01 03:00:02
3150 /// TRACK 03 AUDIO
3151 /// INDEX 00 04:46:17
3152 /// INDEX 01 04:48:64
3153 /// TRACK 04 AUDIO
3154 /// INDEX 00 07:09:01
3155 /// INDEX 01 07:11:49
3156 /// TRACK 05 AUDIO
3157 /// INDEX 00 09:11:47
3158 /// INDEX 01 09:13:54
3159 /// TRACK 06 AUDIO
3160 /// INDEX 00 11:10:13
3161 /// INDEX 01 11:12:51
3162 /// TRACK 07 AUDIO
3163 /// INDEX 00 13:03:74
3164 /// INDEX 01 13:07:19
3165 /// ";
3166 ///
3167 /// let cuesheet = Cuesheet::parse(39731748, file).unwrap();
3168 /// assert!(cuesheet.is_cdda());
3169 ///
3170 /// let mut tracks = cuesheet.tracks();
3171 ///
3172 /// assert_eq!(
3173 /// tracks.next(),
3174 /// Some(Track {
3175 /// offset: 0,
3176 /// number: Some(01),
3177 /// isrc: ISRC::None,
3178 /// non_audio: false,
3179 /// pre_emphasis: false,
3180 /// index_points: vec![
3181 /// Index { number: 01, offset: 0 },
3182 /// ],
3183 /// }),
3184 /// );
3185 ///
3186 /// assert_eq!(
3187 /// tracks.next(),
3188 /// Some(Track {
3189 /// // track's offset is that of its first index point
3190 /// offset: ((2 * 60 * 75) + (57 * 75) + 52) * 588,
3191 /// number: Some(02),
3192 /// isrc: ISRC::None,
3193 /// non_audio: false,
3194 /// pre_emphasis: false,
3195 /// index_points: vec![
3196 /// // index point offsets are stored relative
3197 /// // to the track's offset
3198 /// Index { number: 00, offset: 0 },
3199 /// Index { number: 01, offset: 175 * 588 }
3200 /// ],
3201 /// }),
3202 /// );
3203 ///
3204 /// assert_eq!(
3205 /// tracks.next(),
3206 /// Some(Track {
3207 /// offset: ((4 * 60 * 75) + (46 * 75) + 17) * 588,
3208 /// number: Some(03),
3209 /// isrc: ISRC::None,
3210 /// non_audio: false,
3211 /// pre_emphasis: false,
3212 /// index_points: vec![
3213 /// Index { number: 00, offset: 0 },
3214 /// Index { number: 01, offset: 197 * 588 }
3215 /// ],
3216 /// }),
3217 /// );
3218 ///
3219 /// // skip over some tracks for brevity
3220 /// assert_eq!(tracks.next().and_then(|track| track.number), Some(04));
3221 /// assert_eq!(tracks.next().and_then(|track| track.number), Some(05));
3222 /// assert_eq!(tracks.next().and_then(|track| track.number), Some(06));
3223 /// assert_eq!(tracks.next().and_then(|track| track.number), Some(07));
3224 ///
3225 /// // the final lead-out track has an offset of the stream's total samples
3226 /// // and no index points
3227 /// assert_eq!(
3228 /// tracks.next(),
3229 /// Some(Track {
3230 /// offset: 39731748,
3231 /// number: None,
3232 /// isrc: ISRC::None,
3233 /// non_audio: false,
3234 /// pre_emphasis: false,
3235 /// index_points: vec![],
3236 /// }),
3237 /// );
3238 ///
3239 /// assert!(tracks.next().is_none());
3240 /// ```
3241 pub fn parse(total_samples: u64, cuesheet: &str) -> Result<Self, CuesheetError> {
3242 use cuesheet::Digit;
3243
3244 fn cdda_catalog(s: &str) -> Result<Option<[Digit; 13]>, CuesheetError> {
3245 s.chars()
3246 .map(Digit::try_from)
3247 .collect::<Result<Vec<_>, _>>()
3248 .and_then(|v| {
3249 <[Digit; 13] as TryFrom<Vec<Digit>>>::try_from(v)
3250 .map_err(|_| CuesheetError::InvalidCatalogNumber)
3251 })
3252 .map(Some)
3253 }
3254
3255 fn non_cdda_catalog(s: &str) -> Result<Vec<Digit>, CuesheetError> {
3256 s.chars()
3257 .map(Digit::try_from)
3258 .collect::<Result<Vec<_>, _>>()
3259 .and_then(|v| {
3260 (v.len() <= Cuesheet::CATALOG_LEN)
3261 .then_some(v)
3262 .ok_or(CuesheetError::InvalidCatalogNumber)
3263 })
3264 }
3265
3266 match total_samples.try_into() {
3267 // if total samples is divisible by 588,
3268 // try to parse a CDDA cuesheet
3269 Ok(lead_out_offset) => ParsedCuesheet::parse(cuesheet, cdda_catalog).and_then(
3270 |ParsedCuesheet {
3271 catalog_number,
3272 tracks,
3273 }| {
3274 Ok(Self::CDDA {
3275 catalog_number,
3276 lead_in_samples: Self::LEAD_IN,
3277 lead_out: cuesheet::LeadOutCDDA::new(tracks.last(), lead_out_offset)?,
3278 tracks,
3279 })
3280 },
3281 ),
3282 // if total samples isn't divisible by 588,
3283 // only try a non-CDDA cuesheet
3284 Err(_) => ParsedCuesheet::parse(cuesheet, non_cdda_catalog).and_then(
3285 |ParsedCuesheet {
3286 catalog_number,
3287 tracks,
3288 }| {
3289 Ok(Self::NonCDDA {
3290 catalog_number,
3291 lead_out: cuesheet::LeadOutNonCDDA::new(tracks.last(), total_samples)?,
3292 tracks,
3293 })
3294 },
3295 ),
3296 }
3297 }
3298
3299 fn track_offsets(&self) -> Box<dyn Iterator<Item = u64> + '_> {
3300 match self {
3301 Self::CDDA {
3302 tracks, lead_out, ..
3303 } => Box::new(
3304 tracks
3305 .iter()
3306 .map(|t| u64::from(t.offset + *t.index_points.start()))
3307 .chain(std::iter::once(u64::from(lead_out.offset))),
3308 ),
3309 Self::NonCDDA {
3310 tracks, lead_out, ..
3311 } => Box::new(
3312 tracks
3313 .iter()
3314 .map(|t| t.offset + t.index_points.start())
3315 .chain(std::iter::once(lead_out.offset)),
3316 ),
3317 }
3318 }
3319
3320 /// Iterates over track ranges in channel-indepedent samples
3321 ///
3322 /// Note that the range of each track is from the track
3323 /// start to the start of the next track, which is indicated
3324 /// by `INDEX 01`.
3325 /// It is *not* from the start of the pre-gaps (`INDEX 00`),
3326 /// which may not be present.
3327 ///
3328 /// ```
3329 /// use flac_codec::metadata::Cuesheet;
3330 ///
3331 /// let file = "FILE \"cdimage.wav\" WAVE
3332 /// TRACK 01 AUDIO
3333 /// INDEX 01 00:00:00
3334 /// TRACK 02 AUDIO
3335 /// INDEX 00 02:57:52
3336 /// INDEX 01 03:00:02
3337 /// TRACK 03 AUDIO
3338 /// INDEX 00 04:46:17
3339 /// INDEX 01 04:48:64
3340 /// TRACK 04 AUDIO
3341 /// INDEX 00 07:09:01
3342 /// INDEX 01 07:11:49
3343 /// TRACK 05 AUDIO
3344 /// INDEX 00 09:11:47
3345 /// INDEX 01 09:13:54
3346 /// TRACK 06 AUDIO
3347 /// INDEX 00 11:10:13
3348 /// INDEX 01 11:12:51
3349 /// TRACK 07 AUDIO
3350 /// INDEX 00 13:03:74
3351 /// INDEX 01 13:07:19
3352 /// ";
3353 ///
3354 /// let cuesheet = Cuesheet::parse(39731748, file).unwrap();
3355 /// let mut track_ranges = cuesheet.track_sample_ranges();
3356 ///
3357 /// // 00:00:00 to 03:00:02
3358 /// assert_eq!(
3359 /// track_ranges.next(),
3360 /// Some(0..((3 * 60 * 75) + (0 * 75) + 2) * 588)
3361 /// );
3362 ///
3363 /// // 03:00:02 to 04:48:64
3364 /// assert_eq!(
3365 /// track_ranges.next(),
3366 /// Some(((3 * 60 * 75) + (0 * 75) + 2) * 588..((4 * 60 * 75) + (48 * 75) + 64) * 588),
3367 /// );
3368 ///
3369 /// // skip a few tracks for brevity
3370 /// assert!(track_ranges.next().is_some()); // to 07:11.49
3371 /// assert!(track_ranges.next().is_some()); // to 09:13:54
3372 /// assert!(track_ranges.next().is_some()); // to 11:12:51
3373 /// assert!(track_ranges.next().is_some()); // to 13:07:19
3374 ///
3375 /// // 13:07:19 to the lead-out
3376 /// assert_eq!(
3377 /// track_ranges.next(),
3378 /// Some(((13 * 60 * 75) + (7 * 75) + 19) * 588..39731748),
3379 /// );
3380 ///
3381 /// assert!(track_ranges.next().is_none());
3382 /// ```
3383 pub fn track_sample_ranges(&self) -> impl Iterator<Item = std::ops::Range<u64>> {
3384 self.track_offsets()
3385 .zip(self.track_offsets().skip(1))
3386 .map(|(s, e)| s..e)
3387 }
3388
3389 /// Iterates over track ranges in bytes
3390 ///
3391 /// Much like [`Cuesheet::track_sample_ranges`], but takes
3392 /// a channel count and bits-per-sample to convert the ranges to bytes.
3393 ///
3394 /// For CD-DA, those values are 2 and 16, respectively.
3395 ///
3396 /// # Panics
3397 ///
3398 /// Panics if either `channel_count` or `bits_per_sample` are 0
3399 pub fn track_byte_ranges(
3400 &self,
3401 channel_count: u8,
3402 bits_per_sample: u32,
3403 ) -> impl Iterator<Item = std::ops::Range<u64>> {
3404 assert!(channel_count > 0, "channel_count must be > 0");
3405 assert!(bits_per_sample > 0, "bits_per_sample > 0");
3406
3407 let multiplier = u64::from(channel_count) * u64::from(bits_per_sample.div_ceil(8));
3408
3409 self.track_sample_ranges()
3410 .map(move |std::ops::Range { start, end }| start * multiplier..end * multiplier)
3411 }
3412}
3413
3414block!(Cuesheet, Cuesheet, true);
3415optional_block!(Cuesheet, Cuesheet);
3416
3417impl FromBitStream for Cuesheet {
3418 type Error = Error;
3419
3420 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
3421 let catalog_number: [u8; Self::CATALOG_LEN] = r.read_to()?;
3422 let lead_in_samples: u64 = r.read_to()?;
3423 let is_cdda = r.read_bit()?;
3424 r.skip(7 + 258 * 8)?;
3425 let track_count: u8 = r.read_to()?;
3426
3427 Ok(if is_cdda {
3428 Self::CDDA {
3429 catalog_number: {
3430 match trim_nulls(&catalog_number) {
3431 [] => None,
3432 number => Some(
3433 number
3434 .iter()
3435 .copied()
3436 .map(cuesheet::Digit::try_from)
3437 .collect::<Result<Vec<_>, u8>>()
3438 // any of the digits aren't valid ASCII
3439 .map_err(|_| Error::from(CuesheetError::InvalidCatalogNumber))?
3440 .try_into()
3441 // the number isn't the correct size
3442 .map_err(|_| Error::from(CuesheetError::InvalidCatalogNumber))?,
3443 ),
3444 }
3445 },
3446 lead_in_samples,
3447 tracks: contiguous::Contiguous::try_collect(
3448 (0..track_count
3449 .checked_sub(1)
3450 .filter(|c| *c <= 99)
3451 .ok_or(Error::from(CuesheetError::NoTracks))?)
3452 .map(|_| r.parse()),
3453 )
3454 .map_err(|_| Error::from(CuesheetError::TracksOutOfSequence))??,
3455 lead_out: r.parse()?,
3456 }
3457 } else {
3458 Self::NonCDDA {
3459 catalog_number: trim_nulls(&catalog_number)
3460 .iter()
3461 .copied()
3462 .map(cuesheet::Digit::try_from)
3463 .collect::<Result<Vec<_>, _>>()
3464 .map_err(|_| Error::from(CuesheetError::InvalidCatalogNumber))?,
3465 tracks: contiguous::Contiguous::try_collect(
3466 (0..track_count
3467 .checked_sub(1)
3468 .ok_or(Error::from(CuesheetError::NoTracks))?)
3469 .map(|_| r.parse()),
3470 )
3471 .map_err(|_| Error::from(CuesheetError::TracksOutOfSequence))??,
3472 lead_out: r.parse()?,
3473 }
3474 })
3475 }
3476}
3477
3478impl ToBitStream for Cuesheet {
3479 type Error = Error;
3480
3481 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
3482 match self {
3483 Self::CDDA {
3484 catalog_number,
3485 lead_in_samples,
3486 tracks,
3487 lead_out,
3488 } => {
3489 w.write_from(match catalog_number {
3490 Some(number) => {
3491 let mut catalog_number = [0; Self::CATALOG_LEN];
3492 catalog_number
3493 .iter_mut()
3494 .zip(number)
3495 .for_each(|(o, i)| *o = (*i).into());
3496 catalog_number
3497 }
3498 None => [0; Self::CATALOG_LEN],
3499 })?;
3500 w.write_from(*lead_in_samples)?;
3501 w.write_bit(true)?; // is CD-DA
3502 w.pad(7 + 258 * 8)?;
3503 w.write::<8, _>(u8::try_from(tracks.len() + 1).unwrap())?;
3504 for track in tracks.iter() {
3505 w.build(track)?;
3506 }
3507 w.build(lead_out)
3508 }
3509 Self::NonCDDA {
3510 catalog_number,
3511 tracks,
3512 lead_out,
3513 } => {
3514 w.write_from({
3515 let mut number = [0; Self::CATALOG_LEN];
3516 number
3517 .iter_mut()
3518 .zip(catalog_number)
3519 .for_each(|(o, i)| *o = (*i).into());
3520 number
3521 })?;
3522 w.write_from::<u64>(0)?; // non-CDDA cuesheets have no lead-in samples
3523 w.write_bit(false)?; // not CD-DA
3524 w.pad(7 + 258 * 8)?;
3525 w.write::<8, _>(u8::try_from(tracks.len() + 1).unwrap())?;
3526 for track in tracks.iter() {
3527 w.build(track)?;
3528 }
3529 w.build(lead_out)
3530 }
3531 }
3532 }
3533}
3534
3535// trims any trailing null bytes
3536fn trim_nulls(mut s: &[u8]) -> &[u8] {
3537 while let [rest @ .., 0] = s {
3538 s = rest;
3539 }
3540 s
3541}
3542
3543type ParsedCuesheetTrack<const INDEX_MAX: usize, O> =
3544 cuesheet::Track<O, NonZero<u8>, cuesheet::IndexVec<INDEX_MAX, O>>;
3545
3546struct ParsedCuesheet<const TRACK_MAX: usize, const INDEX_MAX: usize, C, O: contiguous::Adjacent> {
3547 catalog_number: C,
3548 tracks: contiguous::Contiguous<TRACK_MAX, ParsedCuesheetTrack<INDEX_MAX, O>>,
3549}
3550
3551impl<const TRACK_MAX: usize, const INDEX_MAX: usize, C, O>
3552 ParsedCuesheet<TRACK_MAX, INDEX_MAX, C, O>
3553where
3554 C: Default,
3555 O: contiguous::Adjacent
3556 + std::str::FromStr
3557 + Into<u64>
3558 + std::ops::Sub<Output = O>
3559 + Default
3560 + Copy,
3561{
3562 fn parse(
3563 cuesheet: &str,
3564 parse_catalog: impl Fn(&str) -> Result<C, CuesheetError>,
3565 ) -> Result<Self, CuesheetError> {
3566 type WipTrack<const INDEX_MAX: usize, O> = cuesheet::Track<
3567 Option<O>,
3568 NonZero<u8>,
3569 contiguous::Contiguous<INDEX_MAX, cuesheet::Index<O>>,
3570 >;
3571
3572 impl<const INDEX_MAX: usize, O: contiguous::Adjacent> WipTrack<INDEX_MAX, O> {
3573 fn new(number: NonZero<u8>) -> Self {
3574 Self {
3575 offset: None,
3576 number,
3577 isrc: cuesheet::ISRC::None,
3578 non_audio: false,
3579 pre_emphasis: false,
3580 index_points: contiguous::Contiguous::default(),
3581 }
3582 }
3583 }
3584
3585 impl<const INDEX_MAX: usize, O: contiguous::Adjacent> TryFrom<WipTrack<INDEX_MAX, O>>
3586 for ParsedCuesheetTrack<INDEX_MAX, O>
3587 {
3588 type Error = CuesheetError;
3589
3590 fn try_from(track: WipTrack<INDEX_MAX, O>) -> Result<Self, Self::Error> {
3591 // completed tracks need an offset which
3592 // is set by adding the first index point
3593 Ok(Self {
3594 offset: track.offset.ok_or(CuesheetError::InvalidTrack)?,
3595 number: track.number,
3596 isrc: track.isrc,
3597 non_audio: track.non_audio,
3598 pre_emphasis: track.pre_emphasis,
3599 index_points: track.index_points.try_into()?,
3600 })
3601 }
3602 }
3603
3604 // a bit of a hack, but should be good enough for now
3605 fn unquote(s: &str) -> &str {
3606 if s.len() > 1 && s.starts_with('"') && s.ends_with('"') {
3607 &s[1..s.len() - 1]
3608 } else {
3609 s
3610 }
3611 }
3612
3613 let mut wip_track: Option<WipTrack<INDEX_MAX, O>> = None;
3614
3615 let mut parsed = ParsedCuesheet {
3616 catalog_number: None,
3617 tracks: contiguous::Contiguous::default(),
3618 };
3619
3620 for line in cuesheet.lines() {
3621 let line = line.trim();
3622 match line.split_once(' ').unwrap_or((line, "")) {
3623 ("CATALOG", "") => return Err(CuesheetError::CatalogMissingNumber),
3624 ("CATALOG", number) => match parsed.catalog_number {
3625 Some(_) => return Err(CuesheetError::MultipleCatalogNumber),
3626 ref mut num @ None => {
3627 *num = Some(parse_catalog(unquote(number))?);
3628 }
3629 },
3630 ("TRACK", rest) => {
3631 if let Some(finished) = wip_track.replace(WipTrack::new(
3632 rest.split_once(' ')
3633 .ok_or(CuesheetError::InvalidTrack)?
3634 .0
3635 .parse()
3636 .map_err(|_| CuesheetError::InvalidTrack)?,
3637 )) {
3638 parsed
3639 .tracks
3640 .try_push(finished.try_into()?)
3641 .map_err(|_| CuesheetError::TracksOutOfSequence)?
3642 }
3643 }
3644 ("INDEX", rest) => {
3645 let (number, offset) = rest
3646 .split_once(' ')
3647 .ok_or(CuesheetError::InvalidIndexPoint)?;
3648
3649 let number: u8 = number
3650 .parse()
3651 .map_err(|_| CuesheetError::InvalidIndexPoint)?;
3652
3653 let offset: O = offset
3654 .parse()
3655 .map_err(|_| CuesheetError::InvalidIndexPoint)?;
3656
3657 let wip_track = wip_track.as_mut().ok_or(CuesheetError::PrematureIndex)?;
3658
3659 let index = match &mut wip_track.offset {
3660 // work-in progress track has no offset,
3661 // so we set it from this index point's
3662 // and set the index point's offset to 0
3663 track_offset @ None => {
3664 // the first index of the first track must have an offset of 0
3665 if parsed.tracks.is_empty() && offset.into() != 0 {
3666 return Err(CuesheetError::NonZeroFirstIndex);
3667 }
3668
3669 *track_offset = Some(offset);
3670
3671 cuesheet::Index {
3672 number,
3673 offset: O::default(),
3674 }
3675 }
3676 Some(track_offset) => {
3677 // work-in-progress track has offset,
3678 // so deduct that offset from this index point's
3679
3680 cuesheet::Index {
3681 number,
3682 offset: offset - *track_offset,
3683 }
3684 }
3685 };
3686
3687 wip_track
3688 .index_points
3689 .try_push(index)
3690 .map_err(|_| CuesheetError::IndexPointsOutOfSequence)?;
3691 }
3692 ("ISRC", isrc) => {
3693 use cuesheet::ISRC;
3694
3695 let wip_track = wip_track.as_mut().ok_or(CuesheetError::PrematureISRC)?;
3696
3697 if !wip_track.index_points.is_empty() {
3698 return Err(CuesheetError::LateISRC)?;
3699 }
3700
3701 match &mut wip_track.isrc {
3702 track_isrc @ ISRC::None => {
3703 *track_isrc = ISRC::String(
3704 unquote(isrc)
3705 .parse()
3706 .map_err(|_| CuesheetError::InvalidISRC)?,
3707 );
3708 }
3709 ISRC::String(_) => return Err(CuesheetError::MultipleISRC),
3710 }
3711 }
3712 ("FLAGS", "PRE") => {
3713 let wip_track = wip_track.as_mut().ok_or(CuesheetError::PrematureFlags)?;
3714
3715 if !wip_track.index_points.is_empty() {
3716 return Err(CuesheetError::LateFlags)?;
3717 } else {
3718 wip_track.pre_emphasis = true;
3719 }
3720 }
3721 _ => { /*do nothing for now*/ }
3722 }
3723 }
3724
3725 parsed
3726 .tracks
3727 .try_push(
3728 wip_track
3729 .take()
3730 .ok_or(CuesheetError::NoTracks)?
3731 .try_into()?,
3732 )
3733 .map_err(|_| CuesheetError::TracksOutOfSequence)?;
3734
3735 Ok(ParsedCuesheet {
3736 catalog_number: parsed.catalog_number.unwrap_or_default(),
3737 tracks: parsed.tracks,
3738 })
3739 }
3740}
3741
3742/// An error when trying to parse cue sheet data
3743#[derive(Debug)]
3744#[non_exhaustive]
3745pub enum CuesheetError {
3746 /// CATALOG tag missing catalog number
3747 CatalogMissingNumber,
3748 /// multiple CATALOG numbers found
3749 MultipleCatalogNumber,
3750 /// invalid CATALOG number
3751 InvalidCatalogNumber,
3752 /// Multiple ISRC numbers
3753 MultipleISRC,
3754 /// Invalid ISRC number
3755 InvalidISRC,
3756 /// ISRC number found before TRACK
3757 PrematureISRC,
3758 /// ISRC number after INDEX points
3759 LateISRC,
3760 /// FLAGS seen before TRACK
3761 PrematureFlags,
3762 /// FLAGS seen after INDEX points
3763 LateFlags,
3764 /// ISRC tag missing number
3765 ISRCMissingNumber,
3766 /// Unable to parse TRACK field correctly
3767 InvalidTrack,
3768 /// No tracks in cue sheet
3769 NoTracks,
3770 /// Non-Zero starting INDEX in first TRACK
3771 NonZeroStartingIndex,
3772 /// INDEX point occurs before TRACK
3773 PrematureIndex,
3774 /// Invalid INDEX point in cuesheet
3775 InvalidIndexPoint,
3776 /// No INDEX Points in track
3777 NoIndexPoints,
3778 /// Excessive tracks in cue sheet
3779 ExcessiveTracks,
3780 /// INDEX points in track are out of sequence
3781 IndexPointsOutOfSequence,
3782 /// TRACK points in CUESHEET are out of sequence
3783 TracksOutOfSequence,
3784 /// Lead-out track is not beyond all track indices
3785 ShortLeadOut,
3786 /// INDEX points in lead-out TRACK
3787 IndexPointsInLeadout,
3788 /// Invalid offset for CD-DA CUESHEET
3789 InvalidCDDAOffset,
3790 /// first INDEX of first TRACK doesn't have offset of 0
3791 NonZeroFirstIndex,
3792}
3793
3794impl std::error::Error for CuesheetError {}
3795
3796impl std::fmt::Display for CuesheetError {
3797 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
3798 match self {
3799 Self::CatalogMissingNumber => "CATALOG tag missing number".fmt(f),
3800 Self::MultipleCatalogNumber => "multiple CATALOG numbers found".fmt(f),
3801 Self::InvalidCatalogNumber => "invalid CATALOG number".fmt(f),
3802 Self::MultipleISRC => "multiple ISRC numbers found for track".fmt(f),
3803 Self::InvalidISRC => "invalid ISRC number found".fmt(f),
3804 Self::PrematureISRC => "ISRC number found before TRACK".fmt(f),
3805 Self::LateISRC => "ISRC number found after INDEX points".fmt(f),
3806 Self::PrematureFlags => "FLAGS found before TRACK".fmt(f),
3807 Self::LateFlags => "FLAGS found after INDEX points".fmt(f),
3808 Self::ISRCMissingNumber => "ISRC tag missing number".fmt(f),
3809 Self::InvalidTrack => "invalid TRACK entry".fmt(f),
3810 Self::NoTracks => "no TRACK entries in cue sheet".fmt(f),
3811 Self::NonZeroStartingIndex => {
3812 "first INDEX of first track must have 00:00:00 offset".fmt(f)
3813 }
3814 Self::PrematureIndex => "INDEX found before TRACK".fmt(f),
3815 Self::InvalidIndexPoint => "invalid INDEX entry".fmt(f),
3816 Self::NoIndexPoints => "no INDEX points in track".fmt(f),
3817 Self::ExcessiveTracks => "excessive tracks in CUESHEET".fmt(f),
3818 Self::IndexPointsOutOfSequence => "INDEX points out of sequence".fmt(f),
3819 Self::TracksOutOfSequence => "TRACKS out of sequence".fmt(f),
3820 Self::ShortLeadOut => "lead-out track not beyond final INDEX point".fmt(f),
3821 Self::IndexPointsInLeadout => "INDEX points in lead-out TRACK".fmt(f),
3822 Self::InvalidCDDAOffset => "invalid offset for CD-DA CUESHEET".fmt(f),
3823 Self::NonZeroFirstIndex => "first index of first track has non-zero offset".fmt(f),
3824 }
3825 }
3826}
3827
3828/// A PICTURE metadata block
3829///
3830/// Picture blocks are for embedding artwork
3831/// such as album covers, liner notes, etc.
3832///
3833/// This block may occur multiple times in a FLAC file.
3834///
3835/// | Bits | Field | Meaning |
3836/// |-----:|------:|---------|
3837/// | 32 | `picture_type` | picture type
3838/// | 32 | media type len | media type length, in bytes
3839/// | `media type len`×8 | `media_type` | picture's MIME type
3840/// | 32 | description len | description length, in bytes
3841/// | `description len`×8 | `description` | description of picture, in UTF-8
3842/// | 32 | `width` | width of picture, in pixels
3843/// | 32 | `height`| height of picture, in pixels
3844/// | 32 | `color_depth` | color depth of picture in bits-per-pixel
3845/// | 32 | `colors_used` | for indexed-color pictures, number of colors used
3846/// | 32 | data len | length of picture data, in bytes
3847/// | `data len`×8 | `data` | raw picture data
3848///
3849/// # Example
3850/// ```
3851/// use bitstream_io::{BitReader, BitRead, BigEndian};
3852/// use flac_codec::metadata::{Picture, PictureType};
3853///
3854/// let data: &[u8] = &[
3855/// 0x00, 0x00, 0x00, 0x03, // picture type
3856/// 0x00, 0x00, 0x00, 0x09, // media type len (9 bytes)
3857/// 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67,
3858/// 0x00, 0x00, 0x00, 0x0a, // description len (10 bytes)
3859/// 0x54, 0x65, 0x73, 0x74, 0x20, 0x49, 0x6d, 0x61, 0x67, 0x65,
3860/// 0x00, 0x00, 0x00, 0x10, // width
3861/// 0x00, 0x00, 0x00, 0x09, // height
3862/// 0x00, 0x00, 0x00, 0x18, // color depth
3863/// 0x00, 0x00, 0x00, 0x00, // color count
3864/// 0x00, 0x00, 0x00, 0x5c, // data len (92 bytes)
3865/// 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
3866/// 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
3867/// 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x09,
3868/// 0x08, 0x02, 0x00, 0x00, 0x00, 0xb4, 0x48, 0x3b,
3869/// 0x65, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59,
3870/// 0x73, 0x00, 0x00, 0x2e, 0x23, 0x00, 0x00, 0x2e,
3871/// 0x23, 0x01, 0x78, 0xa5, 0x3f, 0x76, 0x00, 0x00,
3872/// 0x00, 0x0e, 0x49, 0x44, 0x41, 0x54, 0x18, 0xd3,
3873/// 0x63, 0x60, 0x18, 0x05, 0x43, 0x12, 0x00, 0x00,
3874/// 0x01, 0xb9, 0x00, 0x01, 0xed, 0x78, 0x29, 0x25,
3875/// 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
3876/// 0xae, 0x42, 0x60, 0x82,
3877/// ];
3878///
3879/// let mut r = BitReader::endian(data, BigEndian);
3880/// assert_eq!(
3881/// r.parse::<Picture>().unwrap(),
3882/// Picture {
3883/// picture_type: PictureType::FrontCover, // type 3
3884/// media_type: "image/png".to_owned(),
3885/// description: "Test Image".to_owned(),
3886/// width: 0x00_00_00_10, // 16 pixels
3887/// height: 0x00_00_00_09, // 9 pixels
3888/// color_depth: 0x00_00_00_18, // 24 bits-per-pixel
3889/// colors_used: None, // not indexed
3890/// data: vec![
3891/// 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
3892/// 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
3893/// 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x09,
3894/// 0x08, 0x02, 0x00, 0x00, 0x00, 0xb4, 0x48, 0x3b,
3895/// 0x65, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59,
3896/// 0x73, 0x00, 0x00, 0x2e, 0x23, 0x00, 0x00, 0x2e,
3897/// 0x23, 0x01, 0x78, 0xa5, 0x3f, 0x76, 0x00, 0x00,
3898/// 0x00, 0x0e, 0x49, 0x44, 0x41, 0x54, 0x18, 0xd3,
3899/// 0x63, 0x60, 0x18, 0x05, 0x43, 0x12, 0x00, 0x00,
3900/// 0x01, 0xb9, 0x00, 0x01, 0xed, 0x78, 0x29, 0x25,
3901/// 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44,
3902/// 0xae, 0x42, 0x60, 0x82,
3903/// ],
3904/// },
3905/// );
3906/// ```
3907#[derive(Debug, Clone, Eq, PartialEq)]
3908pub struct Picture {
3909 /// The picture type
3910 pub picture_type: PictureType,
3911 /// The media type string as specified by RFC2046
3912 pub media_type: String,
3913 /// The description of the picture
3914 pub description: String,
3915 /// The width of the picture in pixels
3916 pub width: u32,
3917 /// The height of the picture in pixels
3918 pub height: u32,
3919 /// The color depth of the picture in bits per pixel
3920 pub color_depth: u32,
3921 /// For indexed-color pictures, the number of colors used
3922 pub colors_used: Option<NonZero<u32>>,
3923 /// The binary picture data
3924 pub data: Vec<u8>,
3925}
3926
3927block!(Picture, Picture, true);
3928optional_block!(Picture, Picture);
3929
3930impl Picture {
3931 /// Attempt to create a new PICTURE block from raw image data
3932 ///
3933 /// Currently supported image types for this method are:
3934 ///
3935 /// - JPEG
3936 /// - PNG
3937 /// - GIF
3938 ///
3939 /// Any type of image data may be placed in a PICTURE block,
3940 /// but the user may have to use external crates
3941 /// to determine their proper image metrics
3942 /// to build a block from.
3943 ///
3944 /// # Errors
3945 ///
3946 /// Returns an error if some problem occurs reading
3947 /// or identifying the file.
3948 pub fn new<S, V>(
3949 picture_type: PictureType,
3950 description: S,
3951 data: V,
3952 ) -> Result<Self, InvalidPicture>
3953 where
3954 S: Into<String>,
3955 V: Into<Vec<u8>> + AsRef<[u8]>,
3956 {
3957 let metrics = PictureMetrics::try_new(data.as_ref())?;
3958 Ok(Self {
3959 picture_type,
3960 description: description.into(),
3961 data: data.into(),
3962 media_type: metrics.media_type.to_owned(),
3963 width: metrics.width,
3964 height: metrics.height,
3965 color_depth: metrics.color_depth,
3966 colors_used: metrics.colors_used,
3967 })
3968 }
3969
3970 /// Attempt to create new PICTURE block from file on disk
3971 pub fn open<S, P>(
3972 picture_type: PictureType,
3973 description: S,
3974 path: P,
3975 ) -> Result<Self, InvalidPicture>
3976 where
3977 S: Into<String>,
3978 P: AsRef<Path>,
3979 {
3980 std::fs::read(path)
3981 .map_err(InvalidPicture::Io)
3982 .and_then(|data| Self::new(picture_type, description, data))
3983 }
3984}
3985
3986impl FromBitStream for Picture {
3987 type Error = Error;
3988
3989 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Error> {
3990 fn prefixed_field<R: BitRead + ?Sized>(r: &mut R) -> std::io::Result<Vec<u8>> {
3991 let size = r.read_to::<u32>()?;
3992 r.read_to_vec(size.try_into().unwrap())
3993 }
3994
3995 Ok(Self {
3996 picture_type: r.parse()?,
3997 media_type: String::from_utf8(prefixed_field(r)?)?,
3998 description: String::from_utf8(prefixed_field(r)?)?,
3999 width: r.read_to()?,
4000 height: r.read_to()?,
4001 color_depth: r.read_to()?,
4002 colors_used: r.read::<32, _>()?,
4003 data: prefixed_field(r)?,
4004 })
4005 }
4006}
4007
4008impl ToBitStream for Picture {
4009 type Error = Error;
4010
4011 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Error> {
4012 fn prefixed_field<W: BitWrite + ?Sized>(
4013 w: &mut W,
4014 field: &[u8],
4015 error: Error,
4016 ) -> Result<(), Error> {
4017 w.write_from::<u32>(field.len().try_into().map_err(|_| error)?)
4018 .map_err(Error::Io)?;
4019 w.write_bytes(field).map_err(Error::Io)
4020 }
4021
4022 w.build(&self.picture_type)?;
4023 prefixed_field(w, self.media_type.as_bytes(), Error::ExcessiveStringLength)?;
4024 prefixed_field(w, self.description.as_bytes(), Error::ExcessiveStringLength)?;
4025 w.write_from(self.width)?;
4026 w.write_from(self.height)?;
4027 w.write_from(self.color_depth)?;
4028 w.write::<32, _>(self.colors_used)?;
4029 prefixed_field(w, &self.data, Error::ExcessivePictureSize)
4030 }
4031}
4032
4033/// Defined variants of PICTURE type
4034#[derive(Debug, Copy, Clone, Eq, PartialEq)]
4035pub enum PictureType {
4036 /// Other
4037 Other = 0,
4038 /// PNG file icon of 32x32 pixels
4039 Png32x32 = 1,
4040 /// General file icon
4041 GeneralFileIcon = 2,
4042 /// Front cover
4043 FrontCover = 3,
4044 /// Back cover
4045 BackCover = 4,
4046 /// Liner notes page
4047 LinerNotes = 5,
4048 /// Media label (e.g., CD, Vinyl or Cassette label)
4049 MediaLabel = 6,
4050 /// Lead artist, lead performer, or soloist
4051 LeadArtist = 7,
4052 /// Artist or performer
4053 Artist = 8,
4054 /// Conductor
4055 Conductor = 9,
4056 /// Band or orchestra
4057 Band = 10,
4058 /// Composer
4059 Composer = 11,
4060 /// Lyricist or text writer
4061 Lyricist = 12,
4062 /// Recording location
4063 RecordingLocation = 13,
4064 /// During recording
4065 DuringRecording = 14,
4066 /// During performance
4067 DuringPerformance = 15,
4068 /// Movie or video screen capture
4069 ScreenCapture = 16,
4070 /// A bright colored fish
4071 Fish = 17,
4072 /// Illustration
4073 Illustration = 18,
4074 /// Band or artist logotype
4075 BandLogo = 19,
4076 /// Publisher or studio logotype
4077 PublisherLogo = 20,
4078}
4079
4080impl std::fmt::Display for PictureType {
4081 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
4082 match self {
4083 Self::Other => "Other".fmt(f),
4084 Self::Png32x32 => "32×32 PNG Icon".fmt(f),
4085 Self::GeneralFileIcon => "General File Icon".fmt(f),
4086 Self::FrontCover => "Cover (front)".fmt(f),
4087 Self::BackCover => "Cover (back)".fmt(f),
4088 Self::LinerNotes => "Liner Notes".fmt(f),
4089 Self::MediaLabel => "Media Label".fmt(f),
4090 Self::LeadArtist => "Lead Artist".fmt(f),
4091 Self::Artist => "Artist".fmt(f),
4092 Self::Conductor => "Conductor".fmt(f),
4093 Self::Band => "Band or Orchestra".fmt(f),
4094 Self::Composer => "Composer".fmt(f),
4095 Self::Lyricist => "lyricist or Text Writer".fmt(f),
4096 Self::RecordingLocation => "Recording Location".fmt(f),
4097 Self::DuringRecording => "During Recording".fmt(f),
4098 Self::DuringPerformance => "During Performance".fmt(f),
4099 Self::ScreenCapture => "Movie or Video Screen Capture".fmt(f),
4100 Self::Fish => "A Bright Colored Fish".fmt(f),
4101 Self::Illustration => "Illustration".fmt(f),
4102 Self::BandLogo => "Band or Artist Logotype".fmt(f),
4103 Self::PublisherLogo => "Publisher or Studio Logotype".fmt(f),
4104 }
4105 }
4106}
4107
4108impl FromBitStream for PictureType {
4109 type Error = Error;
4110
4111 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Error> {
4112 match r.read_to::<u32>()? {
4113 0 => Ok(Self::Other),
4114 1 => Ok(Self::Png32x32),
4115 2 => Ok(Self::GeneralFileIcon),
4116 3 => Ok(Self::FrontCover),
4117 4 => Ok(Self::BackCover),
4118 5 => Ok(Self::LinerNotes),
4119 6 => Ok(Self::MediaLabel),
4120 7 => Ok(Self::LeadArtist),
4121 8 => Ok(Self::Artist),
4122 9 => Ok(Self::Conductor),
4123 10 => Ok(Self::Band),
4124 11 => Ok(Self::Composer),
4125 12 => Ok(Self::Lyricist),
4126 13 => Ok(Self::RecordingLocation),
4127 14 => Ok(Self::DuringRecording),
4128 15 => Ok(Self::DuringPerformance),
4129 16 => Ok(Self::ScreenCapture),
4130 17 => Ok(Self::Fish),
4131 18 => Ok(Self::Illustration),
4132 19 => Ok(Self::BandLogo),
4133 20 => Ok(Self::PublisherLogo),
4134 _ => Err(Error::InvalidPictureType),
4135 }
4136 }
4137}
4138
4139impl ToBitStream for PictureType {
4140 type Error = std::io::Error;
4141
4142 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
4143 w.write_from::<u32>(match self {
4144 Self::Other => 0,
4145 Self::Png32x32 => 1,
4146 Self::GeneralFileIcon => 2,
4147 Self::FrontCover => 3,
4148 Self::BackCover => 4,
4149 Self::LinerNotes => 5,
4150 Self::MediaLabel => 6,
4151 Self::LeadArtist => 7,
4152 Self::Artist => 8,
4153 Self::Conductor => 9,
4154 Self::Band => 10,
4155 Self::Composer => 11,
4156 Self::Lyricist => 12,
4157 Self::RecordingLocation => 13,
4158 Self::DuringRecording => 14,
4159 Self::DuringPerformance => 15,
4160 Self::ScreenCapture => 16,
4161 Self::Fish => 17,
4162 Self::Illustration => 18,
4163 Self::BandLogo => 19,
4164 Self::PublisherLogo => 20,
4165 })
4166 }
4167}
4168
4169/// An error when trying to identify a picture's metrics
4170#[derive(Debug)]
4171#[non_exhaustive]
4172pub enum InvalidPicture {
4173 /// An I/O Error
4174 Io(std::io::Error),
4175 /// Unsupported Image Format
4176 Unsupported,
4177 /// Invalid PNG File
4178 Png(&'static str),
4179 /// Invalid JPEG File
4180 Jpeg(&'static str),
4181 /// Invalid GIF File
4182 Gif(&'static str),
4183}
4184
4185impl From<std::io::Error> for InvalidPicture {
4186 #[inline]
4187 fn from(err: std::io::Error) -> Self {
4188 Self::Io(err)
4189 }
4190}
4191
4192impl std::error::Error for InvalidPicture {}
4193
4194impl std::fmt::Display for InvalidPicture {
4195 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
4196 match self {
4197 Self::Io(err) => err.fmt(f),
4198 Self::Unsupported => "unsupported image format".fmt(f),
4199 Self::Png(s) => write!(f, "PNG parsing error : {s}"),
4200 Self::Jpeg(s) => write!(f, "JPEG parsing error : {s}"),
4201 Self::Gif(s) => write!(f, "GIF parsing error : {s}"),
4202 }
4203 }
4204}
4205
4206struct PictureMetrics {
4207 media_type: &'static str,
4208 width: u32,
4209 height: u32,
4210 color_depth: u32,
4211 colors_used: Option<NonZero<u32>>,
4212}
4213
4214impl PictureMetrics {
4215 fn try_new(data: &[u8]) -> Result<Self, InvalidPicture> {
4216 if data.starts_with(b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
4217 Self::try_png(data)
4218 } else if data.starts_with(b"\xFF\xD8\xFF") {
4219 Self::try_jpeg(data)
4220 } else if data.starts_with(b"GIF") {
4221 Self::try_gif(data)
4222 } else {
4223 Err(InvalidPicture::Unsupported)
4224 }
4225 }
4226
4227 fn try_png(data: &[u8]) -> Result<Self, InvalidPicture> {
4228 // this is an *extremely* cut-down PNG parser
4229 // that handles just enough of the format to get
4230 // image metadata, but does *not* validate things
4231 // like the block CRC32s
4232
4233 fn plte_colors<R: ByteRead>(mut r: R) -> Result<u32, InvalidPicture> {
4234 loop {
4235 let block_len = r.read::<u32>()?;
4236 match r.read::<[u8; 4]>()?.as_slice() {
4237 b"PLTE" => {
4238 if block_len % 3 == 0 {
4239 break Ok(block_len / 3);
4240 } else {
4241 break Err(InvalidPicture::Png("invalid PLTE length"));
4242 }
4243 }
4244 _ => {
4245 r.skip(block_len)?;
4246 let _crc = r.read::<u32>()?;
4247 }
4248 }
4249 }
4250 }
4251
4252 let mut r = ByteReader::endian(data, BigEndian);
4253 if &r.read::<[u8; 8]>()? != b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" {
4254 return Err(InvalidPicture::Png("not a PNG image"));
4255 }
4256
4257 // IHDR chunk must be first
4258 if r.read::<u32>()? != 0x0d {
4259 return Err(InvalidPicture::Png("invalid IHDR length"));
4260 }
4261 if &r.read::<[u8; 4]>()? != b"IHDR" {
4262 return Err(InvalidPicture::Png("IHDR chunk not first"));
4263 }
4264 let width = r.read()?;
4265 let height = r.read()?;
4266 let bit_depth = r.read::<u8>()?;
4267 let color_type = r.read::<u8>()?;
4268 let _compression_method = r.read::<u8>()?;
4269 let _filter_method = r.read::<u8>()?;
4270 let _interlace_method = r.read::<u8>()?;
4271 let _crc = r.read::<u32>()?;
4272
4273 let (color_depth, colors_used) = match color_type {
4274 0 => (bit_depth.into(), None), // grayscale
4275 2 => ((bit_depth * 3).into(), None), // RGB
4276 3 => (0, NonZero::new(plte_colors(r)?)), // palette
4277 4 => ((bit_depth * 2).into(), None), // grayscale + alpha
4278 6 => ((bit_depth * 4).into(), None), // RGB + alpha
4279 _ => return Err(InvalidPicture::Png("invalid color type")),
4280 };
4281
4282 Ok(Self {
4283 media_type: "image/png",
4284 width,
4285 height,
4286 color_depth,
4287 colors_used,
4288 })
4289 }
4290
4291 fn try_jpeg(data: &[u8]) -> Result<Self, InvalidPicture> {
4292 let mut r = ByteReader::endian(data, BigEndian);
4293
4294 if r.read::<u8>()? != 0xFF || r.read::<u8>()? != 0xD8 {
4295 return Err(InvalidPicture::Jpeg("invalid JPEG marker"));
4296 }
4297
4298 loop {
4299 if r.read::<u8>()? != 0xFF {
4300 break Err(InvalidPicture::Jpeg("invalid JPEG marker"));
4301 }
4302 match r.read::<u8>()? {
4303 0xC0 | 0xC1 | 0xC2 | 0xC3 | 0xC5 | 0xC6 | 0xC7 | 0xC9 | 0xCA | 0xCB | 0xCD
4304 | 0xCE | 0xCF => {
4305 let _len = r.read::<u16>()?;
4306 let data_precision = r.read::<u8>()?;
4307 let height = r.read::<u16>()?;
4308 let width = r.read::<u16>()?;
4309 let components = r.read::<u8>()?;
4310 break Ok(Self {
4311 media_type: "image/jpeg",
4312 width: width.into(),
4313 height: height.into(),
4314 color_depth: (data_precision * components).into(),
4315 colors_used: None,
4316 });
4317 }
4318 _ => {
4319 let segment_length = r
4320 .read::<u16>()?
4321 .checked_sub(2)
4322 .ok_or(InvalidPicture::Jpeg("invalid segment length"))?;
4323 r.skip(segment_length.into())?;
4324 }
4325 }
4326 }
4327 }
4328
4329 fn try_gif(data: &[u8]) -> Result<Self, InvalidPicture> {
4330 let mut r = BitReader::endian(data, LittleEndian);
4331
4332 if &r.read_to::<[u8; 3]>()? != b"GIF" {
4333 return Err(InvalidPicture::Gif("invalid GIF signature"));
4334 }
4335
4336 r.skip(3 * 8)?; // ignore version bytes
4337
4338 Ok(Self {
4339 media_type: "image/gif",
4340 width: r.read::<16, _>()?,
4341 height: r.read::<16, _>()?,
4342 colors_used: NonZero::new(1 << (r.read::<3, u32>()? + 1)),
4343 color_depth: 0,
4344 })
4345 }
4346}
4347
4348/// A collection of metadata blocks
4349///
4350/// This collection enforces the restriction that FLAC files
4351/// must always contain a STREAMINFO metadata block
4352/// and that block must always be first in the file.
4353///
4354/// Because it is required, that block may be retrieved
4355/// unconditionally from this collection, while all others
4356/// are optional and may appear in any order.
4357#[derive(Clone, Debug)]
4358pub struct BlockList {
4359 streaminfo: Streaminfo,
4360 blocks: Vec<private::OptionalBlock>,
4361}
4362
4363impl BlockList {
4364 /// Creates `BlockList` from initial STREAMINFO
4365 pub fn new(streaminfo: Streaminfo) -> Self {
4366 Self {
4367 streaminfo,
4368 blocks: Vec::default(),
4369 }
4370 }
4371
4372 /// Reads `BlockList` from the given reader
4373 ///
4374 /// This assumes the reader is rewound to the
4375 /// beginning of the file.
4376 ///
4377 /// Because this may perform many small reads,
4378 /// using a buffered reader is preferred when
4379 /// reading from a raw file.
4380 ///
4381 /// # Errors
4382 ///
4383 /// Returns any error reading or parsing metadata blocks
4384 pub fn read<R: std::io::Read>(r: R) -> Result<Self, Error> {
4385 // TODO - change this to flatten once that stabilizes
4386 read_blocks(r).collect::<Result<Result<Self, _>, _>>()?
4387 }
4388
4389 /// Reads `BlockList` from the given file path
4390 ///
4391 /// # Errors
4392 ///
4393 /// Returns any error reading or parsing metadata blocks
4394 pub fn open<P: AsRef<Path>>(p: P) -> Result<Self, Error> {
4395 File::open(p.as_ref())
4396 .map(BufReader::new)
4397 .map_err(Error::Io)
4398 .and_then(BlockList::read)
4399 }
4400
4401 /// Returns reference to our STREAMINFO metadata block
4402 pub fn streaminfo(&self) -> &Streaminfo {
4403 &self.streaminfo
4404 }
4405
4406 /// Returns exclusive reference to our STREAMINFO metadata block
4407 ///
4408 /// Care must be taken when modifying the STREAMINFO, or
4409 /// one's file could be rendered unplayable.
4410 pub fn streaminfo_mut(&mut self) -> &mut Streaminfo {
4411 &mut self.streaminfo
4412 }
4413
4414 /// Iterates over all the metadata blocks
4415 pub fn blocks(&self) -> impl Iterator<Item = BlockRef<'_>> {
4416 std::iter::once(self.streaminfo.as_block_ref())
4417 .chain(self.blocks.iter().map(|b| b.as_block_ref()))
4418 }
4419
4420 /// Inserts new optional metadata block
4421 ///
4422 /// If the block may only occur once in the stream
4423 /// (such as the SEEKTABLE), any existing block of
4424 /// the same type removed and extracted first.
4425 pub fn insert<B: OptionalMetadataBlock>(&mut self, block: B) -> Option<B> {
4426 if B::MULTIPLE {
4427 self.blocks.push(block.into());
4428 None
4429 } else {
4430 match self
4431 .blocks
4432 .iter_mut()
4433 .find_map(|b| B::try_from_opt_block_mut(b).ok())
4434 {
4435 Some(b) => Some(std::mem::replace(b, block)),
4436 None => {
4437 self.blocks.push(block.into());
4438 None
4439 }
4440 }
4441 }
4442 }
4443
4444 /// Gets reference to metadata block, if present
4445 ///
4446 /// If the block type occurs multiple times,
4447 /// this returns the first instance.
4448 pub fn get<B: OptionalMetadataBlock>(&self) -> Option<&B> {
4449 self.blocks
4450 .iter()
4451 .find_map(|b| B::try_from_opt_block(b).ok())
4452 }
4453
4454 /// Gets mutable reference to metadata block, if present
4455 ///
4456 /// If the block type occurs multiple times,
4457 /// this returns the first instance.
4458 pub fn get_mut<B: OptionalMetadataBlock>(&mut self) -> Option<&mut B> {
4459 self.blocks
4460 .iter_mut()
4461 .find_map(|b| B::try_from_opt_block_mut(b).ok())
4462 }
4463
4464 /// Gets mutable references to a pair of metadata blocks
4465 ///
4466 /// If either block type occurs multiple times,
4467 /// this returns the first instance.
4468 pub fn get_pair_mut<B, C>(&mut self) -> (Option<&mut B>, Option<&mut C>)
4469 where
4470 B: OptionalMetadataBlock,
4471 C: OptionalMetadataBlock,
4472 {
4473 use std::ops::ControlFlow;
4474
4475 match self
4476 .blocks
4477 .iter_mut()
4478 .try_fold((None, None), |acc, block| match acc {
4479 (first @ None, second @ None) => {
4480 ControlFlow::Continue(match B::try_from_opt_block_mut(block) {
4481 Ok(first) => (Some(first), second),
4482 Err(block) => (first, C::try_from_opt_block_mut(block).ok()),
4483 })
4484 }
4485 (first @ Some(_), None) => {
4486 ControlFlow::Continue((first, C::try_from_opt_block_mut(block).ok()))
4487 }
4488 (None, second @ Some(_)) => {
4489 ControlFlow::Continue((B::try_from_opt_block_mut(block).ok(), second))
4490 }
4491 pair @ (Some(_), Some(_)) => ControlFlow::Break(pair),
4492 }) {
4493 ControlFlow::Break(p) | ControlFlow::Continue(p) => p,
4494 }
4495 }
4496
4497 /// Gets references to all metadata blocks of the given type
4498 pub fn get_all<'b, B: OptionalMetadataBlock + 'b>(&'b self) -> impl Iterator<Item = &'b B> {
4499 self.blocks
4500 .iter()
4501 .filter_map(|b| B::try_from_opt_block(b).ok())
4502 }
4503
4504 /// Gets exclusive references to all metadata blocks of the given type
4505 pub fn get_all_mut<'b, B: OptionalMetadataBlock + 'b>(
4506 &'b mut self,
4507 ) -> impl Iterator<Item = &'b mut B> {
4508 self.blocks
4509 .iter_mut()
4510 .filter_map(|b| B::try_from_opt_block_mut(b).ok())
4511 }
4512
4513 /// Returns `true` if block exists in list
4514 pub fn has<B: OptionalMetadataBlock>(&self) -> bool {
4515 self.get::<B>().is_some()
4516 }
4517
4518 /// Removes all instances of the given metadata block type
4519 pub fn remove<B: OptionalMetadataBlock>(&mut self) {
4520 self.blocks.retain(|b| b.block_type() != B::TYPE)
4521 }
4522
4523 /// Removes and returns all instances of the given block type
4524 pub fn extract<B: OptionalMetadataBlock>(&mut self) -> impl Iterator<Item = B> {
4525 self.blocks
4526 .extract_if(.., |block| block.block_type() == B::TYPE)
4527 .filter_map(|b| B::try_from(b).ok())
4528 }
4529
4530 /// Updates first instance of the given block, creating it if necessary
4531 ///
4532 /// # Example
4533 ///
4534 /// ```
4535 /// use flac_codec::metadata::{BlockList, Streaminfo, VorbisComment};
4536 /// use flac_codec::metadata::fields::ARTIST;
4537 ///
4538 /// // build a BlockList with a dummy Streaminfo
4539 /// let mut blocklist = BlockList::new(
4540 /// Streaminfo {
4541 /// minimum_block_size: 0,
4542 /// maximum_block_size: 0,
4543 /// minimum_frame_size: None,
4544 /// maximum_frame_size: None,
4545 /// sample_rate: 44100,
4546 /// channels: 1u8.try_into().unwrap(),
4547 /// bits_per_sample: 16u32.try_into().unwrap(),
4548 /// total_samples: None,
4549 /// md5: None,
4550 /// },
4551 /// );
4552 ///
4553 /// // the block starts out with no comment
4554 /// assert!(blocklist.get::<VorbisComment>().is_none());
4555 ///
4556 /// // update Vorbis Comment with artist field,
4557 /// // which adds a new block to the list
4558 /// blocklist.update::<VorbisComment>(
4559 /// |vc| vc.insert(ARTIST, "Artist 1")
4560 /// );
4561 /// assert!(blocklist.get::<VorbisComment>().is_some());
4562 ///
4563 /// // updating Vorbis Comment again reuses that same block
4564 /// blocklist.update::<VorbisComment>(
4565 /// |vc| vc.insert(ARTIST, "Artist 2")
4566 /// );
4567 ///
4568 /// // the block now has two entries
4569 /// assert_eq!(
4570 /// blocklist.get::<VorbisComment>()
4571 /// .unwrap()
4572 /// .all(ARTIST).collect::<Vec<_>>(),
4573 /// vec!["Artist 1", "Artist 2"],
4574 /// );
4575 /// ```
4576 pub fn update<B>(&mut self, f: impl FnOnce(&mut B))
4577 where
4578 B: OptionalMetadataBlock + Default,
4579 {
4580 match self.get_mut() {
4581 Some(block) => f(block),
4582 None => {
4583 let mut b = B::default();
4584 f(&mut b);
4585 self.blocks.push(b.into());
4586 }
4587 }
4588 }
4589
4590 /// Sorts optional metadata blocks by block type
4591 ///
4592 /// The function converts the type to some key which is
4593 /// used for ordering blocks from smallest to largest.
4594 ///
4595 /// The order of blocks of the same type is preserved.
4596 /// This is an important consideration for APPLICATION
4597 /// metadata blocks, which may contain foreign metadata
4598 /// chunks that must be re-applied in the same order.
4599 ///
4600 /// # Example
4601 ///
4602 /// ```
4603 /// use flac_codec::metadata::{
4604 /// BlockList, Streaminfo, Application, Padding, AsBlockRef,
4605 /// OptionalBlockType,
4606 /// };
4607 ///
4608 /// // build a BlockList with a dummy Streaminfo
4609 /// let streaminfo = Streaminfo {
4610 /// minimum_block_size: 0,
4611 /// maximum_block_size: 0,
4612 /// minimum_frame_size: None,
4613 /// maximum_frame_size: None,
4614 /// sample_rate: 44100,
4615 /// channels: 1u8.try_into().unwrap(),
4616 /// bits_per_sample: 16u32.try_into().unwrap(),
4617 /// total_samples: None,
4618 /// md5: None,
4619 /// };
4620 ///
4621 /// let mut blocklist = BlockList::new(streaminfo.clone());
4622 ///
4623 /// // add some blocks
4624 /// let application_1 = Application {
4625 /// id: 0x1234,
4626 /// data: vec![0x01, 0x02, 0x03, 0x04],
4627 /// };
4628 /// blocklist.insert(application_1.clone());
4629 ///
4630 /// let padding = Padding {
4631 /// size: 10u32.try_into().unwrap(),
4632 /// };
4633 /// blocklist.insert(padding.clone());
4634 ///
4635 /// let application_2 = Application {
4636 /// id: 0x6789,
4637 /// data: vec![0x06, 0x07, 0x08, 0x09],
4638 /// };
4639 /// blocklist.insert(application_2.clone());
4640 ///
4641 /// // check their inital order
4642 /// let mut iter = blocklist.blocks();
4643 /// assert_eq!(iter.next(), Some(streaminfo.as_block_ref()));
4644 /// assert_eq!(iter.next(), Some(application_1.as_block_ref()));
4645 /// assert_eq!(iter.next(), Some(padding.as_block_ref()));
4646 /// assert_eq!(iter.next(), Some(application_2.as_block_ref()));
4647 /// assert_eq!(iter.next(), None);
4648 /// drop(iter);
4649 ///
4650 /// // sort the blocks to put padding last
4651 /// blocklist.sort_by(|t| match t {
4652 /// OptionalBlockType::Application => 0,
4653 /// OptionalBlockType::SeekTable => 1,
4654 /// OptionalBlockType::VorbisComment => 2,
4655 /// OptionalBlockType::Cuesheet => 3,
4656 /// OptionalBlockType::Picture => 4,
4657 /// OptionalBlockType::Padding => 5,
4658 /// });
4659 ///
4660 /// // re-check their new order
4661 /// let mut iter = blocklist.blocks();
4662 /// assert_eq!(iter.next(), Some(streaminfo.as_block_ref()));
4663 /// assert_eq!(iter.next(), Some(application_1.as_block_ref()));
4664 /// assert_eq!(iter.next(), Some(application_2.as_block_ref()));
4665 /// assert_eq!(iter.next(), Some(padding.as_block_ref()));
4666 /// assert_eq!(iter.next(), None);
4667 /// ```
4668 pub fn sort_by<O: Ord>(&mut self, f: impl Fn(OptionalBlockType) -> O) {
4669 self.blocks
4670 .sort_by_key(|block| f(block.optional_block_type()));
4671 }
4672}
4673
4674impl Metadata for BlockList {
4675 fn channel_count(&self) -> u8 {
4676 self.streaminfo.channels.get()
4677 }
4678
4679 fn channel_mask(&self) -> ChannelMask {
4680 use fields::CHANNEL_MASK;
4681
4682 self.get::<VorbisComment>()
4683 .and_then(|c| c.get(CHANNEL_MASK).and_then(|m| m.parse().ok()))
4684 .unwrap_or(ChannelMask::from_channels(self.channel_count()))
4685 }
4686
4687 fn sample_rate(&self) -> u32 {
4688 self.streaminfo.sample_rate
4689 }
4690
4691 fn bits_per_sample(&self) -> u32 {
4692 self.streaminfo.bits_per_sample.into()
4693 }
4694
4695 fn total_samples(&self) -> Option<u64> {
4696 self.streaminfo.total_samples.map(|s| s.get())
4697 }
4698
4699 fn md5(&self) -> Option<&[u8; 16]> {
4700 self.streaminfo.md5.as_ref()
4701 }
4702}
4703
4704impl FromIterator<Block> for Result<BlockList, Error> {
4705 fn from_iter<T: IntoIterator<Item = Block>>(iter: T) -> Self {
4706 let mut iter = iter.into_iter();
4707
4708 let mut list = match iter.next() {
4709 Some(Block::Streaminfo(streaminfo)) => BlockList::new(streaminfo),
4710 Some(_) | None => return Err(Error::MissingStreaminfo),
4711 };
4712
4713 for block in iter {
4714 match block {
4715 Block::Streaminfo(_) => return Err(Error::MultipleStreaminfo),
4716 Block::Padding(p) => {
4717 list.insert(p);
4718 }
4719 Block::Application(p) => {
4720 list.insert(p);
4721 }
4722 Block::SeekTable(p) => {
4723 list.insert(p);
4724 }
4725 Block::VorbisComment(p) => {
4726 list.insert(p);
4727 }
4728 Block::Cuesheet(p) => {
4729 list.insert(p);
4730 }
4731 Block::Picture(p) => {
4732 list.insert(p);
4733 }
4734 }
4735 }
4736
4737 Ok(list)
4738 }
4739}
4740
4741impl IntoIterator for BlockList {
4742 type Item = Block;
4743 type IntoIter = Box<dyn Iterator<Item = Block>>;
4744
4745 fn into_iter(self) -> Self::IntoIter {
4746 Box::new(
4747 std::iter::once(self.streaminfo.into())
4748 .chain(self.blocks.into_iter().map(|b| b.into())),
4749 )
4750 }
4751}
4752
4753impl<B: OptionalMetadataBlock> Extend<B> for BlockList {
4754 fn extend<I>(&mut self, iter: I)
4755 where
4756 I: IntoIterator<Item = B>,
4757 {
4758 for block in iter {
4759 self.insert(block);
4760 }
4761 }
4762}
4763
4764/// A type of FLAC metadata block which is not required
4765///
4766/// The STREAMINFO block is required. All others are optional.
4767pub trait OptionalMetadataBlock: MetadataBlock + private::OptionalMetadataBlock {
4768 /// Our optional block type
4769 const OPTIONAL_TYPE: OptionalBlockType;
4770}
4771
4772/// A type of optional FLAC metadata block which is portable
4773///
4774/// These are blocks which may be safely ported from one
4775/// encoding of a FLAC file to another.
4776///
4777/// All blocks except STREAMINFO and SEEKTABLE are considered portable.
4778pub trait PortableMetadataBlock: OptionalMetadataBlock {}
4779
4780impl PortableMetadataBlock for Padding {}
4781impl PortableMetadataBlock for Application {}
4782impl PortableMetadataBlock for VorbisComment {}
4783impl PortableMetadataBlock for Cuesheet {}
4784impl PortableMetadataBlock for Picture {}
4785
4786mod private {
4787 use super::{
4788 Application, AsBlockRef, Block, BlockRef, BlockType, Cuesheet, OptionalBlockType, Padding,
4789 Picture, SeekTable, Streaminfo, VorbisComment,
4790 };
4791
4792 #[derive(Clone, Debug)]
4793 pub enum OptionalBlock {
4794 Padding(Padding),
4795 Application(Application),
4796 SeekTable(SeekTable),
4797 VorbisComment(VorbisComment),
4798 Cuesheet(Cuesheet),
4799 Picture(Picture),
4800 }
4801
4802 impl OptionalBlock {
4803 pub fn block_type(&self) -> BlockType {
4804 match self {
4805 Self::Padding(_) => BlockType::Padding,
4806 Self::Application(_) => BlockType::Application,
4807 Self::SeekTable(_) => BlockType::SeekTable,
4808 Self::VorbisComment(_) => BlockType::VorbisComment,
4809 Self::Cuesheet(_) => BlockType::Cuesheet,
4810 Self::Picture(_) => BlockType::Picture,
4811 }
4812 }
4813
4814 pub fn optional_block_type(&self) -> OptionalBlockType {
4815 match self {
4816 Self::Padding(_) => OptionalBlockType::Padding,
4817 Self::Application(_) => OptionalBlockType::Application,
4818 Self::SeekTable(_) => OptionalBlockType::SeekTable,
4819 Self::VorbisComment(_) => OptionalBlockType::VorbisComment,
4820 Self::Cuesheet(_) => OptionalBlockType::Cuesheet,
4821 Self::Picture(_) => OptionalBlockType::Picture,
4822 }
4823 }
4824 }
4825
4826 impl From<OptionalBlock> for Block {
4827 fn from(block: OptionalBlock) -> Block {
4828 match block {
4829 OptionalBlock::Padding(p) => Block::Padding(p),
4830 OptionalBlock::Application(a) => Block::Application(a),
4831 OptionalBlock::SeekTable(s) => Block::SeekTable(s),
4832 OptionalBlock::VorbisComment(v) => Block::VorbisComment(v),
4833 OptionalBlock::Cuesheet(c) => Block::Cuesheet(c),
4834 OptionalBlock::Picture(p) => Block::Picture(p),
4835 }
4836 }
4837 }
4838
4839 impl TryFrom<Block> for OptionalBlock {
4840 type Error = Streaminfo;
4841
4842 fn try_from(block: Block) -> Result<Self, Streaminfo> {
4843 match block {
4844 Block::Streaminfo(s) => Err(s),
4845 Block::Padding(p) => Ok(OptionalBlock::Padding(p)),
4846 Block::Application(a) => Ok(OptionalBlock::Application(a)),
4847 Block::SeekTable(s) => Ok(OptionalBlock::SeekTable(s)),
4848 Block::VorbisComment(v) => Ok(OptionalBlock::VorbisComment(v)),
4849 Block::Cuesheet(c) => Ok(OptionalBlock::Cuesheet(c)),
4850 Block::Picture(p) => Ok(OptionalBlock::Picture(p)),
4851 }
4852 }
4853 }
4854
4855 impl AsBlockRef for OptionalBlock {
4856 fn as_block_ref(&self) -> BlockRef<'_> {
4857 match self {
4858 Self::Padding(p) => BlockRef::Padding(p),
4859 Self::Application(a) => BlockRef::Application(a),
4860 Self::SeekTable(s) => BlockRef::SeekTable(s),
4861 Self::VorbisComment(v) => BlockRef::VorbisComment(v),
4862 Self::Cuesheet(v) => BlockRef::Cuesheet(v),
4863 Self::Picture(p) => BlockRef::Picture(p),
4864 }
4865 }
4866 }
4867
4868 pub trait OptionalMetadataBlock: Into<OptionalBlock> + TryFrom<OptionalBlock> {
4869 fn try_from_opt_block(block: &OptionalBlock) -> Result<&Self, &OptionalBlock>;
4870
4871 fn try_from_opt_block_mut(
4872 block: &mut OptionalBlock,
4873 ) -> Result<&mut Self, &mut OptionalBlock>;
4874 }
4875}
4876
4877/// The channel mask
4878///
4879/// This field is used to communicate that the channels
4880/// in the file differ from FLAC's default channel assignment
4881/// definitions.
4882///
4883/// It is generally used for multi-channel audio
4884/// and stored within the [`VorbisComment`] metadata block
4885/// as the [`fields::CHANNEL_MASK`] field.
4886///
4887/// # Example
4888///
4889/// ```
4890/// use flac_codec::metadata::{ChannelMask, Channel};
4891///
4892/// let mask = "0x003F".parse::<ChannelMask>().unwrap();
4893///
4894/// let mut channels = mask.channels();
4895/// assert_eq!(channels.next(), Some(Channel::FrontLeft));
4896/// assert_eq!(channels.next(), Some(Channel::FrontRight));
4897/// assert_eq!(channels.next(), Some(Channel::FrontCenter));
4898/// assert_eq!(channels.next(), Some(Channel::Lfe));
4899/// assert_eq!(channels.next(), Some(Channel::BackLeft));
4900/// assert_eq!(channels.next(), Some(Channel::BackRight));
4901/// assert_eq!(channels.next(), None);
4902/// ```
4903#[derive(Copy, Clone, Debug, Default)]
4904pub struct ChannelMask {
4905 mask: u32,
4906}
4907
4908impl ChannelMask {
4909 /// Iterates over all the mask's defined channels
4910 pub fn channels(&self) -> impl Iterator<Item = Channel> {
4911 [
4912 Channel::FrontLeft,
4913 Channel::FrontRight,
4914 Channel::FrontCenter,
4915 Channel::Lfe,
4916 Channel::BackLeft,
4917 Channel::BackRight,
4918 Channel::FrontLeftOfCenter,
4919 Channel::FrontRightOfCenter,
4920 Channel::BackCenter,
4921 Channel::SideLeft,
4922 Channel::SideRight,
4923 Channel::TopCenter,
4924 Channel::TopFrontLeft,
4925 Channel::TopFrontCenter,
4926 Channel::TopFrontRight,
4927 Channel::TopRearLeft,
4928 Channel::TopRearCenter,
4929 Channel::TopRearRight,
4930 ]
4931 .into_iter()
4932 .filter(|channel| (*channel as u32 & self.mask) != 0)
4933 }
4934
4935 fn from_channels(channels: u8) -> Self {
4936 match channels {
4937 1 => Self {
4938 mask: Channel::FrontCenter as u32,
4939 },
4940 2 => Self {
4941 mask: Channel::FrontLeft as u32 | Channel::FrontRight as u32,
4942 },
4943 3 => Self {
4944 mask: Channel::FrontLeft as u32
4945 | Channel::FrontRight as u32
4946 | Channel::FrontCenter as u32,
4947 },
4948 4 => Self {
4949 mask: Channel::FrontLeft as u32
4950 | Channel::FrontRight as u32
4951 | Channel::BackLeft as u32
4952 | Channel::BackRight as u32,
4953 },
4954 5 => Self {
4955 mask: Channel::FrontLeft as u32
4956 | Channel::FrontRight as u32
4957 | Channel::FrontCenter as u32
4958 | Channel::SideLeft as u32
4959 | Channel::SideRight as u32,
4960 },
4961 6 => Self {
4962 mask: Channel::FrontLeft as u32
4963 | Channel::FrontRight as u32
4964 | Channel::FrontCenter as u32
4965 | Channel::Lfe as u32
4966 | Channel::SideLeft as u32
4967 | Channel::SideRight as u32,
4968 },
4969 7 => Self {
4970 mask: Channel::FrontLeft as u32
4971 | Channel::FrontRight as u32
4972 | Channel::FrontCenter as u32
4973 | Channel::Lfe as u32
4974 | Channel::BackCenter as u32
4975 | Channel::SideLeft as u32
4976 | Channel::SideRight as u32,
4977 },
4978 8 => Self {
4979 mask: Channel::FrontLeft as u32
4980 | Channel::FrontRight as u32
4981 | Channel::FrontCenter as u32
4982 | Channel::Lfe as u32
4983 | Channel::BackLeft as u32
4984 | Channel::BackRight as u32
4985 | Channel::SideLeft as u32
4986 | Channel::SideRight as u32,
4987 },
4988 // FLAC files are limited to 1-8 channels
4989 _ => panic!("undefined channel count"),
4990 }
4991 }
4992}
4993
4994impl std::str::FromStr for ChannelMask {
4995 type Err = ();
4996
4997 fn from_str(s: &str) -> Result<Self, Self::Err> {
4998 match s.split_once('x').ok_or(())? {
4999 ("0", hex) => u32::from_str_radix(hex, 16)
5000 .map(|mask| ChannelMask { mask })
5001 .map_err(|_| ()),
5002 _ => Err(()),
5003 }
5004 }
5005}
5006
5007impl std::fmt::Display for ChannelMask {
5008 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
5009 write!(f, "0x{:04x}", self.mask)
5010 }
5011}
5012
5013impl From<ChannelMask> for u32 {
5014 fn from(mask: ChannelMask) -> u32 {
5015 mask.mask
5016 }
5017}
5018
5019impl From<u32> for ChannelMask {
5020 fn from(mask: u32) -> ChannelMask {
5021 ChannelMask { mask }
5022 }
5023}
5024
5025/// An individual channel mask channel
5026#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
5027pub enum Channel {
5028 /// Front left channel
5029 FrontLeft = 0b1,
5030
5031 /// Front right channel
5032 FrontRight = 0b10,
5033
5034 /// Front center channel
5035 FrontCenter = 0b100,
5036
5037 /// Low-frequency effects (LFE) channel
5038 Lfe = 0b1000,
5039
5040 /// Back left channel
5041 BackLeft = 0b10000,
5042
5043 /// Back right channel
5044 BackRight = 0b100000,
5045
5046 /// Front left of center channel
5047 FrontLeftOfCenter = 0b1000000,
5048
5049 /// Front right of center channel
5050 FrontRightOfCenter = 0b10000000,
5051
5052 /// Back center channel
5053 BackCenter = 0b100000000,
5054
5055 /// Side left channel
5056 SideLeft = 0b1000000000,
5057
5058 /// Side right channel
5059 SideRight = 0b10000000000,
5060
5061 /// Top center channel
5062 TopCenter = 0b100000000000,
5063
5064 /// Top front left channel
5065 TopFrontLeft = 0b1000000000000,
5066
5067 /// Top front center channel
5068 TopFrontCenter = 0b10000000000000,
5069
5070 /// Top front right channel
5071 TopFrontRight = 0b100000000000000,
5072
5073 /// Top rear left channel
5074 TopRearLeft = 0b1000000000000000,
5075
5076 /// Top rear center channel
5077 TopRearCenter = 0b10000000000000000,
5078
5079 /// Top rear right channel
5080 TopRearRight = 0b100000000000000000,
5081}
5082
5083impl std::fmt::Display for Channel {
5084 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
5085 match self {
5086 Self::FrontLeft => "front left".fmt(f),
5087 Self::FrontRight => "front right".fmt(f),
5088 Self::FrontCenter => "front center".fmt(f),
5089 Self::Lfe => "LFE".fmt(f),
5090 Self::BackLeft => "back left".fmt(f),
5091 Self::BackRight => "back right".fmt(f),
5092 Self::FrontLeftOfCenter => "front left of center".fmt(f),
5093 Self::FrontRightOfCenter => "front right of center".fmt(f),
5094 Self::BackCenter => "back center".fmt(f),
5095 Self::SideLeft => "side left".fmt(f),
5096 Self::SideRight => "side right".fmt(f),
5097 Self::TopCenter => "top center".fmt(f),
5098 Self::TopFrontLeft => "top front left".fmt(f),
5099 Self::TopFrontCenter => "top front center".fmt(f),
5100 Self::TopFrontRight => "top front right".fmt(f),
5101 Self::TopRearLeft => "top rear left".fmt(f),
5102 Self::TopRearCenter => "top rear center".fmt(f),
5103 Self::TopRearRight => "top rear right".fmt(f),
5104 }
5105 }
5106}