1 from os import path as os_path, remove as os_remove, listdir as os_listdir
2 from time import strftime
3 from enigma import eTimer, iPlayableService, eServiceCenter, iServiceInformation
4 from Screens.Screen import Screen
5 from Screens.MessageBox import MessageBox
6 from Screens.InputBox import InputBox
7 from Components.ActionMap import NumberActionMap, HelpableActionMap
8 from Components.Label import Label
9 from Components.Pixmap import Pixmap
10 from Components.Label import Label
11 from Components.FileList import FileList
12 from Components.MediaPlayer import PlayList
13 from Tools.Directories import resolveFilename, SCOPE_CONFIG, SCOPE_PLAYLIST, SCOPE_SKIN_IMAGE
14 from Components.ServicePosition import ServicePositionGauge
15 from Components.ServiceEventTracker import ServiceEventTracker
16 from Components.Playlist import PlaylistIOInternal, PlaylistIOM3U, PlaylistIOPLS
17 from Screens.InfoBarGenerics import InfoBarSeek, InfoBarAudioSelection, InfoBarCueSheetSupport, InfoBarNotifications
18 from ServiceReference import ServiceReference
19 from Screens.ChoiceBox import ChoiceBox
20 from Screens.HelpMenu import HelpableScreen
23 class MyPlayList(PlayList):
25 PlayList.__init__(self)
27 def PlayListShuffle(self):
28 random.shuffle(self.list)
29 self.l.setList(self.list)
31 self.oldCurrPlaying = -1
33 class MediaPixmap(Pixmap):
34 def applySkin(self, desktop):
35 self.default_pixmap = None
36 for (attrib, value) in self.skinAttributes:
37 if attrib == "pixmap":
38 self.default_pixmap = value
40 if self.default_pixmap is None:
41 self.default_pixmap = resolveFilename(SCOPE_SKIN_IMAGE, "no_coverArt.png")
42 return Pixmap.applySkin(self, desktop)
44 class MediaPlayer(Screen, InfoBarSeek, InfoBarAudioSelection, InfoBarCueSheetSupport, InfoBarNotifications, HelpableScreen):
46 ENABLE_RESUME_SUPPORT = True
48 def __init__(self, session, args = None):
49 Screen.__init__(self, session)
50 InfoBarAudioSelection.__init__(self)
51 InfoBarCueSheetSupport.__init__(self, actionmap = "MediaPlayerCueSheetActions")
52 InfoBarNotifications.__init__(self)
53 HelpableScreen.__init__(self)
55 self.oldService = self.session.nav.getCurrentlyPlayingServiceReference()
56 self.session.nav.stopService()
58 self.playlistparsers = {}
59 self.addPlaylistParser(PlaylistIOM3U, "m3u")
60 self.addPlaylistParser(PlaylistIOPLS, "pls")
61 self.addPlaylistParser(PlaylistIOInternal, "e2pls")
63 # 'None' is magic to start at the list of mountpoints
64 self.filelist = FileList(None, matchingPattern = "(?i)^.*\.(mp3|ogg|ts|wav|wave|m3u|pls|e2pls|mpg|vob)", useServiceRef = True)
65 self["filelist"] = self.filelist
67 self.playlist = MyPlayList()
68 #self.playlist = PlayList()
69 self.is_closing = False
71 self["playlist"] = self.playlist
73 self["PositionGauge"] = ServicePositionGauge(self.session.nav)
75 self["currenttext"] = Label("")
77 self["artisttext"] = Label(_("Artist:"))
78 self["artist"] = Label("")
79 self["titletext"] = Label(_("Title:"))
80 self["title"] = Label("")
81 self["albumtext"] = Label(_("Album:"))
82 self["album"] = Label("")
83 self["yeartext"] = Label(_("Year:"))
84 self["year"] = Label("")
85 self["genretext"] = Label(_("Genre:"))
86 self["genre"] = Label("")
87 self["coverArt"] = MediaPixmap()
89 self.seek_target = None
91 class MoviePlayerActionMap(NumberActionMap):
92 def __init__(self, player, contexts = [ ], actions = { }, prio=0):
93 NumberActionMap.__init__(self, contexts, actions, prio)
96 def action(self, contexts, action):
98 return NumberActionMap.action(self, contexts, action)
101 self["OkCancelActions"] = HelpableActionMap(self, "OkCancelActions",
103 "ok": (self.ok, _("add file to playlist")),
104 "cancel": (self.exit, _("exit mediaplayer")),
107 self["MediaPlayerActions"] = HelpableActionMap(self, "MediaPlayerActions",
109 "play": (self.playEntry, _("play entry")),
110 "pause": (self.pauseEntry, _("pause")),
111 "stop": (self.stopEntry, _("stop entry")),
112 "previous": (self.previousEntry, _("play previous playlist entry")),
113 "next": (self.nextEntry, _("play next playlist entry")),
114 "menu": (self.showMenu, _("menu")),
115 "skipListbegin": (self.skip_listbegin, _("jump to listbegin")),
116 "skipListend": (self.skip_listend, _("jump to listend")),
117 "prevBouquet": (self.switchToPlayList, _("switch to playlist")),
118 "nextBouquet": (self.switchToFileList, _("switch to filelist")),
119 "delete": (self.deletePlaylistEntry, _("delete playlist entry")),
120 "shift_stop": (self.clear_playlist, _("clear playlist")),
121 "shift_record": (self.playlist.PlayListShuffle, _("shuffle playlist")),
124 self["InfobarEPGActions"] = HelpableActionMap(self, "InfobarEPGActions",
126 "showEventInfo": (self.showEventInformation, _("show event details")),
129 self["actions"] = MoviePlayerActionMap(self, ["DirectionActions"],
131 "right": self.rightDown,
132 "rightRepeated": self.doNothing,
133 "rightUp": self.rightUp,
134 "left": self.leftDown,
135 "leftRepeated": self.doNothing,
136 "leftUp": self.leftUp,
139 "upRepeated": self.up,
140 "upUp": self.doNothing,
142 "downRepeated": self.down,
143 "downUp": self.doNothing,
146 InfoBarSeek.__init__(self, actionmap = "MediaPlayerSeekActions")
148 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
150 #iPlayableService.evStart: self.__serviceStarted,
151 #iPlayableService.evSeekableStatusChanged: InfoBarSeek.__seekableStatusChanged,
153 iPlayableService.evEOF: self.__evEOF,
156 self.onClose.append(self.delMPTimer)
157 self.onClose.append(self.__onClose)
159 self.righttimer = False
160 self.rightKeyTimer = eTimer()
161 self.rightKeyTimer.timeout.get().append(self.rightTimerFire)
163 self.lefttimer = False
164 self.leftKeyTimer = eTimer()
165 self.leftKeyTimer.timeout.get().append(self.leftTimerFire)
167 self.currList = "filelist"
169 self.coverArtFileName = ""
171 self.playlistIOInternal = PlaylistIOInternal()
172 list = self.playlistIOInternal.open(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
175 self.playlist.addFile(x.ref)
176 self.playlist.updateList()
181 def createSummary(self):
182 return MediaPlayerLCDScreen
185 self.session.openWithCallback(self.exitCB, MessageBox, _("Do you really want to exit?"), timeout=5)
187 def exitCB(self, answer):
189 self.playlistIOInternal.clear()
190 for x in self.playlist.list:
191 self.playlistIOInternal.addService(ServiceReference(x[0]))
192 self.playlistIOInternal.save(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
195 def checkSkipShowHideLock(self):
196 self.updatedSeekState()
202 self.session.nav.playService(self.oldService)
204 def delMPTimer(self):
205 del self.rightKeyTimer
206 del self.leftKeyTimer
208 def readTitleInformation(self):
209 currPlay = self.session.nav.getCurrentService()
210 if currPlay is not None:
211 stitle = currPlay.info().getInfoString(iServiceInformation.sTitle)
213 stitle = currPlay.info().getName().split('/')[-1]
215 self.updateMusicInformation( artist = currPlay.info().getInfoString(iServiceInformation.sArtist),
217 album = currPlay.info().getInfoString(iServiceInformation.sAlbum),
218 genre = currPlay.info().getInfoString(iServiceInformation.sGenre),
221 self.updateMusicInformation()
223 def updateMusicInformation(self, artist = "", title = "", album = "", year = "", genre = "", clear = False):
224 self.updateSingleMusicInformation("artist", artist, clear)
225 self.updateSingleMusicInformation("title", title, clear)
226 self.updateSingleMusicInformation("album", album, clear)
227 self.updateSingleMusicInformation("year", year, clear)
228 self.updateSingleMusicInformation("genre", genre, clear)
230 def updateSingleMusicInformation(self, name, info, clear):
231 if info != "" or clear:
232 if self[name].getText() != info:
233 self[name].setText(info)
235 def updateCoverArtPixmap(self, path):
236 while not path.endswith("/"):
238 pngname = path + "folder.png"
240 if not os_path.exists(pngname):
241 pngname = self["coverArt"].default_pixmap
242 if self.coverArtFileName != pngname:
243 self.coverArtFileName = pngname
244 self["coverArt"].instance.setPixmapFromFile(self.coverArtFileName)
247 self.lefttimer = True
248 self.leftKeyTimer.start(1000)
251 self.righttimer = True
252 self.rightKeyTimer.start(1000)
256 self.leftKeyTimer.stop()
257 self.lefttimer = False
258 self[self.currList].pageUp()
259 self.updateCurrentInfo()
263 self.rightKeyTimer.stop()
264 self.righttimer = False
265 self[self.currList].pageDown()
266 self.updateCurrentInfo()
268 def leftTimerFire(self):
269 self.leftKeyTimer.stop()
270 self.lefttimer = False
271 self.switchToFileList()
273 def rightTimerFire(self):
274 self.rightKeyTimer.stop()
275 self.righttimer = False
276 self.switchToPlayList()
278 def switchToFileList(self):
279 self.currList = "filelist"
280 self.filelist.selectionEnabled(1)
281 self.playlist.selectionEnabled(0)
282 self.updateCurrentInfo()
284 def switchToPlayList(self):
285 if len(self.playlist) != 0:
286 self.currList = "playlist"
287 self.filelist.selectionEnabled(0)
288 self.playlist.selectionEnabled(1)
289 self.updateCurrentInfo()
292 self[self.currList].up()
293 self.updateCurrentInfo()
296 self[self.currList].down()
297 self.updateCurrentInfo()
299 def showAfterSeek(self):
302 def showAfterCuesheetOperation(self):
305 def hideAfterResume(self):
308 # FIXME: maybe this code can be optimized
309 def updateCurrentInfo(self):
311 if self.currList == "filelist":
312 idx = self.filelist.getSelectionIndex()
313 r = self.filelist.list[idx]
320 self.summaries.setText(text,1)
323 if idx < len(self.filelist.list):
324 r = self.filelist.list[idx]
328 self.summaries.setText(text,3)
330 self.summaries.setText(" ",3)
333 if idx < len(self.filelist.list):
334 r = self.filelist.list[idx]
338 self.summaries.setText(text,4)
340 self.summaries.setText(" ",4)
343 if not self.filelist.canDescent():
344 r = self.filelist.getServiceRef()
348 self["currenttext"].setText(os_path.basename(text))
350 if self.currList == "playlist":
351 t = self.playlist.getSelection()
354 #display current selected entry on LCD
356 text = text.split('/')[-1]
357 self.summaries.setText(text,1)
358 self["currenttext"].setText(text)
359 idx = self.playlist.getSelectionIndex()
361 if idx < len(self.playlist):
362 currref = self.playlist.getServiceRefList()[idx]
363 text = currref.getPath()
364 text = text.split('/')[-1]
365 self.summaries.setText(text,3)
367 self.summaries.setText(" ",3)
370 if idx < len(self.playlist):
371 currref = self.playlist.getServiceRefList()[idx]
372 text = currref.getPath()
373 text = text.split('/')[-1]
374 self.summaries.setText(text,4)
376 self.summaries.setText(" ",4)
379 if self.currList == "filelist":
380 if self.filelist.canDescent():
381 self.filelist.descent()
382 self.updateCurrentInfo()
386 if self.currList == "playlist":
387 selection = self["playlist"].getSelection()
388 self.changeEntry(self.playlist.getSelectionIndex())
392 if self.currList == "filelist":
393 if self.filelist.canDescent():
394 menu.append((_("add directory to playlist"), "copydir"))
396 menu.append((_("add files to playlist"), "copyfiles"))
397 menu.append((_("switch to playlist"), "playlist"))
399 menu.append((_("switch to filelist"), "filelist"))
401 menu.append((_("shuffle playlist"), "shuffle"))
403 menu.append((_("delete"), "delete"))
404 menu.append((_("clear playlist"), "clear"))
405 menu.append((_("hide player"), "hide"));
406 menu.append((_("save playlist"), "saveplaylist"));
407 menu.append((_("load playlist"), "loadplaylist"));
408 menu.append((_("delete saved playlist"), "deleteplaylist"));
409 self.session.openWithCallback(self.menuCallback, ChoiceBox, title="", list=menu)
411 def menuCallback(self, choice):
415 if choice[1] == "copydir":
416 self.copyDirectory(self.filelist.getSelection()[0])
417 elif choice[1] == "copyfiles":
419 self.playlist.clear()
420 self.copyDirectory(os_path.dirname(self.filelist.getSelection()[0].getPath()) + "/", recursive = False)
421 self.playServiceRefEntry(self.filelist.getServiceRef())
422 elif choice[1] == "playlist":
423 self.switchToPlayList()
424 elif choice[1] == "filelist":
425 self.switchToFileList()
426 elif choice[1] == "delete":
427 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
430 elif choice[1] == "clear":
432 self.playlist.clear()
433 self.switchToFileList()
434 elif choice[1] == "hide":
436 elif choice[1] == "saveplaylist":
438 elif choice[1] == "loadplaylist":
440 elif choice[1] == "deleteplaylist":
441 self.delete_saved_playlist()
442 elif choice[1] == "shuffle":
443 self.playlist.PlayListShuffle()
446 def showEventInformation(self):
447 from Screens.EventView import EventViewSimple
448 from ServiceReference import ServiceReference
449 evt = self[self.currList].getCurrentEvent()
451 self.session.open(EventViewSimple, evt, ServiceReference(self.getCurrent()))
453 # also works on filelist (?)
454 def getCurrent(self):
455 return self["playlist"].getCurrent()
457 def deletePlaylistEntry(self):
458 if self.currList == "playlist":
459 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
463 def skip_listbegin(self):
464 if self.currList == "filelist":
465 self.filelist.moveToIndex(0)
467 self.playlist.moveToIndex(0)
468 self.updateCurrentInfo()
470 def skip_listend(self):
471 if self.currList == "filelist":
472 idx = len(self.filelist.list)
473 self.filelist.moveToIndex(idx - 1)
475 self.playlist.moveToIndex(len(self.playlist)-1)
476 self.updateCurrentInfo()
478 def save_playlist(self):
479 self.session.openWithCallback(self.save_playlist2,InputBox, title=_("Please enter filename (empty = use current date)"),windowTitle = _("Save Playlist"))
481 def save_playlist2(self, name):
485 name = strftime("%y%m%d_%H%M%S")
487 self.playlistIOInternal.clear()
488 for x in self.playlist.list:
489 self.playlistIOInternal.addService(ServiceReference(x[0]))
490 self.playlistIOInternal.save(resolveFilename(SCOPE_PLAYLIST) + name)
492 def load_playlist(self):
494 playlistdir = resolveFilename(SCOPE_PLAYLIST)
496 for i in os_listdir(playlistdir):
497 listpath.append((i,playlistdir + i))
499 print "Error while scanning subdirs ",e
500 self.session.openWithCallback(self.PlaylistSelected, ChoiceBox, title=_("Please select a playlist..."), list = listpath)
502 def PlaylistSelected(self,path):
504 self.clear_playlist()
505 self.playlistIOInternal = PlaylistIOInternal()
506 list = self.playlistIOInternal.open(path[1])
509 self.playlist.addFile(x.ref)
510 self.playlist.updateList()
512 def delete_saved_playlist(self):
514 playlistdir = resolveFilename(SCOPE_PLAYLIST)
516 for i in os_listdir(playlistdir):
517 listpath.append((i,playlistdir + i))
519 print "Error while scanning subdirs ",e
520 self.session.openWithCallback(self.DeletePlaylistSelected, ChoiceBox, title=_("Please select a playlist to delete..."), list = listpath)
522 def DeletePlaylistSelected(self,path):
524 self.delname = path[1]
525 self.session.openWithCallback(self.deleteConfirmed, MessageBox, _("Do you really want to delete %s?") % (path[1]))
527 def deleteConfirmed(self, confirmed):
529 os_remove(self.delname)
531 def clear_playlist(self):
533 self.playlist.clear()
534 self.switchToFileList()
536 def copyDirectory(self, directory, recursive = True):
537 print "copyDirectory", directory
538 filelist = FileList(directory, useServiceRef = True, isTop = True)
540 for x in filelist.getFileList():
541 if x[0][1] == True: #isDir
543 self.copyDirectory(x[0][0])
545 self.playlist.addFile(x[0][0])
546 self.playlist.updateList()
549 if self.filelist.getServiceRef().type == 4098: # playlist
550 ServiceRef = self.filelist.getServiceRef()
551 extension = ServiceRef.getPath()[ServiceRef.getPath().rfind('.') + 1:]
552 print "extension:", extension
553 if self.playlistparsers.has_key(extension):
554 playlist = self.playlistparsers[extension]()
555 list = playlist.open(ServiceRef.getPath())
557 self.playlist.addFile(x.ref)
559 self.playlist.addFile(self.filelist.getServiceRef())
560 self.playlist.updateList()
561 if len(self.playlist) == 1:
564 def addPlaylistParser(self, parser, extension):
565 self.playlistparsers[extension] = parser
568 next = self.playlist.getCurrentIndex() + 1
569 if next < len(self.playlist):
570 self.changeEntry(next)
572 def previousEntry(self):
573 next = self.playlist.getCurrentIndex() - 1
575 self.changeEntry(next)
577 def deleteEntry(self):
578 self.playlist.deleteFile(self.playlist.getSelectionIndex())
579 self.playlist.updateList()
580 if len(self.playlist) == 0:
581 self.switchToFileList()
583 def changeEntry(self, index):
584 self.playlist.setCurrentPlaying(index)
587 def playServiceRefEntry(self, serviceref):
588 serviceRefList = self.playlist.getServiceRefList()
589 for count in range(len(serviceRefList)):
590 if serviceRefList[count] == serviceref:
591 self.changeEntry(count)
595 if len(self.playlist.getServiceRefList()):
596 needsInfoUpdate = False
597 currref = self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()]
598 if self.session.nav.getCurrentlyPlayingServiceReference() is None or currref != self.session.nav.getCurrentlyPlayingServiceReference():
599 self.session.nav.playService(self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()])
600 info = eServiceCenter.getInstance().info(currref)
601 description = info and info.getInfoString(currref, iServiceInformation.sDescription) or ""
602 self["title"].setText(description)
603 # display just playing musik on LCD
604 idx = self.playlist.getCurrentIndex()
605 currref = self.playlist.getServiceRefList()[idx]
606 text = currref.getPath()
607 text = text.split('/')[-1]
609 ext = text[-3:].lower()
611 # FIXME: the information if the service contains video (and we should hide our window) should com from the service instead
612 if ext not in ["mp3", "wav", "ogg"]:
615 needsInfoUpdate = True
616 self.summaries.setText(text,1)
618 # get the next two entries
620 if idx < len(self.playlist):
621 currref = self.playlist.getServiceRefList()[idx]
622 text = currref.getPath()
623 text = text.split('/')[-1]
624 self.summaries.setText(text,3)
626 self.summaries.setText(" ",3)
629 if idx < len(self.playlist):
630 currref = self.playlist.getServiceRefList()[idx]
631 text = currref.getPath()
632 text = text.split('/')[-1]
633 self.summaries.setText(text,4)
635 self.summaries.setText(" ",4)
637 idx = self.playlist.getCurrentIndex()
638 currref = self.playlist.getServiceRefList()[idx]
639 text = currref.getPath()
640 ext = text[-3:].lower()
641 if ext not in ["mp3", "wav", "ogg"]:
644 needsInfoUpdate = True
646 self.unPauseService()
647 if needsInfoUpdate == True:
648 self.updateCoverArtPixmap(currref.getPath())
650 pngname = self["coverArt"].default_pixmap
651 self.coverArtFileName = pngname
652 self["coverArt"].instance.setPixmapFromFile(self.coverArtFileName)
653 self.readTitleInformation()
655 def updatedSeekState(self):
656 if self.seekstate == self.SEEK_STATE_PAUSE:
657 self.playlist.pauseFile()
658 elif self.seekstate == self.SEEK_STATE_PLAY:
659 self.playlist.playFile()
660 elif self.seekstate in ( self.SEEK_STATE_FF_2X,
661 self.SEEK_STATE_FF_4X,
662 self.SEEK_STATE_FF_8X,
663 self.SEEK_STATE_FF_16X,
664 self.SEEK_STATE_FF_32X,
665 self.SEEK_STATE_FF_48X,
666 self.SEEK_STATE_FF_64X,
667 self.SEEK_STATE_FF_128X):
668 self.playlist.forwardFile()
669 elif self.seekstate in ( self.SEEK_STATE_BACK_8X,
670 self.SEEK_STATE_BACK_16X,
671 self.SEEK_STATE_BACK_32X,
672 self.SEEK_STATE_BACK_48X,
673 self.SEEK_STATE_BACK_64X,
674 self.SEEK_STATE_BACK_128X):
675 self.playlist.rewindFile()
677 def pauseEntry(self):
682 self.playlist.stopFile()
683 self.session.nav.playService(None)
684 self.updateMusicInformation(clear=True)
687 def unPauseService(self):
688 self.setSeekState(self.SEEK_STATE_PLAY)
691 class MediaPlayerLCDScreen(Screen):
693 <screen position="0,0" size="132,64" title="LCD Text">
694 <widget name="text1" position="4,0" size="132,35" font="Regular;16"/>
695 <widget name="text3" position="4,36" size="132,14" font="Regular;10"/>
696 <widget name="text4" position="4,49" size="132,14" font="Regular;10"/>
699 def __init__(self, session, parent):
700 Screen.__init__(self, session)
701 self["text1"] = Label("Mediaplayer")
702 self["text3"] = Label("")
703 self["text4"] = Label("")
705 def setText(self, text, line):
706 print "lcd set text:", text, line
708 if text[-4:] == ".mp3":
711 text = text + textleer*10
713 self["text1"].setText(text)
715 self["text3"].setText(text)
717 self["text4"].setText(text)
719 def main(session, **kwargs):
720 session.open(MediaPlayer)
722 def menu(menuid, **kwargs):
723 if menuid == "mainmenu":
724 return [(_("Media player"), main, "media_player", 45)]
727 def filescan_open(list, session, **kwargs):
728 from enigma import eServiceReference
730 mp = session.open(MediaPlayer)
732 mp.switchToPlayList()
734 ref = eServiceReference(4097, 0, file.path)
735 mp.playlist.addFile(ref)
737 # TODO: rather play first than last file?
738 mp.playServiceRefEntry(ref)
739 mp.playlist.updateList()
741 def filescan(**kwargs):
742 from Components.Scanner import Scanner, ScanPath
744 Scanner(mimetypes = ["video/mpeg"],
747 ScanPath(path = "", with_subdirs = False),
750 description = "View Movies...",
751 openfnc = filescan_open,
753 Scanner(mimetypes = ["audio/mpeg", "audio/x-wav", "application/ogg"],
756 ScanPath(path = "", with_subdirs = False),
759 description = "Play Music...",
760 openfnc = filescan_open,
764 from Plugins.Plugin import PluginDescriptor
765 def Plugins(**kwargs):
767 PluginDescriptor(name = "MediaPlayer", description = "Play back media files", where = PluginDescriptor.WHERE_MENU, fnc = menu),
768 PluginDescriptor(name = "MediaPlayer", where = PluginDescriptor.WHERE_FILESCAN, fnc = filescan)