ASS 書き込み / libass 焼き込み 実戦マニュアル
1. ヘッダーテンプレート(そのままコピー)
[Script Info]
ScriptType: v4.00+
PlayResX: 2560
PlayResY: 1080
WrapStyle: 2
ScaledBorderAndShadow: yes
YCbCr Matrix: TV.709
[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: X,Microsoft YaHei,100,&H00F4F1EA,&H00F4F1EA,&H00140F11,&HFF000000,0,0,0,0,100,100,0,0,1,0,0,5,30,30,30,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.39,0:00:03.75,X,,0,0,0,,{\an5\pos(500,300)\fnAnton\fs150\1c&H00F4F1EA&\bord3\3c&H00140F11&\frz-4\fscx118\fscy118\t(0,130,\fscx100\fscy100)\fad(100,100)}WALK
2. 四つの致命的なフォーマット落とし穴(本プロジェクトですべて経験済み)
- タイムスタンプは
H:MM:SS.ccでなければならない- 正:
0:00:01.50、0:01:02.30 - 誤:
0:01.50(libass がBad timestampを報告、その行全体が破棄 → 動画全体に字幕が表示されないが、ffmpeg は終了コード 0)
- 正:
- Style 行の色の末尾に
&をつけてはいけない- 正:
&H00F4F1EA,&H00F4F1EA,&H00140F11,&HFF000000 - 誤:
&H00F4F1EA&,...→ Style 行の解析に失敗 → 同じく「成功したが字幕なし」 - イベント内のインラインタグの色には
&が必要:\1c&H00F4F1EA&
- 正:
- フォントディレクトリ
fontsdirは相対パス + 作業ディレクトリ管理- 出力ディレクトリに
fonts/を置き、cdでそのディレクトリに移動してからass=x.ass:fontsdir=fontsとする。 - ドライブ文字を含む絶対パスはフィルターグラフで問題を起こしやすい。日本語の相対パスは問題ない(cwd は日本語可だが、フィルター引数内はできるだけ ASCII にすること)。
- 出力ディレクトリに
- ASS ファイルは
utf-8-sigで書き、ASCII ファイル名にする- フィルター引数内の日本語ファイル名は不安定。
_s4.assのような名前にする。
- フィルター引数内の日本語ファイル名は不安定。
診断コマンド(libass が実際に何を読み取っているか確認する):
& $ff -hide_banner -v verbose -f lavfi -i "color=c=0x303040:s=2560x1080:r=30:d=2" `
-vf "ass=_s4.ass:fontsdir=fonts" -c:v libx264 -crf 22 -pix_fmt yuv420p _t.mp4 2>&1 | Select-String 'Bad timestamp|Style|font file'
3. 色 / アルファの意味論
- 色:
&HBBGGRR&(青・緑・赤の順)、例:白&H00F4F1EA&、墨黒&H00140F11&、ピンク&H00B35CFF&、ラベンダー&H00FFA4C4&、クリーム&H00D7EEF8&、金&H0054C9FF&。 - 透明度
\1a:&H00&は完全不透明、&HFF&は完全透明(16進数が大きいほど透明)。初期バージョンで点滅が効かなかったのはアルファが逆だったため。 - Style 行の PrimaryColour は
&HAABBGGRR(AA が先頭、末尾 & は不要)。
4. フォント
- libass は fontconfig/directwrite を使用するため、ファミリー名が正確でなければならない。PIL で確認する:
from PIL import ImageFont
ImageFont.truetype(r"...\fonts\Anton-Regular.ttf", 40).getname() # ('Anton','Regular')
- 本プロジェクトで使用するフォント:Anton / Archivo Black / Bebas Neue / Permanent Marker / Silkscreen / Rubik Glitch / Bungee / Monoton / Pirata One / UnifrakturCook;システムフォントの Georgia、Microsoft YaHei は直接
\fnGeorgia、\fnMicrosoft YaHeiで指定可能。 - Permanent Marker は libass 内で実際には約 0.79×fs になる。この係数に基づいてフォントサイズや色ブロックの寸法を調整すること。
- グリフ不足(♥ ★ など)は自動的に ArialMT にフォールバックされる。表示はされるが字形が異なる。正確に表示したい場合は
\fnMicrosoft YaHeiを使用する。
5. アニメーションタグ早見表
| タグ | 使用法 |
|---|---|
\fad(in,out) | ミリ秒単位のフェードイン/アウト |
\move(x1,y1,x2,y2,t1,t2) | 位置移動(\pos とは排他的、move が優先) |
\t(t1,t2,\tag...) | 時間補間、例 \t(0,130,\fscx100\fscy100\blur(0)) |
\frz / \fscx / \fscy | 回転/拡縮(負の値で反転可能) |
\blur(n) | ぼかし(大きな文字が現れる前にぼかし→クリアにすると効果的) |
\clip(x1,y1,x2,y2) | 矩形クリッピング(スライス/スキャンライト/出現効果) |
\org(x,y) | 回転/拡縮の中心点 |
\p1+描画コマンド | m 0 0 l w 0 w h 0 h 矩形など |
落とし穴:同一属性に連続した \t を2つ指定すると片方しか適用されない(例:ルールラインの収縮アニメーション)。これは2つのイベントに分割するか、2回に分けて描画することで回避する。
落とし穴:\p1 描画の座標は左上隅をアンカーとし、さらに libass はブロック全体を高さの半分だけ上に移動させる。\an7\pos(左上隅) + 絶対座標が最も安定。回転/スキャンアウトには \org で原点を指定する。
落とし穴:蛍光ペンの色ブロックをテキストの後ろで「スキャンアウト」させたい場合、描画レイヤーで \fscx0 → \t(0,150,\fscx100) とし、原点は \org で指定する。
6. レンダリングコマンド
6.1 直接焼き込み(最も一般的)
cd 出力ディレクトリ
& $ff -v error -y -i _base_30s.mp4 -vf "ass=_s4.ass:fontsdir=fonts" `
-c:v libx264 -preset medium -crf 16 -pix_fmt yuv420p -movflags +faststart OUT.mp4
30秒 / 2560×1080 / 中文字幕量 で約 1–3 分。
6.2 透明オーバーレイ(編集ソフト用)
& $ff -v error -y -f lavfi -i "[email protected]:s=2560x1080:r=30,format=rgba" `
-vf "fps=30,ass=_sub.ass:alpha=1" -t 154.6 `
-c:v libvpx-vp9 -pix_fmt yuva420p -b:v 0 -crf 30 -row-mt 1 -auto-alt-ref 0 `
-metadata:s:v:0 alpha_mode=1 OUT_alpha.webm
- デコードには
-c:v libvpx-vp9が必須。ネイティブの vp9 デコードではアルファが失われる。 - 互換性優先の「スクリーンオーバーレイ」mp4:黒背景+明るい文字。編集ソフトでスクリーン/覆い焼き(リニアライト)ブレンドモードを使用。
6.3 フレーム抽出での確認(レンダリングログではなく画像で確認)
& $ff -v error -ss 12.4 -i OUT.mp4 -frames:v 1 -q:v 2 -y _check.jpg
注意:
-ssを-iの前に置いた場合(入力シーク)、特定の状況で ASS タイムラインが出力に相対的にリダイレクトされ、プレビューが空白になることがある。検証用のフレーム抽出には出力側シーク(-i OUT.mp4 -ss 12.4)を使用するか、全体を切り出してからフレームを選ぶこと。
7. トラブルシューティング早見表
| 現象 | 原因 | 修正方法 |
|---|---|---|
| 映像全体に文字が一切表示されない | タイムスタンプ形式 / Style 色末尾 & | H:MM:SS.cc に修正。Style 色の末尾 & を削除 |
| 一部の文が表示されない | その行のタイムスタンプが間違っている、または start>end | 各行を確認 |
| 文字が非常に小さい/書体が異なる | フォントファミリー名が間違っている | PIL getname() で確認 |
\p1 色ブロックが上/下にずれる | 描画アンカー + libass が高さの半分だけ上に移動 | \an7 +左上絶対座標 +実測でフレームをキャリブレーション |
| 回転中心が合わない | デフォルト中心が意図と異なる | \org(x,y) を追加 |
| フェードアウトが効かない/点滅が逆 | アルファの意味が逆 | &H00 不透明、&HFF 透明 |
| 30秒のレンダリングで最初の4行だけしか表示されない | ループが最初の数行しか処理していない | イベント生成ループの範囲を確認 |
| 日本語パス/ファイル名でレンダリング失敗 | フィルター引数内に非ASCII文字が含まれている | ASS と元動画は ASCII ファイル名を使用し、日本語の cwd はそのままにする |
8. ジェネレーター骨格(Python)
def ts(sec):
h=int(sec//3600); m=int(sec%3600//60)
return "%d:%02d:%05.2f" % (h,m,sec-h*3600-m*60)
class Ass:
def __init__(self,path,w=2560,h=1080): self.ev=[]; self.path=path; self.w=w; self.h=h
def add(self,layer,t0,t1,tags,text): self.ev.append((layer,t0,t1,tags,text))
def write(self):
head = open("template_header.ass",encoding="utf-8").read() # またはコード内にインライン化
lines=[]
for layer,t0,t1,tags,text in sorted(self.ev,key=lambda e:(e[0],e[1])):
lines.append("Dialogue: %d,%s,%s,X,,0,0,0,,{%s}%s"%(layer,ts(t0),ts(t1),tags,text))
open(self.path,"w",encoding="utf-8-sig",newline="\n").write(head+"\n".join(lines)+"\n")
参照実装:字幕/build/styles_anim.py(S2/S4/S6)、flare_wide.py、neon_wide.py、avoid_side2.py。