scriptconv 0.0.4a7__tar.gz → 0.0.4a9__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 (96) hide show
  1. {scriptconv-0.0.4a7/scriptconv.egg-info → scriptconv-0.0.4a9}/PKG-INFO +1 -1
  2. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/diacritics.py +26 -18
  3. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/en.py +32 -11
  4. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/ja.py +16 -2
  5. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/ko.py +8 -1
  6. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/mul.py +69 -34
  7. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/pt.py +8 -1
  8. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/registry.py +7 -3
  9. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/vi.py +8 -1
  10. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/zh.py +8 -1
  11. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/version.py +1 -1
  12. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9/scriptconv.egg-info}/PKG-INFO +1 -1
  13. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv.egg-info/SOURCES.txt +1 -0
  14. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_diacritics_graph.py +46 -0
  15. scriptconv-0.0.4a9/tests/test_phonemizers_friendly_import_errors.py +129 -0
  16. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/LICENSE +0 -0
  17. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/README.md +0 -0
  18. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/pyproject.toml +0 -0
  19. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/requirements.txt +0 -0
  20. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/__init__.py +0 -0
  21. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/__main__.py +0 -0
  22. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/cangjie.py +0 -0
  23. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/conventions.py +0 -0
  24. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/data/__init__.py +0 -0
  25. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/data/cangjie5_tc.tsv.gz +0 -0
  26. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/graph.py +0 -0
  27. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/notation.py +0 -0
  28. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/__init__.py +0 -0
  29. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/__init__.py +0 -0
  30. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/bw2ipa.py +0 -0
  31. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/hangul2ipa.py +0 -0
  32. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/ko_tables/aspiration.csv +0 -0
  33. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/ko_tables/assimilation.csv +0 -0
  34. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/ko_tables/double_coda.csv +0 -0
  35. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/ko_tables/hanja.tsv +0 -0
  36. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/ko_tables/ipa.csv +0 -0
  37. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/ko_tables/neutralization.csv +0 -0
  38. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/ko_tables/tensification.csv +0 -0
  39. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/ko_tables/yale.csv +0 -0
  40. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/shami/__init__.py +0 -0
  41. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/shami/codeswitch.py +0 -0
  42. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/shami/diacritize.py +0 -0
  43. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/shami/dialectal.py +0 -0
  44. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/shami/english_g2p.py +0 -0
  45. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/shami/espeak.py +0 -0
  46. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/shami/frontend.py +0 -0
  47. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/shami/levantine_g2p.py +0 -0
  48. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/shami/normalize.py +0 -0
  49. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/shami/phoneme_inventory.py +0 -0
  50. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_thirdparty/zh_num.py +0 -0
  51. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/__init__.py +0 -0
  52. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/kog2p/LICENSE.md +0 -0
  53. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/kog2p/__init__.py +0 -0
  54. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/kog2p/rulebook.txt +0 -0
  55. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/mantoq/LICENSE.md +0 -0
  56. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/mantoq/__init__.py +0 -0
  57. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/mantoq/buck/__init__.py +0 -0
  58. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/mantoq/buck/phonetise_buckwalter.py +0 -0
  59. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/mantoq/buck/symbols.py +0 -0
  60. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/mantoq/buck/tokenization.py +0 -0
  61. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/mantoq/num2words.py +0 -0
  62. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/_vendored/mantoq/unicode_symbol2label.py +0 -0
  63. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/ar.py +0 -0
  64. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/base.py +0 -0
  65. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/enums.py +0 -0
  66. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/eu.py +0 -0
  67. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/fa.py +0 -0
  68. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/gl.py +0 -0
  69. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/he.py +0 -0
  70. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/mwl.py +0 -0
  71. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/o2ipa.py +0 -0
  72. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/phonemizers/shami.py +0 -0
  73. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/py.typed +0 -0
  74. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/readings.py +0 -0
  75. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/scripts.py +0 -0
  76. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv/translit.py +0 -0
  77. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv.egg-info/dependency_links.txt +0 -0
  78. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv.egg-info/requires.txt +0 -0
  79. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/scriptconv.egg-info/top_level.txt +0 -0
  80. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/setup.cfg +0 -0
  81. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_arpa_stress.py +0 -0
  82. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_cangjie.py +0 -0
  83. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_cli.py +0 -0
  84. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_conventions.py +0 -0
  85. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_diacritics.py +0 -0
  86. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_errors_policy.py +0 -0
  87. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_examples.py +0 -0
  88. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_graph.py +0 -0
  89. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_notation.py +0 -0
  90. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_phonemizers_base.py +0 -0
  91. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_phonemizers_cjk_ar.py +0 -0
  92. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_readings.py +0 -0
  93. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_readings_zh.py +0 -0
  94. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_scripts.py +0 -0
  95. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_scripts_stressonnx_compat.py +0 -0
  96. {scriptconv-0.0.4a7 → scriptconv-0.0.4a9}/tests/test_translit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scriptconv
