AIで作った字幕テンプレートを売る前に、SRTの時刻重なりを止める

AIで作った字幕テンプレートを売る前に、SRTの時刻重なりを止める

動画教材や短い解説動画に添える字幕テンプレートは、小さなテキストファイルです。だからこそ、AIに下書きを頼んだあと「開けるから大丈夫」と思い込みやすいです。今回は販売用の字幕テンプレートを想定した合成見本で、SRTの時刻が重なったときに納品前で止められるか試しました。

SRTは、連番、開始と終了の時刻、字幕文を空行で区切って並べる形式です。字幕を人が読むだけなら、2番目の開始が1番目の終了より早くても気づかない場合があります。見落としやすいのは、時刻の重なり。動画編集ソフトに読み込んで確かめる方法もありますが、配布するファイルを増やす前に、テキストの段階で止められるほうが扱いやすいと考えました。

まずは、重なりを見逃す入力を置く

合成見本では、2つ目の字幕を1番目が消える100ミリ秒前から始めます。文章として読むと不自然ではありません。でも時刻は重なっています。

1
00:00:00,000 --> 00:00:02,000
はじめの字幕です。

2
00:00:01,900 --> 00:00:04,000
重なった字幕です。

私が最初に迷ったのは、時刻を「形式どおりか」だけ見ればよいのか、それとも前後関係まで見るべきかでした。デジタル商品として渡すなら、購入者が自分の動画へ読み込む前に、少なくとも隣の字幕同士の衝突は作り手側で見つけたいです。そこで、連番、時刻の書式、終了が開始より後か、前の終了より後に始まるかの4点だけを検査します。

小さな検査に絞る

次のコードを$TMPDIR/srt-sample/srt_gate.pyとして保存しました。時刻をミリ秒へ換算し、前の字幕の終了時刻を持ちながら次を読みます。字幕の自然さや読みやすさは判定しません。そこまで混ぜると、数値で止める検査の役割がぼやけるからです。

srt_gate.py
from __future__ import annotations

import re
import sys
from pathlib import Path

TIMECODE = re.compile(
    r"^(?P<sh>\d{2}):(?P<sm>\d{2}):(?P<ss>\d{2}),(?P<sms>\d{3}) --> "
    r"(?P<eh>\d{2}):(?P<em>\d{2}):(?P<es>\d{2}),(?P<ems>\d{3})$"
)


def to_ms(match: re.Match[str], prefix: str) -> int:
    return (
        int(match[f"{prefix}h"]) * 3_600_000
        + int(match[f"{prefix}m"]) * 60_000
        + int(match[f"{prefix}s"]) * 1_000
        + int(match[f"{prefix}ms"])
    )


def main(path: Path) -> int:
    blocks = [block for block in path.read_text(encoding="utf-8").strip().split("\n\n") if block]
    previous_end = -1
    previous_number = 0
    for position, block in enumerate(blocks, start=1):
        lines = block.splitlines()
        if len(lines) < 3 or not lines[0].isdigit():
            print(f"NG cue {position}: sequence number or subtitle text is missing")
            return 1
        number = int(lines[0])
        if number != previous_number + 1:
            print(f"NG cue {position}: expected sequence {previous_number + 1}, got {number}")
            return 1
        match = TIMECODE.fullmatch(lines[1])
        if not match:
            print(f"NG cue {number}: invalid timecode {lines[1]!r}")
            return 1
        start, end = to_ms(match, "s"), to_ms(match, "e")
        if end <= start:
            print(f"NG cue {number}: end must be after start")
            return 1
        if start < previous_end:
            print(f"NG cue {number}: starts before the prior cue ends")
            return 1
        previous_end, previous_number = end, number
    print(f"OK cues={len(blocks)} last_end_ms={previous_end}")
    return 0


if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("usage: python3 srt_gate.py FILE.srt", file=sys.stderr)
        raise SystemExit(2)
    raise SystemExit(main(Path(sys.argv[1])))

合格用、重なり用、時刻の区切りが違う用の3ファイルを作ります。SRTの時刻は時:分:秒,ミリ秒で、開始と終了の間には-->を置きます。

mkdir -p "$TMPDIR/srt-sample"
cd "$TMPDIR/srt-sample"
cat > valid.srt <<'EOF'
1
00:00:00,000 --> 00:00:02,000
はじめの字幕です。

2
00:00:02,200 --> 00:00:04,000
次の字幕です。
EOF
cat > overlap.srt <<'EOF'
1
00:00:00,000 --> 00:00:02,000
はじめの字幕です。

2
00:00:01,900 --> 00:00:04,000
重なった字幕です。
EOF
cat > bad-timecode.srt <<'EOF'
1
00:00:00,000 --> 00:00:02.000
区切りが違う字幕です。
EOF

正常な字幕と、壊れた字幕を同じ条件で流す

まず、時刻が空いているvalid.srtです。最後の終了時刻は4000ミリ秒で、終了コードは0でした。

python3 srt_gate.py valid.srt; printf 'exit=%s\n' "$?"
OK cues=2 last_end_ms=4000
exit=0

次は重なりです。2番目の開始が前の終了より早いので、検査は1を返しました。この1行が出たら、AIへ修正を頼む場合でも、開始時刻を2秒以降へ直すという条件を渡せます。

python3 srt_gate.py overlap.srt; printf 'exit=%s\n' "$?"
NG cue 2: starts before the prior cue ends
exit=1

時刻の書式も、見た目だけでは紛れます。bad-timecode.srtは終了時刻のミリ秒をピリオドで書きました。SRTで使うカンマではないので、ここも止まりました。

python3 srt_gate.py bad-timecode.srt; printf 'exit=%s\n' "$?"
NG cue 1: invalid timecode '00:00:00,000 --> 00:00:02.000'
exit=1

引数を渡さない場合も、何も調べずに成功にはしません。配布前の手順に入れるなら、対象ファイルを渡し忘れた操作を区別できる必要があります。

python3 srt_gate.py; printf 'exit=%s\n' "$?"
usage: python3 srt_gate.py FILE.srt
exit=2

AIへ返す指示を、曖昧な感想にしない

重なりを見つけたあとに「字幕を直してください」だけを返すと、別の場所まで変わることがあります。検査の出力をそのまま渡します。次の条件を短く添えれば、修正後に確認する点が残せます。

cue 2の開始時刻がcue 1の終了時刻より前です。
SRTの連番と本文は変えず、開始時刻を終了時刻以降へ直してください。
修正後にsrt_gate.pyを再実行し、exit=0を確認してください。

この検査は字幕を販売できる品質だと保証しません。行の長さ、言葉の自然さ、映像と話者の同期は、動画を再生して人が見る領域です。それでも、時刻の重なりや書式の誤りを先に落とせば、購入者へ渡す前に見るべき場所を減らせます。

デジタル商品では、AIの出力をそのまま納品物にせず、失敗が数字で出る小さな関門を置くことが大切です。字幕なら、まずexit=0までです。

出典

この記事をシェア