#!/usr/bin/env python
# -*- coding: utf-8 -*-

from ykdl.util.html import get_content
from ykdl.util.match import match1, matchall
from ykdl.extractor import VideoExtractor
from ykdl.videoinfo import VideoInfo
import xml.etree.ElementTree as ET

from ykdl.compact import urlencode, compact_bytes

import random
import base64
import struct
import uuid
import json

PLAYER_PLATFORMS = [11, 2, 1]
PLAYER_VERSION = '3.2.19.333'
"""
LEGACY FOR REFERENCE ONLY

def pack(data):
    target = []
    target.extend(struct.pack('>I', data[0]))
    target.extend(struct.pack('>I', data[1]))
    target = [c for c in target]
    return target

def unpack(data):
    data = ''.join([chr(b) for b in data])
    target = []
    data = data.encode('latin')
    target.extend(struct.unpack('>I', data[:4]))
    target.extend(struct.unpack('>I', data[4:8]))
    return target

def tea_encrypt(v, key):
    delta = 0x9e3779b9
    s = 0
    v = unpack(v)
    rounds = 16
    while rounds:
        s += delta
        s &= 0xffffffff
        v[0] += (v[1]+s) ^ ((v[1]>>5)+key[1]) ^ ((v[1]<<4)+key[0])
        v[0] &= 0xffffffff
        v[1] += (v[0]+s) ^ ((v[0]>>5)+key[3]) ^ ((v[0]<<4)+key[2])
        v[1] &= 0xffffffff
        rounds = rounds - 1
    return pack(v)

def qq_encrypt(data, key):
    temp = [0x00]*8
    enc = tea_encrypt(data, key)
    for i in range(8, len(data), 8):
        d1 = data[i:]
        for j in range(8):
            d1[j] = d1[j] ^ enc[i+j-8]
        d1 = tea_encrypt(d1, key)
        for j in range(len(d1)):
            d1[j] = d1[j]^data[i+j-8]^temp[j]
            enc.append(d1[j])
            temp[j] = enc[i+j-8]
    return enc

def strsum(data):
    s = 0
    for c in data:
        s = s*131 + ord(c)
    return 0x7fffffff & s

def ccc(platform, version, timestamp):
    key = [1735078436, 1281895718, 1815356193, 879325047]
    s1 = '<KEY>'
    d = [0x3039, 0x02]
    d.append(timestamp)
    d.append(platform)
    d.append(strsum(version))
    d.append(strsum(s1))
    data = [0xa6, 0xf1, 0xd9, 0x2a, 0x82, 0xc8, 0xd8, 0xfe, 0x43]
    for i in d:
        data.extend([c for c in struct.pack('>I', i)])
    data.extend([0x00]*7)
    enc = qq_encrypt(data, key)
    return base64.b64encode(bytes(enc), b'_-').replace(b'=', b'')

def load_key():
    url = 'http://vv.video.qq.com/checktime'
    tree = ET.fromstring(get_content(url))
    t = int(tree.find('./t').text)
    return ccc(PLAYER_PLATFORM, PLAYER_VERSION, t)

"""

def qq_get_final_url(url, vid, fmt_id, filename, fvkey, platform):
    params = {
        'appver': PLAYER_VERSION,
        'otype': 'json',
        'platform': platform,
        'filename': filename,
        'vid': vid,
        'format': fmt_id,
    }

    content = get_content('http://vv.video.qq.com/getkey?' + urlencode(params))
    data = json.loads(match1(content, r'QZOutputJson=(.+);$'))

    vkey = data.get('key', fvkey)
    if vkey:
        url = "{}{}?vkey={}".format(url, filename, vkey)
    else:
        url = None
    vip = data.get('msg') == 'not pay'

    return url, vip



