|
|
@@ -11,9 +11,12 @@ use Date::Format;
|
|
|
use File::Basename;
|
|
|
|
|
|
# Command-line
|
|
|
-my $OUT_DIR = abs_path($ARGV[0]);
|
|
|
+my $OUT_DIR = undef();
|
|
|
+if (defined($ARGV[0])) {
|
|
|
+ $OUT_DIR = abs_path($ARGV[0]);
|
|
|
+}
|
|
|
if (!$OUT_DIR || !-d $OUT_DIR) {
|
|
|
- die('Usage: ' . basename($0) . " output_directory\n");
|
|
|
+ die('Usage: ' . basename($0) . " output_directory\n");
|
|
|
}
|
|
|
|
|
|
# Parameters
|
|
|
@@ -27,332 +30,348 @@ my $DL_LIMIT = 50;
|
|
|
# Init
|
|
|
our $DEBUG = 0;
|
|
|
if (defined($ENV{'DEBUG'}) && $ENV{'DEBUG'} =~ /(\d+)/) {
|
|
|
- $DEBUG = $1;
|
|
|
+ $DEBUG = $1;
|
|
|
}
|
|
|
my %episodes = ();
|
|
|
my %have = ();
|
|
|
my @need = ();
|
|
|
my $hs = HTML::Strip->new();
|
|
|
MP3::Tag->config('write_v24' => 1);
|
|
|
+my $ua = LWP::UserAgent->new();
|
|
|
+$ua->agent('UberZach-RSS/1.0');
|
|
|
|
|
|
# Skip if there is a season_done file
|
|
|
if (-e $OUT_DIR . '/season_done') {
|
|
|
- if ($DEBUG) {
|
|
|
- print STDERR 'Podcast disabled: ' . $SERIES . "\n";
|
|
|
- }
|
|
|
- exit(0);
|
|
|
+ if ($DEBUG) {
|
|
|
+ print STDERR 'Podcast disabled: ' . $SERIES . "\n";
|
|
|
+ }
|
|
|
+ exit(0);
|
|
|
}
|
|
|
|
|
|
# Fetch and analyize the feed
|
|
|
my $feed = undef();
|
|
|
{
|
|
|
- open(my $fh, '<', $URL_FILE)
|
|
|
- or die('Unable to open feed file: ' . $URL_FILE . "\n");
|
|
|
- my $url = <$fh>;
|
|
|
- close($fh);
|
|
|
- if (!$url || length($url) < 5) {
|
|
|
- die('Invalid feed URL: ' . $url . "\n");
|
|
|
- }
|
|
|
-
|
|
|
- $feed = XML::Feed->parse(URI->new($url));
|
|
|
- if (!$feed) {
|
|
|
- die('Unable to parse feed (' . XML::Feed->errstr() . '): ' . $url . "\n");
|
|
|
- }
|
|
|
+ open(my $fh, '<', $URL_FILE)
|
|
|
+ or die('Unable to open feed file: ' . $URL_FILE . "\n");
|
|
|
+ my $url = <$fh>;
|
|
|
+ close($fh);
|
|
|
+ if (!$url || length($url) < 5) {
|
|
|
+ die('Invalid feed URL: ' . $url . "\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ $feed = XML::Feed->parse(URI->new($url));
|
|
|
+ if (!$feed) {
|
|
|
+ die('Unable to parse feed (' . XML::Feed->errstr() . '): ' . $url . "\n");
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
# Parse each episode from the feed
|
|
|
for my $entry ($feed->entries()) {
|
|
|
- my $ep = $entry->unwrap();
|
|
|
-
|
|
|
- # Title
|
|
|
- my $title = '';
|
|
|
- if (defined($ep->{'title'})) {
|
|
|
- $title = $ep->{'title'};
|
|
|
- }
|
|
|
- $title = $hs->parse($title);
|
|
|
- $hs->eof();
|
|
|
- $title =~ s/^\s+//;
|
|
|
- $title =~ s/\s+$//;
|
|
|
-
|
|
|
- # Description
|
|
|
- my $desc = '';
|
|
|
- if (defined($ep->{'description'})) {
|
|
|
- $desc = $ep->{'description'};
|
|
|
- }
|
|
|
- $desc = $hs->parse($desc);
|
|
|
- $hs->eof();
|
|
|
- $desc =~ s/^\s+//;
|
|
|
- $desc =~ s/\s+$//;
|
|
|
-
|
|
|
- # Timestamp
|
|
|
- my $time = str2time($ep->{'pubDate'});
|
|
|
-
|
|
|
- # Author
|
|
|
- my $author = undef();
|
|
|
- if (exists($ep->{'author'})) {
|
|
|
- $author = $ep->{'author'};
|
|
|
- $author =~ s/^\s+//;
|
|
|
- $author =~ s/\s+$//;
|
|
|
- }
|
|
|
-
|
|
|
- # Enclosure (i.e. attachment/file) data
|
|
|
- my ($url, $ext, $size) = undef();
|
|
|
- {
|
|
|
- my $enc = $ep->{'enclosure'};
|
|
|
- if ($enc && ref($enc) eq 'HASH' && exists($enc->{'url'})) {
|
|
|
- $url = $enc->{'url'};
|
|
|
- if (exists($enc->{'type'})) {
|
|
|
- if ($enc->{'type'} =~ /audio\/mp4/i) {
|
|
|
- $ext = 'm4a';
|
|
|
- } elsif ($enc->{'type'} =~ /audio\/mpeg/i) {
|
|
|
- $ext = 'mp3';
|
|
|
- } elsif ($enc->{'type'} =~ /video\//i) {
|
|
|
- if ($DEBUG > 1) {
|
|
|
- print STDERR 'Skipping video file: ' . time2str('%Y-%m-%d', $time) . "\n";
|
|
|
- }
|
|
|
- } else {
|
|
|
- print STDERR 'Unknown MIME type: ' . $enc->{'type'} . "\n";
|
|
|
- }
|
|
|
- }
|
|
|
- if (exists($enc->{'length'})) {
|
|
|
- $size = $enc->{'length'};
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- # iTunes data, if available (and useful)
|
|
|
- my $duration = undef();
|
|
|
- if ($ep->{'http://www.itunes.com/dtds/podcast-1.0.dtd'}) {
|
|
|
- my $itunes = $ep->{'http://www.itunes.com/dtds/podcast-1.0.dtd'};
|
|
|
-
|
|
|
- if (exists($itunes->{'duration'})) {
|
|
|
- if ($itunes->{'duration'} =~ /(\d+)\:(\d\d)\:(\d\d)/) {
|
|
|
- $duration = (3600 * int($1)) + (60 * int($2)) + int($3);
|
|
|
- } elsif ($itunes->{'duration'} =~ /(\d+)\:(\d\d)/) {
|
|
|
- $duration = (60 * int($1)) + int($2);
|
|
|
- } elsif ($itunes->{'duration'} =~ /(\d+)/) {
|
|
|
- $duration = int($1);
|
|
|
- } elsif ($DEBUG) {
|
|
|
- print STDERR 'Unknown duration format: ' . $itunes->{'duration'} . "\n";
|
|
|
- }
|
|
|
- }
|
|
|
- if (!$desc && exists($itunes->{'summary'})) {
|
|
|
- $desc = $itunes->{'summary'};
|
|
|
- $desc =~ s/^\s+//;
|
|
|
- $desc =~ s/\s+$//;
|
|
|
- }
|
|
|
- if (!$author && exists($itunes->{'author'})) {
|
|
|
- $author = $itunes->{'author'};
|
|
|
- $author =~ s/^\s+//;
|
|
|
- $author =~ s/\s+$//;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- # Collect extracted, cleaned data
|
|
|
- if ($title && $time && $url && $ext && $desc) {
|
|
|
- my %tmp = (
|
|
|
- 'title' => $title,
|
|
|
- 'time' => $time,
|
|
|
- 'url' => $url,
|
|
|
- 'ext' => $ext,
|
|
|
- 'description' => $desc,
|
|
|
- 'size' => $size,
|
|
|
- 'duration' => $duration,
|
|
|
- 'author' => $author,
|
|
|
- );
|
|
|
- $episodes{$time} = \%tmp;
|
|
|
- } elsif ($DEBUG) {
|
|
|
- print STDERR "Skipping incomplete entry:\n";
|
|
|
- print STDERR "\tTitle: ";
|
|
|
- if ($title) {
|
|
|
- print STDERR $title . "\n";
|
|
|
- } else {
|
|
|
- print STDERR "<missing>\n";
|
|
|
- }
|
|
|
- print STDERR "\tTime: ";
|
|
|
- if ($time) {
|
|
|
- print STDERR time2str('%Y-%m-%d', $time) . "\n";
|
|
|
- } else {
|
|
|
- print STDERR "<missing>\n";
|
|
|
- }
|
|
|
- print STDERR "\tURL: ";
|
|
|
- if ($url) {
|
|
|
- print STDERR $url . "\n";
|
|
|
- } else {
|
|
|
- print STDERR "<missing>\n";
|
|
|
- }
|
|
|
- print STDERR "\tExtension: ";
|
|
|
- if ($ext) {
|
|
|
- print STDERR $ext . "\n";
|
|
|
- } else {
|
|
|
- print STDERR "<missing>\n";
|
|
|
- }
|
|
|
- print STDERR "\tDescription: ";
|
|
|
- if ($desc) {
|
|
|
- print STDERR substr($desc, 0, 20) . "\n";
|
|
|
- } else {
|
|
|
- print STDERR "<missing>\n";
|
|
|
- }
|
|
|
- }
|
|
|
+ my $ep = $entry->unwrap();
|
|
|
+
|
|
|
+ # Title
|
|
|
+ my $title = '';
|
|
|
+ if (defined($ep->{'title'})) {
|
|
|
+ $title = $ep->{'title'};
|
|
|
+ }
|
|
|
+ $title = $hs->parse($title);
|
|
|
+ $hs->eof();
|
|
|
+ $title =~ s/^\s+//;
|
|
|
+ $title =~ s/\s+$//;
|
|
|
+
|
|
|
+ # Description
|
|
|
+ my $desc = '';
|
|
|
+ if (defined($ep->{'description'})) {
|
|
|
+ $desc = $ep->{'description'};
|
|
|
+ }
|
|
|
+ $desc = $hs->parse($desc);
|
|
|
+ $hs->eof();
|
|
|
+ $desc =~ s/^\s+//;
|
|
|
+ $desc =~ s/\s+$//;
|
|
|
+
|
|
|
+ # Timestamp
|
|
|
+ my $time = str2time($ep->{'pubDate'});
|
|
|
+
|
|
|
+ # Author
|
|
|
+ my $author = undef();
|
|
|
+ if (exists($ep->{'author'})) {
|
|
|
+ $author = $ep->{'author'};
|
|
|
+ $author =~ s/^\s+//;
|
|
|
+ $author =~ s/\s+$//;
|
|
|
+ }
|
|
|
+
|
|
|
+ # Enclosure (i.e. attachment/file) data
|
|
|
+ my ($url, $ext, $size) = undef();
|
|
|
+ {
|
|
|
+ my $enc = $ep->{'enclosure'};
|
|
|
+ if ($enc && ref($enc) eq 'HASH' && exists($enc->{'url'})) {
|
|
|
+ $url = $enc->{'url'};
|
|
|
+ if (exists($enc->{'type'})) {
|
|
|
+ if ($enc->{'type'} =~ /audio\/mp4/i) {
|
|
|
+ $ext = 'm4a';
|
|
|
+ } elsif ($enc->{'type'} =~ /audio\/mpeg/i) {
|
|
|
+ $ext = 'mp3';
|
|
|
+ } elsif ($enc->{'type'} =~ /video\//i) {
|
|
|
+ if ($DEBUG > 1) {
|
|
|
+ print STDERR 'Skipping video file: ' . time2str('%Y-%m-%d', $time) . "\n";
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ print STDERR 'Unknown MIME type: ' . $enc->{'type'} . "\n";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (exists($enc->{'length'})) {
|
|
|
+ $size = $enc->{'length'};
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ # iTunes data, if available (and useful)
|
|
|
+ my $duration = undef();
|
|
|
+ if ($ep->{'http://www.itunes.com/dtds/podcast-1.0.dtd'}) {
|
|
|
+ my $itunes = $ep->{'http://www.itunes.com/dtds/podcast-1.0.dtd'};
|
|
|
+
|
|
|
+ if (exists($itunes->{'duration'})) {
|
|
|
+ if ($itunes->{'duration'} =~ /(\d+)\:(\d\d)\:(\d\d)/) {
|
|
|
+ $duration = (3600 * int($1)) + (60 * int($2)) + int($3);
|
|
|
+ } elsif ($itunes->{'duration'} =~ /(\d+)\:(\d\d)/) {
|
|
|
+ $duration = (60 * int($1)) + int($2);
|
|
|
+ } elsif ($itunes->{'duration'} =~ /(\d+)/) {
|
|
|
+ $duration = int($1);
|
|
|
+ } elsif ($DEBUG) {
|
|
|
+ print STDERR 'Unknown duration format: ' . $itunes->{'duration'} . "\n";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!$desc && exists($itunes->{'summary'})) {
|
|
|
+ $desc = $itunes->{'summary'};
|
|
|
+ $desc =~ s/^\s+//;
|
|
|
+ $desc =~ s/\s+$//;
|
|
|
+ }
|
|
|
+ if (!$author && exists($itunes->{'author'})) {
|
|
|
+ $author = $itunes->{'author'};
|
|
|
+ $author =~ s/^\s+//;
|
|
|
+ $author =~ s/\s+$//;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ # Collect extracted, cleaned data
|
|
|
+ if ($title && $time && $url && $ext && $desc) {
|
|
|
+ my %tmp = (
|
|
|
+ 'title' => $title,
|
|
|
+ 'time' => $time,
|
|
|
+ 'url' => $url,
|
|
|
+ 'ext' => $ext,
|
|
|
+ 'description' => $desc,
|
|
|
+ 'size' => $size,
|
|
|
+ 'duration' => $duration,
|
|
|
+ 'author' => $author,
|
|
|
+ );
|
|
|
+ $episodes{$time} = \%tmp;
|
|
|
+ } elsif ($DEBUG) {
|
|
|
+ print STDERR "Skipping incomplete entry:\n";
|
|
|
+ print STDERR "\tTitle: ";
|
|
|
+ if ($title) {
|
|
|
+ print STDERR $title . "\n";
|
|
|
+ } else {
|
|
|
+ print STDERR "<missing>\n";
|
|
|
+ }
|
|
|
+ print STDERR "\tTime: ";
|
|
|
+ if ($time) {
|
|
|
+ print STDERR time2str('%Y-%m-%d', $time) . "\n";
|
|
|
+ } else {
|
|
|
+ print STDERR "<missing>\n";
|
|
|
+ }
|
|
|
+ print STDERR "\tURL: ";
|
|
|
+ if ($url) {
|
|
|
+ print STDERR $url . "\n";
|
|
|
+ } else {
|
|
|
+ print STDERR "<missing>\n";
|
|
|
+ }
|
|
|
+ print STDERR "\tExtension: ";
|
|
|
+ if ($ext) {
|
|
|
+ print STDERR $ext . "\n";
|
|
|
+ } else {
|
|
|
+ print STDERR "<missing>\n";
|
|
|
+ }
|
|
|
+ print STDERR "\tDescription: ";
|
|
|
+ if ($desc) {
|
|
|
+ print STDERR substr($desc, 0, 20) . "\n";
|
|
|
+ } else {
|
|
|
+ print STDERR "<missing>\n";
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
# Check for existing files
|
|
|
opendir(my $fh, $OUT_DIR)
|
|
|
or die('Unable to open output directory (' . $! . '): ' . $OUT_DIR . "\n");
|
|
|
while (my $file = readdir($fh)) {
|
|
|
- my $path = $OUT_DIR . '/' . $file;
|
|
|
- if (-d $path) {
|
|
|
- next;
|
|
|
- }
|
|
|
- if ($file =~ /^(.*)\s+\((\d{7,})\)\.\w{3}$/) {
|
|
|
- my $title = $1;
|
|
|
- my $time = $2;
|
|
|
- if (exists($episodes{$time})) {
|
|
|
- if (exists($episodes{$time}->{'size'}) && $episodes{$time}->{'size'}) {
|
|
|
- (undef(), undef(), undef(), undef(), undef(), undef(), undef(), my $size) = stat($path);
|
|
|
- my $ratio = ($size / $episodes{$time}->{'size'});
|
|
|
- if ($ratio < (1 - $ERR_RATIO) && $DEBUG) {
|
|
|
- print STDERR 'Invalid output file size (' . $size . '/' . $episodes{$time}->{'size'} . '): ' . $file . "\n";
|
|
|
- }
|
|
|
- }
|
|
|
- $have{$time} = 1;
|
|
|
- $episodes{$time}->{'title'} = $title;
|
|
|
- }
|
|
|
- if ($DEBUG) {
|
|
|
- print STDERR 'Found file: ' . $file . "\n";
|
|
|
- }
|
|
|
- } elsif ($DEBUG > 1) {
|
|
|
- print STDERR 'Skipping file: ' . $file . "\n";
|
|
|
- }
|
|
|
+ my $path = $OUT_DIR . '/' . $file;
|
|
|
+ if (-d $path) {
|
|
|
+ next;
|
|
|
+ }
|
|
|
+ if ($file =~ /^(.*)\s+\((\d{7,})\)\.\w{3}$/) {
|
|
|
+ my $title = $1;
|
|
|
+ my $time = $2;
|
|
|
+ if (exists($episodes{$time})) {
|
|
|
+ if (exists($episodes{$time}->{'size'}) && $episodes{$time}->{'size'}) {
|
|
|
+ (undef(), undef(), undef(), undef(), undef(), undef(), undef(), my $size) = stat($path);
|
|
|
+ my $ratio = ($size / $episodes{$time}->{'size'});
|
|
|
+ if ($ratio < (1 - $ERR_RATIO) && $DEBUG) {
|
|
|
+ print STDERR 'Invalid output file size (' . $size . '/' . $episodes{$time}->{'size'} . '): ' . $file . "\n";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $have{$time} = 1;
|
|
|
+ $episodes{$time}->{'title'} = $title;
|
|
|
+ }
|
|
|
+ if ($DEBUG) {
|
|
|
+ print STDERR 'Found file: ' . $file . "\n";
|
|
|
+ }
|
|
|
+ } elsif ($DEBUG > 1) {
|
|
|
+ print STDERR 'Skipping file: ' . $file . "\n";
|
|
|
+ }
|
|
|
}
|
|
|
closedir($fh);
|
|
|
|
|
|
# Decide what we want to download
|
|
|
foreach my $time (keys(%episodes)) {
|
|
|
- if (!exists($have{$time})) {
|
|
|
- push(@need, $time);
|
|
|
- if ($DEBUG > 1) {
|
|
|
- print STDERR 'Will fetch: ' . time2str('%Y-%m-%d', $time) . "\n";
|
|
|
- }
|
|
|
- }
|
|
|
+ if (!exists($have{$time})) {
|
|
|
+ push(@need, $time);
|
|
|
+ if ($DEBUG > 1) {
|
|
|
+ print STDERR 'Will fetch: ' . time2str('%Y-%m-%d', $time) . "\n";
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
if ($DEBUG) {
|
|
|
- print STDERR 'Need/Total: ' . scalar(@need) . '/' . scalar(keys(%episodes)) . "\n";
|
|
|
+ print STDERR 'Need/Total: ' . scalar(@need) . '/' . scalar(keys(%episodes)) . "\n";
|
|
|
}
|
|
|
|
|
|
# Apply naming rules
|
|
|
if (-e $RULE_FILE) {
|
|
|
- if ($DEBUG > 1) {
|
|
|
- print STDERR 'Importing local rules from: ' . $RULE_FILE . "\n";
|
|
|
- }
|
|
|
- require $RULE_FILE;
|
|
|
- localRules(\%episodes);
|
|
|
+ if ($DEBUG > 1) {
|
|
|
+ print STDERR 'Importing local rules from: ' . $RULE_FILE . "\n";
|
|
|
+ }
|
|
|
+ require $RULE_FILE;
|
|
|
+ localRules(\%episodes);
|
|
|
} else {
|
|
|
- foreach my $time (sort(keys(%episodes))) {
|
|
|
- $episodes{$time}->{'title'} = time2str('%Y-%m-%d', $time) . ' - ' . $episodes{$time}->{'title'};
|
|
|
- }
|
|
|
+ foreach my $time (sort(keys(%episodes))) {
|
|
|
+ $episodes{$time}->{'title'} = time2str('%Y-%m-%d', $time) . ' - ' . $episodes{$time}->{'title'};
|
|
|
+ }
|
|
|
}
|
|
|
foreach my $time (keys(%episodes)) {
|
|
|
- $episodes{$time}->{'title'} =~ s/\s*\:\s*/ - /g;
|
|
|
- $episodes{$time}->{'title'} =~ s/\s+/ /g;
|
|
|
- $episodes{$time}->{'title'} =~ s/\"/\'/g;
|
|
|
- $episodes{$time}->{'title'} =~ s/[^\w\s\,\-\.\!\'\(\)\#\&\@]//g;
|
|
|
+ $episodes{$time}->{'title'} =~ s/\s*\:\s*/ - /g;
|
|
|
+ $episodes{$time}->{'title'} =~ s/\s+/ /g;
|
|
|
+ $episodes{$time}->{'title'} =~ s/\"/\'/g;
|
|
|
+ $episodes{$time}->{'title'} =~ s/[^\w\s\,\-\.\!\'\(\)\#\&\@]//g;
|
|
|
}
|
|
|
|
|
|
# Fetch needed files, with basic validation
|
|
|
my $dlCount = 0;
|
|
|
foreach my $time (sort(@need)) {
|
|
|
- if ($dlCount >= $DL_LIMIT) {
|
|
|
- die('Download limit (' . $DL_LIMIT . ') reached: ' . $SERIES . "\n");
|
|
|
- }
|
|
|
-
|
|
|
- # Build a file path
|
|
|
- my $ep = $episodes{$time};
|
|
|
- my $file = $OUT_DIR . '/' . $ep->{'title'} . ' (' . int($time) . ').' . $ep->{'ext'};
|
|
|
- if (-e $file) {
|
|
|
- die('Unexpected output file: ' . $file . "\n");
|
|
|
- }
|
|
|
-
|
|
|
- # Debug
|
|
|
- if ($DEBUG) {
|
|
|
- print STDERR 'Fetching (' . time2str('%Y-%m-%d', $time) . '): ' . $ep->{'title'} . ' => ' . $ep->{'url'} . "\n";
|
|
|
- }
|
|
|
-
|
|
|
- # Fetch
|
|
|
- my $code = getstore($ep->{'url'}, $file);
|
|
|
- $dlCount++;
|
|
|
-
|
|
|
- # Validate
|
|
|
- my $err = undef();
|
|
|
- if ($code != 200) {
|
|
|
- $err = 'Invalid HTTP response: ' . $code;
|
|
|
- goto OUT;
|
|
|
- }
|
|
|
- if (!-e $file) {
|
|
|
- $err = 'No output file: ' . $file;
|
|
|
- goto OUT;
|
|
|
- }
|
|
|
- if ($ep->{'size'}) {
|
|
|
- (undef(), undef(), undef(), undef(), undef(), undef(), undef(), my $size) = stat($file);
|
|
|
- my $ratio = $size / $ep->{'size'};
|
|
|
- if ($ratio < (1 - $ERR_RATIO)) {
|
|
|
- $err = 'Invalid download size (' . $size . '/' . $ep->{'size'} . '): ' . $file;
|
|
|
- goto OUT;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- # Set MP3/M4A tags
|
|
|
- if ($ep->{'ext'} eq 'mp3') {
|
|
|
-
|
|
|
- # Parse the MP3
|
|
|
- my $mp3 = MP3::Tag->new($file);
|
|
|
- if (!$mp3) {
|
|
|
- $err = 'Unable to parse MP3 tags in: ' . $file . "\n";
|
|
|
- goto OUT;
|
|
|
- }
|
|
|
-
|
|
|
- # Delete any ID3v1 tags
|
|
|
- if (exists($mp3->{'ID3v1'})) {
|
|
|
- print STDERR "Removing ID3v1 tag\n";
|
|
|
- $mp3->{'ID3v1'}->remove_tag();
|
|
|
- print STDERR "Removed?\n";
|
|
|
- }
|
|
|
-
|
|
|
- # Find or create the ID3v2 tag
|
|
|
- my $tags = undef();
|
|
|
- if (exists($mp3->{'ID3v2'})) {
|
|
|
- $tags = $mp3->{'ID3v2'};
|
|
|
- } else {
|
|
|
- $tags = $mp3->new_tag('ID3v2');
|
|
|
- }
|
|
|
-
|
|
|
- # Update tags
|
|
|
- $tags->title($ep->{'title'});
|
|
|
- $tags->year(time2str('%Y', $ep->{'time'}));
|
|
|
- $tags->comment($ep->{'description'});
|
|
|
- $tags->track(1);
|
|
|
- $tags->album($SERIES);
|
|
|
- $tags->artist($SERIES);
|
|
|
- $tags->genre($GENRE);
|
|
|
-
|
|
|
- # Save
|
|
|
- if (!$tags->write_tag()) {
|
|
|
- $err = 'Unable to update MP3 tags in: ' . $file . "\n";
|
|
|
- goto OUT;
|
|
|
- }
|
|
|
- $mp3->close();
|
|
|
- } else {
|
|
|
- print STDERR 'Tag munging not available for ' . $ep->{'ext'} . ' files: ' . $file . "\n";
|
|
|
- }
|
|
|
- OUT:
|
|
|
-
|
|
|
- # Cleanup
|
|
|
- if (defined($err)) {
|
|
|
- if ($DEBUG || ! ($err =~ /\b40[34]$/)) {
|
|
|
- print STDERR 'Unable to download ' . $ep->{'url'} . ': ' . $err . "\n";
|
|
|
- }
|
|
|
- if (-e $file) {
|
|
|
- unlink($file);
|
|
|
- }
|
|
|
- }
|
|
|
+ if ($dlCount >= $DL_LIMIT) {
|
|
|
+ die('Download limit (' . $DL_LIMIT . ') reached: ' . $SERIES . "\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ # Build a file path
|
|
|
+ my $ep = $episodes{$time};
|
|
|
+ my $file = $OUT_DIR . '/' . $ep->{'title'} . ' (' . int($time) . ').' . $ep->{'ext'};
|
|
|
+ if (-e $file) {
|
|
|
+ die('Unexpected output file: ' . $file . "\n");
|
|
|
+ }
|
|
|
+
|
|
|
+ # Debug
|
|
|
+ if ($DEBUG) {
|
|
|
+ print STDERR 'Fetching (' . time2str('%Y-%m-%d', $time) . '): ' . $ep->{'title'} . ' => ' . $ep->{'url'} . "\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ # Fetch
|
|
|
+ my $response = $ua->get($ep->{'url'});
|
|
|
+ my $code = $response->code();
|
|
|
+ $dlCount++;
|
|
|
+
|
|
|
+ # Validate HTTP
|
|
|
+ my $err = undef();
|
|
|
+ if ($code != 200) {
|
|
|
+ $err = 'Invalid HTTP response: ' . $code;
|
|
|
+ goto OUT;
|
|
|
+ }
|
|
|
+
|
|
|
+ # Save
|
|
|
+ {
|
|
|
+ my $fh;
|
|
|
+ if (!open($fh, '>', $file)) {
|
|
|
+ $err = 'Unable to open output file: ' . $file;
|
|
|
+ goto OUT;
|
|
|
+ }
|
|
|
+ print {$fh} $response->decoded_content();
|
|
|
+ close($fh);
|
|
|
+ }
|
|
|
+
|
|
|
+ # Validate file
|
|
|
+ if (!-e $file) {
|
|
|
+ $err = 'No output file: ' . $file;
|
|
|
+ goto OUT;
|
|
|
+ }
|
|
|
+ if ($ep->{'size'}) {
|
|
|
+ (undef(), undef(), undef(), undef(), undef(), undef(), undef(), my $size) = stat($file);
|
|
|
+ my $ratio = $size / $ep->{'size'};
|
|
|
+ if ($ratio < (1 - $ERR_RATIO)) {
|
|
|
+ $err = 'Invalid download size (' . $size . '/' . $ep->{'size'} . '): ' . $file;
|
|
|
+ goto OUT;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ # Set MP3/M4A tags
|
|
|
+ if ($ep->{'ext'} eq 'mp3') {
|
|
|
+
|
|
|
+ # Parse the MP3
|
|
|
+ my $mp3 = MP3::Tag->new($file);
|
|
|
+ if (!$mp3) {
|
|
|
+ $err = 'Unable to parse MP3 tags in: ' . $file . "\n";
|
|
|
+ goto OUT;
|
|
|
+ }
|
|
|
+
|
|
|
+ # Delete any ID3v1 tags
|
|
|
+ if (exists($mp3->{'ID3v1'})) {
|
|
|
+ print STDERR "Removing ID3v1 tag\n";
|
|
|
+ $mp3->{'ID3v1'}->remove_tag();
|
|
|
+ print STDERR "Removed?\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ # Find or create the ID3v2 tag
|
|
|
+ my $tags = undef();
|
|
|
+ if (exists($mp3->{'ID3v2'})) {
|
|
|
+ $tags = $mp3->{'ID3v2'};
|
|
|
+ } else {
|
|
|
+ $tags = $mp3->new_tag('ID3v2');
|
|
|
+ }
|
|
|
+
|
|
|
+ # Update tags
|
|
|
+ $tags->title($ep->{'title'});
|
|
|
+ $tags->year(time2str('%Y', $ep->{'time'}));
|
|
|
+ $tags->comment($ep->{'description'});
|
|
|
+ $tags->track(1);
|
|
|
+ $tags->album($SERIES);
|
|
|
+ $tags->artist($SERIES);
|
|
|
+ $tags->genre($GENRE);
|
|
|
+
|
|
|
+ # Save
|
|
|
+ if (!$tags->write_tag()) {
|
|
|
+ $err = 'Unable to update MP3 tags in: ' . $file . "\n";
|
|
|
+ goto OUT;
|
|
|
+ }
|
|
|
+ $mp3->close();
|
|
|
+ } else {
|
|
|
+ print STDERR 'Tag munging not available for ' . $ep->{'ext'} . ' files: ' . $file . "\n";
|
|
|
+ }
|
|
|
+ OUT:
|
|
|
+
|
|
|
+ # Cleanup
|
|
|
+ if (defined($err)) {
|
|
|
+ if ($DEBUG || !($err =~ /\b40[34]$/)) {
|
|
|
+ print STDERR 'Unable to download ' . $ep->{'url'} . ': ' . $err . "\n";
|
|
|
+ }
|
|
|
+ if (-e $file) {
|
|
|
+ unlink($file);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|