extract apply_chapter_text_transforms to domain/chapter_titles.py

This commit is contained in:
Artem Akymenko
2026-07-15 14:29:29 +00:00
parent 86042a3315
commit 514e29a761
3 changed files with 190 additions and 55 deletions
+32
View File
@@ -170,3 +170,35 @@ def format_spoken_chapter_title(title: str, index: int, apply_prefix: bool) -> s
return f"Chapter {number}. {cleaned_suffix}"
return f"Chapter {number}"
return base
def apply_chapter_text_transforms(
text: str,
*,
heading_text: str,
raw_title: str,
strip_heading: bool,
normalize_caps: bool,
) -> Tuple[str, bool, bool]:
"""Strip duplicate heading and normalize opening caps.
Returns ``(text, heading_removed, caps_changed)``.
The caller is responsible for state updates (pending flags, logging,
dict mutation, ``continue``).
"""
heading_removed = False
caps_changed = False
if strip_heading and heading_text:
text, heading_removed = strip_duplicate_heading_line(text, heading_text)
if not heading_removed and raw_title:
match = _HEADING_NUMBER_PREFIX_RE.match(raw_title)
if match:
number = match.group("number")
if number:
text, heading_removed = strip_duplicate_heading_line(text, number)
if normalize_caps and text:
text, caps_changed = normalize_chapter_opening_caps(text)
return text, heading_removed, caps_changed
+44 -55
View File
@@ -48,6 +48,7 @@ from abogen.domain.chapter_titles import (
normalize_caps_word as _normalize_caps_word,
normalize_chapter_opening_caps as _normalize_chapter_opening_caps,
format_spoken_chapter_title as _format_spoken_chapter_title,
apply_chapter_text_transforms as _apply_chapter_text_transforms,
_HEADING_NUMBER_PREFIX_RE,
)
from abogen.domain.metadata_helpers import (
@@ -780,39 +781,32 @@ def run_conversion_job(job: Job) -> None:
continue
mutated_entry = False
if pending_heading_strip and heading_text:
chunk_text, removed_heading = _strip_duplicate_heading_line(chunk_text, heading_text)
if not removed_heading and raw_title:
match = _HEADING_NUMBER_PREFIX_RE.match(raw_title)
if match:
number = match.group("number")
if number:
chunk_text, removed_heading = _strip_duplicate_heading_line(chunk_text, number)
if removed_heading:
pending_heading_strip = False
chunk_text, heading_removed, caps_changed = _apply_chapter_text_transforms(
chunk_text,
heading_text=heading_text,
raw_title=raw_title,
strip_heading=pending_heading_strip,
normalize_caps=opening_caps_pending,
)
if heading_removed:
pending_heading_strip = False
chunk_entry = dict(chunk_entry)
chunk_entry["normalized_text"] = chunk_text
mutated_entry = True
if not chunk_text.strip():
continue
if caps_changed:
if not mutated_entry:
chunk_entry = dict(chunk_entry)
chunk_entry["normalized_text"] = chunk_text
mutated_entry = True
if not chunk_text.strip():
continue
if opening_caps_pending and chunk_text:
normalized_text, normalized_changed = _normalize_chapter_opening_caps(chunk_text)
if normalized_changed:
if not mutated_entry:
chunk_entry = dict(chunk_entry)
mutated_entry = True
chunk_entry["normalized_text"] = normalized_text
chunk_text = normalized_text
if not opening_caps_logged:
job.add_log(
f"Normalized uppercase chapter opening for chapter {idx}.",
level="debug",
)
opening_caps_logged = True
if chunk_text.strip():
opening_caps_pending = False
chunk_entry["normalized_text"] = chunk_text
if not opening_caps_logged:
job.add_log(
f"Normalized uppercase chapter opening for chapter {idx}.",
level="debug",
)
opening_caps_logged = True
if chunk_text.strip():
opening_caps_pending = False
chunk_voice_spec = _chunk_voice_spec(
job,
@@ -878,29 +872,24 @@ def run_conversion_job(job: Job) -> None:
if body_segments == 0:
chapter_body_start = current_time
chapter_text = str(chapter.text or "")
if pending_heading_strip and heading_text:
chapter_text, removed_heading = _strip_duplicate_heading_line(chapter_text, heading_text)
if not removed_heading and raw_title:
match = _HEADING_NUMBER_PREFIX_RE.match(raw_title)
if match:
number = match.group("number")
if number:
chapter_text, removed_heading = _strip_duplicate_heading_line(chapter_text, number)
if removed_heading:
pending_heading_strip = False
if opening_caps_pending and chapter_text:
normalized_body, normalized_changed = _normalize_chapter_opening_caps(chapter_text)
if normalized_changed:
chapter_text = normalized_body
if not opening_caps_logged:
job.add_log(
f"Normalized uppercase chapter opening for chapter {idx}.",
level="debug",
)
opening_caps_logged = True
if str(chapter_text or "").strip():
opening_caps_pending = False
chapter_text, heading_removed, caps_changed = _apply_chapter_text_transforms(
chapter_text,
heading_text=heading_text,
raw_title=raw_title,
strip_heading=pending_heading_strip,
normalize_caps=opening_caps_pending,
)
if heading_removed:
pending_heading_strip = False
if caps_changed:
if not opening_caps_logged:
job.add_log(
f"Normalized uppercase chapter opening for chapter {idx}.",
level="debug",
)
opening_caps_logged = True
if str(chapter_text or "").strip():
opening_caps_pending = False
emitted = emit_text(
chapter_text,
voice_choice=voice_choice,
+114
View File
@@ -124,3 +124,117 @@ def test_normalize_chapter_opening_caps_keeps_mixed_case() -> None:
normalized, changed = _normalize_chapter_opening_caps("Already Mixed Case")
assert normalized == "Already Mixed Case"
assert changed is False
class TestApplyChapterTextTransforms:
"""Tests for the combined heading-strip + opening-caps helper."""
def test_both_enabled_heading_matches(self) -> None:
from abogen.domain.chapter_titles import apply_chapter_text_transforms
text, heading_removed, caps_changed = apply_chapter_text_transforms(
"Chapter 1: The Beginning\nBody text here",
heading_text="Chapter 1: The Beginning",
raw_title="Chapter 1: The Beginning",
strip_heading=True,
normalize_caps=True,
)
assert heading_removed is True
assert "Body text here" in text
assert "Chapter 1" not in text
def test_heading_fallback_to_number(self) -> None:
from abogen.domain.chapter_titles import apply_chapter_text_transforms
text, heading_removed, caps_changed = apply_chapter_text_transforms(
"1. The Beginning\nBody text",
heading_text="Chapter 1: The Beginning",
raw_title="1: The Beginning",
strip_heading=True,
normalize_caps=False,
)
assert heading_removed is True
assert "Body text" in text
def test_only_heading_strip(self) -> None:
from abogen.domain.chapter_titles import apply_chapter_text_transforms
text, heading_removed, caps_changed = apply_chapter_text_transforms(
"Chapter 1: Title\nBody text",
heading_text="Chapter 1: Title",
raw_title="",
strip_heading=True,
normalize_caps=False,
)
assert heading_removed is True
assert caps_changed is False
def test_only_opening_caps(self) -> None:
from abogen.domain.chapter_titles import apply_chapter_text_transforms
text, heading_removed, caps_changed = apply_chapter_text_transforms(
"ALL CAPS START OF CHAPTER",
heading_text="Chapter 1",
raw_title="",
strip_heading=False,
normalize_caps=True,
)
assert heading_removed is False
assert caps_changed is True
assert text == "All Caps Start Of Chapter"
def test_both_disabled_no_change(self) -> None:
from abogen.domain.chapter_titles import apply_chapter_text_transforms
original = "Some text here"
text, heading_removed, caps_changed = apply_chapter_text_transforms(
original,
heading_text="Chapter 1",
raw_title="",
strip_heading=False,
normalize_caps=False,
)
assert text == original
assert heading_removed is False
assert caps_changed is False
def test_heading_not_matching(self) -> None:
from abogen.domain.chapter_titles import apply_chapter_text_transforms
text, heading_removed, caps_changed = apply_chapter_text_transforms(
"Completely different text",
heading_text="Chapter 1: Title",
raw_title="",
strip_heading=True,
normalize_caps=False,
)
assert heading_removed is False
assert text == "Completely different text"
def test_empty_text(self) -> None:
from abogen.domain.chapter_titles import apply_chapter_text_transforms
text, heading_removed, caps_changed = apply_chapter_text_transforms(
"",
heading_text="Chapter 1",
raw_title="",
strip_heading=True,
normalize_caps=True,
)
assert text == ""
assert heading_removed is False
assert caps_changed is False
def test_both_enabled_text_only_has_caps(self) -> None:
from abogen.domain.chapter_titles import apply_chapter_text_transforms
text, heading_removed, caps_changed = apply_chapter_text_transforms(
"NASA MISSION LOG",
heading_text="Chapter 1",
raw_title="",
strip_heading=True,
normalize_caps=True,
)
assert heading_removed is False
assert caps_changed is True
assert text == "NASA Mission Log"