#!/usr/bin/env python3 """下载所有阿里云音色样本到 project/voices/ 目录""" import json, os, re, requests, time, sys OUTPUT_DIR = "voices" os.makedirs(OUTPUT_DIR, exist_ok=True) # 先获取全部音色URL(不只是前80个) print("正在通过浏览器提取全部音色...") import subprocess result = subprocess.run([ "npx", "playwright-cli", "run-code", r"""async (page) => { const data = await page.evaluate(() => { const result = []; document.querySelectorAll('audio').forEach(audio => { const src = audio.querySelector('source')?.src || audio.src; if (src) { const tr = audio.closest('tr'); const text = tr?.textContent || ''; const vm = text.match(/voice\s*参数\s*[::]\s*([a-z]\w*)/i); const nm = text.match(/名称\s*[::]\s*(\S+)/); result.push({url: src, voice: vm?vm[1]:'', name: nm?nm[1]:''}); } }); return result; }); return JSON.stringify(data); }"""], capture_output=True, text=True, timeout=60, cwd=r"c:/Users/caoyg/ai/audio-tts/audio_codebuddy") output = result.stdout # 提取真正的 JSON json_match = re.search(r'"(\[.*\])"', output, re.DOTALL) if not json_match: print("无法解析输出,前500字符:", output[:500]) sys.exit(1) json_str = json_match.group(1) # 反转义 json_str = json_str.replace('\\"', '"').replace('\\\\', '\\') voices = json.loads(json_str) print(f"找到 {len(voices)} 个音色样本\n") # 按模型分类计数器 stats = {} downloaded = 0 failed = 0 skipped = 0 for i, v in enumerate(voices): url = v['url'] voice = v['voice'] name = v.get('name', '') if not url: skipped += 1 continue # 清理文件名 safe_name = f"{name}_{voice}" if name else voice safe_name = re.sub(r'[\\/:*?"<>|]', '_', safe_name) safe_name = voice # 简单用voice参数名 fname = f"{voice}.mp3" fpath = os.path.join(OUTPUT_DIR, fname) if os.path.exists(fpath): stats[voice] = stats.get(voice, 0) + 1 skipped += 1 continue # 进度 pct = (i+1) * 100 // len(voices) bar = "█" * (pct // 5) + "░" * (20 - pct // 5) print(f"\r[{bar}] {pct}% {i+1}/{len(voices)} {voice:25s}", end='', flush=True) try: resp = requests.get(url, timeout=60, headers={ 'Referer': 'https://help.aliyun.com/', 'User-Agent': 'Mozilla/5.0' }) if resp.status_code == 200 and len(resp.content) > 100: with open(fpath, 'wb') as f: f.write(resp.content) downloaded += 1 else: failed += 1 except Exception as e: failed += 1 pass time.sleep(0.2) print(f"\n\n完成!下载 {downloaded} 个, 跳过 {skipped} 个, 失败 {failed} 个") print(f"文件保存在: {OUTPUT_DIR}/") # 生成索引 with open(os.path.join(OUTPUT_DIR, "INDEX.md"), "w", encoding="utf-8") as f: f.write("# 阿里云百炼 官方音色样本\n\n") f.write(f"共 {len(voices)} 个音色,来源:help.aliyun.com 官方帮助中心\n\n") f.write("| # | 音色名 | voice参数 | 文件名 |\n") f.write("|---|--------|----------|--------|\n") for i, v in enumerate(voices): f.write(f"| {i+1} | {v['name']} | `{v['voice']}` | [{v['voice']}.mp3]({v['voice']}.mp3) |\n")