scriptconv 0.0.4a8__tar.gz → 0.0.4a10__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.
Files changed (99) hide show
  1. {scriptconv-0.0.4a8/scriptconv.egg-info → scriptconv-0.0.4a10}/PKG-INFO +3 -2
  2. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/README.md +2 -1
  3. scriptconv-0.0.4a10/scriptconv/phonemizers/_thirdparty/vosk_g2p.py +135 -0
  4. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/en.py +32 -11
  5. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/enums.py +2 -0
  6. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/ja.py +16 -2
  7. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/ko.py +8 -1
  8. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/mul.py +69 -34
  9. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/pt.py +8 -1
  10. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/registry.py +5 -0
  11. scriptconv-0.0.4a10/scriptconv/phonemizers/ru.py +114 -0
  12. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/vi.py +8 -1
  13. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/zh.py +8 -1
  14. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/version.py +1 -1
  15. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10/scriptconv.egg-info}/PKG-INFO +3 -2
  16. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv.egg-info/SOURCES.txt +4 -0
  17. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_phonemizers_base.py +4 -2
  18. scriptconv-0.0.4a10/tests/test_phonemizers_friendly_import_errors.py +129 -0
  19. scriptconv-0.0.4a10/tests/test_phonemizers_ru.py +218 -0
  20. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/LICENSE +0 -0
  21. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/pyproject.toml +0 -0
  22. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/requirements.txt +0 -0
  23. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/__init__.py +0 -0
  24. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/__main__.py +0 -0
  25. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/cangjie.py +0 -0
  26. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/conventions.py +0 -0
  27. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/data/__init__.py +0 -0
  28. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/data/cangjie5_tc.tsv.gz +0 -0
  29. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/diacritics.py +0 -0
  30. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/graph.py +0 -0
  31. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/notation.py +0 -0
  32. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/__init__.py +0 -0
  33. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/__init__.py +0 -0
  34. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/bw2ipa.py +0 -0
  35. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/hangul2ipa.py +0 -0
  36. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/ko_tables/aspiration.csv +0 -0
  37. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/ko_tables/assimilation.csv +0 -0
  38. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/ko_tables/double_coda.csv +0 -0
  39. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/ko_tables/hanja.tsv +0 -0
  40. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/ko_tables/ipa.csv +0 -0
  41. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/ko_tables/neutralization.csv +0 -0
  42. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/ko_tables/tensification.csv +0 -0
  43. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/ko_tables/yale.csv +0 -0
  44. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/shami/__init__.py +0 -0
  45. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/shami/codeswitch.py +0 -0
  46. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/shami/diacritize.py +0 -0
  47. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/shami/dialectal.py +0 -0
  48. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/shami/english_g2p.py +0 -0
  49. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/shami/espeak.py +0 -0
  50. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/shami/frontend.py +0 -0
  51. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/shami/levantine_g2p.py +0 -0
  52. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/shami/normalize.py +0 -0
  53. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/shami/phoneme_inventory.py +0 -0
  54. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_thirdparty/zh_num.py +0 -0
  55. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/__init__.py +0 -0
  56. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/kog2p/LICENSE.md +0 -0
  57. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/kog2p/__init__.py +0 -0
  58. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/kog2p/rulebook.txt +0 -0
  59. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/mantoq/LICENSE.md +0 -0
  60. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/mantoq/__init__.py +0 -0
  61. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/mantoq/buck/__init__.py +0 -0
  62. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/mantoq/buck/phonetise_buckwalter.py +0 -0
  63. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/mantoq/buck/symbols.py +0 -0
  64. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/mantoq/buck/tokenization.py +0 -0
  65. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/mantoq/num2words.py +0 -0
  66. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/_vendored/mantoq/unicode_symbol2label.py +0 -0
  67. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/ar.py +0 -0
  68. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/base.py +0 -0
  69. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/eu.py +0 -0
  70. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/fa.py +0 -0
  71. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/gl.py +0 -0
  72. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/he.py +0 -0
  73. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/mwl.py +0 -0
  74. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/o2ipa.py +0 -0
  75. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/phonemizers/shami.py +0 -0
  76. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/py.typed +0 -0
  77. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/readings.py +0 -0
  78. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/scripts.py +0 -0
  79. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv/translit.py +0 -0
  80. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv.egg-info/dependency_links.txt +0 -0
  81. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv.egg-info/requires.txt +0 -0
  82. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/scriptconv.egg-info/top_level.txt +0 -0
  83. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/setup.cfg +0 -0
  84. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_arpa_stress.py +0 -0
  85. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_cangjie.py +0 -0
  86. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_cli.py +0 -0
  87. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_conventions.py +0 -0
  88. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_diacritics.py +0 -0
  89. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_diacritics_graph.py +0 -0
  90. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_errors_policy.py +0 -0
  91. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_examples.py +0 -0
  92. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_graph.py +0 -0
  93. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_notation.py +0 -0
  94. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_phonemizers_cjk_ar.py +0 -0
  95. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_readings.py +0 -0
  96. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_readings_zh.py +0 -0
  97. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_scripts.py +0 -0
  98. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_scripts_stressonnx_compat.py +0 -0
  99. {scriptconv-0.0.4a8 → scriptconv-0.0.4a10}/tests/test_translit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptconv
