Spaces:
Paused
Paused
File size: 5,521 Bytes
68f10e1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | #!/usr/bin/env python3
"""Normalize Devanagari text for Sanskrit TTS.
Operates on saṃhitā Devanagari from the Anuvyakhyana TTS metadata CSVs.
Output is Devanagari (+ Vedic extension chars ᳵ ᳶ ँ) ready for SLP1 G2P.
Rules:
1. Strip editorial bracketing: ( ) " " ' ' - and ZWNJ. Inner text is kept (all recited).
2. Strip verse-number blocks (e.g., trailing " १॥") and stray Devanagari/ASCII numerals.
3. Expand ॐ → ओम्.
4. Visarga before unvoiced k-varga (क ख) → jihvāmūlīya ᳵ.
Visarga before unvoiced p-varga (प फ) → upadhmānīya ᳶ.
5. Anusvāra before a varga consonant → homorganic nasal + halant.
Anusvāra before य → chandrabindu ँ (G2P will render as nasalized-y).
Anusvāra before {र ल व श ष स ह} → keep as anusvāra (nasalized continuant).
Anusvāra at word-end / before । ॥ → म् (proper /m/).
"""
import re
KVARGA_UNVOICED = 'कख'
PVARGA_UNVOICED = 'पफ'
KVARGA = 'कखगघङ'
CVARGA = 'चछजझञ'
TVARGA_RETRO = 'टठडढण'
TVARGA_DENT = 'तथदधन'
PVARGA = 'पफबभम'
VARGA_TO_NASAL = {}
for c in KVARGA: VARGA_TO_NASAL[c] = 'ङ्'
for c in CVARGA: VARGA_TO_NASAL[c] = 'ञ्'
for c in TVARGA_RETRO: VARGA_TO_NASAL[c] = 'ण्'
for c in TVARGA_DENT: VARGA_TO_NASAL[c] = 'न्'
for c in PVARGA: VARGA_TO_NASAL[c] = 'म्'
VARGA_ALL = set(KVARGA + CVARGA + TVARGA_RETRO + TVARGA_DENT + PVARGA)
NON_VARGA_KEEP = set('रलवशषसह')
JIHVAMULIYA = 'ᳵ'
UPADHMANIYA = 'ᳶ'
CHANDRABINDU = 'ँ'
VISARGA = 'ः'
ANUSVARA = 'ं'
STRIP_CHARS = set('()""\'\'“”‘’-')
WORD_END = set(' \t\n।॥')
def strip_editorial(text: str) -> str:
return ''.join(c for c in text if c not in STRIP_CHARS)
def strip_numerals(text: str) -> str:
# Verse-number block: optional ws + digits + optional ws + closing danda(s)
text = re.sub(r'\s*[०-९0-9]+\s*[।॥]+', '॥', text)
# Any stray digits
text = re.sub(r'[०-९0-9]+', '', text)
return text
def expand_om(text: str) -> str:
return text.replace('ॐ', 'ओम्')
def rewrite_visarga(text: str) -> str:
text = re.sub(f'{VISARGA}([{KVARGA_UNVOICED}])', JIHVAMULIYA + r'\1', text)
text = re.sub(f'{VISARGA}([{PVARGA_UNVOICED}])', UPADHMANIYA + r'\1', text)
return text
def rewrite_anusvara(text: str) -> str:
out = []
n = len(text)
i = 0
while i < n:
ch = text[i]
if ch == ANUSVARA:
nxt = text[i+1] if i+1 < n else ''
if nxt in VARGA_ALL:
out.append(VARGA_TO_NASAL[nxt])
elif nxt == 'य':
out.append(CHANDRABINDU)
elif nxt in NON_VARGA_KEEP:
out.append(ANUSVARA)
elif nxt == '' or nxt in WORD_END:
out.append('म्')
else:
out.append(ANUSVARA)
else:
out.append(ch)
i += 1
return ''.join(out)
def collapse_ws(text: str) -> str:
text = re.sub(r'\s+', ' ', text)
# Collapse repeated danda groups left by verse-number stripping
text = re.sub(r'(॥)(\s*॥)+', '॥', text)
text = re.sub(r'(।)(\s*।)+', '।', text)
return text.strip()
def normalize(text: str) -> str:
text = strip_editorial(text)
text = strip_numerals(text)
text = expand_om(text)
text = rewrite_visarga(text)
text = rewrite_anusvara(text)
text = collapse_ws(text)
return text
if __name__ == '__main__':
import argparse, csv, sys
ap = argparse.ArgumentParser()
ap.add_argument('--input', help='metadata CSV path; if omitted, run built-in samples')
ap.add_argument('--limit', type=int, default=10)
args = ap.parse_args()
if not args.input:
samples = [
'ॐ॥ नारायणं निखिलपूर्णगुणैकदेहं निर्दोषमाप्यतममप्यखिलैः सुवाक्यैः। अस्योद्भवादिदमशेषविशेषतोऽपि वन्द्यं सदा प्रियतमं मम सन्नमामि॥ १॥',
'(॥ ॐ अथातो ब्रह्मजिज्ञासा ॐ॥) ओतत्ववाची ह्योङ्कारः शास्त्रादौ॥ ९॥',
'"द्रव्यं कर्म च कालश्च स्वभावो जीव एव च। यदनुग्रहतः सन्ति न सन्ति यदुपेक्षया॥" १३॥',
'तमेव शास्त्रप्रभवं प्रणम्य जगद्गुरूणां गुरुमञ्जसैव। विशेषतो मे परमाख्यविद्याव्याख्यां करोम्यन्वपि चाहमेव॥ २॥',
'संयोगः संस्कारः अंक पञ्च रङ्ग दुःख दुःप्रसह वाक्क',
]
for s in samples:
print('IN: ', s)
print('OUT:', normalize(s))
print()
sys.exit(0)
with open(args.input) as f:
r = csv.DictReader(f, delimiter='|')
for i, row in enumerate(r):
if i >= args.limit: break
t = row['text']
print(f'#{row["shloka_no"]}')
print(' IN: ', t)
print(' OUT:', normalize(t))
print()
|