scribe-cli 1.2.0__tar.gz → 1.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/PKG-INFO +1 -1
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/docs/cli.md +1 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/_version.py +3 -3
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/app.py +7 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/backends/whisper.py +1 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/backends/whisper_futo.py +1 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/models.py +14 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/output.py +3 -1
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/session.py +11 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/typers/eitype.py +7 -1
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/typers/wtype.py +7 -1
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_cli.egg-info/PKG-INFO +1 -1
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_cli.egg-info/SOURCES.txt +2 -0
- scribe_cli-1.2.1/scribe_cli.egg-info/scm_file_list.json +71 -0
- scribe_cli-1.2.1/scribe_cli.egg-info/scm_version.json +8 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_clip_silence_trim.py +26 -2
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_typers_ascii_fallback.py +23 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/.github/FUNDING.yml +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/.github/workflows/docs.yml +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/.github/workflows/pypi.yml +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/.gitignore +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/LICENSE +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/README.md +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/docs/app-tray-menu.png +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/docs/backends.md +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/docs/desktop-install.md +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/docs/index.md +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/docs/installation.md +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/docs/output.md +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/docs/quickstart.md +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/docs/roadmap-libei.md +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/docs/tray.md +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/icon.xcf +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/mkdocs.yml +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/pyproject.toml +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/__init__.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/audio.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/backends/__init__.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/backends/groq.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/backends/openai_api.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/backends/openai_realtime.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/backends/vosk.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/dialog.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/install_desktop.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/keyboard.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/menu.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/models.toml +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/saverecording.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/testpynput.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/typers/__init__.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/typers/base.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/typers/pynput.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/typers/ydotool.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe/util.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_cli.egg-info/dependency_links.txt +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_cli.egg-info/entry_points.txt +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_cli.egg-info/requires.txt +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_cli.egg-info/top_level.txt +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_data/__init__.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_data/share/icon.png +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_data/share/icon_recording.png +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_data/share/icon_writing.png +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_data/silero_vad.LICENSE +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_data/silero_vad.onnx +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scribe_data/templates/scribe.desktop +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scripts/bench_whisper_local.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/scripts/test_python_versions_install.sh +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/setup.cfg +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_backend_matrix.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_clipboard_backend.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_compose_prompt.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_debug_logging.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_openai_realtime_coalesce.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_output.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_output_file_picker.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_prompt_file_picker.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_pseudo_streaming.py +0 -0
- {scribe_cli-1.2.0 → scribe_cli-1.2.1}/tests/test_whisper_futo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scribe-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Speech-to-text CLI and system-tray app for dictating into any focused window. Local (vosk, faster-whisper) or cloud (groq, openai) backends, batch or streaming.
|
|
5
5
|
Author-email: Mahé Perrette <mahe.perrette@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -65,6 +65,7 @@ flag suppresses only its own side (giving `--prompt ""` still loads
|
|
|
65
65
|
| `-m, --mode {keystroke,clipboard,terminal,file}` | Where transcribed text goes (default `keystroke`). `file` routes the transcript exclusively to `--output-file` and suppresses keyboard/clipboard output. See [output.md](output.md). |
|
|
66
66
|
| `--typer {auto,eitype,pynput,wtype,ydotool}` | Keystroke-injection backend (default `auto`). |
|
|
67
67
|
| `--type-direct` | In keystroke mode, type the transcription as keystrokes instead of synthesising Ctrl+V. |
|
|
68
|
+
| `--typer-delay MS` | With `--type-direct`, milliseconds between key events for eitype/wtype (default `2`). Some apps, such as the Claude Code prompt, drop the end of text typed at 0. |
|
|
68
69
|
| `-o, --output-file FILE` | Path the transcription is appended to when `--mode file`. Defaults to `<user-desktop>/scribe-notes.txt` (the platform Desktop folder — `~/Desktop` on Linux/macOS, `%USERPROFILE%\Desktop` on Windows; falls back to home dir if Desktop is absent). Ignored when `--mode` is anything other than `file` (the four output modes are mutually exclusive). |
|
|
69
70
|
|
|
70
71
|
## Silence detection
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 2,
|
|
21
|
+
__version__ = version = '1.2.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 2, 1)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g7a322c71b'
|
|
@@ -467,6 +467,12 @@ def get_parser():
|
|
|
467
467
|
"is the ^V control character. Cost: slower for long text; on wtype/ydotool "
|
|
468
468
|
"non-ASCII characters fall back to their ASCII equivalents (eitype is "
|
|
469
469
|
"Unicode-correct).")
|
|
470
|
+
group.add_argument("--typer-delay", default=2, type=int,
|
|
471
|
+
help="With --type-direct, delay in milliseconds between key events "
|
|
472
|
+
"for the eitype and wtype typers (default: %(default)s). Some "
|
|
473
|
+
"apps drop keystrokes that arrive faster than they can read them "
|
|
474
|
+
"(e.g. the Claude Code prompt loses the end of the text at 0). "
|
|
475
|
+
"Costs about 2 x delay per character.")
|
|
470
476
|
group.add_argument("-o", "--output-file",
|
|
471
477
|
default=DEFAULT_OUTPUT_FILE,
|
|
472
478
|
help=f"Path the transcription is appended to when "
|
|
@@ -675,6 +681,7 @@ def _resolve_output(o, *, is_streaming, backend_obj):
|
|
|
675
681
|
typer=getattr(o, "typer", None) if mode == "keystroke" else None,
|
|
676
682
|
type_direct=getattr(o, "type_direct", False),
|
|
677
683
|
output_file=getattr(o, "output_file", None) if mode == "file" else None,
|
|
684
|
+
typer_delay=getattr(o, "typer_delay", 0),
|
|
678
685
|
is_streaming=is_streaming,
|
|
679
686
|
backend_obj=backend_obj,
|
|
680
687
|
)
|
|
@@ -199,6 +199,7 @@ class WhisperFutoTranscriber(AbstractTranscriber):
|
|
|
199
199
|
return {"text": text}
|
|
200
200
|
|
|
201
201
|
def finalize(self):
|
|
202
|
+
self.flush_trailing_silence()
|
|
202
203
|
if len(self.session.audio_buffer) == 0:
|
|
203
204
|
return {"text": ""}
|
|
204
205
|
result = self.transcribe_audio(self.session.audio_buffer)
|
|
@@ -482,6 +482,20 @@ class AbstractTranscriber(STTBackend):
|
|
|
482
482
|
def clear_streaming_context(self):
|
|
483
483
|
self._streaming_context = ""
|
|
484
484
|
|
|
485
|
+
def flush_trailing_silence(self):
|
|
486
|
+
"""Clip mode: move the silence held since the last speech block into
|
|
487
|
+
audio_buffer before finalize() transcribes it. The silence gate can
|
|
488
|
+
classify a quiet word ending as silence, and Whisper tends to drop
|
|
489
|
+
the last word when the audio stops right at the end of speech.
|
|
490
|
+
silence_buffer is already capped at clip_max_silence seconds, so at
|
|
491
|
+
most that much is appended. No-op when nothing was spoken, so a
|
|
492
|
+
silent recording still finalizes to empty text."""
|
|
493
|
+
session = self.session
|
|
494
|
+
if self.pseudo_streaming or not session.audio_buffer:
|
|
495
|
+
return
|
|
496
|
+
session.audio_buffer += session.silence_buffer
|
|
497
|
+
session.silence_buffer = b''
|
|
498
|
+
|
|
485
499
|
def transcribe_audio(self, audio_data):
|
|
486
500
|
raise NotImplementedError()
|
|
487
501
|
|
|
@@ -194,7 +194,7 @@ class KeyboardOutput(Output):
|
|
|
194
194
|
|
|
195
195
|
def make_output(mode: str, *, typer: Optional[str], type_direct: bool,
|
|
196
196
|
output_file: Optional[str], is_streaming: bool,
|
|
197
|
-
backend_obj=None) -> Output:
|
|
197
|
+
backend_obj=None, typer_delay: int = 0) -> Output:
|
|
198
198
|
"""Resolve ``(mode, typer, type_direct, output_file, is_streaming)``
|
|
199
199
|
into the right Output subclass.
|
|
200
200
|
|
|
@@ -230,6 +230,8 @@ def make_output(mode: str, *, typer: Optional[str], type_direct: bool,
|
|
|
230
230
|
if type_direct:
|
|
231
231
|
from scribe.typers import pick_typer
|
|
232
232
|
typer_obj = pick_typer(typer if typer and typer != "auto" else None)
|
|
233
|
+
if hasattr(typer_obj, "key_delay_ms"):
|
|
234
|
+
typer_obj.key_delay_ms = typer_delay
|
|
233
235
|
else:
|
|
234
236
|
typer_obj = None
|
|
235
237
|
return KeyboardOutput(
|
|
@@ -168,6 +168,17 @@ class RecordingSession:
|
|
|
168
168
|
microphone.q.queue.clear()
|
|
169
169
|
result = {"text": ""}
|
|
170
170
|
else:
|
|
171
|
+
# The loop above polls the queue every 100 ms, so blocks
|
|
172
|
+
# captured just before stop can still be waiting here. Feed
|
|
173
|
+
# them to batch backends before finalizing so the last words
|
|
174
|
+
# aren't lost. A pseudo-streaming silence cut at this point
|
|
175
|
+
# is moot: finalize() runs right after anyway.
|
|
176
|
+
if not streaming:
|
|
177
|
+
try:
|
|
178
|
+
while not microphone.q.empty():
|
|
179
|
+
self.backend.transcribe_realtime_audio(microphone.q.get())
|
|
180
|
+
except SilenceDetected:
|
|
181
|
+
pass
|
|
171
182
|
try:
|
|
172
183
|
result = self.backend.finalize()
|
|
173
184
|
except Exception as exc:
|
|
@@ -12,6 +12,9 @@ from scribe.typers.base import Typer
|
|
|
12
12
|
|
|
13
13
|
class EitypeTyper:
|
|
14
14
|
name = "eitype"
|
|
15
|
+
# Milliseconds between key events (`eitype -d`). Set from --typer-delay
|
|
16
|
+
# by scribe.output.make_output; 0 sends the whole string as one burst.
|
|
17
|
+
key_delay_ms: int = 0
|
|
15
18
|
|
|
16
19
|
def compatible(self) -> bool:
|
|
17
20
|
"""Linux Wayland session; libei is a Wayland-specific protocol."""
|
|
@@ -29,7 +32,10 @@ class EitypeTyper:
|
|
|
29
32
|
|
|
30
33
|
def _emit(self, text: str) -> None:
|
|
31
34
|
try:
|
|
32
|
-
|
|
35
|
+
cmd = ["eitype"]
|
|
36
|
+
if self.key_delay_ms > 0:
|
|
37
|
+
cmd += ["-d", str(self.key_delay_ms)]
|
|
38
|
+
subprocess.run(cmd + ["--", text], check=True, capture_output=True)
|
|
33
39
|
except subprocess.CalledProcessError as e:
|
|
34
40
|
raise RuntimeError(
|
|
35
41
|
f"eitype failed: {e.stderr.decode(errors='replace')}"
|
|
@@ -13,6 +13,9 @@ from scribe.typers.base import Typer
|
|
|
13
13
|
|
|
14
14
|
class WtypeTyper:
|
|
15
15
|
name = "wtype"
|
|
16
|
+
# Milliseconds between key events (`wtype -d`). Set from --typer-delay
|
|
17
|
+
# by scribe.output.make_output; 0 sends the whole string as one burst.
|
|
18
|
+
key_delay_ms: int = 0
|
|
16
19
|
|
|
17
20
|
def compatible(self) -> bool:
|
|
18
21
|
"""Linux Wayland on a wlroots-based compositor (Sway, Hyprland, …).
|
|
@@ -42,7 +45,10 @@ class WtypeTyper:
|
|
|
42
45
|
|
|
43
46
|
def _emit(self, text: str) -> None:
|
|
44
47
|
try:
|
|
45
|
-
|
|
48
|
+
cmd = ["wtype"]
|
|
49
|
+
if self.key_delay_ms > 0:
|
|
50
|
+
cmd += ["-d", str(self.key_delay_ms)]
|
|
51
|
+
subprocess.run(cmd + ["--", text], check=True, capture_output=True)
|
|
46
52
|
except subprocess.CalledProcessError as e:
|
|
47
53
|
raise RuntimeError(
|
|
48
54
|
f"wtype failed: {e.stderr.decode(errors='replace')}"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scribe-cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: Speech-to-text CLI and system-tray app for dictating into any focused window. Local (vosk, faster-whisper) or cloud (groq, openai) backends, batch or streaming.
|
|
5
5
|
Author-email: Mahé Perrette <mahe.perrette@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -50,6 +50,8 @@ scribe_cli.egg-info/SOURCES.txt
|
|
|
50
50
|
scribe_cli.egg-info/dependency_links.txt
|
|
51
51
|
scribe_cli.egg-info/entry_points.txt
|
|
52
52
|
scribe_cli.egg-info/requires.txt
|
|
53
|
+
scribe_cli.egg-info/scm_file_list.json
|
|
54
|
+
scribe_cli.egg-info/scm_version.json
|
|
53
55
|
scribe_cli.egg-info/top_level.txt
|
|
54
56
|
scribe_data/__init__.py
|
|
55
57
|
scribe_data/silero_vad.LICENSE
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"files": [
|
|
3
|
+
".github/FUNDING.yml",
|
|
4
|
+
".github/workflows/docs.yml",
|
|
5
|
+
".github/workflows/pypi.yml",
|
|
6
|
+
".gitignore",
|
|
7
|
+
"LICENSE",
|
|
8
|
+
"README.md",
|
|
9
|
+
"docs/app-tray-menu.png",
|
|
10
|
+
"docs/backends.md",
|
|
11
|
+
"docs/cli.md",
|
|
12
|
+
"docs/desktop-install.md",
|
|
13
|
+
"docs/index.md",
|
|
14
|
+
"docs/installation.md",
|
|
15
|
+
"docs/output.md",
|
|
16
|
+
"docs/quickstart.md",
|
|
17
|
+
"docs/roadmap-libei.md",
|
|
18
|
+
"docs/tray.md",
|
|
19
|
+
"icon.xcf",
|
|
20
|
+
"mkdocs.yml",
|
|
21
|
+
"pyproject.toml",
|
|
22
|
+
"scribe/__init__.py",
|
|
23
|
+
"scribe/app.py",
|
|
24
|
+
"scribe/audio.py",
|
|
25
|
+
"scribe/backends/__init__.py",
|
|
26
|
+
"scribe/backends/groq.py",
|
|
27
|
+
"scribe/backends/openai_api.py",
|
|
28
|
+
"scribe/backends/openai_realtime.py",
|
|
29
|
+
"scribe/backends/vosk.py",
|
|
30
|
+
"scribe/backends/whisper.py",
|
|
31
|
+
"scribe/backends/whisper_futo.py",
|
|
32
|
+
"scribe/dialog.py",
|
|
33
|
+
"scribe/install_desktop.py",
|
|
34
|
+
"scribe/keyboard.py",
|
|
35
|
+
"scribe/menu.py",
|
|
36
|
+
"scribe/models.py",
|
|
37
|
+
"scribe/models.toml",
|
|
38
|
+
"scribe/output.py",
|
|
39
|
+
"scribe/saverecording.py",
|
|
40
|
+
"scribe/session.py",
|
|
41
|
+
"scribe/testpynput.py",
|
|
42
|
+
"scribe/typers/__init__.py",
|
|
43
|
+
"scribe/typers/base.py",
|
|
44
|
+
"scribe/typers/eitype.py",
|
|
45
|
+
"scribe/typers/pynput.py",
|
|
46
|
+
"scribe/typers/wtype.py",
|
|
47
|
+
"scribe/typers/ydotool.py",
|
|
48
|
+
"scribe/util.py",
|
|
49
|
+
"scribe_data/__init__.py",
|
|
50
|
+
"scribe_data/share/icon.png",
|
|
51
|
+
"scribe_data/share/icon_recording.png",
|
|
52
|
+
"scribe_data/share/icon_writing.png",
|
|
53
|
+
"scribe_data/silero_vad.LICENSE",
|
|
54
|
+
"scribe_data/silero_vad.onnx",
|
|
55
|
+
"scribe_data/templates/scribe.desktop",
|
|
56
|
+
"scripts/bench_whisper_local.py",
|
|
57
|
+
"scripts/test_python_versions_install.sh",
|
|
58
|
+
"tests/test_backend_matrix.py",
|
|
59
|
+
"tests/test_clip_silence_trim.py",
|
|
60
|
+
"tests/test_clipboard_backend.py",
|
|
61
|
+
"tests/test_compose_prompt.py",
|
|
62
|
+
"tests/test_debug_logging.py",
|
|
63
|
+
"tests/test_openai_realtime_coalesce.py",
|
|
64
|
+
"tests/test_output.py",
|
|
65
|
+
"tests/test_output_file_picker.py",
|
|
66
|
+
"tests/test_prompt_file_picker.py",
|
|
67
|
+
"tests/test_pseudo_streaming.py",
|
|
68
|
+
"tests/test_typers_ascii_fallback.py",
|
|
69
|
+
"tests/test_whisper_futo.py"
|
|
70
|
+
]
|
|
71
|
+
}
|
|
@@ -90,14 +90,38 @@ def test_short_pause_kept_verbatim():
|
|
|
90
90
|
assert sess.trimmed_silence_bytes == 0
|
|
91
91
|
|
|
92
92
|
|
|
93
|
-
def
|
|
93
|
+
def test_trailing_silence_capped_at_stop():
|
|
94
94
|
sess = make_session()
|
|
95
95
|
backend = make_backend(sess, clip_max_silence=2.0)
|
|
96
96
|
backend.transcribe_realtime_audio(loud_chunk(1.0))
|
|
97
97
|
for _ in range(50): # 5 s of trailing silence, never followed by speech
|
|
98
98
|
backend.transcribe_realtime_audio(silent_chunk(0.1))
|
|
99
|
-
# finalize() reads audio_buffer only — the pause stays out of it.
|
|
100
99
|
assert secs(sess.audio_buffer) == 1.0
|
|
100
|
+
# At stop, the retained tail (<= clip_max_silence) is appended so a
|
|
101
|
+
# quiet word ending misread as silence still reaches the backend.
|
|
102
|
+
backend.flush_trailing_silence()
|
|
103
|
+
assert secs(sess.audio_buffer) == 3.0
|
|
104
|
+
assert sess.silence_buffer == b''
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_quiet_word_ending_kept_at_stop():
|
|
108
|
+
sess = make_session()
|
|
109
|
+
backend = make_backend(sess, clip_max_silence=2.0)
|
|
110
|
+
backend.transcribe_realtime_audio(loud_chunk(1.0))
|
|
111
|
+
# A trailing-off word ending, below the dB gate threshold.
|
|
112
|
+
quiet = loud_chunk(0.3, amplitude=100)
|
|
113
|
+
backend.transcribe_realtime_audio(quiet)
|
|
114
|
+
backend.flush_trailing_silence()
|
|
115
|
+
assert sess.audio_buffer.endswith(quiet)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_flush_noop_when_nothing_spoken():
|
|
119
|
+
sess = make_session()
|
|
120
|
+
backend = make_backend(sess, clip_max_silence=2.0)
|
|
121
|
+
for _ in range(10):
|
|
122
|
+
backend.transcribe_realtime_audio(silent_chunk(0.1))
|
|
123
|
+
backend.flush_trailing_silence()
|
|
124
|
+
assert sess.audio_buffer == b''
|
|
101
125
|
|
|
102
126
|
|
|
103
127
|
def test_zero_disables_trimming():
|
|
@@ -78,3 +78,26 @@ def test_unrenderable_char_is_skipped_not_fatal():
|
|
|
78
78
|
|
|
79
79
|
type_ascii_safe(emit, "xéy", (LayoutError,))
|
|
80
80
|
assert "".join(typed) == "xy"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _record_subprocess(monkeypatch, module):
|
|
84
|
+
calls = []
|
|
85
|
+
monkeypatch.setattr(module.subprocess, "run",
|
|
86
|
+
lambda cmd, **kw: calls.append(cmd))
|
|
87
|
+
return calls
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_subprocess_typers_pass_key_delay(monkeypatch):
|
|
91
|
+
# --typer-delay reaches eitype/wtype as `-d MS`; some apps (the Claude
|
|
92
|
+
# Code prompt) drop the end of text typed as one zero-delay burst.
|
|
93
|
+
import scribe.typers.eitype as eitype_mod
|
|
94
|
+
import scribe.typers.wtype as wtype_mod
|
|
95
|
+
for module, cls in ((eitype_mod, eitype_mod.EitypeTyper),
|
|
96
|
+
(wtype_mod, wtype_mod.WtypeTyper)):
|
|
97
|
+
calls = _record_subprocess(monkeypatch, module)
|
|
98
|
+
typer = cls()
|
|
99
|
+
typer.type("hi")
|
|
100
|
+
typer.key_delay_ms = 2
|
|
101
|
+
typer.type("hi")
|
|
102
|
+
assert calls == [[cls.name, "--", "hi"],
|
|
103
|
+
[cls.name, "-d", "2", "--", "hi"]]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|