홈시리즈멘토링

© 2026 정기창. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 정기창. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

whisper와 Claude로 영상 자막·무음 컷 자동화하기 — 동작 원리

정기창·2026년 6월 11일

자기소개 쇼츠 하나를 만들려다 멈춰 선 적이 있습니다. 영상에 자막을 입히고, 말이 끊긴 무음 구간을 잘라내는 일. 손으로 하면 30분이면 되는 단순 노동이지만, 똑같은 영상을 또 만들 생각을 하니 막막했습니다. 그때 문득, 마침 옆에 켜둔 대화 세션의 Claude가 이 편집을 대신해 줄 수 있지 않을까 하는 생각이 들었습니다.

결론부터 말씀드리면 가능했습니다. 다만 이 글에서 정말 나누고 싶은 것은 명령어 모음이 아니라, "왜 이렇게 동작하는가"입니다. 자막과 무음 컷을 자동화하는 과정에서 직관을 두 번이나 배신당했고, 그 지점들이 결국 이 파이프라인을 지탱하는 설계가 되었기 때문입니다.

"AI가 영상을 편집한다"는 말의 실체

"AI로 영상을 편집한다"는 표현을 들으면, 모델이 픽셀을 직접 만지는 장면을 상상하기 쉽습니다. 그러나 실제로 영상 편집 자동화를 들여다보면, 일은 성격이 전혀 다른 두 종류로 갈립니다. 하나는 정확한 타이밍과 렌더링입니다. 16kHz로 오디오를 뽑고, 자막을 1080×1920 해상도에 번인하고, 무음 구간을 밀리초 단위로 이어 붙이는 일. 이건 결정적인 도구가 압도적으로 잘합니다. 다른 하나는 "어디서 자르고 어디서 묶을지"라는 판단입니다. 어절을 어느 지점에서 끊어 한 줄로 만들지, 어떤 단어를 강조할지, 어느 침묵을 잘라내고 어느 호흡을 남길지.

그동안 자동화의 진짜 병목은 늘 후자였습니다. 한국어 자막 자동화의 검증된 패턴(인프런·Cotuber의 기술 블로그)을 보면, 이 판단의 자리에 Kiwi 형태소 분석과 "10자 초과면 분리, 10초 초과면 분리" 같은 결정적 휴리스틱을 둡니다. whisper 계열의 --max_line_width 줄 제한이 한국어에서는 사실상 동작하지 않기 때문에, 어절 단위 타임스탬프를 뽑은 뒤 형태소 분석으로 다시 끊는 방식이 표준이 된 것입니다.

이 글의 파이프라인은 바로 그 "형태소 분석 + 휴리스틱" 자리를 이미 작업 중인 대화 세션의 Claude로 대체합니다. 외부 LLM API가 아닙니다. OpenAI나 Gemini 키를 발급받고 SDK를 설치하고 비용을 지불하는 대신, 옆에서 이미 켜져 돌아가고 있는 Claude에게 어절 목록을 건네고 자막 청크를 받습니다. 1회성 편집 작업에 별도의 인프라를 들일 이유가 없다는 판단이었습니다. 추가 비용은 0이고, 분절은 더 자연스러우며, STT 오류 교정과 강조 키워드 선정까지 한 번에 처리됩니다.

자막 파이프라인의 동작 원리

전체 흐름은 이렇게 흘러갑니다. 자세히 보면 결정적 도구와 LLM이 번갈아 등장하는 구조입니다.

영상(.MOV)
 → ① ffmpeg: 16kHz WAV 추출              ← 결정적 도구
 → ② whisper-cli: 어절 단위 타임스탬프 STT  ← 결정적 도구
 → ③ 대화 세션 Claude = "자막 편집자"       ← LLM 판단 (인세션, 비용 0)
 → ④ Claude가 ASS 자막 파일 생성
 → ⑤ ffmpeg-full: 스케일 + 자막 번인         ← 결정적 도구
 → ⑥ 프레임 추출 → Claude가 눈으로 검증 → 필요 시 ③~⑤ 루프

결정적 도구가 맡는 일 — 타이밍

먼저 ffmpeg로 오디오를 16kHz 모노 WAV로 추출합니다. whisper의 입력 규격이기 때문입니다. 그다음이 핵심인데, whisper에게 어절 단위로 타임스탬프를 받아냅니다.

whisper-cli -m ggml-large-v3-turbo.bin \
  -l ko --vad --vad-model ggml-silero-vad.bin \
  -ml 1 -sow \
  audio.wav

