Album Art

Purpose

  Album art is used to provide a visual experience to a users media collection in a familar format. Users are use to having their media collection as Albums, Tapes, or Records. We call the different types Albums in general, and each Album has one or more images associated with it to make it easier for the user to identify.

Art

Album Art comes in various types, and allows for each item to have multiple and even unique images. Meta data tags can have different limits and types of art allowed to be stored in the meta data. By default all images will be stored in a local file cache so that any items that we are not able to write images to will still be able to have images in Songbird. This will also allow for faster loads for the images in Songbird. The main property for images is the primaryImageURL which is a resource://sb-artwork/ url that points to the file in the album art cache.

Metadata types:

Since we started with ID3 we will be using it as our base for what we support and how things are configured. The types of covers we will support is as follows:

Type  Value Hex Value
OTHER (Secondary) 0 0x00
FILEICON 1 0x01
OTHERFILEICON 2 0x02
FRONTCOVER (Primary) 3 0x03
BACKCOVER 4 0x04
LEAFLETPAGE 5 0x05
MEDIA 6 0x06
LEADARTIST 7 0x07
ARTIST 8 0x08
CONDUCTOR 9 0x09
BAND 10 0x0A
COMPOSER 11 0x0B
LYRICIST 12 0x0C
RECORDINGLOCATION 13 0x0D
DURINGRECORDING 14 0x0E
DURINGPERFORMANCE 15 0x0F
MOVIESCREENCAPTURE 16 0x10
COLOUREDFISH 17 0x11
ILLUSTRATION 18 0x12
BANDLOGO 19 0x13
PUBLISHERLOGO 20 0x14

ID3: (MP3)

http://www.id3.org/
 We use TAGLib for suporting the ID3 tags. The artwork is retrieved from the APIC frames with the following structure:

<Header for 'Attached picture', ID: "APIC">
     Text encoding      $xx
     MIME type          <text string> $00
     Picture type       $xx
     Description        <text string according to encoding> $00 (00)
     Picture data       <binary data>

Vorbis Comments: (Vorbis [ogg], Flac, Speex)

http://www.xiph.org/vorbis/doc/v-comment.html
 Currently images are not properly supported in the ogg header specification, there have been hacks at this but the only current implementation is to put a url to the image file in the header. We will default back to only storing the image on the filesystem and in the database.

APEv2: ()

http://wiki.hydrogenaudio.org/index...._specification
 Currently images are not properly supported in the APEv2 header specification. We will default back to only storing the image on the filesystem and in the database.

Primary Image:

The primary image for display is stored as an uri in the primaryImageURL property, the uri points either to a cached image stored in the profile folder or other file/http/etc location. It is not recommended that the images are stored as web uris as a network connection may not be available.

Image Cache:

Images that are cached on the file system, generally for images pasted, copied or retrieved from meta data, are stored in the profile folder for quicker access to the image. The cached images are stored under the artwork folder in the profile folder.

Windows:

/Users/[username]/App Data/Local/Songbird2/Profiles/[profile]/artwork

Linux:

/home/[username]/.songbird2/[profile]/artwork

OS X:

/Users/[username]/Library/Application Data/Songbird2/Profiles/[profile]/artwork

Fetchers:

Fetchers are individual components that follow the same catagory "songbird-album-art-fetcher". The current fetchers are:

  1. sbMetadataAlbumArtFetcher - "@songbirdnest.com/Songbird/album-art/metadata-fetcher;1"
  2. sbFileAlbumArtFetcher - "@songbirdnest.com/Songbird/album-art/file-fetcher;1"
  3. sbLastFMAlbumArtFetcher - "@songbirdnest.com/Songbird/album-art/lastfm-fetcher;1" 
  4. sbAmazonArtFetcher - "@songbirdnest.com/Songbird/album-art/amazon-fetcher;1" [Add on]
  5. sbTestArtFetcher - For QA [Add on]

Other fetchers can be created that will retrieve artwork from online services or other devices. The fetchers are based off the sbIAlbumArtFetcher component defined in sbIAlbumArtFetcher.idl.

Creating a fetcher extension:

To create a fetcher extenison you will need the following:

  1. Knowledge of Javascript or C++ programming.
  2. A Basic folder structure for the extension.
  3. The sbIAlbumArtFetcher.idl

Then follow these steps:

 Replace the following placeholders in the example code below with your values:
  1. [FetcherName] - Your fetchers name (like "LastFM")
  2. [fetchername] - A simple lowercase oneword fetcher name (like "lastfm")
  3. [Fetcher Name] - A user viewable fetcher name (like "Last FM")
First create your folder structure for the extension
/root
    /chrome/
        chrome.manifest
        /locale/en-US
            [fetchername]-fetcher.properties
    /components
        sb[FetcherName]ArtFetcher.js
    /defaults/preferences
        prefs.js
    install.rdf

