Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adaptation new youku api #38

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
258 changes: 84 additions & 174 deletions addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
# default.py

import xbmcgui, xbmcaddon, xbmc
import json, sys, urllib, urllib2, gzip, StringIO, re, os, time, threading, socket, base64, math, cookielib
import json, sys, urllib, urlparse, os, time, threading, socket
from video_concatenate import video_concatenate
try:
import StorageServer
except:
import storageserverdummy as StorageServer

import requests

__addonid__ = "plugin.video.youkutv"
__addon__ = xbmcaddon.Addon(id=__addonid__)
__cwd__ = __addon__.getAddonInfo('path')
Expand All @@ -26,6 +28,10 @@
HOST = 'http://tv.api.3g.youku.com/'
IDS = 'pid=0ce22bfd5ef5d2c5&guid=12d60728bd267e3e0b6ab4d124d6c5f0&ngdid=357e71ee78debf7340d29408b88c85c4&ver=2.6.0&operator=T-Mobile_310260&network=WIFI&launcher=0'

headers = {'Referer': 'http://v.youku.com',
'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.101 Safari/537.36',
}

#Fixed tabs
Navigation = ['首页', '频道', '排行']
ContentID = [520, 560, 580]
Expand All @@ -41,22 +47,23 @@
mainData = [{'title': '搜索', 'image': 'yk_search.jpg', 'mtype': 'search'},
{'title': '观看记录', 'image': 'yk_history.jpg', 'mtype': 'history'},
{'title': '收藏', 'image': 'yk_favor.jpg', 'mtype': 'favor'}]
settings_data = {'resolution':[u'1080P', u'超清', u'高清', u'标清', u'标清(3GP)'],
'resolution_type':[['hd3','mp4hd3'], ['hd2','mp4hd2'], ['mp4','mp4hd'], ['flv','flvhd'], ['3gphd']],
settings_data = {'resolution':[u'1080P', u'超清', u'高清', u'标清'],
'resolution_type':[['mp4hd3', 'mp4hd3v2'], ['mp4hd2', 'mp4hd2v2'], ['mp4hd', '3gphd'], ['mp4sd', 'flvhd']],
'language':[u'默认', u'国语', u'粤语', u'英语'],
'language_code':[u'', u'guoyu', u'yue', u'yingyu'],
'play':['整合(试验阶段)', '分段', '堆叠'],
'play_type':['concatenate', 'list', 'stack']}
settings={'resolution':0, 'language':0, 'play':0}
resolution_map = {'3gphd': '3gp',
'flv': 'flv',
'flvhd': 'flv',
'mp4': 'mp4',
'mp4hd': 'mp4',
'hd2': 'flv',
'mp4hd2': 'flv',
'hd3': 'flv',
'mp4hd3': 'flv'}
resolution_map = {
"3gphd": "mp4",
"mp4hd2": "hd2",
"mp4hd3": "hd3",
"flvhd": "flv",
"mp4hd3v2": "hd3",
"mp4hd2v2": "hd2",
"mp4sd": "flv",
"mp4hd": "mp4"
}


ACTION_MOVE_LEFT = 1
Expand Down Expand Up @@ -1869,92 +1876,6 @@ def getSetting(self,key,default=None):
return setting == 'true'
return setting

class youkuDecoder:
def __init__( self ):
return

def getFileIDMixString(self,seed):
mixed = []
source = list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\:._-1234567890")
seed = float(seed)
for i in range(len(source)):
seed = (seed * 211 + 30031 ) % 65536
index = math.floor(seed /65536 *len(source))
mixed.append(source[int(index)])
source.remove(source[int(index)])
return mixed

def getFileId(self,fileId,seed):
mixed = self.getFileIDMixString(seed)
ids = fileId.split('*')
realId = []
for i in range(0,len(ids)-1):
realId.append(mixed[int(ids[i])])
return ''.join(realId)

def trans_e(self, a, c):
b = range(256)
f = 0
result = ''
h = 0
while h < 256:
f = (f + b[h] + ord(a[h % len(a)])) % 256
b[h], b[f] = b[f], b[h]
h += 1
q = f = h = 0
while q < len(c):
h = (h + 1) % 256
f = (f + b[h]) % 256
b[h], b[f] = b[f], b[h]
if isinstance(c[q], int):
result += chr(c[q] ^ b[(b[h] + b[f]) % 256])
else:
result += chr(ord(c[q]) ^ b[(b[h] + b[f]) % 256])
q += 1
return result

def trans_f(self, a, c):
"""
:argument a: list
:param c:
:return:
"""
b = []
for f in range(len(a)):
i = ord(a[f][0]) - 97 if "a" <= a[f] <= "z" else int(a[f]) + 26
e = 0
while e < 36:
if c[e] == i:
i = e
break
e += 1
v = i - 26 if i > 25 else chr(i + 97)
b.append(str(v))
return ''.join(b)

f_code_1 = 'becaf9be'
f_code_2 = 'bf7e5f01'

def _calc_ep(self, sid, fileId, token):
ep = self.trans_e(self.f_code_2, '%s_%s_%s' % (sid, fileId, token))
return base64.b64encode(ep)

def _calc_ep2(self, vid, ep):
e_code = self.trans_e(self.f_code_1, base64.b64decode(ep))
sid, token = e_code.split('_')
new_ep = self.trans_e(self.f_code_2, '%s_%s_%s' % (sid, vid, token))
return base64.b64encode(new_ep), token, sid

def get_sid(self, ep):
e_code = self.trans_e(self.f_code_1, base64.b64decode(ep))
return e_code.split('_')

def generate_ep(self, no, fileid, sid, token):
ep = urllib.quote(self._calc_ep(sid, fileid, token).encode('latin1'),
safe="~()*!.'"
)
return ep

def getNumber(data, k):
try:
s = data[k]
Expand Down Expand Up @@ -2005,10 +1926,48 @@ def getProperty(item, key):

return value

def fetch_cna():
url = 'http://log.mmstat.com/eg.js'
req = requests.get(url)
cna = req.headers['Set-Cookie'].split(';')[0].split('=')[1]
if '%' not in cna:
cna = requests.compat.quote(cna)
return cna

def youku_ups(vid, ccode='0502'):
url = 'http://ups.youku.com/ups/get.json?vid={}&ccode={}'.format(vid, ccode)
url += '&client_ip=192.168.1.1'
url += '&utid=' + fetch_cna()
url += '&client_ts=' + str(int(time.time()))
# Found in http://g.alicdn.com/player/ykplayer/0.5.28/youku-player.min.js
# grep -oE '"[0-9a-zA-Z+/=]{256}"' youku-player.min.js
ckey = 'DIl58SLFxFNndSV1GFNnMQVYkx1PP5tKe1siZu/86PR1u/Wh1Ptd+WOZsHHWxysSfAOhNJpdVWsdVJNsfJ8Sxd8WKVvNfAS8aS8fAOzYARzPyPc3JvtnPHjTdKfESTdnuTW6ZPvk2pNDh4uFzotgdMEFkzQ5wZVXl2Pf1/Y6hLK0OnCNxBj3+nb0v72gZ6b0td+WOZsHHWxysSo/0y9D2K42SaB8Y/+aD2K42SaB8Y/+ahU+WOZsHcrxysooUeND'
url += '&ckey=' + requests.compat.quote(ckey)
time.sleep(3)
return requests.get(url, headers=headers).json()

def change_cdn(url, dispatcher_url='vali.cp31.ott.cibntv.net'):
# if the cnd_url starts with an ip addr, it should be youku's old CDN
# which rejects http requests randomly with status code > 400
# change it to the dispatcher of aliCDN can do better
# at least a little more recoverable from HTTP 403
if dispatcher_url in url:
return url
elif 'k.youku.com' in url:
return url
else:
url_seg_list = list(urlparse.urlparse(url))
url_seg_list[1] = dispatcher_url
return urlparse.urlunparse(url_seg_list)

def add_headers(url):
# http://kodi.wiki/view/HTTP
param = '&'.join(['%s=%s' % (k, headers[k]) for k in headers])
return '%s|%s' % (url, param)


def play(vid, playContinue=False):
readSettings()
playid = vid

try:
ret = eval(cache.get('history'))
Expand Down Expand Up @@ -2040,42 +1999,30 @@ def play(vid, playContinue=False):

xbmc.executebuiltin("ActivateWindow(busydialog)")
try:
movinfo = json.loads(GetHttpData('http://play.youku.com/play/get.json?vid=%s&ct=12' % playid).replace('\r\n',''))
movdat = movinfo['data']
movinfo = json.loads(GetHttpData('http://play.youku.com/play/get.json?vid=%s&ct=10' % playid).replace('\r\n',''))
movdat1 = movinfo['data']
movdat = youku_ups(vid)['data']
assert 'stream' in movdat
assert 'stream' in movdat1
except:
except Exception, e:
xbmc.executebuiltin( "Dialog.Close(busydialog)" )
xbmcgui.Dialog().ok('提示框', '解析地址异常,无法播放')
xbmcgui.Dialog().ok('提示框', '解析地址异常,无法播放。\nNo stream.')
return

#Select resolution.
stream = {}
resolution = ''
language_code = settings_data['language_code'][settings['language']]
try:
for i in range(settings['resolution'], len(settings_data['resolution'])):
for t in settings_data['resolution_type'][i]:
for s in movdat1['stream'][::-1]:
if settings['language'] == 0 or language_code == s['audio_lang'] or s['audio_lang'] == 'default':
if t == s['stream_type']:
stream = s
resolution = settings_data['resolution_type'][i][0]
break
if stream.has_key('stream_type'):
break
if stream.has_key('stream_type'):
# resolution from higher to lower
for i in range(settings['resolution'], len(settings_data['resolution'])):
if stream:
break
for t in settings_data['resolution_type'][i]:
if stream:
break
except:
xbmc.executebuiltin( "Dialog.Close(busydialog)" )
xbmcgui.Dialog().ok('提示框', '解析地址异常,无法播放')
return
for s in movdat['stream'][::-1]:
if t == s['stream_type']:
stream = s
break

if not stream.has_key('stream_type'):
if not stream:
xbmc.executebuiltin( "Dialog.Close(busydialog)" )
xbmcgui.Dialog().ok('提示框', '解析地址异常,无法播放')
xbmcgui.Dialog().ok('提示框', '解析地址异常,无法播放。\nStream type not matched.')
return


Expand All @@ -2084,34 +2031,19 @@ def play(vid, playContinue=False):
urls = []
segs = stream['segs']

oip = movdat['security']['ip']
ep = movdat['security']['encrypt_string']
sid, token = youkuDecoder().get_sid(ep)

for no in range(len(segs)):
k = segs[no]['key']
assert k != -1
fileid = stream['segs'][no]['fileid']
ep = youkuDecoder().generate_ep(no, fileid, sid, token)
q = urllib.urlencode(dict(
ctype = 12,
ev = 1,
K = k,
ep = urllib.unquote(ep),
oip = oip,
token = token,
yxon = 1
))
u = 'http://k.youku.com/player/getFlvPath/sid/{sid}_00/st/{container}/fileid/{fileid}?{q}'.format(
sid = sid,
container = resolution_map[resolution],
fileid = fileid,
q = q
)
urls += [i['server'] for i in json.loads(GetHttpData(u))]
url = segs[no]["cdn_url"]
url = change_cdn(url)
url = add_headers(url)
urls.append(url)

playlist = xbmc.PlayList(1)
playlist.clear()
title = movdat['video']['title']
listitem=xbmcgui.ListItem(title)
listitem.setInfo(type="Video",infoLabels={"Title":title})

if settings_data['play_type'][settings['play']] == 'concatenate' and resolution_map[resolution] == 'flv':
vc.start(urls)
Expand All @@ -2122,9 +2054,9 @@ def play(vid, playContinue=False):
playlist.add('http://127.0.0.1:%d' % port, listitem)
elif settings_data['play_type'][settings['play']] == 'list':
for i in range(len(urls)):
title =movdat['video']['title'] + u" - 第"+str(i+1)+"/"+str(len(urls)) + u"节"
listitem=xbmcgui.ListItem(title)
listitem.setInfo(type="Video",infoLabels={"Title":title})
_title = u"%s - 第 %s/%s 节" % (title, i+1, len(urls))
listitem = xbmcgui.ListItem(_title)
listitem.setInfo(type="Video", infoLabels={"Title": _title})
playlist.add(urls[i], listitem)
else:
playurl = 'stack://' + ' , '.join(urls)
Expand Down Expand Up @@ -2198,25 +2130,7 @@ def openWindow(window_name,session=None,**kwargs):
def GetHttpData(url):
log('Frech: ' + url)
try:
req = urllib2.Request(url)
req.add_header('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64) {0}{1}'.
format('AppleWebKit/537.36 (KHTML, like Gecko) ',
'Chrome/28.0.1500.71 Safari/537.36'))
req.add_header('Accept-encoding', 'gzip')
if (url.find('play.youku.com') != -1):
req.add_header('referer', 'http://static.youku.com')
response = urllib2.urlopen(req)
httpdata = response.read()
if response.headers.get('content-encoding', None) == 'gzip':
httpdata = gzip.GzipFile(fileobj=StringIO.StringIO(httpdata)).read()
response.close()
match = re.compile('encodingt=(.+?)"').findall(httpdata)
if len(match)<=0:
match = re.compile('meta charset="(.+?)"').findall(httpdata)
if len(match)>0:
charset = match[0].lower()
if (charset != 'utf-8') and (charset != 'utf8'):
httpdata = unicode(httpdata, charset).encode('utf8')
httpdata = requests.get(url, headers=headers).content
except:
if xbmcgui.Dialog().yesno('错误', '网络超时,是否继续?'):
return GetHttpData(url)
Expand Down Expand Up @@ -2279,8 +2193,4 @@ def clearFavor():
openWindow('mysettings')
except:
if __name__ == '__main__':
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
opener.addheaders = [('Cookie', '__ysuid={0}'.format(time.time()))]
urllib2.install_opener(opener)
openWindow('main')
3 changes: 2 additions & 1 deletion addon.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<addon id="plugin.video.youkutv" name="YouKu TV" version="1.7.7" provider-name="catro">
<addon id="plugin.video.youkutv" name="YouKu TV" version="1.7.22" provider-name="catro">
<requires>
<import addon="script.common.plugin.cache" version="0.9.1"/>
<import addon="script.module.requests" version="2.9.1"/>
</requires>
<extension point="xbmc.python.script" library="addon.py">
<provides>video</provides>
Expand Down