[feat] add TTY-aware progress management with indicatif
and file-specific progress bars
This commit is contained in:
44
Cargo.lock
generated
44
Cargo.lock
generated
@@ -103,6 +103,17 @@ version = "1.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -588,6 +599,15 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
@@ -1126,11 +1146,13 @@ name = "polyscribe"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"atty",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
"clap_mangen",
|
"clap_mangen",
|
||||||
"cliclack",
|
"cliclack",
|
||||||
|
"indicatif",
|
||||||
"libc",
|
"libc",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1945,6 +1967,28 @@ dependencies = [
|
|||||||
"fs_extra",
|
"fs_extra",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
|
@@ -30,6 +30,8 @@ sha2 = "0.10"
|
|||||||
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"
|
cliclack = "0.3"
|
||||||
|
indicatif = "0.17"
|
||||||
|
atty = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
@@ -35,12 +35,14 @@ pub trait TranscribeBackend {
|
|||||||
/// - speaker: label to attach to all produced segments.
|
/// - speaker: label to attach to all produced segments.
|
||||||
/// - lang_opt: optional language hint (e.g., "en"); None means auto/multilingual model default.
|
/// - lang_opt: optional language hint (e.g., "en"); None means auto/multilingual model default.
|
||||||
/// - gpu_layers: optional GPU layer count if applicable (ignored by some backends).
|
/// - gpu_layers: optional GPU layer count if applicable (ignored by some backends).
|
||||||
|
/// - progress_cb: optional callback receiving percentage [0..=100] updates.
|
||||||
fn transcribe(
|
fn transcribe(
|
||||||
&self,
|
&self,
|
||||||
audio_path: &Path,
|
audio_path: &Path,
|
||||||
speaker: &str,
|
speaker: &str,
|
||||||
lang_opt: Option<&str>,
|
lang_opt: Option<&str>,
|
||||||
gpu_layers: Option<u32>,
|
gpu_layers: Option<u32>,
|
||||||
|
progress_cb: Option<&(dyn Fn(i32) + Send + Sync)>,
|
||||||
) -> Result<Vec<OutputEntry>>;
|
) -> Result<Vec<OutputEntry>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,8 +150,9 @@ impl TranscribeBackend for CpuBackend {
|
|||||||
speaker: &str,
|
speaker: &str,
|
||||||
lang_opt: Option<&str>,
|
lang_opt: Option<&str>,
|
||||||
_gpu_layers: Option<u32>,
|
_gpu_layers: Option<u32>,
|
||||||
|
progress_cb: Option<&(dyn Fn(i32) + Send + Sync)>,
|
||||||
) -> Result<Vec<OutputEntry>> {
|
) -> Result<Vec<OutputEntry>> {
|
||||||
transcribe_with_whisper_rs(audio_path, speaker, lang_opt)
|
transcribe_with_whisper_rs(audio_path, speaker, lang_opt, progress_cb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,9 +166,10 @@ impl TranscribeBackend for CudaBackend {
|
|||||||
speaker: &str,
|
speaker: &str,
|
||||||
lang_opt: Option<&str>,
|
lang_opt: Option<&str>,
|
||||||
_gpu_layers: Option<u32>,
|
_gpu_layers: Option<u32>,
|
||||||
|
progress_cb: Option<&(dyn Fn(i32) + Send + Sync)>,
|
||||||
) -> Result<Vec<OutputEntry>> {
|
) -> Result<Vec<OutputEntry>> {
|
||||||
// whisper-rs uses enabled CUDA feature at build time; call same code path
|
// whisper-rs uses enabled CUDA feature at build time; call same code path
|
||||||
transcribe_with_whisper_rs(audio_path, speaker, lang_opt)
|
transcribe_with_whisper_rs(audio_path, speaker, lang_opt, progress_cb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,8 +183,9 @@ impl TranscribeBackend for HipBackend {
|
|||||||
speaker: &str,
|
speaker: &str,
|
||||||
lang_opt: Option<&str>,
|
lang_opt: Option<&str>,
|
||||||
_gpu_layers: Option<u32>,
|
_gpu_layers: Option<u32>,
|
||||||
|
progress_cb: Option<&(dyn Fn(i32) + Send + Sync)>,
|
||||||
) -> Result<Vec<OutputEntry>> {
|
) -> Result<Vec<OutputEntry>> {
|
||||||
transcribe_with_whisper_rs(audio_path, speaker, lang_opt)
|
transcribe_with_whisper_rs(audio_path, speaker, lang_opt, progress_cb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +199,7 @@ impl TranscribeBackend for VulkanBackend {
|
|||||||
_speaker: &str,
|
_speaker: &str,
|
||||||
_lang_opt: Option<&str>,
|
_lang_opt: Option<&str>,
|
||||||
_gpu_layers: Option<u32>,
|
_gpu_layers: Option<u32>,
|
||||||
|
_progress_cb: Option<&(dyn Fn(i32) + Send + Sync)>,
|
||||||
) -> Result<Vec<OutputEntry>> {
|
) -> Result<Vec<OutputEntry>> {
|
||||||
Err(anyhow!(
|
Err(anyhow!(
|
||||||
"Vulkan backend not yet wired to whisper.cpp FFI. Build with --features gpu-vulkan and ensure Vulkan SDK is installed. How to fix: install Vulkan loader (libvulkan), set VULKAN_SDK, and run cargo build --features gpu-vulkan."
|
"Vulkan backend not yet wired to whisper.cpp FFI. Build with --features gpu-vulkan and ensure Vulkan SDK is installed. How to fix: install Vulkan loader (libvulkan), set VULKAN_SDK, and run cargo build --features gpu-vulkan."
|
||||||
@@ -301,8 +307,13 @@ pub(crate) fn transcribe_with_whisper_rs(
|
|||||||
audio_path: &Path,
|
audio_path: &Path,
|
||||||
speaker: &str,
|
speaker: &str,
|
||||||
lang_opt: Option<&str>,
|
lang_opt: Option<&str>,
|
||||||
|
progress_cb: Option<&(dyn Fn(i32) + Send + Sync)>,
|
||||||
) -> Result<Vec<OutputEntry>> {
|
) -> Result<Vec<OutputEntry>> {
|
||||||
|
if let Some(cb) = progress_cb { cb(0); }
|
||||||
|
|
||||||
let pcm = decode_audio_to_pcm_f32_ffmpeg(audio_path)?;
|
let pcm = decode_audio_to_pcm_f32_ffmpeg(audio_path)?;
|
||||||
|
if let Some(cb) = progress_cb { cb(5); }
|
||||||
|
|
||||||
let model = find_model_file()?;
|
let model = find_model_file()?;
|
||||||
let is_en_only = model
|
let is_en_only = model
|
||||||
.file_name()
|
.file_name()
|
||||||
@@ -341,6 +352,7 @@ pub(crate) fn transcribe_with_whisper_rs(
|
|||||||
.map_err(|e| anyhow!("Failed to create Whisper state: {:?}", e))?;
|
.map_err(|e| anyhow!("Failed to create Whisper state: {:?}", e))?;
|
||||||
Ok::<_, anyhow::Error>((ctx, state))
|
Ok::<_, anyhow::Error>((ctx, state))
|
||||||
})?;
|
})?;
|
||||||
|
if let Some(cb) = progress_cb { cb(20); }
|
||||||
|
|
||||||
let mut params =
|
let mut params =
|
||||||
whisper_rs::FullParams::new(whisper_rs::SamplingStrategy::Greedy { best_of: 1 });
|
whisper_rs::FullParams::new(whisper_rs::SamplingStrategy::Greedy { best_of: 1 });
|
||||||
@@ -352,13 +364,16 @@ pub(crate) fn transcribe_with_whisper_rs(
|
|||||||
if let Some(lang) = lang_opt {
|
if let Some(lang) = lang_opt {
|
||||||
params.set_language(Some(lang));
|
params.set_language(Some(lang));
|
||||||
}
|
}
|
||||||
|
if let Some(cb) = progress_cb { cb(30); }
|
||||||
|
|
||||||
crate::with_suppressed_stderr(|| {
|
crate::with_suppressed_stderr(|| {
|
||||||
|
if let Some(cb) = progress_cb { cb(40); }
|
||||||
state
|
state
|
||||||
.full(params, &pcm)
|
.full(params, &pcm)
|
||||||
.map_err(|e| anyhow!("Whisper full() failed: {:?}", e))
|
.map_err(|e| anyhow!("Whisper full() failed: {:?}", e))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
if let Some(cb) = progress_cb { cb(90); }
|
||||||
let num_segments = state
|
let num_segments = state
|
||||||
.full_n_segments()
|
.full_n_segments()
|
||||||
.map_err(|e| anyhow!("Failed to get segments: {:?}", e))?;
|
.map_err(|e| anyhow!("Failed to get segments: {:?}", e))?;
|
||||||
@@ -383,5 +398,6 @@ pub(crate) fn transcribe_with_whisper_rs(
|
|||||||
text: text.trim().to_string(),
|
text: text.trim().to_string(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if let Some(cb) = progress_cb { cb(100); }
|
||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
89
src/lib.rs
89
src/lib.rs
@@ -217,6 +217,95 @@ pub mod ui {
|
|||||||
io::stdin().read_line(&mut s)?;
|
io::stdin().read_line(&mut s)?;
|
||||||
Ok(s)
|
Ok(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Progress manager built on indicatif MultiProgress for per-file and aggregate bars
|
||||||
|
/// TTY-aware progress UI built on `indicatif` for per-file and aggregate progress bars.
|
||||||
|
///
|
||||||
|
/// This small helper encapsulates a `MultiProgress` with one aggregate (total) bar and
|
||||||
|
/// one per-file bar. It is intentionally minimal to keep integration lightweight.
|
||||||
|
pub mod progress {
|
||||||
|
use atty::Stream;
|
||||||
|
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||||
|
|
||||||
|
/// Manages a set of per-file progress bars plus a top aggregate bar.
|
||||||
|
pub struct ProgressManager {
|
||||||
|
enabled: bool,
|
||||||
|
mp: Option<MultiProgress>,
|
||||||
|
per: Vec<ProgressBar>,
|
||||||
|
total: Option<ProgressBar>,
|
||||||
|
total_n: usize,
|
||||||
|
completed: usize,
|
||||||
|
done: Vec<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProgressManager {
|
||||||
|
/// Create a new manager with the given enabled flag.
|
||||||
|
pub fn new(enabled: bool) -> Self {
|
||||||
|
Self { enabled, mp: None, per: Vec::new(), total: None, total_n: 0, completed: 0, done: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a manager that enables bars when `n > 1`, stderr is a TTY, and not quiet.
|
||||||
|
pub fn default_for_files(n: usize) -> Self {
|
||||||
|
let enabled = n > 1 && atty::is(Stream::Stderr) && !crate::is_quiet();
|
||||||
|
Self::new(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize bars for the given file labels. If disabled or single file, no-op.
|
||||||
|
pub fn init_files(&mut self, labels: &[String]) {
|
||||||
|
self.total_n = labels.len();
|
||||||
|
if !self.enabled || self.total_n <= 1 {
|
||||||
|
// No bars in single-file mode or when disabled
|
||||||
|
self.enabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mp = MultiProgress::new();
|
||||||
|
// Aggregate bar at the top
|
||||||
|
let total = mp.add(ProgressBar::new(labels.len() as u64));
|
||||||
|
total.set_style(ProgressStyle::with_template("{prefix} [{bar:40.cyan/blue}] {pos}/{len}")
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("=>-"));
|
||||||
|
total.set_prefix("Total");
|
||||||
|
self.total = Some(total);
|
||||||
|
// Per-file bars
|
||||||
|
for label in labels {
|
||||||
|
let pb = mp.add(ProgressBar::new(100));
|
||||||
|
pb.set_style(ProgressStyle::with_template("{prefix} [{bar:40.green/black}] {pos}% {msg}")
|
||||||
|
.unwrap()
|
||||||
|
.progress_chars("=>-"));
|
||||||
|
pb.set_position(0);
|
||||||
|
pb.set_prefix(label.clone());
|
||||||
|
self.per.push(pb);
|
||||||
|
}
|
||||||
|
self.mp = Some(mp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true when bars are enabled (multi-file TTY mode).
|
||||||
|
pub fn is_enabled(&self) -> bool { self.enabled }
|
||||||
|
|
||||||
|
/// Get a clone of the per-file progress bar at index, if enabled.
|
||||||
|
pub fn per_bar(&self, idx: usize) -> Option<ProgressBar> {
|
||||||
|
if !self.enabled { return None; }
|
||||||
|
self.per.get(idx).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a clone of the aggregate (total) progress bar, if enabled.
|
||||||
|
pub fn total_bar(&self) -> Option<ProgressBar> {
|
||||||
|
if !self.enabled { return None; }
|
||||||
|
self.total.as_ref().cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark a file as finished (set to 100% and update total counter).
|
||||||
|
pub fn mark_file_done(&mut self, idx: usize) {
|
||||||
|
if !self.enabled { return; }
|
||||||
|
if let Some(pb) = self.per.get(idx) {
|
||||||
|
pb.set_position(100);
|
||||||
|
pb.finish_with_message("done");
|
||||||
|
}
|
||||||
|
self.completed += 1;
|
||||||
|
if let Some(total) = &self.total { total.set_position(self.completed as u64); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Logging macros and helpers
|
/// Logging macros and helpers
|
||||||
|
104
src/main.rs
104
src/main.rs
@@ -332,6 +332,11 @@ fn run() -> Result<()> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
// Initialize multi-file progress bars (TTY-aware); suppressed for single-file/non-TTY/quiet
|
||||||
|
let mut pm = polyscribe::ui::progress::ProgressManager::default_for_files(speakers.len());
|
||||||
|
// Use speaker names (derived from file names or prompted) as labels
|
||||||
|
pm.init_files(&speakers);
|
||||||
|
|
||||||
if args.merge_and_separate {
|
if args.merge_and_separate {
|
||||||
polyscribe::dlog!(1, "Mode: merge-and-separate; output_dir={:?}", output_path);
|
polyscribe::dlog!(1, "Mode: merge-and-separate; output_dir={:?}", output_path);
|
||||||
// Combined mode: write separate outputs per input and also a merged output set
|
// Combined mode: write separate outputs per input and also a merged output set
|
||||||
@@ -356,20 +361,41 @@ fn run() -> Result<()> {
|
|||||||
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;
|
summary_audio_count += 1;
|
||||||
// Progress log to stderr (suppressed by -q); avoid partial lines
|
// Progress log only when multi-bars are not enabled
|
||||||
polyscribe::ilog!("Processing file: {} ...", path.display());
|
if !pm.is_enabled() {
|
||||||
|
polyscribe::ilog!("Processing file: {} ...", path.display());
|
||||||
|
}
|
||||||
|
// Prepare per-file progress callback if multi-bars enabled
|
||||||
|
let mut cb_holder: Option<Box<dyn Fn(i32) + Send + Sync>> = None;
|
||||||
|
if let Some(pb) = pm.per_bar(idx) {
|
||||||
|
let pb = pb.clone();
|
||||||
|
cb_holder = Some(Box::new(move |p: i32| {
|
||||||
|
let p = p.clamp(0, 100) as u64;
|
||||||
|
pb.set_position(p);
|
||||||
|
}));
|
||||||
|
}
|
||||||
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
||||||
|
let cb_ref = cb_holder.as_ref().map(|b| &**b as &(dyn Fn(i32) + Send + Sync));
|
||||||
sel.backend
|
sel.backend
|
||||||
.transcribe(path, &speaker, lang_hint.as_deref(), args.gpu_layers)
|
.transcribe(path, &speaker, lang_hint.as_deref(), args.gpu_layers, cb_ref)
|
||||||
});
|
});
|
||||||
match res {
|
match res {
|
||||||
Ok(items) => {
|
Ok(items) => {
|
||||||
polyscribe::ilog!("done");
|
if pm.is_enabled() {
|
||||||
|
pm.mark_file_done(idx);
|
||||||
|
} else {
|
||||||
|
polyscribe::ilog!("done");
|
||||||
|
}
|
||||||
entries.extend(items.into_iter());
|
entries.extend(items.into_iter());
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if !polyscribe::is_no_interaction() && polyscribe::stdin_is_tty() {
|
if let Some(pb) = pm.per_bar(idx) {
|
||||||
polyscribe::elog!("{:#}", e);
|
pb.finish_with_message("error");
|
||||||
|
}
|
||||||
|
if !pm.is_enabled() {
|
||||||
|
if !polyscribe::is_no_interaction() && polyscribe::stdin_is_tty() {
|
||||||
|
polyscribe::elog!("{:#}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
@@ -504,23 +530,44 @@ 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;
|
summary_audio_count += 1;
|
||||||
// Progress log to stderr (suppressed by -q)
|
// Progress log only when multi-bars are not enabled
|
||||||
polyscribe::ilog!("Processing file: {} ...", path.display());
|
if !pm.is_enabled() {
|
||||||
|
polyscribe::ilog!("Processing file: {} ...", path.display());
|
||||||
|
}
|
||||||
|
// Prepare per-file progress callback if multi-bars enabled
|
||||||
|
let mut cb_holder: Option<Box<dyn Fn(i32) + Send + Sync>> = None;
|
||||||
|
if let Some(pb) = pm.per_bar(idx) {
|
||||||
|
let pb = pb.clone();
|
||||||
|
cb_holder = Some(Box::new(move |p: i32| {
|
||||||
|
let p = p.clamp(0, 100) as u64;
|
||||||
|
pb.set_position(p);
|
||||||
|
}));
|
||||||
|
}
|
||||||
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
||||||
|
let cb_ref = cb_holder.as_ref().map(|b| &**b as &(dyn Fn(i32) + Send + Sync));
|
||||||
sel.backend
|
sel.backend
|
||||||
.transcribe(path, &speaker, lang_hint.as_deref(), args.gpu_layers)
|
.transcribe(path, &speaker, lang_hint.as_deref(), args.gpu_layers, cb_ref)
|
||||||
});
|
});
|
||||||
match res {
|
match res {
|
||||||
Ok(items) => {
|
Ok(items) => {
|
||||||
polyscribe::ilog!("done");
|
if pm.is_enabled() {
|
||||||
|
pm.mark_file_done(idx);
|
||||||
|
} else {
|
||||||
|
polyscribe::ilog!("done");
|
||||||
|
}
|
||||||
for e in items {
|
for e in items {
|
||||||
entries.push(e);
|
entries.push(e);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if !(polyscribe::is_no_interaction() || !polyscribe::stdin_is_tty()) {
|
if let Some(pb) = pm.per_bar(idx) {
|
||||||
polyscribe::elog!("{:#}", e);
|
pb.finish_with_message("error");
|
||||||
|
}
|
||||||
|
if !pm.is_enabled() {
|
||||||
|
if !(polyscribe::is_no_interaction() || !polyscribe::stdin_is_tty()) {
|
||||||
|
polyscribe::elog!("{:#}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
@@ -645,20 +692,41 @@ fn run() -> Result<()> {
|
|||||||
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;
|
summary_audio_count += 1;
|
||||||
// Progress log to stderr (suppressed by -q)
|
// Progress log only when multi-bars are not enabled
|
||||||
polyscribe::ilog!("Processing file: {} ...", path.display());
|
if !pm.is_enabled() {
|
||||||
|
polyscribe::ilog!("Processing file: {} ...", path.display());
|
||||||
|
}
|
||||||
|
// Prepare per-file progress callback if multi-bars enabled
|
||||||
|
let mut cb_holder: Option<Box<dyn Fn(i32) + Send + Sync>> = None;
|
||||||
|
if let Some(pb) = pm.per_bar(idx) {
|
||||||
|
let pb = pb.clone();
|
||||||
|
cb_holder = Some(Box::new(move |p: i32| {
|
||||||
|
let p = p.clamp(0, 100) as u64;
|
||||||
|
pb.set_position(p);
|
||||||
|
}));
|
||||||
|
}
|
||||||
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
let res = with_quiet_stdio_if_needed(args.quiet, || {
|
||||||
|
let cb_ref = cb_holder.as_ref().map(|b| &**b as &(dyn Fn(i32) + Send + Sync));
|
||||||
sel.backend
|
sel.backend
|
||||||
.transcribe(path, &speaker, lang_hint.as_deref(), args.gpu_layers)
|
.transcribe(path, &speaker, lang_hint.as_deref(), args.gpu_layers, cb_ref)
|
||||||
});
|
});
|
||||||
match res {
|
match res {
|
||||||
Ok(items) => {
|
Ok(items) => {
|
||||||
polyscribe::ilog!("done");
|
if pm.is_enabled() {
|
||||||
|
pm.mark_file_done(idx);
|
||||||
|
} else {
|
||||||
|
polyscribe::ilog!("done");
|
||||||
|
}
|
||||||
entries.extend(items);
|
entries.extend(items);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if !polyscribe::is_no_interaction() && polyscribe::stdin_is_tty() {
|
if let Some(pb) = pm.per_bar(idx) {
|
||||||
polyscribe::elog!("{:#}", e);
|
pb.finish_with_message("error");
|
||||||
|
}
|
||||||
|
if !pm.is_enabled() {
|
||||||
|
if !polyscribe::is_no_interaction() && polyscribe::stdin_is_tty() {
|
||||||
|
polyscribe::elog!("{:#}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
|
@@ -419,7 +419,7 @@ fn prompt_select_models_two_stage(models: &[ModelEntry]) -> Result<Vec<ModelEntr
|
|||||||
crate::ui::println_above_bars(format!(" {}) {}", i + 1, b));
|
crate::ui::println_above_bars(format!(" {}) {}", i + 1, b));
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
let mut line = match crate::ui::prompt_line("Select base (number or name, 'q' to cancel): ") {
|
let line = match crate::ui::prompt_line("Select base (number or name, 'q' to cancel): ") {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(_) => String::new(),
|
Err(_) => String::new(),
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user