download_all_voices.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. #!/usr/bin/env python3
  2. """下载所有阿里云音色样本到 project/voices/ 目录"""
  3. import json, os, re, requests, time, sys
  4. OUTPUT_DIR = "voices"
  5. os.makedirs(OUTPUT_DIR, exist_ok=True)
  6. # 先获取全部音色URL(不只是前80个)
  7. print("正在通过浏览器提取全部音色...")
  8. import subprocess
  9. result = subprocess.run([
  10. "npx", "playwright-cli", "run-code",
  11. r"""async (page) => {
  12. const data = await page.evaluate(() => {
  13. const result = [];
  14. document.querySelectorAll('audio').forEach(audio => {
  15. const src = audio.querySelector('source')?.src || audio.src;
  16. if (src) {
  17. const tr = audio.closest('tr');
  18. const text = tr?.textContent || '';
  19. const vm = text.match(/voice\s*参数\s*[::]\s*([a-z]\w*)/i);
  20. const nm = text.match(/名称\s*[::]\s*(\S+)/);
  21. result.push({url: src, voice: vm?vm[1]:'', name: nm?nm[1]:''});
  22. }
  23. });
  24. return result;
  25. });
  26. return JSON.stringify(data);
  27. }"""], capture_output=True, text=True, timeout=60,
  28. cwd=r"c:/Users/caoyg/ai/audio-tts/audio_codebuddy")
  29. output = result.stdout
  30. # 提取真正的 JSON
  31. json_match = re.search(r'"(\[.*\])"', output, re.DOTALL)
  32. if not json_match:
  33. print("无法解析输出,前500字符:", output[:500])
  34. sys.exit(1)
  35. json_str = json_match.group(1)
  36. # 反转义
  37. json_str = json_str.replace('\\"', '"').replace('\\\\', '\\')
  38. voices = json.loads(json_str)
  39. print(f"找到 {len(voices)} 个音色样本\n")
  40. # 按模型分类计数器
  41. stats = {}
  42. downloaded = 0
  43. failed = 0
  44. skipped = 0
  45. for i, v in enumerate(voices):
  46. url = v['url']
  47. voice = v['voice']
  48. name = v.get('name', '')
  49. if not url:
  50. skipped += 1
  51. continue
  52. # 清理文件名
  53. safe_name = f"{name}_{voice}" if name else voice
  54. safe_name = re.sub(r'[\\/:*?"<>|]', '_', safe_name)
  55. safe_name = voice # 简单用voice参数名
  56. fname = f"{voice}.mp3"
  57. fpath = os.path.join(OUTPUT_DIR, fname)
  58. if os.path.exists(fpath):
  59. stats[voice] = stats.get(voice, 0) + 1
  60. skipped += 1
  61. continue
  62. # 进度
  63. pct = (i+1) * 100 // len(voices)
  64. bar = "█" * (pct // 5) + "░" * (20 - pct // 5)
  65. print(f"\r[{bar}] {pct}% {i+1}/{len(voices)} {voice:25s}", end='', flush=True)
  66. try:
  67. resp = requests.get(url, timeout=60, headers={
  68. 'Referer': 'https://help.aliyun.com/',
  69. 'User-Agent': 'Mozilla/5.0'
  70. })
  71. if resp.status_code == 200 and len(resp.content) > 100:
  72. with open(fpath, 'wb') as f:
  73. f.write(resp.content)
  74. downloaded += 1
  75. else:
  76. failed += 1
  77. except Exception as e:
  78. failed += 1
  79. pass
  80. time.sleep(0.2)
  81. print(f"\n\n完成!下载 {downloaded} 个, 跳过 {skipped} 个, 失败 {failed} 个")
  82. print(f"文件保存在: {OUTPUT_DIR}/")
  83. # 生成索引
  84. with open(os.path.join(OUTPUT_DIR, "INDEX.md"), "w", encoding="utf-8") as f:
  85. f.write("# 阿里云百炼 官方音色样本\n\n")
  86. f.write(f"共 {len(voices)} 个音色,来源:help.aliyun.com 官方帮助中心\n\n")
  87. f.write("| # | 音色名 | voice参数 | 文件名 |\n")
  88. f.write("|---|--------|----------|--------|\n")
  89. for i, v in enumerate(voices):
  90. f.write(f"| {i+1} | {v['name']} | `{v['voice']}` | [{v['voice']}.mp3]({v['voice']}.mp3) |\n")