# -*- coding: utf-8 -*- """ 扫描 SRT 文件, 报告字幕质量指标 + 抽样, 帮你快速判断OCR字幕好不好。 纯标准库。 用法: python3 tools/检查srt质量.py ./srt # 扫目录下所有 .srt python3 tools/检查srt质量.py a.srt b.srt # 扫指定文件 python3 tools/检查srt质量.py ./srt --sample 8 # 多抽几条看 """ import argparse import glob import os import re CJK = re.compile(r'[一-鿿]') def parse_srt(path): """返回 [(idx, start, end, text), ...]""" try: raw = open(path, encoding='utf-8', errors='ignore').read() except OSError: return [] blocks = re.split(r'\n\s*\n', raw.strip()) out = [] for b in blocks: lines = [x for x in b.splitlines() if x.strip()] if len(lines) < 2: continue m = re.search(r'(\d+):(\d+):(\d+)[,.](\d+)\s*-->\s*(\d+):(\d+):(\d+)[,.](\d+)', b) if not m: continue g = list(map(int, m.groups())) st = g[0]*3600 + g[1]*60 + g[2] + g[3]/1000.0 en = g[4]*3600 + g[5]*60 + g[6] + g[7]/1000.0 ti = next((i for i, l in enumerate(lines) if '-->' in l), 0) text = ' '.join(lines[ti+1:]).strip() out.append((st, en, text)) return out def has_self_repeat(s, minlen=4): """检测整条里前半段==后半段这种自我重复(如 AABB → AB 重复)。""" n = len(s) if n < minlen * 2: return False h = n // 2 return s[:h] == s[h:2*h] and len(s[:h]) >= minlen def scan(paths, sample): files = [] for p in paths: if os.path.isdir(p): files += glob.glob(os.path.join(p, '**', '*.srt'), recursive=True) elif p.endswith('.srt'): files.append(p) if not files: print("没找到 .srt") return tot_e = empty = dup = selfrep = tooshort = lowcjk = badtime = 0 chars = 0 samples = [] for f in files: subs = parse_srt(f) prev = None for st, en, text in subs: tot_e += 1 chars += len(text) if not text: empty += 1 if prev is not None and text and text == prev: dup += 1 if has_self_repeat(text): selfrep += 1 if text and len(text) <= 1: tooshort += 1 if text and len(CJK.findall(text)) < len(text) * 0.3 and len(text) > 4: lowcjk += 1 # 中文占比过低 → 可能乱码/纯符号 if en <= st or (en - st) > 30: badtime += 1 prev = text if subs and len(samples) < sample: mid = len(subs) // 2 for s in subs[mid:mid+2]: samples.append((os.path.basename(f), s)) print("=" * 58) print(f"SRT 文件 {len(files)} 个 | 字幕条目 {tot_e} 条 | 平均 {chars/max(tot_e,1):.1f} 字/条") print("-" * 58) def pct(x): return f"{x} ({x/max(tot_e,1)*100:.1f}%)" print(f" 空条目: {pct(empty)} <- 应≈0") print(f" 连续重复(同上条): {pct(dup)} <- 高=去重没做好") print(f" 自我重复(AABB): {pct(selfrep)} <- 高=自重复bug") print(f" 单字条目: {pct(tooshort)}") print(f" 低中文占比(疑乱码):{pct(lowcjk)} <- 高=OCR乱/纯符号") print(f" 时间轴异常: {pct(badtime)} <- 重叠/0时长/超30s") print("=" * 58) print("抽样字幕:") for fn, (st, en, text) in samples[:sample]: print(f" [{st:6.1f}-{en:6.1f}] {text[:50]} ({fn})") if __name__ == '__main__': ap = argparse.ArgumentParser() ap.add_argument('paths', nargs='+') ap.add_argument('--sample', type=int, default=6) a = ap.parse_args() scan(a.paths, a.sample)