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 if self.skinAttributes is not None:
37 for (attrib, value) in self.skinAttributes:
38 if attrib == "pixmap":
39 self.default_pixmap = value
41 if self.default_pixmap is None:
42 self.default_pixmap = resolveFilename(SCOPE_SKIN_IMAGE, "no_coverArt.png")
43 return Pixmap.applySkin(self, desktop)
45 class MediaPlayer(Screen, InfoBarSeek, InfoBarAudioSelection, InfoBarCueSheetSupport, InfoBarNotifications, HelpableScreen):
47 ENABLE_RESUME_SUPPORT = True
49 def __init__(self, session, args = None):
50 Screen.__init__(self, session)
51 InfoBarAudioSelection.__init__(self)
52 InfoBarCueSheetSupport.__init__(self, actionmap = "MediaPlayerCueSheetActions")
53 InfoBarNotifications.__init__(self)
54 HelpableScreen.__init__(self)
56 self.oldService = self.session.nav.getCurrentlyPlayingServiceReference()
57 self.session.nav.stopService()
59 self.playlistparsers = {}
60 self.addPlaylistParser(PlaylistIOM3U, "m3u")
61 self.addPlaylistParser(PlaylistIOPLS, "pls")
62 self.addPlaylistParser(PlaylistIOInternal, "e2pls")
64 # 'None' is magic to start at the list of mountpoints
65 self.filelist = FileList(None, matchingPattern = "(?i)^.*\.(mp3|ogg|ts|wav|wave|m3u|pls|e2pls|mpg|vob)", useServiceRef = True)
66 self["filelist"] = self.filelist
68 self.playlist = MyPlayList()
69 #self.playlist = PlayList()
70 self.is_closing = False
72 self["playlist"] = self.playlist
74 self["PositionGauge"] = ServicePositionGauge(self.session.nav)
76 self["currenttext"] = Label("")
78 self["artisttext"] = Label(_("Artist:"))
79 self["artist"] = Label("")
80 self["titletext"] = Label(_("Title:"))
81 self["title"] = Label("")
82 self["albumtext"] = Label(_("Album:"))
83 self["album"] = Label("")
84 self["yeartext"] = Label(_("Year:"))
85 self["year"] = Label("")
86 self["genretext"] = Label(_("Genre:"))
87 self["genre"] = Label("")
88 self["coverArt"] = MediaPixmap()
90 self.seek_target = None
92 class MoviePlayerActionMap(NumberActionMap):
93 def __init__(self, player, contexts = [ ], actions = { }, prio=0):
94 NumberActionMap.__init__(self, contexts, actions, prio)
97 def action(self, contexts, action):
99 return NumberActionMap.action(self, contexts, action)
102 self["OkCancelActions"] = HelpableActionMap(self, "OkCancelActions",
104 "ok": (self.ok, _("add file to playlist")),
105 "cancel": (self.exit, _("exit mediaplayer")),
108 self["MediaPlayerActions"] = HelpableActionMap(self, "MediaPlayerActions",
110 "play": (self.xplayEntry, _("play entry")),
111 "pause": (self.pauseEntry, _("pause")),
112 "stop": (self.stopEntry, _("stop entry")),
113 "previous": (self.previousMarkOrEntry, _("play from previous mark or playlist entry")),
114 "next": (self.nextMarkOrEntry, _("play from next mark or playlist entry")),
115 "menu": (self.showMenu, _("menu")),
116 "skipListbegin": (self.skip_listbegin, _("jump to listbegin")),
117 "skipListend": (self.skip_listend, _("jump to listend")),
118 "prevBouquet": (self.switchToPlayList, _("switch to playlist")),
119 "nextBouquet": (self.switchToFileList, _("switch to filelist")),
120 "delete": (self.deletePlaylistEntry, _("delete playlist entry")),
121 "shift_stop": (self.clear_playlist, _("clear playlist")),
122 "shift_record": (self.playlist.PlayListShuffle, _("shuffle playlist")),
125 self["InfobarEPGActions"] = HelpableActionMap(self, "InfobarEPGActions",
127 "showEventInfo": (self.showEventInformation, _("show event details")),
130 self["actions"] = MoviePlayerActionMap(self, ["DirectionActions"],
132 "right": self.rightDown,
133 "rightRepeated": self.doNothing,
134 "rightUp": self.rightUp,
135 "left": self.leftDown,
136 "leftRepeated": self.doNothing,
137 "leftUp": self.leftUp,
140 "upRepeated": self.up,
141 "upUp": self.doNothing,
143 "downRepeated": self.down,
144 "downUp": self.doNothing,
147 InfoBarSeek.__init__(self, actionmap = "MediaPlayerSeekActions")
149 self.onClose.append(self.delMPTimer)
150 self.onClose.append(self.__onClose)
152 self.righttimer = False
153 self.rightKeyTimer = eTimer()
154 self.rightKeyTimer.callback.append(self.rightTimerFire)
156 self.lefttimer = False
157 self.leftKeyTimer = eTimer()
158 self.leftKeyTimer.callback.append(self.leftTimerFire)
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()
191 def doEofInternal(self, playing):
198 self.session.nav.playService(self.oldService)
200 def delMPTimer(self):
201 del self.rightKeyTimer
202 del self.leftKeyTimer
204 def readTitleInformation(self):
205 currPlay = self.session.nav.getCurrentService()
206 if currPlay is not None:
207 stitle = currPlay.info().getInfoString(iServiceInformation.sTitle)
209 stitle = currPlay.info().getName().split('/')[-1]
211 self.updateMusicInformation( artist = currPlay.info().getInfoString(iServiceInformation.sArtist),
213 album = currPlay.info().getInfoString(iServiceInformation.sAlbum),
214 genre = currPlay.info().getInfoString(iServiceInformation.sGenre),
217 self.updateMusicInformation()
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, path):
232 while not path.endswith("/"):
234 pngname = path + "folder.png"
236 if not os_path.exists(pngname):
237 pngname = self["coverArt"].default_pixmap
238 if self.coverArtFileName != pngname:
239 self.coverArtFileName = pngname
240 self["coverArt"].instance.setPixmapFromFile(self.coverArtFileName)
243 self.lefttimer = True
244 self.leftKeyTimer.start(1000)
247 self.righttimer = True
248 self.rightKeyTimer.start(1000)
252 self.leftKeyTimer.stop()
253 self.lefttimer = False
254 self[self.currList].pageUp()
255 self.updateCurrentInfo()
259 self.rightKeyTimer.stop()
260 self.righttimer = False
261 self[self.currList].pageDown()
262 self.updateCurrentInfo()
264 def leftTimerFire(self):
265 self.leftKeyTimer.stop()
266 self.lefttimer = False
267 self.switchToFileList()
269 def rightTimerFire(self):
270 self.rightKeyTimer.stop()
271 self.righttimer = False
272 self.switchToPlayList()
274 def switchToFileList(self):
275 self.currList = "filelist"
276 self.filelist.selectionEnabled(1)
277 self.playlist.selectionEnabled(0)
278 self.updateCurrentInfo()
280 def switchToPlayList(self):
281 if len(self.playlist) != 0:
282 self.currList = "playlist"
283 self.filelist.selectionEnabled(0)
284 self.playlist.selectionEnabled(1)
285 self.updateCurrentInfo()
288 self[self.currList].up()
289 self.updateCurrentInfo()
292 self[self.currList].down()
293 self.updateCurrentInfo()
295 def showAfterSeek(self):
298 def showAfterCuesheetOperation(self):
301 def hideAfterResume(self):
304 # FIXME: maybe this code can be optimized
305 def updateCurrentInfo(self):
307 if self.currList == "filelist":
308 idx = self.filelist.getSelectionIndex()
309 r = self.filelist.list[idx]
316 self.summaries.setText(text,1)
319 if idx < len(self.filelist.list):
320 r = self.filelist.list[idx]
324 self.summaries.setText(text,3)
326 self.summaries.setText(" ",3)
329 if idx < len(self.filelist.list):
330 r = self.filelist.list[idx]
334 self.summaries.setText(text,4)
336 self.summaries.setText(" ",4)
339 if not self.filelist.canDescent():
340 r = self.filelist.getServiceRef()
344 self["currenttext"].setText(os_path.basename(text))
346 if self.currList == "playlist":
347 t = self.playlist.getSelection()
350 #display current selected entry on LCD
352 text = text.split('/')[-1]
353 self.summaries.setText(text,1)
354 self["currenttext"].setText(text)
355 idx = self.playlist.getSelectionIndex()
357 if idx < len(self.playlist):
358 currref = self.playlist.getServiceRefList()[idx]
359 text = currref.getPath()
360 text = text.split('/')[-1]
361 self.summaries.setText(text,3)
363 self.summaries.setText(" ",3)
366 if idx < len(self.playlist):
367 currref = self.playlist.getServiceRefList()[idx]
368 text = currref.getPath()
369 text = text.split('/')[-1]
370 self.summaries.setText(text,4)
372 self.summaries.setText(" ",4)
375 if self.currList == "filelist":
376 if self.filelist.canDescent():
377 self.filelist.descent()
378 self.updateCurrentInfo()
382 if self.currList == "playlist":
383 selection = self["playlist"].getSelection()
384 self.changeEntry(self.playlist.getSelectionIndex())
388 if self.currList == "filelist":
389 if self.filelist.canDescent():
390 menu.append((_("add directory to playlist"), "copydir"))
392 menu.append((_("add files to playlist"), "copyfiles"))
393 menu.append((_("switch to playlist"), "playlist"))
395 menu.append((_("switch to filelist"), "filelist"))
397 menu.append((_("shuffle playlist"), "shuffle"))
399 menu.append((_("delete"), "delete"))
400 menu.append((_("clear playlist"), "clear"))
401 menu.append((_("hide player"), "hide"));
402 menu.append((_("save playlist"), "saveplaylist"));
403 menu.append((_("load playlist"), "loadplaylist"));
404 menu.append((_("delete saved playlist"), "deleteplaylist"));
405 self.session.openWithCallback(self.menuCallback, ChoiceBox, title="", list=menu)
407 def menuCallback(self, choice):
411 if choice[1] == "copydir":
412 self.copyDirectory(self.filelist.getSelection()[0])
413 elif choice[1] == "copyfiles":
415 self.playlist.clear()
416 self.copyDirectory(os_path.dirname(self.filelist.getSelection()[0].getPath()) + "/", recursive = False)
417 self.playServiceRefEntry(self.filelist.getServiceRef())
418 elif choice[1] == "playlist":
419 self.switchToPlayList()
420 elif choice[1] == "filelist":
421 self.switchToFileList()
422 elif choice[1] == "delete":
423 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
426 elif choice[1] == "clear":
428 self.playlist.clear()
429 self.switchToFileList()
430 elif choice[1] == "hide":
432 elif choice[1] == "saveplaylist":
434 elif choice[1] == "loadplaylist":
436 elif choice[1] == "deleteplaylist":
437 self.delete_saved_playlist()
438 elif choice[1] == "shuffle":
439 self.playlist.PlayListShuffle()
442 def showEventInformation(self):
443 from Screens.EventView import EventViewSimple
444 from ServiceReference import ServiceReference
445 evt = self[self.currList].getCurrentEvent()
447 self.session.open(EventViewSimple, evt, ServiceReference(self.getCurrent()))
449 # also works on filelist (?)
450 def getCurrent(self):
451 return self["playlist"].getCurrent()
453 def deletePlaylistEntry(self):
454 if self.currList == "playlist":
455 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
459 def skip_listbegin(self):
460 if self.currList == "filelist":
461 self.filelist.moveToIndex(0)
463 self.playlist.moveToIndex(0)
464 self.updateCurrentInfo()
466 def skip_listend(self):
467 if self.currList == "filelist":
468 idx = len(self.filelist.list)
469 self.filelist.moveToIndex(idx - 1)
471 self.playlist.moveToIndex(len(self.playlist)-1)
472 self.updateCurrentInfo()
474 def save_playlist(self):
475 self.session.openWithCallback(self.save_playlist2,InputBox, title=_("Please enter filename (empty = use current date)"),windowTitle = _("Save Playlist"))
477 def save_playlist2(self, name):
481 name = strftime("%y%m%d_%H%M%S")
483 self.playlistIOInternal.clear()
484 for x in self.playlist.list:
485 self.playlistIOInternal.addService(ServiceReference(x[0]))
486 self.playlistIOInternal.save(resolveFilename(SCOPE_PLAYLIST) + name)
488 def load_playlist(self):
490 playlistdir = resolveFilename(SCOPE_PLAYLIST)
492 for i in os_listdir(playlistdir):
493 listpath.append((i,playlistdir + i))
495 print "Error while scanning subdirs ",e
496 self.session.openWithCallback(self.PlaylistSelected, ChoiceBox, title=_("Please select a playlist..."), list = listpath)
498 def PlaylistSelected(self,path):
500 self.clear_playlist()
501 self.playlistIOInternal = PlaylistIOInternal()
502 list = self.playlistIOInternal.open(path[1])
505 self.playlist.addFile(x.ref)
506 self.playlist.updateList()
508 def delete_saved_playlist(self):
510 playlistdir = resolveFilename(SCOPE_PLAYLIST)
512 for i in os_listdir(playlistdir):
513 listpath.append((i,playlistdir + i))
515 print "Error while scanning subdirs ",e
516 self.session.openWithCallback(self.DeletePlaylistSelected, ChoiceBox, title=_("Please select a playlist to delete..."), list = listpath)
518 def DeletePlaylistSelected(self,path):
520 self.delname = path[1]
521 self.session.openWithCallback(self.deleteConfirmed, MessageBox, _("Do you really want to delete %s?") % (path[1]))
523 def deleteConfirmed(self, confirmed):
525 os_remove(self.delname)
527 def clear_playlist(self):
529 self.playlist.clear()
530 self.switchToFileList()
532 def copyDirectory(self, directory, recursive = True):
533 print "copyDirectory", directory
534 filelist = FileList(directory, useServiceRef = True, isTop = True)
536 for x in filelist.getFileList():
537 if x[0][1] == True: #isDir
539 self.copyDirectory(x[0][0])
541 self.playlist.addFile(x[0][0])
542 self.playlist.updateList()
545 if self.filelist.getServiceRef().type == 4098: # playlist
546 ServiceRef = self.filelist.getServiceRef()
547 extension = ServiceRef.getPath()[ServiceRef.getPath().rfind('.') + 1:]
548 print "extension:", extension
549 if self.playlistparsers.has_key(extension):
550 playlist = self.playlistparsers[extension]()
551 list = playlist.open(ServiceRef.getPath())
553 self.playlist.addFile(x.ref)
555 self.playlist.addFile(self.filelist.getServiceRef())
556 self.playlist.updateList()
557 if len(self.playlist) == 1:
560 def addPlaylistParser(self, parser, extension):
561 self.playlistparsers[extension] = parser
564 next = self.playlist.getCurrentIndex() + 1
565 if next < len(self.playlist):
566 self.changeEntry(next)
568 def nextMarkOrEntry(self):
569 if not self.jumpPreviousNextMark(lambda x: x):
570 next = self.playlist.getCurrentIndex() + 1
571 if next < len(self.playlist):
572 self.changeEntry(next)
576 def previousMarkOrEntry(self):
577 if not self.jumpPreviousNextMark(lambda x: -x-5*90000, start=True):
578 next = self.playlist.getCurrentIndex() - 1
580 self.changeEntry(next)
582 def deleteEntry(self):
583 self.playlist.deleteFile(self.playlist.getSelectionIndex())
584 self.playlist.updateList()
585 if len(self.playlist) == 0:
586 self.switchToFileList()
588 def changeEntry(self, index):
589 self.playlist.setCurrentPlaying(index)
592 def playServiceRefEntry(self, serviceref):
593 serviceRefList = self.playlist.getServiceRefList()
594 for count in range(len(serviceRefList)):
595 if serviceRefList[count] == serviceref:
596 self.changeEntry(count)
599 def xplayEntry(self):
600 if self.currList == "playlist":
604 self.playlist.clear()
605 sel = self.filelist.getSelection()
607 if sel[1]: # can descent
608 # add directory to playlist
609 self.copyDirectory(sel[0])
611 # add files to playlist
612 self.copyDirectory(os_path.dirname(sel[0].getPath()) + "/", recursive = False)
613 if len(self.playlist) > 0:
617 if len(self.playlist.getServiceRefList()):
618 needsInfoUpdate = False
619 currref = self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()]
620 if self.session.nav.getCurrentlyPlayingServiceReference() is None or currref != self.session.nav.getCurrentlyPlayingServiceReference():
621 self.session.nav.playService(self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()])
622 info = eServiceCenter.getInstance().info(currref)
623 description = info and info.getInfoString(currref, iServiceInformation.sDescription) or ""
624 self["title"].setText(description)
625 # display just playing musik on LCD
626 idx = self.playlist.getCurrentIndex()
627 currref = self.playlist.getServiceRefList()[idx]
628 text = currref.getPath()
629 text = text.split('/')[-1]
631 ext = text[-3:].lower()
633 # FIXME: the information if the service contains video (and we should hide our window) should com from the service instead
634 if ext not in ["mp3", "wav", "ogg"]:
637 needsInfoUpdate = True
638 self.summaries.setText(text,1)
640 # get the next two entries
642 if idx < len(self.playlist):
643 currref = self.playlist.getServiceRefList()[idx]
644 text = currref.getPath()
645 text = text.split('/')[-1]
646 self.summaries.setText(text,3)
648 self.summaries.setText(" ",3)
651 if idx < len(self.playlist):
652 currref = self.playlist.getServiceRefList()[idx]
653 text = currref.getPath()
654 text = text.split('/')[-1]
655 self.summaries.setText(text,4)
657 self.summaries.setText(" ",4)
659 idx = self.playlist.getCurrentIndex()
660 currref = self.playlist.getServiceRefList()[idx]
661 text = currref.getPath()
662 ext = text[-3:].lower()
663 if ext not in ["mp3", "wav", "ogg"]:
666 needsInfoUpdate = True
668 self.unPauseService()
669 if needsInfoUpdate == True:
670 self.updateCoverArtPixmap(currref.getPath())
672 pngname = self["coverArt"].default_pixmap
673 self.coverArtFileName = pngname
674 self["coverArt"].instance.setPixmapFromFile(self.coverArtFileName)
675 self.readTitleInformation()
677 def updatedSeekState(self):
678 if self.seekstate == self.SEEK_STATE_PAUSE:
679 self.playlist.pauseFile()
680 elif self.seekstate == self.SEEK_STATE_PLAY:
681 self.playlist.playFile()
682 elif self.isStateForward(self.seekstate):
683 self.playlist.forwardFile()
684 elif self.isStateBackward(self.seekstate):
685 self.playlist.rewindFile()
687 def pauseEntry(self):
692 self.playlist.stopFile()
693 self.session.nav.playService(None)
694 self.updateMusicInformation(clear=True)
697 def unPauseService(self):
698 self.setSeekState(self.SEEK_STATE_PLAY)
701 class MediaPlayerLCDScreen(Screen):
703 <screen position="0,0" size="132,64" title="LCD Text">
704 <widget name="text1" position="4,0" size="132,35" font="Regular;16"/>
705 <widget name="text3" position="4,36" size="132,14" font="Regular;10"/>
706 <widget name="text4" position="4,49" size="132,14" font="Regular;10"/>
709 def __init__(self, session, parent):
710 Screen.__init__(self, session)
711 self["text1"] = Label("Mediaplayer")
712 self["text3"] = Label("")
713 self["text4"] = Label("")
715 def setText(self, text, line):
716 print "lcd set text:", text, line
718 if text[-4:] == ".mp3":
721 text = text + textleer*10
723 self["text1"].setText(text)
725 self["text3"].setText(text)
727 self["text4"].setText(text)
729 def main(session, **kwargs):
730 session.open(MediaPlayer)
732 def menu(menuid, **kwargs):
733 if menuid == "mainmenu":
734 return [(_("Media player"), main, "media_player", 45)]
737 def filescan_open(list, session, **kwargs):
738 from enigma import eServiceReference
740 mp = session.open(MediaPlayer)
742 mp.switchToPlayList()
744 ref = eServiceReference(4097, 0, file.path)
745 mp.playlist.addFile(ref)
747 # TODO: rather play first than last file?
748 mp.playServiceRefEntry(ref)
749 mp.playlist.updateList()
751 def filescan(**kwargs):
752 from Components.Scanner import Scanner, ScanPath
754 Scanner(mimetypes = ["video/mpeg"],
757 ScanPath(path = "", with_subdirs = False),
760 description = "View Movies...",
761 openfnc = filescan_open,
763 Scanner(mimetypes = ["audio/mpeg", "audio/x-wav", "application/ogg"],
766 ScanPath(path = "", with_subdirs = False),
769 description = "Play Music...",
770 openfnc = filescan_open,
774 from Plugins.Plugin import PluginDescriptor
775 def Plugins(**kwargs):
777 PluginDescriptor(name = "MediaPlayer", description = "Play back media files", where = PluginDescriptor.WHERE_MENU, fnc = menu),
778 PluginDescriptor(name = "MediaPlayer", where = PluginDescriptor.WHERE_FILESCAN, fnc = filescan)