* Copies files into their correct /export/videos directory if it exists * Attempts to rename the file using tvrenamer * Creates a symlink to the moved file, overwriting the existing torrent-dir copy, permitting seeding without duplicating the file across filesystems. May be a few special cases which fail, and failures may cause data loss; needs testing.
199 lines
5.6 KiB
Perl
Executable File
199 lines
5.6 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Data::Dumper;
|
|
use Getopt::Long;
|
|
use File::Basename;
|
|
use Pod::Usage;
|
|
use Sihnon::Common qw/verbose debug/;
|
|
use Switch;
|
|
use Sys::Pushd;
|
|
use File::Copy;
|
|
use FindBin;
|
|
use lib $FindBin::Bin;
|
|
|
|
|
|
# Globals
|
|
our %options = (
|
|
verbose => 0,
|
|
quiet => 0,
|
|
silent => 0,
|
|
help => 0,
|
|
pretend => 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+' => \$Sihnon::Common::VERBOSE,
|
|
'debug|d+' => \$Sihnon::Common::DEBUG,
|
|
'quiet|q' => \$options{quiet},
|
|
'silent|s' => \$options{silent},
|
|
'help|h' => \$options{help},
|
|
'pretend|n' => \$options{pretend},
|
|
'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;
|
|
|
|
# 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/^\..*/;
|
|
|
|
my $completed_filename = $options{torrent_dir} . '/' . $completed_torrent;
|
|
next if ! -f $completed_filename;
|
|
|
|
verbose("Dealing with $completed_torrent ($completed_filename)");
|
|
|
|
my $output_dir = get_destination_dir_from_filename($completed_torrent);
|
|
if ( ! $output_dir) {
|
|
warn "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);
|
|
}
|
|
|
|
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;
|
|
return $input;
|
|
}
|
|
|
|
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");
|
|
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;
|
|
|
|
verbose("Series: $series, Season: $season, Episode: $episode");
|
|
|
|
return $options{media_dir} . '/' . $series . '/Season ' . $season;
|
|
}
|
|
|
|
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+))/;
|
|
my $series = $1;
|
|
my $season = $4;
|
|
my $episode = $5;
|
|
|
|
# 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/;
|
|
|
|
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';
|
|
verbose("Replacing $torrent_dir_filename with symlink to $media_share_filename");
|
|
if ( ! $options{pretend}) {
|
|
symlink $media_share_filename, $temp_filename;
|
|
move $temp_filename, $torrent_dir_filename;
|
|
}
|
|
}
|
|
|
|
sub rename_media_share_file {
|
|
my $input_filename = shift or die;
|
|
my $output_filename = $input_filename;
|
|
|
|
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;
|
|
|
|
# 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";
|
|
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;
|
|
|
|
verbose("File has been renamed from $input_filename to $output_filename");
|
|
|
|
return $input_dir . '/' . $output_filename;
|
|
}
|
|
}
|
|
|
|
return $output_filename;
|
|
}
|
|
|
|
__END__;
|
|
=head1 NAME
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
=head1 OPTIONS
|
|
|
|
=cut
|