feat: EPUB3 finalizer in service, extraction in ConversionPlan

This commit is contained in:
Artem Akymenko
2026-07-24 19:17:37 +03:00
parent 4123cadd87
commit 3311bef2f7
3 changed files with 59 additions and 9 deletions
+2
View File
@@ -21,6 +21,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
if TYPE_CHECKING:
from abogen.application.conversion_request import ConversionRequest
from abogen.text_extractor import ExtractionResult
@dataclass
@@ -93,3 +94,4 @@ class ConversionPlan:
intro: Optional[IntroOutroSpec] = None
outro: Optional[IntroOutroSpec] = None
output_layout: Optional[OutputLayout] = None
extraction: Optional[ExtractionResult] = None
+13 -6
View File
@@ -50,7 +50,7 @@ def build_conversion_plan(request: ConversionRequest) -> ConversionPlan:
raise ValueError("No text content to convert")
# 2. Extract metadata
metadata = _extract_metadata(request)
metadata, extraction = _extract_metadata(request)
# 3. Parse chapters
raw_chapters = _parse_chapters(source_text, request)
@@ -74,6 +74,7 @@ def build_conversion_plan(request: ConversionRequest) -> ConversionPlan:
intro=intro,
outro=outro,
output_layout=output_layout,
extraction=extraction,
)
@@ -111,10 +112,15 @@ def _extract_source_text(request: ConversionRequest) -> Optional[str]:
return text
def _extract_metadata(request: ConversionRequest) -> Dict[str, Any]:
"""Extract metadata from source file."""
def _extract_metadata(
request: ConversionRequest,
) -> Tuple[Dict[str, Any], Optional[Any]]:
"""Extract metadata from source file.
Returns (metadata, extraction) tuple.
"""
if request.direct_text:
return dict(request.metadata_tags)
return dict(request.metadata_tags), None
if request.source_path and request.source_path.exists():
try:
@@ -123,11 +129,12 @@ def _extract_metadata(request: ConversionRequest) -> Dict[str, Any]:
)
metadata = dict(extraction.metadata) if extraction.metadata else {}
except Exception:
extraction = None
metadata = {}
metadata = merge_metadata(metadata, request.metadata_tags)
return metadata
return metadata, extraction
return dict(request.metadata_tags)
return dict(request.metadata_tags), None
def _parse_chapters(
+44 -3
View File
@@ -79,8 +79,8 @@ def run_conversion(
tts_context=tts_context,
)
# Stage 4: Finalize (m4b metadata embedding)
_finalize(request, result, events)
# Stage 4: Finalize (m4b metadata embedding, EPUB3 generation)
_finalize(request, result, plan, events)
events.log("Conversion complete")
return result
@@ -93,11 +93,13 @@ def run_conversion(
def _finalize(
request: ConversionRequest,
result: ConversionResult,
plan: ConversionPlan,
events: ConversionEvents,
) -> None:
"""Post-conversion finalization (m4b metadata embedding, etc.)."""
"""Post-conversion finalization (m4b metadata embedding, EPUB3 generation, etc.)."""
from abogen.domain.enums import OutputFormat
# m4b metadata embedding
if (
result.audio_path
and request.output_format == OutputFormat.M4B
@@ -122,6 +124,45 @@ def _finalize(
events.log(f"Failed to embed m4b metadata: {exc}", level="error")
raise RuntimeError(f"Failed to embed m4b metadata: {exc}") from exc
# EPUB3 generation
epub3_config = request.epub3_export
if epub3_config and plan.extraction:
audio_asset = result.audio_path
if not audio_asset and result.chapter_paths:
audio_asset = result.chapter_paths[0]
if audio_asset:
try:
from pathlib import Path
from abogen.epub3.exporter import build_epub3_package
epub_root = result.project_root or plan.output_layout.parent_dir
from abogen.domain.output_paths import build_output_path
epub_output_path = build_output_path(epub_root, request.original_filename, "epub")
events.log("Generating EPUB 3 package...")
epub_path = build_epub3_package(
output_path=epub_output_path,
book_id=epub3_config.book_id,
extraction=plan.extraction,
metadata_tags=result.metadata or {},
chapter_markers=result.chapter_markers or [],
chunk_markers=result.chunk_markers or [],
chunks=request.chapter_chunk.chunks if request.chapter_chunk else [],
audio_path=audio_asset,
speaker_mode=request.chapter_chunk.speaker_mode if request.chapter_chunk else "single",
cover_image_path=request.cover_image_path,
cover_image_mime=request.cover_image_mime,
)
result.epub_path = epub_path
result.artifacts["epub3"] = epub_path
events.log(f"EPUB 3 package created at {epub_path}")
except Exception as exc:
events.log(f"Failed to generate EPUB 3: {exc}", level="error")
else:
events.log("Skipped EPUB 3 generation: audio output unavailable.", level="warning")
def _prepare_tts_context(
request: ConversionRequest,