l3.py without a CDM
@sk8ordl3 made an interesting post recently and this has grown from it. I'm not sure how long it will be allowed to work.
Below is code for a drop in replacement for l3.py.
It works for simple sites that do not need headers of any complexity.Code:''' cdrm-project (website of a CDM seller) put up a freely accessible L3 Content Decryption Module and then took down the API details so no-one could use it without first obtaining an API key. @sk8ordl3 posted some code in this forum that used the cdrm-project api, BUT without an api key; which got my attention. Thanks sk8ordl3. The wayback machine has the old API. What follows is a drop in replacement for l3.py which does not need you to have a CDM ''' import requests,json api_url = "https://cdrm-project.com/api" license_url = input("License url? ") pssh = input("PSSH? ") headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Ktesttemp, like Gecko) Chrome/90.0.4430.85 Safari/537.36'} myjson={ "license":license_url, "pssh":pssh, "proxy":"", "cache":False } r = requests.post(api_url,headers=headers ,json=myjson).text #print(r) mydict = json.loads(r) print("\n------------ Keys ------------\n") keys = mydict['keys'] for key in keys: print (key['key'])
Test with License: https://cwip-shaka-proxy.appspot.com/no_auth
PSSH: AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62 dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xq YVNkZmFsa3IzaioCSEQyAA==
Newbie corner
To use: save the code in the white box to a file called cl3.py
Then, assuming you have python on your machine, open a command window in the directory where you saved cl3.py and type
paste responses in the command window to the questions asked.Code:python cl3.py
Support our site by donate $5 directly to us Thanks!!!
Try StreamFab Downloader and download streaming video from Netflix, Amazon!
Try StreamFab Downloader and download streaming video from Netflix, Amazon!
+ Reply to Thread
Results 1 to 16 of 16
-
Last edited by A_n_g_e_l_a; 9th Jun 2023 at 08:16. Reason: Test added
-
Here are a couple of downloader scripts that do not need you to own a CDM. They both use a replacement for the function WV_Function(pssh, lic_url, cert_b64=None): - common to much of widewine decrypting that uses WKS-KEYS.
First is a fully worked up downloader script for sites that use boltdns.net to deliver their drm management. That means, in the UK, STV.tv UKtvPlay.co.uk tptvencore.co.uk - (not everything is drm).
A generic boltdns.net downloader without needing your own CDM
Code:#!/usr/bin/env python3 # # v 3.0 # # A_n_g_e_l_a 09:06:2023 # ''' This program is a generic boltdns.net downloader. It takes only a SINGLE mpd url, master.m3u8 or vmap - as input as a 'media_url' The program downloads, decodes and merges. Written for Linux systems using WKS-KEYS with a working CDM. For encrypted media, this program needs to be in WKS-KEYS folder to use your local CDM it will save to ./output/ from the WKS_KEYS folder and you need create the folder 'output' if it doesn't exist. N_m3u8DL-RE, shaka-packager, mkvmerge and ffmpeg need to be in Path. (Windows users may need to tack .exe on the end of the called programs in the code below) 'pip install vtt_to_srt3' probably needed first; installs a subtitle conversion that works better than N_m3u8DL-RE's It is recommended 'The Stream Detector' browser plugin (Chrome + Firefox) Find here: Firefox version - https://addons.mozilla.org/en-US/firefox/addon/hls-stream-detector/ Chrome version: https://github.com/rowrawer/stream-detector/releases choose hls_stream_detector-2.1X.XX.crx and in chrome://extensions/ set Developer mode (top-right window) and drag and drop the crx file into the window to install If that does not work on Windows for you, try this:- https://forum.videohelp.com/threads/403491-Is-there-a-The-Stream-Detector-like-extension-for-Chrome#post2687785 Once installed configure with an additional filter set for 'vmap'. Under Options: 'Detect additional file extensions:' set checkbox ticked and enter 'vmap' in box. The program accepts calling from commandline, with media-url and videoname as parameters, or manual input from the clipboard. making this an ideal routine to be called by a larger command program working for :- STV.tv both encrypted and encryption free uktvplay.co.uk all encrypted. tptvencore.co.uk mainly unencrypted m3u8 but some encrypted vmap included too. Media_url may need to be contained in single quotes if there are '&' characters present. tg4.ie all encrypted and possibly other providers using boltdns.net for streaming ''' import requests #from pywidevine.L3.cdm import deviceconfig #from base64 import b64encode #from pywidevine.L3.decrypt.wvdecryptcustom import WvDecrypt import re import subprocess import pyfiglet as PF from termcolor import colored import pyperclip as PC import os import math ## globals headers = { 'User-Agent': 'Dalvik/2.9.8 (Linux; U; Android 9.9.2; ALE-L94 Build/NJHGGF)', 'Accept': '*/*', 'Accept-Language': 'en-GB,en;q=0.9', 'Connection': 'keep-alive', } # defs # regex search for pssh and license url def get_pssh_lic(mpd_url): mpd = requests.get(mpd_url, headers).text lines = mpd.split("\n") for line in lines: m = re.search('<cenc:pssh>(AAAA.+?)</cenc:pssh>', line) n = re.search('bc:licenseAcquisitionUrl=\"(http.+?)\" xmlns:bc=\"urn:brightcove:2015\"', line) if m: pssh = m.group(1) if n: lic_url = n.group(1) return pssh, lic_url ## pretty print screen divisions def divides(text): #text = text l = len(text) try: count, lines = os.get_terminal_size() count = int(count) except: count = 78 if count <= 78: count = int(count) else: count = int(math.ceil(count/2)) count = count - l if text != 'null': line = (chr(9601) * int(math.ceil(count/2))) #print("\n") print(colored(line, 'green'), " ", text, " ", colored(line, 'green')) else: line = (chr(9601) * int(count + 10)) print(colored(line, 'green')) ############################### # # there is an experimental choice below uncomment one or other to switch # and uncomment the 'import' statements above that are presently commented out # ############################### '''# WKS-KEYS key fetch using local CDM def WV_Function(pssh, lic_url, cert_b64=None): wvdecrypt = WvDecrypt(init_data_b64=pssh, cert_data_b64=cert_b64, device=deviceconfig.device_android_generic) widevine_license = requests.post(url=lic_url, data=wvdecrypt.get_challenge(), headers=None) license_b64 = b64encode(widevine_license.content) wvdecrypt.update_license(license_b64) Correct, keyswvdecrypt = wvdecrypt.start_process() if Correct: mykeys = '' for key in keyswvdecrypt: mykeys += key + '--key' if mykeys.endswith('--key'): mykeys = mykeys[:-5] divides('Key(s)') print(mykeys) return mykeys ''' #CDRM key remote call to cdrm-project.com def WV_Function(pssh, lic_url, cert_b64=None): lic_url = lic_url headers_cdrm = { 'Connection': 'keep-alive', 'Content-Type': 'application/json', 'Origin': 'https://cdrm-project.com', 'Referer': 'https://cdrm-project.com/', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36', } json_data = { 'license': lic_url, 'headers': 'Connection: keep-alive', 'pssh': pssh, 'buildInfo': '', 'proxy': '', 'cache': True, } resp = requests.post('https://cdrm-project.com/wv', headers=headers_cdrm, json=json_data).text keys = re.findall("<li style=\"font-family:'Courier'\">(.+?)<\/li>", resp) if keys: for match in keys: mykeys = '' mykeys += match + '--key' if mykeys.endswith('--key'): mykeys = mykeys[:-5] divides('Key(s)') print(mykeys) return mykeys # find mpd/m3u8 in vmap def parse_vmap(content): lines = content.split("\n") for line in lines: m = re.search(r'contenturi="(https.+?)" contentlength=', line) if m: media_url = m.group(1) return(media_url) return None # add leading zero to series or episode def pad_number(match): number = int(match.group(1)) return format(number, "02d") if __name__ == '__main__': #### main #### divides("null") print() title = PF.figlet_format(' BoltDNS dot net ', font='smslant') print(colored(title, 'green')) divides('A Generic Boltdns.net single downloader') from sys import argv #print(argv) if argv and len(argv) > 1: media_url = argv[1] videoname = argv[2] videoname = input(f"{videoname} \nVideoname?? ") else: print('\n\n[info] URLs may be of the form:- mpd, master.m3u8 or vmap from boltdns.net.') print('\n\n[info] Use Stream Detector "copy as Table Entry"') input("Ready to read clipboard TSD Table Entry: ") line = PC.paste() if 'master.m3u8?behavior_id' in line: # keeps changing!! STV = True else: STV = False linetuple = line.split('|') media_url = linetuple[0].replace('\n','').replace(' ', '') print(f"[info] downloading {media_url}") # collect videoname from table entry # each site has different naming conventions # this may need tweeking ... if STV: videoname1 = linetuple[1] videoname2 = linetuple[2].split(',') videoname2 = videoname2[0] #videoname = (videoname1 + videoname2).replace(' ','_').replace('__','-') videoname = videoname2.replace(' ','_').replace('__','-').replace(':','.') else: videoname = linetuple[1].replace('\n','').replace(' ','_')\ .replace('_Watch_','').replace('_Online_','') if videoname.endswith('_'): videoname = videoname.rstrip(videoname[-1]) if videoname.startswith('_'): videoname = videoname[1:] if not STV: videoname = videoname.replace('Series_','S').replace('_Episode_','E')\ .replace("'", '').replace('(','').replace(')','').replace(':','.').replace('&','and') videoname = re.sub(r"(\d+)", pad_number, videoname) # ... or reverting #videoname = input("Enter Video name to save: (without mkv) : ") #### decide type dash (mpd), m3u8 or vmap dash = "dash" #encrypted m3u8 = 'm3u8' vmap = 'vmap' #encrypted or not if vmap in media_url: content = requests.get(media_url, headers).text media_url = parse_vmap(content) # either m3u8 or dash if dash in media_url: pssh, lic = get_pssh_lic(media_url) # need keys mykeys = '' mykeys = WV_Function(pssh, lic) divides("getting encrypted streams") command = [ "N_m3u8DL-RE", media_url, "--auto-select", "-sv", "best", "-sa", "id='audio-0'", "-ss", "id='subtitles-0':for=all", "--save-name", videoname, "--save-dir", "./output", "--tmp-dir", "./", "-mt", #"--use-shaka-packager", ## producing error intermittently so revert to ffmpeg "--key", mykeys, #"-M", #"format=mkv:muxer=mkvmerge", ] subprocess.run(command) elif m3u8 in media_url: divides('Getting encryption free stream') command = [ "N_m3u8DL-RE", media_url, "--binary-merge", "--auto-select", "-sv", "best", "-sa", "id='audio-0'", "-ss", "id='subtitles-0':for=all", "--save-name", videoname, "--save-dir", "./output/", "--tmp-dir", "./", "-mt", #"--use-shaka-packager", #"-M", #"format=mkv:muxer=mkvmerge", ] subprocess.run(command) if os.path.isfile(f"./output/{videoname}.en.srt"): os.system(f"perl -i -pe 's/<.*?>//gm' ./output/{videoname}.en.srt") # attempt a '''with open(f'./output/{videoname}.en.srt', 'r') as original: data = original.read() with open(f'./output/{videoname}.srt', 'w') as modified: modified.write("WEBVTT\n\n" + data) original.close() modified.close()''' os.system(f"mkvmerge -q --no-date -o ./output/'{videoname}'.mkv \ -S -B -M --language 0:en --default-duration 0:25000/1000p \ --fix-bitstream-timing-information 0:1 ./output/'{videoname}'.mp4 \ --language 0:en ./output/'{videoname}'.en.m4a --language 0:en \ --track-name 0:English --default-track 0:0 --forced-track 0:0 \ ./output/'{videoname}'.en.srt") elif os.path.isfile(f"./output/{videoname}.mp4"): os.system(f"mkvmerge -q --no-date -o ./output/'{videoname}'.mkv \ -S -B -M --language 0:en --default-duration 0:25000/1000p \ --fix-bitstream-timing-information 0:1 ./output/'{videoname}'.mp4 \ --language 0:en ./output/'{videoname}'.en.m4a") # tptvencore may have ts files elif os.path.isfile(f"./output/{videoname}.ts"): print(videoname) os.system(f"mkvmerge -q --no-date -o ./output/'{videoname}'.mkv \ -S -B -M --language 0:en --default-duration 0:25000/1000p \ --fix-bitstream-timing-information 0:1 ./output/'{videoname}'.ts \ --language 0:en ./output/'{videoname}'.en.ts") os.system("rm -f ./output/*.m4a ./output/*.srt ./output/*.mp4 ./output/*.ts") #os.system("reset") # uncomment if N_m3u8DL-RE leaves text unprinted in terminal exit(0)
An ITVX batch downloader with no CDM required.
Code:#!/usr/bin/env python3 ''' ITVXbatch.py - a batch downloader for ITVX coded by A_n_g_e_l_a v 2.0 09:06:2023 Requirements: N_m3u8DL-RE, shaka-packager and mkvmerge in $PATH and python modules pyperclip and termcolor may be needed, install with 'pip install pyperclip termcolor' Written for Linux systems using WKS-KEYS with a working CDM, place in the WKS-KEYS folder. Should work on Windows with .exe sprinkled where necessary This program loads content from The Stream Detector to provide mpds and program name. The program reads the clipboard for 'Table Entries' saved from 'The Stream Detector'. Use 'The Stream Detector' browser plugin (Chrome + Firefox + Brave + Kiwi-Browser (Android)) Find here: Firefox version - https://addons.mozilla.org/en-US/firefox/addon/hls-stream-detector/ Chrome version: https://github.com/rowrawer/stream-detector/releases choose hls_stream_detector-2.1X.XX.crx and in chrome://extensions/ set Developer mode (top-right window) and drag and drop the crx file into the window to install Windows is ot officially supported and you may need to unpack the crx file with winrar and the drage the unpacked folder to the extensions window whilst switched to developer mode. First clear all old urls from The Stream Detector (TSD) And then set 'Copy stream URL as' to 'Table Entry' in The Stream Detector GUI Now just visit any number of video pages at ITV.com and the mpd urls will shown in TSD GUI. Copy the Table Entry or Entries to the clipboard from 'The Stream Detector' window when finished selecting videos. Then run this program ''' import os import base64 import requests #from pywidevine.L3.cdm import deviceconfig #from base64 import b64encode #from pywidevine.L3.decrypt.wvdecryptcustom import WvDecrypt import subprocess import re from termcolor import colored import pyperclip as PC import time import functools import math import pyfiglet as PF headers = { 'User-Agent': 'Dalvik/2.9.8 (Linux; U; Android 9.9.2; ALE-L94 Build/NJHGGF)', 'Accept': '*/*', 'Accept-Language': 'en-GB,en;q=0.9', 'Connection': 'keep-alive', 'Referer': 'itv.com', } ''' def WV_Function(pssh, lic_url, cert_b64=None): wvdecrypt = WvDecrypt(init_data_b64=pssh, cert_data_b64=cert_b64, device=deviceconfig.device_android_generic) widevine_license = requests.post(url=lic_url, data=wvdecrypt.get_challenge(), headers=None) license_b64 = b64encode(widevine_license.content) wvdecrypt.update_license(license_b64) Correct, keyswvdecrypt = wvdecrypt.start_process() # chain any multiple keys with --key at the start of all but the first kid:key pair if Correct: mykeys = '' for key in keyswvdecrypt: mykeys += key + '--key' if mykeys.endswith('--key'): mykeys = mykeys[:-5] divides("key found") print(mykeys) return mykeys ''' def WV_Function(pssh, lic_url, cert_b64=None): lic_url = lic_url headers_cdrm = { 'Connection': 'keep-alive', 'Content-Type': 'application/json', 'Origin': 'https://cdrm-project.com', 'Referer': 'https://cdrm-project.com/', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36', } json_data = { 'license': lic_url, 'headers': 'Connection: keep-alive', 'pssh': pssh, 'buildInfo': '', 'proxy': '', 'cache': True, } resp = requests.post('https://cdrm-project.com/wv', headers=headers_cdrm, json=json_data).text keys = re.findall("<li style=\"font-family:'Courier'\">(.+?)<\/li>", resp) if keys: for match in keys: mykeys = '' mykeys += match + '--key' if mykeys.endswith('--key'): mykeys = mykeys[:-5] divides('Key(s)') print(mykeys) return mykeys ## pretty print screen divisions def divides(text): #text = text l = len(text) count, lines = os.get_terminal_size() count = int(count) if count <= 78: count = int(count) else: count = int(math.ceil(count/2)) count = count - l if text != 'null': line = (chr(9601) * int(math.ceil(count/2))) #print("\n") print(colored(line, 'green'), " ", text, " ", colored(line, 'green')) else: line = (chr(9601) * int(count + 10)) print(colored(line, 'green')) def findlicense(mpd_url): bit = mpd_url.split('/',8) ContentID = bit[7].rsplit('_',2 ) license = "https://itvpnp.live.ott.irdeto.com/Widevine/getlicense?CrmId=itvpnp&AccountId=itvpnp&ContentId=" + ContentID[0] divides('license') print(license) return license def generate_pssh(kid: str): str1 = '000000387073736800000000edef8ba979d64acea3c827dcd51d21ed000000181210' str3 = '48e3dc959b06' return base64.b64encode(bytes.fromhex(str1+kid+str3)).decode() # add leading zero to series or episode def pad_number(match): number = int(match.group(1)) return format(number, "02d") def getnm3u8(mpd_url, pssh, videoname): # decrypt lic_url = findlicense(mpd_url) key = WV_Function(pssh, lic_url) # organize cookie cookie = mpd_url.split("&") cookie = cookie[1].split('exp%3D') cookie = 'Cookie: hdntl=exp=' + cookie[1].replace('%3D', '=').replace('%2A', '*').replace('nohubplus', 'hdntl,nohubplus') #print (cookie) divides('Downloading') command = [ "N_m3u8DL-RE", mpd_url, '--append-url-params', '--header', cookie, '--header', "host': itvpnpdotcom.blue.content.itv.com", '--header', "User-Agent': Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", "--auto-select", "--save-name", videoname, "--save-dir", "./output", "--tmp-dir", "./", "-mt", "--use-shaka-packager", "--key", key, "-M", "format=mkv:muxer=mkvmerge", ] #print(f"\n\ncommand for N_m3u8DL-re is: {command}\n\n") subprocess.run(command) def timer(func): """Print the runtime of the decorated function""" @functools.wraps(func) def wrapper_timer(*args, **kwargs): start_time = time.perf_counter() # 1 value = func(*args, **kwargs) end_time = time.perf_counter() # 2 run_time = end_time - start_time # 3 #print(f"Finished {func.__name__!r} in {run_time:.4f} secs") print(f"Program execution took {run_time:.2f} secs") return value return wrapper_timer @timer def main(): fmpd = PC.paste().split('\n') mylen = len(fmpd) print(f"There are {mylen} videos to download") divides('ITVX') for line in fmpd: linetuple = line.split('|') mpd_url = linetuple[0].replace('\n','').replace(' ', '') print(f"[info] downloading {mpd_url}") # A_Touch_of_Frost__S_1__E0_1 videoname = linetuple[1].replace('\n','').replace('ITVX','').replace(':','_')\ .replace('-','').replace(' Series ','_S').replace(' Episode ','E').replace(' ','_').lstrip('_').rstrip('_') # two digits for series and episode videoname = re.sub(r"(\d+)", pad_number, videoname) divides('downloading ' + videoname) # parse for KID to get PSSH mpd = requests.get(mpd_url,headers).text mylines = mpd.split("\n") for myline in mylines: m = re.search('cenc:default_KID=\"(.+?)\">', myline) if m: KID = m.group(1) KID = KID.replace('-', '') if KID != '': divides(f'Default_KID:') print(KID) pssh = generate_pssh(KID) divides('pssh found') print(pssh) else: print('No KID was found; exiting!') exit(0) if not os.path.exists("./output/"): os.system("mkdir ./output/") # download getnm3u8(mpd_url, pssh, videoname) divides("All Done.") print('[info] Your files are in ./output/\n') if __name__ == "__main__": divides("null") print() title = PF.figlet_format(' ITVX Downloader ', font='smslant') print(colored(title, 'green')) divides("An ITVX Batch Downloader") print("\nCopy stream URL(s) as 'Table Entry' from The Stream Detector") print("When ready to copy from the clipboard....") input("Press Enter!") main() exit(0)
Last edited by A_n_g_e_l_a; 9th Jun 2023 at 09:56. Reason: Updated
-
thanks for showing the other examples, it's never easy to write an universal script for different sites
i'll leave a version of this,
which does not require python
if the site for extra data in the headers, here use customdata,
but you could also use x-dt-auth-token... etc
[Attachment 71606 - Click to enlarge]
windows batch: (.bat file)
Code:@echo off setlocal enabledelayedexpansion 2>nul del input.html 2>nul del keys.txt set /p "lic_url=lic url: " set /p "pssh=pssh: " set /p "custom_data=custom data token: " curl.exe -s "https://cdrm-project.com/wv" ^ -H "Connection: keep-alive" ^ -H "Origin: https://cdrm-project.com" ^ -H "Referer: https://cdrm-project.com/" ^ -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" ^ --data-raw "{\"license\":\"%lic_url%\",\"headers\":\"Connection: keep-alive\",\"customdata\":\"%custom_data%\",\"pssh\":\"%pssh%\",\"buildInfo\":\"\",\"proxy\":\"\",\"cache\":true}" > input.html set /p "filename=file name: " set /p "mpd=mpd url: " echo. for /f "tokens=3 delims=<>" %%a in ('findstr /r "<li.*>.*</li>" input.html') do ( set line=--key %%a echo|set /p=!line! >> keys.txt ) set /p keys=<keys.txt echo. echo %keys% echo. N_m3u8DL-RE.exe %keys% --auto-select --save-name "%filename%" -M "format=mp4:muxer=ffmpeg" "%mpd%" 2>nul del input.html 2>nul del keys.txt
-
Nice work! I wrote a general downloader based on your findings. It should work on most basic sites where there's no payload. Things like JWT tokens and "?specConform=true" are handled automatically. If the api ever stops working(which I suspect it will), it's easy to swap for a local CDM.
Uses the uncurl module to get necessary data.
Prints keys by default and downloads with --download --file filename arguments. You can add --proxy to bypass geofences.
cdl.py:
Code:""" Credit to @sk8ordi3 & @A_n_g_e_l_a for providing api url(s) Requirements: Python 3.7+ N_m3u8DL-RE ffmpeg or mkvmerge mp4decrypt or shaka-packager Install necessary packages: pip install pyperclip uncurl beautifulsoup4 Download: cdl.py --download --file filename Proxy: cdl.py --proxy 123.456.789.10:8080 Notes: Written on Linux but should work on all systems. Downloads are found in /downloads folder. N_m3u8DL-RE options can be adjusted in the download_stream function (line: 103) !Windows users! Do not use "copy as cURL(windows)" when curling the license. """ import argparse import sys import requests import re import base64 import pyperclip import uncurl import subprocess from pathlib import Path from bs4 import BeautifulSoup as bs def cURL() -> tuple: paste = pyperclip.paste().replace(" \\\n", "").replace("^", "") paste2 = re.sub(r"--data-raw.*", "", paste) context = uncurl.parse_context(paste2) headers = dict(context.headers) lic_url = context.url return headers, lic_url def get_kid(mpd_url: str) -> str: response = requests.get(mpd_url) if response.status_code != 200: raise ValueError(f"Failed to fetch manifest") match = re.search(r'default_KID="(.+?)"', response.content.decode('utf-8')) return match.group(1) if match else None def generate_pssh(kid: str) -> str: array_of_bytes = bytearray(b'\x00\x00\x002pssh\x00\x00\x00\x00') array_of_bytes.extend(bytes.fromhex("edef8ba979d64acea3c827dcd51d21ed")) array_of_bytes.extend(b'\x00\x00\x00\x12\x12\x10') array_of_bytes.extend(bytes.fromhex(kid.replace("-", ""))) return base64.b64encode(bytes.fromhex(array_of_bytes.hex())).decode('utf-8') def get_pssh(mpd_url: str) -> str: try: kid = get_kid(mpd_url) pssh = generate_pssh(kid) except: print("\nPSSH cannot be generated from manifest. Please input manually") pssh = input("PSSH: ") return pssh def get_tokens(headers: dict) -> list: try: token_pairs = [ (key, value) for key, value in headers.items() if isinstance(value, str) and (value.startswith("ey") or value.startswith("Bearer")) ] tokens = [token for pair in token_pairs for token in pair] return tokens except: return None # Use this to make adjustments to license, headers, cookies, proxy etc. def addons(lic_url: str) -> str: if "drmtoday" in lic_url and lic_url.endswith("/cenc/"): lic_url = f"{lic_url}?specConform=true" return lic_url def parse_response(response: str) -> tuple: soup = bs(response, "html.parser") h2_tag = soup.find("h2") result = h2_tag.text if h2_tag else None error = h2_tag.text if result == "ERROR" else None p_tag = soup.find("p") if error else None error_message = p_tag.text.split("\n")[1] if error else None ol_tag = soup.find("ol") keys = ol_tag.text.strip() if ol_tag else None return result, keys, error_message def download_stream(mpd_url: str, file: str, keys: str): try: args = [ 'N_m3u8DL-RE', '--key', keys, mpd_url, '--auto-select', '-mt', '-M', 'format=mp4:muxer=ffmpeg', '--save-name', file, '--save-dir', 'downloads' ] subprocess.run(args, check=True) except: print('Failed downloading stream!') raise def create_argument_parser(): parser = argparse.ArgumentParser(description="General downloader") parser.add_argument( "--download", help="Download the stream", action="store_true" ) parser.add_argument( "--file", help="Filename of video" ) parser.add_argument( "--proxy", help="Use a proxy" ) args = parser.parse_args() return args def main(): parser = create_argument_parser() download = parser.download file = parser.file proxy = parser.proxy if parser.proxy else "" api_url = "https://cdrm-project.com/wv" # pssh = input("\nPSSH: ") mpd_url = input("\nMPD_URL: ") curl = "cURL: Copy the license as cURL and press Enter to continue... " input(curl) headers, lic_url = cURL() lic_url = addons(lic_url) pssh = get_pssh(mpd_url) tokens = get_tokens(headers) if tokens: key, value = tokens headers = f'{key}: "{value}"' else: headers = "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 \ (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" api_headers = { 'Connection': 'keep-alive', 'Content-Type': 'application/json', 'Origin': 'https://cdrm-project.com', 'Referer': 'https://cdrm-project.com/', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 \ (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36', } myjson = { "license": lic_url, "headers": headers, "pssh": pssh, "buildInfo": "", "proxy": proxy, "cache": True } r = requests.post(api_url, headers=api_headers, json=myjson).text result, keys, error_message = parse_response(r) print(f"\n{result}") if result == "ERROR": print(f"\n{error_message}") sys.exit(1) else: print(f"\n--key {keys}\n") if download: Path("downloads").mkdir(parents=True, exist_ok=True) if file.endswith(".mkv") or file.endswith(".mp4"): file = file[:-4] download_stream(mpd_url, file, keys) if __name__ == "__main__": main()
-
-
I don't plan on taking down the API (https://cdrm-project.com/api) - it's just not very versatile which is why I launched a different api (https://api.cdrm-project.com) that uses rlapheonixs pywidevine serve.py to serve the new API, as it has more versatility.
I might update the instructions again to include both - but fear not they will both stay up - everything public facing on my end now serves emulator CDMs, so I'm not too worried.
Glad to see the site being circulated more around here.
Happy decrypting all.
- PS. Love you long time a_n_g_e_l_a
-
Appreciate the response, @TPD94. It's nice to know you plan on keeping them up. I can't imagine these scripts will result in any kind of abuse.
-
In that case I understand you need to repair your Bot! The Bot database is dumped to https://big.thefileditch.ch/ but that site has been down for nearly two weeks. anonfiles.com allows up to 20Gb these days.
-
For Stabbedbybrick's script, when I download "N_m3u8DL-RE" from Github, my Antivirus says it's a file with a virus. I can't trust such files.
Is there any other thing to use instead of "N_m3u8DL-RE"? -
-
People in glass houses shouldn't throw stones. Your script contains an unused import - 'import platform' ; people "should take a moment to clean them up first before re-posting them".
And I'm always suspicious of using classes for encapsulation when encapsulation is not needed - as is the case here. All those 'self ' declarations cluttering defs would disappear .
Secondly, count the key presses you need to download your three example files and get your program set-up to run by first creating, populating and saving a text file. Compare all that to the ONE key press to select a LIST of mpd from The Stream Detector and ONE key press to set the program running using https://forum.videohelp.com/threads/409937-No-CDM-No-Problem!#post2693032 (scroll to "An ITVX batch downloader with no CDM required."
Interesting approach (thanks for posting) but much too hard work for me to use.
Edit: I just added a function timer and ran both your script and mine to download a single program
Yours took 18.60 seconds to complete
Mine took 17.34 seconds.
I think the difference is down to all the lookups for data parsing of the html that goes on in your script.
/edit
Horses for courses.
times shown.
Mine
[Attachment 71785 - Click to enlarge]
Yours
[Attachment 71786 - Click to enlarge]Last edited by A_n_g_e_l_a; 18th Jun 2023 at 06:26.
-
Not work for me
[Attachment 71949 - Click to enlarge] -
Similar Threads
-
Widevine CDM
By KyoGo in forum Video Streaming DownloadingReplies: 8Last Post: 19th Jul 2023, 03:43 -
What After Getting Working CDM ?!
By Anonymous9875434 in forum Video Streaming DownloadingReplies: 2Last Post: 8th Jun 2023, 03:16 -
Chrome CDM
By ronron555 in forum Video Streaming DownloadingReplies: 10Last Post: 3rd Dec 2022, 11:26 -
pywidevine + L3 Problem with new CDM Server [build date 5 AUG 2022]
By Balooshy in forum Video Streaming DownloadingReplies: 7Last Post: 25th Aug 2022, 07:55 -
new cdm
By ThunderRanger in forum Video Streaming DownloadingReplies: 0Last Post: 6th May 2020, 03:16