kcode-audio-ingress 0.7.1

Durable automatic audio transcription with restart recovery
Documentation
use std::io::{Cursor, Read, Seek};

use anyhow::{Context, ensure};
use hound::{SampleFormat, WavReader, WavWriter};

pub(crate) fn interval<R: Read + Seek>(
    source: R,
    start_ms: u64,
    end_ms: u64,
) -> anyhow::Result<Vec<u8>> {
    let mut reader = WavReader::new(source).context("opening retained WAV audio")?;
    let spec = reader.spec();
    ensure!(end_ms > start_ms, "audio interval is empty");

    let start_frame = frame_at(start_ms, spec.sample_rate)?;
    let end_frame = frame_at(end_ms, spec.sample_rate)?.min(reader.duration());
    ensure!(
        start_frame < reader.duration(),
        "audio interval starts beyond the retained WAV"
    );
    ensure!(
        end_frame > start_frame,
        "audio interval contains no samples"
    );

    let sample_count = usize::try_from(
        u64::from(end_frame - start_frame)
            .checked_mul(u64::from(spec.channels))
            .context("audio interval sample count overflowed")?,
    )
    .context("audio interval is too large for this platform")?;
    reader
        .seek(start_frame)
        .context("seeking retained WAV interval")?;

    let mut output = Cursor::new(Vec::new());
    {
        let mut writer = WavWriter::new(&mut output, spec).context("creating WAV interval")?;
        let written = match spec.sample_format {
            SampleFormat::Int => {
                let mut written = 0;
                for sample in reader.samples::<i32>().take(sample_count) {
                    writer
                        .write_sample(sample.context("reading retained integer WAV sample")?)
                        .context("writing integer WAV interval sample")?;
                    written += 1;
                }
                written
            }
            SampleFormat::Float => {
                let mut written = 0;
                for sample in reader.samples::<f32>().take(sample_count) {
                    writer
                        .write_sample(sample.context("reading retained float WAV sample")?)
                        .context("writing float WAV interval sample")?;
                    written += 1;
                }
                written
            }
        };
        ensure!(written == sample_count, "retained WAV interval ended early");
        writer.finalize().context("finalizing WAV interval")?;
    }
    Ok(output.into_inner())
}

fn frame_at(milliseconds: u64, sample_rate: u32) -> anyhow::Result<u32> {
    let frame = milliseconds
        .checked_mul(u64::from(sample_rate))
        .context("audio interval timestamp overflowed")?
        / 1_000;
    u32::try_from(frame).context("audio interval exceeds WAV frame limits")
}

#[cfg(test)]
mod tests {
    use std::io::Cursor;

    use hound::{SampleFormat, WavReader, WavSpec, WavWriter};

    use super::interval;

    fn stereo_wav() -> Vec<u8> {
        let mut output = Cursor::new(Vec::new());
        {
            let mut writer = WavWriter::new(
                &mut output,
                WavSpec {
                    channels: 2,
                    sample_rate: 1_000,
                    bits_per_sample: 16,
                    sample_format: SampleFormat::Int,
                },
            )
            .unwrap();
            for frame in 0_i16..10 {
                writer.write_sample(frame).unwrap();
                writer.write_sample(-frame).unwrap();
            }
            writer.finalize().unwrap();
        }
        output.into_inner()
    }

    #[test]
    fn returns_only_the_requested_frames_in_the_source_format() {
        let bytes = interval(Cursor::new(stereo_wav()), 2, 6).unwrap();
        let mut reader = WavReader::new(Cursor::new(bytes)).unwrap();
        assert_eq!(reader.spec().channels, 2);
        assert_eq!(reader.spec().sample_rate, 1_000);
        assert_eq!(reader.spec().bits_per_sample, 16);
        assert_eq!(reader.spec().sample_format, SampleFormat::Int);
        assert_eq!(reader.duration(), 4);
        assert_eq!(
            reader
                .samples::<i16>()
                .collect::<Result<Vec<_>, _>>()
                .unwrap(),
            vec![2, -2, 3, -3, 4, -4, 5, -5]
        );
    }

    #[test]
    fn clips_the_requested_end_and_rejects_an_out_of_range_start() {
        let bytes = interval(Cursor::new(stereo_wav()), 8, 20).unwrap();
        let reader = WavReader::new(Cursor::new(bytes)).unwrap();
        assert_eq!(reader.duration(), 2);

        assert!(interval(Cursor::new(stereo_wav()), 10, 11).is_err());
    }
}