audio-duration 0.2.2

Audio duration library for Rust
Documentation
use std::convert::Into;
use std::convert::TryFrom;
use std::fmt;
use std::time::Duration;

pub const DAYS_IN_A_WEEK: u64 = 7;
pub const HOURS_IN_A_DAY: u64 = 24;
pub const MINUTES_IN_A_HOUR: u64 = 60;
pub const SECONDS_IN_A_MINUTE: u64 = 60;
pub const SECONDS_IN_A_HOUR: u64 = 3600;
pub const SECONDS_IN_A_DAY: u64 = 86400;
pub const SECONDS_IN_A_WEEK: u64 = 604800;

/// Telephone narrow-band, very low quality.
pub const SAMPLING_FREQUENCY_8000: u32 = 8000;

/// Low quality, subwoofer analysis.
pub const SAMPLING_FREQUENCY_11025: u32 = 11025;

/// Telephone wide-band, used in VoIP.
pub const SAMPLING_FREQUENCY_16000: u32 = 16000;

/// Low quality, low frequency analysis.
pub const SAMPLING_FREQUENCY_22050: u32 = 22050;

/// MiniDV, 4-channel audio for DVCAM, long-play DAT.
pub const SAMPLING_FREQUENCY_32000: u32 = 32000;

/// Audio CD.
pub const SAMPLING_FREQUENCY_44100: u32 = 44100;

/// DVD, TV and Film.
pub const SAMPLING_FREQUENCY_48000: u32 = 48000;

/// Pro-audio recording for CD.
pub const SAMPLING_FREQUENCY_88200: u32 = 88200;

/// DVD-Audio, BD-ROM, HD DVD.
pub const SAMPLING_FREQUENCY_96000: u32 = 96000;

/// Pro-audio recording for CD.
pub const SAMPLING_FREQUENCY_176400: u32 = 176400;

/// DVD-Audio, BD-ROM, HD DVD.
pub const SAMPLING_FREQUENCY_192000: u32 = 192000;

/// Digital eXtreme Definition, used for recording SACDs.
pub const SAMPLING_FREQUENCY_352800: u32 = 352800;

/// Very high PCM sampling frequency on current DACs.
pub const SAMPLING_FREQUENCY_384000: u32 = 384000;

/// Digital eXtreme Definition, used for recording SACDs.
pub const SAMPLING_FREQUENCY_705600: u32 = 705600;

/// Highest PCM sampling frequency on current DACs.
pub const SAMPLING_FREQUENCY_768000: u32 = 768000;

/// DSD rate as used in SACDs, also known as DSD64
pub const SAMPLING_FREQUENCY_2822400: u32 = 2822400;

/// Doube-rate DSD, also known as DSD128
pub const SAMPLING_FREQUENCY_5644800: u32 = 5644800;

/// Quad-rate DSD, also known as DSD256.
pub const SAMPLING_FREQUENCY_11289600: u32 = 11289600;

/// Octuple-rate DSD, also known as DSD512.
pub const SAMPLING_FREQUENCY_22579200: u32 = 22579200;

pub struct AudioDuration {
    samples: u64,
    sampling_frequency: u32,
}
impl AudioDuration {
    /// Make a new `AudioDuration`.
    pub fn new(samples: u64, sampling_frequency: u32) -> AudioDuration {
        AudioDuration {
            samples,
            sampling_frequency,
        }
    }

    /// Return the number of samples in this `AudioDuration`.
    pub fn samples(&self) -> u64 {
        self.samples
    }

    /// Set the samples for this `AudioDuration`.
    pub fn set_samples(&mut self, samples: u64) {
        self.samples = samples
    }

    /// Return the sampling frequency of this `AudioDuration`.
    pub fn sampling_frequency(&self) -> u32 {
        self.sampling_frequency
    }

    /// Return the whole number of seconds in this `AudioDuration`.
    pub fn as_secs(&self) -> u64 {
        self.samples / self.sampling_frequency as u64
    }

    /// Return the whole number of minutes in this `AudioDuration`.
    pub fn as_mins(&self) -> u64 {
        self.as_secs() / SECONDS_IN_A_MINUTE
    }

    /// Return the whole number of hours in this `AudioDuration`.
    pub fn as_hours(&self) -> u64 {
        self.as_secs() / SECONDS_IN_A_HOUR
    }

    /// Return the whole number of days in this `AudioDuration`.
    pub fn as_days(&self) -> u64 {
        self.as_secs() / SECONDS_IN_A_DAY
    }

    /// Return the whole number of weeks in this `AudioDuration`.
    pub fn as_weeks(&self) -> u64 {
        self.as_secs() / SECONDS_IN_A_WEEK
    }

    /// Return the fractional part of this `AudioDuration` in samples.
    ///
    /// This will return a value in the range `0 <= subsec_samples < sampling_frequency`.
    pub fn subsec_samples(&self) -> u32 {
        let remainder = self.samples % self.sampling_frequency as u64;

        u32::try_from(remainder).unwrap()
    }

