[feat] integrate cliclack for TTY-aware UI, add summaries and intro/outro helpers
This commit is contained in:
112
Cargo.lock
generated
112
Cargo.lock
generated
@@ -285,6 +285,20 @@ dependencies = [
|
|||||||
"roff",
|
"roff",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cliclack"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c420bdc04c123a2df04d9c5a07289195f00007af6e45ab18f55e56dc7e04b8"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"indicatif",
|
||||||
|
"once_cell",
|
||||||
|
"strsim",
|
||||||
|
"textwrap",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmake"
|
name = "cmake"
|
||||||
version = "0.1.54"
|
version = "0.1.54"
|
||||||
@@ -300,6 +314,19 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.15.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
"unicode-width",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@@ -362,6 +389,12 @@ version = "1.15.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.35"
|
version = "0.8.35"
|
||||||
@@ -814,6 +847,19 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indicatif"
|
||||||
|
version = "0.17.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"number_prefix",
|
||||||
|
"portable-atomic",
|
||||||
|
"unicode-width",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "io-uring"
|
name = "io-uring"
|
||||||
version = "0.7.9"
|
version = "0.7.9"
|
||||||
@@ -980,6 +1026,12 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "number_prefix"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.7"
|
version = "0.36.7"
|
||||||
@@ -1078,6 +1130,7 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"clap_mangen",
|
"clap_mangen",
|
||||||
|
"cliclack",
|
||||||
"libc",
|
"libc",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1088,6 +1141,12 @@ dependencies = [
|
|||||||
"whisper-rs",
|
"whisper-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "potential_utf"
|
name = "potential_utf"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@@ -1406,6 +1465,12 @@ version = "1.15.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smawk"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
@@ -1499,6 +1564,17 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.16.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
||||||
|
dependencies = [
|
||||||
|
"smawk",
|
||||||
|
"unicode-linebreak",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
@@ -1682,6 +1758,18 @@ version = "1.0.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-linebreak"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "untrusted"
|
name = "untrusted"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@@ -1828,6 +1916,16 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-time"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "whisper-rs"
|
name = "whisper-rs"
|
||||||
version = "0.14.3"
|
version = "0.14.3"
|
||||||
@@ -2147,6 +2245,20 @@ name = "zeroize"
|
|||||||
version = "1.8.1"
|
version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize_derive"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerotrie"
|
name = "zerotrie"
|
||||||
|
@@ -29,6 +29,7 @@ sha2 = "0.10"
|
|||||||
# whisper-rs is always used (CPU-only by default); GPU features map onto it
|
# whisper-rs is always used (CPU-only by default); GPU features map onto it
|
||||||
whisper-rs = { git = "https://github.com/tazz4843/whisper-rs" }
|
whisper-rs = { git = "https://github.com/tazz4843/whisper-rs" }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
cliclack = "0.3"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
100
src/lib.rs
100
src/lib.rs
@@ -173,50 +173,82 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Centralized UI helpers (TTY-aware, quiet/verbose-aware)
|
||||||
|
pub mod ui {
|
||||||
|
use std::io;
|
||||||
|
// Prefer cliclack for all user-visible messages to ensure consistent, TTY-aware output.
|
||||||
|
// Falls back to stderr printing if needed.
|
||||||
|
/// Startup intro/banner (suppressed when quiet).
|
||||||
|
pub fn intro(msg: impl AsRef<str>) {
|
||||||
|
if crate::is_quiet() { return; }
|
||||||
|
// Use cliclack intro to render a nice banner when TTY
|
||||||
|
let _ = cliclack::intro(msg.as_ref());
|
||||||
|
}
|
||||||
|
/// Print an informational line (suppressed when quiet).
|
||||||
|
pub fn info(msg: impl AsRef<str>) {
|
||||||
|
if crate::is_quiet() { return; }
|
||||||
|
let _ = cliclack::log::info(msg.as_ref());
|
||||||
|
}
|
||||||
|
/// Print a warning (always printed).
|
||||||
|
pub fn warn(msg: impl AsRef<str>) {
|
||||||
|
// cliclack provides a warning-level log utility
|
||||||
|
let _ = cliclack::log::warning(msg.as_ref());
|
||||||
|
}
|
||||||
|
/// Print an error (always printed).
|
||||||
|
pub fn error(msg: impl AsRef<str>) {
|
||||||
|
let _ = cliclack::log::error(msg.as_ref());
|
||||||
|
}
|
||||||
|
/// Print a line above any progress bars (maps to cliclack log; synchronized).
|
||||||
|
pub fn println_above_bars(msg: impl AsRef<str>) {
|
||||||
|
if crate::is_quiet() { return; }
|
||||||
|
// cliclack logs are synchronized with its spinners/bars
|
||||||
|
let _ = cliclack::log::info(msg.as_ref());
|
||||||
|
}
|
||||||
|
/// Final outro/summary printed below any progress indicators (suppressed when quiet).
|
||||||
|
pub fn outro(msg: impl AsRef<str>) {
|
||||||
|
if crate::is_quiet() { return; }
|
||||||
|
let _ = cliclack::outro(msg.as_ref());
|
||||||
|
}
|
||||||
|
/// Prompt the user (TTY-aware via cliclack) and read a line from stdin. Returns the raw line with trailing newline removed.
|
||||||
|
pub fn prompt_line(prompt: &str) -> io::Result<String> {
|
||||||
|
// Route prompt through cliclack to keep consistent styling and avoid direct eprint!/println!
|
||||||
|
let _ = cliclack::log::info(prompt);
|
||||||
|
let mut s = String::new();
|
||||||
|
io::stdin().read_line(&mut s)?;
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Logging macros and helpers
|
/// Logging macros and helpers
|
||||||
/// Log an error to stderr (always printed). Recommended for user-visible errors.
|
/// Log an error using the UI helper (always printed). Recommended for user-visible errors.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! elog {
|
macro_rules! elog {
|
||||||
($($arg:tt)*) => {{
|
($($arg:tt)*) => {{
|
||||||
eprintln!("ERROR: {}", format!($($arg)*));
|
$crate::ui::error(format!($($arg)*));
|
||||||
}}
|
|
||||||
}
|
|
||||||
/// Internal helper macro used by other logging macros to centralize the
|
|
||||||
/// common behavior: build formatted message, check quiet/verbose flags,
|
|
||||||
/// and print to stderr with a label.
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! log_with_level {
|
|
||||||
($label:expr, $min_lvl:expr, $always:expr, $($arg:tt)*) => {{
|
|
||||||
let should_print = if $always {
|
|
||||||
true
|
|
||||||
} else if let Some(minv) = $min_lvl {
|
|
||||||
!$crate::is_quiet() && $crate::verbose_level() >= minv
|
|
||||||
} else {
|
|
||||||
!$crate::is_quiet()
|
|
||||||
};
|
|
||||||
if should_print {
|
|
||||||
eprintln!("{}: {}", $label, format!($($arg)*));
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Log a warning to stderr (printed even in quiet mode).
|
/// Log a warning using the UI helper (printed even in quiet mode).
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! wlog {
|
macro_rules! wlog {
|
||||||
($($arg:tt)*) => {{ $crate::log_with_level!("WARN", None, true, $($arg)*); }}
|
($($arg:tt)*) => {{
|
||||||
|
$crate::ui::warn(format!($($arg)*));
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Log an informational line to stderr unless quiet mode is enabled.
|
/// Log an informational line using the UI helper unless quiet mode is enabled.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! ilog {
|
macro_rules! ilog {
|
||||||
($($arg:tt)*) => {{ $crate::log_with_level!("INFO", None, false, $($arg)*); }}
|
($($arg:tt)*) => {{
|
||||||
|
if !$crate::is_quiet() { $crate::ui::info(format!($($arg)*)); }
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Log a debug/trace line when verbose level is at least the given level (u8).
|
/// Log a debug/trace line when verbose level is at least the given level (u8).
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! dlog {
|
macro_rules! dlog {
|
||||||
($lvl:expr, $($arg:tt)*) => {{
|
($lvl:expr, $($arg:tt)*) => {{
|
||||||
$crate::log_with_level!(&format!("DEBUG{}", &$lvl), Some($lvl), false, $($arg)*);
|
if !$crate::is_quiet() && $crate::verbose_level() >= $lvl { $crate::ui::info(format!("DEBUG{}: {}", $lvl, format!($($arg)*))); }
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,7 +262,6 @@ use anyhow::{Context, Result, anyhow};
|
|||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::create_dir_all;
|
use std::fs::create_dir_all;
|
||||||
use std::io::{self, Write};
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -462,10 +493,7 @@ pub fn find_model_file() -> Result<PathBuf> {
|
|||||||
"No models available and interactive mode is disabled. Please set WHISPER_MODEL or run with --download-models."
|
"No models available and interactive mode is disabled. Please set WHISPER_MODEL or run with --download-models."
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
eprint!("Would you like to download models now? [Y/n]: ");
|
let input = crate::ui::prompt_line("Would you like to download models now? [Y/n]: ").unwrap_or_default();
|
||||||
io::stderr().flush().ok();
|
|
||||||
let mut input = String::new();
|
|
||||||
io::stdin().read_line(&mut input).ok();
|
|
||||||
let ans = input.trim().to_lowercase();
|
let ans = input.trim().to_lowercase();
|
||||||
if ans.is_empty() || ans == "y" || ans == "yes" {
|
if ans.is_empty() || ans == "y" || ans == "yes" {
|
||||||
if let Err(e) = models::run_interactive_model_downloader() {
|
if let Err(e) = models::run_interactive_model_downloader() {
|
||||||
@@ -519,16 +547,12 @@ pub fn find_model_file() -> Result<PathBuf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!("Multiple Whisper models found in {}:", models_dir.display());
|
crate::ui::println_above_bars(format!("Multiple Whisper models found in {}:", models_dir.display()));
|
||||||
for (i, p) in candidates.iter().enumerate() {
|
for (i, p) in candidates.iter().enumerate() {
|
||||||
eprintln!(" {}) {}", i + 1, p.display());
|
crate::ui::println_above_bars(format!(" {}) {}", i + 1, p.display()));
|
||||||
}
|
}
|
||||||
eprint!("Select model by number [1-{}]: ", candidates.len());
|
let input = crate::ui::prompt_line(&format!("Select model by number [1-{}]: ", candidates.len()))
|
||||||
io::stderr().flush().ok();
|
.map_err(|_| anyhow!("Failed to read selection"))?;
|
||||||
let mut input = String::new();
|
|
||||||
io::stdin()
|
|
||||||
.read_line(&mut input)
|
|
||||||
.context("Failed to read selection")?;
|
|
||||||
let sel: usize = input
|
let sel: usize = input
|
||||||
.trim()
|
.trim()
|
||||||
.parse()
|
.parse()
|
||||||
|
61
src/main.rs
61
src/main.rs
@@ -142,25 +142,18 @@ fn prompt_speaker_name_for_path(path: &Path, default_name: &str, enabled: bool)
|
|||||||
.and_then(|s| s.to_str())
|
.and_then(|s| s.to_str())
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.unwrap_or_else(|| path.to_string_lossy().to_string());
|
.unwrap_or_else(|| path.to_string_lossy().to_string());
|
||||||
eprint!(
|
let buf = polyscribe::ui::prompt_line(&format!(
|
||||||
"Enter speaker name for {display_owned} [default: {default_name}]: "
|
"Enter speaker name for {display_owned} [default: {default_name}]: "
|
||||||
);
|
)).unwrap_or_default();
|
||||||
io::stderr().flush().ok();
|
let raw = buf.trim();
|
||||||
let mut buf = String::new();
|
if raw.is_empty() {
|
||||||
match io::stdin().read_line(&mut buf) {
|
return default_name.to_string();
|
||||||
Ok(_) => {
|
}
|
||||||
let raw = buf.trim();
|
let sanitized = sanitize_speaker_name(raw);
|
||||||
if raw.is_empty() {
|
if sanitized.is_empty() {
|
||||||
return default_name.to_string();
|
default_name.to_string()
|
||||||
}
|
} else {
|
||||||
let sanitized = sanitize_speaker_name(raw);
|
sanitized
|
||||||
if sanitized.is_empty() {
|
|
||||||
default_name.to_string()
|
|
||||||
} else {
|
|
||||||
sanitized
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => default_name.to_string(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,6 +210,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn run() -> Result<()> {
|
fn run() -> Result<()> {
|
||||||
|
let _t0 = std::time::Instant::now();
|
||||||
// Parse CLI
|
// Parse CLI
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
@@ -225,6 +219,9 @@ fn run() -> Result<()> {
|
|||||||
polyscribe::set_quiet(args.quiet);
|
polyscribe::set_quiet(args.quiet);
|
||||||
polyscribe::set_no_interaction(args.no_interaction);
|
polyscribe::set_no_interaction(args.no_interaction);
|
||||||
|
|
||||||
|
// Startup banner via UI (TTY-aware through cliclack), suppressed when quiet
|
||||||
|
polyscribe::ui::intro(format!("PolyScribe v{}", env!("CARGO_PKG_VERSION")));
|
||||||
|
|
||||||
// Handle auxiliary subcommands that write to stdout and exit early
|
// Handle auxiliary subcommands that write to stdout and exit early
|
||||||
if let Some(aux) = &args.aux {
|
if let Some(aux) = &args.aux {
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
@@ -266,6 +263,10 @@ fn run() -> Result<()> {
|
|||||||
polyscribe::dlog!(1, "Using backend: {:?}", sel.chosen);
|
polyscribe::dlog!(1, "Using backend: {:?}", sel.chosen);
|
||||||
|
|
||||||
// If requested, run the interactive model downloader first. If no inputs were provided, exit after downloading.
|
// If requested, run the interactive model downloader first. If no inputs were provided, exit after downloading.
|
||||||
|
let mut summary_inputs_total: usize = 0;
|
||||||
|
let mut summary_audio_count: usize = 0;
|
||||||
|
let mut summary_json_count: usize = 0;
|
||||||
|
let mut summary_segments_total: usize = 0;
|
||||||
if args.download_models {
|
if args.download_models {
|
||||||
if let Err(e) = polyscribe::models::run_interactive_model_downloader() {
|
if let Err(e) = polyscribe::models::run_interactive_model_downloader() {
|
||||||
polyscribe::elog!("Model downloader failed: {:#}", e);
|
polyscribe::elog!("Model downloader failed: {:#}", e);
|
||||||
@@ -290,6 +291,7 @@ fn run() -> Result<()> {
|
|||||||
// Determine inputs and optional output path
|
// Determine inputs and optional output path
|
||||||
polyscribe::dlog!(1, "Parsed {} input(s)", args.inputs.len());
|
polyscribe::dlog!(1, "Parsed {} input(s)", args.inputs.len());
|
||||||
let mut inputs = args.inputs;
|
let mut inputs = args.inputs;
|
||||||
|
summary_inputs_total = inputs.len();
|
||||||
let mut output_path = args.output;
|
let mut output_path = args.output;
|
||||||
if output_path.is_none() && inputs.len() >= 2 {
|
if output_path.is_none() && inputs.len() >= 2 {
|
||||||
if let Some(last) = inputs.last().cloned() {
|
if let Some(last) = inputs.last().cloned() {
|
||||||
@@ -353,6 +355,7 @@ fn run() -> Result<()> {
|
|||||||
// Collect entries per file and extend merged
|
// Collect entries per file and extend merged
|
||||||
let mut entries: Vec<OutputEntry> = Vec::new();
|
let mut entries: Vec<OutputEntry> = Vec::new();
|
||||||
if is_audio_file(path) {
|
if is_audio_file(path) {
|
||||||
|
summary_audio_count += 1;
|
||||||
// Progress log to stderr (suppressed by -q); avoid partial lines
|
// Progress log to stderr (suppressed by -q); avoid partial lines
|
||||||
polyscribe::ilog!("Processing file: {} ...", path.display());
|
polyscribe::ilog!("Processing file: {} ...", path.display());
|
||||||
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
||||||
@@ -372,6 +375,7 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if is_json_file(path) {
|
} else if is_json_file(path) {
|
||||||
|
summary_json_count += 1;
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
File::open(path)
|
File::open(path)
|
||||||
.with_context(|| format!("Failed to open: {input_path}"))?
|
.with_context(|| format!("Failed to open: {input_path}"))?
|
||||||
@@ -409,6 +413,7 @@ fn run() -> Result<()> {
|
|||||||
for (i, e) in entries.iter_mut().enumerate() {
|
for (i, e) in entries.iter_mut().enumerate() {
|
||||||
e.id = i as u64;
|
e.id = i as u64;
|
||||||
}
|
}
|
||||||
|
summary_segments_total += entries.len();
|
||||||
|
|
||||||
// Write separate outputs to out_dir
|
// Write separate outputs to out_dir
|
||||||
let out = OutputRoot {
|
let out = OutputRoot {
|
||||||
@@ -498,6 +503,7 @@ fn run() -> Result<()> {
|
|||||||
|
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
if is_audio_file(path) {
|
if is_audio_file(path) {
|
||||||
|
summary_audio_count += 1;
|
||||||
// Progress log to stderr (suppressed by -q)
|
// Progress log to stderr (suppressed by -q)
|
||||||
polyscribe::ilog!("Processing file: {} ...", path.display());
|
polyscribe::ilog!("Processing file: {} ...", path.display());
|
||||||
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
||||||
@@ -520,6 +526,7 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if is_json_file(path) {
|
} else if is_json_file(path) {
|
||||||
|
summary_json_count += 1;
|
||||||
File::open(path)
|
File::open(path)
|
||||||
.with_context(|| format!("Failed to open: {}", input_path))?
|
.with_context(|| format!("Failed to open: {}", input_path))?
|
||||||
.read_to_string(&mut buf)
|
.read_to_string(&mut buf)
|
||||||
@@ -559,6 +566,7 @@ fn run() -> Result<()> {
|
|||||||
e.id = i as u64;
|
e.id = i as u64;
|
||||||
}
|
}
|
||||||
let out = OutputRoot { items: entries };
|
let out = OutputRoot { items: entries };
|
||||||
|
summary_segments_total = out.items.len();
|
||||||
|
|
||||||
if let Some(path) = output_path {
|
if let Some(path) = output_path {
|
||||||
let base_path = Path::new(&path);
|
let base_path = Path::new(&path);
|
||||||
@@ -636,6 +644,7 @@ fn run() -> Result<()> {
|
|||||||
// Collect entries per file
|
// Collect entries per file
|
||||||
let mut entries: Vec<OutputEntry> = Vec::new();
|
let mut entries: Vec<OutputEntry> = Vec::new();
|
||||||
if is_audio_file(path) {
|
if is_audio_file(path) {
|
||||||
|
summary_audio_count += 1;
|
||||||
// Progress log to stderr (suppressed by -q)
|
// Progress log to stderr (suppressed by -q)
|
||||||
polyscribe::ilog!("Processing file: {} ...", path.display());
|
polyscribe::ilog!("Processing file: {} ...", path.display());
|
||||||
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
||||||
@@ -655,6 +664,7 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if is_json_file(path) {
|
} else if is_json_file(path) {
|
||||||
|
summary_json_count += 1;
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
File::open(path)
|
File::open(path)
|
||||||
.with_context(|| format!("Failed to open: {input_path}"))?
|
.with_context(|| format!("Failed to open: {input_path}"))?
|
||||||
@@ -692,6 +702,7 @@ fn run() -> Result<()> {
|
|||||||
for (i, e) in entries.iter_mut().enumerate() {
|
for (i, e) in entries.iter_mut().enumerate() {
|
||||||
e.id = i as u64;
|
e.id = i as u64;
|
||||||
}
|
}
|
||||||
|
summary_segments_total += entries.len();
|
||||||
let out = OutputRoot { items: entries };
|
let out = OutputRoot { items: entries };
|
||||||
|
|
||||||
if let Some(dir) = &out_dir {
|
if let Some(dir) = &out_dir {
|
||||||
@@ -736,6 +747,20 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Final summary (TTY-aware via UI), only when not quiet
|
||||||
|
if !polyscribe::is_quiet() {
|
||||||
|
let elapsed = _t0.elapsed();
|
||||||
|
let secs = elapsed.as_secs_f32();
|
||||||
|
let mut out = String::new();
|
||||||
|
out.push_str("Summary:\n");
|
||||||
|
out.push_str(&format!("{:<12} {:>8}\n", "Files:", summary_inputs_total));
|
||||||
|
out.push_str(&format!("{:<12} {:>8}\n", "Audio:", summary_audio_count));
|
||||||
|
out.push_str(&format!("{:<12} {:>8}\n", "JSON:", summary_json_count));
|
||||||
|
out.push_str(&format!("{:<12} {:>8}\n", "Segments:", summary_segments_total));
|
||||||
|
out.push_str(&format!("{:<12} {:>8.2}s\n", "Time:", secs));
|
||||||
|
polyscribe::ui::outro(out);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{File, create_dir_all};
|
use std::fs::{File, create_dir_all};
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -326,8 +326,8 @@ fn fetch_all_models(client: &Client) -> Result<Vec<ModelEntry>> {
|
|||||||
match hf_fetch_repo_models(client, "akashmjn/tinydiarize-whisper.cpp") {
|
match hf_fetch_repo_models(client, "akashmjn/tinydiarize-whisper.cpp") {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ilog!(
|
wlog!(
|
||||||
"Warning: failed to fetch optional repo akashmjn/tinydiarize-whisper.cpp: {:#}",
|
"Failed to fetch optional repo akashmjn/tinydiarize-whisper.cpp: {:#}",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
Vec::new()
|
Vec::new()
|
||||||
@@ -413,18 +413,16 @@ fn prompt_select_models_two_stage(models: &[ModelEntry]) -> Result<Vec<ModelEntr
|
|||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print base selection on stderr
|
// Print base selection via UI
|
||||||
eprintln!("Available base model families:");
|
crate::ui::println_above_bars("Available base model families:");
|
||||||
for (i, b) in bases.iter().enumerate() {
|
for (i, b) in bases.iter().enumerate() {
|
||||||
eprintln!(" {}) {}", i + 1, b);
|
crate::ui::println_above_bars(format!(" {}) {}", i + 1, b));
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
eprint!("Select base (number or name, 'q' to cancel): ");
|
let mut line = match crate::ui::prompt_line("Select base (number or name, 'q' to cancel): ") {
|
||||||
io::stderr().flush().ok();
|
Ok(s) => s,
|
||||||
let mut line = String::new();
|
Err(_) => String::new(),
|
||||||
io::stdin()
|
};
|
||||||
.read_line(&mut line)
|
|
||||||
.context("Failed to read base selection")?;
|
|
||||||
let s = line.trim();
|
let s = line.trim();
|
||||||
if s.eq_ignore_ascii_case("q")
|
if s.eq_ignore_ascii_case("q")
|
||||||
|| s.eq_ignore_ascii_case("quit")
|
|| s.eq_ignore_ascii_case("quit")
|
||||||
@@ -450,12 +448,12 @@ fn prompt_select_models_two_stage(models: &[ModelEntry]) -> Result<Vec<ModelEntr
|
|||||||
let filtered: Vec<ModelEntry> =
|
let filtered: Vec<ModelEntry> =
|
||||||
models.iter().filter(|m| m.base == base).cloned().collect();
|
models.iter().filter(|m| m.base == base).cloned().collect();
|
||||||
if filtered.is_empty() {
|
if filtered.is_empty() {
|
||||||
eprintln!("No models found for base '{base}'.");
|
crate::ui::warn(format!("No models found for base '{base}'."));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Reuse the formatter but only for the chosen base list
|
// Reuse the formatter but only for the chosen base list
|
||||||
let listing = format_model_list(&filtered);
|
let listing = format_model_list(&filtered);
|
||||||
eprint!("{listing}");
|
crate::ui::println_above_bars(listing);
|
||||||
|
|
||||||
// Build index map for filtered list
|
// Build index map for filtered list
|
||||||
let mut index_map: Vec<usize> = Vec::with_capacity(filtered.len());
|
let mut index_map: Vec<usize> = Vec::with_capacity(filtered.len());
|
||||||
@@ -466,12 +464,8 @@ fn prompt_select_models_two_stage(models: &[ModelEntry]) -> Result<Vec<ModelEntr
|
|||||||
}
|
}
|
||||||
// Second prompt: sub-type selection
|
// Second prompt: sub-type selection
|
||||||
loop {
|
loop {
|
||||||
eprint!("Selection: ");
|
let line2 = crate::ui::prompt_line("Selection: ")
|
||||||
io::stderr().flush().ok();
|
.map_err(|_| anyhow!("Failed to read selection"))?;
|
||||||
let mut line2 = String::new();
|
|
||||||
io::stdin()
|
|
||||||
.read_line(&mut line2)
|
|
||||||
.context("Failed to read selection")?;
|
|
||||||
let s2 = line2.trim().to_lowercase();
|
let s2 = line2.trim().to_lowercase();
|
||||||
if s2 == "q" || s2 == "quit" || s2 == "exit" {
|
if s2 == "q" || s2 == "quit" || s2 == "exit" {
|
||||||
return Ok(Vec::new());
|
return Ok(Vec::new());
|
||||||
@@ -501,7 +495,7 @@ fn prompt_select_models_two_stage(models: &[ModelEntry]) -> Result<Vec<ModelEntr
|
|||||||
selected.sort_unstable();
|
selected.sort_unstable();
|
||||||
selected.dedup();
|
selected.dedup();
|
||||||
if selected.is_empty() {
|
if selected.is_empty() {
|
||||||
eprintln!("No valid selection. Please try again or 'q' to cancel.");
|
crate::ui::warn("No valid selection. Please try again or 'q' to cancel.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let chosen: Vec<ModelEntry> = selected
|
let chosen: Vec<ModelEntry> = selected
|
||||||
@@ -511,10 +505,10 @@ fn prompt_select_models_two_stage(models: &[ModelEntry]) -> Result<Vec<ModelEntr
|
|||||||
return Ok(chosen);
|
return Ok(chosen);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!(
|
crate::ui::warn(format!(
|
||||||
"Invalid base selection. Please enter a number from 1-{} or a base name.",
|
"Invalid base selection. Please enter a number from 1-{} or a base name.",
|
||||||
bases.len()
|
bases.len()
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -591,8 +585,8 @@ fn download_one_model(client: &Client, models_dir: &Path, entry: &ModelEntry) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
qlog!(
|
wlog!(
|
||||||
"Warning: failed to hash existing {}: {}. Will re-download to ensure correctness.",
|
"Failed to hash existing {}: {}. Will re-download to ensure correctness.",
|
||||||
final_path.display(),
|
final_path.display(),
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
@@ -618,8 +612,8 @@ fn download_one_model(client: &Client, models_dir: &Path, entry: &ModelEntry) ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
qlog!(
|
wlog!(
|
||||||
"Warning: failed to stat existing {}: {}. Will re-download to ensure correctness.",
|
"Failed to stat existing {}: {}. Will re-download to ensure correctness.",
|
||||||
final_path.display(),
|
final_path.display(),
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
@@ -723,8 +717,8 @@ fn download_one_model(client: &Client, models_dir: &Path, entry: &ModelEntry) ->
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qlog!(
|
wlog!(
|
||||||
"Warning: no SHA-256 available for {}. Skipping verification.",
|
"No SHA-256 available for {}. Skipping verification.",
|
||||||
entry.name
|
entry.name
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -826,7 +820,7 @@ pub fn update_local_models() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
qlog!("Warning: failed hashing {}: {}. Re-downloading.", fname, e);
|
wlog!("Failed hashing {}: {}. Re-downloading.", fname, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
download_one_model(&client, models_dir, remote)?;
|
download_one_model(&client, models_dir, remote)?;
|
||||||
@@ -839,7 +833,7 @@ pub fn update_local_models() -> Result<()> {
|
|||||||
download_one_model(&client, models_dir, remote)?;
|
download_one_model(&client, models_dir, remote)?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
qlog!("Warning: stat failed for {}: {}. Updating...", fname, e);
|
wlog!("Stat failed for {}: {}. Updating...", fname, e);
|
||||||
download_one_model(&client, models_dir, remote)?;
|
download_one_model(&client, models_dir, remote)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user