From 1c45ce71d7d1d91fcc19e05033dca5baeae3eb70 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Wed, 28 Dec 2022 18:39:45 -0800 Subject: [PATCH 1/6] bump: copyright --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1433a5f..85d0d88 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ default-features = false ## License -Copyright © 2022 [Blobfolio, LLC](https://blobfolio.com) <hello@blobfolio.com> +Copyright © 2023 [Blobfolio, LLC](https://blobfolio.com) <hello@blobfolio.com> This work is free. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2. From 4e3a7ddc9254eef86173dd5f489a6ed89585be8b Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Sat, 31 Dec 2022 22:50:24 -0800 Subject: [PATCH 2/6] misc: require leadin >= 150 --- src/error.rs | 6 ++++++ src/lib.rs | 17 ++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/error.rs b/src/error.rs index 76ca2b7..6e3bb9a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -32,6 +32,11 @@ pub enum TocError { /// parsed). Checksums, + /// # Leadin Too Small. + /// + /// Audio CDs require a leadin of at least `150`. + LeadinSize, + /// # No Audio. /// /// At least one audio track is required for a table of contents. @@ -73,6 +78,7 @@ impl fmt::Display for TocError { Self::CDDASampleCount => f.write_str("Invalid CDDA sample count."), Self::CDTOCChars => f.write_str("Invalid character(s), expecting only 0-9, A-F, +, and (rarely) X."), Self::Checksums => f.write_str("Unable to parse checksums."), + Self::LeadinSize => f.write_str("Leadin must be at least 150."), Self::NoAudio => f.write_str("At least one audio track is required."), Self::NoChecksums => f.write_str("No checksums were present."), Self::SectorCount(expected, found) => write!( diff --git a/src/lib.rs b/src/lib.rs index fa7d56b..47e1841 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -238,7 +238,8 @@ impl Toc { /// /// This will return an error if the tag value is improperly formatted, the /// audio track count is outside `1..=99`, there are too many or too few - /// sectors, or the sectors are ordered incorrectly. + /// sectors, the leadin is less than `150`, or the sectors are ordered + /// incorrectly. pub fn from_cdtoc(src: S) -> Result where S: AsRef { let (audio, data, leadout) = parse_cdtoc_metadata(src.as_ref())?; @@ -265,12 +266,19 @@ impl Toc { /// ).unwrap(); /// /// assert_eq!(toc.to_string(), "4+96+2D2B+6256+B327+D84A"); + /// + /// // Sanity matters; the leadin, for example, can't be less than 150. + /// assert!(Toc::from_parts( + /// vec![0, 10525], + /// None, + /// 15000, + /// ).is_err()); /// ``` /// /// ## Errors /// - /// This will return an error if the audio track count is outside `1..=99` - /// or the sectors are in the wrong order. + /// This will return an error if the audio track count is outside `1..=99`, + /// the leadin is less than `150`, or the sectors are in the wrong order. pub fn from_parts(audio: Vec, data: Option, leadout: u32) -> Result { // Check length. @@ -278,6 +286,9 @@ impl Toc { if 0 == audio_len { return Err(TocError::NoAudio); } if 99 < audio_len { return Err(TocError::TrackCount); } + // Audio leadin must be at least 150. + if audio[0] < 150 { return Err(TocError::LeadinSize); } + // Audio is out of order? if (1 < audio_len && audio.windows(2).any(|pair| pair[1] <= pair[0])) || From f36970f9bd8e61936d6731ea7c2d3e7b6e474813 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Sun, 1 Jan 2023 21:36:17 -0800 Subject: [PATCH 3/6] docs --- src/lib.rs | 3 +++ src/time.rs | 12 ++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 47e1841..52ebe2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -320,6 +320,9 @@ impl Toc { /// quite-right CDTOC metadata tag value, such as one that accidentally /// included the data session in its leading track count or ordered the /// sectors of a data-audio CD sequentially. + /// + /// ## Examples + /// /// ``` /// use cdtoc::{Toc, TocKind}; /// diff --git a/src/time.rs b/src/time.rs index 1d1bf82..7939cfb 100644 --- a/src/time.rs +++ b/src/time.rs @@ -174,11 +174,15 @@ impl Sum for Duration { impl Duration { /// # From CDDA Samples. /// - /// Derive the duration from the total number of CDDA — 16-bit stereo @ - /// 44.1 kHz — samples. + /// Derive the duration from the total number of a track's _CDDA-quality_ + /// samples. /// - /// For tracks with non-CDDA bit depths, channel counts, sample rates, or - /// sample totals, use [`Duration::from_samples`] instead. + /// This method assumes the count was captured at a rate of 44.1 kHz, and + /// requires it divide evenly into the samples-per-sector size used by + /// standard audio CDs (`588`). + /// + /// For more flexible (and/or approximate) sample/duration conversions, use + /// [`Duration::from_samples`] instead. /// /// ## Examples /// From 9f5b805b61c173ece62664d0c90200d97a4d0646 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Sun, 1 Jan 2023 21:39:14 -0800 Subject: [PATCH 4/6] new: Toc::from_durations --- src/lib.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 52ebe2f..db93c18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -246,6 +246,63 @@ impl Toc { Self::from_parts(audio, data, leadout) } + /// # From Durations. + /// + /// This will attempt to create an audio-only [`Toc`] from the track + /// durations. (Needless to say, this will only work if all tracks are + /// present and in the right order!) + /// + /// If you happen to know the disc's true leadin offset you can specify it, + /// otherwise the "industry default" value of `150` will be assumed. + /// + /// To create a mixed-mode [`Toc`] from scratch, use [`Toc::from_parts`] + /// instead so you can specify the location of the data session. + /// + /// ## Examples + /// + /// ``` + /// use cdtoc::{Toc, Duration}; + /// + /// let toc = Toc::from_durations( + /// [ + /// Duration::from(46650_u64), + /// Duration::from(41702_u64), + /// Duration::from(30295_u64), + /// Duration::from(37700_u64), + /// Duration::from(40050_u64), + /// Duration::from(53985_u64), + /// Duration::from(37163_u64), + /// Duration::from(59902_u64), + /// ], + /// None, + /// ).unwrap(); + /// assert_eq!( + /// toc.to_string(), + /// "8+96+B6D0+159B6+1D00D+26351+2FFC3+3D2A4+463CF+54DCD", + /// ); + /// ``` + /// + /// ## Errors + /// + /// This will return an error if the track count is outside `1..=99`, the + /// leadin is less than 150, or the sectors overflow `u32`. + pub fn from_durations(src: I, leadin: Option) -> Result + where I: IntoIterator { + let mut last: u32 = leadin.unwrap_or(150); + let mut audio: Vec = vec![last]; + for d in src { + let next = u32::try_from(d.sectors()) + .ok() + .and_then(|n| last.checked_add(n)) + .ok_or(TocError::SectorSize)?; + audio.push(next); + last = next; + } + + let leadout = audio.remove(audio.len() - 1); + Self::from_parts(audio, None, leadout) + } + /// # From Parts. /// /// Instantiate a new [`Toc`] by manually specifying the (starting) sectors From 882c8bd55f3d6078d7cd3f787b6351a4585b24a2 Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Sun, 1 Jan 2023 21:39:28 -0800 Subject: [PATCH 5/6] new: Toc::set_audio_leadin --- src/error.rs | 13 +++++-- src/lib.rs | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/src/error.rs b/src/error.rs index 6e3bb9a..9209acc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,7 @@ # CDTOC: Errors */ +use crate::TocKind; use std::{ error::Error, fmt, @@ -32,6 +33,12 @@ pub enum TocError { /// parsed). Checksums, + /// # Invalid Format For Operation. + /// + /// This is a catch-all error used when a given disc format is incompatible + /// with the operation, such as [`TocKind::DataFirst`] w/ [`Toc::set_audio_leadin`]. + Format(TocKind), + /// # Leadin Too Small. /// /// Audio CDs require a leadin of at least `150`. @@ -78,13 +85,11 @@ impl fmt::Display for TocError { Self::CDDASampleCount => f.write_str("Invalid CDDA sample count."), Self::CDTOCChars => f.write_str("Invalid character(s), expecting only 0-9, A-F, +, and (rarely) X."), Self::Checksums => f.write_str("Unable to parse checksums."), + Self::Format(kind) => write!(f, "This operation can't be applied to {kind} discs."), Self::LeadinSize => f.write_str("Leadin must be at least 150."), Self::NoAudio => f.write_str("At least one audio track is required."), Self::NoChecksums => f.write_str("No checksums were present."), - Self::SectorCount(expected, found) => write!( - f, "Expected {} audio sectors, found {}.", - expected, found, - ), + Self::SectorCount(expected, found) => write!(f, "Expected {expected} audio sectors, found {found}."), Self::SectorOrder => f.write_str("Sectors are incorrectly ordered or overlap."), Self::SectorSize => f.write_str("Sector sizes may not exceed four bytes (u32)."), Self::TrackCount => f.write_str("The number of audio tracks must be between 1..=99."), diff --git a/src/lib.rs b/src/lib.rs index db93c18..2aec25b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -368,6 +368,96 @@ impl Toc { Ok(Self { kind, audio, data: data.unwrap_or_default(), leadout }) } + /// # Set Audio Leadin. + /// + /// Set the audio leadin, nudging all entries up or down accordingly ( + /// including data and leadout). + /// + /// Note: this method cannot be used for data-first mixed-mode CDs. + /// + /// ## Examples + /// + /// ``` + /// use cdtoc::{Toc, TocKind}; + /// + /// let mut toc = Toc::from_cdtoc("4+96+2D2B+6256+B327+D84A").unwrap(); + /// assert_eq!(toc.audio_leadin(), 150); + /// + /// // Bump it up to 182. + /// assert!(toc.set_audio_leadin(182).is_ok()); + /// assert_eq!(toc.audio_leadin(), 182); + /// assert_eq!( + /// toc.to_string(), + /// "4+B6+2D4B+6276+B347+D86A", + /// ); + /// + /// // Back down to 150. + /// assert!(toc.set_audio_leadin(150).is_ok()); + /// assert_eq!(toc.audio_leadin(), 150); + /// assert_eq!( + /// toc.to_string(), + /// "4+96+2D2B+6256+B327+D84A", + /// ); + /// + /// // For CD-Extra, the data track will get nudged too. + /// toc = Toc::from_cdtoc("3+96+2D2B+6256+B327+D84A").unwrap(); + /// assert_eq!(toc.kind(), TocKind::CDExtra); + /// assert_eq!(toc.audio_leadin(), 150); + /// assert_eq!(toc.data_sector(), Some(45863)); + /// + /// assert!(toc.set_audio_leadin(182).is_ok()); + /// assert_eq!(toc.audio_leadin(), 182); + /// assert_eq!(toc.data_sector(), Some(45895)); + /// + /// // And back again. + /// assert!(toc.set_audio_leadin(150).is_ok()); + /// assert_eq!(toc.audio_leadin(), 150); + /// assert_eq!(toc.data_sector(), Some(45863)); + /// ``` + /// + /// ## Errors + /// + /// This will return an error if the leadin is less than `150`, the CD + /// format is data-first, or the nudging causes the sectors to overflow + /// `u32`. + pub fn set_audio_leadin(&mut self, leadin: u32) -> Result<(), TocError> { + use std::cmp::Ordering; + + if leadin < 150 { Err(TocError::LeadinSize) } + else if matches!(self.kind, TocKind::DataFirst) { + Err(TocError::Format(TocKind::DataFirst)) + } + else { + let current = self.audio_leadin(); + match leadin.cmp(¤t) { + // Nudge downward. + Ordering::Less => { + let diff = current - leadin; + for v in &mut self.audio { *v -= diff; } + if self.has_data() { self.data -= diff; } + self.leadout -= diff; + }, + // Nudge upward. + Ordering::Greater => { + let diff = leadin - current; + for v in &mut self.audio { + *v = v.checked_add(diff).ok_or(TocError::SectorSize)?; + } + if self.has_data() { + self.data = self.data.checked_add(diff) + .ok_or(TocError::SectorSize)?; + } + self.leadout = self.leadout.checked_add(diff) + .ok_or(TocError::SectorSize)?; + }, + // Noop. + Ordering::Equal => {}, + } + + Ok(()) + } + } + /// # Set Media Kind. /// /// This method can be used to override the table of content's derived @@ -666,7 +756,25 @@ pub enum TocKind { DataFirst, } +impl fmt::Display for TocKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + impl TocKind { + #[must_use] + /// # As Str. + /// + /// Return the value as a string slice. + pub const fn as_str(self) -> &'static str { + match self { + Self::Audio => "audio-only", + Self::CDExtra => "CD-Extra", + Self::DataFirst => "data+audio", + } + } + #[must_use] /// # Has Data? /// From aebe32a2c894c524730ba17906c91ef24609672f Mon Sep 17 00:00:00 2001 From: Josh Stoik Date: Sun, 1 Jan 2023 21:44:42 -0800 Subject: [PATCH 6/6] bump: 0.1.1 --- CHANGELOG.md | 13 +++++++++++++ CREDITS.md | 4 ++-- Cargo.toml | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bbbb2d..0a2c899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ +## [0.1.1](https://github.com/Blobfolio/cdtoc/releases/tag/v0.1.1) - 2023-01-01 + +### New + +* `Toc::from_durations` +* `Toc::set_audio_leadin` + +### Changed + +* Enforce minimum audio leadin (`150`) + + + ## [0.1.0](https://github.com/Blobfolio/cdtoc/releases/tag/v0.1.0) - 2022-12-25 Initial release! diff --git a/CREDITS.md b/CREDITS.md index 702fe70..9aaa3d8 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,7 +1,7 @@ # Project Dependencies Package: cdtoc - Version: 0.0.1 - Generated: 2022-12-26 06:33:05 UTC + Version: 0.1.1 + Generated: 2023-01-02 05:44:19 UTC | Package | Version | Author(s) | License | | ---- | ---- | ---- | ---- | diff --git a/Cargo.toml b/Cargo.toml index c467ceb..aeb5363 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cdtoc" -version = "0.1.0" +version = "0.1.1" authors = ["Blobfolio, LLC. "] edition = "2021" rust-version = "1.65"