imdb/ofdb: allow to select event from epg ("menu" in plugin)
[enigma2-plugins.git] / ofdb / src / plugin.py
1 # -*- coding: utf8 -*-
2 from Plugins.Plugin import PluginDescriptor
3 from twisted.web.client import downloadPage
4 from enigma import ePicLoad, eServiceReference
5 from Screens.Screen import Screen
6 from Screens.EpgSelection import EPGSelection
7 from Screens.ChannelSelection import SimpleChannelSelection
8 from Components.ActionMap import ActionMap
9 from Components.Pixmap import Pixmap
10 from Components.Label import Label
11 from Components.ScrollLabel import ScrollLabel
12 from Components.Button import Button
13 from Components.AVSwitch import AVSwitch
14 from Components.MenuList import MenuList
15 from Components.Language import language
16 from Components.ProgressBar import ProgressBar
17 from Tools.Directories import resolveFilename, SCOPE_PLUGINS, SCOPE_SKIN_IMAGE
18 import re
19 import htmlentitydefs
20 import urllib
21
22 class OFDBChannelSelection(SimpleChannelSelection):
23         def __init__(self, session):
24                 SimpleChannelSelection.__init__(self, session, _("Channel Selection"))
25                 self.skinName = "SimpleChannelSelection"
26
27                 self["ChannelSelectEPGActions"] = ActionMap(["ChannelSelectEPGActions"],
28                         {
29                                 "showEPGList": self.channelSelected
30                         }
31                 )
32
33         def channelSelected(self):
34                 ref = self.getCurrentSelection()
35                 if (ref.flags & 7) == 7:
36                         self.enterPath(ref)
37                 elif not (ref.flags & eServiceReference.isMarker):
38                         self.session.openWithCallback(
39                                 self.epgClosed,
40                                 OFDBEPGSelection,
41                                 ref,
42                                 openPlugin = False
43                         )
44
45         def epgClosed(self, ret = None):
46                 if ret:
47                         self.close(ret)
48
49 class OFDBEPGSelection(EPGSelection):
50         def __init__(self, session, ref, openPlugin = True):
51                 EPGSelection.__init__(self, session, ref)
52                 self.skinName = "EPGSelection"
53                 self["key_green"].setText(_("Lookup"))
54                 self.openPlugin = openPlugin
55
56         def infoKeyPressed(self):
57                 self.timerAdd()
58
59         def timerAdd(self):
60                 cur = self["list"].getCurrent()
61                 evt = cur[0]
62                 sref = cur[1]
63                 if not evt: 
64                         return
65
66                 if self.openPlugin:
67                         self.session.open(
68                                 OFDB,
69                                 evt.getEventName()
70                         )
71                 else:
72                         self.close(evt.getEventName())
73
74         def onSelectionChanged(self):
75                 pass
76
77 class OFDB(Screen):
78         skin = """
79                 <screen name="OFDb" position="90,95" size="560,420" title="Internet Movie Database Details Plugin" >
80                         <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
81                         <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
82                         <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
83                         <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
84                         <widget name="key_red" position="0,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#9f1313" transparent="1" />
85                         <widget name="key_green" position="140,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />
86                         <widget name="key_yellow" position="280,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#a08500" transparent="1" />
87                         <widget name="key_blue" position="420,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#18188b" transparent="1" />
88                         <widget name="titellabel" position="10,40" size="330,45" valign="center" font="Regular;22"/>
89                         <widget name="detailslabel" position="105,90" size="445,140" font="Regular;18" />
90                         <widget name="castlabel" position="10,235" size="540,155" font="Regular;18" />
91                         <widget name="extralabel" position="10,40" size="540,350" font="Regular;18" />
92                         <widget name="ratinglabel" position="340,62" size="210,20" halign="center" font="Regular;18" foregroundColor="#f0b400"/>
93                         <widget name="statusbar" position="10,404" size="540,16" font="Regular;16" foregroundColor="#cccccc" />
94                         <widget name="poster" position="4,90" size="96,140" alphatest="on" />
95                         <widget name="menu" position="10,115" size="540,275" zPosition="3" scrollbarMode="showOnDemand" />
96                         <widget name="starsbg" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/OFDb/starsbar_empty.png" position="340,40" zPosition="0" size="210,21" transparent="1" alphatest="on" />
97                         <widget name="stars" position="340,40" size="210,21" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/OFDb/starsbar_filled.png" transparent="1" />
98                 </screen>"""
99
100         def __init__(self, session, eventName, args = None):
101                 self.skin = OFDB.skin
102                 Screen.__init__(self, session)
103                 self.eventName = eventName
104                 self.dictionary_init()
105                 self["poster"] = Pixmap()
106                 self.picload = ePicLoad()
107                 self.picload.PictureData.get().append(self.paintPosterPixmapCB)
108
109                 self["stars"] = ProgressBar()
110                 self["starsbg"] = Pixmap()
111                 self["stars"].hide()
112                 self["starsbg"].hide()
113                 self.ratingstars = -1
114                 self["titellabel"] = Label("The Internet Movie Database")
115                 self["detailslabel"] = ScrollLabel("")
116                 self["castlabel"] = ScrollLabel("")
117                 self["extralabel"] = ScrollLabel("")
118                 self["statusbar"] = Label("")
119                 self["ratinglabel"] = Label("")
120                 self.resultlist = []
121                 self["menu"] = MenuList(self.resultlist)
122                 self["menu"].hide()
123                 self["key_red"] = Button(self._("Exit"))
124                 self["key_green"] = Button("")
125                 self["key_yellow"] = Button("")
126                 self["key_blue"] = Button("")
127                 # 0 = multiple query selection menu page
128                 # 1 = movie info page
129                 # 2 = extra infos page
130                 self.Page = 0
131
132                 self["actions"] = ActionMap(["OkCancelActions", "ColorActions", "MovieSelectionActions", "DirectionActions"],
133                 {
134                         "ok": self.showDetails,
135                         "cancel": self.close,
136                         "down": self.pageDown,
137                         "up": self.pageUp,
138                         "red": self.close,
139                         "green": self.showMenu,
140                         "yellow": self.showDetails,
141                         "blue": self.showExtras,
142                         "contextMenu": self.openChannelSelection,
143                         "showEventInfo": self.showDetails
144                 }, -1)
145                 
146                 self.getOFDB()
147
148         def dictionary_init(self):
149                 syslang = language.getLanguage()
150                 if syslang.find("de") is -1:
151                         self.OFDBlanguage = ""  # set to empty ("") for english version
152                 else:
153                         self.OFDBlanguage = "german." # it's a subdomain, so add a '.' at the end
154
155                 self.dict = {}
156                 self.dict["of"]="von"
157                 self.dict[" as "]=" als "
158                 self.dict["Ambiguous results"]="Kein eindeutiger Treffer"
159                 self.dict["Please select the matching entry"]="Bitte passenden Eintrag auswählen"
160                 self.dict["No OFDb match."]="Keine passenden Einträge gefunden."
161                 self.dict["OFDb query failed!"]="OFDb-Query fehlgeschlagen!"
162                 self.dict["No details found."]="Keine Details gefunden."
163                 self.dict["no user rating yet"]="noch keine Nutzerwertung"
164                 self.dict["Cast: "]="Darsteller: "
165                 self.dict["No cast list found in the database."]="Keine Darstellerliste in der Datenbank gefunden."
166                 self.dict["Exit"]="Beenden"
167                 self.dict["Extra Info"]="Zusatzinfos"
168                 self.dict["Title Menu"]="Titelauswahl"
169
170                 self.htmltags = re.compile('<.*?>')
171
172                 self.generalinfomask = re.compile(
173                 '<title>OFDb - (?P<title>.*?)</title>.*?'
174                 '(?P<g_original>Originaltitel):[\s\S]*?class=\"Daten\">(?P<original>.*?)</td>'
175                 '(?:.*?(?P<g_country>Herstellungsland):[\s\S]*?class="Daten">(?P<country>.*?)(?:\.\.\.|</td>))*'
176                 '(?:.*?(?P<g_year>Erscheinungsjahr):[\s\S]*?class="Daten">(?P<year>.*?)</td>)*'
177                 '(?:.*?(?P<g_director>Regie):[\s\S]*?class="Daten">(?P<director>.*?)(?:\.\.\.|</td>))*'
178                 , re.DOTALL)
179
180         def _(self, in_string):
181                 out_string = in_string
182                 if ((self.OFDBlanguage).find("german")) != -1:
183                         out_string = self.dict.get(in_string, in_string)
184                 return out_string
185
186         def resetLabels(self):
187                 self["detailslabel"].setText("")
188                 self["ratinglabel"].setText("")
189                 self["titellabel"].setText("")
190                 self["castlabel"].setText("")
191                 self["titellabel"].setText("")
192                 self["extralabel"].setText("")
193                 self.ratingstars = -1
194
195         def pageUp(self):
196                 if self.Page == 0:
197                         self["menu"].instance.moveSelection(self["menu"].instance.moveUp)
198                 if self.Page == 1:
199                         self["castlabel"].pageUp()
200                         self["detailslabel"].pageUp()
201                 if self.Page == 2:
202                         self["extralabel"].pageUp()
203         
204         def pageDown(self):
205                 if self.Page == 0:
206                         self["menu"].instance.moveSelection(self["menu"].instance.moveDown)
207                 if self.Page == 1:
208                         self["castlabel"].pageDown()
209                         self["detailslabel"].pageDown()
210                 if self.Page == 2:
211                         self["extralabel"].pageDown()
212
213         def showMenu(self):
214                 if ( self.Page is 1 or self.Page is 2 ) and self.resultlist:
215                         self["menu"].show()
216                         self["stars"].hide()
217                         self["starsbg"].hide()
218                         self["ratinglabel"].hide()
219                         self["castlabel"].hide()
220                         self["poster"].hide()
221                         self["extralabel"].hide()
222                         self["titellabel"].setText(self._("Ambiguous results"))
223                         self["detailslabel"].setText(self._("Please select the matching entry"))
224                         self["detailslabel"].show()
225                         self["key_blue"].setText("")
226                         self["key_green"].setText(self._("Title Menu"))
227                         self["key_yellow"].setText(self._("Details"))
228                         self.Page = 0
229
230         def showDetails(self):
231                 self["ratinglabel"].show()
232                 self["castlabel"].show()
233                 self["detailslabel"].show()
234
235                 if self.resultlist and self.Page == 0:
236                         link = self["menu"].getCurrent()[1]
237                         title = self["menu"].getCurrent()[0]
238                         self["statusbar"].setText("Re-Query OFDb: "+title+"...")
239                         localfile = "/tmp/ofdbquery2.html"
240                         fetchurl = "http://www.ofdb.de/film/" + link
241                         print "[OFDb] downloading query " + fetchurl + " to " + localfile
242                         downloadPage(fetchurl,localfile).addCallback(self.OFDBquery2).addErrback(self.fetchFailed)
243                         self["menu"].hide()
244                         self.resetLabels()
245                         self.Page = 1
246
247                 if self.Page == 2:
248                         self["extralabel"].hide()
249                         self["poster"].show()
250                         if self.ratingstars > 0:
251                                 self["starsbg"].show()
252                                 self["stars"].show()
253                                 self["stars"].setValue(self.ratingstars)
254
255                         self.Page = 1
256
257         def showExtras(self):
258                 if self.Page == 1:
259                         self["extralabel"].show()
260                         self["detailslabel"].hide()
261                         self["castlabel"].hide()
262                         self["poster"].hide()
263                         self["stars"].hide()
264                         self["starsbg"].hide()
265                         self["ratinglabel"].hide()
266                         self.Page = 2
267
268         def openChannelSelection(self):
269                 self.session.openWithCallback(
270                         self.channelSelectionClosed,
271                         OFDBChannelSelection
272                 )
273
274         def channelSelectionClosed(self, ret = None):
275                 if ret:
276                         self.eventName = ret
277                         self.getOFDB()
278
279         def getOFDB(self):
280                 self.resetLabels()
281                 if self.eventName is "":
282                         s = self.session.nav.getCurrentService()
283                         info = s.info()
284                         event = info.getEvent(0) # 0 = now, 1 = next
285                         if event:
286                                 self.eventName = event.getEventName()
287                         try:
288                                 pos = self.eventName.index("(")
289                                 self.eventName=self.eventName[0:pos]
290                         except ValueError:
291                                 pass
292
293                 if self.eventName is not "":
294                         self["statusbar"].setText("Query OFDb: " + self.eventName + "...")
295                         try:
296                                 self.eventName = urllib.quote(self.eventName)
297                         except:
298                                 self.eventName = urllib.quote(self.eventName.decode('utf8').encode('ascii','ignore'))
299                         localfile = "/tmp/ofdbquery.html"
300                         fetchurl = "http://www.ofdb.de/view.php?page=suchergebnis&Kat=DTitel&SText=" + self.eventName
301                         print "[OFDb] Downloading Query " + fetchurl + " to " + localfile
302                         downloadPage(fetchurl,localfile).addCallback(self.OFDBquery).addErrback(self.fetchFailed)
303                 else:
304                         self["statusbar"].setText("Could't get Eventname -_-")
305
306         def fetchFailed(self,string):
307                 print "[OFDb] fetch failed " + string
308                 self["statusbar"].setText("OFDb Download failed -_-")
309
310         def html2utf8(self,in_html):
311                 htmlentitynumbermask = re.compile('(&#(\d{1,5}?);)')
312                 htmlentitynamemask = re.compile('(&(\D{1,5}?);)')
313                 
314                 entities = htmlentitynamemask.finditer(in_html)
315                 entitydict = {}
316
317                 for x in entities:
318                         entitydict[x.group(1)] = x.group(2)
319
320                 for key, name in entitydict.items():
321                         entitydict[key] = htmlentitydefs.name2codepoint[name]
322
323                 entities = htmlentitynumbermask.finditer(in_html)
324
325                 for x in entities:
326                         entitydict[x.group(1)] = x.group(2)
327
328                 for key, codepoint in entitydict.items():
329                         in_html = in_html.replace(key, (unichr(int(codepoint)).encode('utf8')))
330
331                 self.inhtml = in_html
332
333         def OFDBquery(self,string):
334                 print "[OFDBquery]"
335                 self["statusbar"].setText("OFDb Download completed")
336
337                 self.html2utf8(open("/tmp/ofdbquery.html", "r").read())
338
339                 self.generalinfos = self.generalinfomask.search(self.inhtml)
340
341                 if self.generalinfos:
342                         self.OFDBparse()
343                 else:
344                         if re.search("<title>OFDb - Suchergebnis</title>", self.inhtml):
345                                 searchresultmask = re.compile("<br>(\d{1,3}\.) <a href=\"film/(.*?)\"(?:.*?)\)\">(.*?)</a>", re.DOTALL)
346                                 searchresults = searchresultmask.finditer(self.inhtml)
347                                 self.resultlist = []
348                                 if searchresults:
349                                         for x in searchresults:
350                                                 self.resultlist.append((self.htmltags.sub('',x.group(3)), x.group(2)))
351                                         self["menu"].l.setList(self.resultlist)
352                                 if len(self.resultlist) == 1:
353                                         self.Page = 0
354                                         self["extralabel"].hide()
355                                         self.showDetails()
356                                 elif len(self.resultlist) > 1:
357                                         self.Page = 1
358                                         self.showMenu()
359                                 else:
360                                         self["detailslabel"].setText(self._("No OFDb match."))
361                                         self["statusbar"].setText("No OFDb match")
362                         else:
363                                 self["detailslabel"].setText(self._("OFDb query failed!"))
364
365         def OFDBquery2(self,string):
366                 self["statusbar"].setText("OFDb Re-Download completed")
367                 self.html2utf8(open("/tmp/ofdbquery2.html", "r").read())
368                 self.generalinfos = self.generalinfomask.search(self.inhtml)
369                 self.OFDBparse()
370
371         def OFDBparse(self):
372                 print "[OFDBparse]"
373                 self.Page = 1
374                 Detailstext = self._("No details found.")
375                 if self.generalinfos:
376                         self["key_yellow"].setText(self._("Details"))
377                         self["statusbar"].setText("OFDb Details parsed ^^")
378                         
379                         Titeltext = self.generalinfos.group("title")
380                         if len(Titeltext) > 57:
381                                 Titeltext = Titeltext[0:54] + "..."
382                         self["titellabel"].setText(Titeltext)
383
384                         Detailstext = ""
385
386                         genreblockmask = re.compile('Genre\(s\):(?:[\s\S]*?)class=\"Daten\">(.*?)</tr>', re.DOTALL)
387                         genreblock = genreblockmask.findall(self.inhtml)
388                         genremask = re.compile('\">(.*?)</a')
389                         if genreblock:
390                                 genres = genremask.finditer(genreblock[0])
391                                 if genres:
392                                         Detailstext += "Genre: "
393                                         for x in genres:
394                                                 Detailstext += self.htmltags.sub('', x.group(1)) + " "
395
396                         detailscategories = ["director", "year", "country", "original"]
397
398                         for category in detailscategories:
399                                 if self.generalinfos.group('g_'+category):
400                                         Detailstext += "\n" + self.generalinfos.group('g_'+category) + ": " + self.htmltags.sub('', self.generalinfos.group(category).replace("<br>",' '))
401
402                         self["detailslabel"].setText(Detailstext)
403
404                         #if self.generalinfos.group("alternativ"):
405                                 #Detailstext += "\n" + self.generalinfos.group("g_alternativ") + ": " + self.htmltags.sub('',(self.generalinfos.group("alternativ").replace('\n','').replace("<br>",'\n').replace("  ",' ')))
406
407                         ratingmask = re.compile('<td>[\s\S]*notenskala.*(?P<g_rating>Note: )(?P<rating>\d.\d{2,2})[\s\S]*</td>', re.DOTALL)
408                         rating = ratingmask.search(self.inhtml)
409                         Ratingtext = self._("no user rating yet")
410                         if rating:
411                                 Ratingtext = rating.group("g_rating") + rating.group("rating") + " / 10"
412                                 self.ratingstars = int(10*round(float(rating.group("rating")),1))
413                                 self["stars"].show()
414                                 self["stars"].setValue(self.ratingstars)
415                                 self["starsbg"].show()
416                         self["ratinglabel"].setText(Ratingtext)
417
418                         castblockmask = re.compile('Darsteller:[\s\S]*?class=\"Daten\">(.*?)(?:\.\.\.|\xbb)', re.DOTALL)
419                         castblock = castblockmask.findall(self.inhtml)
420                         castmask = re.compile('\">(.*?)</a')
421                         Casttext = ""
422                         if castblock:
423                                 cast = castmask.finditer(castblock[0])
424                                 if cast:
425                                         for x in cast:
426                                                 Casttext += "\n" + self.htmltags.sub('', x.group(1))
427                                         if Casttext is not "":
428                                                 Casttext = self._("Cast: ") + Casttext
429                                         else:
430                                                 Casttext = self._("No cast list found in the database.")
431                                         self["castlabel"].setText(Casttext)
432
433                         postermask = re.compile('<img src=\"(http://img.ofdb.de/film.*?)\" alt', re.DOTALL)
434                         posterurl = postermask.search(self.inhtml)
435                         if posterurl and posterurl.group(1).find("jpg") > 0:
436                                 posterurl = posterurl.group(1)
437                                 self["statusbar"].setText("Downloading Movie Poster: "+posterurl+"...")
438                                 localfile = "/tmp/poster.jpg"
439                                 print "[OFDb] downloading poster " + posterurl + " to " + localfile
440                                 downloadPage(posterurl,localfile).addCallback(self.OFDBPoster).addErrback(self.fetchFailed)
441                         else:
442                                 print "no jpg poster!"
443                                 self.OFDBPoster("kein Poster")
444
445                 self["detailslabel"].setText(Detailstext)
446                 
447         def OFDBPoster(self,string):
448                 self["statusbar"].setText("OFDb Details parsed ^^")
449                 if not string:
450                         filename = "/tmp/poster.jpg"
451                 else:
452                         filename = resolveFilename(SCOPE_PLUGINS, "Extensions/OFDb/no_poster.png")
453                 sc = AVSwitch().getFramebufferScale()
454                 self.picload.setPara((self["poster"].instance.size().width(), self["poster"].instance.size().height(), sc[0], sc[1], False, 1, "#00000000"))
455                 self.picload.startDecode(filename)
456
457         def paintPosterPixmapCB(self, picInfo=None):
458                 ptr = self.picload.getData()
459                 if ptr != None:
460                         self["poster"].instance.setPixmap(ptr.__deref__())
461                         self["poster"].show()
462
463         def createSummary(self):
464                 return OFDbLCDScreen
465
466 class OFDbLCDScreen(Screen):
467         skin = """
468         <screen position="0,0" size="132,64" title="OFDb Plugin">
469                 <widget name="headline" position="4,0" size="128,22" font="Regular;20"/>
470                 <widget source="session.Event_Now" render="Label" position="6,26" size="120,34" font="Regular;14" >
471                         <convert type="EventName">Name</convert>
472                 </widget>
473         </screen>"""
474
475         def __init__(self, session, parent):
476                 Screen.__init__(self, session)
477                 self["headline"] = Label("OFDb Plugin")
478
479 def eventinfo(session, servicelist, **kwargs):
480         ref = session.nav.getCurrentlyPlayingServiceReference()
481         session.open(OFDBEPGSelection, ref)
482
483 def main(session, eventName="", **kwargs):
484         session.open(OFDB, eventName)
485         
486 def Plugins(**kwargs):
487         try:
488                 return [PluginDescriptor(name = "OFDb Details",
489                                 description = _("Query details from the Online-Filmdatenbank"),
490                                 icon = "ofdb.png",
491                                 where = PluginDescriptor.WHERE_PLUGINMENU,
492                                 fnc = main),
493                                 PluginDescriptor(name = "OFDb Details",
494                                 description = _("Query details from the Online-Filmdatenbank"),
495                                 where = PluginDescriptor.WHERE_EVENTINFO,
496                                 fnc = eventinfo)
497                                 ]
498         except AttributeError:
499                 wherelist = [PluginDescriptor.WHERE_EXTENSIONSMENU, PluginDescriptor.WHERE_PLUGINMENU]
500                 return PluginDescriptor(name="OFDb Details",
501                                 description=_("Query details from the Online-Filmdatenbank"),
502                                 icon="ofdb.png",
503                                 where = wherelist,
504                                 fnc=main)