직접 해 본 결과만 적습니다 · 확인하지 못한 것은 그렇게 표시합니다
써 AI 마케팅 적용기 시중에 나온 AI 방법을 실무에 넣어 보고, 쓸만한지 남깁니다
반복업무 줄이기

유튜브 자막 추출이 막혔을 때 — 받아쓰기(STT)로 대체한 방법

계속 쓴다2026-08-07·읽기 7분·글 써봄

영상에서 자막추출을 해서 글로 쓰는 자동화를 돌리고 있었는데, 어느 날부터 자막이 안 들어왔습니다. 오류도 안 났습니다.

응답 코드는 200인데 본문이 비어 있었습니다. 프로그램 입장에서는 "성공했고 자막이 없는 영상" 으로 보입니다. 그래서 한참 몰랐습니다.

결론부터 적으면 우회는 못 찾았고, 소리를 받아 글로 옮기는 방식(STT)으로 대체했습니다. 그 순서와 겪은 것을 적습니다.

자막추출이 막혔을 때 순서

1. 응답 코드가 아니라 본문 길이를 먼저 봅니다 2. 같은 도메인의 다른 경로도 막혔는지 대조합니다 3. 우회가 안 되면 받아쓰기(STT) 로 경로를 바꿉니다 4. 확보한 결과는 캐시해서 다시 안 받습니다

증상 — 오류가 아니라 '빈 성공'

같은 도메인에 두 요청을 보내 보면 차이가 분명합니다.

대문은 857KB, 자막 경로는 0바이트를 돌려주는 터미널 화면

응답 코드는 둘 다 200입니다. 대문은 내용이 가득 오는데 자막 경로만 비어 옵니다.

이게 왜 위험하냐면, 오류 처리 코드가 안 걸립니다.

if resp.status != 200:      # 여기 안 걸린다
    raise FetchError()
captions = resp.read()      # 빈 값이 그대로 통과한다

"자막이 없는 영상"과 "자막을 못 받은 것"이 구분되지 않습니다. 저는 처음에 그 영상들에 원래 자막이 없다고 생각했습니다. 여러 영상에서 똑같이 나오는 걸 보고서야 이상하다고 느꼈습니다.

예전에는 이 경로가 명시적인 오류 코드를 돌려줬습니다. 그때가 차라리 나았습니다. 오류는 로그에 남고 알림도 가는데, 빈 성공은 아무 데도 안 남습니다.

시도했지만 안 된 것들

우회부터 찾아봤습니다. 다 실패했습니다.

시도 결과
요청 헤더·프로그램 이름표 바꾸기 그대로 빈 응답
자막 받는 도구를 다른 것으로 교체 같은 경로를 쓰므로 같은 결과
영상 페이지에서 자막 주소를 직접 찾아 호출 그 주소도 빈 응답
다른 언어·형식 지정 차이 없음

원인은 전부 같은 경로를 지난다는 것이었습니다. 자막을 받는 도구가 여럿이어도 내부적으로는 같은 주소를 부릅니다. 도구를 바꿔도 결국 같은 문을 두드리니 결과가 같았습니다.

응답 헤더도 살펴봤는데 Content-Length: 0 외에 단서가 될 만한 것이 없었습니다. 차단이라면 보통 403이나 429 같은 코드가 오는데, 여기는 200에 빈 본문이라 의도적인 것인지 구조가 바뀐 것인지도 구분이 안 됐습니다.

여기서 판단을 내렸습니다. 막힌 문을 계속 두드리는 대신 다른 문을 찾자.

한 가지는 확인하지 못했습니다. 이게 제 서버 쪽만의 문제인지, 아니면 그 경로 자체가 그렇게 바뀐 것인지 구분하지 못했습니다. 다른 회선에서 같은 요청을 넣어 비교해 봐야 하는데 아직 안 해봤습니다.

해결 — STT로 소리를 받아 글로 옮깁니다

자막은 막혔는데 영상과 소리는 정상으로 받아졌습니다. 그러면 소리를 받아서 직접 글로 옮기면 됩니다.

받아쓰기 도구는 무료로 쓸 수 있는 것들이 있고, 서버에서 직접 돌릴 수 있습니다. 외부에 파일을 보내지 않아도 됩니다.

model = WhisperModel("모델이름", device="cpu", compute_type="int8")
segments, info = model.transcribe(audio_path, language="ko")
text = " ".join(seg.text for seg in segments)

품질은 자막보다 조금 떨어집니다. 사람 이름이나 상품명 같은 고유명사에서 틀리는 경우가 있습니다. 다만 내용을 파악하는 용도로는 충분했습니다.

주의할 점이 있습니다. 모델 크기를 키우면 정확도가 오르는 대신 처리 시간이 길어집니다. 저는 처음에 큰 모델을 썼다가 한 건에 시간이 너무 걸려서 중간 크기로 내렸습니다. device="cpu", compute_type="int8" 로 두면 그래픽카드 없이도 돌아갑니다.

