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 MediaPlayer(Screen, InfoBarSeek, InfoBarAudioSelection, InfoBarCueSheetSupport, InfoBarNotifications, HelpableScreen):
35 ENABLE_RESUME_SUPPORT = True
37 def __init__(self, session, args = None):
38 Screen.__init__(self, session)
39 InfoBarAudioSelection.__init__(self)
40 InfoBarCueSheetSupport.__init__(self, actionmap = "MediaPlayerCueSheetActions")
41 InfoBarNotifications.__init__(self)
42 HelpableScreen.__init__(self)
44 self.oldService = self.session.nav.getCurrentlyPlayingServiceReference()
45 self.session.nav.stopService()
47 self.playlistparsers = {}
48 self.addPlaylistParser(PlaylistIOM3U, "m3u")
49 self.addPlaylistParser(PlaylistIOPLS, "pls")
50 self.addPlaylistParser(PlaylistIOInternal, "e2pls")
52 # 'None' is magic to start at the list of mountpoints
53 self.filelist = FileList(None, matchingPattern = "(?i)^.*\.(mp3|ogg|ts|wav|wave|m3u|pls|e2pls|mpg|vob)", useServiceRef = True)
54 self["filelist"] = self.filelist
56 self.playlist = MyPlayList()
57 #self.playlist = PlayList()
58 self.is_closing = False
60 self["playlist"] = self.playlist
62 self["PositionGauge"] = ServicePositionGauge(self.session.nav)
64 self["currenttext"] = Label("")
66 self["artisttext"] = Label(_("Artist:"))
67 self["artist"] = Label("")
68 self["titletext"] = Label(_("Title:"))
69 self["title"] = Label("")
70 self["albumtext"] = Label(_("Album:"))
71 self["album"] = Label("")
72 self["yeartext"] = Label(_("Year:"))
73 self["year"] = Label("")
74 self["genretext"] = Label(_("Genre:"))
75 self["genre"] = Label("")
76 self["coverArt"] = Pixmap()
78 self.seek_target = None
80 class MoviePlayerActionMap(NumberActionMap):
81 def __init__(self, player, contexts = [ ], actions = { }, prio=0):
82 NumberActionMap.__init__(self, contexts, actions, prio)
85 def action(self, contexts, action):
87 return NumberActionMap.action(self, contexts, action)
90 self["OkCancelActions"] = HelpableActionMap(self, "OkCancelActions",
92 "ok": (self.ok, _("add file to playlist")),
93 "cancel": (self.exit, _("exit mediaplayer")),
96 self["MediaPlayerActions"] = HelpableActionMap(self, "MediaPlayerActions",
98 "play": (self.playEntry, _("play entry")),
99 "pause": (self.pauseEntry, _("pause")),
100 "stop": (self.stopEntry, _("stop entry")),
101 "previous": (self.previousEntry, _("play previous playlist entry")),
102 "next": (self.nextEntry, _("play next playlist entry")),
103 "menu": (self.showMenu, _("menu")),
104 "skipListbegin": (self.skip_listbegin, _("jump to listbegin")),
105 "skipListend": (self.skip_listend, _("jump to listend")),
106 "prevBouquet": (self.switchToPlayList, _("switch to playlist")),
107 "nextBouquet": (self.switchToFileList, _("switch to filelist")),
108 "delete": (self.deletePlaylistEntry, _("delete playlist entry")),
109 "shift_stop": (self.clear_playlist, _("clear playlist")),
110 "shift_record": (self.playlist.PlayListShuffle, _("shuffle playlist")),
113 self["InfobarEPGActions"] = HelpableActionMap(self, "InfobarEPGActions",
115 "showEventInfo": (self.showEventInformation, _("show event details")),
118 self["actions"] = MoviePlayerActionMap(self, ["DirectionActions"],
120 "right": self.rightDown,
121 "rightRepeated": self.doNothing,
122 "rightUp": self.rightUp,
123 "left": self.leftDown,
124 "leftRepeated": self.doNothing,
125 "leftUp": self.leftUp,
128 "upRepeated": self.up,
129 "upUp": self.doNothing,
131 "downRepeated": self.down,
132 "downUp": self.doNothing,
135 InfoBarSeek.__init__(self, actionmap = "MediaPlayerSeekActions")
137 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
139 #iPlayableService.evStart: self.__serviceStarted,
140 #iPlayableService.evSeekableStatusChanged: InfoBarSeek.__seekableStatusChanged,
142 iPlayableService.evEOF: self.__evEOF,
145 self.onClose.append(self.delMPTimer)
146 self.onClose.append(self.__onClose)
148 self.righttimer = False
149 self.rightKeyTimer = eTimer()
150 self.rightKeyTimer.timeout.get().append(self.rightTimerFire)
152 self.lefttimer = False
153 self.leftKeyTimer = eTimer()
154 self.leftKeyTimer.timeout.get().append(self.leftTimerFire)
156 self.infoTimer = eTimer()
157 self.infoTimer.timeout.get().append(self.infoTimerFire)
158 self.infoTimer.start(500)
160 self.currList = "filelist"
162 self.coverArtFileName = ""
164 self.playlistIOInternal = PlaylistIOInternal()
165 list = self.playlistIOInternal.open(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
168 self.playlist.addFile(x.ref)
169 self.playlist.updateList()
174 def createSummary(self):
175 return MediaPlayerLCDScreen
178 self.session.openWithCallback(self.exitCB, MessageBox, _("Do you really want to exit?"), timeout=5)
180 def exitCB(self, answer):
182 self.playlistIOInternal.clear()
183 for x in self.playlist.list:
184 self.playlistIOInternal.addService(ServiceReference(x[0]))
185 self.playlistIOInternal.save(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
188 def checkSkipShowHideLock(self):
189 self.updatedSeekState()
195 self.session.nav.playService(self.oldService)
197 def delMPTimer(self):
198 del self.rightKeyTimer
199 del self.leftKeyTimer
202 def infoTimerFire(self):
203 currPlay = self.session.nav.getCurrentService()
204 if currPlay is not None:
205 stitle = currPlay.info().getInfoString(iServiceInformation.sTitle)
207 stitle = currPlay.info().getName().split('/')[-1]
209 self.updateMusicInformation( artist = currPlay.info().getInfoString(iServiceInformation.sArtist),
211 album = currPlay.info().getInfoString(iServiceInformation.sAlbum),
212 genre = currPlay.info().getInfoString(iServiceInformation.sGenre),
214 self.updateCoverArtPixmap( currPlay.info().getName() )
216 self.updateMusicInformation()
217 self.updateCoverArtPixmap( "" )
219 def updateMusicInformation(self, artist = "", title = "", album = "", year = "", genre = "", clear = False):
220 self.updateSingleMusicInformation("artist", artist, clear)
221 self.updateSingleMusicInformation("title", title, clear)
222 self.updateSingleMusicInformation("album", album, clear)
223 self.updateSingleMusicInformation("year", year, clear)
224 self.updateSingleMusicInformation("genre", genre, clear)
226 def updateSingleMusicInformation(self, name, info, clear):
227 if info != "" or clear:
228 if self[name].getText() != info:
229 self[name].setText(info)
231 def updateCoverArtPixmap(self, currentServiceName):
232 filename = currentServiceName
233 # The "getName" usually adds something like "MP3 File:" infront of filename
234 # Get rid of this...by finding the first "/"
235 # FIXME: this should be fixed in the servicemp3.cpp handler
236 filename = filename[filename.find("/"):]
237 path = os_path.dirname(filename)
238 pngname = path + "/" + "folder.png"
239 if not os_path.exists(pngname):
240 pngname = resolveFilename(SCOPE_SKIN_IMAGE, "no_coverArt.png")
241 if self.coverArtFileName != pngname:
242 self.coverArtFileName = pngname
243 self["coverArt"].instance.setPixmapFromFile(self.coverArtFileName)
246 self.lefttimer = True
247 self.leftKeyTimer.start(1000)
250 self.righttimer = True
251 self.rightKeyTimer.start(1000)
255 self.leftKeyTimer.stop()
256 self.lefttimer = False
257 self[self.currList].pageUp()
258 self.updateCurrentInfo()
262 self.rightKeyTimer.stop()
263 self.righttimer = False
264 self[self.currList].pageDown()
265 self.updateCurrentInfo()
267 def leftTimerFire(self):
268 self.leftKeyTimer.stop()
269 self.lefttimer = False
270 self.switchToFileList()
272 def rightTimerFire(self):
273 self.rightKeyTimer.stop()
274 self.righttimer = False
275 self.switchToPlayList()
277 def switchToFileList(self):
278 self.currList = "filelist"
279 self.filelist.selectionEnabled(1)
280 self.playlist.selectionEnabled(0)
281 self.updateCurrentInfo()
283 def switchToPlayList(self):
284 if len(self.playlist) != 0:
285 self.currList = "playlist"
286 self.filelist.selectionEnabled(0)
287 self.playlist.selectionEnabled(1)
288 self.updateCurrentInfo()
291 self[self.currList].up()
292 self.updateCurrentInfo()
295 self[self.currList].down()
296 self.updateCurrentInfo()
298 def showAfterSeek(self):
301 def showAfterCuesheetOperation(self):
304 def hideAfterResume(self):
307 # FIXME: maybe this code can be optimized
308 def updateCurrentInfo(self):
310 if self.currList == "filelist":
311 idx = self.filelist.getSelectionIndex()
312 r = self.filelist.list[idx]
319 self.summaries.setText(text,1)
322 if idx < len(self.filelist.list):
323 r = self.filelist.list[idx]
327 self.summaries.setText(text,3)
329 self.summaries.setText(" ",3)
332 if idx < len(self.filelist.list):
333 r = self.filelist.list[idx]
337 self.summaries.setText(text,4)
339 self.summaries.setText(" ",4)
342 if not self.filelist.canDescent():
343 r = self.filelist.getServiceRef()
347 self["currenttext"].setText(os_path.basename(text))
349 if self.currList == "playlist":
350 t = self.playlist.getSelection()
353 #display current selected entry on LCD
355 text = text.split('/')[-1]
356 self.summaries.setText(text,1)
357 self["currenttext"].setText(text)
358 idx = self.playlist.getSelectionIndex()
360 if idx < len(self.playlist):
361 currref = self.playlist.getServiceRefList()[idx]
362 text = currref.getPath()
363 text = text.split('/')[-1]
364 self.summaries.setText(text,3)
366 self.summaries.setText(" ",3)
369 if idx < len(self.playlist):
370 currref = self.playlist.getServiceRefList()[idx]
371 text = currref.getPath()
372 text = text.split('/')[-1]
373 self.summaries.setText(text,4)
375 self.summaries.setText(" ",4)
378 if self.currList == "filelist":
379 if self.filelist.canDescent():
380 self.filelist.descent()
381 self.updateCurrentInfo()
385 if self.currList == "playlist":
386 selection = self["playlist"].getSelection()
387 self.changeEntry(self.playlist.getSelectionIndex())
391 if self.currList == "filelist":
392 if self.filelist.canDescent():
393 menu.append((_("add directory to playlist"), "copydir"))
395 menu.append((_("add files to playlist"), "copyfiles"))
396 menu.append((_("switch to playlist"), "playlist"))
398 menu.append((_("switch to filelist"), "filelist"))
400 menu.append((_("shuffle playlist"), "shuffle"))
402 menu.append((_("delete"), "delete"))
403 menu.append((_("clear playlist"), "clear"))
404 menu.append((_("hide player"), "hide"));
405 menu.append((_("save playlist"), "saveplaylist"));
406 menu.append((_("load playlist"), "loadplaylist"));
407 menu.append((_("delete saved playlist"), "deleteplaylist"));
408 self.session.openWithCallback(self.menuCallback, ChoiceBox, title="", list=menu)
410 def menuCallback(self, choice):
414 if choice[1] == "copydir":
415 self.copyDirectory(self.filelist.getSelection()[0])
416 elif choice[1] == "copyfiles":
418 self.playlist.clear()
419 self.copyDirectory(os_path.dirname(self.filelist.getSelection()[0].getPath()) + "/", recursive = False)
420 self.playServiceRefEntry(self.filelist.getServiceRef())
421 elif choice[1] == "playlist":
422 self.switchToPlayList()
423 elif choice[1] == "filelist":
424 self.switchToFileList()
425 elif choice[1] == "delete":
426 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
429 elif choice[1] == "clear":
431 self.playlist.clear()
432 self.switchToFileList()
433 elif choice[1] == "hide":
435 elif choice[1] == "saveplaylist":
437 elif choice[1] == "loadplaylist":
439 elif choice[1] == "deleteplaylist":
440 self.delete_saved_playlist()
441 elif choice[1] == "shuffle":
442 self.playlist.PlayListShuffle()
445 def showEventInformation(self):
446 from Screens.EventView import EventViewSimple
447 from ServiceReference import ServiceReference
448 evt = self[self.currList].getCurrentEvent()
450 self.session.open(EventViewSimple, evt, ServiceReference(self.getCurrent()))
452 # also works on filelist (?)
453 def getCurrent(self):
454 return self["playlist"].getCurrent()
456 def deletePlaylistEntry(self):
457 if self.currList == "playlist":
458 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
462 def skip_listbegin(self):
463 if self.currList == "filelist":
464 self.filelist.moveToIndex(0)
466 self.playlist.moveToIndex(0)
467 self.updateCurrentInfo()
469 def skip_listend(self):
470 if self.currList == "filelist":
471 idx = len(self.filelist.list)
472 self.filelist.moveToIndex(idx - 1)
474 self.playlist.moveToIndex(len(self.playlist)-1)
475 self.updateCurrentInfo()
477 def save_playlist(self):
478 self.session.openWithCallback(self.save_playlist2,InputBox, title=_("Please enter filename (empty = use current date)"),windowTitle = _("Save Playlist"))
480 def save_playlist2(self, name):
484 name = strftime("%y%m%d_%H%M%S")
486 self.playlistIOInternal.clear()
487 for x in self.playlist.list:
488 self.playlistIOInternal.addService(ServiceReference(x[0]))
489 self.playlistIOInternal.save(resolveFilename(SCOPE_PLAYLIST) + name)
491 def load_playlist(self):
493 playlistdir = resolveFilename(SCOPE_PLAYLIST)
495 for i in os_listdir(playlistdir):
496 listpath.append((i,playlistdir + i))
498 print "Error while scanning subdirs ",e
499 self.session.openWithCallback(self.PlaylistSelected, ChoiceBox, title=_("Please select a playlist..."), list = listpath)
501 def PlaylistSelected(self,path):
503 self.clear_playlist()
504 self.playlistIOInternal = PlaylistIOInternal()
505 list = self.playlistIOInternal.open(path[1])
508 self.playlist.addFile(x.ref)
509 self.playlist.updateList()
511 def delete_saved_playlist(self):
513 playlistdir = resolveFilename(SCOPE_PLAYLIST)
515 for i in os_listdir(playlistdir):
516 listpath.append((i,playlistdir + i))
518 print "Error while scanning subdirs ",e
519 self.session.openWithCallback(self.DeletePlaylistSelected, ChoiceBox, title=_("Please select a playlist to delete..."), list = listpath)
521 def DeletePlaylistSelected(self,path):
523 self.delname = path[1]
524 self.session.openWithCallback(self.deleteConfirmed, MessageBox, _("Do you really want to delete %s?") % (path[1]))
526 def deleteConfirmed(self, confirmed):
528 os_remove(self.delname)
530 def clear_playlist(self):
532 self.playlist.clear()
533 self.switchToFileList()
535 def copyDirectory(self, directory, recursive = True):
536 print "copyDirectory", directory
537 filelist = FileList(directory, useServiceRef = True, isTop = True)
539 for x in filelist.getFileList():
540 if x[0][1] == True: #isDir
542 self.copyDirectory(x[0][0])
544 self.playlist.addFile(x[0][0])
545 self.playlist.updateList()
548 if self.filelist.getServiceRef().type == 4098: # playlist
549 ServiceRef = self.filelist.getServiceRef()
550 extension = ServiceRef.getPath()[ServiceRef.getPath().rfind('.') + 1:]
551 print "extension:", extension
552 if self.playlistparsers.has_key(extension):
553 playlist = self.playlistparsers[extension]()
554 list = playlist.open(ServiceRef.getPath())
556 self.playlist.addFile(x.ref)
558 self.playlist.addFile(self.filelist.getServiceRef())
559 self.playlist.updateList()
560 if len(self.playlist) == 1:
563 def addPlaylistParser(self, parser, extension):
564 self.playlistparsers[extension] = parser
567 next = self.playlist.getCurrentIndex() + 1
568 if next < len(self.playlist):
569 self.changeEntry(next)
571 def previousEntry(self):
572 next = self.playlist.getCurrentIndex() - 1
574 self.changeEntry(next)
576 def deleteEntry(self):
577 self.playlist.deleteFile(self.playlist.getSelectionIndex())
578 self.playlist.updateList()
579 if len(self.playlist) == 0:
580 self.switchToFileList()
582 def changeEntry(self, index):
583 self.playlist.setCurrentPlaying(index)
586 def playServiceRefEntry(self, serviceref):
587 serviceRefList = self.playlist.getServiceRefList()
588 for count in range(len(serviceRefList)):
589 if serviceRefList[count] == serviceref:
590 self.changeEntry(count)
594 if len(self.playlist.getServiceRefList()):
595 currref = self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()]
596 if self.session.nav.getCurrentlyPlayingServiceReference() is None or currref != self.session.nav.getCurrentlyPlayingServiceReference():
597 self.session.nav.playService(self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()])
598 info = eServiceCenter.getInstance().info(currref)
599 description = info and info.getInfoString(currref, iServiceInformation.sDescription) or ""
600 self["title"].setText(description)
601 # display just playing musik on LCD
602 idx = self.playlist.getCurrentIndex()
603 currref = self.playlist.getServiceRefList()[idx]
604 text = currref.getPath()
605 text = text.split('/')[-1]
607 ext = text[-3:].lower()
609 # FIXME: the information if the service contains video (and we should hide our window) should com from the service instead
610 if ext not in ["mp3", "wav", "ogg"]:
612 self.summaries.setText(text,1)
614 # get the next two entries
616 if idx < len(self.playlist):
617 currref = self.playlist.getServiceRefList()[idx]
618 text = currref.getPath()
619 text = text.split('/')[-1]
620 self.summaries.setText(text,3)
622 self.summaries.setText(" ",3)
625 if idx < len(self.playlist):
626 currref = self.playlist.getServiceRefList()[idx]
627 text = currref.getPath()
628 text = text.split('/')[-1]
629 self.summaries.setText(text,4)
631 self.summaries.setText(" ",4)
633 idx = self.playlist.getCurrentIndex()
634 currref = self.playlist.getServiceRefList()[idx]
635 text = currref.getPath()
636 ext = text[-3:].lower()
637 if ext not in ["mp3", "wav", "ogg"]:
639 self.unPauseService()
641 def updatedSeekState(self):
642 if self.seekstate == self.SEEK_STATE_PAUSE:
643 self.playlist.pauseFile()
644 elif self.seekstate == self.SEEK_STATE_PLAY:
645 self.playlist.playFile()
646 elif self.seekstate in ( self.SEEK_STATE_FF_2X,
647 self.SEEK_STATE_FF_4X,
648 self.SEEK_STATE_FF_8X,
649 self.SEEK_STATE_FF_16X,
650 self.SEEK_STATE_FF_32X,
651 self.SEEK_STATE_FF_48X,
652 self.SEEK_STATE_FF_64X,
653 self.SEEK_STATE_FF_128X):
654 self.playlist.forwardFile()
655 elif self.seekstate in ( self.SEEK_STATE_BACK_8X,
656 self.SEEK_STATE_BACK_16X,
657 self.SEEK_STATE_BACK_32X,
658 self.SEEK_STATE_BACK_48X,
659 self.SEEK_STATE_BACK_64X,
660 self.SEEK_STATE_BACK_128X):
661 self.playlist.rewindFile()
663 def pauseEntry(self):
668 self.playlist.stopFile()
669 self.session.nav.playService(None)
670 self.updateMusicInformation(clear=True)
673 def unPauseService(self):
674 self.setSeekState(self.SEEK_STATE_PLAY)
677 class MediaPlayerLCDScreen(Screen):
679 <screen position="0,0" size="132,64" title="LCD Text">
680 <widget name="text1" position="4,0" size="132,35" font="Regular;16"/>
681 <widget name="text3" position="4,36" size="132,14" font="Regular;10"/>
682 <widget name="text4" position="4,49" size="132,14" font="Regular;10"/>
685 def __init__(self, session, parent):
686 Screen.__init__(self, session)
687 self["text1"] = Label("Mediaplayer")
688 self["text3"] = Label("")
689 self["text4"] = Label("")
691 def setText(self, text, line):
692 print "lcd set text:", text, line
694 if text[-4:] == ".mp3":
697 text = text + textleer*10
699 self["text1"].setText(text)
701 self["text3"].setText(text)
703 self["text4"].setText(text)
705 def main(session, **kwargs):
706 session.open(MediaPlayer)
708 def menu(menuid, **kwargs):
709 if menuid == "mainmenu":
710 return [(_("Media player"), main)]
713 def filescan_open(list, session, **kwargs):
714 from enigma import eServiceReference
716 mp = session.open(MediaPlayer)
718 mp.switchToPlayList()
720 ref = eServiceReference(4097, 0, file.path)
721 mp.playlist.addFile(ref)
723 # TODO: rather play first than last file?
724 mp.playServiceRefEntry(ref)
725 mp.playlist.updateList()
727 def filescan(**kwargs):
728 from Components.Scanner import Scanner, ScanPath
730 Scanner(mimetypes = ["video/mpeg"],
733 ScanPath(path = "", with_subdirs = False),
736 description = "View Movies...",
737 openfnc = filescan_open,
739 Scanner(mimetypes = ["audio/mpeg", "audio/x-wav", "application/ogg"],
742 ScanPath(path = "", with_subdirs = False),
745 description = "Play Music...",
746 openfnc = filescan_open,
750 from Plugins.Plugin import PluginDescriptor
751 def Plugins(**kwargs):
753 PluginDescriptor(name = "MediaPlayer", description = "Play back media files", where = PluginDescriptor.WHERE_MENU, fnc = menu),
754 PluginDescriptor(name = "MediaPlayer", where = PluginDescriptor.WHERE_FILESCAN, fnc = filescan)