Create the chrome.manifest
locale  [fetchername]-fetcher  en-US  chrome/locale/en-US/
Create the install.rdf
<?xml version="1.0" encoding="UTF-8"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"
     xmlns:songbird="http://www.songbirdnest.com/2007/addon-metadata-rdf#">
  <Description about="urn:mozilla:install-manifest">
    <em:id>{bac3da27-34f4-4085-9da4-84595ef36a39}</em:id>
    <em:type>2</em:type>
    <em:name>[Fetcher Name] Album Art Fetcher</em:name>
    <em:version>1.0.0.0</em:version>
    <em:creator></em:creator>
    <em:description>Fetch album art from [Fetcher Name].</em:description>

    <em:targetApplication>
      <Description>
        <em:id>songbird@songbirdnest.com</em:id>
        <em:minVersion>1.1.0</em:minVersion>
        <em:maxVersion>1.2.0</em:maxVersion>
      </Description>
    </em:targetApplication>
  </Description>
</RDF>
Create the properties file
extensions.albumart.[fetchername].name=[Fetcher Name]
extensions.albumart.[fetchername].description=A New Fetcher
Create the prefs file
// Album Art Fetching preferences
// Dump status to console
pref("extensions.albumart.[fetchername].debug", false);
// Enable the Fetcher
pref("extensions.albumart.[fetchername].enabled", true);
// Priority of the Fetcher. Use a number between 1 and 999
// A lower number is higher priority.
pref("extensions.albumart.[fetchername].priority", XXX);
Create the sbFetchernameArtFetcher.js
/*
//
// BEGIN SONGBIRD GPL
//
// This file is part of the Songbird web player.
//
// Copyright(c) 2005-2009 POTI, Inc.
// http://songbirdnest.com
//
// This file may be licensed under the terms of of the
// GNU General Public License Version 2 (the "GPL").
//
// Software distributed under the License is distributed
// on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
// express or implied. See the GPL for the specific language
// governing rights and limitations.
//
// You should have received a copy of the GPL along with this
// program. If not, go to http://www.gnu.org/licenses/gpl.html
// or write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// END SONGBIRD GPL
//
*/


/* This is a XPCOM component for the Album Art Fetcher interface.
 */

const Cc = Components.classes;
const CC = Components.Constructor;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

// The root of our preferences branch
const PREF_BRANCH = "extensions.albumart.[fetchername].";
// The string bundle location
const STRING_BUNDLE = "chrome://[fetchername]-fetcher/locale/[fetchername]-fetcher.properties";

// Importing helper modules
Cu.import('resource://app/jsmodules/sbProperties.jsm');
Cu.import("resource://app/jsmodules/StringUtils.jsm");
Cu.import('resource://gre/modules/XPCOMUtils.jsm');

/**
 * Since we can't use the FUEL components until after all other components have
 * been loaded we define a lazy getter here for when we need it.
 */
__defineGetter__("Application", function() {
  delete this.Application;
  return this.Application = Cc["@mozilla.org/fuel/application;1"]
                              .getService(Ci.fuelIApplication);
});

/**
 * Album Art Fetcher - sb[FetcherName]AlbumArtFetcher
 */
