1 from os import path as os_path, remove as os_remove, listdir as os_listdir, system
2 from enigma import eTimer, iPlayableService, iServiceInformation, eServiceReference, iServiceKeys
3 from Screens.Screen import Screen
4 from Screens.MessageBox import MessageBox
5 from Screens.ChoiceBox import ChoiceBox
6 from Screens.HelpMenu import HelpableScreen
7 from Screens.InfoBarGenerics import InfoBarSeek, InfoBarPVRState, InfoBarCueSheetSupport, InfoBarShowHide, InfoBarNotifications
8 from Components.ActionMap import ActionMap, NumberActionMap, HelpableActionMap
9 from Components.Label import Label
10 from Components.FileList import FileList
11 from Components.MenuList import MenuList
12 from Components.ServiceEventTracker import ServiceEventTracker, InfoBarBase
13 from Components.config import config
14 from Tools.Directories import pathExists, fileExists
15 from Components.Harddisk import harddiskmanager
17 import servicedvd # load c++ part of dvd player plugin
21 class FileBrowser(Screen):
23 <screen name="FileBrowser" position="100,100" size="520,376" title="DVD File Browser" >
24 <widget name="filelist" position="0,0" size="520,376" scrollbarMode="showOnDemand" />
26 def __init__(self, session, dvd_filelist = [ ]):
27 Screen.__init__(self, session)
29 self.dvd_filelist = dvd_filelist
31 self["filelist"] = MenuList(self.dvd_filelist)
34 if lastpath is not None:
35 currDir = lastpath + "/"
37 currDir = "/media/dvd/"
38 if not pathExists(currDir):
41 self.filelist = FileList(currDir, matchingPattern = "(?i)^.*\.(iso)", useServiceRef = True)
42 self["filelist"] = self.filelist
44 self["FilelistActions"] = ActionMap(["OkCancelActions"],
51 if len(self.dvd_filelist):
52 print "OK " + self["filelist"].getCurrent()
53 self.close(self["filelist"].getCurrent())
56 filename = self["filelist"].getFilename()
57 if filename is not None:
58 if filename.upper().endswith("VIDEO_TS/"):
59 print "dvd structure found, trying to open..."
60 dvdpath = filename[0:-9]
61 lastpath = (dvdpath.rstrip("/").rsplit("/",1))[0]
62 print "lastpath video_ts/=", lastpath
65 if self["filelist"].canDescent(): # isDir
66 self["filelist"].descent()
67 pathname = self["filelist"].getCurrentDirectory() or ""
68 if fileExists(pathname+"VIDEO_TS.IFO"):
69 print "dvd structure found, trying to open..."
70 lastpath = (pathname.rstrip("/").rsplit("/",1))[0]
71 print "lastpath video_ts.ifo=", lastpath
74 lastpath = filename[0:filename.rfind("/")]
75 print "lastpath directory=", lastpath
81 class DVDSummary(Screen):
83 <screen position="0,0" size="132,64">
84 <widget source="session.CurrentService" render="Label" position="5,4" size="120,28" font="Regular;12" transparent="1" >
85 <convert type="ServiceName">Name</convert>
87 <widget name="DVDPlayer" position="5,30" size="66,16" font="Regular;12" transparent="1" />
88 <widget name="Chapter" position="72,30" size="54,16" font="Regular;12" transparent="1" halign="right" />
89 <widget source="session.CurrentService" render="Label" position="66,46" size="60,18" font="Regular;16" transparent="1" halign="right" >
90 <convert type="ServicePosition">Position</convert>
92 <widget source="session.CurrentService" render="Progress" position="6,46" size="60,18" borderWidth="1" >
93 <convert type="ServicePosition">Position</convert>
97 def __init__(self, session, parent):
98 Screen.__init__(self, session, parent)
100 self["DVDPlayer"] = Label("DVD Player")
101 self["Title"] = Label("")
102 self["Time"] = Label("")
103 self["Chapter"] = Label("")
105 def updateChapter(self, chapter):
106 self["Chapter"].setText(chapter)
108 def setTitle(self, title):
109 self["Title"].setText(title)
111 class DVDOverlay(Screen):
112 skin = """<screen name="DVDOverlay" position="0,0" size="720,576" flags="wfNoBorder" zPosition="-1" backgroundColor="transparent" />"""
113 def __init__(self, session, args = None):
114 Screen.__init__(self, session)
116 class ChapterZap(Screen):
118 <screen name="ChapterZap" position="235,255" size="250,60" title="Chapter" >
119 <widget name="chapter" position="35,15" size="110,25" font="Regular;23" />
120 <widget name="number" position="145,15" size="80,25" halign="right" font="Regular;23" />
129 self.close(int(self["number"].getText()))
131 def keyNumberGlobal(self, number):
132 self.Timer.start(3000, True) #reset timer
133 self.field = self.field + str(number)
134 self["number"].setText(self.field)
135 if len(self.field) >= 4:
138 def __init__(self, session, number):
139 Screen.__init__(self, session)
140 self.field = str(number)
142 self["chapter"] = Label(_("Chapter:"))
144 self["number"] = Label(self.field)
146 self["actions"] = NumberActionMap( [ "SetupActions" ],
150 "1": self.keyNumberGlobal,
151 "2": self.keyNumberGlobal,
152 "3": self.keyNumberGlobal,
153 "4": self.keyNumberGlobal,
154 "5": self.keyNumberGlobal,
155 "6": self.keyNumberGlobal,
156 "7": self.keyNumberGlobal,
157 "8": self.keyNumberGlobal,
158 "9": self.keyNumberGlobal,
159 "0": self.keyNumberGlobal
162 self.Timer = eTimer()
163 self.Timer.callback.append(self.keyOK)
164 self.Timer.start(3000, True)
166 class DVDPlayer(Screen, InfoBarBase, InfoBarNotifications, InfoBarSeek, InfoBarPVRState, InfoBarShowHide, HelpableScreen, InfoBarCueSheetSupport):
167 # ALLOW_SUSPEND = True
168 ENABLE_RESUME_SUPPORT = True
171 <screen name="DVDPlayer" flags="wfNoBorder" position="0,380" size="720,160" title="InfoBar" backgroundColor="transparent" >
173 <ePixmap position="0,0" zPosition="-2" size="720,160" pixmap="skin_default/info-bg_mp.png" alphatest="off" />
174 <ePixmap position="29,40" zPosition="0" size="665,104" pixmap="skin_default/screws_mp.png" alphatest="on" transparent="1" />
175 <!-- colorbuttons -->
176 <ePixmap position="48,70" zPosition="0" size="108,13" pixmap="skin_default/icons/mp_buttons.png" alphatest="on" />
178 <ePixmap pixmap="skin_default/icons/icon_event.png" position="207,78" zPosition="1" size="15,10" alphatest="on" />
179 <widget source="session.CurrentService" render="Label" position="230,73" size="300,22" font="Regular;20" backgroundColor="#263c59" shadowColor="#1d354c" shadowOffset="-1,-1" transparent="1" noWrap="1">
180 <convert type="ServiceName">Name</convert>
182 <!-- Chapter info -->
183 <widget name="chapterLabel" position="230,96" size="360,22" font="Regular;20" foregroundColor="#c3c3c9" backgroundColor="#263c59" transparent="1" />
184 <!-- Audio track info -->
185 <ePixmap pixmap="skin_default/icons/icon_dolby.png" position="540,73" zPosition="1" size="26,16" alphatest="on"/>
186 <widget name="audioLabel" position="570,73" size="130,22" font="Regular;18" backgroundColor="#263c59" shadowColor="#1d354c" shadowOffset="-1,-1" transparent="1" />
187 <!-- Subtitle track info -->
188 <widget source="session.CurrentService" render="Pixmap" pixmap="skin_default/icons/icon_txt.png" position="540,96" zPosition="1" size="26,16" alphatest="on" >
189 <convert type="ServiceInfo">HasTelext</convert>
190 <convert type="ConditionalShowHide" />
192 <widget name="subtitleLabel" position="570,96" size="130,22" font="Regular;18" backgroundColor="#263c59" shadowColor="#1d354c" shadowOffset="-1,-1" transparent="1" />
193 <!-- Elapsed time -->
194 <widget source="session.CurrentService" render="Label" position="205,129" size="100,20" font="Regular;18" halign="center" valign="center" backgroundColor="#06224f" shadowColor="#1d354c" shadowOffset="-1,-1" transparent="1" >
195 <convert type="ServicePosition">Position,ShowHours</convert>
197 <!-- Progressbar (movie position)-->
198 <widget source="session.CurrentService" render="PositionGauge" position="300,133" size="270,10" zPosition="2" pointer="skin_default/position_pointer.png:540,0" transparent="1" >
199 <convert type="ServicePosition">Gauge</convert>
201 <!-- Remaining time -->
202 <widget source="session.CurrentService" render="Label" position="576,129" size="100,20" font="Regular;18" halign="center" valign="center" backgroundColor="#06224f" shadowColor="#1d354c" shadowOffset="-1,-1" transparent="1" >
203 <convert type="ServicePosition">Remaining,Negate,ShowHours</convert>
207 def save_infobar_seek_config(self):
208 self.saved_config_speeds_forward = config.seek.speeds_forward.value
209 self.saved_config_speeds_backward = config.seek.speeds_backward.value
210 self.saved_config_enter_forward = config.seek.enter_forward.value
211 self.saved_config_enter_backward = config.seek.enter_backward.value
212 self.saved_config_seek_stepwise_minspeed = config.seek.stepwise_minspeed.value
213 self.saved_config_seek_stepwise_repeat = config.seek.stepwise_repeat.value
214 self.saved_config_seek_on_pause = config.seek.on_pause.value
215 self.saved_config_seek_speeds_slowmotion = config.seek.speeds_slowmotion.value
217 def change_infobar_seek_config(self):
218 config.seek.speeds_forward.value = [2, 4, 8, 16, 32, 64]
219 config.seek.speeds_backward.value = [8, 16, 32, 64]
220 config.seek.speeds_slowmotion.value = [ ]
221 config.seek.enter_forward.value = "2"
222 config.seek.enter_backward.value = "2"
223 config.seek.stepwise_minspeed.value = "Never"
224 config.seek.stepwise_repeat.value = "3"
225 config.seek.on_pause.value = "play"
227 def restore_infobar_seek_config(self):
228 config.seek.speeds_forward.value = self.saved_config_speeds_forward
229 config.seek.speeds_backward.value = self.saved_config_speeds_backward
230 config.seek.speeds_slowmotion.value = self.saved_config_seek_speeds_slowmotion
231 config.seek.enter_forward.value = self.saved_config_enter_forward
232 config.seek.enter_backward.value = self.saved_config_enter_backward
233 config.seek.stepwise_minspeed.value = self.saved_config_seek_stepwise_minspeed
234 config.seek.stepwise_repeat.value = self.saved_config_seek_stepwise_repeat
235 config.seek.on_pause.value = self.saved_config_seek_on_pause
237 def __init__(self, session, dvd_device = None, dvd_filelist = [ ], args = None):
238 Screen.__init__(self, session)
239 InfoBarBase.__init__(self)
240 InfoBarNotifications.__init__(self)
241 InfoBarCueSheetSupport.__init__(self, actionmap = "MediaPlayerCueSheetActions")
242 InfoBarShowHide.__init__(self)
243 HelpableScreen.__init__(self)
244 self.save_infobar_seek_config()
245 self.change_infobar_seek_config()
246 InfoBarSeek.__init__(self, useSeekBackHack=False)
247 InfoBarPVRState.__init__(self)
248 self.dvdScreen = self.session.instantiateDialog(DVDOverlay)
250 self.oldService = self.session.nav.getCurrentlyPlayingServiceReference()
251 self.session.nav.stopService()
252 self["audioLabel"] = Label("n/a")
253 self["subtitleLabel"] = Label("")
254 self["chapterLabel"] = Label("")
255 self.last_audioTuple = None
256 self.last_subtitleTuple = None
257 self.totalChapters = 0
258 self.currentChapter = 0
260 self.currentTitle = 0
262 self.__event_tracker = ServiceEventTracker(screen=self, eventmap=
264 iPlayableService.evStopped: self.__serviceStopped,
265 iPlayableService.evUser: self.__timeUpdated,
266 iPlayableService.evUser+1: self.__statePlay,
267 iPlayableService.evUser+2: self.__statePause,
268 iPlayableService.evUser+3: self.__osdFFwdInfoAvail,
269 iPlayableService.evUser+4: self.__osdFBwdInfoAvail,
270 iPlayableService.evUser+5: self.__osdStringAvail,
271 iPlayableService.evUser+6: self.__osdAudioInfoAvail,
272 iPlayableService.evUser+7: self.__osdSubtitleInfoAvail,
273 iPlayableService.evUser+8: self.__chapterUpdated,
274 iPlayableService.evUser+9: self.__titleUpdated,
275 iPlayableService.evUser+11: self.__menuOpened,
276 iPlayableService.evUser+12: self.__menuClosed
279 self["DVDPlayerDirectionActions"] = ActionMap(["DirectionActions"],
281 #MENU KEY DOWN ACTIONS
282 "left": self.keyLeft,
283 "right": self.keyRight,
285 "down": self.keyDown,
287 #MENU KEY REPEATED ACTIONS
288 "leftRepeated": self.doNothing,
289 "rightRepeated": self.doNothing,
290 "upRepeated": self.doNothing,
291 "downRepeated": self.doNothing,
294 "leftUp": self.doNothing,
295 "rightUp": self.doNothing,
296 "upUp": self.doNothing,
297 "downUp": self.doNothing,
300 self["OkCancelActions"] = ActionMap(["OkCancelActions"],
303 "cancel": self.keyCancel,
306 self["DVDPlayerPlaybackActions"] = HelpableActionMap(self, "DVDPlayerActions",
309 "dvdMenu": (self.enterDVDMenu, _("show DVD main menu")),
310 "toggleInfo": (self.toggleInfo, _("toggle time, chapter, audio, subtitle info")),
311 "nextChapter": (self.nextChapter, _("forward to the next chapter")),
312 "prevChapter": (self.prevChapter, _("rewind to the previous chapter")),
313 "nextTitle": (self.nextTitle, _("jump forward to the next title")),
314 "prevTitle": (self.prevTitle, _("jump back to the previous title")),
315 "tv": (self.askLeavePlayer, _("exit DVD player or return to file browser")),
316 "dvdAudioMenu": (self.enterDVDAudioMenu, _("(show optional DVD audio menu)")),
317 "nextAudioTrack": (self.nextAudioTrack, _("switch to the next audio track")),
318 "nextSubtitleTrack": (self.nextSubtitleTrack, _("switch to the next subtitle language")),
319 "seekBeginning": self.seekBeginning,
322 self["NumberActions"] = NumberActionMap( [ "NumberActions"],
324 "1": self.keyNumberGlobal,
325 "2": self.keyNumberGlobal,
326 "3": self.keyNumberGlobal,
327 "4": self.keyNumberGlobal,
328 "5": self.keyNumberGlobal,
329 "6": self.keyNumberGlobal,
330 "7": self.keyNumberGlobal,
331 "8": self.keyNumberGlobal,
332 "9": self.keyNumberGlobal,
333 "0": self.keyNumberGlobal,
336 self.onClose.append(self.__onClose)
339 self.dvd_device = dvd_device
340 self.physicalDVD = True
342 if fileExists(harddiskmanager.getCD()):
343 print "physical dvd found:", harddiskmanager.getCD()
344 self.dvd_device = harddiskmanager.getCD()
345 self.physicalDVD = True
347 self.dvd_device = None
348 self.physicalDVD = False
350 self.dvd_filelist = dvd_filelist
351 self.onFirstExecBegin.append(self.showFileBrowser)
354 self.old_aspect = open("/proc/stb/video/aspect", "r").read()
355 self.old_policy = open("/proc/stb/video/policy", "r").read()
356 self.old_wss = open("/proc/stb/denc/0/wss", "r").read()
358 def keyNumberGlobal(self, number):
359 print "You pressed number " + str(number)
360 self.session.openWithCallback(self.numberEntered, ChapterZap, number)
362 def numberEntered(self, retval):
363 # print self.servicelist
365 self.zapToNumber(retval)
367 def __serviceStopped(self):
368 self.dvdScreen.hide()
369 self.service.subtitle().disableSubtitles(self.session.current_dialog.instance)
371 def serviceStarted(self): #override InfoBarShowHide function
372 self.dvdScreen.show()
373 self.service.subtitle().enableSubtitles(self.dvdScreen.instance, None)
375 def doEofInternal(self, playing):
379 def __menuOpened(self):
382 self["NumberActions"].setEnabled(False)
384 def __menuClosed(self):
387 self["NumberActions"].setEnabled(True)
389 def setChapterLabel(self):
391 chapterOSD = "DVD Menu"
392 if self.currentTitle > 0:
393 chapterLCD = "%s %d" % (_("Chap."), self.currentChapter)
394 chapterOSD = "DVD %s %d/%d" % (_("Chapter"), self.currentChapter, self.totalChapters)
395 chapterOSD += " (%s %d/%d)" % (_("Title"), self.currentTitle, self.totalTitles)
396 self["chapterLabel"].setText(chapterOSD)
398 self.session.summary.updateChapter(chapterLCD)
405 def toggleInfo(self):
410 def __timeUpdated(self):
413 def __statePlay(self):
416 def __statePause(self):
419 def __osdFFwdInfoAvail(self):
420 self.setChapterLabel()
421 print "FFwdInfoAvail"
423 def __osdFBwdInfoAvail(self):
424 self.setChapterLabel()
425 print "FBwdInfoAvail"
427 def __osdStringAvail(self):
430 def __osdAudioInfoAvail(self):
431 audioTuple = self.service.info().getInfoObject(iServiceInformation.sUser+6)
432 print "AudioInfoAvail ", repr(audioTuple)
434 audioString = "%d: %s (%s)" % (audioTuple[0],audioTuple[1],audioTuple[2])
435 self["audioLabel"].setText(audioString)
436 if audioTuple != self.last_audioTuple and not self.in_menu:
438 self.last_audioTuple = audioTuple
440 def __osdSubtitleInfoAvail(self):
441 subtitleTuple = self.service.info().getInfoObject(iServiceInformation.sUser+7)
442 print "SubtitleInfoAvail ", repr(subtitleTuple)
445 if subtitleTuple[0] is not 0:
446 subtitleString = "%d: %s" % (subtitleTuple[0],subtitleTuple[1])
447 self["subtitleLabel"].setText(subtitleString)
448 if subtitleTuple != self.last_subtitleTuple and not self.in_menu:
450 self.last_subtitleTuple = subtitleTuple
452 def __chapterUpdated(self):
453 self.currentChapter = self.service.info().getInfo(iServiceInformation.sCurrentChapter)
454 self.totalChapters = self.service.info().getInfo(iServiceInformation.sTotalChapters)
455 self.setChapterLabel()
456 print "__chapterUpdated: %d/%d" % (self.currentChapter, self.totalChapters)
458 def __titleUpdated(self):
459 self.currentTitle = self.service.info().getInfo(iServiceInformation.sCurrentTitle)
460 self.totalTitles = self.service.info().getInfo(iServiceInformation.sTotalTitles)
461 self.setChapterLabel()
462 print "__titleUpdated: %d/%d" % (self.currentTitle, self.totalTitles)
466 def askLeavePlayer(self):
467 choices = [(_("Continue playing"), "play"), (_("Exit"), "exit")]
468 if not self.physicalDVD:
469 choices.insert(1,(_("Return to file browser"), "browser"))
470 self.session.openWithCallback(self.exitCB, ChoiceBox, title=_("Leave DVD Player?"), list = choices)
472 def nextAudioTrack(self):
474 self.service.keys().keyPressed(iServiceKeys.keyUser)
476 def nextSubtitleTrack(self):
478 self.service.keys().keyPressed(iServiceKeys.keyUser+1)
480 def enterDVDAudioMenu(self):
482 self.service.keys().keyPressed(iServiceKeys.keyUser+2)
484 def nextChapter(self):
486 self.service.keys().keyPressed(iServiceKeys.keyUser+3)
488 def prevChapter(self):
490 self.service.keys().keyPressed(iServiceKeys.keyUser+4)
494 self.service.keys().keyPressed(iServiceKeys.keyUser+5)
498 self.service.keys().keyPressed(iServiceKeys.keyUser+6)
500 def enterDVDMenu(self):
502 self.service.keys().keyPressed(iServiceKeys.keyUser+7)
504 def seekBeginning(self):
506 seekable = self.getSeek()
507 if seekable is not None:
510 def zapToNumber(self, number):
512 seekable = self.getSeek()
513 if seekable is not None:
514 print "seek to chapter %d" % number
515 seekable.seekChapter(number)
520 self.service.keys().keyPressed(iServiceKeys.keyRight)
524 self.service.keys().keyPressed(iServiceKeys.keyLeft)
528 self.service.keys().keyPressed(iServiceKeys.keyUp)
532 self.service.keys().keyPressed(iServiceKeys.keyDown)
538 self.service.keys().keyPressed(iServiceKeys.keyOk)
541 self.askLeavePlayer()
543 def showFileBrowser(self):
544 if self.physicalDVD and len(self.dvd_filelist) == 0:
545 if self.dvd_device == harddiskmanager.getCD():
546 self.session.openWithCallback(self.DVDdriveCB, MessageBox, text=_("Do you want to play DVD in drive?"), timeout=5 )
548 self.DVDdriveCB(True)
549 elif len(self.dvd_filelist) == 1:
550 self.FileBrowserClosed(self.dvd_filelist[0])
552 self.session.openWithCallback(self.FileBrowserClosed, FileBrowser, self.dvd_filelist)
554 def DVDdriveCB(self, answer):
556 self.FileBrowserClosed(self.dvd_device)
558 self.session.openWithCallback(self.FileBrowserClosed, FileBrowser)
559 self.physicalDVD = False
561 def FileBrowserClosed(self, val):
562 curref = self.session.nav.getCurrentlyPlayingServiceReference()
563 print "FileBrowserClosed", val
565 self.askLeavePlayer()
567 newref = eServiceReference(4369, 0, val)
568 print "play", newref.toString()
569 if curref is None or curref != newref:
570 self.session.nav.playService(newref)
571 self.service = self.session.nav.getCurrentService()
572 print "self.service", self.service
573 print "cur_dlg", self.session.current_dialog
575 def exitCB(self, answer):
576 if answer is not None:
577 if answer[1] == "exit":
581 if answer[1] == "browser":
582 #TODO check here if a paused dvd playback is already running... then re-start it...
586 self.showFileBrowser()
591 for i in (("/proc/stb/video/aspect", self.old_aspect), ("/proc/stb/video/policy", self.old_policy), ("/proc/stb/denc/0/wss", self.old_wss)):
593 open(i[0], "w").write(i[1])
595 print "restore", i[0], "failed"
596 self.restore_infobar_seek_config()
597 self.session.nav.playService(self.oldService)
599 def playLastCB(self, answer): # overwrite infobar cuesheet function
600 print "playLastCB", answer, self.resume_point
602 seek = self.service.seek()
604 seek.seekTo(self.resume_point)
605 pause = self.service.pause()
607 self.hideAfterResume()
609 def showAfterCuesheetOperation(self):
613 def createSummary(self):
616 #override some InfoBarSeek functions
618 self.setSeekState(self.SEEK_STATE_PLAY)
620 def calcRemainingTime(self):
623 def main(session, **kwargs):
624 session.open(DVDPlayer)
626 def menu(menuid, **kwargs):
627 if menuid == "mainmenu":
628 return [(_("DVD Player"), main, "dvd_player", 46)]
631 from Plugins.Plugin import PluginDescriptor
633 def filescan_open(list, session, **kwargs):
634 if len(list) == 1 and list[0].mimetype == "video/x-dvd":
635 splitted = list[0].path.split('/')
636 print "splitted", splitted
637 if len(splitted) > 2:
638 if splitted[1] == 'autofs':
639 session.open(DVDPlayer, dvd_device="/dev/%s" %(splitted[2]))
642 print "splitted[0]", splitted[1]
646 if x.mimetype == "video/x-dvd-iso":
647 dvd_filelist.append(x.path)
648 if x.mimetype == "video/x-dvd":
649 dvd_filelist.append(x.path.rsplit('/',1)[0])
650 session.open(DVDPlayer, dvd_filelist=dvd_filelist)
652 def filescan(**kwargs):
653 from Components.Scanner import Scanner, ScanPath
655 # Overwrite checkFile to only detect local
656 class LocalScanner(Scanner):
657 def checkFile(self, file):
658 return fileExists(file.path)
661 LocalScanner(mimetypes = ["video/x-dvd","video/x-dvd-iso"],
664 ScanPath(path = "video_ts", with_subdirs = False),
665 ScanPath(path = "", with_subdirs = False),
668 description = "Play DVD",
669 openfnc = filescan_open,
672 def Plugins(**kwargs):
673 return [PluginDescriptor(name = "DVDPlayer", description = "Play DVDs", where = PluginDescriptor.WHERE_MENU, fnc = menu),
674 PluginDescriptor(where = PluginDescriptor.WHERE_FILESCAN, fnc = filescan)]