여기서 -ml 1(최대 1자 세그먼트)과 -sow(어절 경계 분할)의 조합이 어절 단위 타임스탬프를 얻는 공식적인 방법입니다. 출력은 어절마다 [시작 --> 끝] 형태로 떨어집니다. 43초짜리 영상에서 어절 53개, 타이밍 정밀도는 자막용으로 충분했습니다. 무음 구간에서 모델이 "시청해주셔서 감사합니다" 같은 환각을 내뱉지 않도록 Silero VAD를 앞단에 두는 것은 사실상 필수였습니다.

Claude가 맡는 일 — 판단

이제 Claude의 차례입니다. 어절과 타임스탬프 목록을 읽고 다음을 결정합니다. 어절을 청크로 묶어 한두 줄짜리 자막으로 만들되 줄당 12자 안팎, 어말어미를 기준으로 끊습니다. STT가 잘못 들은 단어는 교정합니다. 다만 구어체는 교정하지 않습니다. "좋겠어가지고" 같은 말투는 그 자체가 콘텐츠이기 때문에, 문어체로 다듬는 순간 영상의 결이 사라진다는 생각이 들었습니다. 마지막으로 강조할 키워드를 골라 색을 입힙니다.

그 결과를 ASS 자막 파일로 직접 작성하게 합니다. ASS는 위치와 색, 외곽선까지 스타일을 세밀하게 제어할 수 있는 자막 포맷입니다. 검증을 거쳐 정착한 스타일 헤더는 이런 모습입니다.

PlayResX: 1080
PlayResY: 1920
Style: Cap,Apple SD Gothic Neo,84, ... ,MarginV,600 ...

이 한 줄에도 작은 시행착오가 묻어 있습니다. MarginV 값을 처음엔 470으로 두었는데, 쇼츠 하단에는 제목과 핸들, 음악 정보가 깔리는 UI 안전 구간이 있어 자막이 그 위에 겹칠 위험이 있었습니다. 600으로 올려 자막 하단이 y≈1320에 오도록 한 뒤에야 안심이 되었습니다.

렌더와 눈으로 하는 검증

자막을 입힐 때 한 가지 원칙이 있습니다. 자막은 반드시 최종 해상도에서 번인해야 한다는 것입니다. 작은 해상도에서 자막을 그린 뒤 영상을 키우면 글자가 함께 늘어나 흐려집니다. 그래서 필터 순서를 scale → format → ass로 두어, 영상이 1080×1920이 된 다음에 자막을 그립니다. 그래야 ASS에 적어둔 PlayRes와 픽셀이 1:1로 맞아 선명합니다.

여기서 끝이 아닙니다. 렌더가 끝나면 영상에서 프레임 서너 장을 뽑아 Claude에게 이미지로 보여줍니다. 자막이 안전 구간 안에 있는지, 줄바꿈이 어색하지 않은지, 글자가 잘리지 않았는지를 눈으로 확인하고, 문제가 있으면 ASS를 고쳐 다시 렌더합니다. 사람이 미리보기를 보고 다듬는 과정을, 결정적 도구로 측정할 수 없는 영역이기에 다시 Claude에게 맡긴 셈입니다.

무음을 잘라내는, 똑같은 아키텍처

흥미로웠던 것은 무음 구간을 자동으로 잘라내는 일도 정확히 같은 골격 위에 얹혔다는 점입니다. 제가 만든 영상은 절반 가까이(약 22초)가 무음이었고, 잘라내고 나니 점프컷 스타일의 쇼츠가 되었습니다.

먼저 ffmpeg의 silencedetect가 무음 구간 목록을 결정적으로 뽑아줍니다.

ffmpeg -i input.MOV -af silencedetect=noise=-30dB:d=0.4 -f null -

그다음 다시 Claude가 "컷 편집자"로 등장해 어디를 잘라낼지 계획을 세웁니다. 여기서도 판단의 근거가 있습니다. 0.4초 이상의 무음만 자릅니다. 너무 짧은 침묵까지 건드리면 자연스러운 호흡마저 사라지기 때문입니다. 자르는 구간의 양 끝에는 ±0.15초의 호흡 패딩을 남깁니다. 칼같이 무음 경계에서 자르면 말의 끝음절이 잘리거나 다음 말이 급발진하듯 튀어나오기 때문입니다. 그리고 패딩을 주고 나서도 0.3초가 안 되는 짧은 컷은 인접 구간과 병합합니다. 점프컷이 너무 잦으면 보기 어지럽다는 단순한 이유에서입니다.

