"""Abstract base class for voice-conversion backends. A teammate adds a new backend by: 1. Subclassing VoiceConverterBackend 2. Implementing setup(), convert(), cleanup() 3. Dropping the file in scripts/voice_convert/backends/ 4. Adding the backend name to BACKEND_REGISTRY in cli.py No other changes required. The CLI auto-dispatches by name. """ from __future__ import annotations from abc import ABC, abstractmethod from dataclasses import dataclass, field from pathlib import Path from typing import Any @dataclass class ConversionResult: """Per-clip result returned by a backend's convert() call.""" output_wav: Path method: str # backend name (e.g. "seed_vc") elapsed_seconds: float metadata: dict[str, Any] = field(default_factory=dict) success: bool = True error: str | None = None class VoiceConverterBackend(ABC): """Backend protocol. Subclass to implement a new voice-conversion method.""" name: str = "abstract" @abstractmethod def setup(self, config: dict[str, Any]) -> None: """Load model weights, allocate GPU, etc. Called once per batch run before any convert() calls. config carries backend-specific options (checkpoint paths, hyperparams). """ @abstractmethod def convert( self, source_wav: Path, target_voice_wav: Path, output_wav: Path, *, preserve_accent: bool = True, preserve_emotion: bool = True, **kwargs: Any, ) -> ConversionResult: """Convert source speech to target voice. Args: source_wav: path to the speech to be converted (TTS output) target_voice_wav: path to a reference clip of the target voice output_wav: path where the converted WAV should be saved preserve_accent: if True, request accent + style transfer (where supported) preserve_emotion: if True, request emotion preservation (where supported) **kwargs: backend-specific overrides (similarity, pitch_shift, etc.) """ @abstractmethod def cleanup(self) -> None: """Free GPU memory and any other resources. Called once after the batch."""