09d2b12ea8
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Frontend Lint (push) Successful in 57s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 3m7s
CI/CD Pipeline / Unit Tests (push) Successful in 3m13s
CI/CD Pipeline / Integration Tests (push) Successful in 1m22s
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m32s
CI Build & Deploy Pipeline / Build Staging API Image (push) Successful in 18m38s
CI Build & Deploy Pipeline / Build Staging Web Image (push) Successful in 19s
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Successful in 8m7s
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Successful in 2m16s
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Successful in 4m35s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
103 lines
2.8 KiB
Python
Executable File
103 lines
2.8 KiB
Python
Executable File
"""TTS 配音配置模型."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from typing import Any, Optional
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class TtsConfig:
|
|
"""TTS 配音配置.
|
|
|
|
Attributes:
|
|
enabled: 是否启用配音
|
|
voice_id: 音色 ID
|
|
speed: 语速 (0.5 ~ 2.0)
|
|
pitch: 语调 (-12 ~ 12 半音)
|
|
volume: 音量 (0.0 ~ 1.0)
|
|
text: 配音文本(整段配音时使用)
|
|
align_mode: 对齐模式 - "subtitle"=按字幕对齐 / "full"=整段配音
|
|
overlap_mode: 与原音的叠加模式 - "replace"=替换 / "mix"=混音
|
|
"""
|
|
|
|
enabled: bool = False
|
|
voice_id: str = ""
|
|
speed: float = 1.0
|
|
pitch: float = 0.0
|
|
volume: float = 0.8
|
|
text: str = ""
|
|
align_mode: str = "full" # subtitle / full
|
|
overlap_mode: str = "replace" # replace / mix
|
|
|
|
@classmethod
|
|
def parse(cls, data: Optional[dict[str, Any]]) -> "TtsConfig":
|
|
"""从 dict 解析配置,无效值回退到默认."""
|
|
if not data or not isinstance(data, dict):
|
|
return cls()
|
|
|
|
enabled = data.get("enabled", False)
|
|
if not isinstance(enabled, bool):
|
|
enabled = False
|
|
|
|
if not enabled:
|
|
return cls(enabled=False)
|
|
|
|
voice_id = data.get("voice_id", "")
|
|
if not isinstance(voice_id, str):
|
|
voice_id = ""
|
|
|
|
speed = data.get("speed", 1.0)
|
|
if not isinstance(speed, (int, float)):
|
|
speed = 1.0
|
|
|
|
pitch = data.get("pitch", 0.0)
|
|
if not isinstance(pitch, (int, float)):
|
|
pitch = 0.0
|
|
|
|
volume = data.get("volume", 0.8)
|
|
if not isinstance(volume, (int, float)):
|
|
volume = 0.8
|
|
|
|
text = data.get("text", "")
|
|
if not isinstance(text, str):
|
|
text = ""
|
|
|
|
align_mode = data.get("align_mode", "full")
|
|
if align_mode not in ("subtitle", "full"):
|
|
align_mode = "full"
|
|
|
|
overlap_mode = data.get("overlap_mode", "replace")
|
|
if overlap_mode not in ("replace", "mix"):
|
|
overlap_mode = "replace"
|
|
|
|
config = cls(
|
|
enabled=enabled,
|
|
voice_id=voice_id,
|
|
speed=float(speed),
|
|
pitch=float(pitch),
|
|
volume=float(volume),
|
|
text=text,
|
|
align_mode=align_mode,
|
|
overlap_mode=overlap_mode,
|
|
)
|
|
config._clamp()
|
|
return config
|
|
|
|
def _clamp(self) -> None:
|
|
"""边界钳制."""
|
|
if self.speed < 0.5:
|
|
self.speed = 0.5
|
|
elif self.speed > 2.0:
|
|
self.speed = 2.0
|
|
|
|
if self.pitch < -12:
|
|
self.pitch = -12
|
|
elif self.pitch > 12:
|
|
self.pitch = 12
|
|
|
|
if self.volume < 0.0:
|
|
self.volume = 0.0
|
|
elif self.volume > 1.0:
|
|
self.volume = 1.0
|