"""Voice clone use cases.""" from __future__ import annotations from typing import List, Optional from packages.domain.voice_clone_profile import VoiceCloneProfile from packages.ports.voice_clone_profile_repository import VoiceCloneProfileRepository class VoiceCloneNotFoundError(Exception): """音色克隆档案未找到。""" pass class VoiceCloneNotRetryableError(Exception): """音色克隆档案不可重试。""" pass class CreateVoiceCloneUseCase: """创建音色克隆档案。""" def __init__(self, repository: VoiceCloneProfileRepository) -> None: self.repository = repository def execute( self, user_id: str, name: str, *, description: str = "", source_audio_url: str = "", voice_model: str = "", language: str = "zh-CN", gender: str = "unknown", max_retries: int = 3, metadata: Optional[dict] = None, ) -> VoiceCloneProfile: profile = VoiceCloneProfile.create( user_id=user_id, name=name, description=description, source_audio_url=source_audio_url, voice_model=voice_model, language=language, gender=gender, max_retries=max_retries, metadata=metadata, ) return self.repository.create(profile) class ListVoiceClonesUseCase: """列出用户的音色克隆档案。""" def __init__(self, repository: VoiceCloneProfileRepository) -> None: self.repository = repository def execute( self, user_id: str, *, status: Optional[str] = None, skip: int = 0, limit: int = 50, ) -> tuple[List[VoiceCloneProfile], int]: items = self.repository.list_by_user(user_id, status=status, limit=limit, offset=skip) total = self.repository.count_by_user(user_id, status=status) return items, total class GetVoiceCloneUseCase: """获取音色克隆档案详情。""" def __init__(self, repository: VoiceCloneProfileRepository) -> None: self.repository = repository def execute(self, clone_id: str, user_id: str) -> VoiceCloneProfile: profile = self.repository.get(clone_id) if profile is None or profile.user_id != user_id: raise VoiceCloneNotFoundError(f"Voice clone {clone_id} not found") return profile class GetVoiceCloneStatusUseCase: """查询音色克隆状态(用于轮询)。""" def __init__(self, repository: VoiceCloneProfileRepository) -> None: self.repository = repository def execute(self, clone_id: str, user_id: str) -> VoiceCloneProfile: profile = self.repository.get(clone_id) if profile is None or profile.user_id != user_id: raise VoiceCloneNotFoundError(f"Voice clone {clone_id} not found") return profile class DeleteVoiceCloneUseCase: """删除音色克隆档案(软删除)。""" def __init__(self, repository: VoiceCloneProfileRepository) -> None: self.repository = repository def execute(self, clone_id: str, user_id: str) -> bool: profile = self.repository.get(clone_id) if profile is None or profile.user_id != user_id: return False return self.repository.delete(clone_id) class RetryVoiceCloneUseCase: """重试失败的音色克隆。""" def __init__(self, repository: VoiceCloneProfileRepository) -> None: self.repository = repository def execute(self, clone_id: str, user_id: str) -> VoiceCloneProfile: profile = self.repository.get(clone_id) if profile is None or profile.user_id != user_id: raise VoiceCloneNotFoundError(f"Voice clone {clone_id} not found") if not profile.is_retryable: raise VoiceCloneNotRetryableError(f"Voice clone {clone_id} is not retryable (status={profile.status})") profile.prepare_retry() return self.repository.update(profile)