2014-03-17, 02:31
Does the solution above work only for Hulu Plus or does it also work for a normal account?
(2014-03-17, 00:25)russelldub Wrote: OK. More work and I've got a full stream solution for edgecast. stream_hulu.py pasted in below. Short solution for those that understand these things : swfUrl changed for swfVerification (so turn swfvfy=true back on and use SWFPlayer = 'http://www.hulu.com/site-player/205970/player.swf?cb=205970'). Otherwise locate your stream_hulu.py and replace it with the below code.
stream_hulu.py is in your add-on directory which depends on your OS. Check google.
Code: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.sleep(5)
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
elif "edgecast" in cdn:
server=server.replace('.com','.com:80')
appName += '?' + token
finalUrl = server + "?" + 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'
SWFPlayer = 'http://www.hulu.com/site-player/205970/player.swf?cb=205970'
finalUrl += " playpath=" + stream + " swfurl=" + SWFPlayer + " pageurl=" + SWFPlayer
finalUrl += " 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()
(2014-03-17, 00:25)russelldub Wrote: OK. More work and I've got a full stream solution for edgecast. stream_hulu.py pasted in below. Short solution for those that understand these things : swfUrl changed for swfVerification (so turn swfvfy=true back on and use SWFPlayer = 'http://www.hulu.com/site-player/205970/player.swf?cb=205970'). Otherwise locate your stream_hulu.py and replace it with the below code.
stream_hulu.py is in your add-on directory which depends on your OS. Check google.
elif "akamai" in cdn:
appName += '?sessionid=sessionId&' + token
finalUrl = server + "?sessionid=sessionId&" + token + " app=" + appName
elif "edgecast" in cdn:
server=server.replace('.com','.com:80')
appName += '?' + token
finalUrl = server + "?" + token + " app=" + appName
SWFPlayer = 'http://download.hulu.com/huludesktop.swf
if "edgecast" in cdn:
SWFPlayer = 'http://www.hulu.com/site-player/205970/player.swf?cb=205970'
else:
SWFPlayer = 'http://download.hulu.com/huludesktop.swf'
cdns = ['level3','limelight','amakia']
cdns = ['level3','limelight','darwin-edgecast']
<string id="30023">....</string>
<string id="30023">16x9 24fps H264 Medium</string>
<string id="30030">....</string>
<string id="30030">darwin-edgecast</string>
(2014-03-17, 09:31)bohdans Wrote: Hi Guys,
Long time reader, first time poster.
Here is what you need to do to get hulu-beta plugin working + no stream prompt.
I assume you are in the plugin directory for the paths.
Edit resources/lib/stream_hulu.py
Find
Code:elif "akamai" in cdn:
appName += '?sessionid=sessionId&' + token
finalUrl = server + "?sessionid=sessionId&" + token + " app=" + appName
and ADD AFTER
Code:elif "edgecast" in cdn:
server=server.replace('.com','.com:80')
appName += '?' + token
finalUrl = server + "?" + token + " app=" + appName
Then find
and REPLACE it with:Code:SWFPlayer = 'http://download.hulu.com/huludesktop.swf
Code:if "edgecast" in cdn:
SWFPlayer = 'http://www.hulu.com/site-player/205970/player.swf?cb=205970'
else:
SWFPlayer = 'http://download.hulu.com/huludesktop.swf'
Edit resources/lib/common.py
REPLACE
withCode:cdns = ['level3','limelight','amakia']
Code:cdns = ['level3','limelight','darwin-edgecast']
Edit resources/language/English/strings.xml
REPLACE
withCode:<string id="30023">....</string>
REPLACECode:<string id="30023">16x9 24fps H264 Medium</string>
withCode:<string id="30030">....</string>
Code:<string id="30030">darwin-edgecast</string>
Thats the hard work done! Now go to the plugin and configure the settings to use Quality "16x9 24fps H264 Medium" and Default CDN "darwin-edgecast"
I have been watching shows for last 2 hours no issues!
EDIT: Code tags to make it readable
(2014-03-17, 00:25)russelldub Wrote: OK. More work and I've got a full stream solution for edgecast. stream_hulu.py pasted in below. Short solution for those that understand these things : swfUrl changed for swfVerification (so turn swfvfy=true back on and use SWFPlayer = 'http://www.hulu.com/site-player/205970/player.swf?cb=205970'). Otherwise locate your stream_hulu.py and replace it with the below code.This was working for me last night, but now it's back to only playing the first few seconds of any video.
stream_hulu.py is in your add-on directory which depends on your OS. Check google.