3
- Version: 0.0.4a7
3
+ Version: 0.0.4a9
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
@@ -245,24 +245,27 @@ def _sense_diacritics_pt(text: str) -> str:
245
245
  return add_extra_diacritics(text)
246
246
 
247
247
 
248
- def diacritize(text: str, lang: str = "und", model=None,
249
- phonikud_model=None, diacritizer_model=None, **_) -> str:
248
+ def diacritize(text: str, lang: str = "und", diacritizer_model=None,
249
+ **kwargs) -> str:
250
250
  """Add pronunciation-disambiguating diacritics to *text* for *lang*.
251
251
 
252
- Four backends, each restoring information ordinary orthography omits but
253
- downstream G2P needs:
254
-
255
- - Hebrew (``he``) — niqqud via phonikud. ``phonikud_model=`` is optional:
256
- omitted, the small public phonikud ONNX model is auto-downloaded and
257
- cached (``$SCRIPTCONV_CACHE``/``$XDG_CACHE_HOME``); pass a path or
258
- zero-arg callable to override.
259
- - Arabic (``ar``) — tashkeel via text2tashkeel (``[tashkeel]``,
260
- ``model=``/``diacritizer_model=``).
252
+ ``diacritizer_model=`` is the one model knob for the diacritizer edge — the
253
+ model for whichever backend the language routes to (there is one such knob,
254
+ parallel to ``phonemizer_model=`` on the ``text -> ipa`` phonemizer edge):
255
+
256
+ - Hebrew (``he``) niqqud via phonikud; ``diacritizer_model`` is the
257
+ phonikud ONNX path (or a zero-arg callable). Optional: when omitted the
258
+ small public phonikud model is auto-downloaded and cached
259
+ (``$SCRIPTCONV_CACHE``/``$XDG_CACHE_HOME``).
260
+ - Arabic (``ar``) — tashkeel via text2tashkeel (``[tashkeel]``);
261
+ ``diacritizer_model`` is the text2tashkeel model name (defaults to
262
+ ``rawi-ensemble``).
261
263
  - East Slavic, Bulgarian/Macedonian/Slovene, Latvian, Armenian, Georgian,
262
264
  and Turkic/Caucasian languages (``STRESS_LANGS``, 26 stressonnx tags) —
263
- word stress via stressonnx (``[stress]``); stress is unwritten or
264
- under-marked in these languages, and in East Slavic unstressed vowels
265
- also reduce, so a missing mark can corrupt more than prosody.
265
+ word stress via stressonnx (``[stress]``); ``diacritizer_model`` is the
266
+ stressonnx model. Stress is unwritten or under-marked in these languages,
267
+ and in East Slavic unstressed vowels also reduce, so a missing mark can
268
+ corrupt more than prosody.
266
269
  - European Portuguese (``pt``/``pt-PT``, never ``pt-BR``) —
267
270
  heterophonic-homograph sense diacritics via bifonia (``[pt]``);
268
271
  ordinary Portuguese orthographic marks that any downstream G2P reads
@@ -272,13 +275,17 @@ def diacritize(text: str, lang: str = "und", model=None,
272
275
  ``ImportError`` naming its extra when the optional dependency is missing
273
276
  — scriptconv never installs anything on the caller's behalf.
274
277
  """
