python-voiceio 0.9.6__tar.gz → 0.9.7__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.
- {python_voiceio-0.9.6/python_voiceio.egg-info → python_voiceio-0.9.7}/PKG-INFO +1 -1
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/pyproject.toml +1 -1
- {python_voiceio-0.9.6 → python_voiceio-0.9.7/python_voiceio.egg-info}/PKG-INFO +1 -1
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_postcorrect.py +19 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_postprocess.py +36 -3
- python_voiceio-0.9.7/voiceio/__init__.py +1 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/config.py +2 -1
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/postcorrect.py +17 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/postprocess.py +39 -13
- python_voiceio-0.9.6/voiceio/__init__.py +0 -1
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/LICENSE +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/README.md +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/python_voiceio.egg-info/SOURCES.txt +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/python_voiceio.egg-info/dependency_links.txt +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/python_voiceio.egg-info/entry_points.txt +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/python_voiceio.egg-info/requires.txt +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/python_voiceio.egg-info/top_level.txt +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/setup.cfg +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_adjudicate.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_app_wiring.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_audio_quality.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_audit.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_autocorrect.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_autocorrect_state.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_backend_probes.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_cli.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_clipboard_read.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_commands.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_concurrency_lockdown.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_config.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_correct_batch.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_corrections.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_evaluate.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_fallback.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_health.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_hints.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_history.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_ibus_pending.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_ibus_ping.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_ibus_typer.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_llm.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_llm_api.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_numbers.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_platform.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_prebuffer.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_prompt.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_recorder_integration.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_retention.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_robustness.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_security_hardening.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_snapshots.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_streaming.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_tokens.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_transcriber.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_tts.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_vad.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_vocabulary.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_wizard.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/tests/test_wordfreq.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/__main__.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/app.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/audit.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/autocorrect.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/autocorrect_state.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/backends.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/cli.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/clipboard_read.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/commands.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/consent.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/corrections.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/demo.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/evaluate.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/feedback.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/health.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/hints.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/history.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/hotkeys/__init__.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/hotkeys/base.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/hotkeys/chain.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/hotkeys/evdev.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/hotkeys/pynput_backend.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/hotkeys/socket_backend.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/ibus/__init__.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/ibus/engine.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/ibus/pending.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/llm.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/llm_api.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/models/__init__.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/models/silero_vad.onnx +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/numbers.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/pidlock.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/platform.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/prompt.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/recorder.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/retention.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/service.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/snapshots.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/sounds/__init__.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/sounds/commit.wav +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/sounds/start.wav +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/sounds/stop.wav +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/streaming.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tokens.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/transcriber.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tray/__init__.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tray/_icons.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tray/_indicator.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tray/_pystray.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tts/__init__.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tts/base.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tts/chain.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tts/edge_engine.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tts/espeak.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tts/piper_engine.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/tts/player.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/typers/__init__.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/typers/base.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/typers/chain.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/typers/clipboard.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/typers/ibus.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/typers/pynput_type.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/typers/wtype.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/typers/xdotool.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/typers/ydotool.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/vad.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/vocab_stats.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/vocabulary.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/wizard.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/wordfreq.py +0 -0
- {python_voiceio-0.9.6 → python_voiceio-0.9.7}/voiceio/worker.py +0 -0
|
@@ -121,6 +121,25 @@ def test_disfluency_mode_rejects_rewording():
|
|
|
121
121
|
assert pc.correct(original) == original
|
|
122
122
|
|
|
123
123
|
|
|
124
|
+
def test_disfluency_mode_rejects_dropped_negation():
|
|
125
|
+
"""Dropping 'not' is one deletion under every fraction cap but inverts
|
|
126
|
+
meaning — the negation guard must reject it."""
|
|
127
|
+
pc = PostCorrector(_cfg(remove_disfluencies=True))
|
|
128
|
+
original = "I do not want the feature to ship today at all"
|
|
129
|
+
inverted = "I do want the feature to ship today at all"
|
|
130
|
+
with patch("voiceio.llm_api.chat", return_value=inverted):
|
|
131
|
+
assert pc.correct(original) == original
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def test_disfluency_mode_rejects_replaced_negation():
|
|
135
|
+
"""Replacing a negation with something else also inverts meaning."""
|
|
136
|
+
pc = PostCorrector(_cfg(remove_disfluencies=True))
|
|
137
|
+
original = "we should never deploy on a friday afternoon"
|
|
138
|
+
changed = "we should always deploy on a friday afternoon"
|
|
139
|
+
with patch("voiceio.llm_api.chat", return_value=changed):
|
|
140
|
+
assert pc.correct(original) == original
|
|
141
|
+
|
|
142
|
+
|
|
124
143
|
def test_fix_mode_still_rejects_big_deletion():
|
|
125
144
|
"""With disfluency mode OFF, the original conservative guards stand: a big
|
|
126
145
|
deletion is a length change and must be rejected."""
|
|
@@ -24,15 +24,48 @@ class TestStripDisfluencies:
|
|
|
24
24
|
assert strip_disfluencies("I had had enough") == "I had had enough"
|
|
25
25
|
|
|
26
26
|
def test_dedups_duplicate_sentence(self):
|
|
27
|
-
# The Whisper re-decode artifact:
|
|
28
|
-
out = strip_disfluencies("Do the research. Do the research.")
|
|
29
|
-
assert out == "Do the research."
|
|
27
|
+
# The Whisper re-decode artifact: a whole sentence repeated verbatim.
|
|
28
|
+
out = strip_disfluencies("Do the deep research now. Do the deep research now.")
|
|
29
|
+
assert out == "Do the deep research now."
|
|
30
30
|
|
|
31
31
|
def test_keeps_meaningful_words(self):
|
|
32
32
|
# 'like' as a real verb/preposition and content must survive.
|
|
33
33
|
text = "I like the design and it works like a charm"
|
|
34
34
|
assert strip_disfluencies(text) == text
|
|
35
35
|
|
|
36
|
+
def test_never_eats_real_words_or_units(self):
|
|
37
|
+
# Default-on runs on everyone's speech: filler patterns must not collide
|
|
38
|
+
# with real words, units, or abbreviations. Case-sensitive matching is
|
|
39
|
+
# what protects the all-caps abbreviations (ER, UM, HM).
|
|
40
|
+
for text in [
|
|
41
|
+
"to err is human",
|
|
42
|
+
"we should err on caution",
|
|
43
|
+
"the bolt is 5 mm wide",
|
|
44
|
+
"set it to 10 mm please",
|
|
45
|
+
"ah yes I remember now",
|
|
46
|
+
"I like the ohm rating",
|
|
47
|
+
"Take him to the ER right now", # ER = emergency room
|
|
48
|
+
"The UM campus in Michigan", # UM = University of Michigan
|
|
49
|
+
"The Er atom is a lanthanide", # Er = erbium
|
|
50
|
+
"we measured 3 hm across", # hm = hectometre (bare, 1 m)
|
|
51
|
+
]:
|
|
52
|
+
assert strip_disfluencies(text) == text, text
|
|
53
|
+
|
|
54
|
+
def test_preserves_newlines_and_structure(self):
|
|
55
|
+
# A filler on its own line must not swallow the paragraph break.
|
|
56
|
+
assert strip_disfluencies("First para.\n\nSecond para.") == \
|
|
57
|
+
"First para.\n\nSecond para."
|
|
58
|
+
assert "\n\n" in strip_disfluencies("First para.\n\num\n\nSecond para.")
|
|
59
|
+
|
|
60
|
+
def test_uh_huh_removed_whole(self):
|
|
61
|
+
# Regression: ordering bug once stranded "-huh".
|
|
62
|
+
assert strip_disfluencies("uh-huh right") == "right"
|
|
63
|
+
assert strip_disfluencies("uh huh yes") == "yes"
|
|
64
|
+
|
|
65
|
+
def test_preserves_emphatic_short_repeat(self):
|
|
66
|
+
# Short repeats are emphasis, not a re-decode artifact — keep them.
|
|
67
|
+
assert strip_disfluencies("No. No.") == "No. No."
|
|
68
|
+
|
|
36
69
|
def test_empty(self):
|
|
37
70
|
assert strip_disfluencies("") == ""
|
|
38
71
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.9.7"
|
|
@@ -93,7 +93,8 @@ class OutputConfig:
|
|
|
93
93
|
# and duplicate re-decode sentences on every pass; when [postcorrect] is
|
|
94
94
|
# also enabled, its LLM pass additionally removes false starts and filler
|
|
95
95
|
# "like", guarded so it can only delete — never add, rephrase, or reorder.
|
|
96
|
-
|
|
96
|
+
# On by default: nobody wants "um" in their notes. Set false for verbatim.
|
|
97
|
+
remove_disfluencies: bool = True
|
|
97
98
|
voice_input_prefix: str = "" # e.g. "[voice]" — empty disables
|
|
98
99
|
# Incremental finalization: once the un-finalized audio tail grows past
|
|
99
100
|
# this many seconds, it is cut at the nearest interior speech pause,
|
|
@@ -34,6 +34,19 @@ _MAX_INSERTED_WORDS = 0 # adding any word = altering meaning → reject
|
|
|
34
34
|
_MAX_REPLACE_FRAC = 0.15 # ASR word-fixes only, never wholesale rewording
|
|
35
35
|
_MAX_DELETE_FRAC = 0.4 # backstop against deleting real content
|
|
36
36
|
|
|
37
|
+
# Words whose deletion/replacement flips meaning — the fraction caps can't catch
|
|
38
|
+
# a single dropped "not". If the edit touches any of these on the original side,
|
|
39
|
+
# reject outright. (Contractions ending in "n't" are handled separately.)
|
|
40
|
+
_MEANING_CRITICAL = frozenset({
|
|
41
|
+
"not", "no", "never", "none", "nor", "neither", "without", "cannot",
|
|
42
|
+
"nothing", "nobody", "nowhere", "n't",
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _is_meaning_critical(word: str) -> bool:
|
|
47
|
+
w = word.lower().strip(".,;:!?\"'()")
|
|
48
|
+
return w in _MEANING_CRITICAL or w.endswith("n't")
|
|
49
|
+
|
|
37
50
|
_SYSTEM_PROMPT = (
|
|
38
51
|
"You fix automatic speech recognition errors in dictated text. "
|
|
39
52
|
"The user dictates about software engineering and their projects. "
|
|
@@ -315,8 +328,12 @@ class PostCorrector:
|
|
|
315
328
|
inserted += j2 - j1
|
|
316
329
|
elif tag == "replace":
|
|
317
330
|
replaced += max(i2 - i1, j2 - j1)
|
|
331
|
+
if any(_is_meaning_critical(w) for w in aw[i1:i2]):
|
|
332
|
+
return False, "negation" # e.g. "not" → something else
|
|
318
333
|
elif tag == "delete":
|
|
319
334
|
deleted += i2 - i1
|
|
335
|
+
if any(_is_meaning_critical(w) for w in aw[i1:i2]):
|
|
336
|
+
return False, "negation" # dropping "not" inverts meaning
|
|
320
337
|
n = len(aw)
|
|
321
338
|
if inserted > _MAX_INSERTED_WORDS:
|
|
322
339
|
return False, "inserted" # added content — meaning changed
|
|
@@ -14,13 +14,25 @@ if TYPE_CHECKING:
|
|
|
14
14
|
_NO_CASE_LANGUAGES = frozenset({"zh", "ja", "ko", "ar", "he", "th", "hi", "bn", "ka", "my"})
|
|
15
15
|
|
|
16
16
|
# Filler SOUNDS only — tokens with no lexical meaning, so deleting them can
|
|
17
|
-
# never change meaning.
|
|
18
|
-
#
|
|
19
|
-
#
|
|
17
|
+
# never change meaning. Deliberately conservative because this runs on every
|
|
18
|
+
# user's speech:
|
|
19
|
+
# * CASE-SENSITIVE (no IGNORECASE): all-caps abbreviations that look like
|
|
20
|
+
# fillers are real words and must survive — "ER" (emergency room), "UM"
|
|
21
|
+
# (University of Michigan), "HM". We match lowercase and Title-case forms
|
|
22
|
+
# ("um", "Um") — Whisper's filler spellings — but never all-caps.
|
|
23
|
+
# * Excluded entirely: "er"/"erm" ("ER"/"Er"=erbium), "mm" (millimetres),
|
|
24
|
+
# "ah" (interjection), bare "hm" (hectometre — require "hmm", 2+ m's).
|
|
25
|
+
# * Surrounding whitespace is horizontal-only ([^\S\n]) so a filler on its
|
|
26
|
+
# own line doesn't swallow the paragraph/list break around it.
|
|
27
|
+
# Word repetitions ("had had" is valid English) and filler "like"/"you know"
|
|
28
|
+
# need judgment and are left to the LLM layer. Order matters: multi-token
|
|
29
|
+
# "uh-huh" before "[Uu]h+" so it isn't clipped to a stray "-huh".
|
|
20
30
|
_FILLER_RE = re.compile(
|
|
21
|
-
r"\
|
|
22
|
-
re.IGNORECASE,
|
|
31
|
+
r"[^\S\n]*,?[^\S\n]*\b(?:[Uu]h[-\s]?huh|[Mm]hm|[Uu]h+m*|[Uu]m+|[Hh]m{2,})\b[^\S\n]*,?[^\S\n]*",
|
|
23
32
|
)
|
|
33
|
+
# A re-decode artifact is a whole duplicated sentence; require this many words
|
|
34
|
+
# so emphatic short repeats ("No. No.", "Stop. Stop.") are preserved.
|
|
35
|
+
_MIN_DEDUP_WORDS = 4
|
|
24
36
|
|
|
25
37
|
|
|
26
38
|
def strip_disfluencies(text: str) -> str:
|
|
@@ -36,19 +48,33 @@ def strip_disfluencies(text: str) -> str:
|
|
|
36
48
|
return text
|
|
37
49
|
text = _FILLER_RE.sub(" ", text)
|
|
38
50
|
text = _dedup_adjacent_sentences(text)
|
|
39
|
-
|
|
51
|
+
# Repair the debris the deletions leave, without crossing newlines (so
|
|
52
|
+
# paragraph/list structure survives even when punctuation_cleanup is off).
|
|
53
|
+
text = re.sub(r"[^\S\n]+([,.;:?!])", r"\1", text) # space before punctuation
|
|
54
|
+
text = re.sub(r"[^\S\n]{2,}", " ", text) # collapse runs of spaces
|
|
40
55
|
return text.strip()
|
|
41
56
|
|
|
42
57
|
|
|
43
58
|
def _dedup_adjacent_sentences(text: str) -> str:
|
|
44
|
-
"""Drop a sentence identical to the one immediately before it.
|
|
45
|
-
|
|
59
|
+
"""Drop a sentence identical to the one immediately before it.
|
|
60
|
+
|
|
61
|
+
Separators are captured and preserved on rejoin so paragraph/list breaks
|
|
62
|
+
survive; only a full (>= _MIN_DEDUP_WORDS) verbatim repeat is removed.
|
|
63
|
+
"""
|
|
64
|
+
tokens = re.split(r"((?<=[.?!])\s+)", text) # [sent, sep, sent, sep, …]
|
|
46
65
|
out: list[str] = []
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
66
|
+
last_kept: str | None = None
|
|
67
|
+
for i in range(0, len(tokens), 2):
|
|
68
|
+
sentence = tokens[i]
|
|
69
|
+
sep = tokens[i + 1] if i + 1 < len(tokens) else ""
|
|
70
|
+
norm = sentence.strip().lower()
|
|
71
|
+
if (last_kept is not None and norm == last_kept
|
|
72
|
+
and len(sentence.split()) >= _MIN_DEDUP_WORDS):
|
|
73
|
+
continue # drop the duplicate sentence and its separator
|
|
74
|
+
out.append(sentence)
|
|
75
|
+
out.append(sep)
|
|
76
|
+
last_kept = norm
|
|
77
|
+
return "".join(out)
|
|
52
78
|
|
|
53
79
|
|
|
54
80
|
def cleanup(text: str, language: str = "en") -> str:
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.9.6"
|
|
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
|
|
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
|