scriptconv 0.0.4a3__tar.gz → 0.0.4a5__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.
- {scriptconv-0.0.4a3/scriptconv.egg-info → scriptconv-0.0.4a5}/PKG-INFO +13 -1
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/README.md +11 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/pyproject.toml +1 -1
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/__init__.py +2 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/cangjie.py +8 -7
- scriptconv-0.0.4a5/scriptconv/diacritics.py +162 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/graph.py +9 -1
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/notation.py +9 -4
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/hangul2ipa.py +41 -15
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/kog2p/__init__.py +11 -1
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/base.py +29 -4
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/en.py +3 -4
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/fa.py +8 -1
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/gl.py +5 -3
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/he.py +8 -1
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/registry.py +6 -2
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/zh.py +48 -8
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/scripts.py +4 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/version.py +1 -1
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5/scriptconv.egg-info}/PKG-INFO +13 -1
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv.egg-info/SOURCES.txt +3 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv.egg-info/requires.txt +1 -0
- scriptconv-0.0.4a5/tests/test_diacritics.py +90 -0
- scriptconv-0.0.4a5/tests/test_diacritics_graph.py +104 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_phonemizers_cjk_ar.py +59 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_scripts.py +12 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/LICENSE +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/requirements.txt +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/__main__.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/conventions.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/data/__init__.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/data/cangjie5_tc.tsv.gz +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/__init__.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/__init__.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/bw2ipa.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/aspiration.csv +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/assimilation.csv +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/double_coda.csv +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/hanja.tsv +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/ipa.csv +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/neutralization.csv +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/tensification.csv +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/yale.csv +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/__init__.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/codeswitch.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/diacritize.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/dialectal.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/english_g2p.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/espeak.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/frontend.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/levantine_g2p.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/normalize.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/phoneme_inventory.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/zh_num.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/__init__.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/kog2p/LICENSE.md +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/kog2p/rulebook.txt +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/LICENSE.md +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/__init__.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/buck/__init__.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/buck/phonetise_buckwalter.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/buck/symbols.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/buck/tokenization.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/num2words.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/unicode_symbol2label.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/ar.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/enums.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/eu.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/ja.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/ko.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/mul.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/mwl.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/o2ipa.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/pt.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/shami.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/vi.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/py.typed +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/readings.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/translit.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv.egg-info/dependency_links.txt +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv.egg-info/top_level.txt +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/setup.cfg +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_arpa_stress.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_cangjie.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_cli.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_conventions.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_errors_policy.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_examples.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_graph.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_notation.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_phonemizers_base.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_readings.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_readings_zh.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_scripts_stressonnx_compat.py +0 -0
- {scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/tests/test_translit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scriptconv
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4a5
|
|
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
|
|
@@ -105,6 +105,7 @@ Requires-Dist: scriptconv[phonemizers]; extra == "en-phonemizers"
|
|
|
105
105
|
Requires-Dist: g2p_en; extra == "en-phonemizers"
|
|
106
106
|
Requires-Dist: openphonemizer; extra == "en-phonemizers"
|
|
107
107
|
Requires-Dist: deep-phonemizer; extra == "en-phonemizers"
|
|
108
|
+
Requires-Dist: requests; extra == "en-phonemizers"
|
|
108
109
|
Provides-Extra: ar-phonemizers
|
|
109
110
|
Requires-Dist: scriptconv[phonemizers]; extra == "ar-phonemizers"
|
|
110
111
|
Requires-Dist: arbtok; extra == "ar-phonemizers"
|
|
@@ -391,6 +392,17 @@ p = GraphemePhonemizer()
|
|
|
391
392
|
p.add_diacritics("Tenho muita sede hoje.", "pt") # 'Tenho muita sêde hoje.' (thirst)
|
|
392
393
|
```
|
|
393
394
|
|
|
395
|
+
Diacritization also joins the graph, like phonemization, via
|
|
396
|
+
`scriptconv.diacritics.register` — a `text -> text-diacritized` edge, opt-in,
|
|
397
|
+
with `text -> ipa` routing unchanged:
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
from scriptconv import diacritics
|
|
401
|
+
g = DEFAULT_GRAPH.extend(diacritics.register).extend(phonemizers.register)
|
|
402
|
+
g.convert("Tenho muita sede hoje.", "text", "text-diacritized", lang="pt")
|
|
403
|
+
# 'Tenho muita sêde hoje.'
|
|
404
|
+
```
|
|
405
|
+
|
|
394
406
|
Stress is unwritten or under-marked in all 26 covered languages; East Slavic
|
|
395
407
|
is the clearest case, where unstressed vowels also reduce (e.g. Russian
|
|
396
408
|
о→[ɐ]/[ə]), so a missing mark corrupts more than prosody there. stressonnx is
|
|
@@ -265,6 +265,17 @@ p = GraphemePhonemizer()
|
|
|
265
265
|
p.add_diacritics("Tenho muita sede hoje.", "pt") # 'Tenho muita sêde hoje.' (thirst)
|
|
266
266
|
```
|
|
267
267
|
|
|
268
|
+
Diacritization also joins the graph, like phonemization, via
|
|
269
|
+
`scriptconv.diacritics.register` — a `text -> text-diacritized` edge, opt-in,
|
|
270
|
+
with `text -> ipa` routing unchanged:
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
from scriptconv import diacritics
|
|
274
|
+
g = DEFAULT_GRAPH.extend(diacritics.register).extend(phonemizers.register)
|
|
275
|
+
g.convert("Tenho muita sede hoje.", "text", "text-diacritized", lang="pt")
|
|
276
|
+
# 'Tenho muita sêde hoje.'
|
|
277
|
+
```
|
|
278
|
+
|
|
268
279
|
Stress is unwritten or under-marked in all 26 covered languages; East Slavic
|
|
269
280
|
is the clearest case, where unstressed vowels also reduce (e.g. Russian
|
|
270
281
|
о→[ɐ]/[ə]), so a missing mark corrupts more than prosody there. stressonnx is
|
|
@@ -58,7 +58,7 @@ pt = ["scriptconv[phonemizers]", "bifonia>=0.1.1"]
|
|
|
58
58
|
ja-phonemizers = ["scriptconv[phonemizers]", "pyopenjtalk", "cutlet", "pykakasi>=2.3,<3"]
|
|
59
59
|
ko = ["scriptconv[phonemizers]", "g2pk", "regex"]
|
|
60
60
|
zh-phonemizers = ["scriptconv[phonemizers]", "jieba", "pypinyin>=0.50,<1", "xpinyin", "g2pM", "pinyin_to_ipa"]
|
|
61
|
-
en-phonemizers = ["scriptconv[phonemizers]", "g2p_en", "openphonemizer", "deep-phonemizer"]
|
|
61
|
+
en-phonemizers = ["scriptconv[phonemizers]", "g2p_en", "openphonemizer", "deep-phonemizer", "requests"]
|
|
62
62
|
ar-phonemizers = ["scriptconv[phonemizers]", "arbtok", "ovos-number-parser"]
|
|
63
63
|
shami = ["scriptconv[phonemizers]", "regex"]
|
|
64
64
|
test = ["pytest", "pytest-timeout", "pykakasi>=2.3,<3", "pypinyin>=0.50,<1", "quebra-frases", "langcodes", "regex", "ovos-number-parser", "bifonia>=0.1.1"]
|
|
@@ -69,6 +69,7 @@ from scriptconv.notation import (
|
|
|
69
69
|
ipa_to_cotovia,
|
|
70
70
|
rfe_to_ipa,
|
|
71
71
|
ipa_to_rfe,
|
|
72
|
+
mantoq_to_ipa,
|
|
72
73
|
looks_like_ipa,
|
|
73
74
|
)
|
|
74
75
|
from scriptconv.translit import (
|
|
@@ -140,6 +141,7 @@ __all__ = [
|
|
|
140
141
|
"ipa_to_cotovia",
|
|
141
142
|
"rfe_to_ipa",
|
|
142
143
|
"ipa_to_rfe",
|
|
144
|
+
"mantoq_to_ipa",
|
|
143
145
|
"looks_like_ipa",
|
|
144
146
|
# translit
|
|
145
147
|
"decompose_hangul",
|
|
@@ -15,7 +15,7 @@ from __future__ import annotations
|
|
|
15
15
|
|
|
16
16
|
import gzip
|
|
17
17
|
from importlib.resources import files
|
|
18
|
-
from typing import Dict, Optional
|
|
18
|
+
from typing import Dict, List, Optional
|
|
19
19
|
|
|
20
20
|
__all__ = ["cangjie_code", "to_cangjie"]
|
|
21
21
|
|
|
@@ -49,15 +49,16 @@ def to_cangjie(text: str, sep: str = " ") -> str:
|
|
|
49
49
|
"""
|
|
50
50
|
table = _table()
|
|
51
51
|
tokens = []
|
|
52
|
-
|
|
52
|
+
raw_run: List[str] = []
|
|
53
53
|
for ch in text:
|
|
54
54
|
code = table.get(ch)
|
|
55
55
|
if code is not None:
|
|
56
|
+
if raw_run:
|
|
57
|
+
tokens.append("".join(raw_run))
|
|
58
|
+
raw_run = []
|
|
56
59
|
tokens.append(code)
|
|
57
|
-
last_raw = False
|
|
58
|
-
elif last_raw:
|
|
59
|
-
tokens[-1] += ch
|
|
60
60
|
else:
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
raw_run.append(ch)
|
|
62
|
+
if raw_run:
|
|
63
|
+
tokens.append("".join(raw_run))
|
|
63
64
|
return sep.join(tokens)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Diacritization as a graph extension — parallel to :mod:`scriptconv.phonemizers`.
|
|
2
|
+
|
|
3
|
+
Diacritization (Arabic tashkeel, Hebrew niqqud, East-Slavic/Turkic/Caucasian
|
|
4
|
+
word stress, European-Portuguese homograph sense marks) is, architecturally,
|
|
5
|
+
just another transform between text representations: it maps the ``"text"``
|
|
6
|
+
node to a lang-contextual ``"text-diacritized"`` node. Like
|
|
7
|
+
:func:`scriptconv.phonemizers.register` it is opt-in — ``DEFAULT_GRAPH`` stays
|
|
8
|
+
free of it until a caller extends a graph::
|
|
9
|
+
|
|
10
|
+
from scriptconv.graph import DEFAULT_GRAPH
|
|
11
|
+
from scriptconv import diacritics, phonemizers
|
|
12
|
+
g = DEFAULT_GRAPH.extend(diacritics.register).extend(phonemizers.register)
|
|
13
|
+
|
|
14
|
+
g.convert("замок стоит на горе", "text", "text-diacritized", lang="ru")
|
|
15
|
+
# 'за́мок сто́ит на горе́' — just the diacritized text
|
|
16
|
+
|
|
17
|
+
g.convert("Tenho muita sede hoje.", "text", "text-diacritized", lang="pt")
|
|
18
|
+
# 'Tenho muita sêde hoje.'
|
|
19
|
+
|
|
20
|
+
Routing ``"text" -> "ipa"`` still takes the direct phonemization edge by
|
|
21
|
+
default: the diacritization edge is model-based (``lossless=False``), so it
|
|
22
|
+
never out-prices a direct phonemization, and enabling this extension does not
|
|
23
|
+
silently change phonemization output. The diacritized route is taken only
|
|
24
|
+
when a caller asks for ``"text-diacritized"`` explicitly.
|
|
25
|
+
|
|
26
|
+
A future per-engine *stance* would push this further into topology rather than
|
|
27
|
+
a flag: an engine that *requires* vocalized input would carry only a
|
|
28
|
+
``"text-diacritized" -> "ipa"`` edge (forcing the detour), one that *forbids*
|
|
29
|
+
diacritics would carry only ``"text" -> "ipa"`` (making the detour
|
|
30
|
+
unroutable), and a tolerant engine (e.g. arbtok, which self-vocalizes) carries
|
|
31
|
+
both. The strip direction (``"text-diacritized" -> "text"``) is registered too, but
|
|
32
|
+
gated to languages whose marks are removable *overlay* diacritics —
|
|
33
|
+
Arabic/Hebrew vocalization and East-Slavic/Turkic/Caucasian stress. Strip
|
|
34
|
+
removes only the specific overlay codepoints each backend adds (combining
|
|
35
|
+
acute/grave for stress, tashkeel for Arabic, niqqud for Hebrew) — never a
|
|
36
|
+
blanket combining-mark filter — so precomposed native letters survive
|
|
37
|
+
(Cyrillic й/ё, Latvian macrons, Azerbaijani ç/ö, Arabic hamza carriers أ إ آ ؤ
|
|
38
|
+
ئ). The gate uses exact primary-subtag matching, so Aragonese (``arg``),
|
|
39
|
+
Herero (``her``), Mapudungun (``arn``) etc. are never misread as Arabic/Hebrew.
|
|
40
|
+
Languages whose diacritics are native orthography (European Portuguese, via
|
|
41
|
+
bifonia) refuse the strip with :class:`ValueError` instead of silently
|
|
42
|
+
corrupting the spelling (``café`` must never become ``cafe``). This split
|
|
43
|
+
tracks which backend/model produced the marks: overlay backends (phonikud,
|
|
44
|
+
tashkeel, stressonnx) are strippable; spelling-integral backends (bifonia) are
|
|
45
|
+
not.
|
|
46
|
+
"""
|
|
47
|
+
from scriptconv.graph import Edge
|
|
48
|
+
from scriptconv.phonemizers.base import _diacritizer_family
|
|
49
|
+
|
|
50
|
+
#: The lang-contextual node produced by diacritization. Like ``"text"`` it is
|
|
51
|
+
#: meaningful only with ``lang=`` context and exists only in opted-in graphs.
|
|
52
|
+
DIACRITIZED = "text-diacritized"
|
|
53
|
+
|
|
54
|
+
_DEFAULT_PHONEMIZER = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _get_phonemizer(phonikud_model=None):
|
|
58
|
+
# any concrete BasePhonemizer subclass works here — it's just a vessel
|
|
59
|
+
# for add_diacritics(); GraphemePhonemizer is picked for having no extra
|
|
60
|
+
# runtime dependencies of its own
|
|
61
|
+
global _DEFAULT_PHONEMIZER
|
|
62
|
+
if phonikud_model:
|
|
63
|
+
from scriptconv.phonemizers.base import GraphemePhonemizer
|
|
64
|
+
return GraphemePhonemizer(phonikud_model=phonikud_model)
|
|
65
|
+
if _DEFAULT_PHONEMIZER is None:
|
|
66
|
+
from scriptconv.phonemizers.base import GraphemePhonemizer
|
|
67
|
+
_DEFAULT_PHONEMIZER = GraphemePhonemizer()
|
|
68
|
+
return _DEFAULT_PHONEMIZER
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def diacritize(text: str, lang: str = "und", model=None, **context) -> str:
|
|
72
|
+
"""Add pronunciation-disambiguating diacritics to *text* for *lang*.
|
|
73
|
+
|
|
74
|
+
Thin wrapper over :meth:`BasePhonemizer.add_diacritics` — the single
|
|
75
|
+
dispatch (he→phonikud, ar→tashkeel, ru/uk/be/…→stressonnx, pt→bifonia).
|
|
76
|
+
Reuses a cached default phonemizer so repeated calls don't reload models; a
|
|
77
|
+
per-call Hebrew ``phonikud_model`` path (via context) builds a fresh one.
|
|
78
|
+
"""
|
|
79
|
+
return _get_phonemizer(context.get("phonikud_model")).add_diacritics(text, lang, model)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Combining marks each diacritization backend overlays onto the bare text.
|
|
83
|
+
# Strip removes ONLY these codepoints — never a blanket combining-mark filter,
|
|
84
|
+
# which (via NFD) would decompose and destroy precomposed native letters:
|
|
85
|
+
# Cyrillic й/ё, Latvian ī, Azerbaijani ç/ö, and Arabic hamza carriers أ إ آ ؤ ئ.
|
|
86
|
+
_STRESS_MARKS = frozenset({0x0300, 0x0301}) # combining grave / acute
|
|
87
|
+
# U+0300 is included defensively alongside the U+0301 that stressonnx
|
|
88
|
+
# actually emits, in case any backend/locale marks secondary stress with a
|
|
89
|
+
# grave instead of an acute; harmless to strip since native precomposed
|
|
90
|
+
# letters (e.g. Cyrillic й/ё) are unaffected either way.
|
|
91
|
+
_ARABIC_MARKS = frozenset(range(0x064B, 0x0660)) | frozenset({0x0670}) # tashkeel + dagger alef
|
|
92
|
+
_HEBREW_MARKS = (frozenset(range(0x05B0, 0x05BE))
|
|
93
|
+
| frozenset({0x05BF, 0x05C1, 0x05C2, 0x05C4, 0x05C5, 0x05C7})) # niqqud
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _overlay_marks(lang: str) -> "frozenset | None":
|
|
97
|
+
"""The overlay codepoints for *lang*'s diacritization backend, or None.
|
|
98
|
+
|
|
99
|
+
Resolves the backend family through :func:`_diacritizer_family` (the shared
|
|
100
|
+
lang→backend routing), so this can never disagree with
|
|
101
|
+
:meth:`BasePhonemizer.add_diacritics` about which language a backend owns.
|
|
102
|
+
Only *overlay* families are strippable: ``"pt"`` (bifonia sense marks are
|
|
103
|
+
native orthography) and ``None`` both return None.
|
|
104
|
+
"""
|
|
105
|
+
family = _diacritizer_family(lang)
|
|
106
|
+
if family == "stress":
|
|
107
|
+
return _STRESS_MARKS
|
|
108
|
+
if family == "ar":
|
|
109
|
+
return _ARABIC_MARKS
|
|
110
|
+
if family == "he":
|
|
111
|
+
return _HEBREW_MARKS
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _supports_strip(lang: str) -> bool:
|
|
116
|
+
"""True for languages whose diacritics are removable overlays (stress marks,
|
|
117
|
+
Arabic tashkeel, Hebrew niqqud) rather than native orthography."""
|
|
118
|
+
return _overlay_marks(lang) is not None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def strip_diacritics(text: str, lang: str = "und", **_) -> str:
|
|
122
|
+
"""Remove the overlay diacritics *lang*'s backend adds, recovering the bare
|
|
123
|
+
text WITHOUT touching native letters.
|
|
124
|
+
|
|
125
|
+
Removes only the specific overlay codepoints (combining acute/grave for
|
|
126
|
+
stress, tashkeel for Arabic, niqqud for Hebrew) — precomposed native
|
|
127
|
+
letters (Cyrillic й/ё, Latvian ī, Azerbaijani ç, Arabic hamza carriers) are
|
|
128
|
+
left intact. Raises :class:`ValueError` for languages whose diacritics are
|
|
129
|
+
part of the native orthography (e.g. European Portuguese), where any removal
|
|
130
|
+
would corrupt the spelling.
|
|
131
|
+
"""
|
|
132
|
+
marks = _overlay_marks(lang)
|
|
133
|
+
if marks is None:
|
|
134
|
+
raise ValueError(
|
|
135
|
+
f"cannot strip diacritics for lang={lang!r}: its diacritics are "
|
|
136
|
+
"part of the native orthography, so removal would corrupt the "
|
|
137
|
+
"spelling. strip is defined only for overlay diacritics "
|
|
138
|
+
"(Arabic/Hebrew vocalization, East-Slavic/Turkic stress).")
|
|
139
|
+
return "".join(c for c in text if ord(c) not in marks)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def register(graph) -> None:
|
|
143
|
+
"""Opt-in graph integration: add the diacritize/strip edge pair.
|
|
144
|
+
|
|
145
|
+
``text -> text-diacritized`` is model-based (``lossless=False``), so a
|
|
146
|
+
direct ``text -> ipa`` phonemization always out-prices the detour —
|
|
147
|
+
enabling this extension is safe and non-invasive. Pair with
|
|
148
|
+
:func:`scriptconv.phonemizers.register` to make
|
|
149
|
+
``"text-diacritized" -> "ipa"`` reachable.
|
|
150
|
+
|
|
151
|
+
``text-diacritized -> text`` (:func:`strip_diacritics`) is lossless and
|
|
152
|
+
cheap, but gated: it raises for languages whose diacritics are native
|
|
153
|
+
orthography rather than removable overlays.
|
|
154
|
+
"""
|
|
155
|
+
graph.register(
|
|
156
|
+
Edge("text", DIACRITIZED,
|
|
157
|
+
lambda text, lang="und", model=None, **c: diacritize(text, lang, model, **c),
|
|
158
|
+
lossless=False))
|
|
159
|
+
graph.register(
|
|
160
|
+
Edge(DIACRITIZED, "text",
|
|
161
|
+
lambda text, lang="und", **_: strip_diacritics(text, lang),
|
|
162
|
+
lossless=True))
|
|
@@ -38,6 +38,11 @@ from typing import Callable, Dict, List, Optional, Tuple
|
|
|
38
38
|
__all__ = ["Representation", "Edge", "ConversionGraph", "DEFAULT_GRAPH",
|
|
39
39
|
"REPRESENTATIONS"]
|
|
40
40
|
|
|
41
|
+
# Chosen so a single lossy hop never wins over a lossless path unless the
|
|
42
|
+
# lossless alternative is at least this many hops longer. A 10-hop lossless
|
|
43
|
+
# chain (cost 10, unit edges) ties a 1-hop lossy edge (cost 10) exactly, and
|
|
44
|
+
# heapq's stable tie-break then decides — a non-issue in practice since real
|
|
45
|
+
# scriptconv graphs are shallow (a handful of hops at most).
|
|
41
46
|
_LOSSY_COST = 10.0
|
|
42
47
|
|
|
43
48
|
|
|
@@ -56,7 +61,10 @@ class Edge:
|
|
|
56
61
|
"""One registered transform between two representations.
|
|
57
62
|
|
|
58
63
|
``fn`` is called as ``fn(text, **context)``; context keys (``lang``,
|
|
59
|
-
engine-specific options…) pass through the router opaquely.
|
|
64
|
+
engine-specific options…) pass through the router opaquely. Every
|
|
65
|
+
registered ``fn`` MUST accept ``**kwargs`` for this reason — a callable
|
|
66
|
+
that only takes ``text`` raises :class:`TypeError` the moment routing
|
|
67
|
+
passes through any context. ``requires``
|
|
60
68
|
names an optional extra the transform needs — metadata only; the
|
|
61
69
|
transform itself raises :class:`ImportError` with an install hint.
|
|
62
70
|
``cost`` defaults from ``lossless`` so routing prefers lossless paths.
|
|
@@ -41,6 +41,7 @@ __all__ = [
|
|
|
41
41
|
"ipa_to_cotovia",
|
|
42
42
|
"rfe_to_ipa",
|
|
43
43
|
"ipa_to_rfe",
|
|
44
|
+
"mantoq_to_ipa",
|
|
44
45
|
"looks_like_ipa",
|
|
45
46
|
]
|
|
46
47
|
|
|
@@ -1053,7 +1054,7 @@ def _tokenize_mantoq(text: str) -> list[str]:
|
|
|
1053
1054
|
return tokens
|
|
1054
1055
|
|
|
1055
1056
|
|
|
1056
|
-
def mantoq_to_ipa(mantoq, errors: str = "pass") -> str:
|
|
1057
|
+
def mantoq_to_ipa(mantoq: str | list[str], errors: str = "pass") -> str:
|
|
1057
1058
|
"""Convert a Mantoq phoneme string to IPA.
|
|
1058
1059
|
|
|
1059
1060
|
``_dbl_`` lengthens/geminates the preceding symbol (``ː``), ``_+_``
|
|
@@ -1235,15 +1236,19 @@ def can_convert(src: str | Notation, dst: str | Notation) -> bool:
|
|
|
1235
1236
|
src = Notation(src)
|
|
1236
1237
|
dst = Notation(dst)
|
|
1237
1238
|
if src == dst:
|
|
1238
|
-
# historical contract: identity is not a "conversion"
|
|
1239
|
+
# historical contract: identity is not a "conversion" here, unlike
|
|
1240
|
+
# graph.ConversionGraph.can_convert (which deliberately returns True
|
|
1241
|
+
# for identity — an empty route is a valid, zero-cost conversion there)
|
|
1239
1242
|
return False
|
|
1240
1243
|
from scriptconv.graph import DEFAULT_GRAPH
|
|
1241
|
-
|
|
1244
|
+
try:
|
|
1245
|
+
route = DEFAULT_GRAPH.route(src.value, dst.value)
|
|
1246
|
+
except ValueError:
|
|
1242
1247
|
return False
|
|
1243
1248
|
# only notation-to-notation reachability counts here; guard against paths
|
|
1244
1249
|
# that would leave the notation node set (none exist today, cheap to keep)
|
|
1245
1250
|
return all(e.src in _NOTATION_VALUES and e.dst in _NOTATION_VALUES
|
|
1246
|
-
for e in
|
|
1251
|
+
for e in route)
|
|
1247
1252
|
|
|
1248
1253
|
|
|
1249
1254
|
# ---------------------------------------------------------------------------
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import csv
|
|
3
3
|
import math
|
|
4
4
|
import os.path
|
|
5
|
+
import threading
|
|
5
6
|
from base64 import b64decode
|
|
6
7
|
from pathlib import Path
|
|
7
|
-
from typing import Union, List, Dict
|
|
8
|
+
from typing import Union, List, Dict, Optional
|
|
8
9
|
|
|
9
10
|
import regex as re
|
|
10
11
|
|
|
@@ -311,21 +312,46 @@ OBSTRUENTS = ()
|
|
|
311
312
|
SONORANTS = ()
|
|
312
313
|
|
|
313
314
|
|
|
315
|
+
_initialized_tables_dir: Optional[Path] = None
|
|
316
|
+
# guards the one-time table population below. The rule functions read the
|
|
317
|
+
# module globals (CT_*, CONSONANTS, VOWELS, ...) without locking, which is safe
|
|
318
|
+
# only because those globals are written exactly once and never mutated
|
|
319
|
+
# afterwards; this lock serialises concurrent first-time initialisation so no
|
|
320
|
+
# thread can observe a half-populated set of tables.
|
|
321
|
+
_init_lock = threading.Lock()
|
|
322
|
+
|
|
323
|
+
|
|
314
324
|
def initialize_conversion_tables(tables_dir: Path):
|
|
315
|
-
global
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
325
|
+
global _initialized_tables_dir
|
|
326
|
+
# the CSV tables never change at runtime, so once a given tables_dir has
|
|
327
|
+
# been parsed there is no need to re-read and re-parse it on every call.
|
|
328
|
+
# Fast path is lock-free: _initialized_tables_dir is published (assigned)
|
|
329
|
+
# last, after every table global is fully populated, so any thread that
|
|
330
|
+
# sees it set also sees the complete tables.
|
|
331
|
+
if _initialized_tables_dir == tables_dir:
|
|
332
|
+
return
|
|
333
|
+
with _init_lock:
|
|
334
|
+
# re-check under the lock: another thread may have initialised while we
|
|
335
|
+
# waited (double-checked locking)
|
|
336
|
+
if _initialized_tables_dir == tables_dir:
|
|
337
|
+
return
|
|
338
|
+
global CT_double_codas, CT_neutral, CT_tensification, CT_assimilation, CT_aspiration, CT_convention
|
|
339
|
+
CT_double_codas = ConversionTable('double_coda', tables_dir)
|
|
340
|
+
CT_neutral = ConversionTable('neutralization', tables_dir)
|
|
341
|
+
CT_tensification = ConversionTable('tensification', tables_dir)
|
|
342
|
+
CT_assimilation = ConversionTable('assimilation', tables_dir)
|
|
343
|
+
CT_aspiration = ConversionTable('aspiration', tables_dir)
|
|
344
|
+
CT_convention = ConversionTable('ipa', tables_dir)
|
|
345
|
+
|
|
346
|
+
global CONSONANTS, VOWELS, OBSTRUENTS, SONORANTS
|
|
347
|
+
CONSONANTS = tuple(
|
|
348
|
+
list(CT_convention.C)[:-2]) # from the C column of the IPA table, remove special characters # and $
|
|
349
|
+
VOWELS = tuple(list(CT_convention.V)) # from the V column of the IPA table
|
|
350
|
+
OBSTRUENTS = tuple(set(CONSONANTS) - set(C_SONORANTS))
|
|
351
|
+
SONORANTS = VOWELS + C_SONORANTS
|
|
352
|
+
|
|
353
|
+
# published last: any thread seeing this set also sees all tables above
|
|
354
|
+
_initialized_tables_dir = tables_dir
|
|
329
355
|
|
|
330
356
|
|
|
331
357
|
def get_substring_ind(string: str, pattern: str) -> List[int]:
|
{scriptconv-0.0.4a3 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/kog2p/__init__.py
RENAMED
|
@@ -33,6 +33,7 @@ import re
|
|
|
33
33
|
import math
|
|
34
34
|
import sys
|
|
35
35
|
import optparse
|
|
36
|
+
from functools import lru_cache
|
|
36
37
|
|
|
37
38
|
# Option
|
|
38
39
|
# Import-time CLI parsing removed for library use (the upstream script
|
|
@@ -366,10 +367,19 @@ kog2p_to_hangul = {
|
|
|
366
367
|
}
|
|
367
368
|
|
|
368
369
|
|
|
370
|
+
@lru_cache(maxsize=None)
|
|
371
|
+
def _cachedReadRules(pver, rule_book):
|
|
372
|
+
# rulebook.txt never changes at runtime, so parse it once per path
|
|
373
|
+
# instead of re-opening and re-parsing it on every runKoG2P call
|
|
374
|
+
rule_in, rule_out = readRules(pver, rule_book)
|
|
375
|
+
return tuple(rule_in), tuple(rule_out)
|
|
376
|
+
|
|
377
|
+
|
|
369
378
|
def runKoG2P(graph, rulebook=None):
|
|
370
379
|
if not rulebook:
|
|
371
380
|
rulebook = f"{os.path.dirname(__file__)}/rulebook.txt"
|
|
372
|
-
|
|
381
|
+
rule_in, rule_out = _cachedReadRules(ver_info[0], rulebook)
|
|
382
|
+
rule_in, rule_out = list(rule_in), list(rule_out)
|
|
373
383
|
words = graph.split()
|
|
374
384
|
phonemized = []
|
|
375
385
|
for w in words:
|
|
@@ -74,6 +74,30 @@ def _is_european_portuguese(lang: str) -> bool:
|
|
|
74
74
|
return norm == "pt" or norm == "pt-pt"
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
def _diacritizer_family(lang: str) -> Optional[str]:
|
|
78
|
+
"""Which diacritization backend family handles *lang*, or ``None``.
|
|
79
|
+
|
|
80
|
+
Single source of truth for lang→backend routing: both the forward
|
|
81
|
+
dispatch (:meth:`BasePhonemizer.add_diacritics`) and the strip direction
|
|
82
|
+
(:func:`scriptconv.diacritics._overlay_marks`) resolve through this, so the
|
|
83
|
+
two can never disagree about which language uses which backend. Returns one
|
|
84
|
+
of ``"he"`` (niqqud), ``"ar"`` (tashkeel), ``"stress"`` (stressonnx), ``"pt"``
|
|
85
|
+
(bifonia sense diacritics), or ``None``. Uses exact primary-subtag matching
|
|
86
|
+
(never ``startswith``), so Aragonese (``arg``), Herero (``her``) and
|
|
87
|
+
Mapudungun (``arn``) are never misread as Arabic/Hebrew.
|
|
88
|
+
"""
|
|
89
|
+
p = _primary_subtag(lang)
|
|
90
|
+
if p == "he":
|
|
91
|
+
return "he"
|
|
92
|
+
if p == "ar":
|
|
93
|
+
return "ar"
|
|
94
|
+
if p in STRESS_LANGS:
|
|
95
|
+
return "stress"
|
|
96
|
+
if _is_european_portuguese(lang):
|
|
97
|
+
return "pt"
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
77
101
|
class BasePhonemizer(metaclass=abc.ABCMeta):
|
|
78
102
|
def __init__(self, alphabet: Alphabet = Alphabet.UNICODE,
|
|
79
103
|
diacritizer_model: str = "rawi-ensemble",
|
|
@@ -199,13 +223,14 @@ class BasePhonemizer(metaclass=abc.ABCMeta):
|
|
|
199
223
|
``ImportError`` naming its extra when the optional dependency is
|
|
200
224
|
missing — scriptconv never installs anything on the caller's behalf.
|
|
201
225
|
"""
|
|
202
|
-
|
|
226
|
+
family = _diacritizer_family(lang)
|
|
227
|
+
if family == "he":
|
|
203
228
|
return self.phonikud.add_diacritics(text)
|
|
204
|
-
|
|
229
|
+
if family == "ar":
|
|
205
230
|
return self.tashkeel(model).diacritize(text)
|
|
206
|
-
|
|
231
|
+
if family == "stress":
|
|
207
232
|
return self._stress(text, lang, model)
|
|
208
|
-
|
|
233
|
+
if family == "pt":
|
|
209
234
|
return self._sense_diacritics_pt(text)
|
|
210
235
|
return text
|
|
211
236
|
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
import requests
|
|
4
|
-
|
|
5
1
|
from scriptconv.notation import _ARPA_TO_IPA as arpa_to_ipa_lookup
|
|
6
2
|
from scriptconv.phonemizers.base import BasePhonemizer
|
|
7
3
|
from scriptconv.phonemizers.enums import Alphabet
|
|
@@ -18,6 +14,8 @@ class DeepPhonemizer(BasePhonemizer):
|
|
|
18
14
|
}
|
|
19
15
|
|
|
20
16
|
def __init__(self, model="latin_ipa_forward.pt"):
|
|
17
|
+
import os
|
|
18
|
+
|
|
21
19
|
import dp
|
|
22
20
|
from dp.phonemizer import Phonemizer
|
|
23
21
|
import torch
|
|
@@ -38,6 +36,7 @@ class DeepPhonemizer(BasePhonemizer):
|
|
|
38
36
|
os.makedirs(cache_dir, exist_ok=True)
|
|
39
37
|
model_path = os.path.join(cache_dir, model)
|
|
40
38
|
if not os.path.isfile(model_path):
|
|
39
|
+
import requests
|
|
41
40
|
print(f"Downloading {model} from {url}...")
|
|
42
41
|
with requests.get(url, stream=True) as r:
|
|
43
42
|
r.raise_for_status()
|
|
@@ -5,7 +5,14 @@ from scriptconv.phonemizers.enums import Alphabet
|
|
|
5
5
|
class PersianPhonemizer(BasePhonemizer):
|
|
6
6
|
"""https://github.com/de-mh/persian_phonemizer"""
|
|
7
7
|
def __init__(self, alphabet=Alphabet.IPA):
|
|
8
|
-
|
|
8
|
+
try:
|
|
9
|
+
from persian_phonemizer import Phonemizer
|
|
10
|
+
except ImportError as e:
|
|
11
|
+
raise ImportError(
|
|
12
|
+
"persian_phonemizer is required for the Persian phonemizer. "
|
|
13
|
+
"Install it with 'pip install persian_phonemizer' "
|
|
14
|
+
"(or 'pip install scriptconv[fa]')."
|
|
15
|
+
) from e
|
|
9
16
|
assert alphabet in [Alphabet.ERAAB, Alphabet.IPA]
|
|
10
17
|
output_format = "IPA" if alphabet == Alphabet.IPA else 'eraab'
|
|
11
18
|
self.g2p = Phonemizer(output_format)
|
|
@@ -10,7 +10,8 @@ class CotoviaPhonemizer(BasePhonemizer):
|
|
|
10
10
|
G2P engine that has verified parity with the original C binary.
|
|
11
11
|
|
|
12
12
|
Output alphabets:
|
|
13
|
-
- ``Alphabet.COTOVIA`` — raw Cotovia phoneme notation (e.g.
|
|
13
|
+
- ``Alphabet.COTOVIA`` (default) — raw Cotovia phoneme notation (e.g.
|
|
14
|
+
``"Este e uN ..."``), the binary-parity-tested native phoneset
|
|
14
15
|
- ``Alphabet.IPA`` — IPA string produced by pycotovia's ``cotovia_to_ipa``
|
|
15
16
|
|
|
16
17
|
Voices trained on Cotovia-alphabet output continue to receive the same
|
|
@@ -21,11 +22,12 @@ class CotoviaPhonemizer(BasePhonemizer):
|
|
|
21
22
|
lazily so that ``import scriptconv.phonemizers`` works without it installed.
|
|
22
23
|
"""
|
|
23
24
|
|
|
24
|
-
def __init__(self, alphabet: Alphabet = Alphabet.
|
|
25
|
+
def __init__(self, alphabet: Alphabet = Alphabet.COTOVIA,
|
|
25
26
|
model: Optional[str] = None):
|
|
26
27
|
"""
|
|
27
28
|
Args:
|
|
28
|
-
alphabet (Alphabet): ``COTOVIA`` (raw notation) or
|
|
29
|
+
alphabet (Alphabet): ``COTOVIA`` (raw notation, the default) or
|
|
30
|
+
``IPA``.
|
|
29
31
|
model (Optional[str]): phonemizer variant, from the voice's
|
|
30
32
|
``phonemizer_model``. ``"stress"`` emits the cotovia notation
|
|
31
33
|
with the stressed vowel marked by a trailing ``^`` (e.g.
|
|
@@ -5,7 +5,14 @@ from scriptconv.phonemizers.base import BasePhonemizer
|
|
|
5
5
|
class PhonikudPhonemizer(BasePhonemizer):
|
|
6
6
|
|
|
7
7
|
def __init__(self):
|
|
8
|
-
|
|
8
|
+
try:
|
|
9
|
+
from phonikud import phonemize
|
|
10
|
+
except ImportError as e:
|
|
11
|
+
raise ImportError(
|
|
12
|
+
"phonikud is required for the Hebrew phonemizer. "
|
|
13
|
+
"Install it with 'pip install phonikud-onnx' "
|
|
14
|
+
"(or 'pip install scriptconv[he]')."
|
|
15
|
+
) from e
|
|
9
16
|
self.g2p = phonemize
|
|
10
17
|
super().__init__(Alphabet.IPA)
|
|
11
18
|
|
|
@@ -17,6 +17,7 @@ from __future__ import annotations
|
|
|
17
17
|
import importlib
|
|
18
18
|
from typing import Dict, Optional, Tuple
|
|
19
19
|
|
|
20
|
+
from scriptconv.phonemizers.base import _primary_subtag
|
|
20
21
|
from scriptconv.phonemizers.enums import Alphabet, Phonemizer
|
|
21
22
|
|
|
22
23
|
__all__ = ["PHONEMIZER_REGISTRY", "LANG_DEFAULTS", "get_phonemizer",
|
|
@@ -184,7 +185,7 @@ def phonemizer_for_lang(lang: str, alphabet: Alphabet = Alphabet.IPA,
|
|
|
184
185
|
"""
|
|
185
186
|
if override is not None:
|
|
186
187
|
return get_phonemizer(override, alphabet, model, **kwargs)
|
|
187
|
-
key = lang
|
|
188
|
+
key = _primary_subtag(lang)
|
|
188
189
|
explicit = LANG_DEFAULTS.get(key, ())
|
|
189
190
|
for candidate in explicit:
|
|
190
191
|
if alphabet in _EMITS.get(candidate, (Alphabet.IPA,)):
|
|
@@ -219,7 +220,9 @@ def register(graph) -> None:
|
|
|
219
220
|
routing context, and present only in graphs that opted in (the
|
|
220
221
|
:data:`scriptconv.graph.DEFAULT_GRAPH` stays orthography-only by
|
|
221
222
|
design) — and one dispatching ``text -> ipa`` edge that resolves the
|
|
222
|
-
per-language default (honouring an ``override=`` context key).
|
|
223
|
+
per-language default (honouring an ``override=`` context key). It also
|
|
224
|
+
accepts already-diacritized input via a ``"text-diacritized" -> "ipa"``
|
|
225
|
+
edge (the same phonemization; pair with :func:`scriptconv.diacritics.register`).
|
|
223
226
|
|
|
224
227
|
Usage::
|
|
225
228
|
|
|
@@ -236,3 +239,4 @@ def register(graph) -> None:
|
|
|
236
239
|
override).phonemize_string(text, lang)
|
|
237
240
|
|
|
238
241
|
graph.register(Edge("text", "ipa", _text_to_ipa, lossless=False))
|
|
242
|
+
graph.register(Edge("text-diacritized", "ipa", _text_to_ipa, lossless=False))
|