| |
| import re |
|
|
| |
| VOWELS = { |
| 'अ': 'a', |
| 'आ': 'aː', |
| 'इ': 'i', |
| 'ई': 'iː', |
| 'उ': 'u', |
| 'ऊ': 'uː', |
| 'ऋ': 'ɾɪ', |
| 'ॠ': 'ɾiː', |
| 'ऌ': 'lɪ', |
| 'ए': 'e', |
| 'ऐ': 'aɪ', |
| 'ओ': 'o', |
| 'औ': 'aʊ' |
| } |
|
|
| |
| VOWEL_SIGNS = { |
| 'ा': 'aː', |
| 'ि': 'i', |
| 'ी': 'iː', |
| 'ु': 'u', |
| 'ू': 'uː', |
| 'ृ': 'ɾɪ', |
| 'ॄ': 'ɾiː', |
| 'ॢ': 'lɪ', |
| 'े': 'e', |
| 'ै': 'aɪ', |
| 'ो': 'o', |
| 'ौ': 'aʊ' |
| } |
|
|
| |
| CONSONANTS = { |
| 'क': 'k', 'ख': 'kʰ', 'ग': 'ɡ', 'घ': 'ɡʰ', 'ङ': 'ŋ', |
| 'च': 'tʃ', 'छ': 'tʃʰ', 'ज': 'dʒ', 'झ': 'dʒʰ', 'ञ': 'ɲ', |
| 'ट': 'ʈ', 'ठ': 'ʈʰ', 'ड': 'ɖ', 'ढ': 'ɖʰ', 'ण': 'ɳ', |
| 'त': 't', 'थ': 'tʰ', 'द': 'd', 'ध': 'dʰ', 'न': 'n', |
| 'प': 'p', 'फ': 'pʰ', 'ब': 'b', 'भ': 'bʰ', 'म': 'm', |
| 'य': 'j', 'र': 'ɾ', 'ल': 'l', 'व': 'v', |
| 'श': 'ʃ', 'ष': 'ʂ', 'स': 's', 'ह': 'h', |
| 'ळ': 'ɭ' |
| } |
|
|
| VIRAMA = '्' |
| ANUSVARA = 'ं' |
| VISARGA = 'ः' |
| AVAGRAHA = 'ऽ' |
|
|
| |
| VARGAS = { |
| 'velar': (set('कखगघङ'), 'ŋ'), |
| 'palatal': (set('चछजझञ'), 'ɲ'), |
| 'retroflex': (set('टठडढण'), 'ɳ'), |
| 'dental': (set('तथदधन'), 'n'), |
| 'labial': (set('पफबभम'), 'm') |
| } |
|
|
| def get_homorganic_nasal(text, index): |
| |
| for i in range(index + 1, len(text)): |
| char = text[i] |
| if char in CONSONANTS: |
| |
| for varga_name, (chars, nasal) in VARGAS.items(): |
| if char in chars: |
| return nasal |
| break |
| elif char in (' ', '\n', '\t', '।', '॥', ',', '.'): |
| |
| break |
| return 'm' |
|
|
| def get_visarga_echo(prev_vowel_ipa): |
| if not prev_vowel_ipa: |
| return 'ha' |
| |
| |
| core_vowel = prev_vowel_ipa.replace('ː', '').strip() |
| if 'a' in core_vowel: |
| return 'ha' |
| elif 'i' in core_vowel: |
| return 'hi' |
| elif 'u' in core_vowel: |
| return 'hu' |
| elif 'e' in core_vowel: |
| return 'he' |
| elif 'o' in core_vowel: |
| return 'ho' |
| else: |
| return 'ha' |
|
|
| def devanagari_to_ipa(text): |
| """ |
| Converts Devanagari Sanskrit text directly to Kokoro-compatible IPA phonemes, |
| preserving short vowels (no schwa deletion), and applying visarga and anusvara sandhi. |
| """ |
| |
| text = re.sub(r'\s+', ' ', text).strip() |
| |
| ipa_out = [] |
| i = 0 |
| n = len(text) |
| |
| |
| last_vowel_ipa = 'a' |
| |
| while i < n: |
| char = text[i] |
| |
| |
| if char in VOWELS: |
| v_ipa = VOWELS[char] |
| ipa_out.append(v_ipa) |
| last_vowel_ipa = v_ipa |
| i += 1 |
| |
| |
| elif char in CONSONANTS: |
| c_ipa = CONSONANTS[char] |
| |
| has_vowel_sign = False |
| has_virama = False |
| |
| |
| if i + 1 < n: |
| nxt = text[i + 1] |
| if nxt == VIRAMA: |
| has_virama = True |
| ipa_out.append(c_ipa) |
| i += 2 |
| elif nxt in VOWEL_SIGNS: |
| has_vowel_sign = True |
| v_ipa = VOWEL_SIGNS[nxt] |
| ipa_out.append(c_ipa + v_ipa) |
| last_vowel_ipa = v_ipa |
| i += 2 |
| |
| if not has_virama and not has_vowel_sign: |
| |
| ipa_out.append(c_ipa + 'a') |
| last_vowel_ipa = 'a' |
| i += 1 |
| |
| |
| elif char == ANUSVARA: |
| |
| nasal = get_homorganic_nasal(text, i) |
| ipa_out.append(nasal) |
| i += 1 |
| |
| |
| elif char == VISARGA: |
| |
| echo = get_visarga_echo(last_vowel_ipa) |
| ipa_out.append(echo) |
| i += 1 |
| |
| |
| elif char == AVAGRAHA: |
| ipa_out.append('ː') |
| i += 1 |
| |
| |
| else: |
| if char in (' ', '\n', '\t'): |
| ipa_out.append(' ') |
| elif char in ('।', '॥'): |
| ipa_out.append('.') |
| elif char in (',', ';', '.', '!', '?'): |
| ipa_out.append(char) |
| |
| i += 1 |
| |
| |
| ipa_str = "".join(ipa_out) |
| |
| ipa_str = re.sub(r' +', ' ', ipa_str) |
| return ipa_str |
|
|
| |
| if __name__ == '__main__': |
| test_phrases = [ |
| "नमः शिवाय", |
| "धर्मक्षेत्रे कुरुक्षेत्रे", |
| "रामः गच्छति", |
| "सच्चिदानन्द", |
| "श्रीमद्भगवद्गीता" |
| ] |
| for phrase in test_phrases: |
| print(f"Deva: {phrase}") |
| print(f"IPA: {devanagari_to_ipa(phrase)}") |
| print("-" * 40) |
|
|