[YTTrailer] small change for easier class inheritance
[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.getData(eTPM.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.getData(eTPM.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(str(self.getTubeId(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, id):
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 = id
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 = ""
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
277                                         if fmtid != "" and fmturl != "" and VIDEO_FMT_PRIORITY_MAP.has_key(fmtid):
278                                                 video_fmt_map[VIDEO_FMT_PRIORITY_MAP[fmtid]] = { 'fmtid': fmtid, 'fmturl': unquote_plus(fmturl)}
279                                                 fmt_infomap[int(fmtid)] = "%s" %(unquote_plus(fmturl))
280                                         fmturl = fmtid = ""
281
282                                 except:
283                                         print "error parsing fmtstring:",fmtstring
284
285                         else:
286                                 (fmtid,fmturl) = fmtstring.split('|')
287                         if VIDEO_FMT_PRIORITY_MAP.has_key(fmtid) and fmtid != "":
288                                 video_fmt_map[VIDEO_FMT_PRIORITY_MAP[fmtid]] = { 'fmtid': fmtid, 'fmturl': unquote_plus(fmturl) }
289                                 fmt_infomap[int(fmtid)] = unquote_plus(fmturl)
290                 print "[YTTrailer] got",sorted(fmt_infomap.iterkeys())
291                 if video_fmt_map and len(video_fmt_map):
292                         if self.l3cert:
293                                 l3key = validate_cert(self.l3cert, l2key)
294                                 if l3key:
295                                         rnd = read_random()
296                                         val = etpm.computeSignature(rnd)
297                                         result = decrypt_block(val, l3key)
298                                         if result[80:88] == rnd:
299                                                 print "[YTTrailer] found best available video format:",video_fmt_map[sorted(video_fmt_map.iterkeys())[0]]['fmtid']
300                                                 best_video = video_fmt_map[sorted(video_fmt_map.iterkeys())[0]]
301                                                 video_url = "%s" %(best_video['fmturl'].split(';')[0])
302                                                 print "[YTTrailer] found best available video url:",video_url
303
304                 return video_url
305
306 class YTTrailerList(Screen, YTTrailer):
307
308         skin = """
309                 <screen name="YTTrailerList" position="center,center" size="580,436" title="YT Trailer-List" backgroundColor="#ff000000">
310                         <widget name="list" position="0,0" size="580,436" />
311                 </screen>"""
312
313         def __init__(self, session, eventname):
314                 Screen.__init__(self, session)
315                 YTTrailer.__init__(self, session)
316
317                 self["actions"] = ActionMap(["WizardActions"],
318                 {
319                         "ok": self.okPressed,
320                         "back": self.close
321                 }, -1)
322
323                 self.eventName = eventname
324                 self["list"] = TrailerList()
325                 self.onLayoutFinish.append(self.startRun)
326
327
328         def startRun(self):
329                 feeds = self.getYTFeeds(self.eventName, config.plugins.yttrailer.max_results.value)
330                 if feeds is not None:
331                         entryList = []
332                         for entry in feeds.entry:
333                                 entryList.append(((entry),))
334                         self["list"].setList(entryList)
335
336         def okPressed(self):
337                 entry = self["list"].getCurrent()
338                 if entry:
339                         ref = self.setServiceReference(entry)
340                         if ref:
341                                 self.session.open(TrailerPlayer, ref)
342
343 class TrailerList(GUIComponent, object):
344
345         GUI_WIDGET = eListbox
346
347         def __init__(self):
348                 GUIComponent.__init__(self)
349                 self.l = eListboxPythonMultiContent()
350                 self.l.setBuildFunc(self.buildList)
351                 self.l.setFont(0, gFont("Regular", 22))
352                 self.l.setFont(1, gFont("Regular", 18))
353                 self.l.setItemHeight(75)
354
355         def buildList(self, entry):
356                 width = self.l.getItemSize().width()
357                 res = [ None ]
358                 res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 0, width , 24, 0, RT_HALIGN_LEFT|RT_VALIGN_CENTER, entry.media.title.text))
359                 res.append((eListboxPythonMultiContent.TYPE_TEXT, 0, 28, width , 40, 1, RT_WRAP, entry.media.description.text))
360                 return res
361
362         def getCurrent(self):
363                 cur = self.l.getCurrentSelection()
364                 return cur and cur[0]
365
366         def postWidgetCreate(self, instance):
367                 instance.setContent(self.l)
368                 self.instance.setWrapAround(True)
369
370         def preWidgetRemove(self, instance):
371                 instance.setContent(None)
372
373         def setList(self, list):
374                 self.l.setList(list)
375
376 class TrailerPlayer(InfoBarBase, InfoBarShowHide, InfoBarSeek, InfoBarAudioSelection, InfoBarNotifications, InfoBarServiceNotifications, InfoBarPVRState, InfoBarMoviePlayerSummarySupport, Screen):
377
378         ENABLE_RESUME_SUPPORT = True
379         ALLOW_SUSPEND = True
380
381         def __init__(self, session, ref):
382                 Screen.__init__(self, session)
383                 self.session = session
384                 self["actions"] = HelpableActionMap(self, "MoviePlayerActions",
385                         {
386                                 "leavePlayer": (self.leavePlayer, _("leave movie player..."))
387                         })
388
389                 if config.plugins.yttrailer.close_player_with_exit.value:
390                         self["closeactions"] = HelpableActionMap(self, "WizardActions",
391                                 {
392                                         "back": (self.close, _("leave movie player..."))
393                                 })
394
395
396                 self.allowPiP = False
397                 for x in InfoBarShowHide, InfoBarBase, InfoBarSeek, \
398                                 InfoBarAudioSelection, InfoBarNotifications, \
399                                 InfoBarServiceNotifications, InfoBarPVRState,  \
400                                 InfoBarMoviePlayerSummarySupport:
401                         x.__init__(self)
402
403                 self.returning = False
404                 self.skinName = "MoviePlayer"
405                 self.lastservice = session.nav.getCurrentlyPlayingServiceReference()
406                 self.session.nav.playService(ref)
407                 self.onClose.append(self.__onClose)
408
409         def leavePlayer(self):
410                 self.close()
411
412         def doEofInternal(self, playing):
413                 self.close()
414
415         def __onClose(self):
416                 self.session.nav.playService(self.lastservice)
417
418 class YTTrailerSetup(ConfigListScreen, Screen):
419         skin = """
420                 <screen position="center,center" size="560,400" title="YT-Trailer Setup">
421                         <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
422                         <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
423                         <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
424                         <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
425                         <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" />
426                         <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" />
427                         <widget name="config" position="20,50" size="520,330" scrollbarMode="showOnDemand" />
428                 </screen>"""
429
430         def __init__(self, session, args = None):
431                 Screen.__init__(self, session)
432                 self["key_red"] = StaticText(_("Cancel"))
433                 self["key_green"] = StaticText(_("Save"))
434
435                 cfglist = [ ]
436                 cfglist.append(getConfigListEntry(_("Show Setup in Extensions menu"), config.plugins.yttrailer.show_in_extensionsmenu))
437                 cfglist.append(getConfigListEntry(_("Extended search filter"), config.plugins.yttrailer.ext_descr))
438                 cfglist.append(getConfigListEntry(_("Best resolution"), config.plugins.yttrailer.best_resolution))
439                 cfglist.append(getConfigListEntry(_("Max. results in list-mode"), config.plugins.yttrailer.max_results))
440                 cfglist.append(getConfigListEntry(_("Close Player with exit-key"), config.plugins.yttrailer.close_player_with_exit))
441
442
443                 ConfigListScreen.__init__(self, cfglist, session)
444                 self["setupActions"] = ActionMap(["SetupActions", "ColorActions"],
445                 {
446                         "green": self.keySave,
447                         "cancel": self.keyClose
448                 }, -2)
449
450         def keySave(self):
451                 for x in self["config"].list:
452                         x[1].save()
453                 configfile.save()
454                 self.close()
455
456         def keyClose(self):
457                 for x in self["config"].list:
458                         x[1].cancel()
459                 self.close()
460