ffmpeg와 파이썬으로 화면녹화를 인스타 릴스로 자동 편집하기
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
화면녹화 4개가 릴스 한 편이 되기까지
ffmpeg 세로영상 변환을 파이썬으로 자동화한 뒤로, 화면녹화 몇 개만 던지면 자막까지 얹힌 9:16 릴스 완성본이 나오는 파이프라인을 직접 만들어 쓰고 있습니다. 처음에는 캡컷으로 다 했습니다. 화면녹화를 불러오고, 세로 캔버스에 맞추고, 구간마다 배속을 다르게 걸고, 자막을 한 줄씩 올리고, 마지막 프레임을 늘려 여운을 줍니다. 한 편에 30분 넘게 걸렸고, 무엇보다 매번 같은 손동작을 반복한다는 게 문제였습니다.
그 편집 과정을 ffmpeg 필터와 파이썬으로 옮기면서, 좌우 잘림·녹화 컨트롤바·자막 타이밍 같은 함정에 부딪혔습니다. 여기서는 그 함정과 실제로 통한 명령을 그대로 적어 둡니다. 캡컷에서 매번 같은 작업을 반복하거나, 화면녹화 릴스 만들기를 코드로 옮기고 싶은 분에게 도움이 될 것입니다.

이 글은 2026년 6월 기준 macOS + Homebrew ffmpeg 환경에서 작성했습니다.
톤앤매너를 한 번 확정한 뒤에는 사람이 개입할 이유가 없었습니다. 그래서 역할을 나눴습니다. 사람은 녹화와 업로드만 하고, 자르기·배속·여백·자막·블러 같은 편집은 전부 스크립트가 합니다. 입력 규칙은 단순합니다. 비트(beat)별로 화면녹화를 따로 찍어 정해진 이름으로 폴더에 둡니다.
~/Downloads/reel_raw/
1_hook.mov # 문제 상황 (느리고 답답한 수동 작업)
2_promise.mov # 해결 동작 한 번
3_how.mov # 설정·원리 화면
4_proof.mov # 실제 결과물
각 녹화는 길어도 됩니다. 어차피 파이프라인이 배속으로 압축하기 때문입니다. 출력 목표는 명확합니다. 9:16 / 1080×1920 / 무음 / H.264 / 30fps. 무음인 이유는, 음악은 업로드 단계에서 트렌딩 오디오로 따로 입히기 때문입니다. 영상은 그림만 책임지고, 메시지는 자막 카드가 전달합니다. ffmpeg 인코딩 옵션은 전 구간에서 동일하게 고정했습니다.
ENC = ["-c:v", "libx264", "-pix_fmt", "yuv420p",
"-r", "30", "-an", "-y", "-loglevel", "error"]
-an으로 오디오를 버리고, -pix_fmt yuv420p로 인스타그램·사파리에서 깨지지 않는 색 포맷을 강제합니다. yuv420p를 빼먹으면 일부 환경에서 영상이 검게 나오는데, 이게 첫 번째로 시간을 잡아먹은 함정이었습니다.
9:16 변환의 진짜 함정: 좌우 잘림과 녹화 컨트롤바
가로로 찍힌 화면녹화를 세로 릴스에 넣는 방법은 두 가지입니다. 꽉 채우거나(cover), 다 보이게 넣거나(contain). scale=1080:1920처럼 강제로 늘리면 화면이 일그러지고, crop으로 꽉 채우면 좌우가 잘려 정작 보여주려던 UI가 사라집니다. 데모 영상에서는 화면 내용이 핵심이라 잘림은 치명적입니다.
그래서 contain 방식으로 갑니다. 가로 폭을 1000px로 맞춰 축소한 뒤, 1080×1920 캔버스 중앙에 놓고 남는 공간을 다크 그레이로 채웁니다.
ffmpeg -i 1_hook.mov \
-vf "scale=1000:-2,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:0x1a1a1a,setsar=1" \
-c:v libx264 -pix_fmt yuv420p -r 30 -an out.mp4
scale=1000:-2— 가로 1000px, 세로는 비율 유지(-2는 2의 배수로 자동, libx264 요구사항).pad=1080:1920:(ow-iw)/2:(oh-ih)/2:0x1a1a1a— 캔버스 1080×1920, 원본을 정중앙에 배치((ow-iw)/2), 여백색#1a1a1a.setsar=1— 픽셀 종횡비를 1로 고정. 이걸 빼면 합칠 때 영상이 미묘하게 늘어납니다.