function sb[FetcherName]AlbumArtFetcher() {
  var stringBundleService = Cc["@mozilla.org/intl/stringbundle;1"]
                             .getService(Ci.nsIStringBundleService);
  this._bundle = stringBundleService.createBundle(STRING_BUNDLE);

  this.DEBUG = Application.prefs.getValue(PREF_BRANCH + "debug", false);
  if (this.DEBUG) {
    this._consoleService = Cc["@mozilla.org/consoleservice;1"]
                            .getService(Ci.nsIConsoleService);
  }
  this._debug("Starting [FetcherName] Album Art Fetcher Component");
};
sb[FetcherName]AlbumArtFetcher.prototype = {
  // XPCOM Magic
  className: 'sb[FetcherName]AlbumArtFetcher',
  classDescription: '[FetcherName] Album Cover Fetcher',
  classID: Components.ID('{}'), // Use uuidgen to get an ID
  contractID: '@songbirdnest.com/Songbird/album-art/[fetchername]-fetcher;1',
  _xpcom_categories: [{
    category: "songbird-album-art-fetcher"
  }],

  // Variables
  DEBUG: false,               // Debug flag
  _shutdown: false,           // Flag to shutdown
  _albumArtSourceList: null,  // Source list
  _bundle: null,              // String bundle
  _shortName: "[fetchername]",
 
  // Services
  _consoleService: null,

  /**
   * Internal debugging functions
   */
  /**
   * \brief Dumps out a message if the DEBUG flag is enabled with
   *        the className pre-appended.
   * \param message String to print out.
   */
  _debug: function (message)
  {
    if (!this.DEBUG) return;
    try {
      dump(this.className + ": " + message + "\n");
      this._consoleService.logStringMessage(this.className + ": " + message);
    } catch (err) {
      // We do not want to throw an exception here
    }
  },
 
  /**
   * \brief Dumps out an error message with the className + ": [ERROR]"
   *        pre-appended, and will report the error so it will appear in the
   *        error console.
   * \param message String to print out.
   */
  _logError: function (message)
  {
    try {
      dump(this.className + ": [ERROR] - " + message + "\n");
      Cu.reportError(this.className + ": [ERROR] - " + message);
    } catch (err) {
      // We do not want to thow an exception here
    }
  },

  /*********************************
   * sbIAlbumArtFetcher
   ********************************/
  // These are a bunch of getters for attributes in the sbIAlbumArtFetcher.idl
  get shortName() {
    this._debug("Getting shortName: " + this._shortName);
    return this._shortName;
  },
 
  // These next few use the .properties file to get the information
  get name() {
    var mName = SBString(PREF_BRANCH + "name", null, this._bundle);
    this._debug("Getting Name: " + mName);
    return mName;
  },
 
  get description() {
    var mDescription = SBString(PREF_BRANCH + "description", null, this._bundle);
    this._debug("Getting Description: " + mDescription);
    return mDescription;
  },
 
  get isLocal() {
    var mLocal = Application.prefs.getValue(PREF_BRANCH + "islocal", true);
    this._debug("Getting isLocal: " + mLocal);
    return mLocal;
  },
 
  // These are preference settings
  get priority() {
    var priority = parseInt(Application.prefs.getValue(PREF_BRANCH + "priority", 15), 10);
    this._debug("Getting Priority: " + priority);
    return priority;
  },
  set priority(aNewVal) {
    this._debug("Setting Priority: " + aNewVal);
    return Application.prefs.setValue(PREF_BRANCH + "priority", aNewVal);
  },
 
  get isEnabled() {
    return Application.prefs.getValue(PREF_BRANCH + "enabled", false);
  },
  set isEnabled(aNewVal) {
    this._debug("Setting isEnabled: " + aNewVal);
    return Application.prefs.setValue(PREF_BRANCH + "enabled", aNewVal);
  },
 
  get albumArtSourceList() {
    this._debug("Getting albumArtSourceList: " +
                 this._albumArtSourceList.Length() +
                 " items");
    return this._albumArtSourceList;
  },
  set albumArtSourceList(aNewVal) {
    if (aNewVal) {
      this._debug("Setting albumArtSourceList: " + aNewVal.Length() + " items");
    } else {
      this._debug("Setting albumArtSourceList: NULL");
    }
    this._albumArtSourceList = aNewVal;
  },

  fetchAlbumArtForAlbum: function (aMediaItems, aListener) {
    this._debug("fetchAlbumArtForAlbum called.");

    // Do what you need to do to fetch artwork for an album.
    // Then if successful call aListener.onAlbumResult(artworkURL, aMediaItems);
    // You can also call aListener.onTrackResult(artworkURL, aMediaItem); for each
    //  one you find an artwork for.
    // If you do not find artwork you do not need to call either of those functions.
    
    // Finally call aListener.onAlbumComplete(aMediaItems);
  },
 
  fetchAlbumArtForTrack: function (aMediaItem, aListener) {
    this._debug("fetchAlbumArtForTrack called.");
 
    // Do what you need to do to fetch artwork for a single track.
    // Then if successful call aListener.onTrackResult(artworkURL, aMediaItem);
    // If not successful then you do not need to call anything.

    // Finally call aListener.onAlbumComplete(items);
    // A helpful piece of code for this is
    // var items = Cc["@songbirdnest.com/moz/xpcom/threadsafe-array;1"]
    //                .createInstance(Ci.nsIMutableArray);
    //  items.appendElement(aMediaItem, false);

  },
 
  shutdown: function () {
    this._debug("Shutdown called.");
  },

  /*********************************
   * nsISupports
   ********************************/
  QueryInterface: XPCOMUtils.generateQI([Ci.sbIAlbumArtFetcher])
}

// This is for XPCOM to register this Fetcher as a module
function NSGetModule(compMgr, fileSpec) {
  return XPCOMUtils.generateModule([sb[FetcherName]AlbumArtFetcher]);
}
If you wish to test the extension you can install the Developer Tools [here] and select the "Link Extension in Test Mode...".

Now you can zip up the folder making sure to have the extension as .xpi and upload it to http://addons.songbirdnest.com.
Tag page
Viewing 3 of 3 comments: view all
extension is spelled wrong here: "To create a fetcher extenison you will need the following:"
Posted 13:01, 5 Aug 2009
Under VorbisComments, you write "Currently images are not properly supported in the ogg header specification". This is no longer the case, please visit:
http://wiki.xiph.org/VorbisComment#Cover_art
Posted 09:57, 28 Aug 2009
Yeah - we already implemented Vorbis album art in 1.3.0. This page is a little out of date.
Posted 10:26, 28 Aug 2009
Viewing 3 of 3 comments: view all
You must login to post a comment.