Sunday, June 12, 2005

iTunes and PHP (Solution)

After working on trying to re-encode the input text in a format that matched PHP's native system, I googled a bit and found Richard James Kendall's iTunes XML Playlist Reader. With this, I was able to export the playlist in xml instead, and then let his code parse it and then comment out the sample HTML output part at the end, and replace it with a for loop that copied each of the files from their diverse locations to a directory I give it. The final code follows:
<?php

  // change this to suit you
  $file = "Good Calm.xml";
  $copyTo = "C:/Documents and Settings/Aron/My Documents/GoodCalm/";

  /*  iTunes XML Playlist Reader v1.01
  By Richard James Kendall
  Bugs to richard@richardjameskendall.com
  Free to use, please acknowledge me

  You need to change the $file variable to point to the right XML file.

  To get iTunes to generate this XML file select the playlist you want and goto
  File > Export Song List and select the XML format.  This DOES NOT WORK on full
  library exports from iTunes but it may do in the future.

  It loads the data into an array structure as follows:

  Array {
     [0] => Array {
        [TRACK ID] =>
        [NAME] =>
        [ARTIST] => (may not exist)
        [ALBUM] => (may not exist)
        [GENRE] => (may not exist)
        [KIND] =>
        [SIZE] =>
        [TOTAL TIME] =>
        [BIT RATE] =>
        [SAMPLE RATE] =>
        [PLAY COUNT] =>
     }

     [1] => Array {
        .
        .
        .
     }

     ...
  }

  I have provided code to format it as HTML (very simple formatting) and a function
  to get the track length in mm:ss instead of milliseconds.
  */

  $tracks = array();
  $track_counter = -1;
  $action = "";
  $k = "";
  $content = "";
  $mode = "GETTRACKINFO";
  $play_order = array();
  $playlist = array();

  function startElement($parser, $name, $attrs) {
     global $action, $k;
     switch($name) {
        case "KEY":
        $k = "";
        $action = "GETKEY";
        break;
        case "STRING":
        case "INTEGER":
        case "DATE":
        $action = "CONTENT";
        break;
        default:
        $action = "";
        break;
     }
  }

  function endElement($parser, $name) {
     global $action, $k, $content, $track_counter, $mode, $tracks, $play_order, $playlist;
     switch(strtoupper($k)) {
        case "TRACK ID":
        if ($mode == "GETTRACKINFO" && $action == "CONTENT") {
           //print ($action . " - " . $k . " - " . $content . " - " . $mode . "<br>");
           $track_counter++;
           $tracks[$track_counter][strtoupper($k)] = $content;
        }
        if ($mode == "PLAYORDER" && $action == "CONTENT") {
           $play_order[$content] = count($play_order);
           //print ($content . "<br>");
        }
        break;
        case "PLAYLISTS":
        $mode = "PLAYORDER";
        break;
        case "NAME":
        case "ARTIST":
        case "ALBUM":
        case "GENRE":
        case "KIND":
        case "SIZE":
        case "TOTAL TIME":
        case "BIT RATE":
        case "SAMPLE RATE":
        case "PLAY COUNT":
        case "LOCATION":
        if ($mode == "GETTRACKINFO") {
           $tracks[$track_counter][strtoupper($k)] = $content;
        } else {
           $playlist[strtoupper($k)] = $content;
        }
        break;
     }
     if ($action != "GETKEY") {
        $k = "";
     }
     $content = "";
     $action = "";
  }

  function characterData($parser, $data) {
     global $action, $k, $content;
     if ($action != "") {
        switch($action) {
           case "GETKEY":
           $k .= $data;
           break;
           case "CONTENT":
           $content .= $data;
           //print ($k . " => " . $content . "<br>");
           break;
        }
     }
  }

  $xml_parser = xml_parser_create();
  xml_set_element_handler($xml_parser, "startElement", "endElement");
  xml_set_character_data_handler($xml_parser, "characterData");
  if (!($fp = fopen($file, "r"))) {
     die("could not open XML input");
  }

  while ($data = fread($fp, 4096)) {
     if (!xml_parse($xml_parser, $data, feof($fp))) {
        die(sprintf("XML error: %s at line %d",
        xml_error_string(xml_get_error_code($xml_parser)),
        xml_get_current_line_number($xml_parser)));
     }
  }
  xml_parser_free($xml_parser);

  // this is the comparison utility funtion used by usort to order 
  // the tracks in play count order.
  function track_cmp($a, $b) {
     global $play_order;
     if ($play_order[$a["TRACK ID"]] > $play_order[$b["TRACK ID"]]) {
        return 1;
     } else {
        if ($play_order[$a["TRACK ID"]] == $play_order[$b["TRACK ID"]]) {
           return 0;
        } else {
           return -1;
        }
     }
  }

  // turns a real number into an integer
  function getint($number) {
     if (strpos($number, ".")) {
        $bits = explode(".", $number);
        return $bits[0];
     } else {
        return $number;
     }
  }

  // formats seconds as mins:secs
  function secs2minsasecs($secs) {
     $secs /= 1000;
     $minutes = $secs / 60;
     $seconds = $secs % 60;
     return getint($minutes) . ":" . $seconds;
  }

  usort($tracks, "track_cmp");

  // simple HTML formatting
  /*
  print ("<html><head><title>iTunes XML Playlist Reader</title></head>\n");
     print ("<body>\n");
        print ("<h1>" . $playlist["NAME"] . "</h1>\n");
        print ("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"1\" border=\"1\">\n");
           print ("<tr>\n");
              print ("<td align=\"center\"><b>Name</b></td>\n");
              print ("<td align=\"center\"><b>Album</b></td>\n");
              print ("<td align=\"center\"><b>Artist</b></td>\n");
              print ("<td align=\"center\"><b>Play Count</b></td>\n");
              print ("<td align=\"center\"><b>Track Length</b></td>\n");
              print ("<td align=\"center\"><b>File Size</b></td>\n");
              print ("<td align=\"center\"><b>Location</b></td>\n");
              print ("</tr>\n");
           for($i = 0;$i < count($tracks);$i++) {
              print ("<tr>\n");
                 print ("<td>" . $tracks[$i]["NAME"] . "</td>");
                 if (isset($tracks[$i]["ALBUM"])) {
                    print ("<td>" . $tracks[$i]["ALBUM"] . "</td>");
                 } else {
                    print ("<td>~</td>");
                 }
                 if (isset($tracks[$i]["ARTIST"])) {
                    print ("<td>" . $tracks[$i]["ARTIST"] . "</td>");
                 } else {
                    print ("<td>~</td>");
                 }
                 print ("<td>" . $tracks[$i]["PLAY COUNT"] . "</td>");
                 print ("<td>" . secs2minsasecs($tracks[$i]["TOTAL TIME"]) . "</td>");
                 print ("<td>" . $tracks[$i]["SIZE"] . " bytes</td>");
                 print ("<td>" . $tracks[$i]["LOCATION"] . "</td>");
                 print ("</tr>\n");
           }
           print ("</table>\n");
        print ("</body>\n</html>\n");
  for($i = 0;$i < count($tracks);$i++) {
     print ($tracks[$i]["LOCATION"]);
  }*/

  for($i = 0;$i < count($tracks);$i++) {

     // following puts the path in $matches[1] and the filename in $matches[2]
     preg_match("/file:\/\/localhost\/(.*\/)(.*)\/$/",urldecode($tracks[$i]["LOCATION"]), $matches);
     echo "\n\nCopying file:".$matches[2];

     //echo $matches[0];
     //echo $buffer;
     if (!copy($matches[1].$matches[2],$copyTo.$matches[2])) {
        echo "\n\nCopy Error";
        echo "\nFrom:".$matches[1].$matches[2];
        echo "\nTo:".$copyTo.$matches[2];
     }
  }
