PHP: Scripting a Mixtape

I had long held on to my hobby of making mix CDs long after I actually stopped buying my music in that form. It was a neat way to create a little musical time capsule, and the products of this little tradition became treasured favorites during my long commutes. But time moves on, and now it’s becoming obvious what an inflexible annoyance Mix CDs are compared to the alternative: Playlists.

I have a rather large music collection, and my hoarding tendencies means that, in general, the best songs are a lot better than the average ones. At home, I’ve begun to create playlists of my favorite combinations. Period collections. “Best Ofs” for those bands with 6 Albums, 3 good songs each. Mood selection. You name it. I’d like to listen to those in my car, but that would mean bringing my entire MP3 collection, which isn’t practical.

For a while, I maintained additional collections. Folders containing copies of all the songs I wanted for my various lists. But this had storage implications, not to mention maintenance issues. Fix the tags in one place, it doesn’t carry to the other. It was inefficient.

What’s I’m doing now days is something of a middle ground. I’m maintaining my collections as playlists, in m3u format. They are tiny and flexible. Decide a song isn’t really a fit? Yank it out. Think of a perfect addition? Add it in. Try that with a CD.

And to get them into my car, or more specifically, my mp3 player, I’ve written a script to temporarily create a subfolder with the requisite music until I can drag it onto my mp3 player. When I’m done with the collection, I just delete it. My hard work in compiling and cataloging is preserved in the tiny little playlist.

Here’s how the script works.

<?php
/*************************************************************
mixtape.php
Purpose: Selects and copies a music file collections based upon
 playlists
Author:  Dan Ziemecki
**************************************************************/

// END-USER SET PREFERENCES HERE
# Folder containing playlists
# Must end with double back slashes
# e.g. $aSOURCE_DIR = "C:\My Music\PlayLists\\";
$sSOURCE_DIR = "D:\Alt\My Music\My Playlists\\";
# Directory to which the files will be written, no ending slash
# e.g. $sTARGET_DIR = "C:\My Music\MixTapes";
# defaults to writing to script directory\MixTapes
# $sTARGET_DIR = end(explode('/', dirname($_SERVER['PHP_SELF'])))."\MixTapes";
$sTARGET_DIR = "D:\Misc\MixTapes";
# Album art.  Should contain jpg with same name as playlist
# Not required
$sART_DIR = "M:\PlayLists\\";

This section is just metadata and global variables. $aSOURCE_DIR is where I want this script to find my playlists. Because I run the script on my desktop, I need the play list on my local PC in order for the file locations to translate. SO, right before I run this script, I open the playlist on my music server, from my PC, and save it to the $aSOURCE_DIR folder. This changes all the file references to local versions of where the remote files are stored. In other words, it changes "d:\music\band\song.mp3" in the play lists to "m:\band\song.mp3", or whatever works with the way you mapped the remote drive.

Finishing out this section, $sTARGET_DIR is the place where the compilation will be written, and $sART_DIR is where you keep the cover art. For simplicity, I keep the jpgs with the playlists, and the script assumes they have the same file name (except extention, of course).

The next section is the function that handles the parsing of the M3U file, and the actual file copying.

