This tutorial is intended to be a step by step guide for those with enough hobbies and interests as it is without having to learn about coding. Here's some background to begin with:
CDM stands for 'Content Decryption Module' which is used for decrypting Widevine protected content. Android devices have one (AndroidCDM) and that's what we'll be using. There are different security levels of encryption, L3 and L1 with L3 being the least secure. Generally speaking, L3 is used for lower quality content and L1 for higher quality.
AndroidCDMs can be 'dumped' from the device but only the L3 type (as I understand it and at the time of writing.) Apparently, L1 is locked away so securely that only the geek Gods can dump them and they're not sharing their methods.
Once you have a CDM you can spoof licence requests and get the decryption keys. In short, media is encrypted using a key which is then locked on a server. The CDM is like a key allowing you to unlock the decryption key from the server. This decryption key is then used to decrypt the media.
Let's start. I write this as a user of Windows 64bit and Firefox although Edge and Chrome are reckoned by some to be better when it comes to using the F12 key (see later).
STEP ONE: Get WKS-KEYS from https://github.com/weapon121/WKS-KEY/releases/tag/WKS-KEY
You want WKS-KEYS.rar listed under Assets. Once downloaded and unrarred, leave the folder WKS-KEYS on your preferred drive. You may delete headers.py, l1.py and l3.py that are in the main folder. All the script in these files has been replaced by a script to be added later.
Go to Control Panel/ Programs and Features and ensure you have Microsoft MS Visual C++ 2015-2019 Redistributable (or latest). With a 64-bit system you need to download both x86 and x64 (but not the ARM64): https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170. Once installed, it's best to reboot the PC.
STEP TWO:
Create a folder and name it something like Tvtools. (If you prefer to keep everything together you could put your Tvtools folder inside WKS-KEYS). Into TVtools put the latest versions of:
yt-dlp: https://github.com/yt-dlp/yt-dlp
mp4decrypt (it's in the bin folder): https://www.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-639.x86_64-microsoft-win32.zip
ffmpeg/ffprobe: https://www.gyan.dev/ffmpeg/builds/ (the ffmpeg-git-essentials.7z one). The files you need (ffmpeg.exe, ffprobe.exe) are in the bin folder
aria2c: https://github.com/aria2/aria2/releases/tag/release-1.36.0
Shaka-packager (packager-win-x64.exe): https://github.com/shaka-project/shaka-packager/releases
N_m3u8DL-RE.exe: https://github.com/nilaoda/N_m3u8DL-RE/releases
mkvmerge.exe: https://mkvtoolnix.download/downloads.html
For absolute beginners: any zip file you download can include lots of things you don't need. I use 7zip. Rightclick the zipped/rarred file and select 7zip/OpenArchive. Then, as with Windows Explorer, navigate to the file(s) you need and highlight it with a click and then click the Extract button.
Add the folder Tvtools (or whatever you named it) to “environmental variables” as per: https://www.architectryan.com/2018/03/17/add-to-the-path-on-windows-10/
Then, not only does Windows automatically know where they are and will auto-execute them, but when new updates come along (which is often with yt-dlp and ffmpeg), you only have to update them in the one place. Before you add the folder Tvtools to environmental variables, decide where you want to keep it. It's fine inside WKS-KEYS (keeping everything in one place) but others prefer to have one location for their Tvtools folder so that it can be automatically accessed from anywhere on their PC.
STEP THREE:
Install Python. A good guide: https://www.youtube.com/watch?v=bCY4D9n3Pew
Open a new Notepad document and copy/paste this:
xmltodict==0.12.0
requests==2.26.0
google==3.0.0
google-api-python-client==2.33.0
protobuf==3.19.1
pycryptodome==3.12.0
pycryptodomex==3.12.0
pyperclip
uncurl
Save it as 'requirements.txt' (no quotes) somewhere on your PC (inside Tvtools is fine). Where you've saved that, open up a Command Prompt (aka cmd box)* and type this command:
pip install -r requirements.txt
Python and the extra modules required is now set up.
*For absolute beginners: You could open the Command Prompt by clicking Start and putting cmd into the searchbox and, once opened, manually point it to the required location but here's the easy way: navigate to (for example) D:\WKS-KEYS\Tvtools. Left-click a white bit of the address bar. The address turns blue ready for overtyping. Type cmd followed by the Enter key. You now have a Command Prompt opened within the Tvtools folder. And don't worry, you haven't renamed the folder as cmd.
STEP FOUR - GET A VIRTUAL ANDROID PHONE.
You could dump the CDM from your own Android smartphone but that means having to 'root' the phone – a can of worms not worth opening. So instead:
Install Android Studio - https://developer.android.com/studio
Follow this excellent VideoHelp tutorial - https://forum.videohelp.com/threads/408031-Dumping-Your-own-L3-CDM-with-Android-Studio
For absolute beginners: Where it says “Let's play with the terminal.... Open a Command Prompt in Windows.” you can open the Command Prompt anywhere (as opposed to opening it inside a specific location). Click 'Start', type cmd in the searchbox and then open cmd.exe. Type 'pip install frida' (no quotes) followed by Enter and wait for the command to execute. Repeat for the 'pip install frida-tools' command.
Where it says “Download The Frida Server for Android. and put it on C:\Users\yourname\AppData\Local\Android\Sdk\platfo rm-tools” you may have to view hidden items. Just click 'View' at the top of Windows Explorer then tick the Hidden items box.
Where it says “put yourself in the right directory and type adb.exe devices” that means opening a Command Prompt inside C:\Users\yourname\AppData\Local\Android\Sdk\platfo rm-tools. See end of Step Three.
Where it says “We can see that our virtual device is recognized” make sure that the virtual phone you created is running.
Where it says “So let's communicate with our Virtual device” those five commands are to be done one at a time i.e. press Enter after each and wait for each command to be executed.
Where it says “Verify that the frida-server version you download is the same as that installed via pip” I found this bit confusing. Open cmd anywhere and type 'pip list' (no quotes). If pip installed 16.0.17 you need frida-server-16.0.17-android-x86.xz. Open up Assets on github page to get it.
Where it says “you just have to rename the 2 files respectively in : device_client_id_blob device_private_key”, this is the procedure:
Go to WKS-KEYS/pywidevine/L3/cdm/devices/android-generic/
The two files named 'device-client-id-blob' and 'device-private-key' are the cdm keys that came with WKS-KEYS but no longer work. Note that they don't have file extensions. As a temporary measure, rename them by putting, say, an X at the start i.e. 'Xdevice-client-id-blob'. Copy the file client_id.bin from dumper-main to WKS-KEYS/pywidevine/L3/cdm/devices/android-generic and rename it to device_client_id_blob (deleting the extension). Do the same with with private_key.pem which becomes device_private_key. You can then delete the two files that you added an X to. Refer to the screenshot in the Android Studio tutorial – you have to dig deep for the two new cdm files!
The keys that you have obtained are unique to you and are universal for all streaming sites that use L3 encryption but will not work for L1 encrypted channels such as Amazon and Netflix.
STEP FIVE – DOWNLOAD
Let's get a show from ITVX. Firstly, write yourself a Python script. Only kidding... Forum member deccavox has kindly posted one here:
https://forum.videohelp.com/threads/407635-Crash-course-on-downloading-ITVx-hub-please...e3#post2677566
He has since made improvements to this script so use this latest version:
Copy his script (everything in the white box, chatty bits and all) to Notepad and name it, say, itvx.py (Notepad documents are .txt by default so delete .txt and add .py). Place itvx.py into the WKS-KEYS folder. To run this script, open a cmd box from within WKS-KEYS and type:Code:####version (20230205-1) do checks for dupe filename and illegal characters ####version (20230129-1) replace aria2c with -N 6 ####version (20230108-1) ''' ITVX downloader inspired by A_n_g_e_l_a to whom I give a huge thank you to. All you need is the mpd. Apparently, using 'The Stream Detector' browser plugin means you can avoid logging in to the site. I just filter for mpd in Network within Developer tools. I believe I have coded it to make it universal (ie. Windows, Linux & Mac). I have only tested it on a Windows system though. Works from within WKS-KEYS folder using your own cdm, but you only need the pywidevine folder. So you can delete the headers.py, l1.py and l3.py files. Use your own cdm within the .\pywidevine\L3\cdm\devices\android_generic folder ''' import os import base64 import requests from pywidevine.L3.cdm import deviceconfig from base64 import b64encode from pywidevine.L3.decrypt.wvdecryptcustom import WvDecrypt import shutil import glob ##########useful when used within VS Code to tell it which directory you're in abspath = os.path.abspath(__file__) dname = os.path.dirname(abspath) os.chdir(dname) ########## headers = { 'Accept': '*/*', 'Accept-Language': 'en-GB,en;q=0.9', 'Connection': 'keep-alive', } def cls(): # posix is os name for Linux or mac if(os.name == 'posix'): os.system('clear') # clear is the bash command # else screen will be cleared for windows (os name is actually nt for Windows) else: os.system('cls') # cls is the batch command 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: return keyswvdecrypt def message(message): print("-" * (len(message)+6) + "\n" + " " + message + "\n" + "-" * (len(message)+6)) 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] print("\nlicence URL found is " + license) return license def generate_pssh(kid: str): str1 = '000000387073736800000000edef8ba979d64acea3c827dcd51d21ed000000181210' str3 = '48e3dc959b06' return base64.b64encode(bytes.fromhex(str1+kid+str3)).decode() def filename(): output_name = input("What do you want to call your final file? (do not include file .extension like .mp4) ") output_name = f"{output_name}.mp4" path = f"./Completed/{output_name}" # look to see if duplicate filename if os.path.isfile(path): print(f"\nyou already have a file called {output_name}. Choose another name\n") return filename() #look to see if any illegal characters in filename illegals = "@$%\/:*?\"<>|~`#^+=\{\};!" any_illegals = (set(output_name) & set(illegals)) if not any_illegals: return output_name else: list_illegals = ' '.join(any_illegals) print(f"\nyou have included the following which are not legal characters: {list_illegals}\n") return filename() if __name__ == "__main__": cls() message('ITVX') mpd_url = input("enter mpd: ") # get mpd file listings mpd_text = requests.get(mpd_url, headers = headers).text # find KID index_kid = mpd_text.find("cenc:default_KID=") for count, i in enumerate(mpd_text[index_kid: ]): if i == ">": kid = mpd_text[index_kid+18: index_kid+count-1] break kid = kid.replace('-', '') message("KID found is " + kid) #get PSSH pssh = generate_pssh(kid) message("PSSH found is " + pssh) #get lience URL lic_url = findlicense(mpd_url) #get KEY keys = WV_Function(pssh, lic_url) message("KID:KEY found is " + (keys[0])) keys = str(keys[0]) division = keys.find(":") key = (keys[division+1:]) print(f" KEY# is {key}") print("\n\n") os.system(f'yt-dlp --allow-u -f bestvideo -N 6 "{mpd_url}" -o encryptVid.mp4') os.system(f'yt-dlp --allow-u -f bestaudio -N 6 "{mpd_url}" -o encryptAud.m4a') print("==============================\n\n Decrypting\n\n==============================") os.system(f"mp4decrypt --show-progress --key 1:{key} encryptVid.mp4 video_decrypt.mp4") os.system(f"mp4decrypt --show-progress --key 1:{key} encryptAud.m4a audio_decrypt.m4a") if not os.path.exists("Throwaway"): os.makedirs("Throwaway") if not os.path.exists("Completed"): os.makedirs("Completed") cls() print("\n\n\n") output_name = filename() os.system(f"ffmpeg -i video_decrypt.mp4 -i audio_decrypt.m4a -c:v copy -c:a copy mux_file.mp4") os.rename("mux_file.mp4", f'{output_name}') shutil.move(f'{output_name}', "./Completed") for data in glob.glob("*_decrypt*.*"): shutil.move(data,"./Throwaway") for data in glob.glob("encrypt*.*"): shutil.move(data,"./Throwaway") shutil.rmtree("./Throwaway") print(f"All done.\n\n Your final file '{output_name}' is in 'Completed' folder")
py itvx.py (followed by Enter)
It asks for mpd. Go to itvx.com and login. Play your show and press F12 which brings up the rather daunting looking Developer Tools. There are various tabs and you should be on the Network one.
Hit your browser's refresh button. The video will stop playing so start it again. This is an important step and can be repeated if necessary. There's a bar in Developer Tools saying 'Filter URLs' into which type mpd. You should have a result that turns blue when you hover over it. This is your mpd and you have to copy it's URL – rightclick anywhere along it/Copy Value/Copy URL.
Return to your cmd box and enter the mpd. A single rightclick in the cmd box is sufficient to copy it. Now wait while the show is downloaded. You'll then be asked to provide a name for your file which then can be found in the 'Completed' folder of WKS-KEYS.
The script will download the highest quality possible. At the time of writing the best you can get from ITVX is 720p video, 96Kbps audio.
CHANNEL4 (All4):
deccavox has two scripts for Channel4, one which will download the best quality and one which lets you choose the quality (for when you just don't need 1080p). Here's the script for best quality only:
And the script allowing you to choose the quality:Code:#### All4 version (20230207-1) """make sure you've done: pip uncurl pip pyperclip""" import requests import xmltodict, base64 import json from pywidevine.L3.cdm import deviceconfig from base64 import b64encode from pywidevine.L3.decrypt.wvdecryptcustom import WvDecrypt from urllib.parse import urlparse import uncurl import pyperclip as PC import subprocess import os import shutil, glob ##########useful when used within VS Code to tell it which directory you're in abspath = os.path.abspath(__file__) dname = os.path.dirname(abspath) os.chdir(dname) ########## def cls(): # posix is os name for Linux or mac if(os.name == 'posix'): os.system('clear') # clear is the bash command # else screen will be cleared for windows (os name is actually nt for Windows) else: os.system('cls') # cls is the batch command def cURL_cmd(): wait = '''\n\n\n Filter for wide (or acquire), do `Copy as cURL(cmd)` to get it on your clipboard NOTE: NOT `cURL(bash)`\n then press Enter to continue....''' input(wait) mycode = PC.paste() mycode = mycode.replace("^", "") context = uncurl.parse_context(mycode) lic_url = context.url myjson = json.loads(context.data) requestid = myjson['request_id'] token = myjson['token'] mpd_url = (myjson['video']['url']) headers = dict(context.headers) #this is an OrderedDict return lic_url, requestid, mpd_url, headers, token def get_pssh(mpd_url, headers): pssh = '' kid="" r = requests.get(url=mpd_url,headers=headers) r.raise_for_status() xml = xmltodict.parse(r.text) mpd = json.loads(json.dumps(xml)) try: def find_str(s, char): index = 0 if char in s: c = char[0] for ch in s: if ch == c: if s[index:index+len(char)] == char: return index index += 1 return -1 teilifis =str(r.content) x=find_str(teilifis,"000000") stringy=str(r.content) y=x+36 kid= stringy[x:y] except: pssh = input('Unable to find PSSH in mpd. Edit getPSSH.py or enter PSSH manually: ') def get_pssh(keyId): 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( keyId.replace("-", ""))) return base64.b64encode(bytes.fromhex(array_of_bytes.hex())) kid = kid.replace('-', '') assert len(kid) == 32 and not isinstance(kid, bytes), "wrong KID length" pssh = get_pssh(kid) if pssh == '': pssh = input(f'\nUnable to find PSSH. \nEdit getPSSH.py or enter PSSH manually: ') return pssh return pssh 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) raw_request = wvdecrypt.get_challenge() request = b64encode(raw_request) #All4 support responses.append(requests.post(url=lic_url, headers=headers, params=params, json={ "request_id":requestid, "token":token, "video":{"type":"ondemand","url":mpd_url}, "message":str(request, "utf-8" ), })) for idx, response in enumerate(responses): if len(str(response.content, "utf-8")) > 500: widevine_license = response print(f'{chr(10)}license response status: {widevine_license}{chr(10)}') lic_field_names = ['license', 'payload', 'getWidevineLicenseResponse'] lic_field_names2 = ['license'] if str(widevine_license.content, 'utf-8').find(':'): for key in lic_field_names: try: license_b64 = json.loads(widevine_license.content.decode())[key] except: pass else: for key2 in lic_field_names2: try: license_b64 = json.loads(widevine_license.content.decode())[key][key2] except: pass wvdecrypt.update_license(license_b64) Correct, keyswvdecrypt = wvdecrypt.start_process() if Correct: return Correct, keyswvdecrypt def filename(): output_name = input("What do you want to call your final file? (do not include file .extension like .mp4) ") output_name = f"{output_name}.mp4" path = f"./Completed/{output_name}" # look to see if duplicate filename if os.path.isfile(path): print(f"\nyou already have a file called {output_name}. Choose another name\n") return filename() #look to see if any illegal characters in filename illegals = "@$%\/:*?\"<>|~`#^+=\{\};!" any_illegals = (set(output_name) & set(illegals)) if not any_illegals: return output_name else: list_illegals = ' '.join(any_illegals) print(f"\nyou have included the following which are not legal characters: {list_illegals}\n") return filename() #--------------------------------------------------------------------------------------------------------------- cls() lic_url, requestid, mpd_url, headers, token = cURL_cmd() print(f"\n\nmpd_url is: {mpd_url}") print(f"\nlicence is: {lic_url}") print(f"\nrequest_id is: {requestid}") print(f"\ntoken is: {token}") responses = [] license_b64 = '' pssh = get_pssh(mpd_url, headers) print(f"\nPSSH obtained is: {pssh.decode('utf-8')}") params = None params = urlparse(lic_url).query correct, keys = WV_Function(pssh, lic_url) # for key in keys: # print('KID:KEY -> ' + key) keys = str(keys[0]) division = keys.find(":") key = (keys[division+1:]) print(f"\n KEY# is {key}") print("\n\n") ##### Determine audio reference com1 = 'yt-dlp --allow-u -F "{0}"' com = com1.format(mpd_url) p1 = subprocess.run(com,shell=True, capture_output=True, text=True) spec_list = p1.stdout print(spec_list) ##### find the correct audio index_audio = spec_list.find("audio=") full_audio = spec_list[index_audio:index_audio+12] print(f"audio ID chosen is {full_audio}") os.system(f'yt-dlp --allow-u -f bestvideo -N 6 "{mpd_url}" -o encryptVid.mp4') os.system(f'yt-dlp --allow-u -f {full_audio} -N 6 "{mpd_url}" -o encryptAud.m4a') print("==============================\n\n Decrypting\n\n==============================") os.system(f"mp4decrypt --show-progress --key 1:{key} encryptVid.mp4 video_decrypt.mp4") os.system(f"mp4decrypt --show-progress --key 1:{key} encryptAud.m4a audio_decrypt.m4a") if not os.path.exists("Throwaway"): os.makedirs("Throwaway") if not os.path.exists("Completed"): os.makedirs("Completed") cls() print("\n\n\n") output_name = filename() os.system(f"ffmpeg -i video_decrypt.mp4 -i audio_decrypt.m4a -c:v copy -c:a copy mux_file.mp4") os.rename("mux_file.mp4", f'{output_name}') shutil.move(f'{output_name}', "./Completed") for data in glob.glob("*_decrypt*.*"): shutil.move(data,"./Throwaway") for data in glob.glob("encrypt*.*"): shutil.move(data,"./Throwaway") shutil.rmtree("./Throwaway") print(f"All done.\n\n Your final file '{output_name}' is in 'Completed' folder")
Reminder: Copy the script (everything in the white box, chatty bits and all) to Notepad and name it, say, c4.py or c4pick.py. Place into the WKS-KEYS folder and run the script via a cmd box from within WKS-KEYS by typing:Code:#### All4 select streams version (20230208-1) """make sure you've done: pip install uncurl pip install pyperclip""" import requests import xmltodict, base64 import json from pywidevine.L3.cdm import deviceconfig from base64 import b64encode from pywidevine.L3.decrypt.wvdecryptcustom import WvDecrypt from urllib.parse import urlparse import uncurl import pyperclip as PC import subprocess import os import shutil, glob ##########useful when used within VS Code to tell it which directory you're in abspath = os.path.abspath(__file__) dname = os.path.dirname(abspath) os.chdir(dname) ########## def cls(): # posix is os name for Linux or mac if(os.name == 'posix'): os.system('clear') # clear is the bash command # else screen will be cleared for windows (os name is actually nt for Windows) else: os.system('cls') # cls is the batch command def cURL_cmd(): wait = '''\n\n\n Filter for wide (or acquire), do `Copy as cURL(cmd)` to get it on your clipboard NOTE: NOT `cURL(bash)`\n then press Enter to continue....''' input(wait) mycode = PC.paste() mycode = mycode.replace("^", "") context = uncurl.parse_context(mycode) lic_url = context.url myjson = json.loads(context.data) requestid = myjson['request_id'] token = myjson['token'] mpd_url = (myjson['video']['url']) headers = dict(context.headers) #this is an OrderedDict return lic_url, requestid, mpd_url, headers, token def get_pssh(mpd_url, headers): pssh = '' kid="" r = requests.get(url=mpd_url,headers=headers) r.raise_for_status() xml = xmltodict.parse(r.text) mpd = json.loads(json.dumps(xml)) try: def find_str(s, char): index = 0 if char in s: c = char[0] for ch in s: if ch == c: if s[index:index+len(char)] == char: return index index += 1 return -1 teilifis =str(r.content) x=find_str(teilifis,"000000") stringy=str(r.content) y=x+36 kid= stringy[x:y] except: pssh = input('Unable to find PSSH in mpd. Edit getPSSH.py or enter PSSH manually: ') def get_pssh(keyId): 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( keyId.replace("-", ""))) return base64.b64encode(bytes.fromhex(array_of_bytes.hex())) kid = kid.replace('-', '') assert len(kid) == 32 and not isinstance(kid, bytes), "wrong KID length" pssh = get_pssh(kid) if pssh == '': pssh = input(f'\nUnable to find PSSH. \nEdit getPSSH.py or enter PSSH manually: ') return pssh return pssh 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) raw_request = wvdecrypt.get_challenge() request = b64encode(raw_request) #All4 support responses.append(requests.post(url=lic_url, headers=headers, params=params, json={ "request_id":requestid, "token":token, "video":{"type":"ondemand","url":mpd_url}, "message":str(request, "utf-8" ), })) for idx, response in enumerate(responses): if len(str(response.content, "utf-8")) > 500: widevine_license = response print(f'{chr(10)}license response status: {widevine_license}{chr(10)}') lic_field_names = ['license', 'payload', 'getWidevineLicenseResponse'] lic_field_names2 = ['license'] if str(widevine_license.content, 'utf-8').find(':'): for key in lic_field_names: try: license_b64 = json.loads(widevine_license.content.decode())[key] except: pass else: for key2 in lic_field_names2: try: license_b64 = json.loads(widevine_license.content.decode())[key][key2] except: pass wvdecrypt.update_license(license_b64) Correct, keyswvdecrypt = wvdecrypt.start_process() if Correct: return Correct, keyswvdecrypt def filename(): output_name = input("What do you want to call your final file? (do not include file .extension like .mp4) ") output_name = f"{output_name}.mp4" path = f"./Completed/{output_name}" # look to see if duplicate filename if os.path.isfile(path): print(f"\nyou already have a file called {output_name}. Choose another name\n") return filename() #look to see if any illegal characters in filename illegals = "@$%\/:*?\"<>|~`#^+=\{\};!" any_illegals = (set(output_name) & set(illegals)) if not any_illegals: return output_name else: list_illegals = ' '.join(any_illegals) print(f"\nyou have included the following which are not legal characters: {list_illegals}\n") return filename() #--------------------------------------------------------------------------------------------------------------- cls() lic_url, requestid, mpd_url, headers, token = cURL_cmd() print(f"\n\nmpd_url is: {mpd_url}") print(f"\nlicence is: {lic_url}") print(f"\nrequest_id is: {requestid}") print(f"\ntoken is: {token}") responses = [] license_b64 = '' pssh = get_pssh(mpd_url, headers) print(f"\nPSSH obtained is: {pssh.decode('utf-8')}") params = None params = urlparse(lic_url).query correct, keys = WV_Function(pssh, lic_url) # for key in keys: # print('KID:KEY -> ' + key) keys = str(keys[0]) division = keys.find(":") key = (keys[division+1:]) print(f"\n KEY# is {key}") print("\n\n") ##### Determine audio reference com1 = 'yt-dlp --allow-u -F "{0}"' com = com1.format(mpd_url) p1 = subprocess.run(com,shell=True, capture_output=True, text=True) spec_list = p1.stdout print(spec_list) ##### find the correct audio index_audio = spec_list.find("audio=") full_audio = spec_list[index_audio:index_audio+12] print(f"audio ID chosen is {full_audio}") ##### Choose video stream specs = list(spec_list.split("\n")) specs = specs[5:-1] print("-" * 120) print("\nhere's the available video streams, please make your selection using the leading number:") index = [] stream_end = [] for idx, stream in enumerate(specs): if stream.startswith("video"): stream = f"{idx:02d} {stream}" index.append(idx) stream_end.append(stream) print(stream) else: continue print("\n") index = set(index) index1 = {str(x) for x in index} while True: try: selection = str(int(input("choose the appropriate number that you want "))) # the int gets rid of any leading zeros, then str reconverts it back to a string except: print("you have made a none-numerical entry. Please try again") else: selection1 = [] selection1.append(selection) if (index1 & set(selection1)): break elif print("you gave a value that is not in the list. Please try again"): selection = str(int(input("choose the appropriate number that you want "))) for i in stream_end: split_it = i.split(" ") if int(selection) == int(split_it[0]): full_video = split_it[1] print(f" you have chosen the {full_video} stream") break os.system(f'yt-dlp --allow-u -f {full_video} -N 6 "{mpd_url}" -o encryptVid.mp4') os.system(f'yt-dlp --allow-u -f {full_audio} -N 6 "{mpd_url}" -o encryptAud.m4a') print("==============================\n\n Decrypting\n\n==============================") os.system(f"mp4decrypt --show-progress --key 1:{key} encryptVid.mp4 video_decrypt.mp4") os.system(f"mp4decrypt --show-progress --key 1:{key} encryptAud.m4a audio_decrypt.m4a") if not os.path.exists("Throwaway"): os.makedirs("Throwaway") if not os.path.exists("Completed"): os.makedirs("Completed") cls() print("\n\n\n") output_name = filename() os.system(f"ffmpeg -i video_decrypt.mp4 -i audio_decrypt.m4a -c:v copy -c:a copy mux_file.mp4") os.rename("mux_file.mp4", f'{output_name}') shutil.move(f'{output_name}', "./Completed") for data in glob.glob("*_decrypt*.*"): shutil.move(data,"./Throwaway") for data in glob.glob("encrypt*.*"): shutil.move(data,"./Throwaway") shutil.rmtree("./Throwaway") print(f"All done.\n\n Your final file '{output_name}' is in 'Completed' folder")
py c4.py (followed by Enter)
These Channel4 scripts differ from the ITVX one in that you are asked to “Filter for wide (or acquire)”. With Firefox, go to the Channel4 show you want but don't play it. Press F12 and in the filter box type acquire (or simply acq). Now start the video. Two or three 'acquires' will appear and you want one that says json under the Type tab. Rightclick/Copy Value/Copy as cURL(POSIX). Now return the cmd box and DO NOT RIGHTCLICK. To copy the acquire simply press the Enter key like the cmd box tells you to.
For Edge and Chrome it's slightly different: instead of Rightclick/Copy Value/Copy as cURL(POSIX) it's Rightclick/Copy/Copy as cURL(cmd). Deccavox says that the acquire to copy will be associated with 'Method POST' but on my portable Chrome I don't see that, they're just listed under Name. I just pick the one with the biggest size (the same rationale my wife used when looking for a husband...)
Hopefully, all of that was comprehensive and straightforward. A massive thanks is due to deccavox for the scripts, github addresses and guidance - what a lad.
+ Reply to Thread
Results 1 to 8 of 8
-
-
Great guide, thanks a lot - I wish I had it a week earlier though, before I had to zig-zag between multiple threads to work it out!
If I could add one thing though: stay clear of v0.1.6 of N_m3u8DL-RE, because it's bugged w.r.t. multiline subtitles (only the first line is outputted). v0.1.53 works.Last edited by PhilipG; 13th May 2023 at 03:29.
-
Angela did these before. Look at the stickies.
No need to reinvent the wheel. -
Well, PhilipG and hopefully others appreciated it.
A few weeks ago the OP pooksahib had little knowledge of these procedures and required a far amount of guidance which I was glad to give in private. And credit where credit's due, he did suss out most of the "harder" parts on his own. His post here reflects his experience and gives an alternative explanation of Angela's excellent posts, hopefully giving a more "dummy's guide" to some of the procedures written with a view to explaining some of the points he struggled with at first.
And he's done it rather well.
Regarding the Python scripts, although the ITVX was indeed very much based on Angela's script (a big thank you to Angela), I heavily modified it to suit my own purposes. The All4 script was all mine, apart from the widevine decryption bit which was based on the original WKS-KEYS script for that. So, "Angela did these before". No.
Then you go and trash his post. Shame on you.
Once again, another sarcastic post.Last edited by deccavox; 13th May 2023 at 09:56.
-
A great guide that includes some of the newer tools and methods in use now:
- N_m3u8DL-RE and shaka-packager
- Android Studio for CDMs (thanks to Magicians: https://forum.videohelp.com/threads/404994-Decryption-and-the-Temple-of-Doom/page7#post2670982, and cedric852: https://forum.videohelp.com/threads/408031-Dumping-Your-own-L3-CDM-with-Android-Studio).
- Scripts from deccavox
They sound complicated but they are very simple to use.
Why? Because, if people want to start another python project with conflicting requirements, e.g. using pywidevine (https://github.com/rlaphoenix/pywidevine) to get 5000kbps All4 streams with Diazole's script: https://forum.videohelp.com/threads/407024-Channel-4-downloader#post2667823), then they can have separate python virtual environments for WKS-KEYS and pywidevine without any conflicts.
Btw, having a virtual environment for pywidevine is useful for other sites too, try getting keys from 'the CW' with WKS-KEYS, you'll see what I mean.Last edited by bamboobali; 14th May 2023 at 02:42. Reason: Edited: How could I forget to mention deccavox!
-
Rather than tearing each other apart let us remember and celebrate how far we've come as a community.
And we are a community; the knowledge here is staggering in its breadth; people wish to help and I continue to learn from their helpful posts.
The process of writing an explanation for others helps me make clear, in my own mind, the things I write about. How could I deny anyone else that experience? So if someone wishes to build on my attempts at clarifying the decryption process I say, "go for it".
I've posted a number of complete scripts and will often take them down after a short while. I do that because I find scripting to be a fun, puzzling activity - a bit like reading a who-done-it - so having a finished script providing a ready answer to the puzzle is perhaps not what everyone wants. I also continue to learn and adapt and update my scripts accordingly.
What we all strive for is a simple download script using just the page URL, or a paste from The_Stream_Detector, to get the pssh, license, keys so as to download and decrypt a video without further input. The scripts here a bit of a step backwards in that respect, but if seen as a step on a learning journey, for the writer they are valid nevertheless.
I would like it better if people waited to post scripts when they are actually an improvement on what is already in the public domain.
Channel 4 provided above has better alternatives, as someone has already said. The one I use starts just with a url.
[Attachment 71001 - Click to enlarge] Single URL no further input
The ITVX script posted here works as a batch downloader without intervention and is faster than any yt-dlp script.
Copying is not an issue for me so long as people acknowledge sources. Copying my scripts, making a few changes to the decoration and furniture arrangement as seen through Windows, and then claiming a major update as their own is a bit galling. But people are strange, perhaps they really believe their own hype - who knows? And who among us hasn't copied code fragment from stackoverflow.com and called it our own? So I am not going to get annoyed with anyone; after all imitation is the sincerest form of flattery. -
Similar Threads
-
AVStoDVD beginners guide - Any video to DVD-Video
By Baldrick in forum User guidesReplies: 358Last Post: 22nd Dec 2024, 18:57 -
Downloading videos from sites that require login
By aruwin in forum Video Streaming DownloadingReplies: 10Last Post: 7th Jan 2021, 00:44 -
Downloading WebVTT/VTT Subtitles from M3u8 web sites?
By vega69-ux in forum Video Streaming DownloadingReplies: 6Last Post: 29th Sep 2020, 12:45 -
Downloading videos from streaming sites (recent change in coding issue)
By Anonymous45 in forum Video Streaming DownloadingReplies: 3Last Post: 5th Aug 2019, 12:29 -
How to download from streaming sites like this?
By jooj in forum Video Streaming DownloadingReplies: 2Last Post: 8th Mar 2019, 04:19