mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
refactor: extract _get_split_pattern into domain/split_pattern.py
- Extract unified split pattern logic to domain/split_pattern.py - Add get_split_pattern() function with language and subtitle_mode support - Remove duplicated logic from pyqt/conversion.py - Update pyqt/conversion.py to use domain.split_pattern.get_split_pattern - Add tests/test_split_pattern.py with 20 tests covering English, CJK, Spanish, French, and pattern structure
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
"""Unified split pattern logic extracted from 3 copies."""
|
||||
import re
|
||||
|
||||
|
||||
PUNCTUATION_SENTENCE = r".!?。!?"
|
||||
PUNCTUATION_SENTENCE_COMMA = r".!?,。!?、,"
|
||||
|
||||
|
||||
def get_split_pattern(language: str, subtitle_mode: str) -> str:
|
||||
"""Get the appropriate split pattern based on language and subtitle mode.
|
||||
|
||||
Args:
|
||||
language: Language code (a, b, e, f, etc.)
|
||||
subtitle_mode: Subtitle mode ("Sentence", "Sentence + Comma", "Line", etc.)
|
||||
|
||||
Returns:
|
||||
Split pattern string
|
||||
"""
|
||||
# For English, always use newline splitting only
|
||||
if language in ("a", "b"):
|
||||
return "\n"
|
||||
|
||||
# Determine spacing pattern based on language
|
||||
spacing = r"\s*" if language in ("z", "j") else r"\s+"
|
||||
|
||||
# For CJK languages, when subtitle mode is Disabled or Line, prefer
|
||||
# punctuation-based splitting instead of plain newline splitting.
|
||||
if subtitle_mode in ("Disabled", "Line") and language in ("z", "j"):
|
||||
return rf"(?<=[{PUNCTUATION_SENTENCE}]){spacing}|\n+"
|
||||
|
||||
if subtitle_mode == "Line":
|
||||
return "\n"
|
||||
elif subtitle_mode == "Sentence":
|
||||
return rf"(?<=[{PUNCTUATION_SENTENCE}]){spacing}|\n+"
|
||||
elif subtitle_mode == "Sentence + Comma":
|
||||
return rf"(?<=[{PUNCTUATION_SENTENCE_COMMA}]){spacing}|\n+"
|
||||
else:
|
||||
return r"\n+"
|
||||
@@ -14,7 +14,6 @@ from abogen.utils import (
|
||||
)
|
||||
from abogen.constants import (
|
||||
LANGUAGE_DESCRIPTIONS,
|
||||
SAMPLE_VOICE_TEXTS,
|
||||
COLORS,
|
||||
CHAPTER_OPTIONS_COUNTDOWN,
|
||||
SUBTITLE_FORMATS,
|
||||
@@ -22,11 +21,11 @@ from abogen.constants import (
|
||||
SUPPORTED_SUBTITLE_FORMATS,
|
||||
)
|
||||
from abogen.voice_formulas import get_new_voice
|
||||
from abogen.domain.split_pattern import get_split_pattern
|
||||
import abogen.hf_tracker as hf_tracker
|
||||
import static_ffmpeg
|
||||
import threading # for efficient waiting
|
||||
import subprocess
|
||||
import platform
|
||||
|
||||
# Configuration constants
|
||||
_USER_RESPONSE_TIMEOUT = (
|
||||
@@ -43,10 +42,7 @@ from abogen.subtitle_utils import (
|
||||
get_sample_voice_text,
|
||||
sanitize_name_for_os,
|
||||
_CHAPTER_MARKER_SEARCH_PATTERN,
|
||||
_VOICE_MARKER_PATTERN,
|
||||
_VOICE_MARKER_SEARCH_PATTERN,
|
||||
split_text_by_voice_markers,
|
||||
validate_voice_name,
|
||||
split_text_by_voice_markers
|
||||
)
|
||||
|
||||
class CountdownDialog(QDialog):
|
||||
@@ -216,40 +212,6 @@ class ConversionThread(QThread):
|
||||
PUNCTUATION_SENTENCE_COMMA = ".!?,।。!?、,"
|
||||
PUNCTUATION_COMMAS = ",,、"
|
||||
|
||||
def _get_split_pattern(self, lang_code, subtitle_mode):
|
||||
"""
|
||||
Get the appropriate split pattern based on language and subtitle mode.
|
||||
|
||||
Args:
|
||||
lang_code: Language code (a, b, e, f, etc.)
|
||||
subtitle_mode: Subtitle mode ("Sentence", "Sentence + Comma", "Line", etc.)
|
||||
|
||||
Returns:
|
||||
Split pattern string
|
||||
"""
|
||||
# For English, always use newline splitting only
|
||||
if lang_code in ["a", "b"]:
|
||||
return "\n"
|
||||
|
||||
# Determine spacing pattern based on language
|
||||
spacing_pattern = r"\s*" if lang_code in ["z", "j"] else r"\s+"
|
||||
|
||||
# For Chinese/Japanese, when subtitle mode is Disabled or Line, prefer
|
||||
# punctuation-based splitting instead of plain newline splitting.
|
||||
if subtitle_mode in ("Disabled", "Line") and lang_code in ["z", "j"]:
|
||||
return r"(?<=[{}]){}|\n+".format(self.PUNCTUATION_SENTENCE, spacing_pattern)
|
||||
|
||||
if subtitle_mode == "Line":
|
||||
return "\n"
|
||||
elif subtitle_mode == "Sentence":
|
||||
return r"(?<=[{}]){}|\n+".format(self.PUNCTUATION_SENTENCE, spacing_pattern)
|
||||
elif subtitle_mode == "Sentence + Comma":
|
||||
return r"(?<=[{}]){}|\n+".format(
|
||||
self.PUNCTUATION_SENTENCE_COMMA, spacing_pattern
|
||||
)
|
||||
else:
|
||||
return r"\n+" # Default to line breaks
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
file_name,
|
||||
@@ -298,7 +260,7 @@ class ConversionThread(QThread):
|
||||
self.silence_duration = 2.0 # Default value, will be overridden from GUI
|
||||
self.use_spacy_segmentation = True # Default, will be overridden from GUI
|
||||
# Set split pattern based on language and subtitle mode
|
||||
self.split_pattern = self._get_split_pattern(lang_code, subtitle_mode)
|
||||
self.split_pattern = get_split_pattern(lang_code, subtitle_mode)
|
||||
self.voice_cache = {} # Cache for loaded voices
|
||||
|
||||
def load_voice_cached(self, voice_name, tts):
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
"""Tests for split pattern logic (3 identical copies in codebase)."""
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
|
||||
import pytest
|
||||
|
||||
from abogen.domain.split_pattern import get_split_pattern
|
||||
|
||||
|
||||
# --- English always returns \n ---
|
||||
|
||||
class TestEnglish:
|
||||
def test_english_sentence(self):
|
||||
assert get_split_pattern("a", "Sentence") == "\n"
|
||||
|
||||
def test_english_sentence_comma(self):
|
||||
assert get_split_pattern("a", "Sentence + Comma") == "\n"
|
||||
|
||||
def test_english_line(self):
|
||||
assert get_split_pattern("a", "Line") == "\n"
|
||||
|
||||
def test_english_disabled(self):
|
||||
assert get_split_pattern("a", "Disabled") == "\n"
|
||||
|
||||
def test_english_b(self):
|
||||
assert get_split_pattern("b", "Sentence") == "\n"
|
||||
|
||||
|
||||
# --- CJK languages ---
|
||||
|
||||
class TestCJK:
|
||||
def test_chinese_disabled(self):
|
||||
pattern = get_split_pattern("z", "Disabled")
|
||||
assert pattern != "\n"
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_chinese_line(self):
|
||||
pattern = get_split_pattern("z", "Line")
|
||||
assert pattern != "\n"
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_chinese_sentence(self):
|
||||
pattern = get_split_pattern("z", "Sentence")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_chinese_sentence_comma(self):
|
||||
pattern = get_split_pattern("z", "Sentence + Comma")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_japanese_disabled(self):
|
||||
pattern = get_split_pattern("j", "Disabled")
|
||||
assert pattern != "\n"
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_japanese_sentence(self):
|
||||
pattern = get_split_pattern("j", "Sentence")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
|
||||
# --- Other languages ---
|
||||
|
||||
class TestOtherLanguages:
|
||||
def test_spanish_sentence(self):
|
||||
pattern = get_split_pattern("e", "Sentence")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_spanish_line(self):
|
||||
assert get_split_pattern("e", "Line") == "\n"
|
||||
|
||||
def test_spanish_disabled(self):
|
||||
# canonical: \n+ for non-CJK Disabled
|
||||
assert get_split_pattern("e", "Disabled") == r"\n+"
|
||||
|
||||
def test_french_sentence_comma(self):
|
||||
pattern = get_split_pattern("f", "Sentence + Comma")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
def test_unknown_lang(self):
|
||||
pattern = get_split_pattern("x", "Sentence")
|
||||
assert r"\n+" in pattern
|
||||
|
||||
|
||||
# --- Pattern structure ---
|
||||
|
||||
class TestPatternStructure:
|
||||
def test_sentence_has_lookbehind(self):
|
||||
pattern = get_split_pattern("e", "Sentence")
|
||||
assert r"(?<=" in pattern
|
||||
|
||||
def test_sentence_comma_has_comma_chars(self):
|
||||
pattern = get_split_pattern("e", "Sentence + Comma")
|
||||
assert "," in pattern
|
||||
|
||||
def test_cjk_spacing_uses_star(self):
|
||||
pattern = get_split_pattern("z", "Sentence")
|
||||
assert r"\s*" in pattern
|
||||
|
||||
def test_non_cjk_spacing_uses_plus(self):
|
||||
pattern = get_split_pattern("e", "Sentence")
|
||||
assert r"\s+" in pattern
|
||||
Reference in New Issue
Block a user