123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 |
- # -*- 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 <http://www.gnu.org/licenses/>.
- '''
-
- 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<id>.+?)(\?|$|&)', url, re.IGNORECASE | re.DOTALL)
|