"""Voice / TTS utilities via Volcengine (ByteDance) WebSocket API.""" import os from pathlib import Path import click from bin.api.voice import synthesize_tts from bin.config import config from bin.utils import handle_errors _VOICE_CHOICES = [ "zh_female_cancan_mars_bigtts", "zh_female_shuangkuaisisi_moon_bigtts", "zh_male_wennuanahu_moon_bigtts", "zh_male_sunwukong_moon_bigtts", ] _FORMAT_CHOICES = ["mp3", "wav", "pcm"] def _get_credentials(): """Get Volcengine credentials from config.""" appid = config.get("volc_appid") access_token = config.get("volc_access_token") if not appid or not access_token: return None, None return appid, access_token def _synthesize_sync( text: str, speaker: str, fmt: str, speech_rate: int, output_path: str, ) -> str: """Synchronous wrapper around async WebSocket TTS.""" appid, access_token = _get_credentials() if not appid or not access_token: raise click.UsageError( "Volcengine credentials not set. Run:\n" " mytoolkit env set volc_appid \n" " mytoolkit env set volc_access_token " ) return synthesize_tts( appid=appid, token=access_token, text=text, speaker=speaker, fmt=fmt, speech_rate=speech_rate, output_path=output_path, ) @click.group() def voice(): """Voice synthesis (TTS) commands.""" pass @voice.command("tts") @click.argument("text", required=False) @click.option("-f", "--file", type=click.Path(exists=True), help="从文件读取文本") @click.option( "-v", "--voice", "speaker", type=click.Choice(_VOICE_CHOICES), default="zh_male_wennuanahu_moon_bigtts", help="音色", ) @click.option("--format", "fmt", type=click.Choice(_FORMAT_CHOICES), default="mp3", help="输出格式") @click.option( "--speed", type=click.IntRange(-50, 100), default=0, help="语速 (-50=0.5倍, 0=正常, 100=2倍)", ) @click.option("-o", "--output", help="输出文件路径") @handle_errors def tts_cmd(text, file, speaker, fmt, speed, output): """使用豆包/火山引擎语音 API 将文本转为语音。""" if file: text = Path(file).read_text(encoding="utf-8").strip() elif not text: raise click.UsageError("必须提供 text 或使用 -f/--file 从文件读取") click.echo(f"Synthesizing: {text[:40]}{'...' if len(text) > 40 else ''}") save_path = output or f"output.{fmt}" result = _synthesize_sync(text, speaker, fmt, speed, save_path) click.echo(os.path.abspath(result))