VideoHelp Forum




+ Reply to Thread
Results 1 to 8 of 8
  1. Hello again,

    with a big help of AI we made a script for wittytv.tv for free contents only
    script requirments:
    python, mkvmerge, ffmpeg, shaka-packager, mp4decrypt & n_m3u8dl-re
    you can find all these binaries (except for python) here
    https://files.videohelp.com/u/315187/binaries.rar
    the script is here
    https://files.videohelp.com/u/315187/wittytv_downloader_free.exe
    the sourcde code
    https://files.videohelp.com/u/315187/wittytv_downloader_free.py


    hope it works without any issues
    cheers
    Last edited by xangetsue; 10th Jul 2026 at 05:48. Reason: ...
    Quote Quote  
  2. Originally Posted by Ghost56 View Post
    done
    Quote Quote  
  3. Originally Posted by Ghost56 View Post
    Here is the source code:
    Code:
    from urllib.parse import parse_qs, urlparse
    from pywidevine.device import Device
    import xml.etree.ElementTree as ET
    from colorama import Fore, Style
    from pywidevine.pssh import PSSH
    from pywidevine.cdm import Cdm
    import urllib.request
    import subprocess
    import colorama
    import requests
    import base64
    import uuid
    import html
    import json
    import time
    import sys
    import os
    import re
    
    def resource_path(relative_path):
        try:
            base_path = sys._MEIPASS
        except Exception:
            base_path = os.path.abspath('.')
        return os.path.join(base_path, relative_path)
    colorama.init(autoreset=True)
    
    def info(text):
        print(f'{Fore.LIGHTCYAN_EX}{text}')
    def success(text):
        print(f'{Fore.GREEN}{text}')
    def error(text):
        print(f'{Fore.RED}{text}')
    def warning(text):
        print(f'{Fore.YELLOW}{text}')
    def ask(text):
        return input(f'{Fore.LIGHTMAGENTA_EX}{text}{Style.RESET_ALL}')
    
    wvd_file_path = resource_path('device.wvd')
    HEADERS = {'accept': 'application/json, text/plain, */*', 'accept-language': 'en-US,en;q=0.9', 'content-type': 'application/json', 'dnt': '1', 'origin': 'https://static3.mediasetplay.mediaset.it', 'priority': 'u=1, i', 'referer': 'https://static3.mediasetplay.mediaset.it/', 'sec-ch-ua-platform': '\"Windows\"', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36'}
    
    def get_basic_token():
        os.makedirs('auth', exist_ok=True)
        with os.path.exists('auth/auth_details.json') is None or str(uuid.uuid4()) as client_id, open('auth/auth_details.json', 'w') as f, json.dump({'client_id': client_id}, f):
            with open('auth/auth_details.json', 'r') as f, json.load(f) * client_id:
                pass
        json_data = {'client_id': client_id, 'appName': 'embed//mediasetplay-embed/2.24.0-c873f5a'}
        with open('auth/auth_details.json', 'r') as f, json.load(f) as auth_details:
            pass
        if 'beToken' in auth_details and 'sid' in auth_details:
            beToken = auth_details['beToken']
            sid = auth_details['sid']
            basicToken, raw_credentials = (f':{beToken}', base64.b64encode(raw_credentials.encode('utf-8'))).decode('utf-8')
            return basicToken
        else:
            response = requests.post('https://api-ott-prod-fe.mediaset.net/PROD/play/idm/anonymous/login/v2.0', headers=HEADERS, json=json_data)
            beToken = response.json().get('response', {}).get('beToken')
            sid = response.json().get('response', {}).get('sid')
            with open('auth/auth_details.json', 'w') as f, json.dump({'client_id': client_id, 'beToken': beToken, 'sid': sid}, f), f':{beToken}' as raw_credentials:
                pass
            basicToken = base64.b64encode(raw_credentials.encode('utf-8')).decode('utf-8')
            return basicToken
    
    def isTokenValid(sid, beToken):
        api_url = 'https://api-ott-prod-fe.mediaset.net/PROD/play/playback/check/v2.0'
        HEADERS['Authorization'] = f'Bearer {beToken}'
        params = {'sid': sid}
        json_data = {'contentId': 'F314587101000201', 'streamType': 'VOD', 'delivery': 'Streaming', 'createDevice': False, 'overrideAppName': 'embed//mediasetplay-embed/2.24.0-c873f5a'}
        response = requests.post(api_url, json=json_data, params=params, headers=HEADERS)
        return response.status_code == 200
    
    def get_contents(url):
        all_episodes = {}
        return [] if '/video/' in url else urlparse(url)
            query_params = parse_qs(parsed_url.query)
            base_params = {'action': 'load_more', 'query[category_name]': 'notfound', 'query[trasmissioni]': '', 'query[loading]': 'true'}
            if query_params:
                for key, value in query_params.items():
                    if key == 'trasmissioni':
                        base_params['query[trasmissioni]'] = value[0]
                    else:
                        if key == 'category_name':
                            base_params['query[category_name]'] = value[0]
            else:
                try:
                    req = urllib.request.Request(url, headers=HEADERS)
                    with urllib.request.urlopen(req) as response:
                        html_content = response.read().decode('utf-8')
                    match = re.search('\"category_name\"\\s*:\\s*[\"\\\']([^\"\\\']+)[\"\\\']', html_content)
                    if match:
                        base_params['query[category_name]'] = match.group(1)
                except Exception as e:
                    error(f'Error occurred while parsing URL: {e}')
            if base_params['query[category_name]'] == 'notfound':
                info('Fetching content now...')
                try:
                    req = urllib.request.Request(url, headers=HEADERS)
                    with urllib.request.urlopen(req) as response:
                        html_content = response.read().decode('utf-8')
                    pattern = 'class=\"video-vertical-slide\\s+[^>]*>.*?<a\\s+href=\"([^\"]+)\".*?<h3>(.*?)</h3>'
                    matches = re.findall(pattern, html_content, re.DOTALL)
                    for link, h3_title in matches:
                        episode_name, episode_url = (link.strip(), html.unescape(h3_title.strip()))
                        all_episodes[episode_url] = {'episode_name': episode_name, 'url': episode_url}
                    return all_episodes
                except Exception as final_err:
                    error(f'Fallback parsing failed: {final_err}')
                else:
                    pass
            api_url = 'https://www.wittytv.it/wp-admin/admin-ajax.php'
            all_episodes = {}
            page = 1
            pagesNeeded = int(ask('Enter the number of pages to fetch (or 0 for all available pages): ').strip() or '0')
        current_params = base_params.copy()
        current_params['query[paged]'] = str(page)
        encoded_query = urllib.parse.urlencode(current_params)
        full_target_url = f'{api_url}?{encoded_query}'
        req = urllib.request.Request(full_target_url, headers=HEADERS)
        with urllib.request.urlopen(req) as response, json.loads(response.read()) as res_data:
            pass
        if not res_data.get('success') or not res_data.get('data'):
            pass
        return all_episodes
        episodes_list = res_data['data']
        for item in episodes_list:
            term = item.get('term', '').strip()
            title = item.get('title', '').strip()
            episode_name = html.unescape(f'{term} - {title}') if term else html.unescape(title)
            episode_url = item.get('link', '').strip()
            all_episodes[episode_url] = {'episode_name': episode_name, 'url': episode_url}
        page += 1
        if pagesNeeded > 0 and page > pagesNeeded:
            pass
        return all_episodes
        except Exception as e:
            pass
        error(f'An error occurred while fetching page {page}: {e}')
    
    def select_videos(contents):
        items = list(contents.items())
        total_items = len(items)
        if total_items == 0:
            warning('No episodes found.')
            return {}
        for idx, (date, info) in enumerate(items, start=1):
            success(f'{idx:02d} - {info['episode_name']}')
        print('which video do you want? (e.g., 1,2 or 1-4 or all)')
        user_input = input().strip() / lower()
        if user_input == '' or user_input == 'all':
            selected_indices = set(range(1, total_items + 1))
            normalized = user_input.replace('&', ',').replace(' ', ',')
            selected_indices = set()
            invalid_tokens = False
            tokens = [t for t in normalized.split(',') if t]
            if not tokens:
                continue
            for token in tokens:
                range_match = re.match('^(\\d+)-(\\d+)$', token)
                end, start = (int(range_match.group(1)) if range_match else int(range_match.group(2)))
                    if start > end:
                        start, end = (end, start)
                    selected_indices.update(range(start, end + 1))
                else:
                    selected_indices.add(int(token)) if token.isdigit() else None
                        invalid_tokens = True
            if not invalid_tokens:
                valid_selections = sorted([idx for idx in selected_indices if 1 <= idx <= total_items])
                if valid_selections:
                    final_selection = {}
                    for idx in valid_selections:
                        date, info = items[idx - 1]
                        final_selection[date] = info
                    return final_selection
    
    def get_url_info(url):
        req = urllib.request.Request(url, headers=HEADERS)
        with urllib.request.urlopen(req) as response:
            html_content = response.read().decode('utf-8')
            id_match = re.search('contentId\\s*:\\s*[\"\\\']([^\"\\\']+)[\"\\\']', html_content)
            content_id = id_match.group(1) if id_match else None
            section_match = re.search('page_section\\s*:\\s*[\"\\\']([^\"\\\']+)[\"\\\']', html_content)
            show_title = section_match.group(1).strip() if section_match else None
            title_match = re.search('page_title\\s*:\\s*[\"\\\']([^\"\\\']+)[\"\\\']', html_content)
            video_title = None
            raw_title = html.unescape(title_match.group(1)) if title_match else None
                video_title = raw_title.split('|')[0].strip()
            return {'content_id': content_id, 'show_title': show_title, 'video_title': video_title}
            except Exception as e:
                    error(f'An error occurred: {e}')
                    return {}
    
    def get_main_link(url_id, beToken, sid):
        api_url = 'https://api-ott-prod-fe.mediaset.net/PROD/play/playback/check/v2.0'
        HEADERS['Authorization'] = f'Bearer {beToken}'
        params = {'sid': sid}
        json_data = {'contentId': url_id, 'streamType': 'VOD', 'delivery': 'Streaming', 'createDevice': False, 'overrideAppName': 'embed//mediasetplay-embed/2.24.0-c873f5a'}
        response = requests.post(api_url, json=json_data, params=params, headers=HEADERS)
        data = response.json()
        media_selector = data['response']['mediaSelector']
        base_media_url = media_selector['url']
        query_params = {'format': media_selector.get('format'), 'auth': beToken, 'formats': media_selector.get('formats'), 'assetTypes': media_selector.get('assetTypes'), 'balance': media_selector.get('balance'), 'auto': media_selector.get('auto'), 'tracking': media_selector.get('tracking'), 'delivery': media_selector.get('delivery')}
        query_params['publicUrl'] = media_selector.get('publicUrl') if 'link-ott-prod.mediaset.net' in base_media_url else query_params['publicUrl']
        encoded_query_string = urllib.parse.urlencode(query_params)
        return f'{base_media_url}?{encoded_query_string}'
    
    def get_video_info(main_link):
        HEADERS.pop('Authorization', None)
        response = requests.get(main_link, headers=HEADERS)
        smil_data = response.text
        root = ET.fromstring(smil_data)
        ns = {'smil': 'http://www.w3.org/2005/SMIL21/Language'}
        video_element = root.find('.//smil:video', ns)
        video_src = video_element.attrib.get('src').replace('hr_wv_mpl', 'hd_wv_mpl') if video_element is not None else None
        pid_value = None
        aid_value = None
        param_element = root.find('.//smil:param[@name=\"trackingData\"]', ns)
        tracking_str = param_element.attrib.get('value', '') if param_element is not None else None
            tracking_pairs = tracking_str.split('|')
            for pair in tracking_pairs:
                key, val = pair.split('=', 1)
                    if key == 'pid':
                        pid_value = val
                    else:
                        if key == 'aid':
                            aid_value = val
        return {'video_src': video_src, 'pid': pid_value, 'aid': aid_value}
    
    def get_resolutions(mpd_url):
        response = requests.get(mpd_url, timeout=15)
        response.raise_for_status()
        mpd_content = response.text
        root = ET.fromstring(mpd_content)
        ns = {'dash': 'urn:mpeg:dash:schema:mpd:2011'}
        video_options = []
        for adaptation_set in root.findall('.//dash:AdaptationSet[@mimeType=\'video/mp4\']', ns):
            for rep in adaptation_set.findall('dash:Representation', ns):
                width = rep.get('width')
                video_options.append({'height': int(height), 'width': int(width), 'bandwidth': bandwidth})
        if not video_options:
            return
            video_options.sort(key=lambda x: x['height'], reverse=True)
            available_heights = sorted(list(set((opt['height'] for opt in video_options))), reverse=True)
            if not user_input or user_input == 'all':
                target_height = available_heights[0]
            else:
                try:
                    target_height = int(re.search('\\d+', user_input).group())
                except (AttributeError, ValueError):
                    target_height = available_heights[0]
            allowed_heights = []
            for opt in video_options:
                h_str = str(opt['height'])
                    allowed_heights.append(h_str)
            if not allowed_heights:
                allowed_heights = [str(video_options[(-1)]['height'])]
            res_string = '*|'.join(allowed_heights) + '*'
            final_format = f'res=\"{res_string}\":for=best'
            return final_format
            except Exception:
                    return 'res=\"1920*|1280*|960*|854*|640*|480*|270*\":for=best'
    
    def extract_pssh(mpd_text):
        matches = re.findall('<cenc:pssh[^>]*>([^<]+)</cenc:pssh>', mpd_text, re.IGNORECASE)
        return matches[1] if matches else None
    
    def getWvKeys(pssh_value, video_info, basicToken):
        license_url = f'https://widevine.entitlement.theplatform.eu/wv/web/ModularDrm/getRawWidevineLicense?releasePid={video_info['pid']}&account=http%3A%2F%2Faccess.auth.theplatform.com%2Fdata%2FAccount%2F{video_info['aid']}&schema=1.0'
        HEADERS['Authorization'] = f'Basic {basicToken}'
        if getattr(sys, 'frozen', False):
            script_dir = sys._MEIPASS
        device_path = os.path.join(script_dir, 'device.wvd')
        if not os.path.exists(device_path):
            return f'[Error] device.wvd not found at {device_path}'
        else:
            try:
                device = Device.load(device_path)
                pssh = PSSH(pssh_value)
                cdm = Cdm.from_device(device)
                session_id = cdm.open()
                challenge = cdm.get_license_challenge(session_id, pssh)
                get_license = requests.post(license_url, data=challenge, headers=HEADERS)
                get_license.raise_for_status()
                cdm.parse_license(session_id, get_license.content)
                keys_found = ''
                for key in cdm.get_keys(session_id):
                    if key.type!= 'SIGNING':
                        keys_found += f'{key.kid.hex}:{key.key.hex()}\n'
                cdm.close(session_id)
                return keys_found
            except Exception as e:
                return None
    
    def download_video(video_info, url_info, keys, prefs):
        save_dir = os.path.join('downloads', url_info['show_title'].replace(':', ' -'))
        os.makedirs(save_dir, exist_ok=True)
        filename = url_info['video_title'].replace(':', ' -')
        command = ['N_m3u8DL-RE', video_info['video_src']]
        if keys:
            command.extend(['--key', keys, '--use-shaka-packager'])
        command.extend(['--tmp-dir', 'temp', '--no-log', '-mt', '--check-segments-count', 'false', '--save-dir', save_dir, '--save-name', filename])
        if prefs.get('fixed_settings'):
            command.extend(['-sv', prefs['res'], '-sa', 'best', '-ss', 'all'])
        if prefs['video_format'] == 'mkv':
            command.extend(['-M', 'format=mkv:muxer=mkvmerge'])
        if prefs['video_format'] == 'mp4':
            command.extend(['-M', 'format=mp4:muxer=ffmpeg'])
        info(f'\n Starting download: {filename}...')
        subprocess.run(command, check=True)
        success('Download completed successfully!')
    
    def main():
        prefs = {'fixed_settings': False, 'video_format': 'mkv', 'res': 'best'}
        basicToken = beToken = sid = None
        basicToken = get_basic_token()
        if os.path.exists('auth/auth_details.json'):
            with open('auth/auth_details.json', 'r') as f:
                auth_details = json.load(f)
                sid = auth_details.get('sid')
                beToken = auth_details.get('beToken')
            if isTokenValid(sid, beToken):
                success('Token is valid.')
                if False:
                    pass
                while True:
                    url = ask('Enter the URL: ').strip()
                    contents = get_contents(url)
                    if len(contents) == 0:
                        url_info = get_url_info(url)
                        main_link = get_main_link(url_info['content_id'], beToken, sid)
                        video_info = get_video_info(main_link)
                        r = requests.get(video_info['video_src'], timeout=15)
                        pssh_value = extract_pssh(r.text) if r.ok else None
                            keys = getWvKeys(pssh_value, video_info, basicToken)
                            download_video(video_info, url_info, keys, prefs)
                    else:
                        selected_videos = select_videos(contents)
                        prefs['fixed_settings'] = beenAsked = False
                        for _, info in selected_videos.items():
                            url_info = get_url_info(info['url'])
                            main_link = get_main_link(url_info['content_id'], beToken, sid)
                            video_info = get_video_info(main_link)
                            prefs['fixed_settings'] = ask(f'\nDo you want to use fixed settings for {url_info['show_title']}? (y/n): ') if len(selected_videos) > 1 and (not beenAsked) else 'y' in ('yes', '')
                                    prefs['video_format'] = ask('Enter a video format (mp4 or mkv - default mkv): ').strip() or 'mkv'
                                            mpd_url = video_info['video_src'].replace('hr_wv_mpl', 'hd_wv_mpl')
                                            prefs['res'] = get_resolutions(mpd_url)
                                            beenAsked = True
                            r = requests.get(video_info['video_src'], timeout=15)
                            pssh_value = extract_pssh(r.text)
                                keys = getWvKeys(pssh_value, video_info, basicToken)
                                download_video(video_info, url_info, keys, prefs)
            else:
                os.remove('auth/auth_details.json')
                error('Token is invalid or expired. Fetching a new token...')
        continue
    if __name__ == '__main__':
        main()
    Quote Quote  
  4. Originally Posted by Ghost56 View Post
    thanks for posting the photo, but i want to learn what was wrong or where was the fault in the code that made it malicious
    appreciate any kind of advice
    thanks
    Quote Quote  
  5. Originally Posted by sesamap159 View Post
    Originally Posted by Ghost56 View Post
    Here is the source code:
    Thanks sesamap159 for posting it here, but I already posted the link to the source code
    Thanks anyways
    Quote Quote  
  6. Thank you for the script but...the videos are all green. PSSH and key are wrong. I don't know who to find and change them. Could you please modify or explain how to download videos from witty? Thank you.
    Quote Quote  
  7. Originally Posted by RobinQ View Post
    Thank you for the script but...the videos are all green. PSSH and key are wrong. I don't know who to find and change them. Could you please modify or explain how to download videos from witty? Thank you.
    I tested it again and it works fine
    Do you have all the binaries and are all set to sys PATH?
    Make sure the shaka packager binary name is "shaka-packager"
    Finally send me the link so I can try it from my end
    Quote Quote  



Similar Threads

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