1#!/usr/bin/perl -w 2# itshell--itunes shell 3 4=head1 NAME 5 6 itshell - a shell for searching and downloading from an itunes server 7 8=head1 SYNOPSIS 9 10 itshell [servername [serverport]] 11 12=head1 DESCRIPTION 13 14This program connects to an iTunes server and lets you search the 15playlists and songs and download the music files to your local 16filesystem. 17 18The following commands are valid from within the shell: 19 20 db view possible databases 21 db id select a database 22 playlist view possible playlists 23 playlist id select a playlist 24 find keyword search song title/album/artist for keyword 25 dir show where files will be saved 26 dir /new/dir set new location for files to be saved 27 findget keyword search for and immediately get title/album/artist 28 get id ... download a song 29 url id ... view persistent URL for a song or playlist 30 dump filename dump databases to the filename 31 quit leave this shell 32 33=head1 AUTHOR 34 35Nathan Torkington. Send patches to C<< <dapple AT torkington.com >>. 36Mail C<< daap-dev-subscribe AT develooper.com >> to join the DAAP 37developers mailing list. 38 39=cut 40 41use Net::DAAP::Client; 42 43# standard modules 44 45use sigtrap; 46use Carp; 47use strict; 48use Text::ParseWords; 49use Data::Dumper; 50use Term::ReadLine; 51 52my $Server_host = shift || 'localhost'; 53my $Server_port = shift || 3689; 54my $Password = shift || ''; 55 56my $daap = Net::DAAP::Client->new(SERVER_HOST => $Server_host, 57 SERVER_PORT => $Server_port, 58 PASSWORD => $Password, 59 DEBUG => 1); 60push @{ $daap->{SONG_ATTRIBUTES} }, 61 qw( daap.songbitrate daap.songsamplerate 62 daap.songstarttime daap.songstoptime 63 daap.songtime ); 64$daap->connect() or die "Can't connect: ".$daap->error(); 65 66 67my $dbs = $daap->databases; 68 69my $term = new Term::ReadLine qw(iTunesHell); 70my $line; 71my $prompt = 'iTunes'; 72my $save_dir = '.'; 73$| = 1; 74 75while (defined ($line = $term->readline("$prompt>"))) { 76 my ($cmd, @arg) = parse_line(qr{\s+}, 0, $line); 77 if ($cmd eq 'db') { 78 db_cmd(@arg); 79 } elsif ($cmd eq 'playlist') { 80 playlist_cmd(@arg); 81 } elsif ($cmd eq 'find') { 82 find_cmd(@arg); 83 } elsif ($cmd eq 'findget') { 84 findget_cmd(@arg); 85 } elsif ($cmd eq 'get') { 86 get_cmd(@arg); 87 } elsif ($cmd eq 'url') { 88 url_cmd(@arg); 89 } elsif ($cmd eq 'dir') { 90 dir_cmd(@arg); 91 } elsif ($cmd eq 'dump') { 92 dump_cmd(@arg); 93 } elsif (($cmd eq 'quit') || ($cmd eq 'exit')) { 94 last; 95 } else { 96 warn <<EOF ; 97Commands: 98 db view possible databases 99 db id select a database 100 playlist view possible playlists 101 playlist id select a playlist 102 find keyword search song title/album/artist for keyword 103 dir show where files will be saved 104 dir /new/dir set new location for files to be saved 105 findget keyword search for and immediately get title/album/artist 106 get id ... download a song 107 url id ... view persistent URL for a song or playlist 108 dump filename dump databases to the filename 109 quit leave this shell 110EOF 111 } 112} 113 114$daap->disconnect(); 115 116sub song_as_text { 117 my $song = shift; 118 return sprintf("%d : %s, %s (%s)\n", 119 $song->{"dmap.itemid"}, 120 $song->{"dmap.itemname"}, 121 $song->{"daap.songartist"}, 122 $song->{"daap.songalbum"}); 123} 124 125sub dir_cmd { 126 my @arg = @_; 127 128 if (@_) { 129 if (! -d $arg[0]) { 130 print "$arg[0] isn't a directory!\n"; 131 } else { 132 $save_dir = $arg[0]; 133 } 134 } else { 135 print "Files will be saved to $save_dir\n"; 136 } 137} 138 139sub dump_cmd { 140 my @arg = @_; 141 if (@arg) { 142 my $filename = shift @arg; 143 open my $fh, ">$filename" or warn("Can't open $filename: $!"),return; 144 print $fh Dumper($dbs); 145 if ($daap->songs) { 146 print $fh "\n\n"; 147 print $fh Dumper($daap->songs); 148 } 149 close $fh; 150 } else { 151 warn("usage: dump filename\n"); 152 } 153} 154 155sub db_cmd { 156 my @arg = @_; 157 if (@arg) { 158 my $db_id = $arg[0]; 159 $daap->db($db_id); 160 if (! $daap->error) { 161 printf "Loading database %s (may take a moment)\n", $daap->databases->{$db_id}{'dmap.itemname'}; 162 } else { 163 warn "Database ID $arg[0] not found\n"; 164 } 165 } else { 166 my $dbs = $daap->databases; 167 foreach my $id (sort { $a <=> $b } keys %$dbs) { 168 printf("%d : %s\n", $id, $dbs->{$id}{"dmap.itemname"}); 169 } 170 } 171} 172 173sub playlist_cmd { 174 my @arg = @_; 175 176 if (! $daap->db) { 177 warn "Select a database with db first\n"; 178 return; 179 } 180 181 if (@arg) { 182 my $playlist_id = $arg[0]; 183 my $songs = $daap->playlist($arg[0]); 184 if (! $daap->error) { 185 foreach my $playlist_song (@$songs) { 186 my $songs = $daap->songs; 187 my $song = $daap->songs->{$playlist_song->{"dmap.itemid"}}; 188 if (defined($song)) { 189 # deleted items are evidentally left on the main playlist 190 print song_as_text($song); 191 } 192 } 193 } else { 194 warn "Playlist ID $arg[0] not found\n"; 195 } 196 } else { 197 my $playlists = $daap->playlists; 198 foreach my $id (sort { $a <=> $b } keys %$playlists) { 199 printf("%d : %s\n", $id, $playlists->{$id}{"dmap.itemname"}); 200 } 201 } 202} 203 204sub findget_cmd { 205 my @arg = @_; 206 207 if (! $daap->db) { 208 warn "Select a database with db first\n"; 209 return; 210 } 211 212 my @songs = search_through_songs(@arg); 213 foreach my $song (@songs) { 214 print "$save_dir/$song->{'dmap.itemid'}.$song->{'daap.songformat'} ($song->{'dmap.itemname'}) ... "; 215 $daap->save($save_dir, $song->{'dmap.itemid'}); 216 print $daap->error ? "failed" : "done"; 217 print "\n"; 218 } 219} 220 221sub search_through_songs { 222 my $word = shift; 223 my @hits = (); 224 225 foreach my $song (values %{$daap->songs}) { 226 my (@f) = map { lc } ( 227 $song->{"dmap.itemname"}, 228 $song->{"daap.songartist"}, 229 $song->{"daap.songalbum"} 230 ); 231 my $to_find = lc($word); 232 if (grep { index(lc($_), $to_find) != -1 } @f) { 233 push @hits, $song; 234 } 235 } 236 237 return @hits; 238} 239 240sub find_cmd { 241 my @arg = @_; 242 243 if (! $daap->db) { 244 warn "Select a database with db first\n"; 245 return; 246 } 247 248 if (@arg) { 249 my @songs = search_through_songs(@arg); 250 foreach my $song (@songs) { 251 print song_as_text($song); 252 } 253 } else { 254 warn "usage: find [string]\n"; 255 } 256} 257 258sub get_cmd { 259 my @arg = @_; 260 my $songs = $daap->songs; 261 262 foreach my $song_id (@arg) { 263 my $song = $songs->{$song_id}; 264 if (defined $song) { 265 print "Fetching ", song_as_text($song); 266 $daap->save($save_dir, $song_id); 267 if ($daap->error) { 268 print "Failed: ", $daap->error, "\n"; 269 } 270 } else { 271 print "Skipping bogus song number $song_id\n"; 272 } 273 } 274} 275 276sub url_cmd { 277 my @arg = @_; 278 my @skipped; 279 280 foreach my $id (@arg) { 281 my $url = $daap->url($id); 282 if ($url) { 283 print "$url\n"; 284 } else { 285 push @skipped, $url; 286 } 287 } 288 289 if (@skipped) { 290 print "Skipped: ", join(", ", @skipped), ".\n"; 291 } 292} 293 294exit; 295 296