YouTube 자막을 어디서 가져올 것인가 — 서버 vs 클라이언트, 웹 vs 모바일 (CORS의 벽)
AWS 배포 직후 YouTube 임포트가 깨졌다. yt-dlp가 데이터센터 IP에서 차단된 것. '그럼 클라이언트에서 가져오면?'을 끝까지 파보니, 웹앱은 CORS 때문에 사실상 불가능하고 모바일 네이티브만 가능하다는 비대칭이 드러났다. 7개 방안을 차원별로 비교하고 Mimi의 전략을 정한다.
발단
AWS(서울)에 백엔드를 올린 직후, 웹에서 YouTube를 임포트하면 "저장은 되는데 라이브러리에 안 보이는" 증상. CloudWatch 로그:
yt-dlp non-zero exit for hJ1oHhf5i8s: ... --cookies for the authentication ...
No transcript for hJ1oHhf5i8s: 이 영상에는 자막이 없습니다.원인은 yt-dlp가 데이터센터 IP(AWS)에서 YouTube 봇 차단에 걸린 것. 내 맥(가정용 IP)에선 되던 게 클라우드에선 막힌다.
"라이브러리에 안 보인다"의 정확한 메커니즘은 코드로 검증했다: 임포트는 자막이 없어도 영상 자체는 저장하고(VideoImportService — log.info("No transcript") 후 videoRepository.save) /video/{id}로 이동시킨다. 그런데 라이브러리는 영상이 아니라 "클립" 목록(queryKey ["clips"])이고, 클립은 영상 페이지에서 자막의 문장을 선택해야 만들어진다. 자막이 없으면 → 고를 문장 0개 → 클립 0개 → 라이브러리가 빈다. 즉 별개 버그가 아니라 자막 실패의 하류 효과 + "라이브러리=클립" 설계다. 영상은 멀쩡히 /video/{id}에 있다.
여기서 자연스러운 다음 질문: "그럼 서버 말고 사용자 기기에서 자막을 가져오면 되잖아?" — 그래서 끝까지 파봤다.
핵심 발견: 웹과 모바일의 비대칭은 CORS다
| 모바일 네이티브 | 웹 브라우저 | |
|---|---|---|
fetch(youtube 자막) | ✅ 읽힘 | ❌ CORS 차단 |
| 이유 | 브라우저가 아니라 CORS 규칙 자체가 없음 | YouTube가 Access-Control-Allow-Origin을 안 줘서 브라우저가 응답 읽기를 막음 |
| 가정용 IP 효과 | 있음 (사람으로 인식) | 없음 (IP 문제 이전에 브라우저가 막음) |
- CORS는 IP 문제가 아니라 브라우저 정책이다. 그래서 웹앱은 가정용 IP를 써도 못 가져온다 — 브라우저가
youtube.com응답을 읽지 못하게 막는다. - IFrame 임베드 플레이어도 안 된다. YouTube IFrame Player API는 자막 텍스트를 노출하는 메서드가 없고(의도적), cross-origin iframe의 DOM도 same-origin 정책 때문에 못 읽는다. 자막을 보여줄 순 있어도 추출할 수 없다.
- 그래서 Language Reactor는 웹사이트가 아니라 브라우저 확장이다. 확장은
host_permissions로 CORS를 우회하고 content script로 youtube.com 페이지 안에서 실행되므로 자막을 직접 읽는다. 일반 웹앱은 이 권한이 없다. - 모바일 네이티브(React Native
fetch등)는 CORS를 적용하지 않는다 — CORS는 브라우저 보안 모델이다. 그래서 폰에서 timedtext/워치페이지를 직접 가져와 읽을 수 있다.
결론 한 줄: 클라이언트 자막 추출은 모바일 전용 능력이다. 웹앱은 구조적으로 못 한다.
7개 방안 비교
| 방안 | 웹앱 | 모바일 | 임의 URL | 비용 | ToS | 복잡도 | 안정성 |
|---|---|---|---|---|---|---|---|
| 1. 서버 yt-dlp/timedtext (현재) | △ 서버가 가정IP면 | △ 동일 | ✅ | 무료 | 회색 | 낮 | ❌ 클라우드서 차단 |
| 2. 클라이언트 자막 추출 | ❌ CORS | ✅ 네이티브 | ✅ | 무료 | 회색 | 중 | ✅ (모바일) |
| 3. 브라우저 확장 | (별도 제품) | 해당없음 | ✅ | 무료 | 회색 | 높 | ✅ |
| 4. 레지덴셜 프록시(서버) | ✅ | ✅ | ✅ | 💰💰 GB당 | 회색 | 중 | ✅ |
| 5. 큐레이션 카탈로그 | ✅ | ✅ | ❌ 임의X | 무료* | 낮음 | 낮 | ✅ |
| 6. 유료 transcript API | ✅ | ✅ | ✅ | 💰 호출당 | 위임 | 낮 | △ 3rd-party 의존 |
| 7. 가정용 워커(셀프호스팅) | ✅ | ✅ | ✅ | 무료 | 회색 | 높 | △ 집 인터넷 |
* 큐레이션의 "무료"는 운영자가 가정 IP에서 미리 채우는 시간 비용을 뜻한다.
핵심 통찰: "웹 + 임의 URL + 무료"를 동시에 만족하는 방안은 없다. 이게 이 문제의 본질이다.
Mimi의 결정
Mimi는 웹 + 모바일을 둘 다 갖고 있고, 임의 URL 임포트를 원한다. 위 표에서 단일 방안으론 안 되므로 플랫폼별로 가른다:
- 모바일: 방안 2 (클라이언트 자막 추출). 무료 + 임의 URL + 네이티브의 정당한 강점. "아무 YouTube나 임포트"는 모바일 기능으로 포지셔닝.
- 웹: 방안 5 (큐레이션 카탈로그). 운영자가
collections(이미 존재)에 미리 채운 영상에서 고르게. 임의 URL은 빼거나 "모바일에서 하세요"로 안내. - 나중에(유료화 시): 웹에서도 임의 URL이 꼭 필요하면 방안 6(유료 API) 또는 4(레지덴셜 프록시) — 비용은 유료 사용자에게 전가.
이건 "정답"이 아니라 트레이드오프 선택이다. 그리고 우연이 아니라 production YouTube 학습앱이 대부분 모바일/확장 중심인 이유와 정확히 일치한다 — 웹앱 단독은 CORS의 벽에 막히기 때문.
면접용 한 줄
"localhost에선 되던 YouTube 임포트가 AWS에서 깨졌습니다. 데이터센터 IP 차단이었고, '클라이언트에서 가져오자'를 검토했더니 모바일 네이티브는 가능하지만 웹앱은 CORS 때문에 불가능하다는 비대칭이 있었습니다. 그래서 모바일은 온디바이스 추출, 웹은 큐레이션 카탈로그로 가르고, 임의 URL 웹 지원은 유료 API로 비용을 전가하는 선택을 했습니다."
— 이 한 단락에 datacenter-IP 차단, CORS, 클라이언트/서버 트레이드오프, 제품 결정이 다 들어간다.