Nhúng cứng subtitle vào video bằng ffmpeg: force_style và ass
💡 Để nhúng cứng subtitle vào video bằng ffmpeg, dùng
-vf subtitles=in.srt:force_style='FontName=Arial,FontSize=28,Outline=2,MarginV=50'cho SRT, hoặc-vf ass=in.asscho file .ass đầy đủ. Video bắt buộc phải mã hóa lại:-c:v libx264 -crf 18 -preset slow. Âm thanh sao chép bằng-c:a copy. Không có cách nào burn-in mà không mã hóa lại video.
Ý chính
- Dùng bộ lọc
subtitlesvớiforce_stylecho SRT hoặc WebVTT. Chuyển sang-vf ass=in.asskhi cần định vị từng dòng riêng biệt hoặc nhiều style cho từng sự kiện. - Video bắt buộc phải mã hóa lại:
-c:v libx264 -crf 18 -preset slowcho bản giao hàng,-crf 23cho bản nháp. Sao chép âm thanh với-c:a copy. - Các tham số
force_styletối thiểu cần đặt:FontName,FontSize,Outline=2(giúp chữ dễ đọc trên mọi nền),MarginV=50(khoảng cách pixel từ mép dưới). - Định dạng màu trong ASS là ABGR, không phải RGB. Màu trắng là
&H00FFFFFF; hai chữ số hex đầu là kênh alpha,00= hoàn toàn không trong suốt. - Với video dọc 9:16 tại 1080x1920, dùng
FontSize=40đến48trongforce_style, hoặcFontsize=64trong khối[V4+ Styles]của file .ass.
Tại sao burn-in luôn cần mã hóa lại video?
Nhúng subtitle vào video có nghĩa là vẽ chữ lên từng frame video đã giải mã trước khi encoder ghi ra đĩa. Các bộ lọc subtitles và ass của ffmpeg hoạt động bên trong video filter graph, nghĩa là luồng video phải được giải mã, lọc và mã hóa lại. Dùng -c:v copy trong khi một video filter đang hoạt động sẽ gây lỗi: hai thao tác này không tương thích trong pipeline của ffmpeg.
Âm thanh thì khác. Subtitle filter không đụng đến luồng âm thanh, vì vậy -c:a copy cho phép sao chép nguyên vẹn. Tôi luôn thêm -c:a copy vào mọi lệnh burn-in để giữ nguyên chất lượng âm thanh và rút ngắn thời gian xử lý.
Bộ lọc subtitles và force_style: cách nhanh nhất với SRT
Bộ lọc subtitles render SRT, WebVTT hoặc bất kỳ định dạng nào libass hỗ trợ. Khi đầu vào là SRT thuần (không có thông tin style), ffmpeg áp dụng style mặc định của libass, thường quá nhỏ và thiếu viền. Tùy chọn force_style ghi đè style mặc định bằng các tên trường ASS V4+ dưới dạng chuỗi phân cách bằng dấu phẩy.
Đây là lệnh tôi dùng cho video 1920x1080 chuẩn:
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
Trên Windows CMD, bỏ dấu nháy đơn bao ngoài giá trị force_style: viết force_style=FontName=Arial,... trực tiếp bên trong chuỗi -vf được đặt trong dấu nháy kép.
Các tham số force_style nào quan trọng, và tôi thường đặt gì?
Chuỗi force_style nhận tên trường ASS V4+. Đây là các trường tôi cấu hình trên hầu hết mọi dự án:
| Tham số | Điều chỉnh | Giá trị tôi dùng |
|---|---|---|
FontName | Họ font (phải được cài sẵn trên máy) | Arial, Noto Sans |
FontSize | Cỡ chữ tính theo điểm ASS (tương đối với PlayRes) | 28 cho 1080p; 40 cho 1080x1920 |
PrimaryColour | Màu nền chữ (định dạng hex ABGR) | &H00FFFFFF (trắng) |
OutlineColour | Màu viền | &H00000000 (đen) |
BorderStyle | 1 = viền + bóng; 3 = hộp nền mờ | 1 |
Outline | Độ dày viền tính bằng pixel | 2 |
Shadow | Khoảng cách bóng đổ tính bằng pixel | 0 (tôi luôn tắt) |
Alignment | Vị trí theo bàn phím số: 2=dưới giữa, 8=trên giữa | 2 |
MarginV | Pixel từ mép dưới (hoặc trên nếu Alignment 8) | 50 cho 1080p |
Định dạng màu ABGR hay gây nhầm lẫn nhất. Thứ tự byte là Alpha-Blue-Green-Red, ngược với RGB thông thường. Alpha 00 là hoàn toàn không trong suốt. Màu vàng (RGB #FFFF00) trở thành &H0000FFFF trong ABGR: A=00, B=00, G=FF, R=FF.
Giới hạn của force_style: nó ghi đè style mặc định toàn cục, không phải định vị từng dòng riêng. Nếu cần đặt các dòng cụ thể lên trên cùng để tránh chữ trên màn hình, hãy dùng bộ lọc ass.
Bộ lọc ass: toàn quyền kiểm soát style từ file .ass
Bộ lọc ass đọc file .ass gốc và render với độ chính xác cao. Toàn bộ style được định nghĩa trong file: khối [V4+ Styles] đặt style mặc định, và mỗi dòng [Events] có thể dùng thẻ override inline như {\an8} (căn trên giữa) hoặc {\pos(960,54)} (vị trí pixel chính xác). Tôi dùng cách này khi cần định vị từng dòng hoặc style khác nhau cho từng sự kiện.
File .ass tối thiểu cho 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.
Các trường chính trong dòng Style (theo thứ tự sau tên):
- Fontsize:
52cho 1920x1080; luôn khớpPlayResXvàPlayResYvới kích thước video thực tế - PrimaryColour:
&H00FFFFFF= màu trắng, hoàn toàn không trong suốt - Bold:
-1= in đậm bật;0= tắt (ASS dùng -1/0 cho boolean) - BorderStyle:
1= viền + bóng tùy chọn;3= hộp nền mờ đằng sau chữ - Outline:
2= viền 2px; tôi tăng lên3cho video màn hình nhỏ hoặc tương phản thấp - Alignment:
2= dưới giữa;8= trên giữa;5= giữa màn hình - MarginV:
50= 50 pixel tính từ mép dưới của frame
Lệnh burn với bộ lọc ass đơn giản hơn vì toàn bộ style nằm trong file:
ffmpeg -i input.mp4 \
-vf "ass=in.ass" \
-c:v libx264 -crf 18 -preset slow \
-pix_fmt yuv420p \
-c:a copy \
output.mp4
Cài đặt encoder: dùng -crf và -preset bao nhiêu?
Với H.264 đầu ra bằng libx264, đây là các cài đặt tôi thực sự dùng:
-crf 18: chất lượng gần như không mất mát cho 1080p. Các frame được mã hóa lại đủ sắc nét để hầu hết người xem không phân biệt được với nguồn. Tôi dùng cho tất cả bản giao hàng.-crf 23: giá trị mặc định của libx264; phù hợp cho bản nháp nội bộ khi tốc độ mã hóa quan trọng hơn chất lượng.-preset slow: nén tốt hơn so vớimediummặc định. Trên máy hiện đại, 1080p vớislowvẫn nhanh hơn thời gian thực. Tôi luôn dùng cho bản render cuối.-pix_fmt yuv420p: bắt buộc lấy mẫu màu 4:2:0 để tương thích rộng. Một số đầu vào ffmpeg tạo ra yuv444p mà nhiều thiết bị và nền tảng phát trực tuyến không chấp nhận.
Khi nào nên nhờ chuyên gia?
Burn-in bằng ffmpeg giải quyết tốt nhu cầu của creator. Nhưng có những trường hợp tôi sẽ chọn cách khác hoặc khuyến nghị khác:
- Bản giao hàng cho broadcast và streaming: các đài truyền hình và nền tảng streaming cần file caption riêng (SCC, SMPTE-TT, SRT), không phải video đã nhúng cứng. Burn-in không bao giờ là định dạng giao hàng hợp lệ cho broadcast.
- Tuân thủ khả năng tiếp cận: WCAG 1.2.2 (video theo yêu cầu) và CVAA (video internet tại Mỹ) yêu cầu caption có thể bật/tắt, thay đổi kích thước và màu sắc. Caption nhúng cứng không thể tắt được.
- Phát hành nhiều ngôn ngữ: mã hóa lại video ba lần cho ba phiên bản ngôn ngữ rất tốn kém. Track subtitle riêng cho mỗi ngôn ngữ hiệu quả hơn nhiều.
- Dịch subtitle sang tiếng Việt: tôi giao subtitle tiếng Việt dưới dạng track riêng để quản lý phiên bản ngôn ngữ dễ dàng. Xem dịch vụ subtitle dọc của tôi để biết thêm chi tiết.
Câu hỏi thường gặp
Có thể burn subtitle mà không mã hóa lại video không?
Không. Các bộ lọc subtitles và ass chạy trong video filter graph, đòi hỏi giải mã và mã hóa lại. Dùng -c:v copy trong khi -vf filter đang hoạt động sẽ gây lỗi trong ffmpeg. Âm thanh không bị ảnh hưởng và luôn có thể sao chép bằng -c:a copy.
Tại sao force_style chỉ thay đổi được một phần style của subtitle?
force_style ghi đè style libass mặc định. Nếu đầu vào đã là file .ass có style đặt tên khác Default, hoặc chứa thẻ override inline, những thứ đó sẽ được ưu tiên hơn. Để kiểm soát style đầy đủ và đáng tin cậy, chuyển sang file .ass được soạn thảo kỹ và bộ lọc ass.
Làm thế nào để đặt subtitle lên trên cùng của frame?
Đặt Alignment=8 (căn trên giữa) và MarginV=30 trong force_style. Trong file .ass, đặt Alignment=8 trong dòng [V4+ Styles], hoặc thêm thẻ inline {\an8} vào đầu các dòng Dialogue riêng lẻ cần hiển thị ở trên.
Nên dùng cỡ chữ bao nhiêu cho video dọc 9:16?
Với 1080x1920, đặt PlayResX=1080 và PlayResY=1920 trong phần [Script Info] của file .ass, rồi dùng Fontsize=64 đến 80 trong dòng Style. Với force_style, FontSize=40 đến 48 là khoảng phù hợp. Kích thước font ASS tính theo PlayRes, nên luôn khớp PlayRes với kích thước video thực.
Có thể burn subtitle vào video 4K bằng ffmpeg không?
Có, quy trình hoàn toàn giống nhau: cung cấp video 4K và ffmpeg giải mã rồi mã hóa lại ở độ phân giải đầy đủ. Dùng -crf 18 -preset slow để đảm bảo chất lượng. Thời gian mã hóa lâu hơn đáng kể so với 1080p. Để nén tốt hơn ở 4K, chuyển sang HEVC: -c:v libx265 -crf 22 -preset slow.
Nguồn
- FFmpeg Documentation: bộ lọc subtitles - tùy chọn force_style và pipeline render libass. Kiểm tra tháng 9/2026.
- FFmpeg Documentation: bộ lọc ass - render subtitle ASS/SSA gốc. Kiểm tra tháng 9/2026.
- FFmpeg Wiki: HowToBurnSubtitlesIntoVideo - hướng dẫn burn-in chính thức từ wiki ffmpeg. Kiểm tra tháng 9/2026.
- Subtitle Edit (nikse.dk) - công cụ miễn phí để soạn thảo .ass, chỉnh SRT và kiểm tra CPS. Kiểm tra tháng 9/2026.
Written by Dao Huy (Lucas), Vietnamese translator & localization specialist (EN · ZH · FR → Vietnamese). See translation services →
