############################################################################### # # itunes_remove_duplicates.pl # # This script will remove duplicates from the user-specified playlist(s). # Duplicates are determined by exact file/path name. # # Copyright (C) 2007 Robert Jacobson # written by: Robert Jacobson (http://mysite.verizon.net/teridon/itunesscripts) # Last Updated: 30 July 2007 # Version 2.1 # # This script is GPL v2. see http://www.gnu.org/copyleft/gpl.html # # Change record: # 1.1 Updated to deal with multiple copies of the same data with similar file # names (e.g. foofile.mp3, foofile 1.mp3, foofile 2.mp3) # 2 Detect whether script is running via cscript or wscript. Create and # select a temporary playlist while the script is running # 2.1 Update loop logic to start at end of list and step backwards through items. # Hopefully this will avoid a possible race condition # ############################################################################### use File::Basename; my $PROGNAME = basename($0); my $VERSION = "1.1"; my $AUTHOR = "Robert Jacobson"; my $HOMEPAGE = "http://mysite.verizon.net/teridon/"; my $YEAR = 2007; my $GNU_URL = "http://www.gnu.org/copyleft/gpl.html"; { print "**************************************************************\n" . "$PROGNAME version $VERSION, Copyright (C) $YEAR $AUTHOR\n" . "Visit $HOMEPAGE for updates\n" . "$PROGNAME comes with ABSOLUTELY NO WARRANTY;\n". "This is free software, and you are welcome\n" . "to redistribute it under certain conditions\n" . "for details see $GNU_URL.\n" . "**************************************************************\n" . "\n" ; } use strict; use Win32::OLE; use Data::Dumper; # Create a signal handler to destroy the iTunes object # in case our program quits before the end use sigtrap 'handler', \&quit, 'normal-signals'; ## Create the OLE Object my $iTunes = Win32::OLE->new('iTunes.Application') or die Win32::OLE->LastError(); # Get the possible sources my $sources = $iTunes->Sources(); my $sourcesCount = $sources->Count(); my $source = ''; my $sourceKind = ''; my $tmp_playlist; my $n = 1; #print "There are " . $sourcesCount . " sources currently available\n"; # For each source, figure out kind for ($n = 1; $n <= $sourcesCount; $n++) { $source = $sources->Item($n); $sourceKind = $source->Kind(); print "source no. " . $n . " is "; print $sourceKind . " -- "; if ($sourceKind == 0) { print "Unknown Source\n"; } if ($sourceKind == 1) { print "Library Source\n"; # Get the playlists in the Library my $playlists = $source->Playlists(); my $num_playlists = $playlists->Count(); print "There are $num_playlists playlists\n"; # For each playlist, show the name and number of tracks for (my $j = 1 ; $j <= $num_playlists; $j++) { my $playlist = $playlists->Item($j); my $playlist_name = $playlist->Name(); print "\t$j : $playlist_name\n"; } print "Enter comma-seperated playlist numbers: "; chomp (my $nums = ); my @nums = split(/,/ , $nums); $tmp_playlist = $iTunes->CreatePlaylist("Temporary Playlist $$"); # For some reason I have to use LetProperty() instead of # $iTunes->BrowserWindow->{SelectedPlaylist} = $tmp_playlist; $iTunes->BrowserWindow->LetProperty('SelectedPlaylist', $tmp_playlist); print "***\n*** Leave the Temporary Playlist selected -- the script will run faster\n***\n"; for my $i (@nums) { my $playlist = $playlists->Item($i); my $playlist_name = $playlist->Name(); print "You selected $playlist_name\n"; my $tracks = $playlist->Tracks; my $num_tracks = $tracks->Count(); print "\t$num_tracks tracks\n"; my %seen; # Get all the tracks in the playlist for (my $k = $tracks->Count ; $k > 0 ; $k-- ) { #print "num: " , $num_tracks , " Count: ", $tracks->Count , " k: ", $k , "\n"; my $track = $tracks->Item($k); my $track_kind = $track->Kind(); if ($track_kind == 1) { # File my $songname = $track->Name(); my $artist = $track->Artist(); my $path = $track->Location(); my $size = $track->Size(); my $length = $track->Duration(); #my $tracknum = $track->TrackNumber(); #print "$songname, $artist, $path\n"; # Special case for multiple copies, like: # foofile.mp3 # foofile 1.mp3 # foofile 2.mp3 # Where all 3 are identical my ($filename, $dir, $suffix) = fileparse($path, qr/\.[^.]*/); # remove " x" from end $filename =~ s/ \d+$//; my $key = "$dir $filename $size $length"; #print $key . "\n"; $seen{$path}++; $seen{$key}++; # Exact path and filename duplicates if ($seen{$path} > 1) { print "\t#### Dup $k : $songname, $artist\n"; #print "delete? "; #chomp (my $ans = ); my $ans = "y"; if ($ans eq "y") { $track->Delete(); #$k -= 1; #$num_tracks = $tracks->Count(); next; # Don't continue, otherwise the only copy of the file will be deleted! } } # This is for approximate matches in path, but exact file size and duration if ($seen{$key} > 1) { print "\t#### Dup $k : $songname, $artist\n"; #print "delete? "; #chomp (my $ans = ); my $ans = "y"; if ($ans eq "y") { # Note, this just deletes it from the playlist # You probably shouldn't delete this from the main library #if (not $playlist_name eq "Library") { $track->Delete(); unlink $path or warn "could not delete file $path: $!"; #$k -= 1; #$num_tracks = $tracks->Count(); } } } } } } if ($sourceKind == 2) { print "iPod Source\n"; } if ($sourceKind == 3) { print "Audio CD Source\n"; } if ($sourceKind == 4) { print "MP3 CD Source\n"; } if ($sourceKind == 5) { print "Device Source\n"; } if ($sourceKind == 6) { print "Radio Source\n"; } if ($sourceKind == 7) { print "Shared Library Source\n"; } } $tmp_playlist->Delete(); print "Press enter to exit "; chomp (my $trash = ); # Destroy the object. Otherwise zombie object will come back # to haunt you quit(); sub quit { # This destroys the object undef $iTunes; exit; }