use anyhow::{Context, Result};
use clap::Parser;
use std::fs::File;
use std::io::{self, Read};
use tracing::error;
use scribble::{Opts, OutputType, Scribble};
fn main() {
scribble::init_logging();
if let Err(err) = run() {
error!(error = ?err, "scribble-cli failed");
std::process::exit(1);
}
}
fn run() -> Result<()> {
let params = Params::parse();
let opts = Opts {
model_key: None,
enable_translate_to_english: params.enable_translation_to_english,
enable_voice_activity_detection: params.enable_voice_activity_detection,
language: params.language.clone(),
output_type: params.output_type,
incremental_min_window_seconds: 1,
};
let input = open_input(¶ms.input)?;
let scribble = Scribble::new([params.model_path], params.vad_model_path)?;
scribble
.transcribe(input, io::stdout(), &opts)
.context("transcription failed")?;
Ok(())
}
fn open_input(path: &str) -> Result<Box<dyn Read + Send>> {
if path == "-" {
Ok(Box::new(io::stdin()))
} else {
let file =
File::open(path).with_context(|| format!("failed to open input file: {path}"))?;
Ok(Box::new(file))
}
}
#[derive(Parser, Debug)]
#[command(name = "scribble")]
#[command(about = "A transcription CLI (audio or video input)")]
struct Params {
#[arg(short = 'm', long = "model", required = true)]
pub model_path: String,
#[arg(short = 'v', long = "vad-model", required = true)]
pub vad_model_path: String,
#[arg(short = 'i', long = "input", required = true)]
pub input: String,
#[arg(
short = 'o',
long = "output-type",
value_enum,
default_value_t = OutputType::Vtt
)]
pub output_type: OutputType,
#[arg(long = "enable-vad", default_value_t = false)]
pub enable_voice_activity_detection: bool,
#[arg(
short = 't',
long = "enable-translation-to-english",
default_value_t = false
)]
pub enable_translation_to_english: bool,
#[arg(short = 'l', long = "language")]
pub language: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn params_parses_with_defaults() {
let params =
Params::try_parse_from(["scribble", "-m", "model.bin", "-v", "vad.bin", "-i", "-"])
.expect("parse params");
assert_eq!(params.input, "-");
assert!(matches!(params.output_type, OutputType::Vtt));
assert!(!params.enable_voice_activity_detection);
assert!(!params.enable_translation_to_english);
assert!(params.language.is_none());
}
#[test]
fn params_parses_all_flags() {
let params = Params::try_parse_from([
"scribble",
"-m",
"model.bin",
"-v",
"vad.bin",
"-i",
"-",
"-o",
"json",
"--enable-vad",
"-t",
"-l",
"en",
])
.expect("parse params");
assert!(matches!(params.output_type, OutputType::Json));
assert!(params.enable_voice_activity_detection);
assert!(params.enable_translation_to_english);
assert_eq!(params.language.as_deref(), Some("en"));
}
#[test]
fn open_input_errors_for_missing_file() {
let err = open_input("definitely-not-a-real-file")
.err()
.expect("expected open_input() to error");
assert!(err.to_string().contains("failed to open input file"));
}
}