VideoHelp Forum



Support our site by donate $5 directly to us Thanks!!!

Try StreamFab Downloader and download streaming video from Netflix, Amazon!



+ Reply to Thread
Results 1 to 2 of 2
  1. Hi everyone,

    I'm working on a Python script to retrieve Widevine keys from a VdoCipher stream. The script seems to work well up to a certain point: it successfully parses the player URL, finds the MPD and the PSSH.

    However, when I send the POST request to the license server (https://license.vdocipher.com/auth), I consistently get a 403 Forbidden error with the message: {"code":2048,"message":"Authentication failed"}.

    I've tried to structure the final JSON payload and headers to mimic what a browser would send, but I must be missing something. I've confirmed that the player URL I'm using as input is active and works correctly when opened in a browser.

    Any help in figuring out what's wrong with my license request would be greatly appreciated.


    I'm using a real WVD, extracted from a device.


    Code:
    https://player.vdocipher.com/v2/?otp=20160313versUSE32305v64nCQtNBmVl8bAvs0U57QsEOH1IkKIADm3TfM6vJ5FT&playbackInfo=eyJ2aWRlb0lkIjoiMGQ4YzczYmYzODBhNDE5NWEyYTZiMjk3ZWZjNzhjNjEifQ%3D%3D&player=r36ybRfkhOyoRUZa&primaryColor=7a2940

    Code:
    # Arquivo: from pywidevine.py (VERSÃO FINAL COM PAYLOAD CORRETO)
    from pywidevine.device import Device
    from pywidevine.pssh import PSSH
    from pywidevine.cdm import Cdm
    from bs4 import BeautifulSoup
    from urllib.parse import urlparse, parse_qs
    import requests
    import base64
    import json
    import re
    import sys
    
    # Lembre-se de manter o caminho com a barra inicial
    wv_CDM = "/content/samsung_sm-a556e_18.0.0@342415000_2305dd0b_34137_l3.wvd"
    
    input_url = input('Cole a URL COMPLETA do player (a que funciona no navegador): ')
    
    # Strip leading/trailing whitespace from the input URL
    input_url = input_url.strip()
    
    # Replace & with & in the input URL
    input_url = input_url.replace('&', '&')
    
    try:
        parsed_url = urlparse(input_url)
        query_params = parse_qs(parsed_url.query)
        otp = query_params.get('otp', [None])[0]
        playback_info = query_params.get('playbackInfo', [None])[0]
        if not otp or not playback_info:
            raise ValueError("A URL não contém 'otp' ou 'playbackInfo'")
    except Exception as e:
        print(f"\n[ERRO FATAL] A URL fornecida é inválida ou está incompleta: {e}")
        sys.exit()
    
    # Add print statements to inspect otp and playback_info
    print(f"\n[DEBUG] Extracted OTP: {otp}")
    print(f"[DEBUG] Extracted PlaybackInfo: {playback_info}")
    
    
    iframe_url = input_url
    
    headers_player = {
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
        'Accept-Language': 'pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3',
        'Connection': 'keep-alive',
        'Host': 'player.vdocipher.com',
        'Referer': iframe_url,
        'Sec-Fetch-Dest': 'iframe',
        'Sec-Fetch-Mode': 'navigate',
        'Sec-Fetch-Site': 'cross-site',
        'Upgrade-Insecure-Requests': '1',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:100.0) Gecko/20100101 Firefox/100.0',
    }
    
    print(f"\n[INFO] Buscando player: {iframe_url}")
    try:
        response = requests.get(iframe_url, headers=headers_player)
        response.raise_for_status()
        iframe = response.content.decode()
    except requests.exceptions.RequestException as e:
        print(f"\n[ERRO DE REDE] Falha ao acessar a URL do player: {e}")
        sys.exit()
    
    soup = BeautifulSoup(iframe, 'html.parser')
    script_tag = soup.find('script', {'type': 'application/json'})
    
    if script_tag is None:
        print("\n[ERRO FATAL] Não foi possível encontrar os metadados do vídeo.")
        print("Causa Probável: A URL/OTP expirou. Tente com uma nova URL.")
        sys.exit()
    
    mpd_path = json.loads(script_tag.string)
    mpd_link = mpd_path['dash']['manifest']
    
    try:
        get_pssh = re.search('<cenc:pssh>(.*)</cenc:pssh>', requests.get(mpd_link).text).group(1)
    except (AttributeError, requests.exceptions.RequestException):
        print(f"\n[ERRO FATAL] Não foi possível encontrar o PSSH no MPD: {mpd_link}")
        sys.exit()
    
    print('\n--- INFORMAÇÕES ENCONTRADAS ---')
    print('PSSH:', get_pssh)
    print(f'MPD: {mpd_link}\n')
    
    # --- DADOS PARA O PEDIDO DE LICENÇA CORRIGIDOS ---
    # Adicionamos 'href' e 'tech' para uma autorização válida
    json_data = {
        "otp": otp,
        "playbackInfo": playback_info,
        "href": input_url,
        "tech": "wv"
    }
    # --- FIM DA CORREÇÃO ---
    
    pssh = PSSH(get_pssh)
    device = Device.load(wv_CDM)
    cdm = Cdm.from_device(device)
    session_id = cdm.open()
    challenge = cdm.get_license_challenge(session_id, pssh)
    
    json_data["licenseRequest"] = base64.b64encode(challenge).decode("utf-8")
    
    # Encode the entire json_data object and put it under the 'token' key - Reverting to this structure based on the "request unwrapping failed" error and re-examining the likely correct structure
    token_payload = {"token": base64.b64encode(json.dumps(json_data).encode("utf-8")).decode("utf-8")}
    
    
    headers_license = {
        'Accept': '*/*',
        'Accept-Language': 'pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3',
        'content-type': 'application/json',
        'Host': 'license.vdocipher.com',
        'Origin': 'https://player.vdocipher.com',
        'Referer': iframe_url,
        'Sec-Fetch-Dest': 'empty',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Site': 'same-site',
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:100.0) Gecko/100101 Firefox/100.0', # Corrected User-Agent typo
        'vdo-sdk': 'VdoWeb/2.5.2'
    }
    
    print("[INFO] Solicitando licença...")
    # Sending json_data directly as the request body
    response = requests.post("https://license.vdocipher.com/auth", headers=headers_license, json=token_payload)
    if not response.ok:
        print(f'ERRO: {response}\n{response.text}')
        cdm.close(session_id)
        sys.exit()
    
    print("\n[SUCCESS] Licença recebida! Obtendo chaves...")
    license = base64.b64decode(response.json()["license"])
    cdm.parse_license(session_id, license)
    
    for key in cdm.get_keys(session_id):
        if key.type != 'SIGNING':
            print(f"✅ [KEY]: {key.kid.hex}:{key.key.hex()}")
    
    cdm.close(session_id)
    Quote Quote  
  2. Recently, VdoCipher started showing error 2074 (Please open in Android app) to license requests from Android CDMs, even in real browsers with OpenWV or Vineless. Probably makes no sense without something to do about integrity tokens.
    Quote Quote  



Similar Threads

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