Files
lingo/scripts/fix_episodes.py
ilaandClaude Opus 4.8 6091c441cd chore: 加入 episodes.json 校验与修正脚本
- scripts/validate_episodes.py:结构/字段/时间戳/音频对账等校验
- scripts/fix_episodes.py:重算统计字段、引号归一、去空白(自动备份)
- scripts/README.md:用法与已知数据缺口说明

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 15:16:59 +08:00

117 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""修正 episodes.json 的廉价格式问题(会先备份原文件)。
只做安全、可逆性强的就地修正,**不臆造数据、不改正文语义**:
1. 重算统计字段,使其与实际一致:
meta.totalEpisodes / meta.totalActs / meta.audioActs(新增)
episode.totalLines / episode.hasAudio
2. 弯引号统一为直引号(“ ”→",‘ ’→'),便于点词/分词匹配
3. 去除 en 首尾空白
**刻意不做**(需人工/补料后另行处理):
- 不剥离 ep18/19 的说话人前缀(来源不一,待补音频后统一)
- 不删除任何 act/line,不修改时间戳数值,不为无音频集补造音频
用法:
python3 scripts/fix_episodes.py # 就地修正,自动备份
python3 scripts/fix_episodes.py --dry-run # 只报告将改什么,不写
python3 scripts/fix_episodes.py --in X --out Y # 指定输入/输出
python3 scripts/fix_episodes.py --no-backup # 跳过备份
"""
import argparse
import datetime
import json
import os
import shutil
HERE = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.dirname(HERE)
DEFAULT_JSON = os.path.join(REPO, "family-album-usa", "episodes.json")
QUOTE_MAP = {"“": '"', "”": '"', "‘": "'", "’": "'"}
def normalize_quotes(s):
for a, b in QUOTE_MAP.items():
s = s.replace(a, b)
return s
def fix(data):
"""就地修正 data,返回改动计数字典。"""
changes = {"quotes": 0, "stripped": 0, "stats": 0}
eps = data["episodes"]
# 1. 正文:引号与空白
for e in eps:
for a in e.get("acts", []):
for l in a.get("lines", []):
en = l.get("en", "")
new = normalize_quotes(en)
if new != en:
changes["quotes"] += 1
en = new
stripped = en.strip()
if stripped != en:
changes["stripped"] += 1
en = stripped
l["en"] = en
# 2. 统计字段重算
acts = [a for e in eps for a in e.get("acts", [])]
meta = data.setdefault("meta", {})
def set_if_diff(d, k, v):
if d.get(k) != v:
d[k] = v
changes["stats"] += 1
set_if_diff(meta, "totalEpisodes", len(eps))
set_if_diff(meta, "totalActs", len(acts))
set_if_diff(meta, "audioActs", sum(1 for a in acts if a.get("hasAudio")))
for e in eps:
set_if_diff(e, "totalLines", sum(len(a.get("lines", [])) for a in e.get("acts", [])))
set_if_diff(e, "hasAudio", any(a.get("hasAudio") for a in e.get("acts", [])))
return changes
def main():
p = argparse.ArgumentParser(description="修正 episodes.json 的格式问题")
p.add_argument("--in", dest="src", default=DEFAULT_JSON, help="输入文件")
p.add_argument("--out", dest="dst", default=None, help="输出文件(默认覆盖输入)")
p.add_argument("--dry-run", action="store_true", help="只报告,不写文件")
p.add_argument("--no-backup", action="store_true", help="不生成 .bak 备份")
args = p.parse_args()
dst = args.dst or args.src
with open(args.src, encoding="utf-8") as f:
data = json.load(f)
changes = fix(data)
print("将进行的改动:")
print(f" 弯引号 -> 直引号 :{changes['quotes']} 处")
print(f" 去首尾空白 :{changes['stripped']} 处")
print(f" 重算统计字段 :{changes['stats']} 个")
if args.dry_run:
print("\n[dry-run] 未写入任何文件。")
return
# 备份(仅覆盖写时)
if dst == args.src and not args.no_backup:
stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
bak = f"{args.src}.{stamp}.bak"
shutil.copy2(args.src, bak)
print(f"\n已备份原文件 -> {bak}")
with open(dst, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
f.write("\n")
print(f"已写入 -> {dst}")
print("建议接着运行:python3 scripts/validate_episodes.py")
if __name__ == "__main__":
main()