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