From c6885ae2312a1be589e945129bdd9d268f14c370 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Sun, 21 Jun 2026 22:45:28 +0800 Subject: feat: replace `--pinyin` flag with generic `--post="+pinyin"` option --- src/args.rs | 50 +++++++++++++++++++++++++++++++++++++++---------- src/main.rs | 6 ++++-- src/post_proc.rs | 25 +++++++++++++++++++++++++ src/post_proc/pinyin.rs | 31 ++++++++++++++++++++++++++++++ src/post_proc/trait.rs | 12 ++++++++++++ 5 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 src/post_proc.rs create mode 100644 src/post_proc/pinyin.rs create mode 100644 src/post_proc/trait.rs (limited to 'src') diff --git a/src/args.rs b/src/args.rs index 11a1f11..7b472c4 100644 --- a/src/args.rs +++ b/src/args.rs @@ -118,9 +118,9 @@ pub struct DMVOPArguments { )] pub subnet_mask: String, - // Convert text output to pinyin (Chinese romanization) - #[arg(long = "pinyin")] - pub use_pinyin: bool, + // Post-processing pipeline, e.g. --post="+pinyin()" or --post="+reverse" + #[arg(long = "post", require_equals = true)] + pub post: Option, } #[derive(Clone, Debug)] @@ -182,13 +182,43 @@ pub fn format_output(pattern: &str, word: &str, confidence: f32, volume: f32) -> result } -/// Convert transcribed text to pinyin if the `use_pinyin` flag is set. -/// Otherwise returns the original text as a String. -pub fn maybe_to_pinyin(text: &str, use_pinyin: bool) -> String { - if use_pinyin { - pinyin::to_pinyin_vec(text, pinyin::Pinyin::plain).join(" ") - } else { - text.to_string() +/// Parse a post-processor spec in the form `+function_name(arg1, arg2, ...)`. +/// Returns `(name, args)`. +pub fn parse_post_spec(spec: &str) -> (&str, Vec<&str>) { + let spec = spec.trim(); + if !spec.starts_with('+') { + return (spec, vec![]); + } + let inner = &spec[1..]; + if let Some(paren) = inner.find('(') { + if inner.ends_with(')') { + let name = inner[..paren].trim(); + let args_str = inner[paren + 1..inner.len() - 1].trim(); + let args: Vec<&str> = if args_str.is_empty() { + vec![] + } else { + // Simple comma split (no nested parens, no escape handling for now) + args_str.split(',').map(|a| a.trim()).collect() + }; + return (name, args); + } + } + // No parens → no args + (inner.trim(), vec![]) +} + +/// Run the post-processing pipeline on the given text. +pub fn run_post_process(text: &str, spec: Option<&str>) -> String { + let Some(spec) = spec else { + return text.to_string(); + }; + let (name, args) = parse_post_spec(spec); + match crate::post_proc::run(text, name, &args) { + Some(result) => result, + None => { + eprintln!("[dmvop] Unknown post-processor: '{}'", name); + text.to_string() + } } } diff --git a/src/main.rs b/src/main.rs index e3a5b84..73b4e06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +pub mod post_proc; + mod args; pub use args::*; @@ -252,7 +254,7 @@ async fn main() { match rx.recv().await { Ok(event) => match event { vtx_engine::EngineEvent::TranscriptionComplete(result) => { - let raw = maybe_to_pinyin(&result.text, args.use_pinyin); + let raw = run_post_process(&result.text, args.post.as_deref()); let formatted = format_output(&pattern, &raw, 0.0, last_volume_db); for ch in &channels { @@ -260,7 +262,7 @@ async fn main() { } } vtx_engine::EngineEvent::TranscriptionSegment(segment) => { - let raw = maybe_to_pinyin(&segment.text, args.use_pinyin); + let raw = run_post_process(&segment.text, args.post.as_deref()); let formatted = format_output(&pattern, &raw, 0.0, last_volume_db); for ch in &channels { diff --git a/src/post_proc.rs b/src/post_proc.rs new file mode 100644 index 0000000..d8dfa74 --- /dev/null +++ b/src/post_proc.rs @@ -0,0 +1,25 @@ +mod r#trait; +pub use r#trait::*; + +pub mod pinyin; + +/// Look up a processor by name and run it. +pub fn run(text: &str, name: &str, args: &[&str]) -> Option { + macro_rules! match_processor { + ($($processor:ty),+ $(,)?) => { + match name { + $( + n if n == <$processor as TextPostProcesser>::processer_name() => { + Some(<$processor as TextPostProcesser>::process(args, text)) + } + )+ + _ => None, + } + }; + } + + match_processor!( + // +pinyin + crate::post_proc::pinyin::PinyinPostProcesser, + ) +} diff --git a/src/post_proc/pinyin.rs b/src/post_proc/pinyin.rs new file mode 100644 index 0000000..c06b040 --- /dev/null +++ b/src/post_proc/pinyin.rs @@ -0,0 +1,31 @@ +use crate::post_proc::TextPostProcesser; + +/// Converts Chinese characters to pinyin (romanized phonetic representation). +/// +/// # Styles +/// - `plain` — ni hao (default) +/// - `tone` — nǐ hǎo (with tone marks, requires `with_tone` feature) +/// - `tone-num` — ni3 hao3 (tone number at end) +/// - `first-letter` — n h (first letter only) +pub struct PinyinPostProcesser; + +impl TextPostProcesser for PinyinPostProcesser { + fn processer_name() -> &'static str { + "pinyin" + } + + fn process(param: &[&str], text: &str) -> String { + let style = param.first().copied().unwrap_or("plain"); + let result: Vec<&'static str> = match style { + "tone" => pinyin::to_pinyin_vec(text, pinyin::Pinyin::with_tone), + "tone-num" | "tone_num" => { + pinyin::to_pinyin_vec(text, pinyin::Pinyin::with_tone_num_end) + } + "first-letter" | "first_letter" => { + pinyin::to_pinyin_vec(text, pinyin::Pinyin::first_letter) + } + _ => pinyin::to_pinyin_vec(text, pinyin::Pinyin::plain), + }; + result.join(" ") + } +} diff --git a/src/post_proc/trait.rs b/src/post_proc/trait.rs new file mode 100644 index 0000000..7fced5b --- /dev/null +++ b/src/post_proc/trait.rs @@ -0,0 +1,12 @@ +/// A text post-processor that transforms transcribed text. +pub trait TextPostProcesser { + /// Name used in `--post="+name(args)"`. + fn processer_name() -> &'static str; + + /// Execute the post-processing. + /// + /// # Arguments + /// * `param` — arguments parsed from `--post="+name(arg1, arg2)"` + /// * `text` — the transcribed text to process + fn process(param: &[&str], text: &str) -> String; +} -- cgit