download_voices.py 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. #!/usr/bin/env python3
  2. """从阿里云帮助页面提取全部音色并下载音频样本"""
  3. import json, os, re, requests, subprocess, time
  4. OUTPUT_DIR = "docs/voices/audio"
  5. os.makedirs(OUTPUT_DIR, exist_ok=True)
  6. # 用 playwright 提取音色数据
  7. print("正在提取音色列表...")
  8. result = subprocess.run([
  9. "npx", "playwright-cli", "run-code",
  10. r"""async (page) => {
  11. const data = [];
  12. // 遍历所有包含音频的表格行
  13. const rows = document.querySelectorAll('tr');
  14. rows.forEach(tr => {
  15. const audioEl = tr.querySelector('audio');
  16. if (!audioEl) return;
  17. const src = audioEl.querySelector('source')?.src || audioEl.src;
  18. if (!src) return;
  19. // 提取voice参数
  20. const text = tr.textContent || '';
  21. const voiceMatch = text.match(/voice参数[::]\s*(\S+)/);
  22. const nameMatch = voiceMatch ? text.match(/名称[::]\s*(\S+)/) : null;
  23. const sceneMatch = text.match(/(?:^|\n)([^\n]{3,20}?)\s*名称[::]/);
  24. data.push({
  25. url: src,
  26. voice: voiceMatch ? voiceMatch[1] : '',
  27. name: nameMatch ? nameMatch[1] : '',
  28. scene: sceneMatch ? sceneMatch[1].trim() : ''
  29. });
  30. });
  31. return JSON.stringify(data);
  32. }"""], capture_output=True, text=True, cwd="c:/Users/caoyg/ai/audio-tts/audio_codebuddy")
  33. # 解析输出
  34. output = result.stdout
  35. # 提取 JSON
  36. json_start = output.find('[')
  37. json_end = output.rfind(']') + 1
  38. if json_start < 0 or json_end <= json_start:
  39. print("未找到音色数据")
  40. print(output[:500])
  41. exit(1)
  42. voices = json.loads(output[json_start:json_end])
  43. print(f"找到 {len(voices)} 个音色")
  44. # 写音色索引
  45. index_lines = ["# 官方音色样本索引\n", f"\n共 {len(voices)} 个音色,来源:阿里云百炼帮助中心\n\n"]
  46. index_lines.append("| 音色名称 | voice参数 | 适用场景 | 文件名 |\n")
  47. index_lines.append("|---------|----------|---------|--------|\n")
  48. downloaded = 0
  49. for i, v in enumerate(voices):
  50. url = v['url']
  51. voice_param = v['voice']
  52. name = v['name']
  53. scene = v['scene']
  54. # 文件名:场景_音色名_voice参数.mp3
  55. safe_name = f"{scene}_{name}_{voice_param}" if scene else f"{name}_{voice_param}"
  56. safe_name = re.sub(r'[\\/:*?"<>|]', '_', safe_name)
  57. fname = f"{safe_name}.mp3"
  58. fpath = os.path.join(OUTPUT_DIR, fname)
  59. index_lines.append(f"| {name} | `{voice_param}` | {scene} | [{fname}](audio/{fname}) |\n")
  60. if os.path.exists(fpath):
  61. print(f" [{i+1}/{len(voices)}] 跳过(已存在): {fname}")
  62. downloaded += 1
  63. continue
  64. try:
  65. print(f" [{i+1}/{len(voices)}] 下载: {voice_param} -> {fname}")
  66. resp = requests.get(url, timeout=30)
  67. if resp.status_code == 200:
  68. with open(fpath, 'wb') as f:
  69. f.write(resp.content)
  70. downloaded += 1
  71. else:
  72. print(f" HTTP {resp.status_code}")
  73. except Exception as e:
  74. print(f" 错误: {e}")
  75. time.sleep(0.3)
  76. # 写索引文件
  77. index_path = os.path.join(OUTPUT_DIR, "INDEX.md")
  78. with open(index_path, 'w', encoding='utf-8') as f:
  79. f.writelines(index_lines)
  80. print(f"\n完成!下载 {downloaded}/{len(voices)} 个音色到 {OUTPUT_DIR}/")