278
+ # one knob for the diacritizer edge; fold the pre-unification kwargs so
279
+ # older callers keep working (phonikud_model was the Hebrew-only name).
280
+ diacritizer_model = (diacritizer_model or kwargs.get("phonikud_model")
281
+ or kwargs.get("model"))
275
282
  family = _diacritizer_family(lang)
276
283
  if family == "he":
277
- return _phonikud(phonikud_model).add_diacritics(text)
284
+ return _phonikud(diacritizer_model).add_diacritics(text)
278
285
  if family == "ar":
279
- return _tashkeel(model or diacritizer_model).diacritize(text)
286
+ return _tashkeel(diacritizer_model).diacritize(text)
280
287
  if family == "stress":
281
- return _stress(text, lang, model)
288
+ return _stress(text, lang, diacritizer_model)
282
289
  if family == "pt":
283
290
  return _sense_diacritics_pt(text)
284
291
  return text
@@ -359,7 +366,8 @@ def register(graph) -> None:
359
366
  """
360
367
  graph.register(
361
368
  Edge("text", DIACRITIZED,
362
- lambda text, lang="und", model=None, **c: diacritize(text, lang, model, **c),
369
+ lambda text, lang="und", diacritizer_model=None, **c:
370
+ diacritize(text, lang, diacritizer_model=diacritizer_model, **c),
363
371
  lossless=False))
364
372
  graph.register(
365
373
  Edge(DIACRITIZED, "text",
@@ -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
 
@@ -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
 
@@ -231,9 +231,13 @@ def register(graph) -> None:
231
231
  from scriptconv.graph import Edge
232
232
 
233
233
  def _text_to_ipa(text: str, lang: str = "und",
234
- override: Optional[Phonemizer] = None, **_):
235
- return phonemizer_for_lang(lang, Alphabet.IPA,
236
- override).phonemize_string(text, lang)
234
+ override: Optional[Phonemizer] = None,
235
+ phonemizer_model: Optional[str] = None, **_):
236
+ # phonemizer_model is the model knob for this (phonemizer) edge —
237
+ # forwarded to model-backed phonemizers (ByT5/Charsiu/…); parallel to
238
+ # diacritizer_model on the text -> text-diacritized edge.
239
+ return phonemizer_for_lang(lang, Alphabet.IPA, override,
240
+ phonemizer_model).phonemize_string(text, lang)
237
241
 
238
242
  graph.register(Edge("text", "ipa", _text_to_ipa, lossless=False))
239
243
  graph.register(Edge("text-diacritized", "ipa", _text_to_ipa, lossless=False))
@@ -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 = 7
5
+ VERSION_ALPHA = 9
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.4a7
3
+ Version: 0.0.4a9
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
@@ -86,6 +86,7 @@ tests/test_graph.py
86
86
  tests/test_notation.py
87
87
  tests/test_phonemizers_base.py
88
88
  tests/test_phonemizers_cjk_ar.py
89
+ tests/test_phonemizers_friendly_import_errors.py
89
90
  tests/test_readings.py
90
91
  tests/test_readings_zh.py
91
92
  tests/test_scripts.py
@@ -101,3 +101,49 @@ class TestDiacriticsGraphExtension(unittest.TestCase):
101
101
 
102
102
  if __name__ == "__main__":
103
103
  unittest.main()
104
+
105
+
106
+ class TestEdgeModelKeys(unittest.TestCase):
107
+ """The two model-bearing edges each read their own context key: the
108
+ diacritizer edge (text -> text-diacritized) reads ``diacritizer_model``,
109
+ the phonemizer edge (text -> ipa) reads ``phonemizer_model``. Neither picks
110
+ up the other's key."""
111
+
112
+ def setUp(self):
113
+ self.graph = DEFAULT_GRAPH.extend(diacritics.register).extend(phonemizers.register)
114
+
115
+ def test_diacritizer_edge_reads_diacritizer_model(self):
116
+ seen = {}
117
+ backend = types.SimpleNamespace(diacritize=lambda t: t)
118
+
119
+ def _spy(m=None):
120
+ seen["m"] = m
121
+ return backend
122
+
123
+ with mock.patch.object(diacritics, "_tashkeel", _spy):
124
+ self.graph.convert("نص", "text", "text-diacritized", lang="ar",
125
+ diacritizer_model="RAWI-X",
126
+ phonemizer_model="IGNORED")
127
+ self.assertEqual(seen["m"], "RAWI-X")
128
+
129
+ def test_phonemizer_edge_reads_phonemizer_model(self):
130
+ from scriptconv.phonemizers import registry
131
+ cap = {}
132
+
133
+ class _P:
134
+ def phonemize_string(self, t, l):
135
+ return "IPA"
136
+
137
+ def _spy(lang, alphabet, override=None, model=None, **k):
138
+ cap["model"] = model
139
+ return _P()
140
+
141
+ with mock.patch.object(registry, "phonemizer_for_lang", _spy):
142
+ self.graph.convert("hello", "text", "ipa", lang="en",
143
+ phonemizer_model="BYT5-CKPT",
144
+ diacritizer_model="IGNORED")
145
+ self.assertEqual(cap["model"], "BYT5-CKPT")
146
+
147
+
148
+ if __name__ == "__main__":
149
+ unittest.main()
@@ -0,0 +1,129 @@
1
+ """Pins the friendly-ImportError convention for the lazy backend imports
2
+ inside phonemizer wrappers.
3
+
4
+ ``get_phonemizer_class`` (see ``registry.py``) already raises a friendly,
5
+ extra-naming ``ImportError`` when the wrapper *module itself* fails to
6
+ import. That does not cover the far more common case: the wrapper module
7
+ imports fine (the backend package is only imported lazily, inside
8
+ ``__init__``/a method), and it is that inner, lazy import that fails when
9
+ the backend is missing. Every wrapper must catch that inner failure and
10
+ re-raise a friendly ``ImportError`` naming the pip extra — never let a bare
11
+ ``ModuleNotFoundError``/``ImportError`` escape.
12
+
13
+ This test forces each wrapper's lazy import to fail (by shadowing the
14
+ backend module in ``sys.modules`` with ``None``, which makes the ``import``
15
+ statement raise ``ImportError`` regardless of whether the package is
16
+ actually installed) and asserts the resulting exception is an
17
+ ``ImportError`` whose message contains an install instruction. No optional
18
+ backend needs to be installed to run this, and nothing is skipped.
19
+ """
20
+ import sys
21
+ import unittest
22
+ from unittest.mock import patch
23
+
24
+ from scriptconv.phonemizers import Phonemizer, get_phonemizer_class
25
+
26
+
27
+ def _trigger_construct(cls):
28
+ cls()
29
+
30
+
31
+ def _trigger_gruut(cls):
32
+ p = cls()
33
+ list(p._text_to_phonemes("hi", "en"))
34
+
35
+
36
+ def _trigger_misaki_en(cls):
37
+ p = cls()
38
+ p._get_phonemizer("en-US")
39
+
40
+
41
+ def _trigger_jieba(cls):
42
+ p = cls()
43
+ p.phonemize_string("你好", "zh")
44
+
45
+
46
+ # member -> (module name to blank out in sys.modules, how to trigger the
47
+ # lazy import that consumes it)
48
+ CASES = {
49
+ Phonemizer.MISAKI: ("misaki", _trigger_misaki_en),
50
+ Phonemizer.MISAKI_EN: ("misaki", _trigger_misaki_en),
51
+ Phonemizer.GRUUT: ("gruut", _trigger_gruut),
52
+ Phonemizer.GORUUT: ("pygoruut", _trigger_construct),
53
+ Phonemizer.EPITRAN: ("epitran", _trigger_construct),
54
+ Phonemizer.TRANSPHONE: ("transphone", _trigger_construct),
55
+ Phonemizer.DEEPPHONEMIZER: ("dp", _trigger_construct),
56
+ Phonemizer.OPENPHONEMIZER: ("openphonemizer", _trigger_construct),
57
+ Phonemizer.G2PEN: ("nltk", _trigger_construct),
58
+ Phonemizer.TUGAPHONE: ("tugaphone", _trigger_construct),
59
+ Phonemizer.CUTLET: ("cutlet", _trigger_construct),
60
+ Phonemizer.PYKAKASI: ("pykakasi", _trigger_construct),
61
+ Phonemizer.VIPHONEME: ("viphoneme", _trigger_construct),
62
+ Phonemizer.G2PK: ("g2pk", _trigger_construct),
63
+ Phonemizer.JIEBA: ("jieba", _trigger_jieba),
64
+ }
65
+
66
+
67
+ class TestLazyBackendImportsAreFriendly(unittest.TestCase):
68
+ def test_all_documented_backends_covered(self):
69
+ # keeps this test list honest if a new Phonemizer member is added
70
+ # backed by one of the wrapper modules touched by this convention
71
+ self.assertEqual(len(CASES), 15)
72
+
73
+ def _assert_friendly(self, member):
74
+ module_name, trigger = CASES[member]
75
+ cls = get_phonemizer_class(member)
76
+ with patch.dict(sys.modules, {module_name: None}):
77
+ with self.assertRaises(ImportError, msg=member) as ctx:
78
+ trigger(cls)
79
+ msg = str(ctx.exception)
80
+ self.assertIn("pip install", msg, member)
81
+
82
+ def test_misaki(self):
83
+ self._assert_friendly(Phonemizer.MISAKI)
84
+
85
+ def test_misaki_en(self):
86
+ self._assert_friendly(Phonemizer.MISAKI_EN)
87
+
88
+ def test_gruut(self):
89
+ self._assert_friendly(Phonemizer.GRUUT)
90
+
91
+ def test_goruut(self):
92
+ self._assert_friendly(Phonemizer.GORUUT)
93
+
94
+ def test_epitran(self):
95
+ self._assert_friendly(Phonemizer.EPITRAN)
96
+
97
+ def test_transphone(self):
98
+ self._assert_friendly(Phonemizer.TRANSPHONE)
99
+
100
+ def test_deepphonemizer(self):
101
+ self._assert_friendly(Phonemizer.DEEPPHONEMIZER)
102
+
103
+ def test_openphonemizer(self):
104
+ self._assert_friendly(Phonemizer.OPENPHONEMIZER)
105
+
106
+ def test_g2pen(self):
107
+ self._assert_friendly(Phonemizer.G2PEN)
108
+
109
+ def test_tugaphone(self):
110
+ self._assert_friendly(Phonemizer.TUGAPHONE)
111
+
112
+ def test_cutlet(self):
113
+ self._assert_friendly(Phonemizer.CUTLET)
114
+
115
+ def test_pykakasi(self):
116
+ self._assert_friendly(Phonemizer.PYKAKASI)
117
+
118
+ def test_viphoneme(self):
119
+ self._assert_friendly(Phonemizer.VIPHONEME)
120
+
121
+ def test_g2pk(self):
122
+ self._assert_friendly(Phonemizer.G2PK)
123
+
124
+ def test_jieba(self):
125
+ self._assert_friendly(Phonemizer.JIEBA)
126
+
127
+
128
+ if __name__ == "__main__":
129
+ unittest.main()
File without changes
File without changes
File without changes