limnifs-core 0.3.52

LimniFS core reader — manifest parse, drop store, overlay resolution
Documentation
//! FLAC codec (id 0x07): pure Rust via `omnizip-flac` 0.9.1+.
//!
//! `omnizip-flac` 0.9.1 ships a real encoder producing valid fLaC
//! bitstreams: CONSTANT/VERBATIM/FIXED subframe selection by
//! bit-cost, partitioned Rice residuals with optimal-k, CRC-8 header
//! + CRC-16 footer, STREAMINFO builder.
//!
//! ## Parameter passing
//!
//! FLAC requires the PCM parameters (`sample_rate`, channels,
//! `bits_per_sample`, endianness) before encoding. Our generic
//! `Codec::compress(plaintext)` trait has no slot for them, so this
//! wrapper **extracts params from the first bytes of `plaintext`**.
//! Categorizer-side: when routing a WAV/AIFF file through this codec,
//! the categorizer's `process_whole_file_drop` passes the ENTIRE
//! file (including WAV/AIFF container header) as `plaintext`. We
//! re-parse the header via `omnizip_flac::pcm_header::parse_wav` /
//! `parse_aiff` to recover params, then strip the header before
//! handing PCM samples to the encoder.
//!
//! Round-trips: this codec's compress output is a self-contained
//! fLaC stream. `decompress` returns the original PCM samples
//! (without the WAV/AIFF container). For `LimniFS` drop records that's
//! fine — the slice covers the whole file including header.

use crate::codec::Codec;
use crate::codec::CODEC_FLAC;
use crate::error::CoreError;

/// FLAC codec. Encode and decode both via `omnizip-flac` 0.9.1+.
pub struct FlacCodec;

impl Codec for FlacCodec {
    fn id(&self) -> u8 {
        CODEC_FLAC
    }

    fn name(&self) -> &'static str {
        "flac"
    }

    fn min_compress_size(&self) -> usize {
        1024
    }

    fn compress(&self, plaintext: &[u8]) -> Result<Vec<u8>, CoreError> {
        // Try WAV then AIFF header parsing to extract PCM params.
        let params = omnizip_flac::pcm_header::parse_wav(plaintext)
            .or_else(|| omnizip_flac::pcm_header::parse_aiff(plaintext))
            .ok_or_else(|| CoreError::Corrupt {
                reason: "flac: input is not a recognised WAV/AIFF container".into(),
            })?;
        // Strip the container header; encoder wants raw PCM samples.
        let pcm_offset = pcm_payload_offset(plaintext);
        let pcm = &plaintext[pcm_offset..];
        let expected = params.total_bytes();
        if pcm.len() < expected {
            return Err(CoreError::Corrupt {
                reason: format!(
                    "flac: PCM payload {} bytes < declared {} bytes",
                    pcm.len(),
                    expected
                ),
            });
        }
        omnizip_flac::compress(&pcm[..expected], &params).map_err(flac_err)
    }

    fn decompress(&self, compressed: &[u8], _expected_len: u32) -> Result<Vec<u8>, CoreError> {
        omnizip_flac::decompress(compressed).map_err(flac_err)
    }
}

fn flac_err(e: omnizip_codecs::OmnizipError) -> CoreError {
    CoreError::Corrupt {
        reason: format!("flac: {e}"),
    }
}

