fix encoding name
[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("IMDb", resolveFilename(SCOPE_PLUGINS, "Extensions/IMDb/locale"))
28
29 def _(txt):
30     t = gettext.dgettext("IMDb", txt)
31     if t == txt:
32         print "[IMDb] 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.getOFDB()
274
275         def getOFDB(self):
276                 self.resetLabels()
277                 if self.eventName is "":
278                         s = self.session.nav.getCurrentService()
279                         info = s.info()
280                         event = info.getEvent(0) # 0 = now, 1 = next
281                         if event:
282                                 self.eventName = event.getEventName()
283
284                 if self.eventName is not "":
285                         try:
286                                 pos = self.eventName.index(" (")
287                                 self.eventName=self.eventName[0:pos]
288                         except ValueError:
289                                 pass
290                         if self.eventName[-3:] == "...":
291                                 self.eventName = self.eventName[:-3]
292                         for article in ["The", "Der", "Die", "Das"]:
293                                 if self.eventName[:4].capitalize() == article + " ":
294                                         self.eventName = self.eventName[4:] + ", " + article
295                         
296                         self["statusbar"].setText(_("Query OFDb: %s...") % (self.eventName))
297                         try:
298                                 self.eventName = urllib.quote(self.eventName)
299                         except:
300                                 self.eventName = urllib.quote(self.eventName.decode('utf8').encode('ascii','ignore'))
301                         localfile = "/tmp/ofdbquery.html"
302                         fetchurl = "http://www.ofdb.de/view.php?page=suchergebnis&Kat=DTitel&SText=" + self.eventName
303                         print "[OFDb] Downloading Query " + fetchurl + " to " + localfile
304                         downloadPage(fetchurl,localfile).addCallback(self.OFDBquery).addErrback(self.fetchFailed)
305                 else:
306                         self["statusbar"].setText(_("Could't get Eventname"))
307
308         def fetchFailed(self,string):
309                 print "[OFDb] fetch failed " + string
310                 self["statusbar"].setText(_("OFDb Download failed"))
311
312         def html2utf8(self,in_html):
313                 htmlentitynumbermask = re.compile('(&#(\d{1,5}?);)')
314                 htmlentitynamemask = re.compile('(&(\D{1,5}?);)')
315                 
316                 entities = htmlentitynamemask.finditer(in_html)
317                 entitydict = {}
318
319                 for x in entities:
320                         entitydict[x.group(1)] = x.group(2)
321
322                 for key, name in entitydict.items():
323                         entitydict[key] = htmlentitydefs.name2codepoint[name]
324
325                 entities = htmlentitynumbermask.finditer(in_html)
326
327                 for x in entities:
328                         entitydict[x.group(1)] = x.group(2)
329
330                 for key, codepoint in entitydict.items():
331                         in_html = in_html.replace(key, (unichr(int(codepoint)).encode('utf8')))
332
333                 self.inhtml = in_html
334
335         def OFDBquery(self,string):
336                 print "[OFDBquery]"
337                 self["statusbar"].setText(_("OFDb Download completed"))
338
339                 self.html2utf8(open("/tmp/ofdbquery.html", "r").read())
340
341                 self.generalinfos = self.generalinfomask.search(self.inhtml)
342
343                 if self.generalinfos:
344                         self.OFDBparse()
345                 else:
346                         if re.search("<title>OFDb - Suchergebnis</title>", self.inhtml):
347                                 searchresultmask = re.compile("<br>(\d{1,3}\.) <a href=\"film/(.*?)\"(?:.*?)\)\">(.*?)</a>", re.DOTALL)
348                                 searchresults = searchresultmask.finditer(self.inhtml)
349                                 self.resultlist = []
350                                 if searchresults:
351                                         for x in searchresults:
352                                                 self.resultlist.append((self.htmltags.sub('',x.group(3)), x.group(2)))
353                                         self["menu"].l.setList(self.resultlist)
354                                 if len(self.resultlist) == 1:
355                                         self.Page = 0
356                                         self["extralabel"].hide()
357                                         self.showDetails()
358                                 elif len(self.resultlist) > 1:
359                                         self.Page = 1
360                                         self.showMenu()
361                                 else:
362                                         self["detailslabel"].setText(_("No OFDb match."))
363                                         self["statusbar"].setText(_("No OFDb match."))
364                         else:
365                                 self["detailslabel"].setText(_("OFDb query failed!"))
366
367         def OFDBquery2(self,string):
368                 self["statusbar"].setText(_("OFDb Re-Download completed"))
369                 self.html2utf8(open("/tmp/ofdbquery2.html", "r").read())
370                 self.generalinfos = self.generalinfomask.search(self.inhtml)
371                 self.OFDBparse()
372
373         def OFDBparse(self):
374                 print "[OFDBparse]"
375                 self.Page = 1
376                 Detailstext = _("No details found.")
377                 if self.generalinfos:
378                         self["key_yellow"].setText(_("Details"))
379                         self["statusbar"].setText(_("OFDb Details parsed"))
380                         
381                         Titeltext = self.generalinfos.group("title")
382                         if len(Titeltext) > 57:
383                                 Titeltext = Titeltext[0:54] + "..."
384                         self["titellabel"].setText(Titeltext)
385
386                         Detailstext = ""
387
388                         genreblockmask = re.compile('Genre\(s\):(?:[\s\S]*?)class=\"Daten\">(.*?)</tr>', re.DOTALL)
389                         genreblock = genreblockmask.findall(self.inhtml)
390                         genremask = re.compile('\">(.*?)</a')
391                         if genreblock:
392                                 genres = genremask.finditer(genreblock[0])
393                                 if genres:
394                                         Detailstext += "Genre: "
395                                         for x in genres:
396                                                 Detailstext += self.htmltags.sub('', x.group(1)) + " "
397
398                         for category in ("director", "year", "country", "original"):
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 = _("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 = _("Cast: ") + Casttext
429                                         else:
430                                                 Casttext = _("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: %s...") % (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(noPoster = True)
444
445                 self["detailslabel"].setText(Detailstext)
446                 
447         def OFDBPoster(self, noPoster = False):
448                 self["statusbar"].setText(_("OFDb Details parsed"))
449                 if not noPoster:
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)