class QQ(VideoExtractor):

    name = u"腾讯视频 (QQ)"
    vip = None

    stream_2_id = {
        'fhd': 'BD',
        'shd': 'TD',
        'hd': 'HD',
        'mp4':'HD',
        'flv': 'HD',
        'sd': 'SD',
        'msd':'LD'
    }
    stream_ids = ['BD', 'TD', 'HD', 'SD', 'LD']


    def get_streams_info(self, profile='shd'):
        for PLAYER_PLATFORM in PLAYER_PLATFORMS.copy():
            params = {
                'otype': 'json',
                'platform': PLAYER_PLATFORM,
                'vid': self.vid,
                'defnpayver': 1,
                'appver': PLAYER_VERSION,
                'defn': profile,
            }

            content = get_content('http://vv.video.qq.com/getinfo?' + urlencode(params))
            data = json.loads(match1(content, r'QZOutputJson=(.+);$'))
            self.logger.debug('data: ' + str(data))

            if 'msg' in data:
                assert data['msg'] != 'vid status wrong', 'wrong vid'
                PLAYER_PLATFORMS.remove(PLAYER_PLATFORM)
                continue

            if PLAYER_PLATFORMS and \
                    profile == 'shd' and \
                    '"name":"shd"' not in content and \
                    '"name":"fhd"' not in content:
                for infos in self.get_streams_info('hd'):
                    yield infos
                return
            break

        assert 'msg' not in data, data['msg']
        #self.fp2p = data.get('fp2p')
        video = data['vl']['vi'][0]
        fn = video['fn']
        title = video['ti']
        td = float(video['td'])
        fvkey = video.get('fvkey')
        # Not to be absolutely accuracy.
        self.vip = video['drm']
        self.slow = not self.vip and (video['iflag'] or video['pl'])

        # Priority for range fetch.
        cdn_url_1 = cdn_url_2 = cdn_url_3 = None
        for cdn in video['ul']['ui']:
            cdn_url = cdn['url']
            # 'video.dispatch.tc.qq.com' supported keep-alive link.
            if cdn_url == 'http://video.dispatch.tc.qq.com/':
                cdn_url_3 = cdn_url
            # IP host.
            elif match1(cdn_url, '(^https?://[0-9\.]+/)'):
                if not cdn_url_2:
                    cdn_url_2 = cdn_url
            elif not cdn_url_1:
                cdn_url_1 = cdn_url
        if self.slow:
            cdn_url = cdn_url_3 or cdn_url_1 or cdn_url_2
        else:
            cdn_url = cdn_url_1 or cdn_url_2 or cdn_url_3

        dt = cdn['dt']
        if dt == 1:
            type_name = 'flv'
        elif dt == 2:
            type_name = 'mp4'
        else:
            type_name = 'unknown'

        _num_clips = video['cl']['fc']

        for fmt in data['fl']['fi']:
            fmt_id = fmt['id']
            fmt_name = fmt['name']
            fmt_cname = fmt['cname']
            size = fmt['fs']
            rate = size // td

            fns = fn.split('.')
            fmt_id_num = fmt_id
            fmt_id_prefix = None
            num_clips = 0

            if fmt_id_num > 100000:
                fmt_id_prefix = 'm'
            elif fmt_id_num > 10000:
                fmt_id_prefix = 'p'
                num_clips = _num_clips or 1
            if fmt_id_prefix:
                fmt_id_name = fmt_id_prefix + str(fmt_id_num % 10000)
                if fns[1][0] in ('p', 'm') and not fns[1].startswith('mp'):
                    fns[1] = fmt_id_name
                else:
                    fns.insert(1, fmt_id_name)
            elif fns[1][0] in ('p', 'm') and not fns[1].startswith('mp'):
                del fns[1]

            urls =[]

            if num_clips == 0:
                filename = '.'.join(fns)
                url, vip = qq_get_final_url(cdn_url, self.vid, fmt_id, filename, fvkey, PLAYER_PLATFORM)
                if vip:
                    self.vip = vip
                elif url:
                    urls.append(url)
            else:
                fns.insert(-1, '1')
                for idx in range(1, num_clips+1):
                    fns[-2] = str(idx)
                    filename = '.'.join(fns)
                    url, vip = qq_get_final_url(cdn_url, self.vid, fmt_id, filename, fvkey, PLAYER_PLATFORM)
                    if vip:
                        self.vip = vip
                        break
                    elif url:
                        urls.append(url)

            yield title, fmt_name, fmt_cname, type_name, urls, size, rate

    def prepare(self):
        info = VideoInfo(self.name)
        if not self.vid:
            self.vid = match1(self.url, 'vid=(\w+)', '/(\w+)\.html')

        if self.vid and match1(self.url, '(^https?://film\.qq\.com)'):
            self.url = 'http://v.qq.com/x/cover/%s.html' % self.vid

        if not self.vid or len(self.vid) != 11:
            html = get_content(self.url)
            self.vid = match1(html, '&vid=(\w+)', 'vid:\s*[\"\'](\w+)', 'vid\s*=\s*[\"\']\s*(\w+)')

            if not self.vid and '<body class="page_404">' in html:
                self.logger.warning('This video has been deleted!')
                return info

        video_rate = {}
        for _ in range(2):
            try:
                for title, fmt_name, stream_profile, type_name, urls, size, rate in self.get_streams_info():
                    stream_id = self.stream_2_id[fmt_name]
                    if urls and stream_id not in info.stream_types:
                        info.stream_types.append(stream_id)
                        info.streams[stream_id] = {'container': type_name, 'video_profile': stream_profile, 'src' : urls, 'size': size}
                        video_rate[stream_id] = rate
                break
            except AssertionError as e:
                if 'wrong vid' in str(e):
                    html = get_content(self.url)
                    self.vid = match1(html, '&vid=(\w+)', 'vid:\s*[\"\'](\w+)', 'vid\s*=\s*[\"\']\s*(\w+)')
                    continue
                raise e

        if self.vip:
            self.logger.warning('This is a VIP video!')
            self.slow = False

        assert len(info.stream_types), "can't play this video!!"
        info.stream_types = sorted(info.stream_types, key = self.stream_ids.index)
        info.title = title

        if self.slow:
            # Downloading some videos is very slow, use multithreading range fetch to speed up.
            # Only for video players now.
            info.extra['rangefetch'] = {'first_size': 1024 * 16, 'max_size': 1024 * 32, 'threads': 10, 'video_rate': video_rate}
            self.logger.warning('This is a slow video!')

        return info

    def prepare_list(self):
        html = get_content(self.url)
        vids = [a.strip('"') for a in match1(html, '\"vid\":\[([^\]]+)').split(',')]
        return vids