3
- Version: 0.0.4a8
3
+ Version: 0.0.4a10
4
4
  Summary: Zero-dependency script & phoneme-notation core — ISO-15924 detection & metadata, IPA↔ARPABET/X-SAMPA/Lexique/Kirshenbaum/Cotovía/RFE, Buckwalter↔Arabic, Hangul→jamo, kana
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/TigreGotico/scriptconv
@@ -350,7 +350,8 @@ phonemize("hello", "en", override=Phonemizer.GRUUT)
350
350
 
351
351
  Defaults resolve in-house engines first: an explicit per-language chain
352
352
  (Arabic → arbtok, Basque → euskaphone, Mirandese, Portuguese → tugaphone,
353
- Hebrew → phonikud, Galician → Cotovía for its own notation), then
353
+ Hebrew → phonikud, Galician → Cotovía and Russian → vosk for their own
354
+ notations), then
354
355
  orthography2ipa wherever it has a language spec, then espeak as the last
355
356
  resort. Arabic never falls back past arbtok — a missing engine raises rather
356
357
  than silently degrading. Every backend resolves lazily; a missing package
@@ -223,7 +223,8 @@ phonemize("hello", "en", override=Phonemizer.GRUUT)
223
223
 
224
224
  Defaults resolve in-house engines first: an explicit per-language chain
225
225
  (Arabic → arbtok, Basque → euskaphone, Mirandese, Portuguese → tugaphone,
226
- Hebrew → phonikud, Galician → Cotovía for its own notation), then
226
+ Hebrew → phonikud, Galician → Cotovía and Russian → vosk for their own
227
+ notations), then
227
228
  orthography2ipa wherever it has a language spec, then espeak as the last
228
229
  resort. Arabic never falls back past arbtok — a missing engine raises rather
229
230
  than silently degrading. Every backend resolves lazily; a missing package
