mirror of
https://github.com/adef17286-sudo/KIJK_dl.git
synced 2025-10-13 17:18:10 +00:00
Backend
This commit is contained in:
parent
8a1734bdae
commit
c6afbb8b13
47
dl.py
Normal file
47
dl.py
Normal 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
43
get_lic.py
Normal 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
73
get_vid.py
Normal 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
145
keys.py
Normal 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
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pywidevine==1.8.0
|
||||||
|
Requests==2.32.5
|
17
tasks.py
Normal file
17
tasks.py
Normal 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()
|
Loading…
x
Reference in New Issue
Block a user