site = QQ()

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from ykdl.util.html import get_content
from ykdl.util.match import match1, matchall
from ykdl.extractor import VideoExtractor
from ykdl.videoinfo import VideoInfo
import xml.etree.ElementTree as ET

from ykdl.compact import urlencode, compact_bytes

import random
import base64
import struct
import uuid
import json

PLAYER_PLATFORMS = [11, 2, 1]
PLAYER_VERSION = '3.2.19.333'
"""
LEGACY FOR REFERENCE ONLY

def pack(data):
    target = []
    target.extend(struct.pack('>I', data[0]))
    target.extend(struct.pack('>I', data[1]))
    target = [c for c in target]
    return target

def unpack(data):
    data = ''.join([chr(b) for b in data])
    target = []
    data = data.encode('latin')
    target.extend(struct.unpack('>I', data[:4]))
    target.extend(struct.unpack('>I', data[4:8]))
    return target

def tea_encrypt(v, key):
    delta = 0x9e3779b9
    s = 0
    v = unpack(v)
    rounds = 16
    while rounds:
        s += delta
        s &= 0xffffffff
        v[0] += (v[1]+s) ^ ((v[1]>>5)+key[1]) ^ ((v[1]<<4)+key[0])
        v[0] &= 0xffffffff
        v[1] += (v[0]+s) ^ ((v[0]>>5)+key[3]) ^ ((v[0]<<4)+key[2])
        v[1] &= 0xffffffff
        rounds = rounds - 1
    return pack(v)

def qq_encrypt(data, key):
    temp = [0x00]*8
    enc = tea_encrypt(data, key)
    for i in range(8, len(data), 8):
        d1 = data[i:]
        for j in range(8):
            d1[j] = d1[j] ^ enc[i+j-8]
        d1 = tea_encrypt(d1, key)
        for j in range(len(d1)):
            d1[j] = d1[j]^data[i+j-8]^temp[j]
            enc.append(d1[j])
            temp[j] = enc[i+j-8]
    return enc

def strsum(data):
    s = 0
    for c in data:
        s = s*131 + ord(c)
    return 0x7fffffff & s

def ccc(platform, version, timestamp):
    key = [1735078436, 1281895718, 1815356193, 879325047]
    s1 = '<KEY>'
    d = [0x3039, 0x02]
    d.append(timestamp)
    d.append(platform)
    d.append(strsum(version))
    d.append(strsum(s1))
    data = [0xa6, 0xf1, 0xd9, 0x2a, 0x82, 0xc8, 0xd8, 0xfe, 0x43]
    for i in d:
        data.extend([c for c in struct.pack('>I', i)])
    data.extend([0x00]*7)
    enc = qq_encrypt(data, key)
    return base64.b64encode(bytes(enc), b'_-').replace(b'=', b'')

def load_key():
    url = 'http://vv.video.qq.com/checktime'
    tree = ET.fromstring(get_content(url))
    t = int(tree.find('./t').text)
    return ccc(PLAYER_PLATFORM, PLAYER_VERSION, t)

"""

