import requests
import xmltodict
import logging
import subprocess
from pywidevine.decrypt.wvdecrypt import WvDecrypt
from config.config import DEFAULT_CONFIG
from pycaption import SRTWriter, WebVTTReader
import argparse
from titlecase import titlecase
import sys
from pymediainfo import MediaInfo
from pathlib import Path
import base64
import pycaption

class ZeeDownloader():
    def __init__(self, args):
        self.quality = args.quality
        self.args = args
        self.id = args.id
        self.season =args.season
        self.license = 'https://wv-keyos-aps1.licensekeyserver.com/'

    def get_video_id(self, video_file):
        if not video_file:
            raise ValueError("No Video file in Dir...")

        media_info = MediaInfo.parse(video_file)
        track = [track for track in media_info.tracks if track.track_type == "Video"][0]

        if track.format == "AVC":
            if track.encoding_settings:
                return "x264"
            return "H.264"
        elif track.format == "HEVC":
            if track.commercial_name == "HDR10" and track.color_primaries:
                return "HDR.HEVC"
            if track.commercial_name == "HEVC" and track.color_primaries:
                return "HEVC"

            return "DV.HEVC"

        return None

    def get_audio_id(self, audio):
        audio_id = None
        media_info = MediaInfo.parse(audio)
        track = [track for track in media_info.tracks if track.track_type == "Audio"][0]

        if track.format == "E-AC-3":
            audioCodec = "DDP"
        elif track.format == "AC-3":
            audioCodec = "DD"
        elif track.format == "AAC":
            audioCodec = "AAC"
        elif track.format == "DTS":
            audioCodec = "DTS"
        elif "DTS" in track.format:
            audioCodec = "DTS"
        else:
            audioCodec = "DDP"

        if track.channel_s == 8:
            channels = "7.1"
        elif track.channel_s == 6:
            channels = "5.1"
        elif track.channel_s == 2:
            channels = "2.0"
        elif track.channel_s == 1:
            channels = "1.0"
        else:
            channels = "5.1"

        audio_id = (
            f"{audioCodec}{channels}.Atmos"
            if "Atmos" in track.commercial_name
            else f"{audioCodec}{channels}"
        )

        return audio_id


    def ReplaceDontLikeWord(self, X):
        try:
            X = X.replace(' : ', ' - ').replace(': ', ' - ').replace(':', ' - ').replace('&', 'and').replace('+',
                                                                                                             '').replace(
                ';', '').replace('Ã³', 'o').replace('[', '').replace("'", '').replace(']', '').replace('/', '').replace(
                '//', '').replace('�', "'").replace('*', 'x').replace('<', '').replace('>', '').replace('|', '').replace(
                '~', '').replace('#', '').replace(' ', '.').replace('%', '').replace('{', '').replace('}', '').replace(',',
                                                                                                                       '').replace(
                '?', '')
        except Exception:
            X = X.decode('utf-8').replace(' : ', ' - ').replace(': ', ' - ').replace(':', ' - ').replace('&',
                                                                                                         'and').replace('+',
                                                                                                                        '').replace(
                ';', '').replace('Ã³', 'o').replace('[', '').replace("'", '').replace(']', '').replace('/', '').replace(
                '//', '').replace('�', "'").replace('*', 'x').replace('<', '').replace('>', '').replace('|', '').replace(
                '~', '').replace('#', '').replace('%', '').replace('{', '').replace('}', '').replace(',', '').replace('?',
                                                                                                                      '').encode(
                'latin-1').decode('latin-1')

        return titlecase(X)

    def get_pssh(self, keyId):
        array_of_bytes = bytearray(b'\x00\x00\x002pssh\x00\x00\x00\x00')
        array_of_bytes.extend(bytes.fromhex('edef8ba979d64acea3c827dcd51d21ed'))
        array_of_bytes.extend(b'\x00\x00\x00\x12\x12\x10')
        array_of_bytes.extend(bytes.fromhex(keyId.replace('-', '')))
        return base64.b64encode(bytes.fromhex(array_of_bytes.hex()))

    def get_headers(self):
        return {'customdata': requests.get('http://hoichoiapi.herokuapp.com/zeeepi/?id={}'.format(self.id)).json()['drm']}

    def createpsshfromkid(self, kid):
        kid = kid.replace('-', '')
        if len(kid) == 32 and isinstance(kid, bytes):
            raise AssertionError('Wrong KID length')
        return self.get_pssh(kid).decode('utf-8')


    def do_decrypt(self, pssh, licurl):
        wvdecrypt = WvDecrypt(pssh)
        chal = wvdecrypt.get_challenge()
        resp = requests.post(url=licurl, data=chal, headers=self.get_headers())
        license_decoded = resp.content
        license_b64 = base64.b64encode(license_decoded)
        wvdecrypt.update_license(license_b64)
        keys = wvdecrypt.start_process()

        return keys


    def keysOnly(self, keys):
        keylist = []
        for key in keys:
            if key.type == 'CONTENT':
                keylist.append(('{}:{}'.format(key.kid.hex(), key.key.hex())))

        return keylist

    def token(self):
        return requests.get("https://useraction.zee5.com/tokennd/").json()["video_token"]


    def session(self):
        session_token = requests.get("https://useraction.zee5.com/token/platform_tokens.php?platform_name=web_app").json()[
            "token"]
        return {'X-ACCESS-TOKEN': session_token}

    def get_pssh_mpd(self, url):
        mpd_dict = xmltodict.parse(requests.get(url).text, dict_constructor=dict)
        init_data_dict = [x for x in mpd_dict['MPD']['Period']['AdaptationSet'][0]['ContentProtection'] if
                          x['@schemeIdUri'] == 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed']
        if init_data_dict:
            init_data_b64 = init_data_dict[0]['cenc:pssh']
            print("init_data found in mpd")
        else:
            print('init_data not found in mpd')
        return init_data_b64

    def mux(self, name, sub_lang, audio_lang):
        cmd = ['bin\\mkvmerge.exe', '-o', 'temp\\{}.mkv'.format(' '.join(name))]
        cmd += ['temp\\{}.mp4'.format(' '.join(name))]
        if audio_lang:
            for audio in audio_lang:
                cmd += ['--language', '0:{}'.format(audio), 'temp\\{}.m4a'.format(' '.join(name))]
        if sub_lang:
            for subs in sub_lang:
                if subs == 'en':
                    cmd = cmd + ['--default-track', '0:yes']
                cmd = cmd + ['--language', '0:{}'.format(subs), 'temp\\{} {}.srt'.format(' '.join(name), subs)]
        subprocess.run(cmd)
        audio = self.get_audio_id("temp\\{}.mkv".format(' '.join(name)))
        video = self.get_video_id("temp\\{}.mkv".format(' '.join(name)))
        Path("temp\\{}.mkv".format(' '.join(name))).rename("output\\{}.{}p.Zee5.WEB-DL.{}.{}-{}.mkv".format(self.ReplaceDontLikeWord(' '.join(name)), self.quality, audio, video, DEFAULT_CONFIG['group']))

    def decrypt(self, naming, pssh):
        print('Decrypting...')
        keys = self.keysOnly(self.do_decrypt(pssh, self.license))
        cmd = [DEFAULT_CONFIG['mp4decrypt']]
        for key in keys:
            cmd += ['--key', key]
        vid = cmd + ['temp\\' + ' '.join(naming) + '_enc.mp4','temp\\' + ' '.join(naming) + '.mp4']
        aud = cmd + ['temp\\' + ' '.join(naming) + '_enc.m4a','temp\\' + ' '.join(naming) + '.m4a']
        subprocess.run(vid)
        subprocess.run(aud)

    def get_stream(self, url, naming):
        subprocess.run([DEFAULT_CONFIG['youtubedl'], '--allow-unplayable-formats',url, '-f', 'bestvideo[height={quality}]'.format(quality=self.quality),'--external-downloader', 'aria2c', '--external-downloader-args', '-s16 -x16 -j32', '-o', 'temp\\' + ' '.join(naming) + '_enc.mp4'])
        subprocess.run([DEFAULT_CONFIG['youtubedl'], '--allow-unplayable-formats',url, '-f', 'bestaudio', '--external-downloader', 'aria2c', '--external-downloader-args', '-s16 -x16 -j32','-o', 'temp\\' + ' '.join(naming) + '_enc.m4a'])
    
    def get_info(self):
        data = requests.get(url='https://gwapi.zee5.com/content/details/{}&translation=en&country=IN'.format(self.id), headers=self.session())
        data = data.json()
        naming = []
        title = data['title']
        drm_stream = data['video_details']['url']
        hls_stream = data['video_details']['hls_url']
        title_type = data['asset_subtype']
        is_drm = data['video_details']['is_drm']
        subtitles = [sublang for sublang in data['video_details']['subtitles']]
        audios = [audlang for audlang in data['video_details']['audiotracks']]
        year = data['release_date'][:4]
        drm_stream = 'https://zee5vod.akamaized.net' + drm_stream
        naming += [title] + [year]
        new = naming
        if title_type == 'episode' or title_type == 'tvshow':
            season_num = data['season_details']['orderid']
            episode_num = data['episode_number']
            naming[0] = data['tvshow_details']['title']
            if self.season:
                for episodes in reversed(data['tvshow_details']['episodes']):
                    name = [] + naming
                    subtitles = [sublang for sublang in episodes['video_details']['subtitles']]
                    audios = [audlang for audlang in episodes['video_details']['audiotracks']]
                    episode_num = episodes['episode_number']
                    is_drm = episodes['video_details']['is_drm']
                    drm_stream = episodes['video_details']['url']
                    hls_stream = episodes['video_details']['hls_url']
                    name += ['S{:02d}E{:02d}'.format(season_num, episode_num)]
                    print('Ripping {}'.format(' '.join(name)))
                    drm_stream = 'https://zee5vod.akamaized.net' + drm_stream
                    pssh = self.get_pssh_mpd(drm_stream)
                    if not self.args.license:
                        self.get_stream(drm_stream, naming)
                        cant = self.get_sub(drm_stream, naming, subtitles)
                        subtitles = [sub for sub in subtitles if sub not in cant]
                        self.decrypt(naming, pssh)
                        self.mux(naming, subtitles, audios)
                    else:
                        keys = self.keysOnly(self.do_decrypt(pssh, self.license))
                        for key in keys:
                            print(key)
            else:
                naming += ['S{:02d}E{:02d}'.format(season_num, episode_num)]
                print('Ripping {}'.format(' '.join(naming)))
                pssh = self.get_pssh_mpd(drm_stream)
                if not self.args.license:
                    self.get_stream(drm_stream, naming)
                    cant = self.get_sub(drm_stream, naming, subtitles)
                    subtitles = [sub for sub in subtitles if sub not in cant]
                    self.decrypt(naming, pssh)
                    self.mux(naming, subtitles, audios)
                else:
                    keys = self.keysOnly(self.do_decrypt(pssh, self.license))
                    for key in keys:
                        print(key)

        else:
            print('Ripping {}'.format(' '.join(naming)))
            pssh = self.get_pssh_mpd(drm_stream)
            if not self.args.license:
                self.get_stream(drm_stream, naming)
                self.get_sub(drm_stream, naming, subtitles)
                self.decrypt(naming, pssh)
                self.mux(naming, subtitles, audios)
            else:
                keys = self.keysOnly(self.do_decrypt(pssh, self.license))
                for key in keys:
                    print(key)
        if not self.args.keep and not self.args.license:
            [f.unlink() for f in Path("temp\\").glob("*") if f.is_file()] 


    def clean(self, mpd):
        filter_object = filter(lambda a: '.mpd' in a, mpd.split('/'))
        delete = mpd.split('/').index(list(filter_object)[0])
        return '/'.join(mpd.split('/')[:delete])

    def get_sub(self, url, naming, lang):
        suburl = self.clean(url)
        cant = []
        for sub in lang:
            try:
                with open('temp\\{} {}.srt'.format(' '.join(naming), sub), 'w', encoding='utf-8') as r:
                    subvtt = requests.get(suburl + '/manifest-{}.vtt'.format(sub))
                    subvtt.encoding = 'UTF-8'
                    subsrt = pycaption.SRTWriter().write(pycaption.WebVTTReader().read(subvtt.text))
                    r.write(subsrt)
            except pycaption.exceptions.CaptionReadNoCaptions:
                cant.append(sub)
        return cant



if __name__ == '__main__':
    try:
        description = 'Zee5 Downloader by BonsaiHD'
        parser = argparse.ArgumentParser(description=description)
        parser.add_argument('-id', type=str, help='Video ID.')
        parser.add_argument('-s', '--season', type=int, help='Season Number.')
        parser.add_argument('-q', '--quality', help='quality', default=1080)
        parser.add_argument('--keep', help='keep files', action='store_true')
        parser.add_argument('--license', help='keep files', action='store_true')

        args = parser.parse_args()
        z = ZeeDownloader(args)
        z.get_info()
    except KeyboardInterrupt:
        print('Exiting..')
        sys.exit()