Decryption: The last Crusade
This post is a follow on to 'Decryption: The Temple of Doom'
And 'Decryption: The Dungeon of Despair' and to a lesser extent Downloading-and-decryption-on-your-Android-phone
By now, if you've have followed those threads you are hopefully using the tools therein and happily getting keys, downloading, decrypting and merging files into playable media. In otherwords you've escpaped both The Temple and The Dungeon and find yourself in pleasant green sunlit meadows.
But the tools you are using for downloading and decryption may have a shelf-life or be eclipsed by other tools. There is a whisper that yt-dlp may be a target for a take-down notice. Thus to remain online the '--allow-u' option to download encrypted media may disappear as a yt-dlp option. There is a newer, alternative downloader available. So have no fear.
Mp4decrypt is slower than its alternative - shaka-packager and ffmpeg can have some of its many long-winded duties eased too, by mkvmerge.
Here is the list of alternatives. I am, very probably, not telling you anything new, after all these tools have been around for a while, but if you haven't yet tried them yet, here is a description of the tools. I'll expand on their use after this short list:
- Shaka-packager
We use it to apply keys to a media file to decrypt it, however it was built to encrypt widevine media! This is a faster alternative to mp4decrypt. Download one of the files suitable for your machine labelled packager* , extract the file and save the binary as 'shaka-packager in your machine's PATH- mkvmerge
Mkvmerge is part of a package of tools called MKVToolNix found at https://mkvtoolnix.download/downloads.html.
Mkv files are a few percent smaller in size than the same video saved as mp4. It does exactly what its name suggests; it merges audio, video and subtitle files into one playable stream.- The Stream Detector (Firefox or Chrome plugin
The Stream Detector is something I use a lot. Not only does it find mpds but it can also create the command to directly download the media with N_m3u8DL-RE
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's address bar type 'chrome://extensions/'; set then Developer mode (top-right window) and drag and drop the crx file into the window to install- N_m3u8DL-RE
- very catchy name that! - this tool morphed from N_m3u8DL-CLI which was a straight m3u8 downloader. The 'RE' version copes with Dynamic Adaptive Streaming over Https (DASH) encrypted streams as well as Http Live Streaming (HLS) streams. DASH streams are associated with an mpd and HLS with m3u8 files that decribe the media. It is the candidate to replace yt-dlp.
Download from https://github.com/nilaoda/N_m3u8DL-RE/releases , extract and place the binary somewhere in your machine's PATH
From a command window or terminal, typing 'N_m3u8DL-RE --help', will reveal all if it is installed correctly.
Using the tools:
Shaka-packager
A typical shaka-packager call to decrypt a stream looks like this:
The same code quoted for easy readability:-Code:shaka-packager in=Frost.faudio\=96000.m4a,stream=audio,output=Frost.m4a --enable_raw_key_decryption --keys key_id=c2471312525641a38487ac8adc5b6f37:key=94b0ba348ce2fa88049378987c462e18 shaka-packager in='Frost.fvideo=3295464.mp4',stream=video,output=Frost.mp4 --enable_raw_key_decryption --keys key_id=c2471312525641a38487ac8adc5b6f37:key=94b0ba348ce2fa88049378987c462e18
Note that in the command string, the commas are not followed by spaces, in fact spaces will prevent the routine working. Both the kid and the key are specifically identified. And also note the in-filenames have an equals-sign that either needs escaping with backslash (\) or the filename can quoted with single quotes. I've used both styles in the example.shaka-packager in=Frost.faudio\=96000.m4a,stream=audio,output=Fro st.m4a --enable_raw_key_decryption --keys key_id=c2471312525641a38487ac8adc5b6f37:key=94b0ba 348ce2fa88049378987c462e18
shaka-packager in='Frost.fvideo=3295464.mp4',stream=video,output= Frost.mp4 --enable_raw_key_decryption --keys key_id=c2471312525641a38487ac8adc5b6f37:key=94b0ba 348ce2fa88049378987c462e18
If you want to run through the command yourself the programme details are :-
https://www.itv.com/watch/a-touch-of-frost/Ya1774/Ya1774a0004
frost s02E01 key c2471312525641a38487ac8adc5b6f37:94b0ba348ce2fa880 49378987c462e18
mkvmerge
Mkvmerge is quite simple to use from the command line:
And a fully qualified real exampleCode:mkvmerge -o output.mkv Video.mp4 Audio.m4a subs.srt
And repeated as a quote so you can read it:Code:mkvmerge -o Woman_on_the_Run.mkv 'Woman On The Run _ New to Encore _ Talking Pictures TV.en.m4a' 'Woman On The Run _ New to Encore _ Talking Pictures TV.mp4' --language 0:en --track-name 0:English 'Woman On The Run _ New to Encore _ Talking Pictures TV.en-GB.srt'
The Stream Detectormkvmerge -o Woman_on_the_Run.mkv 'Woman On The Run _ New to Encore _ Talking Pictures TV.en.m4a' 'Woman On The Run _ New to Encore _ Talking Pictures TV.mp4' --language 0:en --track-name 0:English 'Woman On The Run _ New to Encore _ Talking Pictures TV.en-GB.srt'
As installed, The Stream Detector will list playable media streams such as mpd and m3u8. Look for 'master.m3u8 as that is a descriptor for all the stream parts. Its strength in use is that it can collect a history of the media you have visited. With one click a copy of a the mpd may be made. But there is even more power; it is possible to copy the media in a number of formats. I will be looking at two:_
I will deal with these specifically in use in the next sections.
- N_m3u8DL-RE command string,
[Attachment 69162 - Click to enlarge]- Table entry
[Attachment 69161 - Click to enlarge]
My options for The Stream Detector, so that a sensible programme title appears whereever possible is:-
[Attachment 69166 - Click to enlarge]
N_m3u8DL-RE
At its simplest you can use N_m3u8DL-RE <mpd url> for example (Token has expired!)
Now The stream detector can save a full N_m3u8DL-RE command which mainly worked in testing (except not with ITV.com - more on that later).Code:N_m3u8DL-RE https://manifest.prod.boltdns.net/manifest/v1/dash/live-baseurl/bccenc/1242911124001/64f141c8-40a5-4bc4-a593-318ba8c0950e/6s/manifest.mpd?fastly_token=NjQwOWUxYTlfNDFlNWFlNjdkODE0YWY3NjI2YTRiYjc2YzcyZWVmODJjZmNkNTczMDQwZDVmZjgyZjBmNGJlYTVjYzU4NDM1Yw%3D%3D
For example, this is an m3u8 downloaded from https://www.tptvencore.co.uk/Video/Woman-On-The-Run?id=918aabec-7afe-4f83-9063-8816fd255f10 ; the media link (m3u8) generated by The Stream Detector is
And again as a quote:-Code:N_m3u8DL-RE "https://manifest.prod.boltdns.net/manifest/v1/hls/v4/aes128/6272132012001/591c148f-5b79-4bf3-99ba-23f082f1c336/6s/master.m3u8?fastly_token=NjNlNTJjOWJfYjllZTQ1OGM4NjZjYWQ4ZmQwZjNjOTk5OTdiZDFjN2VjOTY0ZTc3YzE3YzU4NTI3MTEzMjA1MzQ4ZWIzZTFjOQ%3D%3D&bcov_auth=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2NpZCI6IjYyNzIxMzIwMTIwMDEifQ.kfwThZX49U2WSqRYLANL5xP-1CkNYut8YFBgMMwc6fY" --header "User-Agent: Mozilla%2F5.0%20(X11%3B%20Linux%20x86_64%3B%20rv%3A109.0)%20Gecko%2F20100101%20Firefox%2F109.0|Referer: https%3A%2F%2Fwww.tptvencore.co.uk%2F" --save-name "Woman On The Run _ New to Encore _ Talking Pictures TV"And that is nearly good enough.N_m3u8DL-RE "https://manifest.prod.boltdns.net/manifest/v1/hls/v4/aes128/6272132012001/591c148f-5b79-4bf3-99ba-23f082f1c336/6s/master.m3u8?fastly_token=NjNlNTJjOWJfYjllZTQ1OGM4N jZjYWQ4ZmQwZjNjOTk5OTdiZDFjN2VjOTY0ZTc3YzE3YzU4NTI 3MTEzMjA1MzQ4ZWIzZTFjOQ%3D%3D&bcov_auth=eyJ0eXAiOi JKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2NpZCI6IjYyNzIxMzI wMTIwMDEifQ.kfwThZX49U2WSqRYLANL5xP-1CkNYut8YFBgMMwc6fY" --header "User-Agent: Mozilla%2F5.0%20(X11%3B%20Linux%20x86_64%3B%20rv%3 A109.0)%20Gecko%2F20100101%20Firefox%2F109.0|Refer er: https%3A%2F%2Fwww.tptvencore.co.uk%2F" --save-name "Woman On The Run _ New to Encore _ Talking Pictures TV"
If you use as given you will end up with three files - am mp4 an m4a and an srt which you will need to merge. However; N_m3u8DL-RE can be made to merge files to produce a playable video by adding
to the end of the command string.Code:-M format=mkv:muxer=mkvmerge
If you want a faster download - then add '-mt' as an option marker for "Multi-Threading" to your command, forcing N_n3u8DL-RE to download streams for video, audio and subtitle simultaneously.
And again - more readableCode:N_m3u8DL-RE "https://manifest.prod.boltdns.net/manifest/v1/hls/v4/aes128/6272132012001/591c148f-5b79-4bf3-99ba-23f082f1c336/6s/master.m3u8?fastly_token=NjNlNTJjOWJfYjllZTQ1OGM4NjZjYWQ4ZmQwZjNjOTk5OTdiZDFjN2VjOTY0ZTc3YzE3YzU4NTI3MTEzMjA1MzQ4ZWIzZTFjOQ%3D%3D&bcov_auth=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2NpZCI6IjYyNzIxMzIwMTIwMDEifQ.kfwThZX49U2WSqRYLANL5xP-1CkNYut8YFBgMMwc6fY" --header "User-Agent: Mozilla%2F5.0%20(X11%3B%20Linux%20x86_64%3B%20rv%3A109.0)%20Gecko%2F20100101%20Firefox%2F109.0|Referer: https%3A%2F%2Fwww.tptvencore.co.uk%2F" --save-name "Woman On The Run _ New to Encore _ Talking Pictures TV" -M format=mkv:muxer=mkvmerge
If you want to follow along the video is here https://www.tptvencore.co.uk/Video/Woman-On-The-Run?id=91732d44-0280-4a95-93c4-f51908245410N_m3u8DL-RE "https://manifest.prod.boltdns.net/manifest/v1/hls/v4/aes128/6272132012001/591c148f-5b79-4bf3-99ba-23f082f1c336/6s/master.m3u8?fastly_token=NjNlNTJjOWJfYjllZTQ1OGM4N jZjYWQ4ZmQwZjNjOTk5OTdiZDFjN2VjOTY0ZTc3YzE3YzU4NTI 3MTEzMjA1MzQ4ZWIzZTFjOQ%3D%3D&bcov_auth=eyJ0eXAiOi JKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhY2NpZCI6IjYyNzIxMzI wMTIwMDEifQ.kfwThZX49U2WSqRYLANL5xP-1CkNYut8YFBgMMwc6fY" --header "User-Agent: Mozilla%2F5.0%20(X11%3B%20Linux%20x86_64%3B%20rv%3 A109.0)%20Gecko%2F20100101%20Firefox%2F109.0|Refer er: https%3A%2F%2Fwww.tptvencore.co.uk%2F" --save-name "Woman On The Run _ New to Encore _ Talking Pictures TV" -M format=mkv:muxer=mkvmerge
when the command is run in a terminal window we get this:
[Attachment 69159 - Click to enlarge]
N_m3u8DL-RE halts for the user to select which streams they want - not all available are shown.
If you wish, adding --auto-select to the command will download without N_m3u8DL-RE stopping to wait for your selection but makes a pretty good job of selecting the best streams available in most cases.
[Attachment 69160 - Click to enlarge]
So there we are almost done. The problem we have is getting the link to the media and putting a name to the media. If we use programs to download we all like to have as little human intervention as possible.
You might want to spend hours using httptoolkit to find mpd by python code and parsing html or json to arrive at programme, series and episode description. But in my mind using' copy as Table Entry' in stream detector goes pretty far along the full automation route for me.
A few ITV programmes saved as table entries:-
So now we have in easily readable form a list of mpd and programme identifiers for saving.https://itvpnpdotcom.blue.content.itv.com/10-3617-0004-001/34/1/VAR028-HD-s/10-3617-00...0f3f26c321cc69 | Ghislaine: Partner in Crime - Series 1 - Episode 4 - ITVX | 09/02/2023 14:12:15
https://itvpnpdotcom.blue.content.itv.com/10-3617-0003-001/34/1/VAR028-HD-s/10-3617-00...7ee793dac83a56 | Ghislaine: Partner in Crime - Series 1 - Episode 3 - ITVX | 09/02/2023 14:12:11
https://itvpnpdotcom.blue.content.itv.com/10-3617-0002-001/34/1/VAR028-HD-s/10-3617-00...d97ffafb5f4617 | Ghislaine: Partner in Crime - Series 1 - Episode 2 - ITVX | 09/02/2023 14:12:09
https://itvpnpdotcom.blue.content.itv.com/10-3617-0001-001/34/1/VAR028-HD-s/10-3617-00...0aee24d1c85df1 | Ghislaine: Partner in Crime - Series 1 - Episode 1 - ITVX | 09/02/2023 14:12:04
EDIT 2025:
And a fully worked up program to download that batch of results is ITVX.py WKS-KEYS is now defunct and we use pywidevine - a python module
An ITVX Downloader
The code above should be a good starting point for adapting for your own url targets. Note specifically the python way of calling N_m3u8DL-RE. ITV proved a little difficult, in that, with N_m3u8DL-RE, it needed a cookie sent and --append-url-params which normally is not needed..Code:# A_n_g_e_l_a 20:09:2023 # Reworked June 2025 to 1080p # script to download videos from direct URL entry. # uses pywidevine # folder structures are created of the form ./output/ITV/'series-title'/'videos name' import re import subprocess import json import glob import os import time from base64 import b64encode from pathlib import Path import httpx from httpx import URL, Client, Cookies from selectolax.lexbor import LexborHTMLParser from scrapy import Selector from rich.console import Console from beaupy.spinners import Spinner, DOTS, CLOCK from termcolor import colored import pyfiglet as PF import jmespath ######## BEFORE USE ############# ######## configure paths ############ ####################################### WVD_PATH = "/home/angela/Programming/gits/device.wvd" # locatio nof widevine wvd file SAVE_PATH = Path('./') # current folder ####################################### ####################################### SAVE_PATH.mkdir(exist_ok=True, parents=True) SUBS = False console = Console() class ITV: def __init__(self): self.client = Client( headers={ "User-Agent": "okhttp/4.9.3", "Accept-Language": "en-US,en;q=0.8", "Origin": "https://www.itv.com", "Connection": "keep-alive", }, ) return def rinse(self,string): illegals = "*'%$!(),.;" # safe for urls string = ''.join(c for c in string if c.isprintable() and c not in illegals) replacements = { '"': '', ' ': '_', '_-_': '_', '&': 'and', ':': '', '_Content': '', } for rep in replacements: string = string.replace(rep, replacements[rep]) string = re.sub(r'(S\d{1,2})(_)(E\d{1,2})', r'\1\3', string) #selective remove underscore return string def get_pssh(self, mpd_url: str) -> str: r = self.client.get(mpd_url) r.raise_for_status() kid = ( LexborHTMLParser(r.text) .css_first('ContentProtection') .attributes.get('cenc:default_kid') .replace('-', '') ) s = f'000000387073736800000000edef8ba979d64acea3c827dcd51d21ed000000181210{kid}48e3dc959b06' return b64encode(bytes.fromhex(s)).decode() def get_key(self, pssh, license_url): from pywidevine.cdm import Cdm from pywidevine.device import Device from pywidevine.pssh import PSSH device = Device.load(WVD_PATH) cdm = Cdm.from_device(device) session_id = cdm.open() challenge = cdm.get_license_challenge(session_id, PSSH(pssh)) response = httpx.post(license_url, data=challenge) cdm.parse_license(session_id, response.content) keys = [f"{k.kid.hex}:{k.key.hex()}" for k in cdm.get_keys(session_id) if k.type == 'CONTENT'] cdm.close(session_id) return ":".join(keys) def download(self, url, index): title, extendtitle, data = self.get_data(url) compositetitle = title+' '+extendtitle video = data['Playlist']['Video'] media = video['MediaFiles'] illegals = "*'%$!(),.:;" replacements = { 'ITV01': 'ITV1', 'ITV02': 'ITV2', 'ITV03': 'ITV3', 'ITV04': 'ITV4', 'SNone': 'S00', 'Sothers': 'S00', 'ENone': '_Special', ' Episode ': 'E', ' Series ': '_S', 'otherepisodes': '_extra', 'ITVX': '', ' ': '_', '&': 'and', '?': '', } # sanitize videoname videoname = ''.join(c for c in compositetitle if c.isprintable() and c not in illegals) # and compact videoname = re.sub(r"(\d+)", pad_number, videoname).lstrip('_').rstrip('_') for rep in replacements: videoname = videoname.replace(rep, replacements[rep]) try: folder = title folder = self.rinse(folder) except: folder = 'specials' myvideoname = self.rinse(videoname) try: subs_url = video['Subtitles'][0]['Href'] subs = self.client.get(subs_url) if subs.status_code==200: SUBS = True f = open(f"{myvideoname}.subs.vtt", "wb") # bytes needed for N_m's subtitles subtitles = subs.content f.write(subtitles) f.close() os.system(f"ffmpeg -loglevel quiet -hide_banner -i ./{myvideoname}.subs.vtt ./{myvideoname}.subs.srt") else: SUBS=False except: SUBS = False mpd_url = media[0]['Href'] lic_url = media[0]['KeyServiceUrl'] pssh = self.get_pssh(mpd_url) key = self.get_key(pssh, lic_url) if SUBS: subs = f"--mux-import:path=./{myvideoname}.subs.srt:lang=eng:name='English'" else: subs = '--no-log' OUT_PATH = Path(f'{SAVE_PATH}/ITV/{folder}') OUT_PATH.mkdir(exist_ok=True, parents=True) out_path = str(OUT_PATH) m3u8dl = 'N_m3u8DL-RE' command = [ m3u8dl, mpd_url, '--append-url-params', '--auto-select', '--save-name', myvideoname, '--save-dir', out_path, '--tmp-dir', './', '-mt', '--key', key, '-M', 'format=mkv:muxer=mkvmerge', subs, ] cleaned_command = [cmd.replace('\n', ' ').strip() for cmd in command] subprocess.run(command) print(f"File saved to {OUT_PATH}/{title}") print(f"[info] {myvideoname}.mkv is in {OUT_PATH}") if SUBS: for f in glob.glob("*.vtt"): os.remove(f) for f in glob.glob("*.srt"): os.remove(f) def get_data(self, url: str) -> tuple: spinner = Spinner(DOTS) spinner.start() headers={'Referer': 'https://www.itv.com/'} if url.count('/') != 6: initresp = self.client.get(url, headers=headers, follow_redirects=True) sel = Selector(text=initresp.text) myjson = json.loads(re.search(r'\s*({.*})\s*', sel.xpath('//*[@id="__NEXT_DATA__"]').get()).group()) episodeId = myjson['props']['pageProps']['seriesList'][0]['titles'][0]['encodedEpisodeId']['letterA'] res = jmespath.search('{programmeSlug: programmeSlug, programmeId: programmeId}', myjson['query']) url = f"https://www.itv.com/watch/{res['programmeSlug']}/{res['programmeId']}/{episodeId}" r = self.client.get(url, follow_redirects=True) self.cookie_header_value = "; ".join([f"{c.name}={c.value}" for c in self.client.cookies.jar]) sel = Selector(text=r.text) myjson = json.loads(re.search(r'\s*({.*})\s*', sel.xpath('//*[@id="__NEXT_DATA__"]').get()).group()) myjson = myjson['props']['pageProps'] res = jmespath.search(""" episode.{ episode: episode, eptitle: episodeTitle, series: series, description: description, channel: channel, content: contentInfo } """, myjson) title = jmespath.search("programme.title", myjson) title = title episode = res['episode'] episodetitle = res['eptitle'] or '' series = res['series'] channel = res['channel'] extendtitle = f"{episodetitle}_{channel}_S{series}E{episode}" magni_url = jmespath.search('[ seriesList.[*].titles.[*].playlistUrl , episode.playlistUrl]', myjson) magni_url = (next (item for item in magni_url if item is not None)) payload = json.dumps({ "client": {"id": "lg"}, "device": {"deviceGroup": "ctv"}, "variantAvailability": { "player": "dash", "featureset": ["mpeg-dash", "widevine", "outband-webvtt", "hd", "single-track"], "platformTag": "ctv", "drm": {"system": "widevine", "maxSupported": "L3"} } }) headers = { "Accept": "application/vnd.itv.vod.playlist.v4+json", 'Accept-Encoding': 'gzip, deflate, br, zstd', "Accept-Language": "en-US,en;q=0.9,da;q=0.8", "Connection": "keep-alive", "Content-Type": "application/json", "Content-Length": str(len(payload)), "Cookie": self.cookie_header_value, 'Host':'magni.itv.com', 'User-Agent': 'okhttp/4.9.3', } r = self.client.post(magni_url, headers=headers, content=payload) #console.print_json(data=r.json()) spinner.stop() return title, extendtitle, r.json() def run(self) -> int: while True: url = input("Enter video url for download. \n") if 'watch'in url.lower(): break else: print("A correct download url has 'watch/<video-title>/<alpha-numeric>' in the line.") continue self.download(url, 'No') return 0 def pad_number( match): number = int(match.group(1)) return format(number, "02d") if __name__ == "__main__": title = PF.figlet_format(' I T V X ', font='smslant') print(colored(title, 'green')) strapline = "A Single ITVX Downloader:\n\n" print(colored(strapline, 'red')) print() myITV = ITV() myITV.run() exit(0)
And that's all I know!!
PS. I get unwanted messages from people telling me they cannot understand all this. And I agree, in January and February of last year neither could I.
I read loads of posts, evening after evening. and not much made sense.
But I Googled to clarify terms being used and I kept reading and re-reading posts from the heroes on this site.
Eventually I formed some understanding - enough to have a go - I learn best when doing, who doesn't? So I tried examples, myself, wherever possible. I eventually found code from others on github and made adaptations to it. I learnt to look and read the error messages that python gives. (It is trying to be helpful). And I attempted to make sense of the error and correct my code.
Eventually I found I had learned enough to code some python for my own purposes. And I keep learning. The code I write today looks noting like the stuff I was producing last Summer.
So I think my message to you is do not be put off by the seeming complexity of decryption and the need to program. Do not expect to understand it over-night. But if you keep at it, bit by bit the mists will clear.
Please ask questions in this thread and not to me via PM.
And no I don't make videos.
+ Reply to Thread
Results 1 to 30 of 58
-
Last edited by A_n_g_e_l_a; 29th Jul 2025 at 04:45. Reason: ITVX update to 1080 resolution
-
A Fast Channel 4 Downloader
Edit line 41 save pathPHP Code:''''
An alternative Channel 4 downloader using web-browser web endpoints.
Whilst it recovers 1080p resolution video, the bitrate is not quite
as high as Android endpoints provide.
The script is fast!
After the first run, when python creates runtime files and an AES key
is fetched and stored, the time to start downloading appears almost instantaneous.
A_n_g_e_l_a October 2025
USE: Just enter the C4 program_id found at the end of video urls eg 77297-003
Either on command-line enter
python fastALL4.py 74035-001
python fastALL4.py https://www.channel4.com/programmes/astrid-murder-in-paris/on-demand/74035-001
or just python fastALL4.py and enter program_id at prompt.
'''
from httpx import Client, HTTPError
import re
import json
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.padding import PKCS7
import base64
from pywidevine.pssh import PSSH
from pywidevine.device import Device
from pywidevine.cdm import Cdm
import os
import subprocess
import struct, uuid
from concurrent.futures import ThreadPoolExecutor
import sys
import pyfiglet as PF
SERVICE_CERT_HINT = "CAQ="
KEYCACHE = "./.keycache/C4keydata.json"
n_m3u8dl = "N_m3u8DL-RE"
# -- set this for your preferences
DOWNLOAD_DIR = "/home/angela/Downloads/devine/"
WVD_PATH = "./device.wvd"
# -- end set
# --- create tmp folder to hold subtitles
if not os.path.exists("./tmp"):
os.makedirs("./tmp")
# --- ./tmp deleted by N_m3u8dl_re on completion
# --- create keycache folder to store AES KEY/IV
if not os.path.exists("./.keycache"):
os.makedirs("./.keycache")
# ---
class KeyRefreshNeeded(Exception):
pass
# data silo
class Title():
def __init__(self, title, episode,subtitle, key, episode_id = None):
self.title = title
self.episode = episode # any text for episode
self.episode_id = episode_id # in form S01E02
self.subtitle = subtitle
self.key = key # media decryption
# -- OUTLINE
# -- 1. look for cached AES KEY, IV previously obtained
# -- 2. If not found, download app.bundle.js and search for KEY,IV and store
# -- 3. Get VOD Stream data for the program_id
# -- 4. From data returned find enc_token - and decrypt with AES KEY,IV to find license url and ANOTHER token.
# -- 5. Use new token and license url to obtain a certificate. (1st license call)
# -- 6. Create DRM Challenge and include certificate in POST request to license url (2nd license call)
# -- 7. Parse license response for media decryption KEYS
# -- decryption related --
def load_cached_keys():
try:
with open(KEYCACHE, "r") as f:
data = json.load(f)
f.close()
KEY = data.get("KEY", "").encode()
IV = data.get("IV", "").encode()
if len(KEY) != 16 or len(IV) != 16:
raise ValueError("Bad key/iv lengths")
return KEY, IV
except FileNotFoundError:
return None
except Exception:
# Corrupt cache: remove and refetch
try: os.remove(KEYCACHE)
except FileNotFoundError: pass
return None
def save_cached_keys(KEY: bytes, IV: bytes):
tmp = KEYCACHE + ".tmp"
with open(tmp, "w") as f:
json.dump({"KEY": KEY.decode(), "IV": IV.decode()}, f)
f.close()
os.replace(tmp, KEYCACHE)
def fetch_fresh_keys(client):
url = "https://static.c4assets.com/all4-player/latest/bundle.app.js"
try:
bundle = client.get(url, timeout=8).text
except HTTPError as e:
raise RuntimeError(f"fetch bundle.app.js failed: {e}")
k_hits = re.findall(r'"bytes1":"([^"]+)"', bundle)
v_hits = re.findall(r'"bytes2":"([^"]+)"', bundle)
if not k_hits or not v_hits:
raise RuntimeError("bytes1/bytes2 not found in bundle.app.js")
KEY_s, IV_s = k_hits[0], v_hits[0]
KEY, IV = KEY_s.encode("latin-1"), IV_s.encode("latin-1")
if len(KEY) != 16 or len(IV) != 16:
raise RuntimeError(f"KEY/IV wrong size ({len(KEY)}, {len(IV)}), expected 16/16")
save_cached_keys(KEY, IV)
return KEY, IV
def get_keys_with_cache(client):
kv = load_cached_keys()
if kv:
return kv
return fetch_fresh_keys(client)
def decrypt_message(message_b64: str, KEY: bytes, IV: bytes) -> str:
try:
decryptor = Cipher(algorithms.AES(KEY), modes.CBC(IV), backend=default_backend()).decryptor()
unpadder = PKCS7(128).unpadder()
cbytes = b64u_decode(message_b64)
padded = decryptor.update(cbytes) + decryptor.finalize()
pt = unpadder.update(padded) + unpadder.finalize()
return pt.decode("utf-8")
except Exception as e:
# Signal caller to refresh keys
raise KeyRefreshNeeded from e
def b64u_decode(s: str) -> bytes:
"""
Decode a URL-safe Base64 string (with '-' and '_') and
auto-fix missing padding.
"""
s = s.strip().replace("\n", "")
s += "=" * (-len(s) % 4) # pad to multiple of 4
return base64.urlsafe_b64decode(s)
# --- end decryption ops ---
# -- create pssh box
def make_widevine_pssh(kid_hex: str,
system_id: str = "edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",
version: int = 0) -> str:
"""
Build a Widevine PSSH (v0) containing one KID.
Returns base64-encoded PSSH box.
"""
# SystemID as 16 raw bytes (no dashes)
sysid_bytes = uuid.UUID(system_id).bytes # big-endian, correct for MP4/pssh
# KID as 16 bytes
kid_bytes = bytes.fromhex(kid_hex.replace("-", ""))
if len(kid_bytes) != 16:
raise ValueError("KID must be 16 bytes (32 hex chars).")
# Widevine PSSH data: field 0x12 (repeated key_id), length 0x10 (16), then the KID
# This is the minimal valid Widevine protobuf payload.
data = b"\x12\x10" + kid_bytes
# MP4 'pssh' box: size(4) + type(4) + version/flags(4) + systemID(16) + data_size(4) + data
size = 4 + 4 + 4 + 16 + 4 + len(data)
box = struct.pack(">I4sI16sI", size, b"pssh", version, sysid_bytes, len(data)) + data
return base64.b64encode(box).decode("ascii")
def get_kid(transcode_id: str) -> str:
''' C4, uniquely, allows default_kid to be constructed,
transcodeId is normally 7 or 8 binary digits.
code here deals with length differences'''
n = int(transcode_id, 10) # decimal → int
if not (0 <= n <= 0xFFFFFFFF):
raise ValueError("transcode_id out of 32-bit range")
tail = f"{n:08x}" # 8 hex chars, zero-padded
return "0" * 24 + tail
def generate_pssh(transcodeId: str = None):
default_kid = get_kid(transcodeId)
return make_widevine_pssh(default_kid)
# --- Widevine license helper ---
DEFAULT_HEADERS = {
"Content-type": "application/json",
"Accept": "*/*",
"Referer": "https://www.channel4.com/",
"user-agent": "Mozilla/5.0 (X11; Linux x86_64; rv:143.0) Gecko/20100101 Firefox/143.0"
}
def build_drm_today_payload(request_id: str, token: str, mpd_url: str, message_b64: str = "") -> dict:
# build payload to match C4 pattern
payload = {}
if token:
payload["token"] = token
if request_id:
payload["request_id"] = request_id
if mpd_url:
payload["video"] = {"type": "ondemand", "url": mpd_url}
if message_b64:
payload["message"] = message_b64
else:
payload['message'] = SERVICE_CERT_HINT
return payload
def post_json(client, url: str, data: dict, headers: dict) -> dict:
r = client.post(url, json=data, headers=headers)
try:
j = r.json()
except Exception:
raise RuntimeError(f"License server returned non-JSON (HTTP {r.status_code}): {r.text[:500]}")
if r.status_code >= 400:
raise RuntimeError(f"HTTP {r.status_code} from license server: {j}")
return j
def fetch_service_certificate(client, lic_url: str, request_id: str, lic_token: str, mpd_url: str) -> str:
# First POST: get signed DRM service certificate
payload = build_drm_today_payload(request_id, lic_token, mpd_url)
j = post_json(client, lic_url, payload, DEFAULT_HEADERS)
if not j.get("license"):
raise RuntimeError(f"Missing 'license' field in service certificate response: {j}")
return j["license"]
def request_license(client, lic_url: str, request_id: str, lic_token: str, mpd_url: str, challenge_bytes: bytes) -> str:
# Second POST: base64 of the CDM challenge inside 'message'
challenge_b64 = base64.b64encode(challenge_bytes).decode("ascii")
payload = build_drm_today_payload(request_id, lic_token, mpd_url, message_b64=challenge_b64)
j = post_json(client, lic_url, payload, DEFAULT_HEADERS)
if not j.get("license"):
raise RuntimeError(f"Missing 'license' field in license response: {j}")
return j["license"]
def pad_number(number):
return format(int(number), "02d")
def prompt_prog_id(default="74015-001"):
try:
s = input(f"Program ID ({default})?: ").strip()
return s or default
except (KeyboardInterrupt, EOFError):
print("\nAborted.", file=sys.stderr)
raise
def main(url):
client = Client()
with ThreadPoolExecutor(max_workers=1) as ex:
key_future = ex.submit(get_keys_with_cache, client) # disk/network in background
if url == "":
progId = prompt_prog_id()
elif len(url)>10:
progId = url.split('/')[-1] # https://www.channel4.com/programmes/astrid-murder-in-paris/on-demand/74035-001
else:
progId = url # 74035-001
try:
my_data = client.get(f"https://www.channel4.com/vod/stream/{progId}").json()
except HTTPError as e:
raise RuntimeError(f"fetch vod stream data failed: {e}")
vps = my_data["videoProfiles"]
if len(vps) <= 1:
raise RuntimeError(f"Expected at least 2 profiles; got {len(vps)}: {vps!r}")
stream = vps[1]["streams"][0]
# sanity: make sure it's still the one we expect
expected = "dashwv-dyn-stream-1"
actual = vps[1].get("name", "?")
if actual != expected:
raise RuntimeError(f"Profile[1] changed: expected '{expected}', got '{actual}'. Full entry: {vps[1]!r}")
uri = stream["uri"]
enc_token = stream["token"]
asset_id = my_data["transcodeId"] # both names 'asset_id' and 'transcodeId' used in code
# wait for keys from threadPoolExecutor
KEY, IV = key_future.result()
# the stream token hides -> lic_url | lic_token so decrypt with KEY, IV found in bundles.js
try:
decoded = decrypt_message(enc_token, KEY, IV)
except KeyRefreshNeeded:
# Refresh keys ONCE and retry decrypt
try:
os.remove(KEYCACHE)
except FileNotFoundError:
pass
KEY, IV = fetch_fresh_keys(client)
# --- license url and token hidden; use KEY and IV to decrypt
decoded = decrypt_message(enc_token, KEY, IV)
lic_url, lic_token = decoded.split("|", 1)
# PSSH
pssh_b64 = generate_pssh(transcodeId=asset_id)
# --- Widevine essentials start ---
# --- uses a certificate to authorise cdm key exchange ---
# --- first negotialte certificate then get the license ---
device = Device.load(WVD_PATH)
cdm = Cdm.from_device(device)
session_id = cdm.open()
try:
# 1) Service certificate (first POST, message = 'CAQ=' (found using httptoolkit and watching json exchanges))
service_cert_b64 = fetch_service_certificate(client, lic_url, asset_id, lic_token, uri)
# 2) Set the service certificate
cdm.set_service_certificate(session_id, service_cert_b64)
# 3) Build challenge
challenge = cdm.get_license_challenge(session_id, PSSH(pssh_b64), privacy_mode=True)
# 4) Request license (second POST with message=base64(challenge))
license_blob = request_license(client, lic_url, asset_id, lic_token, uri, challenge)
# 5) Parse license and dump keys
cdm.parse_license(session_id, license_blob)
key = None
for k in cdm.get_keys(session_id):
if k.type == "CONTENT":
key = f"{k.kid.hex}:{k.key.hex()}"
if not key:
raise RuntimeError("No CONTENT keys in license response")
finally:
cdm.close(session_id)
# --- Widevine essentials end ---
# --- Have Key, mpd now need
# --- collect data for video download ---
title = my_data["brandTitle"].replace(':','') # ; upsets N_m3u8
websafe_title = my_data['webSafeBrandTitle'] # for url constuction
episode = my_data["episodeTitle"]
subtitle = my_data["subtitlesAssets"][1]["url"]
# -- get series and episode numbers
vid_url = f"https://www.channel4.com/programmes/{websafe_title}/on-demand/{progId}"
try:
vid_cont = client.get(vid_url).text
except HTTPError as e:
raise RuntimeError(f"fetch episode/series data failed: {e}")
regex = re.findall(r"name=\"title\" content=\"(.*?)\W{1,3}On\WDemand", vid_cont)
# --- If we have episode data
try:
_, sernum, _, epnum = regex[0].split(' ')
epnum = pad_number(epnum)
sernum = pad_number(sernum)
episode_id = (f"S{sernum}E{epnum}")
except:
epnum = ''
sernum = ''
episode_id = ''
if episode in title:
episode = '' # avoid repetitive strings in programme title build
# --- store subtitle file 'w' in file open will mean
# over-write of existng contents - easy re-use
my_title = Title(title, episode, subtitle, key, episode_id)
subs = client.get(my_title.subtitle).text
try:
with open("./tmp/subtitles.srt", 'w') as f:
f.write(subs)
f.close()
except FileNotFoundError:
subs = None
except Exception:
# Corrupt cache: remove and refetch
try: os.remove(KEYCACHE)
except FileNotFoundError: pass
subs = None
# N_m3u8DL-RE to do download
command = ([
n_m3u8dl,
uri,
#'--auto-select',
'-sa',
'all',
'-sv',
'best',
'-ds',
'id="0"',
'--save-name',
f"{my_title.title} {my_title.episode} {my_title.episode_id}",
'--save-dir',
f'{DOWNLOAD_DIR}/C4/{my_title.title}/',
'--tmp-dir',
"./tmp",
'--no-log',
'--key',
my_title.key,
'-mt',
'-M',
'format=mkv:muxer=mkvmerge',
])
if subs:
command.append("--mux-import:path=./tmp/subtitles.srt:lang=en:name='English'")
# bastard nilaoda!! Command nothing like --help tells it!
subprocess.run(command)
if __name__=="__main__":
title = PF.figlet_format(' A L L 4 ', font='smslant')
print(title)
try:
main(sys.argv[1])
except:
main("")
Last edited by A_n_g_e_l_a; 20th Oct 2025 at 09:48. Reason: Ringing the changes...
-
I have to echo codehound's remarks. Again, another excellent post. Keep up the truly excellent work.
-
As usual, excellent work Angela.
One or two observations though.
As with your previous STV downloader, by using "--auto-select" as an option for N_m3u8DL-RE when the stream is none-encypted, it selects the 64Kbps audio stream and not the "best" which is 128Kbps. By omitting that option, you get a list of the streams. Just press enter and it will download the best video (1080p) and the best audio (128Kbps).
But that is not the case for encrypted streams.
However, big problem with encrypted streams. Your last downloader worked perfectly for everything I threw at it. But this time, most end up with an unplayable video. MediaInfo typically says:
However, it will not even play in Windows Media player 12 (which does play Quicktime), even after changing extension to .movCode:Video ID : 1 Format : V_QUICKTIME Codec ID : V_QUICKTIME Duration : 51 min 17 s Bit rate : 3 554 kb/s Width : 1 920 pixels Height : 1 080 pixels Display aspect ratio : 16:9 Frame rate mode : Constant Frame rate : 25.000 FPS
Neither ffmpeg nor HandBrake recognise it as a valid codec.
But, not all (just most that I've tried).
Catching Milat S01E01 is actually perfect: https://player.stv.tv/episode/461c/banijay1-milat
Some that fail:
I've tried most of The Bridge: https://player.stv.tv/summary/banijay1-thebridge
and
Six Nations Rugby: Italy v France: https://player.stv.tv/episode/4ejz/six-nations-live
I'll have to spend more time on it when I do have the time to try to suss it out.
The strange thing is, during downloading with N_m3u8DL-RE, it does actually say "[0x1]: Video, h264 (avc1), 1920x1080", which of course is correct.
Incidently, many of my final files I want as mp4, so instead of using
"-M",
"format=mkv:muxer=mkvmerge",
I use:
"-M",
"format=mp4:muxer=ffmpeg", -
One or two observations of my own.
- One: My aged-ears with attendant high frequency hearing loss are more low-bit rate rather than high these days: I cannot hear the difference. If you think you really stil can, then you are a capable coder; change it to your preference.
- Two: I only tried The_Bridge Pilot, saved as mkv; it downloaded and it played fine. More likely the problem is with your Windows set-up missing the needed codecs. Try using VLC as your media player. Or getting decent operating system
- Three: "format=mp4:muxer=ffmpeg" You do not meed any muxer specified; so "format=mp4" is enough.
Edit: Just tested the Rugby stream and that works too.
Last edited by A_n_g_e_l_a; 12th Feb 2023 at 12:12.
-
Surely no one nowadays is using anything but VLC (?) Its the ripping go-to for all testing purposes, especially if you're an uploader. You test in VLC and if its G2G in VLC - that's your testing done. Anyone using a micky mouse media player does it at their own peril.
VLC needs no sideloading of any codecs. Those days when out with the ark and the k-lite codec pack. -
Of course I used VLC to test it. That's my default player. It was VLC that came up with:
Codec not supported:
VLC could not decode the format " " (No description for this codec)
and that's what prompted me to try other things.
My comments weren't meant as a criticism, merely what I had found happened at my end when first trying it. Maybe there is something in the code that's good for linux and not windows. I'll investigate when I have time. I made them mainly wondering if others were finding the same effects.
Reading through many posts on here about many things, there's an awful lot of snide comments towards members. Not nice. -
Fair comment but your in the 1% minority with linux
Its frustration through having to repeat ad-nauseum what's been posted multiple times before and the more times its repeated and spoonfed the more chance its got of being revoked. You've only got to monitor what's happened over vdo on here to see there are moles amongst us.
If you use the forum's own search facility 95% of asked questions have already been answered.Last edited by codehound; 12th Feb 2023 at 16:14.
-
It's a rare error report, and usually reserved for x265/h265 codecs (hevc is a whole different level of system requirements and multi threaded cpus if the host machine is older than the codec. VLC may not compile with support if the host can't play.)VLC could not decode the format " " (No description for this codec)
-
V_QUICKTIME
Your video stream download is corrupted.
"V_QUICKTIME
Codec ID: V_QUICKTIME
Codec Name: Video taken from QuickTime(TM) files
Description: Several codecs as stored in QuickTime, e.g., Sorenson or Cinepak.
Initialization: The Private Data contains all additional data that is stored in the ‘stsd’ (sample description) atom in the QuickTime file after the mandatory video descriptor structure (starting with the size and FourCC fields). For an explanation of the QuickTime file format read QuickTime File Format Specification."
https://www.matroska.org/technical/codec_specs.html
https://forum.videolan.org/viewtopic.php?t=156824 -
I'm not quite sure which bit of my above post has been repeated multiple times before (did you read it?).
Anyway, it's the "--use-shaka-packager", "--key", key,
that's killing it. Basically, it's not decrypting. I'm not sure why yet. But I will invesigate on my own.
If I replace that small part of the code with good old fashioned mp4decrypt script it all works fine.
and yes, before you ask, of course I have packager-win-x64.exe in my Windows PATH.Last edited by deccavox; 12th Feb 2023 at 19:06.
-
-
This is unfortunately a very common occurrence in this part of the internet, and not exclusive to this forum. I'm new here, but was a part of the piracy/release scene for many years, and it tends to attract lots of unhappy people who get a little buzz from being condescending towards others. Reading through many posts on here reminds me of old-school IRC channels where scene members would jump at the chance to show of their skills/knowledge, just so they could tell people to **** off when someone inevitably asked how it was done. It's like their favorite thing. This is speaking generally, of course; I'm not referring to a specific person. It just comes with the territory, and the best practice is to just let it roll of your back.Originally Posted by deccavox
Try renaming it to shaka-packager.exe.Originally Posted by deccavox -
I started here, on this forum, this time last year and agree people, including me, can be unhelpful at times. My motivation for these Decryption threads was to provide answers to common problems I had met and was continuing to meet on my decryption journey.
I confess to feelings of frustration when people jump straight in and demand answers to stuff that I know for certain has already been answered over, and over, and over again. I feel frustration when people ask questions in the forum that Google is better placed to answer instead.
I confess to enormous frustration when people skim read and expect instant insight into the tools and methods used in decrypting. Or those that claim to have read a thread but then happily proclaim their ignorance and demand help to understand. There is a case in point right here. Renaming shaka-packager was explained in the very first post on this thread.
I confess to feeling of annoyance when people take a script, written as an example and posted in a tutorial style, and start complaining the script doesn't work for them for some reason. I clearly say my scripts are written for Linux; I only post them when they work on my machine. I really do not wish to become software support person with endless discussion about getting the scripts to work on Windows. Not my job.. The example scripts are just that, things for you to change to suit your circumstance and in the process learn a little about coding. And things to cut and paste from when you start stringing Python code together yourself. If I was offering a 'solution' to downloading from itvx or stv I would have posted these scripts each in their own new thread.
And should you make a big discovery and improve one of my example scripts, great, I am pleased the acorn I set has grown, but please start your own thread should you wish to tell the World about it.
So I understand the old-timers and indeed notice myself getting a bit short with people sometimes. So I would say in summary, I think it is a truism people generally wish to be helpful but there is a wariness to avoid fools. Here 'fools' are people who ask questions without doing their own research first; or those who wish to be baby-stepped through everything and have no concept of 'study'.
I'll happily help those whom have truly tried to help themselves first.Last edited by A_n_g_e_l_a; 13th Feb 2023 at 03:23.
-
@A_n_g_e_l_a
I promise, I wasn't singling you out here. I genuinely meant in more general terms. I've spent a couple of months here reading through everything I can get my eyes on, and you've been an immense help in regards to my learning of both decryption and Python. I'm stuck on a Linux machine at the moment, and studying your scripts have been a fantastic learning tool. But there are a lot of posts around here that serve no other purpose than to make the poster feel superior for knowing something the OP don't. And that is a frustrating thing for a new guy. But of course I understand the lack of patience when someone hasn't made any kind of effort before asking or even complaining. There are a lot of those posts, as well.
Either way, sorry for derailing your thread. I hope you continue to teach us new guys how it's done
-
I know you weren't. Just as I am not picking on you. I am trying to make the point that it is not some 'malicious delight in knowing and not telling' that drives me, and I assume, probably everyone else, but a frustration that grown adults can appear as helpless as small children when faced with a little bit of complexity.
Decryption is a complex topic and it is not within everyone's grasp. People arrive here from differing knowledge backgrounds and experiences. Sadly, some need to be shown the door otherwise threads become clogged with "OK I've done what you said but it didn't work?" type questions which do no-one any good and only breed dependency and even higher expectations. -
One gotcha occurs if you are used to yt-dlp, and prefacing your keys from WideVine with "--key". So if you just drop N_m3u8DL-RE in as a replacement and specify the ~RE --key option and then the key variable, it might actually look like this --key --key xxxx
xxx
-
You are correct. But Deccavox, in amongst his rather verbose reply stated the script was failing when n-m3u8-re was calling shaka-packager.
He stated again, in that verbose response, that he had shaka-packager added to his $PATH.
The solution for windows is the same as for linux, the binary needs to be renamed. Either by cp on linux, or whatever equivalent windows uses.
As for why certain responses can seem terse, or abrupt. Well, we all have lives outside this forum. Some of us have other projects we are involved in and offer help on a voluntary basis.
Seeing the same type of query appear again and again, the "I've not read the man pages and haven't bothered to really look at the project page for this tool I'm trying to use. The search bar is beyond my capabilities, give me my answer now god damn it."
Well, it can get up certain peoples backs.
Brevity can go a long way too.
And worse still, the scammers who see a reply on the thread/forum and proceed to flood your inbox with even more demands. -
Let's move on. Please. Angela posted very good information and scripts for ITV and STV. I gave feedback of my initial experiences for STV. Yes, I had/ have a problem with shaka-packager on this script, despite having used same package very successfully in the past. So I was basically wondering if others were having the same issue. I may not be a pro coder but I am at the stage where I'm hopefully not a noobie asking questions that have been asked before. Indeed, I have posted some of my own solutions to various downloaders for others to share.
I tried renaming shaka-packager as some suggested. Still the same. I even tried putting shaka-packager in same folder as the STV downloader, so PATH would not be required. The same.
I've been through the script. There's nothing specific to linux or Windows. As I said, all works fine (including N_m3u8DL-RE downloading encrypted streams). It's just that final shaka-packager stage of decryption. But, it's not the downloaded streams that are faulty. Using the very same key and same downloads and using mp4decrypt instead, it's good.
I've tried a few STV encrypted downloads as I said, all the same, apart from "Catching Milat". Why the difference? It can't be because Milat has no subtitles and all the rest do, surely?
As I said, I was hoping to promote positive discussion about this specific problem. Has anybody else actually tried to download (say) The Bridge S01E01: https://player.stv.tv/episode/461s/banijay1-thebridge
I know Angela said she did when she was composing the script. Could somebody else have a go please?
And I'm sorry if this is a little verbose. -
"I tried renaming shaka-packager as some suggested. Still the same. I even tried putting shaka-packager in same folder as the STV downloader, so PATH would not be required. The same."
If you are flagging n-m3u8re to use shaka packager, it has to be on your systems path, you are calling an installed application.
In cmd
reame the binary to shaka-packager.exe and save in the newly created folder bin at root of your C: driveCode:mkdir C:\bin
restart cmdCode:setx PATH "C:\bin;%PATH%"
type shaka-packager and you get the same man page readout that packager gives
In fact, in that newly created bin folder, you can place ALL your apps that you keep in a folder (yt-dlp.exe ffmpeg.exe even GPAC if you want it. )Last edited by Sorenb; 13th Feb 2023 at 13:54. Reason: Spelling
-
Last edited by libero08; 14th Feb 2023 at 19:59.
-
Last edited by A_n_g_e_l_a; 15th Feb 2023 at 02:56.
-
i have a lot of stuffs into dev area, and 110% , 24/7 ,365 day /year "small button" into rigth upper corner are ever "pushed" on the rigth side
well maybe i am not at the first step ad i knew "crx" is for chrome
I repat, when i drag & drop, a warn message on bottom of dev windows come, after when i allow , in the left corner, upper side come an error message . Nothing more, i thougth someone have a idea, because i think are very weird.
PACK ERROR:
error: "CRX_REQUIRED_PROOF_MISSING"Last edited by libero08; 15th Feb 2023 at 03:46.
-
Look all sorts come on here asking for help. Ok you've got lots of experience - sorry I was unable to help you.
But if you had posted the information you just have I might have suggested you have a corrupted crx and you try to re-download. But since that is such an obvious thing to try, of course you will already have tried it. And of course you will have also tried an earlier release. So maestro I can't think of a thing you can do. -
@libero08
Stream Detector extension doesn't work in Chrome browser. Chrome moans it's not an official extension from their web store and it won't let you enable it. I've even tried it in Edge, which is essentially Chrome. Although Edge has an additional option: Allow extensions from other stores, it still won't let you enable it.
It more or less tells you that on the Github entry anyway: https://github.com/rowrawer/stream-detector
Quote "The Chrome version of this addon is not maintained or supported in any way. It's only included on the off chance that it works. Don't expect it to."
Similar Threads
-
Decryption and the Temple of Doom
By A_n_g_e_l_a in forum Video Streaming DownloadingReplies: 610Last Post: 21st Aug 2025, 12:43 -
An issue with mp4 decryption
By CrymanChen in forum Video Streaming DownloadingReplies: 16Last Post: 27th Apr 2022, 06:43 -
widevine decryption help
By birbal1 in forum Video Streaming DownloadingReplies: 2Last Post: 5th Dec 2021, 10:11 -
Help with video download and decryption
By herschel in forum Video Streaming DownloadingReplies: 4Last Post: 26th Jul 2021, 04:31 -
How do I get the decryption key
By Bakekalu in forum Video Streaming DownloadingReplies: 6Last Post: 5th Jul 2021, 01:25


Quote
