iTunes Export to RSS (class::ItunesExport)

I've recently been downloading lots of music (legally of course *cough*) and thought it would be a fun idea to display this in my "mini-feed" on facebook so my friends could see what new music I had added to my library. After a bit of thinking, I worked out that I could probably run a bit of AppleScript to export my library and FTP it to my server, and then I'd just need a bit of PHP to work out what was new, get the details and album art, and then build it into an RSS feed which could be imported into Facebook. Unfortunately, I'm only a beginner at AppleScript and this was a little out of my league, but I have completed the PHP class to do such a thing and you can find it below along with the RSS feed that I'm currently importing into my facebook account:

RSS Feed of the latest music added to my iTunes library

The code

This code is released under Creative Commons GNU GPL and so may be freely used and edited. Just copy and paste it from below.

<?php

/**

*
*    [0] => Name
*    [1] => Artist
*    [2] => Composer
*    [3] => Album
*    [4] => Grouping
*    [5] => Genre
*    [6] => Size
*    [7] => Time
*    [8] => Disc Number
*    [9] => Disc Count
*    [10] => Track Number
*    [11] => Track Count
*    [12] => Year
*    [13] => Date Modified
*    [14] => Date Added
*    [15] => Bit Rate
*    [16] => Sample Rate
*    [17] => Volume Adjustment
*    [18] => Kind
*    [19] => Equalizer
*    [20] => Comments
*    [21] => Play Count
*    [22] => Last Played
*    [23] => Skip Count
*    [24] => Last Skipped
*    [25] => My Rating
*    [26] => Location
*
*/


class ItunesExport
{
    
// define constants
    
const export_file 'music.txt';
    const 
line_endings "\n";
    const 
export_cache 'export.cache';
    const 
amazon_access_key_id '0SWDKFA2SDTA1YFZBYG2';
    const 
amazon_affiliate_id 'bendodsoncom-21';
    const 
amazon_url 'http://ecs.amazonaws.co.uk/onca/xml';
    const 
rss_template 'rss.php';
    const 
rss_feed 'rss.xml';
    const 
rss_title 'Ben Dodson\'s iTunes Updates';
    const 
rss_link 'http://labs.bendodson.com/itunes-export/rss.xml';
    const 
rss_description 'The latest music added to Ben Dodson\'s iTunes Library';
    const 
rss_ttl '60';
    
    
// define variables
    
protected $tracks = array();
    public 
$albums = array();
    public 
$last_export '';
    
    
// set the last_export variable with constructor
    
function __construct()
    {
        
$this->get_last_export_date();
    }
    
    protected function 
get_last_export_date()
    {
        
$this->last_export = @file_get_contents(ItunesExport::export_cache);
    }
    
    public function 
update_last_export_date()
    {
        
$timestamp date('U');
        @
file_put_contents(ItunesExport::export_cache,$timestamp);
        
$this->last_export $timestamp;
    }
    
    public function 
export($update=true)
    {
        
// check if the itunes export actually exists.
        
if (!file_exists(ItunesExport::export_file)) {
            echo 
'iTunes export file not found at '.ItunesExport::export_file.'... aborting.';
            exit;
        }

        
// get contents of export file into string
        
$lines explode(ItunesExport::line_endings,file_get_contents(ItunesExport::export_file));
        
// remove the first line which contains column headers
        
$headers array_shift($lines);

        
// loop through each line and get relevant columns only if all are present and if after last export
        
$i 0;
        foreach (
$lines as $line) {
            
// break each line into it's columns
            
$columns explode("\t",$line);
            
// assign variables to relevant columns
            
$title $columns[0];
            
$artist $columns[1];
            
$album $columns[3];
            
$date_added $columns[14];
            
$location $columns[26];
            
// if all variables are present (so if ID3 tags filled out correctly)
            
if ($title && $artist && $album && $date_added) {
                
// get timestamp (have to rearrange the iTunes date_added format a little...)
                
$timestamp date('U',strtotime(str_replace('/','-',substr($date_added,0,10)).' '.substr($date_added,11).':00'));
                if (
$timestamp $this->last_export) {
                    
$this->tracks[$i]['title'] = $title;
                    
$this->tracks[$i]['artist'] = $artist;
                    
$this->tracks[$i]['album'] = $album;
                    
$this->tracks[$i]['timestamp'] = $timestamp;
                    
$this->tracks[$i]['location'] = $location;
                    
$i++;
                }        
            }
        }
        
        
// if there are no tracks since last export, then stop.
        
if (!$this->tracks) {
            echo 
'No new tracks added since last export... aborting.';
            exit;
        }    
        
        
// sort the tracks
        
$this->sort_tracks();
        
        
// group tracks into albums / singles
        
$this->group_tracks();
        
        
// update last export date
        
if ($update) {
            
$this->update_last_export_date();
        }
    }
    
    protected function 
sort_tracks()
    {
        
usort($this->tracks, array('ItunesExport','timestamp_compare'));
        
$this->tracks array_reverse($this->tracks);
        while(
count($this->tracks) > 75) {
            
array_pop($this->tracks);
        }
    }

    static function 
timestamp_compare($a,$b)
    {
        return 
strcmp($a['timestamp'], $b['timestamp']);
    }
    
