mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-22 07:10:28 +02:00
refactor: extract TTSContext dataclass for normalization parameters (#5)
Bundles pronunciation_rules, heteronym_rules, normalization_overrides, usage_counter, and split_pattern into a single TTSContext dataclass. Both UIs create it once and use tts_context.normalize() instead of threading 5 separate parameters through prepare_text_for_tts calls. Tests: 1253 passed
This commit is contained in:
@@ -5,10 +5,14 @@ and the comprehensive ``prepare_text_for_tts`` that chains all three normalizati
|
||||
stages used during conversion: heteronym rules → pronunciation rules → pipeline
|
||||
normalization. The latter is the single entry point that both the Web UI and
|
||||
PyQt Desktop GUI should use.
|
||||
|
||||
Also provides ``TTSContext`` — a dataclass bundling all pre-compiled normalization
|
||||
resources so they can be created once and passed as a single object.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Mapping, Optional
|
||||
|
||||
from abogen.kokoro_text_normalization import (
|
||||
@@ -24,6 +28,31 @@ from abogen.normalization_settings import (
|
||||
_BASE_APOSTROPHE_CONFIG = ApostropheConfig()
|
||||
|
||||
|
||||
@dataclass
|
||||
class TTSContext:
|
||||
"""Bundles pre-compiled normalization resources for TTS processing.
|
||||
|
||||
Created once per conversion job and passed to ``prepare_text_for_tts``
|
||||
instead of threading 5 separate parameters.
|
||||
"""
|
||||
|
||||
split_pattern: str = r"(?<=[.!?\-])\s+"
|
||||
pronunciation_rules: Optional[List[Dict[str, Any]]] = None
|
||||
heteronym_rules: Optional[List[Dict[str, Any]]] = None
|
||||
normalization_overrides: Optional[Mapping[str, Any]] = None
|
||||
usage_counter: Dict[str, int] = field(default_factory=dict)
|
||||
|
||||
def normalize(self, text: str) -> str:
|
||||
"""Shorthand: normalize text using this context's compiled rules."""
|
||||
return prepare_text_for_tts(
|
||||
text,
|
||||
heteronym_rules=self.heteronym_rules,
|
||||
pronunciation_rules=self.pronunciation_rules,
|
||||
normalization_overrides=self.normalization_overrides,
|
||||
usage_counter=self.usage_counter,
|
||||
)
|
||||
|
||||
|
||||
def normalize_text_for_pipeline(
|
||||
text: str,
|
||||
*,
|
||||
|
||||
+10
-12
@@ -47,7 +47,7 @@ from abogen.domain.audio_buffer import (
|
||||
from abogen.domain.subtitle_generation import process_subtitle_tokens
|
||||
from abogen.domain.voice_loader import VoiceCache, load_voice_cached, resolve_voice
|
||||
from abogen.domain.progress import calc_etr_str
|
||||
from abogen.domain.normalization import prepare_text_for_tts
|
||||
from abogen.domain.normalization import TTSContext
|
||||
from abogen.domain.pronunciation import (
|
||||
compile_pronunciation_rules,
|
||||
compile_heteronym_sentence_rules,
|
||||
@@ -566,15 +566,19 @@ class ConversionThread(QThread):
|
||||
)
|
||||
|
||||
# --- Compile normalization rules (heteronym + pronunciation) ---
|
||||
from abogen.domain.normalization import TTSContext
|
||||
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._tts_context = TTSContext(
|
||||
split_pattern=self.split_pattern,
|
||||
pronunciation_rules=compile_pronunciation_rules(pronunciation_overrides),
|
||||
heteronym_rules=compile_heteronym_sentence_rules(
|
||||
getattr(self, "heteronym_overrides", None)
|
||||
),
|
||||
normalization_overrides=getattr(self, "normalization_overrides", None),
|
||||
)
|
||||
self._usage_counter = {}
|
||||
|
||||
# --- Chapter splitting logic ---
|
||||
chapters = parse_chapters_from_text(text, clean=False)
|
||||
@@ -980,13 +984,7 @@ class ConversionThread(QThread):
|
||||
for text_segment in text_segments:
|
||||
# Normalize text before TTS
|
||||
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),
|
||||
)
|
||||
text_segment = self._tts_context.normalize(text_segment)
|
||||
except Exception as exc:
|
||||
self.log_updated.emit(
|
||||
(f"Warning: Text normalization failed: {exc}", "orange")
|
||||
|
||||
@@ -70,7 +70,7 @@ from abogen.domain.pronunciation import (
|
||||
apply_pronunciation_rules as _apply_pronunciation_rules,
|
||||
merge_pronunciation_overrides as _merge_pronunciation_overrides,
|
||||
)
|
||||
from abogen.domain.normalization import prepare_text_for_tts
|
||||
from abogen.domain.normalization import TTSContext
|
||||
from abogen.domain.voice_resolution import (
|
||||
spec_to_voice_ids as _spec_to_voice_ids,
|
||||
job_voice_fallback as _job_voice_fallback,
|
||||
@@ -242,6 +242,14 @@ def run_conversion_job(job: Job) -> None:
|
||||
f"Applying {count} pronunciation override{'s' if count != 1 else ''} during conversion.",
|
||||
level="debug",
|
||||
)
|
||||
|
||||
tts_context = TTSContext(
|
||||
split_pattern=job_split_pattern,
|
||||
pronunciation_rules=pronunciation_rules,
|
||||
heteronym_rules=heteronym_sentence_rules,
|
||||
normalization_overrides=getattr(job, "normalization_overrides", None),
|
||||
usage_counter=usage_counter,
|
||||
)
|
||||
for override_entry in pronunciation_overrides or []:
|
||||
if not isinstance(override_entry, Mapping):
|
||||
continue
|
||||
@@ -415,16 +423,10 @@ def run_conversion_job(job: Job) -> None:
|
||||
) -> int:
|
||||
nonlocal processed_chars, current_time
|
||||
if split_pattern is None:
|
||||
split_pattern = job_split_pattern
|
||||
split_pattern = tts_context.split_pattern
|
||||
source_text = str(text or "")
|
||||
try:
|
||||
normalized = prepare_text_for_tts(
|
||||
source_text,
|
||||
heteronym_rules=heteronym_sentence_rules,
|
||||
pronunciation_rules=pronunciation_rules,
|
||||
normalization_overrides=getattr(job, "normalization_overrides", None),
|
||||
usage_counter=usage_counter,
|
||||
)
|
||||
normalized = tts_context.normalize(source_text)
|
||||
except LLMClientError as exc:
|
||||
job.add_log(f"LLM normalization failed: {exc}", level="error")
|
||||
raise
|
||||
@@ -890,8 +892,8 @@ def run_conversion_job(job: Job) -> None:
|
||||
"generate_epub3": job.generate_epub3,
|
||||
}
|
||||
|
||||
if usage_counter:
|
||||
_record_override_usage(job, usage_counter, override_token_map)
|
||||
if tts_context.usage_counter:
|
||||
_record_override_usage(job, tts_context.usage_counter, override_token_map)
|
||||
|
||||
if metadata_dir:
|
||||
metadata_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
Reference in New Issue
Block a user