From 0d74e50bbc5089fe2ffd782357ac4267d1edeaf4 Mon Sep 17 00:00:00 2001 From: dev Date: Sun, 12 Jul 2026 21:58:47 +0100 Subject: [PATCH] fix(normalizer): expand wh-word 're contractions espeak voices as -ray --- .../services/text_processing/normalizer.py | 11 ++++++++ api/tests/test_normalizer.py | 27 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/api/src/services/text_processing/normalizer.py b/api/src/services/text_processing/normalizer.py index 13d76763..9db89233 100644 --- a/api/src/services/text_processing/normalizer.py +++ b/api/src/services/text_processing/normalizer.py @@ -410,6 +410,17 @@ def handle_time(t: re.Match[str]) -> str: def normalize_text(text: str, normalization_options: NormalizationOptions) -> str: """Normalize text for TTS processing""" + # Expand the "'re" contractions that espeak mis-phonemizes with a spurious + # /ɹeɪ/ ("-ray") ending, e.g. "how're" -> /haʊɹeɪ/ which sounds like "harry". + # Only the wh-words and there/these/those break this way; "you're", "we're" + # and "they're" already phonemize correctly and are left untouched. + text = re.sub( + r"\b(how|what|where|who|when|why|there|these|those)['’]re\b", + r"\1 are", + text, + flags=re.IGNORECASE, + ) + # Handle email addresses first if enabled if normalization_options.email_normalization: text = EMAIL_PATTERN.sub(handle_email, text) diff --git a/api/tests/test_normalizer.py b/api/tests/test_normalizer.py index 35f72fc8..5fff4df1 100644 --- a/api/tests/test_normalizer.py +++ b/api/tests/test_normalizer.py @@ -334,3 +334,30 @@ def test_remaining_symbol(): ) == "I love buying products at good store here and at other store" ) + + +def test_re_contraction_expansion(): + """Wh-word "'re" contractions are expanded so espeak does not voice a + spurious "-ray" ending (e.g. "how're" was phonemized like "harry").""" + assert ( + normalize_text( + "Hello there, how're you doing this fine day?", + normalization_options=NormalizationOptions(), + ) + == "Hello there, how are you doing this fine day?" + ) + assert ( + normalize_text( + "What're these and where're they going?", + normalization_options=NormalizationOptions(), + ) + == "What are these and where are they going?" + ) + # Contractions that already phonemize correctly must be left untouched. + assert ( + normalize_text( + "You're sure we're not late and they're here?", + normalization_options=NormalizationOptions(), + ) + == "You're sure we're not late and they're here?" + )