How to burn subtitles into video with ffmpeg (force_style and ass)
← 博客
🎬 字幕与隐藏字幕7 min read

How to burn subtitles into video with ffmpeg (force_style and ass)

💡 To burn subtitles into video with ffmpeg, use -vf subtitles=in.srt:force_style='FontName=Arial,FontSize=28,Outline=2,MarginV=50' for SRT, or -vf ass=in.ass for a full .ass file. Video must be re-encoded: -c:v libx264 -crf 18 -preset slow. Audio copies with -c:a copy. There is no lossless burn-in path.

Key takeaways

  • Use the subtitles filter with force_style for SRT or WebVTT input. Switch to -vf ass=in.ass when you need per-line positioning or multiple styles per event.
  • Video must be re-encoded: -c:v libx264 -crf 18 -preset slow for deliverables, -crf 23 for drafts. Copy audio with -c:a copy to preserve quality.
  • Minimum force_style keys to set: FontName, FontSize, Outline=2 (keeps text readable on any background), MarginV=50 (pixels from the bottom edge).
  • ASS colour format is ABGR, not RGB. White is &H00FFFFFF; the first two hex digits are the alpha channel, where 00 = fully opaque.
  • For vertical 9:16 at 1080x1920, use FontSize=40 to 48 in force_style, or Fontsize=64 in an .ass [V4+ Styles] block.

Why does burn-in always require re-encoding the video?

Burning subtitles into video means painting text onto decoded video frames before the encoder writes them to disk. ffmpeg's subtitles and ass filters operate inside the video filter graph, so the video stream must be decoded, filtered, and re-encoded. Using -c:v copy while a video filter is active causes an error: the two are mutually exclusive in ffmpeg's pipeline.

Audio is a different story. The subtitle filter does not touch the audio stream, so -c:a copy passes it through unchanged. I always include -c:a copy on every burn-in command to preserve audio quality and speed up the encode.

The subtitles filter and force_style: the fastest path for SRT

The subtitles filter renders SRT, WebVTT, or any format libass supports. When the input is plain SRT (which carries no style metadata), ffmpeg applies a libass default that is often too small and has no outline. The force_style option overrides that default using ASS V4+ field names in a comma-separated string.

This is the command I use for a standard 1920x1080 landscape video:

ffmpeg -i input.mp4 \
  -vf "subtitles=in.srt:force_style='FontName=Arial,FontSize=28,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=2,Shadow=0,MarginV=50'" \
  -c:v libx264 -crf 18 -preset slow \
  -pix_fmt yuv420p \
  -c:a copy \
  output.mp4

On Windows CMD, omit the outer single quotes inside the -vf value: write force_style=FontName=Arial,... directly without wrapping single quotes around the value.

Which force_style keys matter, and what do I actually set?

The force_style string accepts ASS V4+ field names. These are the fields I configure on nearly every job:

KeyWhat it controlsValue I use
FontNameFont family (must be installed on the system)Arial, Noto Sans
FontSizeSize in ASS points (relative to PlayRes)28 for 1080p; 40 for 1080x1920
PrimaryColourText fill color in ABGR hex&H00FFFFFF (white)
OutlineColourOutline border color in ABGR hex&H00000000 (black)
BorderStyle1 = outline + shadow; 3 = opaque background box1
OutlineOutline thickness in pixels2
ShadowShadow distance in pixels0 (I always turn this off)
AlignmentNumpad position: 2=bottom center, 8=top center2
MarginVPixels from the bottom (or top for Alignment 8)50 for 1080p

