use crate::config::Config;
use crate::paths::Paths;
use anyhow::{bail, Context, Result};
use clap::Args as ClapArgs;
use furigana::{Furigana, RomajiStyle, TtsOptions};
use std::path::PathBuf;
#[derive(ClapArgs, Debug)]
pub struct Args {
text: Option<String>,
#[arg(long)]
batch: bool,
#[arg(short, long, default_value = "tts")]
mode: String,
#[arg(long)]
rules_dir: Option<PathBuf>,
#[arg(long)]
core_dict_dir: Vec<PathBuf>,
#[arg(long, default_value = " ")]
short_pause: String,
#[arg(long, default_value = " ")]
long_pause: String,
#[arg(long)]
drop_period: bool,
#[arg(long)]
silence_symbols: bool,
#[arg(long)]
no_devoice: bool,
#[arg(long, default_value_t = 0)]
max_len: usize,
#[arg(long)]
estimate_accent: bool,
}
pub fn run(args: &Args, paths: &Paths, _cfg: &Config) -> Result<()> {
if args.batch && args.text.is_some() {
bail!("--batch と text 引数は同時に指定できません (入力は stdin から読みます)");
}
if args.batch && matches!(args.mode.as_str(), "analyze" | "accent") {
bail!(
"--batch は mode `{}` では使えません (出力が複数行になるため)",
args.mode
);
}
if args.batch && args.mode == "aquestalk" && args.max_len != 0 {
bail!("--batch は `aquestalk` + `--max-len` では使えません (出力が複数行になるため)");
}
let f = if args.rules_dir.is_some() || !args.core_dict_dir.is_empty() {
let mut b = Furigana::builder();
if let Some(rules) = &args.rules_dir {
b = b.rules_dir(rules);
}
for core in &args.core_dict_dir {
b = b.core_dict_dir(core);
}
b.estimate_accent(args.estimate_accent).build()?
} else {
super::furigana_builder(paths)
.estimate_accent(args.estimate_accent)
.build()?
};
if args.batch {
use std::io::{BufRead, BufWriter, Write};
let stdin = std::io::stdin();
let mut out = BufWriter::new(std::io::stdout().lock());
for line in stdin.lock().lines() {
let line = line.context("read stdin")?;
writeln!(out, "{}", convert(&f, &line, args)?).context("write stdout")?;
}
out.flush().context("flush stdout")?;
return Ok(());
}
let text = args
.text
.clone()
.context("変換対象テキストを指定してください (または --batch で stdin から読みます)")?;
println!("{}", convert(&f, &text, args)?);
Ok(())
}
fn convert(f: &Furigana, text: &str, args: &Args) -> Result<String> {
let result = match args.mode.as_str() {
"kanji" => text.to_string(),
"ruby" => f.to_ruby(text),
"hiragana" | "hira" => f.to_hiragana(text),
"romaji" => f.to_romaji(text, RomajiStyle::Hepburn),
"romaji-kunrei" | "kunrei" => f.to_romaji(text, RomajiStyle::Kunrei),
"tts" | "bouyomi" => {
let opts = TtsOptions::default()
.with_short_pause(args.short_pause.clone())
.with_long_pause(args.long_pause.clone())
.with_keep_period(!args.drop_period)
.with_silence_symbols(args.silence_symbols);
f.to_tts(text, &opts)
}
"voicevox-aques" | "voicevox" => {
ja_furigana_voicevox::to_aques_kana(&f.to_accent(text))
}
"aquestalk" => {
let symbols = ja_furigana_aquestalk::to_aquestalk_with(
&f.to_accent(text),
ja_furigana_aquestalk::Options {
devoice: !args.no_devoice,
trailing_period: !args.drop_period,
},
);
if args.max_len == 0 {
symbols
} else {
ja_furigana_aquestalk::split_for_aquestalk(&symbols, args.max_len).join("\n")
}
}
"analyze" => {
let result = f.analyze(text);
serde_json::to_string_pretty(&result).context("serialize AnalyzeResult to JSON")?
}
"accent" => {
let result = f.to_accent(text);
serde_json::to_string_pretty(&result).context("serialize AccentResult to JSON")?
}
other => bail!(
"未知の mode: {other} (使用可能: tts | bouyomi | hiragana | ruby | kanji | romaji | romaji-kunrei | analyze | accent | voicevox-aques | aquestalk)"
),
};
Ok(result)
}