Skip to content

Commit 966a23d

Browse files
JarbasAlclaude
andcommitted
feat: Arabic diacritization via text2tashkeel (rawi); drop vendored libtashkeel
Replace the vendored libtashkeel with text2tashkeel for all Arabic tashkeel. The default model is rawi-ensemble, which restores hamza and the dagger alef in addition to the standard marks — so it fixes inconsistently-spelled input that libtashkeel's 15-class scheme left as-is (e.g. bare 'ا' -> 'أ'). - remove phoonnx/thirdparty/tashkeel/ (vendored libtashkeel + its 4.8 MB onnx). - BasePhonemizer: a lazy text2tashkeel diacritizer; add_diacritics(text, lang, model=None) routes Arabic to it. - config: a generic 'diacritizer_model' key (default rawi-ensemble) on VoiceConfig and SynthesisConfig, round-tripped through config and threaded through voice.py — named generically so future languages that need a diacritizer model reuse it. - the [ar] extra requires text2tashkeel; a clear ImportError is raised if missing. Config and Arabic phonemizer tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b2faeef commit 966a23d

11 files changed

Lines changed: 43 additions & 340 deletions

File tree

phoonnx/config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ class VoiceConfig:
125125
noise_scale: float = DEFAULT_NOISE_SCALE
126126
noise_w_scale: float = DEFAULT_NOISE_W_SCALE
127127
add_diacritics: bool = None # arabic and hebrew
128+
# diacritizer model name (for languages that need one — e.g. Arabic uses text2tashkeel models like "rawi-ensemble")
129+
diacritizer_model: str = "rawi-ensemble"
128130

