Download skill/scripts/preview_voices.py from cpuai/ahp-development-workstation: direct link, hf CLI and curl.
- Browser
- Download file 4.61 kB
-
https://huggingface.co/spaces/cpuai/ahp-development-workstation/resolve/main/skill/scripts/preview_voices.py
- Command line
-
hf download hf://spaces/cpuai/ahp-development-workstation/skill/scripts/preview_voices.py
-
curl -L -o preview_voices.py https://huggingface.co/spaces/cpuai/ahp-development-workstation/resolve/main/skill/scripts/preview_voices.py
4.61 kB
| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| """在线生成 6 种可试听 Edge TTS MP3,方便为新故事选择主讲和角色音色。 | |
| 此脚本需要联网;不会自动修改工程现有配音配置。 | |
| 用法:python scripts/preview_voices.py --output ./voice-previews | |
| """ | |
| import argparse | |
| import asyncio | |
| import html | |
| import json | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parents[1] | |
| PRESETS = ROOT / "voice-presets.json" if (ROOT / "voice-presets.json").is_file() else ROOT / "assets/voice-presets.json" | |
| DEFAULT_SAMPLE = "从前,有一个人发现了一口大钟。哇,这钟真漂亮!他举起锤子,铛的一声,钟声传遍了整个村子!你猜后来发生了什么?" | |
| async def run(output: Path, sample: str, rate: str, verify: bool) -> None: | |
| try: | |
| import edge_tts | |
| except ImportError as exc: | |
| raise RuntimeError("缺少 edge-tts,请安装 requirements.txt") from exc | |
| presets = json.loads(PRESETS.read_text(encoding="utf-8-sig")) | |
| output.mkdir(parents=True, exist_ok=True) | |
| available = {i["ShortName"] for i in await edge_tts.list_voices()} if verify else None | |
| sem = asyncio.Semaphore(2) | |
| async def make_item(v: dict) -> dict: | |
| voice = v["id"] | |
| if available is not None and voice not in available: | |
| return {"id": voice, "error": "当前 Edge 服务未提供此声音"} | |
| dest = output / (voice + ".mp3") | |
| async with sem: | |
| for attempt in range(3): | |
| try: | |
| if not dest.is_file() or dest.stat().st_size < 500: | |
| await edge_tts.Communicate(sample, voice=voice, rate=rate, | |
| pitch="+0Hz").save(str(dest)) | |
| if dest.stat().st_size < 500: | |
| raise RuntimeError("空白或不完整音频") | |
| return {"id": voice, "label": " / ".join(v.get("traits", [])), | |
| "rate": rate, "file": dest.name, "bytes": dest.stat().st_size} | |
| except Exception as exc: | |
| if attempt == 2: | |
| return {"id": voice, "error": f"{type(exc).__name__}: {exc}"} | |
| await asyncio.sleep(2 + attempt * 2) | |
| rows = await asyncio.gather(*(make_item(v) for v in presets["voices"])) | |
| cards = [] | |
| for row in rows: | |
| label = html.escape(row.get("label", "暂时不可用")) | |
| name = html.escape(row["id"]) | |
| if row.get("file"): | |
| player = f'<audio controls preload="none" src="{html.escape(row["file"])}"></audio>' | |
| else: | |
| player = f'<p class="error">{html.escape(row.get("error", "生成失败"))}</p>' | |
| cards.append(f'<article><strong>{name}</strong><p>{label}</p>{player}</article>') | |
| page = '''<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"><title>Edge TTS 成语配音试听</title> | |
| <style>*{box-sizing:border-box}body{font-family:system-ui,'Microsoft YaHei',sans-serif;max-width:1090px;margin:30px auto;padding:20px;color:#253346;background:#f5f7f8}h1{font-size:27px}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:18px}article{background:#fff;border:1px solid #e2e5ed;border-radius:14px;padding:22px}strong{font-size:16px}audio{width:100%}p{line-height:1.6}.error{color:#9e3b34}</style> | |
| </head><body><h1>成语故事 Skill · 角色音色试听</h1>''' | |
| page += f'<p>相同试听台词、相同语速 {html.escape(rate)},听完再决定适合角色的音色。</p>' | |
| page += '<div class="grid">' + "".join(cards) + '</div></body></html>' | |
| (output / "index.html").write_text(page, encoding="utf-8") | |
| (output / "preview-results.json").write_text(json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8") | |
| print(json.dumps({"output": str(output / "index.html"), "voices": rows}, ensure_ascii=False, indent=2)) | |
| if __name__ == "__main__": | |
| ap = argparse.ArgumentParser(description="生成 Edge TTS 六种普通话音色的试听网页") | |
| ap.add_argument("--output", default="voice-previews", help="要创建的试听目录") | |
| ap.add_argument("--text", default=DEFAULT_SAMPLE, help="所有音色朗读的相同测试文本") | |
| ap.add_argument("--rate", default="+5%", help="统一语速,例如 +5%") | |
| ap.add_argument("--skip-availability-check", action="store_true", help="不请求最新音色列表") | |
| args = ap.parse_args() | |
| asyncio.run(run(Path(args.output).resolve(), args.text, args.rate, not args.skip_availability_check)) | |