scriptconv 0.0.4a5__tar.gz → 0.0.4a7__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.4a5/scriptconv.egg-info → scriptconv-0.0.4a7}/PKG-INFO +8 -4
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/README.md +7 -3
- scriptconv-0.0.4a7/scriptconv/diacritics.py +367 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/base.py +4 -177
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/registry.py +2 -5
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/version.py +1 -1
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7/scriptconv.egg-info}/PKG-INFO +8 -4
- scriptconv-0.0.4a7/tests/test_diacritics.py +296 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_diacritics_graph.py +1 -2
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_phonemizers_base.py +0 -100
- scriptconv-0.0.4a5/scriptconv/diacritics.py +0 -162
- scriptconv-0.0.4a5/tests/test_diacritics.py +0 -90
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/LICENSE +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/pyproject.toml +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/requirements.txt +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/__init__.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/__main__.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/cangjie.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/conventions.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/data/__init__.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/data/cangjie5_tc.tsv.gz +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/graph.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/notation.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/__init__.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/__init__.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/bw2ipa.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/hangul2ipa.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/aspiration.csv +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/assimilation.csv +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/double_coda.csv +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/hanja.tsv +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/ipa.csv +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/neutralization.csv +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/tensification.csv +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/ko_tables/yale.csv +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/__init__.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/codeswitch.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/diacritize.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/dialectal.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/english_g2p.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/espeak.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/frontend.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/levantine_g2p.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/normalize.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/shami/phoneme_inventory.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_thirdparty/zh_num.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/__init__.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/kog2p/LICENSE.md +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/kog2p/__init__.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/kog2p/rulebook.txt +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/LICENSE.md +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/__init__.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/buck/__init__.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/buck/phonetise_buckwalter.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/buck/symbols.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/buck/tokenization.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/num2words.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/_vendored/mantoq/unicode_symbol2label.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/ar.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/en.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/enums.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/eu.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/fa.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/gl.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/he.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/ja.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/ko.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/mul.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/mwl.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/o2ipa.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/pt.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/shami.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/vi.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/phonemizers/zh.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/py.typed +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/readings.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/scripts.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv/translit.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv.egg-info/SOURCES.txt +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv.egg-info/dependency_links.txt +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv.egg-info/requires.txt +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/scriptconv.egg-info/top_level.txt +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/setup.cfg +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_arpa_stress.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_cangjie.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_cli.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_conventions.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_errors_policy.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_examples.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_graph.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_notation.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_phonemizers_cjk_ar.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_readings.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_readings_zh.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_scripts.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/tests/test_scripts_stressonnx_compat.py +0 -0
- {scriptconv-0.0.4a5 → scriptconv-0.0.4a7}/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.4a7
|
|
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
|
|
@@ -373,9 +373,13 @@ Two design points worth knowing:
|
|
|
373
373
|
phonemizing; that needs language resources scriptconv doesn't ship. Pass
|
|
374
374
|
`normalizer=` (a `(text, lang) -> str` callable) to run yours inside the
|
|
375
375
|
pipeline; without it, text is phonemized as-is.
|
|
376
|
-
- **
|
|
377
|
-
local `model=` path; resolving and caching model
|
|
378
|
-
concern.
|
|
376
|
+
- **Large/licensed model-backed engines never download.** ByT5/Charsiu
|
|
377
|
+
require an explicit local `model=` path; resolving and caching those model
|
|
378
|
+
files is the caller's concern. Small, known-good, unencumbered models are
|
|
379
|
+
the exception: the Hebrew phonikud diacritizer auto-provisions its ONNX
|
|
380
|
+
model to a cache dir on first use (`phonikud_model=` still overrides it
|
|
381
|
+
with a path or callable; cache location via `SCRIPTCONV_CACHE`/
|
|
382
|
+
`XDG_CACHE_HOME`).
|
|
379
383
|
|
|
380
384
|
**Pre-G2P disambiguation.** `add_diacritics(text, lang, model=None)` restores
|
|
381
385
|
information ordinary orthography omits but a G2P needs, before phonemization:
|
|
@@ -246,9 +246,13 @@ Two design points worth knowing:
|
|
|
246
246
|
phonemizing; that needs language resources scriptconv doesn't ship. Pass
|
|
247
247
|
`normalizer=` (a `(text, lang) -> str` callable) to run yours inside the
|
|
248
248
|
pipeline; without it, text is phonemized as-is.
|
|
249
|
-
- **
|
|
250
|
-
local `model=` path; resolving and caching model
|
|
251
|
-
concern.
|
|
249
|
+
- **Large/licensed model-backed engines never download.** ByT5/Charsiu
|
|
250
|
+
require an explicit local `model=` path; resolving and caching those model
|
|
251
|
+
files is the caller's concern. Small, known-good, unencumbered models are
|
|
252
|
+
the exception: the Hebrew phonikud diacritizer auto-provisions its ONNX
|
|
253
|
+
model to a cache dir on first use (`phonikud_model=` still overrides it
|
|
254
|
+
with a path or callable; cache location via `SCRIPTCONV_CACHE`/
|
|
255
|
+
`XDG_CACHE_HOME`).
|
|
252
256
|
|
|
253
257
|
**Pre-G2P disambiguation.** `add_diacritics(text, lang, model=None)` restores
|
|
254
258
|
information ordinary orthography omits but a G2P needs, before phonemization:
|
|
@@ -0,0 +1,367 @@
|
|
|
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
|
+
import os
|
|
55
|
+
import tempfile
|
|
56
|
+
import urllib.request
|
|
57
|
+
from pathlib import Path
|
|
58
|
+
from typing import Optional
|
|
59
|
+
|
|
60
|
+
from scriptconv.graph import Edge
|
|
61
|
+
from scriptconv.phonemizers.base import _primary_subtag
|
|
62
|
+
|
|
63
|
+
#: The lang-contextual node produced by diacritization. Like ``"text"`` it is
|
|
64
|
+
#: meaningful only with ``lang=`` context and exists only in opted-in graphs.
|
|
65
|
+
DIACRITIZED = "text-diacritized"
|
|
66
|
+
|
|
67
|
+
# Across East Slavic, Bulgarian/Macedonian/Slovene, Latvian, Armenian,
|
|
68
|
+
# Georgian, and several Turkic/Caucasian languages, lexical word stress is
|
|
69
|
+
# free (not fixed to a syllable) and ordinary orthography leaves it unwritten
|
|
70
|
+
# or under-marked. The clearest case is East Slavic: stress is also mobile
|
|
71
|
+
# (it shifts between forms of the same word) and unstressed vowels *reduce*
|
|
72
|
+
# — Russian unstressed "о" surfaces as [ɐ] or [ə] depending on distance from
|
|
73
|
+
# the stress, not [o] — so a wrong or missing mark there corrupts the vowel
|
|
74
|
+
# quality of the whole word, not just its prosody. Other families in this set
|
|
75
|
+
# don't necessarily reduce vowels, but still need the mark for correct stress
|
|
76
|
+
# placement and prosody. stressonnx restores it as a combining acute (U+0301)
|
|
77
|
+
# after the stressed vowel, covering 26 BCP-47 tags across these families
|
|
78
|
+
# (24 primary subtags; Azerbaijani and Uzbek each have Cyrillic/Latin script
|
|
79
|
+
# variants routed by the full tag).
|
|
80
|
+
STRESS_LANGS = {
|
|
81
|
+
"az", "ba", "be", "bg", "cv", "hy", "ka", "kbd", "kjh", "kk", "ky", "lv",
|
|
82
|
+
"mdf", "mk", "myv", "ru", "sah", "sl", "tg", "tt", "udm", "uk", "uz", "xal",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _is_european_portuguese(lang: str) -> bool:
|
|
87
|
+
"""True for European Portuguese (``pt`` or a ``pt-PT`` region tag).
|
|
88
|
+
|
|
89
|
+
False for Brazilian Portuguese (``pt-BR``) and everything else — the two
|
|
90
|
+
varieties' vowel systems differ, and bifonia's open/closed diacritics are
|
|
91
|
+
only valid for European Portuguese phonology.
|
|
92
|
+
"""
|
|
93
|
+
norm = lang.lower().replace("_", "-")
|
|
94
|
+
return norm == "pt" or norm == "pt-pt"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _diacritizer_family(lang: str) -> Optional[str]:
|
|
98
|
+
"""Which diacritization backend family handles *lang*, or ``None``.
|
|
99
|
+
|
|
100
|
+
Single source of truth for lang→backend routing: both the forward
|
|
101
|
+
dispatch (:func:`diacritize`) and the strip direction (:func:`_overlay_marks`)
|
|
102
|
+
resolve through this, so the two can never disagree about which language
|
|
103
|
+
uses which backend. Returns one of ``"he"`` (niqqud), ``"ar"`` (tashkeel),
|
|
104
|
+
``"stress"`` (stressonnx), ``"pt"`` (bifonia sense diacritics), or
|
|
105
|
+
``None``. Uses exact primary-subtag matching (never ``startswith``), so
|
|
106
|
+
Aragonese (``arg``), Herero (``her``) and Mapudungun (``arn``) are never
|
|
107
|
+
misread as Arabic/Hebrew.
|
|
108
|
+
"""
|
|
109
|
+
p = _primary_subtag(lang)
|
|
110
|
+
if p == "he":
|
|
111
|
+
return "he"
|
|
112
|
+
if p == "ar":
|
|
113
|
+
return "ar"
|
|
114
|
+
if p in STRESS_LANGS:
|
|
115
|
+
return "stress"
|
|
116
|
+
if _is_european_portuguese(lang):
|
|
117
|
+
return "pt"
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
_DEFAULT_DIACRITIZER_MODEL = "rawi-ensemble"
|
|
122
|
+
|
|
123
|
+
_PHONIKUD_CACHE: dict = {} # resolved model path/string -> Phonikud instance
|
|
124
|
+
_TASHKEEL_CACHE: dict = {} # model name -> text2tashkeel Diacritizer
|
|
125
|
+
|
|
126
|
+
# phonikud is a small (~100MB), public, unlicensed-restriction ONNX model, so
|
|
127
|
+
# — unlike the large/licensed models behind mul.py's ByT5/Charsiu backends —
|
|
128
|
+
# scriptconv auto-provisions it: no local path is required unless the caller
|
|
129
|
+
# wants to override the cache (e.g. an air-gapped host).
|
|
130
|
+
_PHONIKUD_URL = "https://huggingface.co/thewh1teagle/phonikud-onnx/resolve/main/phonikud-1.0.int8.onnx"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _default_phonikud_model() -> str:
|
|
134
|
+
"""Resolve (downloading and caching on first use if needed) the path to
|
|
135
|
+
the default phonikud ONNX model.
|
|
136
|
+
|
|
137
|
+
Cache directory: ``<base>/scriptconv/phonikud`` where ``<base>`` is
|
|
138
|
+
``$SCRIPTCONV_CACHE`` if set, else ``$XDG_CACHE_HOME`` (default
|
|
139
|
+
``~/.cache``) — i.e. ``~/.cache/scriptconv/phonikud`` by default. The
|
|
140
|
+
download is written to a temp file
|
|
141
|
+
in the same directory and atomically moved into place via
|
|
142
|
+
:func:`os.replace`, so a failed or interrupted download never leaves a
|
|
143
|
+
partial file at the destination path.
|
|
144
|
+
"""
|
|
145
|
+
base = os.environ.get("SCRIPTCONV_CACHE") or os.environ.get(
|
|
146
|
+
"XDG_CACHE_HOME", os.path.expanduser("~/.cache"))
|
|
147
|
+
cache_dir = Path(base) / "scriptconv" / "phonikud"
|
|
148
|
+
dest = cache_dir / "phonikud-1.0.int8.onnx"
|
|
149
|
+
if not dest.is_file():
|
|
150
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
fd, tmp_path = tempfile.mkstemp(dir=str(cache_dir), prefix=".phonikud-", suffix=".tmp")
|
|
152
|
+
try:
|
|
153
|
+
with os.fdopen(fd, "wb") as tmp_f, urllib.request.urlopen(_PHONIKUD_URL) as resp:
|
|
154
|
+
while True:
|
|
155
|
+
chunk = resp.read(1024 * 1024)
|
|
156
|
+
if not chunk:
|
|
157
|
+
break
|
|
158
|
+
tmp_f.write(chunk)
|
|
159
|
+
os.replace(tmp_path, dest)
|
|
160
|
+
except BaseException:
|
|
161
|
+
if os.path.exists(tmp_path):
|
|
162
|
+
os.remove(tmp_path)
|
|
163
|
+
raise
|
|
164
|
+
return str(dest)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _phonikud(phonikud_model=None):
|
|
168
|
+
"""Lazily build (and cache) the phonikud Phonikud instance used for Hebrew.
|
|
169
|
+
|
|
170
|
+
``phonikud_model`` is optional: a local path to a phonikud ONNX model, or
|
|
171
|
+
a zero-arg callable resolving one lazily. When omitted, scriptconv
|
|
172
|
+
auto-provisions the small public phonikud model, downloading it once into
|
|
173
|
+
a cache dir (see :func:`_default_phonikud_model`) and reusing it on
|
|
174
|
+
subsequent calls. Pass an explicit path/callable to override — e.g. to
|
|
175
|
+
point at a model already on disk, or on an air-gapped host. Install
|
|
176
|
+
phonikud-onnx with ``pip install scriptconv[he]`` (or ``pip install
|
|
177
|
+
phonikud-onnx``)."""
|
|
178
|
+
model = phonikud_model() if callable(phonikud_model) else phonikud_model
|
|
179
|
+
if not model:
|
|
180
|
+
model = _default_phonikud_model()
|
|
181
|
+
if model not in _PHONIKUD_CACHE:
|
|
182
|
+
try:
|
|
183
|
+
from phonikud_onnx import Phonikud
|
|
184
|
+
except ImportError:
|
|
185
|
+
raise ImportError(
|
|
186
|
+
"Hebrew diacritization needs phonikud-onnx — install "
|
|
187
|
+
"with `pip install scriptconv[he]`") from None
|
|
188
|
+
_PHONIKUD_CACHE[model] = Phonikud(model)
|
|
189
|
+
return _PHONIKUD_CACHE[model]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _tashkeel(model: Optional[str] = None):
|
|
193
|
+
"""Lazily build (and cache) the text2tashkeel Diacritizer used for Arabic.
|
|
194
|
+
|
|
195
|
+
text2tashkeel is a dependency of the ``[ar]`` extra; it restores hamza and the
|
|
196
|
+
dagger alef in addition to the standard marks. Install with
|
|
197
|
+
``pip install scriptconv[tashkeel]`` (or ``pip install text2tashkeel``)."""
|
|
198
|
+
model = model or _DEFAULT_DIACRITIZER_MODEL
|
|
199
|
+
if model not in _TASHKEEL_CACHE:
|
|
200
|
+
try:
|
|
201
|
+
from text2tashkeel import Diacritizer
|
|
202
|
+
except ImportError as e:
|
|
203
|
+
raise ImportError(
|
|
204
|
+
"Arabic diacritization requires the text2tashkeel package: "
|
|
205
|
+
"pip install scriptconv[tashkeel] (or pip install text2tashkeel)"
|
|
206
|
+
) from e
|
|
207
|
+
_TASHKEEL_CACHE[model] = Diacritizer(model)
|
|
208
|
+
return _TASHKEEL_CACHE[model]
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _stress(text: str, lang: str, model: Optional[str] = None) -> str:
|
|
212
|
+
"""Word-stress restoration via stressonnx, for the 26 language tags
|
|
213
|
+
it covers (see ``STRESS_LANGS``) — East Slavic, Bulgarian/Macedonian/
|
|
214
|
+
Slovene, Latvian, Armenian, Georgian, and Turkic/Caucasian languages.
|
|
215
|
+
|
|
216
|
+
stressonnx is not on PyPI yet; install straight from source. Install
|
|
217
|
+
with ``pip install scriptconv[stress]`` (or ``pip install
|
|
218
|
+
stressonnx``)."""
|
|
219
|
+
try:
|
|
220
|
+
from stressonnx import stress
|
|
221
|
+
except ImportError as e:
|
|
222
|
+
raise ImportError(
|
|
223
|
+
"stress restoration requires the stressonnx package: "
|
|
224
|
+
"pip install scriptconv[stress] (or pip install stressonnx)"
|
|
225
|
+
) from e
|
|
226
|
+
return stress(text, lang, model=model)
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _sense_diacritics_pt(text: str) -> str:
|
|
230
|
+
"""European-Portuguese heterophonic-homograph sense diacritics via bifonia.
|
|
231
|
+
|
|
232
|
+
Rewrites homographs whose pronunciation depends on meaning (e.g.
|
|
233
|
+
"sede" thirst/closed vs. seat/open) with an explicit open/closed
|
|
234
|
+
vowel diacritic. These are ordinary Portuguese orthographic marks,
|
|
235
|
+
chosen so any downstream G2P — rule-based, neural, or espeak —
|
|
236
|
+
reads them correctly. Install with ``pip install scriptconv[pt]``
|
|
237
|
+
(or ``pip install bifonia``)."""
|
|
238
|
+
try:
|
|
239
|
+
from bifonia import add_extra_diacritics
|
|
240
|
+
except ImportError as e:
|
|
241
|
+
raise ImportError(
|
|
242
|
+
"European-Portuguese sense diacritics require the bifonia package: "
|
|
243
|
+
"pip install scriptconv[pt] (or pip install bifonia)"
|
|
244
|
+
) from e
|
|
245
|
+
return add_extra_diacritics(text)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def diacritize(text: str, lang: str = "und", model=None,
|
|
249
|
+
phonikud_model=None, diacritizer_model=None, **_) -> str:
|
|
250
|
+
"""Add pronunciation-disambiguating diacritics to *text* for *lang*.
|
|
251
|
+
|
|
252
|
+
Four backends, each restoring information ordinary orthography omits but
|
|
253
|
+
downstream G2P needs:
|
|
254
|
+
|
|
255
|
+
- Hebrew (``he``) — niqqud via phonikud. ``phonikud_model=`` is optional:
|
|
256
|
+
omitted, the small public phonikud ONNX model is auto-downloaded and
|
|
257
|
+
cached (``$SCRIPTCONV_CACHE``/``$XDG_CACHE_HOME``); pass a path or
|
|
258
|
+
zero-arg callable to override.
|
|
259
|
+
- Arabic (``ar``) — tashkeel via text2tashkeel (``[tashkeel]``,
|
|
260
|
+
``model=``/``diacritizer_model=``).
|
|
261
|
+
- East Slavic, Bulgarian/Macedonian/Slovene, Latvian, Armenian, Georgian,
|
|
262
|
+
and Turkic/Caucasian languages (``STRESS_LANGS``, 26 stressonnx tags) —
|
|
263
|
+
word stress via stressonnx (``[stress]``); stress is unwritten or
|
|
264
|
+
under-marked in these languages, and in East Slavic unstressed vowels
|
|
265
|
+
also reduce, so a missing mark can corrupt more than prosody.
|
|
266
|
+
- European Portuguese (``pt``/``pt-PT``, never ``pt-BR``) —
|
|
267
|
+
heterophonic-homograph sense diacritics via bifonia (``[pt]``);
|
|
268
|
+
ordinary Portuguese orthographic marks that any downstream G2P reads
|
|
269
|
+
correctly.
|
|
270
|
+
|
|
271
|
+
Unrecognized languages are returned unchanged. Each backend raises
|
|
272
|
+
``ImportError`` naming its extra when the optional dependency is missing
|
|
273
|
+
— scriptconv never installs anything on the caller's behalf.
|
|
274
|
+
"""
|
|
275
|
+
family = _diacritizer_family(lang)
|
|
276
|
+
if family == "he":
|
|
277
|
+
return _phonikud(phonikud_model).add_diacritics(text)
|
|
278
|
+
if family == "ar":
|
|
279
|
+
return _tashkeel(model or diacritizer_model).diacritize(text)
|
|
280
|
+
if family == "stress":
|
|
281
|
+
return _stress(text, lang, model)
|
|
282
|
+
if family == "pt":
|
|
283
|
+
return _sense_diacritics_pt(text)
|
|
284
|
+
return text
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
# Combining marks each diacritization backend overlays onto the bare text.
|
|
288
|
+
# Strip removes ONLY these codepoints — never a blanket combining-mark filter,
|
|
289
|
+
# which (via NFD) would decompose and destroy precomposed native letters:
|
|
290
|
+
# Cyrillic й/ё, Latvian ī, Azerbaijani ç/ö, and Arabic hamza carriers أ إ آ ؤ ئ.
|
|
291
|
+
_STRESS_MARKS = frozenset({0x0300, 0x0301}) # combining grave / acute
|
|
292
|
+
# U+0300 is included defensively alongside the U+0301 that stressonnx
|
|
293
|
+
# actually emits, in case any backend/locale marks secondary stress with a
|
|
294
|
+
# grave instead of an acute; harmless to strip since native precomposed
|
|
295
|
+
# letters (e.g. Cyrillic й/ё) are unaffected either way.
|
|
296
|
+
_ARABIC_MARKS = frozenset(range(0x064B, 0x0660)) | frozenset({0x0670}) # tashkeel + dagger alef
|
|
297
|
+
_HEBREW_MARKS = (frozenset(range(0x05B0, 0x05BE))
|
|
298
|
+
| frozenset({0x05BF, 0x05C1, 0x05C2, 0x05C4, 0x05C5, 0x05C7})) # niqqud
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _overlay_marks(lang: str) -> "frozenset | None":
|
|
302
|
+
"""The overlay codepoints for *lang*'s diacritization backend, or None.
|
|
303
|
+
|
|
304
|
+
Resolves the backend family through :func:`_diacritizer_family` (the shared
|
|
305
|
+
lang→backend routing), so this can never disagree with
|
|
306
|
+
:func:`diacritize` about which language a backend owns.
|
|
307
|
+
Only *overlay* families are strippable: ``"pt"`` (bifonia sense marks are
|
|
308
|
+
native orthography) and ``None`` both return None.
|
|
309
|
+
"""
|
|
310
|
+
family = _diacritizer_family(lang)
|
|
311
|
+
if family == "stress":
|
|
312
|
+
return _STRESS_MARKS
|
|
313
|
+
if family == "ar":
|
|
314
|
+
return _ARABIC_MARKS
|
|
315
|
+
if family == "he":
|
|
316
|
+
return _HEBREW_MARKS
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _supports_strip(lang: str) -> bool:
|
|
321
|
+
"""True for languages whose diacritics are removable overlays (stress marks,
|
|
322
|
+
Arabic tashkeel, Hebrew niqqud) rather than native orthography."""
|
|
323
|
+
return _overlay_marks(lang) is not None
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def strip_diacritics(text: str, lang: str = "und", **_) -> str:
|
|
327
|
+
"""Remove the overlay diacritics *lang*'s backend adds, recovering the bare
|
|
328
|
+
text WITHOUT touching native letters.
|
|
329
|
+
|
|
330
|
+
Removes only the specific overlay codepoints (combining acute/grave for
|
|
331
|
+
stress, tashkeel for Arabic, niqqud for Hebrew) — precomposed native
|
|
332
|
+
letters (Cyrillic й/ё, Latvian ī, Azerbaijani ç, Arabic hamza carriers) are
|
|
333
|
+
left intact. Raises :class:`ValueError` for languages whose diacritics are
|
|
334
|
+
part of the native orthography (e.g. European Portuguese), where any removal
|
|
335
|
+
would corrupt the spelling.
|
|
336
|
+
"""
|
|
337
|
+
marks = _overlay_marks(lang)
|
|
338
|
+
if marks is None:
|
|
339
|
+
raise ValueError(
|
|
340
|
+
f"cannot strip diacritics for lang={lang!r}: its diacritics are "
|
|
341
|
+
"part of the native orthography, so removal would corrupt the "
|
|
342
|
+
"spelling. strip is defined only for overlay diacritics "
|
|
343
|
+
"(Arabic/Hebrew vocalization, East-Slavic/Turkic stress).")
|
|
344
|
+
return "".join(c for c in text if ord(c) not in marks)
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def register(graph) -> None:
|
|
348
|
+
"""Opt-in graph integration: add the diacritize/strip edge pair.
|
|
349
|
+
|
|
350
|
+
``text -> text-diacritized`` is model-based (``lossless=False``), so a
|
|
351
|
+
direct ``text -> ipa`` phonemization always out-prices the detour —
|
|
352
|
+
enabling this extension is safe and non-invasive. Pair with
|
|
353
|
+
:func:`scriptconv.phonemizers.register` to make
|
|
354
|
+
``"text-diacritized" -> "ipa"`` reachable.
|
|
355
|
+
|
|
356
|
+
``text-diacritized -> text`` (:func:`strip_diacritics`) is lossless and
|
|
357
|
+
cheap, but gated: it raises for languages whose diacritics are native
|
|
358
|
+
orthography rather than removable overlays.
|
|
359
|
+
"""
|
|
360
|
+
graph.register(
|
|
361
|
+
Edge("text", DIACRITIZED,
|
|
362
|
+
lambda text, lang="und", model=None, **c: diacritize(text, lang, model, **c),
|
|
363
|
+
lossless=False))
|
|
364
|
+
graph.register(
|
|
365
|
+
Edge(DIACRITIZED, "text",
|
|
366
|
+
lambda text, lang="und", **_: strip_diacritics(text, lang),
|
|
367
|
+
lossless=True))
|
|
@@ -34,163 +34,25 @@ RawPhonemizedChunks = List[Tuple[str, str, bool]]
|
|
|
34
34
|
|
|
35
35
|
PhonemizedChunks = list[list[str]]
|
|
36
36
|
|
|
37
|
-
# Across East Slavic, Bulgarian/Macedonian/Slovene, Latvian, Armenian,
|
|
38
|
-
# Georgian, and several Turkic/Caucasian languages, lexical word stress is
|
|
39
|
-
# free (not fixed to a syllable) and ordinary orthography leaves it unwritten
|
|
40
|
-
# or under-marked. The clearest case is East Slavic: stress is also mobile
|
|
41
|
-
# (it shifts between forms of the same word) and unstressed vowels *reduce*
|
|
42
|
-
# — Russian unstressed "о" surfaces as [ɐ] or [ə] depending on distance from
|
|
43
|
-
# the stress, not [o] — so a wrong or missing mark there corrupts the vowel
|
|
44
|
-
# quality of the whole word, not just its prosody. Other families in this set
|
|
45
|
-
# don't necessarily reduce vowels, but still need the mark for correct stress
|
|
46
|
-
# placement and prosody. stressonnx restores it as a combining acute (U+0301)
|
|
47
|
-
# after the stressed vowel, covering 26 BCP-47 tags across these families
|
|
48
|
-
# (24 primary subtags; Azerbaijani and Uzbek each have Cyrillic/Latin script
|
|
49
|
-
# variants routed by the full tag).
|
|
50
|
-
STRESS_LANGS = {
|
|
51
|
-
"az", "ba", "be", "bg", "cv", "hy", "ka", "kbd", "kjh", "kk", "ky", "lv",
|
|
52
|
-
"mdf", "mk", "myv", "ru", "sah", "sl", "tg", "tt", "udm", "uk", "uz", "xal",
|
|
53
|
-
}
|
|
54
|
-
|
|
55
37
|
|
|
56
38
|
def _primary_subtag(lang: str) -> str:
|
|
57
39
|
"""Lowercase, ``_``→``-`` normalized primary language subtag.
|
|
58
40
|
|
|
59
|
-
Used for exact-match routing (
|
|
60
|
-
``str.startswith``, so
|
|
61
|
-
Belarusian (``be``).
|
|
41
|
+
Used for exact-match language routing (e.g. registry lang defaults,
|
|
42
|
+
diacritization backend selection) rather than ``str.startswith``, so
|
|
43
|
+
e.g. Berber (``ber``) never false-matches Belarusian (``be``).
|
|
62
44
|
"""
|
|
63
45
|
return lang.lower().replace("_", "-").split("-")[0]
|
|
64
46
|
|
|
65
47
|
|
|
66
|
-
def _is_european_portuguese(lang: str) -> bool:
|
|
67
|
-
"""True for European Portuguese (``pt`` or a ``pt-PT`` region tag).
|
|
68
|
-
|
|
69
|
-
False for Brazilian Portuguese (``pt-BR``) and everything else — the two
|
|
70
|
-
varieties' vowel systems differ, and bifonia's open/closed diacritics are
|
|
71
|
-
only valid for European Portuguese phonology.
|
|
72
|
-
"""
|
|
73
|
-
norm = lang.lower().replace("_", "-")
|
|
74
|
-
return norm == "pt" or norm == "pt-pt"
|
|
75
|
-
|
|
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
|
-
|
|
101
48
|
class BasePhonemizer(metaclass=abc.ABCMeta):
|
|
102
49
|
def __init__(self, alphabet: Alphabet = Alphabet.UNICODE,
|
|
103
|
-
|
|
104
|
-
normalizer: Optional[Callable[[str, str], str]] = None,
|
|
105
|
-
phonikud_model: Optional[str] = None):
|
|
50
|
+
normalizer: Optional[Callable[[str, str], str]] = None):
|
|
106
51
|
super().__init__()
|
|
107
52
|
self.alphabet = alphabet
|
|
108
53
|
# optional (text, lang) -> str hook run before chunking; see module
|
|
109
54
|
# docstring — scriptconv performs no normalization of its own
|
|
110
55
|
self.normalizer = normalizer
|
|
111
|
-
# local path to a phonikud ONNX model (Hebrew diacritization), or a
|
|
112
|
-
# zero-arg callable resolving one lazily; scriptconv never downloads
|
|
113
|
-
# — the consumer resolves the file
|
|
114
|
-
self.phonikud_model = phonikud_model
|
|
115
|
-
|
|
116
|
-
# diacritizer model name, for languages that need one. Arabic uses
|
|
117
|
-
# text2tashkeel; the default "rawi-ensemble" restores hamza and the dagger
|
|
118
|
-
# alef in addition to the standard marks.
|
|
119
|
-
self.diacritizer_model = diacritizer_model
|
|
120
|
-
self._phonikud = None # hebrew only
|
|
121
|
-
self._tashkeel: dict = {} # model name -> text2tashkeel Diacritizer
|
|
122
|
-
|
|
123
|
-
@property
|
|
124
|
-
def phonikud(self):
|
|
125
|
-
if self._phonikud is None:
|
|
126
|
-
model = self.phonikud_model() if callable(self.phonikud_model) \
|
|
127
|
-
else self.phonikud_model
|
|
128
|
-
if not model:
|
|
129
|
-
raise ValueError(
|
|
130
|
-
"Hebrew diacritization needs a local phonikud ONNX model: "
|
|
131
|
-
"pass phonikud_model=<path> (scriptconv never downloads "
|
|
132
|
-
"models; obtain one from the phonikud-onnx release)")
|
|
133
|
-
try:
|
|
134
|
-
from phonikud_onnx import Phonikud
|
|
135
|
-
except ImportError:
|
|
136
|
-
raise ImportError(
|
|
137
|
-
"Hebrew diacritization needs phonikud-onnx — install "
|
|
138
|
-
"with `pip install scriptconv[he]`") from None
|
|
139
|
-
self._phonikud = Phonikud(model)
|
|
140
|
-
return self._phonikud
|
|
141
|
-
|
|
142
|
-
def tashkeel(self, model: Optional[str] = None):
|
|
143
|
-
"""Lazily build (and cache) the text2tashkeel Diacritizer used for Arabic.
|
|
144
|
-
|
|
145
|
-
text2tashkeel is a dependency of the ``[ar]`` extra; it restores hamza and the
|
|
146
|
-
dagger alef in addition to the standard marks. Install with
|
|
147
|
-
``pip install scriptconv[tashkeel]`` (or ``pip install text2tashkeel``)."""
|
|
148
|
-
model = model or self.diacritizer_model
|
|
149
|
-
if model not in self._tashkeel:
|
|
150
|
-
try:
|
|
151
|
-
from text2tashkeel import Diacritizer
|
|
152
|
-
except ImportError as e:
|
|
153
|
-
raise ImportError(
|
|
154
|
-
"Arabic diacritization requires the text2tashkeel package: "
|
|
155
|
-
"pip install scriptconv[tashkeel] (or pip install text2tashkeel)"
|
|
156
|
-
) from e
|
|
157
|
-
self._tashkeel[model] = Diacritizer(model)
|
|
158
|
-
return self._tashkeel[model]
|
|
159
|
-
|
|
160
|
-
def _stress(self, text: str, lang: str, model: Optional[str] = None) -> str:
|
|
161
|
-
"""Word-stress restoration via stressonnx, for the 26 language tags
|
|
162
|
-
it covers (see ``STRESS_LANGS``) — East Slavic, Bulgarian/Macedonian/
|
|
163
|
-
Slovene, Latvian, Armenian, Georgian, and Turkic/Caucasian languages.
|
|
164
|
-
|
|
165
|
-
stressonnx is not on PyPI yet; install straight from source. Install
|
|
166
|
-
with ``pip install scriptconv[stress]`` (or ``pip install
|
|
167
|
-
stressonnx``)."""
|
|
168
|
-
try:
|
|
169
|
-
from stressonnx import stress
|
|
170
|
-
except ImportError as e:
|
|
171
|
-
raise ImportError(
|
|
172
|
-
"stress restoration requires the stressonnx package: "
|
|
173
|
-
"pip install scriptconv[stress] (or pip install stressonnx)"
|
|
174
|
-
) from e
|
|
175
|
-
return stress(text, lang, model=model)
|
|
176
|
-
|
|
177
|
-
def _sense_diacritics_pt(self, text: str) -> str:
|
|
178
|
-
"""European-Portuguese heterophonic-homograph sense diacritics via bifonia.
|
|
179
|
-
|
|
180
|
-
Rewrites homographs whose pronunciation depends on meaning (e.g.
|
|
181
|
-
"sede" thirst/closed vs. seat/open) with an explicit open/closed
|
|
182
|
-
vowel diacritic. These are ordinary Portuguese orthographic marks,
|
|
183
|
-
chosen so any downstream G2P — rule-based, neural, or espeak —
|
|
184
|
-
reads them correctly. Install with ``pip install scriptconv[pt]``
|
|
185
|
-
(or ``pip install bifonia``)."""
|
|
186
|
-
try:
|
|
187
|
-
from bifonia import add_extra_diacritics
|
|
188
|
-
except ImportError as e:
|
|
189
|
-
raise ImportError(
|
|
190
|
-
"European-Portuguese sense diacritics require the bifonia package: "
|
|
191
|
-
"pip install scriptconv[pt] (or pip install bifonia)"
|
|
192
|
-
) from e
|
|
193
|
-
return add_extra_diacritics(text)
|
|
194
56
|
|
|
195
57
|
@abc.abstractmethod
|
|
196
58
|
def phonemize_string(self, text: str, lang: str) -> str:
|
|
@@ -199,41 +61,6 @@ class BasePhonemizer(metaclass=abc.ABCMeta):
|
|
|
199
61
|
def phonemize_to_list(self, text: str, lang: str) -> List[str]:
|
|
200
62
|
return list(self.phonemize_string(text, lang))
|
|
201
63
|
|
|
202
|
-
def add_diacritics(self, text: str, lang: str,
|
|
203
|
-
model: Optional[str] = None) -> str:
|
|
204
|
-
"""Disambiguate pronunciation before G2P by adding diacritics.
|
|
205
|
-
|
|
206
|
-
Four backends, each restoring information ordinary orthography
|
|
207
|
-
omits but downstream G2P needs:
|
|
208
|
-
|
|
209
|
-
- Hebrew (``he``) — niqqud via phonikud (``phonikud_model=``).
|
|
210
|
-
- Arabic (``ar``) — tashkeel via text2tashkeel (``[tashkeel]``).
|
|
211
|
-
- East Slavic, Bulgarian/Macedonian/Slovene, Latvian, Armenian,
|
|
212
|
-
Georgian, and Turkic/Caucasian languages (``STRESS_LANGS``, 26
|
|
213
|
-
stressonnx tags) — word stress via stressonnx (``[stress]``);
|
|
214
|
-
stress is unwritten or under-marked in these languages, and in
|
|
215
|
-
East Slavic unstressed vowels also reduce, so a missing mark can
|
|
216
|
-
corrupt more than prosody.
|
|
217
|
-
- European Portuguese (``pt``/``pt-PT``, never ``pt-BR``) —
|
|
218
|
-
heterophonic-homograph sense diacritics via bifonia (``[pt]``);
|
|
219
|
-
ordinary Portuguese orthographic marks that any downstream G2P
|
|
220
|
-
reads correctly.
|
|
221
|
-
|
|
222
|
-
Unrecognized languages are returned unchanged. Each backend raises
|
|
223
|
-
``ImportError`` naming its extra when the optional dependency is
|
|
224
|
-
missing — scriptconv never installs anything on the caller's behalf.
|
|
225
|
-
"""
|
|
226
|
-
family = _diacritizer_family(lang)
|
|
227
|
-
if family == "he":
|
|
228
|
-
return self.phonikud.add_diacritics(text)
|
|
229
|
-
if family == "ar":
|
|
230
|
-
return self.tashkeel(model).diacritize(text)
|
|
231
|
-
if family == "stress":
|
|
232
|
-
return self._stress(text, lang, model)
|
|
233
|
-
if family == "pt":
|
|
234
|
-
return self._sense_diacritics_pt(text)
|
|
235
|
-
return text
|
|
236
|
-
|
|
237
64
|
def phonemize(self, text: str, lang: str) -> PhonemizedChunks:
|
|
238
65
|
# PhonemizedChunks is list[list[str]]; empty text yields no
|
|
239
66
|
# sentences. (Returning the raw (str, str, bool) tuple form here
|
|
@@ -102,10 +102,9 @@ def get_phonemizer(phonemizer: Phonemizer,
|
|
|
102
102
|
"""
|
|
103
103
|
phonemizer = Phonemizer(phonemizer)
|
|
104
104
|
cls = get_phonemizer_class(phonemizer)
|
|
105
|
-
# normalizer
|
|
106
|
-
#
|
|
105
|
+
# normalizer is a plain BasePhonemizer attribute; set it after
|
|
106
|
+
# construction so wrapper __init__ signatures stay untouched
|
|
107
107
|
normalizer = kwargs.pop("normalizer", None)
|
|
108
|
-
phonikud_model = kwargs.pop("phonikud_model", None)
|
|
109
108
|
if phonemizer in (_P.BYT5, _P.CHARSIU, _P.DEEPPHONEMIZER):
|
|
110
109
|
inst = cls(model, **kwargs)
|
|
111
110
|
else:
|
|
@@ -124,8 +123,6 @@ def get_phonemizer(phonemizer: Phonemizer,
|
|
|
124
123
|
inst = cls(**kwargs)
|
|
125
124
|
if normalizer is not None:
|
|
126
125
|
inst.normalizer = normalizer
|
|
127
|
-
if phonikud_model is not None:
|
|
128
|
-
inst.phonikud_model = phonikud_model
|
|
129
126
|
return inst
|
|
130
127
|
|
|
131
128
|
|