Skip to content

Instantly share code, notes, and snippets.

@oylenshpeegul
Created September 16, 2012 23:18
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oylenshpeegul/3734797 to your computer and use it in GitHub Desktop.
Save oylenshpeegul/3734797 to your computer and use it in GitHub Desktop.
Perl script to download a playlist from tunes.io
#!/usr/bin/env perl
# Download all the files in a daily playlist from tunes.io
#
# https://gist.github.com/3734797
$ENV{MOJO_MAX_MESSAGE_SIZE} = 30_000_000;
use v5.16;
use warnings;
use CLI::Startup;
use Data::Printer;
use DateTime;
use IO::All;
use JSON;
use Mojo::IOLoop;
use Mojo::UserAgent;
use Time::HiRes qw(time);
our $VERSION = '0.0.1';
my $app = CLI::Startup->new({
options => {
'basedir=s' => 'Output directory [default: ~/tunesio]',
'date=s' => 'Date yyyy-mm-dd [default: today]',
'debug!' => 'Debug output',
'download=i' => 'Download files [default: 1 (yes)]',
'mono=i' => 'Bounce files down to mono [default: 0 (no)]',
'normalize=i' => 'Normalize files [default: 0 (no)]',
'verbose!' => 'Verbose output',
},
default_settings => {
basedir => "$ENV{HOME}/tunesio",
date => DateTime->today->strftime('%F'),
download => 1,
mono => 0,
normalize => 0,
},
});
$app->init;
my $opt = $app->get_options;
say $opt->{date} if $opt->{debug};
my $outdir = "$opt->{basedir}/$opt->{date}";
say $outdir if $opt->{debug};
io($outdir)->mkpath;
# Get the playlist from the website.
my $playlist = get_playlist($outdir, $opt);
p $playlist if $opt->{debug};
chdir $outdir or die "$outdir: $!";
# Download all the files concurrently.
get_songs($playlist, $opt) if $opt->{download};
# Bounce the files down to mono.
if ($opt->{mono}) {
foreach my $file (glob "*.mp3") {
my $out = $file =~ s/\.mp3$/.mono.mp3/r;
my @cmd = ('sox', '-G', $file, '-c', '1', $out);
say "@cmd" if $opt->{debug};
system(@cmd) == 0 or die "Cannot run '@cmd'";
rename $out, $file;
}
}
# Normalize all the files as a set.
if ($opt->{normalize}) {
my $cmd = "normalize-audio --mix *.mp3";
system($cmd) == 0 or warn "Cannot run '$cmd'";
}
# If the playlist JSON file exists locally, just read it in and decode
# it. Otherwise, download the playlist from the website (and encode
# and save it for next time).
sub get_playlist {
my($outdir, $opt) = @_;
my $file = "$outdir/playlist";
say $file if $opt->{debug};
if ( -e $file ) {
return decode_json(io($file)->all);
}
else {
my $url = "http://tunes.io/playlist.jsp?date=$opt->{date}";
say $url if $opt->{debug};
my $dom = Mojo::UserAgent->new->get($url)->res->dom;
my @songs = $dom->find('ul.playlist')
->map(sub{$_->children->each}) # li
->map(sub{$_->children->each}) # a
->each;
my $playlist = [];
for my $song (@songs) {
my $title = $song->text;
$title =~ s/\s/_/g;
$title =~ s{/}{.}g;
push $playlist, {title => $title, url => $song->{href}};
}
io($file)->print(encode_json($playlist));
return $playlist;
}
}
# Download all of the songs in the given playlist.
sub get_songs {
my($playlist, $opt) = @_;
my $ua = Mojo::UserAgent->new(max_redirects => 3);
my $iol = Mojo::IOLoop->delay(sub { warn "done"; });
my $start = time;
say 'Downloading ', scalar @$playlist, ' songs asynchronously'
if $opt->{verbose};
for my $song (@$playlist) {
say "getting '$song->{title}' from\n$song->{url}"
if $opt->{verbose};
my $now = time;
$iol->begin;
# Use the song title plus the file extension from the URL as
# the filename.
my $file = $song->{title} . substr($song->{url}, -4);
$ua->get($song->{url} => sub {
my($ua, $tx) = @_;
my $u = sprintf "%-25s", $song->{url};
my $content = $tx->res->body;
io($file)->print($content);
my $retval = "$u has ";
if ($tx->success) {
my $len = sprintf "%8d", length($content);
my $et = sprintf "%5.3f", time - $now;
$retval .= "length $len and loaded in $et ms";
}
else {
my($message, $code) = $tx->error;
$code = "??" unless defined $code;
$retval .= "error ($code) $message";
}
warn "\n$retval\n" if $opt->{verbose};
$iol->end($retval);
});
}
say for $iol->wait;
printf "Total elapsed time: %5.3f s\n", time - $start;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment