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