Files
handbrake-cluster/handbrake-cluster-client.pl
Ben Roberts fd0e1f4777 Report handbrake failure to client
Added check on return status of handbrake process, and reports failure
to cluster client.
Fixed a bug whereby handbrake processes were not reaped after the rip
completes
2010-02-17 01:41:46 +00:00

227 lines
6.7 KiB
Perl
Executable File

#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
use Gearman::Client;
use Getopt::Long;
use Log::Handler;
use MIME::Lite::TT::HTML;
use Pod::Usage;
use Storable qw/freeze thaw/;
use YAML::Any;
# Handle interrupts, and term signals
$SIG{'INT'} = 'INT_handler';
$SIG{'TERM'} = 'TERM_handler';
# Globals
our %default_options = (
verbose => 0,
debug => 0,
quiet => 0,
log_file => '/tmp/handbrake-cluster-client.log',
silent => 0,
help => 0,
pretend => 0,
job_servers => ['build0.sihnon.net', 'build1.sihnon.net', 'build2.sihnon.net'],
report_email => '',
config_file => '',
);
our %options = map { $_ => undef } keys %default_options;
our %rip_options = (
nice => 15,
input_filename => '/dev/sr0',
output_filename => 'rip-output.mkv',
title => 0,
format => 'mkv',
video_codec => 'x264',
video_width => 720, # DVD resolution
video_height => 576, # DVD resolution
quantizer => 0.61,# x264 quantizer = 20
deinterlace => 0, # 0 = off, 1 = on, 2 = selective
audio_tracks => 1,
audio_codec => 'ac3',
audio_names => 'English',
subtitle_tracks => 1,
);
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},
'job-servers|j=s@' => \$options{job_servers},
'report|r=s' => \$options{report_email},
'config|c=s' => \$options{config_file},
'nice|N=i' => \$rip_options{nice},
'input|i=s' => \$rip_options{input_filename},
'output|o=s' => \$rip_options{output_filename},
'title|t=s' => \$rip_options{title},
'format|f=s' => \$rip_options{format},
'video-encoder|e=s' => \$rip_options{video_codec},
'width|w=i' => \$rip_options{video_width},
'height|l=i' => \$rip_options{video_height},
'quantizer|Q=f' => \$rip_options{quantizer},
'deinterlate|D=i' => \$rip_options{deinterlace},
'audio-tracks|a=s' => \$rip_options{audio_tracks},
'audio-encoder|E=s' => \$rip_options{audio_codec},
'audio-name|A=s' => \$rip_options{audio_names},
'subtitle-tracks|S=s' => \$rip_options{subtitle_tracks},
) or pod2usage(-verbose => 0);
pod2usage(-verbose => 1) if ($options{help});
my @jobs;
my $config;
# Read a configuration file, if provided
if ($options{config_file}) {
$config = parse_config($options{config_file});
push @jobs, @{ $config->{jobs} };
}
# A list of jobs to run, formed from command line options or config file
if ($rip_options{title}) {
push @jobs, \%rip_options;
}
# 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 distribution client
my $client = Gearman::Client->new;
$client->job_servers($options{job_servers});
# Add new ripping task for each job to run
my $taskset = $client->new_task_set;
foreach my $job (@jobs) {
$taskset->add_task("rip", freeze($job),
{
on_complete => \&on_complete_handler,
on_fail => \&on_fail_handler,
}
);
}
$taskset->wait;
sub on_complete_handler {
my $result_ref = shift or die;
return on_fail_handler() unless defined $$result_ref;
if ($options{report_email}) {
my $email = MIME::Lite::TT::HTML->new(
From => $options{report_email},,
To => $options{report_email},
Subject => 'Rip completed',
TimeZone => 'Europe/London',
Encoding => 'quoted-printable',
Template => {
html => 'rip-completed.html',
text => 'rip-completed.txt',
},
Charset => 'utf8',
TmplOptions => {},
TmplParams => {},
);
$email->send;
}
$log->notice("Completed rip to $$result_ref");
}
sub on_fail_handler {
$log->error("Rip failed");
}
sub parse_config {
my $config_file = shift or die;
my $config = YAML::Any::LoadFile($options{config_file}) or die 'Unable to load configuration file: ' . $!;
# Update any unset options with values from config file
foreach my $option (keys %options) {
# Update the value of any option which has not been set by a command line argument
if (!$options{$option}) {
# Try the config file first, otherwise use the default value
if (defined $config->{options}->{$option}) {
$options{$option} = $config->{options}->{$option};
} else {
$options{$option} = $default_options{$option};
}
}
}
# Iterate through each job, and inject any preset variables that haven't been redefined by the job
foreach my $job (@{ $config->{jobs} }) {
if ($job->{presets}) {
foreach my $preset_name (@{ $job->{presets} }) {
foreach my $preset_key (keys %{$config->{presets}->{$preset_name}}) {
$job->{$preset_key} = $config->{presets}->{$preset_name}->{$preset_key} unless $job->{$preset_key};
}
}
}
}
return $config;
}
sub INT_handler {
$log->error("Caught interrupt, exiting.");
exit 0;
}
sub TERM_handler {
$log->error("Caught SIGTERM, exiting.");
exit 0;
}
__END__;
=head1 NAME
handbrake-cluster-client - Sets up DVD ripping tasks to be distributed
=head1 SYNOPSIS
handbrake-cluster-client.pl -h
handbrake-cluster-client.pl [-v [-d]|-q|-s]
[-l logfile]
[-n]
=head1 DESCRIPTION
Sets up one or more DVD ripping tasks, and hands them off to gearman for
distribution. Logging and ripping configuration can be controlled with
command line arguments.
=head1 OPTIONS
=cut