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.previousEntry, _("play previous playlist entry")),
114 "next": (self.nextEntry, _("play next 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.__event_tracker = ServiceEventTracker(screen=self, eventmap=
151 #iPlayableService.evStart: self.__serviceStarted,
152 #iPlayableService.evSeekableStatusChanged: InfoBarSeek.__seekableStatusChanged,
154 iPlayableService.evEOF: self.__evEOF,
157 self.onClose.append(self.delMPTimer)
158 self.onClose.append(self.__onClose)
160 self.righttimer = False
161 self.rightKeyTimer = eTimer()
162 self.rightKeyTimer.timeout.get().append(self.rightTimerFire)
164 self.lefttimer = False
165 self.leftKeyTimer = eTimer()
166 self.leftKeyTimer.timeout.get().append(self.leftTimerFire)
168 self.currList = "filelist"
170 self.coverArtFileName = ""
172 self.playlistIOInternal = PlaylistIOInternal()
173 list = self.playlistIOInternal.open(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
176 self.playlist.addFile(x.ref)
177 self.playlist.updateList()
182 def createSummary(self):
183 return MediaPlayerLCDScreen
186 self.session.openWithCallback(self.exitCB, MessageBox, _("Do you really want to exit?"), timeout=5)
188 def exitCB(self, answer):
190 self.playlistIOInternal.clear()
191 for x in self.playlist.list:
192 self.playlistIOInternal.addService(ServiceReference(x[0]))
193 self.playlistIOInternal.save(resolveFilename(SCOPE_CONFIG, "playlist.e2pls"))
196 def checkSkipShowHideLock(self):
197 self.updatedSeekState()
203 self.session.nav.playService(self.oldService)
205 def delMPTimer(self):
206 del self.rightKeyTimer
207 del self.leftKeyTimer
209 def readTitleInformation(self):
210 currPlay = self.session.nav.getCurrentService()
211 if currPlay is not None:
212 stitle = currPlay.info().getInfoString(iServiceInformation.sTitle)
214 stitle = currPlay.info().getName().split('/')[-1]
216 self.updateMusicInformation( artist = currPlay.info().getInfoString(iServiceInformation.sArtist),
218 album = currPlay.info().getInfoString(iServiceInformation.sAlbum),
219 genre = currPlay.info().getInfoString(iServiceInformation.sGenre),
222 self.updateMusicInformation()
224 def updateMusicInformation(self, artist = "", title = "", album = "", year = "", genre = "", clear = False):
225 self.updateSingleMusicInformation("artist", artist, clear)
226 self.updateSingleMusicInformation("title", title, clear)
227 self.updateSingleMusicInformation("album", album, clear)
228 self.updateSingleMusicInformation("year", year, clear)
229 self.updateSingleMusicInformation("genre", genre, clear)
231 def updateSingleMusicInformation(self, name, info, clear):
232 if info != "" or clear:
233 if self[name].getText() != info:
234 self[name].setText(info)
236 def updateCoverArtPixmap(self, path):
237 while not path.endswith("/"):
239 pngname = path + "folder.png"
241 if not os_path.exists(pngname):
242 pngname = self["coverArt"].default_pixmap
243 if self.coverArtFileName != pngname:
244 self.coverArtFileName = pngname
245 self["coverArt"].instance.setPixmapFromFile(self.coverArtFileName)
248 self.lefttimer = True
249 self.leftKeyTimer.start(1000)
252 self.righttimer = True
253 self.rightKeyTimer.start(1000)
257 self.leftKeyTimer.stop()
258 self.lefttimer = False
259 self[self.currList].pageUp()
260 self.updateCurrentInfo()
264 self.rightKeyTimer.stop()
265 self.righttimer = False
266 self[self.currList].pageDown()
267 self.updateCurrentInfo()
269 def leftTimerFire(self):
270 self.leftKeyTimer.stop()
271 self.lefttimer = False
272 self.switchToFileList()
274 def rightTimerFire(self):
275 self.rightKeyTimer.stop()
276 self.righttimer = False
277 self.switchToPlayList()
279 def switchToFileList(self):
280 self.currList = "filelist"
281 self.filelist.selectionEnabled(1)
282 self.playlist.selectionEnabled(0)
283 self.updateCurrentInfo()
285 def switchToPlayList(self):
286 if len(self.playlist) != 0:
287 self.currList = "playlist"
288 self.filelist.selectionEnabled(0)
289 self.playlist.selectionEnabled(1)
290 self.updateCurrentInfo()
293 self[self.currList].up()
294 self.updateCurrentInfo()
297 self[self.currList].down()
298 self.updateCurrentInfo()
300 def showAfterSeek(self):
303 def showAfterCuesheetOperation(self):
306 def hideAfterResume(self):
309 # FIXME: maybe this code can be optimized
310 def updateCurrentInfo(self):
312 if self.currList == "filelist":
313 idx = self.filelist.getSelectionIndex()
314 r = self.filelist.list[idx]
321 self.summaries.setText(text,1)
324 if idx < len(self.filelist.list):
325 r = self.filelist.list[idx]
329 self.summaries.setText(text,3)
331 self.summaries.setText(" ",3)
334 if idx < len(self.filelist.list):
335 r = self.filelist.list[idx]
339 self.summaries.setText(text,4)
341 self.summaries.setText(" ",4)
344 if not self.filelist.canDescent():
345 r = self.filelist.getServiceRef()
349 self["currenttext"].setText(os_path.basename(text))
351 if self.currList == "playlist":
352 t = self.playlist.getSelection()
355 #display current selected entry on LCD
357 text = text.split('/')[-1]
358 self.summaries.setText(text,1)
359 self["currenttext"].setText(text)
360 idx = self.playlist.getSelectionIndex()
362 if idx < len(self.playlist):
363 currref = self.playlist.getServiceRefList()[idx]
364 text = currref.getPath()
365 text = text.split('/')[-1]
366 self.summaries.setText(text,3)
368 self.summaries.setText(" ",3)
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,4)
377 self.summaries.setText(" ",4)
380 if self.currList == "filelist":
381 if self.filelist.canDescent():
382 self.filelist.descent()
383 self.updateCurrentInfo()
387 if self.currList == "playlist":
388 selection = self["playlist"].getSelection()
389 self.changeEntry(self.playlist.getSelectionIndex())
393 if self.currList == "filelist":
394 if self.filelist.canDescent():
395 menu.append((_("add directory to playlist"), "copydir"))
397 menu.append((_("add files to playlist"), "copyfiles"))
398 menu.append((_("switch to playlist"), "playlist"))
400 menu.append((_("switch to filelist"), "filelist"))
402 menu.append((_("shuffle playlist"), "shuffle"))
404 menu.append((_("delete"), "delete"))
405 menu.append((_("clear playlist"), "clear"))
406 menu.append((_("hide player"), "hide"));
407 menu.append((_("save playlist"), "saveplaylist"));
408 menu.append((_("load playlist"), "loadplaylist"));
409 menu.append((_("delete saved playlist"), "deleteplaylist"));
410 self.session.openWithCallback(self.menuCallback, ChoiceBox, title="", list=menu)
412 def menuCallback(self, choice):
416 if choice[1] == "copydir":
417 self.copyDirectory(self.filelist.getSelection()[0])
418 elif choice[1] == "copyfiles":
420 self.playlist.clear()
421 self.copyDirectory(os_path.dirname(self.filelist.getSelection()[0].getPath()) + "/", recursive = False)
422 self.playServiceRefEntry(self.filelist.getServiceRef())
423 elif choice[1] == "playlist":
424 self.switchToPlayList()
425 elif choice[1] == "filelist":
426 self.switchToFileList()
427 elif choice[1] == "delete":
428 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
431 elif choice[1] == "clear":
433 self.playlist.clear()
434 self.switchToFileList()
435 elif choice[1] == "hide":
437 elif choice[1] == "saveplaylist":
439 elif choice[1] == "loadplaylist":
441 elif choice[1] == "deleteplaylist":
442 self.delete_saved_playlist()
443 elif choice[1] == "shuffle":
444 self.playlist.PlayListShuffle()
447 def showEventInformation(self):
448 from Screens.EventView import EventViewSimple
449 from ServiceReference import ServiceReference
450 evt = self[self.currList].getCurrentEvent()
452 self.session.open(EventViewSimple, evt, ServiceReference(self.getCurrent()))
454 # also works on filelist (?)
455 def getCurrent(self):
456 return self["playlist"].getCurrent()
458 def deletePlaylistEntry(self):
459 if self.currList == "playlist":
460 if self.playlist.getSelectionIndex() == self.playlist.getCurrentIndex():
464 def skip_listbegin(self):
465 if self.currList == "filelist":
466 self.filelist.moveToIndex(0)
468 self.playlist.moveToIndex(0)
469 self.updateCurrentInfo()
471 def skip_listend(self):
472 if self.currList == "filelist":
473 idx = len(self.filelist.list)
474 self.filelist.moveToIndex(idx - 1)
476 self.playlist.moveToIndex(len(self.playlist)-1)
477 self.updateCurrentInfo()
479 def save_playlist(self):
480 self.session.openWithCallback(self.save_playlist2,InputBox, title=_("Please enter filename (empty = use current date)"),windowTitle = _("Save Playlist"))
482 def save_playlist2(self, name):
486 name = strftime("%y%m%d_%H%M%S")
488 self.playlistIOInternal.clear()
489 for x in self.playlist.list:
490 self.playlistIOInternal.addService(ServiceReference(x[0]))
491 self.playlistIOInternal.save(resolveFilename(SCOPE_PLAYLIST) + name)
493 def load_playlist(self):
495 playlistdir = resolveFilename(SCOPE_PLAYLIST)
497 for i in os_listdir(playlistdir):
498 listpath.append((i,playlistdir + i))
500 print "Error while scanning subdirs ",e
501 self.session.openWithCallback(self.PlaylistSelected, ChoiceBox, title=_("Please select a playlist..."), list = listpath)
503 def PlaylistSelected(self,path):
505 self.clear_playlist()
506 self.playlistIOInternal = PlaylistIOInternal()
507 list = self.playlistIOInternal.open(path[1])
510 self.playlist.addFile(x.ref)
511 self.playlist.updateList()
513 def delete_saved_playlist(self):
515 playlistdir = resolveFilename(SCOPE_PLAYLIST)
517 for i in os_listdir(playlistdir):
518 listpath.append((i,playlistdir + i))
520 print "Error while scanning subdirs ",e
521 self.session.openWithCallback(self.DeletePlaylistSelected, ChoiceBox, title=_("Please select a playlist to delete..."), list = listpath)
523 def DeletePlaylistSelected(self,path):
525 self.delname = path[1]
526 self.session.openWithCallback(self.deleteConfirmed, MessageBox, _("Do you really want to delete %s?") % (path[1]))
528 def deleteConfirmed(self, confirmed):
530 os_remove(self.delname)
532 def clear_playlist(self):
534 self.playlist.clear()
535 self.switchToFileList()
537 def copyDirectory(self, directory, recursive = True):
538 print "copyDirectory", directory
539 filelist = FileList(directory, useServiceRef = True, isTop = True)
541 for x in filelist.getFileList():
542 if x[0][1] == True: #isDir
544 self.copyDirectory(x[0][0])
546 self.playlist.addFile(x[0][0])
547 self.playlist.updateList()
550 if self.filelist.getServiceRef().type == 4098: # playlist
551 ServiceRef = self.filelist.getServiceRef()
552 extension = ServiceRef.getPath()[ServiceRef.getPath().rfind('.') + 1:]
553 print "extension:", extension
554 if self.playlistparsers.has_key(extension):
555 playlist = self.playlistparsers[extension]()
556 list = playlist.open(ServiceRef.getPath())
558 self.playlist.addFile(x.ref)
560 self.playlist.addFile(self.filelist.getServiceRef())
561 self.playlist.updateList()
562 if len(self.playlist) == 1:
565 def addPlaylistParser(self, parser, extension):
566 self.playlistparsers[extension] = parser
569 next = self.playlist.getCurrentIndex() + 1
570 if next < len(self.playlist):
571 self.changeEntry(next)
573 def previousEntry(self):
574 next = self.playlist.getCurrentIndex() - 1
576 self.changeEntry(next)
578 def deleteEntry(self):
579 self.playlist.deleteFile(self.playlist.getSelectionIndex())
580 self.playlist.updateList()
581 if len(self.playlist) == 0:
582 self.switchToFileList()
584 def changeEntry(self, index):
585 self.playlist.setCurrentPlaying(index)
588 def playServiceRefEntry(self, serviceref):
589 serviceRefList = self.playlist.getServiceRefList()
590 for count in range(len(serviceRefList)):
591 if serviceRefList[count] == serviceref:
592 self.changeEntry(count)
595 def xplayEntry(self):
596 if self.currList == "playlist":
600 self.playlist.clear()
601 sel = self.filelist.getSelection()
603 if sel[1]: # can descent
604 # add directory to playlist
605 self.copyDirectory(sel[0])
607 # add files to playlist
608 self.copyDirectory(os_path.dirname(sel[0].getPath()) + "/", recursive = False)
609 if len(self.playlist) > 0:
613 if len(self.playlist.getServiceRefList()):
614 needsInfoUpdate = False
615 currref = self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()]
616 if self.session.nav.getCurrentlyPlayingServiceReference() is None or currref != self.session.nav.getCurrentlyPlayingServiceReference():
617 self.session.nav.playService(self.playlist.getServiceRefList()[self.playlist.getCurrentIndex()])
618 info = eServiceCenter.getInstance().info(currref)
619 description = info and info.getInfoString(currref, iServiceInformation.sDescription) or ""
620 self["title"].setText(description)
621 # display just playing musik on LCD
622 idx = self.playlist.getCurrentIndex()
623 currref = self.playlist.getServiceRefList()[idx]
624 text = currref.getPath()
625 text = text.split('/')[-1]
627 ext = text[-3:].lower()
629 # FIXME: the information if the service contains video (and we should hide our window) should com from the service instead
630 if ext not in ["mp3", "wav", "ogg"]:
633 needsInfoUpdate = True
634 self.summaries.setText(text,1)
636 # get the next two entries
638 if idx < len(self.playlist):
639 currref = self.playlist.getServiceRefList()[idx]
640 text = currref.getPath()
641 text = text.split('/')[-1]
642 self.summaries.setText(text,3)
644 self.summaries.setText(" ",3)
647 if idx < len(self.playlist):
648 currref = self.playlist.getServiceRefList()[idx]
649 text = currref.getPath()
650 text = text.split('/')[-1]
651 self.summaries.setText(text,4)
653 self.summaries.setText(" ",4)
655 idx = self.playlist.getCurrentIndex()
656 currref = self.playlist.getServiceRefList()[idx]
657 text = currref.getPath()
658 ext = text[-3:].lower()
659 if ext not in ["mp3", "wav", "ogg"]:
662 needsInfoUpdate = True
664 self.unPauseService()
665 if needsInfoUpdate == True:
666 self.updateCoverArtPixmap(currref.getPath())
668 pngname = self["coverArt"].default_pixmap
669 self.coverArtFileName = pngname
670 self["coverArt"].instance.setPixmapFromFile(self.coverArtFileName)
671 self.readTitleInformation()
673 def updatedSeekState(self):
674 if self.seekstate == self.SEEK_STATE_PAUSE:
675 self.playlist.pauseFile()
676 elif self.seekstate == self.SEEK_STATE_PLAY:
677 self.playlist.playFile()
678 elif self.seekstate in ( self.SEEK_STATE_FF_2X,
679 self.SEEK_STATE_FF_4X,
680 self.SEEK_STATE_FF_8X,
681 self.SEEK_STATE_FF_16X,
682 self.SEEK_STATE_FF_32X,
683 self.SEEK_STATE_FF_48X,
684 self.SEEK_STATE_FF_64X,
685 self.SEEK_STATE_FF_128X):
686 self.playlist.forwardFile()
687 elif self.seekstate in ( self.SEEK_STATE_BACK_8X,
688 self.SEEK_STATE_BACK_16X,
689 self.SEEK_STATE_BACK_32X,
690 self.SEEK_STATE_BACK_48X,
691 self.SEEK_STATE_BACK_64X,
692 self.SEEK_STATE_BACK_128X):
693 self.playlist.rewindFile()
695 def pauseEntry(self):
700 self.playlist.stopFile()
701 self.session.nav.playService(None)
702 self.updateMusicInformation(clear=True)
705 def unPauseService(self):
706 self.setSeekState(self.SEEK_STATE_PLAY)
709 class MediaPlayerLCDScreen(Screen):
711 <screen position="0,0" size="132,64" title="LCD Text">
712 <widget name="text1" position="4,0" size="132,35" font="Regular;16"/>
713 <widget name="text3" position="4,36" size="132,14" font="Regular;10"/>
714 <widget name="text4" position="4,49" size="132,14" font="Regular;10"/>
717 def __init__(self, session, parent):
718 Screen.__init__(self, session)
719 self["text1"] = Label("Mediaplayer")
720 self["text3"] = Label("")
721 self["text4"] = Label("")
723 def setText(self, text, line):
724 print "lcd set text:", text, line
726 if text[-4:] == ".mp3":
729 text = text + textleer*10
731 self["text1"].setText(text)
733 self["text3"].setText(text)
735 self["text4"].setText(text)
737 def main(session, **kwargs):
738 session.open(MediaPlayer)
740 def menu(menuid, **kwargs):
741 if menuid == "mainmenu":
742 return [(_("Media player"), main, "media_player", 45)]
745 def filescan_open(list, session, **kwargs):
746 from enigma import eServiceReference
748 mp = session.open(MediaPlayer)
750 mp.switchToPlayList()
752 ref = eServiceReference(4097, 0, file.path)
753 mp.playlist.addFile(ref)
755 # TODO: rather play first than last file?
756 mp.playServiceRefEntry(ref)
757 mp.playlist.updateList()
759 def filescan(**kwargs):
760 from Components.Scanner import Scanner, ScanPath
762 Scanner(mimetypes = ["video/mpeg"],
765 ScanPath(path = "", with_subdirs = False),
768 description = "View Movies...",
769 openfnc = filescan_open,
771 Scanner(mimetypes = ["audio/mpeg", "audio/x-wav", "application/ogg"],
774 ScanPath(path = "", with_subdirs = False),
777 description = "Play Music...",
778 openfnc = filescan_open,
782 from Plugins.Plugin import PluginDescriptor
783 def Plugins(**kwargs):
785 PluginDescriptor(name = "MediaPlayer", description = "Play back media files", where = PluginDescriptor.WHERE_MENU, fnc = menu),
786 PluginDescriptor(name = "MediaPlayer", where = PluginDescriptor.WHERE_FILESCAN, fnc = filescan)