import xbmc import xbmcgui import xbmcplugin import common import ads import subtitles import sys import binascii import base64 import os import hmac import operator import time import urllib import re import md5 from array import array from BeautifulSoup import BeautifulStoneSoup try: from xml.etree import ElementTree except: from elementtree import ElementTree smildeckeys = [ common.xmldeckeys[9] ] class Main: def __init__( self ): if 'http://' in common.args.url: video_id=self.getIDS4HTTP(common.args.url) self.queue=True httpplay=True else: self.queue=False httpplay=False video_id=common.args.url admodule = ads.Main() common.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) if common.args.mode.endswith('TV_play'): if os.path.isfile(common.ADCACHE): os.remove(common.ADCACHE) self.NoResolve=False self.GUID = common.makeGUID() if common.args.mode.startswith('Captions'): common.settings['enable_captions']='true' common.settings['segmentvideos'] = 'false' self.NoResolve=True elif common.args.mode.startswith('NoCaptions'): common.settings['enable_captions']='false' common.settings['segmentvideos'] = 'false' self.NoResolve=True elif common.args.mode.startswith('Select'): common.settings['quality']='0' common.settings['segmentvideos'] = 'false' self.NoResolve=True if ((common.settings['segmentvideos'] == 'true') or (common.settings['networkpreroll'] == 'true') or (common.settings['prerollads'] > 0) or (common.settings['trailads'] > 0)): common.playlist.clear() # POST VIEW if common.settings['enable_login']=='true' and common.settings['usertoken']: common.viewed(common.args.videoid) if not self.NoResolve: if (common.settings['networkpreroll'] == 'true'): self.NetworkPreroll() addcount = admodule.PreRoll(video_id,self.GUID,self.queue) if addcount > 0: self.queue=True else: addcount = 0 if common.settings['segmentvideos'] == 'true': segments = self.playSegment(video_id) if segments: adbreaks = common.settings['adbreaks'] for i in range(1,len(segments)+1): admodule.queueAD(video_id,adbreaks+addcount,addcount) addcount += adbreaks self.queueVideoSegment(video_id,segment=i) else: self.play(video_id) admodule.Trailing(addcount,video_id,self.GUID) if common.settings['queueremove']=='true' and common.settings['enable_login']=='true' and common.settings['usertoken']: self.queueViewComplete() if httpplay: xbmc.Player().play(common.playlist) elif common.args.mode == 'SEGMENT_play': self.queue=False self.NoResolve=False self.GUID = common.args.guid self.playSegment(video_id,segment=int(common.args.segment)) elif common.args.mode == 'AD_play': self.NoResolve=False self.GUID = common.args.guid pod = int(common.args.pod) admodule.playAD(video_id,pod,self.GUID) elif common.args.mode == 'SUBTITLE_play': subtitles.Main().SetSubtitles(video_id) def getIDS4HTTP(self, url): pagedata=common.getFEED(url) common.args.videoid = url.split('watch/')[1].split('/')[0] content_id = re.compile('so.addVariable\("content_id", (.*?)\);').findall(pagedata)[0].strip() common.args.eid = self.cid2eid(content_id) return content_id def cid2eid(self, content_id): m = md5.new() m.update(str(content_id) + "MAZxpK3WwazfARjIpSXKQ9cmg9nPe5wIOOfKuBIfz7bNdat6gQKHj69ZWNWNVB1") value = m.digest() return base64.encodestring(value).replace("+", "-").replace("/", "_").replace("=", "").replace('\n','') def getSMIL(self, video_id,retry=0): epoch = int(time.mktime(time.gmtime())) parameters = {'video_id' : video_id, 'v' : '888324234', 'ts' : str(epoch), 'np' : '1', 'vp' : '1', 'enable_fa' : '1', 'device_id' : self.GUID, 'pp' : 'Desktop', 'dp_id' : 'Hulu', 'region' : 'US', 'ep' : '1', 'language' : 'en' } if retry > 0: parameters['retry']=str(retry) if common.settings['enable_login']=='true' and common.settings['enable_plus']=='true' and common.settings['usertoken']: parameters['token'] = common.settings['usertoken'] smilURL = False for item1, item2 in parameters.iteritems(): if not smilURL: smilURL = 'http://s.hulu.com/select?'+item1+'='+item2 else: smilURL += '&'+item1+'='+item2 smilURL += '&bcs='+self.content_sig(parameters) print 'HULU --> SMILURL: ' + smilURL if common.settings['proxy_enable'] == 'true': proxy=True else: proxy=False smilXML=common.getFEED(smilURL,proxy=proxy) if smilXML: smilXML=self.decrypt_SMIL(smilXML) print "GOT SMIL" if smilXML: smilSoup=BeautifulStoneSoup(smilXML, convertEntities=BeautifulStoneSoup.HTML_ENTITIES) print smilSoup.prettify() return smilSoup else: return False else: return False def content_sig(self, parameters): hmac_key = 'f6daaa397d51f568dd068709b0ce8e93293e078f7dfc3b40dd8c32d36d2b3ce1' sorted_parameters = sorted(parameters.iteritems(), key=operator.itemgetter(0)) data = '' for item1, item2 in sorted_parameters: data += item1 + item2 sig = hmac.new(hmac_key, data) return sig.hexdigest() def decrypt_SMIL(self, encsmil): encdata = binascii.unhexlify(encsmil) expire_message = 'Your access to play this content has expired.' plus_message = 'please close any Hulu Plus videos you may be watching on other devices' proxy_message = 'you are trying to access Hulu through an anonymous proxy tool' for key in smildeckeys[:]: cbc = common.AES_CBC(binascii.unhexlify(key[0])) smil = cbc.decrypt(encdata,key[1]) print smil if (smil.find("<smil") == 0): #print key i = smil.rfind("</smil>") smil = smil[0:i+7] return smil elif expire_message in smil: xbmcgui.Dialog().ok('Content Expired',expire_message) return False elif plus_message in smil: xbmcgui.Dialog().ok('Too many sessions','please close any Hulu Plus videos','you may be watching on other devices') return False elif proxy_message in smil: xbmcgui.Dialog().ok('Proxy Detected','Based on your IP address we noticed','you are trying to access Hulu','through an anonymous proxy tool') return False def queueViewComplete(self): u = sys.argv[0] u += "?mode='viewcomplete'" u += '&videoid="'+urllib.quote_plus(common.args.videoid)+'"' item=xbmcgui.ListItem("Remove from Queue") common.playlist.add(url=u, listitem=item) def queueVideoSegment( self, video_id, segment=False): mode='SEGMENT_play' u = sys.argv[0] u += '?url="'+urllib.quote_plus(video_id)+'"' u += '&mode="'+urllib.quote_plus(mode)+'"' u += '&videoid="'+urllib.quote_plus(common.args.videoid)+'"' u += '&segment="'+urllib.quote_plus(str(segment))+'"' u += '&guid="'+urllib.quote_plus(self.GUID)+'"' item=xbmcgui.ListItem(self.displayname) item.setInfo( type="Video", infoLabels=self.infoLabels) item.setProperty('IsPlayable', 'true') common.playlist.add(url=u, listitem=item) def time2ms( self, time): hour,minute,seconds = time.split(';')[0].split(':') frame = int((float(time.split(';')[1])/24)*1000) milliseconds = (((int(hour)*60*60)+(int(minute)*60)+int(seconds))*1000)+frame return milliseconds def NetworkPreroll( self ): url = 'http://r.hulu.com/videos?eid='+common.args.eid+'&include=video_assets&include_eos=1&_language=en&_package_group_id=1&_region=US' data=common.getFEED(url) tree=BeautifulStoneSoup(data, convertEntities=BeautifulStoneSoup.HTML_ENTITIES) networkPreroll = tree.find('show').find('link-url').string if networkPreroll is not None: if '.flv' in networkPreroll: name = tree.find('channel').string infoLabels={ "Title":name } item = xbmcgui.ListItem(name+' Intro',path=networkPreroll) item.setInfo( type="Video", infoLabels=infoLabels) if self.queue: item.setProperty('IsPlayable', 'true') common.playlist.add(url=networkPreroll, listitem=item) else: self.queue = True xbmcplugin.setResolvedUrl(common.handle, True, item) def playSegment( self, video_id, segment=0): try: if segments > 0: smilSoup = self.getSMIL(video_id,retry=1) else: smilSoup = self.getSMIL(video_id) except: smilSoup = self.getSMIL(video_id,retry=1) if smilSoup: finalUrl = self.selectStream(smilSoup) self.displayname, self.infoLabels, segments = self.getMeta(smilSoup) segmentUrl = finalUrl if segments: segmentUrl = finalUrl if segment > 0: startseconds = self.time2ms(segments[segment-1]) segmentUrl += " start="+str(startseconds) if len(segments) > segment: stopseconds = self.time2ms(segments[segment]) segmentUrl += " stop="+str(stopseconds) item = xbmcgui.ListItem(self.displayname,path=segmentUrl) item.setInfo( type="Video", infoLabels=self.infoLabels) if self.queue: item.setProperty('IsPlayable', 'true') common.playlist.add(url=segmentUrl, listitem=item) else: self.queue = True xbmcplugin.setResolvedUrl(common.handle, True, item) return segments def play( self, video_id): if (common.settings['enable_captions'] == 'true'): subtitles.Main().checkCaptions(video_id) try: smilSoup = self.getSMIL(video_id) except: smilSoup = self.getSMIL(video_id,retry=1) if smilSoup: finalUrl = self.selectStream(smilSoup) displayname, infoLabels, segments = self.getMeta(smilSoup) item = xbmcgui.ListItem(displayname,path=finalUrl) item.setInfo( type="Video", infoLabels=infoLabels) if self.queue: item.setProperty('IsPlayable', 'true') common.playlist.add(url=finalUrl, listitem=item) else: self.queue = True if self.NoResolve: xbmc.Player().play(finalUrl,item) else: xbmcplugin.setResolvedUrl(common.handle, True, item) if (common.settings['enable_captions'] == 'true'): subtitles.Main().PlayWaitSubtitles(video_id) return segments def getMeta( self, smilSoup ): refs = smilSoup.findAll('ref') ref = refs[1] title = ref['title'] series_title = ref['tp:series_title'] plot = ref['abstract'] try:season = int(ref['tp:season_number']) except:season = -1 try:episode = int(ref['tp:episode_number']) except:episode = -1 displayname = series_title+' - '+str(season)+'x'+str(episode)+' - '+title try: playlist = refs[0]['src'] mpaa=re.compile('rating,([^\]]+)').findall(playlist)[0] except: mpaa='' infoLabels={ "Title":title, "TVShowTitle":series_title, "Plot":plot, "MPAA":mpaa, "Season":season, "Episode":episode} try: segments = ref['tp:segments'] if segments <> '': segments=segments.replace('T:','').split(',') else: segments = False except:segments = False return displayname, infoLabels, segments def selectStream( self, smilSoup ): video=smilSoup.findAll('video') if video is None or len(video) == 0: xbmcgui.Dialog().ok('No Video Streams','SMIL did not contain video links','Geo-Blocked') return streams=[] selectedStream = None cdn = None qtypes=['ask', 'p011', 'p010', 'p009', 'p008', 'H264 Medium', 'H264 650K', 'H264 400K', 'VP6 400K'] qt = int(common.settings['quality']) if qt < 0 or qt > 8: qt = 0 while qt < 8: qtext = qtypes[qt] for vid in video: streams.append([vid['profile'],vid['cdn'],vid['server'],vid['stream'],vid['token']]) if qtext in vid['profile']: if vid['cdn'] == common.settings['defaultcdn']: selectedStream = [vid['server'],vid['stream'],vid['token']] print selectedStream cdn = vid['cdn'] break if qt == 0 or selectedStream != None: break qt += 1 if qt == 0 or selectedStream == None: if selectedStream == None: #ask user for quality level quality=xbmcgui.Dialog().select('Please select a quality level:', [stream[0]+' ('+stream[1]+')' for stream in streams]) print quality if quality!=-1: selectedStream = [streams[quality][2], streams[quality][3], streams[quality][4]] cdn = streams[quality][1] print "stream url" print selectedStream if selectedStream != None: server = selectedStream[0] stream = selectedStream[1] token = selectedStream[2] protocolSplit = server.split("://") pathSplit = protocolSplit[1].split("/") hostname = pathSplit[0] appName = protocolSplit[1].split(hostname + "/")[1] if "level3" in cdn: appName += "?sessionid=sessionId&" + token stream = stream[0:len(stream)-4] finalUrl = server + "?sessionid=sessionId&" + token + " app=" + appName elif "limelight" in cdn: appName += '?sessionid=sessionId&' + token stream = stream[0:len(stream)-4] finalUrl = server + "?sessionid=sessionId&" + token + " app=" + appName elif "akamai" in cdn: appName += '?sessionid=sessionId&' + token finalUrl = server + "?sessionid=sessionId&" + token + " app=" + appName else: xbmcgui.Dialog().ok('Unsupported Content Delivery Network',cdn+' is unsupported at this time') return print "item url -- > " + finalUrl print "app name -- > " + appName print "playPath -- > " + stream #define item SWFPlayer = 'http://download.hulu.com/huludesktop.swf' finalUrl += " playpath=" + stream + " swfurl=" + SWFPlayer + " pageurl=" + SWFPlayer + " swfvfy=true" #if (common.settings['swfverify'] == 'true'): # finalUrl += " swfvfy=true" return finalUrl ################# OLD FUNCTIONS # might be useful def decrypt_cid(self, p): cidkey = '48555bbbe9f41981df49895f44c83993a09334d02d17e7a76b237d04c084e342' v3 = binascii.unhexlify(p) ecb = common.AES(binascii.unhexlify(cidkey)) return ecb.decrypt(v3).split("~")[0] def cid2eidOLD(self, p): import md5 dec_cid = int(p.lstrip('m'), 36) xor_cid = dec_cid ^ 3735928559 # 0xDEADBEEF m = md5.new() m.update(str(xor_cid) + "MAZxpK3WwazfARjIpSXKQ9cmg9nPe5wIOOfKuBIfz7bNdat6gQKHj69ZWNWNVB1") value = m.digest() return base64.encodestring(value).replace("+", "-").replace("/", "_").replace("=", "").replace('/n','') def decrypt_pid(self, p): import re cp_strings = [ '6fe8131ca9b01ba011e9b0f5bc08c1c9ebaf65f039e1592d53a30def7fced26c', 'd3802c10649503a60619b709d1278ffff84c1856dfd4097541d55c6740442d8b', 'c402fb2f70c89a0df112c5e38583f9202a96c6de3fa1aa3da6849bb317a983b3', 'e1a28374f5562768c061f22394a556a75860f132432415d67768e0c112c31495', 'd3802c10649503a60619b709d1278efef84c1856dfd4097541d55c6740442d8b' ] v3 = p.split("~") v3a = binascii.unhexlify(v3[0]) v3b = binascii.unhexlify(v3[1]) ecb = common.AES(v3b) tmp = ecb.decrypt(v3a) for v1 in cp_strings[:]: ecb = common.AES(binascii.unhexlify(v1)) v2 = ecb.decrypt(tmp) if (re.match("[0-9A-Za-z_-]{32}", v2)): return v2 def pid_auth(self, pid): import md5 m=md5.new() m.update(str(pid) + "yumUsWUfrAPraRaNe2ru2exAXEfaP6Nugubepreb68REt7daS79fase9haqar9sa") return m.hexdigest()