mirror of
				https://github.com/adef17286-sudo/KIJK_dl.git
				synced 2025-11-04 05:14:49 +00:00 
			
		
		
		
	
		
			
	
	
		
			146 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			146 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								import subprocess
							 | 
						||
| 
								 | 
							
								import sys
							 | 
						||
| 
								 | 
							
								import requests
							 | 
						||
| 
								 | 
							
								import xml.etree.ElementTree as ET
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								from pywidevine.cdm import Cdm
							 | 
						||
| 
								 | 
							
								from pywidevine.device import Device
							 | 
						||
| 
								 | 
							
								from pywidevine.pssh import PSSH
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def call_tasks(subcommand, *args):
							 | 
						||
| 
								 | 
							
								    command = [sys.executable, 'tasks.py', subcommand] + list(args)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        result = subprocess.run(command, capture_output=True, text=True, check=True)
							 | 
						||
| 
								 | 
							
								        return result.stdout.strip()
							 | 
						||
| 
								 | 
							
								    except subprocess.CalledProcessError as e:
							 | 
						||
| 
								 | 
							
								        print(f"Error executing {subcommand}: {e}")
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def parse_output(output):
							 | 
						||
| 
								 | 
							
								    dash_link = None
							 | 
						||
| 
								 | 
							
								    widevine_license_server = None
							 | 
						||
| 
								 | 
							
								    token = None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    lines = output.splitlines()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    for line in lines:
							 | 
						||
| 
								 | 
							
								        if line.startswith("DASH Link:"):
							 | 
						||
| 
								 | 
							
								            dash_link = line.split("DASH Link: ", 1)[1].strip()
							 | 
						||
| 
								 | 
							
								        elif line.startswith("Widevine License Server:"):
							 | 
						||
| 
								 | 
							
								            widevine_license_server = line.split("Widevine License Server: ", 1)[1].strip()
							 | 
						||
| 
								 | 
							
								        elif line.startswith("Token:"):
							 | 
						||
| 
								 | 
							
								            token = line.split("Token: ", 1)[1].strip()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return dash_link, widevine_license_server, token
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def download_mpd(dash_link):
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        response = requests.get(dash_link)
							 | 
						||
| 
								 | 
							
								        response.raise_for_status()
							 | 
						||
| 
								 | 
							
								        return response.text
							 | 
						||
| 
								 | 
							
								    except requests.RequestException as e:
							 | 
						||
| 
								 | 
							
								        print(f"Error downloading MPD file: {e}")
							 | 
						||
| 
								 | 
							
								        return None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def extract_pssh(mpd_content):
							 | 
						||
| 
								 | 
							
								    pssh_strings = set()
							 | 
						||
| 
								 | 
							
								    root = ET.fromstring(mpd_content)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # More flexible approach to find <pssh> elements regardless of namespaces
							 | 
						||
| 
								 | 
							
								    for elem in root.iter():
							 | 
						||
| 
								 | 
							
								        if elem.tag.endswith('pssh') and elem.text:
							 | 
						||
| 
								 | 
							
								            pssh_strings.add(elem.text.strip())
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    return pssh_strings
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								KEY_REGEX = re.compile(r'\b([0-9a-fA-F]{32}):([0-9a-fA-F]{32})\b')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def extract_keys_from_cdm_output(text):
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    Extract and return list of keys matching 32hex:32hex from CDM output text.
							 | 
						||
| 
								 | 
							
								    """
							 | 
						||
| 
								 | 
							
								    matches = KEY_REGEX.findall(text)
							 | 
						||
| 
								 | 
							
								    return [f"{kid.lower()}:{key.lower()}" for kid, key in matches]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def fetch_keys_from_pssh(pssh_b64, license_url, token=None):
							 | 
						||
| 
								 | 
							
								    try:
							 | 
						||
| 
								 | 
							
								        pssh = PSSH(pssh_b64)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Load device (adjust path to your provisioned .wvd file)
							 | 
						||
| 
								 | 
							
								        device = Device.load("cdm.wvd")  # <-- Replace with your actual path
							 | 
						||
| 
								 | 
							
								        cdm = Cdm.from_device(device)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        session_id = cdm.open()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Get license challenge
							 | 
						||
| 
								 | 
							
								        challenge = cdm.get_license_challenge(session_id, pssh)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        headers = {'x-vudrm-token': token} if token else {}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Send license request
							 | 
						||
| 
								 | 
							
								        response = requests.post(license_url, data=challenge, headers=headers)
							 | 
						||
| 
								 | 
							
								        response.raise_for_status()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Parse license
							 | 
						||
| 
								 | 
							
								        cdm.parse_license(session_id, response.content)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Extract keys from CDM object
							 | 
						||
| 
								 | 
							
								        keys_text = ""
							 | 
						||
| 
								 | 
							
								        for key in cdm.get_keys(session_id):
							 | 
						||
| 
								 | 
							
								            keys_text += f"[{key.type}] {key.kid.hex}:{key.key.hex()}\n"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Close session
							 | 
						||
| 
								 | 
							
								        cdm.close(session_id)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Extract and print only keys matching 32hex:32hex pattern
							 | 
						||
| 
								 | 
							
								        keys = extract_keys_from_cdm_output(keys_text)
							 | 
						||
| 
								 | 
							
								        if keys:
							 | 
						||
| 
								 | 
							
								            for k in keys:
							 | 
						||
| 
								 | 
							
								                print(k)
							 | 
						||
| 
								 | 
							
								        else:
							 | 
						||
| 
								 | 
							
								            print("No 32hex:32hex keys found in this session.")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    except Exception as e:
							 | 
						||
| 
								 | 
							
								        print(f"Error processing PSSH: {e}")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								if __name__ == "__main__":
							 | 
						||
| 
								 | 
							
								    if len(sys.argv) < 2:
							 | 
						||
| 
								 | 
							
								        print("Usage: python wrapper.py <subcommand> [args...]")
							 | 
						||
| 
								 | 
							
								        sys.exit(1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    subcommand = sys.argv[1]
							 | 
						||
| 
								 | 
							
								    args = sys.argv[2:]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Step 1: Run tasks.py and get output
							 | 
						||
| 
								 | 
							
								    output = call_tasks(subcommand, *args)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if output is not None:
							 | 
						||
| 
								 | 
							
								        # Step 2: Parse output
							 | 
						||
| 
								 | 
							
								        dash_link, widevine_license_server, token = parse_output(output)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Step 3: Download MPD file
							 | 
						||
| 
								 | 
							
								        mpd_content = download_mpd(dash_link)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if mpd_content:
							 | 
						||
| 
								 | 
							
								            # Step 4: Extract PSSHs
							 | 
						||
| 
								 | 
							
								            pssh_strings = extract_pssh(mpd_content)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            
							 | 
						||
| 
								 | 
							
								            for pssh in pssh_strings:
							 | 
						||
| 
								 | 
							
								                print("")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            # Step 5: Fetch keys using each PSSH
							 | 
						||
| 
								 | 
							
								            print("")
							 | 
						||
| 
								 | 
							
								            for pssh in sorted(pssh_strings):
							 | 
						||
| 
								 | 
							
								                print()
							 | 
						||
| 
								 | 
							
								                fetch_keys_from_pssh(pssh, widevine_license_server, token)
							 |