0

Ghép banner từ giây thứ 3 và end card vào video dọc chỉ với một lệnh ffmpeg (Python)

Mình đang chạy một pipeline Python tự động tạo video dọc (1080x1920) rồi đăng lên. Phần render mình không muốn đụng vào, nên sau khi render xong mình muốn ghép thêm hai thứ:

  1. Một banner hiện từ giây thứ 3 đến hết video
  2. Một ảnh tĩnh (end card) dài 3,5 giây nối sau khung hình cuối, kèm âm thanh câm để độ dài audio khớp

Tách ra thành bước hậu kỳ giúp renderer đơn giản và sau này dễ đổi nội dung banner. Dưới đây là đoạn code chỉ cần gọi ffmpeg một lần, và ba lỗi mình đã gặp trên đường.

Filter graph

Có bốn input:

  • 0 video đã render (có audio)
  • 1 PNG end card (lặp 3,5 giây)
  • 2 âm thanh câm cho end card
  • 3 PNG banner (RGBA, góc trong suốt)
import subprocess, imageio_ffmpeg

FF = imageio_ffmpeg.get_ffmpeg_exe()   # ffmpeg đi kèm gói, không cần cài riêng
DUR = 3.5

def add_banner_and_endcard(src, endcard_png, banner_png, out, w, h, fps, sr):
    sx, sy = int(w * 40 / 1080), int(h * 50 / 1920)   # tính vị trí theo kích thước video
    sw = int(w * 1000 / 1080)
    graph = (
        rf"[3:v]scale={sw}:-1[s];"
        rf"[0:v][s]overlay={sx}:{sy}:enable=gte(t\,3)[o];"          # banner từ giây 3
        rf"[1:v]scale={w}:{h},fps={fps},format=yuv420p,setsar=1[e];"  # end card cùng thông số với video chính
        rf"[o]format=yuv420p,setsar=1[m];"
        rf"[m][0:a][e][2:a]concat=n=2:v=1:a=1[v][a]"
    )
    cmd = [FF, "-y",
           "-i", src,
           "-loop", "1", "-t", str(DUR), "-i", endcard_png,
           "-f", "lavfi", "-t", str(DUR), "-i", f"anullsrc=r={sr}:cl=mono",
           "-i", banner_png,
           "-filter_complex", graph,
           "-map", "[v]", "-map", "[a]",
           "-c:v", "libx264", "-preset", "veryfast", "-crf", "20",
           "-c:a", "aac", "-b:a", "160k", "-movflags", "+faststart", out]
    subprocess.run(cmd, check=True)

w, h, fps và sample rate sr được đọc từ file gốc: chạy ffmpeg -i file_goc rồi dùng regex quét stderr là đủ.

Lỗi 1: dấu phẩy trong enable=

enable=gte(t,3) trông bình thường, nhưng trong -filter_complex dấu phẩy là ký tự phân tách filter. Biểu thức bị cắt làm đôi và ffmpeg báo lỗi parse rất khó hiểu. Hãy escape: gte(t\,3).

Trong Python phải dùng raw string (rf"...") để dấu gạch chéo ngược đi đến ffmpeg nguyên vẹn. Với f-string thường thì hôm nay là SyntaxWarning, ngày mai là bug.

Lỗi 2: concat từ chối end card

concat yêu cầu mọi đoạn nối có cùng kích thước, frame rate, pixel format và SAR. PNG lặp là RGB, không có thông tin fps, SAR cũng có thể khác. Sau khi đưa cả hai phía về scale=…,fps=…,format=yuv420p,setsar=1, lỗi "Input link parameters do not match" biến mất.

Lỗi 3: layout kênh audio

Renderer của mình xuất audio mono 24 kHz. Khi mình tạo âm câm bằng cl=stereo, concat báo audio không khớp. Âm câm phải có cùng sample rate và kênh với bản gốc (anullsrc=r=24000:cl=mono), hoặc đọc thông số từ bản gốc trước.

Giữ lại bản gốc

Hàm trên ghi ra file mới. Trong pipeline thật, mình đổi tên bản gốc thành ten.noend.mp4 trước, và chỉ ghi đè cùng tên khi ffmpeg thành công. Nếu tiến trình chết giữa chừng thì bản gốc vẫn còn; và vì sự tồn tại của file đó nghĩa là "đã xử lý", chạy lại cùng một việc hai lần vẫn an toàn.

Kết quả

Banner (từ giây 3 đến hết) và end card được ghép chỉ trong một lần encode. Với video 40 giây, mất khoảng 10 đến 20 giây trên CPU desktop thông thường. Đây là mẫu dùng được cho các việc như "watermark từ một thời điểm nhất định" hoặc "thêm slate cuối video".


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí