Removing old svn layout
This commit is contained in:
315
tvmover.pl
Executable file
315
tvmover.pl
Executable file
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Data::Dumper;
|
||||
use Getopt::Long;
|
||||
use File::Basename;
|
||||
use Log::Handler;
|
||||
use Pod::Usage;
|
||||
use Switch;
|
||||
use Sys::Pushd;
|
||||
use File::Copy;
|
||||
use FindBin;
|
||||
use lib $FindBin::Bin;
|
||||
|
||||
|
||||
# Globals
|
||||
our %options = (
|
||||
verbose => 0,
|
||||
quiet => 0,
|
||||
log_file => '/tmp/tvmover.log',
|
||||
silent => 0,
|
||||
help => 0,
|
||||
pretend => 0,
|
||||
interactive => 0,
|
||||
media_dir => '/export/videos/',
|
||||
torrent_dir => '/export/torrents/completed/',
|
||||
tvrenamer => '/usr/bin/tvrenamer',
|
||||
);
|
||||
|
||||
Getopt::Long::Configure( qw(bundling no_getopt_compat) );
|
||||
GetOptions(
|
||||
'verbose|v+' => \$options{verbose},
|
||||
'debug|d' => \$options{debug},
|
||||
'quiet|q' => \$options{quiet},
|
||||
'silent|s' => \$options{silent},
|
||||
'log|l=s' => \$options{log_file},
|
||||
'help|h' => \$options{help},
|
||||
'pretend|n' => \$options{pretend},
|
||||
'interactive|i' => \$options{interactive},
|
||||
'media-dir|m=s' => \$options{media_dir},
|
||||
'torrent-dir|t=s' => \$options{torrent_dir},
|
||||
'tvrenamer=s' => \$options{tvrenamer},
|
||||
) or pod2usage(-verbose => 0);
|
||||
pod2usage(-verbose => 1) if ($options{help});
|
||||
|
||||
# Override the series name for calls to tvrenamer for select shows
|
||||
my %series_overrides = (
|
||||
csinewyork => 'CSINY',
|
||||
v => 'V_2009',
|
||||
survivors => 'Survivors_2008',
|
||||
);
|
||||
# Specify additional postproc commands for tvrenamer on a per-series basis
|
||||
my %series_postprocs = (
|
||||
v => 's/V_2009/V/;',
|
||||
survivors => 's/Survivors_2008/Survivors/;'
|
||||
);
|
||||
|
||||
#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
|
||||
my %series_dir = read_series_directories($options{media_dir});
|
||||
|
||||
# Scan the torrent directory for completed files
|
||||
process_completed_directory($options{torrent_dir});
|
||||
|
||||
# done!
|
||||
|
||||
sub process_completed_directory {
|
||||
my $base_directory = shift or die;
|
||||
|
||||
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) {
|
||||
# Skip any "sample" directories
|
||||
next if $completed_torrent =~ m/sample/i;
|
||||
|
||||
# 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
|
||||
if (!copy_to_media_share($completed_filename, $output_filename)) {
|
||||
$log->error("Failed to copy $completed_filename to $output_filename: $!");
|
||||
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 {
|
||||
my $media_share_dir = shift or die;
|
||||
|
||||
my %series_dir = ();
|
||||
my $dh;
|
||||
opendir $dh, $media_share_dir;
|
||||
while (my $file = readdir $dh) {
|
||||
next if $file =~ m/^\..*/;
|
||||
|
||||
my $canonical_file = canonicalise($file);
|
||||
$series_dir{$canonical_file} = $file;
|
||||
}
|
||||
|
||||
return %series_dir;
|
||||
}
|
||||
|
||||
sub canonicalise {
|
||||
my $input = shift or die;
|
||||
$input =~ tr/A-Z/a-z/;
|
||||
$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;
|
||||
}
|
||||
|
||||
sub copy_to_media_share {
|
||||
my $torrent_dir_filename = shift or die;
|
||||
my $output_filename = shift or die;
|
||||
|
||||
$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}) {
|
||||
return copy $torrent_dir_filename, $output_filename;
|
||||
} else {
|
||||
return 1; # Return true if only pretending
|
||||
}
|
||||
}
|
||||
|
||||
sub get_destination_dir_from_filename {
|
||||
my $source_filename = shift or die;
|
||||
|
||||
my ($series, $season, $episode) = decode_filename($source_filename);
|
||||
return unless $series;
|
||||
|
||||
if ($season) {
|
||||
return $options{media_dir} . '/' . $series . '/Season ' . $season;
|
||||
} else {
|
||||
return $options{media_dir} . '/' . $series;
|
||||
}
|
||||
}
|
||||
|
||||
sub decode_filename {
|
||||
my $input_filename = shift or die;
|
||||
|
||||
# grab the series, and season number from the torrented filename
|
||||
$input_filename =~ m/(.*?)[-_\.]*(?:(\d+)x(\d+)|[sS](\d+)[eE](\d+)|P(?:ar)?t(\d+))/;
|
||||
my $series = $1;
|
||||
my $season = $2 || $4;
|
||||
my $episode = $3 || $5 || $6;
|
||||
|
||||
return unless $series;
|
||||
|
||||
$log->debug("Series: $series, Season: $season, Episode: $episode");
|
||||
|
||||
# Get the real series name if it exists
|
||||
my $canonical_series = canonicalise($series) or return;
|
||||
# Sanitise the season and episode numbers
|
||||
$season =~ s/0+(\d+)/$1/ if $season;
|
||||
|
||||
return ($series_dir{$canonical_series}, $season, $episode);
|
||||
}
|
||||
|
||||
sub replace_with_symlink {
|
||||
my $media_share_filename = shift or die;
|
||||
my $torrent_dir_filename = shift or die;
|
||||
|
||||
my $temp_filename = $torrent_dir_filename . '.tmp';
|
||||
$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}) {
|
||||
symlink $media_share_filename, $temp_filename or return;
|
||||
move $temp_filename, $torrent_dir_filename;
|
||||
}
|
||||
}
|
||||
|
||||
sub rename_media_share_file {
|
||||
my $input_filename = shift or die;
|
||||
my $output_filename = $input_filename;
|
||||
|
||||
$log->notice("Attempting to rename $input_filename using tvrenamer");
|
||||
|
||||
return $output_filename if $options{pretend};
|
||||
|
||||
my $input_dir = dirname($input_filename);
|
||||
|
||||
# Find out the season and episode number for the file we are renaming
|
||||
my ($series, $season, $episode) = decode_filename(basename($input_filename));
|
||||
return $output_filename unless $series;
|
||||
my $canonical_series = canonicalise($series);
|
||||
|
||||
# Change into the output directory temporarily, to run the renamer
|
||||
my $changed_dir = new Sys::Pushd $input_dir;
|
||||
|
||||
# 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}'";
|
||||
}
|
||||
|
||||
# Certain shows may provide additional postproc arguments
|
||||
my $additional_postproc = '';
|
||||
if ($series_postprocs{$canonical_series}) {
|
||||
$log->debug("Adding additional postproc for $series: $series_postprocs{$canonical_series}");
|
||||
$additional_postproc = $series_postprocs{$canonical_series};
|
||||
}
|
||||
|
||||
my $tvrenamer_cmd = "$options{tvrenamer} --unattended --noANSI $override_series --rangemin=$episode --rangemax=$episode --postproc='s/-img---a- -a-//;$additional_postproc'";
|
||||
open my $tvrenamer_fh, "$tvrenamer_cmd|";
|
||||
my @tvrenamer_output = <$tvrenamer_fh>;
|
||||
close $tvrenamer_fh;
|
||||
|
||||
# Scan through the output and look for the new filename for the episode with this season and episode number
|
||||
foreach my $line (@tvrenamer_output) {
|
||||
my $identifier = $season . 'x' . $episode;
|
||||
if ($line =~ m/$identifier/) {
|
||||
$output_filename = $line;
|
||||
chomp $output_filename;
|
||||
|
||||
$log->notice("File has been renamed from $input_filename to $output_filename");
|
||||
|
||||
return $input_dir . '/' . $output_filename;
|
||||
}
|
||||
}
|
||||
|
||||
return $output_filename;
|
||||
}
|
||||
|
||||
sub confirm {
|
||||
my $message = shift or die;
|
||||
print $message, ": ";
|
||||
my $response = <STDIN>;
|
||||
return $response =~ m/^[yY]$/;
|
||||
}
|
||||
|
||||
__END__;
|
||||
=head1 NAME
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=cut
|
||||
Reference in New Issue
Block a user