kijk_DL/keys.py
2025-10-12 18:46:12 +02:00

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)