mirror of
https://github.com/denizsafak/abogen.git
synced 2026-09-20 11:40:57 +02:00
- Single definition in application layer - Both adapters import from ports instead of defining locally
113 lines
2.9 KiB
Python
113 lines
2.9 KiB
Python
"""Ports / interfaces for the conversion service.
|
|
|
|
These protocols define how the conversion service communicates with
|
|
the outside world (UI, TTS backends, voice resolvers).
|
|
|
|
The service ONLY depends on these interfaces, never on concrete
|
|
implementations (PyQt signals, Flask Job, etc.).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any, List, Optional, Protocol, runtime_checkable
|
|
|
|
|
|
class ConversionCancelled(Exception):
|
|
"""Raised when conversion is cancelled by user."""
|
|
pass
|
|
|
|
|
|
class ConversionEvents(Protocol):
|
|
"""UI-specific actions the conversion service delegates back to the caller.
|
|
|
|
Implementations:
|
|
- PyQt: emits signals (log_updated, progress_updated, etc.)
|
|
- WebUI: updates Job attributes (job.add_log, job.progress, etc.)
|
|
"""
|
|
|
|
def log(self, message: str, level: str = "info") -> None:
|
|
"""Log a message to the UI."""
|
|
...
|
|
|
|
def progress(self, processed: int, total: int, etr: str) -> None:
|
|
"""Update progress display."""
|
|
...
|
|
|
|
def check_cancelled(self) -> None:
|
|
"""Check if conversion was cancelled.
|
|
|
|
Should raise ConversionCancelled (or UI-specific exception)
|
|
if cancellation is requested. Normal return means "continue".
|
|
"""
|
|
...
|
|
|
|
|
|
class PipelineProvider(Protocol):
|
|
"""Provides access to TTS backends (Kokoro, SuperTonic, etc.).
|
|
|
|
Implementations:
|
|
- PyQt: wraps self.backend (single pipeline)
|
|
- WebUI: wraps PipelinePool (multi-provider)
|
|
"""
|
|
|
|
def get(self, provider: str, language: str, use_gpu: bool) -> Any:
|
|
"""Get a TTS backend instance."""
|
|
...
|
|
|
|
def dispose_all(self) -> None:
|
|
"""Dispose all backend resources."""
|
|
...
|
|
|
|
|
|
@dataclass
|
|
class ResolvedVoice:
|
|
"""A resolved voice ready for TTS synthesis."""
|
|
|
|
provider: str
|
|
resolved_spec: str
|
|
voice: Any # loaded voice tensor or name
|
|
speed: float
|
|
supertonic_steps: int
|
|
|
|
|
|
class VoiceResolver(Protocol):
|
|
"""Resolves voice specs into loaded voice objects.
|
|
|
|
Implementations:
|
|
- PyQt: wraps load_voice_cached + VoiceCache
|
|
- WebUI: wraps resolve_voice_choice + PipelinePool + VoiceCache
|
|
"""
|
|
|
|
def resolve(self, voice_spec: str) -> ResolvedVoice:
|
|
"""Resolve a voice spec into a loaded voice."""
|
|
...
|
|
|
|
|
|
class SubtitleWriter(Protocol):
|
|
"""Writes subtitle entries to a file."""
|
|
|
|
def open(self) -> None:
|
|
"""Open the subtitle file for writing."""
|
|
...
|
|
|
|
def write_entry(self, start: float, end: float, text: str) -> None:
|
|
"""Write a single subtitle entry."""
|
|
...
|
|
|
|
def close(self) -> None:
|
|
"""Close the subtitle file."""
|
|
...
|
|
|
|
|
|
class AudioSink(Protocol):
|
|
"""Writes audio data to a file."""
|
|
|
|
def write(self, audio: Any) -> None:
|
|
"""Write audio samples to the sink."""
|
|
...
|
|
|
|
def close(self) -> None:
|
|
"""Close the audio file."""
|
|
...
|