최종적으로는 남길 구간(keep 세그먼트)만 trim과 concat으로 이어 붙입니다.

# keep 세그먼트별로
[0:v]trim=start=S:end=E,setpts=PTS-STARTPTS[vN];
[0:a]atrim=start=S:end=E,asetpts=PTS-STARTPTS[aN];
... → concat=n=N:v=1:a=1 → 최종 출력

이렇게 해서 43.9초 영상이 29.8초로, 약 32% 짧아졌습니다. 자막과 똑같이 "결정적 탐지 → Claude 판단 → 결정적 렌더"의 분업이 그대로 반복된 것입니다.

직관을 두 번 배신한 설계 결정

여기까지는 비교적 매끄럽게 이어졌습니다. 문제는 자막과 무음 컷을 합치는 순간 터졌습니다. 무음을 잘라낸 영상에 자막을 다시 입히려니, 원본 자막의 타임스탬프가 더 이상 맞지 않았습니다. 이걸 해결하는 과정에서 제 직관은 두 번 배신당했고, 그 두 지점이 이 글에서 가장 하고 싶은 이야기입니다.

타임스탬프를 다시 계산하지 말고, 컷한 영상에 STT를 다시 돌려라

가장 먼저 떠오른 직관은 단순한 산수였습니다. 원본 자막의 타임스탬프에서, 그 앞에 잘려나간 무음의 총 길이만큼 빼면 새 타임라인이 나오지 않겠는가. 깔끔하고 빠른 해법처럼 보였습니다.

그런데 실제 데이터로 맞춰보니 어긋났습니다. 원인을 들여다보니 whisper의 어절 경계가 무음을 흡수하고 있었습니다. 한 어절의 끝 타임스탬프가 다음 말이 시작될 때까지 늘어나 있어서, "무음 구간"과 "자막 구간"의 경계가 깔끔하게 분리되지 않았습니다. 침묵이 어절의 꼬리에 붙어 있으니, 잘려나간 무음 길이를 빼는 연산이 맞아떨어질 수가 없었던 것입니다.

그래서 산수를 포기하고, 컷이 끝난 영상에 STT를 처음부터 다시 돌렸습니다. 40초 안팎의 영상에서 STT는 수 초면 끝나므로 부담이 없었습니다. 그런데 여기서 뜻밖의 선물이 따라왔습니다. 재STT로 나온 어절 수(52개)를 원본 전사의 어절 수(53개)와 비교하니, "컷 때문에 말이 잘려나가지 않았다"는 사실이 기계적으로 검증되었습니다. 사람이 일일이 들어보며 확인할 일을, 두 숫자를 맞춰보는 것으로 대신할 수 있게 된 셈입니다. 돌이켜 보면, 더 정확한 길을 택했더니 검증까지 덤으로 따라온 보기 드문 경우였습니다.

컷 경계의 단어는 원본 전사를 진실로 삼아라

두 번째 배신은 그 재STT 안에 숨어 있었습니다. 무음을 사이에 두고 발음된 단어가 문제였습니다. 예를 들어 "가볍게"라는 단어를, 첫 음만 내뱉고 1.4초쯤 쉬었다가 나머지를 이어 말한 구간이 있었습니다. 사람 귀에는 한 단어지만, 무음을 잘라내고 두 조각을 붙인 뒤 STT를 돌리니 whisper가 이를 "가요께"처럼 엉뚱하게 알아들었습니다. 타이밍을 얻으려고 다시 돌린 STT가, 이번엔 텍스트를 망가뜨린 것입니다.

여기서 결정적 도구의 출력을 맹신하면 안 된다는 것을 다시 배웠습니다. 해법은 두 소스에 역할을 나눠 주는 것이었습니다. 타이밍은 재STT에서, 텍스트는 원본 전사에서 가져와 결합했습니다. 컷 경계에 걸친 단어일수록 재STT의 텍스트는 의심하고, 원본 전사를 진실(ground truth)로 삼았습니다. 같은 whisper의 출력이라도, 어떤 상황에서는 타이밍만 믿고 텍스트는 믿지 않는다는 판단. 이 역시 사람이 들여다봐야 알 수 있는 영역이라, 다시 LLM의 몫이 되었습니다.

도구가 발목을 잡은 순간들

