[Syncropated-commits] r161 - trunk/src
zimmerle at garage.maemo.org
zimmerle at garage.maemo.org
Tue Jan 30 01:44:05 EET 2007
Author: zimmerle
Date: 2007-01-30 01:44:04 +0200 (Tue, 30 Jan 2007)
New Revision: 161
Added:
trunk/src/LocalMusicManager.py
Log:
LocalMusicManager first import
Added: trunk/src/LocalMusicManager.py
===================================================================
--- trunk/src/LocalMusicManager.py 2007-01-29 22:21:49 UTC (rev 160)
+++ trunk/src/LocalMusicManager.py 2007-01-29 23:44:04 UTC (rev 161)
@@ -0,0 +1,752 @@
+# LocalMusicManager
+#
+# Copyright (c) 2006 INdT (Instituto Nokia de Technologia)
+#
+# Author: Felipe Zimmerle <felipe at zimmerle.org>
+# Kenneth Rohde Christiansen <kenneth.christiansen at gmail.com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+
+import gobject
+import gtk
+import eyeD3
+import mutagen
+import mutagen.easyid3
+import os
+import sys
+import re
+from Logger import Logger
+from Configuration import Configuration
+from Icons import Icons
+from Utils import Utils
+from ElementMusic import ElementMusic
+
+log = Logger()
+config = Configuration()
+icons = Icons()
+utils = Utils()
+
+class LocalMusicManager(gobject.GObject):
+ """
+ Manager the music files, reading the id3 tags, indexing it and also displaying in a correct
+ format according to filters criteria
+
+ """
+
+ __gsignals__ = {
+ 'media_content_change' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING,
+ gobject.TYPE_PYOBJECT))
+ }
+
+ (
+ ICON,
+ TITLE,
+ TRACKNUMBER,
+ ARTIST,
+ ALBUM,
+ DATE,
+ BITRATE,
+ PLAY_TIME,
+ GENRE,
+ PLAYCOUNT,
+ SIZE,
+ PATH,
+ FILE_PATH,
+ OBJECT
+ ) = range(14)
+
+ # Configuration key, label, value function, editable, indexable, #
+ OPTIONAL_TREEVIEW_COLUMNS = [('audio_view_track_number_check', 'Track Number',
+ 'tracknumber', True, False, TRACKNUMBER),
+ ('audio_view_artist_check', 'Artist',
+ 'artist', True, True, ARTIST),
+ ('audio_view_album_check', 'Album',
+ 'album', True, True, ALBUM),
+ ('audio_view_year_check', 'Year',
+ 'date', True, False, DATE),
+ ('audio_view_quality_check', 'Quality',
+ 'getBitRateString', False, False, BITRATE),
+ ('audio_view_time_check', 'Time',
+ 'getPlayTimeString', False, False, PLAY_TIME),
+ ('audio_view_genre_check', 'Genre',
+ 'genre', True, True, GENRE),
+ ('audio_view_play_count_check', 'Play Counter',
+ 'getPlayCount', True, False, PLAYCOUNT),
+ ('audio_view_file_size_check', 'File Size',
+ 'getSize', False, False, SIZE),
+ ('audio_view_file_path_check', 'File Path',
+ 'getPath', False, True, FILE_PATH)]
+
+
+
+
+ TYPE = 'audio'
+
+ __model = None
+ __tree_view = None
+ __root_node = None
+ __total_loaded_files = 0
+ __filter_key = None
+
+ __search_box_entry = None
+ __search_box_timeout = 0
+
+
+
+ def __init__(self):
+ """
+ Constructor
+
+ """
+ gobject.GObject.__init__(self)
+ log.info('LocalMusicManager :: Starting ...')
+ config.connect('configuration_changed', self.cb_configuration_changed)
+
+ self.__filter_count_elements = {}
+ self.__element_count_timeout = 0
+
+ self.__content_filter_artist = {}
+ self.__content_filter_album = {}
+ self.__content_filter_genre = {}
+
+ self.filter_nodes = {}
+ self.master_filter_key = None
+
+ self.master_filter_column = LocalMusicManager.ARTIST
+
+
+ def cb_configuration_changed(self, emitent, key, value):
+ """
+ Callback called when a configuration changed,
+ used show the correct columns at the music treeview.
+
+ """
+ log.debug('LocalMusicManager :: Configuration changed: ' + str(key))
+ for i in self.OPTIONAL_TREEVIEW_COLUMNS:
+ if i[0] == key:
+ self.__reconfigure_columns(key, value)
+ return
+
+
+ def __reconfigure_columns(self, key, value):
+ """
+ Used to reconfigure the music tree view.
+
+ """
+ log.debug('LocalMusicManager :: I Got the configuration key: ' +
+ key + ' in the TreeView.')
+ title = None
+ for i in self.OPTIONAL_TREEVIEW_COLUMNS:
+ if i[0] == key:
+ title = i[1]
+
+ if title == None:
+ return
+
+ for i in self.__tree_view.get_columns():
+ if i.get_widget() == None:
+ continue
+
+ log.debug('LocalMusicManager :: Looping inside the tree views' +
+ ' columns ... key: ' + key + ' tree v.: ' +
+ str(i.get_widget().get_text()) + ' - value: ' + value +
+ '.')
+
+ if str(i.get_widget().get_text()) == title:
+ if value == str(True):
+ i.set_visible(True)
+ else:
+ i.set_visible(False)
+
+
+ def content_verify_func(self, file):
+ """
+ Function called to check if the file is know and supported.
+
+ """
+ if not os.path.isfile(file):
+ return False
+ # Faster but not that right way:
+ return eyeD3.isMp3File(file)
+
+
+ def __get_header_menu(self):
+ """
+ Return the menu used when the user clicked with the 3rd button in the
+ item treeview header.
+
+ """
+ menu = gtk.Menu()
+
+ for i in self.OPTIONAL_TREEVIEW_COLUMNS:
+ new_bnt = gtk.CheckMenuItem(i[1])
+ new_bnt.set_active(config.getValue(i[0]) == str(True))
+ new_bnt.connect("toggled", self.__header_menu_set_value, i[0])
+ menu.append(new_bnt)
+
+ return menu
+
+
+ def __header_menu_set_value(self, bnt, prop):
+ """
+ Called when the user enable or disable the visualisation
+ of a column, at the header 3rd button menu.
+
+ """
+ config.setValue(prop, str(bnt.get_active()))
+
+
+ def __get_menu(self, path):
+ """
+ Return the 3rd button menu, when the focous is an item.
+
+ """
+ iter = self.__model.get_iter(path)
+ value = self.__model.get_value(iter, LocalMusicManager.FILE_PATH)
+
+ menu = gtk.Menu()
+
+ if value != '':
+ play_in_amarok_menu_item = gtk.ImageMenuItem("Play in amarok")
+ play_in_amarok_menu_item.connect("activate",
+ self.cb_tree_view_play_in_amarok_button_pressed, iter)
+ play_in_amarok_menu_item.set_image(
+ icons.load_icon_as_image('media-playback-start', 12))
+ menu.append(play_in_amarok_menu_item)
+
+
+ if log.getLevel() == 'debug':
+ debug_menu_item = gtk.ImageMenuItem("Debug")
+ debug_menu_item.connect("activate",
+ self.cb_tree_view_debug_button_pressed, iter)
+ debug_menu_item.set_image(
+ icons.load_icon_as_image('gtk-info', 12))
+ menu.append(debug_menu_item)
+
+
+ return menu
+
+
+ def cb_tree_view_play_in_amarok_button_pressed(self, menu_item, iter):
+ """
+ To play a song at amarok
+
+ """
+ log.info('Playing ' + str(iter) + ' in amarok...')
+ value = self.__model.get_value(iter, LocalMusicManager.FILE_PATH)
+ os.system('dcop amarok playlist playMedia "' + value + '"')
+
+
+ def cb_tree_view_debug_button_pressed(self, menu_item, iter):
+ """
+ Just print the iter values at the console (when in debug mode)
+
+ """
+ log.debug('LocalMusicManager :: Debug button pressed. (iter: ' +
+ str(iter) + '.')
+ value = self.__model.get_value(iter, LocalMusicManager.FILE_PATH)
+ log.debug('LocalMusicManager :: Item: ' + str(value))
+
+
+ def cb_tree_view_button_pressed(self, tree_view, event):
+ """
+ Capture the button pressed event and decide which function
+ will handle it.
+
+ """
+ log.debug('LocalMusicManager :: Tree view clicked: ' + str(tree_view))
+ if event.button == 3:
+ x = int(event.x)
+ y = int(event.y)
+ time = event.time
+ pthinfo = tree_view.get_path_at_pos(x, y)
+ if pthinfo != None:
+ path, col, cellx, celly = pthinfo
+ tree_view.grab_focus()
+ tree_view.set_cursor(path, col, 0)
+
+ menu = self.__get_menu(path)
+ menu.show_all()
+
+ menu.popup( None, None, None, event.button, time)
+ return True
+
+
+ def cb_tree_view_header_button_pressed(self, tree_view, event):
+ """
+ Capture the button pressed event and decide which function
+ will handle it.
+
+ """
+ log.debug('LocalMusicManager :: Tree view header clicked: ' +
+ str(tree_view))
+ if event.button == 3:
+ x = int(event.x)
+ y = int(event.y)
+ time = event.time
+ menu = self.__get_header_menu()
+ menu.show_all()
+ menu.popup( None, None, None, event.button, time)
+ return True
+
+
+ def cb_visible_items(self, model, iter):
+ """
+ Decide which item is visible or not. Called by gtkTreeView
+
+ """
+ key = self.__filter_key
+ master_key = self.master_filter_key
+ value = model.get_value(iter, LocalMusicManager.TITLE)
+ value_master_filter = model.get_value(iter,
+ int(self.master_filter_column))
+
+
+ if value == None:
+ return True
+
+ log.debug('LocalMusicManager :: master value : ' + str(value_master_filter))
+
+ if (value_master_filter != None and master_key != None) and value_master_filter != master_key:
+ return False
+
+
+
+ if (key == None or value.find(key) != -1):
+ log.debug('LocalMusicManager :: visible_items :: Count: ' +
+ str(len(self.__filter_count_elements)))
+
+ play_time = model.get_value(iter, LocalMusicManager.PLAY_TIME)
+ if play_time == None:
+ play_time = "0:00"
+
+ file_size = model.get_value(iter, LocalMusicManager.SIZE)
+ self.__filter_count_elements[value] = (int(play_time.split(':')[0])*60 +
+ int(play_time.split(':')[1]), file_size)
+
+ if self.__element_count_timeout != 0:
+ gobject.source_remove (self.__element_count_timeout)
+ self.__element_count_timeout = 0
+
+ self.__element_count_timeout = gobject.timeout_add(300, self.update_ui)
+
+ return True
+
+ return False
+
+
+ def update_ui(self):
+ """
+ Update the status widgets.
+ """
+
+ total_time = 0
+ total_size= 0
+ while gtk.events_pending():
+ gtk.main_iteration(False)
+
+ for i in self.__filter_count_elements.values():
+
+ log.debug('LocalMusicManager :: visible_items :: Total size: ' +
+ str(i[1]) )
+
+ total_time += int(i[0])
+
+ try:
+ total_size += int(i[1])
+ except:
+ pass
+
+ log.debug('LocalMusicManager :: visible_items :: Total time: ' +
+ str(total_time))
+ log.debug('LocalMusicManager :: visible_items :: Total size: ' +
+ str(total_size))
+
+ self.__stats_size.set_label('Total of ' +
+ str(len(self.__filter_count_elements.values())) +
+ ' files, ' + str(utils.get_friendly_size(int(total_size))))
+
+ self.__stats_play_time.set_label(str(utils.get_friendly_time(total_time)) + ' ')
+
+
+ def cb_text_changed(self, text):
+ """
+ Callback called when the user change the text in search_box_entry. If
+ the user don't type anything more in 500 ms, call the model_refilter()
+
+ """
+ text = text.get_text()
+ self.__filter_key = text
+
+ if self.__search_box_timeout != 0:
+ gobject.source_remove (self.__search_box_timeout)
+ self.__search_box_timeout = 0
+
+ self.__filter_count_elements = {}
+ self.__model_filter.clear_cache()
+ self.__search_box_timeout = gobject.timeout_add(500, self.__model_filter.refilter)
+ self.update_ui()
+
+ return False
+
+
+ def attach_stats_bars(self, stats_size, stats_play_time):
+ """
+ Attach the stats bars widgets
+
+ """
+ self.__stats_size = stats_size
+ self.__stats_play_time = stats_play_time
+
+
+ def attach_search_box_entry(self, search_box_entry):
+ """
+ Attach a text entry to be the new search_box
+
+ """
+ self.__search_box_entry = search_box_entry
+ self.__search_box_entry.connect('changed', self.cb_text_changed)
+ return True
+
+
+ def get_attached_tree_view(self):
+ """
+ Return the attached tree view
+
+ """
+ return self.__tree_view
+
+
+ def cb_filter_tree_view_button_pressed(self, tree_view, event):
+ """
+ Capture the button pressed event and decide which function
+ will handle it.
+
+ """
+ log.debug('LocalMusicManager :: Tree view clicked: ' + str(tree_view))
+ if event.button == 1:
+ x = int(event.x)
+ y = int(event.y)
+ time = event.time
+ pthinfo = tree_view.get_path_at_pos(x, y)
+ if pthinfo != None:
+ path, col, cellx, celly = pthinfo
+ tree_view.grab_focus()
+ tree_view.set_cursor(path, col, 0)
+
+ if len(path) < 3:
+ self.master_filter_key = None
+ self.__model_filter.refilter()
+ return
+
+ self.__filter_count_elements = {}
+ self.__model_filter.clear_cache()
+ self.update_ui()
+
+ iter = self.__model_tree_view_filter.get_iter(path)
+ value = self.__model_tree_view_filter.get_value(iter,
+ LocalMusicManager.TITLE)
+
+ if value == 'Unknow':
+ value = ''
+
+ parent = self.__model_tree_view_filter.get_iter(path[:2])
+ self.master_filter_column = self.__model_tree_view_filter.get_value(
+ parent, 2)
+
+ self.master_filter_key = value
+ log.debug('LocalMusicManager :: Master filter key, changed to: ' +
+ str(self.master_filter_key) + ' Path: ' +
+ str(path) + ' - Parent: ' + str(parent) +
+ ' Column to search: ' + str(self.master_filter_column))
+
+ self.__model_filter.refilter()
+
+
+
+ def attach_filter_tree_view(self, filter_tree_view):
+ """
+ Attach the filter tree view. It configure the filter_tree_view
+ with the correct options.
+
+ """
+ log.debug('LocalMusicManager :: Attaching a filter TreeView...')
+ self.__model_tree_view_filter = gtk.TreeStore(gtk.gdk.Pixbuf,
+ gobject.TYPE_STRING,
+ gobject.TYPE_STRING)
+
+ # Icon
+ col = gtk.TreeViewColumn()
+ renderer = gtk.CellRendererPixbuf()
+ col.pack_start(renderer, expand=False)
+ col.set_attributes(renderer, pixbuf=0)
+ col.set_alignment(0)
+ filter_tree_view.append_column(col)
+
+ # Filter Name
+ renderer = gtk.CellRendererText()
+ renderer.set_property('editable', False);
+ col.pack_start(renderer, expand=True)
+ col.set_attributes(renderer, text=1)
+ col.set_resizable(0)
+ col.set_sort_column_id(1)
+ filter_tree_view.append_column(col)
+
+ filter_tree_view.set_model(self.__model_tree_view_filter)
+
+ filter_tree_view.connect('button_press_event', self.cb_filter_tree_view_button_pressed)
+
+ tags = []
+ tags.append(icons.load_icon('sound_folder_icon', 64))
+ tags.append('Filters')
+ tags.append(1)
+ x = self.__model_tree_view_filter.append(None, tags)
+
+ for i in LocalMusicManager.OPTIONAL_TREEVIEW_COLUMNS:
+ if i[4] == True:
+ tags = []
+ tags.append(icons.load_icon('sound_folder_icon', 64))
+ tags.append(i[1])
+ tags.append(i[5])
+ self.filter_nodes[i[1]] = self.__model_tree_view_filter.append(x, tags)
+ filter_tree_view.expand_all()
+
+
+ def attach_tree_view(self, tree_view):
+ """
+ Attach the tree view. It configure the tree_view
+ with the correct options.
+
+ """
+ log.debug('LocalMusicManager :: Attaching TreeView...')
+
+
+ tree_view.set_enable_search(True)
+
+ tree_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+
+ tree_view.get_parent().set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC)
+
+ self.__model = gtk.TreeStore(gtk.gdk.Pixbuf, gobject.TYPE_STRING, gobject.TYPE_STRING,
+ gobject.TYPE_STRING, gobject.TYPE_STRING,
+ gobject.TYPE_STRING, gobject.TYPE_STRING,
+ gobject.TYPE_STRING, gobject.TYPE_STRING,
+ gobject.TYPE_STRING, gobject.TYPE_STRING,
+ gobject.TYPE_STRING, gobject.TYPE_STRING,
+ gobject.TYPE_OBJECT)
+
+ self.__model_filter = self.__model.filter_new()
+ self.__model_filter.set_visible_func(self.cb_visible_items)
+
+ self.__model_sort = gtk.TreeModelSort(self.__model_filter)
+
+
+ tree_view.set_model(self.__model_sort)
+ tree_view.connect('button_press_event', self.cb_tree_view_button_pressed)
+ tree_view.set_rules_hint(1)
+
+ # Icon
+ col = gtk.TreeViewColumn()
+ renderer = gtk.CellRendererPixbuf()
+ col.pack_start(renderer, False)
+ col.set_attributes(renderer, pixbuf=0)
+ col.set_alignment(0)
+ tree_view.append_column(col)
+
+ # Text (Title)
+ renderer = gtk.CellRendererText()
+ renderer.set_property('editable', True);
+ col.pack_start(renderer, False)
+ col.set_attributes(renderer, text=1)
+ col.set_resizable(1)
+ renderer.connect('edited', self.__edit_done, 'title')
+
+ header_label = gtk.Label('Title')
+ col.set_widget(header_label)
+ header_label.show()
+
+ # The TreeView header the 3rd parent of the header label is the a button.
+ button_widget = col.get_widget().get_parent().get_parent().get_parent()
+ button_widget.connect('button_press_event', self.cb_tree_view_header_button_pressed)
+
+ col.set_sort_column_id(1)
+ tree_view.append_column(col)
+
+ j = 2
+ for i in self.OPTIONAL_TREEVIEW_COLUMNS:
+ col = gtk.TreeViewColumn()
+ renderer = gtk.CellRendererText()
+ renderer.set_property('xalign', 1)
+ renderer.set_property('editable', i[3]);
+ renderer.connect('edited', self.__edit_done, i[2])
+
+ header_label = gtk.Label(i[1])
+ col.set_widget(header_label)
+ header_label.show()
+
+ col.pack_start(renderer, True)
+ col.set_attributes(renderer, markup=j)
+ col.set_sort_column_id(j)
+ col.set_alignment(0.5)
+ col.set_resizable(1)
+
+ if config.getValue(i[0]) != str(True):
+ col.set_visible(False)
+
+ tree_view.append_column(col)
+ j+=1
+
+ # The TreeView header the 3rd parent of the header label is the a button.
+ button_widget = col.get_widget().get_parent().get_parent().get_parent()
+ button_widget.connect('button_press_event',
+ self.cb_tree_view_header_button_pressed)
+
+
+
+ tree_view.set_headers_clickable(True)
+ self.__tree_view = tree_view
+ #self.__root_node = self.__model.append(None, [icons.load_icon('folder-open')
+ #, 'Syncropated\'s directories watch', '', '', '', '', '', '', '', ''])
+
+
+
+ def del_registrys(self, uri, obj):
+ """
+ Used to del registrys from the tree view.
+
+ """
+ mp3 = self.__model.get_value(obj, LocalMusicManager.OBJECT)
+ log.debug('LocalMusicManager :: content changed :: ' +
+ 'Removing items in: ' + uri + ' obj: ' + str(obj) +
+ ' mp3: ' + str(mp3))
+
+ for i in self.OPTIONAL_TREEVIEW_COLUMNS:
+ if i[4] == True:
+ self.recalculate_ref_count(self.filter_nodes[i[1]],
+ mp3.get_property(i[2]), True)
+
+ self.__model.remove(obj)
+
+
+ def add_registry(self, uri, gtks):
+ """
+ Used to add registrys from the tree view.
+
+ """
+ log.debug('LocalMusicManager :: content changed :: Adding item: ' + uri)
+ if gtks != None:
+ file = gtks[0]
+ files_loaded_label = gtks[1]
+ progress_bar = gtks[2]
+ lenght = gtks[3]
+
+ file_label = os.path.split(uri)[1]
+ file_label = utils.unicodify(file_label)
+ file_label = utils.resize_string(file_label, 40)
+ file.set_text(file_label)
+
+ files_loaded_label.set_text(str(self.__total_loaded_files) +
+ ' files loaded.')
+
+ log.debug('LocalMusicManager :: progress_bar ' +
+ ': Total of loaded files: ' + str(self.__total_loaded_files))
+ log.debug('LocalMusicManager :: progress_bar ' +
+ ': Total of files: ' + str(lenght))
+
+ #progress_bar.set_fraction(float(float(self.__total_loaded_files)
+ #/float(lenght-2)))
+
+ if not os.path.isfile(uri):
+ return;
+
+ music = ElementMusic(uri)
+ tags = []
+ tags.append(icons.load_icon('sound_icon', 64))
+ tags.append(music.get_property('title'))
+
+ for i in self.OPTIONAL_TREEVIEW_COLUMNS:
+ tags.append(music.get_property(i[2]))
+ if i[4] == True:
+ self.recalculate_ref_count(self.filter_nodes[i[1]],
+ music.get_property(i[2]))
+
+ tags.append(music.get_property('filename'))
+ tags.append(music)
+
+ self.__total_loaded_files += 1
+
+ return self.__model.append(None, tags)
+
+
+ def recalculate_ref_count(self, node, property, reduce=False):
+ """
+ Recalculate the references counts of the id3s tags. If no
+ reference exist for a specific tag, it should be removed from
+ the filter tree view.
+
+ """
+ log.debug('LocalMusicManager :: recalculate ref counts ...')
+ i = 0
+ if property == '':
+ property = 'Unknow'
+ while i < self.__model_tree_view_filter.iter_n_children(node):
+ log.debug('LocalMusicManager :: recalculate ref counts :: ' +
+ str(i))
+ iter = self.__model_tree_view_filter.iter_nth_child(node, i)
+ if property == self.__model_tree_view_filter.get_value(iter,
+ LocalMusicManager.TITLE):
+ num = int(self.__model_tree_view_filter.get_value(iter,
+ 2))
+ if reduce:
+ num -= 1
+ log.debug('LocalMusicManager :: recalculate ref ' +
+ 'counts :: reducing ref count of: ' +
+ str(self.__model_tree_view_filter.get_value(iter,
+ LocalMusicManager.TITLE)) + ' to: ' +
+ str(num) )
+ else:
+ num += 1
+
+ if num <= 0:
+ log.debug('LocalMusicManager :: recalculate ref ' +
+ 'counts :: removing iter....')
+ self.__model_tree_view_filter.remove(iter)
+ log.debug('LocalMusicManager :: removed!')
+ else:
+ # Set the new refcount value
+ self.__model_tree_view_filter.set_value(iter, 2, num)
+
+ return True
+
+ i += 1
+ self.__model_tree_view_filter.append(node, (None, property, 1))
+
+ return
+
+
+ def __edit_done(self, cel, path, new_text, col):
+ """
+ Callback called on the user finish to edit an mp3 information.
+
+ """
+ iter = self.__model_sort.get_iter_from_string(path)
+ music = self.__model_sort.get_value(iter, 13)
+ music.set_property(col, new_text)
+
+
+
+
More information about the Syncropated-commits
mailing list