Using anonymous subs for the simple callbacks instead of named subs Using reference rather than direct hash access to construct the response
242 lines
7.3 KiB
Perl
Executable File
242 lines
7.3 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
use warnings;
|
|
use strict;
|
|
|
|
use Data::Dumper;
|
|
use Gearman::Worker;
|
|
use Getopt::Long;
|
|
use IPC::Open3;
|
|
use Log::Handler;
|
|
use Pod::Usage;
|
|
use String::Random qw/random_string/;
|
|
use Storable qw/freeze thaw/;
|
|
use Switch;
|
|
|
|
# Globals
|
|
our %options = (
|
|
verbose => 0,
|
|
quiet => 0,
|
|
log_file => '/tmp/handbrake-cluster-worker.log',
|
|
silent => 0,
|
|
help => 0,
|
|
pretend => 0,
|
|
job_servers => ['build0.sihnon.net', 'build1.sihnon.net', 'build2.sihnon.net'],
|
|
handbrake => '/usr/bin/HandBrakeCLI',
|
|
);
|
|
|
|
Getopt::Long::Configure( qw(bundling no_getopt_compat) );
|
|
GetOptions(
|
|
'help|h' => \$options{help},
|
|
'verbose|v' => \$options{verbose},
|
|
'debug|d' => \$options{debug},
|
|
'quiet|q' => \$options{quiet},
|
|
'silent|s' => \$options{silent},
|
|
'log|l=s' => \$options{log_file},
|
|
'pretend|n' => \$options{pretend},
|
|
'job-servers|j=s@' => \$options{job_servers},
|
|
'handbrake' => \$options{handbrake},
|
|
) or pod2usage(-verbose => 0);
|
|
pod2usage(-verbose => 1) if ($options{help});
|
|
|
|
# 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,
|
|
},
|
|
);
|
|
}
|
|
|
|
# Setup the worker, and listen for jobs
|
|
my $worker = Gearman::Worker->new(job_servers => $options{job_servers});
|
|
$worker->register_function(handbrake_rip => \&handbrake_rip);
|
|
$worker->work while 1;
|
|
|
|
sub handbrake_rip {
|
|
my $job = shift;
|
|
my $rip_options = thaw($job->arg);
|
|
|
|
my $response = {};
|
|
|
|
$log->notice("Beginning new rip to $rip_options->{output_filename}");
|
|
|
|
# Generate a unique filename based on the output filename to prevent clashes from previous runs
|
|
my $uuid = random_string('cccccc');
|
|
$rip_options->{real_output_filename} = $rip_options->{output_filename};
|
|
$rip_options->{real_output_filename} =~ s/\.([^\.]+)$/\.$uuid\.$1/;
|
|
$response->{real_output_filename} = $rip_options->{real_output_filename};
|
|
|
|
# Generate the command line for handbrake
|
|
my @handbrake_cmd = (
|
|
# Reduce the priority of the ripping process, since it is very processor intensive
|
|
'nice', '-n', $rip_options->{nice},
|
|
|
|
# Construct the handbrake command line
|
|
$options{handbrake},
|
|
get_options($rip_options, 'input_filename', '-i'),
|
|
get_options($rip_options, 'real_output_filename', '-o'),
|
|
get_options($rip_options, 'title'),
|
|
get_options($rip_options, 'format', '-f'),
|
|
get_options($rip_options, 'video_codec', '-e'),
|
|
get_options($rip_options, 'quantizer', '-q'),
|
|
get_options($rip_options, 'video_width', '-w'),
|
|
get_options($rip_options, 'video_height', '-l'),
|
|
get_options($rip_options, 'deinterlace'),
|
|
get_options($rip_options, 'audio_tracks', '-a'),
|
|
get_options($rip_options, 'audio_codec', '-E'),
|
|
get_options($rip_options, 'audio_names', '-A'),
|
|
get_options($rip_options, 'subtitle_tracks', '-s'),
|
|
);
|
|
|
|
# Return a copy of the command used to rip the title
|
|
$response->{handbrake_cmd} = @handbrake_cmd;
|
|
|
|
# flag the start of the job
|
|
$job->set_status(0, 100);
|
|
|
|
# Execute the ripping process
|
|
$log->debug("Beginning ripping process with command:\n" . join(' ', @handbrake_cmd));
|
|
my ($child_in, $child_out, $child_err);
|
|
my $child_pid = open3($child_in, $child_out, $child_err, @handbrake_cmd);
|
|
# No need to write to the child process
|
|
close($child_in);
|
|
|
|
# Log the output from handbrake, and return it back to the client
|
|
$response->{log} = ();
|
|
my $line;
|
|
while ($line = <$child_out>) {
|
|
# If the line is a progress report, record the current status
|
|
# otherwise, log the line
|
|
|
|
# Encoding: task 1 of 1, 0.87 % (34.71 fps, avg 62.95 fps, ETA 00h07m56s)
|
|
if ($line =~ m/Encoding: task \d+ of \d+, (\d+\.\d+) %/) {
|
|
my $numerator = $1 * 100;
|
|
$job->set_status($numerator, 100);
|
|
$log->notice("Task is $numerator% complete");
|
|
} else {
|
|
push @{ $response->{log} }, $line;
|
|
$log->notice($line);
|
|
}
|
|
}
|
|
close($child_out);
|
|
|
|
$job->set_status(100, 100);
|
|
|
|
# If the rip process failed, report an error status here
|
|
waitpid($child_pid, 0);
|
|
$response->{status} = $? >> 8;
|
|
$response->{success} = $response->{status} == 0;
|
|
if ($response->{success}) {
|
|
$log->notice("Finished rip to $response->{real_output_filename}");
|
|
} else {
|
|
$log->warning("Ripping process returned error status: $response->{status}");
|
|
}
|
|
|
|
return freeze($response);
|
|
}
|
|
|
|
sub get_options {
|
|
my $rip_options = shift or die;
|
|
my $option_name = shift or die;
|
|
my $option = shift;
|
|
|
|
switch ($option_name) {
|
|
case 'title' {
|
|
return ('-L') if ! defined($rip_options->{$option_name}) || $rip_options->{$option_name} < 0;
|
|
return ('-t', $rip_options->{$option_name});
|
|
}
|
|
|
|
case 'deinterlace' {
|
|
switch ($rip_options->{$option_name}) {
|
|
case 1 { return ('-d') }
|
|
case 2 { return ('-5') }
|
|
return ();
|
|
}
|
|
}
|
|
|
|
return (defined $rip_options->{$option_name} ? ($option, $rip_options->{$option_name}) : ());
|
|
}
|
|
}
|
|
|
|
__END__;
|
|
=head1 NAME
|
|
|
|
handbrake-cluster-worker - Processes DVD rips farmed out by gearman
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
handbrake-cluster-worker.pl -h|--help
|
|
handbrake-cluster-worker.pl [[-v|--verbose] | [-d|--debug] |
|
|
[-q|--quiet] | [-s|--silent]]
|
|
[-l|--log <log file>]
|
|
[-n|--pretend]
|
|
[-j|--job-servers <server list>]
|
|
[--handbrake <handbrake executable>]
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
Processes ripping tasks as requested by a gearman job server. Logging and the
|
|
job servers to use are configurable by command line arguments.
|
|
|
|
=head1 OPTIONS
|
|
|
|
=over 4
|
|
|
|
=item B<-h|--help>
|
|
|
|
Displays this help information and quits.
|
|
|
|
=item B<-v|--verbose>
|
|
|
|
Displays verbose logging information.
|
|
|
|
=item B<-d|--debug>
|
|
|
|
Displays full debugging information, including all output from HandBrake.
|
|
|
|
=item B<-q|--quiet>
|
|
|
|
Displays only errors.
|
|
|
|
=item B<-s|--silent>
|
|
|
|
Displays no output whatsoever, useful for scripting. Output will still be
|
|
logged to disk if a file is specified.
|
|
|
|
=item B<-l|--log E<lt>log fileE<gt>>
|
|
|
|
Specifies the name of a file to write logging information to.
|
|
|
|
=item B<-n|--pretend>
|
|
|
|
Only shows what action would be taken, no rips are actually performed.
|
|
|
|
=item B<-j|--job-servers E<lt>server listE<gt>>
|
|
|
|
Specifies a comma separated list of alternate servers to use for job
|
|
distribution.
|
|
|
|
=item B<--handbrake E<lt>handbrake executableE<gt>>
|
|
|
|
Specifies an alternate HandBrake executable to use. Default is
|
|
C</usr/bin/HandBrakeCLI>.
|
|
|
|
=cut
|