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.infoTimer = eTimer()
168 self.infoTimer.timeout.get().append(self.infoTimerFire)
169 self.infoTimer.start(500)
171 self.currList = "filelist"
173 self.coverArtFileName = ""
175 self.playlistIOInternal = PlaylistIOInternal()
176 list = self.playlistIOInternal.open(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
179 self.playlist.addFile(x.ref)
180 self.playlist.updateList()
185 def createSummary(self):
186 return MediaPlayerLCDScreen
189 self.session.openWithCallback(self.exitCB, MessageBox, _("Do you really want to exit?"), timeout=5)
191 def exitCB(self, answer):
193 self.playlistIOInternal.clear()
194 for x in self.playlist.list:
195 self.playlistIOInternal.addService(ServiceReference(x[0]))
196 self.playlistIOInternal.save(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
199 def checkSkipShowHideLock(self):
200 self.updatedSeekState()
206 self.session.nav.playService(self.oldService)
208 def delMPTimer(self):
209 del self.rightKeyTimer
210 del self.leftKeyTimer
213 def infoTimerFire(self):
214 currPlay = self.session.nav.getCurrentService()
215 if currPlay is not None:
216 stitle = currPlay.info().getInfoString(iServiceInformation.sTitle)
218 stitle = currPlay.info().getName().split('/')[-1]
220 self.updateMusicInformation( artist = currPlay.info().getInfoString(iServiceInformation.sArtist),
222 album = currPlay.info().getInfoString(iServiceInformation.sAlbum),
223 genre = currPlay.info().getInfoString(iServiceInformation.sGenre),
225 self.updateCoverArtPixmap( currPlay.info().getName() )
227 self.updateMusicInformation()
228 self.updateCoverArtPixmap( "" )
230 def updateMusicInformation(self, artist = "", title = "", album = "", year = "", genre = "", clear = False):
231 self.updateSingleMusicInformation("artist", artist, clear)
232 self.updateSingleMusicInformation("title", title, clear)
233 self.updateSingleMusicInformation("album", album, clear)
234 self.updateSingleMusicInformation("year", year, clear)
235 self.updateSingleMusicInformation("genre", genre, clear)
237 def updateSingleMusicInformation(self, name, info, clear):
238 if info != "" or clear:
239 if self[name].getText() != info:
240 self[name].setText(info)
242 def updateCoverArtPixmap(self, currentServiceName):
243 filename = currentServiceName
244 # The "getName" usually adds something like "MP3 File:" infront of filename
245 # Get rid of this...by finding the first "/"
246 # FIXME: this should be fixed in the servicemp3.cpp handler
247 filename = filename[filename.find("/"):]
248 path = os_path.dirname(filename)
249 pngname = path + "/" + "folder.png"
250 if not os_path.exists(pngname):
251 pngname = self["coverArt"].default_pixmap
252 if self.coverArtFileName != pngname:
253 self.coverArtFileName = pngname
254 self["coverArt"].instance.setPixmapFromFile(self.coverArtFileName)
257 self.lefttimer = True
258 self.leftKeyTimer.start(1000)
261 self.righttimer = True
262 self.rightKeyTimer.start(1000)
266 self.leftKeyTimer.stop()
267 self.lefttimer = False
268 self[self.currList].pageUp()
269 self.updateCurrentInfo()
273 self.rightKeyTimer.stop()
274 self.righttimer = False
275 self[self.currList].pageDown()
276 self.updateCurrentInfo()
278 def leftTimerFire(self):
279 self.leftKeyTimer.stop()
280 self.lefttimer = False
281 self.switchToFileList()
283 def rightTimerFire(self):
284 self.rightKeyTimer.stop()
285 self.righttimer = False
286 self.switchToPlayList()
288 def switchToFileList(self):
289 self.currList = "filelist"
290 self.filelist.selectionEnabled(1)
291 self.playlist.selectionEnabled(0)
292 self.updateCurrentInfo()
294 def switchToPlayList(self):
295 if len(self.playlist) != 0:
296 self.currList = "playlist"
297 self.filelist.selectionEnabled(0)
298 self.playlist.selectionEnabled(1)
299 self.updateCurrentInfo()
302 self[self.currList].up()
303 self.updateCurrentInfo()
306 self[self.currList].down()
307 self.updateCurrentInfo()
309 def showAfterSeek(self):
312 def showAfterCuesheetOperation(self):
315 def hideAfterResume(self):
318 # FIXME: maybe this code can be optimized
319 def updateCurrentInfo(self):
321 if self.currList == "filelist":
322 idx = self.filelist.getSelectionIndex()
323 r = self.filelist.list[idx]
330 self.summaries.setText(text,1)
333 if idx < len(self.filelist.list):
334 r = self.filelist.list[idx]
338 self.summaries.setText(text,3)
340 self.summaries.setText(" ",3)
343 if idx < len(self.filelist.list):
344 r = self.filelist.list[idx]
348 self.summaries.setText(text,4)
350 self.summaries.setText(" ",4)
353 if not self.filelist.canDescent():
354 r = self.filelist.getServiceRef()
358 self["currenttext"].setText(os_path.basename(text))
360 if self.currList == "playlist":
361 t = self.playlist.getSelection()
364 #display current selected entry on LCD
366 text = text.split('/')[-1]
367 self.summaries.setText(text,1)
368 self["currenttext"].setText(text)
369 idx = self.playlist.getSelectionIndex()
371 if idx < len(self.playlist):
372 currref = self.playlist.getServiceRefList()[idx]
373 text = currref.getPath()
374 text = text.split('/')[-1]
375 self.summaries.setText(text,3)
377 self.summaries.setText(" ",3)
380 if idx < len(self.playlist):
381 currref = self.playlist.getServiceRefList()[idx]
382 text = currref.getPath()
383 text = text.split('/')[-1]
384 self.summaries.setText(text,4)
386 self.summaries.setText(" ",4)
389 if self.currList == "filelist":
390 if self.filelist.canDescent():
391 self.filelist.descent()
392 self.updateCurrentInfo()
396 if self.currList == "playlist":
397 selection = self["playlist"].getSelection()
398 self.changeEntry(self.playlist.getSelectionIndex())
402 if self.currList == "filelist":
403 if self.filelist.canDescent():
404 menu.append((_("add directory to playlist"), "copydir"))
406 menu.append((_("add files to playlist"), "copyfiles"))
407 menu.append((_("switch to playlist"), "playlist"))
409 menu.append((_("switch to filelist"), "filelist"))
411 menu.append((_("shuffle playlist"), "shuffle"))
413 menu.append((_("delete"), "delete"))
414 menu.append((_("clear playlist"), "clear"))
415 menu.append((_("hide player"), "hide"));
416 menu.append((_("save playlist"), "saveplaylist"));
417 menu.append((_("load playlist"), "loadplaylist"));
418 menu.append((_("delete saved playlist"), "deleteplaylist"));
419 self.session.openWithCallback(self.menuCallback, ChoiceBox, title="", list=menu)
421 def menuCallback(self, choice):
425 if choice[1] == "copydir":
426 self.copyDirectory(self.filelist.getSelection()[0])
427 elif choice[1] == "copyfiles":
429 self.playlist.clear()
430 self.copyDirectory(os_path.dirname(self.filelist.getSelection()[0].getPath()) + "/", recursive = False)
431 self.playServiceRefEntry(self.filelist.getServiceRef())
432 elif choice[1] == "playlist":
433 self.switchToPlayList()
434 elif choice[1] == "filelist":
435 self.switchToFileList()
436 elif choice[1] == "delete":
437 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
440 elif choice[1] == "clear":
442 self.playlist.clear()
443 self.switchToFileList()
444 elif choice[1] == "hide":
446 elif choice[1] == "saveplaylist":
448 elif choice[1] == "loadplaylist":
450 elif choice[1] == "deleteplaylist":
451 self.delete_saved_playlist()
452 elif choice[1] == "shuffle":
453 self.playlist.PlayListShuffle()
456 def showEventInformation(self):
457 from Screens.EventView import EventViewSimple
458 from ServiceReference import ServiceReference
459 evt = self[self.currList].getCurrentEvent()
461 self.session.open(EventViewSimple, evt, ServiceReference(self.getCurrent()))
463 # also works on filelist (?)
464 def getCurrent(self):
465 return self["playlist"].getCurrent()
467 def deletePlaylistEntry(self):
468 if self.currList == "playlist":
469 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
473 def skip_listbegin(self):
474 if self.currList == "filelist":
475 self.filelist.moveToIndex(0)
477 self.playlist.moveToIndex(0)
478 self.updateCurrentInfo()
480 def skip_listend(self):
481 if self.currList == "filelist":
482 idx = len(self.filelist.list)
483 self.filelist.moveToIndex(idx - 1)
485 self.playlist.moveToIndex(len(self.playlist)-1)
486 self.updateCurrentInfo()
488 def save_playlist(self):
489 self.session.openWithCallback(self.save_playlist2,InputBox, title=_("Please enter filename (empty = use current date)"),windowTitle = _("Save Playlist"))
491 def save_playlist2(self, name):
495 name = strftime("%y%m%d_%H%M%S")
497 self.playlistIOInternal.clear()
498 for x in self.playlist.list:
499 self.playlistIOInternal.addService(ServiceReference(x[0]))
500 self.playlistIOInternal.save(resolveFilename(SCOPE_PLAYLIST) + name)
502 def load_playlist(self):
504 playlistdir = resolveFilename(SCOPE_PLAYLIST)
506 for i in os_listdir(playlistdir):
507 listpath.append((i,playlistdir + i))
509 print "Error while scanning subdirs ",e
510 self.session.openWithCallback(self.PlaylistSelected, ChoiceBox, title=_("Please select a playlist..."), list = listpath)
512 def PlaylistSelected(self,path):
514 self.clear_playlist()
515 self.playlistIOInternal = PlaylistIOInternal()
516 list = self.playlistIOInternal.open(path[1])
519 self.playlist.addFile(x.ref)
520 self.playlist.updateList()
522 def delete_saved_playlist(self):
524 playlistdir = resolveFilename(SCOPE_PLAYLIST)
526 for i in os_listdir(playlistdir):
527 listpath.append((i,playlistdir + i))
529 print "Error while scanning subdirs ",e
530 self.session.openWithCallback(self.DeletePlaylistSelected, ChoiceBox, title=_("Please select a playlist to delete..."), list = listpath)
532 def DeletePlaylistSelected(self,path):
534 self.delname = path[1]
535 self.session.openWithCallback(self.deleteConfirmed, MessageBox, _("Do you really want to delete %s?") % (path[1]))
537 def deleteConfirmed(self, confirmed):
539 os_remove(self.delname)
541 def clear_playlist(self):
543 self.playlist.clear()
544 self.switchToFileList()
546 def copyDirectory(self, directory, recursive = True):
547 print "copyDirectory", directory
548 filelist = FileList(directory, useServiceRef = True, isTop = True)
550 for x in filelist.getFileList():
551 if x[0][1] == True: #isDir
553 self.copyDirectory(x[0][0])
555 self.playlist.addFile(x[0][0])
556 self.playlist.updateList()
559 if self.filelist.getServiceRef().type == 4098: # playlist
560 ServiceRef = self.filelist.getServiceRef()
561 extension = ServiceRef.getPath()[ServiceRef.getPath().rfind('.') + 1:]
562 print "extension:", extension
563 if self.playlistparsers.has_key(extension):
564 playlist = self.playlistparsers[extension]()
565 list = playlist.open(ServiceRef.getPath())
567 self.playlist.addFile(x.ref)
569 self.playlist.addFile(self.filelist.getServiceRef())
570 self.playlist.updateList()
571 if len(self.playlist) == 1:
574 def addPlaylistParser(self, parser, extension):
575 self.playlistparsers[extension] = parser
578 next = self.playlist.getCurrentIndex() + 1
579 if next < len(self.playlist):
580 self.changeEntry(next)
582 def previousEntry(self):
583 next = self.playlist.getCurrentIndex() - 1
585 self.changeEntry(next)
587 def deleteEntry(self):
588 self.playlist.deleteFile(self.playlist.getSelectionIndex())
589 self.playlist.updateList()
590 if len(self.playlist) == 0:
591 self.switchToFileList()
593 def changeEntry(self, index):
594 self.playlist.setCurrentPlaying(index)
597 def playServiceRefEntry(self, serviceref):
598 serviceRefList = self.playlist.getServiceRefList()
599 for count in range(len(serviceRefList)):
600 if serviceRefList[count] == serviceref:
601 self.changeEntry(count)
605 if len(self.playlist.getServiceRefList()):
606 currref = self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()]
607 if self.session.nav.getCurrentlyPlayingServiceReference() is None or currref != self.session.nav.getCurrentlyPlayingServiceReference():
608 self.session.nav.playService(self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()])
609 info = eServiceCenter.getInstance().info(currref)
610 description = info and info.getInfoString(currref, iServiceInformation.sDescription) or ""
611 self["title"].setText(description)
612 # display just playing musik on LCD
613 idx = self.playlist.getCurrentIndex()
614 currref = self.playlist.getServiceRefList()[idx]
615 text = currref.getPath()
616 text = text.split('/')[-1]
618 ext = text[-3:].lower()
620 # FIXME: the information if the service contains video (and we should hide our window) should com from the service instead
621 if ext not in ["mp3", "wav", "ogg"]:
623 self.summaries.setText(text,1)
625 # get the next two entries
627 if idx < len(self.playlist):
628 currref = self.playlist.getServiceRefList()[idx]
629 text = currref.getPath()
630 text = text.split('/')[-1]
631 self.summaries.setText(text,3)
633 self.summaries.setText(" ",3)
636 if idx < len(self.playlist):
637 currref = self.playlist.getServiceRefList()[idx]
638 text = currref.getPath()
639 text = text.split('/')[-1]
640 self.summaries.setText(text,4)
642 self.summaries.setText(" ",4)
644 idx = self.playlist.getCurrentIndex()
645 currref = self.playlist.getServiceRefList()[idx]
646 text = currref.getPath()
647 ext = text[-3:].lower()
648 if ext not in ["mp3", "wav", "ogg"]:
650 self.unPauseService()
652 def updatedSeekState(self):
653 if self.seekstate == self.SEEK_STATE_PAUSE:
654 self.playlist.pauseFile()
655 elif self.seekstate == self.SEEK_STATE_PLAY:
656 self.playlist.playFile()
657 elif self.seekstate in ( self.SEEK_STATE_FF_2X,
658 self.SEEK_STATE_FF_4X,
659 self.SEEK_STATE_FF_8X,
660 self.SEEK_STATE_FF_16X,
661 self.SEEK_STATE_FF_32X,
662 self.SEEK_STATE_FF_48X,
663 self.SEEK_STATE_FF_64X,
664 self.SEEK_STATE_FF_128X):
665 self.playlist.forwardFile()
666 elif self.seekstate in ( self.SEEK_STATE_BACK_8X,
667 self.SEEK_STATE_BACK_16X,
668 self.SEEK_STATE_BACK_32X,
669 self.SEEK_STATE_BACK_48X,
670 self.SEEK_STATE_BACK_64X,
671 self.SEEK_STATE_BACK_128X):
672 self.playlist.rewindFile()
674 def pauseEntry(self):
679 self.playlist.stopFile()
680 self.session.nav.playService(None)
681 self.updateMusicInformation(clear=True)
684 def unPauseService(self):
685 self.setSeekState(self.SEEK_STATE_PLAY)
688 class MediaPlayerLCDScreen(Screen):
690 <screen position="0,0" size="132,64" title="LCD Text">
691 <widget name="text1" position="4,0" size="132,35" font="Regular;16"/>
692 <widget name="text3" position="4,36" size="132,14" font="Regular;10"/>
693 <widget name="text4" position="4,49" size="132,14" font="Regular;10"/>
696 def __init__(self, session, parent):
697 Screen.__init__(self, session)
698 self["text1"] = Label("Mediaplayer")
699 self["text3"] = Label("")
700 self["text4"] = Label("")
702 def setText(self, text, line):
703 print "lcd set text:", text, line
705 if text[-4:] == ".mp3":
708 text = text + textleer*10
710 self["text1"].setText(text)
712 self["text3"].setText(text)
714 self["text4"].setText(text)
716 def main(session, **kwargs):
717 session.open(MediaPlayer)
719 def menu(menuid, **kwargs):
720 if menuid == "mainmenu":
721 return [(_("Media player"), main, "media_player", 45)]
724 def filescan_open(list, session, **kwargs):
725 from enigma import eServiceReference
727 mp = session.open(MediaPlayer)
729 mp.switchToPlayList()
731 ref = eServiceReference(4097, 0, file.path)
732 mp.playlist.addFile(ref)
734 # TODO: rather play first than last file?
735 mp.playServiceRefEntry(ref)
736 mp.playlist.updateList()
738 def filescan(**kwargs):
739 from Components.Scanner import Scanner, ScanPath
741 Scanner(mimetypes = ["video/mpeg"],
744 ScanPath(path = "", with_subdirs = False),
747 description = "View Movies...",
748 openfnc = filescan_open,
750 Scanner(mimetypes = ["audio/mpeg", "audio/x-wav", "application/ogg"],
753 ScanPath(path = "", with_subdirs = False),
756 description = "Play Music...",
757 openfnc = filescan_open,
761 from Plugins.Plugin import PluginDescriptor
762 def Plugins(**kwargs):
764 PluginDescriptor(name = "MediaPlayer", description = "Play back media files", where = PluginDescriptor.WHERE_MENU, fnc = menu),
765 PluginDescriptor(name = "MediaPlayer", where = PluginDescriptor.WHERE_FILESCAN, fnc = filescan)