대안으로 외부 받아쓰기 서비스를 쓸 수도 있습니다. 빠르고 정확한 대신 소리 파일을 밖으로 보내야 합니다. 남의 영상이나 민감한 내용이라면 서버에서 직접 돌리는 쪽이 낫습니다.

확보 경로를 3단으로 만듭니다

한 방법에 매달리지 않게, 위에서부터 시도하고 되는 순간 멈추도록 만들었습니다.

캐시·자막·받아쓰기 세 단계를 순서대로 시도하는 방식을 정리한 터미널 화면

def get_transcript(video_id):
    if cached := load_cache(video_id):        # 1) 이미 받아둔 것
        return cached
    if text := fetch_captions(video_id):      # 2) 자막이 열려 있으면
        save_cache(video_id, text)
        return text
    text = transcribe_audio(video_id)         # 3) 받아쓰기
    save_cache(video_id, text)
    return text

이렇게 두면 나중에 자막이 다시 열려도 코드를 안 고쳐도 됩니다. 2번이 먼저 성공하면 3번은 안 돌아갑니다.

캐시 — 영상요약을 두 번 하지 않기

받아쓰기는 느립니다. 몇 분짜리 영상에 몇 분씩 걸립니다. 같은 영상을 두 번 처리하면 그만큼 그대로 낭비입니다.

한 번 확보한 결과는 파일로 남기고 다시 안 받습니다.

outputs/.cache/transcript_<영상ID>.txt

이게 있으면 글을 다시 쓰거나 실패해서 재실행할 때 몇 분이 0초가 됩니다. 저는 이걸 나중에 붙였는데, 처음부터 넣었어야 했습니다.

용량 정책도 같이 정했습니다. 글로 옮기고 나면 소리 파일은 지웁니다. 남길 이유가 없는데 쌓이면 디스크를 먹습니다.

남기는 것 지우는 것
받아쓴 글 (텍스트) 소리 파일
필요한 장면 이미지 원본 영상

무거운 도구는 따로 가둡니다

받아쓰기 라이브러리는 딸려오는 것이 많습니다. 이걸 본체에 그대로 깔면 다른 부분까지 영향을 받습니다.

받아쓰기 라이브러리가 별도 환경에만 설치돼 본체는 깨끗한 상태를 보여주는 터미널 화면

그래서 전용 가상환경을 따로 만들고 거기서만 돌립니다. 본체 프로그램은 그 환경의 실행 파일을 불러 쓰기만 합니다.

subprocess.run([".venv-stt/bin/python", "stt.py", audio_path], ...)

이렇게 하면 본체 의존성이 안 흔들립니다. 받아쓰기 도구를 버전 올리거나 통째로 바꿔도 나머지에 영향이 없습니다.

다시 안 겪으려면

장치 무엇을 막나
빈 응답을 실패로 처리 '빈 성공'을 성공으로 착각하는 것
확보 경로 다단화 한 경로가 막히면 전부 멈추는 것
캐시 같은 작업을 두 번 하는 것
전용 환경 분리 무거운 의존성이 본체를 오염시키는 것
결과 길이 검사 너무 짧은 결과가 그대로 흘러가는 것

마지막 줄을 덧붙였습니다. 받아쓰기가 실패하거나 소리가 안 좋으면 몇 글자짜리 결과가 나옵니다. 그것도 성공으로 처리되면 빈 자막과 같은 문제가 반복됩니다.

if len(text.strip()) < MIN_CHARS:
    raise RuntimeError(f"받아쓴 결과가 너무 짧습니다: {len(text)}자")

판정 — 계속 쓴다

받아쓰기 대체는 잘 돌아갑니다. 자막이 막힌 뒤로 이 경로로만 돌고 있고, 결과물 품질에 큰 문제는 없었습니다.

속도는 감수해야 합니다. 자막은 몇 초, 받아쓰기는 몇 분입니다. 실시간으로 쓸 일이면 맞지 않습니다. 저는 하루 한 번 도는 작업이라 괜찮았습니다.

고유명사는 틀립니다. 사람 이름·상품명이 중요한 용도라면 사람이 한 번 봐야 합니다. 내용 파악용으로는 충분합니다.

직접 하실 분께

하나. 빈 응답을 실패로 처리하세요. 200이라고 성공이 아닙니다. 이게 이 글에서 제일 중요한 부분입니다.

둘. 확보 경로를 여러 단으로 만듭니다. 한 경로가 막혔을 때 전부 멈추지 않게요.

셋. 캐시를 처음부터 붙입니다. 나중에 붙이면 그때까지 낭비한 시간이 아깝습니다.

넷. 무거운 도구는 전용 환경에 가둡니다. 본체에 직접 깔면 나중에 되돌리기 어렵습니다.

다음에는 다른 회선에서 같은 요청을 넣어, 이게 제 서버만의 문제인지 확인해보려고 합니다. 결과가 나오면 이어서 적겠습니다.