#!/usr/bin/perl -w ################################################## # TO DO # Improve the documentation and comments # Add ID3 tags: libmp3-tag-perl, libmp3-info-perl # Add a licence: GPL or Artistic # Support -c option ################################################## use strict; use Getopt::Std; use LWP::UserAgent; use warnings; use Pod::Usage; use File::Slurp; my ( %option, $episode_arg ) ; my $target_directory = $ENV{HOME} . "/music/car-talk" ; getopts("nd:xcavhmWD:", \%option) ; if ($option{d}) { # set target directory $target_directory = $option{d} ; } if ($option{h}) { pod2usage(-verbose => 2); } elsif ($option{c}) { print_smil(); } else { my $delay = $option{D} || 2; # default: wait 2 minutes test_directory($target_directory) ; my $playlist = "" ; my @at_job = () ; foreach $episode_arg (@ARGV) { $playlist = get_playlist($episode_arg) ; push(@at_job, download_playlist($playlist)); } push(@at_job, add_wav2mp3(@at_job)) if ($option{m}); push(@at_job, add_wav2wav(@at_job)) unless ($option{W}); push(@at_job, "ls -l\n"); my $at_filename = tmp_filename(); push(@at_job, "rm -v $at_filename\n"); write_file($at_filename, @at_job); print("** Produced at job file: $at_filename\n") ; run_at_job($at_filename, $delay) unless ($option{n}) ; print("** Done.\n") ; } ################################################## ##### END MAIN ################################################## sub get_playlist { # Pad episode no. to 4 characters and splice into URL my $episode = sprintf("%04u", $_[0]) ; my $url = "http://play.rbn.com/?url=cartalk/cartalk/demand/CTXXXX-01.ra?url=cartalk/cartalk/demand/CTXXXX-02.ra?" . "url=cartalk/cartalk/demand/CTXXXX-03.ra?url=cartalk/cartalk/demand/CTXXXX-04.ra?" . "url=cartalk/cartalk/demand/CTXXXX-05.ra?url=cartalk/cartalk/demand/CTXXXX-06.ra?" . "url=cartalk/cartalk/demand/CTXXXX-07.ra?url=cartalk/cartalk/demand/CTXXXX-08.ra?" . "url=cartalk/cartalk/demand/CTXXXX-09.ra?url=cartalk/cartalk/demand/CTXXXX-10.ra" ; $url =~ s/XXXX/$episode/g ; print ("** URL:\n$url\n") if ($option{v}) ; my $agent = LWP::UserAgent->new( agent => "RMA/1.0 (compatible; RealMedia)", from => "" ); # Can we set the referer header too? # I think it's # $agent->default_header('Referer' => "...") ; my $response = $agent->get($url) ; my $playlist = $response->content ; return $playlist ; } sub print_smil { my $url = "http://www.cartalk.com/Radio/Show/show.smil"; my $agent = LWP::UserAgent->new( agent => "RMA/1.0 (compatible; RealMedia)", from => "" ); # Can we set the referer header too? # I think it's # $agent->default_header('Referer' => "...") ; my $response = $agent->get($url) ; my $smil = $response->content ; $smil =~ s!>!>\n!g; print("*****\n", $smil, "\n*****\n"); } sub download_playlist { # Convert the string to a list of lines my @playlist = split("\n", $_[0]) ; my @job = ("cd $target_directory\n", "date\n"); my ( $line, $url, $filename ) ; my $mplayer_ctr = 0 ; foreach $line (@playlist) { if ( $line =~ /^\s*(rtsp\S+\/)([\w\-]+)(\.\S+)\s*$/ ) { $url = $1 . $2 . $3 ; $filename = lc($2) . ".wav" ; $filename =~ s/-(\d\d)\./$1\./; # 20080108: Modified the following to download one part after another # but at high speed, rather than 10 simultaneously. push(@job, "mplayer -really-quiet -bandwidth 999999999999 " . "-ao pcm:file=" . $filename . " \'" . $url . "\'\n"); $mplayer_ctr++ ; } } print("** Added mplayer lines: $mplayer_ctr\n") ; push(@job, "echo \"downloading finished\"\n", "date\n"); return @job ; } sub add_wav2mp3 { my ($line, $wav_file, $mp3_file) ; my @addenda = (); my $lame_ctr = 0 ; foreach $line (@_) { if ( $line =~ /=(\S+)(\.wav)/ ) { $wav_file = $1 . $2 ; $mp3_file = $1 . ".mp3" ; if ($option{x}) { # -x to retain wav after encoding push(@addenda, "lame --quiet $wav_file $mp3_file\n"); } else { push(@addenda, "lame --quiet $wav_file $mp3_file && rm -v $wav_file\n"); } $lame_ctr++ ; } } push(@addenda, "date\n"); print("** Added lame lines: $lame_ctr\n") ; return @addenda ; } sub add_wav2wav { my ($line, $ww_file, $wav_file) ; my @addenda = (); my $ctr = 0 ; foreach $line (@_) { if ( $line =~ /=(\S+)(\.wav)/ ) { $wav_file = $1 . $2 ; $ww_file = $1 . "_w.wav" ; if ($option{x}) { # -x to retain wav after encoding push(@addenda, "sox $wav_file -w -c 2 -r 44100 $ww_file\n"); } else { push(@addenda, "sox $wav_file -w -c 2 -r 44100 $ww_file && rm -v $wav_file\n"); } $ctr++ ; } } push(@addenda, "date\n"); print("** Added sox lines: $ctr\n") ; return @addenda ; } sub tmp_filename { return( "/tmp/car-talk-" . time() ."-" . $$ . ".at" ); } sub dump2at { my $at_job = $_[0] ; my $at_filename = "/tmp/car-talk-" . time() ."-" . $$ . ".at" ; print("***** BEGIN at JOB\n" . $at_job . "*****END at JOB\n") if ($option{v}) ; open(ATFILE, ">$at_filename") or die ("Couldn't open $at_filename\n") ; print(ATFILE $at_job) ; close(ATFILE) or die ("Couldn't close $at_filename\n") ; return $at_filename ; } sub run_at_job { my $at_filename = $_[0] ; my $now_min = 'now+' . $_[1] . 'min'; print ("** About to queue $at_filename\n") ; system ('at', '-f', $at_filename, $now_min) ; system ("atq") ; } sub test_directory { my $dir = $_[0] ; my $result = 0 ; if (! (-d $dir) ) { print("Not a directory: $dir\n") ; } elsif (! (-w $dir) ) { print("Not writable: $dir\n") ; } else { $result = 1 ; } return $result ; } ################################################## ##### DOCUMENTATION ################################################## =head1 Options =over =item -h Print this help and quit. =item -v Increase verbosity. =item -D I Delay at job by I minutes (default 2). =item -c Print information about the current episode. =item -n Dry run: produce the at job file but do not enqueue it. =item -x Retain the B<.wav> files. (The default is to delete each one after successfully encoding it to B<.mp3>.) =item -d I Override the default target directory. =item -m Produce MP3s and delete the WAVE files. =item -W Do not convert to CD-writable WAVE files and delete the originals. (This is done by default.) =back =cut ######################################################################