@@ -0,0 +1,135 @@
1
+ """
2
+ Russian grapheme-to-phoneme rules and pronunciation-dictionary loader for the
3
+ Vosk-TTS voices.
4
+
5
+ Vendored from ``vosk_tts/g2p.py`` — https://github.com/alphacep/vosk-tts
6
+ (Apache-2.0) — so the Vosk Russian front-end is available without the
7
+ ``vosk-tts`` package at runtime.
8
+
9
+ ``convert`` is a faithful port of ``vosk_tts/g2p.py``: it turns an (optionally
10
+ stress-marked) Russian word into the Vosk phoneme inventory — palatalised
11
+ consonants get a trailing ``j`` (``bj``, ``tj`` …), vowels carry a stress digit
12
+ (``a0`` unstressed, ``a1`` stressed). A ``+`` immediately before a vowel marks
13
+ it as stressed; without any ``+`` every vowel is emitted unstressed.
14
+
15
+ The shipped ``dictionary`` file (word → phonemes) overrides the rules for known
16
+ words; ``load_dictionary`` reads both the historical ``word phon…`` layout and
17
+ the newer ``word prob phon…`` layout (keeping the highest-probability variant).
18
+ The rules alone already produce usable Russian, so the dictionary is optional.
19
+ """
20
+ import os
21
+ from typing import Dict, List, Optional
22
+
23
+ # Cyrillic letters that soften the preceding consonant.
24
+ softletters = set(u"яёюиье")
25
+ # Contexts after which я/ю/е/ё gains a leading glide /j/.
26
+ startsyl = set(u"#ъьаяоёуюэеиы-")
27
+ # Markers dropped from the final phoneme stream.
28
+ others = set(["#", "+", "-", u"ь", u"ъ"])
29
+
30
+ softhard_cons = {
31
+ u"б": u"b", u"в": u"v", u"г": u"g", u"Г": u"g", u"д": u"d",
32
+ u"з": u"z", u"к": u"k", u"л": u"l", u"м": u"m", u"н": u"n",
33
+ u"п": u"p", u"р": u"r", u"с": u"s", u"т": u"t", u"ф": u"f",
34
+ u"х": u"h",
35
+ }
36
+
37
+ other_cons = {
38
+ u"ж": u"zh", u"ц": u"c", u"ч": u"ch", u"ш": u"sh",
39
+ u"щ": u"sch", u"й": u"j",
40
+ }
41
+
42
+ vowels = {
43
+ u"а": u"a", u"я": u"a", u"у": u"u", u"ю": u"u", u"о": u"o",
44
+ u"ё": u"o", u"э": u"e", u"е": u"e", u"и": u"i", u"ы": u"y",
45
+ }
46
+
47
+
48
+ def pallatize(phones: List[tuple]) -> None:
49
+ """In-place: map consonants to their (palatalised) phoneme, looking one
50
+ character ahead to decide whether a soft vowel follows."""
51
+ for i, phone in enumerate(phones[:-1]):
52
+ if phone[0] in softhard_cons:
53
+ if phones[i + 1][0] in softletters:
54
+ phones[i] = (softhard_cons[phone[0]] + "j", 0)
55
+ else:
56
+ phones[i] = (softhard_cons[phone[0]], 0)
57
+ if phone[0] in other_cons:
58
+ phones[i] = (other_cons[phone[0]], 0)
59
+
60
+
61
+ def convert_vowels(phones: List[tuple]) -> List[str]:
62
+ """Emit vowels with their stress digit, inserting a glide /j/ before
63
+ iotated vowels at syllable starts."""
64
+ new_phones: List[str] = []
65
+ prev = ""
66
+ for phone in phones:
67
+ if prev in startsyl:
68
+ if phone[0] in set(u"яюеё"):
69
+ new_phones.append("j")
70
+ if phone[0] in vowels:
71
+ new_phones.append(vowels[phone[0]] + str(phone[1]))
72
+ else:
73
+ new_phones.append(phone[0])
74
+ prev = phone[0]
75
+ return new_phones
76
+
77
+
78
+ def convert(stressword: str) -> str:
79
+ """Convert a (possibly ``+``-stress-marked) Russian word to a
80
+ space-separated Vosk phoneme string."""
81
+ phones = ("#" + stressword + "#")
82
+
83
+ # Assign stress marks: a '+' sets the stress flag for the next character.
84
+ stress_phones = []
85
+ stress = 0
86
+ for phone in phones:
87
+ if phone == "+":
88
+ stress = 1
89
+ else:
90
+ stress_phones.append((phone, stress))
91
+ stress = 0
92
+
93
+ pallatize(stress_phones)
94
+ phones = convert_vowels(stress_phones)
95
+ phones = [x for x in phones if x not in others]
96
+ return " ".join(phones)
97
+
98
+
99
+ def load_dictionary(path: Optional[str]) -> Dict[str, List[str]]:
100
+ """
101
+ Load a Vosk pronunciation dictionary into a ``word -> [phoneme, …]`` map.
102
+
103
+ Handles both file layouts:
104
+ - ``word phon1 phon2 …`` (older voices, e.g. 0.1)
105
+ - ``word prob phon1 phon2 …`` (newer voices; highest prob wins)
106
+
107
+ Returns an empty dict when ``path`` is falsy or missing — the rule-based
108
+ :func:`convert` fallback covers any out-of-dictionary word.
109
+ """
110
+ dic: Dict[str, List[str]] = {}
111
+ if not path or not os.path.isfile(path):
112
+ return dic
113
+ probs: Dict[str, float] = {}
114
+ with open(path, encoding="utf-8") as f:
115
+ for line in f:
116
+ parts = line.split()
117
+ if len(parts) < 2:
118
+ continue
119
+ word, rest = parts[0], parts[1:]
120
+ # The second column is a probability only when it parses as a float;
121
+ # a real phoneme (a0, sch, …) never does.
122
+ try:
123
+ prob = float(rest[0])
124
+ phones = rest[1:]
125
+ except ValueError:
126
+ prob = None
127
+ phones = rest
128
+ if not phones:
129
+ continue
130
+ if prob is None:
131
+ dic.setdefault(word, phones)
132
+ elif probs.get(word, -1.0) < prob:
133
+ dic[word] = phones
134
+ probs[word] = prob
135
+ return dic
@@ -16,9 +16,16 @@ class DeepPhonemizer(BasePhonemizer):
16
16
  def __init__(self, model="latin_ipa_forward.pt"):
17
17
  import os
18
18
 
19
- import dp
20
- from dp.phonemizer import Phonemizer
21
- import torch
19
+ try:
20
+ import dp
21
+ from dp.phonemizer import Phonemizer
22
+ import torch
23
+ except ImportError as e:
24
+ raise ImportError(
25
+ "deep-phonemizer and torch are required for the DeepPhonemizer "
26
+ "phonemizer. Install them with 'pip install deep-phonemizer torch' "
27
+ "(or 'pip install scriptconv[en-phonemizers]')."
28
+ ) from e
22
29
  # needed for latest torch version
