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
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"))
30 t = gettext.dgettext("IMDb", txt)
32 print "[IMDb] fallback to default translation for", txt
33 t = gettext.gettext(txt)
37 language.addCallback(localeInit)
39 class IMDBChannelSelection(SimpleChannelSelection):
40 def __init__(self, session):
41 SimpleChannelSelection.__init__(self, session, _("Channel Selection"))
42 self.skinName = "SimpleChannelSelection"
44 self["ChannelSelectEPGActions"] = ActionMap(["ChannelSelectEPGActions"],
46 "showEPGList": self.channelSelected
50 def channelSelected(self):
51 ref = self.getCurrentSelection()
52 if (ref.flags & 7) == 7:
54 elif not (ref.flags & eServiceReference.isMarker):
55 self.session.openWithCallback(
62 def epgClosed(self, ret = None):
66 class IMDBEPGSelection(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
73 def infoKeyPressed(self):
77 cur = self["list"].getCurrent()
89 self.close(evt.getEventName())
91 def onSelectionChanged(self):
96 <screen name="IMDB" position="90,95" size="560,420" title="Internet Movie Database 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/IMDb/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/IMDb/starsbar_filled.png" transparent="1" />
117 def __init__(self, session, eventName, args = None):
118 self.skin = IMDB.skin
119 Screen.__init__(self, session)
121 self.eventName = eventName
123 self.dictionary_init()
125 self["poster"] = Pixmap()
126 self.picload = ePicLoad()
127 self.picload.PictureData.get().append(self.paintPosterPixmapCB)
129 self["stars"] = ProgressBar()
130 self["starsbg"] = Pixmap()
132 self["starsbg"].hide()
133 self.ratingstars = -1
135 self["titellabel"] = Label(_("The Internet Movie Database"))
136 self["detailslabel"] = ScrollLabel("")
137 self["castlabel"] = ScrollLabel("")
138 self["extralabel"] = ScrollLabel("")
139 self["statusbar"] = Label("")
140 self["ratinglabel"] = Label("")
142 self["menu"] = MenuList(self.resultlist)
145 self["key_red"] = Button(_("Exit"))
146 self["key_green"] = Button("")
147 self["key_yellow"] = Button("")
148 self["key_blue"] = Button("")
150 # 0 = multiple query selection menu page
151 # 1 = movie info page
152 # 2 = extra infos page
155 self["actions"] = ActionMap(["OkCancelActions", "ColorActions", "MovieSelectionActions", "DirectionActions"],
157 "ok": self.showDetails,
158 "cancel": self.close,
159 "down": self.pageDown,
162 "green": self.showMenu,
163 "yellow": self.showDetails,
164 "blue": self.showExtras,
165 "contextMenu": self.openChannelSelection,
166 "showEventInfo": self.showDetails
171 def dictionary_init(self):
172 syslang = language.getLanguage()
173 if "de" not in syslang:
174 self.IMDBlanguage = "" # set to empty ("") for english version
176 self.IMDBlanguage = "german." # it's a subdomain, so add a '.' at the end
178 self.htmltags = re.compile('<.*?>')
180 self.generalinfomask = re.compile(
181 '<h1>(?P<title>.*?) <.*?</h1>.*?'
182 '(?:.*?<h5>(?P<g_director>Regisseur|Directors?):</h5>.*?>(?P<director>.*?)</a>)*'
183 '(?:.*?<h5>(?P<g_creator>Sch\S*?pfer|Creators?):</h5>.*?>(?P<creator>.*?)</a>)*'
184 '(?:.*?<h5>(?P<g_seasons>Seasons):</h5>(?:.*?)<a href=\".*?\">(?P<seasons>\d+?)</a>\s+?(?:<a class|\|\s+?<a href="episodes#season-unknown))*'
185 '(?:.*?<h5>(?P<g_writer>Drehbuch|Writer).*?</h5>.*?>(?P<writer>.*?)</a>)*'
186 '(?:.*?<h5>(?P<g_premiere>Premiere|Release Date).*?</h5>\s.*?\n?(?P<premiere>.*?)\n\s.*?<)*'
187 '(?:.*?<h5>(?P<g_alternativ>Alternativ|Also Known As):</h5>(?P<alternativ>.*?)<br>\s{0,8}<a.*?>(?:mehr|more))*'
188 '(?:.*?<h5>(?P<g_country>Produktionsland|Country):</h5>.*?<a.*?>(?P<country>.*?)</a>(?:.*?mehr|\n</div>))*'
191 self.extrainfomask = re.compile(
192 '(?:.*?<h5>(?P<g_tagline>Werbezeile|Tagline?):</h5>\n(?P<tagline>.+?)<)*'
193 '(?:.*?<h5>(?P<g_outline>Kurzbeschreibung|Plot Outline):</h5>(?P<outline>.+?)<)*'
194 '(?:.*?<h5>(?P<g_synopsis>Plot Synopsis):</h5>(?:.*?)(?:<a href=\".*?\">)*?(?P<synopsis>.+?)(?:</a>|</div>))*'
195 '(?:.*?<h5>(?P<g_keywords>Plot Keywords):</h5>(?P<keywords>.+?)(?:mehr|more</a>|</div>))*'
196 '(?:.*?<h5>(?P<g_awards>Filmpreise|Awards):</h5>(?P<awards>.+?)(?:mehr|more</a>|</div>))*'
197 '(?:.*?<h5>(?P<g_runtime>L\S*?nge|Runtime):</h5>(?P<runtime>.+?)<)*'
198 '(?:.*?<h5>(?P<g_language>Sprache|Language):</h5>(?P<language>.+?)</div>)*'
199 '(?:.*?<h5>(?P<g_color>Farbe|Color):</h5>(?P<color>.+?)</div>)*'
200 '(?:.*?<h5>(?P<g_aspect>Seitenverh\S*?ltnis|Aspect Ratio):</h5>(?P<aspect>.+?)(?:mehr|more</a>|</div>))*'
201 '(?:.*?<h5>(?P<g_sound>Tonverfahren|Sound Mix):</h5>(?P<sound>.+?)</div>)*'
202 '(?:.*?<h5>(?P<g_cert>Altersfreigabe|Certification):</h5>(?P<cert>.+?)</div>)*'
203 '(?:.*?<h5>(?P<g_locations>Drehorte|Filming Locations):</h5>(?P<locations>.+?)(?:mehr|more</a>|</div>))*'
204 '(?:.*?<h5>(?P<g_company>Firma|Company):</h5>(?P<company>.+?)(?:mehr|more</a>|</div>))*'
205 '(?:.*?<h5>(?P<g_trivia>Dies und das|Trivia):</h5>(?P<trivia>.+?)(?:mehr|more</a>|</div>))*'
206 '(?:.*?<h5>(?P<g_goofs>Pannen|Goofs):</h5>(?P<goofs>.+?)(?:mehr|more</a>|</div>))*'
207 '(?:.*?<h5>(?P<g_quotes>Dialogzitate|Quotes):</h5>(?P<quotes>.+?)(?:mehr|more</a>|</div>))*'
208 '(?:.*?<h5>(?P<g_connections>Bez\S*?ge zu anderen Titeln|Movie Connections):</h5>(?P<connections>.+?)(?:mehr|more</a>|</div>))*'
209 '(?:.*?<h3>(?P<g_comments>Nutzerkommentare|User Comments)</h3>.*?<a href="/user/ur\d{7,7}/comments">(?P<commenter>.+?)\n</div>.*?<p>(?P<comment>.+?)</p>)*'
212 def resetLabels(self):
213 self["detailslabel"].setText("")
214 self["ratinglabel"].setText("")
215 self["titellabel"].setText("")
216 self["castlabel"].setText("")
217 self["titellabel"].setText("")
218 self["extralabel"].setText("")
219 self.ratingstars = -1
223 self["menu"].instance.moveSelection(self["menu"].instance.moveUp)
225 self["castlabel"].pageUp()
226 self["detailslabel"].pageUp()
228 self["extralabel"].pageUp()
232 self["menu"].instance.moveSelection(self["menu"].instance.moveDown)
234 self["castlabel"].pageDown()
235 self["detailslabel"].pageDown()
237 self["extralabel"].pageDown()
240 if ( self.Page is 1 or self.Page is 2 ) and self.resultlist:
243 self["starsbg"].hide()
244 self["ratinglabel"].hide()
245 self["castlabel"].hide()
246 self["poster"].hide()
247 self["extralabel"].hide()
248 self["titellabel"].setText(_("Ambiguous results"))
249 self["detailslabel"].setText(_("Please select the matching entry"))
250 self["detailslabel"].show()
251 self["key_blue"].setText("")
252 self["key_green"].setText(_("Title Menu"))
253 self["key_yellow"].setText(_("Details"))
256 def showDetails(self):
257 self["ratinglabel"].show()
258 self["castlabel"].show()
259 self["detailslabel"].show()
261 if self.resultlist and self.Page == 0:
262 link = self["menu"].getCurrent()[1]
263 title = self["menu"].getCurrent()[0]
264 self["statusbar"].setText(_("Re-Query IMDb: %s...") % (title))
265 localfile = "/tmp/imdbquery2.html"
266 fetchurl = "http://" + self.IMDBlanguage + "imdb.com/title/" + link
267 print "[IMDB] downloading query " + fetchurl + " to " + localfile
268 downloadPage(fetchurl,localfile).addCallback(self.IMDBquery2).addErrback(self.fetchFailed)
274 self["extralabel"].hide()
275 self["poster"].show()
276 if self.ratingstars > 0:
277 self["starsbg"].show()
279 self["stars"].setValue(self.ratingstars)
283 def showExtras(self):
285 self["extralabel"].show()
286 self["detailslabel"].hide()
287 self["castlabel"].hide()
288 self["poster"].hide()
290 self["starsbg"].hide()
291 self["ratinglabel"].hide()
294 def openChannelSelection(self):
295 self.session.openWithCallback(
296 self.channelSelectionClosed,
300 def channelSelectionClosed(self, ret = None):
307 if self.eventName is "":
308 s = self.session.nav.getCurrentService()
310 event = info.getEvent(0) # 0 = now, 1 = next
312 self.eventName = event.getEventName()
313 if self.eventName is not "":
314 self["statusbar"].setText(_("Query IMDb: %s...") % (self.eventName))
315 event_quoted = urllib.quote(self.eventName.decode('utf8').encode('latin-1','ignore'))
316 localfile = "/tmp/imdbquery.html"
317 fetchurl = "http://" + self.IMDBlanguage + "imdb.com/find?q=" + event_quoted + "&s=tt&site=aka"
318 print "[IMDB] Downloading Query " + fetchurl + " to " + localfile
319 downloadPage(fetchurl,localfile).addCallback(self.IMDBquery).addErrback(self.fetchFailed)
321 self["statusbar"].setText(_("Could't get Eventname"))
323 def fetchFailed(self,string):
324 print "[IMDB] fetch failed " + string
325 self["statusbar"].setText(_("IMDb Download failed"))
327 def html2utf8(self,in_html):
328 htmlentitynumbermask = re.compile('(&#(\d{1,5}?);)')
329 htmlentitynamemask = re.compile('(&(\D{1,5}?);)')
331 entities = htmlentitynamemask.finditer(in_html)
335 entitydict[x.group(1)] = x.group(2)
337 for key, name in entitydict.items():
338 entitydict[key] = htmlentitydefs.name2codepoint[name]
340 entities = htmlentitynumbermask.finditer(in_html)
343 entitydict[x.group(1)] = x.group(2)
345 for key, codepoint in entitydict.items():
346 in_html = in_html.replace(key, (unichr(int(codepoint)).encode('latin-1')))
348 self.inhtml = in_html.decode('latin-1').encode('utf8')
350 def IMDBquery(self,string):
352 self["statusbar"].setText(_("IMDb Download completed"))
354 self.html2utf8(open("/tmp/imdbquery.html", "r").read())
356 self.generalinfos = self.generalinfomask.search(self.inhtml)
358 if self.generalinfos:
361 if re.search("<title>(?:IMDb.{0,9}Search|IMDb Titelsuche)</title>", self.inhtml):
362 searchresultmask = re.compile("<tr> <td.*?img src.*?>.*?<a href=\".*?/title/(tt\d{7,7})/\".*?>(.*?)</td>", re.DOTALL)
363 searchresults = searchresultmask.finditer(self.inhtml)
366 for x in searchresults:
367 self.resultlist.append((self.htmltags.sub('',x.group(2)), x.group(1)))
368 self["menu"].l.setList(self.resultlist)
369 if len(self.resultlist) > 1:
373 self["detailslabel"].setText(_("No IMDb match."))
374 self["statusbar"].setText(_("No IMDb match."))
376 splitpos = self.eventName.find('(')
377 if splitpos > 0 and self.eventName.endswith(')'):
378 self.eventName = self.eventName[splitpos+1:-1]
379 self["statusbar"].setText(_("Re-Query IMDb: %s...") % (self.eventName))
380 event_quoted = urllib.quote(self.eventName.decode('utf8').encode('latin-1','ignore'))
381 localfile = "/tmp/imdbquery.html"
382 fetchurl = "http://" + self.IMDBlanguage + "imdb.com/find?q=" + event_quoted + "&s=tt&site=aka"
383 print "[IMDB] Downloading Query " + fetchurl + " to " + localfile
384 downloadPage(fetchurl,localfile).addCallback(self.IMDBquery).addErrback(self.fetchFailed)
386 self["detailslabel"].setText(_("IMDb query failed!"))
388 def IMDBquery2(self,string):
389 self["statusbar"].setText(_("IMDb Re-Download completed"))
390 self.html2utf8(open("/tmp/imdbquery2.html", "r").read())
391 self.generalinfos = self.generalinfomask.search(self.inhtml)
397 Detailstext = _("No details found.")
398 if self.generalinfos:
399 self["key_yellow"].setText(_("Details"))
400 self["statusbar"].setText(_("IMDb Details parsed"))
401 Titeltext = self.generalinfos.group("title")
402 if len(Titeltext) > 57:
403 Titeltext = Titeltext[0:54] + "..."
404 self["titellabel"].setText(Titeltext)
408 genreblockmask = re.compile('<h5>Genre:</h5>(.*?)(?:mehr|more|</div>)', re.DOTALL)
409 genreblock = genreblockmask.findall(self.inhtml)
410 genremask = re.compile('(?:\">|\s)(.*?)(?:</a|\s)')
412 genres = genremask.finditer(genreblock[0])
414 Detailstext += "Genre: "
416 Detailstext += x.group(1) + " "
418 for category in ("director", "creator", "writer", "premiere", "seasons", "country"):
419 if self.generalinfos.group('g_'+category):
420 Detailstext += "\n" + self.generalinfos.group('g_'+category) + ": " + self.generalinfos.group(category)
422 if self.generalinfos.group("alternativ"):
423 Detailstext += "\n" + self.generalinfos.group("g_alternativ") + ": " + self.htmltags.sub('',(self.generalinfos.group("alternativ").replace('\n','').replace("<br>",'\n').replace(" ",' ')))
425 ratingmask = re.compile('<h5>(?P<g_rating>Nutzer-Bewertung|User Rating):</h5>.*?<b>(?P<rating>.*?)/10</b>', re.DOTALL)
426 rating = ratingmask.search(self.inhtml)
427 Ratingtext = _("no user rating yet")
429 Ratingtext = rating.group("g_rating") + ": " + rating.group("rating") + " / 10"
430 self.ratingstars = int(10*round(float(rating.group("rating").replace(',','.')),1))
432 self["stars"].setValue(self.ratingstars)
433 self["starsbg"].show()
434 self["ratinglabel"].setText(Ratingtext)
435 castmask = re.compile('<td class="nm">.*?>(.*?)</a>.*?<td class="char">(?:<a.*?>)?(.*?)(?:</a>)?</td>', re.DOTALL)
436 castresult = castmask.finditer(self.inhtml)
440 Casttext += "\n" + self.htmltags.sub('', x.group(1))
442 Casttext += _(" as ") + self.htmltags.sub('', x.group(2).replace('/ ...',''))
443 if Casttext is not "":
444 Casttext = _("Cast: ") + Casttext
446 Casttext = _("No cast list found in the database.")
447 self["castlabel"].setText(Casttext)
448 postermask = re.compile('<div class="photo">.*?<img .*? src=\"(http.*?)\" .*?>', re.DOTALL)
449 posterurl = postermask.search(self.inhtml)
450 if posterurl and posterurl.group(1).find("jpg") > 0:
451 posterurl = posterurl.group(1)
452 self["statusbar"].setText(_("Downloading Movie Poster: %s...") % (posterurl))
453 localfile = "/tmp/poster.jpg"
454 print "[IMDB] downloading poster " + posterurl + " to " + localfile
455 downloadPage(posterurl,localfile).addCallback(self.IMDBPoster).addErrback(self.fetchFailed)
457 self.IMDBPoster("kein Poster")
458 extrainfos = self.extrainfomask.search(self.inhtml)
461 Extratext = "Extra Info\n"
463 for category in ("tagline","outline","synopsis","keywords","awards","runtime","language","color","aspect","sound","cert","locations","company","trivia","goofs","quotes","connections"):
464 if extrainfos.group('g_'+category):
465 Extratext += extrainfos.group('g_'+category) + ": " + self.htmltags.sub('',extrainfos.group(category).replace("\n",'').replace("<br>",'\n')) + "\n"
466 if extrainfos.group("g_comments"):
467 Extratext += extrainfos.group("g_comments") + " [" + self.htmltags.sub('',extrainfos.group("commenter")) + "]: " + self.htmltags.sub('',extrainfos.group("comment").replace("\n",' ')) + "\n"
469 self["extralabel"].setText(Extratext)
470 self["extralabel"].hide()
471 self["key_blue"].setText(_("Extra Info"))
473 self["detailslabel"].setText(Detailstext)
475 def IMDBPoster(self,string):
476 self["statusbar"].setText(_("IMDb Details parsed"))
478 filename = "/tmp/poster.jpg"
480 filename = resolveFilename(SCOPE_PLUGINS, "Extensions/IMDb/no_poster.png")
481 sc = AVSwitch().getFramebufferScale()
482 self.picload.setPara((self["poster"].instance.size().width(), self["poster"].instance.size().height(), sc[0], sc[1], False, 1, "#00000000"))
483 self.picload.startDecode(filename)
485 def paintPosterPixmapCB(self, picInfo=None):
486 ptr = self.picload.getData()
488 self["poster"].instance.setPixmap(ptr.__deref__())
489 self["poster"].show()
491 def createSummary(self):
494 class IMDbLCDScreen(Screen):
496 <screen position="0,0" size="132,64" title="IMDB Plugin">
497 <widget name="headline" position="4,0" size="128,22" font="Regular;20"/>
498 <widget source="session.Event_Now" render="Label" position="6,26" size="120,34" font="Regular;14" >
499 <convert type="EventName">Name</convert>
503 def __init__(self, session, parent):
504 Screen.__init__(self, session)
505 self["headline"] = Label(_("IMDb Plugin"))
507 def eventinfo(session, servicelist, **kwargs):
508 ref = session.nav.getCurrentlyPlayingServiceReference()
509 session.open(IMDBEPGSelection, ref)
511 def main(session, eventName="", **kwargs):
512 session.open(IMDB, eventName)
514 def Plugins(**kwargs):
516 return [PluginDescriptor(name="IMDb Details",
517 description=_("Query details from the Internet Movie Database"),
519 where = PluginDescriptor.WHERE_PLUGINMENU,
521 PluginDescriptor(name="IMDb Details",
522 description=_("Query details from the Internet Movie Database"),
523 where = PluginDescriptor.WHERE_EVENTINFO,
526 except AttributeError:
527 wherelist = [PluginDescriptor.WHERE_EXTENSIONSMENU, PluginDescriptor.WHERE_PLUGINMENU]
528 return PluginDescriptor(name="IMDb Details",
529 description=_("Query details from the Internet Movie Database"),