A script to keep playlists synced with iPhones/iPods

Jul 10, '09 07:30:00AM

Contributed by: midget2000x

I am always adding to and editing iTunes playlists, particularly the more oft-listened ones like my punk, ambient, and programs & podcasts playlists. Since I listen to a lot of stuff on my iPhone, I naturally want these playlists always available and up-to-date on my device. I can't tell you how many times I plugged in my iPhone to my car stereo and found, to my disappointment, that I forgot to drag-and-drop the most recent version of a playlist to the device.

Why not just set the iPhone to automatically update? Because I want the finite control that manually managing affords me. My experience with auto-updating: unintended results, creates more problems than it solves.

So I created a script that updates a specified list of playlists to all attached iPods/iPhones. It can run on a schedule (with cron), so I never have to worry that my iPhone won't have the most recent versions of my playlists!

Features

You can find the latest version of the script (in case of any changes) in this entry on my site.

Usage

First, the script requires the iPod/iPhone be set to 'Manually manage music and videos.' I named the script updateipod.pl, and placed it in my user's bin directory. It's recommended you specify the playlists that you want to sync in the @playlists array at the top of the script:

my @playlists = (

 "jamz",
 "0pods",
 "punk best",
 "stoner best"
 
 );
After chmoding the script to 755, just invoke it as you would any script: /Users/user/bin/updateipod.pl. Optionally, you can specify the names of the playlists as arguments on the command line, which is useful for one-off updating. Note that if playlists are passed as command-line arguments, they will be processed instead of whatever is specified in the @playlists array at the top of the script.

Make sure to enclose playlist names in quotes if the playlists have spaces or special characters. For example, here's how I'd send three playlists to my connected devices on the command line:
/User/user/bin/updateipod.pl "michael best" 0pods "80's by rory"
I schedule it to run hourly with cron:
0 * * * * /Users/user/bin/updateipod.pl > /dev/null 2>&1
But you can do this if you want more output:
0 * * * * /Users/user/bin/updateipod.pl >> /Users/user/Desktop/ipod_update_log.txt 2>&1
That's it! Here's the script:
#!/usr/bin/perl

# 2009-07-05
# Auto-sync specified playlists to all attached iPods/iPhones
# Specify playlist names in the @playlist array OR enter them
# as arguments (in single quotes) on the command line like this:
#
# $ ./updateipod.pl "michael best" "0pods" "80's by rory"
#
# I run this hourly with the cronjob:
#
# 0 * * * * /Users/user/bin/updateipod.pl > /dev/null 2>&1
# 
# use something like this if you want to see the output for each run:
# 
# 0 * * * * /Users/user/bin/updateipod.pl >> /Users/user/Desktop/ipod_update_log.txt 2>&1
#
# some applescript in this script was borrowed from "Selected Playlists To iPod v2.1" at the very excellent http://www.dougscripts.com/

use strict;
use POSIX qw(strftime);

my @playlists = (

"jamz",
"0pods",
"punk best",
"stoner best"

);

# if there are playlists entered as arguments on the cmd line
# those are gonna trump whatever is specified above
if ($ARGV[0]) {
@playlists = @ARGV;
}

my $time = strftime( "%Y-%m-%d %H:%M:%S",localtime(time));
print "syncing of playlists started at $time\n";

# check if itunes is running, start if not
checkIfRunning();

# get the sources of any attached ipods.  exit if there are none attached.
my ($id_ref,$name_ref) = getiPodSources();
my @ipod_ids = @$id_ref;
my @ipod_names = @$name_ref;
my $devices = @ipod_ids;

# exit if we have no attached ipods
if (!$ipod_ids[0]) {
print "no manually managed iPods detected, exiting...\n";
exit;
}

# make sure the playlists exist in the itunes lib.
my @confirmed_playlists = ();
foreach my $playlist (@playlists) {
my $exists = checkPlaylistExists($playlist);
if($exists) {
 push(@confirmed_playlists,$playlist);
} else {
 print "\"$playlist\" is not a valid playlist in the iTunes library, skipping...\n";
}
}

# make sure we have at least one valid playlist
if (!$confirmed_playlists[0]) {
print "no valid playlists entered, exiting...\n";
exit;
}

# loop thru our ipods first, then within that,
# loop thru our playlists array and sync them to each ipod
for (my $i=0;$i<$devices;$i++) {
foreach my $playlist (@confirmed_playlists) {
 print "telling itunes to sync playlist \"$playlist\" to $ipod_names[$i]\n";
 my $output = syncPlaylist($ipod_ids[$i],$ipod_names[$i],$playlist);
 if ($output ne '1') {
  print "${output}, skipping...\n";
  next;
 }
} # end loop thru playlists
} # end for thru ipods

print "done.\n\n";
exit;

#################################################################################################
###################                      SUBROUTINES                   #####################
#################################################################################################

sub stripNonAlph {
# strips non-alphanumeric characters from strings

my($text) = shift;
$text =~ s/([^0-9a-zA-z ])//g;
return ($text);

} # end sub strip non alph

sub checkIfRunning {
# checks if iTunes is running
# starts it if not

my $start=`/usr/bin/osascript &lt;&lt;END
--start the program if necessary
set isRunning to 0
tell application "System Events"
 set isRunning to ((application processes whose (name is equal to "iTunes")) count)
end tell
if isRunning is 0 then
 tell application "iTunes"
  activate
 end tell
end if
`;

} # end sub check if running