129131
# tokenization settings
130132
tokenizer: Optional[TTSTokenizer] = None
@@ -256,6 +258,7 @@ def from_dict(config: dict[str, Any], # phoonnx/piper/coqui/mimic3
256258
phoneme_type = phoneme_type or config.get("phoneme_type")
257259
alphabet = alphabet or config.get("alphabet")
258260
diacritics = False
261+
ar_diacritizer_model = "rawi-ensemble"
259262

260263
if VoiceConfig.is_phoonnx(config):
261264
engine = engine or config.get("engine") or Engine.PHOONNX
@@ -264,6 +267,7 @@ def from_dict(config: dict[str, Any], # phoonnx/piper/coqui/mimic3
264267
phoneme_type = phoneme_type or config.get("phoneme_type", PhonemeType.ESPEAK)
265268
alphabet = alphabet or Alphabet(config.get("alphabet", "ipa"))
266269
diacritics = config.get("inference", {}).get("add_diacritics", True)
270+
ar_diacritizer_model = config.get("inference", {}).get("diacritizer_model", "rawi-ensemble")
267271

268272
# Preserve the model's own special tokens when present (a native
269273
# config may use any pad/blank/bos/eos); fall back to phoonnx defaults.
@@ -394,6 +398,7 @@ def from_dict(config: dict[str, Any], # phoonnx/piper/coqui/mimic3
394398
length_scale=inference.get("length_scale", DEFAULT_LENGTH_SCALE),
395399
noise_w_scale=inference.get("noise_w", DEFAULT_NOISE_W_SCALE),
396400
add_diacritics=diacritics,
401+
diacritizer_model=ar_diacritizer_model,
397402
lang_code=lang_code,
398403
alphabet=Alphabet(alphabet) if isinstance(alphabet, str) else alphabet,
399404
engine=Engine(engine) if isinstance(engine, str) else engine,
@@ -443,6 +448,7 @@ def to_native_dict(self) -> Dict[str, Any]:
443448
"lang_id_map": dict(self.lang_id_map or {}),
444449
"phonemizer_model": self.phonemizer_model,
445450
"add_diacritics": self.add_diacritics,
451+
"diacritizer_model": self.diacritizer_model,
446452
"inference": {
447453
"noise_scale": self.noise_scale,
448454
"length_scale": self.length_scale,
@@ -490,6 +496,9 @@ class SynthesisConfig:
490496
"""for arabic and hebrew models"""
491497
add_diacritics: bool = True
492498

499+
# diacritizer model name (for languages that need one — e.g. Arabic uses text2tashkeel models like "rawi-ensemble")
500+
diacritizer_model: str = "rawi-ensemble"
501+
493502
# Engine-specific per-call params (d_factor, p_factor, e_factor, …)
494503
extra_params: Dict[str, Any] = field(default_factory=dict)
495504

phoonnx/phonemizers/base.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from phoonnx.config import Alphabet
1010
from phoonnx.util import normalize, match_lang
1111
from phoonnx.thirdparty.phonikud import PhonikudDiacritizer
12-
from phoonnx.thirdparty.tashkeel import TashkeelDiacritizer
1312

1413
# list of (substring, terminator, end_of_sentence) tuples.
1514
TextChunks = List[Tuple[str, str, bool]]
@@ -21,25 +20,40 @@
2120

2221
class BasePhonemizer(metaclass=abc.ABCMeta):
2322
def __init__(self, alphabet: Alphabet = Alphabet.UNICODE,
24-
taskeen_threshold: Optional[float] = 0.8):
23+
diacritizer_model: str = "rawi-ensemble"):
2524
super().__init__()
2625
self.alphabet = alphabet
2726

28-
self.taskeen_threshold = taskeen_threshold # arabic only
29-
self._tashkeel: Optional[TashkeelDiacritizer] = None
27+
# diacritizer model name, for languages that need one. Arabic uses
28+
# text2tashkeel; the default "rawi-ensemble" restores hamza and the dagger
29+
# alef in addition to the standard marks.
30+
self.diacritizer_model = diacritizer_model
3031
self._phonikud: Optional[PhonikudDiacritizer] = None # hebrew only
32+
self._tashkeel: dict = {} # model name -> text2tashkeel Diacritizer
3133

3234
@property
3335
def phonikud(self) -> PhonikudDiacritizer:
3436
if self._phonikud is None:
3537
self._phonikud = PhonikudDiacritizer()
3638
return self._phonikud
3739

38-
@property
39-
def tashkeel(self) -> TashkeelDiacritizer:
40-
if self._tashkeel is None:
41-
self._tashkeel = TashkeelDiacritizer()
42-
return self._tashkeel
40+
def tashkeel(self, model: Optional[str] = None):
41+
"""Lazily build (and cache) the text2tashkeel Diacritizer used for Arabic.
42+
43+
text2tashkeel is a dependency of the ``[ar]`` extra; it restores hamza and the
44+
dagger alef in addition to the standard marks. Install with
45+
``pip install phoonnx[ar]`` (or ``pip install text2tashkeel``)."""
46+
model = model or self.diacritizer_model
47+
if model not in self._tashkeel:
48+
try:
49+
from text2tashkeel import Diacritizer
50+
except ImportError as e:
51+
raise ImportError(
52+
"Arabic diacritization requires the text2tashkeel package: "
53+
"pip install phoonnx[ar] (or pip install text2tashkeel)"
54+
) from e
55+
self._tashkeel[model] = Diacritizer(model)
56+
return self._tashkeel[model]
4357

4458
@abc.abstractmethod
4559
def phonemize_string(self, text: str, lang: str) -> str:
@@ -48,11 +62,12 @@ def phonemize_string(self, text: str, lang: str) -> str:
4862
def phonemize_to_list(self, text: str, lang: str) -> List[str]:
4963
return list(self.phonemize_string(text, lang))
5064

51-
def add_diacritics(self, text: str, lang: str) -> str:
65+
def add_diacritics(self, text: str, lang: str,
66+
model: Optional[str] = None) -> str:
5267
if lang.startswith("he"):
5368
return self.phonikud.diacritize(text)
5469
elif lang.startswith("ar"):
55-
return self.tashkeel.diacritize(text, self.taskeen_threshold)
70+
return self.tashkeel(model).diacritize(text)
5671
return text
5772

5873
def phonemize(self, text: str, lang: str) -> PhonemizedChunks:

phoonnx/thirdparty/tashkeel/LICENSE

Lines changed: 0 additions & 22 deletions
This file was deleted.

phoonnx/thirdparty/tashkeel/SOURCE

Lines changed: 0 additions & 1 deletion
This file was deleted.

phoonnx/thirdparty/tashkeel/__init__.py

Lines changed: 0 additions & 212 deletions
This file was deleted.

phoonnx/thirdparty/tashkeel/hint_id_map.json

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)