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:
Artem Akymenko
2026-07-21 11:30:15 +03:00
parent fd659d0f4f
commit 7973de3868
+174
View File
@@ -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,
)