HTML5 重写:6 款游戏 + 用户系统 + 全局设置 + 5 轮迭代重构
- 新增 web/ 纯音频 HTML5 平台:共享引擎(手势/WebAudio 三层/设置/震动)、 Node 零依赖服务器(静态 + 注册登录 + 进度存档 API) - 游戏:剑与声(RPG 新作)、声命线、暗室(重设计)、音序回响(重设计)、 节拍行者(跟随领奏重构)、盲航(同向闪避重构);声东击西删除 - 全局功能:男声/女声双套 TTS 资产、三档语速、BGM 音量三档、 震动反馈语言(10 种震型)、服务器进度同步 - 资产:899 ogg + 899 m4a(Qwen3-TTS 双性别语音、Stable Audio 3 音效、 Mureka BGM 4+3 首),终检零缺失 - 文档:docs/design/ 13 篇设计审查文档、docs/audio/ 资产管线、 web/server/README.md API 文档
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""全项目音频资产终检(R5)。
|
||||
|
||||
检查 6 个游戏 web/<game>/audio/:
|
||||
1. 每个 .ogg 都有同名 .m4a(反之亦然)
|
||||
2. voice/male 与 voice/female 的 id 集合一致(storyline/rpg 的平铺 NPC/角色句除外)
|
||||
3. 无 0 字节文件;ogg/m4a 均可被 ffprobe 解码且时长 > 0
|
||||
输出 Markdown 报告到 stdout(重定向进 docs/audio/final-asset-check.md)。
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = "/home/ubuntu/SoundGame/web"
|
||||
GAMES = ["storyline", "escape", "simon", "rhythm", "space", "rpg"]
|
||||
# 平铺 voice 文件(角色/NPC,单音色,不做性别集合一致性要求)
|
||||
FLAT_OK = {"storyline", "rpg"}
|
||||
|
||||
|
||||
def ids(d, ext):
|
||||
if not os.path.isdir(d):
|
||||
return set()
|
||||
return {f[: -len(ext)] for f in os.listdir(d) if f.endswith(ext)}
|
||||
|
||||
|
||||
def main():
|
||||
problems = []
|
||||
print("# 全项目音频资产终检(R5,2026-08-29)\n")
|
||||
print("| 游戏 | ogg | m4a | 0字节 | 解码失败 | voice male/female id 一致 |")
|
||||
print("|---|---|---|---|---|---|")
|
||||
for g in GAMES:
|
||||
base = os.path.join(ROOT, g, "audio")
|
||||
oggs, m4as, zero, bad = [], [], [], []
|
||||
for dirpath, _dirs, files in os.walk(base):
|
||||
for f in files:
|
||||
p = os.path.join(dirpath, f)
|
||||
if os.path.getsize(p) == 0:
|
||||
zero.append(p)
|
||||
if f.endswith(".ogg"):
|
||||
oggs.append(p)
|
||||
elif f.endswith(".m4a"):
|
||||
m4as.append(p)
|
||||
# 双格式配对
|
||||
o_set = {p[:-4] for p in oggs}
|
||||
m_set = {p[:-4] for p in m4as}
|
||||
for p in sorted(o_set - m_set):
|
||||
problems.append(f"{g}: 缺 m4a —— {p}.ogg")
|
||||
for p in sorted(m_set - o_set):
|
||||
problems.append(f"{g}: 缺 ogg —— {p}.m4a")
|
||||
# 解码校验(抽查全量 ffprobe)
|
||||
for p in oggs + m4as:
|
||||
r = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration",
|
||||
"-of", "csv=p=0", p], capture_output=True, text=True)
|
||||
try:
|
||||
if float(r.stdout.strip()) <= 0:
|
||||
bad.append(p)
|
||||
except ValueError:
|
||||
bad.append(p)
|
||||
for p in zero:
|
||||
problems.append(f"{g}: 0 字节 —— {p}")
|
||||
for p in bad:
|
||||
problems.append(f"{g}: 解码失败/时长为 0 —— {p}")
|
||||
# voice 性别集合一致性
|
||||
vm = ids(os.path.join(base, "voice", "male"), ".ogg")
|
||||
vf = ids(os.path.join(base, "voice", "female"), ".ogg")
|
||||
if vm == vf and vm:
|
||||
pair = f"✅ 各 {len(vm)}"
|
||||
else:
|
||||
pair = f"❌ male={len(vm)} female={len(vf)}"
|
||||
for i in sorted(vm - vf):
|
||||
problems.append(f"{g}: voice 缺 female/{i}")
|
||||
for i in sorted(vf - vm):
|
||||
problems.append(f"{g}: voice 缺 male/{i}")
|
||||
flat = ids(os.path.join(base, "voice"), ".ogg")
|
||||
flat_note = f"(平铺 {len(flat)} 个角色/NPC 句,豁免)" if g in FLAT_OK and flat else \
|
||||
(f"(⚠️ 非豁免游戏存在平铺 {len(flat)} 个)" if flat and g not in FLAT_OK else "")
|
||||
print(f"| {g} | {len(oggs)} | {len(m4as)} | {len(zero)} | {len(bad)} | {pair}{flat_note} |")
|
||||
print()
|
||||
if problems:
|
||||
print(f"## 问题清单({len(problems)} 条)\n")
|
||||
for p in problems:
|
||||
print(f"- {p}")
|
||||
return 1
|
||||
print("## 结论\n\n全部通过:双格式配对完整、无 0 字节、全部可解码、voice 性别目录 id 集合一致(storyline/rpg 平铺角色句按约定豁免)。")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user