mirror of
https://github.com/denizsafak/abogen.git
synced 2026-09-20 19:50:59 +02:00
feat: EPUB3 finalizer in service, extraction in ConversionPlan
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user