ClaudeでSRT字幕を翻訳する — 1行の字数と秒数を検査して収める
SRTのタイムコードを崩さずにClaudeで字幕を訳し、1行の字数・読む速さ・表示秒数をスクリプトで検査して縮め直させる手順。物差しはNetflixの日本語字幕ガイドです。
ClaudeでSRT字幕を訳すとき、意味の質より先に壊れるのは「枠」です。訳文が原文より長くなり、1行に収まらず、読み切る前に次の字幕に切り替わります。
対策は、Claudeに訳させる範囲を本文だけに絞り、枠の判定を人の目ではなくスクリプトに任せることです。違反をClaudeに返して縮めさせれば、字数オーバーの手直しがほぼ自動で回ります。
この記事では、物差しにNetflixのパートナー向けスタイルガイドの数値を借り、日本語字幕の検査スクリプトとClaude Codeのhooks設定までを通しで扱います。
SRTを訳すと何が壊れるのか
SRTは、字幕の番号、開始 --> 終了 のタイムコード、本文の3点を空行で区切って並べた単純なテキストです。壊れやすいのは次の3つです。
- タイムコードや番号の書き換え: ファイル全体を渡すと、表示時間を書き換えたり、2つの字幕を1つにまとめたりする余地が生まれます
- 字数の超過: 英語から日本語、日本語から英語のどちらでも、訳文の長さは原文と揃いません
- 読む速さの超過: 字数が1行に収まっていても、表示時間が短ければ読み切れません
3つ目が見落とされがちです。字数の上限は目視でも気づけますが、秒数との割り算は機械でないと続きません。
訳す前に決めておくこと
何を訳すか
番号とタイムコードは訳させず、本文だけを渡して本文だけ受け取ります。
何で測るか
1行の字数、1秒あたりの字数、表示秒数の上限と下限を数値で決めます。
誰が判定するか
スクリプトが判定し、違反の一覧だけをClaudeに返します。
物差しにするNetflixの数値
字幕の字数や速さには、配信先ごとの規格があります。ここではNetflixが公開している言語別のタイムドテキスト・スタイルガイドを物差しにします。Netflixへ納品する案件に限らず、「どの数値をどの粒度で決めればよいか」の見本として使えるためです。YouTubeなど他の配信先へ出すときや、納品先の仕様が別にあるときは、その仕様に合わせて後述のスクリプトの数値だけを差し替えます。
| 項目 | 英語字幕 | 日本語字幕 | 日本語SDH |
|---|---|---|---|
| 1行の字数 | 英語字幕42字 | 日本語字幕全角13字(横書き) | 日本語SDH全角16字(横書き) |
| 読む速さの上限 | 英語字幕大人向け20字/秒、子ども向け17字/秒 | 日本語字幕4字/秒 | 日本語SDH7字/秒 |
SDHは聴覚障害者向けの字幕で、台詞以外の音の情報も入ります。通常の翻訳字幕は左の2列を見ます。
日本語は、全角・スペース・句読点を1字、半角を0.5字と数えます。1行13字は、全角で13文字分の幅ということです。
共通の規則も押さえておきます。
- 1つの字幕イベントは最大2行です
- 表示時間は最長7秒です。最短は共通ガイドで5/6秒(24fpsなら20フレーム)、日本語ガイドでは500ミリ秒と書かれています
- 日本語字幕では、句点「。」と読点「、」を使いません。読点は半角スペース、句点は全角スペースに置き換えます。書名など公式訳に句読点が含まれる場合は例外です
- 字幕と字幕の間は最低2フレーム空けます
最短秒数の記述は、ガイド間で表現が揃っていません。共通ガイドは5/6秒、日本語ガイドは0.5秒、タイミングガイドは「20フレーム(または4/5秒)」です。24fpsの20フレームは5/6秒で、4/5秒とは一致しません。日本語の納品なら日本語ガイドの0.5秒、共通ガイドにも合わせるなら5/6秒(約0.83秒)が基準になります。後述のスクリプトは日本語ガイドに合わせて0.5秒のままにしてあります。
手順1: 訳させる範囲を本文だけに切る
SRTをそのまま貼ると、Claudeはタイムコードも「訳す対象」として読みます。まず番号と本文だけを取り出し、字幕ごとの表示秒数と字数の予算を添えて渡す形にします。
予算は、表示秒数に読む速さの上限を掛けた値と、2行分の字数のうち小さいほうです。日本語字幕なら次の式になります。
予算(全角換算) = min(13 × 2, 表示秒数 × 4)2.0秒の字幕は8字、0.8秒の字幕は3.2字です。4字/秒という上限はかなり厳しく、台詞の完全な訳はほぼ不可能で、「何を落とすか」の判断になります。
Claude Codeで作業するなら、判断の基準をCLAUDE.mdに書いておきます。次は、そのまま使える規約の断片です。
## 字幕翻訳の規約(日本語字幕)
- *.en.srt から *.ja.srt を作る。番号とタイムコードは原文のまま複製する
- 1行は全角13字まで(半角は0.5字)。1イベントは最大2行
- 読む速さは4字/秒まで。表示秒数 × 4 を超える訳文は書かない
- 句点「。」と読点「、」は使わない。区切りが要るときはスペースを置く
- 縮めるときは削る・言い換えるで対応し、意味を足さない
- 仕上げに python3 -I check_srt.py ep01.en.srt ep01.ja.srt を実行し、出力が空になるまで直す最後の1行が要点です。検査コマンドを規約に書いておくと、Claudeは訳した後に自分で実行して結果を読み、直しに戻れます。
手順2: タイムコードと字数を検査するスクリプト
検査は、原文と訳文の2つのSRTを突き合わせて行います。見るのは、番号とタイムコードが一致しているか、行数、1行の幅、1秒あたりの幅、表示秒数、句読点の6点です。
import re, sys, unicodedata
LIMITS = {"cpl": 13, "cps": 4.0, "min_s": 0.5, "max_s": 7.0, "lines": 2}
TC = re.compile(r"(\d+):(\d\d):(\d\d)[,.](\d{3}) --> (\d+):(\d\d):(\d\d)[,.](\d{3})")
def secs(h, m, s, ms):
return int(h) * 3600 + int(m) * 60 + int(s) + int(ms) / 1000
def width(text): # 全角=1、半角=0.5
return sum(0.5 if unicodedata.east_asian_width(c) in ("Na", "H") else 1.0 for c in text)
def parse(path):
cues = []
for block in re.split(r"\n\s*\n", open(path, encoding="utf-8").read().strip()):
rows = block.splitlines()
g = TC.match(rows[1]).groups()
cues.append({"n": rows[0], "tc": rows[1], "dur": secs(*g[4:]) - secs(*g[:4]), "lines": rows[2:]})
return cues
def main(src, dst):
a, b, errs = parse(src), parse(dst), []
if len(a) != len(b):
errs.append(f"cue数 {len(a)} -> {len(b)}")
for x, y in zip(a, b):
n, d = y["n"], y["dur"]
if (x["n"], x["tc"]) != (y["n"], y["tc"]):
errs.append(f"#{n} 番号かタイムコードが原文と違う")
if len(y["lines"]) > LIMITS["lines"]:
errs.append(f"#{n} {len(y['lines'])}行ある")
for l in y["lines"]:
if width(l) > LIMITS["cpl"]:
errs.append(f"#{n} 1行{width(l):g}字 > {LIMITS['cpl']}: {l}")
if "、" in l or "。" in l:
errs.append(f"#{n} 句読点が残っている: {l}")
cps = sum(width(l) for l in y["lines"]) / d
if cps > LIMITS["cps"]:
errs.append(f"#{n} {cps:.1f}字/秒 > {LIMITS['cps']}")
if not LIMITS["min_s"] <= d <= LIMITS["max_s"]:
errs.append(f"#{n} 表示{d:.2f}秒")
print("\n".join(errs), file=sys.stderr)
sys.exit(2 if errs else 0)
main(sys.argv[1], sys.argv[2])違反をstderrに出し、終了コードを2にしている理由は次の手順で説明します。
字幕2件の小さな原文で、実際に動かした結果を示します。原文は英語で、1件目が2.0秒、2件目が0.8秒です。
1
00:00:01,000 --> 00:00:03,000
I never thought
you would come back.
2
00:00:03,200 --> 00:00:04,000
Sit down.直訳寄りで句読点付きの訳文は、次のようにはねられます。
python3 -I check_srt.py en.srt bad.srt#1 句読点が残っている: まさか戻ってくるなんて、
#1 句読点が残っている: 思ってもいなかったよ。
#1 11.5字/秒 > 4.01行の幅は12字と11字で、13字の枠には収まっています。それでも23字を2秒で読ませるので、毎秒11.5字になり、上限の約3倍です。字数だけの検査では通ってしまう例です。
「まさか戻るとは」「思わなかった」の2行に縮めると、13字を2秒で表示するので6.5字/秒となり、まだ上限を超えます。
#1 6.5字/秒 > 4.0内容を絞って「戻るとはね」の1行にすると、5字を2秒で2.5字/秒になります。2件目の「座って」は3字を0.8秒で3.75字/秒です。この2件は通り、出力は空で終了コードは0でした。
もう1つ、タイムコードを1か所だけ変えた訳文を渡すと、#2 番号かタイムコードが原文と違う と報告されます。Claudeが秒数を触った場合に、変更箇所がすぐ分かります。
手順3: 違反をClaudeに返して縮めさせる
検査コマンドをCLAUDE.mdに書くだけでは、実行されない回が出ます。確実にするなら、Claude Codeのhooksで、ファイルを書き換えるたびに検査を走らせます。
hooksは、Claude Codeの動作の前後で任意のシェルコマンドを実行する仕組みです。PostToolUse はツールの実行に成功した後に動きます。ここで終了コード2を返し、理由をstderrに書くと、Claudeにそのstderrが渡されます。終了コード0のhookのstderrはClaudeには見えません。検査スクリプトが違反時に2を返してstderrへ書くのは、このためです。
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "if [ -f ep01.ja.srt ]; then python3 -I check_srt.py ep01.en.srt ep01.ja.srt; fi"
}
]
}
]
}
}これを .claude/settings.json に置きます。書き方の要点は2つです。
Edit|Writeのmatcherは、字幕以外のファイルを編集したときも動きます。訳文ファイルがまだ無い間は、存在確認で空振りさせますif文は最後のコマンドの終了コードをそのまま返します。ファイルがあって違反があれば2、無ければ0です。|| trueを末尾に付けると、2が0に潰れてClaudeに何も渡らなくなります
hooksを使わない運用でも構いません。claude -p のような非対話実行でファイルごとに訳させ、シェルスクリプトで検査し、違反が残る字幕だけを再依頼する組み方もできます。字幕ファイルが数十本あるなら、後者のほうが並列にしやすくなります。
縮めさせるときの指示と順番
違反を返すだけだと、Claudeは訳文を丸ごと作り直しがちです。違反した字幕の番号だけを直すよう、範囲を明示します。
check_srt.py の出力にある字幕だけを直してください。
- 番号とタイムコードは変えない
- 削る優先順位: 言い淀み・相づち、繰り返し、修飾語、固有名詞以外の補足
- 直した字幕の番号を最後に列挙するNetflixの日本語SDHの項には、読み速度に合わせて編集するときは「削る・間引く・凝縮する」を優先し、言い換えで別の内容にしないという趣旨の記述があります。縮める順序の参考になります。
落とし穴
縮めたつもりで字幕を分割・延長してしまう
字数が足りないとき、字幕を2つに割ったり表示を長く延ばしたりすれば、確かに数値は通ります。ただし、番号とタイムコードを動かすことになり、検査の前提が崩れます。表示の延長は、前後の字幕との間隔(最低2フレーム)やカットの切れ目との関係を見る判断なので、翻訳とは別の工程として人が行うのが安全です。
検査の外に残る確認
スクリプトが見ていない項目も、納品前には確認が要ります。
- 使える文字: Netflixでは、指定のグリフ一覧に含まれる文字だけが使えます。一覧はここでは検査していません
- 固有名詞の統一: 話数をまたいで人名や用語がぶれないよう、表記表を作って使う規則があります。用語集を使った訳し分けは、英文プレスリリースを用語集で翻訳する手順と同じ考え方で運用できます
- 縦書き: 日本語ガイドには縦書きの規則(1行は全角11字まで)もあります。今回の検査は横書き前提です
- 画面上の文字との重なり: 字幕は上か下に置き、画面上の文字と重なる場合は読みやすい側にします。位置はSRTでは表せません
英語字幕を作る逆方向の場合は、スクリプトの LIMITS を {"cpl": 42, "cps": 20.0, ...} に変え、句読点の検査を外します。子ども向けなら20を17に下げます。最短秒数は5/6秒(約0.83秒)に変えると共通ガイドと揃いますが、その場合は例の2件目(0.8秒)が最短表示秒数で落ちます。
翻訳の精度を訳す前後で確かめる流れは、Claude翻訳の精度を検証してから確定するワークフローに別にまとめています。専用の翻訳サービスを使う方法もあり、用語集を効かせる場合はClaudeとDeepLの連携が参考になります。
まとめ
字幕翻訳で品質を決めるのは、訳の巧さより枠の管理です。番号とタイムコードは機械に固定させ、字数と速さは数値で判定し、違反だけをClaudeに戻すと、人が見る範囲は「落としてよい情報の選択」に絞れます。
とくに日本語字幕の4字/秒は厳しく、2秒の字幕に入る内容は約8字です。最初の訳は必ず検査に落ちる前提で、縮める工程を最初から組み込んでおくほうが、後から慌てずに済みます。