This commit is contained in:
adef17286-sudo 2025-10-12 18:46:12 +02:00 committed by GitHub
parent 8a1734bdae
commit c6afbb8b13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 327 additions and 0 deletions

47
dl.py Normal file
View File

@ -0,0 +1,47 @@
import sys
import subprocess
def main():
args = sys.argv[1:]
savename = None
if '--save-name' in args:
idx = args.index('--save-name')
if idx + 1 < len(args):
savename = args[idx + 1]
args = args[:idx] + args[idx+2:]
tasks_result = subprocess.run([sys.executable, 'tasks.py'] + args, capture_output=True, text=True)
dash_link = None
for line in tasks_result.stdout.splitlines():
if line.startswith("DASH Link:"):
dash_link = line[len("DASH Link:"):].strip()
break
get_result = subprocess.run([sys.executable, 'keys.py'] + args, capture_output=True, text=True)
get_output = get_result.stdout.strip()
string = f"DASH Link: {dash_link}\n{get_output}" if dash_link else get_output
print(string)
if savename:
print(savename)
command = [
'N_m3u8dl-re',
'-sv', 'best',
'-sa', 'best',
'--key', string,
]
if savename:
command.extend(['--save-name', savename])
if dash_link:
command.append(dash_link)
try:
subprocess.run(command, check=True)
except subprocess.CalledProcessError as e:
print(f"Error running N_m3u8dl-re: {e}")
if __name__ == "__main__":
main()

43
get_lic.py Normal file
View File

@ -0,0 +1,43 @@
import requests
import json
# Define the API URL
url = "https://api.prd.video.talpa.network/graphql"
# Prepare the headers
headers = {
"Accept": "*/*",
"Accept-Language": "nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7",
"Content-Type": "text/plain;charset=UTF-8",
"Priority": "u=1, i",
"Sec-CH-UA": "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
"Sec-CH-UA-Mobile": "?0",
"Sec-CH-UA-Platform": "\"Windows\"",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "cross-site",
}
# Prepare the body of the request
query = {
"query": """query DrmTokenQuery($provider: DrmProvider) {
drmToken(drmProvider: $provider) {
expiration
token
}
}""",
"variables": {
"provider": "JWP"
}
}
# Send the POST request
response = requests.post(url, headers=headers, data=json.dumps(query))
# Print the output
if response.status_code == 200:
# Extract the token
token = response.json()['data']['drmToken']['token']
print("Token:", token)
else:
print("Error:", response.status_code, response.text)

73
get_vid.py Normal file
View File

@ -0,0 +1,73 @@
import requests
import re
import sys
def fetch_video_info(guid):
url = f"https://api.prd.video.talpa.network/graphql?query=query+GetVideoQuery%28%24guid%3A%5BString%5D%29%7Bprograms%28guid%3A%24guid%29%7Bitems%7Bguid+type+metadata+availableRegion+...Media+...Tracks+...Sources%7D%7D%7Dfragment+Media+on+Program%7Bmedia%7Btype+availableDate+availabilityState+airedDateTime+expirationDate%7D%7Dfragment+Tracks+on+Program%7Btracks%7Bfile+kind+label%7D%7Dfragment+Sources+on+Program%7Bsources%7Btype+file+drm%7D%7D&variables=%7B%22guid%22%3A%22{guid}%22%7D"
headers = {
"accept": "*/*",
"accept-language": "nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7",
"content-type": "application/json",
"priority": "u=1, i",
"sec-ch-ua": "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "cross-site",
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
json_response = response.json()
dash_link = None
widevine_license_server = None
items = json_response.get('data', {}).get('programs', {}).get('items', [])
for item in items:
for source in item.get('sources', []):
if source['type'] == 'dash' and 'drm' in source and 'widevine' in source['drm']:
dash_link = source['file']
widevine_license_server = source['drm']['widevine']['url']
break # Exit loop once we find the first match
return {
"dash_link": dash_link,
"widevine_license_server": widevine_license_server
}
else:
return {"error": f"Request failed with status code {response.status_code}"}
def extract_guid(url):
# Check if the URL starts with the required base URL
if not url.startswith("https://www.kijk.nl/programmas/"):
return None
# Regex to extract GUID (the segment before the last slash)
match = re.match(r'https://www\.kijk\.nl/programmas/[^/]+/([^/?]+)', url)
if match:
return match.group(1)
return None
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python script.py {url}")
sys.exit(1)
user_url = sys.argv[1]
guid = extract_guid(user_url)
if guid:
video_info = fetch_video_info(guid)
if 'error' not in video_info:
print(f"DASH Link: {video_info['dash_link']}")
print(f"Widevine License Server: {video_info['widevine_license_server']}")
else:
print(video_info['error'])
else:
print("Invalid URL. Please ensure it starts with 'https://www.kijk.nl/programmas/' and contains a GUID.")

145
keys.py Normal file
View File

@ -0,0 +1,145 @@
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)

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
pywidevine==1.8.0
Requests==2.32.5

17
tasks.py Normal file
View File

@ -0,0 +1,17 @@
import subprocess
import sys
def run_commands():
# Run get_vid.py with the provided subcommands
if len(sys.argv) > 1:
vid_command = ['python', 'get_vid.py'] + sys.argv[1:]
vid_output = subprocess.run(vid_command, capture_output=True, text=True)
print(vid_output.stdout)
# Run get_lic.py without any subcommands
lic_command = ['python', 'get_lic.py']
lic_output = subprocess.run(lic_command, capture_output=True, text=True)
print(lic_output.stdout)
if __name__ == '__main__':
run_commands()