여기까지는 검색하면 나옵니다. 진짜 함정은 그다음이었습니다. macOS 화면녹화(⇧⌘5)는 하단에 녹화 컨트롤바가 같이 찍히는 경우가 있습니다. 영역 녹화를 해도 마우스를 내리면 컨트롤바가 올라와 프레임에 들어옵니다. 완성본에 회색 막대가 박혀 나오는 걸 보고서야 알았습니다. 해결은 padding 전에 하단을 잘라내는 것입니다.
# 컨트롤바 제거: 하단 150px crop을 pad보다 먼저
-vf "crop=iw:ih-150:0:0,scale=1000:-2,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:0x1a1a1a,setsar=1"
crop=iw:ih-150:0:0은 "가로는 그대로(iw), 세로는 150px 줄여서(ih-150), 좌상단(0,0)부터" 잘라낸다는 뜻입니다. 순서가 중요합니다. crop을 scale·pad보다 먼저 걸어야 원본 픽셀 기준 150px가 정확히 잘립니다. scale 뒤에 걸면 축소된 좌표계라 엉뚱한 영역이 날아갑니다. ffmpeg 필터는 왼쪽부터 순서대로 적용된다는 걸 잊으면 이런 데서 깨집니다.
비트별 배속: 같은 영상도 리듬이 달라야 한다
릴스가 지루해지는 가장 큰 이유는 모든 구간을 같은 속도로 보여주기 때문입니다. 긴 타이핑은 빠르게 넘겨야 하고, 설정 화면은 읽을 시간을 줘야 하며, 결과물은 압축하되 마지막은 멈춰서 여운을 남겨야 합니다. 그래서 비트마다 배속을 다르게 겁니다.
| 비트 | 처리 | 목표 길이 |
|---|---|---|
| Hook(문제) | setpts=PTS/6 (6배속) |
3초 |
| Promise(해결) | setpts=PTS/3 (3배속) |
3.5초 |
| How(방법) | 1배속 + -t로 자름 |
3.5~5초 |
| Proof(결과) | setpts=PTS/5 + 마지막 정지 |
5초 + 2.5초 |
ffmpeg에서 배속은 setpts(presentation timestamp)로 겁니다. 속도를 N배 빠르게 하려면 타임스탬프를 1/N로 줄입니다.
# 6배속
-vf "...,setpts=PTS/6"
PTS/6이 6배속인 게 직관에 어긋나 헷갈리는데, "각 프레임이 표시되는 시각을 1/6로 당긴다 → 6배 빨리 지나간다"로 이해하면 됩니다. 무음 영상이라 atempo 같은 오디오 배속을 맞출 필요가 없어 오히려 단순합니다.
마지막 결과(Proof) 비트는 배속으로 압축한 뒤, 마지막 프레임을 2.5초 정지시켜 CTA를 얹을 시간을 만듭니다.
-vf "...,setpts=PTS/5,tpad=stop_mode=clone:stop_duration=2.5"
tpad=stop_mode=clone:stop_duration=2.5는 "마지막 프레임을 복제(clone)해 2.5초 더 붙인다"는 뜻입니다. 이미지로 따로 정지 화면을 만들어 붙이는 것보다 깔끔하고, 영상 흐름이 끊기지 않습니다. 이 배속 값들을 파이썬 설정으로 빼두면, 주제가 바뀌어도 숫자만 조정하면 됩니다.
"beats": [
{"src": "1_hook.mov", "speed": 6, "dur": 3.0},
{"src": "2_promise.mov", "speed": 3, "dur": 3.5},
{"src": "3_how.mov", "speed": 1, "dur": 3.5, "ss": 0.5},
{"src": "4_proof.mov", "speed": 5, "dur": 5.0, "hold": 2.5},
],
ss는 시작 지점을 살짝 건너뛰는 옵션(-ss 0.5)으로, 녹화 첫 부분의 군더더기를 잘라낼 때 씁니다.
자막은 영상이 아니라 PNG로 — 그리고 타이밍 함정
자막을 ffmpeg drawtext 필터로 직접 그리는 방법도 있지만, 한글 줄바꿈·둥근 박스·여백을 세밀하게 잡기가 까다롭습니다. 그래서 자막을 투명 PNG로 미리 렌더링한 뒤 영상에 얹는 방식을 택했습니다. 디자인 제어가 훨씬 쉽고, 같은 스타일을 매 편 똑같이 재현할 수 있습니다. PNG는 Pillow로 만듭니다. 핵심은 폰트 지정입니다.
from PIL import Image, ImageDraw, ImageFont
FONT = "/System/Library/Fonts/AppleSDGothicNeo.ttc"
FONT_IDX = 6 # Bold (이 .ttc 안의 Bold 인덱스)
font = ImageFont.truetype(FONT, 58, index=FONT_IDX)
여기서 막혔던 부분이 .ttc 파일입니다. macOS 기본 한글 폰트 AppleSDGothicNeo.ttc는 여러 굵기를 한 파일에 담은 컬렉션이라, index를 지정하지 않으면 엉뚱한 굵기가 나옵니다. Bold가 몇 번 인덱스인지는 환경마다 다를 수 있어, 0번부터 출력해보며 찾아야 했습니다. 이 환경에서는 6번이 Bold였습니다.
박스는 클레이 톤(#D97757) 둥근 사각형에 검은 글씨, 테두리는 없습니다. 텍스트 블록의 세로 중심을 화면 y≈380(상단-중앙)에 둬서, 인스타그램 우측 버튼과 하단 캡션 영역을 피합니다.
W, H = 1080, 1920
CLAY = (217, 119, 87, 240) # #D97757
CY, PAD, RADIUS = 380, 28, 30
img = Image.new("RGBA", (W, H), (0, 0, 0, 0))
d = ImageDraw.Draw(img)
# (텍스트 크기 측정 → 중앙 정렬 → 둥근 박스 → 글씨 순서로 그림)
PNG가 준비되면 영상에 overlay 필터로 얹습니다. 여기가 두 번째 큰 함정이었습니다. 자막은 해당 비트가 화면에 떠 있는 동안에만 보여야 합니다. 즉 시간창(time window)을 정확히 지정해야 합니다.
ffmpeg -i base.mp4 -i cap0.png \
-filter_complex "[0:v][1:v]overlay=enable='between(t\,0.1\,3.0)'[vout]" \
-map "[vout]" ...
overlay=enable='between(t,a,b)'는 "영상 시간 t가 a~b초 사이일 때만 이 오버레이를 켠다"는 뜻입니다. 각 비트의 시작·끝 시각을 누적 계산해 자막창을 만듭니다. 주의할 점은 filter_complex 안에서 콤마를 그대로 쓰면 ffmpeg가 필터 구분자로 오해하므로, between(t\,a\,b)처럼 콤마를 이스케이프해야 한다는 것입니다. 이걸 놓쳐서 "Invalid argument" 에러로 한참 헤맸습니다.

자막창은 비트 구간보다 0.1초 안쪽으로 잡습니다. 세그먼트 경계에서 자막이 깜빡이거나 다음 비트로 새는 걸 막기 위한 여유입니다.
개인정보 블러와 전체 파이프라인 조립
데모 화면에는 종종 아이디·이메일 같은 개인정보가 노출됩니다. 특정 영역만 블러 처리하려면, 영상을 둘로 복제해 한쪽만 잘라 흐리게 만든 뒤 원본 위에 다시 얹습니다.
-vf "split=2[m][b];[b]crop=980:185:260:480,boxblur=28:2[bb];[m][bb]overlay=260:480,crop=iw:ih-150:0:0,..."
split=2[m][b]— 스트림을 main과 blur용으로 복제.[b]crop=w:h:x:y,boxblur=28:2[bb]— 블러 영역만 잘라 강하게 흐림(boxblur=28:2).[m][bb]overlay=x:y— 흐린 조각을 원래 위치에 다시 덮음.
이제 조각들을 하나로 합칩니다. 비트별 세그먼트를 먼저 각각 mp4로 뽑은 뒤, concat 디먹서로 이어 붙입니다.
# list.txt 에 file seg0.mp4 / file seg1.mp4 ... 나열
ffmpeg -f concat -safe 0 -i list.txt -c copy base.mp4
세그먼트를 모두 같은 코덱·해상도·fps로 뽑았기 때문에 -c copy로 재인코딩 없이 빠르게 붙습니다. 마지막으로 base 영상 위에 자막 PNG들을 시간창에 맞춰 한 번에 올립니다. 오버레이가 여러 개면 명령줄이 길어지므로, filter_complex를 파일로 빼서 -filter_complex_script로 넘기면 깔끔합니다.
# 오버레이 체인을 문자열로 만들어 파일로 저장 후
run([FF, "-i", "base.mp4", *png_inputs,
"-filter_complex_script", "fc.txt", "-map", "[vout]", *ENC, "final.mp4"])
전체 흐름을 정리하면 이렇습니다.
녹화 4개 → (비트별) crop·배속·여백·블러 → 세그먼트 mp4
→ concat → base.mp4
→ 자막 PNG들 overlay(between) → final.mp4
한 가지 운영 팁. 빌드는 /tmp에서 돌리고 결과만 최종 폴더로 복사합니다. 클라우드 동기화 폴더(구글 드라이브 등)에서 직접 ffmpeg 중간 파일을 쓰면, 동기화와 파일 쓰기가 충돌해 간헐적으로 깨집니다. 작업은 로컬 임시 폴더, 산출물만 복사가 안전합니다.
실제로 돌려보며 다듬은 것
검증을 자동화하면 매 편 눈으로 확인하는 수고가 줄어듭니다. 빌드 마지막에 완성본에서 비트별 대표 프레임을 뽑아 가로로 이어 붙이면(hstack), 자막·블러·여백을 한 장에서 점검할 수 있습니다. 길이는 ffprobe로 확인합니다.
ffprobe -v error -show_entries format=duration -of csv=p=0 final.mp4
핵심은 ffmpeg 필터를 한 번 확정해두면, 이후로는 녹화본과 설정값만 바꾸면 같은 톤의 결과가 반복적으로 나온다는 점입니다.
마치며
"릴스 편집"을 디자인 작업으로 붙들고 있을 때는 매번 30분이 들었는데, 재현 가능한 파이프라인으로 옮기고 나니 반복이 사라졌습니다. contain 패딩으로 잘림을 막고 crop으로 컨트롤바를 지운 다음, 비트별 setpts로 리듬을 주고 자막은 PNG로 따로 만들어 between(t) 시간창에 얹는 식입니다. 한 번 톤을 고정하면, 다음부터는 녹화본만 던지고 설정값 몇 개만 바꾸면 됩니다.
처음부터 스크립트로 만들 필요는 없습니다. 먼저 손으로 한 편을 완성해 ffmpeg 필터 조합을 확정한 뒤, 반복되는 부분만 파이썬으로 옮기는 순서를 권합니다. 검색해도 잘 안 나오는 함정 — .ttc 폰트 인덱스, 필터 순서, between 콤마 이스케이프 — 만 피하면 나머지는 ffmpeg 필터 조립의 문제입니다. 같은 원리로 유튜브 쇼츠, 제품 데모 GIF도 같은 파이프라인으로 뽑아 쓰고 있습니다.
자주 묻는 질문
Q. ffmpeg를 몰라도 따라 할 수 있나요?
A. 이 글의 명령을 그대로 복사해 파일명만 바꾸면 동작합니다. 핵심은 필터 4개(crop·scale/pad·setpts·overlay)뿐이고, 나머지는 파이썬으로 묶는 일입니다. 처음 한 번은 명령을 하나씩 돌려보며 결과를 확인하길 권합니다.
Q. 캡컷·프리미어보다 정말 빠른가요?
A. 한 편을 처음 손으로 만들면 30분~1시간이지만, 파이프라인이 확정된 뒤로는 녹화본만 폴더에 넣고 스크립트를 돌리면 수십 초에 끝납니다. "같은 포맷의 영상을 반복 발행"하는 경우 자동화 이득이 가장 큽니다.
Q. 화면녹화에 컨트롤바가 자꾸 찍히는데 어떻게 막나요?
A. macOS 녹화 자체에서 완전히 막기 어렵다면, 후처리로 crop=iw:ih-150:0:0을 필터 맨 앞에 걸어 하단을 잘라내면 됩니다. 잘리는 픽셀 수(150)는 녹화 해상도에 맞춰 조정하세요.
Q. 자막이 엉뚱한 시간에 뜨거나 안 보입니다.
A. overlay=enable='between(t,a,b)'의 a·b 시각이 비트 구간과 맞는지 확인하세요. 그리고 filter_complex 안에서는 콤마를 \,로 이스케이프해야 합니다. 이스케이프를 빼면 "Invalid argument" 에러가 납니다.
이 글은 제가 실제 운영 중인 릴스 자동 빌드 스크립트(ffmpeg + Pillow)의 코드와 시행착오를 기준으로 정리했습니다. (2026년 6월 기준)
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
댓글
댓글 쓰기