Communitygithub.com

字幕焼き込み(Istok-Media-Tool/subtitle)

実際のASS字幕ファイルとffmpegのlibassレンダラーを使用して、スタイル付きで読みやすい字幕を動画に焼き込みます。drawtextハックは使いません。

subtitle) とは?

このスキルは、動画にスタイル付き字幕を焼き込むための包括的な手順を提供します。特に、実際の制作プロジェクトで得られた知見に基づき、単なる技術的な手順だけでなく、字幕の品質を左右する多くの実践的な注意点をカバーしています。例えば、適切なffmpegビルドの確認とインストール、自然な区切り位置での行分割、プラットフォーム(Instagram等)によるトリミングを考慮したマージン設定、フォントサイズや位置の主観的な調整、そしてキャプション間の微妙な空白による可読性の向上などが含まれます。また、ユーザーからのフィードバックに基づいて直接.assファイルを編集し、フレームチェックで迅速に確認、再レンダリングする反復的なワークフローにも対応しています。このスキルは、Claude Code、Cursor、CodexなどのAIエージェントツールと連携して使用することを想定しており、開発者やパワーユーザーが高品質な字幕焼き込みを効率的に行うのに役立ちます。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Istok-Media-Tool/video-edit-skills/tree/HEAD/video-edit/skills/subtitle

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

字幕焼き込み

なぜこのスキルが存在するのか

実際のプロジェクトで字幕を正しく表示させるには、何度も修正を重ねる必要がありました。それぞれの教訓を一から学び直さないために、ここにまとめます:

  1. システム標準のHomebrew版ffmpegにはdrawtext/subtitlesフィルタがまったく含まれていない(libass/freetypeでビルドされていない)ため、実際にスタイル付き字幕を焼き込むには、回避策ではなく、異なるffmpegビルドが必要です。
  2. キャプションの行を文字数バランスで分割すると意味が通じなくなります。改行は、人が話すときに自然に間を置く場所に置く必要があり、フレーズの途中であってはなりません。
  3. 左右のフレーム端に近すぎる行は、投稿後にInstagram自身のクロッピングで切れてしまう可能性があります。単に「フレームに収まる」だけでなく、実際のマージンが必要です。
  4. フォントサイズと垂直位置はプロジェクトごとに主観的なものであり、確認する価値があります。ただし、フルレンダリングの前に、静止フレームで視覚的に確認してください。静止フレームチェックは、オーバーフローやクリッピングを数秒で発見できますが、フルレンダリングではそうはいきません。
  5. キャプションは画面間で移動してはいけません。最初のバージョンではBOTTOM(Alignment 2、MarginV 500)に固定していたため、3行のキャプションは2行や1行のものより高い位置から始まり、前のキャプションと数百ミリ秒重なったキャプションは、libassの衝突回避によって全期間にわたって押し上げられ、ユーザーにはタイトルが「上下に動いている」ように見えました。修正ルール:TOPに固定する(Alignment 8、MarginV = 最初の行のy座標。1080x1920の場合1215にすると、3行のキャプションの最下行が以前のボトムアンカーの位置に来る)。これにより、すべてのキャプションの最初の行は同じ位置に配置され、追加の行は下方向に伸びます。また、2つのDialogueイベントが時間的に重なることは絶対に避けます。さらに、連続するキャプションの間には短い空白を設けます。各イベントは、次のイベントが始まる0.12秒前(25fpsで3フレーム)に終了します。これはユーザーから明示的に要求されたものです。この短い空白が、キャプションが変わったことを脳に伝えます。連続したキャプションは同じテキストがちらついているように読まれます。前のキャプションのEndを短くし、次のStartを遅らせることは絶対にしないでください(Startは音声に同期しています)。
  6. キャプションテキストは、一字一句正確な書き起こしである必要はありません。文法や表現の問題(聞き間違い、不自然な構文など)を修正し、ユーザーが変更を依頼した単語を追加または削除します。これは、誰かが読むテキストを校正するときと同じです。

ステップ1: ffmpegが実際に字幕を焼き込めることを確認する

ffmpeg -hide_banner -filters 2>&1 | grep -E "subtitles|^\s*\S*\s+ass\b"

何も一致しない場合、標準のffmpegではこれができません。フルビルドをインストールします:

brew install ffmpeg-full

ffmpeg-fullはkeg-only(PATHにシンボリックリンクされない)です。字幕焼き込みのステップでは、明示的に使用します:/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg。

