Hi everyone,
I’ve put together a simple Python script to help download Kinescope videos protected by ClearKey DRM ONLY. This was developed with assistance from ChatGPT and Gemini, and I’m sharing it here as a way to give back to this helpful community.
Prerequisites:
Before running the script, ensure you have the following binaries installed or added to your system PATH:
N_m3u8DL-RE
FFmpeg
shaka-packager
mp4decrypt
Download & Source:
Direct Script (Google Drive): https://drive.google.com/file/d/10iqDJlgeEag9yYVm3K-sznG0XysP7MIx/view?usp=sharing
Open Source Code (VideoHelp): https://files.videohelp.com/u/315187/kinescope4clearkey.py
Note on Widevine:
I initially intended to include Widevine support, but I ran into hurdles retrieving the keys. I’ve left that portion incomplete—if any experts in the community want to pick up where I left off and add Widevine functionality, feel free to modify and improve the code!
Cheers!
+ Reply to Thread
Results 1 to 7 of 7
-
-
-
an update to the code because it doesn't tell you if the clearkey_url URL exists.
Code:import requests import json import re import base64 import binascii import subprocess from urllib.parse import urlparse from colorama import Fore, Style, init init(autoreset=True) HEADERS = {"User-Agent": "Mozilla/5.0", "Referer": "https://kinescope.io/", "Origin": "https://kinescope.io/"} # ---------- UI ---------- def info(t): print(Fore.CYAN + t) def success(t): print(Fore.GREEN + t) def error(t): print(Fore.RED + t) def ask(t): return input(Fore.CYAN + t + Style.RESET_ALL) # ---------- utils ---------- def req(url, method="get", **kwargs): return requests.request(method, url, headers=HEADERS, timeout=10, **kwargs) def replace_token(url): return url.replace("kinescope.io/", "license.kinescope.io/v1/vod/") # ---------- parsing ---------- def get_pssh(master_url): r = req(master_url) r.raise_for_status() lines = r.text.splitlines() variants = [] i = 0 # --- parse video streams only --- while i < len(lines) - 1: if "#EXT-X-STREAM-INF" in lines[i]: url = lines[i + 1].strip() # ignore audio if "type=audio" in url: i += 2 continue res = re.search(r"RESOLUTION=(\d+)x(\d+)", lines[i]) if res: height = int(res.group(2)) variants.append((height, url)) i += 2 else: i += 1 if not variants: return None, None # --- sort best first --- variants.sort(reverse=True) # --- fallback 1080 → 720 → 480 --- chosen_url = None for h, url in variants: if h <= 1080: chosen_url = url break if not chosen_url: chosen_url = variants[-1][1] # build full URL if needed media_url = (master_url.rsplit("/", 1)[0] + "/" + chosen_url if not chosen_url.startswith("http") else chosen_url) # --- fetch media for pssh --- r2 = req(media_url) r2.raise_for_status() pssh = re.search(r"base64,(AAA[A-Za-z0-9+/=]+)", r2.text) return (pssh.group(1) if pssh else None, media_url) def extract_player_data(url): r = req(url) r.raise_for_status() match = re.search(r"var playerOptions\s*=\s*({.*?});", r.text, re.S) if not match: return None data = json.loads(match.group(1)) item = data["playlist"][0] master = item["sources"]["hls"]["src"] title = item.get("title", "unknown") pssh, media_url = get_pssh(master) base = {"master_url": master, "title": title, "pssh": pssh, "media_url": media_url} if pssh: base["widevine_url"] = master.replace("master.m3u8", "acquire/widevine?token=") base["fairplay_url"] = master.replace("master.m3u8", "acquire/fairplay?token=") else: base["clearkey_url"] = master.replace("master.m3u8", "acquire/clearkey?token=") return base # ---------- license ---------- def get_kid(media_url): r = req(media_url) match = re.search(r"/sample-aes/([a-zA-Z0-9=]+)\?", r.text) if not match: return None return base64.b64encode(binascii.unhexlify(match.group(1))).decode() def get_key(kid, url): payload = {"kids": [kid.rstrip("=")], "type": "temporary"} r = req(url, "post", data=json.dumps(payload)) return r.json()["keys"][0]["k"] # ---------- decode ---------- def fix_b64(b): return base64.b64decode(b + "=" * (-len(b) % 4)).hex() # ---------- download ---------- def download(data, kid, key): cmd = [ "n_m3u8dl-re", data["master_url"], "--key", f"{kid}:{key}", "--tmp-dir", "temp", "--save-dir", "downloads", "--save-name", data["title"], "-M", "mkv", ] subprocess.run(cmd) success(f"Done: {data['title']}") # ---------- main ---------- def main(): while True: url = ask("URL: ") parsed = urlparse(url) clean = f"{parsed.scheme}://{parsed.netloc}{parsed.path}" data = extract_player_data(clean) if not data: error("Invalid page") continue for k in ["clearkey_url", "widevine_url", "fairplay_url"]: if data.get(k): data[k] = replace_token(data[k]) if not data.get("clearkey_url"): error("No ClearKey URL found, skipping DRM step") continue try: kid = get_kid(data["media_url"]) key = get_key(kid, data["clearkey_url"]) download(data, fix_b64(kid), fix_b64(key)) except Exception as e: error(str(e)) if __name__ == "__main__": main() -
Thanks a lot for having an interest in the script and trying to update it

