#!/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 <<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 <<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 <<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 <<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__