From: Ivan Frade Date: Tue, 5 Jul 2011 07:28:03 +0000 (+0300) Subject: Move the qml/* files to the src folder. Now it is the main version X-Git-Url: https://vcs.maemo.org/git/?a=commitdiff_plain;h=35d10ba4c00f0d26f3fb50ed8d1af44dbb3102b0;p=mussorgsky Move the qml/* files to the src folder. Now it is the main version --- diff --git a/src/aa_search.py b/src/aa_search.py new file mode 100644 index 0000000..db716c5 --- /dev/null +++ b/src/aa_search.py @@ -0,0 +1,258 @@ +#!/usr/bin/env python2.5 +import os +from utils import UrllibWrapper +import dbus, time +import string +import urllib + + +LASTFM_APIKEY = "1e1d53528c86406757a6887addef0ace" +BASE_LASTFM = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo" + + +BASE_MSN = "http://www.bing.com/images/search?q=" +MSN_MEDIUM = "+filterui:imagesize-medium" +MSN_SMALL = "+filterui:imagesize-medium" +MSN_SQUARE = "+filterui:aspect-square" +MSN_PHOTO = "+filterui:photo-graphics" + +CACHE_LOCATION = os.path.join (os.getenv ("HOME"), ".cache", "mussorgsky") +# LastFM: +# http://www.lastfm.es/api/show?service=290 +# + + +import threading +class AADownloadThread (threading.Thread): + + def __init__ (self, url, artist, album, counter): + threading.Thread.__init__ (self, target=self.grab_image, args=(url,)) + self.counter = counter + self.artistName = artist.replace (" ", "_") + self.albumName = album.replace (" ", "_") + self.image_path = None + self.urllib_wrapper = UrllibWrapper () + + def grab_image (self, image_url): + print "Working", self.counter + image = self.urllib_wrapper.get_url (image_url) + if (image): + self.image_path = os.path.join (CACHE_LOCATION, self.artistName + self.albumName + str(self.counter)) + self.urllib_wrapper.save_content_into_file (image, self.image_path) + + def get_result (self): + return self.image_path + + + +class MussorgskyAlbumArt: + + def __init__ (self): + bus = dbus.SessionBus () + + if (not os.path.exists (CACHE_LOCATION)): + os.makedirs (CACHE_LOCATION) + + self.urllib_wrapper = UrllibWrapper () + + def get_possible_url (self, artist, album, amount=4): + results_page = self.__msn_images (artist, album) + return self.__get_url_from_msn_results_page (results_page) + + + def get_album_art (self, albumItem, force=False): + """ + Save the first available result as the albumart for that item + """ + filename = albumItem.get_aa().get_media_art_path () + if (os.path.exists (filename) and not force): + print "Album art already there " + filename + return + + results_page = self.__msn_images (albumItem.artist, albumItem.title) + for online_resource in self.__get_url_from_msn_results_page (results_page): + print "Trying:", online_resource + content = self.urllib_wrapper.get_url (online_resource) + if (content): + print "Saved on: %s " % (filename) + self.urllib_wrapper.save_content_into_file (content, filename) + albumItem.album_art = filename + break + + def get_alternatives (self, artist, album, max_alternatives=4): + """ + return a list of images in the local disk + """ + results_page = self.__msn_images (artist, album) + return self.__process_results_page (results_page, artist, album, max_alternatives) + + def get_alternatives_free_text (self, search_text, max_alternatives=4): + results_page = self.__msn_images_free_text (search_text) + return self.__process_results_page (results_page, max_alternatives) + + def __process_results_page (self, results_page, artist, album, max_alternatives): + counter = 0 + threads = [] + for image_url in self.__get_url_from_msn_results_page (results_page): + if (not image_url): + # Some searches doesn't return anything at all! + break + + if (counter >= max_alternatives): + break + + t = AADownloadThread (image_url, artist, album, counter) + t.start () + threads.append (t) + counter += 1 + + for t in threads: + t.join (5) + if (t.isAlive ()): + yield None + else: + yield t.get_result () + + + def save_alternative (self, artist, album, img_path, thumb_path): + """ + This is done now in the controller + """ + if not os.path.exists (img_path) or not os.path.exists (thumb_path): + print "**** CRITICAL **** image in path", path, "doesn't exist!" + return (None, None) + + filename = getCoverArtFileName (album) + thumbnail = getCoverArtThumbFileName (album) + + os.rename (img_path, filename) + os.rename (thumb_path, thumbnail) + + return (filename, thumbnail) + + def reset_alternative (self, artist, album): + + for filepath in [getCoverArtFileName (album), + getCoverArtThumbFileName (album)]: + if os.path.exists (filepath): + os.remove (filepath) + + def __msn_images (self, artist, album): + + good_artist = self.__clean_string_for_search (artist) + good_album = self.__clean_string_for_search (album) + + if (good_album and good_artist): + full_try = BASE_MSN + good_album + "+" + good_artist + MSN_MEDIUM + MSN_SQUARE + print "Searching (album + artist): %s" % (full_try) + result = self.urllib_wrapper.get_url (full_try) + if (result and result.find ("no_results") == -1): + return result + + if (album): + if (album.lower ().find ("greatest hit") != -1): + print "Ignoring '%s': too generic" % (album) + pass + else: + album_try = BASE_MSN + good_album + MSN_MEDIUM + MSN_SQUARE + print "Searching (album): %s" % (album_try) + result = self.urllib_wrapper.get_url (album_try) + if (result and result.find ("no_results") == -1): + return result + + if (artist): + artist_try = BASE_MSN + good_artist + "+CD+music" + MSN_SMALL + MSN_SQUARE + MSN_PHOTO + print "Searching (artist CD): %s" % (artist_try) + result = self.urllib_wrapper.get_url (artist_try) + if (result and result.find ("no_results") == -1): + return result + + return None + + def __msn_images_free_text (self, search_text): + full_try = BASE_MSN + self.__clean_string_for_search (search_text) + MSN_MEDIUM + MSN_SQUARE + result = self.urllib_wrapper.get_url (full_try) + return result + + + def __get_url_from_msn_results_page (self, page): + if (not page): + return + + current_option = None + starting_at = 0 + + # 500 is just a safe limit + for i in range (0, 500): + # Iterate until find a jpeg + start = page.find ("imgurl:"", starting_at) + if (start == -1): + yield None + end = page.find ("&", start + len ("imgurl:"")) + current_option = page [start + len ("imgurl:""): end].replace ("amp;", "") + if (current_option.lower().endswith (".jpg") or + current_option.lower().endswith (".jpeg")): + yield current_option + starting_at = end + + + def __clean_string_for_search (self, text): + if (not text or len (text) < 1): + return None + + bad_stuff = "_:?\\-~" + clean = text + for c in bad_stuff: + clean = clean.replace (c, " ") + + clean.replace ("/", "%2F") + clean = clean.replace (" CD1", "").replace(" CD2", "") + return urllib.quote(clean) + + +if __name__ == "__main__": + import sys + from optparse import OptionParser + + parser = OptionParser() + parser.add_option ("-p", "--print", dest="print_paths", + action="store_true", default=True, + help="Print the destination paths") + parser.add_option ("-r", "--retrieve", dest="retrieve", + action="store_true", default=False, + help="Try to retrieve the online content") + parser.add_option ("-m", "--multiple", dest="multiple", + action="store_true", default=False, + help="Show more than one option") + parser.add_option ("-a", "--artist", dest="artist", type="string", + help="ARTIST to look for", metavar="ARTIST") + parser.add_option ("-b", "--album", dest="album", type="string", + help="ALBUM to look for", metavar="ALBUM") + + (options, args) = parser.parse_args () + print options + if (not options.artist and not options.album): + parser.print_help () + sys.exit (-1) + + if (options.multiple and options.retrieve): + print "Multiple and retrieve are incompatible" + parser.print_help () + sys.exit (-1) + + if options.print_paths and not options.retrieve: + print "Album art:", getCoverArtFileName (options.album) + print "Thumbnail:", getCoverArtThumbFileName (options.album) + + if options.retrieve: + maa = MussorgskyAlbumArt () + maa.get_album_art (options.artist, options.album) + + if options.multiple: + start = time.time () + maa = MussorgskyAlbumArt () + for (img, thumb) in maa.get_alternatives (options.artist, options.album, 5): + print img + print thumb + end = time.time () + print end - start diff --git a/src/albumArt.py b/src/albumArt.py new file mode 100644 index 0000000..74a6432 --- /dev/null +++ b/src/albumArt.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +from PySide.QtCore import QRegExp +import re +import hashlib +import os + +MULTIWHITE = re.compile ("\s\s+") + +MEDIAART_PATH = os.path.join (os.environ["HOME"], ".cache", "media-art") +GENERATED_PATH = os.path.join (os.environ["HOME"], ".cache", "music-suite", "generated") +THUMB_PATH = os.path.join (os.environ["HOME"], ".cache", "music-suite", "thumbnails") + +# +# Python translation of : +# https://qt.gitorious.org/qt/qt/blobs/d12681a4cf1227d0e92fc7cf12aa3977e6ffe3fe/src/corelib/tools/qhash.cpp#line76 +# +def qhash (inputstr): + + if isinstance (inputstr, str): + instr = inputstr + elif isinstance (inputstr, unicode): + instr = inputstr.encode ("utf8") + else: + return -1 + + h = 0x00000000 + for i in range (0, len (instr)): + h = (h << 4) + ord(instr[i]) + h ^= (h & 0xf0000000) >> 23 + h &= 0x0fffffff + + return h + +class AlbumArt: + def __init__ (self, album, artist, flavor="common", aaType="album"): + self.album = album + self.albumMD5 = self.__get_md5 (album) + self.artist = artist + self.artistMD5 = self.__get_md5 (artist) + self.flavor = "common" + self.aaType = "album" + + def get_media_art_path (self): + """ + The raw media-art downloaded or taken from the folder with the music files + """ + filename = "album-" + self.artistMD5 + "-" + self.albumMD5 + ".jpeg" + return os.path.join (MEDIAART_PATH, filename) + + def get_generated (self): + filename = str (qhash (self.album + self.artist + self.flavor + self.aaType)) + ".png" + return os.path.join (GENERATED_PATH, self.flavor, filename) + + def get_thumbnail (self): + filename = str (qhash (self.album + self.artist + self.flavor + self.aaType)) + ".jpeg" + return os.path.join (THUMB_PATH, self.flavor, filename) + + + + def __get_md5 (self, something): + if not something or len (something) == 0: + something = " " + else: + # This can very probably be done with the native python regexp + blocks = QRegExp ("(\\([^\\)]*\\))" + + "|(\\[[^\\]]*\\])" + + "|(\\{[^\\}]*\\})" + + "|(\\<[^\\>]*\\>)") + something = blocks.replace (something, "") + + evilchars = QRegExp ("[\\(\\)\\_\\{\\}\\[\\]\\!\\@\\#\\$\\^\\&\\*\\+\\=\\|\\\\\\/\\\"\\'\\?\\<\\>\\~\\`]+") + something = evilchars.replace (something, "") + + + something = MULTIWHITE.sub (" ", something) + something = something.strip () + + md5 = hashlib.md5 (something.lower ().encode ("utf-8")) + return md5.hexdigest () + + +if __name__ == "__main__": + + aa = AlbumArt ("Breakfast in america", "Björk") + print "Source: \"" + aa.get_media_art_path () + "\"" + print "Generated: \"" + aa.get_generated () + "\"" + print "Thumbnail: \"" + aa.get_thumbnail () + "\"" + diff --git a/src/albumItem.py b/src/albumItem.py new file mode 100644 index 0000000..0cf25c8 --- /dev/null +++ b/src/albumItem.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +import os +import sys +import time +from PySide import QtCore +from PySide import QtGui +from PySide import QtDeclarative + +from albumArt import AlbumArt + +class AlbumItem (QtCore.QObject): + + def __init__ (self, title, artist): + QtCore.QObject.__init__(self) + self._title = title + self._artist = artist + self.aa = AlbumArt (self._title, self._artist) + self.require_download = False + if os.path.exists (self.aa.get_media_art_path ()): + self._album_art = self.aa.get_media_art_path () + elif os.path.exists (self.aa.get_generated ()): + self._album_art = self.aa.get_generated () + self.require_download = True + else: + self.require_download = True + self._album_art = "images/blank_record.png" + + def _title (self): + return self._title + + def _artist (self): + return self._artist + + def _albumArt (self): + return self._album_art + + def _setAlbumArt (self, path): + print "Setting the new album art to", path + self.require_download = False + self._album_art = path + self.album_art_changed.emit () + + def resetAlbumArt (self): + print "Reset album art!" + #self._album_art = None + counter = 0 + while counter < 3: + if os.path.exists (self.aa.get_generated ()): + break + time.sleep (1) + counter += 1 + + if os.path.exists (self.aa.get_generated ()): + print "Using the generated" + self._album_art = self.aa.get_generated () + self.require_download = True + else: + print "Setting to none (", self.aa.get_generated (), " doesn't exist)" + self.require_download = True + self._album_art = "images/blank_record.png" + self.album_art_changed.emit () + + def get_aa (self): + return self.aa + + prop_changed = QtCore.Signal () + album_art_changed = QtCore.Signal () + + title = QtCore.Property (unicode, _title, notify=prop_changed) + artist = QtCore.Property (unicode, _artist, notify=prop_changed) + album_art = QtCore.Property (unicode, + _albumArt, + _setAlbumArt, + notify=album_art_changed) + diff --git a/src/albumModel.py b/src/albumModel.py new file mode 100644 index 0000000..a8190c9 --- /dev/null +++ b/src/albumModel.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +import os +import sys +from PySide import QtCore +from PySide import QtGui +from PySide import QtDeclarative + +from albumItem import AlbumItem + +class AlbumModel (QtCore.QAbstractListModel): + COLUMNS = ('album', ) + + def __init__ (self, data): + QtCore.QAbstractListModel.__init__ (self) + self._albums = data + self.setRoleNames (dict(enumerate(AlbumModel.COLUMNS))) + + def rowCount (self, parent=QtCore.QModelIndex()): + return len (self._albums) + + def data (self, index, role): + if index.isValid () and role == AlbumModel.COLUMNS.index ('album'): + return self._albums[index.row ()] + return None + + def getAlbumInRow (self, row): + assert row >= 0 and row < len (self._albums) + return self._albums [row] + + def updateThumb (self, row, url): + print "Changing album_art from",self._albums[row].album_art, "to:", url + assert row >= 0 and row < len (self._albums) + self._albums[row].album_art = url + + def get_albums (self): + return self._albums diff --git a/src/controller.py b/src/controller.py new file mode 100644 index 0000000..db4f296 --- /dev/null +++ b/src/controller.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +import os +import sys +from PySide import QtCore +from PySide import QtGui +from PySide import QtDeclarative + +from aa_search import MussorgskyAlbumArt + +try: + from tracker_backend_gi import TrackerBackendGI as TrackerBackend +except ImportError: + from tracker_backend_dbus import TrackerBackendDBus as TrackerBackend + +from albumItem import AlbumItem +from coverModel import CoversModel + +class DownloadThread (QtCore.QThread): + + def __init__ (self, model, album): + QtCore.QThread.__init__ (self) + self.downloader = MussorgskyAlbumArt () + self.model = model + self.album = album + + def run (self): + print "Running the thread" + MAX_OPTIONS = 4 + counter = 0 + for img in self.downloader.get_alternatives (self.album.artist, + self.album.title, MAX_OPTIONS): + if counter >= MAX_OPTIONS: + break + + self.model.updateData (counter, img) + counter += 1 + + +class MassiveDownloadsThread (QtCore.QThread): + + def __init__ (self, albumModel): + QtCore.QThread.__init__ (self) + self.downloader = MussorgskyAlbumArt () + self.albumModel = albumModel + + def run (self): + print "Download one cover per-album in a thread" + import time + time.sleep (4) + for albumItem in self.albumModel.get_albums (): + if albumItem.require_download: + self.downloader.get_album_art (albumItem) + + +class MussorgskyController (QtCore.QObject): + + def __init__ (self, rootContext): + QtCore.QObject.__init__ (self) + self.download = None + self.ctx = rootContext + self.tracker = TrackerBackend () + self.__is_downloading = False + + def _is_downloading (self): + return self.__is_downloading + + def _set_is_downloading (self, value): + if (value != self.__is_downloading): + self.__is_downloading = value + self.is_downloading_changed.emit () + + is_downloading_changed = QtCore.Signal () + is_downloading = QtCore.Property (bool, _is_downloading, notify=is_downloading_changed) + + @QtCore.Slot (QtCore.QObject) + def download_all (self, albumModel): + self._set_is_downloading (True) + self.download_all_thread = MassiveDownloadsThread (albumModel) + self.download_all_thread.finished.connect (self.download_all_finished) + self.download_all_thread.start () + print "now we are downloading..." + + @QtCore.Slot () + def download_all_finished (self): + self._set_is_downloading (False) + + + @QtCore.Slot (QtCore.QObject) + def get_options_for (self, albumItem): + print "Getting options for", albumItem.title + m = CoversModel (albumItem) + print m.rowCount () + self.ctx.setContextProperty ("coversModel", m) + self.download = DownloadThread (m, albumItem) + self.download.start () + + @QtCore.Slot (QtCore.QObject, int) + def save_option_for (self, coversModel, index): + print "Saving option", index + coverItem = coversModel.getData (index) + coverItem.save (coversModel.albumItem.get_aa ().get_media_art_path ()) + # Update the main model. Is this enough? + if not coverItem.initialImage and not coverItem.deleteAction: + coversModel.albumItem.album_art = None + coversModel.albumItem.album_art = coversModel.albumItem.get_aa().get_media_art_path () + elif coverItem.deleteAction: + coversModel.albumItem.resetAlbumArt () + + coversModel.cleanCache () + + + @QtCore.Slot () + def stop_pending_jobs (self): + if self.download : + self.download.quit () + + def get_all_albums (self): + """ + Return a list of AlbumItem objects to build the model + This is not called from QML, no need to make it a slot + """ + results = [] + for album_title, album_artist in self.tracker.get_all_albums (): + results.append (AlbumItem (album_title, album_artist)) + return results + diff --git a/src/coverItem.py b/src/coverItem.py new file mode 100644 index 0000000..5033364 --- /dev/null +++ b/src/coverItem.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +import os +import sys +from PySide import QtCore +from PySide import QtGui +from PySide import QtDeclarative + + +class CoverItem (QtCore.QObject): + + def __init__ (self, image, initialImage=False, deleteAction=False): + QtCore.QObject.__init__(self) + self.initialImage = initialImage + self.deleteAction = deleteAction + self._url = image + + def _url (self): + return self._url + + def _setUrl (self, url): + self._url = url + self.url_changed.emit () + + def save (self, destination): + if self.initialImage: + print " -> No changes" + return + + if self.deleteAction: + print " -> Remove the album art" + if os.path.exists (destination): + os.remove (destination) + return + + print " -> Saving:", self.url, "to", destination + os.rename (self.url, destination) + + + def remove (self): + print "Removing", self.url + if os.path.exists (self.url): + os.remove (self.url) + + + url_changed = QtCore.Signal () + url = QtCore.Property (unicode, _url, _setUrl, notify=url_changed) diff --git a/src/coverModel.py b/src/coverModel.py new file mode 100644 index 0000000..9e15909 --- /dev/null +++ b/src/coverModel.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +import os +import sys +from PySide import QtCore +from PySide import QtGui +from PySide import QtDeclarative + +from coverItem import CoverItem +from albumItem import AlbumItem + +class CoversModel (QtCore.QAbstractListModel): + COLUMNS = ('cover', ) + + def __init__ (self, albumItem): + QtCore.QAbstractListModel.__init__ (self) + self.albumItem = albumItem + + self.startOffset = 2 + self._alternatives = [ + CoverItem (self.albumItem.album_art, initialImage=True), + CoverItem ("images/blank_record.png", deleteAction = True), + CoverItem (None), + CoverItem (None), + CoverItem (None), + CoverItem (None) + ] + + self.setRoleNames (dict(enumerate(CoversModel.COLUMNS))) + + def rowCount (self, parent=QtCore.QModelIndex()): + return len (self._alternatives) + + def data (self, index, role): + if index.isValid () and role == CoversModel.COLUMNS.index ('cover'): + return self._alternatives[index.row ()] + return None + + def updateData (self, row, url): + position = row + self.startOffset + assert position >= 0 and position < len (self._alternatives) + self._alternatives[position].url = url + + def getData (self, row): + assert row >= 0 and row < len (self._alternatives) + return self._alternatives[row] + + def cleanCache (self): + """ + The model contains the urls of the downloaded alternatives. + This method removes those files. The selected image should have already been copied + to its final location. + """ + for i in range (self.startOffset, len (self._alternatives)): + # This url can be None if the image didn't finish downloading + if self._alternatives[i].url and os.path.exists (self._alternatives[i].url): + os.remove (self._alternatives[i].url) diff --git a/src/mussorgsky-qml.py b/src/mussorgsky-qml.py new file mode 100644 index 0000000..bc97a75 --- /dev/null +++ b/src/mussorgsky-qml.py @@ -0,0 +1,44 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import sys +from PySide.QtCore import * +from PySide.QtGui import * +from PySide.QtDeclarative import QDeclarativeView + +from albumModel import AlbumModel +from controller import MussorgskyController + +# Create Qt application and the QDeclarative view +app = QApplication(sys.argv) +view = QDeclarativeView() + +controller = MussorgskyController (view.rootContext ()) + +#from albumItem import AlbumItem +#MOCK_DATA = [AlbumItem (u"x", u"y") for i in xrange (0, 100)] +#albumModel = AlbumModel (MOCK_DATA) + +albumModel = AlbumModel (controller.get_all_albums()) +print "Model with", albumModel.rowCount(), "rows" + +#from coverModel import CoversModel +#coverModel = CoversModel () + +rc = view.rootContext () +rc.setContextProperty ('albumModel', albumModel) +rc.setContextProperty ('missionControl', controller) +#rc.setContextProperty ('coversModel', coverModel) + +# Create an URL to the QML file +#url = QUrl('view.qml') +url = QUrl ("../../ui/main.qml") +# Set the QML file and show +view.setSource(url) +view.engine().quit.connect (app.quit) + +view.show() + + +# Enter Qt main loop +sys.exit(app.exec_()) diff --git a/src/qml/aa_search.py b/src/qml/aa_search.py deleted file mode 100644 index db716c5..0000000 --- a/src/qml/aa_search.py +++ /dev/null @@ -1,258 +0,0 @@ -#!/usr/bin/env python2.5 -import os -from utils import UrllibWrapper -import dbus, time -import string -import urllib - - -LASTFM_APIKEY = "1e1d53528c86406757a6887addef0ace" -BASE_LASTFM = "http://ws.audioscrobbler.com/2.0/?method=album.getinfo" - - -BASE_MSN = "http://www.bing.com/images/search?q=" -MSN_MEDIUM = "+filterui:imagesize-medium" -MSN_SMALL = "+filterui:imagesize-medium" -MSN_SQUARE = "+filterui:aspect-square" -MSN_PHOTO = "+filterui:photo-graphics" - -CACHE_LOCATION = os.path.join (os.getenv ("HOME"), ".cache", "mussorgsky") -# LastFM: -# http://www.lastfm.es/api/show?service=290 -# - - -import threading -class AADownloadThread (threading.Thread): - - def __init__ (self, url, artist, album, counter): - threading.Thread.__init__ (self, target=self.grab_image, args=(url,)) - self.counter = counter - self.artistName = artist.replace (" ", "_") - self.albumName = album.replace (" ", "_") - self.image_path = None - self.urllib_wrapper = UrllibWrapper () - - def grab_image (self, image_url): - print "Working", self.counter - image = self.urllib_wrapper.get_url (image_url) - if (image): - self.image_path = os.path.join (CACHE_LOCATION, self.artistName + self.albumName + str(self.counter)) - self.urllib_wrapper.save_content_into_file (image, self.image_path) - - def get_result (self): - return self.image_path - - - -class MussorgskyAlbumArt: - - def __init__ (self): - bus = dbus.SessionBus () - - if (not os.path.exists (CACHE_LOCATION)): - os.makedirs (CACHE_LOCATION) - - self.urllib_wrapper = UrllibWrapper () - - def get_possible_url (self, artist, album, amount=4): - results_page = self.__msn_images (artist, album) - return self.__get_url_from_msn_results_page (results_page) - - - def get_album_art (self, albumItem, force=False): - """ - Save the first available result as the albumart for that item - """ - filename = albumItem.get_aa().get_media_art_path () - if (os.path.exists (filename) and not force): - print "Album art already there " + filename - return - - results_page = self.__msn_images (albumItem.artist, albumItem.title) - for online_resource in self.__get_url_from_msn_results_page (results_page): - print "Trying:", online_resource - content = self.urllib_wrapper.get_url (online_resource) - if (content): - print "Saved on: %s " % (filename) - self.urllib_wrapper.save_content_into_file (content, filename) - albumItem.album_art = filename - break - - def get_alternatives (self, artist, album, max_alternatives=4): - """ - return a list of images in the local disk - """ - results_page = self.__msn_images (artist, album) - return self.__process_results_page (results_page, artist, album, max_alternatives) - - def get_alternatives_free_text (self, search_text, max_alternatives=4): - results_page = self.__msn_images_free_text (search_text) - return self.__process_results_page (results_page, max_alternatives) - - def __process_results_page (self, results_page, artist, album, max_alternatives): - counter = 0 - threads = [] - for image_url in self.__get_url_from_msn_results_page (results_page): - if (not image_url): - # Some searches doesn't return anything at all! - break - - if (counter >= max_alternatives): - break - - t = AADownloadThread (image_url, artist, album, counter) - t.start () - threads.append (t) - counter += 1 - - for t in threads: - t.join (5) - if (t.isAlive ()): - yield None - else: - yield t.get_result () - - - def save_alternative (self, artist, album, img_path, thumb_path): - """ - This is done now in the controller - """ - if not os.path.exists (img_path) or not os.path.exists (thumb_path): - print "**** CRITICAL **** image in path", path, "doesn't exist!" - return (None, None) - - filename = getCoverArtFileName (album) - thumbnail = getCoverArtThumbFileName (album) - - os.rename (img_path, filename) - os.rename (thumb_path, thumbnail) - - return (filename, thumbnail) - - def reset_alternative (self, artist, album): - - for filepath in [getCoverArtFileName (album), - getCoverArtThumbFileName (album)]: - if os.path.exists (filepath): - os.remove (filepath) - - def __msn_images (self, artist, album): - - good_artist = self.__clean_string_for_search (artist) - good_album = self.__clean_string_for_search (album) - - if (good_album and good_artist): - full_try = BASE_MSN + good_album + "+" + good_artist + MSN_MEDIUM + MSN_SQUARE - print "Searching (album + artist): %s" % (full_try) - result = self.urllib_wrapper.get_url (full_try) - if (result and result.find ("no_results") == -1): - return result - - if (album): - if (album.lower ().find ("greatest hit") != -1): - print "Ignoring '%s': too generic" % (album) - pass - else: - album_try = BASE_MSN + good_album + MSN_MEDIUM + MSN_SQUARE - print "Searching (album): %s" % (album_try) - result = self.urllib_wrapper.get_url (album_try) - if (result and result.find ("no_results") == -1): - return result - - if (artist): - artist_try = BASE_MSN + good_artist + "+CD+music" + MSN_SMALL + MSN_SQUARE + MSN_PHOTO - print "Searching (artist CD): %s" % (artist_try) - result = self.urllib_wrapper.get_url (artist_try) - if (result and result.find ("no_results") == -1): - return result - - return None - - def __msn_images_free_text (self, search_text): - full_try = BASE_MSN + self.__clean_string_for_search (search_text) + MSN_MEDIUM + MSN_SQUARE - result = self.urllib_wrapper.get_url (full_try) - return result - - - def __get_url_from_msn_results_page (self, page): - if (not page): - return - - current_option = None - starting_at = 0 - - # 500 is just a safe limit - for i in range (0, 500): - # Iterate until find a jpeg - start = page.find ("imgurl:"", starting_at) - if (start == -1): - yield None - end = page.find ("&", start + len ("imgurl:"")) - current_option = page [start + len ("imgurl:""): end].replace ("amp;", "") - if (current_option.lower().endswith (".jpg") or - current_option.lower().endswith (".jpeg")): - yield current_option - starting_at = end - - - def __clean_string_for_search (self, text): - if (not text or len (text) < 1): - return None - - bad_stuff = "_:?\\-~" - clean = text - for c in bad_stuff: - clean = clean.replace (c, " ") - - clean.replace ("/", "%2F") - clean = clean.replace (" CD1", "").replace(" CD2", "") - return urllib.quote(clean) - - -if __name__ == "__main__": - import sys - from optparse import OptionParser - - parser = OptionParser() - parser.add_option ("-p", "--print", dest="print_paths", - action="store_true", default=True, - help="Print the destination paths") - parser.add_option ("-r", "--retrieve", dest="retrieve", - action="store_true", default=False, - help="Try to retrieve the online content") - parser.add_option ("-m", "--multiple", dest="multiple", - action="store_true", default=False, - help="Show more than one option") - parser.add_option ("-a", "--artist", dest="artist", type="string", - help="ARTIST to look for", metavar="ARTIST") - parser.add_option ("-b", "--album", dest="album", type="string", - help="ALBUM to look for", metavar="ALBUM") - - (options, args) = parser.parse_args () - print options - if (not options.artist and not options.album): - parser.print_help () - sys.exit (-1) - - if (options.multiple and options.retrieve): - print "Multiple and retrieve are incompatible" - parser.print_help () - sys.exit (-1) - - if options.print_paths and not options.retrieve: - print "Album art:", getCoverArtFileName (options.album) - print "Thumbnail:", getCoverArtThumbFileName (options.album) - - if options.retrieve: - maa = MussorgskyAlbumArt () - maa.get_album_art (options.artist, options.album) - - if options.multiple: - start = time.time () - maa = MussorgskyAlbumArt () - for (img, thumb) in maa.get_alternatives (options.artist, options.album, 5): - print img - print thumb - end = time.time () - print end - start diff --git a/src/qml/albumArt.py b/src/qml/albumArt.py deleted file mode 100644 index 74a6432..0000000 --- a/src/qml/albumArt.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -from PySide.QtCore import QRegExp -import re -import hashlib -import os - -MULTIWHITE = re.compile ("\s\s+") - -MEDIAART_PATH = os.path.join (os.environ["HOME"], ".cache", "media-art") -GENERATED_PATH = os.path.join (os.environ["HOME"], ".cache", "music-suite", "generated") -THUMB_PATH = os.path.join (os.environ["HOME"], ".cache", "music-suite", "thumbnails") - -# -# Python translation of : -# https://qt.gitorious.org/qt/qt/blobs/d12681a4cf1227d0e92fc7cf12aa3977e6ffe3fe/src/corelib/tools/qhash.cpp#line76 -# -def qhash (inputstr): - - if isinstance (inputstr, str): - instr = inputstr - elif isinstance (inputstr, unicode): - instr = inputstr.encode ("utf8") - else: - return -1 - - h = 0x00000000 - for i in range (0, len (instr)): - h = (h << 4) + ord(instr[i]) - h ^= (h & 0xf0000000) >> 23 - h &= 0x0fffffff - - return h - -class AlbumArt: - def __init__ (self, album, artist, flavor="common", aaType="album"): - self.album = album - self.albumMD5 = self.__get_md5 (album) - self.artist = artist - self.artistMD5 = self.__get_md5 (artist) - self.flavor = "common" - self.aaType = "album" - - def get_media_art_path (self): - """ - The raw media-art downloaded or taken from the folder with the music files - """ - filename = "album-" + self.artistMD5 + "-" + self.albumMD5 + ".jpeg" - return os.path.join (MEDIAART_PATH, filename) - - def get_generated (self): - filename = str (qhash (self.album + self.artist + self.flavor + self.aaType)) + ".png" - return os.path.join (GENERATED_PATH, self.flavor, filename) - - def get_thumbnail (self): - filename = str (qhash (self.album + self.artist + self.flavor + self.aaType)) + ".jpeg" - return os.path.join (THUMB_PATH, self.flavor, filename) - - - - def __get_md5 (self, something): - if not something or len (something) == 0: - something = " " - else: - # This can very probably be done with the native python regexp - blocks = QRegExp ("(\\([^\\)]*\\))" + - "|(\\[[^\\]]*\\])" + - "|(\\{[^\\}]*\\})" + - "|(\\<[^\\>]*\\>)") - something = blocks.replace (something, "") - - evilchars = QRegExp ("[\\(\\)\\_\\{\\}\\[\\]\\!\\@\\#\\$\\^\\&\\*\\+\\=\\|\\\\\\/\\\"\\'\\?\\<\\>\\~\\`]+") - something = evilchars.replace (something, "") - - - something = MULTIWHITE.sub (" ", something) - something = something.strip () - - md5 = hashlib.md5 (something.lower ().encode ("utf-8")) - return md5.hexdigest () - - -if __name__ == "__main__": - - aa = AlbumArt ("Breakfast in america", "Björk") - print "Source: \"" + aa.get_media_art_path () + "\"" - print "Generated: \"" + aa.get_generated () + "\"" - print "Thumbnail: \"" + aa.get_thumbnail () + "\"" - diff --git a/src/qml/albumItem.py b/src/qml/albumItem.py deleted file mode 100644 index 0cf25c8..0000000 --- a/src/qml/albumItem.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import sys -import time -from PySide import QtCore -from PySide import QtGui -from PySide import QtDeclarative - -from albumArt import AlbumArt - -class AlbumItem (QtCore.QObject): - - def __init__ (self, title, artist): - QtCore.QObject.__init__(self) - self._title = title - self._artist = artist - self.aa = AlbumArt (self._title, self._artist) - self.require_download = False - if os.path.exists (self.aa.get_media_art_path ()): - self._album_art = self.aa.get_media_art_path () - elif os.path.exists (self.aa.get_generated ()): - self._album_art = self.aa.get_generated () - self.require_download = True - else: - self.require_download = True - self._album_art = "images/blank_record.png" - - def _title (self): - return self._title - - def _artist (self): - return self._artist - - def _albumArt (self): - return self._album_art - - def _setAlbumArt (self, path): - print "Setting the new album art to", path - self.require_download = False - self._album_art = path - self.album_art_changed.emit () - - def resetAlbumArt (self): - print "Reset album art!" - #self._album_art = None - counter = 0 - while counter < 3: - if os.path.exists (self.aa.get_generated ()): - break - time.sleep (1) - counter += 1 - - if os.path.exists (self.aa.get_generated ()): - print "Using the generated" - self._album_art = self.aa.get_generated () - self.require_download = True - else: - print "Setting to none (", self.aa.get_generated (), " doesn't exist)" - self.require_download = True - self._album_art = "images/blank_record.png" - self.album_art_changed.emit () - - def get_aa (self): - return self.aa - - prop_changed = QtCore.Signal () - album_art_changed = QtCore.Signal () - - title = QtCore.Property (unicode, _title, notify=prop_changed) - artist = QtCore.Property (unicode, _artist, notify=prop_changed) - album_art = QtCore.Property (unicode, - _albumArt, - _setAlbumArt, - notify=album_art_changed) - diff --git a/src/qml/albumModel.py b/src/qml/albumModel.py deleted file mode 100644 index a8190c9..0000000 --- a/src/qml/albumModel.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import sys -from PySide import QtCore -from PySide import QtGui -from PySide import QtDeclarative - -from albumItem import AlbumItem - -class AlbumModel (QtCore.QAbstractListModel): - COLUMNS = ('album', ) - - def __init__ (self, data): - QtCore.QAbstractListModel.__init__ (self) - self._albums = data - self.setRoleNames (dict(enumerate(AlbumModel.COLUMNS))) - - def rowCount (self, parent=QtCore.QModelIndex()): - return len (self._albums) - - def data (self, index, role): - if index.isValid () and role == AlbumModel.COLUMNS.index ('album'): - return self._albums[index.row ()] - return None - - def getAlbumInRow (self, row): - assert row >= 0 and row < len (self._albums) - return self._albums [row] - - def updateThumb (self, row, url): - print "Changing album_art from",self._albums[row].album_art, "to:", url - assert row >= 0 and row < len (self._albums) - self._albums[row].album_art = url - - def get_albums (self): - return self._albums diff --git a/src/qml/controller.py b/src/qml/controller.py deleted file mode 100644 index db4f296..0000000 --- a/src/qml/controller.py +++ /dev/null @@ -1,126 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import sys -from PySide import QtCore -from PySide import QtGui -from PySide import QtDeclarative - -from aa_search import MussorgskyAlbumArt - -try: - from tracker_backend_gi import TrackerBackendGI as TrackerBackend -except ImportError: - from tracker_backend_dbus import TrackerBackendDBus as TrackerBackend - -from albumItem import AlbumItem -from coverModel import CoversModel - -class DownloadThread (QtCore.QThread): - - def __init__ (self, model, album): - QtCore.QThread.__init__ (self) - self.downloader = MussorgskyAlbumArt () - self.model = model - self.album = album - - def run (self): - print "Running the thread" - MAX_OPTIONS = 4 - counter = 0 - for img in self.downloader.get_alternatives (self.album.artist, - self.album.title, MAX_OPTIONS): - if counter >= MAX_OPTIONS: - break - - self.model.updateData (counter, img) - counter += 1 - - -class MassiveDownloadsThread (QtCore.QThread): - - def __init__ (self, albumModel): - QtCore.QThread.__init__ (self) - self.downloader = MussorgskyAlbumArt () - self.albumModel = albumModel - - def run (self): - print "Download one cover per-album in a thread" - import time - time.sleep (4) - for albumItem in self.albumModel.get_albums (): - if albumItem.require_download: - self.downloader.get_album_art (albumItem) - - -class MussorgskyController (QtCore.QObject): - - def __init__ (self, rootContext): - QtCore.QObject.__init__ (self) - self.download = None - self.ctx = rootContext - self.tracker = TrackerBackend () - self.__is_downloading = False - - def _is_downloading (self): - return self.__is_downloading - - def _set_is_downloading (self, value): - if (value != self.__is_downloading): - self.__is_downloading = value - self.is_downloading_changed.emit () - - is_downloading_changed = QtCore.Signal () - is_downloading = QtCore.Property (bool, _is_downloading, notify=is_downloading_changed) - - @QtCore.Slot (QtCore.QObject) - def download_all (self, albumModel): - self._set_is_downloading (True) - self.download_all_thread = MassiveDownloadsThread (albumModel) - self.download_all_thread.finished.connect (self.download_all_finished) - self.download_all_thread.start () - print "now we are downloading..." - - @QtCore.Slot () - def download_all_finished (self): - self._set_is_downloading (False) - - - @QtCore.Slot (QtCore.QObject) - def get_options_for (self, albumItem): - print "Getting options for", albumItem.title - m = CoversModel (albumItem) - print m.rowCount () - self.ctx.setContextProperty ("coversModel", m) - self.download = DownloadThread (m, albumItem) - self.download.start () - - @QtCore.Slot (QtCore.QObject, int) - def save_option_for (self, coversModel, index): - print "Saving option", index - coverItem = coversModel.getData (index) - coverItem.save (coversModel.albumItem.get_aa ().get_media_art_path ()) - # Update the main model. Is this enough? - if not coverItem.initialImage and not coverItem.deleteAction: - coversModel.albumItem.album_art = None - coversModel.albumItem.album_art = coversModel.albumItem.get_aa().get_media_art_path () - elif coverItem.deleteAction: - coversModel.albumItem.resetAlbumArt () - - coversModel.cleanCache () - - - @QtCore.Slot () - def stop_pending_jobs (self): - if self.download : - self.download.quit () - - def get_all_albums (self): - """ - Return a list of AlbumItem objects to build the model - This is not called from QML, no need to make it a slot - """ - results = [] - for album_title, album_artist in self.tracker.get_all_albums (): - results.append (AlbumItem (album_title, album_artist)) - return results - diff --git a/src/qml/coverItem.py b/src/qml/coverItem.py deleted file mode 100644 index 5033364..0000000 --- a/src/qml/coverItem.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import sys -from PySide import QtCore -from PySide import QtGui -from PySide import QtDeclarative - - -class CoverItem (QtCore.QObject): - - def __init__ (self, image, initialImage=False, deleteAction=False): - QtCore.QObject.__init__(self) - self.initialImage = initialImage - self.deleteAction = deleteAction - self._url = image - - def _url (self): - return self._url - - def _setUrl (self, url): - self._url = url - self.url_changed.emit () - - def save (self, destination): - if self.initialImage: - print " -> No changes" - return - - if self.deleteAction: - print " -> Remove the album art" - if os.path.exists (destination): - os.remove (destination) - return - - print " -> Saving:", self.url, "to", destination - os.rename (self.url, destination) - - - def remove (self): - print "Removing", self.url - if os.path.exists (self.url): - os.remove (self.url) - - - url_changed = QtCore.Signal () - url = QtCore.Property (unicode, _url, _setUrl, notify=url_changed) diff --git a/src/qml/coverModel.py b/src/qml/coverModel.py deleted file mode 100644 index 9e15909..0000000 --- a/src/qml/coverModel.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -import os -import sys -from PySide import QtCore -from PySide import QtGui -from PySide import QtDeclarative - -from coverItem import CoverItem -from albumItem import AlbumItem - -class CoversModel (QtCore.QAbstractListModel): - COLUMNS = ('cover', ) - - def __init__ (self, albumItem): - QtCore.QAbstractListModel.__init__ (self) - self.albumItem = albumItem - - self.startOffset = 2 - self._alternatives = [ - CoverItem (self.albumItem.album_art, initialImage=True), - CoverItem ("images/blank_record.png", deleteAction = True), - CoverItem (None), - CoverItem (None), - CoverItem (None), - CoverItem (None) - ] - - self.setRoleNames (dict(enumerate(CoversModel.COLUMNS))) - - def rowCount (self, parent=QtCore.QModelIndex()): - return len (self._alternatives) - - def data (self, index, role): - if index.isValid () and role == CoversModel.COLUMNS.index ('cover'): - return self._alternatives[index.row ()] - return None - - def updateData (self, row, url): - position = row + self.startOffset - assert position >= 0 and position < len (self._alternatives) - self._alternatives[position].url = url - - def getData (self, row): - assert row >= 0 and row < len (self._alternatives) - return self._alternatives[row] - - def cleanCache (self): - """ - The model contains the urls of the downloaded alternatives. - This method removes those files. The selected image should have already been copied - to its final location. - """ - for i in range (self.startOffset, len (self._alternatives)): - # This url can be None if the image didn't finish downloading - if self._alternatives[i].url and os.path.exists (self._alternatives[i].url): - os.remove (self._alternatives[i].url) diff --git a/src/qml/mussorgsky-qml.py b/src/qml/mussorgsky-qml.py deleted file mode 100644 index bc97a75..0000000 --- a/src/qml/mussorgsky-qml.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -import sys -from PySide.QtCore import * -from PySide.QtGui import * -from PySide.QtDeclarative import QDeclarativeView - -from albumModel import AlbumModel -from controller import MussorgskyController - -# Create Qt application and the QDeclarative view -app = QApplication(sys.argv) -view = QDeclarativeView() - -controller = MussorgskyController (view.rootContext ()) - -#from albumItem import AlbumItem -#MOCK_DATA = [AlbumItem (u"x", u"y") for i in xrange (0, 100)] -#albumModel = AlbumModel (MOCK_DATA) - -albumModel = AlbumModel (controller.get_all_albums()) -print "Model with", albumModel.rowCount(), "rows" - -#from coverModel import CoversModel -#coverModel = CoversModel () - -rc = view.rootContext () -rc.setContextProperty ('albumModel', albumModel) -rc.setContextProperty ('missionControl', controller) -#rc.setContextProperty ('coversModel', coverModel) - -# Create an URL to the QML file -#url = QUrl('view.qml') -url = QUrl ("../../ui/main.qml") -# Set the QML file and show -view.setSource(url) -view.engine().quit.connect (app.quit) - -view.show() - - -# Enter Qt main loop -sys.exit(app.exec_()) diff --git a/src/qml/tracker_backend_dbus.py b/src/qml/tracker_backend_dbus.py deleted file mode 100644 index 0a78d4a..0000000 --- a/src/qml/tracker_backend_dbus.py +++ /dev/null @@ -1,59 +0,0 @@ -import dbus -import os - -TRACKER = "org.freedesktop.Tracker1" -TRACKER_OBJ = "/org/freedesktop/Tracker1/Resources" - -# (album name, artist1|artist2|... , number of artists) -ALBUM_ARTISTS_COUNTER = """ -SELECT nie:title (?album) nmm:artistName (?artist) COUNT (?artist) -WHERE - { - ?album a nmm:MusicAlbum ; - nmm:albumArtist ?artist . - } -GROUP BY ?album -""" - -SONGS = """ -SELECT ?u nmm:artistName(?artist) nmm:albumTitle(?album) WHERE -{ - ?u a nmm:MusicPiece ; - nmm:performer ?artist ; - nmm:musicAlbum ?album . -} -""" - -class TrackerBackendDBus: - - def __init__ (self): - self.dbus = dbus.SessionBus () - self.tracker = self.dbus.get_object (TRACKER, TRACKER_OBJ) - self.resources = dbus.Interface (self.tracker, - "org.freedesktop.Tracker1.Resources") - - def get_all_albums (self): - results = self.resources.SparqlQuery (ALBUM_ARTISTS_COUNTER) - return self.__iter_results (results) - - def get_all_songs (self): - results = self.resources.SparqlQuery (SONGS) - for uri, artist, album in results: - yield (uri, artist, album) - - def __iter_results (self, results): - for (title, artist, counter) in results: - print "Executed the yield", counter - if int(counter) > 1: - yield (unicode(title), "Various artists") - else: - yield (unicode(title), unicode(artist)) - - -if __name__ == "__main__": - b = TrackerBackendDBus () - for pair in b.get_all_songs (): - print "Outter loop" - print pair - print "ok" - diff --git a/src/qml/tracker_backend_gi.py b/src/qml/tracker_backend_gi.py deleted file mode 100644 index f5329a0..0000000 --- a/src/qml/tracker_backend_gi.py +++ /dev/null @@ -1,40 +0,0 @@ -import gi -from gi.repository import Tracker - -# (album name, artist1|artist2|... , number of artists) -ALBUM_ARTISTS_COUNTER = """ -SELECT nie:title (?album) nmm:artistName (?artist) COUNT (?artist) -WHERE - { - ?album a nmm:MusicAlbum ; - nmm:albumArtist ?artist . - } -GROUP BY ?album -""" - -class TrackerBackendGI: - - def __init__ (self): - self.conn = Tracker.SparqlConnection.get (None) - - def get_all_albums (self): - """ - Generator returning (album_title, album_artist) - If the album has more than one artist, "Various artists" - """ - cursor = self.conn.query (ALBUM_ARTISTS_COUNTER, None) - while cursor.next (None): - album_title = cursor.get_string (0)[0] - if cursor.get_integer(2) > 1: - album_artist = "Various artists" - else: - album_artist = cursor.get_string (1)[0] - - yield (album_title, album_artist) - - - -if __name__ == "__main__": - - tracker = TrackerBackendGI() - tracker.get_all_albums () diff --git a/src/qml/utils.py b/src/qml/utils.py deleted file mode 100644 index daffb06..0000000 --- a/src/qml/utils.py +++ /dev/null @@ -1,59 +0,0 @@ -import gobject - -def escape_html (text, max_length=40): - if (len (text) > max_length): - cutpoint = text.find (' ', max_length-10) - if (cutpoint == -1 or cutpoint > max_length): - cutpoint = max_length - text = text [0:cutpoint] + "..." - return gobject.markup_escape_text (text) - -def is_empty (text): - return not text or len (text.strip ()) == 0 - -# Set socket timeout -import socket -import urllib2 - -timeout = 5 -socket.setdefaulttimeout(timeout) - -class UrllibWrapper (): - - def save_content_into_file (self, content, filename): - output = open (filename, 'w') - output.write (content) - output.close () - - 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 - - - -class Set: - - def __init__ (self): - self.d = {} - self.k = None - - def insert (self, element): - if (not self.d.has_key (element)): - self.d[element] = 1 - self.k = None - - def as_list (self): - if (self.k): - return self.k - - self.k = self.d.keys () - self.k.sort () - return self.k - - - diff --git a/src/tracker_backend_dbus.py b/src/tracker_backend_dbus.py new file mode 100644 index 0000000..0a78d4a --- /dev/null +++ b/src/tracker_backend_dbus.py @@ -0,0 +1,59 @@ +import dbus +import os + +TRACKER = "org.freedesktop.Tracker1" +TRACKER_OBJ = "/org/freedesktop/Tracker1/Resources" + +# (album name, artist1|artist2|... , number of artists) +ALBUM_ARTISTS_COUNTER = """ +SELECT nie:title (?album) nmm:artistName (?artist) COUNT (?artist) +WHERE + { + ?album a nmm:MusicAlbum ; + nmm:albumArtist ?artist . + } +GROUP BY ?album +""" + +SONGS = """ +SELECT ?u nmm:artistName(?artist) nmm:albumTitle(?album) WHERE +{ + ?u a nmm:MusicPiece ; + nmm:performer ?artist ; + nmm:musicAlbum ?album . +} +""" + +class TrackerBackendDBus: + + def __init__ (self): + self.dbus = dbus.SessionBus () + self.tracker = self.dbus.get_object (TRACKER, TRACKER_OBJ) + self.resources = dbus.Interface (self.tracker, + "org.freedesktop.Tracker1.Resources") + + def get_all_albums (self): + results = self.resources.SparqlQuery (ALBUM_ARTISTS_COUNTER) + return self.__iter_results (results) + + def get_all_songs (self): + results = self.resources.SparqlQuery (SONGS) + for uri, artist, album in results: + yield (uri, artist, album) + + def __iter_results (self, results): + for (title, artist, counter) in results: + print "Executed the yield", counter + if int(counter) > 1: + yield (unicode(title), "Various artists") + else: + yield (unicode(title), unicode(artist)) + + +if __name__ == "__main__": + b = TrackerBackendDBus () + for pair in b.get_all_songs (): + print "Outter loop" + print pair + print "ok" + diff --git a/src/tracker_backend_gi.py b/src/tracker_backend_gi.py new file mode 100644 index 0000000..f5329a0 --- /dev/null +++ b/src/tracker_backend_gi.py @@ -0,0 +1,40 @@ +import gi +from gi.repository import Tracker + +# (album name, artist1|artist2|... , number of artists) +ALBUM_ARTISTS_COUNTER = """ +SELECT nie:title (?album) nmm:artistName (?artist) COUNT (?artist) +WHERE + { + ?album a nmm:MusicAlbum ; + nmm:albumArtist ?artist . + } +GROUP BY ?album +""" + +class TrackerBackendGI: + + def __init__ (self): + self.conn = Tracker.SparqlConnection.get (None) + + def get_all_albums (self): + """ + Generator returning (album_title, album_artist) + If the album has more than one artist, "Various artists" + """ + cursor = self.conn.query (ALBUM_ARTISTS_COUNTER, None) + while cursor.next (None): + album_title = cursor.get_string (0)[0] + if cursor.get_integer(2) > 1: + album_artist = "Various artists" + else: + album_artist = cursor.get_string (1)[0] + + yield (album_title, album_artist) + + + +if __name__ == "__main__": + + tracker = TrackerBackendGI() + tracker.get_all_albums () diff --git a/src/utils.py b/src/utils.py new file mode 100644 index 0000000..daffb06 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,59 @@ +import gobject + +def escape_html (text, max_length=40): + if (len (text) > max_length): + cutpoint = text.find (' ', max_length-10) + if (cutpoint == -1 or cutpoint > max_length): + cutpoint = max_length + text = text [0:cutpoint] + "..." + return gobject.markup_escape_text (text) + +def is_empty (text): + return not text or len (text.strip ()) == 0 + +# Set socket timeout +import socket +import urllib2 + +timeout = 5 +socket.setdefaulttimeout(timeout) + +class UrllibWrapper (): + + def save_content_into_file (self, content, filename): + output = open (filename, 'w') + output.write (content) + output.close () + + 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 + + + +class Set: + + def __init__ (self): + self.d = {} + self.k = None + + def insert (self, element): + if (not self.d.has_key (element)): + self.d[element] = 1 + self.k = None + + def as_list (self): + if (self.k): + return self.k + + self.k = self.d.keys () + self.k.sort () + return self.k + + +