#!/usr/bin/perl -w
# itshell--itunes shell
=head1 NAME
itshell - a shell for searching and downloading from an itunes server
=head1 SYNOPSIS
itshell [servername [serverport]]
=head1 DESCRIPTION
This program connects to an iTunes server and lets you search the
playlists and songs and download the music files to your local
filesystem.
The following commands are valid from within the shell:
db view possible databases
db id select a database
playlist view possible playlists
playlist id select a playlist
find keyword search song title/album/artist for keyword
dir show where files will be saved
dir /new/dir set new location for files to be saved
findget keyword search for and immediately get title/album/artist
get id ... download a song
url id ... view persistent URL for a song or playlist
dump filename dump databases to the filename
quit leave this shell
=head1 AUTHOR
Nathan Torkington. Send patches to C<< <dapple AT torkington.com >>.
Mail C<< daap-dev-subscribe AT develooper.com >> to join the DAAP
developers mailing list.
=cut
use Net::DAAP::Client;
# standard modules
use sigtrap;
use Carp;
use strict;
use Text::ParseWords;
use Data::Dumper;
use Term::Readline;
my $Server_host = shift || 'localhost';
my $Server_port = shift || 3689;
my $daap = Net::DAAP::Client->new(SERVER_HOST => $Server_host,
SERVER_PORT => $Server_port);
$daap->connect() or die "Can't connect: ".$daap->error();
my $dbs = $daap->databases;
my $term = new Term::ReadLine qw(iTunesHell);
my $line;
my $prompt = 'iTunes';
my $save_dir = '.';
$| = 1;
while (defined ($line = $term->readline("$prompt>"))) {
my ($cmd, @arg) = parse_line(qr{\s+}, 0, $line);
if ($cmd eq 'db') {
db_cmd(@arg);
} elsif ($cmd eq 'playlist') {
playlist_cmd(@arg);
} elsif ($cmd eq 'find') {
find_cmd(@arg);
} elsif ($cmd eq 'findget') {
findget_cmd(@arg);
} elsif ($cmd eq 'get') {
get_cmd(@arg);
} elsif ($cmd eq 'url') {
url_cmd(@arg);
} elsif ($cmd eq 'dir') {
dir_cmd(@arg);
} elsif ($cmd eq 'dump') {
dump_cmd(@arg);
} elsif (($cmd eq 'quit') || ($cmd eq 'exit')) {
last;
} else {
warn <<EOF ;
Commands:
db view possible databases
db id select a database
playlist view possible playlists
playlist id select a playlist
find keyword search song title/album/artist for keyword
dir show where files will be saved
dir /new/dir set new location for files to be saved
findget keyword search for and immediately get title/album/artist
get id ... download a song
url id ... view persistent URL for a song or playlist
dump filename dump databases to the filename
quit leave this shell
EOF
}
}
$daap->disconnect();
sub song_as_text {
my $song = shift;
return sprintf("%d : %s, %s (%s)\n",
$song->{"dmap.itemid"},
$song->{"dmap.itemname"},
$song->{"daap.songartist"},
$song->{"daap.songalbum"});
}
sub dir_cmd {
my @arg = @_;
if (@_) {
if (! -d $arg[0]) {
print "$arg[0] isn't a directory!\n";
} else {
$save_dir = $arg[0];
}
} else {
print "Files will be saved to $save_dir\n";
}
}
sub dump_cmd {
my @arg = @_;
if (@arg) {
my $filename = shift @arg;
open my $fh, ">$filename" or warn("Can't open $filename: $!"),return;
print $fh Dumper($dbs);
if ($daap->songs) {
print $fh "\n\n";
print $fh Dumper($daap->songs);
}
close $fh;
} else {
warn("usage: dump filename\n");
}
}
sub db_cmd {
my @arg = @_;
if (@arg) {
my $db_id = $arg[0];
$daap->db($db_id);
if (! $daap->error) {
printf "Loading database %s (may take a moment)\n", $daap->databases->{$db_id}{'dmap.itemname'};
} else {
warn "Database ID $arg[0] not found\n";
}
} else {
my $dbs = $daap->databases;
foreach my $id (sort { $a <=> $b } keys %$dbs) {
printf("%d : %s\n", $id, $dbs->{$id}{"dmap.itemname"});
}
}
}
sub playlist_cmd {
my @arg = @_;
if (! $daap->db) {
warn "Select a database with db first\n";
return;
}
if (@arg) {
my $playlist_id = $arg[0];
my $songs = $daap->playlist($arg[0]);
if (! $daap->error) {
foreach my $playlist_song (@$songs) {
my $songs = $daap->songs;
my $song = $daap->songs->{$playlist_song->{"dmap.itemid"}};
if (defined($song)) {
# deleted items are evidentally left on the main playlist
print song_as_text($song);
}
}
} else {
warn "Playlist ID $arg[0] not found\n";
}
} else {
my $playlists = $daap->playlists;
foreach my $id (sort { $a <=> $b } keys %$playlists) {
printf("%d : %s\n", $id, $playlists->{$id}{"dmap.itemname"});
}
}
}
sub findget_cmd {
my @arg = @_;
if (! $daap->db) {
warn "Select a database with db first\n";
return;
}
my @songs = search_through_songs(@arg);
foreach my $song (@songs) {
print "$save_dir/$song->{'dmap.itemid'}.$song->{'daap.songformat'} ($song->{'dmap.itemname'}) ... ";
$daap->save($save_dir, $song->{'dmap.itemid'});
print $daap->error ? "failed" : "done";
print "\n";
}
}
sub search_through_songs {
my $word = shift;
my @hits = ();
foreach my $song (values %{$daap->songs}) {
my (@f) = map { lc } (
$song->{"dmap.itemname"},
$song->{"daap.songartist"},
$song->{"daap.songalbum"}
);
my $to_find = lc($word);
if (grep { index(lc($_), $to_find) != -1 } @f) {
push @hits, $song;
}
}
return @hits;
}
sub find_cmd {
my @arg = @_;
if (! $daap->db) {
warn "Select a database with db first\n";
return;
}
if (@arg) {
my @songs = search_through_songs(@arg);
foreach my $song (@songs) {
print song_as_text($song);
}
} else {
warn "usage: find [string]\n";
}
}
sub get_cmd {
my @arg = @_;
my $songs = $daap->songs;
foreach my $song_id (@arg) {
my $song = $songs->{$song_id};
if (defined $song) {
print "Fetching ", song_as_text($song);
$daap->save($save_dir, $song_id);
if ($daap->error) {
print "Failed: ", $daap->error, "\n";
}
} else {
print "Skipping bogus song number $song_id\n";
}
}
}
sub url_cmd {
my @arg = @_;
my @skipped;
foreach my $id (@arg) {
my $url = $daap->url($id);
if ($url) {
print "$url\n";
} else {
push @skipped, $url;
}
}
if (@skipped) {
print "Skipped: ", join(", ", @skipped), ".\n";
}
}
exit;