매끄러워 보이는 파이프라인이지만, 실제로는 도구 자체에 발이 걸려 한참을 헤맸습니다. 같은 길을 걸으실 분들을 위해 두 가지만 남겨둡니다.

첫째, Homebrew로 설치한 ffmpeg에는 자막을 입히는 ass 필터가 아예 없었습니다. 최신 ffmpeg 8.x가 슬림 빌드로 바뀌면서 libass, drawtext, zscale 같은 무거운 의존성을 의도적으로 제외했기 때문입니다. ass 필터를 쓰려 하면 'Unknown filter'라며 거부당하는데, 애석하게도 upgrade나 reinstall로는 해결되지 않습니다. 별도 포뮬러인 ffmpeg-full을 설치해 그 경로의 바이너리로 호출해야 비로소 자막 번인이 됩니다.

둘째, 아이폰으로 찍은 세로 영상이 메타데이터상으로는 가로였습니다. raw 해상도를 찍어보면 3840×2160, 누가 봐도 가로 영상입니다. 그런데 ffprobe로 들여다보니 회전(rotation) 메타가 -90도로 박혀 있었습니다. 픽셀은 가로로 저장하되 재생할 때 세로로 돌리라는 의미입니다. 이걸 모르고 raw 해상도만 믿으면 쇼츠인지조차 알아보지 못합니다. 결국 해상도가 아니라 회전을 적용한 뒤의 기준으로 세로/가로를 판정해야 했습니다.

결국, 분업과 교차 검증의 이야기

이 작업을 끝내고 나서, 이 파이프라인의 동작 원리를 한 문장으로 줄이면 이렇습니다. "AI로 영상을 편집한다"의 실체는, LLM이 픽셀을 만지는 것이 아니라 결정적 도구가 못 하는 '판단'만 LLM이 메우고 나머지는 검증 가능한 도구에 맡기는 분업이라는 것. whisper는 타이밍을, ffmpeg는 렌더링을, 그리고 Claude는 "어디서 자르고 묶을지"라는 판단만 담당했습니다.

그리고 그 판단을 맡긴 LLM이 멀리 있는 API가 아니라 마침 옆에 켜둔 대화 세션이었다는 점이, 이 파이프라인을 가볍게 만들었습니다. 외부 영상 편집 SaaS의 무음 컷과 자막 기능을, 추가 비용 없이 무료 OSS와 옆에 있는 Claude만으로 재현할 수 있었습니다.

다만 가장 중요한 교훈은 따로 있었습니다. 도구의 출력을 맹신하지 않고 교차 검증한다는 것. 컷한 영상에 STT를 다시 돌려 말이 잘리지 않았음을 확인하고, 같은 STT라도 타이밍은 믿되 컷 경계의 텍스트는 원본을 진실로 삼았던 그 두 결정이, 결국 이 파이프라인을 신뢰할 만하게 만든 핵심이었습니다. 이 전체를 명령 한 번으로 재사용할 수 있도록 스킬로 패키징하는 일은, 다음 과제로 남겨두려 합니다.

whisperffmpegClaude자막 자동화영상 편집 자동화무음 컷

관련 글

영상에서 텍스트를 뽑고 싶었다 — ffmpeg, whisper-cpp, VAD로 로컬 STT 파이프라인 만들기

영상 파일에서 텍스트를 추출하는 로컬 STT 파이프라인을 구축한 경험입니다. ffmpeg가 오디오를 왜 16kHz 모노 PCM으로 변환하는지, whisper-cpp가 Python 없이 어떻게 동작하는지, VAD가 STT 품질을 어떻게 개선하는지 하나씩 따라가봅니다.

관련도 91%

큰 작업의 거처를 챗봇 UI에서 로컬 하네스로 — 옆에서 함께 옮긴 이야기

주변에서 외부 챗봇의 커스텀 기능 위에 무거운 작업을 굴리던 분이 매번 같은 자리에서 막히는 걸 보고, 그 작업의 거처를 Claude Code 위 로컬 하네스로 옮겨드린 과정. 옆에서 본 네 가지 막힘과 함께 갖춰둔 여섯 가지 패턴을 정리했습니다.

관련도 90%

대화 세션에서 돌리던 slash skill을 배치 자동화로 옮긴 이야기

매일 같은 slash skill을 손으로 돌리던 습관을 LaunchAgent 배치로 옮기면서 느낀 것은 기술 장벽보다 역할 재정의가 본질이라는 점이었습니다. 집행자에서 큐레이터로의 전환에 대한 기록입니다.

관련도 89%