# -*- coding: UTF-8 -*-
import urllib2
# source from https://github.com/rg3/youtube-dl/issues/1208
# removed some unnecessary debug messages..
class CVevoSignAlgoExtractor:
# MAX RECURSION Depth for security
MAX_REC_DEPTH = 5
def __init__(self):
self.algoCache = {}
self._cleanTmpVariables()
def _cleanTmpVariables(self):
self.fullAlgoCode = ''
self.allLocalFunNamesTab = []
self.playerData = ''
def _jsToPy(self, jsFunBody):
pythonFunBody = jsFunBody.replace('function', 'def').replace('{', ':\n\t').replace('}', '').replace(';', '\n\t').replace('var ', '')
pythonFunBody = pythonFunBody.replace('.reverse()', '[::-1]')
lines = pythonFunBody.split('\n')
for i in range(len(lines)):
# a.split("") -> list(a)
match = re.search('(\w+?)\.split\(""\)', lines[i])
if match:
lines[i] = lines[i].replace(match.group(0), 'list(' + match.group(1) + ')')
# a.length -> len(a)
match = re.search('(\w+?)\.length', lines[i])
if match:
lines[i] = lines[i].replace(match.group(0), 'len(' + match.group(1) + ')')
# a.slice(3) -> a[3:]
match = re.search('(\w+?)\.slice\(([0-9]+?)\)', lines[i])
if match:
lines[i] = lines[i].replace(match.group(0), match.group(1) + ('[%s:]' % match.group(2)))
# a.join("") -> "".join(a)
match = re.search('(\w+?)\.join\(("[^"]*?")\)', lines[i])
if match:
lines[i] = lines[i].replace(match.group(0), match.group(2) + '.join(' + match.group(1) + ')')
return "\n".join(lines)
def _getLocalFunBody(self, funName):
# get function body
match = re.search('(function %s\([^)]+?\){[^}]+?})' % funName, self.playerData)
if match:
# return jsFunBody
return match.group(1)
return ''
def _getAllLocalSubFunNames(self, mainFunBody):
match = re.compile('[ =(,](\w+?)\([^)]*?\)').findall(mainFunBody)
if len(match):
# first item is name of main function, so omit it
funNameTab = set(match[1:])
return funNameTab
return set()
def decryptSignature(self, s, playerUrl):
playerUrl = playerUrl[:4] != 'http' and 'http:' + playerUrl or playerUrl
util.debug("decrypt_signature sign_len[%d] playerUrl[%s]" % (len(s), playerUrl))
# clear local data
self._cleanTmpVariables()
# use algoCache
if playerUrl not in self.algoCache:
# get player HTML 5 sript
request = urllib2.Request(playerUrl)
try:
self.playerData = urllib2.urlopen(request).read()
self.playerData = self.playerData.decode('utf-8', 'ignore')
except:
util.debug('Unable to download playerUrl webpage')
return ''
# get main function name
match = re.search("signature=(\w+?)\([^)]\)", self.playerData)
if match:
mainFunName = match.group(1)
util.debug('Main signature function name = "%s"' % mainFunName)
else:
util.debug('Can not get main signature function name')
return ''
self._getfullAlgoCode(mainFunName)
# wrap all local algo function into one function extractedSignatureAlgo()
algoLines = self.fullAlgoCode.split('\n')
for i in range(len(algoLines)):
algoLines[i] = '\t' + algoLines[i]
self.fullAlgoCode = 'def extractedSignatureAlgo(param):'
self.fullAlgoCode += '\n'.join(algoLines)
self.fullAlgoCode += '\n\treturn %s(param)' % mainFunName
self.fullAlgoCode += '\noutSignature = extractedSignatureAlgo( inSignature )\n'
# after this function we should have all needed code in self.fullAlgoCode
try:
algoCodeObj = compile(self.fullAlgoCode, '', 'exec')
except:
util.debug('decryptSignature compile algo code EXCEPTION')
return ''
else:
# get algoCodeObj from algoCache
util.debug('Algo taken from cache')
algoCodeObj = self.algoCache[playerUrl]
# for security alow only flew python global function in algo code
vGlobals = {"__builtins__": None, 'len': len, 'list': list}
# local variable to pass encrypted sign and get decrypted sign
vLocals = { 'inSignature': s, 'outSignature': '' }
# execute prepared code
try:
exec(algoCodeObj, vGlobals, vLocals)
except:
util.debug('decryptSignature exec code EXCEPTION')
return ''
util.debug('Decrypted signature = [%s]' % vLocals['outSignature'])
# if algo seems ok and not in cache, add it to cache
if playerUrl not in self.algoCache and '' != vLocals['outSignature']:
util.debug('Algo from player [%s] added to cache' % playerUrl)
self.algoCache[playerUrl] = algoCodeObj
# free not needed data
self._cleanTmpVariables()
return vLocals['outSignature']
# Note, this method is using a recursion
def _getfullAlgoCode(self, mainFunName, recDepth=0):
if self.MAX_REC_DEPTH <= recDepth:
util.debug('_getfullAlgoCode: Maximum recursion depth exceeded')
return
funBody = self._getLocalFunBody(mainFunName)
if '' != funBody:
funNames = self._getAllLocalSubFunNames(funBody)
if len(funNames):
for funName in funNames:
if funName not in self.allLocalFunNamesTab:
self.allLocalFunNamesTab.append(funName)
util.debug("Add local function %s to known functions" % mainFunName)
self._getfullAlgoCode(funName, recDepth + 1)
# conver code from javascript to python
funBody = self._jsToPy(funBody)
self.fullAlgoCode += '\n' + funBody + '\n'
return
decryptor = CVevoSignAlgoExtractor()
'''
YouTube plugin for XBMC
Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
'''
import sys
import urllib
import cgi
import simplejson as json
class YoutubePlayer(object):
fmt_value = {
5: "240p",
18: "360p",
22: "720p",
26: "???",
33: "???",
34: "360p",
35: "480p",
37: "1080p",
38: "720p",
43: "360p",
44: "480p",
45: "720p",
46: "520p",
59: "480",
78: "400",
82: "360p",
83: "240p",
84: "720p",
85: "520p",
100: "360p",
101: "480p",
102: "720p",
120: "hd720",
121: "hd1080"
}
# YouTube Playback Feeds
urls = {}
urls['video_stream'] = "http://www.youtube.com/watch?v=%s&safeSearch=none"
urls['embed_stream'] = "http://www.youtube.com/get_video_info?video_id=%s"
urls['video_info'] = "http://gdata.youtube.com/feeds/api/videos/%s"
def __init__(self):
pass
def removeAdditionalEndingDelimiter(self, data):
pos = data.find("};")
if pos != -1:
data = data[:pos + 1]
return data
def extractFlashVars(self, data, assets):
flashvars = {}
found = False
for line in data.split("\n"):
if line.strip().find(";ytplayer.config = ") > 0:
found = True
p1 = line.find(";ytplayer.config = ") + len(";ytplayer.config = ") - 1
p2 = line.rfind(";")
if p1 <= 0 or p2 <= 0:
continue
data = line[p1 + 1:p2]
break
data = self.removeAdditionalEndingDelimiter(data)
if found:
data = json.loads(data)
if assets:
flashvars = data["assets"]
else:
flashvars = data["args"]
return flashvars
def scrapeWebPageForVideoLinks(self, result, video):
links = {}
flashvars = self.extractFlashVars(result, 0)
if not flashvars.has_key(u"url_encoded_fmt_stream_map"):
return links
if flashvars.has_key(u"ttsurl"):
video[u"ttsurl"] = flashvars[u"ttsurl"]
if flashvars.has_key("title"):
video["title"] = flashvars["title"]
for url_desc in flashvars[u"url_encoded_fmt_stream_map"].split(u","):
url_desc_map = cgi.parse_qs(url_desc)
if not (url_desc_map.has_key(u"url") or url_desc_map.has_key(u"stream")):
continue
key = int(url_desc_map[u"itag"][0])
url = u""
if url_desc_map.has_key(u"url"):
url = urllib.unquote(url_desc_map[u"url"][0])
elif url_desc_map.has_key(u"conn") and url_desc_map.has_key(u"stream"):
url = urllib.unquote(url_desc_map[u"conn"][0])
if url.rfind("/") < len(url) - 1:
url = url + "/"
url = url + urllib.unquote(url_desc_map[u"stream"][0])
elif url_desc_map.has_key(u"stream") and not url_desc_map.has_key(u"conn"):
url = urllib.unquote(url_desc_map[u"stream"][0])
if url_desc_map.has_key(u"sig"):
url = url + u"&signature=" + url_desc_map[u"sig"][0]
elif url_desc_map.has_key(u"s"):
sig = url_desc_map[u"s"][0]
flashvars = self.extractFlashVars(result, 1)
js = flashvars[u"js"]
url = url + u"&signature=" + self.decrypt_signature(sig, js)
links[key] = url
return links
def decrypt_signature(self, s, js):
return decryptor.decryptSignature(s, js)
def extractVideoLinksFromYoutube(self, url, videoid, video):
result = util.request(self.urls[u"video_stream"] % videoid)
links = self.scrapeWebPageForVideoLinks(result, video)
if len(links) == 0:
util.error(u"Couldn't find video url- or stream-map.")
return links
# /*
# * Copyright (C) 2011 Libor Zoubek
# *
# *
# * This Program is free software; you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation; either version 2, or (at your option)
# * any later version.
# *
# * This Program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; see the file COPYING. If not, write to
# * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
# * http://www.gnu.org/copyleft/gpl.html
# *
# */
import re, util, urllib
__name__ = 'youtube'
def supports(url):
return not _regex(url) == None
def resolve(url):
m = _regex(url)
if not m == None:
player = YoutubePlayer()
video = {'title':'žádný název'}
index = url.find('&') # strip out everytihing after &
if index > 0:
url = url[:index]
links = player.extractVideoLinksFromYoutube(url, m.group('id'), video)
resolved = []
for q in links:
if q in player.fmt_value.keys():
quality = player.fmt_value[q]
item = {}
item['name'] = __name__
item['url'] = links[q]
item['quality'] = quality
item['surl'] = url
item['subs'] = ''
item['title'] = video['title']
item['fmt'] = q
resolved.append(item)
return resolved
def _regex(url):
return re.search('www\.youtube\.com/(watch\?v=|v/|embed/)(?P.+?)(\?|$|&)', url, re.IGNORECASE | re.DOTALL)