scriptconv-copyleft 0.1.0a1__py3-none-any.whl

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.
@@ -0,0 +1,112 @@
1
+ from typing import Optional
2
+
3
+ from scriptconv.phonemizers.base import BasePhonemizer
4
+ from scriptconv.phonemizers.enums import Alphabet
5
+
6
+
7
+ # AhoTTS engine variant, selected per-voice via ``phonemizer_model`` in the
8
+ # voice config. Each maps to an ``ahotts_g2p.phonemize`` call.
9
+ ENGINES = {
10
+ "classic": {"version": "classic"}, # original AhoTTS engine (HiTZ VITS voices)
11
+ "modern": {"version": "modern"}, # StyleTTS-era build (HiTZ/StyleTTS2-eu)
12
+ "northern": {"dialect": "northern"}, # Northern (Iparralde / Iparrahotsa) dialect
13
+ }
14
+ DEFAULT_ENGINE = "modern"
15
+
16
+
17
+ class AhoTTSPhonemizer(BasePhonemizer):
18
+ """
19
+ AhoTTS phonemizer backed by ``ahotts-g2p``, a pure-Python port of the
20
+ AhoTTS engine (no C build, no runtime dependencies).
21
+
22
+ Covers the two AhoTTS-frontend languages: Basque (``eu``) and Spanish
23
+ (``es``). The target language is taken from the per-call ``lang`` argument
24
+ (so the same phonemizer instance serves both); ``ahotts_g2p.phonemize``
25
+ dispatches on it.
26
+
27
+ The engine variant is chosen per-voice with ``phonemizer_model`` in the
28
+ voice config (passed to the constructor as ``engine``):
29
+
30
+ * ``classic`` -- the original AhoTTS engine; the HiTZ VITS voices (eu+es).
31
+ * ``modern`` -- the StyleTTS-era build; HiTZ/StyleTTS2-eu. The default.
32
+ * ``northern`` -- the Northern (Iparralde / Iparrahotsa) Basque dialect:
33
+ pronounced /h/, French vowels (ü -> /y/), uvular /ʁ/, a remapped
34
+ sibilant system. Basque only.
35
+
36
+ ``ahotts_g2p.phonemize`` already returns the collapsed single-char training
37
+ string, so no further token folding is needed. It is imported lazily so
38
+ importing ``scriptconv`` does not hard-require it.
39
+ """
40
+
41
+ engine_distribution = "ahotts_g2p"
42
+
43
+ SUPPORTED_LANGS = ["eu-ES", "es-ES"]
44
+
45
+ def __init__(self, engine: Optional[str] = None,
46
+ alphabet: Alphabet = Alphabet.IPA):
47
+ """
48
+ Args:
49
+ engine (Optional[str]): AhoTTS variant -- ``"classic"``, ``"modern"``
50
+ or ``"northern"`` (from the voice's ``phonemizer_model``).
51
+ Defaults to ``"modern"``.
52
+ alphabet (Alphabet): Accepted for signature parity; AhoTTS always
53
+ produces IPA-derived single-char tokens.
54
+ """
55
+ engine = (engine or DEFAULT_ENGINE).lower()
56
+ if engine not in ENGINES:
57
+ raise ValueError(
58
+ f"unknown AhoTTS engine {engine!r} "
59
+ f"(supported: {', '.join(ENGINES)})"
60
+ )
61
+ self.engine = engine
62
+ self._phonemize = None
63
+ super().__init__(alphabet)
64
+
65
+ @property
66
+ def phonemize_fn(self):
67
+ """Lazily import and cache ``ahotts_g2p.phonemize``."""
68
+ if self._phonemize is None:
69
+ try:
70
+ from ahotts_g2p import phonemize
71
+ except ImportError as e:
72
+ raise ImportError(
73
+ "ahotts-g2p is required for the AhoTTS Basque phonemizer. "
74
+ "Install it with 'pip install ahotts-g2p' "
75
+ "(or 'pip install scriptconv[eu]')."
76
+ ) from e
77
+ self._phonemize = phonemize
78
+ return self._phonemize
79
+
80
+ @classmethod
81
+ def get_lang(cls, target_lang: str) -> str:
82
+ """
83
+ Validate and return the closest supported language code (eu or es).
84
+
85
+ Raises:
86
+ ValueError: If the language code is unsupported.
87
+ """
88
+ return cls.match_lang(target_lang, cls.SUPPORTED_LANGS)
89
+
90
+ def phonemize_string(self, text: str, lang: str) -> str:
91
+ """
92
+ Convert Basque or Spanish text into the AhoTTS single-char IPA training
93
+ string.
94
+
95
+ Args:
96
+ text (str): The input text to phonemize.
97
+ lang (str): The language code (``eu`` or ``es``). The ``northern``
98
+ dialect engine is Basque-only; combining it with ``es`` raises.
99
+
100
+ Returns:
101
+ str: Space-separated single-char phoneme tokens.
102
+ """
103
+ g2p_lang = self.get_lang(lang).split("-")[0]
104
+ kwargs = dict(ENGINES[self.engine])
105
+ if g2p_lang != "eu" and "dialect" in kwargs:
106
+ raise ValueError(
107
+ f"the {self.engine!r} AhoTTS engine (dialect) is Basque-only; "
108
+ f"it cannot phonemize lang={lang!r}"
109
+ )
110
+ return self.phonemize_fn(text, lang=g2p_lang, **kwargs)
111
+
112
+
@@ -0,0 +1,44 @@
1
+ from scriptconv.phonemizers.base import BasePhonemizer
2
+ from scriptconv.phonemizers.enums import Alphabet
3
+
4
+
5
+ class PersianPhonemizer(BasePhonemizer):
6
+ """https://github.com/de-mh/persian_phonemizer"""
7
+
8
+ engine_distribution = "persian_phonemizer"
9
+ def __init__(self, alphabet=Alphabet.IPA):
10
+ try:
11
+ from persian_phonemizer import Phonemizer
12
+ except ImportError as e:
13
+ raise ImportError(
14
+ "persian_phonemizer is required for the Persian phonemizer. "
15
+ "Install it with 'pip install persian_phonemizer' "
16
+ "(or 'pip install scriptconv[fa]')."
17
+ ) from e
18
+ assert alphabet in [Alphabet.ERAAB, Alphabet.IPA]
19
+ output_format = "IPA" if alphabet == Alphabet.IPA else 'eraab'
20
+ self.g2p = Phonemizer(output_format)
21
+ super().__init__(alphabet)
22
+
23
+ @classmethod
24
+ def get_lang(cls, target_lang: str) -> str:
25
+ """
26
+ Validates and returns the closest supported language code.
27
+
28
+ Args:
29
+ target_lang (str): The language code to validate.
30
+
31
+ Returns:
32
+ str: The validated language code.
33
+
34
+ Raises:
35
+ ValueError: If the language code is unsupported.
36
+ """
37
+ # this check is here only to throw an exception if invalid language is provided
38
+ return cls.match_lang(target_lang, ["fa"])
39
+
40
+ def phonemize_string(self, text: str, lang: str = "fa") -> str:
41
+ """
42
+ """
43
+ lang = self.get_lang(lang)
44
+ return self.g2p.phonemize(text)
@@ -0,0 +1,81 @@
1
+ from typing import Any, Optional
2
+
3
+ from scriptconv.phonemizers.base import BasePhonemizer
4
+ from scriptconv.phonemizers.enums import Alphabet
5
+
6
+
7
+ class CotoviaPhonemizer(BasePhonemizer):
8
+ """
9
+ Galician phonemizer backed by pycotovia — a pure-Python port of the Cotovia
10
+ G2P engine that has verified parity with the original C binary.
11
+
12
+ Output alphabets:
13
+ - ``Alphabet.COTOVIA`` (default) — raw Cotovia phoneme notation (e.g.
14
+ ``"Este e uN ..."``), the binary-parity-tested native phoneset
15
+ - ``Alphabet.IPA`` — IPA string produced by pycotovia's ``cotovia_to_ipa``
16
+
17
+ Voices trained on Cotovia-alphabet output continue to receive the same
18
+ notation strings as before because pycotovia is binary-parity-tested
19
+ (see pycotovia/docs/parity.md).
20
+
21
+ ``pycotovia`` is an optional dependency (the ``gl`` extra); it is imported
22
+ lazily so that ``import scriptconv.phonemizers`` works without it installed.
23
+ """
24
+
25
+ engine_distribution = "pycotovia"
26
+
27
+ def __init__(self, alphabet: Alphabet = Alphabet.COTOVIA,
28
+ model: Optional[str] = None):
29
+ """
30
+ Args:
31
+ alphabet (Alphabet): ``COTOVIA`` (raw notation, the default) or
32
+ ``IPA``.
33
+ model (Optional[str]): phonemizer variant, from the voice's
34
+ ``phonemizer_model``. ``"stress"`` emits the cotovia notation
35
+ with the stressed vowel marked by a trailing ``^`` (e.g.
36
+ ``"o^la"``) and multi-char phonemes preserved -- matching the
37
+ AhoTTS Cotovia front-end the HiTZ gl VITS voices were trained on,
38
+ whose tokenizer gives stressed vowels their own ids. The default
39
+ (``None``) is the original stressless transcription used by the
40
+ proxectonos gl voices.
41
+ """
42
+ self._engine: Optional[Any] = None
43
+ self.model = (model or "").lower() or None
44
+ super().__init__(alphabet)
45
+
46
+ @property
47
+ def engine(self) -> Any:
48
+ if self._engine is None:
49
+ try:
50
+ from pycotovia import Phonemizer as _PycotoviaPhonemizer
51
+ except ImportError as e:
52
+ raise ImportError(
53
+ "pycotovia is required for the Galician (Cotovia) phonemizer. "
54
+ "Install it with 'pip install pycotovia' or 'pip install scriptconv[gl]'."
55
+ ) from e
56
+ self._engine = _PycotoviaPhonemizer()
57
+ return self._engine
58
+
59
+ @classmethod
60
+ def get_lang(cls, target_lang: str) -> str:
61
+ return cls.match_lang(target_lang, ["gl-ES"])
62
+
63
+ def phonemize_string(self, text: str, lang: str) -> str:
64
+ self.get_lang(lang)
65
+ if self.model == "stress":
66
+ # tra=2 -> stressed vowel followed by '^'. The trailing space the
67
+ # engine appends per word is kept (word separator); the tokenizer
68
+ # folds 'V^' and multi-char phonemes (tS, rr, ...) back into single
69
+ # tokens via the voice's phoneme_id_map compound keys.
70
+ return self.engine.phonemize(text, tra=2)
71
+ cotovia_output = self.engine.phonemize(text)
72
+ if self.alphabet == Alphabet.IPA:
73
+ try:
74
+ from pycotovia import cotovia_to_ipa as _cotovia_to_ipa
75
+ except ImportError as e:
76
+ raise ImportError(
77
+ "pycotovia is required for the Galician (Cotovia) phonemizer. "
78
+ "Install it with 'pip install pycotovia' or 'pip install scriptconv[gl]'."
79
+ ) from e
80
+ return _cotovia_to_ipa(cotovia_output)
81
+ return cotovia_output
@@ -0,0 +1,47 @@
1
+ from scriptconv.phonemizers.base import BasePhonemizer, _check_alphabet
2
+ from scriptconv.phonemizers.enums import Alphabet
3
+
4
+
5
+ class PyKakasiPhonemizer(BasePhonemizer):
6
+ engine_distribution = "pykakasi"
7
+
8
+ def __init__(self, alphabet=Alphabet.HEPBURN):
9
+ _check_alphabet(self, alphabet,
10
+ [Alphabet.HEPBURN, Alphabet.KANA, Alphabet.HIRA])
11
+ # kana, hira, hepburn
12
+ try:
13
+ import pykakasi
14
+ except ImportError as e:
15
+ raise ImportError(
16
+ "pykakasi is required for the PyKakasi phonemizer. "
17
+ "Install it with 'pip install pykakasi' "
18
+ "(or 'pip install scriptconv[ja-phonemizers]')."
19
+ ) from e
20
+ self.g2p = pykakasi.kakasi()
21
+ super().__init__(alphabet)
22
+
23
+ @classmethod
24
+ def get_lang(cls, target_lang: str) -> str:
25
+ """
26
+ Validates and returns the closest supported language code.
27
+
28
+ Args:
29
+ target_lang (str): The language code to validate.
30
+
31
+ Returns:
32
+ str: The validated language code.
33
+
34
+ Raises:
35
+ ValueError: If the language code is unsupported.
36
+ """
37
+ # this check is here only to throw an exception if invalid language is provided
38
+ return cls.match_lang(target_lang, ["ja"])
39
+
40
+ def phonemize_string(self, text: str, lang: str = "ja") -> str:
41
+ """
42
+ """
43
+ lang = self.get_lang(lang)
44
+ return " ".join([
45
+ a[self.alphabet] for a in
46
+ self.g2p.convert(text)
47
+ ])
@@ -0,0 +1,102 @@
1
+
2
+
3
+ import sys
4
+ from importlib import metadata
5
+ from typing import Tuple
6
+
7
+ from scriptconv.phonemizers.base import BasePhonemizer, _check_alphabet
8
+ from scriptconv.phonemizers._thirdparty.hangul2ipa import hangul2ipa
9
+ from scriptconv.phonemizers.enums import Alphabet
10
+
11
+
12
+ class G2PKPhonemizer(BasePhonemizer):
13
+ engine_distribution = "g2pk"
14
+
15
+ def __init__(self, descriptive=True, group_vowels=True, to_syl=True,
16
+ alphabet=Alphabet.IPA):
17
+ _check_alphabet(self, alphabet, [Alphabet.IPA, Alphabet.HANGUL])
18
+ try:
19
+ from g2pk import G2p
20
+ except ImportError as e:
21
+ if sys.version_info >= (3, 13):
22
+ raise ImportError(
23
+ "g2pk is required for the G2PK phonemizer. Its dependency "
24
+ "python-mecab-ko has wheels up to CPython 3.12 only, so on "
25
+ "Python 3.13 and later pip builds it from source. Install "
26
+ "MeCab-ko first, so that mecab-config is on PATH, then run "
27
+ "'pip install g2pk'."
28
+ ) from e
29
+ raise ImportError(
30
+ "g2pk is required for the G2PK phonemizer. "
31
+ "Install it with 'pip install g2pk' "
32
+ "(or 'pip install scriptconv[ko]')."
33
+ ) from e
34
+ self.g2p = G2p()
35
+ self.descriptive = descriptive
36
+ self.group_vowels = group_vowels
37
+ self.to_syl = to_syl
38
+ super().__init__(alphabet)
39
+
40
+ @classmethod
41
+ def get_lang(cls, target_lang: str) -> str:
42
+ """
43
+ Validates and returns the closest supported language code.
44
+
45
+ Args:
46
+ target_lang (str): The language code to validate.
47
+
48
+ Returns:
49
+ str: The validated language code.
50
+
51
+ Raises:
52
+ ValueError: If the language code is unsupported.
53
+ """
54
+ # this check is here only to throw an exception if invalid language is provided
55
+ return cls.match_lang(target_lang, ["ko"])
56
+
57
+ def phonemize_string(self, text: str, lang: str = "ko") -> str:
58
+ """
59
+ """
60
+ lang = self.get_lang(lang)
61
+ p = self.g2p(text, descriptive=self.descriptive,
62
+ group_vowels=self.group_vowels,
63
+ to_syl=self.to_syl)
64
+ if self.alphabet == Alphabet.IPA:
65
+ return hangul2ipa(p)
66
+ return p
67
+
68
+
69
+ class KoG2PPhonemizer(BasePhonemizer):
70
+ """Korean phonemizer wrapping an external ``kog2p`` distribution.
71
+
72
+ Uses an externally-installed ``kog2p`` when present, otherwise the
73
+ quarantined vendored copy under ``_vendored/kog2p`` — GPL-3.0 under its
74
+ own license, NOT this library's Apache-2.0. Never selected
75
+ automatically; :class:`G2PKPhonemizer` is the unencumbered default.
76
+ """
77
+
78
+ def __init__(self, alphabet: Alphabet = Alphabet.IPA):
79
+ super().__init__(alphabet)
80
+ try:
81
+ # an externally-installed kog2p distribution wins
82
+ from kog2p import runKoG2P
83
+ except ImportError:
84
+ # quarantined vendored copy, GPL-3.0 under its own license
85
+ # (see _vendored/kog2p/LICENSE.md); explicit opt-in by
86
+ # requesting this phonemizer
87
+ from scriptconv_copyleft.phonemizers._vendored.kog2p import runKoG2P
88
+ self.g2p = runKoG2P
89
+
90
+ def _engine_release(self) -> Tuple[str, str]:
91
+ """An installed ``kog2p`` runs in place of the vendored copy."""
92
+ if self.g2p.__module__.startswith("scriptconv_copyleft."):
93
+ return super()._engine_release()
94
+ return "kog2p", metadata.version("kog2p")
95
+
96
+ @classmethod
97
+ def get_lang(cls, target_lang: str) -> str:
98
+ return cls.match_lang(target_lang, ["ko"])
99
+
100
+ def phonemize_string(self, text: str, lang: str) -> str:
101
+ # contract: the installed module's runKoG2P(text) -> phoneme string
102
+ return self.g2p(text)