From 4ff09be66454cb50792553110ab56a32d6109b93 Mon Sep 17 00:00:00 2001 From: Artem Akymenko Date: Thu, 16 Jul 2026 09:56:49 +0000 Subject: [PATCH] =?UTF-8?q?refactor(pyt):=20add=20text=20normalization=20v?= =?UTF-8?q?ia=20prepare=5Ftext=5Ffor=5Ftts=20=E2=80=94=20P0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PyQt desktop GUI now calls the shared normalization pipeline before TTS synthesis, matching the Web UI's behavior: 1. Heteronym sentence rules (context-dependent pronunciation) 2. Pronunciation rules (token-level replacements) 3. Pipeline normalization (apostrophe handling, LLM) Before: PyQt passed raw text to the backend — no normalization at all, resulting in inferior audio quality compared to the Web UI. The normalization rules are compiled once at the start of run() from pronunciation_overrides and heteronym_overrides (currently None since the PyQt GUI doesn't expose these settings yet — basic apostrophe normalization still applies). 1053 tests pass. --- abogen/pyqt/conversion.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/abogen/pyqt/conversion.py b/abogen/pyqt/conversion.py index 7ef291b..75604b0 100644 --- a/abogen/pyqt/conversion.py +++ b/abogen/pyqt/conversion.py @@ -38,6 +38,7 @@ from abogen.domain.audio_buffer import ( from abogen.domain.subtitle_generation import process_subtitle_tokens from abogen.domain.voice_loader import load_voice_cached from abogen.domain.progress import calc_etr_str +from abogen.domain.normalization import prepare_text_for_tts from abogen.domain.metadata_extraction import ( extract_metadata_and_build_args, read_text_for_metadata, @@ -535,6 +536,22 @@ class ConversionThread(QThread): fix_punct, ) + # --- Compile normalization rules (heteronym + pronunciation) --- + from abogen.domain.pronunciation import ( + compile_pronunciation_rules, + compile_heteronym_sentence_rules, + merge_pronunciation_overrides, + ) + pronunciation_overrides = merge_pronunciation_overrides( + getattr(self, "pronunciation_overrides", None), + getattr(self, "manual_overrides", None), + ) + self._pronunciation_rules = compile_pronunciation_rules(pronunciation_overrides) + self._heteronym_rules = compile_heteronym_sentence_rules( + getattr(self, "heteronym_overrides", None) + ) + self._usage_counter = {} + # --- Chapter splitting logic --- # Use pre-compiled pattern for better performance chapter_splits = list(_CHAPTER_MARKER_SEARCH_PATTERN.finditer(text)) @@ -1092,6 +1109,21 @@ class ConversionThread(QThread): print("Using split pattern: (unprintable)") for text_segment in text_segments: + # Normalize text through the shared pipeline + # (heteronym + pronunciation + apostrophe normalization) + try: + text_segment = prepare_text_for_tts( + text_segment, + heteronym_rules=getattr(self, "_heteronym_rules", None), + pronunciation_rules=getattr(self, "_pronunciation_rules", None), + normalization_overrides=getattr(self, "normalization_overrides", None), + usage_counter=getattr(self, "_usage_counter", None), + ) + except Exception as exc: + self.log_updated.emit( + (f"Warning: Text normalization failed: {exc}", "orange") + ) + for result in self.backend( text_segment, voice=loaded_voice,