but now it won't accept m3u8 urls
and it doesn't work for some urls as well
this won't work because its widevine protected so let it be
https://kinescope.io/embed/5f4vBv2JTCX41498QWVAqg
this should work but it doesn't
https://kinescope.io/embed/dPBScbxEMJsd6H3uD65xjq
thanks anyways -
I made a correction to the script to simplify things:
Code:import requests import json import re import base64 import binascii import subprocess from urllib.parse import urlparse from colorama import Fore, Style, init init(autoreset=True) HEADERS = {"User-Agent": "Mozilla/5.0", "Referer": "https://kinescope.io/", "Origin": "https://kinescope.io/"} # ---------- UI ---------- def info(t): print(Fore.YELLOW + t) def success(t): print(Fore.GREEN + t) def error(t): print(Fore.RED + t) def ask(t): return input(Fore.CYAN + t + Style.RESET_ALL) # ---------- utils ---------- def req(url, method="get", **kwargs): return requests.request(method, url, headers=HEADERS, timeout=10, **kwargs) def replace_token(url): return url.replace("kinescope.io/", "license.kinescope.io/v1/vod/") # ---------- parsing ---------- def get_pssh(master_url): r = req(master_url) r.raise_for_status() lines = r.text.splitlines() variants = [] i = 0 # --- parse video streams only --- while i < len(lines) - 1: if "#EXT-X-STREAM-INF" in lines[i]: url = lines[i + 1].strip() # ignore audio if "type=audio" in url: i += 2 continue res = re.search(r"RESOLUTION=(\d+)x(\d+)", lines[i]) if res: height = int(res.group(2)) variants.append((height, url)) i += 2 else: i += 1 if not variants: return None, None # --- sort best first --- variants.sort(reverse=True) # --- fallback 1080 → 720 → 480 --- chosen_url = None for h, url in variants: if h <= 1080: chosen_url = url break if not chosen_url: chosen_url = variants[-1][1] # build full URL if needed media_url = (master_url.rsplit("/", 1)[0] + "/" + chosen_url if not chosen_url.startswith("http") else chosen_url) # --- fetch media for pssh --- r2 = req(media_url) r2.raise_for_status() pssh = re.search(r"base64,(AAA[A-Za-z0-9+/=]+)", r2.text) return (pssh.group(1) if pssh else None, media_url) def extract_player_data(url): info("Extraction in progress...") r = req(url) r.raise_for_status() match = re.search(r"var playerOptions\s*=\s*({.*?});", r.text, re.S) if not match: return None data = json.loads(match.group(1)) item = data["playlist"][0] master = item["sources"]["hls"]["src"] title = item.get("title", "unknown") pssh, media_url = get_pssh(master) base = {"master_url": master, "title": title, "pssh": pssh, "media_url": media_url} if pssh: base["widevine_url"] = master.replace("master.m3u8", "acquire/widevine?token=") base["fairplay_url"] = master.replace("master.m3u8", "acquire/fairplay?token=") else: base["clearkey_url"] = master.replace("master.m3u8", "acquire/clearkey?token=") return base # ---------- license ---------- def get_kid(media_url): r = req(media_url) match = re.search(r"/sample-aes/([a-zA-Z0-9=]+)\?", r.text) if not match: return None return base64.b64encode(binascii.unhexlify(match.group(1))).decode() def get_key(x_key, url): payload = {"kids": [x_key.rstrip("=")], "type": "temporary"} r = req(url, "post", data=json.dumps(payload)) data = r.json() #info(f"[+] Data: {data}") kid_b64 = data["keys"][0]["kid"] k_b64 = data["keys"][0]["k"] kid_bytes = b64url_decode(kid_b64) k_bytes = b64url_decode(k_b64) return kid_bytes.hex(), k_bytes.hex() # ---------- decode ---------- def b64url_decode(data): data += '=' * (-len(data) % 4) return base64.urlsafe_b64decode(data) # ---------- download ---------- def download(data, kid, key): info("download in progress...") cmd = [ "n_m3u8dl-re", data["master_url"], "--key", f"{kid}:{key}", "--tmp-dir", "temp", "--save-dir", "downloads", "--save-name", data["title"], "-M", "mkv", ] subprocess.run(cmd) success(f"Done: {data['title']}") # ---------- main ---------- def main(): while True: url = ask("URL: ") parsed = urlparse(url) clean = f"{parsed.scheme}://{parsed.netloc}{parsed.path}" data = extract_player_data(clean) #info(f"[+] Data: {data}") if not data: error("Invalid page") continue info("Clearkey verification in progress...") for k in ["clearkey_url", "widevine_url", "fairplay_url"]: if data.get(k): data[k] = replace_token(data[k]) if not data.get("clearkey_url"): error("No ClearKey URL found, skipping DRM step") continue try: x_key = get_kid(data["media_url"]) kid ,key = get_key(x_key, data["clearkey_url"]) success(f"--key {kid}:{key}") #download(data, kid, key) except Exception as e: error(str(e)) if __name__ == "__main__": main()
[Attachment 92135 - Click to enlarge]
Similar Threads
-
Kinescope
By vladimir1919 in forum Video Streaming DownloadingReplies: 8Last Post: 7th Apr 2026, 11:12 -
I need Clearkey
By Devshree in forum Video Streaming DownloadingReplies: 20Last Post: 3rd May 2025, 09:07 -
Kinescope help
By cbehr91 in forum RestorationReplies: 8Last Post: 5th Jul 2024, 18:18 -
Kinescope.io DRM protection. Help ^_^
By Arina in forum Video Streaming DownloadingReplies: 2Last Post: 25th May 2024, 06:59 -
Help me with video downloading from Kinescope!
By teri4 in forum Video Streaming DownloadingReplies: 0Last Post: 27th Sep 2022, 14:20


Quote
