use crate::bit_ogg_stream::BitOggStream;
use crate::bit_reader::{BitRead, BitSliceReader};
use crate::error::{WemError, WemResult};
use crate::vorbis_helpers::{book_map_type1_quantvals, ilog};
use std::io::Write;
use std::path::Path;
static DEFAULT_CODEBOOKS: &[u8] = include_bytes!("codebooks/packed_codebooks.bin");
static AOTUV_CODEBOOKS: &[u8] = include_bytes!("codebooks/packed_codebooks_aoTuV_603.bin");
pub struct CodebookLibrary {
codebook_data: Vec<u8>,
codebook_offsets: Vec<u32>,
}
impl CodebookLibrary {
pub fn empty() -> Self {
Self {
codebook_data: Vec::new(),
codebook_offsets: Vec::new(),
}
}
pub fn default_codebooks() -> WemResult<Self> {
Self::from_bytes(DEFAULT_CODEBOOKS)
}
pub fn aotuv_codebooks() -> WemResult<Self> {
Self::from_bytes(AOTUV_CODEBOOKS)
}
pub fn from_bytes(data: &[u8]) -> WemResult<Self> {
if data.len() < 4 {
return Err(WemError::parse("codebook file too small"));
}
let file_size = data.len();
let offset_offset = u32::from_le_bytes([
data[file_size - 4],
data[file_size - 3],
data[file_size - 2],
data[file_size - 1],
]) as usize;
if offset_offset >= file_size {
return Err(WemError::parse("invalid codebook offset table pointer"));
}
let codebook_count = (file_size - offset_offset) / 4;
let codebook_data = data[..offset_offset].to_vec();
let mut codebook_offsets = Vec::with_capacity(codebook_count);
for i in 0..codebook_count {
let pos = offset_offset + i * 4;
let offset =
u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]);
codebook_offsets.push(offset);
}
Ok(Self {
codebook_data,
codebook_offsets,
})
}
pub fn from_file<P: AsRef<Path>>(path: P) -> WemResult<Self> {
let data = std::fs::read(path.as_ref())
.map_err(|e| WemError::file_open(format!("{}: {}", path.as_ref().display(), e)))?;
Self::from_bytes(&data)
}
pub fn codebook_count(&self) -> usize {
if self.codebook_offsets.is_empty() {
0
} else {
self.codebook_offsets.len() - 1
}
}
fn get_codebook(&self, index: usize) -> WemResult<&[u8]> {
if self.codebook_offsets.is_empty() {
return Err(WemError::parse("codebook library not loaded"));
}
if index >= self.codebook_count() {
return Err(WemError::invalid_codebook_id(index as i32));
}
let start = self.codebook_offsets[index] as usize;
let end = self.codebook_offsets[index + 1] as usize;
if end > self.codebook_data.len() || start > end {
return Err(WemError::parse("invalid codebook offset"));
}
Ok(&self.codebook_data[start..end])
}
pub fn get_codebook_size(&self, index: usize) -> i32 {
if self.codebook_offsets.is_empty() || index >= self.codebook_count() {
return -1;
}
let start = self.codebook_offsets[index];
let end = self.codebook_offsets[index + 1];
(end - start) as i32
}
pub fn rebuild<W: Write>(&self, index: usize, output: &mut BitOggStream<W>) -> WemResult<()> {
let codebook = self.get_codebook(index)?;
let mut reader = BitSliceReader::new(codebook);
self.rebuild_internal(&mut reader, Some(codebook.len() as u32), output)
}
pub fn copy<B: BitRead, W: Write>(
&self,
input: &mut B,
output: &mut BitOggStream<W>,
) -> WemResult<()> {
let id = input.read_bits(24)?;
let dimensions = input.read_bits(16)?;
let entries = input.read_bits(24)?;
if id != 0x564342 {
return Err(WemError::parse("invalid codebook identifier"));
}
output.write_bits(id, 24)?;
output.write_bits(dimensions, 16)?;
output.write_bits(entries, 24)?;
self.copy_codebook_data(input, output, entries, dimensions)
}
pub fn rebuild_from_reader<B: BitRead, W: Write>(
&self,
input: &mut B,
output: &mut BitOggStream<W>,
) -> WemResult<()> {
self.rebuild_internal(input, None, output)
}
fn rebuild_internal<B: BitRead, W: Write>(
&self,
input: &mut B,
codebook_size: Option<u32>,
output: &mut BitOggStream<W>,
) -> WemResult<()> {
let dimensions = input.read_bits(4)?;
let entries = input.read_bits(14)?;
output.write_bits(0x564342, 24)?; output.write_bits(dimensions, 16)?;
output.write_bits(entries, 24)?;
self.rebuild_codebook_data(input, output, entries, dimensions, codebook_size)
}
fn copy_codebook_data<B: BitRead, W: Write>(
&self,
input: &mut B,
output: &mut BitOggStream<W>,
entries: u32,
dimensions: u32,
) -> WemResult<()> {
let ordered = input.read_bits(1)?;
output.write_bits(ordered, 1)?;
if ordered != 0 {
let initial_length = input.read_bits(5)?;
output.write_bits(initial_length, 5)?;
let mut current_entry = 0u32;
while current_entry < entries {
let num_bits = ilog(entries - current_entry);
let number = input.read_bits(num_bits)?;
output.write_bits(number, num_bits)?;
current_entry += number;
}
if current_entry > entries {
return Err(WemError::parse("current_entry out of range"));
}
} else {
let sparse = input.read_bits(1)?;
output.write_bits(sparse, 1)?;
for _ in 0..entries {
let mut present_bool = true;
if sparse != 0 {
let present = input.read_bits(1)?;
output.write_bits(present, 1)?;
present_bool = present != 0;
}
if present_bool {
let codeword_length = input.read_bits(5)?;
output.write_bits(codeword_length, 5)?;
}
}
}
let lookup_type = input.read_bits(4)?;
output.write_bits(lookup_type, 4)?;
self.handle_lookup_table(input, output, entries, dimensions, lookup_type, false)
}
fn rebuild_codebook_data<B: BitRead, W: Write>(
&self,
input: &mut B,
output: &mut BitOggStream<W>,
entries: u32,
dimensions: u32,
codebook_size: Option<u32>,
) -> WemResult<()> {
let ordered = input.read_bits(1)?;
output.write_bits(ordered, 1)?;
if ordered != 0 {
let initial_length = input.read_bits(5)?;
output.write_bits(initial_length, 5)?;
let mut current_entry = 0u32;
while current_entry < entries {
let num_bits = ilog(entries - current_entry);
let number = input.read_bits(num_bits)?;
output.write_bits(number, num_bits)?;
current_entry += number;
}
if current_entry > entries {
return Err(WemError::parse("current_entry out of range"));
}
} else {
let codeword_length_length = input.read_bits(3)?;
let sparse = input.read_bits(1)?;
if codeword_length_length == 0 || codeword_length_length > 5 {
return Err(WemError::parse("nonsense codeword length"));
}
output.write_bits(sparse, 1)?;
for _ in 0..entries {
let mut present_bool = true;
if sparse != 0 {
let present = input.read_bits(1)?;
output.write_bits(present, 1)?;
present_bool = present != 0;
}
if present_bool {
let codeword_length = input.read_bits(codeword_length_length as u8)?;
output.write_bits(codeword_length, 5)?;
}
}
}
let lookup_type = input.read_bits(1)?;
output.write_bits(lookup_type, 4)?;
self.handle_lookup_table(input, output, entries, dimensions, lookup_type, true)?;
if let Some(size) = codebook_size {
if size != 0 {
let bytes_read = input.total_bits_read() / 8 + 1;
if bytes_read != size as u64 {
return Err(WemError::size_mismatch(size as u64, bytes_read));
}
}
}
Ok(())
}
fn handle_lookup_table<B: BitRead, W: Write>(
&self,
input: &mut B,
output: &mut BitOggStream<W>,
entries: u32,
dimensions: u32,
lookup_type: u32,
is_rebuild: bool,
) -> WemResult<()> {
if lookup_type == 1 {
let min = input.read_bits(32)?;
let max = input.read_bits(32)?;
let value_length = input.read_bits(4)?;
let sequence_flag = input.read_bits(1)?;
output.write_bits(min, 32)?;
output.write_bits(max, 32)?;
output.write_bits(value_length, 4)?;
output.write_bits(sequence_flag, 1)?;
let quantvals = book_map_type1_quantvals(entries, dimensions);
for _ in 0..quantvals {
let val = input.read_bits((value_length + 1) as u8)?;
output.write_bits(val, (value_length + 1) as u8)?;
}
} else if lookup_type == 2 {
if !is_rebuild {
return Err(WemError::parse("didn't expect lookup type 2"));
} else {
return Err(WemError::parse("invalid lookup type"));
}
} else if lookup_type != 0 {
return Err(WemError::parse("invalid lookup type"));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_default_codebooks() {
let lib = CodebookLibrary::default_codebooks().unwrap();
assert!(lib.codebook_count() > 0);
}
#[test]
fn test_load_aotuv_codebooks() {
let lib = CodebookLibrary::aotuv_codebooks().unwrap();
assert!(lib.codebook_count() > 0);
}
#[test]
fn test_empty_codebook() {
let lib = CodebookLibrary::empty();
assert_eq!(lib.codebook_count(), 0);
assert_eq!(lib.get_codebook_size(0), -1);
}
#[test]
fn test_get_codebook() {
let lib = CodebookLibrary::default_codebooks().unwrap();
let cb = lib.get_codebook(0);
assert!(cb.is_ok());
assert!(!cb.unwrap().is_empty());
}
#[test]
fn test_codebook_count_matches_offsets() {
let lib = CodebookLibrary::default_codebooks().unwrap();
let count = lib.codebook_count();
assert!(count > 0);
for i in 0..count {
assert!(lib.get_codebook(i).is_ok());
}
assert!(lib.get_codebook(count).is_err());
}
#[test]
fn test_get_codebook_size() {
let lib = CodebookLibrary::default_codebooks().unwrap();
let size = lib.get_codebook_size(0);
assert!(size > 0);
let data = lib.get_codebook(0).unwrap();
assert_eq!(size as usize, data.len());
}
#[test]
fn test_get_codebook_invalid_index() {
let lib = CodebookLibrary::default_codebooks().unwrap();
let result = lib.get_codebook(999999);
assert!(result.is_err());
}
#[test]
fn test_get_codebook_size_invalid_index() {
let lib = CodebookLibrary::default_codebooks().unwrap();
assert_eq!(lib.get_codebook_size(999999), -1);
}
#[test]
fn test_empty_codebook_get_fails() {
let lib = CodebookLibrary::empty();
assert!(lib.get_codebook(0).is_err());
}
#[test]
fn test_from_bytes_too_small() {
let result = CodebookLibrary::from_bytes(&[0, 1, 2]);
assert!(result.is_err());
}
#[test]
fn test_from_bytes_invalid_offset() {
let mut data = vec![0u8; 8];
data[4] = 100;
data[5] = 0;
data[6] = 0;
data[7] = 0;
let result = CodebookLibrary::from_bytes(&data);
assert!(result.is_err());
}
#[test]
fn test_default_and_aotuv_have_same_count() {
let default = CodebookLibrary::default_codebooks().unwrap();
let aotuv = CodebookLibrary::aotuv_codebooks().unwrap();
assert_eq!(default.codebook_count(), aotuv.codebook_count());
}
#[test]
fn test_codebook_data_is_different() {
let default = CodebookLibrary::default_codebooks().unwrap();
let aotuv = CodebookLibrary::aotuv_codebooks().unwrap();
let mut found_difference = false;
for i in 0..default.codebook_count().min(10) {
let d = default.get_codebook(i).unwrap();
let a = aotuv.get_codebook(i).unwrap();
if d != a {
found_difference = true;
break;
}
}
assert!(found_difference, "Expected some codebooks to differ");
}
}