use crate::types::{TextGrid, TextGridError, Tier, TierType, Interval, Point};
use std::fs::File;
use std::io::{Read, Write, BufReader, BufWriter};
use std::path::Path;
pub fn read_binary<P: AsRef<Path>>(path: P) -> Result<TextGrid, TextGridError> {
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let mut buffer = Vec::new();
reader.read_to_end(&mut buffer)?;
let mut cursor = 0;
if &buffer[cursor..cursor + 12] != b"ooBinaryFile" {
return Err(TextGridError::Format("Not a Praat binary TextGrid".into()));
}
cursor += 12;
let obj_len = u16::from_le_bytes(buffer[cursor..cursor + 2].try_into().unwrap()) as usize;
cursor += 2;
if &buffer[cursor..cursor + obj_len] != b"TextGrid" {
return Err(TextGridError::Format("Invalid object class".into()));
}
cursor += obj_len;
let xmin = f64::from_le_bytes(buffer[cursor..cursor + 8].try_into().unwrap());
cursor += 8;
let xmax = f64::from_le_bytes(buffer[cursor..cursor + 8].try_into().unwrap());
cursor += 8;
let size = u32::from_le_bytes(buffer[cursor..cursor + 4].try_into().unwrap()) as usize;
cursor += 4;
let mut tiers = Vec::with_capacity(size);
for _ in 0..size {
let class_len = u16::from_le_bytes(buffer[cursor..cursor + 2].try_into().unwrap()) as usize;
cursor += 2;
let class = String::from_utf8(buffer[cursor..cursor + class_len].to_vec())?;
let tier_type = if class == "IntervalTier" {
TierType::IntervalTier
} else if class == "TextTier" {
TierType::PointTier
} else {
return Err(TextGridError::Format("Unknown tier type".into()));
};
cursor += class_len;
let name_len = u16::from_le_bytes(buffer[cursor..cursor + 2].try_into().unwrap()) as usize;
cursor += 2;
let name = String::from_utf8(buffer[cursor..cursor + name_len].to_vec())?;
cursor += name_len;
let tier_xmin = f64::from_le_bytes(buffer[cursor..cursor + 8].try_into().unwrap());
cursor += 8;
let tier_xmax = f64::from_le_bytes(buffer[cursor..cursor + 8].try_into().unwrap());
cursor += 8;
let count = u32::from_le_bytes(buffer[cursor..cursor + 4].try_into().unwrap()) as usize;
cursor += 4;
let mut intervals = Vec::new();
let mut points = Vec::new();
match tier_type {
TierType::IntervalTier => {
for _ in 0..count {
let xmin = f64::from_le_bytes(buffer[cursor..cursor + 8].try_into().unwrap());
cursor += 8;
let xmax = f64::from_le_bytes(buffer[cursor..cursor + 8].try_into().unwrap());
cursor += 8;
let text_len = u16::from_le_bytes(buffer[cursor..cursor + 2].try_into().unwrap()) as usize;
cursor += 2;
let text = String::from_utf8(buffer[cursor..cursor + text_len].to_vec())?;
cursor += text_len;
intervals.push(Interval { xmin, xmax, text });
}
}
TierType::PointTier => {
for _ in 0..count {
let time = f64::from_le_bytes(buffer[cursor..cursor + 8].try_into().unwrap());
cursor += 8;
let mark_len = u16::from_le_bytes(buffer[cursor..cursor + 2].try_into().unwrap()) as usize;
cursor += 2;
let mark = String::from_utf8(buffer[cursor..cursor + mark_len].to_vec())?;
cursor += mark_len;
points.push(Point { time, mark });
}
}
}
tiers.push(Tier { name, tier_type, xmin: tier_xmin, xmax: tier_xmax, intervals, points });
}
Ok(TextGrid::new(xmin, xmax)?.with_tiers(tiers))
}
pub fn write_binary<P: AsRef<Path>>(textgrid: &TextGrid, path: P) -> Result<(), TextGridError> {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
writer.write_all(b"ooBinaryFile")?;
let class = b"TextGrid";
writer.write_all(&(class.len() as u16).to_le_bytes())?;
writer.write_all(class)?;
writer.write_all(&textgrid.xmin.to_le_bytes())?;
writer.write_all(&textgrid.xmax.to_le_bytes())?;
writer.write_all(&(textgrid.tiers.len() as u32).to_le_bytes())?;
for tier in &textgrid.tiers {
let class = match tier.tier_type {
TierType::IntervalTier => b"IntervalTier" as &[u8],
TierType::PointTier => b"TextTier" as &[u8],
};
writer.write_all(&(class.len() as u16).to_le_bytes())?;
writer.write_all(class)?;
let name_bytes = tier.name.as_bytes();
writer.write_all(&(name_bytes.len() as u16).to_le_bytes())?;
writer.write_all(name_bytes)?;
writer.write_all(&tier.xmin.to_le_bytes())?;
writer.write_all(&tier.xmax.to_le_bytes())?;
match tier.tier_type {
TierType::IntervalTier => {
writer.write_all(&(tier.intervals.len() as u32).to_le_bytes())?;
for interval in &tier.intervals {
writer.write_all(&interval.xmin.to_le_bytes())?;
writer.write_all(&interval.xmax.to_le_bytes())?;
let text_bytes = interval.text.as_bytes();
writer.write_all(&(text_bytes.len() as u16).to_le_bytes())?;
writer.write_all(text_bytes)?;
}
}
TierType::PointTier => {
writer.write_all(&(tier.points.len() as u32).to_le_bytes())?;
for point in &tier.points {
writer.write_all(&point.time.to_le_bytes())?;
let mark_bytes = point.mark.as_bytes();
writer.write_all(&(mark_bytes.len() as u16).to_le_bytes())?;
writer.write_all(mark_bytes)?;
}
}
}
}
writer.flush()?;
Ok(())
}