scriptconv 0.0.4a4__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.4a4/scriptconv.egg-info → scriptconv-0.0.4a5}/PKG-INFO +2 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/pyproject.toml +1 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/__init__.py +2 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/cangjie.py +8 -7
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/diacritics.py +17 -8
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/graph.py +9 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/notation.py +9 -4
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/hangul2ipa.py +41 -15
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/kog2p/__init__.py +11 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/base.py +29 -4
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/en.py +3 -4
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/fa.py +8 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/gl.py +5 -3
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/he.py +8 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/registry.py +2 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/zh.py +48 -8
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/scripts.py +4 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/version.py +1 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5/scriptconv.egg-info}/PKG-INFO +2 -1
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv.egg-info/SOURCES.txt +1 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv.egg-info/requires.txt +1 -0
- scriptconv-0.0.4a5/tests/test_diacritics.py +90 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_phonemizers_cjk_ar.py +59 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_scripts.py +12 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/LICENSE +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/README.md +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/requirements.txt +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/__main__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/conventions.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/data/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/data/cangjie5_tc.tsv.gz +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/bw2ipa.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/aspiration.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/assimilation.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/double_coda.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/hanja.tsv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/ipa.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/neutralization.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/tensification.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/yale.csv +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/codeswitch.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/diacritize.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/dialectal.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/english_g2p.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/espeak.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/frontend.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/levantine_g2p.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/normalize.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/phoneme_inventory.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/zh_num.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/kog2p/LICENSE.md +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/kog2p/rulebook.txt +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/LICENSE.md +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/buck/__init__.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/buck/phonetise_buckwalter.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/buck/symbols.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/buck/tokenization.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/num2words.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/unicode_symbol2label.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/ar.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/enums.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/eu.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/ja.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/ko.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/mul.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/mwl.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/o2ipa.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/pt.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/shami.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/vi.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/py.typed +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/readings.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/translit.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv.egg-info/dependency_links.txt +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv.egg-info/top_level.txt +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/setup.cfg +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_arpa_stress.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_cangjie.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_cli.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_conventions.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_diacritics_graph.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_errors_policy.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_examples.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_graph.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_notation.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_phonemizers_base.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_readings.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_readings_zh.py +0 -0
- {scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/tests/test_scripts_stressonnx_compat.py +0 -0
- {scriptconv-0.0.4a4 → 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"
|
|
@@ -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)
|
|
@@ -45,7 +45,7 @@ tashkeel, stressonnx) are strippable; spelling-integral backends (bifonia) are
|
|
|
45
45
|
not.
|
|
46
46
|
"""
|
|
47
47
|
from scriptconv.graph import Edge
|
|
48
|
-
from scriptconv.phonemizers.base import
|
|
48
|
+
from scriptconv.phonemizers.base import _diacritizer_family
|
|
49
49
|
|
|
50
50
|
#: The lang-contextual node produced by diacritization. Like ``"text"`` it is
|
|
51
51
|
#: meaningful only with ``lang=`` context and exists only in opted-in graphs.
|
|
@@ -55,6 +55,9 @@ _DEFAULT_PHONEMIZER = None
|
|
|
55
55
|
|
|
56
56
|
|
|
57
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
|
|
58
61
|
global _DEFAULT_PHONEMIZER
|
|
59
62
|
if phonikud_model:
|
|
60
63
|
from scriptconv.phonemizers.base import GraphemePhonemizer
|
|
@@ -81,6 +84,10 @@ def diacritize(text: str, lang: str = "und", model=None, **context) -> str:
|
|
|
81
84
|
# which (via NFD) would decompose and destroy precomposed native letters:
|
|
82
85
|
# Cyrillic й/ё, Latvian ī, Azerbaijani ç/ö, and Arabic hamza carriers أ إ آ ؤ ئ.
|
|
83
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.
|
|
84
91
|
_ARABIC_MARKS = frozenset(range(0x064B, 0x0660)) | frozenset({0x0670}) # tashkeel + dagger alef
|
|
85
92
|
_HEBREW_MARKS = (frozenset(range(0x05B0, 0x05BE))
|
|
86
93
|
| frozenset({0x05BF, 0x05C1, 0x05C2, 0x05C4, 0x05C5, 0x05C7})) # niqqud
|
|
@@ -89,16 +96,18 @@ _HEBREW_MARKS = (frozenset(range(0x05B0, 0x05BE))
|
|
|
89
96
|
def _overlay_marks(lang: str) -> "frozenset | None":
|
|
90
97
|
"""The overlay codepoints for *lang*'s diacritization backend, or None.
|
|
91
98
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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.
|
|
95
104
|
"""
|
|
96
|
-
|
|
97
|
-
if
|
|
105
|
+
family = _diacritizer_family(lang)
|
|
106
|
+
if family == "stress":
|
|
98
107
|
return _STRESS_MARKS
|
|
99
|
-
if
|
|
108
|
+
if family == "ar":
|
|
100
109
|
return _ARABIC_MARKS
|
|
101
|
-
if
|
|
110
|
+
if family == "he":
|
|
102
111
|
return _HEBREW_MARKS
|
|
103
112
|
return None
|
|
104
113
|
|
|
@@ -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.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,)):
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import abc
|
|
2
|
+
import unicodedata
|
|
2
3
|
from typing import List
|
|
3
4
|
|
|
4
5
|
|
|
@@ -46,7 +47,9 @@ class JiebaPhonemizer(BasePhonemizer):
|
|
|
46
47
|
import jieba
|
|
47
48
|
lang = self.get_lang(lang)
|
|
48
49
|
seg_list = jieba.cut(text, cut_all=False)
|
|
49
|
-
|
|
50
|
+
# NFKC folds full-width digits (e.g. "3") to ASCII so num2str's
|
|
51
|
+
# DIGITS table (keyed by ASCII "0"-"9") never raises KeyError on them
|
|
52
|
+
seg_list = [num2str(unicodedata.normalize("NFKC", w)) if w.isdigit() else w for w in seg_list]
|
|
50
53
|
return " ".join(seg_list)
|
|
51
54
|
|
|
52
55
|
|
|
@@ -68,7 +71,13 @@ class BaseChinesePinyinPhonemizer(BasePhonemizer):
|
|
|
68
71
|
super().__init__(alphabet)
|
|
69
72
|
self.jieba = jieba
|
|
70
73
|
self.retone = retone
|
|
71
|
-
|
|
74
|
+
try:
|
|
75
|
+
from pinyin_to_ipa import pinyin_to_ipa
|
|
76
|
+
except ImportError:
|
|
77
|
+
raise ImportError(
|
|
78
|
+
"pinyin->IPA conversion needs pinyin_to_ipa — install with "
|
|
79
|
+
"`pip install scriptconv[zh-phonemizers]`"
|
|
80
|
+
) from None
|
|
72
81
|
self.pinyin_to_ipa = pinyin_to_ipa
|
|
73
82
|
|
|
74
83
|
@classmethod
|
|
@@ -95,7 +104,12 @@ class BaseChinesePinyinPhonemizer(BasePhonemizer):
|
|
|
95
104
|
p = p.replace('˥˩', '↘') # fourth tone
|
|
96
105
|
p = p.replace('˥', '→') # first tone
|
|
97
106
|
p = p.replace(chr(635) + chr(809), 'ɨ').replace(chr(633) + chr(809), 'ɨ')
|
|
98
|
-
|
|
107
|
+
# U+0329 (combining vertical line below, syllabic diacritic) is only
|
|
108
|
+
# ever expected attached to ɻ/ɹ (U+027B/U+0279) above, which the two
|
|
109
|
+
# replacements just consumed; any leftover means pinyin_to_ipa
|
|
110
|
+
# emitted an unanticipated syllabic marking. Rather than crashing in
|
|
111
|
+
# production, leave it in place — it's still valid IPA, just not
|
|
112
|
+
# collapsed to the ɨ shorthand.
|
|
99
113
|
return p
|
|
100
114
|
|
|
101
115
|
def to_ipa(self, phones: List[str]) -> List[str]:
|
|
@@ -132,7 +146,9 @@ class BaseChinesePinyinPhonemizer(BasePhonemizer):
|
|
|
132
146
|
import jieba
|
|
133
147
|
for chunk in jieba.cut(text, cut_all=False):
|
|
134
148
|
if chunk.isdigit():
|
|
135
|
-
|
|
149
|
+
# NFKC folds full-width digits (e.g. "3") to ASCII so
|
|
150
|
+
# num2str's DIGITS table never raises KeyError on them
|
|
151
|
+
chunk = num2str(unicodedata.normalize("NFKC", chunk))
|
|
136
152
|
phones += self.get_pinyin(chunk)
|
|
137
153
|
phones += [" "] # keep jieba whitespace
|
|
138
154
|
else:
|
|
@@ -176,7 +192,13 @@ class G2pCPhonemizer(BaseChinesePinyinPhonemizer):
|
|
|
176
192
|
"""
|
|
177
193
|
|
|
178
194
|
def __init__(self, alphabet=Alphabet.PINYIN, jieba: bool = True):
|
|
179
|
-
|
|
195
|
+
try:
|
|
196
|
+
from g2pc import G2pC
|
|
197
|
+
except ImportError:
|
|
198
|
+
raise ImportError(
|
|
199
|
+
"g2pc phonemization needs g2pc — install with "
|
|
200
|
+
"`pip install scriptconv[zh-phonemizers]`"
|
|
201
|
+
) from None
|
|
180
202
|
self.g2p = G2pC()
|
|
181
203
|
super().__init__(alphabet, jieba)
|
|
182
204
|
|
|
@@ -200,7 +222,13 @@ class G2pMPhonemizer(BaseChinesePinyinPhonemizer):
|
|
|
200
222
|
"""
|
|
201
223
|
|
|
202
224
|
def __init__(self, alphabet=Alphabet.PINYIN, tone: bool = True, char_split: bool = False, jieba: bool = True):
|
|
203
|
-
|
|
225
|
+
try:
|
|
226
|
+
from g2pM import G2pM
|
|
227
|
+
except ImportError:
|
|
228
|
+
raise ImportError(
|
|
229
|
+
"g2pM phonemization needs g2pM — install with "
|
|
230
|
+
"`pip install scriptconv[zh-phonemizers]`"
|
|
231
|
+
) from None
|
|
204
232
|
self.g2p = G2pM()
|
|
205
233
|
self.tone = tone
|
|
206
234
|
self.char_split = char_split
|
|
@@ -225,7 +253,13 @@ class XpinyinPhonemizer(BaseChinesePinyinPhonemizer):
|
|
|
225
253
|
"""
|
|
226
254
|
|
|
227
255
|
def __init__(self, alphabet=Alphabet.PINYIN, tone_marks: str = "numbers", jieba: bool = True):
|
|
228
|
-
|
|
256
|
+
try:
|
|
257
|
+
from xpinyin import Pinyin
|
|
258
|
+
except ImportError:
|
|
259
|
+
raise ImportError(
|
|
260
|
+
"xpinyin phonemization needs xpinyin — install with "
|
|
261
|
+
"`pip install scriptconv[zh-phonemizers]`"
|
|
262
|
+
) from None
|
|
229
263
|
self.g2p = Pinyin()
|
|
230
264
|
self.tone_marks = tone_marks
|
|
231
265
|
super().__init__(alphabet, jieba)
|
|
@@ -249,7 +283,13 @@ class PypinyinPhonemizer(BaseChinesePinyinPhonemizer):
|
|
|
249
283
|
"""
|
|
250
284
|
|
|
251
285
|
def __init__(self, alphabet=Alphabet.PINYIN, jieba: bool = True):
|
|
252
|
-
|
|
286
|
+
try:
|
|
287
|
+
from pypinyin import pinyin
|
|
288
|
+
except ImportError:
|
|
289
|
+
raise ImportError(
|
|
290
|
+
"pypinyin phonemization needs pypinyin — install with "
|
|
291
|
+
"`pip install scriptconv[zh]`"
|
|
292
|
+
) from None
|
|
253
293
|
self.g2p = pinyin
|
|
254
294
|
super().__init__(alphabet, jieba)
|
|
255
295
|
|
|
@@ -416,6 +416,10 @@ def char_script(ch: str) -> Optional[str]:
|
|
|
416
416
|
if len(ch) != 1:
|
|
417
417
|
return None
|
|
418
418
|
cp = ord(ch)
|
|
419
|
+
# Only the interval immediately at-or-before cp (idx) is ever checked —
|
|
420
|
+
# correct only because SCRIPT_REGISTRY's char_ranges are non-overlapping,
|
|
421
|
+
# so at most one interval can ever contain cp. If ranges ever overlapped,
|
|
422
|
+
# this single-candidate check would silently miss matches.
|
|
419
423
|
idx = bisect_right(_SORTED_STARTS, cp) - 1
|
|
420
424
|
if idx >= 0 and cp <= _SORTED_ENDS[idx]:
|
|
421
425
|
return _SORTED_SCRIPTS[idx]
|
|
@@ -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"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Pure-python tests for scriptconv.diacritics strip/overlay helpers.
|
|
2
|
+
|
|
3
|
+
No optional backend is required — strip_diacritics/_overlay_marks/_supports_strip
|
|
4
|
+
never load a phonemizer model.
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from scriptconv.diacritics import (
|
|
9
|
+
strip_diacritics,
|
|
10
|
+
_overlay_marks,
|
|
11
|
+
_supports_strip,
|
|
12
|
+
_STRESS_MARKS,
|
|
13
|
+
_ARABIC_MARKS,
|
|
14
|
+
_HEBREW_MARKS,
|
|
15
|
+
)
|
|
16
|
+
from scriptconv.phonemizers.base import STRESS_LANGS
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_strip_removes_combining_acute_preserves_cyrillic():
|
|
20
|
+
assert strip_diacritics("мой родно́й край", "ru") == "мой родной край"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_strip_preserves_precomposed_cyrillic_letters():
|
|
24
|
+
# й (U+0439) and ё (U+0451) are precomposed, not combining marks — they
|
|
25
|
+
# must survive stripping even alongside a combining acute.
|
|
26
|
+
text = "ёж, война́, май" # combining acute (U+0301) after "а" in "война́"
|
|
27
|
+
stripped = strip_diacritics(text, "ru")
|
|
28
|
+
assert "ё" in stripped
|
|
29
|
+
assert "й" in stripped
|
|
30
|
+
assert stripped == "ёж, война, май"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_strip_arabic_removes_tashkeel_preserves_consonants():
|
|
34
|
+
assert strip_diacritics("مُحَمَّد", "ar") == "محمد"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_strip_arabic_preserves_hamza_carrier():
|
|
38
|
+
stripped = strip_diacritics("أَحْمَد", "ar")
|
|
39
|
+
assert stripped.startswith("أ")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_strip_hebrew_removes_niqqud_preserves_consonants():
|
|
43
|
+
# שָׁלוֹם with niqqud -> bare consonants שלום
|
|
44
|
+
stripped = strip_diacritics("שָׁלוֹם", "he")
|
|
45
|
+
assert stripped == "שלום"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.parametrize("text,lang", [
|
|
49
|
+
("café", "pt"),
|
|
50
|
+
("café", "en"),
|
|
51
|
+
("café", "es"),
|
|
52
|
+
("café", "arg"),
|
|
53
|
+
("café", "her"),
|
|
54
|
+
("café", "arn"),
|
|
55
|
+
])
|
|
56
|
+
def test_strip_raises_for_native_orthography_langs(text, lang):
|
|
57
|
+
with pytest.raises(ValueError):
|
|
58
|
+
strip_diacritics(text, lang)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@pytest.mark.parametrize("lang", [
|
|
62
|
+
"ru", "uk", "be", "ar", "ar-SA", "he", "he-IL", "bg", "ka",
|
|
63
|
+
])
|
|
64
|
+
def test_supports_strip_true(lang):
|
|
65
|
+
assert _supports_strip(lang) is True
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.mark.parametrize("lang", [
|
|
69
|
+
"pt", "pt-PT", "en", "arg", "her", "arn",
|
|
70
|
+
])
|
|
71
|
+
def test_supports_strip_false(lang):
|
|
72
|
+
assert _supports_strip(lang) is False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_overlay_marks_stress_lang():
|
|
76
|
+
lang = next(iter(STRESS_LANGS))
|
|
77
|
+
assert _overlay_marks(lang) == _STRESS_MARKS
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_overlay_marks_arabic():
|
|
81
|
+
assert _overlay_marks("ar") == _ARABIC_MARKS
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_overlay_marks_hebrew():
|
|
85
|
+
assert _overlay_marks("he") == _HEBREW_MARKS
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_overlay_marks_none_for_native_orthography():
|
|
89
|
+
assert _overlay_marks("pt") is None
|
|
90
|
+
assert _overlay_marks("en") is None
|
|
@@ -21,6 +21,65 @@ class TestVendoredChinese(unittest.TestCase):
|
|
|
21
21
|
from scriptconv.phonemizers._thirdparty.zh_num import num2str
|
|
22
22
|
self.assertEqual(num2str("123"), "一百二十三")
|
|
23
23
|
|
|
24
|
+
def test_zh_num_fullwidth_digits_normalized(self):
|
|
25
|
+
# zh_num.DIGITS is keyed by ASCII "0"-"9"; full-width digits (e.g.
|
|
26
|
+
# "3") must be NFKC-folded before lookup or num2str raises KeyError
|
|
27
|
+
import unicodedata
|
|
28
|
+
from scriptconv.phonemizers._thirdparty.zh_num import num2str
|
|
29
|
+
fullwidth = "123"
|
|
30
|
+
self.assertEqual(
|
|
31
|
+
num2str(unicodedata.normalize("NFKC", fullwidth)),
|
|
32
|
+
num2str("123"),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestPinyinRetone(unittest.TestCase):
|
|
37
|
+
def test_retone_tone_marks(self):
|
|
38
|
+
from scriptconv.phonemizers.zh import BaseChinesePinyinPhonemizer
|
|
39
|
+
retoned = BaseChinesePinyinPhonemizer._retone("ma˥")
|
|
40
|
+
self.assertEqual(retoned, "ma→")
|
|
41
|
+
|
|
42
|
+
def test_retone_leftover_syllabic_mark_does_not_raise(self):
|
|
43
|
+
# any pinyin_to_ipa output carrying an unanticipated combining
|
|
44
|
+
# U+0329 (not attached to ɻ/ɹ) must degrade gracefully, not crash
|
|
45
|
+
from scriptconv.phonemizers.zh import BaseChinesePinyinPhonemizer
|
|
46
|
+
leftover = "n" + chr(809)
|
|
47
|
+
retoned = BaseChinesePinyinPhonemizer._retone(leftover)
|
|
48
|
+
self.assertEqual(retoned, leftover)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TestZhBackendFriendlyImportErrors(unittest.TestCase):
|
|
52
|
+
def _assert_friendly(self, module_name, cls, extra):
|
|
53
|
+
# sys.modules[name] = None makes the `import` statement raise
|
|
54
|
+
# ImportError regardless of whether the package is actually
|
|
55
|
+
# installed, so this simulates the missing-dependency case cleanly
|
|
56
|
+
import sys
|
|
57
|
+
from unittest.mock import patch
|
|
58
|
+
with patch.dict(sys.modules, {module_name: None}):
|
|
59
|
+
with self.assertRaises(ImportError) as ctx:
|
|
60
|
+
cls()
|
|
61
|
+
self.assertIn(f"scriptconv[{extra}]", str(ctx.exception))
|
|
62
|
+
|
|
63
|
+
def test_pypinyin_missing_dep_message(self):
|
|
64
|
+
from scriptconv.phonemizers.zh import PypinyinPhonemizer
|
|
65
|
+
self._assert_friendly("pypinyin", PypinyinPhonemizer, "zh")
|
|
66
|
+
|
|
67
|
+
def test_xpinyin_missing_dep_message(self):
|
|
68
|
+
from scriptconv.phonemizers.zh import XpinyinPhonemizer
|
|
69
|
+
self._assert_friendly("xpinyin", XpinyinPhonemizer, "zh-phonemizers")
|
|
70
|
+
|
|
71
|
+
def test_g2pm_missing_dep_message(self):
|
|
72
|
+
from scriptconv.phonemizers.zh import G2pMPhonemizer
|
|
73
|
+
self._assert_friendly("g2pM", G2pMPhonemizer, "zh-phonemizers")
|
|
74
|
+
|
|
75
|
+
def test_g2pc_missing_dep_message(self):
|
|
76
|
+
from scriptconv.phonemizers.zh import G2pCPhonemizer
|
|
77
|
+
self._assert_friendly("g2pc", G2pCPhonemizer, "zh-phonemizers")
|
|
78
|
+
|
|
79
|
+
def test_pinyin_to_ipa_missing_dep_message(self):
|
|
80
|
+
from scriptconv.phonemizers.zh import PypinyinPhonemizer
|
|
81
|
+
self._assert_friendly("pinyin_to_ipa", PypinyinPhonemizer, "zh-phonemizers")
|
|
82
|
+
|
|
24
83
|
|
|
25
84
|
class TestShamiFrontend(unittest.TestCase):
|
|
26
85
|
def test_codeswitch_language_ids_align(self):
|
|
@@ -742,3 +742,15 @@ def test_script_runs_empty_and_pure_punctuation():
|
|
|
742
742
|
def test_script_runs_reconstructs_input():
|
|
743
743
|
for text in ["привет hello", "Hello مرحبا world", "приве́т", " hi", "日本語 abc"]:
|
|
744
744
|
assert "".join(t for _, t in script_runs(text)) == text
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def test_char_script_ranges_are_non_overlapping():
|
|
748
|
+
# char_script's binary search only checks one candidate interval; that
|
|
749
|
+
# is only correct if no two registered char_ranges overlap.
|
|
750
|
+
ranges = sorted(
|
|
751
|
+
(lo, hi)
|
|
752
|
+
for script in SCRIPT_REGISTRY.values()
|
|
753
|
+
for lo, hi in script.char_ranges
|
|
754
|
+
)
|
|
755
|
+
for (_, prev_hi), (next_lo, _) in zip(ranges, ranges[1:]):
|
|
756
|
+
assert next_lo > prev_hi
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/hanja.tsv
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/ipa.csv
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/ko_tables/yale.csv
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/__init__.py
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/codeswitch.py
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/diacritize.py
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/dialectal.py
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/english_g2p.py
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/espeak.py
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/frontend.py
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/levantine_g2p.py
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_thirdparty/shami/normalize.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/kog2p/rulebook.txt
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/LICENSE.md
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/__init__.py
RENAMED
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/buck/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/buck/symbols.py
RENAMED
|
File without changes
|
|
File without changes
|
{scriptconv-0.0.4a4 → scriptconv-0.0.4a5}/scriptconv/phonemizers/_vendored/mantoq/num2words.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|