added url_encoded_fmt_stream_map - element -> fix for video playing
[enigma2-plugins.git] / yttrailer / src / plugin.py
1
2 #  YTTrailer
3 #
4 #  Coded by Dr.Best (c) 2011
5 #  Support: www.dreambox-tools.info
6 #
7 #  All Files of this Software are licensed under the Creative Commons 
8 #  Attribution-NonCommercial-ShareAlike 3.0 Unported 
9 #  License if not stated otherwise in a Files Head. To view a copy of this license, visit
10 #  http://creativecommons.org/licenses/by-nc-sa/3.0/ or send a letter to Creative
11 #  Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA.
12
13 #  Additionally, this plugin may only be distributed and executed on hardware which
14 #  is licensed by Dream Multimedia GmbH.
15
16 #  This plugin is NOT free software. It is open source, you are allowed to
17 #  modify it (if you keep the license), but it may not be commercially 
18 #  distributed other than under the conditions noted above.
19 #  This applies to the source code as a whole as well as to parts of it, unless
20 #  explicitely stated otherwise.
21
22 from __init__ import decrypt_block, validate_cert, read_random, rootkey, l2key
23 from Screens.Screen import Screen
24 from Plugins.Plugin import PluginDescriptor
25 from Components.ActionMap import ActionMap, HelpableActionMap
26 from Components.PluginComponent import plugins
27 from Plugins.Plugin import PluginDescriptor
28 from Components.Sources.StaticText import StaticText
29 from Components.GUIComponent import GUIComponent
30 from enigma import eServiceReference,  RT_WRAP, RT_VALIGN_CENTER, RT_HALIGN_LEFT, gFont, eListbox, eListboxPythonMultiContent, eTPM
31
32 import gdata.youtube
33 import gdata.youtube.service
34 from socket import gaierror, error as sorcket_error
35 from urllib2 import Request, URLError, urlopen as urlopen2
36 from urllib import unquote_plus
37 from httplib import HTTPException
38 from urlparse import parse_qs
39
40 from Components.config import config, ConfigSubsection, ConfigSelection, getConfigListEntry, configfile, ConfigText, ConfigInteger, ConfigBoolean
41 from Components.ConfigList import ConfigListScreen
42
43 from Screens.InfoBarGenerics import InfoBarShowHide, InfoBarSeek, InfoBarAudioSelection, InfoBarNotifications, InfoBarServiceNotifications, InfoBarPVRState, InfoBarMoviePlayerSummarySupport
44 from Components.ServiceEventTracker import InfoBarBase
45
46 # for localized messages
47 from . import _
48
49 config.plugins.yttrailer = ConfigSubsection()
50 config.plugins.yttrailer.best_resolution = ConfigSelection(default="2", choices = [("0", _("1080p")),("1", _("720p")), ("2", _("No HD streaming"))])
51 config.plugins.yttrailer.ext_descr = ConfigText(default="german", fixed_size = False)
52 config.plugins.yttrailer.max_results =  ConfigInteger(5,limits = (1, 10))
53 config.plugins.yttrailer.close_player_with_exit =  ConfigBoolean(False)
54
55 from Screens.EventView import EventViewBase
56 baseEventViewBase__init__ = None
57
58 from Screens.EpgSelection import EPGSelection
59 baseEPGSelection__init__ = None
60 etpm = eTPM()
61
62
63 def autostart(reason, **kwargs):
64         global l2key
65         l2cert = etpm.getCert(eTPM.TPMD_DT_LEVEL2_CERT)
66         if l2cert:
67                 l2key = validate_cert(l2cert, rootkey)
68                 if l2key:
69                         global baseEventViewBase__init__, baseEPGSelection__init__
70                         if baseEventViewBase__init__ is None:
71                                 baseEventViewBase__init__ = EventViewBase.__init__
72                         EventViewBase.__init__ = EventViewBase__init__
73                         EventViewBase.showTrailer = showTrailer
74                         EventViewBase.showTrailerList = showTrailerList
75                         EventViewBase.showConfig = showConfig
76
77                         if baseEPGSelection__init__ is None:
78                                 baseEPGSelection__init__ = EPGSelection.__init__
79                         EPGSelection.__init__ = EPGSelection__init__
80                         EPGSelection.showTrailer = showTrailer
81                         EPGSelection.showConfig = showConfig
82                         EPGSelection.showTrailerList = showTrailerList
83
84
85 def setup(session,**kwargs):
86         session.open(YTTrailerSetup)
87
88 def Plugins(**kwargs):
89
90         list = [PluginDescriptor(where = PluginDescriptor.WHERE_SESSIONSTART, fnc = autostart)] 
91         list.append(PluginDescriptor(name="YTTrailerSetup", description=_("YT-Trailer Setup"), where = [PluginDescriptor.WHERE_PLUGINMENU], fnc=setup))
92         return list
93
94 def EventViewBase__init__(self, Event, Ref, callback=None, similarEPGCB=None):
95         baseEventViewBase__init__(self, Event, Ref, callback, similarEPGCB)
96         self["trailerActions"] = ActionMap(["InfobarActions", "InfobarTeletextActions"],
97         {
98                 "showTv": self.showTrailer,
99                 "showRadio": self.showTrailerList,
100                 "startTeletext": self.showConfig
101         })
102         
103
104 def EPGSelection__init__(self, session, service, zapFunc=None, eventid=None, bouquetChangeCB=None, serviceChangeCB=None):
105         baseEPGSelection__init__(self, session, service, zapFunc, eventid, bouquetChangeCB, serviceChangeCB)
106         self["trailerActions"] = ActionMap(["InfobarActions", "InfobarTeletextActions"],
107         {
108                 "showTv": self.showTrailer,
109                 "showRadio": self.showTrailerList,
110                 "startTeletext": self.showConfig
111         })
112
113 def showConfig(self):
114         self.session.open(YTTrailerSetup)
115
116 def showTrailer(self):
117         eventname = ""
118         if isinstance(self, EventViewBase):
119                 if self.event:
120                         eventname = self.event.getEventName()
121         else:
122                 cur = self["list"].getCurrent()
123                 if cur and cur[0]:
124                         event = cur[0]
125                         eventname = event.getEventName()
126
127         ytTrailer = YTTrailer(self.session)
128         ytTrailer.showTrailer(eventname)
129
130 def showTrailerList(self):
131         eventname = ""
132         if isinstance(self, EventViewBase):
133                 if self.event:
134                         eventname = self.event.getEventName()
135         else:
136                 cur = self["list"].getCurrent()
137                 if cur and cur[0]:
138                         event = cur[0]
139                         eventname = event.getEventName()
140
141         self.session.open(YTTrailerList, eventname)
142
143 class YTTrailer:
144         def __init__(self, session):
145                 self.session = session
146                 self.l3cert = etpm.getCert(eTPM.TPMD_DT_LEVEL3_CERT)
147
148         def showTrailer(self, eventname):
149                 if eventname:
150                         feeds = self.getYTFeeds(eventname, 1)
151                         if feeds and len(feeds.entry) >= 1:
152                                 ref = self.setServiceReference(feeds.entry[0])
153                                 if ref:
154                                         self.session.open(TrailerPlayer, ref)
155
156         def getYTFeeds(self, eventname, max_results):
157                 yt_service = gdata.youtube.service.YouTubeService()
158                 # developer key and client id taken from mytube-plugin with permission from acid_burn.
159                 yt_service.developer_key = 'AI39si4AjyvU8GoJGncYzmqMCwelUnqjEMWTFCcUtK-VUzvWygvwPO-sadNwW5tNj9DDCHju3nnJEPvFy4WZZ6hzFYCx8rJ6Mw'
160                 yt_service.client_id = 'ytapi-dream-MyTubePlayer-i0kqrebg-0'
161                 query = gdata.youtube.service.YouTubeVideoQuery()
162                 if int(config.plugins.yttrailer.best_resolution.value) <= 1:
163                         shd = "HD"
164                 else:
165                         shd = ""
166                 query.vq = "%s %s Trailer %s" % (eventname, shd, config.plugins.yttrailer.ext_descr.value)
167                 query.max_results = max_results
168                 try:
169                         feeds = yt_service.YouTubeQuery(query)
170                 except gaierror:
171                         feeds = None
172                 return feeds
173
174         def setServiceReference(self, entry):
175                 url = self.getVideoUrl(entry)
176                 if url:
177                         ref = eServiceReference(4097,0,url)
178                         ref.setName(entry.media.title.text)
179                 else:
180                         ref = None
181                 return ref
182
183         def getTubeId(self, entry):
184                 ret = None
185                 if entry.media.player:
186                         split = entry.media.player.url.split("=")
187                         ret = split.pop()
188                         if ret.startswith('youtube_gdata'):
189                                 tmpval=split.pop()
190                                 if tmpval.endswith("&feature"):
191                                         tmp = tmpval.split("&")
192                                         ret = tmp.pop(0)
193                 return ret
194
195         def getVideoUrl(self, entry):
196                 std_headers = {
197                         'User-Agent': 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.6) Gecko/20100627 Firefox/3.6.6',
198                         'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
199                         'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
200                         'Accept-Language': 'en-us,en;q=0.5',
201                 }
202
203                 VIDEO_FMT_PRIORITY_MAP = {
204                         '18' : 4, #MP4 360p
205                         '35' : 5, #FLV 480p
206                         '34' : 6, #FLV 360p
207                 }
208
209                 if int(config.plugins.yttrailer.best_resolution.value) <= 1:
210                         VIDEO_FMT_PRIORITY_MAP["38"] = 1 #MP4 Original (HD)
211                         VIDEO_FMT_PRIORITY_MAP["22"] = 3 #MP4 720p (HD)
212
213                         if int(config.plugins.yttrailer.best_resolution.value) == 0:
214                                 VIDEO_FMT_PRIORITY_MAP["37"] = 2 #MP4 1080p (HD)
215
216                 video_url = None
217                 video_id = str(self.getTubeId(entry))
218
219                 # Getting video webpage
220                 #URLs for YouTube video pages will change from the format http://www.youtube.com/watch?v=ylLzyHk54Z0 to http://www.youtube.com/watch#!v=ylLzyHk54Z0.
221                 watch_url = 'http://www.youtube.com/watch?v=%s&gl=US&hl=en' % video_id
222                 watchrequest = Request(watch_url, None, std_headers)
223                 try:
224                         print "[YTTrailer] trying to find out if a HD Stream is available",watch_url
225                         watchvideopage = urlopen2(watchrequest).read()
226                 except (URLError, HTTPException, socket_error), err:
227                         print "[YTTrailer] Error: Unable to retrieve watchpage - Error code: ", str(err)
228                         return video_url
229
230                 # Get video info
231                 for el in ['&el=embedded', '&el=detailpage', '&el=vevo', '']:
232                         info_url = ('http://www.youtube.com/get_video_info?&video_id=%s%s&ps=default&eurl=&gl=US&hl=en' % (video_id, el))
233                         request = Request(info_url, None, std_headers)
234                         try:
235                                 infopage = urlopen2(request).read()
236                                 videoinfo = parse_qs(infopage)
237                                 if ('url_encoded_fmt_stream_map' or 'fmt_url_map') in videoinfo:
238                                         break
239                         except (URLError, HTTPException, socket_error), err:
240                                 print "[YTTrailer] Error: unable to download video infopage",str(err)
241                                 return video_url
242
243                 if ('url_encoded_fmt_stream_map' or 'fmt_url_map') not in videoinfo:
244                         # Attempt to see if YouTube has issued an error message
245                         if 'reason' not in videoinfo:
246                                 print '[YTTrailer] Error: unable to extract "url_encoded_fmt_stream_map" or "fmt_url_map" parameter for unknown reason'
247                         else:
248                                 reason = unquote_plus(videoinfo['reason'][0])
249                                 print '[YTTrailer] Error: YouTube said: %s' % reason.decode('utf-8')
250                         return video_url
251
252                 video_fmt_map = {}
253                 fmt_infomap = {}
254                 if videoinfo.has_key('url_encoded_fmt_stream_map'):
255                         tmp_fmtUrlDATA = videoinfo['url_encoded_fmt_stream_map'][0].split(',url=')
256                 else:
257                         tmp_fmtUrlDATA = videoinfo['fmt_url_map'][0].split(',')
258                 for fmtstring in tmp_fmtUrlDATA:
259                         if videoinfo.has_key('url_encoded_fmt_stream_map'):
260                                 (fmturl, fmtid) = fmtstring.split('&itag=')
261                                 if fmturl.find("url=") !=-1:
262                                         fmturl = fmturl.replace("url=","")
263                         else:
264                                 (fmtid,fmturl) = fmtstring.split('|')
265                         if VIDEO_FMT_PRIORITY_MAP.has_key(fmtid):
266                                 video_fmt_map[VIDEO_FMT_PRIORITY_MAP[fmtid]] = { 'fmtid': fmtid, 'fmturl': unquote_plus(fmturl) }
267                         fmt_infomap[int(fmtid)] = unquote_plus(fmturl)
268                 print "[YTTrailer] got",sorted(fmt_infomap.iterkeys())
269                 if video_fmt_map and len(video_fmt_map):
270                         if self.l3cert:
271                                 l3key = validate_cert(self.l3cert, l2key)
272                                 if l3key:
273                                         rnd = read_random()
274                                         val = etpm.challenge(rnd)
275                                         result = decrypt_block(val, l3key)
276                                         if result[80:88] == rnd:
277                                                 print "[YTTrailer] found best available video format:",video_fmt_map[sorted(video_fmt_map.iterkeys())[0]]['fmtid']
278                                                 video_url = video_fmt_map[sorted(video_fmt_map.iterkeys())[0]]['fmturl'].split(';')[0]
279                                                 print "[YTTrailer] found best available video url:",video_url
280                 return video_url
281
282 class YTTrailerList(Screen, YTTrailer):
283
284         skin = """
285                 <screen name="YTTrailerList" position="center,center" size="580,436" title="YT Trailer-List" backgroundColor="#ff000000">
286                         <widget name="list" position="0,0" size="580,436" />
287                 </screen>"""
288
289         def __init__(self, session, eventname):
290                 Screen.__init__(self, session)
291                 YTTrailer.__init__(self, session)
292
293                 self["actions"] = ActionMap(["WizardActions"],
294                 {
295                         "ok": self.okPressed,
296                         "back": self.close
297                 }, -1)
298
299                 self.eventName = eventname
300                 self["list"] = TrailerList()
301                 self.onLayoutFinish.append(self.startRun)
302
303
304         def startRun(self):
305                 feeds = self.getYTFeeds(self.eventName, config.plugins.yttrailer.max_results.value)
306                 if feeds is not None:
307                         entryList = []
308                         for entry in feeds.entry:
309                                 entryList.append(((entry),))
310                         self["list"].setList(entryList)
311
312         def okPressed(self):
313                 entry = self["list"].getCurrent()
314                 if entry:
315                         ref = self.setServiceReference(entry)
316                         if ref:
317                                 self.session.open(TrailerPlayer, ref)
318
319 class TrailerList(GUIComponent, object):
320
321         GUI_WIDGET = eListbox
322
323         def __init__(self):
324                 GUIComponent.__init__(self)
325                 self.l = eListboxPythonMultiContent()
326                 self.l.setBuildFunc(self.buildList)
327                 self.l.setFont(0, gFont("Regular", 22))
328                 self.l.setFont(1, gFont("Regular", 18))
329                 self.l.setItemHeight(75)
330
331         def buildList(self, entry):
332                 width = self.l.getItemSize().width()
333                 res = [ None ]
334                 res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 0, width , 24, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, entry.media.title.text))
335                 res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 28, width , 40, 1, RT_WRAP, entry.media.description.text))
336                 return res
337
338         def getCurrent(self):
339                 cur = self.l.getCurrentSelection()
340                 return cur and cur[0]
341
342         def postWidgetCreate(self, instance):
343                 instance.setContent(self.l)
344                 self.instance.setWrapAround(True)
345
346         def preWidgetRemove(self, instance):
347                 instance.setContent(None)
348
349         def setList(self, list):
350                 self.l.setList(list)
351
352 class TrailerPlayer(InfoBarBase, InfoBarShowHide, InfoBarSeek, InfoBarAudioSelection, InfoBarNotifications, InfoBarServiceNotifications, InfoBarPVRState, InfoBarMoviePlayerSummarySupport, Screen):
353
354         ENABLE_RESUME_SUPPORT = True
355         ALLOW_SUSPEND = True
356                 
357         def __init__(self, session, ref):
358                 Screen.__init__(self, session)
359                 self.session = session
360                 self["actions"] = HelpableActionMap(self, "MoviePlayerActions",
361                         {
362                                 "leavePlayer": (self.leavePlayer, _("leave movie player..."))
363                         })
364
365                 if config.plugins.yttrailer.close_player_with_exit.value:
366                         self["closeactions"] = HelpableActionMap(self, "WizardActions",
367                                 {
368                                         "back": (self.close, _("leave movie player..."))
369                                 })
370
371
372                 self.allowPiP = False
373                 for x in InfoBarShowHide, InfoBarBase, InfoBarSeek, \
374                                 InfoBarAudioSelection, InfoBarNotifications, \
375                                 InfoBarServiceNotifications, InfoBarPVRState,  \
376                                 InfoBarMoviePlayerSummarySupport:
377                         x.__init__(self)
378
379                 self.returning = False
380                 self.skinName = "MoviePlayer"
381                 self.lastservice = session.nav.getCurrentlyPlayingServiceReference()
382                 self.session.nav.playService(ref)               
383                 self.onClose.append(self.__onClose)
384
385         def leavePlayer(self):
386                 self.close()
387
388         def doEofInternal(self, playing):
389                 self.close()
390
391         def __onClose(self):
392                 self.session.nav.playService(self.lastservice)
393
394 class YTTrailerSetup(ConfigListScreen, Screen):
395         skin = """
396                 <screen position="center,center" size="560,400" title="YT-Trailer Setup">
397                         <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
398                         <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
399                         <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
400                         <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
401                         <widget render="Label" source="key_red" position="0,0" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
402                         <widget render="Label" source="key_green" position="140,0" size="140,40" zPosition="5" valign="center" halign="center" backgroundColor="red" font="Regular;21" transparent="1" foregroundColor="white" shadowColor="black" shadowOffset="-1,-1" />
403                         <widget name="config" position="20,50" size="520,330" scrollbarMode="showOnDemand" />
404                 </screen>"""
405
406         def __init__(self, session, args = None):
407                 Screen.__init__(self, session)
408                 self["key_red"] = StaticText(_("Cancel"))
409                 self["key_green"] = StaticText(_("Save"))
410
411                 cfglist = [ ]
412                 cfglist.append(getConfigListEntry(_("Extended search filter"), config.plugins.yttrailer.ext_descr))
413                 cfglist.append(getConfigListEntry(_("Best resolution"), config.plugins.yttrailer.best_resolution))
414                 cfglist.append(getConfigListEntry(_("Max. results in list-mode"), config.plugins.yttrailer.max_results))
415                 cfglist.append(getConfigListEntry(_("Close Player with exit-key"), config.plugins.yttrailer.close_player_with_exit))
416
417
418                 ConfigListScreen.__init__(self, cfglist, session)
419                 self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
420                 {
421                         "green": self.keySave,
422                         "cancel": self.keyClose
423                 }, -2)
424
425         def keySave(self):
426                 for x in self["config"].list:
427                         x[1].save()
428                 configfile.save()
429                 self.close()
430
431         def keyClose(self):
432                 for x in self["config"].list:
433                         x[1].cancel()
434                 self.close()
435