Added better log handling
Added support for moving files in subdirectories of the torrent-dir Added support for altering the --series option for the call to tvrenamer Added filters to ignore trailing country codes, or year numbers from series names Added support for tv shows without conventional season numbers, and episode numbers using Pt# or Part#
This commit is contained in:
177
trunk/tvmover.pl
177
trunk/tvmover.pl
@@ -6,8 +6,8 @@ use warnings;
|
|||||||
use Data::Dumper;
|
use Data::Dumper;
|
||||||
use Getopt::Long;
|
use Getopt::Long;
|
||||||
use File::Basename;
|
use File::Basename;
|
||||||
|
use Log::Handler;
|
||||||
use Pod::Usage;
|
use Pod::Usage;
|
||||||
use Sihnon::Common qw/verbose debug/;
|
|
||||||
use Switch;
|
use Switch;
|
||||||
use Sys::Pushd;
|
use Sys::Pushd;
|
||||||
use File::Copy;
|
use File::Copy;
|
||||||
@@ -19,9 +19,11 @@ use lib $FindBin::Bin;
|
|||||||
our %options = (
|
our %options = (
|
||||||
verbose => 0,
|
verbose => 0,
|
||||||
quiet => 0,
|
quiet => 0,
|
||||||
|
log_file => '',
|
||||||
silent => 0,
|
silent => 0,
|
||||||
help => 0,
|
help => 0,
|
||||||
pretend => 0,
|
pretend => 0,
|
||||||
|
interactive => 0,
|
||||||
media_dir => '/export/videos/',
|
media_dir => '/export/videos/',
|
||||||
torrent_dir => '/export/torrents/completed/',
|
torrent_dir => '/export/torrents/completed/',
|
||||||
tvrenamer => '/usr/bin/tvrenamer',
|
tvrenamer => '/usr/bin/tvrenamer',
|
||||||
@@ -29,50 +31,103 @@ our %options = (
|
|||||||
|
|
||||||
Getopt::Long::Configure( qw(bundling no_getopt_compat) );
|
Getopt::Long::Configure( qw(bundling no_getopt_compat) );
|
||||||
GetOptions(
|
GetOptions(
|
||||||
'verbose|v+' => \$Sihnon::Common::VERBOSE,
|
'verbose|v+' => \$options{verbose},
|
||||||
'debug|d+' => \$Sihnon::Common::DEBUG,
|
'debug|d' => \$options{debug},
|
||||||
'quiet|q' => \$options{quiet},
|
'quiet|q' => \$options{quiet},
|
||||||
'silent|s' => \$options{silent},
|
'silent|s' => \$options{silent},
|
||||||
|
'log|l=s' => \$options{log_file},
|
||||||
'help|h' => \$options{help},
|
'help|h' => \$options{help},
|
||||||
'pretend|n' => \$options{pretend},
|
'pretend|n' => \$options{pretend},
|
||||||
|
'interactive|i' => \$options{interactive},
|
||||||
'media-dir|m=s' => \$options{media_dir},
|
'media-dir|m=s' => \$options{media_dir},
|
||||||
'torrent-dir|t=s' => \$options{torrent_dir},
|
'torrent-dir|t=s' => \$options{torrent_dir},
|
||||||
'tvrenamer=s' => \$options{tvrenamer},
|
'tvrenamer=s' => \$options{tvrenamer},
|
||||||
) or pod2usage(-verbose => 0);
|
) or pod2usage(-verbose => 0);
|
||||||
pod2usage(-verbose => 1) if ($options{help});
|
pod2usage(-verbose => 1) if ($options{help});
|
||||||
|
|
||||||
my @args = @ARGV;
|
# Override the series name for calls to tvrenamer for select shows
|
||||||
|
my %series_overrides = (
|
||||||
|
csinewyork => 'CSINY',
|
||||||
|
);
|
||||||
|
|
||||||
|
#setup logging
|
||||||
|
my $log = Log::Handler->new();
|
||||||
|
my $maxLogLevel = ($options{debug} ? 'debug' : ($options{quiet} ? 3 : $options{verbose} + 4)); # default to logging warning+, unless quiet in which case log error+, or debug in which case log everything
|
||||||
|
my $maxFileLogLevel = ($options{debug} ? 'debug' : 'info');
|
||||||
|
|
||||||
|
if ( ! $options{silent}) {
|
||||||
|
$log->add(
|
||||||
|
screen => {
|
||||||
|
log_to => 'STDOUT',
|
||||||
|
minlevel => 'emergency',
|
||||||
|
maxlevel => $maxLogLevel,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( $options{log_file}) {
|
||||||
|
$log->add(
|
||||||
|
file => {
|
||||||
|
filename => $options{log_file},
|
||||||
|
minlevel => 'emergency',
|
||||||
|
maxlevel => $maxFileLogLevel,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
# Get a list of tv series directories, and their normalised names
|
# Get a list of tv series directories, and their normalised names
|
||||||
my %series_dir = read_series_directories($options{media_dir});
|
my %series_dir = read_series_directories($options{media_dir});
|
||||||
|
|
||||||
my $dh;
|
# Scan the torrent directory for completed files
|
||||||
verbose("Reading contents of $options{torrent_dir}");
|
process_completed_directory($options{torrent_dir});
|
||||||
opendir $dh, $options{torrent_dir};
|
|
||||||
while (my $completed_torrent = readdir $dh) {
|
|
||||||
next if $completed_torrent =~ m/^\..*/;
|
|
||||||
|
|
||||||
my $completed_filename = $options{torrent_dir} . '/' . $completed_torrent;
|
# done!
|
||||||
next if ! -f $completed_filename;
|
|
||||||
|
|
||||||
verbose("Dealing with $completed_torrent ($completed_filename)");
|
|
||||||
|
|
||||||
my $output_dir = get_destination_dir_from_filename($completed_torrent);
|
sub process_completed_directory {
|
||||||
if ( ! $output_dir) {
|
my $base_directory = shift or die;
|
||||||
warn "Failed to find directory for $completed_torrent";
|
|
||||||
next;
|
my $dh;
|
||||||
|
$log->notice("Reading contents of $base_directory");
|
||||||
|
opendir $dh, $base_directory;
|
||||||
|
|
||||||
|
while (my $completed_torrent = readdir $dh) {
|
||||||
|
next if $completed_torrent =~ m/^\..*/;
|
||||||
|
|
||||||
|
my $completed_filename = $base_directory . '/' . $completed_torrent;
|
||||||
|
next if -l $completed_filename; # ignore symlinks
|
||||||
|
|
||||||
|
# If the entry is a directory and is named after an episode, attempt to scan it
|
||||||
|
if ( -d $completed_filename) {
|
||||||
|
# Attempt to grab the series name from the dir name
|
||||||
|
my ($series) = decode_filename($completed_torrent);
|
||||||
|
next unless $series;
|
||||||
|
|
||||||
|
$log->info("Moving into subdirectory $completed_filename");
|
||||||
|
|
||||||
|
process_completed_directory($completed_filename);
|
||||||
|
} else {
|
||||||
|
$log->notice("Dealing with $completed_torrent ($completed_filename)");
|
||||||
|
|
||||||
|
# Only move video files
|
||||||
|
next unless ($completed_torrent =~ m/\.(avi|mkv)$/i);
|
||||||
|
|
||||||
|
my $output_dir = get_destination_dir_from_filename($completed_torrent);
|
||||||
|
if ( ! $output_dir) {
|
||||||
|
$log->warning("Failed to find directory for $completed_torrent");
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
my $output_filename = $output_dir . '/' . $completed_torrent;
|
||||||
|
|
||||||
|
# Copy the file over to the media share
|
||||||
|
copy_to_media_share($completed_filename, $output_filename) or next;
|
||||||
|
|
||||||
|
# Attempt to rename the file
|
||||||
|
my $final_filename = $output_filename;
|
||||||
|
$final_filename = rename_media_share_file($output_filename);
|
||||||
|
|
||||||
|
# Symlink the file back into the torrent directory for seeding
|
||||||
|
replace_with_symlink($final_filename, $completed_filename);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
my $output_filename = $output_dir . '/' . $completed_torrent;
|
|
||||||
|
|
||||||
# Copy the file over to the media share
|
|
||||||
copy_to_media_share($completed_filename, $output_filename) or next;
|
|
||||||
|
|
||||||
# Attempt to rename the file
|
|
||||||
my $final_filename = $output_filename;
|
|
||||||
$final_filename = rename_media_share_file($output_filename);
|
|
||||||
|
|
||||||
# Symlink the file back into the torrent directory for seeding
|
|
||||||
replace_with_symlink($final_filename, $completed_filename);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub read_series_directories {
|
sub read_series_directories {
|
||||||
@@ -95,6 +150,17 @@ sub canonicalise {
|
|||||||
my $input = shift or die;
|
my $input = shift or die;
|
||||||
$input =~ tr/A-Z/a-z/;
|
$input =~ tr/A-Z/a-z/;
|
||||||
$input =~ s/[^a-z0-9]//g;
|
$input =~ s/[^a-z0-9]//g;
|
||||||
|
|
||||||
|
# Strip out trailing year numbers
|
||||||
|
$input =~ s/(19|20)\d\d$//;
|
||||||
|
# Strip out trailing country codes
|
||||||
|
$input =~ s/(us|uk)$//;
|
||||||
|
|
||||||
|
# Handle a couple of series specific haxes
|
||||||
|
$input =~ s/csiny/csinewyork/;
|
||||||
|
$input =~ s/ncisla/ncislosangeles/;
|
||||||
|
$input =~ s/theclevelandshow/clevelandshow/;
|
||||||
|
|
||||||
return $input;
|
return $input;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +168,11 @@ sub copy_to_media_share {
|
|||||||
my $torrent_dir_filename = shift or die;
|
my $torrent_dir_filename = shift or die;
|
||||||
my $output_filename = shift or die;
|
my $output_filename = shift or die;
|
||||||
|
|
||||||
verbose("Copying $torrent_dir_filename to $output_filename");
|
$log->info("Copying $torrent_dir_filename to $output_filename");
|
||||||
|
if ($options{interactive}) {
|
||||||
|
return unless confirm("Copy $torrent_dir_filename to $output_filename? [y/N]");
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! $options{pretend}) {
|
if ( ! $options{pretend}) {
|
||||||
return copy $torrent_dir_filename, $output_filename;
|
return copy $torrent_dir_filename, $output_filename;
|
||||||
} else {
|
} else {
|
||||||
@@ -116,24 +186,30 @@ sub get_destination_dir_from_filename {
|
|||||||
my ($series, $season, $episode) = decode_filename($source_filename);
|
my ($series, $season, $episode) = decode_filename($source_filename);
|
||||||
return unless $series;
|
return unless $series;
|
||||||
|
|
||||||
verbose("Series: $series, Season: $season, Episode: $episode");
|
if ($season) {
|
||||||
|
return $options{media_dir} . '/' . $series . '/Season ' . $season;
|
||||||
return $options{media_dir} . '/' . $series . '/Season ' . $season;
|
} else {
|
||||||
|
return $options{media_dir} . '/' . $series;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sub decode_filename {
|
sub decode_filename {
|
||||||
my $input_filename = shift or die;
|
my $input_filename = shift or die;
|
||||||
|
|
||||||
# grab the series, and season number from the torrented filename
|
# grab the series, and season number from the torrented filename
|
||||||
$input_filename =~ m/(.*)[-_\.]*(?:(\d+)x(\d+)|[sS](\d+)[eE](\d+))/;
|
$input_filename =~ m/(.*?)[-_\.]*(?:(\d+)x(\d+)|[sS](\d+)[eE](\d+)|P(?:ar)?t(\d+))/;
|
||||||
my $series = $1;
|
my $series = $1;
|
||||||
my $season = $4;
|
my $season = $2 || $4;
|
||||||
my $episode = $5;
|
my $episode = $3 || $5 || $6;
|
||||||
|
|
||||||
|
return unless $series;
|
||||||
|
|
||||||
|
$log->debug("Series: $series, Season: $season, Episode: $episode");
|
||||||
|
|
||||||
# Get the real series name if it exists
|
# Get the real series name if it exists
|
||||||
my $canonical_series = canonicalise($series) or return;
|
my $canonical_series = canonicalise($series) or return;
|
||||||
# Sanitise the season and episode numbers
|
# Sanitise the season and episode numbers
|
||||||
$season =~ s/0*(\d+)/$1/;
|
$season =~ s/0+(\d+)/$1/ if $season;
|
||||||
|
|
||||||
return ($series_dir{$canonical_series}, $season, $episode);
|
return ($series_dir{$canonical_series}, $season, $episode);
|
||||||
}
|
}
|
||||||
@@ -143,9 +219,13 @@ sub replace_with_symlink {
|
|||||||
my $torrent_dir_filename = shift or die;
|
my $torrent_dir_filename = shift or die;
|
||||||
|
|
||||||
my $temp_filename = $torrent_dir_filename . '.tmp';
|
my $temp_filename = $torrent_dir_filename . '.tmp';
|
||||||
verbose("Replacing $torrent_dir_filename with symlink to $media_share_filename");
|
$log->notice("Replacing $torrent_dir_filename with symlink to $media_share_filename");
|
||||||
|
if ($options{interactive}) {
|
||||||
|
return unless confirm("Replace $torrent_dir_filename with symlink to $media_share_filename? [y/N]");
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! $options{pretend}) {
|
if ( ! $options{pretend}) {
|
||||||
symlink $media_share_filename, $temp_filename;
|
symlink $media_share_filename, $temp_filename or return;
|
||||||
move $temp_filename, $torrent_dir_filename;
|
move $temp_filename, $torrent_dir_filename;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,6 +234,8 @@ sub rename_media_share_file {
|
|||||||
my $input_filename = shift or die;
|
my $input_filename = shift or die;
|
||||||
my $output_filename = $input_filename;
|
my $output_filename = $input_filename;
|
||||||
|
|
||||||
|
$log->notice("Attempting to rename $input_filename using tvrenamer");
|
||||||
|
|
||||||
return $output_filename if $options{pretend};
|
return $output_filename if $options{pretend};
|
||||||
|
|
||||||
my $input_dir = dirname($input_filename);
|
my $input_dir = dirname($input_filename);
|
||||||
@@ -161,11 +243,19 @@ sub rename_media_share_file {
|
|||||||
# Find out the season and episode number for the file we are renaming
|
# Find out the season and episode number for the file we are renaming
|
||||||
my ($series, $season, $episode) = decode_filename(basename($input_filename));
|
my ($series, $season, $episode) = decode_filename(basename($input_filename));
|
||||||
return $output_filename unless $series;
|
return $output_filename unless $series;
|
||||||
|
my $canonical_series = canonicalise($series);
|
||||||
|
|
||||||
# Change into the output directory temporarily, to run the renamer
|
# Change into the output directory temporarily, to run the renamer
|
||||||
my $changed_dir = new Sys::Pushd $input_dir;
|
my $changed_dir = new Sys::Pushd $input_dir;
|
||||||
|
|
||||||
my $tvrenamer_cmd = "$options{tvrenamer} --unattended --noANSI --rangemin=$episode --rangemax=$episode";
|
# Certain shows require a custom call to tvrenamer in order to work, eg CSI:NY
|
||||||
|
my $override_series = '';
|
||||||
|
if ($series_overrides{$canonical_series}) {
|
||||||
|
$log->debug("Overriding series name for $series with $series_overrides{$canonical_series}");
|
||||||
|
$override_series = "--series='$series_overrides{$canonical_series}'";
|
||||||
|
}
|
||||||
|
|
||||||
|
my $tvrenamer_cmd = "$options{tvrenamer} --unattended --noANSI $override_series --rangemin=$episode --rangemax=$episode --postproc='s/-img---a- -a-//;'";
|
||||||
open my $tvrenamer_fh, "$tvrenamer_cmd|";
|
open my $tvrenamer_fh, "$tvrenamer_cmd|";
|
||||||
my @tvrenamer_output = <$tvrenamer_fh>;
|
my @tvrenamer_output = <$tvrenamer_fh>;
|
||||||
close $tvrenamer_fh;
|
close $tvrenamer_fh;
|
||||||
@@ -177,7 +267,7 @@ sub rename_media_share_file {
|
|||||||
$output_filename = $line;
|
$output_filename = $line;
|
||||||
chomp $output_filename;
|
chomp $output_filename;
|
||||||
|
|
||||||
verbose("File has been renamed from $input_filename to $output_filename");
|
$log->notice("File has been renamed from $input_filename to $output_filename");
|
||||||
|
|
||||||
return $input_dir . '/' . $output_filename;
|
return $input_dir . '/' . $output_filename;
|
||||||
}
|
}
|
||||||
@@ -186,6 +276,13 @@ sub rename_media_share_file {
|
|||||||
return $output_filename;
|
return $output_filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub confirm {
|
||||||
|
my $message = shift or die;
|
||||||
|
print $message, ": ";
|
||||||
|
my $response = <STDIN>;
|
||||||
|
return $response =~ m/^[yY]$/;
|
||||||
|
}
|
||||||
|
|
||||||
__END__;
|
__END__;
|
||||||
=head1 NAME
|
=head1 NAME
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user