"재생해봤는데 괜찮던데요" — 영상 검수를 코드로 하는 법

완성본을 재생해서 판단하면 안 된다. 앞부분 유실도, 자막 텍스트 변형도 눈으로는 안 잡힌다.

완성본은 재생해서 판단하면 안 됩니다. 앞부분이 통째로 잘려도, 자막 텍스트가 대본과 달라져도 눈과 귀로는 잡히지 않습니다. 한 편을 만들며 내보내기를 네 번 했고, 네 번 모두 코드로 찾았습니다.
내보내기 네 번
| 회차 | 문제 | 발견 수단 |
|---|---|---|
| 1 | 자막 하단 여백 8% — 쇼츠 UI가 덮음 | 격자 크롭 |
| 2 | 타임라인 앞 2.4초 유실 | 오디오 상관분석 |
| 3 | 자막 3건이 대본과 다름 | 컨택트 시트 |
| 4 | 출처 자막 상단 11% — 위쪽 UI에 걸림 | 격자 크롭 |
사고 ① 자막 위치
쇼츠는 하단 15~20%를 UI(채널명·제목·음원)가, 상단 12%를 검색·카메라가 덮습니다. 캡컷 기본 자막 위치는 아래 여백 8% 근처라 그대로 두면 아랫줄이 가려집니다. 격자를 겹쳐 실측합니다 — 한 칸이 화면의 5%입니다.
ffmpeg -ss <t> -i final.mp4 -frames:v 1 \
-vf "crop=1080:600:0:1320,drawgrid=w=1080:h=96:t=2:c=red" /tmp/bot.png
목표는 아래 여백 20% 이상, 1920px 기준 약 250px 위입니다. 출처 자막을 올릴 때 반대쪽으로 넘어가지 않게 합니다 — 4회차에서 상단 11%까지 올라가 이번엔 위쪽 UI에 걸렸습니다.
사고 ② 앞 2.4초가 사라졌다 — 이 글의 핵심
자막 위치를 고쳐 재출력했더니 길이가 70.96초 → 68.58초로 줄었습니다. 나레이션은 70.50초입니다. 재생해보면 멀쩡해 보입니다. 훅이 사라진 걸 몰랐습니다.
나레이션 원본과 완성본의 음량 포락선을 상관분석해 시간 어긋남(lag)을 잽니다.
구간별 lag → 0s:+2.40 8s:+2.35 16s:+2.40 24s:+2.35
32s:+2.35 40s:+2.35 48s:+2.35 56s:+2.40
전 구간이 일정하게 +2.4초 → 앞이 잘린 것입니다. 중간에서 값이 튀면 그 지점에서 구간이 삭제·삽입된 것입니다. 정상본은 이렇게 나옵니다.
구간별 lag → 0s:+0.00 8s:-0.15 16s:-0.10 24s:-0.10
32s:-0.10 40s:-0.15 48s:-0.15 56s:-0.10
배속 변경 가설도 검증했다
68.58 / 70.50 = 2.8% 차이라 "속도가 바뀐 것 아닌가" 의심할 수 있습니다. 재봤습니다.
| 가설 | 최적 lag | 상관 |
|---|---|---|
| 속도 동일 | 2.35초 | 0.673 |
| 2.8% 빨라짐 | 1.55초 | 0.192 |
배속 아닙니다. 가설을 세우고 수치로 죽이는 과정을 그대로 남깁니다.
알고리즘(numpy 없이)
- ffmpeg로 두 오디오를 4000Hz 모노 s16le로 디코드
- 50ms 창의 RMS로 포락선 계산
- 정규화(평균 0, 표준편차 1)
- 8초 창을 밀어가며 −2초~+4초 범위에서 내적 최대인 lag 탐색
def envelope(raw, hop):
a = array.array('h'); a.frombytes(raw)
return [math.sqrt(sum(v*v for v in a[i:i+hop])/hop)
for i in range(0, len(a)-hop, hop)]
사고 ③ 자막 텍스트가 대본과 달랐다
자막 55블록의 중간 시점 프레임에서 자막 띠만 잘라 세로로 이어 붙였습니다(14개씩 4장). 대본과 한 줄씩 대조하니 3건이 나왔습니다.
| 대본 | 화면 | 심각도 |
|---|---|---|
| 동물들이 똑같이 속는다면, 건물은 원인이 | 동물들이 똑같이 속는다면 원인이 | 논지 붕괴 |
| 비판의 대표 증거 | 비판의 대표적인 증거 | 경미 |
| 1960년대에 연구자들이 | 1960년대 연구자들이 | 무시 가능 |
건물은이 빠지면 무엇이 원인이 아닌지가 사라집니다. 이 문장은 각진 세계 가설을 무너뜨리는 자리인데 "원인이 될 수 없다"가 허공에 뜹니다. 게다가 소리로는 들리니 자막이 틀린 게 드러납니다. 고쳤더니 건물은이 옆 블록으로 들어가 어순이 뒤집혔습니다 — 한 번 더 왕복했습니다.
그래서 자동 캡션을 쓰지 않는다
자동 캡션을 피하는 이유는 정확도가 아니라 출처입니다. 음성을 다시 인식하므로 화면 문장이 대본과 달라질 수 있습니다. 음소거 시청자에게는 자막이 곧 원문입니다. 대본에서 기계적으로 SRT를 만들면 어긋날 수가 없습니다. 무손실 왕복을 검증합니다.
자막 글자수 586 / 대본 글자수 586
완전 일치: True
문단 경계를 TTS 자막에 고정한다
글자 수 비례로만 배분하면 뒤로 갈수록 자막이 앞서 나갑니다. 문단마다 낭독 속도가 다르기 때문입니다.
타입캐스트가 주는 SRT는 문단 단위(한 블록이 12~13초)라 자막으로는 못 씁니다. 그런데 문단이 언제 시작하고 끝나는지는 정확합니다. 그걸 못으로 박고 문단 안에서만 나눕니다.
| 문단 | 글자 수 비례 | 문단 고정 |
|---|---|---|
| 4 | −0.77초 | +0.01초 |
| 5 | −1.63초 | +0.00초 |
| 6 | −0.87초 | +0.12초 |
최대 어긋남 1.63초 → 0.12초. 13배 좋아졌습니다. TTS 도구가 자막 파일을 주면 버리지 말고 받아 둡니다. 자막으로 쓰려는 게 아니라 기준점으로 쓰려는 것입니다.
부수 효과 — 줄바꿈도 여기서 잡힌다
캡컷은 자막 한 줄에 12~14자가 들어가고, 넘치면 어절 중간에서 자릅니다(아니었습니 / 다, 반대로 가장 심하게 속 / 은). 생성기에서 블록 24자 상한 + 어절 경계에서만 분할해 원천 차단합니다.
SRT 만들 때 낸 버그
f"{start} --> {end}\n{text}".replace('.', ',') # ← 이러면 자막 본문의 마침표까지 쉼표가 된다
타임스탬프 변환은 전용 함수로 분리합니다. 그리고 HH:를 빠뜨리면 어떤 플레이어는 조용히 무시합니다.
왕복을 없앤 방법 — 조각을 미리 굽는다
1편에서 내보내기를 네 번 했습니다. 2·3편에서는 캡컷에 트리밍을 시키지 않았습니다.
컷 표대로 ffmpeg로 조각을 미리 구워 cuts/01.mp4 … 17.mp4로 넘깁니다. 캡컷은 전부 골라 순서대로 놓기만 합니다. 자를 것도 없고 키프레임을 찍을 것도 없습니다.
| 001 | 002 | 003 | |
|---|---|---|---|
| 방식 | 캡컷에서 트리밍 | cuts/ 재단 | cuts/ 재단 + 이미지 굽기 |
| 오디오 정렬 | 0.15초 | 0.05초 | 0.05초(한 번은 0.00초) |
| 내보내기 | 4회 | 1회 | 3회(전부 자막 높이) |
프레임 단위로 자릅니다. 초 단위로 자르면 24fps 경계에서 반올림이 누적돼 마지막에 어긋납니다. 시작 시각을 프레임으로 환산하고 길이를 역산하면 합계가 나레이션과 정확히 맞습니다.
17구간 · 1740프레임 · 72.500초
나레이션 72.500초
3편은 이미지 8장도 여기서 같이 구웠습니다 — zoompan으로 줌·팬을 입혀 영상으로 만들었습니다(5편). 움직임을 바꾸려면 컷 표의 값만 고치고 그 번호만 다시 굽습니다.
캡컷에 트리밍을 시키지 않으면 오차가 낄 자리가 없다.
캡컷 실무 요약
- 오디오를 먼저 깐다. 나레이션이 기준선이고 영상이 거기 맞춰진다
- 컷은 말이 끝나는 지점에서 전환한다
- 출처·수치는 자막 트랙과 다른 트랙에 올린다(같은 트랙이면 자막이 밀린다)
- 자막을 옮길 때 모서리 핸들을 잡으면 크기가 바뀐다 — 가운데를 잡는다
오디오 내보내기는 별도 음원 파일 옵션이지 영상 소리와 무관하다
전 구간이 일정하게 +2.4초면 앞이 잘린 것이고, 중간에서 튀면 그 지점이 삭제된 것이다.
음소거로 보는 시청자에게는 자막이 곧 원문이다.
이 시리즈는 제가 실제로 만든 유튜브 쇼츠 한 편(완성본 보기)의 제작 기록입니다. 문화인류학·심리학 소재로 상식을 뒤집는 지식 쇼츠를 만듭니다.
발행 2026-08-19 · 최종 확인 2026-08-20(3편 반영)