yt-dlp Rumble 403 fix: fall back to player data
Published
Download fix plugin rumble_player.py
If yt-dlp fails to download a Rumble video with Unable to download JSON metadata: HTTP Error 403: Forbidden, you can load the compatibility plugin below. When Rumble's embedJS/u3 JSON endpoint returns 403, the plugin fetches the same video's official embed player page (/embed/), extracts its inline media data, and continues the download.
To skip local plugin setup and manual URL conversion, use our Rumble video downloader, which already includes this fix.
Identify where the 403 occurs
A Rumble download can fail with 403 at three different stages. This plugin addresses the second one:
| Error | Where the request is blocked | What to do |
|---|---|---|
Unable to download webpage: HTTP Error 403 |
The watch page HTML is blocked (see issue #15089) | Use the official oEmbed API to obtain the /embed/ URL, together with --impersonate firefox |
Unable to download JSON metadata: HTTP Error 403 |
The embedJS/u3 metadata endpoint is blocked (see issue #17496) |
Use this plugin to fall back to inline data from the /embed/ page |
| Formats are listed, but a fragment or MP4 download returns 403 | The CDN rejects the media request | Check the proxy and request headers, or try another quality format |
Quick fix steps
1. Save the plugin file
Create this directory structure in your working directory and save the full plugin code at the end as rumble_player.py. You do not need to edit yt-dlp's source code.
plugins/
└── rumble/
└── yt_dlp_plugins/
└── extractor/
└── rumble_player.py
2. Get the video's official /embed/ URL
The ID in a Rumble watch page URL, such as v6xxxx, differs from the embed player ID, such as vb0ofn. You cannot construct the embed URL by copying that ID. If you only have a watch page URL, request the real /embed/ URL from Rumble's public oEmbed API:
curl -fsSG --data-urlencode "url=https://rumble.com/your-video.html" "https://rumble.com/api/Media/oembed.json"
Find <iframe src="https://rumble.com/embed/xxxx/..." in the JSON response's html field and copy the https://rumble.com/embed/xxxx/ URL.
If oEmbed returns 403, install curl_cffi and retry with a Firefox fingerprint. Replace the example URL below with the full watch-page 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"
If the retry fails, use the online downloader to convert the link automatically.
3. List formats and check the fallback
Run this command from the directory containing plugins, replacing the example URL with your video's /embed/ URL:
yt-dlp --ignore-config --plugin-dirs ./plugins --no-cache-dir -v -F "https://rumble.com/embed/vb0ofn/"
The [RumbleEmbed+PlayerFallback] label confirms that the plugin loaded:
- If the JSON endpoint returns 403, a
Downloading player webpagelog entry follows. This indicates that the fallback ran; the resulting format list confirms that parsing succeeded. - If the JSON endpoint returns 200 on your connection, yt-dlp follows its normal extraction path without an extra player page request.
4. Download and check decoding
Choose a format ID from the -F output, such as mp4-360p, and download it:
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/"
The terminal output from our test shows the fallback being used and the download completing:
[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
Use ffprobe and ffmpeg to inspect the MP4 container and check its video and audio streams for decoding errors:
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 -
How the player page provides a fallback for the JSON 403
1. Reuse inline data from the same site
By default, RumbleEmbedIE requests JSON metadata from https://rumble.com/embedJS/u3/?request=video&ver=2&v=.... Extraction stops if Cloudflare or the site's request filtering blocks that endpoint with HTTP 403.
The official embed page at https://rumble.com/embed/<video_id>/ also includes a video configuration object in its scripts. It has the same structure, including the ua field containing media URLs for each quality, the title, and the duration. Returning this object from _download_json lets yt-dlp's existing format parser continue without changes.
2. Remove the non-JSON field (loaded: h())
The inline object is not strictly valid JSON. It ends with a runtime function call, loaded: h(), as in this example:
u.f["vb0ofn"] = {"ua": {"mp4": {"360": {"url": "https://..."}}}, "title": "...", loaded: h()};
A JSON parser rejects h(). The plugin uses the transform_source hook in _search_json to remove only the trailing , loaded: xxx() with a regular expression before parsing. It works entirely on text in memory and does not execute the page's JavaScript:
- Match the requested video: extract only the object whose key matches the current
video_id(\b[\w$]+\.f\[\s*["']{video_id}["']\s*\]\s*=), then check that the result is a dictionary with a nonemptyuamedia field. - Limit when the fallback runs: use it only when
_download_jsoncatches anHTTPErrorwith status403. Errors such as 401, 404, 429, and 500, or a page without a valid object, preserve the original exception. - Handle watch URLs in the online service: alongside this plugin, our service uses oEmbed to convert watch page URLs to
/embed/URLs. It also defaults to thefirefox-135fingerprint when--impersonatehas not been specified.
Full plugin code
Save this code as plugins/rumble/yt_dlp_plugins/extractor/rumble_player.py. It has been verified with Python 3.12+ and 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