1
# PiTiVi , Non-linear video editor
3
# ui/sourcefactories.py
5
# Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
7
# This program is free software; you can redistribute it and/or
8
# modify it under the terms of the GNU Lesser General Public
9
# License as published by the Free Software Foundation; either
10
# version 2.1 of the License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
# Lesser General Public License for more details.
17
# You should have received a copy of the GNU Lesser General Public
18
# License along with this program; if not, write to the
19
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20
# Boston, MA 02111-1307, USA.
23
Source and effects list widgets
34
from urllib import unquote
36
import pitivi.instance as instance
37
import pitivi.dnd as dnd
38
from pitivi.configure import get_pixmap_dir
39
from pitivi.signalgroup import SignalGroup
40
from pitivi.threads import Thread
42
from filelisterrordialog import FileListErrorDialog
44
from gettext import gettext as _
46
def beautify_length(length):
47
sec = length / gst.SECOND
51
return "%02dm%02ds" % (mins, sec)
54
return "%02dh%02dm%02ds" % (hours, mins, sec)
56
class SourceFactoriesWidget(gtk.Notebook):
58
Widget for the various source factories (files, effects, live,...)
63
gtk.Notebook.__init__(self)
67
""" set up the gui """
68
self.set_tab_pos(gtk.POS_TOP)
69
self.sourcelist = SourceListWidget()
70
self.append_page(self.sourcelist, gtk.Label(_("Clips")))
72
## FIXME: The following are deactivated until they do more than just
75
## self.audiofxlist = AudioFxListWidget()
76
## #self.audiofxlist.set_sensitive(False)
77
## self.append_page(self.audiofxlist, gtk.Label("Audio FX"))
78
## self.videofxlist = VideoFxListWidget()
79
## #self.videofxlist.set_sensitive(False)
80
## self.append_page(self.videofxlist, gtk.Label("Video FX"))
81
## self.transitionlist = TransitionListWidget()
82
## self.transitionlist.set_sensitive(False)
83
## self.append_page(self.transitionlist, gtk.Label("Transitions"))
90
COL_LENGTH) = range(5)
92
class SourceListWidget(gtk.VBox):
93
""" Widget for listing sources """
96
gtk.VBox.__init__(self)
99
# icon, infotext, objectfactory, uri, length
100
self.storemodel = gtk.ListStore(gtk.gdk.Pixbuf, str, object, str, str)
102
self.set_border_width(5)
106
self.scrollwin = gtk.ScrolledWindow()
107
self.scrollwin.set_policy(gtk.POLICY_NEVER,gtk.POLICY_AUTOMATIC)
108
self.scrollwin.set_shadow_type(gtk.SHADOW_ETCHED_IN)
111
self.popup = gtk.Menu()
112
additem = gtk.ImageMenuItem(_("Add Clips..."))
114
image.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
115
additem.set_image(image)
117
remitem = gtk.ImageMenuItem(_("Remove Clip"))
119
image.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
120
remitem.set_image(image)
121
playmenuitem = gtk.MenuItem(_("Play Clip"))
122
playmenuitem.connect("activate", self._playButtonClickedCb)
123
additem.connect("activate", self._addButtonClickedCb)
124
remitem.connect("activate", self._removeButtonClickedCb)
128
self.popup.append(additem)
129
self.popup.append(remitem)
130
self.popup.append(playmenuitem)
132
# import sources dialogbox
133
self._importDialog = None
134
self._lastFolder = None
137
# Displays icon, name, type, length
138
self.treeview = gtk.TreeView(self.storemodel)
139
self.treeview.connect("button-press-event", self._treeViewButtonPressEventCb)
140
self.treeview.connect("row-activated", self._rowActivatedCb)
141
self.treeview.set_property("rules_hint", True)
142
self.treeview.set_headers_visible(False)
143
tsel = self.treeview.get_selection()
144
tsel.set_mode(gtk.SELECTION_MULTIPLE)
146
pixbufcol = gtk.TreeViewColumn(_("Icon"))
147
pixbufcol.set_expand(False)
148
pixbufcol.set_spacing(5)
149
self.treeview.append_column(pixbufcol)
150
pixcell = gtk.CellRendererPixbuf()
151
pixcell.props.xpad = 6
152
pixbufcol.pack_start(pixcell)
153
pixbufcol.add_attribute(pixcell, 'pixbuf', COL_ICON)
155
namecol = gtk.TreeViewColumn(_("Information"))
156
self.treeview.append_column(namecol)
157
namecol.set_expand(True)
158
namecol.set_spacing(5)
159
namecol.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
160
namecol.set_min_width(150)
161
txtcell = gtk.CellRendererText()
162
txtcell.set_property("ellipsize", pango.ELLIPSIZE_END)
163
namecol.pack_start(txtcell)
164
namecol.add_attribute(txtcell, "markup", COL_INFOTEXT)
166
namecol = gtk.TreeViewColumn(_("Duration"))
167
namecol.set_expand(False)
168
self.treeview.append_column(namecol)
169
txtcell = gtk.CellRendererText()
170
txtcell.set_property("yalign", 0.0)
171
namecol.pack_start(txtcell)
172
namecol.add_attribute(txtcell, "markup", COL_LENGTH)
174
# Start up with tree view
175
self.scrollwin.add(self.treeview)
177
# Explanatory message label
178
textbox = gtk.EventBox()
179
textbox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('white'))
182
txtlabel = gtk.Label()
183
txtlabel.set_padding(10, 10)
184
txtlabel.set_line_wrap(True)
185
txtlabel.set_line_wrap_mode(pango.WRAP_WORD)
186
txtlabel.set_justify(gtk.JUSTIFY_CENTER)
188
_("<span size='x-large'>Import your clips by dragging them here or "
189
"by using the buttons above.</span>"))
190
textbox.add(txtlabel)
191
self.txtlabel = txtlabel
193
self.textbox = textbox
195
self.pack_start(self.textbox, expand=True, fill=True)
196
self.reorder_child(self.textbox, 0)
197
self.showingTreeView = False
199
self.dragMotionSigId = self.txtlabel.connect("drag-motion",
202
self.infostub = InfoStub()
203
self.infostub.connect("remove-me", self._removeInfoStub)
205
# Connect to project. We must remove and reset the callbacks when
207
self.project_signals = SignalGroup()
208
self._connectToProject(instance.PiTiVi.current)
209
instance.PiTiVi.connect("new-project-loaded",
210
self._newProjectLoadedCb)
211
instance.PiTiVi.connect("new-project-failed",
212
self._newProjectFailedCb)
215
icontheme = gtk.icon_theme_get_default()
216
self.filepixbuf = icontheme.load_icon("misc", 32, 0)
217
if not self.filepixbuf:
218
self.filepixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(pixdir, "pitivi-file.png"))
219
self.audiofilepixbuf = icontheme.load_icon("audio-x-generic", 32, 0)
220
if not self.audiofilepixbuf:
221
self.audiofilepixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(pixdir, "pitivi-sound.png"))
222
self.videofilepixbuf = icontheme.load_icon("video-x-generic", 32, 0)
223
if not self.videofilepixbuf:
224
self.videofilepixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(pixdir, "pitivi-video.png"))
227
self.drag_dest_set(gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_MOTION,
228
[dnd.URI_TUPLE, dnd.FILE_TUPLE],
230
self.connect("drag_data_received", self._dndDataReceivedCb)
232
self.treeview.drag_source_set(gtk.gdk.BUTTON1_MASK,
233
[dnd.URI_TUPLE, dnd.FILESOURCE_TUPLE],
235
self.treeview.connect("drag_begin", self._dndTreeBeginCb)
236
self.treeview.connect("drag_data_get", self._dndDataGetCb)
238
# Hack so that the views have the same method as self
239
self.treeview.getSelectedItems = self.getSelectedItems
242
self.errorDialogBox = None
244
def _connectToProject(self, project):
245
"""Connect signal handlers to a project.
247
This first disconnects any handlers connected to an old project.
248
If project is None, this just disconnects any connected handlers.
251
self.project_signals.connect(
252
project.sources, "file_added", None, self._fileAddedCb)
253
self.project_signals.connect(
254
project.sources, "file_removed", None, self._fileRemovedCb)
255
self.project_signals.connect(
256
project.sources, "not_media_file", None, self._notMediaFileCb)
257
self.project_signals.connect(
258
project.sources, "ready", None, self._sourcesStoppedImportingCb)
259
self.project_signals.connect(
260
project.sources, "starting", None, self._sourcesStartedImportingCb)
263
## Explanatory message methods
265
def _displayTreeView(self, displayed=True, usesignals=True):
266
""" Display the tree view in the scrolled window.
267
If displayed is False, then the default explanation message will be
269
If usesignals is True, then signals on the mesagewindow will be
273
if self.showingTreeView:
275
gst.debug("displaying tree view")
276
self.remove(self.textbox)
279
if self.dragMotionSigId:
280
self.txtlabel.disconnect(self.dragMotionSigId)
281
self.dragMotionSigId = 0
282
self.pack_start(self.scrollwin)
283
self.reorder_child(self.scrollwin, 0)
284
self.scrollwin.show_all()
285
self.showingTreeView = True
287
if not self.showingTreeView:
289
gst.debug("hiding tree view")
290
self.remove(self.scrollwin)
291
self.scrollwin.hide()
292
self.pack_start(self.textbox)
293
self.reorder_child(self.textbox, 0)
295
self.showingTreeView = False
297
def _dragMotionCb(self, unused_layout, unused_context, x, unused_y,
300
gobject.idle_add(self._displayTreeView, True, False)
302
def showImportSourcesDialog(self, select_folders=False):
303
if self._importDialog:
307
chooser_action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
308
dialogtitle = _("Import a folder")
310
chooser_action = gtk.FILE_CHOOSER_ACTION_OPEN
311
dialogtitle = _("Import a clip")
313
self._importDialog = gtk.FileChooserDialog(dialogtitle, None,
315
(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE,
316
gtk.STOCK_ADD, gtk.RESPONSE_OK))
318
self._importDialog.set_default_response(gtk.RESPONSE_OK)
319
self._importDialog.set_select_multiple(True)
320
self._importDialog.set_modal(False)
322
self._importDialog.set_current_folder(self._lastFolder)
323
self._importDialog.connect('response', self._dialogBoxResponseCb, select_folders)
324
self._importDialog.connect('close', self._dialogBoxCloseCb)
325
self._importDialog.show()
327
def addFiles(self, list):
328
""" Add files to the list """
329
instance.PiTiVi.current.sources.addUris(list)
331
def addFolders(self, list):
332
""" walks the trees of the folders in the list and adds the files it finds """
333
instance.PiTiVi.threads.addThread(PathWalker, list, instance.PiTiVi.current.sources.addUris)
335
def _addFactory(self, factory):
337
pixbuf = gtk.gdk.pixbuf_new_from_file(factory.thumbnail)
340
thumbnail = self.videofilepixbuf
341
elif factory.is_audio:
342
thumbnail = self.audiofilepixbuf
343
#FIXME: should this be continue??
345
if not factory.video_info_stream:
346
desiredheight = 64 * pixbuf.get_height() / pixbuf.get_width()
348
vi = factory.video_info_stream
349
desiredheight = int(64 / float(vi.dar))
350
thumbnail = pixbuf.scale_simple(64, desiredheight, gtk.gdk.INTERP_BILINEAR)
351
self.storemodel.append([thumbnail,
352
factory.getPrettyInfo(),
355
"<b>%s</b>" % beautify_length(factory.length)])
356
self._displayTreeView()
358
# sourcelist callbacks
360
def _fileAddedCb(self, unused_sourcelist, factory):
361
""" a file was added to the sourcelist """
362
self._addFactory(factory)
364
def _fileRemovedCb(self, unused_sourcelist, uri):
365
""" the given uri was removed from the sourcelist """
366
# find the good line in the storemodel and remove it
367
model = self.storemodel
369
if uri == row[COL_URI]:
370
model.remove(row.iter)
373
self._displayTreeView(False)
375
def _notMediaFileCb(self, unused_sourcelist, uri, reason, extra):
376
""" The given uri isn't a media file """
377
self.infostub.addErrors(uri, reason, extra)
379
def _sourcesStartedImportingCb(self, unused_sourcelist):
380
if not self.infostub.showing:
381
self.pack_start(self.infostub, expand=False)
382
self.infostub.startingImport()
384
def _sourcesStoppedImportingCb(self, unused_sourcelist):
385
self.infostub.stoppingImport()
387
def _removeInfoStub(self, infostub):
388
self.remove(self.infostub)
390
## Error Dialog Box callbacks
392
def _errorDialogBoxCloseCb(self, unused_dialog):
393
self.errorDialogBox.destroy()
394
self.errorDialogBox = None
396
def _errorDialogBoxResponseCb(self, unused_dialog, unused_response):
397
self.errorDialogBox.destroy()
398
self.errorDialogBox = None
401
## Import Sources Dialog Box callbacks
403
def _dialogBoxResponseCb(self, dialogbox, response, select_folders):
404
gst.debug("response:%r" % response)
405
if response == gtk.RESPONSE_OK:
406
self._lastFolder = dialogbox.get_current_folder()
407
filenames = dialogbox.get_uris()
409
self.addFolders(filenames)
411
self.addFiles(filenames)
414
self._importDialog = None
416
def _dialogBoxCloseCb(self, unused_dialogbox):
418
self._importDialog = None
421
## UI Button callbacks
423
def _addButtonClickedCb(self, unused_widget=None):
424
""" called when a user clicks on the add button """
425
self.showImportSourcesDialog()
427
def _removeButtonClickedCb(self, unused_widget=None):
428
""" Called when a user clicks on the remove button """
429
tsel = self.treeview.get_selection()
430
if tsel.count_selected_rows() < 1:
432
model, selected = tsel.get_selected_rows()
433
# Sort the list in reverse order so we remove from
434
# the end and make sure that the paths are always valid
435
selected.sort(reverse=True)
436
for path in selected:
437
uri = model[path][COL_URI]
438
del instance.PiTiVi.current.sources[uri]
440
def _errorDialogButtonClickedCb(self, unused_widget=None):
441
""" called when the user click on the import errors button """
442
if self.errorDialogBox:
443
self.errorDialogBox.show()
444
if self.errorDialogButton.parent:
445
self.bothbox.remove(self.errorDialogButton)
447
def _playButtonClickedCb(self, unused_widget):
448
""" Called when a user clicks on the play button """
449
# get the selected filesourcefactory
450
model, paths = self.treeview.get_selection().get_selected_rows()
454
factory = model[path][COL_FACTORY]
455
gst.debug("Let's play %s" % factory.name)
456
instance.PiTiVi.playground.playTemporaryFilesourcefactory(factory)
458
def _treeViewButtonPressEventCb(self, unused_treeview, event):
459
if event.button == 3:
460
self.popup.popup(None, None, None, event.button, event.time)
462
def _rowActivatedCb(self, unused_treeview, path, unused_column):
463
factory = self.storemodel[path][COL_FACTORY]
464
instance.PiTiVi.playground.playTemporaryFilesourcefactory(factory)
466
def _newProjectLoadedCb(self, unused_pitivi, project):
467
# clear the storemodel
468
self.storemodel.clear()
469
self._connectToProject(project)
470
# synchronize the storemodel with the new project's sourcelist
471
for uri, factory in project.sources:
472
gst.log("loading uri %s" % uri)
473
self._addFactory(factory)
475
def _newProjectFailedCb(self, unused_pitivi, unused_reason,
477
self.storemodel.clear()
478
self.project_signals.disconnectAll()
483
def _dndDataReceivedCb(self, unused_widget, unused_context, unused_x,
484
unused_y, selection, targetType, unused_time):
486
if path[:7] == "file://":
487
# either it's on local system and we know if it's a directory
488
return os.path.isfile(path[7:])
490
# or it's not, in which case we assume it's a file
492
# or it's on local system with "file://"
493
return os.path.isfile(path)
495
gst.debug("targetType:%d, selection.data:%r" % (targetType, selection.data))
497
if targetType == dnd.TYPE_URI_LIST:
498
incoming = [unquote(x.strip('\x00')) for x in selection.data.strip().split("\r\n") if x.strip('\x00')]
499
filenames = [x for x in incoming if isfile(x)]
500
directories = [x for x in incoming if not isfile(x)]
501
elif targetType == dnd.TYPE_TEXT_PLAIN:
502
incoming = selection.data.strip()
504
filenames = [incoming]
506
directories = [incoming]
508
self.addFolders(directories)
509
self.addFiles(filenames)
511
def _dndTreeBeginCb(self, unused_widget, context):
512
gst.info("tree drag_begin")
513
model, paths = self.treeview.get_selection().get_selected_rows()
515
context.drag_abort(int(time.time()))
517
row = model[paths[0]]
518
self.treeview.drag_source_set_icon_pixbuf(row[COL_ICON])
520
def getSelectedItems(self):
521
""" returns a list of selected items uri """
522
model, rows = self.treeview.get_selection().get_selected_rows()
523
return [model[path][COL_URI] for path in rows]
525
def _dndDataGetCb(self, unused_widget, unused_context, selection,
526
targetType, unused_eventTime):
527
gst.info("data get, type:%d" % targetType)
528
uris = self.getSelectedItems()
531
if targetType == dnd.TYPE_PITIVI_FILESOURCE:
532
selection.set(selection.target, 8,
534
elif targetType == dnd.TYPE_URI_LIST:
535
selection.set(selection.target, 8,
536
string.join(uris, "\n"))
538
class AudioFxListWidget(gtk.VBox):
539
""" Widget for listing video effects """
542
gtk.VBox.__init__(self)
543
self.set_border_width(5)
546
self.storemodel = gtk.ListStore(str, str, object)
548
self.scrollwin = gtk.ScrolledWindow()
549
self.scrollwin.set_policy(gtk.POLICY_NEVER,
550
gtk.POLICY_AUTOMATIC)
551
self.pack_start(self.scrollwin)
553
self.iconview = gtk.IconView(self.storemodel)
554
self.treeview = gtk.TreeView(self.storemodel)
556
namecol = gtk.TreeViewColumn(_("Name"))
557
self.treeview.append_column(namecol)
558
namecell = gtk.CellRendererText()
559
namecol.pack_start(namecell)
560
namecol.add_attribute(namecell, "text", 0)
562
namecol = gtk.TreeViewColumn(_("Description"))
563
self.treeview.append_column(namecol)
564
namecell = gtk.CellRendererText()
565
namecell.set_property("ellipsize", pango.ELLIPSIZE_END)
566
namecol.pack_start(namecell)
567
namecol.add_attribute(namecell, "text", 1)
569
self.scrollwin.add(self.treeview)
573
def _fillUpModel(self):
574
for factory in instance.PiTiVi.effects.simple_audio:
575
self.storemodel.append([factory.get_longname(),
576
factory.get_description(),
581
COL_FACTORY) = range(3)
583
class VideoFxListWidget(gtk.VBox):
584
""" Widget for listing video effects """
587
gtk.VBox.__init__(self)
588
self.set_border_width(5)
591
self.storemodel = gtk.ListStore(str, str, object)
593
self.scrollwin = gtk.ScrolledWindow()
594
self.scrollwin.set_policy(gtk.POLICY_NEVER,
595
gtk.POLICY_AUTOMATIC)
596
self.pack_start(self.scrollwin)
598
self.iconview = gtk.IconView(self.storemodel)
599
self.treeview = gtk.TreeView(self.storemodel)
601
namecol = gtk.TreeViewColumn(_("Name"))
602
self.treeview.append_column(namecol)
603
namecell = gtk.CellRendererText()
604
namecol.pack_start(namecell)
605
namecol.add_attribute(namecell, "text", COL_NAME)
607
namecol = gtk.TreeViewColumn(_("Description"))
608
self.treeview.append_column(namecol)
609
namecell = gtk.CellRendererText()
610
namecell.set_property("ellipsize", pango.ELLIPSIZE_END)
611
namecol.pack_start(namecell)
612
namecol.add_attribute(namecell, "text", COL_DESCRIPTION)
614
self.scrollwin.add(self.treeview)
618
def _fillUpModel(self):
619
for factory in instance.PiTiVi.effects.simple_video:
620
self.storemodel.append([factory.get_longname(),
621
factory.get_description(),
625
class TransitionListWidget(gtk.VBox):
626
""" Widget for listing transitions """
629
gtk.VBox.__init__(self)
630
self.iconview = gtk.IconView()
631
self.treeview = gtk.TreeView()
632
self.pack_start(self.iconview)
634
class InfoStub(gtk.HBox):
636
Box used to display information on the current state of the lists
640
"remove-me" : (gobject.SIGNAL_RUN_LAST,
646
gtk.HBox.__init__(self)
649
self._importingmessage = _("Importing clips...")
650
self._errorsmessage = _("Error(s) occured while importing")
651
self._errormessage = _("An error occured while importing")
656
anim = gtk.gdk.PixbufAnimation(get_pixmap_dir() + "/busy.gif")
657
self.busyanim = gtk.image_new_from_animation(anim)
659
self.erroricon = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING,
660
gtk.ICON_SIZE_SMALL_TOOLBAR)
662
self.infolabel = gtk.Label(self._importingmessage)
663
self.infolabel.set_alignment(0, 0.5)
665
self.questionbutton = gtk.Button()
666
self.questionbutton.set_image(gtk.image_new_from_stock(gtk.STOCK_INFO,
667
gtk.ICON_SIZE_SMALL_TOOLBAR))
668
self.questionbutton.connect("clicked", self._questionButtonClickedCb)
669
self._questionshowing = False
671
self.pack_start(self.busyanim, expand=False)
672
self._busyshowing = True
673
self.pack_start(self.infolabel, expand=True, fill=True)
675
def startingImport(self):
678
# if we're already showing and we have errors, show spinner
682
self.infolabel.set_text(self._importingmessage)
683
self._showQuestionButton(False)
686
def stoppingImport(self):
688
self._showErrorIcon()
689
if len(self.errors) > 1:
690
self.infolabel.set_text(self._errorsmessage)
692
self.infolabel.set_text(self._errormessage)
693
self._showQuestionButton()
696
self.emit("remove-me")
698
def addErrors(self, *args):
699
self.errors.append(args)
701
def _showBusyAnim(self):
702
if self._busyshowing:
704
self.remove(self.erroricon)
705
self.pack_start(self.busyanim, expand=False)
706
self.reorder_child(self.busyanim, 0)
708
self._busyshowing = True
710
def _showErrorIcon(self):
711
if not self._busyshowing:
713
self.remove(self.busyanim)
714
self.pack_start(self.erroricon, expand=False)
715
self.reorder_child(self.erroricon, 0)
716
self.erroricon.show()
717
self._busyshowing = False
719
def _showQuestionButton(self, visible=True):
720
if visible and not self._questionshowing:
721
self.pack_start(self.questionbutton, expand=False)
722
self.questionbutton.show()
723
self._questionshowing = True
724
elif not visible and self._questionshowing:
725
self.remove(self.questionbutton)
726
self._questionshowing = False
728
def _errorDialogBoxCloseCb(self, dialog):
731
def _errorDialogBoxResponseCb(self, dialog, unused_response):
734
def _questionButtonClickedCb(self, unused_button):
735
if len(self.errors) > 1:
736
msgs = (_("Error while analyzing files"),
737
_("The following files can not be used with PiTiVi."))
739
msgs = (_("Error while analyzing a file"),
740
_("The following file can not be used with PiTiVi."))
742
dbox = FileListErrorDialog(*msgs)
743
dbox.connect("close", self._errorDialogBoxCloseCb)
744
dbox.connect("response", self._errorDialogBoxResponseCb)
745
for uri,reason,extra in self.errors:
746
dbox.addFailedFile(uri, reason, extra)
751
self.emit("remove-me")
764
class PathWalker(Thread):
766
Thread for recursively searching in a list of directories
769
def __init__(self, paths, callback):
770
Thread.__init__(self)
771
gst.log("New PathWalker for %s" % paths)
773
self.callback = callback
774
self.stopme = threading.Event()
777
for folder in self.paths:
778
gst.log("folder %s" % folder)
779
if folder.startswith("file://"):
780
folder = folder[len("file://"):]
781
for path, dirs, files in os.walk(folder):
782
if self.stopme.isSet():
786
uriList.append("file://%s" % os.path.join(path, afile))
788
self.callback(uriList)