use std::path::{Path, PathBuf};
use clap::{Parser, Subcommand};
use dotenv::dotenv;
use substudy::{
align::combine_files, export, import, lang::Lang,
services::oai::translate_subtitle_file, srt::SubtitleFile, video, Result,
};
use tokio::task::spawn_blocking;
#[derive(Debug, Parser)]
#[command(name = "substudy", version)]
enum Args {
#[command(name = "clean")]
Clean {
subs: PathBuf,
},
#[command(name = "combine")]
Combine {
foreign_subs: PathBuf,
native_subs: PathBuf,
},
#[command(name = "export")]
Export {
#[command(subcommand)]
format: ExportFormat,
},
#[command(name = "import")]
Import {
#[command(subcommand)]
format: ImportFormat,
},
#[command(name = "list")]
List {
#[command(subcommand)]
to_list: ToList,
},
#[command(name = "translate")]
Translate {
foreign_subs: PathBuf,
#[arg(long)]
native_lang: String,
},
}
#[derive(Debug, Subcommand)]
enum ExportFormat {
#[command(name = "csv")]
Csv {
video: PathBuf,
foreign_subs: PathBuf,
native_subs: Option<PathBuf>,
},
#[command(name = "review")]
Review {
video: PathBuf,
foreign_subs: PathBuf,
native_subs: Option<PathBuf>,
},
#[command(name = "tracks")]
Tracks {
video: PathBuf,
foreign_subs: PathBuf,
},
}
impl ExportFormat {
fn name(&self) -> &str {
match *self {
ExportFormat::Csv { .. } => "csv",
ExportFormat::Review { .. } => "review",
ExportFormat::Tracks { .. } => "tracks",
}
}
fn video(&self) -> &Path {
match *self {
ExportFormat::Csv { ref video, .. } => &video,
ExportFormat::Review { ref video, .. } => &video,
ExportFormat::Tracks { ref video, .. } => &video,
}
}
fn foreign_subs(&self) -> &Path {
match *self {
ExportFormat::Csv {
ref foreign_subs, ..
} => &foreign_subs,
ExportFormat::Review {
ref foreign_subs, ..
} => &foreign_subs,
ExportFormat::Tracks {
ref foreign_subs, ..
} => &foreign_subs,
}
}
fn native_subs(&self) -> Option<&Path> {
match *self {
ExportFormat::Csv {
ref native_subs, ..
} => native_subs.as_ref().map(|p| p.as_path()),
ExportFormat::Review {
ref native_subs, ..
} => native_subs.as_ref().map(|p| p.as_path()),
ExportFormat::Tracks { .. } => None,
}
}
}
#[derive(Debug, Subcommand)]
enum ImportFormat {
#[command(name = "whisper-json")]
WhisperJson {
whisper_json: PathBuf,
},
}
#[derive(Debug, Subcommand)]
enum ToList {
#[command(name = "tracks")]
Tracks {
video: PathBuf,
},
}
#[tokio::main]
async fn main() -> Result<()> {
dotenv().ok();
env_logger::init();
let args: Args = Args::parse();
match args {
Args::Clean { subs } => spawn_blocking(move || cmd_clean(&subs)).await?,
Args::Combine {
foreign_subs,
native_subs,
} => spawn_blocking(move || cmd_combine(&foreign_subs, &native_subs)).await?,
Args::Export { format } => {
spawn_blocking(move || {
cmd_export(
format.name(),
format.video(),
format.foreign_subs(),
format.native_subs(),
)
})
.await?
}
Args::Import { format } => spawn_blocking(move || cmd_import(format)).await?,
Args::List {
to_list: ToList::Tracks { video },
} => spawn_blocking(move || cmd_tracks(&video)).await?,
Args::Translate {
foreign_subs,
native_lang,
} => cmd_translate(&foreign_subs, &native_lang).await,
}
}
fn cmd_clean(path: &Path) -> Result<()> {
let file1 = SubtitleFile::cleaned_from_path(path)?;
print!("{}", file1.to_string());
Ok(())
}
fn cmd_combine(path1: &Path, path2: &Path) -> Result<()> {
let file1 = SubtitleFile::cleaned_from_path(path1)?;
let file2 = SubtitleFile::cleaned_from_path(path2)?;
print!("{}", combine_files(&file1, &file2).to_string());
Ok(())
}
fn cmd_tracks(path: &Path) -> Result<()> {
let v = video::Video::new(path)?;
for stream in v.streams() {
let lang = stream.language();
let lang_str = lang
.map(|l| l.as_str().to_owned())
.unwrap_or("??".to_owned());
println!("#{} {} {:?}", stream.index, &lang_str, stream.codec_type);
}
Ok(())
}
fn cmd_export(
kind: &str,
video_path: &Path,
foreign_sub_path: &Path,
native_sub_path: Option<&Path>,
) -> Result<()> {
let video = video::Video::new(video_path)?;
let foreign_subs = SubtitleFile::cleaned_from_path(foreign_sub_path)?;
let native_subs = match native_sub_path {
None => None,
Some(p) => Some(SubtitleFile::cleaned_from_path(p)?),
};
let mut exporter = export::Exporter::new(video, foreign_subs, native_subs, kind)?;
match kind {
"csv" => export::export_csv(&mut exporter)?,
"review" => export::export_review(&mut exporter)?,
"tracks" => export::export_tracks(&mut exporter)?,
_ => panic!("Uknown export type: {}", kind),
}
Ok(())
}
fn cmd_import(format: ImportFormat) -> std::prelude::v1::Result<(), anyhow::Error> {
match format {
ImportFormat::WhisperJson { whisper_json } => {
let srt = import::import_whisper_json(&whisper_json)?;
print!("{}", srt.to_string());
Ok(())
}
}
}
async fn cmd_translate(foreign_subs: &Path, native_lang: &str) -> Result<()> {
let file = SubtitleFile::cleaned_from_path(foreign_subs)?;
let native_lang = Lang::iso639(native_lang)?;
let translated = translate_subtitle_file(&file, native_lang).await?;
print!("{}", translated.to_string());
Ok(())
}