refactor: QQ → OBv11 重命名 + 平台格式统一抽象

- 所有对外称呼从 QQ 改为 OBv11(注释/提示词/日志/配置项)
- 新增 PlatformFormat 结构体,统一管理平台消息标记格式
- defaultPlatformFormats() 注册表替代硬编码 qqTargetRe
- extractProactiveMessage 改为 Thinker 方法,遍历格式注册表匹配
- 配置项重命名: QQ_BOT_PORT → OBV11_BOT_PORT, QQBotPort → OBv11BotPort
- 标记格式: 【QQ群聊】→【OBv11群聊】、【QQ私聊】→【OBv11私聊】

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-22 20:48:07 +08:00
parent 70dbb23234
commit 41f653b672
25 changed files with 1609 additions and 82 deletions
+142
View File
@@ -0,0 +1,142 @@
#!/usr/bin/env python3
"""
使用 AnimeWwise 引擎无头提取昔涟语音。
不需要 GUI,直接调用 extract.py 的核心逻辑。
"""
import os
import sys
# 添加 AnimeWwise 到路径
AWW_DIR = r"D:\Project\Code\Uni\Cyrene-Voice-Model\tools\AnimeWwise"
sys.path.insert(0, AWW_DIR)
os.chdir(AWW_DIR)
from extract import WwiseExtract
# 输入目录
HSR_AUDIO = r"D:\MeowG\HonkaiStar_Rail\StarRail_Data\Persistent\Audio\AudioPackage\Windows\Chinese(PRC)"
OUTPUT_DIR = r"D:\Project\Code\Uni\Cyrene-Voice-Model\data\cyrene_extracted"
MAP_FILE = "hkrpg.map"
print("=" * 60)
print("昔涟语音提取 (AnimeWwise Headless)")
print("=" * 60)
# 1. 初始化提取器
extractor = WwiseExtract()
# 2. 加载映射
print("\n[1/4] 加载角色映射...")
extractor.load_map(MAP_FILE)
# 3. 加载音频文件
print("\n[2/4] 加载 .pck 文件...")
pck_files = sorted([
os.path.join(HSR_AUDIO, f)
for f in os.listdir(HSR_AUDIO)
if f.endswith(".pck") and ("VoBanks" in f or "External_del_3." in f)
])
print(f" 找到 {len(pck_files)} 个目标 .pck 文件")
# 进度回调
def progress(data):
if data[0] == "total":
pct = int(data[1])
if pct % 20 == 0:
print(f" 加载进度: {pct:.0f}%")
file_structure = extractor.load_folder(
_map=MAP_FILE,
files=pck_files,
diff_path="",
base_path=HSR_AUDIO,
progress=progress,
)
# 4. 搜索昔涟
print("\n[3/4] 搜索昔涟语音...")
def search_cyrene(structure, path=""):
"""递归搜索文件结构中包含昔涟相关名称的文件"""
results = []
cyrene_terms = ['cyrene', 'mimi', 'xilian', 'mem', 'de_moi_ge']
# 搜索文件夹
folders = structure.get("folders", {})
for folder_name, folder_content in folders.items():
sub_path = f"{path}/{folder_name}" if path else folder_name
results.extend(search_cyrene(folder_content, sub_path))
# 搜索文件
for file_entry in structure.get("files", []):
file_name = file_entry[0].lower()
file_path = f"{path}/{file_entry[0]}"
for term in cyrene_terms:
if term in file_name or term in file_path.lower():
results.append((path, file_entry[0], file_entry[1]))
break
return results
matches = search_cyrene(file_structure)
print(f" 找到 {len(matches)} 个昔涟相关文件")
if len(matches) == 0:
print("\n 未匹配到昔涟,显示所有顶层目录结构:")
folders = file_structure.get("folders", {})
for name in sorted(folders.keys()):
count = len(folders[name].get("files", []))
# 递归统计
def count_all(s):
total = len(s.get("files", []))
for v in s.get("folders", {}).values():
total += count_all(v)
return total
total = count_all(folders[name])
print(f" {name}/ ({total} files)")
# 也搜一下其他可能的名称
print("\n 搜索所有包含'voice'的路径...")
voice_matches = search_cyrene(file_structure)
# 换个方式搜
def search_all(structure, prefix=""):
for folder_name, folder_content in structure.get("folders", {}).items():
sub = f"{prefix}/{folder_name}" if prefix else folder_name
sub_lower = sub.lower()
if any(t in sub_lower for t in ['voice', 'char', 'npc', 'player', 'avatar']):
print(f" {sub}/")
search_all(folder_content, sub)
search_all(file_structure)
else:
for path, name, meta in matches[:30]:
print(f" {path}/{name}")
# 5. 提取
if matches:
print(f"\n[4/4] 提取 {len(matches)} 个文件到 {OUTPUT_DIR}...")
output_files = []
for path, name, meta in matches:
output_files.append({
"path": path.split("/") if path else [],
"name": name,
"source": meta["source"],
"offset": meta["offset"],
"size": meta["size"],
"original_name": meta["original_name"],
})
extractor.extract_files(
_input=HSR_AUDIO,
files=output_files,
output=OUTPUT_DIR,
_format="wav",
progress=progress,
)
print(f"\n完成!文件保存在 {OUTPUT_DIR}")
else:
print("\n未找到昔涟语音。可能需要用 AnimeWwise GUI 手动浏览文件结构。")
extractor.reset()