1 # -*- coding: UTF-8 -*-
2 # ZDF Mediathek by AliAbdul
\r
3 from Components.ActionMap import HelpableActionMap
4 from Components.AVSwitch import AVSwitch
\r
5 from Components.Label import Label
\r
6 from Components.MenuList import MenuList
\r
7 from Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest
8 from Components.Pixmap import Pixmap
9 from Components.PluginComponent import plugins
\r
10 from enigma import eListboxPythonMultiContent, ePicLoad, eServiceReference, eTimer, getDesktop, gFont
11 from os import listdir, popen
12 from Plugins.Plugin import PluginDescriptor
13 from Screens.ChoiceBox import ChoiceBox
14 from Screens.HelpMenu import HelpableScreen
15 from Screens.InfoBar import MoviePlayer
16 from Screens.MessageBox import MessageBox
\r
17 from Screens.Screen import Screen
\r
18 from Screens.VirtualKeyBoard import VirtualKeyBoard
19 from time import sleep
20 from Tools.BoundFunction import boundFunction
21 from Tools.Directories import resolveFilename, SCOPE_PLUGINS
22 from Tools.HardwareInfo import HardwareInfo
23 from Tools.LoadPixmap import LoadPixmap
\r
24 from twisted.web.client import downloadPage, getPage
\r
25 import htmlentitydefs, re, urllib2
27 ###################################################
29 MAIN_PAGE = "http://www.zdf.de"
31 PNG_PATH = resolveFilename(SCOPE_PLUGINS)+"/Extensions/ZDFMediathek/"
36 TYPE_MOVIELIST_CATEGORY = 3
42 deviceName = HardwareInfo().get_device_name()
46 if not deviceName.startswith("dm7025"):
48 #FIXMEE add better check ! ? !
49 for line in popen("opkg info gst-plugin-rtsp").readlines():
50 if line.find("Version: ") != -1:
51 if line[9:] >= "0.10.23-r7.1":
57 from LT.LTStreamPlayer import streamplayer
60 from Plugins.Extensions.LTMediaCenter.LTStreamPlayer import streamplayer
65 from Plugins.Extensions.VlcPlayer.VlcServerConfig import vlcServerConfig
67 vlcServerConfig = None
69 ###################################################
72 pat = re.compile(r'\\u(....)')
\r
74 return unichr(fromHex(mo.group(1)))
\r
75 return pat.sub(sub, unicode(line))
\r
78 pat = re.compile(r'&#(\d+);')
\r
80 return unichr(int(mo.group(1)))
\r
81 return decode3(pat.sub(sub, unicode(line)))
\r
84 dic = htmlentitydefs.name2codepoint
\r
85 for key in dic.keys():
\r
86 entity = "&" + key + ";"
\r
87 line = line.replace(entity, unichr(dic[key]))
\r
93 ###################################################
95 class ChangedMoviePlayer(MoviePlayer):
96 def __init__(self, session, service):
97 MoviePlayer.__init__(self, session, service)
98 self.skinName = "MoviePlayer"
100 def leavePlayer(self):
101 self.session.openWithCallback(self.leavePlayerConfirmed, MessageBox, "Abspielen beenden?")
103 def leavePlayerConfirmed(self, answer):
107 def doEofInternal(self, playing):
110 def getPluginList(self):
112 for p in plugins.getPlugins(where=PluginDescriptor.WHERE_EXTENSIONSMENU):
113 if p.name != "ZDF Mediathek":
114 list.append(((boundFunction(self.getPluginName, p.name), boundFunction(self.runPlugin, p), lambda: True), None))
117 def showMovies(self):
120 ###################################################
122 def getMovieDetails(div):
125 reonecat = re.compile(r'<p class="grey"><a href="(.+?)">(.+?)</a></p>', re.DOTALL)
126 content = reonecat.findall(div)
128 broadcast = decode2(decode(content[0][1])).encode("UTF-8")
129 list.append(content[0][0])
130 if broadcast.startswith("<"):
132 list.append(broadcast)
134 reonecat = re.compile(r'<p><b><a href=".+?">(.+?)</a></b></p>', re.DOTALL)
135 titles = reonecat.findall(div)
139 idx = title.index('<br/>')
141 if '<br />' in title:
142 idx = title.index('<br />')
144 title = decode2(decode(title)).encode("UTF-8")
146 # Lese Thumbnail-URL...
147 reonecat = re.compile(r'<img src="(.+?)"', re.DOTALL)
148 thumbnails = reonecat.findall(div)
150 list.append(thumbnails[0])
152 if ('VIDEO, ' in div):
153 reonecat = re.compile(r'>VIDEO, (.+?)</a></p>', re.DOTALL)
154 lengths = reonecat.findall(div)
156 list.append(lengths[0])
165 def getCategoryDetails(div):
168 reonecat = re.compile(r'<p class="grey"><a href="(.+?)">(.+?)</a></p>', re.DOTALL)
169 content = reonecat.findall(div)
171 broadcast = decode2(decode(content[0][1])).encode("UTF-8")
172 list.append(content[0][0])
173 if broadcast.startswith("<"):
175 list.append(broadcast)
177 reonecat = re.compile(r'<p><b><a href=".+?">(.+?)</a></b></p>', re.DOTALL)
178 titles = reonecat.findall(div)
182 idx = title.index('<br/>')
184 if '<br />' in title:
185 idx = title.index('<br />')
187 title = decode2(decode(title)).encode("UTF-8")
189 # Lese Thumbnail-URL...
190 reonecat = re.compile(r'<img src="(.+?)"', re.DOTALL)
191 thumbnails = reonecat.findall(div)
193 list.append(thumbnails[0])
194 # Lese Beitragsanzahl...
195 reonecat = re.compile(r'">(.+?)BEITRÄGE ZUR SENDUNG</a></p>', re.DOTALL)
196 counts = reonecat.findall(div)
201 idx = count.index('">')
202 count = count[idx+2:]
204 while '"/>' in count:
205 idx = count.index('"/>')
206 count = count[idx+3:]
207 list.append("%sBeitraege"%count)
209 reonecat = re.compile(r'">(.+?)BEITRÄGE ZUM THEMA</a></p>', re.DOTALL)
210 counts = reonecat.findall(div)
215 idx = count.index('">')
216 count = count[idx+2:]
218 while '"/>' in count:
219 idx = count.index('"/>')
220 count = count[idx+3:]
221 list.append("%sBeitraege"%count)
223 reonecat = re.compile(r'">(.+?)BEITRÄGE ZUR RUBRIK</a></p>', re.DOTALL)
224 counts = reonecat.findall(div)
229 idx = count.index('">')
230 count = count[idx+2:]
232 while '"/>' in count:
233 idx = count.index('"/>')
234 count = count[idx+3:]
235 list.append("%sBeitraege"%count)
242 ###################################################
244 def getMovieUrl(url):
246 f = urllib2.urlopen(url)
\r
251 if ('rtsp' in txt) and ('.mp4' in txt):
252 idx = txt.index('rtsp')
253 idx2 = txt.index('.mp4')
254 return txt[idx:idx2+4]
258 def getTitleLinks(html):
260 start = '<div id="breadcrumbContainer">'
263 idx = html.index(start)
265 idx = html.index(end)
267 reonecat = re.compile(r'<a href="(.+?)">(.+?)</a>', re.DOTALL)
268 for url, name in reonecat.findall(html):
269 name = decode2(decode(name)).encode("UTF-8")
270 links.append([url, name])
273 def getLeftMenu(html):
275 reonecat = re.compile(r'<div id="navigationContainer">(.+?)</div>', re.DOTALL)
276 leftMenu = reonecat.findall(html)
278 reonecat = re.compile(r'<li><a href="(.+?)"(.+?)</a>', re.DOTALL)
279 for url, name in reonecat.findall(leftMenu[0]):
280 if name.startswith(' class="active">'):
286 if (name != "Hilfe") and (not 'Podcasts' in name): # TODO: Podcasts brauchen noch etwas Arbeit... derzeit deaktiviert
287 list.append([url, name, active])
290 def getRightMenu(html):
293 if '" class="play" target="_blank">Abspielen</a></li>' in html:
294 reonecat = re.compile(r'<li>(.+?)<a href="(.+?)" class="play" target="_blank">Abspielen</a></li>', re.DOTALL)
295 for speed, movie in reonecat.findall(html):
296 list.append([speed, movie])
298 return [TYPE_MOVIE, list]
300 if '<!-- Start:Podcasts -->' in html:
301 reonecat = re.compile(r'<!-- Start:Podcasts -->(.+?)<!-- Ende:Podcasts -->', re.DOTALL)
302 tmp = reonecat.findall(html)
304 reonecat = re.compile(r'<p><b><a href="(.+?)".+?">(.+?)</a></b></p>', re.DOTALL)
305 podcasts = reonecat.findall(tmp[0])
306 for podcast in podcasts:
307 list.append([podcast[0], podcast[1]])
309 return [TYPE_PODCAST, list]
310 # Suche Videos und Rubriken...
311 start = '<div class="beitragListe">'
312 if '<div class="beitragFooterSuche">' in html:
313 end = '<div class="beitragFooterSuche">'
315 end = '<div class="beitragFooter">'
316 if (start in html) and (end in html):
317 while (start in html) and (end in html):
318 idx = html.index(start)
320 reonecat = re.compile(r'%s(.+?)%s'%(start, end), re.DOTALL)
321 blocks = reonecat.findall(html)
323 reonecat = re.compile(r'<div class="image">(.+?)</li>', re.DOTALL)
324 divs = reonecat.findall(blocks[0])
327 if ('VIDEO, ' in div) or ('>LIVE<' in div):
328 details = getMovieDetails(div)
329 elif 'BEITRÄGE ZU' in div:
330 details = getCategoryDetails(div)
332 list.append([details[0], details[1], details[2], details[3], details[4]])
334 reonecat = re.compile(r'<a href="(.+?)" class="weitereBeitraege"', re.DOTALL)
335 more = reonecat.findall(html)
339 while 'href="' in more:
340 idx = more.index('href="')
342 list.append([more, "", "", "", "Weitere Beitraege laden."])
344 return [TYPE_MOVIELIST_CATEGORY, list]
346 return [TYPE_NOTHING, list]
348 ###################################################
350 class LeftMenuList(MenuList):
352 MenuList.__init__(self, [], False, eListboxPythonMultiContent)
353 self.l.setItemHeight(20)
354 self.l.setFont(0, gFont("Regular", 18))
359 def setActive(self, active):
361 self.SetList(self.menu, True)
363 def entry(self, text, active, selected):
365 if text.startswith("- Heute"):
367 elif text.startswith("- Gestern"):
369 elif text.startswith("- Morgen"):
372 res.append(MultiContentEntryPixmapAlphaTest(pos=(0, 0), size=(20, 20), png=LoadPixmap(cached=True, path=PNG_PATH+"active.png")))
374 res.append(MultiContentEntryText(pos=(25, 0), size=(175, 20), font=0, text=text, color=0xf47d19))
376 res.append(MultiContentEntryText(pos=(25, 0), size=(175, 20), font=0, text=text, color=0xffffff))
379 def SetList(self, l, moveCursor=False):
391 if (idx == self.current) and self.active:
395 list.append(self.entry(x[1], x[2], selected))
399 def getCurrentUrl(self):
401 return self.menu[self.current][0]
405 def select(self, index):
407 if (index > -1) and (index < len(self.menu)):
409 self.SetList(self.menu)
415 self.select(len(self.menu)-1)
419 self.select(self.current-1)
423 self.select(self.current+1)
425 ###################################################
427 class RightMenuList(MenuList):
429 MenuList.__init__(self, [], False, eListboxPythonMultiContent)
430 self.l.setFont(0, gFont("Regular", 18))
431 self.l.setFont(1, gFont("Regular", 16))
432 self.listCompleted = []
437 def buildEntries(self):
438 if self.type == TYPE_PODCAST:
443 idx = title.index('<br/>')
445 title = decode2(decode(title)).encode("UTF-8")
446 res = [(x[0], title)]
447 res.append(MultiContentEntryText(pos=(0, 0), size=(430, 20), font=0, text=title))
452 elif self.type == TYPE_MOVIELIST_CATEGORY:
453 if self.idx == len(self.list):
454 self.setList(self.listCompleted)
458 self.downloadThumbnail()
460 def downloadThumbnail(self):
461 thumbUrl = self.list[self.idx][3]
462 if not thumbUrl.startswith("http://"):
463 thumbUrl = "%s%s"%(MAIN_PAGE, thumbUrl)
465 req = urllib2.Request(thumbUrl)
466 url_handle = urllib2.urlopen(req)
\r
467 headers = url_handle.info()
\r
468 contentType = headers.getheader("content-type")
472 if 'image/jpeg' in contentType:
473 self.thumb = "/tmp/zdf.jpg"
474 elif 'image/gif' in contentType:
475 self.thumb = "/tmp/zdf.gif"
476 elif 'image/png' in contentType:
477 self.thumb = "/tmp/zdf.png"
479 print "[ZDF Mediathek] Unknown thumbnail content-type:", contentType
484 downloadPage(thumbUrl, self.thumb).addCallback(self.downloadThumbnailCallback).addErrback(self.downloadThumbnailError)
486 self.buildEntry(None)
488 def downloadThumbnailError(self, err):
489 print "[ZDF Mediathek] Error:", err
490 self.buildEntry(None)
492 def downloadThumbnailCallback(self, txt=""):
493 sc = AVSwitch().getFramebufferScale()
494 self.picload = ePicLoad()
495 self.picload.PictureData.get().append(self.buildEntry)
496 self.picload.setPara((94, 60, sc[0], sc[1], False, 1, "#00000000"))
497 self.picload.startDecode(self.thumb)
499 def buildEntry(self, picInfo=None):
500 x = self.list[self.idx]
503 ptr = self.picload.getData()
505 res.append(MultiContentEntryPixmapAlphaTest(pos=(0, 0), size=(94, 60), png=ptr))
506 res.append(MultiContentEntryText(pos=(100, 0), size=(430, 20), font=0, text=x[2]))
507 res.append(MultiContentEntryText(pos=(100, 20), size=(430, 20), font=0, text=x[4]))
508 res.append(MultiContentEntryText(pos=(100, 40), size=(430, 20), font=1, text=x[1]))
509 self.listCompleted.append(res)
513 def SetList(self, l):
516 if self.type == TYPE_PODCAST:
517 self.l.setItemHeight(20)
519 elif self.type == TYPE_MOVIELIST_CATEGORY:
520 self.l.setItemHeight(60)
521 del self.listCompleted
522 self.listCompleted = []
530 ###################################################
532 class ZDFMediathekCache(Screen):
534 <screen position="center,center" size="76,76" flags="wfNoBorder" backgroundColor="#ffffff" >
535 <eLabel position="2,2" zPosition="1" size="72,72" font="Regular;18" backgroundColor="#252525" />
536 <widget name="spinner" position="14,14" zPosition="2" size="48,48" alphatest="on" />
539 def __init__(self, session):
540 self.session = session
541 Screen.__init__(self, session)
543 self["spinner"] = Pixmap()
546 self.timer = eTimer()
547 self.timer.callback.append(self.showNextSpinner)
551 self.timer.start(200, False)
557 def showNextSpinner(self):
561 png = LoadPixmap(cached=True, path=PNG_PATH + str(self.curr) + ".png")
562 self["spinner"].instance.setPixmap(png)
564 ###################################################
566 class ZDFMediathek(Screen, HelpableScreen):
567 def __init__(self, session):
568 self.session = session
570 desktop = getDesktop(0)
571 size = desktop.size()
575 self.skin = """<screen position="0,0" size="720,576" flags="wfNoBorder" backgroundColor="#252525" >"""
577 self.skin = """<screen position="center,center" size="720,576" title="ZDF Mediathek" backgroundColor="#252525" >"""
578 self.skin += """<ePixmap position="40,30" size="133,40" pixmap="%s" />
579 <widget name="navigationTitle" position="250,40" size="430,25" font="Regular;18" backgroundColor="#252525" foregroundColor="#f47d19" noWrap="1" />
580 <widget name="leftList" position="40,70" size="200,440" transparent="1" selectionDisabled="1" />
581 <widget name="rightList" position="250,70" size="430,480" backgroundColor="#3d3c3c" backgroundColorSelected="#565656" selectionDisabled="1" scrollbarMode="showOnDemand" />
582 <ePixmap pixmap="skin_default/buttons/key_menu.png" position="40,520" size="35,25" transparent="1" alphatest="on" />
583 <widget name="serverName" position="80,520" size="160,20" font="Regular;18" backgroundColor="#252525" foregroundColor="#f47d19" />
584 <widget name="fakeList" position="0,0" size="0,0" />
585 </screen>""" % (PNG_PATH+"logo.png")
587 Screen.__init__(self, session)
589 self["navigationTitle"] = Label(" ")
590 self["leftList"] = LeftMenuList()
591 self["rightList"] = RightMenuList()
592 self["fakeList"] = MenuList([])
593 self["serverName"] = Label("Server")
595 HelpableScreen.__init__(self)
597 self["actions"] = HelpableActionMap(self, "ZDFMediathekActions",
599 "back": (self.exit, "Beenden"),
600 "ok": (self.ok, "Selektieren"),
601 "left": (self.left, "Seite hoch"),
602 "right": (self.right, "Seite runter"),
603 "up": (self.up, "Hoch"),
604 "down": (self.down, "Runter"),
605 "previousList": (self.toggleList, "Liste umschalten"),
606 "nextList": (self.toggleList, "Liste umschalten"),
607 "menu": (self.selectServer, "Selektiere Server"),
608 "search": (self.search, "Suche"),
609 "previousPage": (self.previousPage, "Vorherige Seite")
612 self.cacheDialog = self.session.instantiateDialog(ZDFMediathekCache)
613 self["rightList"].callback = self.deactivateCacheDialog
615 self.currentList = LIST_RIGHT
616 self.linkPreviousPage = ""
618 self.transcodeServer = None
619 self.cacheTimer = eTimer()
620 self.cacheTimer.callback.append(self.chechCachedFile)
622 self.onLayoutFinish.append(self.getPage)
624 def getPage(self, page=None):
627 page = "/ZDFmediathek/hauptnavigation/startseite?flash=off"
628 url = "%s%s"%(MAIN_PAGE, page)
629 getPage(url).addCallback(self.gotPage).addErrback(self.error)
631 def error(self, err=""):
632 print "[ZDF Mediathek] Error:", err
634 self.deactivateCacheDialog()
636 def gotPage(self, html=""):
637 rightMenu = getRightMenu(html)
638 if rightMenu[0] == TYPE_MOVIE:
640 for x in rightMenu[1]:
641 list.append(("%s %s"%(x[0], x[1].split(".")[-1]), x[1]))
643 self.session.openWithCallback(self.play, ChoiceBox, title="Selektiere...", list=list)
647 self.cacheDialog.start()
648 self.currentList = LIST_NONE
649 links = getTitleLinks(html)
652 txt = txt + x[1] + " / "
656 self.linkPreviousPage = links[-2][0]
658 self.linkPreviousPage = ""
660 self.linkPreviousPage = ""
661 self["navigationTitle"].setText(txt)
662 self["leftList"].SetList(getLeftMenu(html), True)
663 self["rightList"].SetList(rightMenu)
664 self["leftList"].selectionEnabled(0)
665 self["rightList"].selectionEnabled(1)
666 self["fakeList"].selectionEnabled(0)
667 self["leftList"].setActive(False)
669 def previousPage(self):
670 self.getPage(self.linkPreviousPage)
673 self.session.openWithCallback(self.searchCallback, VirtualKeyBoard, title="Suche nach:")
675 def searchCallback(self, callback):
676 if callback and (callback != ""):
677 self.getPage("/ZDFmediathek/suche?sucheText=%s&offset=0&flash=off"%(callback.replace(" ", "+")))
679 def play(self, callback):
681 if callback is not None:
683 if url.endswith(".mov"):
684 url = getMovieUrl(url)
686 if PLAY_MP4 and url.endswith(".mp4"):
687 ref = eServiceReference(4097, 0, url)
688 self.session.open(ChangedMoviePlayer, ref)
689 else: # Die Hardware kann das Format nicht direkt abspielen, mit Stream2Dream oder vlc Server probieren...
690 if self.transcodeServer is not None:
691 if self.transcodeServer == "LT Stream2Dream":
692 r = streamplayer.play(url)
695 self.currentList = LIST_NONE
696 self.cacheDialog.start()
697 self.cacheTimer.start(1000, False)
699 self.transcodeServer.play(self.session, url, self["rightList"].getCurrent()[0][1])
701 self.session.open(MessageBox, "Es wurde kein Server ausgewählt!", MessageBox.TYPE_ERROR)
703 self.session.open(MessageBox, "Fehler beim Ermitteln der Film-URL!", MessageBox.TYPE_ERROR)
705 def chechCachedFile(self):
707 f = open ("/tmp/mpstream/progress.txt")
710 list = content.split("-")
711 cacheMB = int(list[0])
712 if cacheMB > 10: # Starte nach 10 MB Bufferung
713 self.cacheTimer.stop()
714 self.playCachedFile()
718 def deactivateCacheDialog(self):
719 self.cacheDialog.stop()
720 self.currentList = LIST_RIGHT
723 def playCachedFile(self):
724 self.deactivateCacheDialog()
725 ref = eServiceReference(1, 0, "/tmp/mpstream/MPStream.ts")
726 self.session.openWithCallback(self.stopStream2Dream, ChangedMoviePlayer, ref)
728 def stopStream2Dream(self, callback=None):
732 def toggleList(self):
734 if self.currentList == LIST_LEFT:
735 self.currentList = LIST_RIGHT
736 self["leftList"].setActive(False)
737 self["fakeList"].selectionEnabled(0)
738 self["rightList"].selectionEnabled(1)
739 elif self.currentList == LIST_RIGHT:
740 self.currentList = LIST_LEFT
741 self["leftList"].setActive(True)
742 self["rightList"].selectionEnabled(0)
743 self["fakeList"].selectionEnabled(1)
745 def selectServer(self):
748 list.append(("LT Stream2Dream", "LT Stream2Dream"))
750 serverList = vlcServerConfig.getServerlist()
752 list.append((x.getName(), x))
754 self.session.openWithCallback(self.serverChosen, ChoiceBox, title="Waehle den Server...", list=list)
756 def serverChosen(self, callback):
759 if server == "LT Stream2Dream":
760 if not streamplayer.connected:
761 self.transcodeServer = "LT Stream2Dream"
762 self["serverName"].setText("LT Stream2Dream")
763 self.connectToStream2Dream()
766 if streamplayer.connected:
767 streamplayer.logout()
768 self.transcodeServer = server
769 self["serverName"].setText(server.getName())
771 def connectToStream2Dream(self):
774 list = listdir("/tmp/mp")
778 self.session.open(MessageBox, "Die Verbindung zu LT Stream2Dream konnte nicht hergestellt werden!", MessageBox.TYPE_ERROR)
779 streamplayer.logout()
780 self.transcodeServer = None
781 self["serverName"].setText("Server")
785 if self.currentList == LIST_LEFT:
787 elif self.currentList == LIST_RIGHT:
789 if streamplayer.connected:
790 streamplayer.logout()
791 self.session.deleteDialog(self.cacheDialog)
795 if streamplayer.connected:
798 self.deactivateCacheDialog()
802 if self.currentList == LIST_LEFT:
803 self.getPage(self["leftList"].getCurrentUrl())
804 elif self.currentList == LIST_RIGHT:
805 curr = self["rightList"].getCurrent()
807 self.getPage(curr[0][0])
809 if streamplayer.connected:
810 if streamplayer.caching or streamclient.streaming:
811 self.playCachedFile()
815 if self.currentList == LIST_LEFT:
816 self["leftList"].first()
817 elif self.currentList == LIST_RIGHT:
818 self["rightList"].pageUp()
822 if self.currentList == LIST_LEFT:
823 self["leftList"].last()
824 elif self.currentList == LIST_RIGHT:
825 self["rightList"].pageDown()
829 if self.currentList == LIST_LEFT:
830 self["leftList"].previous()
831 elif self.currentList == LIST_RIGHT:
832 self["rightList"].up()
836 if self.currentList == LIST_LEFT:
837 self["leftList"].next()
838 elif self.currentList == LIST_RIGHT:
839 self["rightList"].down()
841 ###################################################
843 def start(session, **kwargs):
844 session.open(ZDFMediathek)
846 def Plugins(**kwargs):
847 return PluginDescriptor(name="ZDF Mediathek", description="Streame von der ZDF Mediathek", where=[PluginDescriptor.WHERE_EXTENSIONSMENU, PluginDescriptor.WHERE_PLUGINMENU], fnc=start)