joyn.de - from url to download (I have external yt-dlp extractors for joyn, plus.rtl.de, pluto.tv):
- get access token like aqzs described; if logged in you can refresh your token as well - refresh token is valid for 180 days.
- from regex r'^https://www.joyn.(de|at)(/play)?(?P<path>/serien/[\w-]+/[\w-]+)' save path
- get player_video_id for path from graphql with operationName=PageSeriesEpisodePlayerClientSide; sha256Hash for graphql is constant, x-api-key for all graphql queries is constant as well. video_id is found at data.page.episode.id in the returned json object
- fetch metadata for video_id from graphql PlayerSeriesEpisode
- find video_id in metadata at data.episode.video.id
- check if video is age restricted at data.episode.ageRating.minAge in metadata
- get entitlement_token with video_id and flag if age restricted (minAge >= 16)
- last: get playlist streams; playlist needs a signature that's built from
- inside playlist you'll find manifest and license urls.Code:SIGNATURE_KEY = '3543373833383336354337383634363635433738363633383236354337383330' \ '3634353935433738333933353234354337383635333935433738333833323346' \ '35433738363633333344334235433738333836363335' sha_input = f'{data},{entitlement_token}{SIGNATURE_KEY}' signature = sha1(sha_input.encode('utf-8')).hexdigest()
"filme" instead of "serien" is quite similar, just needs some different graphql requests.
Now if aqzs knows a way to manipulate the mpd url to fetch higer resolution than usually 576p - that would be the burner- but I guess that's not possible for joyn as the requests are signed.
+ Reply to Thread
Results 61 to 90 of 136
-
-
Request are signed by you know how to signature is generated, I tried with the best payload possible and still got 576p too :
Code:{"manufacturer":"unknown","platform":"browser","maxSecurityLevel":1,"streamingFormat":"dash","model":"unknown","protectionSystem":"widevine","enableDolbyAudio":True,"enableSubtitles":True,"maxResolution":2160,"variantName":"default","version":"v1"}
Code:getClientCapabilities: _(eo, Object.assign(Object.assign({}, X), { maxSecurityLevel: 1, platform: "browser" })),
Code:let i = r.z.enum(["widevine", "fairplay", "playready"]); let t.streamingFormatSchema = r.z.enum(["dash", "hls"]);
Code:{"manufacturer":"unknown","platform":"browser","maxSecurityLevel":1,"streamingFormat":"dash","model":"unknown","protectionSystem":"playready","enableDolbyAudio":True,"enableSubtitles":True,"maxResolution":2160,"variantName":"default","version":"v1"} {"manufacturer":"unknown","platform":"browser","maxSecurityLevel":1,"streamingFormat":"hls","model":"unknown","protectionSystem":"fairplay","enableDolbyAudio":True,"enableSubtitles":True,"maxResolution":2160,"variantName":"default","version":"v1"}
The thing that limit manifest to 576p is the token, when decoded we get that :
Code:{'ads_break_spacing': 13, 'ads_limit_midroll': 5, 'ads_limit_preroll': 3, 'ads_max_midroll_blocks': 10, 'ads_test': '', 'ads_variant': '', 'anonymous_id': 'a5aeb22d-ed1a-4571-8bd2-beec7f720bb0', 'business_model': 'RVOD', 'catalog_country': 'DE', 'content_id': 'a_p82ioscdb42', 'copyrights': ['Brainpool Entertainment GmbH'], 'distribution_tenant': 'JOYN', 'entitlement_id': '00ffa125-3263-46e1-8c14-9ae966f29b37', 'exp': 1723971982, 'iat': 1723885582, 'joyn_packages': ['DE_FREE'], 'key_sign': 'prod', 'location_country': 'DE', 'profile_id': 'JNAA-0fbbe8bc-9844-5d8b-aa06-9a69de0e318f_0123456789abcdef', 'quality': 'SD', 'user_id': 'JNAA-0fbbe8bc-9844-5d8b-aa06-9a69de0e318f'}
Thanks ! -
Joyn.de doesn't serve higher qualities than 576p
Also don't forget joyn.chBypass HMACs, One-time-tokens and Lic.Wrapping: https://github.com/DevLARLEY/WidevineProxy2 -
For some series I got 720p, but that seems to be the exception. I even tested the paid grace period once, and even then I didn't get higher resolution. I have the impression that my TV (some newer LG model) gets higher resolution from the joyn app, but it might just be my impression.
-
Thank you guys for help! Also there is 1080p with Premium Accounts but i think you need to manipulate the API a little bit. I read something with "Android" instead of "Browser"
Also the Kodi Plugin does that. Seems like Android with an L3 Key gets 1080p but you need an Premium Account for that.
TV Total has 1080p btw.
https://www.joyn.de/play/serien/dcs-stargirl/3-1-kapitel-eins-der-mord
Has 1280x720 with free acc and 1080p with Premium (atleast with Kodi Addon) has someone tested an premium account in a Browser? If you really get 1080p? Maybe its only 1080p for Android. Also the joyn App gets 1080p with Premium maybe there is a way to track that?
I don't think you need an L1 key for 1080p.
I still dont get the {data} part? Data of what? The Age restrict thing is btw. the "PIN" option in the account settings you need an German ID for that (you can generate one not a Problem) had that handled in my Autohotkey version (i just searched for elements and then send the PIN).
Sorry for all the questions i'am quiet new to Python and its quiet hard to understand if you never learned the language but well i'am trying to learnLast edited by Lostion; 17th Aug 2024 at 07:08.
-
The signature and data part hopefully get much more clear after reading this snippet:
Code:def _build_signature(self, data, token): sha_input = f'{data},{token}{self._SIGNATURE_KEY}' return sha1(sha_input.encode('utf-8')).hexdigest() def _get_streams(self, video_id, entitlement_token): data = json.dumps({ 'manufacturer': 'unknown', 'platform': 'browser', 'maxSecurityLevel': 1, 'streamingFormat': 'dash', 'model': 'unknown', 'protectionSystem': 'widevine', 'enableDolbyAudio': False, 'enableSubtitles': True, 'maxResolution': 1080, 'variantName': 'default', 'version': 'v1' }, separators=(',',':')) signature = self._build_signature(data, entitlement_token) ...
-
But what happens if its age restricted? Still trying to understand that code. But thanks a lot!
Has someone tested if you do 'platform': 'android', ? -
Age restriction flag is used when you fetch the entitlement_token (which then needs the pin). The entitlement_token is used to get the stream urls.
I haven't tested with platform 'android' yet; I'd need to implement the login procedure first - right now I have my saved acess_token that gets renewed with the refresh_token. I've copied the access_token from a browser request. And I only have a free account.
The Kodi code is not the nicest and cleanest piece of python code on earth and rather hard to understand. I had to work through the requests the browser makes. That's what I've tried to summarize in #61. -
Maybe i use the free premium trial (which i didn't knew about becaue at the beginning there were no trial at all) and test in Browser if i get 1080p.
Because what i've understand the MPD and the License url is protected with a token and the generated signature it would be interesting to see if the browser then plays a 1080p file if you have premium and only the API gets the lower resolution. Maybe 1080p is only with Playready possible? -
Guys its working:
[Attachment 81542 - Click to enlarge]
Is there a way to see the API endpoints?
Edit: I censored the generated token because who knows if joyn can track that...
Edit2: Only works for productions from joyn everything else like "The Irrational" is limited to 768x432 -.-
Edit3: Same with Stargirl but that doesnt make any sense because with free acc its 1280x720 oO
Edit4: Yeah with free acc its 1280x720 wtf also "FBI" is 1024x576 even with premium in Browser. Wtf is that? Why do people pay for such horrible quality seems like only the joyn productions are 1080p atleast in Browser.Last edited by Lostion; 17th Aug 2024 at 16:09.
-
Can someone with premium get these in 1080p for me? Joyn bought the rights to s05 and the older season are 576p. Yet 720p on Kabel Eins for the older seasons.
https://www.joyn.de/play/serien/yes-we-camp/5-1-luxus-camper-und-campingneulinge
https://www.joyn.de/play/serien/yes-we-camp/5-2-camping-auf-dem-dem-boot-das-abenteuer...er-wendt-girls
https://www.joyn.de/play/serien/yes-we-camp/5-3-unterwegs-mit-dem-luxus-penthouse-auf-raedern
https://www.joyn.de/play/serien/yes-we-camp/5-4-maennerurlaub-an-der-ostsee
https://www.joyn.de/play/serien/yes-we-camp/5-5-schon-vorbei-schade-urlaubsende-in-italien -
I will download them and send you a pm. Because you need a premium acc for the playlist.
Edit: check your dm's links sendLast edited by Lostion; 17th Aug 2024 at 21:59.
-
Ok guys signature works thanks a lot! But now next there is the next problem
Failed to retrieve entitlement token: [{'code': 'ENT_AgeVerificationSetupRequired', 'msg': "If you want to watch videos or channels with parental controls, you must first set a parental control PIN. You can't do this here, only in a web browser. Then set the PIN in the Joyn account area."}]
Is there a way to add the login function?
i think its something like that here?
Code:params = dict(username=username, password=password, requestId=request_id) a, b = request_helper.get_url(url='https://auth.7pass.de/login-srv/login', config=self.config, post_data=params, cookie_file=cookie_file, return_final_url=True, no_cache=True)
login.txt
Email
PW
PIN
Is that possible to do? Sorry for the many questions its just like the first time with APIs and Python etc. Its a lot to understand and learn but my premium has 6 days left and i want to play around with the payload like @aqzs did. Would be nice if somebody could help me out, i will ofcourse post the finished .py script at the end -
Yes, ofcourse it's possible; but it's a little tricky.
It seems you need to use the httpx module instead of the requests module, because joyn is hosted at Cloudflare and uses anti bot measures; so HTTP/2 protocol is mandatory, capturing cookies as well, maybe more headers like User-Agent.
For your personal use it is in my opinion much easier to once capture the JSON return of the token endpoint https://auth.joyn.de/auth/7pass/token, save the JSON, don't use the browser session anymore, and work with the saved tokens.
Below is what I use in my yt-dlp plugin:
Code:def _get_token(self): cached = self.cache.load('joyn', 'token') if cached: expire, client_id, client_name = traverse_obj( cached['access_token'], ({jwt_decode_hs256}, ('exp', 'cId', 'cN'))) if time.time() > expire: self._ACCESS_TOKEN = self._refresh_token(client_id, client_name, cached['refresh_token']) else: self._ACCESS_TOKEN = cached['access_token'] if not self._ACCESS_TOKEN: # refresh token is valid for 180 days and I'm therefore # hesitant to implement 7pass authentication raise ExtractorError('Login reuqired.') self.decryption_keys = None def _refresh_token(self, client_id, client_name, refresh_token): data = { 'client_id': client_id, 'client_name': client_name, 'grant_type': 'Bearer', 'refresh_token': refresh_token } headers = { 'content-type': 'application/json', 'joyn-client-version': self._ACCESS_TOKEN, 'joyn-country': 'DE', 'joyn-distribution-tenant': 'JOYN', 'joyn-platform': 'web', 'joyn-request-id': f'{uuid.uuid4()}', 'origin': 'https://www.joyn.de', } token = self._download_json( 'https://auth.joyn.de/auth/refresh', None, note='Refresh access token', headers=headers, data=json.dumps(data).encode('ascii')) self.cache.store('joyn', 'token', token) return token['access_token']
-
hm i don't understand where the login is? I need to send the Username and the pw to joyn or? because in your code i only see the "https://auth.joyn.de/auth/refresh" but how can i like "login" to joyn first?
-
As I wrote:
Log in with the browser, in developer mode search the network traffic for the mentionend url, and save the JSON response for later use in your script. Don't use this browser session anymore (so it's best to do this in browsers private mode), that ensures you can use the refresh token in your script. An access token is valid for one hour, refresh token is valid for 180 days.
The JSON object looks like (formatted)
Code:{ "access_token": "eyJh...", "refresh_token": "eyJh...", "token_type": "Bearer", "expires_in": 3600000 }
Last edited by Obo; 18th Aug 2024 at 14:27.
-
--[----->+<]>.++++++++++++.---.--------.
[*drm mass downloader: widefrog*]~~~~~~~~~~~[*how to make your own mass downloader: guide*] -
Yes, that would be great. I guess the sequence that the Kodi plugin uses doesn't really work (anymore probably). The sequence that doesn't work for me is:
- https://auth.joyn.de/sso/endpoints
- web-login with added redirect_uri
- https://auth.7pass.de/registration-setup-srv/public/list
- https://auth.7pass.de/users-srv/user/checkexists/
- https://auth.7pass.de/verification-srv/v2/setup/public/configured/list
- https://auth.7pass.de/login-srv/login
- redeem-token
What really don't understand is why this sequence sometimes works in debugger but almost never without.
My HAR has some additional requests, that I've not yet added to the sequence. -
access token method worked but i still dont get 1080p for non joyn productions
some interesting error i found:
Code:Failed to retrieve playlist: 400 {'issues': [{'received': 'joyn_app', 'code': 'invalid_enum_value', 'options': ['android-tv', 'android', 'browser', 'chromecast', 'fire-tv', 'hisense', 'ios', 'ipados', 'kepler', 'netrange', 'panasonic', 'philips', 'playstation', 'sky-q', 'tivo', 'tizen', 'tvos', 'vestel', 'webos'], 'path': ['body', 'platform'], 'message': "Invalid enum value. Expected 'android-tv' | 'android' | 'browser' | 'chromecast' | 'fire-tv' | 'hisense' | 'ios' | 'ipados' | 'kepler' | 'netrange' | 'panasonic' | 'philips' | 'playstation' | 'sky-q' | 'tivo' | 'tizen' | 'tvos' | 'vestel' | 'webos', received 'joyn_app'"}]}
-
Ok then. This is for joyn.de . Haven't used any paid account or VPN. Only the basic email/password
Code:from urllib.parse import parse_qs, urlparse import requests EMAIL = "YOUR_EMAIL" PASSWORD = 'YOUR_PASSWORD' response = requests.get( 'https://auth.joyn.de/sso/endpoints', params={ 'client_id': 'client_id', 'client_name': 'web' } ).json() response = response["web-login"] response = parse_qs(urlparse(response).query) client_id = response["client_id"][0] response = requests.get( 'https://auth.7pass.de/authz-srv/authz', params={ 'client_id': client_id, 'response_type': 'code' }, allow_redirects=False ) response = response.headers["location"] response = parse_qs(urlparse(response).query) request_id = response["requestId"][0] response = requests.post( 'https://auth.7pass.de/login-srv/login', data={ 'username': EMAIL, 'requestId': request_id, 'password': PASSWORD }, allow_redirects=False ) response = response.headers["location"] response = parse_qs(urlparse(response).query) code = response["code"][0] response = requests.post( 'https://auth.joyn.de/auth/7pass/token', json={ 'code': code, 'client_id': client_id, 'redirect_uri': 'https://www.joyn.de/oauth', 'tracking_name': 'web' } ) print(response.json())
Edit: I spammed that script for a while, and if you do too many logins, it may crash, so be careful--[----->+<]>.++++++++++++.---.--------.
[*drm mass downloader: widefrog*]~~~~~~~~~~~[*how to make your own mass downloader: guide*] -
Can someone point me out how to get the video id for
https://www.joyn.de/play/playlist/1018628
There is no video id in the header :/
Edit:
https://www.joyn.de/play/highlight/serien/ran-fussball-baller-league-dp4mjk4u2ejx/zusa...ung-spieltag-4
Now it works its just a playlist with different videos one after another.Last edited by Lostion; 18th Aug 2024 at 19:19.
-
Guys i have some good news:
https://www.joyn.de/play/serien/the-irrational-kriminell-logisch/1-1-erinnerungsluecken
Audio EAC3 256.
with
Code:'manufacturer': 'unknown', 'platform': 'browser', 'maxSecurityLevel': 1, 'streamingFormat': 'dash', 'model': 'unknown', 'protectionSystem': 'widevine', 'enableDolbyAudio': False, 'enableSubtitles': True, 'maxResolution': 1080, 'variantName': 'default', 'version': 'v1'
the trick is
Code:'manufacturer': 'unknown', 'platform': 'browser', 'maxSecurityLevel': 1, 'streamingFormat': 'dash', 'model': 'unknown', 'protectionSystem': 'widevine', 'enableDolbyAudio': True, 'enableSubtitles': True, 'maxResolution': 1080, 'variantName': 'default', 'version': 'v2'
Edit:
Code:data = json.dumps({ 'manufacturer': 'unknown', 'platform': 'tvos', 'maxSecurityLevel': 5, 'streamingFormat': 'dash', 'model': 'unknown', 'protectionSystem': 'widevine', 'enableDolbyAudio': True, 'enableSubtitles': True, 'maxResolution': 2160, 'variantName': 'default', 'version': 'v2' }, separators=(',', ':'))
atleast with Premium Account. Not sure about Free Acc.
Edit done testing with Premium:
FullHD/EAC3 = 'android-tv', 'android', 'chromecast', 'fire-tv', 'hisense', 'ios', 'ipados', 'kepler', 'netrange', 'panasonic', 'philips', 'playstation', 'sky-q', 'tivo', 'tizen', 'tvos', 'vestel', 'webos'
SD/EAC3 = 'browser'
Some also gives you Stereo + EAC3 5.1 some not will look into that later.Last edited by Lostion; 18th Aug 2024 at 20:37.
-
Thanks for the script!
Yeah, that may have happended for me - it doesn't work with my login right now. I guess Cloudflare measures are active here. I'll wait for a week (or longer) and try again.
Nice findings! But in result unfortunately for free accounts only more and different audio options, not the higher resolution. -
Did joyn change something?
raise RequestsJSONDecodeError(e.msg, e.doc, e.pos)
requests.exceptions.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
my script isn't working anymore oO but it did 5 hours ago wtf.
Edit:
Error: Received status code 404 when fetching the URL.
Failed to retrieve content details.
wtf -.-
def getid(videourl):
if 'https://www.joyn.de' in videourl:
slug = videourl.replace('https://www.joyn.de/', '')
data = requests.get(f'https://www.joyn.de/_next/data/-ED4ji1a6D8khV7D7beLb/{slug}.json').json()
isnt working anymore it just shows 404
Edit: fixxed it with another method hope it works more.Last edited by Lostion; 19th Aug 2024 at 06:49.
-
--[----->+<]>.++++++++++++.---.--------.
[*drm mass downloader: widefrog*]~~~~~~~~~~~[*how to make your own mass downloader: guide*]
Similar Threads
-
How can i decrypt a stream from Joyn?
By PyNoob in forum Video Streaming DownloadingReplies: 7Last Post: 29th Jan 2025, 04:48 -
DOWNLOAD protected DRM video separated video and audio from (learnyst)
By yassin in forum Video Streaming DownloadingReplies: 4Last Post: 8th Jan 2024, 04:36 -
Cannot download encrypted m3u8 video, the video works on the website
By krestek in forum Video Streaming DownloadingReplies: 6Last Post: 21st Feb 2022, 14:27 -
Download HLS video using FFMPEG with separate video and audio URLs?
By oschrndz in forum Video Streaming DownloadingReplies: 2Last Post: 15th Dec 2020, 13:53 -
how to download a portion of video from m3u8 that has separate video audio.
By adi111 in forum Video Streaming DownloadingReplies: 8Last Post: 5th Aug 2020, 14:53