From 45ac1305e89e5417ac882828559b7d7a911cd4f7 Mon Sep 17 00:00:00 2001 From: Ivan Frade Date: Tue, 23 Jun 2009 18:32:19 +0300 Subject: [PATCH] Initial import --- src/album_art.py | 138 +++++++++++++++++++++++++++ src/album_art_spec.py | 62 ++++++++++++ src/edit_panel.py | 246 ++++++++++++++++++++++++++++++++++++++++++++++++ src/mussorgsky.py | 104 ++++++++++++++++++++ src/mutagen_backend.py | 104 ++++++++++++++++++++ src/player_backend.py | 51 ++++++++++ src/tracker_backend.py | 107 +++++++++++++++++++++ 7 files changed, 812 insertions(+) create mode 100755 src/album_art.py create mode 100644 src/album_art_spec.py create mode 100755 src/edit_panel.py create mode 100755 src/mussorgsky.py create mode 100755 src/mutagen_backend.py create mode 100755 src/player_backend.py create mode 100755 src/tracker_backend.py diff --git a/src/album_art.py b/src/album_art.py new file mode 100755 index 0000000..54811f7 --- /dev/null +++ b/src/album_art.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python2.5 +import urllib2, urllib +import libxml2 +import os +from album_art_spec import getCoverArtFileName, getCoverArtThumbFileName, get_thumb_filename_for_path +import dbus, time + +try: + import PIL + import Image + pil_available = True +except ImportException: + pil_available = False + + +LASTFM_APIKEY = "1e1d53528c86406757a6887addef0ace" +BASE_LASTFM = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo" + +# LastFM: +# http://www.lastfm.es/api/show?service=290 +# +class MussorgskyAlbumArt: + + def __init__ (self): + bus = dbus.SessionBus () + handle = time.time() + try: + self.thumbnailer = bus.get_object ('org.freedesktop.thumbnailer', + '/org/freedesktop/thumbnailer/Generic') + except dbus.exceptions.DBusException: + if (pil_available): + self.thumbnailer = LocalThumbnailer () + else: + print "No thumbnailer available" + self.thumbnailer = None + + def get_album_art (self, artist, album): + """ + Return a tuple (album_art, thumbnail_album_art) + """ + filename = getCoverArtFileName (album) + thumbnail = getCoverArtThumbFileName (album) + + if (os.path.exists (filename)): + print "Album art already there " + filename + else: + online_resource = self.__last_fm (artist, album) + if (online_resource): + self.__save_url_into_file (online_resource, filename) + else: + return (None, None) + + if (os.path.exists (thumbnail)): + print "Thumbnail exists" + else: + print "Requesting thumbnail" + self.__request_thumbnail (filename) + + return (filename, thumbnail) + + def __last_fm (self, artist, album): + if (not artist and not album): + return + + URL = BASE_LASTFM + "&api_key=" + LASTFM_APIKEY + if (artist): + URL += "&artist=" + urllib.quote(artist) + if (album): + URL += "&album=" + urllib.quote(album) + + print "Retrieving: %s" % (URL) + result = self.__get_url (URL) + if (not result): + return None + doc = libxml2.parseDoc (result) + image_nodes = doc.xpathEval ("//image[@size='large']") + if len (image_nodes) < 1: + return None + else: + return image_nodes[0].content + + def __save_url_into_file (self, url, filename): + image = self.__get_url (url) + output_image = open (filename, 'w') + output_image.write (image) + output_image.close () + print "Saved %s -> %s " % (url, filename) + + def __get_url (self, url): + request = urllib2.Request (url) + request.add_header ('User-Agent', 'Mussorgsky/0.1 Test') + opener = urllib2.build_opener () + try: + return opener.open (request).read () + except: + return None + + def __request_thumbnail (self, filename): + if (not self.thumbnailer): + print "No thumbnailer available" + return + uri = "file://" + filename + handle = time.time () + print "Call to thumbnailer" + self.thumbnailer.Queue ([uri], ["image/jpeg"], dbus.UInt32 (handle)) + + +class LocalThumbnailer: + def __init__ (self): + self.THUMBNAIL_SIZE = (124,124) + + def Queue (self, uris, mimes, handle): + print "Queue" + for i in range (0, len(uris)): + uri = uris[i] + fullCoverFileName = uri[7:] + print fullCoverFileName + if (os.path.exists (fullCoverFileName)): + thumbFile = get_thumb_filename_for_path (fullCoverFileName) + + image = Image.open (fullCoverFileName) + image = image.resize (self.THUMBNAIL_SIZE, Image.ANTIALIAS ) + image.save( thumbFile, "JPEG" ) + print "Thumbnail: " + thumbFile + + +if __name__ == "__main__": + import sys + if ( len (sys.argv) > 2): + artist = sys.argv[1] + album = sys.argv[2] + else: + print "ARTIST ALBUM" + sys.exit (-1) + maa = MussorgskyAlbumArt () + print "Artist %s - Album %s" % (artist, unicode(album)) + print maa.get_album_art (artist, unicode(album)) + #assert (None, None) == maa.get_album_art ("muse", "absolution") diff --git a/src/album_art_spec.py b/src/album_art_spec.py new file mode 100644 index 0000000..67b68b1 --- /dev/null +++ b/src/album_art_spec.py @@ -0,0 +1,62 @@ +import os +import md5 +import unicodedata +import string + +COVERS_LOCATION = os.getenv ("HOME") + "/.cache/media-art/" +THUMBS_LOCATION = os.getenv ("HOME") + "/.thumbnails/cropped/" + +def getCoverArtFileName (album): + """Returns the cover art's filename that is formed from the album name.""" + print "Calculating album art for " + album + album = unicode (album) + albumString=dropInsideContent(album,"[","]" ) + albumString=dropInsideContent(albumString,"{","}" ) + albumString=dropInsideContent(albumString,"(",")" ) + albumString=albumString.strip('()_{}[]!@#$^&*+=|\\/"\'?<>~`') + albumString=albumString.lstrip(' ') + albumString=albumString.rstrip(' ') + albumString=dropInsideContent(albumString,"{","}" ) + albumString=albumString.lower() + albumString=string.replace(albumString,"\t"," ") + albumString=string.replace(albumString," "," ") + + try: + print "Trying NFKD" + albumString=unicodedata.normalize('NFKD',albumString).encode() + albumString=albumString.encode() + print albumString + except: + try: + print "Trying latin-1" + albumString=albumString.encode('latin-1', 'ignore') + albumString=unicodedata.normalize('NFKD',albumString).encode("ascii") + albumString=str(albumString) + print albumString + except Exception, e: + albumString="unknown" + print "unknown" + str(e) + if len(albumString)==0: albumString=" " + + albumMD5=md5.new(albumString).hexdigest() + emptyMD5=md5.new(" ").hexdigest() + albumArt=COVERS_LOCATION + "album-" + emptyMD5 + "-" + albumMD5 + ".jpeg" + print "Album art:" + albumArt + return albumArt + + +def getCoverArtThumbFileName (album): + artFile = getCoverArtFileName(album) + thumbFile = THUMBS_LOCATION + md5.new(artFile).hexdigest()+".jpeg" + return thumbFile + +def get_thumb_filename_for_path (path): + thumbnail = THUMBS_LOCATION + md5.new (path).hexdigest () + ".jpeg" + return thumbnail + +def dropInsideContent(s, startMarker, endMarker): + startPos=s.find(startMarker) + endPos=s.find(endMarker) + if startPos>0 and endPos>0 and endPos>startPos: + return s[0:startPos]+s[endPos+1:len(s)] + return s diff --git a/src/edit_panel.py b/src/edit_panel.py new file mode 100755 index 0000000..289bdd4 --- /dev/null +++ b/src/edit_panel.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python2.5 +import hildon +import gtk +from mutagen_backend import MutagenBackend +from player_backend import MediaPlayer + +# Fields in the tuple! +FILE_URI = 0 +ARTIST_KEY = 2 +TITLE_KEY = 3 +ALBUM_KEY = 4 +MIME_KEY = 5 + +class MussorgskyEditPanel (hildon.StackableWindow): + + def __init__ (self, songs_list=None, albums_list=None, artists_list=None): + hildon.StackableWindow.__init__ (self) + self.set_title ("Edit") + self.writer = MutagenBackend () + self.player = MediaPlayer () + self.albums_list = albums_list + self.artists_list = artists_list + self.add (self.__create_view ()) + if (songs_list): + self.set_songs_list (songs_list) + self.banner = None + + self.artists_selector = None + self.artists_dialog = None + + self.albums_selector = None + self.albums_dialog = None + + def set_songs_list (self, songs_list): + self.songs_list = songs_list + self.set_data_in_view (songs_list [0]) + self.song_counter = 0 + + def press_back_cb (self, widget): + if (self.player.is_playing ()): + self.player.stop () + + if (self.banner and self.banner.get_property("visible")): + self.banner.destroy () + + if self.__is_view_dirty (): + print "Modified data. Save!" + self.save_metadata () + + if (self.song_counter > 0): + self.song_counter -= 1 + self.set_data_in_view (self.songs_list [self.song_counter]) + + def press_next_cb (self, widget): + if (self.player.is_playing ()): + self.player.stop () + + if (self.banner and self.banner.get_property("visible")): + self.banner.destroy () + + if self.__is_view_dirty (): + print "Modified data. Save!" + self.save_metadata () + + if (self.song_counter < len (self.songs_list) -1): + self.song_counter += 1 + self.set_data_in_view (self.songs_list [self.song_counter]) + else: + self.destroy () + + def save_metadata (self): + # Save the data in the online model to show the appropiate data + # in the UI while tracker process the update. + song = self.songs_list [self.song_counter] + + new_song = (song[FILE_URI], song[1], + self.artist_entry.get_text (), + self.title_entry.get_text (), + self.album_entry.get_text (), + song[MIME_KEY]) + self.songs_list [self.song_counter] = new_song + try: + self.writer.save_metadata_on_file (new_song[FILE_URI], + new_song[MIME_KEY], + self.artist_entry.get_text (), + self.title_entry.get_text (), + self.album_entry.get_text ()) + except IOError, e: + # This error in case of tracker returning unexistent files. + # Uhm.... for instance after removing a memory card we are editing! + dialog = gtk.MessageDialog (self, + gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + "%s" % str(e)); + dialog.run () + + + + def __is_view_dirty (self): + """ + True if the data has been modified in the widgets + """ + song = self.songs_list [self.song_counter] + + return not (self.filename_data.get_text() == song[FILE_URI] and + self.artist_entry.get_text () == song[ARTIST_KEY] and + self.title_entry.get_text () == song[TITLE_KEY] and + self.album_entry.get_text () == song[ALBUM_KEY] ) + + + def __create_view (self): + view_vbox = gtk.VBox (homogeneous=False, spacing = 42) + + filename_row = gtk.HBox () + filename_label = gtk.Label ("Filename:") + filename_row.pack_start (filename_label, expand=False, padding=12); + self.filename_data = gtk.Label ("") + filename_row.pack_start (self.filename_data, expand=True) + + play_button = gtk.Button (stock=gtk.STOCK_MEDIA_PLAY) + play_button.connect ("clicked", self.clicked_play) + filename_row.pack_start (play_button, expand=False, fill=False, padding=12) + view_vbox.pack_start (filename_row, expand=True); + + # Artist row + artist_row = gtk.HBox () + button_artist = gtk.Button ("Artist:") + if (not self.artists_list): + button_artist.set_sensitive (False) + button_artist.connect ("clicked", self.artist_selection_cb) + artist_row.pack_start (button_artist, expand=False, padding=12) + self.artist_entry = gtk.Entry() + artist_row.pack_start (self.artist_entry, padding=12) + + view_vbox.pack_start (artist_row, expand=False) + + # Title row + title_row = gtk.HBox () + label_title = gtk.Label ("Title:") + title_row.pack_start (label_title, expand=False, padding=12) + self.title_entry = gtk.Entry() + title_row.pack_start (self.title_entry, padding=12) + + view_vbox.pack_start (title_row, expand=False) + + # Album row + album_row = gtk.HBox () + button_album = gtk.Button ("Album:") + if (not self.albums_list): + button_album.set_sensitive (False) + button_album.connect ("clicked", self.album_selection_cb) + album_row.pack_start (button_album, expand=False, padding=12) + self.album_entry = gtk.Entry() + album_row.pack_start (self.album_entry, padding=12) + + view_vbox.pack_start (album_row, expand=False) + + # Buttons row + button_box = gtk.HButtonBox () + button_box.set_layout (gtk.BUTTONBOX_END) + + back_button = gtk.Button (stock=gtk.STOCK_GO_BACK) + back_button.connect ("clicked", self.press_back_cb) + button_box.pack_start (back_button, padding=12) + + next_button = gtk.Button (stock=gtk.STOCK_GO_FORWARD) + button_box.pack_start (next_button, padding=12) + next_button.connect ("clicked", self.press_next_cb) + + view_vbox.pack_start (button_box, expand=True) + + return view_vbox + + + def set_data_in_view (self, song): + """ + Place in the screen the song information. + Song is a tuple like (filename, 'Music', title, artist, album, mime) + """ + assert len (song) == 6 + self.filename_data.set_text (song[FILE_URI]) + self.artist_entry.set_text (song[ARTIST_KEY]) + self.title_entry.set_text (song[TITLE_KEY]) + self.album_entry.set_text (song[ALBUM_KEY]) + + if (not song[MIME_KEY] in self.writer.get_supported_mimes ()): + print "show notification" + self.banner = hildon.Banner () + self.banner.set_text ("Unsupported format (%s)" % song[MIME_KEY]) + self.banner.show_all () + + def clicked_play (self, widget): + if (self.player.is_playing ()): + self.player.stop () + else: + song = self.songs_list [self.song_counter] + self.player.play ("file://" + song[FILE_URI]) + + def album_selection_cb (self, widget): + if (not self.albums_selector): + self.albums_selector = hildon.hildon_touch_selector_new_text () + for album in self.albums_list : + self.albums_selector.append_text (album[0]) + + if (not self.albums_dialog): + self.albums_dialog = hildon.PickerDialog (self) + self.albums_dialog.set_title ("Choose album...") + self.albums_dialog.set_selector (self.albums_selector) + + response = self.albums_dialog.run () + if (response == gtk.RESPONSE_OK): + print "Ok (%s)" % (self.albums_selector.get_current_text ()) + self.album_entry.set_text (self.albums_selector.get_current_text ()) + self.albums_dialog.hide () + + def artist_selection_cb (self, widget): + if (not self.artists_selector): + self.artists_selector = hildon.hildon_touch_selector_new_text () + for artist in self.artists_list : + self.artists_selector.append_text (artist[0]) + + if (not self.artists_dialog): + self.artists_dialog = hildon.PickerDialog (self) + self.artists_dialog.set_title ("Choose artist...") + self.artists_dialog.set_selector (self.artists_selector) + + response = self.artists_dialog.run () + + if (response == gtk.RESPONSE_OK): + print "Ok (%s)" % (self.artists_selector.get_current_text ()) + self.artist_entry.set_text (str(self.artists_selector.get_current_text ())) + self.artists_dialog.hide () + +# Testing porpuses +if __name__ == "__main__": + + TEST_DATA = [("/a/b/c/d.mp3", "Music", "", "title", "album", "audio/mpeg"), + ("/home/user/mufix/dejame.mp3", "Music", "", "title", "album 2", "a/b"), + ("/home/user/mufix/3.mp2", "Music", "", "titlex", "album 3", "audio/mpeg")] + ALBUMS = [["Album %d" % i] for i in range (0, 10)] + ARTISTS = [["Artist %d" % i] for i in range (0, 10)] + window = MussorgskyEditPanel (TEST_DATA, ALBUMS, ARTISTS) + window.connect ("destroy", gtk.main_quit) + window.show_all () + gtk.main () diff --git a/src/mussorgsky.py b/src/mussorgsky.py new file mode 100755 index 0000000..e5c88a6 --- /dev/null +++ b/src/mussorgsky.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python2.5 +import hildon +import gtk, gobject +from tracker_backend import TrackerBackend +from edit_panel import MussorgskyEditPanel + + +class MussorgskyMainWindow (hildon.StackableWindow): + + def __init__ (self): + hildon.StackableWindow.__init__ (self) + self.tracker = TrackerBackend () + self.set_title ("Mussorgsky") + self.connect ("destroy", gtk.main_quit) + main_view_box = self.create_main_view () + self.add (main_view_box) + self.update_values (None) + self.show_all () + + def show_edit_panel (self, songs, albums, artists): + panel = MussorgskyEditPanel (songs, albums, artists) + panel.connect ("destroy", self.back_to_main_view) + panel.show_all () + + def back_to_main_view (self, widget): + print "Waiting to update" + gobject.timeout_add_seconds (3, self.update_values, None) + + def artists_clicked (self, widget): + list_songs = self.tracker.get_songs_without_artist () + list_albums = self.tracker.get_list_of_known_albums () + list_artists = self.tracker.get_list_of_known_artists () + self.show_edit_panel (list_songs, list_albums, list_artists) + + def titles_clicked (self, widget): + list_songs = self.tracker.get_songs_without_title () + list_albums = self.tracker.get_list_of_known_albums () + list_artists = self.tracker.get_list_of_known_artists () + self.show_edit_panel (list_songs, list_albums, list_artists) + + def albums_clicked (self, widget): + list_songs = self.tracker.get_songs_without_album () + list_albums = self.tracker.get_list_of_known_albums () + list_artists = self.tracker.get_list_of_known_artists () + self.show_edit_panel (list_songs, list_albums, list_artists) + + def update_values (self, user_data): + print "Updating labels" + self.label_no_artist.set_text ("%s songs without artist" % + self.tracker.count_songs_wo_artist ()) + self.label_no_title.set_text ("%s songs without title" % + self.tracker.count_songs_wo_title ()) + self.label_no_album.set_text ("%s songs without album" % + self.tracker.count_songs_wo_album ()) + return False + + def create_main_view (self): + vbox = gtk.VBox () + + # Artist row + artist_row = gtk.HBox (homogeneous=True) + self.label_no_artist = gtk.Label ("") + artist_row.add (self.label_no_artist) + button_artists = gtk.Button ("Fix empty artists!") + button_artists.connect ("clicked", self.artists_clicked) + artist_row.add (button_artists) + + vbox.add (artist_row) + + # Title row + title_row = gtk.HBox (homogeneous=True) + self.label_no_title = gtk.Label ("") + title_row.add (self.label_no_title) + button_titles = gtk.Button ("Fix empty titles!") + button_titles.connect ("clicked", self.titles_clicked) + title_row.add (button_titles) + + vbox.add (title_row) + + # Album row + album_row = gtk.HBox (homogeneous=True) + self.label_no_album = gtk.Label ("") + album_row.add (self.label_no_album) + button_albums = gtk.Button ("Fix empty albums!") + button_albums.connect ("clicked", self.albums_clicked) + album_row.add (button_albums) + + vbox.add (album_row) + + return vbox + +if __name__ == "__main__": + + try: + window = MussorgskyMainWindow () + gtk.main () + except Exception, e: + dialog = gtk.MessageDialog (None, + gtk.DIALOG_DESTROY_WITH_PARENT, + gtk.MESSAGE_ERROR, + gtk.BUTTONS_CLOSE, + "Error (%s)" % str(e)); + dialog.run () + diff --git a/src/mutagen_backend.py b/src/mutagen_backend.py new file mode 100755 index 0000000..f1a4f8b --- /dev/null +++ b/src/mutagen_backend.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python2.5 +import mutagen + +class MutagenBackend (): + + def __init__ (self): + self.formats = {"audio/mpeg": self.__id3_writer , + "audio/x-ms-wma" : self.__wma_writer } + def get_supported_mimes (self): + return self.formats.keys () + + def save_metadata_on_file (self, filename, mime, artist, title, album): + if (self.formats.has_key (mime)): + return self.formats[mime] (filename, artist, title, album) + else: + return False + + def __id3_writer (self, filename, artist, title, album): + from mutagen.mp3 import MP3 + from mutagen.easyid3 import EasyID3 + audio = MP3 (filename, ID3=EasyID3) + try: + audio.add_tags (ID3=EasyID3) + print "Adding tags to %s" % (filename) + except mutagen.id3.error: + # It already has tags! + pass + + audio["artist"] = artist + audio["title"] = title + audio["album"] = album + try: + audio.save() + return True + except: + return False + + def __wma_writer (self, filename, artist, title, album): + from mutagen.asf import ASF + audio = ASF (filename) + audio["Author"] = artist + audio["Title"] = title + audio["WM/AlbumTitle"] = album + try: + audio.save() + return True + except: + return False + + + + +if __name__ == "__main__": + + # use id3lib for verification + import pyid3lib + + def verify (filename, expected_artist, expected_title, expected_album): + from mutagen.easyid3 import EasyID3 + audio = EasyID3 (filename) + assert audio["artist"][0] == expected_artist + assert audio["title"][0] == expected_title + assert audio["album"][0] == expected_album + + def verify_wma (filename, expected_artist, expected_title, expected_album): + from mutagen.asf import ASF + audio = ASF (filename) + assert str(audio["Author"][0]) == expected_artist + assert str(audio["Title"][0]) == expected_title + assert str(audio["WM/AlbumTitle"][0]) == expected_album + + + writer = MutagenBackend () + + TEST_FILE = "test-files/strange.mp3" + TEST_FILE_TO_BREAK = "test-files/strange-fixed.mp3" + + out = open (TEST_FILE_TO_BREAK, 'w') + out.write (open (TEST_FILE,'r').read ()) + out.close () + + writer.save_metadata_on_file (TEST_FILE_TO_BREAK, "audio/mpeg", + "artist_test", "title_test", "album_test") + verify (TEST_FILE_TO_BREAK, "artist_test", "title_test", "album_test") + + writer.save_metadata_on_file (TEST_FILE_TO_BREAK, "audio/mpeg", + "artist_test_2", "title_test_2", "album_2") + verify (TEST_FILE_TO_BREAK, "artist_test_2", "title_test_2", "album_2") + + + + READONLY_FILE = "test-files/no-write.mp3" + assert not writer.save_metadata_on_file (READONLY_FILE, "audio/mpeg", + "artist_test", "title_test", "album_test") + + + WMA_FILE = "test-files/hooverphonic.wma" + assert writer.save_metadata_on_file (WMA_FILE, "audio/x-ms-wma", + "artist_wma", "title_wma", "album_wma") + verify_wma (WMA_FILE, "artist_wma", "title_wma", "album_wma") + + assert writer.save_metadata_on_file (WMA_FILE, "audio/x-ms-wma", + "artist_wma_2", "title_wma_2", "album_wma_2") + verify_wma (WMA_FILE, "artist_wma_2", "title_wma_2", "album_wma_2") diff --git a/src/player_backend.py b/src/player_backend.py new file mode 100755 index 0000000..db7abbd --- /dev/null +++ b/src/player_backend.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python2.5 +import pygst +pygst.require('0.10') +import gst +import gobject, sys + +class MediaPlayer: + + def __init__ (self): + self.playing = False + self.player = gst.element_factory_make ("playbin2", "player") + + def play (self, uri): + self.playing = True + print 'Playing:', uri + self.player.set_property ('uri', uri) + self.player.set_state(gst.STATE_PLAYING) + + def stop (self): + print 'Stop' + if (self.playing): + self.playing = False + self.player.set_state(gst.STATE_NULL) + + def is_playing (self): + return self.playing + + + +def button_clicked_cb (widget, mediaplayer): + #TESTFILE="file:///scratchbox/users/ivan/home/ivan/mufix/dejame.mp3" + TESTFILE="file:///home/user/mufix/dejame.mp3" + if (mediaplayer.is_playing ()): + mediaplayer.stop () + else: + mediaplayer.play (TESTFILE) + +if __name__ == "__main__": + + import gtk + + w = gtk.Window () + w.connect ("destroy", gtk.main_quit) + player = MediaPlayer () + + button = gtk.Button (stock=gtk.STOCK_MEDIA_PLAY) + button.connect ("clicked", button_clicked_cb, player) + + w.add (button) + w.show_all () + gtk.main () diff --git a/src/tracker_backend.py b/src/tracker_backend.py new file mode 100755 index 0000000..1a9d6fe --- /dev/null +++ b/src/tracker_backend.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python2.5 +import dbus +import os + +TRACKER = 'org.freedesktop.Tracker' +TRACKER_OBJ = '/org/freedesktop/Tracker/Metadata' +TRACKER_SEARCH_OBJ = '/org/freedesktop/Tracker/Search' + +RDF_NO_PROPX = """ + + + + + + + + +""" + +RDF_NO_ARTIST = RDF_NO_PROPX % "Audio:Artist" +RDF_NO_ALBUM = RDF_NO_PROPX % "Audio:Album" +RDF_NO_TITLE = RDF_NO_PROPX % "Audio:Title" + +class TrackerBackend: + + def __init__ (self): + print "Tracker backend up" + bus = dbus.SessionBus () + self.tracker_metadata = bus.get_object (TRACKER, TRACKER_OBJ) + self.iface_metadata = dbus.Interface (self.tracker_metadata, + "org.freedesktop.Tracker.Metadata") + + self.tracker_search = bus.get_object (TRACKER, TRACKER_SEARCH_OBJ) + self.iface_search = dbus.Interface (self.tracker_search, + "org.freedesktop.Tracker.Search") + + def count_songs_wo_artist (self): + return self.iface_metadata.GetCount ("Music", "*", RDF_NO_ARTIST) + + def count_songs_wo_title (self): + return self.iface_metadata.GetCount ("Music", "*", RDF_NO_TITLE) + + def count_songs_wo_album (self): + return self.iface_metadata.GetCount ("Music", "*", RDF_NO_ALBUM) + + def __run_rdf_query (self, rdf_query): + results = self.iface_search.Query (-1, "Music", + ["Audio:Artist", + "Audio:Title", + "Audio:Album", + "File:Mime"], + "", [], rdf_query, False, + [], False, 0, 32000) + return results + + + def get_songs_without_artist (self): + """ + Return tuples with the following fields: + (uri, "Music", artist, title, album, mimetype) + """ + return self.__run_rdf_query (RDF_NO_ARTIST) + + def get_songs_without_title (self): + """ + Return tuples with the following fields: + (uri, "Music", artist, title, album, mimetype) + """ + return self.__run_rdf_query (RDF_NO_TITLE) + + def get_songs_without_album (self): + """ + Return tuples with the following fields: + (uri, "Music", artist, title, album, mimetype) + """ + return self.__run_rdf_query (RDF_NO_ALBUM) + + def get_list_of_known_albums (self): + return self.iface_metadata.GetUniqueValues ("Music", + ["Audio:Album"], + "", False, 0, 32000) + + def get_list_of_known_artists (self): + return self.iface_metadata.GetUniqueValues ("Music", + ["Audio:Artist"], + "", False, 0, 32000) + +# Test +if __name__ == "__main__": + + tracker = TrackerBackend () + + print "Songs without artist: " + str(tracker.count_songs_wo_artist ()) + + results = tracker.get_songs_without_artist () + for r in results: + print "'%s', '%s', '%s', '%s', '%s'" % (r[0], r[2], r[3], r[4], r[5]) + + + print "Songs without album " + str(tracker.count_songs_wo_album ()) + print "Songs without title " + str(tracker.count_songs_wo_title ()) + + albums = tracker.get_list_of_known_albums () + print "%d different albums" % (len (albums)) + for a in albums: + print a[0] + -- 1.7.9.5