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 7 of 7
  1. Banned
    Join Date
    Apr 2022
    Location
    Hong Kong
    Search Comp PM
    Code:
    --key 92be0da472f63d9f85b9f0b9e6df5a81:2848888ff5bc5a104cde898cbc878c6f
    Quote Quote  
  2. Originally Posted by CrymanChen View Post
    Code:
    --key 92be0da472f63d9f85b9f0b9e6df5a81:2848888ff5bc5a104cde898cbc878c6f
    Thank you. It's not working for me. Is it working for you? Anyway, what steps did you take to get that key?
    Quote Quote  
  3. Hello, do you need the keys? Directv usually changes the key daily on some channels, what I don't know is the time at which it changes.
    Maybe the same thing happens in this one and it changes every day.
    To get the key, you need an android user-agent
    Quote Quote  
  4. Feels Good Man 2nHxWW6GkN1l916N3ayz8HQoi's Avatar
    Join Date
    Jan 2024
    Location
    Pepe Island
    Search Comp PM
    Here is the downloader for https://www.directvgo.com . Only for free content. Needs Argentinian IP, cdm, and an account (email/password).

    Code:
    import json
    import os.path
    import re
    import time
    from os import devnull
    
    import fake_useragent
    import requests
    from pywidevine.cdm import Cdm
    from pywidevine.device import Device
    from pywidevine.pssh import PSSH
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.firefox.service import Service
    from selenium.webdriver.support import expected_conditions as exp_cond
    from selenium.webdriver.support.ui import WebDriverWait
    
    BURNER_ACCOUNT_EMAIL = "YOUR_EMAIL"
    BURNER_ACCOUNT_PASSWORD = "YOUR_PASSWORD"
    WVD_FILE = "device_wvd_file.wvd"
    REGION = 'ssla'
    
    USE_CACHE_TOKENS = True
    CACHE_TOKENS = os.path.join(".", "directvgo_cache_tokens.txt")
    CACHE_DEVICE_ID = os.path.join(".", "directvgo_cache_device_id_do_not_delete.txt")
    RESOLUTION_PRIORITY = {"SD-HD": 1}
    SELENIUM_TIMEOUT = 10
    USER_AGENT = fake_useragent.UserAgent(os="windows").chrome
    
    if not os.path.isfile(CACHE_DEVICE_ID):
        print(
            "[WARNING] Always use a burner account (one that you don't mind losing in case things go bad).\n"
            "[WARNING] Do not delete the directvgo cache device id txt file unless you want to use a different account.\n"
            "[WARNING] Do not be logged into the burner account with your browser or any other device.\n"
        )
        answer = input("Write yes if you understand and agree: ")
        if answer != "yes":
            exit(0)
        print()
    
    AUTH_URL = 'https://sm-dgo.vrioservices.com/v2/oauth2/token'
    PLAYBACK_URL = 'https://api.directvgo.com/entitlement/v3/playback/token/authorizer'
    LOGIN_PAGE = "https://www.directvgo.com/ar/acceder-ar"
    ORIGIN = 'https://www.directvgo.com'
    
    
    def file_to_dict(file_path):
        try:
            with open(file_path, 'r') as file:
                content = file.read().strip()
                for ch in [" ", "\n", "\t"]:
                    content = content.replace(ch, "")
    
                if not content:
                    raise
                return json.loads(content)
        except:
            return None
    
    
    def manual_captcha():
        driver = None
        try:
            driver = webdriver.Firefox(service=Service(log_path=devnull))
            driver.get(LOGIN_PAGE)
    
            for field in ["email", "password"]:
                WebDriverWait(driver, SELENIUM_TIMEOUT).until(
                    exp_cond.presence_of_element_located((By.ID, field))
                ).send_keys("sample_text")
            WebDriverWait(driver, SELENIUM_TIMEOUT).until(
                exp_cond.presence_of_element_located((By.ID, "validation"))
            ).click()
    
            g_recaptcha_response = WebDriverWait(driver, SELENIUM_TIMEOUT).until(
                exp_cond.presence_of_element_located((By.ID, "g-recaptcha-response"))
            )
            WebDriverWait(driver, 12 * SELENIUM_TIMEOUT).until(
                lambda drv: drv.find_element(
                    By.ID, "g-recaptcha-response"
                ).get_attribute("value").strip() != ""
            )
    
            g_recaptcha_response = g_recaptcha_response.get_attribute("value")
            device_id = file_to_dict(CACHE_DEVICE_ID)
    
            if device_id is None or device_id.get("device_id", None) is None:
                try:
                    WebDriverWait(driver, SELENIUM_TIMEOUT).until(
                        exp_cond.invisibility_of_element_located((By.XPATH, "//iframe[@title=reCAPTCHA]"))
                    )
                    time.sleep(1)
    
                    device_id = driver.execute_script('return localStorage;')["deviceId"]
                except:
                    print("Failed to get new device id")
                    raise
            else:
                device_id = device_id["device_id"]
    
            driver.quit()
            driver = None
        except Exception as exc:
            if driver is not None:
                driver.quit()
            print("Something went wrong with Selenium Firefox.")
            raise exc
    
        with open(CACHE_DEVICE_ID, 'w') as f:
            f.write(json.dumps({"device_id": device_id}))
        response = json.loads(requests.post(
            AUTH_URL, headers={
                'x-client-type': 'web', 'Origin': ORIGIN,
                'x-device-id': device_id
            },
            json={
                'grantType': 'password', 'region': REGION,
                'password': BURNER_ACCOUNT_PASSWORD,
                'username': BURNER_ACCOUNT_EMAIL,
                'g-recaptcha-response': g_recaptcha_response
            }
        ).content.decode())
    
        if response.get("details", "") == "INVALID_CREDENTIALS" or "region" in response.get("message", ""):
            print(
                "Wrong email/password/region.\n"
                "If you are sure the credentials are correct, then switch the region value.\n"
                "It has to be either ssla or br."
            )
            exit(0)
    
        try:
            response = {"id_token": response["id_token"]}
        except:
            print(f"Unknown auth login response format: {str(response)}. Try again.")
            exit(0)
        with open(CACHE_TOKENS, 'w') as f:
            f.write(json.dumps(response))
        return response
    
    
    def get_account_tokens():
        if not USE_CACHE_TOKENS:
            return manual_captcha()
        tokens = file_to_dict(CACHE_TOKENS)
        if tokens is None:
            return manual_captcha()
        return tokens
    
    
    ACC_TOKENS = get_account_tokens()
    
    
    def get_keys(pssh_value, license_url, auth):
        if pssh_value is None:
            return []
        try:
            device = Device.load(WVD_FILE)
        except:
            return []
    
        pssh_value = PSSH(pssh_value)
        cdm = Cdm.from_device(device)
        cdm_session_id = cdm.open()
    
        challenge = cdm.get_license_challenge(cdm_session_id, pssh_value)
        licence = requests.post(
            license_url, data=challenge,
            headers={
                'User-Agent': USER_AGENT,
                'authorization': f"Bearer {auth}"
            }
        )
        licence.raise_for_status()
        cdm.parse_license(cdm_session_id, licence.content)
    
        keys = []
        for key in cdm.get_keys(cdm_session_id):
            if "CONTENT" in key.type:
                keys += [f"{key.kid.hex}:{key.key.hex()}"]
        cdm.close(cdm_session_id)
        return keys
    
    
    def get_video_data(source_url):
        content_id = re.search(r"/player/[^/]+/([^/]+)", source_url).group(1)
        response = requests.post(
            PLAYBACK_URL, headers={
                'Authorization': f'Bearer {ACC_TOKENS["id_token"]}',
                'x-client-id': 'web', 'Origin': ORIGIN
            }, json={
                'delay': 0, 'mobileNetwork': False, 'contentId': content_id,
                'isLive': "/player/live/" in source_url
            }
        ).content.decode()
    
        if "403 ERROR" in response:
            print("VPN Failure. Use Argentinian IP.")
            exit(0)
        response = json.loads(response)
    
        if ("SCLPU" in response.get("details", "") or "slot" in response.get("message", "")) and \
                response.get("code", "") == "403":
            print(
                f"Bad manifest response format: {str(response)}\n"
                "You have no more slots available in your account.\n"
                "Make a new burner account and delete both the device id and tokens cache files."
            )
            exit(0)
    
        try:
            license_url = [(m["licenseUrl"], m["quality"]) for m in response["drms"]["widevine"]]
            license_url = sorted(license_url, key=lambda m: RESOLUTION_PRIORITY[m[1]], reverse=True)[0][0]
            manifest = response["manifest"]["dash"]["primary"]
            auth = response["authorization"]
        except:
            print(
                f"Unknown manifest response format: {response} for: {source_url}\n"
                "Possible solution: delete the directvgo cache tokens txt file.\n"
            )
            exit(0)
    
        try:
            pssh_value = str(min(re.findall(
                r'<cenc:pssh\b[^>]*>(.*?)</cenc:pssh>',
                requests.get(manifest).content.decode()
            ), key=len))
        except:
            return manifest, None, None, None
        return manifest, pssh_value, license_url, auth
    
    
    def get_download_command(source_url):
        manifest, pssh, license_url, auth = get_video_data(source_url)
        keys = get_keys(pssh, license_url, auth)
    
        if len(keys) == 0:
            if pssh is not None:
                return f"Need local CDM (in WVD format) for {source_url}"
            return f'N_m3u8DL-RE.exe "{manifest}" -M format=mkv'
        return f'N_m3u8DL-RE.exe "{manifest}" {" ".join([f"--key {k}" for k in keys])} -M format=mkv'
    
    
    SOURCE_URLS = [
        "https://www.directvgo.com/player/vod/4ea45bd5-3823-4fe3-879c-91b15f325188",
        "https://www.directvgo.com/player/vod/ec9673b0-df38-448a-9197-ced0139438be",
        "https://www.directvgo.com/player/episode/c89081cb-4824-4f9d-8da8-7078cc8b0d57",
        "https://www.directvgo.com/player/episode/265d63e7-78a2-448c-88d4-582bff348c3a",
        "https://www.directvgo.com/player/live/CH0100000000354",
        "https://www.directvgo.com/player/live/CH0100000000331",
    ]
    
    for s in SOURCE_URLS:
        print(get_download_command(s))
    Output (the mpd URLs which contained tokens or private information were shortened):
    Code:
    N_m3u8DL-RE.exe "https://vod-ssla.dtvott.com/D001619686/manifest_03.mpd?da=0" --key 015aa2f9f79b5891acc5f016dd9ecd4b:e91aa4d95143b327c56cd584a8238c7a -M format=mkv
    N_m3u8DL-RE.exe "https://vod-ssla.dtvott.com/D001619687/manifest_03.mpd?da=0" --key a2372d5dada3578fb9665a91f5c490e5:cf45cb061e470da27da323d88e5d02dc -M format=mkv
    N_m3u8DL-RE.exe "https://vod-ssla.dtvott.com/D001620553/manifest_03.mpd?da=0" --key 29333da456355d3897ea5c82841e2690:a34319f84ec2258cf5fdadabd3f0550a -M format=mkv
    N_m3u8DL-RE.exe "https://vod-ssla.dtvott.com/D001624393/manifest_03.mpd?da=0" --key 0e21a0aedd395b23b79e04d7e19abd5b:f2c79627c960f588019a9e15c43a3ed1 -M format=mkv
    N_m3u8DL-RE.exe "https://dtvott-vos-mt.akamaized.net/v1/dash/f761a4100cd1d44ec919aa9543945ae7f63d684e/live_1289/DASH/manifest.mpd?hdnts=exp..." --key 92be0da472f63d9f85b9f0b9e6df5a81:2848888ff5bc5a104cde898cbc878c6f -M format=mkv
    N_m3u8DL-RE.exe "https://1276-vos-da-mt.dtvott.com/v1/dash/f761a4100cd1d44ec919aa9543945ae7f63d684e/live_1276/DASH/manifest.mpd?da=0&country=..." --key bc47ff5936b73ecc9005605b53d2debc:a356ca9d055e8d1445d296040bf4fb77 --key cf1689d5979f5765a5b8dcb236e69136:9cfa3ff0897a000929b66ffd7e01c08a -M format=mkv
    Some of the sample URLs used in the demo may not work anymore because the content was available only temporarily, so make sure you can actually watch what you want to download. The download commands can be used without a VPN so don't forget to turn it off to avoid wasting data on the content download. For the livestream download commands you might wanna add other parameters according to what you need. You can read more on the n_m3u8 GitHub page.

    Some things worth mentioning:
    1. The site uses Google Recaptcha v2 when you log into your account. And to download the videos you need some tokens that you obtain when you log in. The token obtained has a lifetime of 2 days so you need to manually complete a Google Recaptcha when the script needs a new account token. On top of that, if you're using a VPN, you may need a new token when using a different IP because the token is somehow tied to your IP. The obtained token is saved in a cache text file so it can be reused but still needs to be updated when it expires.

    As a side note, the recaptcha could in theory be fully automated using audio text extraction tools but it would be just a temporary solution. This is Google after all and they constantly update their bot detection stuff. If someone wants to give it a shot just Google "python google recaptcha v2 solver" and you'll get a bunch of GitHub repos.

    2. The site allows a specific maximum number of devices per account to be logged into simultaneously. The solution was to use only one slot for the downloader script so you won't "lock" your account. The device ID is kept in a cache text file and is always reused when logging in. Warning: do not delete it or you may end up using too many devices with that account and you're gonna get it blocked from viewing any content. Also, I would advise you not to use your real account with the script (just make a new one since their registration mechanism is a joke). If you wreck your main account by misusing this script, that's on you.

    Edit: I won't be maintaining this script because their tokens are a mess. I will leave however the script in case someone wants to improve it.
    Last edited by 2nHxWW6GkN1l916N3ayz8HQoi; 26th Apr 2024 at 08:40.
    --[----->+<]>.++++++++++++.---.--------.
    [*drm mass downloader: widefrog*]~~~~~~~~~~~[*how to make your own mass downloader: guide*]
    Quote Quote  
  5. Member aqzs's Avatar
    Join Date
    Mar 2024
    Location
    Paris
    Search Comp PM
    Hey 2nHxWW6GkN1l916N3ayz8HQoi;2730785 you are in fire today ! Many thanks for all your scripts and what you are doing to help the community
    Quote Quote  
  6. Member
    Join Date
    Jul 2023
    Location
    Argentina
    Search Comp PM
    Does anyone know what kind of encryption or encoding this is? I've seen several URLs of transmissions like this.

    "name": "DISCOVERY WORLD OPC 2(PROBAR SI NO FUNCIONA LA OTRA)",
    "uri": "c8dznZikVfQgSCCQfde+2u1yM09Zlih8lt0hzHRASmWI//sWr+g4URsoxkm6n2U2J5DJ4cMcUBjWres/JDZRDA==",
    "drm_scheme": "clearkey",
    "drm_license_uri": "vYfcFq8IZDpPY58GvPpH4ekp2nhBmoIFEPas5PivksNpmCrdv pTZx5w4WvnxhlL5pwyu8Np713tulF1ZBRm9fgXndnhUQiOctrC 7zP0oVV96t+S7slzJte4CVKEDqJNHb0PP0MH67w8XbjEQCo3VK HYSyzJt7FNkUV0QOeoZUU4=",
    Quote Quote  



Similar Threads

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