Dugan Chen's Homepage

Various Things

Using PyQt4 to download images from Last.fm

The following is code that I’m planning to roll almost directly into Quetzalcoatl. It asynchronously downloads an image from Last.fm and loads it into a widget. The image then scales with its containing widget, using maximum amount of space while keeping its aspect ratio.

Keep in mind that it’s example code, not production code, and thus doesn’t have the requisite error checking (for example, it doesn’t check if a directory exists before creating it).

John Paul’s blog entry, Why QNetworkAccessManager should not have the finished(QNetworkReply *) signal, was an influence.

Here it is.

#!/usr/bin/env python2.6
 
from sip import setapi
 
setapi("QDate", 2)
setapi("QDateTime", 2)
setapi("QTextStream", 2)
setapi("QTime", 2)
setapi("QVariant", 2)
setapi("QString", 2)
setapi("QUrl", 2)
 
from PyQt4.QtCore import QUrl, QFile, QIODevice, Qt
from PyQt4.QtGui import QApplication, QMainWindow, QLabel, QPixmap, QSizePolicy
from PyQt4.QtNetwork import QNetworkAccessManager, QNetworkRequest
from sys import argv
from base64 import b64decode
from urllib import urlencode
from json import loads
from os.path import join, split
from os import makedirs
from urlparse import urlunsplit

LAST_FM_KEY = b64decode('Mjk1YTAxY2ZhNjVmOWU1MjFiZGQyY2MzYzM2ZDdjODk=')
MUSICBRAINZ_ALBUMID='4321855e-8e8e-4786-8506-28e6d69633b9'


def main():
    app = QApplication(argv)
    main = MainWindow()
    main.show()
    exit(app.exec_())


class MainWindow(QMainWindow):
 
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.manager = QNetworkAccessManager(self)
        self.artLabel = ArtLabel()
        self.setCentralWidget(self.artLabel)

        url = album_info_url(MUSICBRAINZ_ALBUMID)

        request = QNetworkRequest()
        
        request.setUrl(QUrl(url))
        request.setRawHeader('User-Agent', 'Quetzalcoatl 2.0')
        reply = self.manager.get(request)
        reply.finished.connect(self.replyFinished)

    def replyFinished(self):
        reply = self.sender()
        raw = reply.readAll()
        json = loads(raw.data())
        mega = [x['#text'] for x in json['album']['image'] if x['size'] == 'mega'][0]

        imageRequest = QNetworkRequest()
        imageRequest.setUrl(QUrl(mega))

        imageReply = self.manager.get(imageRequest)
        imageReply.finished.connect(self.imageDownloaded)

        reply.deleteLater()

    def imageDownloaded(self):
        reply = self.sender()
        url = reply.url().path()
        path = join(*url.split('/')[2:])
        head, _ = split(path)
        makedirs(head)
        newFile = QFile()
        newFile.setFileName(path)
        newFile.open(QIODevice.WriteOnly)
        newFile.write(reply.readAll())
        newFile.close()
        self.artLabel.load(path)
        reply.deleteLater()


class ArtLabel(QLabel):
    
    """
    A QLabel that scales album art images properly.
    """

    def __init__(self, parent=None):
        
        """ Initializes the art label. """
        
        super(ArtLabel, self).__init__(parent)
        self.pixmap = QPixmap() 
        self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
        self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

    def resizeEvent(self, event):
        
        """ Handles resize events. """

        if not self.pixmap.isNull():
            size = event.size()
            self.refresh(size.width(), size.height())

    def load(self, filename):
        self.pixmap.load(filename)
        self.refresh(self.width(), self.height())

    def refresh(self, width, height):
        pixmapWidth = self.pixmap.width()
        pixmapHeight = self.pixmap.height()

        if pixmapHeight > 0:
            pixmapRatio = pixmapWidth / pixmapHeight
        else:
            pixmapRatio = pixmapWidth

        w = width
        h = height

        if h > 0:
            ratio = w / h
        else:
            ratio = w

        if pixmapRatio < ratio:
            self.setPixmap(self.pixmap.scaledToHeight(h, Qt.SmoothTransformation))
        else:
            self.setPixmap(self.pixmap.scaledToWidth(w, Qt.SmoothTransformation))

def album_info_url(mbid):
    scheme = 'http'
    netloc = 'ws.audioscrobbler.com'
    path = '/2.0/'
    query = urlencode({'mbid': mbid, 'api_key': LAST_FM_KEY,
        'method': 'album.getinfo', 'format': 'json'})
    fragment = ''
    parts = (scheme, netloc, path, query, fragment)
    return urlunsplit(parts)

 
if __name__ == "__main__":
   main()

Album art downloading will be coming to Quetzalcoatl soon. Hopefully this week.