diff --git a/trunk/tvmover.pl b/trunk/tvmover.pl index bd2900b..b969b99 100755 --- a/trunk/tvmover.pl +++ b/trunk/tvmover.pl @@ -6,8 +6,8 @@ use warnings; use Data::Dumper; use Getopt::Long; use File::Basename; +use Log::Handler; use Pod::Usage; -use Sihnon::Common qw/verbose debug/; use Switch; use Sys::Pushd; use File::Copy; @@ -19,9 +19,11 @@ use lib $FindBin::Bin; our %options = ( verbose => 0, quiet => 0, + log_file => '', silent => 0, help => 0, pretend => 0, + interactive => 0, media_dir => '/export/videos/', torrent_dir => '/export/torrents/completed/', tvrenamer => '/usr/bin/tvrenamer', @@ -29,50 +31,103 @@ our %options = ( Getopt::Long::Configure( qw(bundling no_getopt_compat) ); GetOptions( - 'verbose|v+' => \$Sihnon::Common::VERBOSE, - 'debug|d+' => \$Sihnon::Common::DEBUG, + '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}); -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 my %series_dir = read_series_directories($options{media_dir}); -my $dh; -verbose("Reading contents of $options{torrent_dir}"); -opendir $dh, $options{torrent_dir}; -while (my $completed_torrent = readdir $dh) { - next if $completed_torrent =~ m/^\..*/; +# Scan the torrent directory for completed files +process_completed_directory($options{torrent_dir}); - my $completed_filename = $options{torrent_dir} . '/' . $completed_torrent; - next if ! -f $completed_filename; - - verbose("Dealing with $completed_torrent ($completed_filename)"); +# done! - my $output_dir = get_destination_dir_from_filename($completed_torrent); - if ( ! $output_dir) { - warn "Failed to find directory for $completed_torrent"; - next; +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) { + # 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 { @@ -95,6 +150,17 @@ 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; } @@ -102,7 +168,11 @@ sub copy_to_media_share { my $torrent_dir_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}) { return copy $torrent_dir_filename, $output_filename; } else { @@ -116,24 +186,30 @@ sub get_destination_dir_from_filename { my ($series, $season, $episode) = decode_filename($source_filename); return unless $series; - verbose("Series: $series, Season: $season, Episode: $episode"); - - return $options{media_dir} . '/' . $series . '/Season ' . $season; + 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+))/; + $input_filename =~ m/(.*?)[-_\.]*(?:(\d+)x(\d+)|[sS](\d+)[eE](\d+)|P(?:ar)?t(\d+))/; my $series = $1; - my $season = $4; - my $episode = $5; + 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/; + $season =~ s/0+(\d+)/$1/ if $season; return ($series_dir{$canonical_series}, $season, $episode); } @@ -143,9 +219,13 @@ sub replace_with_symlink { my $torrent_dir_filename = shift or die; 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}) { - symlink $media_share_filename, $temp_filename; + symlink $media_share_filename, $temp_filename or return; move $temp_filename, $torrent_dir_filename; } } @@ -154,6 +234,8 @@ 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); @@ -161,11 +243,19 @@ sub rename_media_share_file { # 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; - 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|"; my @tvrenamer_output = <$tvrenamer_fh>; close $tvrenamer_fh; @@ -177,7 +267,7 @@ sub rename_media_share_file { $output_filename = $line; 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; } @@ -186,6 +276,13 @@ sub rename_media_share_file { return $output_filename; } +sub confirm { + my $message = shift or die; + print $message, ": "; + my $response = ; + return $response =~ m/^[yY]$/; +} + __END__; =head1 NAME