Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/releases/UNRELEASED.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ then reset this file.
preserves completed segments and only re-transcribes the unfinished tail.
- PickScribe now finds `whisper-cli` in `~/.local/bin` when desktop sessions omit
that directory from `PATH`.
- Whisper model auto-detection now recognizes English-only `.en` models and
other `ggml-*.bin` variants in the standard local model directory.
- Double-clicking empty titlebar space now maximizes or restores the window.
- On Linux, the float capsule now keeps its glow visible without capturing
clicks in the transparent margin. Other platforms keep a snug window to
Expand Down Expand Up @@ -101,6 +103,9 @@ then reset this file.

### Tested

- Whisper model auto-detection fix: `cargo test` and `cargo clippy --workspace
--all-targets -- -D warnings`. Live-verified the installed
`~/.local/share/whisper.cpp/models/ggml-base.en.bin` model is selected.
- pickscribe#66 PR 2 (macOS audio capture via ffmpeg/avfoundation): `cargo
test` (workspace root, 122 tests including new `recorder_args` and
`platform` coverage), `cargo test --manifest-path src-tauri/Cargo.toml` (37
Expand Down
83 changes: 72 additions & 11 deletions src/engine/stt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,8 @@ pub fn detect_model_path() -> Option<PathBuf> {
}
let home = std::env::var("HOME").ok()?;
let model_dir = PathBuf::from(&home).join(".local/share/whisper.cpp/models");
for name in [
"ggml-large-v3-turbo.bin",
"ggml-large-v3-turbo-q5_0.bin",
"ggml-small.bin",
"ggml-base.bin",
"ggml-tiny.bin",
] {
let candidate = model_dir.join(name);
if candidate.is_file() {
return Some(candidate);
}
if let Some(path) = detect_model_in_dir(&model_dir) {
return Some(path);
}
// Arch whisper.cpp-model-* packages
if let Ok(entries) = fs::read_dir("/usr/share") {
Expand All @@ -75,6 +66,39 @@ pub fn detect_model_path() -> Option<PathBuf> {
None
}

fn detect_model_in_dir(model_dir: &Path) -> Option<PathBuf> {
for name in [
"ggml-large-v3-turbo.bin",
"ggml-large-v3-turbo-q5_0.bin",
"ggml-small.bin",
"ggml-small.en.bin",
"ggml-base.bin",
"ggml-base.en.bin",
"ggml-tiny.bin",
"ggml-tiny.en.bin",
] {
let candidate = model_dir.join(name);
if candidate.is_file() {
return Some(candidate);
}
}

let mut models = fs::read_dir(model_dir)
.ok()?
.flatten()
.map(|entry| entry.path())
.filter(|path| {
path.is_file()
&& path
.file_name()
.is_some_and(|name| name.to_string_lossy().starts_with("ggml-"))
&& path.extension().is_some_and(|extension| extension == "bin")
})
.collect::<Vec<_>>();
models.sort();
models.into_iter().next()
}

/// List models available in the default model directory (for the settings UI).
pub fn available_models() -> Vec<PathBuf> {
let mut out = Vec::new();
Expand Down Expand Up @@ -303,6 +327,43 @@ fn parse_progress_percentage(line: &str) -> Option<u8> {
mod tests {
use super::*;

#[test]
fn detects_english_model_from_preference_list() {
let dir = tempfile::tempdir().unwrap();
let model = dir.path().join("ggml-base.en.bin");
fs::write(&model, []).unwrap();

assert_eq!(detect_model_in_dir(dir.path()), Some(model));
}

#[test]
fn detects_unknown_model_by_sorted_filename() {
let dir = tempfile::tempdir().unwrap();
let first = dir.path().join("ggml-foo.bin");
fs::write(dir.path().join("ggml-zeta.bin"), []).unwrap();
fs::write(&first, []).unwrap();

assert_eq!(detect_model_in_dir(dir.path()), Some(first));
}

#[test]
fn returns_none_for_empty_model_directory() {
let dir = tempfile::tempdir().unwrap();

assert_eq!(detect_model_in_dir(dir.path()), None);
}

#[test]
fn respects_model_preference_order() {
let dir = tempfile::tempdir().unwrap();
let preferred = dir.path().join("ggml-small.bin");
fs::write(dir.path().join("ggml-base.bin"), []).unwrap();
fs::write(dir.path().join("ggml-small.en.bin"), []).unwrap();
fs::write(&preferred, []).unwrap();

assert_eq!(detect_model_in_dir(dir.path()), Some(preferred));
}

#[test]
fn parses_whisper_progress_percentages() {
assert_eq!(
Expand Down