mirror of
				https://github.com/adef17286-sudo/NPO_start_DL.git
				synced 2025-11-04 10:24:49 +00:00 
			
		
		
		
	Add files via upload
This commit is contained in:
		
							parent
							
								
									ebecfc57dc
								
							
						
					
					
						commit
						75da966da1
					
				
							
								
								
									
										89
									
								
								NPO.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								NPO.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
			
		||||
import requests
 | 
			
		||||
import re
 | 
			
		||||
import json
 | 
			
		||||
import argparse
 | 
			
		||||
 | 
			
		||||
def get_stream_url(url):
 | 
			
		||||
    if url.startswith("https://npo.nl/start/serie/") and url.endswith("/afspelen"):
 | 
			
		||||
        try:
 | 
			
		||||
            # Step 1: Get the JSON data
 | 
			
		||||
            response = requests.get(url)
 | 
			
		||||
            response.raise_for_status()
 | 
			
		||||
 | 
			
		||||
            match = re.search(r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>', response.text, re.DOTALL)
 | 
			
		||||
            if match:
 | 
			
		||||
                json_data = match.group(1)
 | 
			
		||||
                data = json.loads(json_data)
 | 
			
		||||
 | 
			
		||||
                product_info = None
 | 
			
		||||
                for item in data.get('props', {}).get('pageProps', {}).get('dehydratedState', {}).get('queries', []):
 | 
			
		||||
                    for episode_data in item.get('state', {}).get('data', []):
 | 
			
		||||
                        # Debug output to understand structure
 | 
			
		||||
                        if isinstance(episode_data, dict) and episode_data.get('slug') == url.split('/')[-2]:
 | 
			
		||||
                            product_info = {
 | 
			
		||||
                                'productId': episode_data.get('productId'),
 | 
			
		||||
                                'guid': episode_data.get('guid')
 | 
			
		||||
                            }
 | 
			
		||||
                            break
 | 
			
		||||
                    if product_info:
 | 
			
		||||
                        break
 | 
			
		||||
 | 
			
		||||
                if product_info:
 | 
			
		||||
                    # Step 2: Get JWT
 | 
			
		||||
                    token_url = f"https://npo.nl/start/api/domain/player-token?productId={product_info['productId']}"
 | 
			
		||||
                    token_response = requests.get(token_url)
 | 
			
		||||
                    token_response.raise_for_status()
 | 
			
		||||
                    jwt = token_response.json().get('jwt')
 | 
			
		||||
 | 
			
		||||
                    if jwt:
 | 
			
		||||
                        # Step 3: Make POST request to get stream link
 | 
			
		||||
                        headers = {
 | 
			
		||||
                            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36",
 | 
			
		||||
                            "Authorization": jwt,
 | 
			
		||||
                            "Content-Type": "application/json",
 | 
			
		||||
                            "Accept": "*/*",
 | 
			
		||||
                            "Referer": "https://npo.nl/"
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        body = {
 | 
			
		||||
                            "profileName": "dash",
 | 
			
		||||
                            "drmType": "widevine",
 | 
			
		||||
                            "referrerUrl": url,
 | 
			
		||||
                            "ster": {
 | 
			
		||||
                                "identifier": "npo-app-desktop",
 | 
			
		||||
                                "deviceType": 4,
 | 
			
		||||
                                "player": "web"
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        stream_response = requests.post("https://prod.npoplayer.nl/stream-link", headers=headers, json=body)
 | 
			
		||||
                        stream_response.raise_for_status()
 | 
			
		||||
                        
 | 
			
		||||
                        # Step 4: Extract streams URL and drmToken
 | 
			
		||||
                        stream_data = stream_response.json().get('stream', {})
 | 
			
		||||
                        stream_url = stream_data.get('streamURL', "streamURL not found in response.")
 | 
			
		||||
                        drm_token = stream_data.get('drmToken', "drmToken not found in response.")
 | 
			
		||||
                        
 | 
			
		||||
                        return (stream_url, drm_token)  # Return both if needed
 | 
			
		||||
                
 | 
			
		||||
                return "Product ID and GUID not found for the given slug."
 | 
			
		||||
            return "JSON script not found in the response."
 | 
			
		||||
        except requests.exceptions.RequestException as e:
 | 
			
		||||
            return f"An error occurred: {str(e)}"
 | 
			
		||||
        except json.JSONDecodeError:
 | 
			
		||||
            return "Failed to decode JSON data."
 | 
			
		||||
    return "Invalid URL. Please provide a URL that starts with 'https://npo.nl/start/serie/' and ends with '/afspelen'."
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    parser = argparse.ArgumentParser(description="Get the streaming URL from an NPO series page.")
 | 
			
		||||
    parser.add_argument("url", type=str, help="The URL of the NPO series page.")
 | 
			
		||||
    args = parser.parse_args()
 | 
			
		||||
 | 
			
		||||
    stream_url_response = get_stream_url(args.url)
 | 
			
		||||
 | 
			
		||||
    # Print the final result
 | 
			
		||||
    if isinstance(stream_url_response, tuple):
 | 
			
		||||
        print(f"Stream URL: {stream_url_response[0]}")
 | 
			
		||||
        print(f"DRM Token: {stream_url_response[1]}")
 | 
			
		||||
    else:
 | 
			
		||||
        print(stream_url_response)
 | 
			
		||||
							
								
								
									
										186
									
								
								getpssh.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								getpssh.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,186 @@
 | 
			
		||||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
import subprocess
 | 
			
		||||
import re
 | 
			
		||||
import requests
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import traceback
 | 
			
		||||
 | 
			
		||||
from pywidevine.cdm import Cdm
 | 
			
		||||
from pywidevine.device import Device
 | 
			
		||||
from pywidevine.pssh import PSSH
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_npo_get_output(link):
 | 
			
		||||
    try:
 | 
			
		||||
        p = subprocess.run([sys.executable, 'NPO.py', link],
 | 
			
		||||
                           stdout=subprocess.PIPE, stderr=subprocess.PIPE,
 | 
			
		||||
                           text=True)
 | 
			
		||||
        return p.stdout or "", p.stderr or "", p.returncode
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        print("[!] Failed to run NPO.py:", e)
 | 
			
		||||
        return "", str(e), 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def extract_mpd_url(text):
 | 
			
		||||
    m = re.search(r'(https?://[^\s\'"]+?\.mpd(?:[^\s\'"]*)?)', text, re.IGNORECASE)
 | 
			
		||||
    return m.group(1).strip() if m else None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def extract_drm_token(text):
 | 
			
		||||
    match = re.search(r'DRM Token:\s*([A-Za-z0-9_\-\.=]+)', text)
 | 
			
		||||
    if match:
 | 
			
		||||
        return match.group(1).strip()
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def download_mpd(mpd_url, out_path="manifest.mpd"):
 | 
			
		||||
    try:
 | 
			
		||||
        r = requests.get(mpd_url, timeout=20)
 | 
			
		||||
        r.raise_for_status()
 | 
			
		||||
        Path(out_path).write_text(r.text, encoding='utf-8', errors='ignore')
 | 
			
		||||
        return out_path
 | 
			
		||||
    except requests.RequestException as e:
 | 
			
		||||
        print("[!] Failed to download MPD:", e)
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def extract_pssh_blocks_from_mpd(path, max_len=200):
 | 
			
		||||
    text = Path(path).read_text(encoding='utf-8', errors='ignore')
 | 
			
		||||
    pattern = re.compile(r"<cenc:pssh>(.*?)</cenc:pssh>", re.DOTALL | re.IGNORECASE)
 | 
			
		||||
    found = pattern.findall(text)
 | 
			
		||||
    short = [f.strip() for f in found if len(f.strip()) <= max_len]
 | 
			
		||||
    return short
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_hex(val):
 | 
			
		||||
    if isinstance(val, bytes):
 | 
			
		||||
        return val.hex()
 | 
			
		||||
    elif isinstance(val, str):
 | 
			
		||||
        return val
 | 
			
		||||
    else:
 | 
			
		||||
        return str(val)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def kid_to_nodash_hex(kid_val):
 | 
			
		||||
    if isinstance(kid_val, bytes):
 | 
			
		||||
        return kid_val.hex()
 | 
			
		||||
    if isinstance(kid_val, str):
 | 
			
		||||
        return kid_val.replace("-", "").lower()
 | 
			
		||||
    return str(kid_val)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def process_pssh_with_pywidevine(pssh_b64, provision_path, license_url):
 | 
			
		||||
    try:
 | 
			
		||||
        pssh = PSSH(pssh_b64)
 | 
			
		||||
        device = Device.load(provision_path)
 | 
			
		||||
        cdm = Cdm.from_device(device)
 | 
			
		||||
        session_id = cdm.open()
 | 
			
		||||
 | 
			
		||||
        challenge = cdm.get_license_challenge(session_id, pssh)
 | 
			
		||||
 | 
			
		||||
        headers = {'Content-Type': 'application/octet-stream'}
 | 
			
		||||
        resp = requests.post(license_url, data=challenge, headers=headers, timeout=30)
 | 
			
		||||
        resp.raise_for_status()
 | 
			
		||||
 | 
			
		||||
        cdm.parse_license(session_id, resp.content)
 | 
			
		||||
 | 
			
		||||
        keys = cdm.get_keys(session_id)
 | 
			
		||||
        if not keys:
 | 
			
		||||
            print("[!] No keys returned.")
 | 
			
		||||
            cdm.close(session_id)
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        keypairs = []
 | 
			
		||||
        for key in keys:
 | 
			
		||||
            kid_raw = kid_to_nodash_hex(key.kid).lower()
 | 
			
		||||
            key_hex = to_hex(key.key).lower()
 | 
			
		||||
            kid_raw = kid_raw.replace("-", "")
 | 
			
		||||
            output = f"{kid_raw}:{key_hex}"
 | 
			
		||||
            if len(output) < 70:
 | 
			
		||||
                print(output)
 | 
			
		||||
                keypairs.append(output)
 | 
			
		||||
 | 
			
		||||
        cdm.close(session_id)
 | 
			
		||||
        return keypairs
 | 
			
		||||
 | 
			
		||||
    except Exception:
 | 
			
		||||
        traceback.print_exc()
 | 
			
		||||
        return []
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    if len(sys.argv) < 2:
 | 
			
		||||
        print(f"Usage: {sys.argv[0]} <link>")
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    link = sys.argv[1]
 | 
			
		||||
    provision_path = "motorola_moto.wvd"
 | 
			
		||||
    if not Path(provision_path).is_file():
 | 
			
		||||
        print(f"[!] Provisioning file '{provision_path}' not found.")
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    out, err, code = run_npo_get_output(link)
 | 
			
		||||
    if code != 0:
 | 
			
		||||
        print("[!] NPO.py exited with code", code)
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    combined = out + "\n" + err
 | 
			
		||||
    mpd_url = extract_mpd_url(combined)
 | 
			
		||||
    if not mpd_url:
 | 
			
		||||
        print("[!] Could not find .mpd URL in output.")
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    drm_token = extract_drm_token(combined)
 | 
			
		||||
    if not drm_token:
 | 
			
		||||
        print("[!] Could not find DRM token.")
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    license_url = f"https://npo-drm-gateway.samgcloud.nepworldwide.nl/authentication?custom_data={drm_token}"
 | 
			
		||||
 | 
			
		||||
    mpd_file = download_mpd(mpd_url)
 | 
			
		||||
    if not mpd_file:
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    psshs = extract_pssh_blocks_from_mpd(mpd_file, max_len=200)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        os.remove(mpd_file)
 | 
			
		||||
    except Exception:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    if not psshs:
 | 
			
		||||
        print("[!] No suitable PSSH blocks found.")
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
    all_keypairs = []
 | 
			
		||||
    for pssh in psshs:
 | 
			
		||||
        p_clean = "".join(pssh.split())
 | 
			
		||||
        keys = process_pssh_with_pywidevine(p_clean, provision_path, license_url)
 | 
			
		||||
        if not keys:
 | 
			
		||||
            sys.exit(1)
 | 
			
		||||
        all_keypairs.extend(keys)
 | 
			
		||||
 | 
			
		||||
    # Build command for N_m3u8dl-re
 | 
			
		||||
    cmd = ["N_m3u8dl-re", mpd_url]
 | 
			
		||||
    for keypair in all_keypairs:
 | 
			
		||||
        cmd += ["--key", keypair]
 | 
			
		||||
 | 
			
		||||
    # Append fixed options
 | 
			
		||||
    cmd += ["-sv", "best", "-sa", "best", "-M", "mkv"]
 | 
			
		||||
 | 
			
		||||
    print("\n[+] Running command:")
 | 
			
		||||
    print(" ".join(cmd))
 | 
			
		||||
 | 
			
		||||
    # Run the command
 | 
			
		||||
    try:
 | 
			
		||||
        subprocess.run(cmd, check=True)
 | 
			
		||||
    except subprocess.CalledProcessError as e:
 | 
			
		||||
        print("[!] N_m3u8dl-re failed:", e)
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    main()
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user