From d1cda84f0d9acf2700e4c2eeafff4dbb63cc51fe Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Sat, 20 Jun 2026 06:58:31 +0800 Subject: feat: implement core capture engine with CLI and output protocols --- src/args.rs | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 20 deletions(-) (limited to 'src/args.rs') diff --git a/src/args.rs b/src/args.rs index fcf224c..52dddfc 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,12 +1,51 @@ use std::path::PathBuf; +/// Whether verbose debug output is enabled. +/// Set by `DMVOPArguments::verbose`. +pub static VERBOSE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); + +/// Print a debug message only when `--verbose` is set. +#[macro_export] +macro_rules! debug_log { + ($($arg:tt)*) => { + if $crate::args::VERBOSE.load(std::sync::atomic::Ordering::Relaxed) { + eprintln!($($arg)*); + } + }; +} + #[derive(clap::Parser)] -#[command( - no_binary_name = true, - disable_help_flag = true, - disable_version_flag = true -)] +#[command(name = "dmvop", disable_help_flag = true, disable_version_flag = true)] pub struct DMVOPArguments { + // Verbose output (show debug messages) + #[arg(long = "verbose", short = 'V')] + pub verbose: bool, + + // List all available input devices and exit + #[arg(long = "list-devices", alias = "list", short = 'L')] + pub list_devices: bool, + + // List all available Whisper models and exit + #[arg(long = "list-models")] + pub list_models: bool, + + // Download a specific Whisper model and exit + #[arg(long = "download-model", alias = "get-model", require_equals = true)] + pub download_model: Option, + + // Language hint for Whisper (e.g. en, zh, ja). Auto-detects if not set. + #[arg(long = "lang", require_equals = true)] + pub lang: Option, + + // Whisper model to use (e.g. tiny, base, small, medium, large-v3) + #[arg( + long = "model", + short = 'm', + default_value = "base_en", + require_equals = true + )] + pub model: String, + // Devices (unix/linux device or WASAPI name) #[arg( long = "device", @@ -14,7 +53,7 @@ pub struct DMVOPArguments { allow_hyphen_values = true, require_equals = true )] - device_name: String, + pub device_name: Option, // Format // Use %{param} to represent a parameter @@ -30,24 +69,25 @@ pub struct DMVOPArguments { default_value = "%{vol},%{word}", require_equals = true )] - format_pattern: Option, + pub format_pattern: Option, + + #[arg(long = "format-file", short = 'S', require_equals = true)] + pub format_file: Option, + // Output (can be specified multiple times) #[arg( - long = "format-file", - short = 'S', - alias = "fmt", - require_equals = true + long, + short = 'O', + require_equals = true, + default_value = "stdout", + num_args = 1 )] - format_file: Option, - - // Output - #[arg(long, short = 'O', require_equals = true, default_value = "stdout")] - output: OutputMode, + pub output: Vec, // MISC // Port (default: 5117) #[arg(long, short = 'p', default_value_t = 5117, require_equals = true)] - port: u16, + pub port: u16, // Socket file (default: ./dmvop.sock in current directory) #[arg( @@ -56,7 +96,7 @@ pub struct DMVOPArguments { default_value = "./dmvop.sock", require_equals = true )] - socket_file: PathBuf, + pub socket_file: PathBuf, // Subnet mask for UDP broadcast (default: only last octet, e.g., "255.255.255.0") #[arg( @@ -65,10 +105,10 @@ pub struct DMVOPArguments { default_value = "255.255.255.0", require_equals = true )] - subnet_mask: String, + pub subnet_mask: String, } -#[derive(Clone)] +#[derive(Clone, Debug)] #[allow(non_camel_case_types)] pub enum OutputMode { /// Establish a basic TCP service, broadcasting output to all connected sockets @@ -103,3 +143,43 @@ impl std::str::FromStr for OutputMode { } } } + +/// Parse a format pattern string and produce a formatted output string +/// from the provided values. +/// +/// Supported placeholders: +/// - `%{vol}` — volume 0–100 +/// - `%{word}` — transcribed word/text +/// - `%{confid}` / `%{confidence}` — confidence score +pub fn format_output(pattern: &str, word: &str, confidence: f32, volume: f32) -> String { + let mut result = pattern.to_string(); + + // Volume: clamp dB range (~-60 to 0) to 0–100 scale + // Typical speech is around -30 dB to -12 dB + let vol_normalized = ((volume + 60.0).clamp(0.0, 60.0) / 60.0 * 100.0) as u32; + + // Replace known placeholders + result = result.replace("%{vol}", &vol_normalized.to_string()); + result = result.replace("%{word}", word); + result = result.replace("%{confid}", &format!("{:.1}", confidence)); + result = result.replace("%{confidence}", &format!("{:.1}", confidence)); + + result +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_format_output() { + let s = format_output("%{vol},%{word},%{confid}", "hello", 95.0, -12.0); + assert_eq!(s, "80,hello,95.0"); + } + + #[test] + fn test_format_output_no_volume() { + let s = format_output("text: %{word}", "world", 0.0, -60.0); + assert_eq!(s, "text: world"); + } +} -- cgit