実際に発生した落とし穴: ffmpeg-fullをインストールすると、新しい共有x265が取り込まれ、PATH上の通常のffmpeg/ffprobeが壊れることがあります(dyld "Library not loaded: libx265.*.dylib"エラー)。これは、古いバージョンに対してリンクされていたためです。ffmpeg-fullをインストールした直後に、以下を実行します:

ffmpeg -version && ffprobe -version

どちらかがエラーになった場合は、brew reinstall ffmpeg(通常のフォーミュラ、-fullではない)で修正し、両方のコマンドが動作することを確認してから続行します。通常のffmpegがインストールによって影響を受けずに残っているとは想定しないでください。

ステップ2: 正確なキャプションテキストとタイミングを取得する

動画が基本スキルのカッティングワークフローから得られた場合、セグメントごとの正確な保持テキストとその元のソースタイムスタンプはすでにわかっています。ただし、セグメントがカットされて連結されると、タイミングは新しい動画の独自のタイムラインにリセットされます。最終的な動画における各セグメントの位置を、実際にレンダリングされたクリップの長さを順に合計して計算します:

ffprobe -v error -show_entries format=duration -of csv=p=0 clip_01.mp4

...そして累積します。各セグメントのウィンドウ内で、クリップ自体の開始/終了からわずかに内側に設定し、音声が実際に開始/停止する場所に合わせます(クリップにカットステップからのプレ/ポストパディングが含まれている場合は、それを差し引きます)。これにより、無音部分でキャプションが表示/非表示にならないようにします。

ステップ3: ASSファイルを作成する

これを開始テンプレートとして使用します。これは実際のプロジェクトで確認された正確なスタイルです(Inter Bold、白テキスト、不透明な黒いボックス、中下の位置、十分なサイドマージン):

[Script Info]
Title: <動画名> - 字幕
ScriptType: v4.00+
WrapStyle: 2
ScaledBorderAndShadow: yes
PlayResX: 1080
PlayResY: 1920

