|
@@ -0,0 +1,67 @@
|
|
|
|
|
+#!/usr/bin/env python3
|
|
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
|
|
+"""parse-cosyvoice-dom.py — 解析阿里云 CosyVoice 音色页面 DOM,输出 JSON + 摘要"""
|
|
|
|
|
+import re
|
|
|
|
|
+import json
|
|
|
|
|
+import sys
|
|
|
|
|
+from collections import defaultdict
|
|
|
|
|
+
|
|
|
|
|
+PATH = r"C:/Users/caoyg/AppData/Local/Temp/cosyvoice.html"
|
|
|
|
|
+text = open(PATH, encoding='utf-8').read()
|
|
|
|
|
+
|
|
|
|
|
+# 切分 4 个模型段
|
|
|
|
|
+SECTIONS = [
|
|
|
|
|
+ ('cosyvoice-v3-flash', r'cosyvoice-v3-flash音色列表(.*?)cosyvoice-v3-plus音色列表'),
|
|
|
|
|
+ ('cosyvoice-v3-plus', r'cosyvoice-v3-plus音色列表(.*?)(?=下一篇|cosyvoice-v2音色列表)'),
|
|
|
|
|
+ ('cosyvoice-v2', r'cosyvoice-v2音色列表(.*?)cosyvoice-v1音色列表'),
|
|
|
|
|
+ ('cosyvoice-v1', r'cosyvoice-v1音色列表(.*?)(?=上一篇|下一篇|\Z)'),
|
|
|
|
|
+]
|
|
|
|
|
+
|
|
|
|
|
+all_models = {}
|
|
|
|
|
+
|
|
|
|
|
+for model, pat in SECTIONS:
|
|
|
|
|
+ m = re.search(pat, text, re.DOTALL)
|
|
|
|
|
+ if not m:
|
|
|
|
|
+ continue
|
|
|
|
|
+ section = m.group(1)
|
|
|
|
|
+ voices = []
|
|
|
|
|
+ positions = [mm.start() for mm in re.finditer(r'名称[::]\s*.+?\n', section)]
|
|
|
|
|
+ positions.append(len(section))
|
|
|
|
|
+ for i in range(len(positions) - 1):
|
|
|
|
|
+ chunk = section[positions[i]:positions[i+1]]
|
|
|
|
|
+ voice = {}
|
|
|
|
|
+ n = re.search(r'^名称[::]\s*(.+?)\s*$', chunk, re.MULTILINE)
|
|
|
|
|
+ if n: voice['name'] = n.group(1).strip()
|
|
|
|
|
+ v = re.search(r'voice参数[::]\s*(\w+)', chunk)
|
|
|
|
|
+ if v: voice['voice_id'] = v.group(1)
|
|
|
|
|
+ t = re.search(r'特质[::]\s*(.+?)\n', chunk)
|
|
|
|
|
+ if t: voice['desc'] = t.group(1).strip()[:80]
|
|
|
|
|
+ a = re.search(r'年龄[::]\s*(.+?)\n', chunk)
|
|
|
|
|
+ if a: voice['age'] = a.group(1).strip()
|
|
|
|
|
+ l = re.search(r'语言[::]\s*(.+?)\n', chunk)
|
|
|
|
|
+ if l: voice['lang'] = l.group(1).strip()[:60]
|
|
|
|
|
+ voice['instruct'] = '支持' if 'Instruct:支持' in chunk else '不支持' if 'Instruct:不支持' in chunk else '?'
|
|
|
|
|
+ # scene/地区
|
|
|
|
|
+ s = re.search(r'适用场景[::]\s*(.+?)\n', chunk)
|
|
|
|
|
+ if s: voice['scene'] = s.group(1).strip()[:40]
|
|
|
|
|
+ if voice.get('name'):
|
|
|
|
|
+ voices.append(voice)
|
|
|
|
|
+ all_models[model] = voices
|
|
|
|
|
+
|
|
|
|
|
+# 输出 JSON
|
|
|
|
|
+with open(r"C:/Users/caoyg/AppData/Local/Temp/cosyvoice-voices.json", "w", encoding="utf-8") as f:
|
|
|
|
|
+ json.dump(all_models, f, ensure_ascii=False, indent=2)
|
|
|
|
|
+
|
|
|
|
|
+# 输出摘要
|
|
|
|
|
+sys.stdout.reconfigure(encoding='utf-8')
|
|
|
|
|
+for model, voices in all_models.items():
|
|
|
|
|
+ print(f"\n=== {model}: {len(voices)} 个音色 ===")
|
|
|
|
|
+ lang_count = defaultdict(int)
|
|
|
|
|
+ for v in voices:
|
|
|
|
|
+ lang = v.get('lang', '?')
|
|
|
|
|
+ # 简化为第一个语种
|
|
|
|
|
+ first = lang.split('、')[0].split('(')[0]
|
|
|
|
|
+ lang_count[first] += 1
|
|
|
|
|
+ print(f" 语种分布:")
|
|
|
|
|
+ for lang, cnt in sorted(lang_count.items(), key=lambda x: -x[1]):
|
|
|
|
|
+ print(f" {lang}: {cnt}")
|