본문 바로가기
튜토리얼/자동화

[Python] 유튜브 자막 추출기 만들기

by 루까(Luka) 2026. 2. 28.
반응형

유튜브 자막 추출기 — Python으로 만들기

왜 만들었나

유튜브 영상 내용을 텍스트로 뽑아야 할 때가 있다. 강의 요약, 번역, AI 분석 등 용도가 다양하다. youtube-transcript-api 라이브러리를 쓰면 YouTube Data API 키 없이 자막을 바로 가져올 수 있다. 여기에 CLI 인터페이스를 붙여서 실용적인 스크립트로 만들었다.

구현 기능:

  • YouTube URL 또는 Video ID로 자막 추출
  • 언어 우선순위 지정 (기본: 한국어 → 영어)
  • 타임스탬프 포함/미포함 선택
  • 파일 저장 또는 터미널 출력
  • 사용 가능한 자막 언어 목록 확인

기술 상세

youtube-transcript-api

YouTube의 자막 요청은 공개 엔드포인트를 통해 이루어진다. youtube-transcript-api는 이 과정을 추상화한 라이브러리다. API 키 불필요, 설치 후 바로 사용 가능하다.

v1.2.0부터 인스턴스 기반 API로 변경됐다.

버전 사용 방식
v0.x YouTubeTranscriptApi.get_transcript(id) (클래스 메서드)
v1.x YouTubeTranscriptApi().fetch(id) (인스턴스 메서드)

구조

main.py
├── extract_video_id()   ← URL/ID 파싱 (정규식 4종)
├── format_timestamp()   ← 초 → HH:MM:SS
├── list_available_languages()  ← 자막 언어 목록 출력
├── fetch_transcript()   ← 자막 fetch + 폴백 처리
├── build_output()       ← 엔트리 → 문자열 변환
└── main()               ← CLI argparse 진입점

단일 파일로 전체 로직을 담았다. requirements.txt 하나면 설치 끝이다.


핵심 소스 코드

URL에서 Video ID 추출

def extract_video_id(url_or_id: str) -> str:
    # 이미 ID 형식이면 그대로 반환
    if re.fullmatch(r'[a-zA-Z0-9_-]{11}', url_or_id):
        return url_or_id

    patterns = [
        r'(?:v=)([a-zA-Z0-9_-]{11})',          # ?v=ID
        r'(?:youtu\.be/)([a-zA-Z0-9_-]{11})',   # youtu.be/ID
        r'(?:embed/)([a-zA-Z0-9_-]{11})',        # /embed/ID
        r'(?:shorts/)([a-zA-Z0-9_-]{11})',       # /shorts/ID
    ]
    for pattern in patterns:
        match = re.search(pattern, url_or_id)
        if match:
            return match.group(1)

    raise ValueError(f"유효한 YouTube URL 또는 Video ID가 아닙니다: {url_or_id}")

YouTube Video ID는 항상 11자리 [a-zA-Z0-9_-] 조합이다. 일반 URL, 단축 URL, 임베드 URL, Shorts까지 4가지 패턴을 처리한다.

자막 fetch + 폴백 처리

def fetch_transcript(api: YouTubeTranscriptApi, video_id: str, languages: list[str]) -> list[dict]:
    try:
        fetched = api.fetch(video_id, languages=languages)
        return [{'text': s.text, 'start': s.start, 'duration': s.duration} for s in fetched]
    except NoTranscriptFound:
        # 요청 언어가 없으면 첫 번째 사용 가능한 자막으로 대체
        transcript_list = api.list(video_id)
        fallback = next(iter(transcript_list))
        print(f"[info] 요청 언어 없음. '{fallback.language_code}' 자막으로 대체합니다.", file=sys.stderr)
        fetched = fallback.fetch()
        return [{'text': s.text, 'start': s.start, 'duration': s.duration} for s in fetched]

api.fetch()FetchedTranscript 객체를 반환한다. Snippet 객체의 .text, .start, .duration을 dict로 변환해서 이후 처리를 단순하게 만들었다.

타임스탬프 출력

def format_timestamp(seconds: float) -> str:
    total = int(seconds)
    h = total // 3600
    m = (total % 3600) // 60
    s = total % 60
    if h:
        return f"{h:02d}:{m:02d}:{s:02d}"
    return f"{m:02d}:{s:02d}"

--timestamp 플래그를 주면 각 자막 구간 앞에 [MM:SS] 또는 [HH:MM:SS]를 붙인다.

CLI 설계

parser.add_argument('url',   help='YouTube URL 또는 Video ID')
parser.add_argument('-l', '--lang',  nargs='+', default=['ko', 'en'])
parser.add_argument('-o', '--output', metavar='FILE')
parser.add_argument('--timestamp',   action='store_true')
parser.add_argument('--list',        action='store_true')

argparsenargs='+'를 쓰면 -l ko en ja 식으로 여러 언어를 한 번에 지정할 수 있다. 앞에 있을수록 우선순위가 높다.


실행 결과

# 사용 가능한 자막 언어 확인
$ python main.py VIDEO_ID --list

[수동 자막]
  en       English
  ja       Japanese

[자동 생성 자막]
  en       English (auto-generated)

# 한국어 우선, 없으면 영어로 추출
$ python main.py VIDEO_ID -l ko en

# 타임스탬프 포함 파일로 저장
$ python main.py VIDEO_ID --timestamp -o transcript.txt
[완료] 'transcript.txt' 저장됨 (12,847자, 253개 구간)

기술 선택 비교

방법 API 키 속도 한계
youtube-transcript-api (이번) 불필요 빠름 자막 없는 영상 불가
YouTube Data API v3 필요 빠름 할당량 제한
yt-dlp + --write-sub 불필요 느림 yt-dlp 설치 필요
Whisper (OpenAI) 불필요 매우 느림 GPU/시간 필요, 오류율 있음

자막이 있는 영상이라면 youtube-transcript-api가 가장 빠르고 간편하다.


삽질 기록

1. v1.x에서 클래스 메서드 사라짐

YouTubeTranscriptApi.get_transcript(id) 방식이 v1.0에서 제거됐다. YouTubeTranscriptApi().fetch(id) 인스턴스 방식으로 변경됐다. 라이브러리 설치 후 버전 확인 필수.

2. FetchedTranscript가 dict가 아님

fetched[0]['text'] 접근이 안 됐다. v1.x에서는 Snippet 객체를 반환한다. .text, .start, .duration 속성으로 접근하거나, 명시적으로 dict로 변환해야 한다.

3. 자동 생성 자막과 수동 자막 구분

api.list(video_id)로 가져온 TranscriptList를 순회하면 t.is_generated 속성으로 자동/수동을 구분할 수 있다.


마무리

이 스크립트 하나면 강의 유튜브를 텍스트로 변환해서 AI 요약에 넘기거나, 자막 파일을 만들거나, 번역 작업 소스로 쓸 수 있다. youtube-transcript-api 덕분에 핵심 로직이 30줄도 안 된다.

다음 단계로 확장하면: --format srt 옵션 추가, 여러 영상 일괄 처리, Claude API로 자동 요약 연결.


기술 스택: Python 3.13, youtube-transcript-api 1.2.x, argparse
소스 코드: GitHub

반응형