?>

iTunes and PHP

I have a laptop and a desktop, and I use iTunes as my Music Service, but I don't have an iPod. I wanted to move a certain play list from my Desktop to my Laptop, through my network, but didn't want to have to go find all 280 songs. I noticed, though, that you could export a playlist to a text file, and the text file had the path to the file in it. Since I have a little experience in PHP, I wanted to see if I could quick hack together a system that would copy all the files in a given playlist file into a given directory. I tried to use the plain text file output for a while, until I discovered that the text file that was exported was UTF8 encoded (I think...), and I couldn't get php to read from the text file properly. My code is shown below...
<?php
   // doesn't work...
function decode_utf8($str){
       # erase null signs in string
         $str=eregi_replace("^.{10,13}q\?","",$str);
       # paterns
           $pat = "/=([0-9A-F]{2})/";
           $cha="'.chr(hexdec(";
       # to decode with eval and replace
         eval("\$str='".
                 preg_replace($pat,$cha."'$1')).'",$str).
                 "';");
       # return
           return $str;
       }

   $copyTo = "C:\\Documents and Settings\\Aron\\My Documents\\CopyToDir\\";
   $handle = fopen("AirPlayList.txt", "r");
   while ($buffer = fgets($handle,4096)) {

      //$buffer = decode_utf8($buffer);
      $buffer = html_entity_decode(htmlentities($buffer, ENT_COMPAT, 'UTF-8'));
      
      //$copyTo = iconv('ISO-8859-1', 'UTF-8', $copyTo);
      preg_match("/\t([^\t]*\\\)([^\t]*?)$/",$buffer, $matches);
      //echo "\n full path:".$matches[0];
      //echo "\n\n\npath:".$matches[1];
      //echo "\n\nfile:".$matches[2];
      
      //echo $matches[0];
      //echo $buffer;
      if (!copy($matches[1].$matches[2],$copyTo.$matches[2])) {
         echo "\n\nCopy Error";
         echo "\nFrom:".$matches[1].$matches[2];
         echo "\nTo:".$copyTo.$matches[2];
      }

   }
   fclose($handle);
?>