    /// Return the fractional part of this `AudioDuration` as a whole
    /// number of nanoseconds.
    pub fn subsec_nanos(&self) -> u32 {
        let nanos_as_f64 = self.subsec_secs() * 1000000000.0;

        nanos_as_f64 as u32
    }

    /// Return the fractional part of this `AudioDuration` as seconds.
    ///
    /// This will return a value in the range `0.0 <= subsec_secs < 1.0`.
    pub fn subsec_secs(&self) -> f64 {
        self.subsec_samples() as f64 / self.sampling_frequency as f64
    }

    /// Return the fractional part of minutes in seconds.
    pub fn submin_secs(&self) -> u64 {
        self.as_secs() % SECONDS_IN_A_MINUTE
    }

    /// Return the fractional part of hours in minutes.
    pub fn subhour_mins(&self) -> u64 {
        self.as_mins() % MINUTES_IN_A_HOUR
    }

    /// Return the fractional part of days in hours.
    pub fn subday_hours(&self) -> u64 {
        self.as_hours() % HOURS_IN_A_DAY
    }

    /// Return the fractional part of days in hours.
    pub fn subweek_days(&self) -> u64 {
        self.as_days() % DAYS_IN_A_WEEK
    }

    /// Return this `AudioDuration` as a [Duration](std::time::Duration).
    pub fn duration(&self) -> Duration {
        Duration::new(self.as_secs(), self.subsec_nanos())
    }

    /// Return the duration as a `String` formatted as `hh:mm:ss;samples`.
    pub fn fmt_hhmmss_samples(&self) -> String {
        format!(
            "{:02}:{:02}:{:02};{}",
            self.as_hours(),
            self.subhour_mins(),
            self.submin_secs(),
            self.subsec_samples(),
        )
    }

    /// Return the duration as a `String` formatted as `hh:mm:ss`.
    pub fn fmt_hhmmss(&self) -> String {
        format!(
            "{:02}:{:02}:{:02}",
            self.as_hours(),
            self.subhour_mins(),
            self.submin_secs(),
        )
    }

    /// Return the duration formatted as `mm:ss` if the duration is
    /// less than an hour, as `hh:mm:ss` otherwise.
    pub fn fmt_opt_hh_mmss(&self) -> String {
        if self.as_hours() == 0 {
            format!("{:02}:{:02}", self.subhour_mins(), self.submin_secs())
        } else {
            self.fmt_hhmmss()
        }
    }
}
impl fmt::Display for AudioDuration {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.fmt_hhmmss_samples())
    }
}
impl Into<Duration> for AudioDuration {
    fn into(self) -> Duration {
        self.duration()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new_and_getters() {
        let samples: u64 = 32572394598;
        let audio_duration = AudioDuration::new(samples, SAMPLING_FREQUENCY_44100);

        assert_eq!(audio_duration.samples(), samples);
        assert_eq!(
            audio_duration.sampling_frequency(),
            SAMPLING_FREQUENCY_44100
        );
        assert_eq!(audio_duration.as_secs(), 738603);
        assert_eq!(audio_duration.as_mins(), 12310);
        assert_eq!(audio_duration.as_hours(), 205);
        assert_eq!(audio_duration.as_days(), 8);
        assert_eq!(audio_duration.as_weeks(), 1);
        assert_eq!(audio_duration.subsec_samples(), 2298);
        assert_eq!(audio_duration.subsec_nanos(), 52108843);
        assert_eq!(audio_duration.subsec_secs(), 0.05210884353741497);
        assert_eq!(audio_duration.submin_secs(), 3);
        assert_eq!(audio_duration.subhour_mins(), 10);
        assert_eq!(audio_duration.subday_hours(), 13);
        assert_eq!(audio_duration.subweek_days(), 1);

        assert_eq!(audio_duration.to_string(), "205:10:03;2298");
    }

    #[test]
    fn duration() {
        let samples: u64 = 52394599;
        let audio_duration = AudioDuration::new(samples, SAMPLING_FREQUENCY_44100);
        let duration: Duration = audio_duration.into();

        assert_eq!(duration, Duration::new(1188, 86145124));
    }

    #[test]
    fn fmt() {
        let samples: u64 = 52394599;
        let audio_duration = AudioDuration::new(samples, SAMPLING_FREQUENCY_44100);
        assert_eq!(audio_duration.fmt_hhmmss(), "00:19:48");
        assert_eq!(audio_duration.fmt_opt_hh_mmss(), "19:48");

        let samples: u64 = 952394599;
        let audio_duration = AudioDuration::new(samples, SAMPLING_FREQUENCY_44100);
        assert_eq!(audio_duration.fmt_hhmmss(), "05:59:56");
        assert_eq!(audio_duration.fmt_opt_hh_mmss(), "05:59:56");
    }
}