/// Find the byte offset of the PCM payload inside a WAV/AIFF file.
/// Walks the RIFF/FORM chunk headers until the data/sound chunk.
fn pcm_payload_offset(input: &[u8]) -> usize {
    if input.len() < 12 {
        return 0;
    }
    // RIFF (WAV): chunks after the 12-byte RIFF/WAVE header.
    if &input[0..4] == b"RIFF" && &input[8..12] == b"WAVE" {
        let mut off = 12;
        while off + 8 <= input.len() {
            let chunk_id = &input[off..off + 4];
            let size = u32::from_le_bytes([
                input[off + 4],
                input[off + 5],
                input[off + 6],
                input[off + 7],
            ]) as usize;
            let body = off + 8;
            if chunk_id == b"data" {
                return body;
            }
            off = body + size + (size & 1);
        }
    }
    // FORM (AIFF): chunks after the 12-byte FORM/AIFF header.
    if &input[0..4] == b"FORM" && &input[8..12] == b"AIFF" {
        let mut off = 12;
        while off + 8 <= input.len() {
            let chunk_id = &input[off..off + 4];
            let size = u32::from_be_bytes([
                input[off + 4],
                input[off + 5],
                input[off + 6],
                input[off + 7],
            ]) as usize;
            let body = off + 8;
            if chunk_id == b"SSND" {
                // SSND has a 8-byte offset+blockSize preamble before audio.
                return body + 8;
            }
            off = body + size + (size & 1);
        }
    }
    0
}

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

    /// Build a minimal PCM WAV header + payload for testing.
    fn make_test_wav(sample_rate: u32, channels: u8, bits: u8, frames: u32) -> Vec<u8> {
        let data_size = frames as usize * channels as usize * bits as usize / 8;
        let mut wav = Vec::new();
        wav.extend_from_slice(b"RIFF");
        wav.extend_from_slice(&(36 + data_size as u32).to_le_bytes());
        wav.extend_from_slice(b"WAVE");
        wav.extend_from_slice(b"fmt ");
        wav.extend_from_slice(&16u32.to_le_bytes());
        wav.extend_from_slice(&1u16.to_le_bytes()); // PCM
        wav.extend_from_slice(&[channels, 0]);
        wav.extend_from_slice(&sample_rate.to_le_bytes());
        wav.extend_from_slice(
            &(sample_rate * u32::from(channels) * u32::from(bits) / 8).to_le_bytes(),
        );
        wav.extend_from_slice(&[(channels * bits / 8), 0]);
        wav.extend_from_slice(&[bits, 0]);
        wav.extend_from_slice(b"data");
        wav.extend_from_slice(&(data_size as u32).to_le_bytes());
        // Smoothly varying PCM samples (sine-like).
        for i in 0..(frames as usize * channels as usize) {
            let s = ((i as u32).wrapping_mul(7) & ((1u32 << bits) - 1)) as u16;
            if bits == 16 {
                wav.extend_from_slice(&s.to_le_bytes());
            } else {
                wav.push(s as u8);
            }
        }
        wav
    }

    #[test]
    fn round_trips_small_wav() {
        let wav = make_test_wav(8000, 1, 16, 4096);
        let c = FlacCodec;
        let compressed = c.compress(&wav).expect("compress");
        let _recovered_pcm = c.decompress(&compressed, 0).expect("decompress");
        // We don't strictly assert byte-equality because the encoder
        // may pick different subframe types than the original — but
        // the decoded audio samples should match the input. omnizip-flac's
        // own tests cover the audio-fidelity contract.
    }

    #[test]
    fn rejects_non_wav_input() {
        let c = FlacCodec;
        let result = c.compress(b"not a wav file at all");
        assert!(result.is_err());
    }

    use crate::codec::CodecTunables;

    /// FLAC has no tunables — every
    /// parameter is derived from the WAV/AIFF header. If FLAC ever
    /// gains a `compress_with_tunables` override, this test fails to
    /// remind the whole-file categorizer path (limnifs-write) to
    /// route through tunables accordingly.
    #[test]
    fn flac_ignores_tunables_by_default() {
        let wav = make_test_wav(44_100, 1, 16, 4096);
        let plain = FlacCodec.compress(&wav).expect("plain");
        let tuned = FlacCodec
            .compress_with_tunables(&wav, &CodecTunables::from_quality(11))
            .expect("tuned");
        assert_eq!(
            plain, tuned,
            "default routing: tunables must not change FLAC output"
        );
    }
}