hematite-kokoros 0.1.2

Hematite-maintained fork of the Kokoros Rust text-to-speech engine
Documentation
use mp3lame_encoder::{Builder, FlushNoGap, MonoPcm};

pub fn pcm_to_mp3(pcm_data: &[f32], sample_rate: u32) -> Result<Vec<u8>, std::io::Error> {
    let mut mp3_encoder = Builder::new().ok_or(std::io::Error::new(
        std::io::ErrorKind::Other,
        format!("Encoder init failed"),
    ))?;

    mp3_encoder.set_num_channels(1).map_err(|e| {
        std::io::Error::new(
            std::io::ErrorKind::Other,
            format!("Set channels failed: {:?}", e),
        )
    })?;
    mp3_encoder.set_sample_rate(sample_rate).map_err(|e| {
        std::io::Error::new(
            std::io::ErrorKind::Other,
            format!("Set sample rate failed: {:?}", e),
        )
    })?;
    mp3_encoder
        .set_brate(mp3lame_encoder::Bitrate::Kbps192)
        .map_err(|e| {
            std::io::Error::new(
                std::io::ErrorKind::Other,
                format!("Set bitrate failed: {:?}", e),
            )
        })?;
    mp3_encoder
        .set_quality(mp3lame_encoder::Quality::Best)
        .map_err(|e| {
            std::io::Error::new(
                std::io::ErrorKind::Other,
                format!("Set quality failed: {:?}", e),
            )
        })?;

    // skip id3 tag for now to avoid version-specific field mismatches

    let mut mp3_encoder = mp3_encoder.build().map_err(|e| {
        std::io::Error::new(
            std::io::ErrorKind::Other,
            format!("Build encoder failed: {:?}", e),
        )
    })?;

    let pcm_i16: Vec<i16> = pcm_data
        .iter()
        .map(|&x| (x * i16::MAX as f32) as i16)
        .collect();
    let pcm = MonoPcm(&pcm_i16);

    let mut mp3_out_buffer = Vec::new();
    mp3_out_buffer.reserve(mp3lame_encoder::max_required_buffer_size(pcm.0.len()));

    let encoded_size = mp3_encoder
        .encode(pcm, mp3_out_buffer.spare_capacity_mut())
        .map_err(|e| {
            std::io::Error::new(
                std::io::ErrorKind::Other,
                format!("Encoding failed: {:?}", e),
            )
        })?;

    unsafe {
        mp3_out_buffer.set_len(mp3_out_buffer.len().wrapping_add(encoded_size));
    }

    let flush_size = mp3_encoder
        .flush::<FlushNoGap>(mp3_out_buffer.spare_capacity_mut())
        .map_err(|e| {
            std::io::Error::new(std::io::ErrorKind::Other, format!("Flush failed: {:?}", e))
        })?;
    unsafe {
        mp3_out_buffer.set_len(mp3_out_buffer.len().wrapping_add(flush_size));
    }

    Ok(mp3_out_buffer)
}