sub getiPodSources {
# gets the system source IDs of any attached ipods
# (assumes there's more than 1 in case there actually are)
# returns in semicolon-delimited list

my $source=`/usr/bin/osascript &lt;&lt;END
tell application "iTunes"
 set validiPods to {}
 set myPods to ""
 set myPodsNames to ""
 set myPodReturn to ""
 set ipodSources to (get every source whose kind is iPod)
 if ipodSources is {} then
  return 0
 end if

 repeat with s in ipodSources
  try -- can't make a playlist on an iPod set to sync automatically, so this will fail.  
   set tp to make new user playlist at s with properties {name:("p)" & my get_temp_name() & "-^")}
   delete tp
   set end of validiPods to s
  end try
 end repeat

 if validiPods is {} then
  return 0
 end if

 repeat with i from 1 to (length of validiPods)
  set myPods to myPods & ";" & id of item i of ipodSources
  set myPodsNames to myPodsNames & ";" & name of item i of ipodSources
 end repeat

 set myPodsReturn to myPods & "|" & myPodsNames
 return myPodsReturn

end tell
to get_temp_name()
 return (get time of (get current date)) as text
end get_temp_name
END`;

chomp($source);
$source =~ /^;(.*?)\|;(.*?)$/;
my ($id,$name) = ($1,$2);
my @ids = split(/;/,$id);
my @names = split(/;/,$name);
return (\@ids,\@names);

} # end sub getiPodSources

sub checkPlaylistExists {
# checks if the itunes playlist exists

my $playlist = shift;
my $exists=`/usr/bin/osascript &lt;&lt;END
tell application "iTunes"
 if (exists playlist "$playlist") then
  return 1
 else
  return 0
 end if
end tell
END`;
$exists = &stripNonAlph($exists);
return ($exists);

} # end check if playlist exist

sub syncPlaylist {
# takes the id of the source (ipod) and the name of the playlist to sync as arguments

my ($ipod_id,$ipod_name,$playlist) = @_;
my $sync = `/usr/bin/osascript &lt;&lt;END

tell application "iTunes"
 set playlistName to "$playlist"
 set main_lib to library playlist 1
 set ipod_src to item 1 of (get every source whose id is $ipod_id)
 set ipod_lib to library playlist 1 of ipod_src
 set playlistSize to 0
 set iPodFreeSpace to free space of ipod_src
 set copySuccess to 0
 set playlistSuccess to 0

 set myPlaylists to (get every user playlist)
 repeat with thisPlaylist in myPlaylists
  if name of thisPlaylist is playlistName then

   --loop thru the playlist to determine if there is enough
   --free space on the ipod for any songs not already on it
   set eop to index of last track of thisPlaylist
   repeat with i from 1 to eop
    --determine if the song is already on ipod lib
    set i_track to track i of thisPlaylist
    if (get class of i_track) is file track then
     tell i_track to set {nom, art, alb, siz, kin, tim, pid} to {get name, get artist, get album, get size, get kind, get time, get persistent ID}
     try
      -- is track already in iPod library?  setting this will fail if not
      set new_i_track to (some track of ipod_lib whose name is nom and artist is art and album is alb and size is siz and kind is kin and time is tim)
     on error -- if not, add size of track to running total of tracks not on ipod
      set playlistSize to playlistSize + siz
     end try
    end if
   end repeat --thru selected playlist

   if free space of ipod_src is greater than playlistSize then
    -- establish iPod playlist
    try
     --does the iPod playlist already exist?  if so we delete it.
     --why?  because if we add additional tracks to the end of it, the track list will no be in the 
     --same order as they are on our iTunes playlist
     set new_ipodplaylist to user playlist playlistName of ipod_src
     delete new_ipodplaylist
     set new_ipodplaylist to (make new user playlist at ipod_src with properties {name:playlistName})
    on error
     -- if not, create the new iPod playlist
     set new_ipodplaylist to (make new user playlist at ipod_src with properties {name:playlistName})
    end try

    --I'm not checking to see if the tracks already exist on the ipod before copying
    --because it *appears* itunes won't duplicate them.  maybe this is a new feature
    try
     duplicate (every track of thisPlaylist) to ipod_lib
     set copySuccess to 1
    on error
     return "error copying tracks to iPod $ipod_name"
     set copySuccess to 0
    end try
    try
     duplicate (every track of thisPlaylist) to new_ipodplaylist
     set playlistSuccess to 1
    on error
     return "error duplicating track listing to playlist $playlist on iPod $ipod_name"
     set playlistSuccess to 0
    end try

    if copySuccess is 1 and playlistSuccess is 1 
     return 1
    end if

   else
    return "not enough free space on iPod $ipod_name to copy all tracks on $playlist"
   end if

  end if --current playlist = selected playlist
 end repeat

end tell
`;
$sync = &stripNonAlph($sync);
return ($sync);

} # end sub syncPlaylist

__END__
[robg adds: I haven't tested this one.]

Comments (14)


Mac OS X Hints
http://hints.macworld.com/article.php?story=20090708105355975