scriptconv 0.0.4a4__tar.gz → 0.0.4a6__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.4a4/scriptconv.egg-info → scriptconv-0.0.4a6}/PKG-INFO +2 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/pyproject.toml +1 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/__init__.py +2 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/cangjie.py +8 -7
- scriptconv-0.0.4a6/scriptconv/diacritics.py +319 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/graph.py +9 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/notation.py +9 -4
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/hangul2ipa.py +41 -15
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/kog2p/__init__.py +11 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/base.py +4 -152
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/en.py +3 -4
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/fa.py +8 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/gl.py +5 -3
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/he.py +8 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/registry.py +4 -6
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/zh.py +48 -8
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/scripts.py +4 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/version.py +1 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6/scriptconv.egg-info}/PKG-INFO +2 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv.egg-info/SOURCES.txt +1 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv.egg-info/requires.txt +1 -0
- scriptconv-0.0.4a6/tests/test_diacritics.py +201 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_diacritics_graph.py +1 -2
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_phonemizers_base.py +0 -100
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_phonemizers_cjk_ar.py +59 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_scripts.py +12 -0
- scriptconv-0.0.4a4/scriptconv/diacritics.py +0 -153
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/LICENSE +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/README.md +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/requirements.txt +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/__main__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/conventions.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/data/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/data/cangjie5_tc.tsv.gz +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/bw2ipa.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/ko_tables/aspiration.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/ko_tables/assimilation.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/ko_tables/double_coda.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/ko_tables/hanja.tsv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/ko_tables/ipa.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/ko_tables/neutralization.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/ko_tables/tensification.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/ko_tables/yale.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/shami/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/shami/codeswitch.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/shami/diacritize.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/shami/dialectal.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/shami/english_g2p.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/shami/espeak.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/shami/frontend.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/shami/levantine_g2p.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/shami/normalize.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/shami/phoneme_inventory.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_thirdparty/zh_num.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/kog2p/LICENSE.md +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/kog2p/rulebook.txt +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/mantoq/LICENSE.md +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/mantoq/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/mantoq/buck/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/mantoq/buck/phonetise_buckwalter.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/mantoq/buck/symbols.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/mantoq/buck/tokenization.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/mantoq/num2words.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/_vendored/mantoq/unicode_symbol2label.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/ar.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/enums.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/eu.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/ja.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/ko.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/mul.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/mwl.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/o2ipa.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/pt.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/shami.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/phonemizers/vi.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/py.typed +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/readings.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv/translit.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv.egg-info/dependency_links.txt +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/scriptconv.egg-info/top_level.txt +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/setup.cfg +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_arpa_stress.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_cangjie.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_cli.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_conventions.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_errors_policy.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_examples.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_graph.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_notation.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_readings.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_readings_zh.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/tests/test_scripts_stressonnx_compat.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a6}/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.4a6
|
|
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"
|
|
@@ -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,319 @@
|
|
|
1
|
+
"""Diacritization: mechanism, dispatch, and graph extension.
|
|
2
|
+
|
|
3
|
+
This module OWNS diacritization — lang→backend routing, lazy/cached backend
|
|
4
|
+
loading (phonikud, text2tashkeel, stressonnx, bifonia), and the overlay-strip
|
|
5
|
+
helpers. :mod:`scriptconv.phonemizers` (``BasePhonemizer`` and subclasses)
|
|
6
|
+
knows nothing about diacritics: phonemization (orthography → sound) and
|
|
7
|
+
diacritization (a text → text graph transform disambiguating pronunciation
|
|
8
|
+
before G2P) are separate concerns.
|
|
9
|
+
|
|
10
|
+
Diacritization (Arabic tashkeel, Hebrew niqqud, East-Slavic/Turkic/Caucasian
|
|
11
|
+
word stress, European-Portuguese homograph sense marks) is, architecturally,
|
|
12
|
+
just another transform between text representations: it maps the ``"text"``
|
|
13
|
+
node to a lang-contextual ``"text-diacritized"`` node. Like
|
|
14
|
+
:func:`scriptconv.phonemizers.register` it is opt-in — ``DEFAULT_GRAPH`` stays
|
|
15
|
+
free of it until a caller extends a graph::
|
|
16
|
+
|
|
17
|
+
from scriptconv.graph import DEFAULT_GRAPH
|
|
18
|
+
from scriptconv import diacritics, phonemizers
|
|
19
|
+
g = DEFAULT_GRAPH.extend(diacritics.register).extend(phonemizers.register)
|
|
20
|
+
|
|
21
|
+
g.convert("замок стоит на горе", "text", "text-diacritized", lang="ru")
|
|
22
|
+
# 'за́мок сто́ит на горе́' — just the diacritized text
|
|
23
|
+
|
|
24
|
+
g.convert("Tenho muita sede hoje.", "text", "text-diacritized", lang="pt")
|
|
25
|
+
# 'Tenho muita sêde hoje.'
|
|
26
|
+
|
|
27
|
+
Routing ``"text" -> "ipa"`` still takes the direct phonemization edge by
|
|
28
|
+
default: the diacritization edge is model-based (``lossless=False``), so it
|
|
29
|
+
never out-prices a direct phonemization, and enabling this extension does not
|
|
30
|
+
silently change phonemization output. The diacritized route is taken only
|
|
31
|
+
when a caller asks for ``"text-diacritized"`` explicitly.
|
|
32
|
+
|
|
33
|
+
A future per-engine *stance* would push this further into topology rather than
|
|
34
|
+
a flag: an engine that *requires* vocalized input would carry only a
|
|
35
|
+
``"text-diacritized" -> "ipa"`` edge (forcing the detour), one that *forbids*
|
|
36
|
+
diacritics would carry only ``"text" -> "ipa"`` (making the detour
|
|
37
|
+
unroutable), and a tolerant engine (e.g. arbtok, which self-vocalizes) carries
|
|
38
|
+
both. The strip direction (``"text-diacritized" -> "text"``) is registered too, but
|
|
39
|
+
gated to languages whose marks are removable *overlay* diacritics —
|
|
40
|
+
Arabic/Hebrew vocalization and East-Slavic/Turkic/Caucasian stress. Strip
|
|
41
|
+
removes only the specific overlay codepoints each backend adds (combining
|
|
42
|
+
acute/grave for stress, tashkeel for Arabic, niqqud for Hebrew) — never a
|
|
43
|
+
blanket combining-mark filter — so precomposed native letters survive
|
|
44
|
+
(Cyrillic й/ё, Latvian macrons, Azerbaijani ç/ö, Arabic hamza carriers أ إ آ ؤ
|
|
45
|
+
ئ). The gate uses exact primary-subtag matching, so Aragonese (``arg``),
|
|
46
|
+
Herero (``her``), Mapudungun (``arn``) etc. are never misread as Arabic/Hebrew.
|
|
47
|
+
Languages whose diacritics are native orthography (European Portuguese, via
|
|
48
|
+
bifonia) refuse the strip with :class:`ValueError` instead of silently
|
|
49
|
+
corrupting the spelling (``café`` must never become ``cafe``). This split
|
|
50
|
+
tracks which backend/model produced the marks: overlay backends (phonikud,
|
|
51
|
+
tashkeel, stressonnx) are strippable; spelling-integral backends (bifonia) are
|
|
52
|
+
not.
|
|
53
|
+
"""
|
|
54
|
+
from typing import Optional
|
|
55
|
+
|
|
56
|
+
from scriptconv.graph import Edge
|
|
57
|
+
from scriptconv.phonemizers.base import _primary_subtag
|
|
58
|
+
|
|
59
|
+
#: The lang-contextual node produced by diacritization. Like ``"text"`` it is
|
|
60
|
+
#: meaningful only with ``lang=`` context and exists only in opted-in graphs.
|
|
61
|
+
DIACRITIZED = "text-diacritized"
|
|
62
|
+
|
|
63
|
+
# Across East Slavic, Bulgarian/Macedonian/Slovene, Latvian, Armenian,
|
|
64
|
+
# Georgian, and several Turkic/Caucasian languages, lexical word stress is
|
|
65
|
+
# free (not fixed to a syllable) and ordinary orthography leaves it unwritten
|
|
66
|
+
# or under-marked. The clearest case is East Slavic: stress is also mobile
|
|
67
|
+
# (it shifts between forms of the same word) and unstressed vowels *reduce*
|
|
68
|
+
# — Russian unstressed "о" surfaces as [ɐ] or [ə] depending on distance from
|
|
69
|
+
# the stress, not [o] — so a wrong or missing mark there corrupts the vowel
|
|
70
|
+
# quality of the whole word, not just its prosody. Other families in this set
|
|
71
|
+
# don't necessarily reduce vowels, but still need the mark for correct stress
|
|
72
|
+
# placement and prosody. stressonnx restores it as a combining acute (U+0301)
|
|
73
|
+
# after the stressed vowel, covering 26 BCP-47 tags across these families
|
|
74
|
+
# (24 primary subtags; Azerbaijani and Uzbek each have Cyrillic/Latin script
|
|
75
|
+
# variants routed by the full tag).
|
|
76
|
+
STRESS_LANGS = {
|
|
77
|
+
"az", "ba", "be", "bg", "cv", "hy", "ka", "kbd", "kjh", "kk", "ky", "lv",
|
|
78
|
+
"mdf", "mk", "myv", "ru", "sah", "sl", "tg", "tt", "udm", "uk", "uz", "xal",
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _is_european_portuguese(lang: str) -> bool:
|
|
83
|
+
"""True for European Portuguese (``pt`` or a ``pt-PT`` region tag).
|
|
84
|
+
|
|
85
|
+
False for Brazilian Portuguese (``pt-BR``) and everything else — the two
|
|
86
|
+
varieties' vowel systems differ, and bifonia's open/closed diacritics are
|
|
87
|
+
only valid for European Portuguese phonology.
|
|
88
|
+
"""
|
|
89
|
+
norm = lang.lower().replace("_", "-")
|
|
90
|
+
return norm == "pt" or norm == "pt-pt"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _diacritizer_family(lang: str) -> Optional[str]:
|
|
94
|
+
"""Which diacritization backend family handles *lang*, or ``None``.
|
|
95
|
+
|
|
96
|
+
Single source of truth for lang→backend routing: both the forward
|
|
97
|
+
dispatch (:func:`diacritize`) and the strip direction (:func:`_overlay_marks`)
|
|
98
|
+
resolve through this, so the two can never disagree about which language
|
|
99
|
+
uses which backend. Returns one of ``"he"`` (niqqud), ``"ar"`` (tashkeel),
|
|
100
|
+
``"stress"`` (stressonnx), ``"pt"`` (bifonia sense diacritics), or
|
|
101
|
+
``None``. Uses exact primary-subtag matching (never ``startswith``), so
|
|
102
|
+
Aragonese (``arg``), Herero (``her``) and Mapudungun (``arn``) are never
|
|
103
|
+
misread as Arabic/Hebrew.
|
|
104
|
+
"""
|
|
105
|
+
p = _primary_subtag(lang)
|
|
106
|
+
if p == "he":
|
|
107
|
+
return "he"
|
|
108
|
+
if p == "ar":
|
|
109
|
+
return "ar"
|
|
110
|
+
if p in STRESS_LANGS:
|
|
111
|
+
return "stress"
|
|
112
|
+
if _is_european_portuguese(lang):
|
|
113
|
+
return "pt"
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
_DEFAULT_DIACRITIZER_MODEL = "rawi-ensemble"
|
|
118
|
+
|
|
119
|
+
_PHONIKUD_CACHE: dict = {} # resolved model path/string -> Phonikud instance
|
|
120
|
+
_TASHKEEL_CACHE: dict = {} # model name -> text2tashkeel Diacritizer
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _phonikud(phonikud_model):
|
|
124
|
+
"""Lazily build (and cache) the phonikud Phonikud instance used for Hebrew.
|
|
125
|
+
|
|
126
|
+
``phonikud_model`` is a local path to a phonikud ONNX model, or a
|
|
127
|
+
zero-arg callable resolving one lazily; scriptconv never downloads —
|
|
128
|
+
the consumer resolves the file. Install with ``pip install
|
|
129
|
+
scriptconv[he]`` (or ``pip install phonikud-onnx``)."""
|
|
130
|
+
model = phonikud_model() if callable(phonikud_model) else phonikud_model
|
|
131
|
+
if not model:
|
|
132
|
+
raise ValueError(
|
|
133
|
+
"Hebrew diacritization needs a local phonikud ONNX model: "
|
|
134
|
+
"pass phonikud_model=<path> (scriptconv never downloads "
|
|
135
|
+
"models; obtain one from the phonikud-onnx release)")
|
|
136
|
+
if model not in _PHONIKUD_CACHE:
|
|
137
|
+
try:
|
|
138
|
+
from phonikud_onnx import Phonikud
|
|
139
|
+
except ImportError:
|
|
140
|
+
raise ImportError(
|
|
141
|
+
"Hebrew diacritization needs phonikud-onnx — install "
|
|
142
|
+
"with `pip install scriptconv[he]`") from None
|
|
143
|
+
_PHONIKUD_CACHE[model] = Phonikud(model)
|
|
144
|
+
return _PHONIKUD_CACHE[model]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _tashkeel(model: Optional[str] = None):
|
|
148
|
+
"""Lazily build (and cache) the text2tashkeel Diacritizer used for Arabic.
|
|
149
|
+
|
|
150
|
+
text2tashkeel is a dependency of the ``[ar]`` extra; it restores hamza and the
|
|
151
|
+
dagger alef in addition to the standard marks. Install with
|
|
152
|
+
``pip install scriptconv[tashkeel]`` (or ``pip install text2tashkeel``)."""
|
|
153
|
+
model = model or _DEFAULT_DIACRITIZER_MODEL
|
|
154
|
+
if model not in _TASHKEEL_CACHE:
|
|
155
|
+
try:
|
|
156
|
+
from text2tashkeel import Diacritizer
|
|
157
|
+
except ImportError as e:
|
|
158
|
+
raise ImportError(
|
|
159
|
+
"Arabic diacritization requires the text2tashkeel package: "
|
|
160
|
+
"pip install scriptconv[tashkeel] (or pip install text2tashkeel)"
|
|
161
|
+
) from e
|
|
162
|
+
_TASHKEEL_CACHE[model] = Diacritizer(model)
|
|
163
|
+
return _TASHKEEL_CACHE[model]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _stress(text: str, lang: str, model: Optional[str] = None) -> str:
|
|
167
|
+
"""Word-stress restoration via stressonnx, for the 26 language tags
|
|
168
|
+
it covers (see ``STRESS_LANGS``) — East Slavic, Bulgarian/Macedonian/
|
|
169
|
+
Slovene, Latvian, Armenian, Georgian, and Turkic/Caucasian languages.
|
|
170
|
+
|
|
171
|
+
stressonnx is not on PyPI yet; install straight from source. Install
|
|
172
|
+
with ``pip install scriptconv[stress]`` (or ``pip install
|
|
173
|
+
stressonnx``)."""
|
|
174
|
+
try:
|
|
175
|
+
from stressonnx import stress
|
|
176
|
+
except ImportError as e:
|
|
177
|
+
raise ImportError(
|
|
178
|
+
"stress restoration requires the stressonnx package: "
|
|
179
|
+
"pip install scriptconv[stress] (or pip install stressonnx)"
|
|
180
|
+
) from e
|
|
181
|
+
return stress(text, lang, model=model)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _sense_diacritics_pt(text: str) -> str:
|
|
185
|
+
"""European-Portuguese heterophonic-homograph sense diacritics via bifonia.
|
|
186
|
+
|
|
187
|
+
Rewrites homographs whose pronunciation depends on meaning (e.g.
|
|
188
|
+
"sede" thirst/closed vs. seat/open) with an explicit open/closed
|
|
189
|
+
vowel diacritic. These are ordinary Portuguese orthographic marks,
|
|
190
|
+
chosen so any downstream G2P — rule-based, neural, or espeak —
|
|
191
|
+
reads them correctly. Install with ``pip install scriptconv[pt]``
|
|
192
|
+
(or ``pip install bifonia``)."""
|
|
193
|
+
try:
|
|
194
|
+
from bifonia import add_extra_diacritics
|
|
195
|
+
except ImportError as e:
|
|
196
|
+
raise ImportError(
|
|
197
|
+
"European-Portuguese sense diacritics require the bifonia package: "
|
|
198
|
+
"pip install scriptconv[pt] (or pip install bifonia)"
|
|
199
|
+
) from e
|
|
200
|
+
return add_extra_diacritics(text)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def diacritize(text: str, lang: str = "und", model=None,
|
|
204
|
+
phonikud_model=None, diacritizer_model=None, **_) -> str:
|
|
205
|
+
"""Add pronunciation-disambiguating diacritics to *text* for *lang*.
|
|
206
|
+
|
|
207
|
+
Four backends, each restoring information ordinary orthography omits but
|
|
208
|
+
downstream G2P needs:
|
|
209
|
+
|
|
210
|
+
- Hebrew (``he``) — niqqud via phonikud (``phonikud_model=``).
|
|
211
|
+
- Arabic (``ar``) — tashkeel via text2tashkeel (``[tashkeel]``,
|
|
212
|
+
``model=``/``diacritizer_model=``).
|
|
213
|
+
- East Slavic, Bulgarian/Macedonian/Slovene, Latvian, Armenian, Georgian,
|
|
214
|
+
and Turkic/Caucasian languages (``STRESS_LANGS``, 26 stressonnx tags) —
|
|
215
|
+
word stress via stressonnx (``[stress]``); stress is unwritten or
|
|
216
|
+
under-marked in these languages, and in East Slavic unstressed vowels
|
|
217
|
+
also reduce, so a missing mark can corrupt more than prosody.
|
|
218
|
+
- European Portuguese (``pt``/``pt-PT``, never ``pt-BR``) —
|
|
219
|
+
heterophonic-homograph sense diacritics via bifonia (``[pt]``);
|
|
220
|
+
ordinary Portuguese orthographic marks that any downstream G2P reads
|
|
221
|
+
correctly.
|
|
222
|
+
|
|
223
|
+
Unrecognized languages are returned unchanged. Each backend raises
|
|
224
|
+
``ImportError`` naming its extra when the optional dependency is missing
|
|
225
|
+
— scriptconv never installs anything on the caller's behalf.
|
|
226
|
+
"""
|
|
227
|
+
family = _diacritizer_family(lang)
|
|
228
|
+
if family == "he":
|
|
229
|
+
return _phonikud(phonikud_model).add_diacritics(text)
|
|
230
|
+
if family == "ar":
|
|
231
|
+
return _tashkeel(model or diacritizer_model).diacritize(text)
|
|
232
|
+
if family == "stress":
|
|
233
|
+
return _stress(text, lang, model)
|
|
234
|
+
if family == "pt":
|
|
235
|
+
return _sense_diacritics_pt(text)
|
|
236
|
+
return text
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# Combining marks each diacritization backend overlays onto the bare text.
|
|
240
|
+
# Strip removes ONLY these codepoints — never a blanket combining-mark filter,
|
|
241
|
+
# which (via NFD) would decompose and destroy precomposed native letters:
|
|
242
|
+
# Cyrillic й/ё, Latvian ī, Azerbaijani ç/ö, and Arabic hamza carriers أ إ آ ؤ ئ.
|
|
243
|
+
_STRESS_MARKS = frozenset({0x0300, 0x0301}) # combining grave / acute
|
|
244
|
+
# U+0300 is included defensively alongside the U+0301 that stressonnx
|
|
245
|
+
# actually emits, in case any backend/locale marks secondary stress with a
|
|
246
|
+
# grave instead of an acute; harmless to strip since native precomposed
|
|
247
|
+
# letters (e.g. Cyrillic й/ё) are unaffected either way.
|
|
248
|
+
_ARABIC_MARKS = frozenset(range(0x064B, 0x0660)) | frozenset({0x0670}) # tashkeel + dagger alef
|
|
249
|
+
_HEBREW_MARKS = (frozenset(range(0x05B0, 0x05BE))
|
|
250
|
+
| frozenset({0x05BF, 0x05C1, 0x05C2, 0x05C4, 0x05C5, 0x05C7})) # niqqud
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _overlay_marks(lang: str) -> "frozenset | None":
|
|
254
|
+
"""The overlay codepoints for *lang*'s diacritization backend, or None.
|
|
255
|
+
|
|
256
|
+
Resolves the backend family through :func:`_diacritizer_family` (the shared
|
|
257
|
+
lang→backend routing), so this can never disagree with
|
|
258
|
+
:func:`diacritize` about which language a backend owns.
|
|
259
|
+
Only *overlay* families are strippable: ``"pt"`` (bifonia sense marks are
|
|
260
|
+
native orthography) and ``None`` both return None.
|
|
261
|
+
"""
|
|
262
|
+
family = _diacritizer_family(lang)
|
|
263
|
+
if family == "stress":
|
|
264
|
+
return _STRESS_MARKS
|
|
265
|
+
if family == "ar":
|
|
266
|
+
return _ARABIC_MARKS
|
|
267
|
+
if family == "he":
|
|
268
|
+
return _HEBREW_MARKS
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _supports_strip(lang: str) -> bool:
|
|
273
|
+
"""True for languages whose diacritics are removable overlays (stress marks,
|
|
274
|
+
Arabic tashkeel, Hebrew niqqud) rather than native orthography."""
|
|
275
|
+
return _overlay_marks(lang) is not None
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def strip_diacritics(text: str, lang: str = "und", **_) -> str:
|
|
279
|
+
"""Remove the overlay diacritics *lang*'s backend adds, recovering the bare
|
|
280
|
+
text WITHOUT touching native letters.
|
|
281
|
+
|
|
282
|
+
Removes only the specific overlay codepoints (combining acute/grave for
|
|
283
|
+
stress, tashkeel for Arabic, niqqud for Hebrew) — precomposed native
|
|
284
|
+
letters (Cyrillic й/ё, Latvian ī, Azerbaijani ç, Arabic hamza carriers) are
|
|
285
|
+
left intact. Raises :class:`ValueError` for languages whose diacritics are
|
|
286
|
+
part of the native orthography (e.g. European Portuguese), where any removal
|
|
287
|
+
would corrupt the spelling.
|
|
288
|
+
"""
|
|
289
|
+
marks = _overlay_marks(lang)
|
|
290
|
+
if marks is None:
|
|
291
|
+
raise ValueError(
|
|
292
|
+
f"cannot strip diacritics for lang={lang!r}: its diacritics are "
|
|
293
|
+
"part of the native orthography, so removal would corrupt the "
|
|
294
|
+
"spelling. strip is defined only for overlay diacritics "
|
|
295
|
+
"(Arabic/Hebrew vocalization, East-Slavic/Turkic stress).")
|
|
296
|
+
return "".join(c for c in text if ord(c) not in marks)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def register(graph) -> None:
|
|
300
|
+
"""Opt-in graph integration: add the diacritize/strip edge pair.
|
|
301
|
+
|
|
302
|
+
``text -> text-diacritized`` is model-based (``lossless=False``), so a
|
|
303
|
+
direct ``text -> ipa`` phonemization always out-prices the detour —
|
|
304
|
+
enabling this extension is safe and non-invasive. Pair with
|
|
305
|
+
:func:`scriptconv.phonemizers.register` to make
|
|
306
|
+
``"text-diacritized" -> "ipa"`` reachable.
|
|
307
|
+
|
|
308
|
+
``text-diacritized -> text`` (:func:`strip_diacritics`) is lossless and
|
|
309
|
+
cheap, but gated: it raises for languages whose diacritics are native
|
|
310
|
+
orthography rather than removable overlays.
|
|
311
|
+
"""
|
|
312
|
+
graph.register(
|
|
313
|
+
Edge("text", DIACRITIZED,
|
|
314
|
+
lambda text, lang="und", model=None, **c: diacritize(text, lang, model, **c),
|
|
315
|
+
lossless=False))
|
|
316
|
+
graph.register(
|
|
317
|
+
Edge(DIACRITIZED, "text",
|
|
318
|
+
lambda text, lang="und", **_: strip_diacritics(text, lang),
|
|
319
|
+
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.4a4 → scriptconv-0.0.4a6}/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:
|