The ABGR colour format is the most common source of confusion. The byte order is Alpha-Blue-Green-Red, the reverse of RGB. Alpha 00 is fully opaque. Yellow (RGB #FFFF00) becomes &H0000FFFF in ABGR: A=00, B=00, G=FF, R=FF.

One limit of force_style: it overrides the global default style, not per-line placement. If specific lines need to appear at the top of the frame to avoid on-screen text conflicts, use the ass filter route instead.

The ass filter: full style control from a .ass file

The ass filter reads a native .ass file and renders it with full fidelity. Style is defined inside the file: the [V4+ Styles] block sets the default, and individual [Events] dialogue lines can use inline override tags such as {\an8} for top-center or {\pos(960,54)} for an exact pixel position. I use this route whenever I need per-line placement or different styles for different events.

A minimal .ass file for 1920x1080:

[Script Info]
ScriptType: v4.00+
PlayResX: 1920
PlayResY: 1080
WrapStyle: 0

[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,Arial,52,&H00FFFFFF,&H000000FF,&H00000000,&H80000000,-1,0,0,0,100,100,0,0,1,2,0,2,10,10,50,1

[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:01.00,0:00:04.00,Default,,0,0,0,,First subtitle line.

Key fields in the Style line (in order after the name):

  • Fontsize: 52 for 1920x1080; always match PlayResX and PlayResY to your actual video dimensions
  • PrimaryColour: &H00FFFFFF = white fill, fully opaque
  • Bold: -1 = bold on; 0 = off (ASS uses -1/0 for boolean)
  • BorderStyle: 1 = outline + optional shadow; 3 = opaque background box behind the text
  • Outline: 2 = 2px outline; I bump this to 3 for small-screen or low-contrast footage
  • Alignment: 2 = bottom center; 8 = top center; 5 = middle center
  • MarginV: 50 = 50 pixels from the bottom edge of the frame

The burn command with the ass filter is simpler because all style lives in the file:

ffmpeg -i input.mp4 \
  -vf "ass=in.ass" \
  -c:v libx264 -crf 18 -preset slow \
  -pix_fmt yuv420p \
  -c:a copy \
  output.mp4

Encoder settings: what -crf and -preset should I use?

For H.264 output with libx264, these are the settings I use in practice:

  • -crf 18: visually lossless for 1080p. The re-encoded frames are clean enough that most viewers cannot distinguish them from the source. I use this for all client deliverables.
  • -crf 23: the libx264 default; good for internal review drafts where encode speed matters more than quality.
  • -preset slow: better compression than the default medium. On a modern machine, 1080p at slow still encodes faster than real-time. I always use it for final renders.
  • -pix_fmt yuv420p: forces 4:2:0 chroma subsampling for broad player compatibility. Some ffmpeg inputs produce yuv444p, which many devices and streaming platforms do not accept.

When does it make sense to bring in a specialist?

Burn-in with ffmpeg covers the creator use case cleanly. But there are situations where I take a different approach or recommend one:

  • Broadcast and streaming deliverables: broadcasters and streaming platforms need a separate sidecar caption file (SCC, SMPTE-TT, SRT), not a burned-in video. Burn-in is never a valid broadcast deliverable.
  • Accessibility compliance: WCAG 1.2.2 (on-demand video) and the CVAA (US internet video) require captions that viewers can toggle, resize, and recolor. Burned-in captions cannot be turned off.
  • Multi-language releases: encoding three separate re-encodes for three language versions is wasteful. A soft subtitle track per language is far more efficient.
  • Vietnamese subtitle translation: I deliver Vietnamese subtitles as a managed soft track so language versions stay clean and editable. My vertical subtitle work covers both creator burn-in and soft-track management.

FAQ

Can I burn subtitles without re-encoding the video?

No. The subtitles and ass filters run inside the video filter graph, which requires decoding and re-encoding. Using -c:v copy while a -vf filter is active causes an ffmpeg error. Audio is separate and can always be copied with -c:a copy.

Why does force_style only partially change my subtitle appearance?

force_style overrides the default libass style. If your input is already a .ass file with named styles other than Default, or contains inline override tags, those take precedence. For reliable full-style control, switch to an authored .ass file and the ass filter.

How do I place subtitles at the top of the frame?

Set Alignment=8 (top center) and MarginV=30 in force_style. In a .ass file, set Alignment=8 in the [V4+ Styles] line, or add the inline tag {\an8} at the start of individual Dialogue lines that need to be at the top.

What font size should I use for 9:16 vertical video?

For 1080x1920, set PlayResX=1080 and PlayResY=1920 in the .ass [Script Info] block, then use Fontsize=64 to 80 in the Style line. For force_style, FontSize=40 to 48 is a reliable starting range. ASS font sizes scale relative to PlayRes, so always match PlayRes to the actual output dimensions.

Can I burn subtitles into 4K video with ffmpeg?

Yes, the process is identical: provide the 4K source and ffmpeg decodes and re-encodes at full resolution. Use -crf 18 -preset slow for quality. Encode time is significantly longer than 1080p. For better 4K compression, switch to HEVC: -c:v libx265 -crf 22 -preset slow.

Official Sources

Written by Dao Huy (Lucas), Vietnamese translator & localization specialist (EN · ZH · FR → Vietnamese). See translation services →

报价WhatsApp