23
30
  torch.serialization.add_safe_globals([dp.preprocessing.text.Preprocessor])
24
31
  torch.serialization.add_safe_globals([dp.preprocessing.text.LanguageTokenizer])
@@ -90,10 +97,17 @@ class OpenPhonemizer(BasePhonemizer):
90
97
  """
91
98
 
92
99
  def __init__(self):
93
- from openphonemizer import OpenPhonemizer
94
- import torch
95
- # needed for latest torch version
96
- import dp
100
+ try:
101
+ from openphonemizer import OpenPhonemizer
102
+ import torch
103
+ # needed for latest torch version
104
+ import dp
105
+ except ImportError as e:
106
+ raise ImportError(
107
+ "openphonemizer and torch are required for the OpenPhonemizer "
108
+ "phonemizer. Install them with 'pip install openphonemizer torch' "
109
+ "(or 'pip install scriptconv[en-phonemizers]')."
110
+ ) from e
97
111
  torch.serialization.add_safe_globals([dp.preprocessing.text.Preprocessor])
98
112
  torch.serialization.add_safe_globals([dp.preprocessing.text.LanguageTokenizer])
99
113
  torch.serialization.add_safe_globals([dp.preprocessing.text.SequenceTokenizer])
@@ -142,10 +156,17 @@ class G2PEnPhonemizer(BasePhonemizer):
142
156
 
143
157
  def __init__(self, alphabet=Alphabet.IPA):
144
158
  assert alphabet in [Alphabet.IPA, Alphabet.ARPA]
145
- import nltk
146
- nltk.download('averaged_perceptron_tagger_eng')
147
- nltk.download('cmudict')
148
- from g2p_en import G2p
159
+ try:
160
+ import nltk
161
+ nltk.download('averaged_perceptron_tagger_eng')
162
+ nltk.download('cmudict')
163
+ from g2p_en import G2p
164
+ except ImportError as e:
165
+ raise ImportError(
166
+ "g2p_en is required for the G2PEn phonemizer. "
167
+ "Install it with 'pip install g2p_en' "
168
+ "(or 'pip install scriptconv[en-phonemizers]')."
169
+ ) from e
149
170
  self.g2p = G2p()
150
171
  super().__init__(alphabet)
151
172
 
@@ -30,6 +30,7 @@ class Alphabet(str, Enum):
30
30
  BUCKWALTER = "buckwalter"
31
31
  MANTOQ = "mantoq" # ar — Halabi Arabic-Phonetiser inventory # ar
32
32
  CANGJIE = "cangjie" # zh (Cangjie input method)
33
+ VOSK = "vosk" # ru — vosk-tts phoneme inventory (a0, bj, sch ...)
33
34
  GRAPHEMES = "graphemes" # plain text / grapheme input (user-side)
34
35
 
35
36
 
@@ -73,6 +74,7 @@ class Phonemizer(str, Enum):
73
74
  PYPINYIN = "pypinyin" # chinese
74
75
  XPINYIN = "xpinyin" # chinese
75
76
  JIEBA = "jieba" # chinese (not a real phonemizer!)
77
+ VOSK = "vosk" # russian (no ipa!)
76
78
  SHAMI = "shami" # Levantine Arabic / English code-switching (ShamiVITS)
77
79
  ARBTOK = "arbtok" # arabic (dialect-aware, undiacritized text; o2i lattice)
78
80
  EUSKAPHONE = "euskaphone" # basque (dialect-aware; o2i lattice)
@@ -40,7 +40,14 @@ class CutletPhonemizer(BasePhonemizer):
40
40
  # If `use_foreign_spelling` is true, output will use the foreign spelling
41
41
  # provided in a UniDic lemma when available. For example, "カツ" will
42
42
  # become "cutlet" instead of "katsu".
43
- import cutlet
43
+ try:
44
+ import cutlet
45
+ except ImportError as e:
46
+ raise ImportError(
47
+ "cutlet is required for the Cutlet phonemizer. "
48
+ "Install it with 'pip install cutlet' "
49
+ "(or 'pip install scriptconv[ja-phonemizers]')."
50
+ ) from e
44
51
  self.g2p = cutlet.Cutlet(alphabet)
45
52
  self.g2p.use_foreign_spelling = use_foreign_spelling
46
53
  super().__init__(alphabet)
@@ -73,7 +80,14 @@ class PyKakasiPhonemizer(BasePhonemizer):
73
80
  def __init__(self, alphabet=Alphabet.HEPBURN):
74
81
  assert alphabet in [Alphabet.HEPBURN, Alphabet.KANA, Alphabet.HIRA]
75
82
  # kana, hira, hepburn
76
- import pykakasi
83
+ try:
84
+ import pykakasi
85
+ except ImportError as e:
86
+ raise ImportError(
87
+ "pykakasi is required for the PyKakasi phonemizer. "
88
+ "Install it with 'pip install pykakasi' "
89
+ "(or 'pip install scriptconv[ja-phonemizers]')."
90
+ ) from e
77
91
  self.g2p = pykakasi.kakasi()
78
92
  super().__init__(alphabet)
79
93
 
@@ -10,7 +10,14 @@ class G2PKPhonemizer(BasePhonemizer):
10
10
  def __init__(self, descriptive=True, group_vowels=True, to_syl=True,
11
11
  alphabet=Alphabet.IPA):
12
12
  assert alphabet in [Alphabet.IPA, Alphabet.HANGUL]
13
- from g2pk import G2p
13
+ try:
14
+ from g2pk import G2p
15
+ except ImportError as e:
16
+ raise ImportError(
17
+ "g2pk is required for the G2PK phonemizer. "
18
+ "Install it with 'pip install g2pk' "
19
+ "(or 'pip install scriptconv[ko]')."
20
+ ) from e
14
21
  self.g2p = G2p()
15
22
  self.descriptive = descriptive
16
23
  self.group_vowels = group_vowels
@@ -435,7 +435,14 @@ class GruutPhonemizer(BasePhonemizer):
435
435
  Yields lists of word phonemes for each sentence.
436
436
  """
