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,1157 @@
1
+ """multilingual phonemizers"""
2
+
3
+ import os
4
+ import re
5
+ import subprocess
6
+ from importlib import metadata
7
+ from typing import List, Optional, Tuple
8
+
9
+ from scriptconv.phonemizers.enums import Alphabet
10
+ from scriptconv.phonemizers.base import BasePhonemizer, _check_alphabet
11
+
12
+
13
+ # Hong Kong and Macau Cantonese. A bare "yue" means mainland Cantonese in
14
+ # Simplified script, so these tags are far from it by language distance. The
15
+ # Cantonese resources of Charsiu, Goruut and Transphone carry no region and are
16
+ # written in Traditional script, so these tags resolve to them directly.
17
+ _HK_CANTONESE = frozenset({
18
+ "yue-hk", "yue-mo", "yue-hant", "yue-hant-hk", "yue-hant-mo",
19
+ "zh-hk", "zh-mo", "zh-hant-hk", "zh-hant-mo",
20
+ })
21
+
22
+
23
+ def _is_hk_cantonese(lang: str) -> bool:
24
+ return lang.lower().replace("_", "-") in _HK_CANTONESE
25
+
26
+
27
+ class EspeakError(Exception):
28
+ """Custom exception for espeak-ng related errors."""
29
+ pass
30
+
31
+
32
+ # espeak-ng 1.52 hands a few French words that carry an apostrophe to its English
33
+ # voice: "d'aujourd'hui" comes back as "(en)dˈɔːdʒədhjˌuːi(fr)". The language-switch
34
+ # tags are stripped further on, which leaves English phones inside a French
35
+ # sentence, so a French voice reads "d'aujourd'hui" as English. Respell each of
36
+ # them as French text espeak-ng reads correctly (checked against espeak-ng 1.52.0):
37
+ # "de aujourd'hui" -> də oʒuʁdyˈi, "grand-mère" -> ɡʁˈɑ̃mˈɛʁ, "prud-homme" -> pʁˈydˈɔm.
38
+ _FRENCH_ENGLISH_SWITCH_FIXES = (
39
+ (re.compile(r"\b([Dd])['\u2019]aujourd['\u2019]hui\b"), r"\1e aujourd'hui"),
40
+ (re.compile(r"\b([Gg])rand['\u2019](m[\u00e8e]res?)\b"), r"\1rand-\2"),
41
+ (re.compile(r"\b([Pp])rud['\u2019](hommes?)\b"), r"\1rud-\2"),
42
+ )
43
+
44
+
45
+ def _respell_for_espeak(text: str, lang: str) -> str:
46
+ """Respell the few words espeak-ng mistakes for English in a French text."""
47
+ if lang.split("-")[0].lower() != "fr":
48
+ return text
49
+ for pattern, replacement in _FRENCH_ENGLISH_SWITCH_FIXES:
50
+ text = pattern.sub(replacement, text)
51
+ return text
52
+
53
+
54
+ # A combining acute (U+0301) after a vowel marks the stressed syllable of a
55
+ # Russian or Ukrainian word. espeak-ng keeps it inside the word, because
56
+ # IsAlpha() in src/libespeak-ng/common.c counts U+0300-U+036F as letters. The
57
+ # word then misses its ru_list entry ("тысяча $1"), no letter rule reads the
58
+ # acute, and the default stress rule places the stress: "ты́сяча" gives
59
+ # tysʲˈɑtʃʲʌ. espeak-ng takes a stress position from text as phoneme input,
60
+ # "[[...]]" with "'" before the stressed syllable (docs/dictionary.md, "Phoneme
61
+ # names").
62
+ _ACUTE = "\u0301"
63
+ _ACUTE_STRESS_LANGS = frozenset({"ru", "uk"})
64
+ _CYRILLIC_VOWELS = frozenset("аеёиоуыэюяіїєАЕЁИОУЫЭЮЯІЇЄ")
65
+ _IPA_VOWELS = frozenset("iyɨʉɯuɪʏʊeøɘɵɤoəɛœɜɞʌɔæɐaɶɑɒ")
66
+ _MARKED_WORD = re.compile(r"[^\W\d_](?:[^\W\d_]|[\u0300-\u036f]|['ʼ’](?=[^\W\d_]))*")
67
+ # a word that a hyphen joins to the next word, as "кто" in "кто-то"
68
+ _HYPHENED_WORD = re.compile(_MARKED_WORD.pattern + r"(?=-[^\W\d_])")
69
+ # espeak-ng prints this between phonemes for --sep=z
70
+ _PHONEME_SEPARATOR = "\u200c"
71
+
72
+
73
+ def _is_stress_marked(word: str) -> bool:
74
+ """True when *word* has an acute, and each acute follows a Cyrillic vowel."""
75
+ marks = [i for i, c in enumerate(word) if c == _ACUTE]
76
+ return bool(marks) and all(i and word[i - 1] in _CYRILLIC_VOWELS for i in marks)
77
+
78
+
79
+ def _marked_syllable(word: str) -> Optional[int]:
80
+ """The 1-based syllable that the acute in a stress-marked *word* marks.
81
+
82
+ ``None`` when the word has more than one acute, or has one syllable, so
83
+ the mark gives no position.
84
+ """
85
+ if word.count(_ACUTE) != 1 or sum(c in _CYRILLIC_VOWELS for c in word) < 2:
86
+ return None
87
+ return sum(c in _CYRILLIC_VOWELS for c in word[:word.index(_ACUTE)])
88
+
89
+
90
+ def _ipa_stress(ipa: str) -> Optional[Tuple[int, int]]:
91
+ """The 1-based syllable with the primary stress, and the syllable count."""
92
+ if ipa.count("ˈ") != 1:
93
+ return None
94
+ head = ipa.split("ˈ")[0]
95
+ return sum(c in _IPA_VOWELS for c in head) + 1, sum(c in _IPA_VOWELS for c in ipa)
96
+
97
+
98
+ # Vowel phonemes of espeak-ng's Russian table, the "vwl" phonemes of
99
+ # phsource/ph_russian without the non-syllabic I^.
100
+ _RU_VOWEL_PHONEMES = frozenset({
101
+ "i", "I#", "y", "I", "I2", "I3", "E", "E#", "E2", "E3", "E%", "o", "O", "u", "u#", 'u"',
102
+ "8", "e", "ju", "ja", "ja#", "V#", "Y", "A", "a", "V", "a#",
103
+ })
104
+ # The phoneme of each Cyrillic vowel letter under stress, as espeak-ng reads it.
105
+ _RU_STRESSED_PHONEMES = {"а": "A", "я": "A", "о": "o", "ё": "8", "е": "e", "э": "E",
106
+ "и": "i", "ы": "y", "у": "u", "ю": 'u"'}
107
+
108
+
109
+ def _ru_stressed_phoneme(letter: str, phoneme: str, previous: str) -> str:
110
+ if letter in "яю" and phoneme.startswith("j"):
111
+ return {"я": "ja", "ю": "ju"}[letter]
112
+ if letter == "ё" and previous in ("Z", "S"):
113
+ return "o"
114
+ if letter == "и" and phoneme == "y":
115
+ return "y"
116
+ return _RU_STRESSED_PHONEMES[letter]
117
+
118
+
119
+ def _ru_unstressed_phoneme(phoneme: str, previous: str, final: bool) -> str:
120
+ # Russian reduces an unstressed vowel. After a palatalised consonant every
121
+ # non-high vowel neutralises to [i] (ikanye). Elsewhere /e/ raises to [i],
122
+ # and /a/ and /o/ reduce to [ɐ] before the stress and to [ə] in other
123
+ # syllables (akanye). Padgett and Tabain (2005), "Adaptive Dispersion Theory
124
+ # and Phonological Vowel Reduction in Russian", Phonetica 62, section 2.1,
125
+ # p. 2 of the authors' preprint. espeak-ng's phsource/ph_russian writes these
126
+ # as phonemes: ChangeIfNotStressed turns o and 8 into V, e into i, and e
127
+ # after Z, S and ts into y (a stressed e there becomes E), and an unstressed
128
+ # a after a palatal phoneme becomes I. espeak-ng runs ChangeIfNotStressed for
129
+ # text but not for phoneme input, so this function writes the reduced
130
+ # phoneme. A final а or я keeps V, as espeak-ng reads it in text ("няня"
131
+ # nʲˈɑnʲʌ). Phoneme input reads V before the stressed vowel as [a], so the
132
+ # reduced vowel is written V#, which espeak-ng reads as [ʌ], as V reads in text.
133
+ if phoneme in ("e", "E") and previous in ("Z", "S", "ts"):
134
+ return "y"
135
+ if phoneme == "A" and not final and (previous.endswith(";") or previous == "j"):
136
+ return "I"
137
+ if phoneme in ("V", "o", "8", "A", "O"):
138
+ return "V#"
139
+ if phoneme == "e":
140
+ return "i"
141
+ return phoneme
142
+
143
+
144
+ def _ru_phoneme_input(phonemes: List[str], word: str) -> Optional[str]:
145
+ """Phoneme input for a Russian *word* with the stress on the marked syllable.
146
+
147
+ *phonemes* is espeak-ng's reading of the bare word, without primary stress marks.
148
+ The marked vowel takes the stressed value of its letter, and every other
149
+ vowel takes its unstressed value. ``None`` when the vowel phonemes do not
150
+ pair up with the vowel letters.
151
+ """
152
+ phonemes = [p.lstrip(",%") for p in phonemes]
153
+ vowels = [i for i, p in enumerate(phonemes) if p in _RU_VOWEL_PHONEMES]
154
+ letters = [c for c in word.lower() if c in _CYRILLIC_VOWELS]
155
+ if len(vowels) != len(letters):
156
+ return None
157
+ marked = _marked_syllable(word) - 1
158
+ respelled = []
159
+ for i, phoneme in enumerate(phonemes):
160
+ previous = phonemes[i - 1] if i else ""
161
+ if i == vowels[marked]:
162
+ respelled.append("'" + _ru_stressed_phoneme(letters[marked], phoneme, previous))
163
+ else:
164
+ respelled.append(_ru_unstressed_phoneme(phoneme, previous, i == len(phonemes) - 1))
165
+ # "|" keeps two adjacent phonemes apart (docs/dictionary.md)
166
+ return "[[" + "|".join(respelled) + "]]"
167
+
168
+
169
+ class EspeakPhonemizer(BasePhonemizer):
170
+ """
171
+ A phonemizer class that uses the espeak-ng command-line tool to convert text into phonemes.
172
+ It segments the input text heuristically based on punctuation to mimic clause-by-clause processing.
173
+ """
174
+
175
+ engine_distribution = "espeak-ng"
176
+ ESPEAK_LANGS = ['es-419', 'ca', 'qya', 'ga', 'et', 'ky', 'io', 'fa-latn', 'en-gb', 'fo', 'haw', 'kl',
177
+ 'ta', 'ml', 'gd', 'sd', 'es', 'hy', 'ur', 'ro', 'hi', 'or', 'ti', 'ca-va', 'om', 'tr', 'pa',
178
+ 'smj', 'mk', 'bg', 'cv', "fr", 'fi', 'en-gb-x-rp', 'ru', 'mt', 'an', 'mr', 'pap', 'vi', 'id',
179
+ 'fr-be', 'ltg', 'my', 'nl', 'shn', 'ba', 'az', 'cmn', 'da', 'as', 'sw',
180
+ 'piqd', 'en-us', 'hr', 'it', 'ug', 'th', 'mi', 'cy', 'ru-lv', 'ia', 'tt', 'hu', 'xex', 'te', 'ne',
181
+ 'eu', 'ja', 'bpy', 'hak', 'cs', 'en-gb-scotland', 'hyw', 'uk', 'pt', 'bn', 'mto', 'yue',
182
+ 'be', 'gu', 'sv', 'sl', 'cmn-latn-pinyin', 'lfn', 'lv', 'fa', 'sjn', 'nog', 'ms',
183
+ 'vi-vn-x-central', 'lt', 'kn', 'he', 'qu', 'ca-ba', 'quc', 'nb', 'sk', 'tn', 'py', 'si', 'de',
184
+ 'ar', 'en-gb-x-gbcwmd', 'bs', 'qdb', 'sq', 'sr', 'tk', 'en-029', 'ht', 'ru-cl', 'af', 'pt-br',
185
+ 'fr-ch', 'ka', 'en-gb-x-gbclan', 'ko', 'is', 'ca-nw', 'gn', 'kok', 'la', 'lb', 'am', 'kk', 'ku',
186
+ 'kaa', 'jbo', 'eo', 'uz', 'nci', 'vi-vn-x-south', 'el', 'pl', 'grc', ]
187
+
188
+ _binary_available: Optional[bool] = None
189
+
190
+ def __init__(self, prefer_espyak: bool = False):
191
+ """
192
+ Args:
193
+ prefer_espyak: Use the pure-Python espyak G2P even when the
194
+ espeak-ng binary is installed. When False, espyak is only
195
+ used as a fallback if the binary is missing.
196
+ """
197
+ super().__init__(Alphabet.IPA)
198
+ self.prefer_espyak = prefer_espyak
199
+ self._espyak_g2p: dict = {}
200
+
201
+ def _espyak_phonemize(self, text: str, lang: str) -> str:
202
+ from espyak import G2P
203
+ if lang not in self._espyak_g2p:
204
+ self._espyak_g2p[lang] = G2P(lang)
205
+ return self._espyak_g2p[lang].phonemize(text)
206
+
207
+ def _engine_release(self) -> Tuple[str, str]:
208
+ """The engine that runs here: the espeak-ng binary, or the espyak port."""
209
+ if self.prefer_espyak or not self._has_binary():
210
+ try:
211
+ return "espyak", metadata.version("espyak")
212
+ except metadata.PackageNotFoundError:
213
+ # phonemize_string falls back to the binary in this case
214
+ if not self._has_binary():
215
+ raise
216
+ # eSpeak NG text-to-speech: 1.52.0 Data at: ...
217
+ banner = self._run_espeak_command(["--version"])
218
+ return self.engine_distribution, banner.split(":")[1].split()[0]
219
+
220
+ @classmethod
221
+ def _has_binary(cls) -> bool:
222
+ if cls._binary_available is None:
223
+ import shutil
224
+ cls._binary_available = shutil.which("espeak-ng") is not None
225
+ return cls._binary_available
226
+
227
+ @classmethod
228
+ def get_lang(cls, target_lang: str) -> str:
229
+ """
230
+ Validates and returns the closest supported language code.
231
+
232
+ Args:
233
+ target_lang (str): The language code to validate.
234
+
235
+ Returns:
236
+ str: The validated language code.
237
+
238
+ Raises:
239
+ ValueError: If the language code is unsupported.
240
+ """
241
+ if target_lang.lower() == "en-gb":
242
+ return "en-gb-x-rp"
243
+ # espeak-ng has no bare "zh"/region-tagged Chinese codes, only "cmn" (Mandarin,
244
+ # any script) and "yue" (Cantonese); map BCP-47 Chinese tags onto those voices
245
+ # directly instead of falling through to lang_distance, which scores them below
246
+ # the match threshold and raises. Hong Kong/Macau are the only regions where
247
+ # Cantonese, not Mandarin, is the spoken vernacular.
248
+ ZH_ALIASES = {
249
+ "zh": "cmn", "zh-cn": "cmn", "zh-sg": "cmn",
250
+ "zh-hans": "cmn", "zh-hans-cn": "cmn", "zh-hans-sg": "cmn",
251
+ "zh-tw": "cmn", "zh-hant": "cmn", "zh-hant-tw": "cmn",
252
+ "zh-hk": "yue", "zh-mo": "yue", "zh-hant-hk": "yue", "zh-hant-mo": "yue",
253
+ }
254
+ # ESPEAK_LANGS is all lowercase, so the tag is lowercased once and
255
+ # matched as a whole BEFORE the primary subtag. A BCP-47 tag such as
256
+ # pt-BR or fr-BE used to miss the exact check on case and fall to the
257
+ # bare pt or fr, which are European Portuguese and metropolitan
258
+ # French: a Brazilian voice phonemized as European.
259
+ tag = target_lang.lower().replace("_", "-")
260
+ if tag in ZH_ALIASES:
261
+ return ZH_ALIASES[tag]
262
+ if tag in cls.ESPEAK_LANGS:
263
+ return tag
264
+ if tag.split("-")[0] in cls.ESPEAK_LANGS:
265
+ return tag.split("-")[0]
266
+ return cls.match_lang(tag, cls.ESPEAK_LANGS)
267
+
268
+ @staticmethod
269
+ def _run_espeak_command(args: List[str], input_text: str = None, check: bool = True) -> str:
270
+ """
271
+ Helper function to run espeak-ng commands via subprocess.
272
+ Executes 'espeak-ng' with the given arguments and input text.
273
+ Captures stdout and stderr, and raises EspeakError on failure.
274
+
275
+ Args:
276
+ args (List[str]): A list of command-line arguments for espeak-ng.
277
+ input_text (str, optional): The text to pass to espeak-ng's stdin. Defaults to None.
278
+ check (bool, optional): If True, raises a CalledProcessError if the command returns a non-zero exit code. Defaults to True.
279
+
280
+ Returns:
281
+ str: The stripped standard output from the espeak-ng command.
282
+
283
+ Raises:
284
+ EspeakError: If espeak-ng command is not found, or if the subprocess call fails.
285
+ """
286
+ command: List[str] = ['espeak-ng'] + args
287
+
288
+ # Standard arguments for subprocess.run
289
+ subprocess_args = {
290
+ 'input': input_text,
291
+ 'capture_output': True,
292
+ 'text': True,
293
+ 'check': check,
294
+ 'encoding': 'utf-8',
295
+ 'errors': 'replace' # Replaces unencodable characters with a placeholder
296
+ }
297
+
298
+ # Add 'creationflags' to hide the terminal window on Windows
299
+ if os.name == 'nt':
300
+ subprocess_args['creationflags'] = 0x08000000
301
+
302
+ try:
303
+ process: subprocess.CompletedProcess = subprocess.run(
304
+ command,
305
+ **subprocess_args # Use the dynamic arguments
306
+ )
307
+ return process.stdout.strip()
308
+ except FileNotFoundError:
309
+ raise EspeakError(
310
+ "espeak-ng command not found. Please ensure espeak-ng is installed "
311
+ "and available in your system's PATH."
312
+ )
313
+ except subprocess.CalledProcessError as e:
314
+ raise EspeakError(
315
+ f"espeak-ng command failed with error code {e.returncode}:\n"
316
+ f"STDOUT: {e.stdout}\n"
317
+ f"STDERR: {e.stderr}"
318
+ )
319
+ except Exception as e:
320
+ raise EspeakError(f"An unexpected error occurred while running espeak-ng: {e}")
321
+
322
+ def _clause_lines(self, args: List[str], lines: List[str], lang: str) -> Optional[List[str]]:
323
+ """espeak-ng's output for each line, read as a clause of its own.
324
+
325
+ ``None`` when the output lines do not pair up with the input lines.
326
+ """
327
+ if not lines:
328
+ return []
329
+ # a comma ends a clause, and espeak-ng prints each clause on its own line
330
+ output = self._run_espeak_command(['-q', '-x', *args, '-v', lang], input_text=",\n".join(lines))
331
+ output = [line.strip() for line in output.split("\n")]
332
+ return output if len(output) == len(lines) else None
333
+
334
+ def _read_acute_stress(self, text: str, lang: str) -> str:
335
+ """Respell each stress-marked polysyllable so that espeak-ng stresses the marked syllable.
336
+
337
+ A word that espeak-ng already stresses on the marked syllable loses the
338
+ acute, so espeak-ng reads it from its dictionary. Any other word becomes
339
+ espeak-ng's own phonemes for it as phoneme input, with the primary
340
+ stress on the marked syllable. For Russian, the vowels also take their
341
+ stressed and unstressed values for that stress. A word that no phoneme
342
+ input matches loses the acute only. A stress-marked monosyllable keeps
343
+ its acute, which espeak-ng reads as a stressed word. When a hyphen joins
344
+ the monosyllable to the next word, it loses the acute, and espeak-ng
345
+ reads the compound from its dictionary entry ("кто то" kt'otV).
346
+ """
347
+ text = _HYPHENED_WORD.sub(
348
+ lambda m: m.group().replace(_ACUTE, "")
349
+ if _is_stress_marked(m.group()) and sum(c in _CYRILLIC_VOWELS for c in m.group()) == 1
350
+ else m.group(), text)
351
+ respelled = {w: w.replace(_ACUTE, "") for w in _MARKED_WORD.findall(text)
352
+ if _is_stress_marked(w) and sum(c in _CYRILLIC_VOWELS for c in w) > 1}
353
+ targets = {w: (_marked_syllable(w), sum(c in _CYRILLIC_VOWELS for c in w)) for w in respelled}
354
+ words = sorted(w for w, (syllable, _) in targets.items() if syllable)
355
+ readings = self._clause_lines(['--ipa'], [respelled[w] for w in words], lang) or []
356
+ moved = [w for w, ipa in zip(words, readings) if _ipa_stress(ipa) != targets[w]]
357
+ phonemes = self._clause_lines(['--sep=z'], [respelled[w] for w in moved], lang) or []
358
+ candidates = []
359
+ for word, reading in zip(moved, phonemes):
360
+ if " " in reading:
361
+ continue
362
+ tokens = [p.lstrip("'") for p in reading.split(_PHONEME_SEPARATOR)]
363
+ russian = _ru_phoneme_input(tokens, word) if lang.startswith("ru") else None
364
+ if russian:
365
+ candidates.append((word, russian))
366
+ continue
367
+ for stressed in range(len(tokens)):
368
+ marked = tokens[:stressed] + ["'" + tokens[stressed].lstrip(",%")] + tokens[stressed + 1:]
369
+ candidates.append((word, "[[" + "|".join(marked) + "]]"))
370
+ ipa = self._clause_lines(['--ipa'], [option for _, option in candidates], lang) or []
371
+ chosen = {}
372
+ for (word, option), line in zip(candidates, ipa):
373
+ if _ipa_stress(line) == targets[word]:
374
+ # espeak-ng drops phoneme input that touches a quote, bracket or symbol
375
+ chosen.setdefault(word, f" {option} ")
376
+ respelled.update(chosen)
377
+ return _MARKED_WORD.sub(lambda m: respelled.get(m.group(), m.group()), text)
378
+
379
+ def phonemize_string(self, text: str, lang: str) -> str:
380
+ lang = self.get_lang(lang)
381
+ text = _respell_for_espeak(text, lang)
382
+ if self.prefer_espyak or not self._has_binary():
383
+ try:
384
+ return self._espyak_phonemize(text, lang)
385
+ except ImportError:
386
+ if not self._has_binary():
387
+ raise EspeakError(
388
+ "espeak-ng binary not found and espyak is not installed. "
389
+ "Install espeak-ng or `pip install espyak` for the "
390
+ "pure-Python fallback."
391
+ )
392
+ if _ACUTE in text and lang.split("-")[0] in _ACUTE_STRESS_LANGS:
393
+ text = self._read_acute_stress(text, lang)
394
+ return self._run_espeak_command(
395
+ ['-q', '-x', '--ipa', '-v', lang],
396
+ input_text=text
397
+ )
398
+
399
+
400
+ class MisakiPhonemizer(BasePhonemizer):
401
+ """
402
+ https://github.com/hexgrad/misaki
403
+ """
404
+
405
+ engine_distribution = "misaki"
406
+ MISAKI_LANGS = ['en-US', 'en-GB', 'ko', 'ja', 'vi', 'zh']
407
+
408
+ def __init__(self, alphabet: Alphabet = Alphabet.IPA):
409
+ # misaki emits IPA for every language except zh, where the alphabet selects
410
+ # the representation (IPA vs bopomofo) — see zh_version below. IPA is the
411
+ # sensible default for all of them.
412
+ super().__init__(alphabet)
413
+ self.g2p_en = self.g2p_zh = self.g2p_ko = self.g2p_vi = self.g2p_ja = None
414
+ self.g2p_en_gb = None
415
+
416
+ @property
417
+ def zh_version(self) -> str:
418
+ # misaki's zh G2P emits IPA + tone marks ("1.0") or bopomofo + tone numbers
419
+ # ("1.1"); the requested alphabet picks which, no separate class/param needed.
420
+ return "1.1" if self.alphabet == Alphabet.BOPOMOFO else "1.0"
421
+
422
+ @classmethod
423
+ def get_lang(cls, target_lang: str) -> str:
424
+ """
425
+ Validates and returns the closest supported language code.
426
+
427
+ Args:
428
+ target_lang (str): The language code to validate.
429
+
430
+ Returns:
431
+ str: The validated language code.
432
+
433
+ Raises:
434
+ ValueError: If the language code is unsupported.
435
+ """
436
+ return cls.match_lang(target_lang, cls.MISAKI_LANGS)
437
+
438
+ def _get_phonemizer(self, lang: str):
439
+ """lazy load language specific phonemizer on first usage
440
+ NOTE: this can be slow
441
+ """
442
+ lang = self.get_lang(lang)
443
+
444
+ try:
445
+ if lang == "zh":
446
+ if self.g2p_zh is None:
447
+ from misaki.zh import ZHG2P
448
+ self.g2p_zh = ZHG2P(version=self.zh_version)
449
+ return self.g2p_zh
450
+ elif lang == "ko":
451
+ if self.g2p_ko is None:
452
+ from misaki.ko import KOG2P
453
+ self.g2p_ko = KOG2P()
454
+ return self.g2p_ko
455
+ elif lang == "vi":
456
+ if self.g2p_vi is None:
457
+ from misaki.vi import VIG2P
458
+ self.g2p_vi = VIG2P()
459
+ return self.g2p_vi
460
+ elif lang == "ja":
461
+ if self.g2p_ja is None:
462
+ from misaki.ja import JAG2P
463
+ self.g2p_ja = JAG2P()
464
+ return self.g2p_ja
465
+ elif lang == "en-GB":
466
+ # misaki loads the British or American lexicon when the G2P is
467
+ # built, so the variety is a constructor argument.
468
+ if self.g2p_en_gb is None:
469
+ from misaki import en
470
+ self.g2p_en_gb = en.G2P(british=True)
471
+ return self.g2p_en_gb
472
+ else:
473
+ if self.g2p_en is None:
474
+ from misaki import en
475
+ self.g2p_en = en.G2P(british=False)
476
+ return self.g2p_en
477
+ except ImportError as e:
478
+ raise ImportError(
479
+ "misaki is required for the Misaki phonemizer. "
480
+ "Install it with 'pip install misaki' "
481
+ "(or 'pip install scriptconv[misaki]')."
482
+ ) from e
483
+
484
+ def phonemize_string(self, text: str, lang: str) -> str:
485
+ pho = self._get_phonemizer(lang)
486
+ phonemes, tokens = pho(text)
487
+ return phonemes
488
+
489
+
490
+ # Per-language misaki phonemizers. misaki is NOT a thin wrapper: en ships ~6MB of
491
+ # curated us/gb lexicons + spacy disambiguation, ja a cutlet romanizer + lexicon,
492
+ # zh adds tone sandhi + an IPA/bopomofo frontend on top of pypinyin. Each language
493
+ # has different heavy deps and params, so they are split into dedicated classes
494
+ # (sharing the dispatch base). The base class above remains a back-compat
495
+ # dispatcher for the legacy ``misaki`` phoneme type.
496
+ class MisakiEnPhonemizer(MisakiPhonemizer):
497
+ """misaki English (curated us/gb lexicons + spacy; espeak only as OOV fallback)."""
498
+ MISAKI_LANGS = ['en-US', 'en-GB']
499
+
500
+
501
+ class MisakiJaPhonemizer(MisakiPhonemizer):
502
+ """misaki Japanese (cutlet romanizer + ja_words lexicon; needs fugashi/unidic)."""
503
+ MISAKI_LANGS = ['ja']
504
+
505
+
506
+ class MisakiZhPhonemizer(MisakiPhonemizer):
507
+ """misaki Chinese (pypinyin + tone sandhi + frontend).
508
+
509
+ The output representation follows the requested ``alphabet``: ``Alphabet.IPA``
510
+ (default) emits IPA + tone marks (ni↓xau↓), ``Alphabet.BOPOMOFO`` emits bopomofo
511
+ + tone numbers (ㄋㄧ2ㄏㄠ3). It must match the target model's training vocab.
512
+ """
513
+ MISAKI_LANGS = ['zh']
514
+
515
+
516
+ class MisakiKoPhonemizer(MisakiPhonemizer):
517
+ """misaki Korean (KOG2P).
518
+
519
+ KOG2P writes the pronunciation in Hangul conjoining jamo (NFD), with
520
+ sound changes applied: 한국말 gives 한궁말. So the output alphabet is
521
+ Hangul, not IPA.
522
+ """
523
+ MISAKI_LANGS = ['ko']
524
+
525
+ def __init__(self, alphabet: Alphabet = Alphabet.HANGUL):
526
+ _check_alphabet(self, alphabet, [Alphabet.HANGUL])
527
+ super().__init__(alphabet)
528
+
529
+
530
+ class MisakiViPhonemizer(MisakiPhonemizer):
531
+ """misaki Vietnamese (VIG2P + acronym/teencode dicts)."""
532
+ MISAKI_LANGS = ['vi']
533
+
534
+
535
+ class TransphonePhonemizer(BasePhonemizer):
536
+ """
537
+ It provides approximiated phoneme tokenizers and G2P model for 7546 languages registered in the Glottolog database.
538
+ """
539
+
540
+ engine_distribution = "transphone"
541
+ TRANSPHONE_LANGS = ['aaa', 'aab', 'aac', 'aad', 'aae', 'aaf', 'aag', 'aah', 'aai', 'aak', 'aal', 'aan', 'aao',
542
+ 'aap', 'aaq', 'aar', 'aas', 'aat', 'aau', 'aaw', 'aax', 'aaz', 'aba', 'abb', 'abc', 'abd',
543
+ 'abe', 'abf', 'abg', 'abh', 'abi', 'abj', 'abk', 'abl', 'abm', 'abn', 'abo', 'abp', 'abq',
544
+ 'abr', 'abs', 'abt', 'abu', 'abv', 'abw', 'abx', 'aby', 'abz', 'aca', 'acd', 'ace', 'acf',
545
+ 'ach', 'aci', 'ack', 'acl', 'acm', 'acn', 'acp', 'acq', 'acr', 'acs', 'act', 'acu', 'acv',
546
+ 'acw', 'acx', 'acy', 'acz', 'ada', 'add', 'ade', 'adf', 'adg', 'adh', 'adi', 'adj', 'adl',
547
+ 'adn', 'ado', 'adq', 'adr', 'ads', 'adt', 'adw', 'adx', 'ady', 'adz', 'aea', 'aeb', 'aec',
548
+ 'aed', 'aee', 'aek', 'ael', 'aem', 'aen', 'aeq', 'aer', 'aes', 'aeu', 'aew', 'aey', 'aez',
549
+ 'afb', 'afd', 'afe', 'afg', 'afi', 'afk', 'afn', 'afo', 'afp', 'afr', 'afs', 'aft', 'afu',
550
+ 'afz', 'agb', 'agc', 'agd', 'age', 'agf', 'agg', 'agh', 'agj', 'agk', 'agl', 'agm', 'agn',
551
+ 'ago', 'agq', 'agr', 'ags', 'agt', 'agu', 'agv', 'agw', 'agx', 'agy', 'agz', 'aha', 'ahb',
552
+ 'ahg', 'ahh', 'ahi', 'ahk', 'ahl', 'ahm', 'ahn', 'aho', 'ahp', 'ahs', 'aht', 'aia', 'aib',
553
+ 'aic', 'aid', 'aie', 'aif', 'aig', 'aih', 'aii', 'aij', 'aik', 'ail', 'aim', 'ain', 'aio',
554
+ 'aip', 'aiq', 'air', 'ais', 'ait', 'aiw', 'aix', 'aiy', 'aja', 'ajg', 'aji', 'ajn', 'ajp',
555
+ 'ajt', 'aju', 'ajw', 'ajz', 'aka', 'akb', 'akc', 'akd', 'ake', 'akf', 'akg', 'akh', 'aki',
556
+ 'akj', 'akk', 'akl', 'akm', 'ako', 'akp', 'akq', 'akr', 'aks', 'akt', 'aku', 'akv', 'akw',
557
+ 'akx', 'aky', 'akz', 'ala', 'alc', 'ald', 'ale', 'alf', 'alh', 'ali', 'alj', 'alk', 'all',
558
+ 'alm', 'aln', 'alo', 'alp', 'alq', 'alr', 'als', 'alt', 'alu', 'alw', 'alx', 'aly', 'alz',
559
+ 'ama', 'amb', 'amc', 'ame', 'amf', 'amg', 'amh', 'ami', 'amj', 'amk', 'aml', 'amm', 'amn',
560
+ 'amo', 'amp', 'amq', 'amr', 'ams', 'amt', 'amu', 'amv', 'amw', 'amx', 'amy', 'amz', 'ana',
561
+ 'anb', 'anc', 'and', 'ane', 'anf', 'ang', 'anh', 'ani', 'anj', 'ank', 'anl', 'anm', 'ann',
562
+ 'ano', 'anp', 'anq', 'anr', 'ans', 'ant', 'anu', 'anv', 'anw', 'anx', 'any', 'anz', 'aoa',
563
+ 'aob', 'aoc', 'aod', 'aoe', 'aof', 'aog', 'aoi', 'aoj', 'aok', 'aol', 'aom', 'aon', 'aor',
564
+ 'aos', 'aot', 'aou', 'aox', 'aoz', 'apb', 'apc', 'apd', 'ape', 'apf', 'apg', 'aph', 'api',
565
+ 'apj', 'apk', 'apl', 'apm', 'apn', 'apo', 'app', 'apq', 'apr', 'aps', 'apt', 'apu', 'apw',
566
+ 'apx', 'apy', 'apz', 'aqc', 'aqd', 'aqg', 'aqm', 'aqn', 'aqp', 'aqr', 'aqt', 'aqz', 'arb',
567
+ 'arc', 'ard', 'are', 'arg', 'arh', 'ari', 'arj', 'ark', 'arl', 'arn', 'aro', 'arp', 'arq',
568
+ 'arr', 'ars', 'aru', 'arv', 'arw', 'arx', 'ary', 'arz', 'asa', 'asb', 'asc', 'asd', 'ase',
569
+ 'asf', 'asg', 'ash', 'asi', 'asj', 'ask', 'asl', 'asm', 'asn', 'aso', 'asp', 'asq', 'asr',
570
+ 'ass', 'ast', 'asu', 'asv', 'asw', 'asx', 'asy', 'asz', 'ata', 'atb', 'atc', 'atd', 'ate',
571
+ 'atg', 'ati', 'atj', 'atk', 'atl', 'atm', 'atn', 'ato', 'atp', 'atq', 'atr', 'ats', 'att',
572
+ 'atu', 'atv', 'atw', 'atx', 'aty', 'atz', 'aua', 'aub', 'auc', 'aud', 'aug', 'auh', 'aui',
573
+ 'auj', 'auk', 'aul', 'aum', 'aun', 'auo', 'aup', 'auq', 'aur', 'aut', 'auu', 'auw', 'aux',
574
+ 'auy', 'auz', 'ava', 'avb', 'avd', 'ave', 'avi', 'avl', 'avm', 'avn', 'avs', 'avt', 'avu',
575
+ 'avv', 'awa', 'awb', 'awc', 'awe', 'awg', 'awh', 'awi', 'awk', 'awm', 'awn', 'awo', 'awr',
576
+ 'aws', 'awt', 'awu', 'awv', 'aww', 'awx', 'awy', 'axb', 'axe', 'axg', 'axk', 'axl', 'axx',
577
+ 'aya', 'ayb', 'ayc', 'ayd', 'aye', 'ayg', 'ayh', 'ayi', 'ayk', 'ayl', 'aym', 'ayn', 'ayo',
578
+ 'ayp', 'ayq', 'ayr', 'ayt', 'ayu', 'ayz', 'aza', 'azb', 'azd', 'azg', 'azj', 'azm', 'azn',
579
+ 'azo', 'azt', 'azz', 'baa', 'bab', 'bac', 'bae', 'baf', 'bag', 'bah', 'baj', 'bak', 'bam',
580
+ 'ban', 'bao', 'bap', 'bar', 'bas', 'bau', 'bav', 'baw', 'bax', 'bay', 'bba', 'bbb', 'bbc',
581
+ 'bbd', 'bbe', 'bbf', 'bbg', 'bbh', 'bbi', 'bbj', 'bbk', 'bbl', 'bbm', 'bbn', 'bbo', 'bbp',
582
+ 'bbq', 'bbr', 'bbs', 'bbt', 'bbu', 'bbv', 'bbw', 'bby', 'bca', 'bcc', 'bcd', 'bce', 'bcf',
583
+ 'bcg', 'bch', 'bci', 'bcj', 'bck', 'bcl', 'bcm', 'bcn', 'bco', 'bcp', 'bcq', 'bcr', 'bcs',
584
+ 'bct', 'bcu', 'bcv', 'bcw', 'bcy', 'bcz', 'bda', 'bdb', 'bdc', 'bdd', 'bde', 'bdf', 'bdg',
585
+ 'bdh', 'bdi', 'bdj', 'bdk', 'bdl', 'bdm', 'bdn', 'bdo', 'bdp', 'bdq', 'bdr', 'bds', 'bdt',
586
+ 'bdu', 'bdv', 'bdw', 'bdx', 'bdy', 'bea', 'beb', 'bec', 'bed', 'bee', 'bef', 'beg', 'beh',
587
+ 'bei', 'bej', 'bek', 'bel', 'bem', 'ben', 'beo', 'bep', 'beq', 'bes', 'bet', 'beu', 'bev',
588
+ 'bew', 'bex', 'bey', 'bez', 'bfa', 'bfb', 'bfc', 'bfd', 'bfe', 'bff', 'bfg', 'bfh', 'bfi',
589
+ 'bfj', 'bfk', 'bfl', 'bfm', 'bfn', 'bfo', 'bfp', 'bfq', 'bfs', 'bft', 'bfu', 'bfw', 'bfx',
590
+ 'bfy', 'bfz', 'bga', 'bgb', 'bgc', 'bgd', 'bge', 'bgf', 'bgg', 'bgi', 'bgj', 'bgk', 'bgl',
591
+ 'bgm', 'bgn', 'bgo', 'bgp', 'bgq', 'bgr', 'bgs', 'bgt', 'bgu', 'bgv', 'bgw', 'bgx', 'bgy',
592
+ 'bgz', 'bha', 'bhb', 'bhc', 'bhd', 'bhe', 'bhf', 'bhg', 'bhh', 'bhi', 'bhj', 'bhl', 'bhm',
593
+ 'bhn', 'bho', 'bhp', 'bhq', 'bhr', 'bhs', 'bht', 'bhu', 'bhv', 'bhw', 'bhy', 'bhz', 'bia',
594
+ 'bib', 'bid', 'bie', 'bif', 'big', 'bij', 'bil', 'bim', 'bin', 'bio', 'bip', 'biq', 'bir',
595
+ 'bis', 'bit', 'biu', 'biv', 'biw', 'bix', 'biy', 'biz', 'bja', 'bjb', 'bjc', 'bje', 'bjf',
596
+ 'bjg', 'bjh', 'bji', 'bjj', 'bjk', 'bjl', 'bjm', 'bjn', 'bjo', 'bjp', 'bjr', 'bjs', 'bjt',
597
+ 'bju', 'bjv', 'bjw', 'bjx', 'bjy', 'bjz', 'bka', 'bkc', 'bkd', 'bkf', 'bkh', 'bki', 'bkj',
598
+ 'bkk', 'bkl', 'bkm', 'bkn', 'bko', 'bkp', 'bkq', 'bkr', 'bks', 'bkt', 'bku', 'bkv', 'bkw',
599
+ 'bkx', 'bky', 'bkz', 'bla', 'blb', 'blc', 'bld', 'ble', 'blf', 'blh', 'bli', 'blj', 'blk',
600
+ 'bll', 'blm', 'bln', 'blo', 'blp', 'blq', 'blr', 'bls', 'blt', 'blv', 'blw', 'blx', 'bly',
601
+ 'blz', 'bma', 'bmb', 'bmc', 'bmd', 'bme', 'bmf', 'bmg', 'bmh', 'bmi', 'bmj', 'bmk', 'bml',
602
+ 'bmm', 'bmn', 'bmo', 'bmp', 'bmq', 'bmr', 'bms', 'bmt', 'bmu', 'bmv', 'bmw', 'bmx', 'bmz',
603
+ 'bna', 'bnb', 'bnc', 'bnd', 'bne', 'bnf', 'bng', 'bni', 'bnj', 'bnk', 'bnl', 'bnm', 'bnn',
604
+ 'bno', 'bnp', 'bnq', 'bnr', 'bns', 'bnu', 'bnv', 'bnw', 'bnx', 'bny', 'bnz', 'boa', 'bob',
605
+ 'bod', 'boe', 'bof', 'bog', 'boh', 'boi', 'boj', 'bok', 'bol', 'bom', 'bon', 'boo', 'bop',
606
+ 'boq', 'bor', 'bos', 'bot', 'bou', 'bov', 'bow', 'box', 'boy', 'boz', 'bpa', 'bpd', 'bpg',
607
+ 'bph', 'bpi', 'bpj', 'bpk', 'bpm', 'bpn', 'bpp', 'bpq', 'bpr', 'bps', 'bpt', 'bpu', 'bpv',
608
+ 'bpw', 'bpx', 'bpy', 'bpz', 'bqa', 'bqb', 'bqc', 'bqd', 'bqg', 'bqh', 'bqi', 'bqj', 'bqk',
609
+ 'bql', 'bqm', 'bqn', 'bqo', 'bqp', 'bqq', 'bqr', 'bqs', 'bqt', 'bqu', 'bqv', 'bqw', 'bqx',
610
+ 'bqy', 'bqz', 'bra', 'brb', 'brc', 'brd', 'bre', 'brf', 'brg', 'brh', 'bri', 'brj', 'brk',
611
+ 'brl', 'brm', 'brn', 'bro', 'brp', 'brq', 'brr', 'brs', 'brt', 'bru', 'brv', 'brw', 'brx',
612
+ 'bry', 'brz', 'bsa', 'bsb', 'bsc', 'bse', 'bsf', 'bsg', 'bsh', 'bsi', 'bsj', 'bsk', 'bsl',
613
+ 'bsm', 'bsn', 'bsp', 'bsq', 'bsr', 'bss', 'bst', 'bsu', 'bsw', 'bsx', 'bsy', 'bta', 'btc',
614
+ 'btd', 'bte', 'btf', 'btg', 'bth', 'bti', 'btj', 'btm', 'btn', 'bto', 'btp', 'btq', 'btr',
615
+ 'bts', 'btt', 'btu', 'btv', 'btw', 'btx', 'bty', 'btz', 'bua', 'bub', 'buc', 'bud', 'bue',
616
+ 'buf', 'bug', 'buh', 'bui', 'buj', 'buk', 'bul', 'bum', 'bun', 'buo', 'bup', 'buq', 'bus',
617
+ 'but', 'buu', 'buv', 'buw', 'bux', 'buy', 'buz', 'bva', 'bvb', 'bvc', 'bvd', 'bve', 'bvf',
618
+ 'bvg', 'bvh', 'bvi', 'bvj', 'bvk', 'bvl', 'bvm', 'bvn', 'bvo', 'bvq', 'bvr', 'bvt', 'bvu',
619
+ 'bvv', 'bvw', 'bvx', 'bvy', 'bvz', 'bwa', 'bwb', 'bwc', 'bwd', 'bwe', 'bwf', 'bwg', 'bwh',
620
+ 'bwi', 'bwj', 'bwk', 'bwl', 'bwm', 'bwn', 'bwo', 'bwp', 'bwq', 'bwr', 'bws', 'bwt', 'bwu',
621
+ 'bww', 'bwx', 'bwy', 'bwz', 'bxa', 'bxb', 'bxc', 'bxd', 'bxe', 'bxf', 'bxg', 'bxh', 'bxi',
622
+ 'bxj', 'bxk', 'bxl', 'bxm', 'bxn', 'bxp', 'bxq', 'bxr', 'bxs', 'bxu', 'bxv', 'bxw', 'bxz',
623
+ 'bya', 'byb', 'byc', 'byd', 'bye', 'byf', 'byg', 'byh', 'byi', 'byj', 'byk', 'byl', 'bym',
624
+ 'byn', 'byo', 'byp', 'byq', 'byr', 'bys', 'byt', 'byv', 'byw', 'byx', 'byz', 'bza', 'bzb',
625
+ 'bzc', 'bzd', 'bze', 'bzf', 'bzg', 'bzh', 'bzi', 'bzj', 'bzk', 'bzl', 'bzm', 'bzn', 'bzo',
626
+ 'bzp', 'bzq', 'bzr', 'bzs', 'bzu', 'bzv', 'bzw', 'bzx', 'bzy', 'bzz', 'caa', 'cab', 'cac',
627
+ 'cad', 'cae', 'caf', 'cag', 'cah', 'caj', 'cak', 'cal', 'cam', 'can', 'cao', 'cap', 'caq',
628
+ 'car', 'cas', 'cat', 'cav', 'cax', 'cay', 'caz', 'cbb', 'cbc', 'cbd', 'cbg', 'cbi', 'cbj',
629
+ 'cbk', 'cbl', 'cbn', 'cbo', 'cbq', 'cbr', 'cbs', 'cbt', 'cbu', 'cbv', 'cbw', 'ccc', 'ccd',
630
+ 'cce', 'ccg', 'cch', 'ccj', 'ccl', 'ccm', 'cco', 'ccp', 'ccr', 'cda', 'cde', 'cdf', 'cdh',
631
+ 'cdi', 'cdj', 'cdm', 'cdn', 'cdo', 'cdr', 'cds', 'cdy', 'cdz', 'cea', 'ceb', 'ceg', 'cek',
632
+ 'cen', 'ces', 'cet', 'cfa', 'cfd', 'cfg', 'cfm', 'cga', 'cgc', 'cgg', 'cgk', 'cha', 'chb',
633
+ 'chc', 'chd', 'che', 'chf', 'chg', 'chh', 'chj', 'chk', 'chl', 'chm', 'chn', 'cho', 'chp',
634
+ 'chq', 'chr', 'cht', 'chu', 'chv', 'chw', 'chx', 'chy', 'chz', 'cia', 'cib', 'cic', 'cid',
635
+ 'cie', 'cih', 'cik', 'cim', 'cin', 'cip', 'cir', 'ciw', 'ciy', 'cja', 'cje', 'cjh', 'cji',
636
+ 'cjk', 'cjm', 'cjn', 'cjo', 'cjp', 'cjs', 'cjv', 'cjy', 'ckb', 'ckh', 'ckl', 'cko', 'ckq',
637
+ 'ckr', 'cks', 'ckt', 'cku', 'ckv', 'ckx', 'cky', 'cla', 'clc', 'cld', 'cle', 'clh', 'cli',
638
+ 'clk', 'cll', 'clm', 'clo', 'clt', 'clu', 'clw', 'cly', 'cma', 'cme', 'cmi', 'cml', 'cmn',
639
+ 'cmo', 'cmr', 'cms', 'cna', 'cnb', 'cnc', 'cng', 'cnh', 'cni', 'cnk', 'cnl', 'cns', 'cnt',
640
+ 'cnu', 'cnw', 'coa', 'cob', 'coc', 'cod', 'coe', 'cof', 'cog', 'coh', 'coj', 'cok', 'col',
641
+ 'com', 'con', 'coo', 'cop', 'coq', 'cor', 'cos', 'cot', 'cou', 'cov', 'cow', 'cox', 'coz',
642
+ 'cpa', 'cpb', 'cpc', 'cpg', 'cpn', 'cpo', 'cps', 'cpu', 'cpx', 'cpy', 'cra', 'crb', 'crc',
643
+ 'crd', 'cre', 'crf', 'crg', 'crh', 'cri', 'crj', 'crk', 'crl', 'crm', 'crn', 'cro', 'crq',
644
+ 'crr', 'crs', 'crt', 'crv', 'crw', 'crx', 'cry', 'crz', 'csa', 'csb', 'csc', 'csd', 'cse',
645
+ 'csf', 'csg', 'csh', 'csi', 'csk', 'csl', 'csm', 'csn', 'cso', 'csq', 'csr', 'css', 'cst',
646
+ 'csv', 'csw', 'csy', 'csz', 'cta', 'ctd', 'cte', 'ctg', 'ctl', 'ctm', 'ctn', 'cto', 'ctp',
647
+ 'cts', 'ctt', 'ctu', 'ctz', 'cua', 'cub', 'cuc', 'cug', 'cuh', 'cui', 'cuj', 'cuk', 'cul',
648
+ 'cuo', 'cup', 'cuq', 'cur', 'cut', 'cuu', 'cuv', 'cuw', 'cux', 'cvg', 'cvn', 'cwa', 'cwb',
649
+ 'cwd', 'cwe', 'cwg', 'cwt', 'cya', 'cyb', 'cym', 'cyo', 'czh', 'czn', 'czo', 'czt', 'daa',
650
+ 'dac', 'dad', 'dae', 'dag', 'dah', 'dai', 'daj', 'dak', 'dal', 'dam', 'dan', 'dao', 'daq',
651
+ 'dar', 'das', 'dau', 'dav', 'daw', 'dax', 'daz', 'dba', 'dbb', 'dbd', 'dbe', 'dbf', 'dbg',
652
+ 'dbi', 'dbj', 'dbl', 'dbm', 'dbn', 'dbo', 'dbp', 'dbq', 'dbr', 'dbt', 'dbu', 'dbw', 'dby',
653
+ 'dcc', 'dcr', 'dda', 'ddd', 'dde', 'ddg', 'ddi', 'ddj', 'ddn', 'ddo', 'ddr', 'dds', 'ddw',
654
+ 'dec', 'ded', 'dee', 'def', 'deg', 'deh', 'dei', 'del', 'dem', 'den', 'deq', 'der', 'des',
655
+ 'deu', 'dev', 'dez', 'dga', 'dgb', 'dgc', 'dgd', 'dge', 'dgg', 'dgh', 'dgi', 'dgk', 'dgl',
656
+ 'dgn', 'dgo', 'dgr', 'dgs', 'dgt', 'dgw', 'dgx', 'dgz', 'dhd', 'dhg', 'dhi', 'dhl', 'dhm',
657
+ 'dhn', 'dho', 'dhr', 'dhs', 'dhu', 'dhv', 'dhw', 'dhx', 'dia', 'dib', 'dic', 'did', 'dif',
658
+ 'dig', 'dih', 'dii', 'dij', 'dik', 'dil', 'dim', 'din', 'dio', 'dip', 'diq', 'dir', 'dis',
659
+ 'dit', 'diu', 'div', 'diw', 'dix', 'diy', 'diz', 'djb', 'djc', 'djd', 'dje', 'djf', 'dji',
660
+ 'djj', 'djk', 'djm', 'djn', 'djo', 'djr', 'dju', 'djw', 'dka', 'dkk', 'dkr', 'dks', 'dkx',
661
+ 'dlg', 'dlk', 'dlm', 'dln', 'dma', 'dmb', 'dmc', 'dmd', 'dme', 'dmg', 'dmk', 'dml', 'dmm',
662
+ 'dmo', 'dmr', 'dms', 'dmu', 'dmv', 'dmx', 'dmy', 'dna', 'dnd', 'dne', 'dng', 'dni', 'dnj',
663
+ 'dnk', 'dnn', 'dnr', 'dnt', 'dnu', 'dnv', 'dnw', 'dny', 'doa', 'dob', 'doc', 'doe', 'dof',
664
+ 'doh', 'doi', 'dok', 'dol', 'don', 'doo', 'dop', 'doq', 'dor', 'dos', 'dot', 'dov', 'dow',
665
+ 'dox', 'doy', 'doz', 'dpp', 'drb', 'drc', 'drd', 'dre', 'drg', 'dri', 'drl', 'drn', 'dro',
666
+ 'drq', 'drs', 'drt', 'dru', 'dry', 'dsb', 'dse', 'dsh', 'dsi', 'dsl', 'dsn', 'dsq', 'dta',
667
+ 'dtb', 'dtd', 'dth', 'dti', 'dtk', 'dtm', 'dto', 'dtp', 'dtr', 'dts', 'dtt', 'dtu', 'dty',
668
+ 'dua', 'dub', 'duc', 'dud', 'due', 'duf', 'dug', 'duh', 'dui', 'duj', 'duk', 'dul', 'dum',
669
+ 'dun', 'duo', 'dup', 'duq', 'dur', 'dus', 'duu', 'duv', 'duw', 'dux', 'duy', 'duz', 'dva',
670
+ 'dwa', 'dwr', 'dww', 'dya', 'dyb', 'dyd', 'dyi', 'dym', 'dyn', 'dyo', 'dyu', 'dyy', 'dza',
671
+ 'dze', 'dzg', 'dzl', 'dzn', 'dzo', 'ebg', 'ebo', 'ebr', 'ebu', 'ecs', 'eee', 'efa', 'efe',
672
+ 'efi', 'ega', 'egl', 'ego', 'egy', 'ehu', 'eip', 'eit', 'eiv', 'eja', 'eka', 'eke', 'ekg',
673
+ 'eki', 'ekk', 'ekl', 'ekm', 'eko', 'ekp', 'ekr', 'eky', 'ele', 'elh', 'eli', 'elk', 'ell',
674
+ 'elm', 'elo', 'elu', 'elx', 'ema', 'emb', 'eme', 'emg', 'emi', 'emk', 'emn', 'emp', 'ems',
675
+ 'emu', 'emw', 'emy', 'ena', 'enb', 'enc', 'end', 'enf', 'eng', 'enh', 'enl', 'enm', 'enn',
676
+ 'eno', 'enq', 'enr', 'enu', 'env', 'enw', 'enx', 'eot', 'epi', 'era', 'erg', 'erh', 'eri',
677
+ 'erk', 'ero', 'err', 'ers', 'ert', 'erw', 'ese', 'esh', 'esi', 'esk', 'esl', 'esn', 'eso',
678
+ 'esq', 'ess', 'esu', 'etb', 'eth', 'etn', 'eto', 'etr', 'ets', 'ett', 'etu', 'etx', 'etz',
679
+ 'eus', 'eve', 'evh', 'evn', 'ewe', 'ewo', 'ext', 'eya', 'eyo', 'eza', 'eze', 'faa', 'fab',
680
+ 'fad', 'faf', 'fag', 'fah', 'fai', 'faj', 'fak', 'fal', 'fam', 'fan', 'fao', 'fap', 'far',
681
+ 'fat', 'fau', 'fax', 'fay', 'fcs', 'fer', 'ffm', 'fgr', 'fia', 'fie', 'fij', 'fil', 'fin',
682
+ 'fip', 'fir', 'fit', 'fiw', 'fkk', 'fkv', 'fla', 'flh', 'fli', 'fll', 'fln', 'flr', 'fmp',
683
+ 'fmu', 'fni', 'fod', 'foi', 'fon', 'for', 'fos', 'fpe', 'fqs', 'fra', 'frc', 'frd', 'frk',
684
+ 'frm', 'fro', 'frp', 'frq', 'frr', 'frs', 'frt', 'fry', 'fse', 'fsl', 'fss', 'fub', 'fuc',
685
+ 'fud', 'fue', 'fuf', 'fuh', 'fui', 'fuj', 'ful', 'fun', 'fuq', 'fur', 'fut', 'fuu', 'fuv',
686
+ 'fuy', 'fvr', 'fwa', 'fwe', 'gaa', 'gab', 'gac', 'gad', 'gae', 'gaf', 'gag', 'gah', 'gai',
687
+ 'gaj', 'gak', 'gal', 'gam', 'gan', 'gao', 'gap', 'gaq', 'gar', 'gas', 'gat', 'gau', 'gaw',
688
+ 'gax', 'gay', 'gaz', 'gbb', 'gbd', 'gbe', 'gbf', 'gbg', 'gbh', 'gbi', 'gbj', 'gbk', 'gbl',
689
+ 'gbm', 'gbn', 'gbo', 'gbp', 'gbq', 'gbr', 'gbs', 'gbu', 'gbv', 'gbw', 'gbx', 'gby', 'gbz',
690
+ 'gcc', 'gcd', 'gce', 'gcf', 'gcl', 'gcn', 'gcr', 'gct', 'gda', 'gdb', 'gdc', 'gdd', 'gde',
691
+ 'gdf', 'gdg', 'gdh', 'gdi', 'gdj', 'gdk', 'gdl', 'gdm', 'gdn', 'gdo', 'gdq', 'gdr', 'gds',
692
+ 'gdu', 'gdx', 'gea', 'geb', 'gec', 'ged', 'geh', 'gei', 'gej', 'gek', 'gel', 'geq', 'ges',
693
+ 'gev', 'gew', 'gex', 'gey', 'gez', 'gfk', 'gft', 'gga', 'ggb', 'ggd', 'gge', 'ggg', 'ggk',
694
+ 'ggl', 'ggn', 'ggo', 'ggt', 'ggu', 'ggw', 'gha', 'ghc', 'ghe', 'ghh', 'ghk', 'ghl', 'ghn',
695
+ 'gho', 'ghr', 'ghs', 'ght', 'gia', 'gid', 'gig', 'gih', 'gil', 'gim', 'gin', 'gip', 'giq',
696
+ 'gir', 'gis', 'git', 'giu', 'giw', 'gix', 'giz', 'gjk', 'gjm', 'gjn', 'gju', 'gka', 'gke',
697
+ 'gkn', 'gko', 'gkp', 'gku', 'gla', 'glc', 'gld', 'gle', 'glg', 'glh', 'glj', 'glk', 'gll',
698
+ 'glo', 'glr', 'glu', 'glv', 'glw', 'gly', 'gma', 'gmb', 'gmd', 'gmg', 'gmh', 'gml', 'gmm',
699
+ 'gmn', 'gmu', 'gmv', 'gmx', 'gmy', 'gmz', 'gna', 'gnb', 'gnc', 'gnd', 'gne', 'gng', 'gnh',
700
+ 'gni', 'gnk', 'gnl', 'gnm', 'gnn', 'gno', 'gnq', 'gnr', 'gnt', 'gnu', 'gnw', 'gnz', 'goa',
701
+ 'gob', 'goc', 'god', 'goe', 'gof', 'gog', 'goh', 'goi', 'gol', 'gom', 'gon', 'goo', 'gop',
702
+ 'goq', 'gor', 'gos', 'got', 'gou', 'gow', 'gox', 'goy', 'goz', 'gpa', 'gpe', 'gpn', 'gqa',
703
+ 'gqi', 'gqn', 'gqr', 'gqu', 'gra', 'grb', 'grc', 'grd', 'grg', 'grh', 'gri', 'grj', 'grm',
704
+ 'gro', 'grq', 'grr', 'grs', 'grt', 'gru', 'grv', 'grw', 'grx', 'gry', 'grz', 'gse', 'gsg',
705
+ 'gsl', 'gsm', 'gsn', 'gso', 'gsp', 'gss', 'gsw', 'gta', 'gua', 'gub', 'guc', 'gud', 'gue',
706
+ 'guf', 'gug', 'guh', 'gui', 'guj', 'guk', 'gul', 'gum', 'gun', 'guo', 'gup', 'guq', 'gur',
707
+ 'gus', 'gut', 'guu', 'guw', 'gux', 'guz', 'gva', 'gvc', 'gve', 'gvf', 'gvj', 'gvl', 'gvm',
708
+ 'gvn', 'gvo', 'gvp', 'gvr', 'gvs', 'gvy', 'gwa', 'gwb', 'gwc', 'gwd', 'gwe', 'gwf', 'gwg',
709
+ 'gwi', 'gwj', 'gwm', 'gwn', 'gwr', 'gwt', 'gwu', 'gww', 'gwx', 'gxx', 'gya', 'gyb', 'gyd',
710
+ 'gye', 'gyf', 'gyg', 'gyi', 'gyl', 'gym', 'gyn', 'gyr', 'gyy', 'gza', 'gzi', 'gzn', 'haa',
711
+ 'hab', 'hac', 'had', 'hae', 'haf', 'hag', 'hah', 'hai', 'haj', 'hak', 'hal', 'ham', 'han',
712
+ 'hao', 'hap', 'haq', 'har', 'has', 'hat', 'hau', 'hav', 'haw', 'hax', 'hay', 'haz', 'hba',
713
+ 'hbb', 'hbn', 'hbo', 'hbs', 'hbu', 'hca', 'hch', 'hdn', 'hds', 'hdy', 'hea', 'heb', 'hed',
714
+ 'heg', 'heh', 'hei', 'hem', 'her', 'hgm', 'hgw', 'hhi', 'hhr', 'hhy', 'hia', 'hib', 'hid',
715
+ 'hif', 'hig', 'hih', 'hii', 'hij', 'hik', 'hil', 'hin', 'hio', 'hit', 'hiw', 'hix', 'hji',
716
+ 'hka', 'hke', 'hkk', 'hks', 'hla', 'hlb', 'hld', 'hle', 'hlt', 'hlu', 'hma', 'hmb', 'hmc',
717
+ 'hmd', 'hme', 'hmf', 'hmg', 'hmh', 'hmi', 'hmj', 'hml', 'hmm', 'hmp', 'hmq', 'hmr', 'hms',
718
+ 'hmt', 'hmu', 'hmv', 'hmw', 'hmy', 'hmz', 'hna', 'hnd', 'hne', 'hnh', 'hni', 'hnj', 'hnn',
719
+ 'hno', 'hns', 'hnu', 'hoa', 'hob', 'hoc', 'hod', 'hoe', 'hoh', 'hoi', 'hoj', 'hol', 'hom',
720
+ 'hoo', 'hop', 'hor', 'hos', 'hot', 'hov', 'how', 'hoy', 'hoz', 'hpo', 'hps', 'hra', 'hrc',
721
+ 'hre', 'hrk', 'hrm', 'hro', 'hrp', 'hrt', 'hru', 'hrv', 'hrw', 'hrx', 'hrz', 'hsb', 'hsh',
722
+ 'hsl', 'hsn', 'hss', 'hti', 'hto', 'hts', 'htu', 'hub', 'huc', 'hud', 'hue', 'huf', 'hug',
723
+ 'huh', 'hui', 'huj', 'huk', 'hul', 'hum', 'hun', 'huo', 'hup', 'huq', 'hur', 'hus', 'hut',
724
+ 'huu', 'huv', 'huw', 'hux', 'huy', 'huz', 'hve', 'hvk', 'hvn', 'hvv', 'hwa', 'hwc', 'hwo',
725
+ 'hya', 'hye', 'iai', 'ian', 'iar', 'iba', 'ibb', 'ibd', 'ibe', 'ibg', 'ibl', 'ibm', 'ibn',
726
+ 'ibo', 'ibr', 'ibu', 'iby', 'ica', 'ich', 'icl', 'icr', 'ida', 'idb', 'idc', 'idd', 'ide',
727
+ 'idi', 'idr', 'idt', 'idu', 'ifa', 'ifb', 'ife', 'iff', 'ifk', 'ifm', 'ifu', 'ify', 'igb',
728
+ 'ige', 'igg', 'igl', 'igm', 'ign', 'igo', 'igw', 'ihp', 'ihw', 'iii', 'iin', 'ijc', 'ije',
729
+ 'ijj', 'ijn', 'ijs', 'ike', 'iki', 'ikk', 'ikl', 'iko', 'ikp', 'ikr', 'iks', 'ikt', 'ikv',
730
+ 'ikw', 'ikx', 'ikz', 'ila', 'ilb', 'ilg', 'ili', 'ilk', 'ill', 'ilo', 'ils', 'ilu', 'ilv',
731
+ 'ima', 'imi', 'iml', 'imn', 'imo', 'imr', 'imy', 'inb', 'ind', 'ing', 'inh', 'inj', 'inl',
732
+ 'inm', 'inn', 'ino', 'inp', 'ins', 'int', 'inz', 'ior', 'iou', 'iow', 'ipi', 'ipk', 'ipo',
733
+ 'iqu', 'iqw', 'ire', 'irh', 'iri', 'irk', 'irn', 'iru', 'irx', 'iry', 'isa', 'isc', 'isd',
734
+ 'ise', 'isg', 'ish', 'isi', 'isk', 'isl', 'ism', 'isn', 'iso', 'isr', 'ist', 'isu', 'ita',
735
+ 'itb', 'ite', 'iti', 'itk', 'itl', 'itm', 'ito', 'itr', 'its', 'itt', 'itv', 'itw', 'itx',
736
+ 'ity', 'itz', 'ium', 'ivb', 'ivv', 'iwk', 'iwm', 'iwo', 'iws', 'ixc', 'ixl', 'iya', 'iyo',
737
+ 'iyx', 'izh', 'izr', 'izz', 'jaa', 'jab', 'jac', 'jad', 'jae', 'jaf', 'jah', 'jaj', 'jak',
738
+ 'jal', 'jam', 'jan', 'jao', 'jaq', 'jas', 'jat', 'jau', 'jav', 'jax', 'jay', 'jaz', 'jbi',
739
+ 'jbj', 'jbk', 'jbn', 'jbr', 'jbt', 'jbu', 'jbw', 'jcs', 'jct', 'jda', 'jdg', 'jdt', 'jeb',
740
+ 'jee', 'jeg', 'jeh', 'jei', 'jek', 'jel', 'jen', 'jer', 'jet', 'jeu', 'jgb', 'jge', 'jgo',
741
+ 'jhi', 'jhs', 'jia', 'jib', 'jic', 'jid', 'jie', 'jig', 'jih', 'jii', 'jil', 'jim', 'jio',
742
+ 'jiq', 'jit', 'jiu', 'jiv', 'jiy', 'jje', 'jkm', 'jko', 'jkr', 'jku', 'jle', 'jls', 'jma',
743
+ 'jmb', 'jmc', 'jmd', 'jmi', 'jml', 'jmn', 'jmr', 'jms', 'jmw', 'jmx', 'jna', 'jnd', 'jng',
744
+ 'jni', 'jnj', 'jnl', 'jns', 'job', 'jod', 'jor', 'jos', 'jow', 'jpa', 'jpn', 'jpr', 'jqr',
745
+ 'jra', 'jrr', 'jru', 'jsl', 'jua', 'jub', 'juc', 'jud', 'juh', 'jui', 'juk', 'jul', 'jum',
746
+ 'jun', 'juo', 'jup', 'jur', 'jus', 'jut', 'juu', 'juw', 'juy', 'jvd', 'jvn', 'jwi', 'jya',
747
+ 'jye', 'jyy', 'kaa', 'kab', 'kac', 'kad', 'kae', 'kaf', 'kag', 'kah', 'kai', 'kaj', 'kak',
748
+ 'kal', 'kam', 'kan', 'kao', 'kap', 'kaq', 'kas', 'kat', 'kaw', 'kax', 'kay', 'kaz', 'kba',
749
+ 'kbb', 'kbc', 'kbd', 'kbe', 'kbg', 'kbh', 'kbi', 'kbj', 'kbk', 'kbl', 'kbm', 'kbn', 'kbo',
750
+ 'kbp', 'kbq', 'kbr', 'kbs', 'kbt', 'kbu', 'kbv', 'kbw', 'kbx', 'kby', 'kbz', 'kca', 'kcb',
751
+ 'kcc', 'kcd', 'kcf', 'kcg', 'kci', 'kcj', 'kck', 'kcl', 'kcm', 'kcn', 'kco', 'kcp', 'kcq',
752
+ 'kcr', 'kcs', 'kct', 'kcu', 'kcv', 'kcw', 'kcx', 'kcy', 'kcz', 'kda', 'kdc', 'kdd', 'kde',
753
+ 'kdf', 'kdg', 'kdh', 'kdi', 'kdj', 'kdk', 'kdl', 'kdm', 'kdn', 'kdp', 'kdq', 'kdr', 'kdt',
754
+ 'kdu', 'kdw', 'kdx', 'kdy', 'kdz', 'kea', 'keb', 'kec', 'ked', 'kee', 'kef', 'keg', 'keh',
755
+ 'kei', 'kej', 'kek', 'kel', 'kem', 'ken', 'keo', 'kep', 'keq', 'ker', 'kes', 'ket', 'keu',
756
+ 'kev', 'kew', 'key', 'kez', 'kfa', 'kfb', 'kfc', 'kfd', 'kfe', 'kff', 'kfg', 'kfh', 'kfk',
757
+ 'kfl', 'kfm', 'kfn', 'kfo', 'kfp', 'kfq', 'kfr', 'kfs', 'kft', 'kfu', 'kfv', 'kfw', 'kfx',
758
+ 'kfy', 'kfz', 'kga', 'kgb', 'kgd', 'kge', 'kgf', 'kgg', 'kgi', 'kgj', 'kgk', 'kgl', 'kgn',
759
+ 'kgo', 'kgp', 'kgq', 'kgr', 'kgs', 'kgt', 'kgu', 'kgv', 'kgw', 'kgx', 'kgy', 'kha', 'khb',
760
+ 'khc', 'khd', 'khe', 'khf', 'khg', 'khh', 'khj', 'khk', 'khl', 'khm', 'khn', 'kho', 'khp',
761
+ 'khq', 'khr', 'khs', 'kht', 'khu', 'khv', 'khw', 'khx', 'khy', 'khz', 'kia', 'kib', 'kic',
762
+ 'kid', 'kie', 'kif', 'kig', 'kih', 'kii', 'kij', 'kik', 'kil', 'kim', 'kin', 'kio', 'kip',
763
+ 'kiq', 'kir', 'kis', 'kit', 'kiu', 'kiv', 'kiw', 'kix', 'kiy', 'kiz', 'kja', 'kjb', 'kjc',
764
+ 'kjd', 'kje', 'kjg', 'kjh', 'kji', 'kjj', 'kjk', 'kjl', 'kjm', 'kjn', 'kjo', 'kjp', 'kjq',
765
+ 'kjr', 'kjs', 'kjt', 'kju', 'kjv', 'kjx', 'kjy', 'kjz', 'kka', 'kkb', 'kkc', 'kkd', 'kke',
766
+ 'kkf', 'kkg', 'kkh', 'kki', 'kkj', 'kkk', 'kkl', 'kkm', 'kko', 'kkp', 'kkq', 'kkr', 'kks',
767
+ 'kkt', 'kkv', 'kkw', 'kkx', 'kky', 'kkz', 'kla', 'klb', 'klc', 'kld', 'kle', 'klf', 'klg',
768
+ 'klh', 'kli', 'klj', 'klk', 'kll', 'klm', 'kln', 'klo', 'klp', 'klq', 'klr', 'kls', 'klt',
769
+ 'klu', 'klv', 'klw', 'klx', 'kly', 'klz', 'kma', 'kmb', 'kmc', 'kmd', 'kme', 'kmf', 'kmg',
770
+ 'kmh', 'kmi', 'kmj', 'kmk', 'kml', 'kmm', 'kmn', 'kmo', 'kmp', 'kmq', 'kmr', 'kms', 'kmt',
771
+ 'kmu', 'kmv', 'kmw', 'kmx', 'kmy', 'kmz', 'kna', 'knb', 'knc', 'knd', 'kne', 'knf', 'kng',
772
+ 'kni', 'knj', 'knk', 'knl', 'knm', 'knn', 'kno', 'knp', 'knq', 'knr', 'kns', 'knt', 'knu',
773
+ 'knv', 'knw', 'knx', 'kny', 'knz', 'koa', 'koc', 'kod', 'koe', 'kof', 'kog', 'koh', 'koi',
774
+ 'kol', 'kom', 'koo', 'kop', 'koq', 'kor', 'kos', 'kot', 'kou', 'kov', 'kow', 'koy', 'koz',
775
+ 'kpa', 'kpb', 'kpc', 'kpd', 'kpf', 'kpg', 'kph', 'kpi', 'kpj', 'kpk', 'kpl', 'kpm', 'kpn',
776
+ 'kpo', 'kpq', 'kpr', 'kps', 'kpt', 'kpu', 'kpv', 'kpw', 'kpx', 'kpy', 'kpz', 'kqa', 'kqb',
777
+ 'kqc', 'kqd', 'kqe', 'kqf', 'kqg', 'kqi', 'kqj', 'kqk', 'kql', 'kqm', 'kqn', 'kqo', 'kqp',
778
+ 'kqq', 'kqr', 'kqs', 'kqt', 'kqu', 'kqv', 'kqw', 'kqx', 'kqy', 'kqz', 'kra', 'krb', 'krc',
779
+ 'krd', 'kre', 'krf', 'krh', 'kri', 'krj', 'krk', 'krl', 'krm', 'krn', 'krp', 'krr', 'krs',
780
+ 'krt', 'kru', 'krv', 'krw', 'krx', 'kry', 'krz', 'ksb', 'ksc', 'ksd', 'kse', 'ksf', 'ksg',
781
+ 'ksh', 'ksi', 'ksj', 'ksk', 'ksl', 'ksm', 'ksn', 'ksp', 'ksq', 'ksr', 'kss', 'kst', 'ksu',
782
+ 'ksv', 'ksw', 'ksx', 'ksy', 'ksz', 'kta', 'ktb', 'ktc', 'ktd', 'kte', 'ktf', 'ktg', 'kth',
783
+ 'kti', 'ktj', 'ktk', 'ktl', 'ktm', 'ktn', 'kto', 'ktp', 'ktr', 'kts', 'ktt', 'ktu', 'ktv',
784
+ 'ktw', 'ktx', 'kty', 'ktz', 'kua', 'kub', 'kuc', 'kud', 'kue', 'kuf', 'kug', 'kuh', 'kui',
785
+ 'kuj', 'kuk', 'kul', 'kum', 'kun', 'kuo', 'kup', 'kuq', 'kur', 'kus', 'kut', 'kuu', 'kuv',
786
+ 'kuw', 'kux', 'kuy', 'kuz', 'kva', 'kvb', 'kvc', 'kvd', 'kve', 'kvf', 'kvg', 'kvh', 'kvi',
787
+ 'kvj', 'kvk', 'kvl', 'kvm', 'kvn', 'kvo', 'kvp', 'kvq', 'kvr', 'kvu', 'kvv', 'kvw', 'kvx',
788
+ 'kvy', 'kvz', 'kwa', 'kwb', 'kwc', 'kwd', 'kwe', 'kwf', 'kwg', 'kwh', 'kwi', 'kwj', 'kwk',
789
+ 'kwl', 'kwm', 'kwn', 'kwo', 'kwp', 'kwr', 'kws', 'kwt', 'kwu', 'kwv', 'kww', 'kwx', 'kwy',
790
+ 'kwz', 'kxa', 'kxb', 'kxc', 'kxd', 'kxf', 'kxh', 'kxi', 'kxj', 'kxk', 'kxl', 'kxm', 'kxn',
791
+ 'kxo', 'kxp', 'kxq', 'kxr', 'kxs', 'kxt', 'kxu', 'kxv', 'kxw', 'kxx', 'kxy', 'kxz', 'kya',
792
+ 'kyb', 'kyc', 'kyd', 'kye', 'kyf', 'kyg', 'kyh', 'kyi', 'kyj', 'kyk', 'kyl', 'kyn', 'kyo',
793
+ 'kyq', 'kyr', 'kys', 'kyt', 'kyu', 'kyw', 'kyx', 'kyy', 'kyz', 'kza', 'kzb', 'kzc', 'kzd',
794
+ 'kzf', 'kzg', 'kzi', 'kzj', 'kzk', 'kzl', 'kzm', 'kzn', 'kzo', 'kzp', 'kzq', 'kzr', 'kzs',
795
+ 'kzt', 'kzu', 'kzv', 'kzx', 'kzy', 'kzz', 'laa', 'lac', 'lad', 'lae', 'laf', 'lag', 'lai',
796
+ 'laj', 'lak', 'lal', 'lam', 'lan', 'lao', 'lap', 'laq', 'lar', 'las', 'lat', 'lav', 'law',
797
+ 'lax', 'laz', 'lbb', 'lbc', 'lbe', 'lbf', 'lbj', 'lbk', 'lbl', 'lbm', 'lbn', 'lbo', 'lbq',
798
+ 'lbr', 'lbs', 'lbt', 'lbu', 'lbv', 'lbw', 'lbx', 'lby', 'lbz', 'lcc', 'lcd', 'lce', 'lcf',
799
+ 'lch', 'lcl', 'lcm', 'lcp', 'lcq', 'lcs', 'lda', 'ldb', 'ldd', 'ldg', 'ldh', 'ldi', 'ldj',
800
+ 'ldk', 'ldl', 'ldm', 'ldo', 'ldp', 'ldq', 'lea', 'leb', 'lec', 'led', 'lee', 'lef', 'leh',
801
+ 'lei', 'lej', 'lek', 'lel', 'lem', 'leo', 'lep', 'leq', 'ler', 'les', 'let', 'leu', 'lev',
802
+ 'lew', 'lex', 'ley', 'lez', 'lfa', 'lga', 'lgb', 'lgg', 'lgh', 'lgi', 'lgk', 'lgl', 'lgm',
803
+ 'lgn', 'lgq', 'lgr', 'lgt', 'lgu', 'lgz', 'lha', 'lhh', 'lhi', 'lhl', 'lhm', 'lhn', 'lhp',
804
+ 'lhs', 'lht', 'lhu', 'lia', 'lib', 'lic', 'lid', 'lie', 'lif', 'lig', 'lih', 'lij', 'lik',
805
+ 'lil', 'lim', 'lin', 'lio', 'lip', 'liq', 'lis', 'lit', 'liu', 'liv', 'liw', 'lix', 'liy',
806
+ 'liz', 'lja', 'lje', 'lji', 'ljl', 'ljp', 'ljw', 'ljx', 'lka', 'lkb', 'lkc', 'lkd', 'lke',
807
+ 'lkh', 'lki', 'lkj', 'lkl', 'lkm', 'lkn', 'lko', 'lkr', 'lks', 'lkt', 'lku', 'lky', 'lla',
808
+ 'llb', 'llc', 'lld', 'lle', 'llf', 'llg', 'llh', 'lli', 'llj', 'llk', 'lll', 'llm', 'lln',
809
+ 'llp', 'llq', 'lls', 'llu', 'llx', 'lma', 'lmb', 'lmc', 'lmd', 'lme', 'lmf', 'lmg', 'lmi',
810
+ 'lmj', 'lmk', 'lml', 'lmn', 'lmo', 'lmp', 'lmq', 'lmr', 'lmu', 'lmv', 'lmw', 'lmx', 'lmy',
811
+ 'lna', 'lnb', 'lnd', 'lnh', 'lni', 'lnj', 'lnl', 'lnm', 'lnn', 'lno', 'lns', 'lnu', 'lnz',
812
+ 'loa', 'lob', 'loc', 'loe', 'lof', 'log', 'loh', 'loi', 'loj', 'lok', 'lol', 'lom', 'lon',
813
+ 'loo', 'lop', 'loq', 'lor', 'los', 'lot', 'lou', 'low', 'lox', 'loy', 'loz', 'lpa', 'lpe',
814
+ 'lpn', 'lpo', 'lpx', 'lra', 'lrc', 'lre', 'lrg', 'lri', 'lrl', 'lrm', 'lrn', 'lro', 'lrr',
815
+ 'lrt', 'lrv', 'lrz', 'lsa', 'lsd', 'lse', 'lsh', 'lsi', 'lsl', 'lsm', 'lsp', 'lsr', 'lss',
816
+ 'lst', 'lsy', 'ltc', 'ltg', 'lti', 'ltn', 'lto', 'lts', 'ltu', 'ltz', 'lua', 'lub', 'luc',
817
+ 'lud', 'lue', 'luf', 'lug', 'lui', 'luj', 'luk', 'lul', 'lum', 'lun', 'luo', 'lup', 'luq',
818
+ 'lur', 'lus', 'lut', 'luv', 'luw', 'luz', 'lva', 'lvk', 'lvs', 'lvu', 'lwa', 'lwe', 'lwg',
819
+ 'lwh', 'lwl', 'lwm', 'lwo', 'lwt', 'lwu', 'lww', 'lya', 'lyg', 'lyn', 'lzh', 'lzl', 'lzn',
820
+ 'lzz', 'maa', 'mab', 'mad', 'mae', 'maf', 'mag', 'mah', 'mai', 'maj', 'mak', 'mal', 'mam',
821
+ 'maq', 'mar', 'mas', 'mat', 'mau', 'mav', 'maw', 'max', 'maz', 'mba', 'mbb', 'mbc', 'mbd',
822
+ 'mbe', 'mbf', 'mbh', 'mbi', 'mbj', 'mbk', 'mbl', 'mbm', 'mbn', 'mbo', 'mbp', 'mbq', 'mbr',
823
+ 'mbs', 'mbt', 'mbu', 'mbv', 'mbw', 'mbx', 'mby', 'mbz', 'mca', 'mcb', 'mcc', 'mcd', 'mce',
824
+ 'mcf', 'mcg', 'mch', 'mci', 'mcj', 'mck', 'mcl', 'mcm', 'mcn', 'mco', 'mcp', 'mcq', 'mcr',
825
+ 'mcs', 'mct', 'mcu', 'mcv', 'mcw', 'mcx', 'mcy', 'mcz', 'mda', 'mdb', 'mdc', 'mdd', 'mde',
826
+ 'mdf', 'mdg', 'mdh', 'mdi', 'mdj', 'mdk', 'mdl', 'mdm', 'mdn', 'mdp', 'mdq', 'mdr', 'mds',
827
+ 'mdt', 'mdu', 'mdv', 'mdw', 'mdx', 'mdy', 'mdz', 'mea', 'meb', 'mec', 'med', 'mee', 'mef',
828
+ 'meh', 'mei', 'mej', 'mek', 'mel', 'mem', 'men', 'meo', 'mep', 'meq', 'mer', 'mes', 'met',
829
+ 'meu', 'mev', 'mew', 'mey', 'mez', 'mfa', 'mfb', 'mfc', 'mfd', 'mfe', 'mff', 'mfg', 'mfh',
830
+ 'mfi', 'mfj', 'mfk', 'mfl', 'mfm', 'mfn', 'mfo', 'mfp', 'mfq', 'mfr', 'mfs', 'mft', 'mfu',
831
+ 'mfv', 'mfw', 'mfx', 'mfy', 'mfz', 'mga', 'mgb', 'mgc', 'mgd', 'mge', 'mgf', 'mgg', 'mgh',
832
+ 'mgi', 'mgj', 'mgk', 'mgl', 'mgm', 'mgn', 'mgo', 'mgp', 'mgq', 'mgr', 'mgs', 'mgt', 'mgu',
833
+ 'mgv', 'mgw', 'mgy', 'mgz', 'mha', 'mhb', 'mhc', 'mhd', 'mhe', 'mhf', 'mhg', 'mhi', 'mhj',
834
+ 'mhk', 'mhl', 'mhm', 'mhn', 'mho', 'mhp', 'mhq', 'mhr', 'mhs', 'mht', 'mhu', 'mhw', 'mhx',
835
+ 'mhy', 'mhz', 'mia', 'mib', 'mic', 'mid', 'mie', 'mif', 'mig', 'mih', 'mii', 'mij', 'mik',
836
+ 'mil', 'mim', 'min', 'mio', 'mip', 'miq', 'mir', 'mit', 'miu', 'miw', 'mix', 'miy', 'miz',
837
+ 'mjc', 'mjd', 'mje', 'mjg', 'mjh', 'mji', 'mjj', 'mjk', 'mjl', 'mjm', 'mjn', 'mjo', 'mjp',
838
+ 'mjq', 'mjr', 'mjs', 'mjt', 'mju', 'mjv', 'mjw', 'mjx', 'mjy', 'mjz', 'mka', 'mkb', 'mkc',
839
+ 'mkd', 'mke', 'mkf', 'mkg', 'mki', 'mkj', 'mkk', 'mkl', 'mkm', 'mkn', 'mko', 'mkp', 'mkq',
840
+ 'mkr', 'mks', 'mkt', 'mku', 'mkv', 'mkw', 'mkx', 'mky', 'mkz', 'mla', 'mlb', 'mlc', 'mle',
841
+ 'mlf', 'mlh', 'mli', 'mlj', 'mlk', 'mll', 'mlm', 'mln', 'mlo', 'mlp', 'mlq', 'mlr', 'mls',
842
+ 'mlt', 'mlu', 'mlv', 'mlw', 'mlx', 'mlz', 'mma', 'mmb', 'mmc', 'mmd', 'mme', 'mmf', 'mmg',
843
+ 'mmh', 'mmi', 'mmj', 'mmk', 'mml', 'mmm', 'mmn', 'mmo', 'mmp', 'mmq', 'mmr', 'mmt', 'mmu',
844
+ 'mmv', 'mmw', 'mmx', 'mmy', 'mmz', 'mna', 'mnb', 'mnc', 'mnd', 'mne', 'mnf', 'mng', 'mnh',
845
+ 'mni', 'mnj', 'mnk', 'mnl', 'mnm', 'mnn', 'mnp', 'mnq', 'mnr', 'mns', 'mnu', 'mnv', 'mnw',
846
+ 'mnx', 'mny', 'mnz', 'moa', 'moc', 'moe', 'mog', 'moh', 'moi', 'moj', 'mok', 'mom', 'mon',
847
+ 'moo', 'mop', 'moq', 'mor', 'mos', 'mot', 'mou', 'mov', 'mow', 'mox', 'moy', 'moz', 'mpa',
848
+ 'mpb', 'mpc', 'mpd', 'mpe', 'mpg', 'mph', 'mpi', 'mpj', 'mpk', 'mpl', 'mpm', 'mpn', 'mpo',
849
+ 'mpp', 'mpq', 'mpr', 'mps', 'mpt', 'mpu', 'mpv', 'mpw', 'mpx', 'mpy', 'mpz', 'mqa', 'mqb',
850
+ 'mqc', 'mqe', 'mqf', 'mqg', 'mqh', 'mqi', 'mqj', 'mqk', 'mql', 'mqm', 'mqn', 'mqo', 'mqp',
851
+ 'mqq', 'mqr', 'mqs', 'mqt', 'mqu', 'mqv', 'mqw', 'mqx', 'mqy', 'mqz', 'mra', 'mrb', 'mrc',
852
+ 'mrd', 'mre', 'mrf', 'mrg', 'mrh', 'mri', 'mrj', 'mrk', 'mrl', 'mrm', 'mrn', 'mro', 'mrp',
853
+ 'mrq', 'mrr', 'mrs', 'mrt', 'mru', 'mrv', 'mrw', 'mrx', 'mry', 'mrz', 'msb', 'msc', 'msd',
854
+ 'mse', 'msf', 'msg', 'msh', 'msi', 'msj', 'msk', 'msl', 'msm', 'msn', 'mso', 'msp', 'msq',
855
+ 'msr', 'mss', 'msu', 'msv', 'msw', 'msx', 'msy', 'msz', 'mta', 'mtb', 'mtc', 'mtd', 'mte',
856
+ 'mtf', 'mtg', 'mth', 'mti', 'mtj', 'mtk', 'mtl', 'mtm', 'mtn', 'mto', 'mtp', 'mtq', 'mtr',
857
+ 'mts', 'mtt', 'mtu', 'mtv', 'mtw', 'mtx', 'mty', 'mua', 'mub', 'muc', 'mud', 'mug', 'muh',
858
+ 'mui', 'muj', 'muk', 'mum', 'muo', 'mup', 'muq', 'mur', 'mus', 'mut', 'muu', 'muv', 'mux',
859
+ 'muy', 'muz', 'mva', 'mvb', 'mvd', 'mve', 'mvf', 'mvg', 'mvh', 'mvi', 'mvk', 'mvl', 'mvn',
860
+ 'mvo', 'mvp', 'mvq', 'mvr', 'mvs', 'mvt', 'mvu', 'mvv', 'mvw', 'mvx', 'mvy', 'mvz', 'mwa',
861
+ 'mwb', 'mwc', 'mwe', 'mwf', 'mwg', 'mwh', 'mwi', 'mwk', 'mwl', 'mwm', 'mwn', 'mwo', 'mwp',
862
+ 'mwq', 'mws', 'mwt', 'mwu', 'mwv', 'mww', 'mwy', 'mwz', 'mxa', 'mxb', 'mxc', 'mxd', 'mxe',
863
+ 'mxf', 'mxg', 'mxh', 'mxi', 'mxj', 'mxk', 'mxl', 'mxm', 'mxn', 'mxo', 'mxp', 'mxq', 'mxr',
864
+ 'mxs', 'mxt', 'mxu', 'mxv', 'mxw', 'mxx', 'mxy', 'mxz', 'mya', 'myb', 'myc', 'mye', 'myf',
865
+ 'myg', 'myh', 'myj', 'myk', 'myl', 'mym', 'myo', 'myp', 'myr', 'mys', 'myu', 'myv', 'myw',
866
+ 'myx', 'myy', 'myz', 'mza', 'mzb', 'mzc', 'mzd', 'mze', 'mzg', 'mzh', 'mzi', 'mzj', 'mzk',
867
+ 'mzl', 'mzm', 'mzn', 'mzo', 'mzp', 'mzq', 'mzr', 'mzs', 'mzt', 'mzu', 'mzv', 'mzw', 'mzy',
868
+ 'mzz', 'naa', 'nab', 'nac', 'nae', 'naf', 'nag', 'naj', 'nak', 'nal', 'nam', 'nan', 'nao',
869
+ 'nap', 'naq', 'nar', 'nas', 'nat', 'nau', 'nav', 'naw', 'nax', 'nay', 'naz', 'nba', 'nbb',
870
+ 'nbc', 'nbd', 'nbe', 'nbh', 'nbi', 'nbj', 'nbk', 'nbl', 'nbm', 'nbn', 'nbo', 'nbp', 'nbq',
871
+ 'nbr', 'nbs', 'nbt', 'nbu', 'nbv', 'nbw', 'nby', 'nca', 'ncb', 'ncc', 'ncd', 'nce', 'ncf',
872
+ 'ncg', 'nch', 'nci', 'ncj', 'nck', 'ncl', 'ncm', 'ncn', 'nco', 'ncp', 'ncr', 'ncs', 'nct',
873
+ 'ncu', 'ncx', 'ncz', 'nda', 'ndb', 'ndc', 'ndd', 'nde', 'ndg', 'ndh', 'ndi', 'ndj', 'ndk',
874
+ 'ndl', 'ndm', 'ndn', 'ndo', 'ndp', 'ndq', 'ndr', 'nds', 'ndt', 'ndu', 'ndv', 'ndw', 'ndx',
875
+ 'ndy', 'ndz', 'nea', 'neb', 'nec', 'nee', 'neg', 'neh', 'nej', 'nek', 'nem', 'nen', 'nep',
876
+ 'neq', 'ner', 'nes', 'net', 'nev', 'new', 'nex', 'ney', 'nez', 'nfa', 'nfd', 'nfl', 'nfr',
877
+ 'nfu', 'nga', 'ngb', 'ngc', 'ngd', 'nge', 'ngg', 'ngh', 'ngi', 'ngj', 'ngk', 'ngl', 'ngn',
878
+ 'ngp', 'ngq', 'ngr', 'ngs', 'ngt', 'ngu', 'ngv', 'ngw', 'ngx', 'ngy', 'ngz', 'nha', 'nhb',
879
+ 'nhc', 'nhd', 'nhe', 'nhf', 'nhg', 'nhh', 'nhi', 'nhk', 'nhm', 'nhn', 'nho', 'nhp', 'nhq',
880
+ 'nhr', 'nht', 'nhu', 'nhv', 'nhw', 'nhx', 'nhy', 'nhz', 'nia', 'nib', 'nid', 'nie', 'nif',
881
+ 'nig', 'nih', 'nii', 'nij', 'nik', 'nil', 'nim', 'nin', 'nio', 'niq', 'nir', 'nis', 'nit',
882
+ 'niu', 'niv', 'niw', 'nix', 'niy', 'niz', 'nja', 'njb', 'njh', 'nji', 'njj', 'njl', 'njm',
883
+ 'njn', 'njo', 'njr', 'njs', 'nju', 'njx', 'njy', 'njz', 'nka', 'nkb', 'nkc', 'nkd', 'nke',
884
+ 'nkg', 'nkh', 'nki', 'nkj', 'nkk', 'nkm', 'nkn', 'nko', 'nkp', 'nkq', 'nkr', 'nks', 'nkt',
885
+ 'nku', 'nkv', 'nkw', 'nkx', 'nkz', 'nla', 'nlc', 'nld', 'nle', 'nlg', 'nli', 'nlj', 'nlk',
886
+ 'nll', 'nlo', 'nlu', 'nlv', 'nlx', 'nly', 'nlz', 'nma', 'nmb', 'nmc', 'nmd', 'nme', 'nmf',
887
+ 'nmg', 'nmh', 'nmi', 'nmk', 'nml', 'nmm', 'nmn', 'nmo', 'nmp', 'nmq', 'nmr', 'nms', 'nmt',
888
+ 'nmu', 'nmv', 'nmw', 'nmx', 'nmy', 'nmz', 'nna', 'nnb', 'nnc', 'nnd', 'nne', 'nnf', 'nng',
889
+ 'nnh', 'nni', 'nnj', 'nnk', 'nnl', 'nnm', 'nnn', 'nno', 'nnp', 'nnq', 'nnr', 'nns', 'nnt',
890
+ 'nnu', 'nnv', 'nnw', 'nny', 'nnz', 'noa', 'nob', 'noc', 'nod', 'noe', 'nof', 'nog', 'noh',
891
+ 'noi', 'noj', 'nok', 'nol', 'non', 'nop', 'noq', 'nor', 'nos', 'not', 'nou', 'now', 'noy',
892
+ 'noz', 'npa', 'nph', 'npi', 'npl', 'npn', 'npo', 'nps', 'npy', 'nqg', 'nqk', 'nqm', 'nqn',
893
+ 'nra', 'nrb', 'nrc', 'nre', 'nrf', 'nrg', 'nri', 'nrk', 'nrl', 'nrm', 'nrn', 'nrt', 'nru',
894
+ 'nrz', 'nsa', 'nsd', 'nse', 'nsf', 'nsg', 'nsh', 'nsi', 'nsk', 'nsl', 'nsm', 'nsn', 'nso',
895
+ 'nsp', 'nsq', 'nsr', 'nss', 'nst', 'nsu', 'nsw', 'nsx', 'nsy', 'nsz', 'nte', 'nti', 'ntj',
896
+ 'ntk', 'ntm', 'nto', 'ntp', 'ntr', 'ntu', 'ntw', 'nty', 'ntz', 'nua', 'nuc', 'nud', 'nue',
897
+ 'nuf', 'nug', 'nuh', 'nui', 'nuj', 'nuk', 'nul', 'num', 'nun', 'nuo', 'nup', 'nuq', 'nur',
898
+ 'nus', 'nut', 'nuu', 'nuv', 'nuw', 'nux', 'nuy', 'nuz', 'nvh', 'nvm', 'nvo', 'nwa', 'nwb',
899
+ 'nwe', 'nwi', 'nwm', 'nwo', 'nwr', 'nxa', 'nxd', 'nxe', 'nxg', 'nxi', 'nxl', 'nxn', 'nxo',
900
+ 'nxq', 'nxr', 'nxx', 'nya', 'nyb', 'nyc', 'nyd', 'nye', 'nyf', 'nyg', 'nyh', 'nyi', 'nyj',
901
+ 'nyk', 'nyl', 'nym', 'nyn', 'nyo', 'nyp', 'nyq', 'nyr', 'nys', 'nyt', 'nyu', 'nyv', 'nyw',
902
+ 'nyx', 'nyy', 'nza', 'nzb', 'nzi', 'nzk', 'nzm', 'nzs', 'nzu', 'nzy', 'nzz', 'oaa', 'oac',
903
+ 'oar', 'obi', 'obl', 'obm', 'obo', 'obr', 'obu', 'oca', 'och', 'oci', 'oco', 'ocu', 'odk',
904
+ 'odt', 'odu', 'ofo', 'ofs', 'ofu', 'ogb', 'ogc', 'oge', 'ogg', 'ogo', 'ogu', 'oia', 'oin',
905
+ 'ojb', 'ojc', 'ojg', 'ojp', 'ojs', 'ojv', 'ojw', 'oka', 'okb', 'okd', 'oke', 'okh', 'oki',
906
+ 'okj', 'okk', 'okl', 'okn', 'okr', 'oks', 'oku', 'okv', 'okx', 'ola', 'old', 'ole', 'olm',
907
+ 'olo', 'olr', 'olt', 'oma', 'omb', 'omc', 'omg', 'omi', 'omk', 'oml', 'omo', 'omr', 'omt',
908
+ 'omu', 'omw', 'omx', 'ona', 'onb', 'one', 'ong', 'oni', 'onj', 'onk', 'onn', 'ono', 'onp',
909
+ 'onr', 'ons', 'onu', 'onw', 'ood', 'oog', 'oon', 'oor', 'oos', 'opa', 'opk', 'opm', 'opo',
910
+ 'opt', 'opy', 'ora', 'orc', 'ore', 'org', 'orh', 'orn', 'oro', 'orr', 'ors', 'ort', 'oru',
911
+ 'orv', 'orw', 'orx', 'ory', 'orz', 'osa', 'osc', 'osi', 'oso', 'osp', 'oss', 'ost', 'osu',
912
+ 'osx', 'ota', 'otd', 'ote', 'oti', 'otl', 'otm', 'otn', 'otq', 'otr', 'ots', 'ott', 'otu',
913
+ 'otw', 'otx', 'oty', 'otz', 'oua', 'oub', 'oue', 'oui', 'oum', 'owi', 'owl', 'oyb', 'oyd',
914
+ 'oym', 'oyy', 'ozm', 'pab', 'pac', 'pad', 'pae', 'paf', 'pag', 'pah', 'pai', 'pak', 'pal',
915
+ 'pam', 'pan', 'pao', 'pap', 'paq', 'par', 'pas', 'pat', 'pau', 'pav', 'paw', 'pay', 'paz',
916
+ 'pbb', 'pbc', 'pbe', 'pbf', 'pbg', 'pbh', 'pbi', 'pbl', 'pbn', 'pbo', 'pbp', 'pbr', 'pbs',
917
+ 'pbt', 'pbu', 'pbv', 'pby', 'pca', 'pcb', 'pcc', 'pcd', 'pce', 'pcf', 'pcg', 'pci', 'pcj',
918
+ 'pck', 'pcl', 'pcm', 'pcn', 'pcp', 'pcw', 'pda', 'pdc', 'pdi', 'pdn', 'pdo', 'pdt', 'pdu',
919
+ 'pea', 'peb', 'ped', 'pee', 'pef', 'peg', 'peh', 'pei', 'pej', 'pek', 'pel', 'pem', 'peo',
920
+ 'pep', 'peq', 'pes', 'pev', 'pex', 'pey', 'pez', 'pfa', 'pfe', 'pfl', 'pga', 'pgg', 'pgi',
921
+ 'pgk', 'pgs', 'pgu', 'pha', 'phd', 'phg', 'phh', 'phk', 'phl', 'phm', 'phn', 'pho', 'phq',
922
+ 'phr', 'pht', 'phu', 'phv', 'pia', 'pib', 'pic', 'pid', 'pie', 'pif', 'pih', 'pii', 'pil',
923
+ 'pim', 'pin', 'pio', 'pip', 'pir', 'pis', 'pit', 'piu', 'piv', 'piw', 'pix', 'piy', 'piz',
924
+ 'pjt', 'pkb', 'pkg', 'pkh', 'pkn', 'pko', 'pkp', 'pkr', 'pks', 'pkt', 'pku', 'pla', 'plb',
925
+ 'plc', 'ple', 'plg', 'plh', 'pli', 'plj', 'plk', 'pll', 'pln', 'plo', 'plp', 'plq', 'plr',
926
+ 'pls', 'plt', 'plu', 'plv', 'plw', 'ply', 'plz', 'pma', 'pmb', 'pmd', 'pme', 'pmf', 'pmh',
927
+ 'pmi', 'pmj', 'pmm', 'pmn', 'pmo', 'pmq', 'pmr', 'pms', 'pmt', 'pmw', 'pmx', 'pmy', 'pmz',
928
+ 'pna', 'pnb', 'pnc', 'pne', 'png', 'pnh', 'pni', 'pnj', 'pnk', 'pnl', 'pnm', 'pnn', 'pno',
929
+ 'pnp', 'pnq', 'pnr', 'pns', 'pnt', 'pnu', 'pnv', 'pnw', 'pnx', 'pny', 'pnz', 'poc', 'poe',
930
+ 'pof', 'pog', 'poh', 'poi', 'pol', 'pom', 'pon', 'poo', 'pop', 'poq', 'por', 'pos', 'pot',
931
+ 'pov', 'pow', 'pox', 'poy', 'ppe', 'ppi', 'ppk', 'ppl', 'ppm', 'ppn', 'ppo', 'ppp', 'ppq',
932
+ 'pps', 'ppt', 'ppu', 'pqa', 'pqm', 'prc', 'pre', 'prf', 'prg', 'prh', 'pri', 'prk', 'prl',
933
+ 'prm', 'prn', 'pro', 'prq', 'prr', 'prs', 'prt', 'pru', 'prw', 'prx', 'prz', 'psa', 'psc',
934
+ 'psd', 'pse', 'psg', 'psh', 'psi', 'psl', 'psm', 'psn', 'pso', 'psp', 'psq', 'psr', 'pss',
935
+ 'pst', 'psu', 'psw', 'psy', 'pta', 'pth', 'pti', 'ptn', 'pto', 'ptp', 'ptr', 'ptt', 'ptu',
936
+ 'ptv', 'ptw', 'pty', 'pua', 'pub', 'pud', 'pue', 'puf', 'pug', 'pui', 'puj', 'pum', 'puo',
937
+ 'pup', 'puq', 'pur', 'pus', 'puu', 'puw', 'pux', 'puy', 'pwa', 'pwb', 'pwg', 'pwi', 'pwm',
938
+ 'pwn', 'pwo', 'pwr', 'pww', 'pye', 'pym', 'pyn', 'pys', 'pyu', 'pyx', 'pyy', 'pzn', 'qua',
939
+ 'qub', 'quc', 'qud', 'quf', 'qug', 'quh', 'qui', 'quk', 'qul', 'qum', 'qun', 'qup', 'qur',
940
+ 'qus', 'quv', 'quw', 'qux', 'quy', 'quz', 'qva', 'qvc', 'qve', 'qvh', 'qvi', 'qvj', 'qvl',
941
+ 'qvm', 'qvn', 'qvo', 'qvp', 'qvs', 'qvw', 'qvy', 'qvz', 'qwa', 'qwc', 'qwh', 'qws', 'qwt',
942
+ 'qxa', 'qxc', 'qxh', 'qxl', 'qxn', 'qxo', 'qxp', 'qxq', 'qxr', 'qxs', 'qxt', 'qxu', 'qxw',
943
+ 'qyp', 'raa', 'rab', 'rac', 'rad', 'raf', 'rag', 'rah', 'rai', 'rak', 'ral', 'ram', 'ran',
944
+ 'rao', 'rap', 'raq', 'rar', 'ras', 'rat', 'rau', 'rav', 'raw', 'rax', 'ray', 'raz', 'rbb',
945
+ 'rbp', 'rcf', 'rdb', 'rea', 'reb', 'ree', 'reg', 'rei', 'rej', 'rel', 'rem', 'ren', 'res',
946
+ 'ret', 'rey', 'rga', 'rge', 'rgk', 'rgn', 'rgr', 'rgs', 'rgu', 'rhg', 'rhp', 'ria', 'rif',
947
+ 'ril', 'rim', 'rin', 'rir', 'rit', 'riu', 'rjg', 'rji', 'rjs', 'rka', 'rkb', 'rkh', 'rki',
948
+ 'rkm', 'rkt', 'rma', 'rmb', 'rmc', 'rme', 'rmf', 'rmh', 'rmi', 'rmk', 'rml', 'rmm', 'rmn',
949
+ 'rmo', 'rmp', 'rmq', 'rms', 'rmt', 'rmw', 'rmx', 'rmy', 'rmz', 'rnd', 'rng', 'rnl', 'rnn',
950
+ 'rnp', 'rnr', 'rnw', 'rob', 'roc', 'rod', 'roe', 'rof', 'rog', 'roh', 'rol', 'rom', 'ron',
951
+ 'roo', 'rop', 'ror', 'rou', 'row', 'rpn', 'rpt', 'rri', 'rro', 'rsl', 'rth', 'rtm', 'rtw',
952
+ 'rub', 'ruc', 'rue', 'ruf', 'rug', 'ruh', 'ruk', 'run', 'ruo', 'rup', 'ruq', 'rus', 'rut',
953
+ 'ruu', 'rwa', 'rwk', 'rwm', 'rwo', 'rwr', 'rxd', 'rxw', 'ryn', 'rys', 'ryu', 'saa', 'sab',
954
+ 'sac', 'sad', 'sae', 'saf', 'sag', 'sah', 'saj', 'sak', 'sam', 'san', 'sao', 'saq', 'sar',
955
+ 'sas', 'sat', 'sau', 'sav', 'saw', 'sax', 'say', 'saz', 'sba', 'sbb', 'sbc', 'sbd', 'sbe',
956
+ 'sbf', 'sbg', 'sbh', 'sbi', 'sbj', 'sbk', 'sbl', 'sbm', 'sbn', 'sbo', 'sbp', 'sbq', 'sbr',
957
+ 'sbs', 'sbt', 'sbu', 'sbv', 'sbw', 'sbx', 'sby', 'sbz', 'scb', 'sce', 'scf', 'scg', 'sch',
958
+ 'sci', 'sck', 'scl', 'scn', 'sco', 'scp', 'scq', 'scs', 'scu', 'scv', 'scw', 'sda', 'sdb',
959
+ 'sdc', 'sde', 'sdf', 'sdg', 'sdh', 'sdj', 'sdk', 'sdl', 'sdm', 'sdn', 'sdo', 'sdp', 'sdr',
960
+ 'sds', 'sdu', 'sdx', 'sdz', 'sea', 'seb', 'sec', 'sed', 'see', 'sef', 'seg', 'seh', 'sei',
961
+ 'sej', 'sek', 'sel', 'sen', 'seo', 'sep', 'seq', 'ser', 'ses', 'set', 'seu', 'sev', 'sew',
962
+ 'sey', 'sez', 'sfb', 'sfe', 'sfm', 'sfs', 'sfw', 'sga', 'sgb', 'sgc', 'sgd', 'sge', 'sgg',
963
+ 'sgh', 'sgi', 'sgj', 'sgk', 'sgm', 'sgp', 'sgr', 'sgs', 'sgt', 'sgu', 'sgw', 'sgx', 'sgy',
964
+ 'sgz', 'sha', 'shb', 'shc', 'shd', 'she', 'shg', 'shh', 'shi', 'shj', 'shk', 'shl', 'shm',
965
+ 'shn', 'sho', 'shp', 'shq', 'shr', 'shs', 'sht', 'shu', 'shv', 'shw', 'shx', 'shy', 'shz',
966
+ 'sia', 'sib', 'sid', 'sie', 'sif', 'sig', 'sih', 'sii', 'sij', 'sil', 'sim', 'sin', 'sip',
967
+ 'siq', 'sir', 'sis', 'siu', 'siv', 'siw', 'six', 'siy', 'siz', 'sja', 'sjb', 'sjd', 'sje',
968
+ 'sjg', 'sjk', 'sjl', 'sjm', 'sjo', 'sjp', 'sjr', 'sjs', 'sjt', 'sju', 'sjw', 'ska', 'skb',
969
+ 'skc', 'skd', 'ske', 'skf', 'skg', 'skh', 'ski', 'skj', 'skk', 'skm', 'skn', 'sko', 'skp',
970
+ 'skq', 'skr', 'sks', 'skt', 'sku', 'skv', 'skw', 'skx', 'sky', 'skz', 'slc', 'sld', 'sle',
971
+ 'slf', 'slg', 'slh', 'sli', 'slk', 'sll', 'slm', 'sln', 'slp', 'slq', 'slr', 'slt', 'slu',
972
+ 'slv', 'slw', 'slx', 'sly', 'slz', 'sma', 'smb', 'smc', 'smd', 'sme', 'smf', 'smg', 'smh',
973
+ 'smj', 'smk', 'sml', 'smm', 'smn', 'smo', 'smp', 'smq', 'smr', 'sms', 'smt', 'smu', 'smv',
974
+ 'smw', 'smx', 'smy', 'smz', 'sna', 'snb', 'snc', 'snd', 'sne', 'snf', 'sng', 'sni', 'snj',
975
+ 'snk', 'snl', 'snm', 'snn', 'sno', 'snp', 'snq', 'snr', 'sns', 'snu', 'snv', 'snw', 'snx',
976
+ 'sny', 'snz', 'soa', 'sob', 'soc', 'sod', 'soe', 'sog', 'soh', 'soi', 'soj', 'sok', 'sol',
977
+ 'som', 'soo', 'sop', 'soq', 'sor', 'sos', 'sot', 'sou', 'sov', 'sow', 'sox', 'soy', 'soz',
978
+ 'spa', 'spb', 'spc', 'spd', 'spe', 'spg', 'spi', 'spk', 'spl', 'spm', 'spn', 'spo', 'spp',
979
+ 'spq', 'spr', 'sps', 'spt', 'spu', 'spv', 'spx', 'spy', 'sqa', 'sqh', 'sqi', 'sqk', 'sqm',
980
+ 'sqn', 'sqo', 'sqq', 'sqs', 'sqt', 'squ', 'sra', 'srb', 'src', 'sre', 'srf', 'srg', 'srh',
981
+ 'sri', 'srk', 'srl', 'srm', 'srn', 'sro', 'srp', 'srq', 'srr', 'srs', 'srt', 'sru', 'srv',
982
+ 'srw', 'srx', 'sry', 'srz', 'ssb', 'ssc', 'ssd', 'sse', 'ssf', 'ssg', 'ssh', 'ssi', 'ssj',
983
+ 'ssk', 'ssl', 'ssm', 'ssn', 'sso', 'ssp', 'ssq', 'ssr', 'sss', 'sst', 'ssu', 'ssv', 'ssw',
984
+ 'ssx', 'ssy', 'ssz', 'stb', 'ste', 'stf', 'stg', 'sti', 'stj', 'stk', 'stl', 'stm', 'stn',
985
+ 'sto', 'stp', 'stq', 'str', 'sts', 'stt', 'stu', 'stv', 'stw', 'sty', 'sua', 'sub', 'suc',
986
+ 'sue', 'sug', 'sui', 'suj', 'suk', 'sun', 'suq', 'sur', 'sus', 'sut', 'suv', 'suw', 'sux',
987
+ 'suy', 'suz', 'sva', 'svb', 'svc', 'sve', 'svk', 'svm', 'svs', 'swa', 'swb', 'swc', 'swe',
988
+ 'swf', 'swg', 'swh', 'swi', 'swj', 'swk', 'swl', 'swm', 'swn', 'swo', 'swp', 'swq', 'swr',
989
+ 'sws', 'swt', 'swu', 'swv', 'sww', 'swx', 'swy', 'sxb', 'sxe', 'sxg', 'sxk', 'sxn', 'sxr',
990
+ 'sxs', 'sxu', 'sxw', 'sya', 'syb', 'syc', 'syi', 'syk', 'syl', 'sym', 'syn', 'syo', 'sys',
991
+ 'syw', 'syx', 'syy', 'sza', 'szb', 'szc', 'sze', 'szg', 'szl', 'szn', 'szp', 'szv', 'szw',
992
+ 'taa', 'tab', 'tac', 'tad', 'tae', 'taf', 'tag', 'tah', 'taj', 'tak', 'tal', 'tam', 'tan',
993
+ 'tao', 'tap', 'taq', 'tar', 'tat', 'tau', 'tav', 'taw', 'tax', 'tay', 'taz', 'tba', 'tbc',
994
+ 'tbd', 'tbe', 'tbf', 'tbg', 'tbh', 'tbi', 'tbj', 'tbk', 'tbl', 'tbm', 'tbn', 'tbo', 'tbp',
995
+ 'tbr', 'tbs', 'tbt', 'tbu', 'tbv', 'tbw', 'tbx', 'tby', 'tbz', 'tca', 'tcb', 'tcc', 'tcd',
996
+ 'tce', 'tcf', 'tcg', 'tch', 'tci', 'tck', 'tcl', 'tcm', 'tcn', 'tco', 'tcp', 'tcq', 'tcs',
997
+ 'tct', 'tcu', 'tcw', 'tcx', 'tcy', 'tcz', 'tda', 'tdb', 'tdc', 'tdd', 'tde', 'tdf', 'tdg',
998
+ 'tdh', 'tdi', 'tdj', 'tdk', 'tdl', 'tdn', 'tdo', 'tdq', 'tdr', 'tds', 'tdt', 'tdu', 'tdv',
999
+ 'tdx', 'tdy', 'tea', 'tec', 'ted', 'tee', 'tef', 'teg', 'teh', 'tei', 'tek', 'tel', 'tem',
1000
+ 'ten', 'teo', 'tep', 'teq', 'ter', 'tes', 'tet', 'teu', 'tev', 'tew', 'tex', 'tey', 'tfi',
1001
+ 'tfn', 'tfo', 'tfr', 'tft', 'tga', 'tgb', 'tgc', 'tgd', 'tge', 'tgf', 'tgh', 'tgi', 'tgj',
1002
+ 'tgk', 'tgl', 'tgn', 'tgo', 'tgp', 'tgq', 'tgs', 'tgt', 'tgu', 'tgw', 'tgx', 'tgy', 'tgz',
1003
+ 'tha', 'thd', 'the', 'thf', 'thh', 'thi', 'thk', 'thl', 'thm', 'thn', 'thp', 'thq', 'thr',
1004
+ 'ths', 'tht', 'thu', 'thv', 'thy', 'thz', 'tia', 'tic', 'tid', 'tif', 'tig', 'tih', 'tii',
1005
+ 'tij', 'tik', 'til', 'tim', 'tin', 'tio', 'tip', 'tiq', 'tir', 'tis', 'tit', 'tiu', 'tiv',
1006
+ 'tiw', 'tix', 'tiy', 'tiz', 'tja', 'tjg', 'tji', 'tjl', 'tjm', 'tjn', 'tjo', 'tjs', 'tju',
1007
+ 'tjw', 'tkb', 'tkd', 'tke', 'tkg', 'tkl', 'tkm', 'tkn', 'tkp', 'tkq', 'tkr', 'tks', 'tkt',
1008
+ 'tku', 'tkv', 'tkw', 'tkx', 'tkz', 'tla', 'tlb', 'tlc', 'tld', 'tlf', 'tlg', 'tli', 'tlj',
1009
+ 'tlk', 'tll', 'tlm', 'tln', 'tlo', 'tlp', 'tlq', 'tlr', 'tls', 'tlt', 'tlu', 'tlv', 'tlx',
1010
+ 'tly', 'tma', 'tmb', 'tmc', 'tmd', 'tmf', 'tmg', 'tmh', 'tmi', 'tmj', 'tml', 'tmm', 'tmn',
1011
+ 'tmo', 'tmp', 'tmq', 'tmr', 'tms', 'tmt', 'tmu', 'tmv', 'tmw', 'tmy', 'tmz', 'tna', 'tnb',
1012
+ 'tnc', 'tnd', 'tne', 'tng', 'tnh', 'tni', 'tnk', 'tnl', 'tnm', 'tnn', 'tno', 'tnp', 'tnq',
1013
+ 'tnr', 'tns', 'tnt', 'tnu', 'tnv', 'tnw', 'tnx', 'tny', 'tnz', 'tob', 'toc', 'tod', 'tof',
1014
+ 'tog', 'toh', 'toi', 'toj', 'tol', 'tom', 'ton', 'too', 'top', 'toq', 'tor', 'tos', 'tou',
1015
+ 'tov', 'tow', 'tox', 'toy', 'toz', 'tpa', 'tpc', 'tpe', 'tpf', 'tpg', 'tpi', 'tpj', 'tpk',
1016
+ 'tpl', 'tpm', 'tpn', 'tpo', 'tpp', 'tpq', 'tpr', 'tpt', 'tpu', 'tpv', 'tpw', 'tpx', 'tpy',
1017
+ 'tpz', 'tqb', 'tql', 'tqm', 'tqn', 'tqo', 'tqp', 'tqq', 'tqr', 'tqt', 'tqu', 'tqw', 'tra',
1018
+ 'trb', 'trc', 'trd', 'tre', 'trf', 'trg', 'trh', 'tri', 'trj', 'trm', 'trn', 'tro', 'trp',
1019
+ 'trq', 'trr', 'trs', 'trt', 'tru', 'trv', 'trw', 'trx', 'try', 'trz', 'tsa', 'tsb', 'tsc',
1020
+ 'tsd', 'tse', 'tsg', 'tsh', 'tsi', 'tsj', 'tsk', 'tsl', 'tsm', 'tsn', 'tso', 'tsp', 'tsq',
1021
+ 'tsr', 'tss', 'tst', 'tsu', 'tsv', 'tsw', 'tsx', 'tsy', 'tsz', 'tta', 'ttb', 'ttc', 'ttd',
1022
+ 'tte', 'ttf', 'ttg', 'tth', 'tti', 'ttj', 'ttk', 'ttl', 'ttm', 'ttn', 'tto', 'ttp', 'ttq',
1023
+ 'ttr', 'tts', 'ttt', 'ttu', 'ttv', 'ttw', 'tty', 'ttz', 'tua', 'tub', 'tuc', 'tud', 'tue',
1024
+ 'tuf', 'tug', 'tuh', 'tui', 'tuj', 'tuk', 'tul', 'tum', 'tun', 'tuo', 'tuq', 'tur', 'tus',
1025
+ 'tuu', 'tuv', 'tux', 'tuy', 'tuz', 'tva', 'tvd', 'tve', 'tvk', 'tvl', 'tvm', 'tvn', 'tvo',
1026
+ 'tvs', 'tvt', 'tvu', 'tvw', 'tvy', 'twa', 'twb', 'twc', 'twd', 'twe', 'twf', 'twg', 'twh',
1027
+ 'twi', 'twl', 'twn', 'two', 'twp', 'twq', 'twr', 'twt', 'twu', 'tww', 'twx', 'twy', 'txa',
1028
+ 'txb', 'txc', 'txe', 'txg', 'txh', 'txi', 'txj', 'txm', 'txn', 'txo', 'txq', 'txs', 'txt',
1029
+ 'txu', 'txx', 'txy', 'tya', 'tye', 'tyh', 'tyi', 'tyj', 'tyn', 'typ', 'tyr', 'tys', 'tyt',
1030
+ 'tyu', 'tyv', 'tyx', 'tyz', 'tza', 'tzh', 'tzj', 'tzm', 'tzn', 'tzo', 'tzx', 'uan', 'uar',
1031
+ 'uba', 'ubi', 'ubl', 'ubr', 'ubu', 'uby', 'uda', 'ude', 'udg', 'udi', 'udj', 'udl', 'udm',
1032
+ 'udu', 'ues', 'ufi', 'uga', 'ugb', 'uge', 'ugn', 'ugo', 'ugy', 'uha', 'uhn', 'uig', 'uis',
1033
+ 'uiv', 'uji', 'uka', 'ukg', 'ukh', 'ukl', 'ukp', 'ukq', 'ukr', 'uks', 'uku', 'ukw', 'uky',
1034
+ 'ula', 'ulb', 'ulc', 'ule', 'ulf', 'uli', 'ulk', 'ull', 'ulm', 'uln', 'ulu', 'ulw', 'uma',
1035
+ 'umb', 'umd', 'umg', 'umi', 'umm', 'umn', 'umo', 'ump', 'umr', 'ums', 'umu', 'una', 'une',
1036
+ 'ung', 'unk', 'unm', 'unn', 'unr', 'unu', 'unz', 'upi', 'upv', 'ura', 'urb', 'urc', 'urd',
1037
+ 'ure', 'urg', 'urh', 'uri', 'urk', 'url', 'urm', 'urn', 'uro', 'urr', 'urt', 'uru', 'urv',
1038
+ 'urw', 'urx', 'ury', 'urz', 'usa', 'ush', 'usi', 'usk', 'usp', 'usu', 'uta', 'ute', 'utp',
1039
+ 'utr', 'utu', 'uum', 'uun', 'uur', 'uuu', 'uve', 'uvh', 'uvl', 'uwa', 'uya', 'uzb', 'uzn',
1040
+ 'uzs', 'vaa', 'vae', 'vaf', 'vag', 'vah', 'vai', 'vaj', 'val', 'vam', 'van', 'vao', 'vap',
1041
+ 'var', 'vas', 'vau', 'vav', 'vay', 'vbb', 'vec', 'ved', 'vel', 'vem', 'ven', 'veo', 'vep',
1042
+ 'ver', 'vgr', 'vgt', 'vic', 'vid', 'vie', 'vif', 'vig', 'vil', 'vin', 'vis', 'vit', 'viv',
1043
+ 'vka', 'vki', 'vkj', 'vkk', 'vkl', 'vkm', 'vko', 'vkp', 'vkt', 'vku', 'vlp', 'vls', 'vma',
1044
+ 'vmb', 'vmc', 'vmd', 'vme', 'vmf', 'vmg', 'vmh', 'vmi', 'vmj', 'vmk', 'vml', 'vmm', 'vmp',
1045
+ 'vmq', 'vmr', 'vmu', 'vmv', 'vmw', 'vmx', 'vmy', 'vmz', 'vnk', 'vnm', 'vnp', 'vor', 'vot',
1046
+ 'vra', 'vro', 'vrs', 'vrt', 'vsi', 'vsl', 'vsv', 'vto', 'vum', 'vun', 'vut', 'vwa', 'waa',
1047
+ 'wab', 'wac', 'wad', 'wae', 'wag', 'wah', 'waj', 'wal', 'wam', 'wan', 'wao', 'wap', 'waq',
1048
+ 'war', 'was', 'wat', 'wau', 'wav', 'waw', 'wax', 'way', 'waz', 'wba', 'wbb', 'wbe', 'wbf',
1049
+ 'wbh', 'wbi', 'wbj', 'wbk', 'wbl', 'wbm', 'wbp', 'wbq', 'wbr', 'wbt', 'wbv', 'wbw', 'wca',
1050
+ 'wci', 'wdd', 'wdg', 'wdj', 'wdk', 'wdu', 'wea', 'wec', 'wed', 'weg', 'weh', 'wei', 'wem',
1051
+ 'weo', 'wep', 'wer', 'wes', 'wet', 'wew', 'wfg', 'wga', 'wgb', 'wgg', 'wgi', 'wgo', 'wgu',
1052
+ 'wgy', 'wha', 'whg', 'whk', 'wib', 'wic', 'wie', 'wig', 'wih', 'wii', 'wij', 'wik', 'wil',
1053
+ 'wim', 'win', 'wir', 'wiu', 'wiv', 'wiy', 'wja', 'wji', 'wka', 'wkd', 'wkl', 'wku', 'wkw',
1054
+ 'wla', 'wlc', 'wle', 'wlg', 'wli', 'wlk', 'wll', 'wln', 'wlo', 'wlr', 'wls', 'wlu', 'wlv',
1055
+ 'wlw', 'wlx', 'wly', 'wmb', 'wmc', 'wmd', 'wme', 'wmh', 'wmi', 'wmm', 'wmn', 'wmo', 'wms',
1056
+ 'wmt', 'wmw', 'wmx', 'wnb', 'wnc', 'wnd', 'wne', 'wng', 'wni', 'wnk', 'wnm', 'wno', 'wnp',
1057
+ 'wnu', 'wnw', 'wny', 'woa', 'wob', 'woc', 'wod', 'woe', 'wof', 'wog', 'woi', 'wok', 'wol',
1058
+ 'wom', 'won', 'woo', 'wor', 'wos', 'wow', 'wpc', 'wrb', 'wrg', 'wrh', 'wri', 'wrk', 'wrl',
1059
+ 'wrm', 'wrn', 'wro', 'wrp', 'wrr', 'wrs', 'wru', 'wrv', 'wrw', 'wrx', 'wry', 'wrz', 'wsa',
1060
+ 'wsi', 'wsk', 'wsr', 'wss', 'wsv', 'wtf', 'wth', 'wti', 'wtk', 'wtm', 'wtw', 'wua', 'wub',
1061
+ 'wud', 'wuh', 'wul', 'wum', 'wun', 'wur', 'wut', 'wuu', 'wuv', 'wux', 'wuy', 'wwa', 'wwo',
1062
+ 'wwr', 'www', 'wxa', 'wxw', 'wya', 'wyb', 'wyi', 'wym', 'wyr', 'wyy', 'xaa', 'xab', 'xac',
1063
+ 'xad', 'xag', 'xal', 'xam', 'xan', 'xap', 'xar', 'xas', 'xat', 'xau', 'xav', 'xaw', 'xay',
1064
+ 'xbc', 'xbe', 'xbg', 'xbi', 'xbj', 'xbn', 'xbo', 'xbp', 'xbr', 'xby', 'xce', 'xcg', 'xch',
1065
+ 'xcl', 'xcm', 'xcn', 'xco', 'xcr', 'xct', 'xcv', 'xcw', 'xcy', 'xda', 'xdc', 'xdk', 'xdm',
1066
+ 'xdy', 'xeb', 'xed', 'xeg', 'xel', 'xem', 'xer', 'xes', 'xet', 'xeu', 'xfa', 'xga', 'xgb',
1067
+ 'xgd', 'xgf', 'xgg', 'xgm', 'xgr', 'xgu', 'xgw', 'xhd', 'xhe', 'xho', 'xhr', 'xht', 'xhu',
1068
+ 'xib', 'xii', 'xir', 'xis', 'xiy', 'xjb', 'xjt', 'xka', 'xkb', 'xkc', 'xkd', 'xke', 'xkf',
1069
+ 'xkg', 'xki', 'xkj', 'xkk', 'xkl', 'xkn', 'xkp', 'xkq', 'xkr', 'xks', 'xkt', 'xku', 'xkv',
1070
+ 'xkw', 'xkx', 'xky', 'xkz', 'xla', 'xlc', 'xld', 'xlo', 'xlp', 'xls', 'xlu', 'xma', 'xmb',
1071
+ 'xmc', 'xmd', 'xmf', 'xmg', 'xmh', 'xmj', 'xml', 'xmm', 'xmp', 'xmr', 'xms', 'xmt', 'xmu',
1072
+ 'xmv', 'xmw', 'xmx', 'xmy', 'xmz', 'xna', 'xnb', 'xng', 'xni', 'xnn', 'xno', 'xnr', 'xns',
1073
+ 'xnt', 'xny', 'xnz', 'xoc', 'xod', 'xog', 'xoi', 'xok', 'xom', 'xon', 'xoo', 'xop', 'xor',
1074
+ 'xow', 'xpa', 'xpc', 'xpe', 'xpg', 'xpj', 'xpk', 'xpm', 'xpo', 'xpq', 'xpr', 'xps', 'xpt',
1075
+ 'xpu', 'xqt', 'xra', 'xrb', 'xrd', 'xre', 'xri', 'xrn', 'xru', 'xrw', 'xsa', 'xsb', 'xsd',
1076
+ 'xse', 'xsh', 'xsi', 'xsl', 'xsm', 'xsn', 'xsp', 'xsq', 'xsr', 'xsu', 'xsy', 'xta', 'xtb',
1077
+ 'xtc', 'xtd', 'xte', 'xtg', 'xth', 'xti', 'xtj', 'xtl', 'xtm', 'xtn', 'xto', 'xtp', 'xtq',
1078
+ 'xts', 'xtt', 'xtu', 'xtv', 'xtw', 'xty', 'xua', 'xub', 'xud', 'xug', 'xuj', 'xul', 'xum',
1079
+ 'xun', 'xuo', 'xup', 'xur', 'xut', 'xuu', 'xve', 'xvi', 'xvo', 'xwa', 'xwc', 'xwd', 'xwe',
1080
+ 'xwg', 'xwj', 'xwk', 'xwl', 'xwr', 'xwt', 'xww', 'xxb', 'xxk', 'xxm', 'xxr', 'xxt', 'xya',
1081
+ 'xyb', 'xyj', 'xyk', 'xyt', 'xyy', 'xzh', 'yaa', 'yab', 'yac', 'yad', 'yae', 'yaf', 'yag',
1082
+ 'yah', 'yai', 'yaj', 'yak', 'yal', 'yam', 'yan', 'yao', 'yap', 'yaq', 'yar', 'yas', 'yat',
1083
+ 'yau', 'yav', 'yaw', 'yay', 'yaz', 'yba', 'ybb', 'ybe', 'ybh', 'ybi', 'ybj', 'ybk', 'ybl',
1084
+ 'ybm', 'ybn', 'ybo', 'ybx', 'yby', 'ych', 'ycl', 'ycn', 'ycp', 'yda', 'ydd', 'yde', 'ydg',
1085
+ 'ydk', 'yea', 'yee', 'yei', 'yej', 'yel', 'yer', 'yes', 'yet', 'yeu', 'yev', 'yey', 'yga',
1086
+ 'ygl', 'ygm', 'ygp', 'ygr', 'ygs', 'ygw', 'yha', 'yhd', 'yhl', 'yia', 'yid', 'yif', 'yig',
1087
+ 'yih', 'yii', 'yij', 'yik', 'yil', 'yim', 'yin', 'yip', 'yiq', 'yir', 'yis', 'yit', 'yiu',
1088
+ 'yiv', 'yix', 'yiz', 'yka', 'ykg', 'yki', 'ykk', 'ykl', 'ykm', 'ykn', 'yko', 'ykr', 'ykt',
1089
+ 'yku', 'yky', 'yla', 'yle', 'ylg', 'yli', 'yll', 'ylm', 'yln', 'ylo', 'ylr', 'ylu', 'yly',
1090
+ 'ymb', 'ymc', 'ymd', 'yme', 'ymh', 'ymi', 'ymk', 'yml', 'ymm', 'ymn', 'ymo', 'ymp', 'ymq',
1091
+ 'ymr', 'yms', 'ymx', 'ymz', 'yna', 'ynd', 'yng', 'ynk', 'ynl', 'ynn', 'yno', 'ynq', 'yns',
1092
+ 'ynu', 'yob', 'yog', 'yoi', 'yok', 'yol', 'yom', 'yon', 'yor', 'yot', 'yox', 'yoy', 'ypa',
1093
+ 'ypb', 'ypg', 'yph', 'ypm', 'ypn', 'ypo', 'ypp', 'ypz', 'yra', 'yrb', 'yre', 'yrk', 'yrl',
1094
+ 'yrm', 'yrn', 'yrw', 'yry', 'ysd', 'ysg', 'ysl', 'ysn', 'yso', 'ysr', 'yss', 'ysy', 'yta',
1095
+ 'ytl', 'ytp', 'ytw', 'yua', 'yub', 'yuc', 'yud', 'yue', 'yuf', 'yug', 'yui', 'yuj', 'yuk',
1096
+ 'yul', 'yum', 'yun', 'yup', 'yuq', 'yur', 'yut', 'yuw', 'yux', 'yuy', 'yuz', 'yva', 'yvt',
1097
+ 'ywa', 'ywg', 'ywl', 'ywn', 'ywq', 'ywr', 'ywt', 'ywu', 'yww', 'yxg', 'yxl', 'yxm', 'yxu',
1098
+ 'yxy', 'yyr', 'yyu', 'yyz', 'yzg', 'yzk', 'zaa', 'zab', 'zac', 'zad', 'zae', 'zaf', 'zag',
1099
+ 'zah', 'zai', 'zaj', 'zak', 'zal', 'zam', 'zao', 'zap', 'zaq', 'zar', 'zas', 'zat', 'zau',
1100
+ 'zav', 'zaw', 'zax', 'zay', 'zaz', 'zbc', 'zbe', 'zbt', 'zbw', 'zca', 'zch', 'zdj', 'zea',
1101
+ 'zeg', 'zeh', 'zen', 'zga', 'zgb', 'zgh', 'zgm', 'zgn', 'zgr', 'zha', 'zhb', 'zhd', 'zhi',
1102
+ 'zhn', 'zhw', 'zia', 'zib', 'zik', 'zil', 'zim', 'zin', 'zir', 'ziw', 'ziz', 'zka', 'zkb',
1103
+ 'zkd', 'zkk', 'zkn', 'zko', 'zkp', 'zkr', 'zkt', 'zku', 'zlj', 'zlm', 'zln', 'zlq', 'zma',
1104
+ 'zmb', 'zmc', 'zmd', 'zme', 'zmf', 'zmg', 'zmh', 'zmi', 'zmj', 'zmk', 'zml', 'zmm', 'zmn',
1105
+ 'zmo', 'zmp', 'zmq', 'zmr', 'zms', 'zmt', 'zmu', 'zmv', 'zmw', 'zmx', 'zmy', 'zmz', 'zna',
1106
+ 'zne', 'zng', 'zns', 'zoc', 'zoh', 'zom', 'zoo', 'zoq', 'zor', 'zos', 'zpa', 'zpb', 'zpc',
1107
+ 'zpd', 'zpe', 'zpf', 'zpg', 'zph', 'zpi', 'zpj', 'zpk', 'zpl', 'zpm', 'zpn', 'zpo', 'zpp',
1108
+ 'zpq', 'zpr', 'zps', 'zpt', 'zpu', 'zpv', 'zpw', 'zpx', 'zpy', 'zpz', 'zqe', 'zrn', 'zro',
1109
+ 'zrs', 'zsa', 'zsl', 'zsm', 'zsr', 'zsu', 'zte', 'ztg', 'ztl', 'ztm', 'ztn', 'ztp', 'ztq',
1110
+ 'zts', 'ztt', 'ztu', 'ztx', 'zty', 'zua', 'zuh', 'zul', 'zum', 'zun', 'zuy', 'zwa', 'zyb',
1111
+ 'zyg', 'zyj', 'zyn', 'zyp', 'zza', 'zzj'
1112
+ ]
1113
+
1114
+ def __init__(self):
1115
+ super().__init__(Alphabet.IPA)
1116
+ try:
1117
+ from transphone import read_tokenizer
1118
+ except ImportError as e:
1119
+ raise ImportError(
1120
+ "transphone is required for the Transphone phonemizer. "
1121
+ "Install it with 'pip install transphone' "
1122
+ "(or 'pip install scriptconv[transphone]')."
1123
+ ) from e
1124
+ self.read_tokenizer = read_tokenizer
1125
+ self._models = {}
1126
+
1127
+ @classmethod
1128
+ def get_lang(cls, target_lang: str) -> str:
1129
+ """
1130
+ Validates and returns the closest supported language code.
1131
+
1132
+ Args:
1133
+ target_lang (str): The language code to validate.
1134
+
1135
+ Returns:
1136
+ str: The validated language code.
1137
+
1138
+ Raises:
1139
+ ValueError: If the language code is unsupported.
1140
+ """
1141
+ if target_lang.lower() in cls.TRANSPHONE_LANGS:
1142
+ return target_lang
1143
+ if _is_hk_cantonese(target_lang):
1144
+ return "yue"
1145
+ return cls.match_lang(target_lang, cls.TRANSPHONE_LANGS)
1146
+
1147
+ def phonemize_string(self, text: str, lang: str) -> str:
1148
+ lang = self.get_lang(lang)
1149
+ pho = self._models.get(lang)
1150
+ if pho is None:
1151
+ self._models[lang] = pho = self.read_tokenizer(lang)
1152
+ return "".join(
1153
+ [p if p != "<SPACE>" else " "
1154
+ for p in pho.tokenize(text, use_space=True)]
1155
+ ).strip()
1156
+
1157
+