mirror of
https://github.com/denizsafak/abogen.git
synced 2026-09-20 11:40:57 +02:00
refactor: heading transforms + dedup moved to shared layer
- conversion_planner.py: caps normalization in _build_chapters() - conversion_executor.py: heading dedup + state machine via headings_equivalent() - Fixed seg_start_time → chapter_body_start bug in executor - Removed getattr fallback defaults in both adapters - Added 4 tests for caps normalization and heading dedup - Updated ARCHITECTURE_REFACTOR_PLAN.md with deferred WebUI cleanup
This commit is contained in:
@@ -32,6 +32,10 @@ from abogen.domain.conversion_engine import (
|
||||
)
|
||||
from abogen.domain.enums import OutputFormat, SubtitleMode
|
||||
from abogen.domain.normalization import TTSContext
|
||||
from abogen.domain.chapter_titles import (
|
||||
apply_chapter_text_transforms,
|
||||
headings_equivalent as _headings_equivalent,
|
||||
)
|
||||
from abogen.domain.output_paths import sanitize_filename_for_chapter
|
||||
from abogen.infrastructure.subtitle_writer import make_subtitle_writer
|
||||
|
||||
@@ -249,6 +253,7 @@ def execute_conversion(
|
||||
)
|
||||
|
||||
# Process heading
|
||||
heading_text = ""
|
||||
if chapter.title:
|
||||
heading_text = _format_heading(chapter.title, chapter_idx, request)
|
||||
if heading_text:
|
||||
@@ -269,11 +274,37 @@ def execute_conversion(
|
||||
stats=stats,
|
||||
)
|
||||
|
||||
# Heading dedup: check if first line of body matches heading
|
||||
pending_heading_strip = False
|
||||
if heading_text and chapter.body_text:
|
||||
first_line = next(
|
||||
(line.strip() for line in chapter.body_text.splitlines() if line.strip()),
|
||||
"",
|
||||
)
|
||||
if first_line and _headings_equivalent(first_line, heading_text):
|
||||
pending_heading_strip = True
|
||||
|
||||
# Process body segments
|
||||
chapter_chunk_markers: List[Dict[str, Any]] = []
|
||||
chapter_body_start = stats.current_time
|
||||
for seg_idx, segment in enumerate(chapter.segments):
|
||||
check_cancelled()
|
||||
|
||||
# Apply heading dedup to first segment (consume-once)
|
||||
seg_text = segment.text
|
||||
if pending_heading_strip and seg_text.strip():
|
||||
seg_text, heading_removed, _ = apply_chapter_text_transforms(
|
||||
seg_text,
|
||||
heading_text=heading_text,
|
||||
raw_title=chapter.title,
|
||||
strip_heading=True,
|
||||
normalize_caps=False,
|
||||
)
|
||||
if heading_removed:
|
||||
pending_heading_strip = False
|
||||
if not seg_text.strip():
|
||||
continue
|
||||
|
||||
# Resolve segment voice (may differ from chapter voice)
|
||||
if segment.voice_spec != chapter.voice_spec:
|
||||
seg_provider, seg_voice, seg_speed, seg_steps = _resolve_voice(
|
||||
@@ -288,7 +319,7 @@ def execute_conversion(
|
||||
|
||||
seg_start_time = stats.current_time
|
||||
local_segments, accumulated_tokens = synthesize_text(
|
||||
text=segment.text,
|
||||
text=seg_text,
|
||||
params=synth,
|
||||
backend=seg_backend,
|
||||
voice=seg_voice,
|
||||
@@ -355,7 +386,7 @@ def execute_conversion(
|
||||
result.chapter_markers.append({
|
||||
"chapter_index": chapter_idx - 1,
|
||||
"title": chapter.title,
|
||||
"start": stats.current_time - (stats.current_time - seg_start_time) if chapter.segments else stats.current_time,
|
||||
"start": chapter_body_start,
|
||||
"end": stats.current_time,
|
||||
})
|
||||
|
||||
|
||||
@@ -210,9 +210,15 @@ def _build_chapters(
|
||||
selected_chapters: List[Tuple[str, str, str]], request: ConversionRequest
|
||||
) -> List[ChapterPlan]:
|
||||
"""Build ChapterPlan with SegmentPlan for each chapter."""
|
||||
from abogen.domain.chapter_titles import normalize_chapter_opening_caps
|
||||
|
||||
chapters = []
|
||||
|
||||
for idx, (title, body_text, default_voice) in enumerate(selected_chapters, 1):
|
||||
# Apply caps normalization to body text if enabled
|
||||
if request.normalize_chapter_opening_caps and body_text:
|
||||
body_text, _ = normalize_chapter_opening_caps(body_text)
|
||||
|
||||
# Build segments for this chapter (idx is 1-based, chunks use 0-based)
|
||||
segments = _build_segments(body_text, default_voice, request, chapter_index=idx - 1)
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ def build_conversion_request_from_thread(thread: Any) -> ConversionRequest:
|
||||
read_title_intro=getattr(thread, "read_title_intro", False),
|
||||
read_closing_outro=getattr(thread, "read_closing_outro", True),
|
||||
auto_prefix_chapter_titles=getattr(thread, "auto_prefix_chapter_titles", True),
|
||||
normalize_chapter_opening_caps=getattr(thread, "normalize_chapter_opening_caps", False),
|
||||
normalize_chapter_opening_caps=thread.normalize_chapter_opening_caps,
|
||||
# Metadata
|
||||
metadata_tags=getattr(thread, "metadata_tags", {}) or {},
|
||||
# Artifacts
|
||||
|
||||
@@ -474,7 +474,7 @@ def run_conversion_job(job: Job) -> None:
|
||||
heading_text = spoken_title or raw_title
|
||||
chapter_display_title = heading_text or f"Chapter {idx}"
|
||||
job.add_log(f"Processing chapter {idx}/{total_chapters}: {chapter_display_title}")
|
||||
normalize_opening_caps = bool(getattr(job, "normalize_chapter_opening_caps", True))
|
||||
normalize_opening_caps = bool(job.normalize_chapter_opening_caps)
|
||||
|
||||
chapter_start_time = current_time
|
||||
chapter_override = (
|
||||
|
||||
Reference in New Issue
Block a user