    protected function 
group_tracks()
    {
        
$i 0;
        
$temp '';
        foreach (
$this->tracks as $track) {
            if (
strpos($track['location'], 'Various Artists') !== false) {
                
$track['artist'] = 'Various Artists';
            }
            
$current $track['artist'].'-'.$track['album'];
            
// if we've moved to a new album
            
if ($current != $temp) {
                
// increase counter and set temp variable to current album
                
$temp $current;
                
$i++;
                
                
// set album, artist, and a timestamp
                
$this->albums[$i]['album'] = $track['album'];
                
$this->albums[$i]['artist'] = $track['artist'];
                
$this->albums[$i]['timestamp'] = $track['timestamp'];
                
                
// get amazon data
                
$this->get_amazon_data($i);    
            }
            
$this->albums[$i]['tracks'][] = $track['title'];
        }
        
        
// set type as 'album' or 'single'
        
$this->add_album_type();
    }

    protected function 
get_amazon_data($i)
    {
        
// get amazon link
        
$request ItunesExport::amazon_url;
        
$request .= '?Service=AWSECommerceService';
        
$request .= '&AssociateTag='.ItunesExport::amazon_affiliate_id;
        
$request .= '&AWSAccessKeyId='.ItunesExport::amazon_access_key_id;
        
$request .= '&Operation=ItemSearch';
        
$request .= '&Artist='.urlencode($this->albums[$i]['artist']);
        
$request .= '&Title='.urlencode($this->albums[$i]['album']);
        
$request .= '&SearchIndex=Music';
        
$request .= '&ResponseGroup=Images,ItemAttributes';
        
$response file_get_contents($request);
        if (
$response) {
            
$xml simplexml_load_string($response);
            if (
$xml) {
                
$amazon = (array) $xml->Items->Item;
            }
        }

        if (
$amazon) {
            
// get images
            
$this->albums[$i]['small_image'] = (string) $amazon['SmallImage']->URL;
            
$this->albums[$i]['medium_image'] = (string) $amazon['MediumImage']->URL;
            
$this->albums[$i]['large_image'] = (string) $amazon['LargeImage']->URL;
            
$this->albums[$i]['url'] = $amazon['DetailPageURL'];
        }
        
$this->albums[$i]['album_link'] = ($this->albums[$i]['url']) ? '<a href="'.$this->albums[$i]['url'].'">'.$this->albums[$i]['album'].'</a>' $this->albums[$i]['album'];
    }

    protected function 
add_album_type()
    {        
        for (
$i=1$i <= count($this->albums); $i++) { 
            
$this->albums[$i]['type'] = (count($this->albums[$i]['tracks']) > 1) ? 'album' 'single';
        }
    }
    
    public function 
generate_rss()
    {    
        global 
$feed,$items;

        
$feed['title'] = ItunesExport::rss_title;
        
$feed['link'] = ItunesExport::rss_link;
        
$feed['description'] = ItunesExport::rss_description;
        
$feed['ttl'] = ItunesExport::rss_ttl;
        
        
$i 0;
        foreach (
$this->albums as $album) {
            
$items[$i]['pubdate'] = date('r',$album['timestamp']);
            
$items[$i]['link'] = $album['url'];
            if (
$album['type'] == 'album') {
                
$items[$i]['title'] = $album['album'].' by '.$album['artist'];
                
$items[$i]['description'] = 'Added the album '.$album['album'].' by <em>'.$album['artist'].'</em> which contains '.count($album['tracks']).' tracks';
            } else {
                
$items[$i]['title'] = $album['tracks'][0].' by '.$album['artist'];
                
$items[$i]['description'] = 'Added the song '.$album['tracks'][0].' by <em>'.$album['artist'].'</em> from the album '.$album['album'];
            }
            
            if (
$album['small_image']) {
                
$items[$i]['description'] = '<img src="'.$album['small_image'].'" align="left" />'.$items[$i]['description'];
            }

            
$i++;
        }

        
$compiled $this->compile_template('feed','items');
        
file_put_contents(ItunesExport::rss_feed,$compiled);
    }
    
    protected function 
compile_template()
    {
        
$arguments func_get_args();
        foreach (
$arguments as $var) {
            global $
$var;
        }

        
ob_start();
        require 
ItunesExport::rss_template;
        return 
ob_get_clean();
    }

}

?>

It's not 100% ready yet and the class still needs tidying up and commenting made throughout, but for now it works which is the main thing. It's currently running on a CRON job that checks every hour to see if the music.txt file has been updated (and if it has it generates a new RSS feed). The main problem at the moment is that I have to do a manual export but I'm hoping to remove the need for that once I've mastered some AppleScript!

Future Improvements: Full documentation of the above code and a bit of a code cleanup. My iTunes library is a bit of a mess at the moment but I'm currently going through and correcting the ID3 tags, etc. Once that is done, I'll be making a page here which shows all of the albums in my library (along with some funky jQuery magic to make it look good) sortable via different criteria.

  • Uses a plain text iTunes export
  • Gets album artwork from Amazon
  • Groups albums together
  • Compatible with iTunes 8
Ben 'Music' Dodson