mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 05:40:26 +02:00
extract apply_chapter_text_transforms to domain/chapter_titles.py
This commit is contained in:
@@ -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}. {cleaned_suffix}"
|
||||||
return f"Chapter {number}"
|
return f"Chapter {number}"
|
||||||
return base
|
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
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ from abogen.domain.chapter_titles import (
|
|||||||
normalize_caps_word as _normalize_caps_word,
|
normalize_caps_word as _normalize_caps_word,
|
||||||
normalize_chapter_opening_caps as _normalize_chapter_opening_caps,
|
normalize_chapter_opening_caps as _normalize_chapter_opening_caps,
|
||||||
format_spoken_chapter_title as _format_spoken_chapter_title,
|
format_spoken_chapter_title as _format_spoken_chapter_title,
|
||||||
|
apply_chapter_text_transforms as _apply_chapter_text_transforms,
|
||||||
_HEADING_NUMBER_PREFIX_RE,
|
_HEADING_NUMBER_PREFIX_RE,
|
||||||
)
|
)
|
||||||
from abogen.domain.metadata_helpers import (
|
from abogen.domain.metadata_helpers import (
|
||||||
@@ -780,39 +781,32 @@ def run_conversion_job(job: Job) -> None:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
mutated_entry = False
|
mutated_entry = False
|
||||||
if pending_heading_strip and heading_text:
|
chunk_text, heading_removed, caps_changed = _apply_chapter_text_transforms(
|
||||||
chunk_text, removed_heading = _strip_duplicate_heading_line(chunk_text, heading_text)
|
chunk_text,
|
||||||
if not removed_heading and raw_title:
|
heading_text=heading_text,
|
||||||
match = _HEADING_NUMBER_PREFIX_RE.match(raw_title)
|
raw_title=raw_title,
|
||||||
if match:
|
strip_heading=pending_heading_strip,
|
||||||
number = match.group("number")
|
normalize_caps=opening_caps_pending,
|
||||||
if number:
|
)
|
||||||
chunk_text, removed_heading = _strip_duplicate_heading_line(chunk_text, number)
|
if heading_removed:
|
||||||
|
pending_heading_strip = False
|
||||||
if removed_heading:
|
chunk_entry = dict(chunk_entry)
|
||||||
pending_heading_strip = False
|
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 = dict(chunk_entry)
|
||||||
chunk_entry["normalized_text"] = chunk_text
|
chunk_entry["normalized_text"] = chunk_text
|
||||||
mutated_entry = True
|
if not opening_caps_logged:
|
||||||
if not chunk_text.strip():
|
job.add_log(
|
||||||
continue
|
f"Normalized uppercase chapter opening for chapter {idx}.",
|
||||||
|
level="debug",
|
||||||
if opening_caps_pending and chunk_text:
|
)
|
||||||
normalized_text, normalized_changed = _normalize_chapter_opening_caps(chunk_text)
|
opening_caps_logged = True
|
||||||
if normalized_changed:
|
if chunk_text.strip():
|
||||||
if not mutated_entry:
|
opening_caps_pending = False
|
||||||
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_voice_spec = _chunk_voice_spec(
|
chunk_voice_spec = _chunk_voice_spec(
|
||||||
job,
|
job,
|
||||||
@@ -878,29 +872,24 @@ def run_conversion_job(job: Job) -> None:
|
|||||||
if body_segments == 0:
|
if body_segments == 0:
|
||||||
chapter_body_start = current_time
|
chapter_body_start = current_time
|
||||||
chapter_text = str(chapter.text or "")
|
chapter_text = str(chapter.text or "")
|
||||||
if pending_heading_strip and heading_text:
|
chapter_text, heading_removed, caps_changed = _apply_chapter_text_transforms(
|
||||||
chapter_text, removed_heading = _strip_duplicate_heading_line(chapter_text, heading_text)
|
chapter_text,
|
||||||
if not removed_heading and raw_title:
|
heading_text=heading_text,
|
||||||
match = _HEADING_NUMBER_PREFIX_RE.match(raw_title)
|
raw_title=raw_title,
|
||||||
if match:
|
strip_heading=pending_heading_strip,
|
||||||
number = match.group("number")
|
normalize_caps=opening_caps_pending,
|
||||||
if number:
|
)
|
||||||
chapter_text, removed_heading = _strip_duplicate_heading_line(chapter_text, number)
|
if heading_removed:
|
||||||
|
pending_heading_strip = False
|
||||||
if removed_heading:
|
if caps_changed:
|
||||||
pending_heading_strip = False
|
if not opening_caps_logged:
|
||||||
if opening_caps_pending and chapter_text:
|
job.add_log(
|
||||||
normalized_body, normalized_changed = _normalize_chapter_opening_caps(chapter_text)
|
f"Normalized uppercase chapter opening for chapter {idx}.",
|
||||||
if normalized_changed:
|
level="debug",
|
||||||
chapter_text = normalized_body
|
)
|
||||||
if not opening_caps_logged:
|
opening_caps_logged = True
|
||||||
job.add_log(
|
if str(chapter_text or "").strip():
|
||||||
f"Normalized uppercase chapter opening for chapter {idx}.",
|
opening_caps_pending = False
|
||||||
level="debug",
|
|
||||||
)
|
|
||||||
opening_caps_logged = True
|
|
||||||
if str(chapter_text or "").strip():
|
|
||||||
opening_caps_pending = False
|
|
||||||
emitted = emit_text(
|
emitted = emit_text(
|
||||||
chapter_text,
|
chapter_text,
|
||||||
voice_choice=voice_choice,
|
voice_choice=voice_choice,
|
||||||
|
|||||||
@@ -124,3 +124,117 @@ def test_normalize_chapter_opening_caps_keeps_mixed_case() -> None:
|
|||||||
normalized, changed = _normalize_chapter_opening_caps("Already Mixed Case")
|
normalized, changed = _normalize_chapter_opening_caps("Already Mixed Case")
|
||||||
assert normalized == "Already Mixed Case"
|
assert normalized == "Already Mixed Case"
|
||||||
assert changed is False
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user