"""配音引擎音色预设. 与 CosyVoice 的 preset_voices 区分: - preset_voices.py: CosyVoice 真实音色(阿里云) - voice_presets.py: 配音引擎通用音色预设(含 mock/后续接入的真实 TTS) """ from __future__ import annotations import sys from dataclasses import dataclass if sys.version_info >= (3, 11): from enum import StrEnum else: from enum import Enum class StrEnum(str, Enum): pass class VoiceGender(StrEnum): """音色性别.""" MALE = "male" FEMALE = "female" CHILD = "child" class VoiceStyle(StrEnum): """音色风格.""" STABLE = "stable" # 沉稳 LIVELY = "lively" # 活泼 CUSTOMER_SERVICE = "customer_service" # 客服 NARRATION = "narration" # 旁白 NEWS = "news" # 新闻 STORY = "story" # 故事 @dataclass(slots=True) class VoicePreset: """音色预设. Attributes: voice_id: 音色唯一标识 name: 音色名称 gender: 性别 style: 风格 description: 描述 provider: 供应商(mock/aliyun/xunfei) provider_voice_id: 供应商侧音色 ID default_speed: 默认语速 default_pitch: 默认语调 sample_rate: 采样率 language: 语言 """ voice_id: str name: str gender: VoiceGender = VoiceGender.FEMALE style: VoiceStyle = VoiceStyle.NARRATION description: str = "" provider: str = "mock" provider_voice_id: str = "" default_speed: float = 1.0 default_pitch: float = 0.0 sample_rate: int = 22050 language: str = "zh-CN" # ─── Mock 音色预设列表 ────────────────────────────────────── MOCK_VOICES: list[VoicePreset] = [ VoicePreset( voice_id="female_warm", name="温暖女声", gender=VoiceGender.FEMALE, style=VoiceStyle.NARRATION, description="温柔温暖的女声,适合情感类、生活类视频", provider="mock", provider_voice_id="sine_220", default_speed=1.0, default_pitch=0.0, sample_rate=22050, language="zh-CN", ), VoicePreset( voice_id="male_stable", name="沉稳男声", gender=VoiceGender.MALE, style=VoiceStyle.STABLE, description="沉稳厚重的男声,适合商务、知识类视频", provider="mock", provider_voice_id="sine_110", default_speed=0.9, default_pitch=0.0, sample_rate=22050, language="zh-CN", ), VoicePreset( voice_id="female_lively", name="活泼女声", gender=VoiceGender.FEMALE, style=VoiceStyle.LIVELY, description="明亮活泼的女声,适合vlog、美食、旅行类视频", provider="mock", provider_voice_id="sine_280", default_speed=1.2, default_pitch=2.0, sample_rate=22050, language="zh-CN", ), VoicePreset( voice_id="child_cute", name="可爱童声", gender=VoiceGender.CHILD, style=VoiceStyle.STORY, description="清脆可爱的童声,适合儿童教育、动画类视频", provider="mock", provider_voice_id="sine_380", default_speed=1.0, default_pitch=4.0, sample_rate=22050, language="zh-CN", ), VoicePreset( voice_id="female_service", name="客服女声", gender=VoiceGender.FEMALE, style=VoiceStyle.CUSTOMER_SERVICE, description="专业清晰的客服女声,适合产品介绍、教程类视频", provider="mock", provider_voice_id="sine_250", default_speed=1.0, default_pitch=1.0, sample_rate=22050, language="zh-CN", ), VoicePreset( voice_id="male_news", name="新闻男声", gender=VoiceGender.MALE, style=VoiceStyle.NEWS, description="字正腔圆的新闻播报声,适合资讯、时政类视频", provider="mock", provider_voice_id="sine_140", default_speed=1.0, default_pitch=0.0, sample_rate=22050, language="zh-CN", ), VoicePreset( voice_id="female_soft", name="轻柔女声", gender=VoiceGender.FEMALE, style=VoiceStyle.STORY, description="轻柔舒缓的女声,适合睡前故事、冥想类视频", provider="mock", provider_voice_id="sine_180", default_speed=0.8, default_pitch=0.0, sample_rate=22050, language="zh-CN", ), VoicePreset( voice_id="male_magnetic", name="磁性男声", gender=VoiceGender.MALE, style=VoiceStyle.STORY, description="低沉磁性的男声,适合电影解说、读书类视频", provider="mock", provider_voice_id="sine_90", default_speed=0.85, default_pitch=-2.0, sample_rate=22050, language="zh-CN", ), ] # voice_id → VoicePreset _MOCK_VOICE_MAP: dict[str, VoicePreset] = {v.voice_id: v for v in MOCK_VOICES} def get_voice(voice_id: str, *, provider: str = "mock") -> VoicePreset | None: """根据 voice_id 获取音色预设.""" if provider == "mock": return _MOCK_VOICE_MAP.get(voice_id) return None def list_voices( *, gender: str | None = None, style: str | None = None, provider: str | None = None, keyword: str | None = None, ) -> list[VoicePreset]: """按条件筛选音色列表.""" # 目前只有 mock 音色 result = list(MOCK_VOICES) if provider and provider != "mock": return [] if gender: result = [v for v in result if v.gender.value == gender] if style: result = [v for v in result if v.style.value == style] if keyword: kw = keyword.lower() result = [v for v in result if kw in v.name.lower() or kw in v.description.lower() or kw in v.voice_id.lower()] return result def get_default_voice() -> VoicePreset: """获取默认音色.""" return MOCK_VOICES[0]