437
437
  lang = self.get_lang(lang)
438
- import gruut
438
+ try:
439
+ import gruut
440
+ except ImportError as e:
441
+ raise ImportError(
442
+ "gruut is required for the Gruut phonemizer. "
443
+ "Install it with 'pip install gruut' "
444
+ "(or 'pip install scriptconv[gruut]')."
445
+ ) from e
439
446
  for sentence in gruut.sentences(text, lang=lang):
440
447
  sent_phonemes = [w.phonemes for w in sentence if w.phonemes]
441
448
  if sentence and not sent_phonemes:
@@ -601,8 +608,15 @@ class GoruutPhonemizer(BasePhonemizer):
601
608
 
602
609
  def __init__(self, remote_url=None):
603
610
  super().__init__(Alphabet.IPA)
604
- from pygoruut.pygoruut import Pygoruut
605
- from pygoruut.pygoruut_languages import PygoruutLanguages
611
+ try:
612
+ from pygoruut.pygoruut import Pygoruut
613
+ from pygoruut.pygoruut_languages import PygoruutLanguages
614
+ except ImportError as e:
615
+ raise ImportError(
616
+ "pygoruut is required for the Goruut phonemizer. "
617
+ "Install it with 'pip install pygoruut' "
618
+ "(or 'pip install scriptconv[goruut]')."
619
+ ) from e
606
620
 
607
621
  self.pygoruut_langs = PygoruutLanguages()
608
622
  if remote_url is not None:
@@ -665,7 +679,14 @@ class EpitranPhonemizer(BasePhonemizer):
665
679
 
666
680
  def __init__(self):
667
681
  super().__init__(Alphabet.IPA)
668
- import epitran
682
+ try:
683
+ import epitran
684
+ except ImportError as e:
685
+ raise ImportError(
686
+ "epitran is required for the Epitran phonemizer. "
687
+ "Install it with 'pip install epitran' "
688
+ "(or 'pip install scriptconv[epitran]')."
689
+ ) from e
669
690
  self.epitran = epitran
670
691
  self._epis: Dict[str, epitran.Epitran] = {}
671
692
 
