use compact_str::CompactString;
use log::trace;
use regex::Regex;
use std::{
fmt, fs,
io::{self, prelude::*, BufReader},
path::Path,
sync::LazyLock,
};
use super::{
palette::{palette, DEFAULT_PALETTE},
Palette, VobSubError,
};
use crate::{time::TimePoint, vobsub::IResultExt as _};
#[derive(Debug, Clone)]
pub struct Lang(CompactString);
impl Lang {
#[allow(clippy::missing_const_for_fn)]
pub fn lang(&self) -> &str {
&self.0
}
}
impl TryFrom<&str> for Lang {
type Error = VobSubError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
static KEY_VALUE: LazyLock<Regex> =
LazyLock::new(|| Regex::new("^([a-z]+), index: (.*)").unwrap());
KEY_VALUE
.captures(value)
.map_or(Err(VobSubError::LangParsing), |cap| {
let lang = cap.get(1).unwrap().as_str();
Ok(Self(lang.into()))
})
}
}
#[repr(transparent)]
pub struct TimePointIdx(TimePoint);
impl From<TimePoint> for TimePointIdx {
fn from(value: TimePoint) -> Self {
Self(value)
}
}
impl fmt::Display for TimePointIdx {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt_separator(f, ':')
}
}
#[derive(Debug)]
pub struct Index {
palette: Palette,
lang: Option<Lang>,
}
const PALETTE_KEY: &str = "palette";
const LANG_KEY: &str = "id";
impl Index {
#[profiling::function]
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, VobSubError> {
let path = path.as_ref();
let mkerr_idx = |source| VobSubError::Io {
source,
path: path.into(),
};
let f = fs::File::open(path).map_err(mkerr_idx)?;
let input = io::BufReader::new(f);
Self::read_index(input, &mkerr_idx)
}
#[profiling::function]
pub fn read_index<T, Err>(mut input: BufReader<T>, mkerr: &Err) -> Result<Self, VobSubError>
where
T: std::io::Read,
Err: Fn(io::Error) -> VobSubError,
{
static KEY_VALUE: LazyLock<Regex> =
LazyLock::new(|| Regex::new("^([A-Za-z/ ]+): (.*)").unwrap());
let mut palette_val = None;
let mut lang = None;
let mut buf = String::with_capacity(256);
while input.read_line(&mut buf).map_err(mkerr)? > 0 {
let line = buf.trim_end();
if let Some(cap) = KEY_VALUE.captures(line) {
let key = cap.get(1).unwrap().as_str();
let val = cap.get(2).unwrap().as_str();
match key {
PALETTE_KEY => {
palette_val = Some(
palette(val.as_bytes())
.to_result_no_rest()
.map_err(VobSubError::PaletteError)?,
);
}
LANG_KEY => {
lang = Lang::try_from(val).ok();
}
_ => trace!("Unimplemented idx key: {key}"),
}
}
buf.clear();
}
let palette = palette_val.unwrap_or(DEFAULT_PALETTE);
Ok(Self { palette, lang })
}
#[must_use]
pub const fn init(palette: Palette, lang: Option<Lang>) -> Self {
Self { palette, lang }
}
#[must_use]
pub const fn palette(&self) -> &Palette {
&self.palette
}
#[must_use]
pub const fn lang(&self) -> &Option<Lang> {
&self.lang
}
}
#[cfg(test)]
mod tests {
use image::Rgb;
use crate::vobsub::Index;
#[test]
fn parse_index() {
env_logger::init();
let idx = Index::open("./fixtures/example.idx").unwrap();
assert_eq!(idx.palette()[0], Rgb([0x00, 0x00, 0x00]));
assert_eq!(idx.palette()[15], Rgb([0x11, 0xbb, 0xbb]));
}
}