def qq_get_final_url(url, vid, fmt_id, filename, fvkey, platform):
    params = {
        'appver': PLAYER_VERSION,
        'otype': 'json',
        'platform': platform,
        'filename': filename,
        'vid': vid,
        'format': fmt_id,
    }

    content = get_content('http://vv.video.qq.com/getkey?' + urlencode(params))
    data = json.loads(match1(content, r'QZOutputJson=(.+);$'))

    vkey = data.get('key', fvkey)
    if vkey:
        url = "{}{}?vkey={}".format(url, filename, vkey)
    else:
        url = None
    vip = data.get('msg') == 'not pay'

    return url, vip



class QQ(VideoExtractor):

    name = u"腾讯视频 (QQ)"
    vip = None

    stream_2_id = {
        'fhd': 'BD',
        'shd': 'TD',
        'hd': 'HD',
        'mp4':'HD',
        'flv': 'HD',
        'sd': 'SD',
        'msd':'LD'
    }
    stream_ids = ['BD', 'TD', 'HD', 'SD', 'LD']


    def get_streams_info(self, profile='shd'):
        for PLAYER_PLATFORM in PLAYER_PLATFORMS.copy():
            params = {
                'otype': 'json',
                'platform': PLAYER_PLATFORM,
                'vid': self.vid,
                'defnpayver': 1,
                'appver': PLAYER_VERSION,
                'defn': profile,
            }

            content = get_content('http://vv.video.qq.com/getinfo?' + urlencode(params))
            data = json.loads(match1(content, r'QZOutputJson=(.+);$'))
            self.logger.debug('data: ' + str(data))

            if 'msg' in data:
                assert data['msg'] != 'vid status wrong', 'wrong vid'
                PLAYER_PLATFORMS.remove(PLAYER_PLATFORM)
                continue

            if PLAYER_PLATFORMS and \
                    profile == 'shd' and \
                    '"name":"shd"' not in content and \
                    '"name":"fhd"' not in content:
                for infos in self.get_streams_info('hd'):
                    yield infos
                return
            break

        assert 'msg' not in data, data['msg']
        #self.fp2p = data.get('fp2p')
        video = data['vl']['vi'][0]
        fn = video['fn']
        title = video['ti']
        td = float(video['td'])
        fvkey = video.get('fvkey')
        # Not to be absolutely accuracy.
        self.vip = video['drm']
        self.slow = not self.vip and (video['iflag'] or video['pl'])

        # Priority for range fetch.
        cdn_url_1 = cdn_url_2 = cdn_url_3 = None
        for cdn in video['ul']['ui']:
            cdn_url = cdn['url']
            # 'video.dispatch.tc.qq.com' supported keep-alive link.
            if cdn_url == 'http://video.dispatch.tc.qq.com/':
                cdn_url_3 = cdn_url
            # IP host.
            elif match1(cdn_url, '(^https?://[0-9\.]+/)'):
                if not cdn_url_2:
                    cdn_url_2 = cdn_url
            elif not cdn_url_1:
                cdn_url_1 = cdn_url
        if self.slow:
            cdn_url = cdn_url_3 or cdn_url_1 or cdn_url_2
        else:
            cdn_url = cdn_url_1 or cdn_url_2 or cdn_url_3

        dt = cdn['dt']
        if dt == 1:
            type_name = 'flv'
        elif dt == 2:
            type_name = 'mp4'
        else:
            type_name = 'unknown'

        _num_clips = video['cl']['fc']

        for fmt in data['fl']['fi']:
            fmt_id = fmt['id']
            fmt_name = fmt['name']
            fmt_cname = fmt['cname']
            size = fmt['fs']
            rate = size // td

            fns = fn.split('.')
            fmt_id_num = fmt_id
            fmt_id_prefix = None
            num_clips = 0

            if fmt_id_num > 100000:
                fmt_id_prefix = 'm'
            elif fmt_id_num > 10000:
                fmt_id_prefix = 'p'
                num_clips = _num_clips or 1
            if fmt_id_prefix:
                fmt_id_name = fmt_id_prefix + str(fmt_id_num % 10000)
                if fns[1][0] in ('p', 'm') and not fns[1].startswith('mp'):
                    fns[1] = fmt_id_name
                else:
                    fns.insert(1, fmt_id_name)
            elif fns[1][0] in ('p', 'm') and not fns[1].startswith('mp'):
                del fns[1]

            urls =[]

            if num_clips == 0:
                filename = '.'.join(fns)
                url, vip = qq_get_final_url(cdn_url, self.vid, fmt_id, filename, fvkey, PLAYER_PLATFORM)
                if vip:
                    self.vip = vip
                elif url:
                    urls.append(url)
            else:
                fns.insert(-1, '1')
                for idx in range(1, num_clips+1):
                    fns[-2] = str(idx)
                    filename = '.'.join(fns)
                    url, vip = qq_get_final_url(cdn_url, self.vid, fmt_id, filename, fvkey, PLAYER_PLATFORM)
                    if vip:
                        self.vip = vip
                        break
                    elif url:
                        urls.append(url)

            yield title, fmt_name, fmt_cname, type_name, urls, size, rate

    def prepare(self):
        info = VideoInfo(self.name)
        if not self.vid:
            self.vid = match1(self.url, 'vid=(\w+)', '/(\w+)\.html')

        if self.vid and match1(self.url, '(^https?://film\.qq\.com)'):
            self.url = 'http://v.qq.com/x/cover/%s.html' % self.vid

        if not self.vid or len(self.vid) != 11:
            html = get_content(self.url)
            self.vid = match1(html, '&vid=(\w+)', 'vid:\s*[\"\'](\w+)', 'vid\s*=\s*[\"\']\s*(\w+)')

            if not self.vid and '<body class="page_404">' in html:
                self.logger.warning('This video has been deleted!')
                return info

        video_rate = {}
        for _ in range(2):
            try:
                for title, fmt_name, stream_profile, type_name, urls, size, rate in self.get_streams_info():
                    stream_id = self.stream_2_id[fmt_name]
                    if urls and stream_id not in info.stream_types:
                        info.stream_types.append(stream_id)
                        info.streams[stream_id] = {'container': type_name, 'video_profile': stream_profile, 'src' : urls, 'size': size}
                        video_rate[stream_id] = rate
                break
            except AssertionError as e:
                if 'wrong vid' in str(e):
                    html = get_content(self.url)
                    self.vid = match1(html, '&vid=(\w+)', 'vid:\s*[\"\'](\w+)', 'vid\s*=\s*[\"\']\s*(\w+)')
                    continue
                raise e

        if self.vip:
            self.logger.warning('This is a VIP video!')
            self.slow = False

        assert len(info.stream_types), "can't play this video!!"
        info.stream_types = sorted(info.stream_types, key = self.stream_ids.index)
        info.title = title

        if self.slow:
            # Downloading some videos is very slow, use multithreading range fetch to speed up.
            # Only for video players now.
            info.extra['rangefetch'] = {'first_size': 1024 * 16, 'max_size': 1024 * 32, 'threads': 10, 'video_rate': video_rate}
            self.logger.warning('This is a slow video!')

        return info

    def prepare_list(self):
        html = get_content(self.url)
        vids = [a.strip('"') for a in match1(html, '\"vid\":\[([^\]]+)').split(',')]
        return vids

