112 lines
3.7 KiB
Python
112 lines
3.7 KiB
Python
# -*- 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)
|