yt-dlp Rumble 403 오류 해결: JSON 요청 실패 시 플레이어 데이터로 전환
게시일
오류 수정 플러그인 다운로드 rumble_player.py
yt-dlp로 Rumble 동영상을 다운로드할 때 Unable to download JSON metadata: HTTP Error 403: Forbidden 오류가 발생하면 이 글의 호환성 플러그인을 불러와 해결할 수 있습니다. Rumble의 embedJS/u3 JSON API가 403을 반환하면, 플러그인이 같은 동영상의 공식 임베드 플레이어 페이지(/embed/)를 가져옵니다. 페이지에 포함된 미디어 데이터를 추출한 뒤 다운로드를 계속합니다.
로컬에서 플러그인을 설정하거나 링크를 직접 변환하고 싶지 않다면, 이 수정 사항이 내장된 사이트의 Rumble 동영상 다운로더를 이용할 수도 있습니다.
먼저 403이 발생한 단계를 확인하세요
Rumble 다운로드에서는 다음 세 단계에서 403이 발생할 수 있습니다. 이 글의 플러그인은 두 번째 경우를 해결합니다.
| 오류 또는 증상 | 차단된 요청 | 해결 방법 |
|---|---|---|
Unable to download webpage: HTTP Error 403 |
시청 페이지 HTML 차단(이슈 #15089 참고) | 공식 oEmbed API로 /embed/ 주소를 얻고 --impersonate firefox를 함께 사용합니다 |
Unable to download JSON metadata: HTTP Error 403 |
embedJS/u3 메타데이터 API 차단(이슈 #17496 참고) |
이 글의 플러그인으로 해결: /embed/ 페이지의 인라인 데이터를 읽는 경로로 자동 전환합니다 |
| 형식 목록은 표시되지만 세그먼트나 MP4 다운로드 중 403 발생 | CDN 미디어 스트림 요청 거부 | 프록시와 요청 헤더를 확인하거나 다른 화질의 형식을 선택합니다 |
빠른 해결 방법
1. 플러그인 파일 저장
작업 디렉터리에 다음 구조로 폴더를 만들고, 글 끝의 전체 플러그인 코드를 rumble_player.py로 저장하세요. yt-dlp 소스 코드를 수정할 필요는 없습니다.
plugins/
└── rumble/
└── yt_dlp_plugins/
└── extractor/
└── rumble_player.py
2. 동영상의 공식 /embed/ 주소 가져오기
Rumble 시청 페이지 URL의 ID(예: v6xxxx)와 임베드 플레이어의 ID(예: vb0ofn)는 서로 다릅니다. 따라서 시청 페이지 ID로 임베드 주소를 직접 조합하면 안 됩니다. 일반 시청 페이지 링크만 있다면 Rumble이 공개한 공식 oEmbed API를 호출해 실제 /embed/ 주소를 가져오세요.
curl -fsSG --data-urlencode "url=https://rumble.com/your-video.html" "https://rumble.com/api/Media/oembed.json"
응답 JSON의 html 필드에서 <iframe src="https://rumble.com/embed/xxxx/..."를 찾은 뒤 https://rumble.com/embed/xxxx/ 주소를 추출하면 됩니다.
oEmbed가 403을 반환하면 curl_cffi를 설치하고 Firefox 지문으로 다시 시도하세요. 아래 예시 URL을 동영상 시청 페이지의 전체 URL로 바꾸세요.
python -m pip install curl_cffi
python -c "import sys; from curl_cffi import requests; r = requests.get('https://rumble.com/api/Media/oembed.json', params=dict(url=sys.argv[1]), impersonate='firefox', timeout=15); r.raise_for_status(); print(r.text)" "https://rumble.com/your-video.html"
다시 시도해도 실패하면 온라인 다운로더에서 링크를 자동으로 변환할 수 있습니다.
3. 사용 가능한 형식과 폴백 동작 확인
plugins 폴더가 있는 디렉터리에서 다음 명령을 실행하세요. 실제로 사용할 때는 대상 동영상의 /embed/ 주소로 바꾸면 됩니다.
yt-dlp --ignore-config --plugin-dirs ./plugins --no-cache-dir -v -F "https://rumble.com/embed/vb0ofn/"
로그에 [RumbleEmbed+PlayerFallback]이 표시되면 플러그인이 로드된 것입니다.
- JSON API가 403을 반환하면 이어서
Downloading player webpage로그가 나타납니다. 이 로그는 대체 경로가 시작됐다는 뜻입니다. 이후 형식 목록이 표시되어야 데이터 파싱이 성공했음을 확인할 수 있습니다. - 현재 네트워크 환경에서 JSON API가 200을 반환하면 yt-dlp의 기존 처리 경로를 그대로 사용합니다. 플레이어 페이지를 추가로 요청하지 않습니다.
4. 동영상 다운로드 및 디코딩 검증
-F로 표시된 형식 ID 중 하나(예: mp4-360p)를 골라 다운로드하세요.
yt-dlp --ignore-config --plugin-dirs ./plugins --no-cache-dir -v --no-playlist -f mp4-360p -o "rumble-test.%(ext)s" "https://rumble.com/embed/vb0ofn/"
실제 테스트에서 폴백이 실행됐을 때의 터미널 로그는 다음과 같습니다.
[RumbleEmbed+PlayerFallback] vb0ofn: Downloading JSON metadata
[RumbleEmbed+PlayerFallback] vb0ofn: Downloading player webpage
[info] vb0ofn: Downloading 1 format(s): mp4-360p
[download] 100% of 7.85MiB in 00:01:20 at 99.48KiB/s
다운로드한 MP4 컨테이너와 영상·음성 스트림에 누락이나 손상이 없는지 확인하려면 ffprobe와 ffmpeg를 사용하세요.
ffprobe -v error -show_entries stream=codec_name,codec_type,width,height:format=duration,size -of json "rumble-test.mp4"
ffmpeg -nostdin -v error -xerror -i "rumble-test.mp4" -map 0:v:0 -map "0:a:0?" -f null -
작동 원리: 플레이어 페이지로 JSON 403을 피할 수 있는 이유
1. 같은 사이트의 인라인 데이터 재사용
RumbleEmbedIE는 기본적으로 https://rumble.com/embedJS/u3/?request=video&ver=2&v=...에 직접 요청해 JSON 메타데이터를 가져옵니다. Cloudflare나 사이트의 접근 제어 정책이 이 API만 차단해 HTTP 403을 반환하면 추출이 중단됩니다.
하지만 공식 임베드 페이지 https://rumble.com/embed/<video_id>/의 스크립트에는 동일한 구조의 동영상 설정 객체가 이미 포함되어 있습니다. 여기에는 화질별 스트림 주소를 담은 ua, 제목, 재생 시간 등의 필드가 있습니다. 이 객체를 추출해 _download_json의 반환값으로 전달하면, yt-dlp의 후속 형식 파싱 코드를 수정하지 않고도 처리를 계속할 수 있습니다.
2. 표준 JSON이 아닌 필드(loaded: h()) 제거
임베드 페이지의 인라인 데이터 객체는 엄격한 JSON 리터럴이 아닙니다. 다음과 같이 끝부분에 런타임 함수 호출 loaded: h()가 들어 있습니다.
u.f["vb0ofn"] = {"ua": {"mp4": {"360": {"url": "https://..."}}}, "title": "...", loaded: h()};
이 객체를 그대로 JSON 파서에 전달하면 h() 때문에 구문 오류가 발생합니다. 플러그인은 _search_json의 transform_source 훅에서 정규 표현식으로 끝부분의 , loaded: xxx()만 제거한 뒤 파싱합니다. 모든 처리는 메모리의 텍스트에 대해 수행하며, 웹페이지의 JavaScript는 실행하지 않습니다.
- 정확한 대상 지정: 키 이름이 현재
video_id와 정확히 일치하는 객체만 추출합니다(\b[\w$]+\.f\[\s*["']{video_id}["']\s*\]\s*=). 반환값이 딕셔너리이며 비어 있지 않은ua미디어 스트림 필드를 포함하는지도 확인합니다. - 최소한의 개입:
_download_json에서 잡힌HTTPError의 상태 코드가403일 때만 폴백합니다. 401, 404, 429, 500 등의 오류이거나 페이지에 유효한 객체가 없으면 원래 예외를 그대로 다시 발생시킵니다. - 온라인 서비스의 추가 처리: 사이트에서는 이 플러그인을 로드하는 것 외에도 oEmbed API를 자동 호출해 사용자가 입력한 시청 페이지 URL을
/embed/주소로 변환합니다.--impersonate를 지정하지 않았다면 기본으로firefox-135지문을 사용합니다.
전체 플러그인 코드
아래 코드를 plugins/rumble/yt_dlp_plugins/extractor/rumble_player.py로 저장하세요. Python 3.12+와 yt-dlp 2026.10.10+에서 동작을 확인했습니다.
"""Reuse official player media data when Rumble's JSON endpoint is blocked."""
import re
from yt_dlp.extractor.rumble import RumbleEmbedIE as BaseRumbleEmbedIE
from yt_dlp.networking.exceptions import HTTPError
from yt_dlp.utils import ExtractorError
class RumbleEmbedIE(BaseRumbleEmbedIE, plugin_name='PlayerFallback'):
def _real_extract(self, url):
self._player_url = url
return super()._real_extract(url)
def _download_json(self, url_or_request, video_id, *args, **kwargs):
try:
return super()._download_json(url_or_request, video_id, *args, **kwargs)
except ExtractorError as error:
if not isinstance(error.cause, HTTPError) or error.cause.status != 403:
raise
webpage = self._download_webpage(self._player_url, video_id, note='Downloading player webpage')
# Parse this video's object and strip the runtime call without executing page scripts.
data = self._search_json(
rf'''\b[\w$]+\.f\[\s*["']{re.escape(video_id)}["']\s*\]\s*=\s*''',
webpage, 'player data', video_id,
fatal=False,
transform_source=lambda source: re.sub(r',\s*loaded\s*:\s*[\w$]+\(\s*\)\s*(?=})', '', source))
if not isinstance(data, dict) or not data.get('ua'):
raise error
return data