use std::path::Path;
use splintr::{AnyTokenizer, PretrainedVocab, from_json_path, from_vocab};
use crate::error::{Error, Result};
use crate::vad::VadSegmentOptions;
use crate::enhance::EnhanceOptions;
pub const MAX_UTTERANCE_SECS: f32 = 30.0;
pub const PRETRAINED_TOKENIZER_NAMES: [&str; 4] =
["cl100k_base", "o200k_base", "llama3", "deepseek_v3"];
#[derive(Debug, Clone, Copy)]
pub enum TextTokenizer<'a> {
Pretrained(&'a str),
JsonFile(&'a Path),
}
impl TextTokenizer<'_> {
pub fn resolve(&self) -> Result<AnyTokenizer> {
match self {
Self::Pretrained(name) => {
let vocab =
PretrainedVocab::from_name(name).ok_or_else(|| Error::InvalidArgument {
arg: "tokenizer",
reason: format!(
"unknown pretrained tokenizer name {name:?}; this crate documents \
{}. A documented name also needs its vocabulary bundled — enable \
boostr's matching `vocab-*` feature — or pass a `tokenizer.json` \
path, which always works and carries a checkpoint's added tokens.",
PRETRAINED_TOKENIZER_NAMES.join(", ")
),
})?;
from_vocab(vocab).map_err(|e| Error::ModelError {
reason: format!("loading pretrained tokenizer {name:?}: {e}"),
})
}
Self::JsonFile(path) => from_json_path(path).map_err(|e| Error::ModelError {
reason: format!("loading tokenizer from {}: {e}", path.display()),
}),
}
}
}
#[derive(Debug, Clone)]
pub struct CorpusOptions<'a> {
pub sample_rate: usize,
pub vad: VadSegmentOptions,
pub language: Option<&'a str>,
pub speaker: Option<&'a str>,
pub max_new_tokens: Option<usize>,
pub min_utterance_secs: f32,
pub pad_to_multiple: Option<usize>,
pub enhance: Option<EnhanceOptions>,
}
impl Default for CorpusOptions<'_> {
fn default() -> Self {
Self {
sample_rate: 16_000,
vad: VadSegmentOptions {
max_speech_duration_s: MAX_UTTERANCE_SECS,
..VadSegmentOptions::default()
},
language: None,
speaker: None,
max_new_tokens: None,
min_utterance_secs: 0.5,
pad_to_multiple: None,
enhance: None,
}
}
}
pub fn check_max_speech_duration(max_speech_duration_s: f32) -> Result<()> {
if !max_speech_duration_s.is_finite() {
return Err(Error::InvalidArgument {
arg: "opts.vad.max_speech_duration_s",
reason: format!(
"is {max_speech_duration_s}, which sets no cap; cap it at \
{MAX_UTTERANCE_SECS} s or less, because every segment is transcribed by \
Whisper, whose encoder window is fixed at {MAX_UTTERANCE_SECS} s"
),
});
}
if max_speech_duration_s > MAX_UTTERANCE_SECS {
return Err(Error::InvalidArgument {
arg: "opts.vad.max_speech_duration_s",
reason: format!(
"is {max_speech_duration_s} s; cap it at {MAX_UTTERANCE_SECS} s or less, \
because every segment is transcribed by Whisper, whose encoder window is \
fixed at {MAX_UTTERANCE_SECS} s"
),
});
}
if max_speech_duration_s <= 0.0 {
return Err(Error::InvalidArgument {
arg: "opts.vad.max_speech_duration_s",
reason: format!("is {max_speech_duration_s} s; it must be greater than zero"),
});
}
Ok(())
}