mirror of
https://github.com/denizsafak/abogen.git
synced 2026-09-20 11:40:57 +02:00
feat: ConversionService
Main orchestrator for the conversion flow. Both UIs call run_conversion(). Functions: - run_conversion(request, events, pipeline_provider, voice_resolver) -> ConversionResult - _prepare_tts_context(request, events) -> TTSContext The service ties together planner, executor, and finalizers.
This commit is contained in:
@@ -0,0 +1,174 @@
|
|||||||
|
"""ConversionService — main orchestrator for the conversion flow.
|
||||||
|
|
||||||
|
Ties together planner, executor, and finalizers into a single entry point.
|
||||||
|
Both UIs (PyQt, WebUI) call ConversionService.run() to execute a conversion.
|
||||||
|
|
||||||
|
Responsibilities:
|
||||||
|
- Prepare TTSContext (normalization settings, pronunciation rules)
|
||||||
|
- Build ConversionPlan via planner
|
||||||
|
- Execute conversion via executor
|
||||||
|
- Handle lifecycle (cleanup, error handling)
|
||||||
|
- Return ConversionResult
|
||||||
|
|
||||||
|
The service NEVER imports from PyQt or WebUI.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
from typing import Any, Callable, Dict, Optional
|
||||||
|
|
||||||
|
from abogen.application.conversion_executor import execute_conversion
|
||||||
|
from abogen.application.conversion_models import ConversionPlan
|
||||||
|
from abogen.application.conversion_planner import build_conversion_plan
|
||||||
|
from abogen.application.conversion_ports import (
|
||||||
|
ConversionEvents,
|
||||||
|
PipelineProvider,
|
||||||
|
VoiceResolver,
|
||||||
|
)
|
||||||
|
from abogen.application.conversion_request import ConversionRequest
|
||||||
|
from abogen.application.conversion_result import ConversionResult
|
||||||
|
from abogen.domain.normalization import TTSContext
|
||||||
|
from abogen.domain.split_pattern import get_split_pattern
|
||||||
|
|
||||||
|
|
||||||
|
def run_conversion(
|
||||||
|
request: ConversionRequest,
|
||||||
|
events: ConversionEvents,
|
||||||
|
pipeline_provider: PipelineProvider,
|
||||||
|
voice_resolver: VoiceResolver,
|
||||||
|
) -> ConversionResult:
|
||||||
|
"""Execute a conversion request and return the result.
|
||||||
|
|
||||||
|
This is the single entry point for both UIs. It orchestrates:
|
||||||
|
1. TTS context preparation
|
||||||
|
2. Conversion planning
|
||||||
|
3. Conversion execution
|
||||||
|
4. Resource cleanup
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: Normalized conversion request
|
||||||
|
events: UI-specific callbacks (log, progress, check_cancelled)
|
||||||
|
pipeline_provider: Provides TTS backends
|
||||||
|
voice_resolver: Resolves voice specs into loaded voices
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ConversionResult with paths and markers
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ConversionCancelled: If conversion was cancelled
|
||||||
|
ValueError: If request is invalid
|
||||||
|
Exception: On TTS or I/O errors
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Stage 1: Prepare TTS context
|
||||||
|
events.log("Preparing conversion pipeline")
|
||||||
|
tts_context = _prepare_tts_context(request, events)
|
||||||
|
|
||||||
|
# Stage 2: Build conversion plan
|
||||||
|
events.log("Building conversion plan")
|
||||||
|
plan = build_conversion_plan(request)
|
||||||
|
|
||||||
|
# Stage 3: Execute conversion
|
||||||
|
events.log("Starting conversion")
|
||||||
|
result = execute_conversion(
|
||||||
|
plan=plan,
|
||||||
|
events=events,
|
||||||
|
pipeline_provider=pipeline_provider,
|
||||||
|
voice_resolver=voice_resolver,
|
||||||
|
tts_context=tts_context,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stage 4: Finalize
|
||||||
|
events.log("Conversion complete")
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
events.log(f"Conversion failed: {e}", level="error")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def _prepare_tts_context(
|
||||||
|
request: ConversionRequest,
|
||||||
|
events: ConversionEvents,
|
||||||
|
) -> TTSContext:
|
||||||
|
"""Prepare TTSContext with normalization settings.
|
||||||
|
|
||||||
|
This compiles pronunciation/heteronym rules and creates the
|
||||||
|
normalization context used during conversion.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: Conversion request with override settings
|
||||||
|
events: For logging warnings about missing features
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TTSContext ready for text normalization
|
||||||
|
"""
|
||||||
|
from abogen.domain.normalization import (
|
||||||
|
apply_normalization_overrides,
|
||||||
|
build_apostrophe_config,
|
||||||
|
compile_heteronym_sentence_rules,
|
||||||
|
compile_pronunciation_rules,
|
||||||
|
get_runtime_settings,
|
||||||
|
merge_pronunciation_overrides,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get runtime normalization settings
|
||||||
|
normalization_settings = get_runtime_settings()
|
||||||
|
|
||||||
|
# Apply job-level overrides
|
||||||
|
job_overrides = request.normalization_overrides
|
||||||
|
if job_overrides:
|
||||||
|
normalization_settings = apply_normalization_overrides(
|
||||||
|
normalization_settings, job_overrides
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build apostrophe config
|
||||||
|
apostrophe_config = build_apostrophe_config(
|
||||||
|
settings=normalization_settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check for num2words availability
|
||||||
|
if apostrophe_config.convert_numbers:
|
||||||
|
try:
|
||||||
|
import num2words # noqa: F401
|
||||||
|
except ImportError:
|
||||||
|
events.log(
|
||||||
|
"Number normalization is enabled but 'num2words' library is not available. "
|
||||||
|
"Numbers will NOT be converted to words.",
|
||||||
|
level="warning",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compute split pattern
|
||||||
|
split_pattern = get_split_pattern(
|
||||||
|
str(request.language or "a"),
|
||||||
|
str(request.subtitle_mode or "Disabled"),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Merge pronunciation overrides
|
||||||
|
pronunciation_overrides = merge_pronunciation_overrides(
|
||||||
|
request.pronunciation_overrides,
|
||||||
|
request.manual_overrides,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Compile rules
|
||||||
|
pronunciation_rules = compile_pronunciation_rules(pronunciation_overrides)
|
||||||
|
heteronym_rules = compile_heteronym_sentence_rules(request.heteronym_overrides)
|
||||||
|
|
||||||
|
if heteronym_rules:
|
||||||
|
events.log(
|
||||||
|
f"Applying {len(heteronym_rules)} heteronym override(s) during conversion.",
|
||||||
|
level="debug",
|
||||||
|
)
|
||||||
|
if pronunciation_rules:
|
||||||
|
events.log(
|
||||||
|
f"Applying {len(pronunciation_rules)} pronunciation override(s) during conversion.",
|
||||||
|
level="debug",
|
||||||
|
)
|
||||||
|
|
||||||
|
return TTSContext(
|
||||||
|
split_pattern=split_pattern,
|
||||||
|
pronunciation_rules=pronunciation_rules,
|
||||||
|
heteronym_rules=heteronym_rules,
|
||||||
|
normalization_overrides=job_overrides,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user