exception handling to make it work without patched Plugin.py descriptor
[enigma2-plugins.git] / imdb / src / plugin.py
1 # -*- coding: utf8 -*-
2 from Screens.Screen import Screen
3 from Screens.MessageBox import MessageBox
4 from Components.ActionMap import ActionMap, NumberActionMap
5 from Components.Input import Input
6 from Components.Pixmap import Pixmap
7 from Components.FileList import FileList
8 from Screens.ChoiceBox import ChoiceBox
9 from Plugins.Plugin import PluginDescriptor
10 from twisted.web.client import downloadPage
11 from enigma import eServiceCenter, eServiceReference, iServiceInformation, eEPGCache
12 from Components.Sources.Source import Source
13 from Components.config import config
14 from ServiceReference import ServiceReference
15 from RecordTimer import RecordTimerEntry, RecordTimer, AFTEREVENT, parseEvent
16 from Components.Label import Label
17 from Components.ScrollLabel import ScrollLabel
18 from Components.Button import Button
19 from os import popen
20 import re
21 from enigma import loadPic
22 from enigma import loadPNG
23 from enigma import gFont
24 import htmlentitydefs
25 from Components.AVSwitch import AVSwitch
26 import urllib
27 from Components.MenuList import MenuList
28 from Components.Language import language
29 #from PictureScreen import PictureScreen
30
31 from Components.ProgressBar import ProgressBar
32
33 class IMDB(Screen):
34         skin = """
35                 <screen name="IMDB" position="90,95" size="560,420" title="Internet Movie Database Details Plugin" >
36                         <ePixmap pixmap="skin_default/key-red.png" position="0,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
37                         <ePixmap pixmap="skin_default/key-green.png" position="140,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
38                         <ePixmap pixmap="skin_default/key-yellow.png" position="280,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
39                         <ePixmap pixmap="skin_default/key-blue.png" position="420,0" zPosition="0" size="140,40" transparent="1" alphatest="on" />
40                         <widget name="key_red" position="0,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#9f1313" transparent="1" />
41                         <widget name="key_green" position="140,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />
42                         <widget name="key_yellow" position="280,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#a08500" transparent="1" />
43                         <widget name="key_blue" position="420,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#18188b" transparent="1" />
44                         <widget name="titellabel" position="10,40" size="330,45" valign="center" font="Regular;22"/>
45                         <widget name="detailslabel" position="105,90" size="445,140" font="Regular;18" />
46                         <widget name="castlabel" position="10,235" size="540,155" font="Regular;18" />
47                         <widget name="extralabel" position="10,40" size="540,350" font="Regular;18" />
48                         <widget name="ratinglabel" position="340,62" size="210,20" halign="center" font="Regular;18" foregroundColor="#f0b400"/>
49                         <widget name="statusbar" position="10,404" size="540,16" font="Regular;16" foregroundColor="#cccccc" />
50                         <widget name="poster" position="4,90" size="96,140" alphatest="on" />
51                         <widget name="menu" position="10,115" size="540,275" zPosition="3" scrollbarMode="showOnDemand" />
52                         <widget name="starsbg" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/IMDb/starsbar_empty.png" position="340,40" zPosition="0" size="210,21" transparent="1" alphatest="on" />
53                         <widget name="stars" position="340,40" size="210,21" pixmap="/usr/lib/enigma2/python/Plugins/Extensions/IMDb/starsbar_filled.png" transparent="1" />
54                 </screen>"""
55         
56         def __init__(self, session, eventName, args = None):
57                 self.skin = IMDB.skin
58                 Screen.__init__(self, session)
59                 
60                 self.eventName = eventName
61                 
62                 self.dictionary_init()
63
64                 self["poster"] = Pixmap()
65                 self.preview = Pixmap()
66                 
67                 self["stars"] = ProgressBar()
68                 self["starsbg"] = Pixmap()
69                 self["stars"].hide()
70                 self["starsbg"].hide()
71                 self.ratingstars = -1
72         
73                 self["titellabel"] = Label("The Internet Movie Database")
74                 self["detailslabel"] = ScrollLabel("")
75                 self["castlabel"] = ScrollLabel("")
76                 self["extralabel"] = ScrollLabel("")
77                 self["statusbar"] = Label("")
78                 self["ratinglabel"] = Label("")
79                 self.resultlist = []
80                 self["menu"] = MenuList(self.resultlist)
81                 self["menu"].hide()
82                 
83                 self["key_red"] = Button(self._("Exit"))
84                 self["key_green"] = Button("")
85                 self["key_yellow"] = Button("")
86                 self["key_blue"] = Button("")
87                         
88                 # 0 = multiple query selection menu page
89                 # 1 = movie info page
90                 # 2 = extra infos page
91                 self.Page = 0
92                                 
93                 self["actions"] = ActionMap(["OkCancelActions", "ColorActions", "MovieSelectionActions", "DirectionActions"],
94                 {
95                         "ok": self.showDetails,
96                         "cancel": self.close,
97                         "down": self.pageDown,
98                         "up": self.pageUp,
99                         "red": self.close,
100                         "green": self.showMenu,
101                         "yellow": self.showDetails,
102                         "blue": self.showExtras,
103                         "showEventInfo": self.showDetails
104                 }, -1)
105                 
106                 self.getIMDB()
107                 
108         def dictionary_init(self):
109                 syslang = language.getLanguage()
110                 if syslang.find("de") is -1:
111                         self.IMDBlanguage = ""  # set to empty ("") for english version
112                 else:
113                         self.IMDBlanguage = "german." # it's a subdomain, so add a '.' at the end
114                 
115                 self.dict = {}
116                 self.dict["of"]="von"
117                 self.dict[" as "]=" als "
118                 self.dict["Ambiguous results"]="Kein eindeutiger Treffer"
119                 self.dict["Please select the matching entry"]="Bitte passenden Eintrag auswählen"
120                 self.dict["No IMDb match."]="Keine passenden Einträge gefunden."
121                 self.dict["IMDb query failed!"]="IMDb-Query fehlgeschlagen!"
122                 self.dict["No details found."]="Keine Details gefunden."
123                 self.dict["no user rating yet"]="noch keine Nutzerwertung"
124                 self.dict["Cast: "]="Darsteller: "
125                 self.dict["No cast list found in the database."]="Keine Darstellerliste in der Datenbank gefunden."
126                 self.dict["Exit"]="Beenden"
127                 self.dict["Extra Info"]="Zusatzinfos"
128                 self.dict["Title Menu"]="Titelauswahl"
129                 
130                 self.htmltags = re.compile('<.*?>')
131                 
132                 self.generalinfomask = re.compile(
133                 '<h1>(?P<title>.*?) <.*?</h1>.*?'
134                 '(?:.*?<h5>(?P<g_director>Regisseur|Directors?):</h5>.*?>(?P<director>.*?)</a>)*'
135                 '(?:.*?<h5>(?P<g_creator>Sch\S*?pfer|Creators?):</h5>.*?>(?P<creator>.*?)</a>)*'
136                 '(?:.*?<h5>(?P<g_seasons>Seasons):</h5>(?:.*?)<a href=\".*?\">(?P<seasons>\d+?)</a>\s+?(?:<a class|\|\s+?<a href="episodes#season-unknown))*'
137                 '(?:.*?<h5>(?P<g_writer>Drehbuch|Writer).*?</h5>.*?>(?P<writer>.*?)</a>)*'
138                 '(?:.*?<h5>(?P<g_premiere>Premiere|Release Date).*?</h5>\s.*?\n?(?P<premiere>.*?)\n\s.*?<)*'
139                 '(?:.*?<h5>(?P<g_alternativ>Alternativ|Also Known As):</h5>(?P<alternativ>.*?)<br>\s{0,8}<a.*?>(?:mehr|more))*'
140                 '(?:.*?<h5>(?P<g_country>Produktionsland|Country):</h5>.*?<a.*?>(?P<country>.*?)</a>(?:.*?mehr|\n</div>))*'
141                 , re.DOTALL)
142                 
143                 self.extrainfomask = re.compile(
144                 '(?:.*?<h5>(?P<g_tagline>Werbezeile|Tagline?):</h5>\n(?P<tagline>.+?)<)*'
145                 '(?:.*?<h5>(?P<g_outline>Kurzbeschreibung|Plot Outline):</h5>(?P<outline>.+?)<)*'
146                 '(?:.*?<h5>(?P<g_synopsis>Plot Synopsis):</h5>(?:.*?)(?:<a href=\".*?\">)*?(?P<synopsis>.+?)(?:</a>|</div>))*'
147                 '(?:.*?<h5>(?P<g_keywords>Plot Keywords):</h5>(?P<keywords>.+?)(?:mehr|more</a>|</div>))*'
148                 '(?:.*?<h5>(?P<g_awards>Filmpreise|Awards):</h5>(?P<awards>.+?)(?:mehr|more</a>|</div>))*'
149                 '(?:.*?<h5>(?P<g_runtime>L\S*?nge|Runtime):</h5>(?P<runtime>.+?)<)*'
150                 '(?:.*?<h5>(?P<g_language>Sprache|Language):</h5>(?P<language>.+?)</div>)*'
151                 '(?:.*?<h5>(?P<g_color>Farbe|Color):</h5>(?P<color>.+?)</div>)*'
152                 '(?:.*?<h5>(?P<g_aspect>Seitenverh\S*?ltnis|Aspect Ratio):</h5>(?P<aspect>.+?)(?:mehr|more</a>|</div>))*'
153                 '(?:.*?<h5>(?P<g_sound>Tonverfahren|Sound Mix):</h5>(?P<sound>.+?)</div>)*'
154                 '(?:.*?<h5>(?P<g_cert>Altersfreigabe|Certification):</h5>(?P<cert>.+?)</div>)*'
155                 '(?:.*?<h5>(?P<g_locations>Drehorte|Filming Locations):</h5>(?P<locations>.+?)(?:mehr|more</a>|</div>))*'
156                 '(?:.*?<h5>(?P<g_company>Firma|Company):</h5>(?P<company>.+?)(?:mehr|more</a>|</div>))*'
157                 '(?:.*?<h5>(?P<g_trivia>Dies und das|Trivia):</h5>(?P<trivia>.+?)(?:mehr|more</a>|</div>))*'
158                 '(?:.*?<h5>(?P<g_goofs>Pannen|Goofs):</h5>(?P<goofs>.+?)(?:mehr|more</a>|</div>))*'
159                 '(?:.*?<h5>(?P<g_quotes>Dialogzitate|Quotes):</h5>(?P<quotes>.+?)(?:mehr|more</a>|</div>))*'
160                 '(?:.*?<h5>(?P<g_connections>Bez\S*?ge zu anderen Titeln|Movie Connections):</h5>(?P<connections>.+?)(?:mehr|more</a>|</div>))*'
161                 '(?:.*?<h3>(?P<g_comments>Nutzerkommentare|User Comments)</h3>.*?<a href="/user/ur\d{7,7}/comments">(?P<commenter>.+?)\n</div>.*?<p>(?P<comment>.+?)</p>)*'
162                 , re.DOTALL)
163
164         def _(self, in_string):
165                 out_string = in_string
166                 if ((self.IMDBlanguage).find("german")) != -1:
167                         out_string = self.dict.get(in_string, in_string)
168                 return out_string
169                 
170         def resetLabels(self):
171                 self["detailslabel"].setText("")
172                 self["ratinglabel"].setText("")
173                 self["titellabel"].setText("")
174                 self["castlabel"].setText("")
175                 self["titellabel"].setText("")
176                 self["extralabel"].setText("")
177                 self.ratingstars = -1
178
179         def pageUp(self):
180                 if self.Page == 0:
181                         self["menu"].instance.moveSelection(self["menu"].instance.moveUp)
182                 if self.Page == 1:
183                         self["castlabel"].pageUp()
184                         self["detailslabel"].pageUp()
185                 if self.Page == 2:
186                         self["extralabel"].pageUp()
187         
188         def pageDown(self):
189                 if self.Page == 0:
190                         self["menu"].instance.moveSelection(self["menu"].instance.moveDown)
191                 if self.Page == 1:
192                         self["castlabel"].pageDown()
193                         self["detailslabel"].pageDown()
194                 if self.Page == 2:
195                         self["extralabel"].pageDown()
196
197         def showMenu(self):
198                 if ( self.Page is 1 or self.Page is 2 ) and self.resultlist:
199                         self["menu"].show()
200                         self["stars"].hide()
201                         self["starsbg"].hide()
202                         self["ratinglabel"].hide()
203                         self["castlabel"].hide()
204                         self["poster"].hide()
205                         self["extralabel"].hide()
206                         self["titellabel"].setText(self._("Ambiguous results"))
207                         self["detailslabel"].setText(self._("Please select the matching entry"))
208                         self["detailslabel"].show()
209                         self["key_blue"].setText("")
210                         self["key_green"].setText(self._("Title Menu"))
211                         self["key_yellow"].setText(self._("Details"))
212                         self.Page = 0
213                 
214         def showDetails(self):
215                 self["ratinglabel"].show()
216                 self["castlabel"].show()
217                 self["detailslabel"].show()
218                 
219                 if self.resultlist and self.Page == 0:
220                         link = self["menu"].getCurrent()[1]
221                         title = self["menu"].getCurrent()[0]
222                         self["statusbar"].setText("Re-Query IMDb: "+title+"...")
223                         localfile = "/home/root/imdbquery2.html"
224                         fetchurl = "http://" + self.IMDBlanguage + "imdb.com/title/" + link
225                         print "[IMDB] downloading query " + fetchurl + " to " + localfile
226                         downloadPage(fetchurl,localfile).addCallback(self.IMDBquery2).addErrback(self.fetchFailed)
227                         self["menu"].hide()
228                         self.resetLabels()
229                         self.Page = 1
230                         
231                 if self.Page == 2:
232                         self["extralabel"].hide()
233                         self["poster"].show()
234                         if self.ratingstars > 0:
235                                 self["starsbg"].show()
236                                 self["stars"].show()
237                                 self["stars"].setValue(self.ratingstars)
238
239                         self.Page = 1
240                                 
241         def showExtras(self):
242                 if self.Page == 1:
243                         self["extralabel"].show()
244                         self["detailslabel"].hide()
245                         self["castlabel"].hide()
246                         self["poster"].hide()
247                         self["stars"].hide()
248                         self["starsbg"].hide()
249                         self["ratinglabel"].hide()
250                         self.Page = 2
251                 
252         def getIMDB(self):
253                 self.resetLabels()
254                 if self.eventName is "":
255                         s = self.session.nav.getCurrentService()
256                         info = s.info()
257                         event = info.getEvent(0) # 0 = now, 1 = next
258                         if event:
259                                 self.eventName = event.getEventName()
260                 if self.eventName is not "":
261                         self["statusbar"].setText("Query IMDb: " + self.eventName + "...")
262                         self.eventName = urllib.quote(self.eventName.decode('utf8').encode('latin-1'))
263                         localfile = "/home/root/imdbquery.html"
264                         fetchurl = "http://" + self.IMDBlanguage + "imdb.com/find?q=" + self.eventName + "&s=tt&site=aka"
265                         print "[IMDB] Downloading Query " + fetchurl + " to " + localfile
266                         downloadPage(fetchurl,localfile).addCallback(self.IMDBquery).addErrback(self.fetchFailed)
267                 else:
268                         self["statusbar"].setText("Could't get Eventname -_-")
269                                 
270         def fetchFailed(self,string):
271                 print "[IMDB] fetch failed " + string
272                 self["statusbar"].setText("IMDb Download failed -_-")
273                 
274         def html2utf8(self,in_html):
275                 htmlentitynumbermask = re.compile('(&#(\d{1,5}?);)')
276                 htmlentitynamemask = re.compile('(&(\D{1,5}?);)')
277                 
278                 entities = htmlentitynamemask.finditer(in_html)
279                 entitydict = {}
280                 
281                 for x in entities:
282                         entitydict[x.group(1)] = x.group(2)
283                 
284                 for key, name in entitydict.items():
285                         entitydict[key] = htmlentitydefs.name2codepoint[name]
286                 
287                 entities = htmlentitynumbermask.finditer(in_html)
288                 
289                 for x in entities:
290                         entitydict[x.group(1)] = x.group(2)
291                 
292                 for key, codepoint in entitydict.items():
293                         in_html = in_html.replace(key, (unichr(int(codepoint)).encode('latin-1')))
294         
295                 self.inhtml = in_html.decode('latin-1').encode('utf8')
296
297         def IMDBquery(self,string):
298                 print "[IMDBquery]"
299                 self["statusbar"].setText("IMDb Download completed")
300                 
301                 self.html2utf8(open("/home/root/imdbquery.html", "r").read())
302                 
303                 self.generalinfos = self.generalinfomask.search(self.inhtml)
304                 
305                 if self.generalinfos:
306                         self.IMDBparse()
307                 else:
308                         if re.search("<title>IMDb.{0,9}Search</title>", self.inhtml):
309                                 searchresultmask = re.compile("href=\".*?/title/(tt\d{7,7})/\">(.*?)</td>", re.DOTALL)
310                                 searchresults = searchresultmask.finditer(self.inhtml)
311                                 self.resultlist = []
312                                 if searchresults:
313                                         for x in searchresults:
314                                                 self.resultlist.append((self.htmltags.sub('',x.group(2)), x.group(1)))
315                                         self["menu"].l.setList(self.resultlist)
316                                 if len(self.resultlist) > 1:
317                                         self.Page = 1
318                                         self.showMenu()
319                                 else:
320                                         self["detailslabel"].setText(self._("No IMDb match."))
321                                         self["statusbar"].setText("No IMDb match")
322                         else:
323                                 self["detailslabel"].setText(self._("IMDb query failed!"))
324                 
325         def IMDBquery2(self,string):
326                 self["statusbar"].setText("IMDb Re-Download completed")
327                 self.html2utf8(open("/home/root/imdbquery2.html", "r").read())
328                 self.generalinfos = self.generalinfomask.search(self.inhtml)
329                 self.IMDBparse()
330                 
331         def IMDBparse(self):
332                 print "[IMDBparse]"
333                 self.Page = 1
334                 Detailstext = self._("No details found.")
335                 if self.generalinfos:
336                         self["key_yellow"].setText(self._("Details"))
337                         self["statusbar"].setText("IMDb Details parsed ^^")
338                         
339                         Titeltext = self.generalinfos.group("title")
340                         if len(Titeltext) > 57:
341                                 Titeltext = Titeltext[0:54] + "..."
342                         self["titellabel"].setText(Titeltext)
343                         
344                         Detailstext = ""
345                         
346                         genreblockmask = re.compile('<h5>Genre:</h5>(.*?)(?:mehr|more|</div>)', re.DOTALL)
347                         genreblock = genreblockmask.findall(self.inhtml)
348                         genremask = re.compile('\">(.*?)</a')
349                         if genreblock:
350                                 genres = genremask.finditer(genreblock[0])
351                                 if genres:
352                                         Detailstext += "Genre: "
353                                         for x in genres:
354                                                 Detailstext += x.group(1) + " "
355                                                 
356                         detailscategories = ["director", "creator", "writer", "premiere", "seasons", "country"]
357                                 
358                         for category in detailscategories:
359                                 if self.generalinfos.group('g_'+category):
360                                         Detailstext += "\n" + self.generalinfos.group('g_'+category) + ": " + self.generalinfos.group(category)
361
362                         if self.generalinfos.group("alternativ"):
363                                 Detailstext += "\n" + self.generalinfos.group("g_alternativ") + ": " + self.htmltags.sub('',(self.generalinfos.group("alternativ").replace('\n','').replace("<br>",'\n').replace("  ",' ')))
364                                 
365                         ratingmask = re.compile('(?P<g_rating>Nutzer-Bewertung|User Rating):</b>.{0,2}<b>(?P<rating>.*?)/10</b>', re.DOTALL)
366                         rating = ratingmask.search(self.inhtml)
367                         Ratingtext = self._("no user rating yet")
368                         if rating:
369                                 Ratingtext = rating.group("g_rating") + ": " + rating.group("rating") + " / 10"
370                                 self.ratingstars = int(10*round(float(rating.group("rating")),1))
371                                 self["stars"].show()
372                                 self["stars"].setValue(self.ratingstars)
373                                 self["starsbg"].show()
374                         self["ratinglabel"].setText(Ratingtext)
375
376                         castmask = re.compile('<td class="nm">.*?>(.*?)</a>.*?<td class="char">(?:<a.*?>)?(.*?)(?:</a>)?</td>', re.DOTALL)
377                         castresult = castmask.finditer(self.inhtml)
378                         if castresult:
379                                 Casttext = ""
380                                 for x in castresult:
381                                         Casttext += "\n" + self.htmltags.sub('', x.group(1))
382                                         if x.group(2):
383                                                 Casttext += self._(" als ") + self.htmltags.sub('', x.group(2).replace('/ ...',''))
384                                 if Casttext is not "":
385                                         Casttext = self._("Cast: ") + Casttext
386                                 else:
387                                         Casttext = self._("No cast list found in the database.")
388                                 self["castlabel"].setText(Casttext)
389                         
390                         postermask = re.compile('<div class="photo">.*?<img .*? src=\"(http.*?)\" .*?>', re.DOTALL)
391                         posterurl = postermask.search(self.inhtml).group(1)
392                         if posterurl.find("jpg") > 0:
393                                 self["statusbar"].setText("Downloading Movie Poster: "+posterurl+"...")
394                                 localfile = "/home/root/poster.jpg"
395                                 print "[IMDB] downloading poster " + posterurl + " to " + localfile
396                                 downloadPage(posterurl,localfile).addCallback(self.IMDBPoster).addErrback(self.fetchFailed)
397                         else:
398                                 self.IMDBPoster("kein Poster")
399
400                         extrainfos = self.extrainfomask.search(self.inhtml)
401                         
402                         if extrainfos:
403                                 Extratext = "Extra Info\n"
404                                 extracategories = ["tagline","outline","synopsis","keywords","awards","runtime","language","color","aspect","sound","cert","locations","company","trivia","goofs","quotes","connections"]
405                                         
406                                 for category in extracategories:
407                                         if extrainfos.group('g_'+category):
408                                                 Extratext += extrainfos.group('g_'+category) + ": " + self.htmltags.sub('',extrainfos.group(category).replace("\n",'').replace("<br>",'\n')) + "\n"
409                                 if extrainfos.group("g_comments"):
410                                         Extratext += extrainfos.group("g_comments") + " [" + self.htmltags.sub('',extrainfos.group("commenter")) + "]: " + self.htmltags.sub('',extrainfos.group("comment").replace("\n",'')) + "\n"
411         
412                                 self["extralabel"].setText(Extratext)
413                                 self["extralabel"].hide()
414                                 self["key_blue"].setText(self._("Extra Info"))
415                 
416                 self["detailslabel"].setText(Detailstext)
417                 
418         def IMDBPoster(self,string):
419                 self["statusbar"].setText("IMDb Details parsed ^^")
420                 if not string:
421                         filename = "/home/root/poster.jpg"
422                 else:
423                         filename = "/usr/lib/enigma2/python/Plugins/Extensions/IMDb/no_poster.png"
424                 pixmap = loadPic(filename, 96,140, AVSwitch().getAspectRatioSetting()/2,1,0,0)
425                 if pixmap is not None:
426                         self["poster"].instance.setPixmap(pixmap.__deref__())
427                         self["poster"].move(4,90)
428                         self["poster"].show()
429
430 def main(session, eventName="", **kwargs):
431         session.open(IMDB, eventName)
432         
433 def Plugins(**kwargs):
434         try:
435                 wherelist = [PluginDescriptor.WHERE_EVENTINFO, PluginDescriptor.WHERE_PLUGINMENU]
436                 return PluginDescriptor(name="IMDb Details",
437                                 description="Filmdetails aus der Internet Movie Database",
438                                 icon="imdb.png",
439                                 where = wherelist,
440                                 fnc=main)
441         except AttributeError:
442                 wherelist = [PluginDescriptor.WHERE_EXTENSIONSMENU, PluginDescriptor.WHERE_PLUGINMENU]
443                 return PluginDescriptor(name="IMDb Details",
444                                 description="Filmdetails aus der Internet Movie Database",
445                                 icon="imdb.png",
446                                 where = wherelist,
447                                 fnc=main)