site = QQ()

#!/usr/bin/env python # -*- coding: utf-8 -*- LEGACY FOR REFERENCE ONLY  def pack(data):     target = []     target.extend(struct.pack('>I', data[0]))     target.extend(struct.pack('>I', data[1]))     target = [c for c in target]     return target  def unpack(data):     data = ''.join([chr(b) for b in data])     target = []     data = data.encode('latin')     target.extend(struct.unpack('>I', data[:4]))     target.extend(struct.unpack('>I', data[4:8]))     return target  def tea_encrypt(v, key):     delta = 0x9e3779b9     s = 0     v = unpack(v)     rounds = 16     while rounds:         s += delta         s &= 0xffffffff         v[0] += (v[1]+s) ^ ((v[1]>>5)+key[1]) ^ ((v[1]<<4)+key[0])         v[0] &= 0xffffffff         v[1] += (v[0]+s) ^ ((v[0]>>5)+key[3]) ^ ((v[0]<<4)+key[2])         v[1] &= 0xffffffff         rounds = rounds - 1     return pack(v)  def qq_encrypt(data, key):     temp = [0x00]*8     enc = tea_encrypt(data, key)     for i in range(8, len(data), 8):         d1 = data[i:]         for j in range(8):             d1[j] = d1[j] ^ enc[i+j-8]         d1 = tea_encrypt(d1, key)         for j in range(len(d1)):             d1[j] = d1[j]^data[i+j-8]^temp[j]             enc.append(d1[j])             temp[j] = enc[i+j-8]     return enc  def strsum(data):     s = 0     for c in data:         s = s*131 + ord(c)     return 0x7fffffff & s  def ccc(platform, version, timestamp):     key = [1735078436, 1281895718, 1815356193, 879325047]     s1 = '<KEY>'     d = [0x3039, 0x02]     d.append(timestamp)     d.append(platform)     d.append(strsum(version))     d.append(strsum(s1))     data = [0xa6, 0xf1, 0xd9, 0x2a, 0x82, 0xc8, 0xd8, 0xfe, 0x43]     for i in d:         data.extend([c for c in struct.pack('>I', i)])     data.extend([0x00]*7)     enc = qq_encrypt(data, key)     return base64.b64encode(bytes(enc), b'_-').replace(b'=', b'')  def load_key():     url = 'http://vv.video.qq.com/checktime'     tree = ET.fromstring(get_content(url))     t = int(tree.find('./t').text)     return ccc(PLAYER_PLATFORM, PLAYER_VERSION, t) #self.fp2p = data.get('fp2p') # Not to be absolutely accuracy. # Priority for range fetch. # 'video.dispatch.tc.qq.com' supported keep-alive link. # IP host. # Downloading some videos is very slow, use multithreading range fetch to speed up. # Only for video players now.