// FUNCTIONS
function copyFiles($sList,$sNewDir){
  GLOBAL $sART_DIR;
  // open m3u and loop thru song files
  $oM3U = file($sList);
  $sListBase = pathinfo($sList, PATHINFO_BASENAME);
  echo "Reading ". $sListBase."\n";
  $i = 0;
  $patterna = "/^\\\\+[a-zA-Z]+\\\.*(.mp3$|.ogg$)/i"; # look for "\\Az\...mp3|ogg"
 $patternb = "/^[a-zA-Z]{1}:\\\.*(.mp3$|.ogg$)/i"; # look for "A:\...mp3|ogg"

Here, I used regular expressions to describe two possible pattern matches for how the files may be written in the m3u file. I’ll make no attempt to explain the regex as that is an entirely separate matter, and has been done better by others. The key here to note is that I have it looking for either mp3s or oggs. If you have additional music formats, you’d need to tweak this line to make sure the script finds those files.

This next loop loops through each of the matched file patterns and copies those files to the target folder.

  foreach($oM3U as $sLine) {
    $sLine = trim($sLine);
    if(preg_match($patterna, $sLine)||preg_match($patternb, $sLine)){
      $i++;
      $sTrack = substr("0".$i, -2)." - "; # prepend ordinal to play order  
     $sDir = pathinfo($sLine, PATHINFO_DIRNAME);
      $sFile = pathinfo($sLine, PATHINFO_BASENAME);    
      if(is_file($sLine)){
        // copy file to new location
        $sNewfile = $sNewDir . "\\" . $sTrack.$sFile;
        echo "Copying " . $sFile ."\n";
        if (!copy($sLine, $sNewfile)) {
          echo "!!Failed to copy $sFile!!\n";
        }  // if copy
      } //if is_file
    } // if preg_match
  } // foreach

Finally, the function finishes off by finding the associated album art and copying it to the appropriate target with a name of “cover.ext”, as many music players require. Not that, per the requirements of my own systems, I search only for jpegs, but you could tweak the $aImgTypes array for your own needs.

  $sListName = pathinfo($sList, PATHINFO_FILENAME);
  $sAlbumArt = $sART_DIR. "\\" . $sListName;
  $aImgTypes = array('jpg','jpeg');
  foreach($aImgTypes as $sExt){ # loop through file extention possibilities
   if(is_file($sAlbumArt . '.' . $sExt)){
      $sNewArt = $sNewDir . "\\cover" . '.' . $sExt;
      echo $sAlbumArt . '.' . $sExt."\n";
      if (!copy($sAlbumArt . '.' . $sExt, $sNewArt)) {
          echo "!!Failed to copy " . $sListName . '.' . $sExt .  "!!\n";
      }  // if copy  
    } // if is_file
  } // foreach
}

The main section handles all the directory validation and creation, as well as loops though successive lists if you wanted to do more than one.

// MAIN PROCESS
// Test target directory
$bGo = true; # variable used to toggle a hard stop
if(!is_dir($sTARGET_DIR)){
        if(!mkdir($sTARGET_DIR)){ # try to make the directory
                echo "!!Cannot create $sTARGET_DIR!!\n";
                $bGo = false; # this should stop the rest of the program
        }else{
                echo "Creating target directory $sTARGET_DIR\n";
        }
}

At this point, we’ve made sure the target directory exists. Now to see if the source files are really there ...

// Loop through source directory
$oCurrDir = opendir($sSOURCE_DIR);
while (false !== ($sItem = readdir($oCurrDir))) {
                $sFullItem = $sSOURCE_DIR . $sItem; # directory + name
// look for m3u, parse name
if (is_file($sFullItem)
    && pathinfo($sItem, PATHINFO_EXTENSION)== "m3u"){
    // mkdir under target for each m3u
    $sNewDir = $sTARGET_DIR . "\\" . pathinfo($sItem, PATHINFO_FILENAME);
    if(!mkdir($sNewDir)){ # try to make the directory
     echo "!!Cannot create $sNewDir!!\n";
      $bGo = false; # this should stop the rest of the program
   }else{
      echo "Creating target directory $sNewDir\n";
    }

This line does the heavy lifting, calling the file copy function for this list.

    if($bGo){copyFiles($sFullItem,$sNewDir);}
  } // endif
} // end while

// Return a success message when completed.
echo "The script has finished\n";
?>

And we’re done.

To use this, first make sure the global variables at the top are correct, then (assuming you have a PHP interpreter installed on your desktop), enter this at the command line:

c:\> php \location\of\your\script\mixtape.php

I’ve been using this for a few months now and can honestly say that I have no intention of burning CD mixtapes ever again. Except, perhaps, from my cave dwelling friends who aren’t into digital music. As for my own needs, however, virtual music collections are definitely easier, and I recommend this method highly.

AttachmentSize
Plain text icon mixtape.php_.txt3.53 KB