Initial import
authorIvan Frade <ivan.frade@gmail.com>
Tue, 23 Jun 2009 15:32:19 +0000 (18:32 +0300)
committerIvan Frade <ivan.frade@gmail.com>
Tue, 23 Jun 2009 15:32:19 +0000 (18:32 +0300)
src/album_art.py [new file with mode: 0755]
src/album_art_spec.py [new file with mode: 0644]
src/edit_panel.py [new file with mode: 0755]
src/mussorgsky.py [new file with mode: 0755]
src/mutagen_backend.py [new file with mode: 0755]
src/player_backend.py [new file with mode: 0755]
src/tracker_backend.py [new file with mode: 0755]

diff --git a/src/album_art.py b/src/album_art.py
new file mode 100755 (executable)
index 0000000..54811f7
--- /dev/null
@@ -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 (file)
index 0000000..67b68b1
--- /dev/null
@@ -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 (executable)
index 0000000..289bdd4
--- /dev/null
@@ -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 (executable)
index 0000000..e5c88a6
--- /dev/null
@@ -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 (executable)
index 0000000..f1a4f8b
--- /dev/null
@@ -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 (executable)
index 0000000..db7abbd
--- /dev/null
@@ -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 (executable)
index 0000000..1a9d6fe
--- /dev/null
@@ -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 = """
+<rdfq:Condition>
+  <rdfq:and>
+    <rdfq:equals>
+      <rdfq:Property name="%s" />
+      <rdf:String></rdf:String> 
+    </rdfq:equals>
+  </rdfq:and>
+</rdfq:Condition>
+"""
+
+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]
+