🧑‍💻 개발 지식

홈 › 기술 블로그

ffmpeg 한 번에 세로 영상에 「3초 뒤 배너」와 「끝 카드」 붙이기 (파이썬)

📄 PDF 다운로드

작성 2026-10-20 · 읽는 시간 약 6분 · 작성 루리의 줍줍 편집

#자동화

파이썬으로 세로 영상(1080×1920)을 만들어 올리는 자동화를 돌리고 있습니다. 렌더러는 건드리지 않고, 렌더가 끝난 뒤에 두 가지를 더 붙이고 싶었습니다.

  1. 영상 3초 지점부터 끝까지 계속 보이는 배너
  2. 마지막 프레임 뒤에 붙는 3.5초짜리 정지 이미지(끝 카드). 오디오 길이를 맞추려고 무음도 같이 붙입니다.

후처리로 빼 두면 렌더러가 단순하게 유지되고 배너 내용도 나중에 바꾸기 쉽습니다. 아래는 제가 쓰는 ffmpeg 호출 한 번짜리 코드와, 가는 길에 만난 실수 세 가지입니다.

필터 그래프

입력은 네 개입니다.

import subprocess, imageio_ffmpeg

FF = imageio_ffmpeg.get_ffmpeg_exe()   # 번들 ffmpeg, 따로 설치 안 해도 됨
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)   # 영상 크기에 맞춰 위치 계산
    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];"          # 3초부터 배너
        rf"[1:v]scale={w}:{h},fps={fps},format=yuv420p,setsar=1[e];"  # 끝 카드를 본편과 같은 규격으로
        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, 오디오 샘플레이트 sr은 원본에서 읽어 옵니다. ffmpeg -i 원본을 실행해 나오는 stderr를 정규식으로 훑으면 충분합니다.

실수 1: enable= 안의 쉼표

enable=gte(t,3)은 멀쩡해 보이지만 -filter_complex 안에서 쉼표는 필터 구분자입니다. 식이 둘로 쪼개져서 알아보기 힘든 파싱 오류가 납니다. gte(t\,3)처럼 쉼표를 이스케이프하면 됩니다.

파이썬에서는 그래프 문자열을 raw 문자열(rf"...")로 써야 백슬래시가 ffmpeg까지 그대로 갑니다. 일반 f-string이면 오늘은 SyntaxWarning, 내일은 버그입니다.

실수 2: concat이 끝 카드를 거부함

concat은 이어 붙이는 조각들의 크기, 프레임레이트, 픽셀 포맷, SAR이 모두 같아야 합니다. PNG 반복은 RGB이고 fps 정보가 없으며 SAR도 다를 수 있습니다. 양쪽 모두 scale=…,fps=…,format=yuv420p,setsar=1로 규격을 맞추자 "Input link parameters do not match" 오류가 모두 사라졌습니다.

실수 3: 오디오 채널 배치

제 렌더러는 24kHz 모노 오디오를 씁니다. 무음을 cl=stereo로 만들었더니 concat이 오디오가 안 맞는다고 했습니다. 무음도 원본과 같은 샘플레이트·채널(anullsrc=r=24000:cl=mono)로 만들거나, 원본을 먼저 읽어서 맞춰야 합니다.

원본은 남겨 두기

위 함수는 새 파일에 씁니다. 실제 파이프라인에서는 원본을 먼저 이름.noend.mp4로 옮기고 성공했을 때만 같은 이름으로 덮어씁니다. 중간에 프로세스가 죽어도 원본이 남아 있고, 그 파일이 있으면 "이미 처리한 영상"이라고 보고 건너뛰게 만들어 두었기 때문에 같은 작업을 두 번 돌려도 안전합니다.

결과

배너(3초부터 끝까지)와 끝 카드가 인코딩 한 번에 붙습니다. 40초짜리 영상이면 일반 데스크톱 CPU에서 10~20초 걸립니다. 「특정 시각부터 워터마크」나 「끝에 슬레이트 붙이기」 같은 작업에 그대로 쓸 수 있는 패턴입니다.

저는 정보 사이트를 AI 코딩 에이전트와 함께 운영하며 이런 자동화를 매일 돌리고 있습니다. 그 사이트는 루리의 줍줍이고, 자동화·개발 기록은 개발 지식에 쌓고 있습니다.

English summary

A single ffmpeg filter_complex call that adds a banner starting at 3 seconds and a 3.5-second end card (with matching silent audio) to a finished vertical video, driven from Python. Three mistakes are explained: the comma inside enable=gte(t,3) must be escaped, the concat inputs and audio channels must match in format, and the Python graph string must be a raw string so the backslashes reach ffmpeg.

관련 글

← 기술 블로그 홈 · 사이트 제작 로그 · RSS

관련: 개발 지식 전체

이 글에 대해 묻고 싶으면 ❓ 개발 FAQ에 남겨 주세요.

📤 공유