@@ -735,35 +756,42 @@ class MisakiPhonemizer(BasePhonemizer):
735
756
  """
736
757
  lang = self.get_lang(lang)
737
758
 
738
- if lang == "zh":
739
- if self.g2p_zh is None:
740
- from misaki.zh import ZHG2P
741
- self.g2p_zh = ZHG2P(version=self.zh_version)
742
- return self.g2p_zh
743
- elif lang == "ko":
744
- if self.g2p_ko is None:
745
- from misaki.ko import KOG2P
746
- self.g2p_ko = KOG2P()
747
- return self.g2p_ko
748
- elif lang == "vi":
749
- if self.g2p_vi is None:
750
- from misaki.vi import VIG2P
751
- self.g2p_vi = VIG2P()
752
- return self.g2p_vi
753
- elif lang == "ja":
754
- if self.g2p_ja is None:
755
- from misaki.ja import JAG2P
756
- self.g2p_ja = JAG2P()
757
- return self.g2p_ja
758
- else:
759
- if self.g2p_en is None:
760
- from misaki import en
761
- self.g2p_en = en.G2P()
762
- if lang == "en-GB":
763
- self.g2p_en.british = True
764
- elif lang == "en-US":
765
- self.g2p_en.british = False
766
- return self.g2p_en
759
+ try:
760
+ if lang == "zh":
761
+ if self.g2p_zh is None:
762
+ from misaki.zh import ZHG2P
763
+ self.g2p_zh = ZHG2P(version=self.zh_version)
764
+ return self.g2p_zh
765
+ elif lang == "ko":
766
+ if self.g2p_ko is None:
767
+ from misaki.ko import KOG2P
768
+ self.g2p_ko = KOG2P()
769
+ return self.g2p_ko
770
+ elif lang == "vi":
771
+ if self.g2p_vi is None:
772
+ from misaki.vi import VIG2P
773
+ self.g2p_vi = VIG2P()
774
+ return self.g2p_vi
775
+ elif lang == "ja":
776
+ if self.g2p_ja is None:
777
+ from misaki.ja import JAG2P
778
+ self.g2p_ja = JAG2P()
779
+ return self.g2p_ja
780
+ else:
781
+ if self.g2p_en is None:
782
+ from misaki import en
783
+ self.g2p_en = en.G2P()
784
+ if lang == "en-GB":
785
+ self.g2p_en.british = True
786
+ elif lang == "en-US":
787
+ self.g2p_en.british = False
788
+ return self.g2p_en
789
+ except ImportError as e:
790
+ raise ImportError(
791
+ "misaki is required for the Misaki phonemizer. "
792
+ "Install it with 'pip install misaki' "
793
+ "(or 'pip install scriptconv[misaki]')."
794
+ ) from e
767
795
 
768
796
  def phonemize_string(self, text: str, lang: str) -> str:
769
797
  pho = self._get_phonemizer(lang)
@@ -1386,7 +1414,14 @@ class TransphonePhonemizer(BasePhonemizer):
1386
1414
 
1387
1415
  def __init__(self):
1388
1416
  super().__init__(Alphabet.IPA)
1389
- from transphone import read_tokenizer
1417
+ try:
1418
+ from transphone import read_tokenizer
1419
+ except ImportError as e:
1420
+ raise ImportError(
1421
+ "transphone is required for the Transphone phonemizer. "
1422
+ "Install it with 'pip install transphone' "
1423
+ "(or 'pip install scriptconv[transphone]')."
1424
+ ) from e
1390
1425
  self.read_tokenizer = read_tokenizer
1391
1426
  self._models = {}
1392
1427
 
@@ -7,7 +7,14 @@ from scriptconv.phonemizers.enums import Alphabet
7
7
  class TugaphonePhonemizer(BasePhonemizer):
8
8
 
9
9
  def __init__(self):
10
- from tugaphone import TugaPhonemizer
10
+ try:
11
+ from tugaphone import TugaPhonemizer
12
+ except ImportError as e:
13
+ raise ImportError(
14
+ "tugaphone is required for the Tugaphone phonemizer. "
15
+ "Install it with 'pip install tugaphone' "
16
+ "(or 'pip install scriptconv[pt-phonemizers]')."
17
+ ) from e
11
18
  self.tuga = TugaPhonemizer()
12
19
  super().__init__(Alphabet.IPA)
13
20
 
@@ -71,6 +71,7 @@ PHONEMIZER_REGISTRY: Dict[Phonemizer, Tuple[str, str, Optional[str]]] = {
71
71
  _P.MANTOQ: (f"{_BASE}.ar", "MantoqPhonemizer", "ar-phonemizers"),
72
72
  _P.ARBTOK: (f"{_BASE}.ar", "ArbtokPhonemizer", "ar-phonemizers"),
73
73
  _P.SHAMI: (f"{_BASE}.shami", "ShamiPhonemizer", "shami"),
74
+ _P.VOSK: (f"{_BASE}.ru", "VoskPhonemizer", "phonemizers"),
74
75
  }
75
76
 
76
77
 
@@ -135,6 +136,7 @@ _EMITS: Dict[Phonemizer, Tuple[Alphabet, ...]] = {
135
136
  _P.TUGAPHONE: (Alphabet.IPA,),
136
137
  _P.PHONIKUD: (Alphabet.IPA,),
137
138
  _P.COTOVIA: (Alphabet.COTOVIA,),
139
+ _P.VOSK: (Alphabet.VOSK,),
138
140
  _P.ESPEAK: (Alphabet.IPA,),
139
141
  }
140
142
 
@@ -147,6 +149,9 @@ LANG_DEFAULTS: Dict[str, Tuple[Phonemizer, ...]] = {
147
149
  "pt": (_P.TUGAPHONE,),
148
150
  "he": (_P.PHONIKUD,),
149
151
  "gl": (_P.COTOVIA, _P.ORTHOGRAPHY2IPA, _P.ESPEAK),
152
+ # vosk emits its own inventory, so it is the Russian default only
153
+ # when that notation is requested (same shape as Cotovía above)
154
+ "ru": (_P.VOSK, _P.ORTHOGRAPHY2IPA, _P.ESPEAK),
150
155
  }
151
156
 
152
157
  # Languages whose explicit entry is exhaustive: no generic fallback beyond it.
@@ -0,0 +1,114 @@
1
+ """Russian phonemizers.
2
+
3
+ Currently one backend: the Vosk-TTS front-end, wrapping the vendored
4
+ :mod:`scriptconv.phonemizers._thirdparty.vosk_g2p` rules so the alphacep
5
+ Russian voices can be driven from text without the ``vosk-tts`` package.
6
+ """
7
+ import re
8
+ from typing import List, Optional
9
+
10
+ from quebra_frases import sentence_tokenize
11
+
12
+ from scriptconv.phonemizers.base import BasePhonemizer, PhonemizedChunks
13
+ from scriptconv.phonemizers.enums import Alphabet
14
+ from scriptconv.phonemizers._thirdparty.vosk_g2p import convert, load_dictionary
15
+
16
+ __all__ = ["VoskPhonemizer"]
17
+
18
+
19
+ class VoskPhonemizer(BasePhonemizer):
20
+ """
21
+ Russian phonemizer for the Vosk-TTS voices (alphacep).
22
+
23
+ It reproduces ``vosk_tts``'s grapheme-to-phoneme exactly: each word is
24
+ looked up in the voice's pronunciation ``dictionary`` (word -> phonemes),
25
+ falling back to the rule-based
26
+ :func:`~scriptconv.phonemizers._thirdparty.vosk_g2p.convert` for
27
+ out-of-dictionary words. Spaces and punctuation are kept as their own
28
+ tokens (Vosk feeds them to the model as short/long pauses); the BOS ``^`` /
29
+ EOS ``$`` markers and the inter-phoneme blanks belong to the consumer's
30
+ tokenizer, so they are *not* emitted here.
31
+
32
+ The output is the Vosk phoneme inventory (``a0``, ``bj``, ``sch`` …), not
33
+ IPA, so the only supported alphabet is :attr:`Alphabet.VOSK` — like
34
+ Cotovía, this backend is eligible only when its own notation is requested.
35
+
36
+ The dictionary is optional: without it the rules alone still produce usable
37
+ Russian (only the curated stress and exception entries are lost).
38
+ scriptconv never downloads anything — the caller resolves the file and
39
+ passes its path as ``model`` (the registry's ``phonemizer_model`` knob).
40
+
41
+ Args:
42
+ alphabet (Alphabet): must be :attr:`Alphabet.VOSK`.
43
+ model (Optional[str]): path to the voice's ``dictionary`` file. When
44
+ absent or missing, only the rule-based fallback is used.
45
+ """
46
+
47
+ # Matches the per-character split used by vosk_tts: spaces and punctuation
48
+ # are captured so they survive as standalone pause tokens.
49
+ _SPLIT = re.compile(r'([,.?!;:"() ])')
50
+
51
+ def __init__(self, alphabet: Alphabet = Alphabet.VOSK,
52
+ model: Optional[str] = None):
53
+ if alphabet != Alphabet.VOSK:
54
+ raise ValueError(
55
+ "VoskPhonemizer emits the vosk-tts phoneme inventory, not "
56
+ f"{Alphabet(alphabet).value!r} — use Alphabet.VOSK")
57
+ self._dict_path = model
58
+ self._dictionary: Optional[dict] = None # lazy: dictionaries are large
59
+ super().__init__(alphabet)
60
+
61
+ @property
62
+ def dictionary(self) -> dict:
63
+ """The loaded ``word -> [phoneme, …]`` map (empty without a path)."""
64
+ if self._dictionary is None:
65
+ self._dictionary = load_dictionary(self._dict_path)
66
+ return self._dictionary
67
+
68
+ @classmethod
69
+ def get_lang(cls, target_lang: str) -> str:
70
+ return cls.match_lang(target_lang, ["ru-RU"])
71
+
72
+ def _g2p_tokens(self, text: str) -> List[str]:
73
+ """Word/punctuation stream -> Vosk phoneme tokens (no BOS/EOS/blanks)."""
74
+ tokens: List[str] = []
75
+ # the em dash is a pause, and vosk only knows the ASCII hyphen
76
+ text = text.replace("—", "-")
77
+ for word in self._SPLIT.split(text.lower()):
78
+ if word == "":
79
+ continue
80
+ if self._SPLIT.match(word) or word == "-":
81
+ # space or punctuation: kept verbatim as a pause token
82
+ tokens.append(word)
83
+ elif word in self.dictionary:
84
+ tokens.extend(self.dictionary[word])
85
+ else:
86
+ tokens.extend(convert(word).split())
87
+ return tokens
88
+
89
+ def phonemize(self, text: str, lang: str) -> PhonemizedChunks:
90
+ """Sentence-level lists of Vosk phoneme tokens.
91
+
92
+ Punctuation is preserved (it drives pausing); each sentence becomes one
93
+ synthesis chunk. Multi-character tokens (``sch``, ``bj``, ``a1``) stay
94
+ whole, so :meth:`BasePhonemizer.phonemize`'s per-character split is
95
+ deliberately bypassed.
96
+ """
97
+ self.get_lang(lang)
98
+ if not text:
99
+ return []
100
+ if self.normalizer is not None:
101
+ text = self.normalizer(text, lang)
102
+ results: PhonemizedChunks = []
103
+ for sentence in sentence_tokenize(text):
104
+ tokens = self._g2p_tokens(sentence)
105
+ if tokens:
106
+ results.append(tokens)
107
+ return results
108
+
109
+ def phonemize_to_list(self, text: str, lang: str) -> List[str]:
110
+ self.get_lang(lang)
111
+ return self._g2p_tokens(text.lower())
112
+
113
+ def phonemize_string(self, text: str, lang: str) -> str:
114
+ return " ".join(self.phonemize_to_list(text, lang))
@@ -5,7 +5,14 @@ from scriptconv.phonemizers.enums import Alphabet
5
5
  class VIPhonemePhonemizer(BasePhonemizer):
6
6
  """https://github.com/v-nhandt21/Viphoneme"""
7
7
  def __init__(self):
8
- from viphoneme import vi2IPA
8
+ try:
9
+ from viphoneme import vi2IPA
10
+ except ImportError as e:
11
+ raise ImportError(
12
+ "viphoneme is required for the VIPhoneme phonemizer. "
13
+ "Install it with 'pip install viphoneme' "
14
+ "(or 'pip install scriptconv[vi]')."
15
+ ) from e
9
16
  self.g2p = vi2IPA
10
17
  super().__init__(Alphabet.IPA)
11
18
 
@@ -44,7 +44,14 @@ class JiebaPhonemizer(BasePhonemizer):
44
44
  Returns:
45
45
  str: Tokenized text with words separated by spaces.
46
46
  """
47
- import jieba
47
+ try:
48
+ import jieba
49
+ except ImportError as e:
50
+ raise ImportError(
51
+ "jieba is required for the Jieba phonemizer. "
52
+ "Install it with 'pip install jieba' "
53
+ "(or 'pip install scriptconv[zh-phonemizers]')."
54
+ ) from e
48
55
  lang = self.get_lang(lang)
49
56
  seg_list = jieba.cut(text, cut_all=False)
50
57
  # NFKC folds full-width digits (e.g. "3") to ASCII so num2str's
@@ -2,7 +2,7 @@
2
2
  VERSION_MAJOR = 0
3
3
  VERSION_MINOR = 0
4
4
  VERSION_BUILD = 4
5
- VERSION_ALPHA = 8
5
+ VERSION_ALPHA = 10
6
6
  # END_VERSION_BLOCK
7
7
 
8
8
  VERSION_STR = f"{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_BUILD}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptconv
3
- Version: 0.0.4a8
3
+ Version: 0.0.4a10
4
4
  Summary: Zero-dependency script & phoneme-notation core — ISO-15924 detection & metadata, IPA↔ARPABET/X-SAMPA/Lexique/Kirshenbaum/Cotovía/RFE, Buckwalter↔Arabic, Hangul→jamo, kana
5
5
  License: Apache-2.0
6
6
  Project-URL: Homepage, https://github.com/TigreGotico/scriptconv
@@ -350,7 +350,8 @@ phonemize("hello", "en", override=Phonemizer.GRUUT)
350
350
 
351
351
  Defaults resolve in-house engines first: an explicit per-language chain
352
352
  (Arabic → arbtok, Basque → euskaphone, Mirandese, Portuguese → tugaphone,
353
- Hebrew → phonikud, Galician → Cotovía for its own notation), then
353
+ Hebrew → phonikud, Galician → Cotovía and Russian → vosk for their own
354
+ notations), then
354
355
  orthography2ipa wherever it has a language spec, then espeak as the last
355
356
  resort. Arabic never falls back past arbtok — a missing engine raises rather
356
357
  than silently degrading. Every backend resolves lazily; a missing package