From 0c1a3c19043a9470e46717c7baae692bcdcc56f0 Mon Sep 17 00:00:00 2001 From: Artem Akymenko Date: Thu, 16 Jul 2026 09:12:46 +0000 Subject: [PATCH] refactor(webui): use shared get_split_pattern instead of hardcoded \\n+ All three Web UI consumers now call domain.split_pattern.get_split_pattern() which selects the correct split pattern based on language and subtitle mode. Before: WebUI always split on \\n+ regardless of language (CJK missed punctuation-based splitting that PyQt already had). After: Both UIs share identical language-aware splitting logic. 1038 tests pass. --- abogen/webui/conversion_runner.py | 14 +++++++++++--- abogen/webui/debug_tts_runner.py | 5 +++-- abogen/webui/routes/utils/synthesize.py | 8 +++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/abogen/webui/conversion_runner.py b/abogen/webui/conversion_runner.py index ad49dfb..7950ac7 100644 --- a/abogen/webui/conversion_runner.py +++ b/abogen/webui/conversion_runner.py @@ -111,6 +111,7 @@ from abogen.domain.output_paths import ( resolve_project_layout as _resolve_project_layout, ) from abogen.domain.device import select_device as _select_device +from abogen.domain.split_pattern import get_split_pattern from abogen.domain.audio_helpers import ( build_ffmpeg_command as _build_ffmpeg_command, to_float32 as _to_float32, @@ -127,7 +128,7 @@ from .service import Job, JobStatus _export_svc = ExportService() -SPLIT_PATTERN = r"\n+" +SPLIT_PATTERN = r"\n+" # Kept for backward compatibility; prefer get_split_pattern() SAMPLE_RATE = 24000 @@ -172,6 +173,12 @@ def run_conversion_job(job: Job) -> None: "LLM-based apostrophe normalization is selected, but the LLM configuration is incomplete." ) + # Compute language-aware split pattern once for the entire job + job_split_pattern = get_split_pattern( + str(job.language or "a"), + str(job.subtitle_mode or "Disabled"), + ) + sink_stack = ExitStack() subtitle_writer = None chapter_paths: list[Path] = [] @@ -438,12 +445,14 @@ def run_conversion_job(job: Job) -> None: voice_choice: Any, chapter_sink: Optional[AudioSink], preview_prefix: Optional[str] = None, - split_pattern: Optional[str] = SPLIT_PATTERN, + split_pattern: Optional[str] = None, tts_provider: Optional[str] = None, speed_override: Optional[float] = None, supertonic_steps_override: Optional[int] = None, ) -> int: nonlocal processed_chars, current_time + if split_pattern is None: + split_pattern = job_split_pattern source_text = str(text or "") try: normalized = prepare_text_for_tts( @@ -625,7 +634,6 @@ def run_conversion_job(job: Job) -> None: voice_choice=voice_choice, chapter_sink=chapter_sink, preview_prefix=f"Chapter {idx} title", - split_pattern=SPLIT_PATTERN, tts_provider=chapter_provider, speed_override=chapter_speed, supertonic_steps_override=chapter_steps, diff --git a/abogen/webui/debug_tts_runner.py b/abogen/webui/debug_tts_runner.py index 4221eb4..374a8f9 100644 --- a/abogen/webui/debug_tts_runner.py +++ b/abogen/webui/debug_tts_runner.py @@ -14,7 +14,8 @@ from abogen.kokoro_text_normalization import normalize_for_pipeline from abogen.normalization_settings import build_apostrophe_config from abogen.text_extractor import extract_from_path from abogen.voice_cache import ensure_voice_assets -from abogen.webui.conversion_runner import SAMPLE_RATE, SPLIT_PATTERN, _select_device, _to_float32, _resolve_voice, _spec_to_voice_ids +from abogen.webui.conversion_runner import SAMPLE_RATE, _select_device, _to_float32, _resolve_voice, _spec_to_voice_ids +from abogen.domain.split_pattern import get_split_pattern from abogen.tts_plugin.utils import create_pipeline @@ -200,7 +201,7 @@ def run_debug_tts_wavs( normalized, voice=voice_choice, speed=speed, - split_pattern=SPLIT_PATTERN, + split_pattern=get_split_pattern(language, "Disabled"), ): audio = _to_float32(getattr(segment, "audio", None)) if audio.size: diff --git a/abogen/webui/routes/utils/synthesize.py b/abogen/webui/routes/utils/synthesize.py index 75b59c0..3f3cb7b 100644 --- a/abogen/webui/routes/utils/synthesize.py +++ b/abogen/webui/routes/utils/synthesize.py @@ -7,9 +7,9 @@ from flask import current_app, send_file from flask.typing import ResponseReturnValue from abogen.domain.device import select_device as _select_device +from abogen.domain.split_pattern import get_split_pattern -SPLIT_PATTERN = r"\n+" SAMPLE_RATE = 24000 _preview_pipelines: Dict[Tuple[str, str], Any] = {} @@ -123,6 +123,8 @@ def generate_preview_audio( current_app.logger.exception("Preview normalization failed; using raw text") normalized_text = source_text + preview_split = get_split_pattern(str(language or "a"), "Disabled") + if provider == "supertonic": from abogen.tts_plugin.utils import create_pipeline @@ -131,7 +133,7 @@ def generate_preview_audio( normalized_text, voice=voice_spec, speed=speed, - split_pattern=SPLIT_PATTERN, + split_pattern=preview_split, total_steps=supertonic_total_steps, ) else: @@ -149,7 +151,7 @@ def generate_preview_audio( normalized_text, voice=voice_choice, speed=speed, - split_pattern=SPLIT_PATTERN, + split_pattern=preview_split, ) audio_chunks: List[np.ndarray] = []