VideoHelp Forum




+ Reply to Thread
Results 1 to 7 of 7
  1. 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!
    Quote Quote  
  2. Originally Posted by Frieren View Post
    didn't know that one existed already
    no issues at least I learnt something new

    thanks for sharing Frieren
    hope it gets widevine videos too
    Quote Quote  
  3. 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()
    Quote Quote  
  4. Originally Posted by Frieren View Post
    I tried it Frieren but sadly it doens't work
    Quote Quote  
  5. Originally Posted by sesamap159 View Post
    an update to the code because it doesn't tell you if the clearkey_url URL exists.
    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
    Quote Quote  
  6. 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()
    Image
    [Attachment 92135 - Click to enlarge]
    Quote Quote  



Similar Threads

Visit our sponsor! Try DVDFab and backup Blu-rays!