aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-06-20 07:20:46 +0800
committer魏曹先生 <1992414357@qq.com>2026-06-20 07:32:54 +0800
commit9b896d697eb582dedd2533f6d2054f062c8381a0 (patch)
treeb7e8db33af8e6837f3d389d4302b821d401ec0d8
parent69354929928d1d071698262c259cdffbf9aec468 (diff)
feat: add custom help message and fix clap help conflict
Disable clap's built-in help flag and reroute `-h`/`--help` to print an embedded help text from `help.txt`. Use `try_parse` to avoid clap's default exit on parse failure, allowing custom error output.
-rw-r--r--README.md6
-rw-r--r--help.txt33
-rw-r--r--src/args.rs6
-rw-r--r--src/main.rs28
-rw-r--r--src/output_protocol/ipc.rs2
5 files changed, 59 insertions, 16 deletions
diff --git a/README.md b/README.md
index 0ac230d..394622d 100644
--- a/README.md
+++ b/README.md
@@ -7,11 +7,7 @@ This is a CLI program that reads your microphone device and outputs information
# Usage
```
-dmvop --output=stdout --fmt="%{vol},%{word},%{confidence}" --device="/dev/mymic"
- --output=stderr --fmt-file="./fmt.txt"
- --output=ipc
- --output=tcp
- --output=udp
+dmvop --help
```
# License
diff --git a/help.txt b/help.txt
new file mode 100644
index 0000000..013c782
--- /dev/null
+++ b/help.txt
@@ -0,0 +1,33 @@
+Usage: dmvop [OPTIONS]
+
+Options
+ --device=<name> Microphone (WASAPI name or device ID). Required.
+ -O, --output=<mode> Output destination(s). May be repeated. [default: stdout]
+ stdout | stderr | tcp | udp | udp-broadcast | ipc
+ -m, --model=<model> Whisper model. [default: base_en]
+ --lang=<code> Language hint (zh, ja, en, fr...). Skips detection.
+ -f, --format=<pattern> Output format with %{vol}, %{word}, %{confid}.
+ [default: %{vol},%{word}]
+ -S, --format-file=<path> Read format from file.
+ -p, --port=<num> Port for TCP/UDP modes. [default: 5117]
+ --socket-file=<path> Unix socket path for IPC. [default: ./dmvop.sock]
+ --subnet-mask=<mask> Subnet mask for UDP broadcast.
+ [default: 255.255.255.0]
+ -L, --list-devices List microphones and exit.
+ --list-models List Whisper models and exit.
+ --download-model=<name> Download a model and exit.
+ -V, --verbose Show debug output.
+ -h, --help Show this help.
+
+Examples
+ dmvop --list-devices
+ dmvop --device="My Mic" -O stdout -O tcp -m small --lang=zh
+ dmvop --device="My Mic" -f "[%{vol}] %{word}"
+ dmvop --device="My Mic" -O udp-broadcast -p 8888
+ dmvop --download-model=small
+
+Notes
+ - First run downloads the model (~142 MB for base_en).
+ - Use --verbose to see speech events and timing.
+ - Ctrl+C to stop.
+ - All outputs initialize in parallel.
diff --git a/src/args.rs b/src/args.rs
index 52dddfc..c27ff78 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -14,9 +14,15 @@ macro_rules! debug_log {
};
}
+/// The full help text embedded at compile time.
+pub static HELP_TEXT: &str = include_str!("../help.txt");
+
#[derive(clap::Parser)]
#[command(name = "dmvop", disable_help_flag = true, disable_version_flag = true)]
pub struct DMVOPArguments {
+ // Show help
+ #[arg(long = "help", short = 'h')]
+ pub help: bool,
// Verbose output (show debug messages)
#[arg(long = "verbose", short = 'V')]
pub verbose: bool,
diff --git a/src/main.rs b/src/main.rs
index 875ac39..15f23f7 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,8 +8,6 @@ use std::path::PathBuf;
use std::sync::Arc;
use vtx_engine::EngineBuilder;
-/// Output channel enum — wraps each OutputProtocol implementation so we can
-/// store a heterogeneous collection and dispatch `send` without trait objects.
enum OutputChannel {
Stdout(Arc<output_protocol::StandardOutputProtocol>),
Stderr(Arc<output_protocol::StandardErrorProtocol>),
@@ -58,7 +56,19 @@ async fn main() {
.with_target(false)
.init();
- let args = DMVOPArguments::parse();
+ let args = match DMVOPArguments::try_parse() {
+ Ok(a) => a,
+ Err(_) => {
+ eprintln!("error: invalid arguments. Use --help for usage.");
+ std::process::exit(1);
+ }
+ };
+
+ // Handle --help immediately
+ if args.help {
+ print!("{}", HELP_TEXT);
+ return;
+ }
// Set global verbose flag
VERBOSE.store(args.verbose, std::sync::atomic::Ordering::Relaxed);
@@ -86,7 +96,7 @@ async fn main() {
}
// ---------------------------------------------------------------
- // 1. Build the vtx-engine (needed for both listing and capture)
+ // Build the vtx-engine (needed for both listing and capture)
// ---------------------------------------------------------------
debug_log!("[dmvop] Initializing voice engine...");
@@ -138,7 +148,7 @@ async fn main() {
}
// ---------------------------------------------------------------
- // 2. List devices and exit?
+ // List devices and exit?
// ---------------------------------------------------------------
let devices = engine.list_input_devices();
@@ -158,12 +168,12 @@ async fn main() {
}
// ---------------------------------------------------------------
- // 3. Resolve the format pattern
+ // Resolve the format pattern
// ---------------------------------------------------------------
let pattern = resolve_format_pattern(args.format_pattern.as_deref(), args.format_file.as_ref());
// ---------------------------------------------------------------
- // 4. Create and initialize output channels
+ // Create and initialize output channels
// ---------------------------------------------------------------
let mut channels: Vec<OutputChannel> = Vec::new();
@@ -186,7 +196,7 @@ async fn main() {
}
// ---------------------------------------------------------------
- // 5. Find the requested device and start capture
+ // Find the requested device and start capture
// ---------------------------------------------------------------
let device_name = match &args.device_name {
Some(n) => n.as_str(),
@@ -224,7 +234,7 @@ async fn main() {
debug_log!("[dmvop] Capture started. Waiting for speech...");
// ---------------------------------------------------------------
- // 5. Event loop — listen for transcription & audio level events
+ // Event loop — listen for transcription & audio level events
// ---------------------------------------------------------------
let mut last_volume_db: f32 = -60.0;
diff --git a/src/output_protocol/ipc.rs b/src/output_protocol/ipc.rs
index fb6df80..ee27e42 100644
--- a/src/output_protocol/ipc.rs
+++ b/src/output_protocol/ipc.rs
@@ -60,8 +60,6 @@ impl OutputProtocol for IPCOutputProtocol {
debug_log!("[IPC] Connected to {}", self.socket_path.display());
let bytes = format!("{}\n", message);
let _ = stream.writable().await;
- // We can't use the stream directly here since we need to store it
- // for future sends. Let's store and send.
let _ = stream.try_write(bytes.as_bytes());
*guard = Some(stream);
}