3v324v23 commited on
Commit
bae73bb
·
1 Parent(s): 5a7335d

fix: implement pronunciation adjustments for Palestinian past forms in TTS

Browse files
Files changed (3) hide show
  1. app/main.py +2 -2
  2. app/tts.py +23 -0
  3. tests/test_scoring.py +7 -0
app/main.py CHANGED
@@ -34,7 +34,7 @@ from .reference import References
34
  from .scorer import Scorer, decode_audio, load_scorer
35
  from .speech_evaluation import SpeechEvaluationService
36
  from .texts import read_line, text_phrases
37
- from .tts import VOICES, SawtakTTS, TTSUnavailable, load_tts
38
  from .verb_game import VerbExerciseGenerator
39
 
40
  logging.basicConfig(level=logging.INFO)
@@ -1127,7 +1127,7 @@ def create_app(scorer: Scorer | None = None, tts: SawtakTTS | None = None,
1127
  names = {i: n for n, i in VOICES.items()}
1128
  if voice and voice not in names:
1129
  raise HTTPException(400, "unknown voice")
1130
- text = phrase_of(phrase_id)["variants"][0][0]
1131
  if app.state.tts is None:
1132
  raise HTTPException(503, "tts not configured")
1133
  try:
 
34
  from .scorer import Scorer, decode_audio, load_scorer
35
  from .speech_evaluation import SpeechEvaluationService
36
  from .texts import read_line, text_phrases
37
+ from .tts import VOICES, SawtakTTS, TTSUnavailable, load_tts, pronunciation_text
38
  from .verb_game import VerbExerciseGenerator
39
 
40
  logging.basicConfig(level=logging.INFO)
 
1127
  names = {i: n for n, i in VOICES.items()}
1128
  if voice and voice not in names:
1129
  raise HTTPException(400, "unknown voice")
1130
+ text = pronunciation_text(phrase_of(phrase_id)["variants"][0][0])
1131
  if app.state.tts is None:
1132
  raise HTTPException(503, "tts not configured")
1133
  try:
app/tts.py CHANGED
@@ -14,6 +14,7 @@ import io
14
  import json
15
  import logging
16
  import os
 
17
  import threading
18
  import urllib.error
19
  import urllib.parse
@@ -33,6 +34,28 @@ VOICES = {
33
  "חנאן": "e41781f5-127c-5eb0-b83c-50bf52a64caf",
34
  }
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  class TTSUnavailable(Exception):
38
  """No key configured, or the provider refused / failed. The message is safe to show."""
 
14
  import json
15
  import logging
16
  import os
17
+ import re
18
  import threading
19
  import urllib.error
20
  import urllib.parse
 
34
  "חנאן": "e41781f5-127c-5eb0-b83c-50bf52a64caf",
35
  }
36
 
37
+ # Unvowelled three-letter Palestinian past forms are often read by a TTS as MSA
38
+ # (for example سمع as samaʿa). Keep the course spelling untouched, but give the
39
+ # voice the short vowels taught in the workbook's fiʿil pattern.
40
+ _PAST_FIIL = {
41
+ "شرب": "شِرِب", "طلع": "طِلِع", "سمع": "سِمِع", "فهم": "فِهِم", "رجع": "رِجِع",
42
+ "نزل": "نِزِل", "وصل": "وِصِل", "ضحك": "ضِحِك", "لبس": "لِبِس", "لعب": "لِعِب",
43
+ }
44
+ _ARABIC_WORD = re.compile(r"[\u0621-\u064A\u066E-\u06D3]+")
45
+
46
+
47
+ def pronunciation_text(text: str) -> str:
48
+ """Add only pronunciation-critical vowels for TTS; never changes displayed Arabic."""
49
+ def voiced(match: re.Match[str]) -> str:
50
+ word = match.group()
51
+ if word in _PAST_FIIL:
52
+ return _PAST_FIIL[word]
53
+ if word[:1] in ("و", "ف") and word[1:] in _PAST_FIIL:
54
+ return word[0] + _PAST_FIIL[word[1:]]
55
+ return word
56
+
57
+ return _ARABIC_WORD.sub(voiced, text)
58
+
59
 
60
  class TTSUnavailable(Exception):
61
  """No key configured, or the provider refused / failed. The message is safe to show."""
tests/test_scoring.py CHANGED
@@ -213,6 +213,13 @@ def test_tts_endpoint_serves_phrase_audio_and_503_without_key():
213
  assert c.get("/api/health").json()["tts"] is True
214
 
215
 
 
 
 
 
 
 
 
216
  def test_spelling_variants_of_a_sound_are_not_errors():
217
  # the multilingual vocab spells one sound several ways; the model saying t̪ / ɾ / æ for t / r / a is not a mistake
218
  syms = SYMS + ["t", "t̪", "tː", "r", "ɾ", "æ", "b"]
 
213
  assert c.get("/api/health").json()["tts"] is True
214
 
215
 
216
+ def test_tts_vowels_palestinian_fiil_past_without_changing_other_words():
217
+ from app.tts import pronunciation_text
218
+
219
+ assert pronunciation_text("هوّ سمع موسيقى، وبعدين رجع ولعب") == "هوّ سِمِع موسيقى، وبعدين رِجِع ولِعِب"
220
+ assert pronunciation_text("سمعت سمعو يسمع") == "سمعت سمعو يسمع"
221
+
222
+
223
  def test_spelling_variants_of_a_sound_are_not_errors():
224
  # the multilingual vocab spells one sound several ways; the model saying t̪ / ɾ / æ for t / r / a is not a mistake
225
  syms = SYMS + ["t", "t̪", "tː", "r", "ɾ", "æ", "b"]