python-voiceio 1.8.2__py3-none-any.whl → 1.8.3__py3-none-any.whl
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-1.8.2.dist-info → python_voiceio-1.8.3.dist-info}/METADATA +1 -1
- {python_voiceio-1.8.2.dist-info → python_voiceio-1.8.3.dist-info}/RECORD +11 -11
- voiceio/__init__.py +1 -1
- voiceio/cli/learn.py +14 -1
- voiceio/learn/teacher.py +1 -1
- voiceio/postprocess.py +32 -3
- voiceio/wordfreq.py +15 -3
- {python_voiceio-1.8.2.dist-info → python_voiceio-1.8.3.dist-info}/WHEEL +0 -0
- {python_voiceio-1.8.2.dist-info → python_voiceio-1.8.3.dist-info}/entry_points.txt +0 -0
- {python_voiceio-1.8.2.dist-info → python_voiceio-1.8.3.dist-info}/licenses/LICENSE +0 -0
- {python_voiceio-1.8.2.dist-info → python_voiceio-1.8.3.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
python_voiceio-1.8.
|
|
2
|
-
voiceio/__init__.py,sha256=
|
|
1
|
+
python_voiceio-1.8.3.dist-info/licenses/LICENSE,sha256=Gz61o8jFTAvZUZyB3nWDB3DQQVuipjfPkVu9W8hBHM0,1072
|
|
2
|
+
voiceio/__init__.py,sha256=8wMM-82fXotDjTthMLVtQUtYqY8Z1cIWh3uJI0OjV3s,22
|
|
3
3
|
voiceio/__main__.py,sha256=xT5QCGGreYMisHO7Lh_Y-xAQ2TOzG2D7npKknGNcWSY,53
|
|
4
4
|
voiceio/app.py,sha256=kOvdXNDovKInC1B5jCNx1iyjasFzLTQy_ZEizEF3YkI,42268
|
|
5
5
|
voiceio/audio_source.py,sha256=rR6NRtNzmZ8uqmNPxareBWTJmuBaOxIGI5QINR_l-qc,9102
|
|
@@ -23,7 +23,7 @@ voiceio/numbers.py,sha256=CXJtlMhmV6_9Z1FtKAF4-RXycw8voV-BYHPBpdw1I6Y,13571
|
|
|
23
23
|
voiceio/pidlock.py,sha256=OZLNskUyv9_F3WgyHZWw8WmKt0W7ioxH6xgHitOwCww,1216
|
|
24
24
|
voiceio/platform.py,sha256=uG_5KeYey_29GCn4nSOQJ1UpoCrBC_m2DR7IeoRnuL4,19142
|
|
25
25
|
voiceio/postcorrect.py,sha256=JN5l9xnyGfYq2_owSUwkJ513QiQQKxCrT3hjU5aJCpg,17322
|
|
26
|
-
voiceio/postprocess.py,sha256=
|
|
26
|
+
voiceio/postprocess.py,sha256=9vpth8u2xOIKU6SimY-hsEDrs4X9YJSI67ZO_f2PpxM,6048
|
|
27
27
|
voiceio/power.py,sha256=mKiG5ioHbzgtW2S-OKGFOPzZFbG0HwUUyMn8kE5JRoQ,681
|
|
28
28
|
voiceio/privacy.py,sha256=FqnA9jx9RCqWW-i6Dh7JcT7vzz97kbUGyQEvsHLvpqc,573
|
|
29
29
|
voiceio/prompt.py,sha256=9iza8KoQxvO_U9NPMJ8Ep7j-zkIs9nW8N5sacCRpsEk,2904
|
|
@@ -39,13 +39,13 @@ voiceio/vad.py,sha256=VPeefjJ0Ml5hOuRE2V6BWoBXmzawxLB7if5UP3vBKiM,6030
|
|
|
39
39
|
voiceio/vocab_stats.py,sha256=md_85Bs0UsM-tmSZDEyQFe0C7z4UnsZ5QSV5vuZ8A5w,5408
|
|
40
40
|
voiceio/vocabulary.py,sha256=i2RRYGmT81tJcYhnZ4sVuVbmZjClqFZVlNUUFa35tuY,11351
|
|
41
41
|
voiceio/watchdog.py,sha256=XnrpM3ULeUoJ-sMt_aNNzJSLqOuK4ocJAMHQ00knYuw,7838
|
|
42
|
-
voiceio/wordfreq.py,sha256=
|
|
42
|
+
voiceio/wordfreq.py,sha256=AxmVgpQRfm2YAYQpHCRRrBUTpa12y_ngiFNScrGr4GM,3543
|
|
43
43
|
voiceio/cli/__init__.py,sha256=EM2Qa1COWkqSQ9hFuPKdf8_OD5JoGsY3QCpQA-waEJQ,8353
|
|
44
44
|
voiceio/cli/configcmd.py,sha256=YF7t-LkwEYEGH4qcO86xehWVX0LvltqxjDFpMF5ljjA,2858
|
|
45
45
|
voiceio/cli/corrections.py,sha256=OBjgiOhtWlTQpQq0HLAvk5-EfKJ7NNt-hnNBHm7obEc,4247
|
|
46
46
|
voiceio/cli/doctor.py,sha256=P8bOn4xedMistlKHhNeeGxRsmogV6jBVKc-CUbo0LXs,7769
|
|
47
47
|
voiceio/cli/history.py,sha256=7FvEEgdw8jf5XXvC7-4S7iIpcjBbn7dx4czIijg61C0,2597
|
|
48
|
-
voiceio/cli/learn.py,sha256=
|
|
48
|
+
voiceio/cli/learn.py,sha256=ajVP7RRFBLXa0Sf-pbYrccPpuB58oHjd4wR9JkcOwtw,32914
|
|
49
49
|
voiceio/cli/models.py,sha256=fye_mHbsXLY11uRC1BCDlucCHPny9ftoehAeUJPMJv0,4263
|
|
50
50
|
voiceio/cli/servicecmd.py,sha256=2St2WfLkt06EpC51I5i9Ykv_pvLKzu_dz01M-29O3-4,4830
|
|
51
51
|
voiceio/cli/uninstall.py,sha256=Vqnq6-4hjhDrxqkU_1wtXkY5BU_Tqa-TjZA0Qpf12e4,8921
|
|
@@ -85,7 +85,7 @@ voiceio/learn/metrics.py,sha256=b2xWUzPpNpXHn6zgS1upPbEcpW3ExH9SmSLIUzPhStQ,5174
|
|
|
85
85
|
voiceio/learn/quiet.py,sha256=5Ov3mvE1G3CjhwFQOuJvGeju242zFASi29tketnUyew,1523
|
|
86
86
|
voiceio/learn/review.py,sha256=X5qs5yEf20Ao3gMhsbW3Eur62hSqy9AYlv08Pcrllxo,5572
|
|
87
87
|
voiceio/learn/store.py,sha256=MauWhGaI2drMbiChIZOblpPS_uVAw-1sNWGyGACHYw8,12816
|
|
88
|
-
voiceio/learn/teacher.py,sha256=
|
|
88
|
+
voiceio/learn/teacher.py,sha256=FhCv08Js3chs4Vbh0VN3ZI9p05HFxeeeFJF8jTjjzPQ,3751
|
|
89
89
|
voiceio/learn/train.py,sha256=FtZMa4ddsPMRUxkyqC-Qf9OveZmgRMHiGXx5WHj_jT8,18214
|
|
90
90
|
voiceio/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
91
|
voiceio/models/silero_vad.onnx,sha256=GhU6IvRQnikqlOZ9b5uF6N6yW0mIaCt-F0xlJ52HiOM,2327524
|
|
@@ -132,8 +132,8 @@ voiceio/typers/pynput_type.py,sha256=f3w131YWJfNGd4IAcWtuKd2Gcwu3467QwGW3OBUEXNI
|
|
|
132
132
|
voiceio/typers/wtype.py,sha256=11vOAKHjMat-fITd4sUoUWwgniyVp65EbH3i4mL6Vhg,1762
|
|
133
133
|
voiceio/typers/xdotool.py,sha256=OuT_zW_z07jSAqu9Cmn_gL1mJfQsiX62ecqFRvpAarQ,1332
|
|
134
134
|
voiceio/typers/ydotool.py,sha256=oY5EXB-nI7PvxTL_5Tb3JjfvyhmLNYNnUFvkb1XUn_U,5978
|
|
135
|
-
python_voiceio-1.8.
|
|
136
|
-
python_voiceio-1.8.
|
|
137
|
-
python_voiceio-1.8.
|
|
138
|
-
python_voiceio-1.8.
|
|
139
|
-
python_voiceio-1.8.
|
|
135
|
+
python_voiceio-1.8.3.dist-info/METADATA,sha256=Eeq7y0M9-FMTabCANoqNTbMuqXGPCNBUZ8VN_3q1lN0,7519
|
|
136
|
+
python_voiceio-1.8.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
137
|
+
python_voiceio-1.8.3.dist-info/entry_points.txt,sha256=iDIDmhvQ3xeN_CWsFne1t9STZ08I25I_wp0xmde_vMs,94
|
|
138
|
+
python_voiceio-1.8.3.dist-info/top_level.txt,sha256=piwtn309lD6uexQyXdZ-efAVBJF9y6Wfr48Z-8zkNhg,8
|
|
139
|
+
python_voiceio-1.8.3.dist-info/RECORD,,
|
voiceio/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.8.
|
|
1
|
+
__version__ = "1.8.3"
|
voiceio/cli/learn.py
CHANGED
|
@@ -417,6 +417,16 @@ def cmd_train(args: argparse.Namespace) -> None:
|
|
|
417
417
|
f" (compare with the current model, then `voiceio learn promote {run.name}`)")
|
|
418
418
|
|
|
419
419
|
|
|
420
|
+
def _train_language() -> str:
|
|
421
|
+
"""The language a fine-tune is trained under: the one dictation decodes
|
|
422
|
+
with. "auto" has no single answer, so it has to be set first."""
|
|
423
|
+
lang = _cfg().model.language or "en"
|
|
424
|
+
if lang == "auto":
|
|
425
|
+
raise SystemExit("Fine-tuning needs one language: set it with "
|
|
426
|
+
"`voiceio config set model.language es` (your dictation language).")
|
|
427
|
+
return lang
|
|
428
|
+
|
|
429
|
+
|
|
420
430
|
def _train(ds: Path, *, base: str = "small", epochs: int = 4, synthetic_share: float = 0.3,
|
|
421
431
|
full: bool = False, remote: bool = False) -> Path | None:
|
|
422
432
|
"""Export `ds` and fine-tune on it; returns the run dir (None for --remote)."""
|
|
@@ -433,8 +443,11 @@ def _train(ds: Path, *, base: str = "small", epochs: int = 4, synthetic_share: f
|
|
|
433
443
|
return None
|
|
434
444
|
train.require_train_deps()
|
|
435
445
|
hotwords = (folder / train.HOTWORDS_FILE).read_text(encoding="utf-8")
|
|
446
|
+
# The language token the model is trained under must be the one it decodes
|
|
447
|
+
# with: training a Spanish voice under "en" taught it the wrong prompt.
|
|
436
448
|
hf = train.train(folder, run, train.TrainConfig(
|
|
437
|
-
base=base, epochs=epochs, decoder_only=False if full else None, hotwords=hotwords
|
|
449
|
+
base=base, epochs=epochs, decoder_only=False if full else None, hotwords=hotwords,
|
|
450
|
+
language=_train_language()))
|
|
438
451
|
train.convert(hf, run / "ct2")
|
|
439
452
|
return run
|
|
440
453
|
|
voiceio/learn/teacher.py
CHANGED
|
@@ -45,7 +45,7 @@ class WhisperTeacher:
|
|
|
45
45
|
# A background batch job (see cli.learn._yield_cpu): use every core idle.
|
|
46
46
|
self._model = load_model(model, device=device, compute_type=compute_type,
|
|
47
47
|
cpu_threads=max(4, (os.cpu_count() or 8) // 2))
|
|
48
|
-
self._language = language
|
|
48
|
+
self._language = None if language in ("", "auto") else language # None: detect
|
|
49
49
|
terms = [t for t in vocabulary if t]
|
|
50
50
|
# Prompt, not hotwords: hotwords are capped at 223 tokens; a prompt
|
|
51
51
|
# of the most important terms still biases spelling. The tokenizer
|
voiceio/postprocess.py
CHANGED
|
@@ -23,6 +23,21 @@ _NO_CASE_LANGUAGES = frozenset({"zh", "ja", "ko", "ar", "he", "th", "hi", "bn",
|
|
|
23
23
|
_FILLER_RE = re.compile(
|
|
24
24
|
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]*",
|
|
25
25
|
)
|
|
26
|
+
# A word (2+ letters, not part of a dotted name or an ellipsis) ending in "."
|
|
27
|
+
# and followed by a lowercase word: a segment boundary, not a sentence end.
|
|
28
|
+
_SEGMENT_STOP_RE = re.compile(r"(?<![.\w])([A-Za-zÀ-ɏ]{2,})\.(\s+)(?=[a-zà-ÿ])")
|
|
29
|
+
_ABBREVIATIONS = frozenset({
|
|
30
|
+
"etc", "vs", "approx", "cf", "al", "mr", "mrs", "ms", "dr", "st", "jr", "sr",
|
|
31
|
+
"inc", "ltd", "co", "fig", "eq", "vol", "no", "pp", "ca", "min", "max", "sec",
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _not_an_ending(word: str) -> bool:
|
|
36
|
+
"""The word before a "." is an abbreviation or a dotted name, not a
|
|
37
|
+
sentence's last word."""
|
|
38
|
+
return "." in word or word.lower() in _ABBREVIATIONS
|
|
39
|
+
|
|
40
|
+
|
|
26
41
|
# A re-decode artifact is a whole duplicated sentence; require this many words
|
|
27
42
|
# so emphatic short repeats ("No. No.", "Stop. Stop.") are preserved.
|
|
28
43
|
_MIN_DEDUP_WORDS = 4
|
|
@@ -95,14 +110,28 @@ def cleanup(text: str, language: str = "en") -> str:
|
|
|
95
110
|
text = re.sub(r"\s+([.,;:?!])", r"\1", text)
|
|
96
111
|
|
|
97
112
|
if language not in _NO_CASE_LANGUAGES:
|
|
113
|
+
# Whisper closes every segment with "." even mid-sentence, and the next
|
|
114
|
+
# segment then carries on in lowercase ("a bit bigger. and remove the
|
|
115
|
+
# latch"): on real dictation that was 1 in 4 notes. A lowercase
|
|
116
|
+
# continuation means the sentence did not end, so it gets a comma —
|
|
117
|
+
# before the capitalization below would make the false stop permanent.
|
|
118
|
+
text = _SEGMENT_STOP_RE.sub(
|
|
119
|
+
lambda m: m.group(0) if m.group(1).lower() in _ABBREVIATIONS
|
|
120
|
+
else m.group(1) + "," + m.group(2),
|
|
121
|
+
text,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
98
125
|
# Capitalize first character
|
|
99
126
|
text = text[0].upper() + text[1:]
|
|
100
127
|
|
|
101
128
|
# Capitalize after sentence-ending punctuation (an ellipsis is a
|
|
102
|
-
# pause, not an ending
|
|
129
|
+
# pause, not an ending, and so is the dot of "etc.", "e.g." or a name
|
|
130
|
+
# like "hugo0.com")
|
|
103
131
|
text = re.sub(
|
|
104
|
-
r"(?<!\.\.)([.?!]\s+)([a-z\u00E0-\u00FF])",
|
|
105
|
-
lambda m: m.group(
|
|
132
|
+
r"(\S*)(?<!\.\.)([.?!]\s+)([a-z\u00E0-\u00FF])",
|
|
133
|
+
lambda m: m.group(0) if m.group(2)[0] == "." and _not_an_ending(m.group(1))
|
|
134
|
+
else m.group(1) + m.group(2) + m.group(3).upper(),
|
|
106
135
|
text,
|
|
107
136
|
)
|
|
108
137
|
|
voiceio/wordfreq.py
CHANGED
|
@@ -41,14 +41,26 @@ _CONTRACTIONS = frozenset({
|
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
def _zipf(word: str, language: str) -> float:
|
|
45
|
+
"""Zipf frequency, 0.0 for a language wordfreq has no list for. "auto"
|
|
46
|
+
(detect per dictation) has no single list either: score it as English,
|
|
47
|
+
which is where the jargon in a vocabulary list usually comes from."""
|
|
48
|
+
if language in ("", "auto"):
|
|
49
|
+
language = "en"
|
|
50
|
+
try:
|
|
51
|
+
return zipf_frequency(word.lower(), language)
|
|
52
|
+
except LookupError:
|
|
53
|
+
return 0.0
|
|
54
|
+
|
|
55
|
+
|
|
44
56
|
def is_common(word: str, language: str = "en", threshold: float = _ZIPF_COMMON_THRESHOLD) -> bool:
|
|
45
57
|
"""Check if a word is common (high frequency). Used for suggesting corrections."""
|
|
46
|
-
return
|
|
58
|
+
return _zipf(word, language) >= threshold
|
|
47
59
|
|
|
48
60
|
|
|
49
61
|
def rank(word: str, language: str = "en") -> int | None:
|
|
50
62
|
"""Get approximate frequency rank. None if not found."""
|
|
51
|
-
z =
|
|
63
|
+
z = _zipf(word, language)
|
|
52
64
|
if z == 0.0:
|
|
53
65
|
return None
|
|
54
66
|
# Convert zipf to approximate rank (higher zipf = lower rank number)
|
|
@@ -61,7 +73,7 @@ def is_known(word: str, language: str = "en") -> bool:
|
|
|
61
73
|
wl = word.lower()
|
|
62
74
|
if wl in _CONTRACTIONS:
|
|
63
75
|
return True
|
|
64
|
-
return
|
|
76
|
+
return _zipf(wl, language) >= _ZIPF_KNOWN_THRESHOLD
|
|
65
77
|
|
|
66
78
|
|
|
67
79
|
def extract_words(text: str) -> list[str]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|