VideoHelp Forum




+ Reply to Thread
Results 1 to 21 of 21
  1. Member
    Join Date
    Apr 2024
    Location
    World
    Search PM
    Hey

    I've dumped my own L3 CDM using the sticky and would like to use WKS-KEYS to download an mpd-stream.

    I got the mpd by using The Stream Detector:

    Code:
    https://video.dpgmedia.net/out/v1/24feb673b0094cc4b52f5198611d4bff/eb76c933215848938246f10a6161dc4b/75aa63df75e944e0aeafbdbcbf096fc1/index.mpd?aws.manifestfilter=subtitle_language%3Azzz%3Btrickplay_type%3Aiframe
    In there, I see a PSSH that starts with 'AAA' instead of what I usually see ('AAAA').

    Image
    [Attachment 78154 - Click to enlarge]


    I threw it in Axinom's PSSH Box Decoder and found the following:


    Code:
    <WRMHEADER xmlns="http://schemas.microsoft.com/DRM/2007/03/PlayReadyHeader" version="4.0.0.0">
      <DATA>
        <PROTECTINFO>
          <KEYLEN>16</KEYLEN>
          <ALGID>AESCTR</ALGID>
        </PROTECTINFO>
        <KID>QFtmU21Z/DGcI2q54BVAEQ==</KID>
        <LA_URL>https://lic.drmtoday.com/license-proxy-headerauth/drmtoday/RightsManager.asmx</LA_URL>
        <LUI_URL>https://playready-ui.example.com</LUI_URL>
        <CHECKSUM>MqlqXxjeHqQ=</CHECKSUM>
      </DATA>
    </WRMHEADER>
    The license seems to correspond with what I found, so that's good, but how do I get to a PSSH-key l3.py can use?


    Thanks!
    Quote Quote  
  2. widevine pssh in mpd too, it is short one, and found 4 cached key

    pssh:
    Code:
    AAAAenBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAFoIARIQJPbPlaOSMOGxqDSVInr4fiJEZXlKaGMzTmxkRWxrSWpvaU0yVTNOV1psT0RrdE5qQmxZUzAwTldReExUaGtZVFl0TWpBM01tWmxPRGsyT1dReEluMD0=
    keys:
    Code:
    24f6cf95a39230e1b1a83495227af87e:3da21a12603fdd5fa28cfa29305595d1
    b2705e5a25c0384b97b3301cd3743b9c:119285e7c033ea0f7012e624880a6135
    53665b40596d31fc9c236ab9e0154011:51aa899b9b5dc13058bec17b1b3ae734
    e48393f7b84036c0bec386437ad2854c:76ac2cc1142f4d88aab8ed0f406aa265
    Last edited by shellcmd; 5th Apr 2024 at 05:30.
    Quote Quote  
  3. Member aqzs's Avatar
    Join Date
    Mar 2024
    Location
    Paris
    Search Comp PM
    You selected the wrong pssh it's the one 3 lines above :

    Code:
    AAAAenBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAFoIARIQJPbPlaOSMOGxqDSVInr4fiJEZXlKaGMzTmxkRWxrSWpvaU0yVTNOV1psT0RrdE5qQmxZUzAwTldReExUaGtZVFl0TWpBM01tWmxPRGsyT1dReEluMD0=
    Here is a minimal python script to get the key :

    Code:
    from pywidevine.cdm import Cdm
    from pywidevine.device import Device
    from pywidevine.pssh import PSSH
    import requests
    
    headers = {
        'Accept': '*/*',
        # 'x-dt-auth-token': 'XXXXX'
    }
    
    pssh = input("PSSH? ")
    pssh = PSSH(pssh)
    lic_url = input("License URL? ")
    
    device = Device.load(r"device.wvd")
    cdm = Cdm.from_device(device)
    session_id = cdm.open()
    challenge = cdm.get_license_challenge(session_id, pssh)
    licence = requests.post(lic_url, headers = headers, data=challenge)
    licence.raise_for_status()
    cdm.parse_license(session_id, licence.content)
    for key in cdm.get_keys(session_id):
        if key.type=='CONTENT':
            print(f"\n--key {key.kid.hex}:{key.key.hex()}")
    cdm.close(session_id)
    You have to give the right path for device.wvd (and provide pssh and licence server when prompted). If you need a special header grab it with right clic on the request then copy as ccurl and then https://curlconverter.com/
    Quote Quote  
  4. If you only have a PR pssh you can convert it using pywidevine:
    Code:
    from pywidevine.pssh import PSSH
    
    wv = PSSH("<PR PSSH HERE>")
    wv.to_widevine()
    Quote Quote  
  5. Member
    Join Date
    Apr 2024
    Location
    World
    Search PM
    Thanks a lot for the help already!

    I'm working with the following code now:

    Code:
    from pywidevine.cdm import Cdm
    from pywidevine.device import Device
    from pywidevine.pssh import PSSH
    import requests
    
    headers = {
        'Accept': '*/*',
        'x-dt-auth-token':'<snip>',
        'Connection': 'keep-alive',
        'Sec-Fetch-Dest': 'empty',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Site': 'cross-site',
        'Content-Type': 'application/x-www-form-urlencoded',
    }
    
    pssh = input("PSSH? ")
    pssh = PSSH(pssh)
    lic_url = input("License URL? ")
    
    device = Device.load(r"google_aosp_on_ia_emulator_14.0.0_<snip>_l3.wvd")
    cdm = Cdm.from_device(device)
    session_id = cdm.open()
    challenge = cdm.get_license_challenge(session_id, pssh)
    licence = requests.post(lic_url, headers = headers, data=challenge)
    licence.raise_for_status()
    cdm.parse_license(session_id, licence.content)
    for key in cdm.get_keys(session_id):
        if key.type=='CONTENT':
            print(f"\n--key {key.kid.hex}:{key.key.hex()}")
    cdm.close(session_id)

    But I keep getting back a 400 from the license server.

    Code:
    Traceback (most recent call last):
      File "/Users/<snip>/VideoStuff/streamz.py", line 30, in <module>
        licence.raise_for_status()
      File "/usr/local/lib/python3.9/site-packages/requests/models.py", line 1021, in raise_for_status
        raise HTTPError(http_error_msg, response=self)
    requests.exceptions.HTTPError: 400 Client Error:  for url: https://lic.drmtoday.com/license-proxy-widevine/cenc/?specConform=true
    Is there anything else I'm missing?

    Thanks!
    Quote Quote  
  6. Are you able to print license.text before 'raise_for_status'?
    I'd remove that anyway because it's only meant for debugging
    Quote Quote  
  7. Member
    Join Date
    Apr 2024
    Location
    World
    Search PM
    I can, but it's also giving me a 400.

    Code:
    <!doctype html><html lang="en"><head><title>HTTP Status 400 – Bad Request</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 400 – Bad Request</h1></body></html>
    I guess I need some dt-custom-data, but I'm trying to figure out what exactly.
    Quote Quote  
  8. Originally Posted by Bartie__ View Post
    Thanks a lot for the help already!

    I'm working with the following code now:

    Code:
    from pywidevine.cdm import Cdm
    from pywidevine.device import Device
    from pywidevine.pssh import PSSH
    import requests
    
    headers = {
        'Accept': '*/*',
        'x-dt-auth-token':'<snip>',
        'Connection': 'keep-alive',
        'Sec-Fetch-Dest': 'empty',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Site': 'cross-site',
        'Content-Type': 'application/x-www-form-urlencoded',
    }
    
    pssh = input("PSSH? ")
    pssh = PSSH(pssh)
    lic_url = input("License URL? ")
    
    device = Device.load(r"google_aosp_on_ia_emulator_14.0.0_<snip>_l3.wvd")
    cdm = Cdm.from_device(device)
    session_id = cdm.open()
    challenge = cdm.get_license_challenge(session_id, pssh)
    licence = requests.post(lic_url, headers = headers, data=challenge)
    licence.raise_for_status()
    cdm.parse_license(session_id, licence.content)
    for key in cdm.get_keys(session_id):
        if key.type=='CONTENT':
            print(f"\n--key {key.kid.hex}:{key.key.hex()}")
    cdm.close(session_id)

    But I keep getting back a 400 from the license server.

    Code:
    Traceback (most recent call last):
      File "/Users/<snip>/VideoStuff/streamz.py", line 30, in <module>
        licence.raise_for_status()
      File "/usr/local/lib/python3.9/site-packages/requests/models.py", line 1021, in raise_for_status
        raise HTTPError(http_error_msg, response=self)
    requests.exceptions.HTTPError: 400 Client Error:  for url: https://lic.drmtoday.com/license-proxy-widevine/cenc/?specConform=true
    Is there anything else I'm missing?

    Thanks!
    Only use the 'X-Dt-Auth-Token' header in your headers dict (although it's probably just the 'Content-Type' header added by curlconverter that breaks it). Also, to correctly parse the license, you'll probably need:
    Code:
    cdm.parse_license(session_id, json.loads(licence.content)['license'])
    Quote Quote  
  9. Member
    Join Date
    Apr 2024
    Location
    World
    Search PM
    Thanks white_snake.

    Removing everything but the x-dt-auth-token changes the 400 to a 403 (Unauthorised).

    Code:
    <Response [403]>
    <!doctype html><html lang="en"><head><title>HTTP Status 403 – Forbidden</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 403 – Forbidden</h1></body></html>
    Traceback (most recent call last):
      File "/Users/<snip>/VideoStuff/streamz.py", line 23, in <module>
        licence.raise_for_status()
      File "/usr/local/lib/python3.9/site-packages/requests/models.py", line 1021, in raise_for_status
        raise HTTPError(http_error_msg, response=self)
    requests.exceptions.HTTPError: 403 Client Error:  for url: https://lic.drmtoday.com/license-proxy-widevine/cenc/?specConform=true
    Getting closer
    Quote Quote  
  10. Originally Posted by Bartie__ View Post
    Thanks white_snake.

    Removing everything but the x-dt-auth-token changes the 400 to a 403 (Unauthorised).

    Code:
    <Response [403]>
    <!doctype html><html lang="en"><head><title>HTTP Status 403 – Forbidden</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 403 – Forbidden</h1></body></html>
    Traceback (most recent call last):
      File "/Users/<snip>/VideoStuff/streamz.py", line 23, in <module>
        licence.raise_for_status()
      File "/usr/local/lib/python3.9/site-packages/requests/models.py", line 1021, in raise_for_status
        raise HTTPError(http_error_msg, response=self)
    requests.exceptions.HTTPError: 403 Client Error:  for url: https://lic.drmtoday.com/license-proxy-widevine/cenc/?specConform=true
    Getting closer
    It's probably just expired then. If you're using the site I think you're using, it expires very very fast, probably less than 10 seconds.
    Quote Quote  
  11. Member
    Join Date
    Apr 2024
    Location
    World
    Search PM
    Indeed, had to be faster

    Got them:

    Code:
    --key 75e30eb062c84f81ab92e287ace41e3f:86196121dc0bbc42d6d0b1d6b8b52cdf
    --key b2705e5a25c0384b97b3301cd3743b9c:119285e7c033ea0f7012e624880a6135
    --key 24f6cf95a39230e1b1a83495227af87e:3da21a12603fdd5fa28cfa29305595d1
    --key e48393f7b84036c0bec386437ad2854c:76ac2cc1142f4d88aab8ed0f406aa265
    --key b00ac2f926293d45b2a99c96707df630:e66234c713daf860204f49f24031ad38
    --key 9556639230893d6bb48ea967ce3ad2c9:90874234820a7e3c5584ed0778005fbe
    --key 53665b40596d31fc9c236ab9e0154011:51aa899b9b5dc13058bec17b1b3ae734
    Thanks a lot everyone!
    Quote Quote  
  12. hello, to simplify things you can do this:
    Code:
    from pywidevine.cdm import Cdm
    from pywidevine.device import Device
    from pywidevine.pssh import PSSH
    import requests
    
    
    pssh = input("PSSH : ")
    pssh = PSSH(pssh)
    Token = input("x-dt-auth-token : ")
    lic_url = "https://lic.drmtoday.com/license-proxy-widevine/cenc/?specConform=true"
    
    headers = {'x-dt-auth-token': '{0}'.format(Token)}
    
    device = Device.load(r"google_aosp_on_ia_emulator_14.0.0_<snip>_l3.wvd")
    cdm = Cdm.from_device(device)
    session_id = cdm.open()
    challenge = cdm.get_license_challenge(session_id, pssh)
    licence = requests.post(lic_url, headers = headers, data=challenge)
    licence.raise_for_status()
    cdm.parse_license(session_id, licence.content)
    for key in cdm.get_keys(session_id):
        if key.type=='CONTENT':
            print(f"\n--key {key.kid.hex}:{key.key.hex()}")
    cdm.close(session_id)
    Quote Quote  
  13. Originally Posted by aqzs View Post
    You selected the wrong pssh it's the one 3 lines above :

    Code:
    AAAAenBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAFoIARIQJPbPlaOSMOGxqDSVInr4fiJEZXlKaGMzTmxkRWxrSWpvaU0yVTNOV1psT0RrdE5qQmxZUzAwTldReExUaGtZVFl0TWpBM01tWmxPRGsyT1dReEluMD0=
    Here is a minimal python script to get the key :

    Code:
    from pywidevine.cdm import Cdm
    from pywidevine.device import Device
    from pywidevine.pssh import PSSH
    import requests
    
    headers = {
        'Accept': '*/*',
        # 'x-dt-auth-token': 'XXXXX'
    }
    
    pssh = input("PSSH? ")
    pssh = PSSH(pssh)
    lic_url = input("License URL? ")
    
    device = Device.load(r"device.wvd")
    cdm = Cdm.from_device(device)
    session_id = cdm.open()
    challenge = cdm.get_license_challenge(session_id, pssh)
    licence = requests.post(lic_url, headers = headers, data=challenge)
    licence.raise_for_status()
    cdm.parse_license(session_id, licence.content)
    for key in cdm.get_keys(session_id):
        if key.type=='CONTENT':
            print(f"\n--key {key.kid.hex}:{key.key.hex()}")
    cdm.close(session_id)
    You have to give the right path for device.wvd (and provide pssh and licence server when prompted). If you need a special header grab it with right clic on the request then copy as ccurl and then https://curlconverter.com/
    Thanks for the code mate It's Perfectly working

    Can you please add a download function in this script to download with N_m3u8DL-RE?
    As I am not good with python so couldn't do it myself. I tried to take help of chatGPT but it didn't worked

    Here's sample command

    Code:
    N_m3u8DL-RE -M format=mkv -sa best -sv best --key key goes here "URL of manifest " --save-name filename
    Thanks
    Quote Quote  
  14. Member aqzs's Avatar
    Join Date
    Mar 2024
    Location
    Paris
    Search Comp PM
    Originally Posted by LittleSoldier View Post
    Can you please add a download function in this script to download with N_m3u8DL-RE?
    As I am not good with python so couldn't do it myself. I tried to take help of chatGPT but it didn't worked
    Code:
    from pywidevine.cdm import Cdm
    from pywidevine.device import Device
    from pywidevine.pssh import PSSH
    import requests
    import subprocess
    
    headers = {
        'Accept': '*/*',
        # 'x-dt-auth-token': 'XXXXX'
    }
    
    def getkey(pssh, lic_url):
        device = Device.load(r"device.wvd")
        cdm = Cdm.from_device(device)
        session_id = cdm.open()
        challenge = cdm.get_license_challenge(session_id, pssh)
        licence = requests.post(lic_url, headers = headers, data=challenge)
        licence.raise_for_status()
        cdm.parse_license(session_id, licence.content)
        keys = ''
        for key in cdm.get_keys(session_id):
            if key.type=='CONTENT':
                print(f"\n--key {key.kid.hex}:{key.key.hex()}")
                keys += f"\n--key {key.kid.hex}:{key.key.hex()}"
        cdm.close(session_id)
        return keys
    
    def dlN_m3u8DLRE(path, url_mpd, save_name, keys, debug):
        command = [path, '-M', 'format=mkv', '-sa', 'best', '-sv', 'best', url_mpd, '--save-name', f'{save_name}', keys]
        if debug:
            subprocess.run(command, check=True)
        else:
            subprocess.run(command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    pssh = input("PSSH? ")
    pssh = PSSH(pssh)
    lic_url = input("License URL? ")
    path = "N_m3u8DL-RE" # path to N_m3u8DL-RE
    url_mpd = input("Url of the manifest (.mpd) ")
    save_name = input("Save name? ")
    dlN_m3u8DLRE(path, url_mpd, save_name, getkey(pssh, lic_url), debug=False)
    You have to set the path of N_m3u8DL-RE (like C://Download/N_m3u8DL-RE.exe or /home/user/dl/N_m3u8DL-RE)
    You can turn debug=True if you want to get some prompt informations when running the script
    Quote Quote  
  15. Search, Learn, Download! Karoolus's Avatar
    Join Date
    Oct 2022
    Location
    Belgium
    Search Comp PM
    Originally Posted by Bartie__ View Post
    Indeed, had to be faster

    Got them:

    Code:
    --key 75e30eb062c84f81ab92e287ace41e3f:86196121dc0bbc42d6d0b1d6b8b52cdf
    --key b2705e5a25c0384b97b3301cd3743b9c:119285e7c033ea0f7012e624880a6135
    --key 24f6cf95a39230e1b1a83495227af87e:3da21a12603fdd5fa28cfa29305595d1
    --key e48393f7b84036c0bec386437ad2854c:76ac2cc1142f4d88aab8ed0f406aa265
    --key b00ac2f926293d45b2a99c96707df630:e66234c713daf860204f49f24031ad38
    --key 9556639230893d6bb48ea967ce3ad2c9:90874234820a7e3c5584ed0778005fbe
    --key 53665b40596d31fc9c236ab9e0154011:51aa899b9b5dc13058bec17b1b3ae734
    Thanks a lot everyone!

    VTMGo or Streamz by any chance?
    Quote Quote  
  16. Member aqzs's Avatar
    Join Date
    Mar 2024
    Location
    Paris
    Search Comp PM
    Originally Posted by Karoolus View Post
    VTMGo or Streamz by any chance?
    Streamz I think :
    In a previous message he sent :
    Traceback (most recent call last):
    File "/Users/<snip>/VideoStuff/streamz.py", line 23, in <module>
    Quote Quote  
  17. Search, Learn, Download! Karoolus's Avatar
    Join Date
    Oct 2022
    Location
    Belgium
    Search Comp PM
    Yeah I just found the episode he was after
    Quote Quote  
  18. [QUOTE=aqzs;2730533]
    Originally Posted by LittleSoldier View Post
    Can you please add a download function in this script to download with N_m3u8DL-RE?
    As I am not good with python so couldn't do it myself. I tried to take help of chatGPT but it didn't worked
    Code:
    from pywidevine.cdm import Cdm
    from pywidevine.device import Device
    from pywidevine.pssh import PSSH
    import requests
    import subprocess
    
    headers = {
        'Accept': '*/*',
        # 'x-dt-auth-token': 'XXXXX'
    }
    
    def getkey(pssh, lic_url):
        device = Device.load(r"device.wvd")
        cdm = Cdm.from_device(device)
        session_id = cdm.open()
        challenge = cdm.get_license_challenge(session_id, pssh)
        licence = requests.post(lic_url, headers = headers, data=challenge)
        licence.raise_for_status()
        cdm.parse_license(session_id, licence.content)
        keys = ''
        for key in cdm.get_keys(session_id):
            if key.type=='CONTENT':
                print(f"\n--key {key.kid.hex}:{key.key.hex()}")
                keys += f"\n--key {key.kid.hex}:{key.key.hex()}"
        cdm.close(session_id)
        return keys
    
    def dlN_m3u8DLRE(path, url_mpd, save_name, keys, debug):
        command = [path, '-M', 'format=mkv', '-sa', 'best', '-sv', 'best', url_mpd, '--save-name', f'{save_name}', keys]
        if debug:
            subprocess.run(command, check=True)
        else:
            subprocess.run(command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    pssh = input("PSSH? ")
    pssh = PSSH(pssh)
    lic_url = input("License URL? ")
    path = "N_m3u8DL-RE" # path to N_m3u8DL-RE
    url_mpd = input("Url of the manifest (.mpd) ")
    save_name = input("Save name? ")
    dlN_m3u8DLRE(path, url_mpd, save_name, getkey(pssh, lic_url), debug=False)
    You have to set the path of N_m3u8DL-RE (like C://Download/N_m3u8DL-RE.exe or /home/user/dl/N_m3u8DL-RE)
    You can turn debug=True if you want to get some prompt informations when running the script
    Got it

    I really appreciate your help
    Thanks
    Quote Quote  
  19. Member
    Join Date
    Apr 2024
    Location
    World
    Search PM
    Indeed - thanks all .

    First episodes are in. Working on subtitle automation now.
    I'm trying to make a comprehensive script to do it all now - start from browser URL, get auth tokens for drmtoday, get mpd, calculate keys, download, decrypt and subtitles.
    Quote Quote  
  20. Originally Posted by Bartie__ View Post
    Indeed - thanks all .

    First episodes are in. Working on subtitle automation now.
    I'm trying to make a comprehensive script to do it all now - start from browser URL, get auth tokens for drmtoday, get mpd, calculate keys, download, decrypt and subtitles.
    Interesting...
    Please keep us updated here with your progress with automated script
    Quote Quote  
  21. Member aqzs's Avatar
    Join Date
    Mar 2024
    Location
    Paris
    Search Comp PM
    Originally Posted by Bartie__ View Post
    Indeed - thanks all .

    First episodes are in. Working on subtitle automation now.
    I'm trying to make a comprehensive script to do it all now - start from browser URL, get auth tokens for drmtoday, get mpd, calculate keys, download, decrypt and subtitles.
    Quite easy to do, I did that for https://www.tf1.fr and every night it's getting new vids for a show. Keep up !
    Quote Quote  



Similar Threads

Visit our sponsor! Try DVDFab and backup Blu-rays!