[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Inter,70,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,3,16,0,8,120,120,1215,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.15,0:00:05.93,Default,,0,0,0,,Zamisli univerzum koji postoji\Ntoliko dugo da su sve zvezde\Nodavno ugašene

焼き込む前に、すべてのイベントが次のイベントの開始0.12秒前に終了するようにします(前のイベントのEndを次のStart - 0.12に設定します。Startを決して移動しないでください)- 教訓5を参照。

PlayResX/PlayResYを実際の動画解像度に合わせて調整します(この例は1080x1920の縦型です)。WrapStyle: 2は、libassがテキストを自動的に折り返さないことを意味します。すべての改行は、Textフィールドで明示的な\Nでなければなりません。これは意図的です。これにより、レンダラーに任せるのではなく、すべての改行を意図的に選択する必要があります(ステップ4を参照)。また、オーバーフローする行を、静かに追加された余分な行ではなく、クリップされた端として検出できます。

フォント/サイズ/色/位置はすべて開始時のデフォルトであり、固定ルールではありません。新しいプロジェクトの場合はユーザーに確認するか、このユーザーの以前の確認済みの値がわかっている場合はそれを再利用します。

実際のプロジェクトで確認された句読点の規則

キャプション画面の最終行の末尾にある末尾のピリオドと末尾のカンマは削除されます(キャプションには散文のような終止符は必要ありません。そのサイズでは視覚的なノイズとして読まれます)。これは、文が次のキャプション画面に続く場合でも適用されます。画面の変更自体がすでに間として読まれるため、「svoje znanje kroz praksu,」は「svoje znanje kroz praksu」と表示されます。ユーザーはこれを明示的に要求しました(2026-11-05、「ピリオドと同じように」)。疑問符と感嘆符はそのまま残します。カンマは画面内(行の途中、または画面の最終行ではない行の末尾)ではそのまま残します。これをすべての動画にデフォルトで適用し、バイリンガル字幕の第二言語の行や、ユーザーがプランエディターで編集したキャプションテキストにも適用します。

ステップ4: 人が実際に話すときに間を置く場所で各行を分割する

これは最も正確に行う価値があり、怠惰に間違えやすい部分です。文を実際に声に出して言うように読み、自然な呼吸/フレーズの境界(節の境界、完全な思考の後、実際の話す間を表すカンマ)で分割します。自然な分割ポイントが他の場所にある場合、行を「短くする必要がある」からといって分割しないでください。代わりに、より短い自然な分割ポイントを見つけてください(長い文にはほとんどの場合、有効な間のポイントが複数あります)。幅の目標を達成するためだけに不自然な分割を強制しないでください。

実際の修正から得られた具体例 - 間違い(文字数でバランスを取り、フレーズの途中で分割):

Zamisli univerzum koji postoji toliko
dugo da su sve zvezde odavno ugasene

正解(自然な話す間で分割):

Zamisli univerzum koji postoji
toliko dugo da su sve zvezde
odavno ugašene

1行、2行、3行のいずれでも問題ありません。固定された最大値はありません。重要なのは、すべての分割が話し手が実際に間を置く場所であり、すべての行がマージン内に収まっていることです(次のステップ)。

ステップ5: 実際のサイドマージンに対して幅を確認し、生のフレーム幅ではないことを確認する

スタイルのMarginL/MarginR(上記のテンプレートでは1080幅のフレームに対してそれぞれ120px、約11%)は、動画の投稿先プラットフォーム(Instagramなど)がアップロード後に独自のわずかなクロップを適用するために存在します。生の端に近すぎるテキストは、フレーム境界だけでなく、そのクロップによってクリップされる可能性があります。行が長すぎるかどうかを判断するときは、PlayResX - MarginL - MarginRを実際の使用可能な幅として扱い、フレーム全体の幅ではありません。

WrapStyle: 2は自動折り返しを無効にするため、実際に幅が広すぎる行は自動的に折り返されず、マージンを超えてオーバーフローし、クリップ/途切れたテキストとして読み取られます。これは、フルレンダリングに進む前に、ステップ6でキャッチします。

ステップ6: 動画全体を焼き込む前にテストフレームをレンダリングする

最も長い/リスクの高い2~3のキャプションからタイムスタンプを選び、そのフレームだけをレンダリングします:

/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg -nostdin -y -i video.mp4 -ss <t> -update 1 \
  -vf "subtitles=captions.ass" -frames:v 1 check.png -loglevel error

それぞれについて以下を確認します:テキストがマージンを超えてオーバーフローしていないか、意図したよりも行数が多いか(行が予想よりも広く、上流で言い換えや分割の見直しが必要な兆候)、垂直位置が「中下」として読めるか、下端に近すぎたり、顔を覆うほど高くなかったりするか。修正してから次に進みます。このループは高速(フレームあたり数秒)で、フルレンダリングでなければ何分もかかるようなミスを正確にキャッチできます。

ステップ7: 最終動画を焼き込む

/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg -nostdin -y -i video.mp4 \
  -vf "subtitles=captions.ass" -c:v libx264 -preset medium -crf 18 -pix_fmt yuv420p \
  -c:a copy output_titlovi.mp4 -loglevel error

字幕はビデオのみのため、音声はそのままです(-c:a copy)。出力をファイルサイズ制限のあるツールに通す必要がある場合は、マスターを圧縮するのではなく、セカンダリの圧縮コピーを作成します。これは基本およびズームスキルと同じパターンです:

ffmpeg -nostdin -y -i output_titlovi.mp4 -c:v libx264 -preset slow -crf 24 -pix_fmt yuv420p -c:a aac -b:a 128k output_titlovi_share.mp4 -loglevel error

フィードバックに基づく反復

字幕作業は、小さな具体的な修正(フォントサイズ、位置、間違った単語、句読点)のラウンドを数回経ることがよくあります。各ラウンドでは、変更があった部分のみに触れます。.assファイルを直接編集し(プレーンテキストです)、影響を受けた部分に対してステップ6のクイックフレームチェックを再実行し、ステップ7で再焼き込みします。基になる動画自体が変更されない限り、ステップ1~2をやり直す必要はありません。

インタラクティブツールを使用した間違った単語の修正

ユーザーは、同じプランエディターツール(プランエディタースキル - ユーザーの標準エディター)でキャプションテキストを自分で修正することを好みます。「テキストを編集」トグルを使用すると、すべての文がその場で編集可能になります。キャプションが存在する場合は常に、散文で修正を依頼する代わりに、--ass captions.ass --durations ...(プランエディタースキルを参照)を指定してそのツールを表示します。また、キャプションのみをチェックする必要があり、ズームが計画されていない場合も同様です(その場合は、すべてのクリップをワイドで開始し、ズームプランには触れないでください)。

返信には、変更された画面のみを含むCAPTIONS_JSON: {"<ダイアログインデックス>": "行1\n行2"}が含まれます。以下のように適用します:

python3 scripts/apply_caption_edits.py captions.ass caption_edits.json captions_fixed.ass

これにより、テキストのみが変更され(タイミングとスタイルは保持)、各行が明示的な\N改行になります。その後、編集された画面に対してステップ6のフレームチェックを再実行し(長い単語がマージンを超える可能性があります)、ステップ7で再焼き込みします。

Individual skills in this repo

This repo contains 2 individual skills — each has its own dedicated page.

関連スキル