Files
handbrake-cluster/handbrake-cluster-client.pl
Ben Roberts 3ea5aa5706 Batch processing and config file support
Added a config file to describe multiple jobs to run, and to provide
values for options not specified on command line.
In the config file, presets can be used to save duplication of rip
options common to multiple jobs.
2010-02-17 01:13:58 +00:00

226 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;
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} }) {
print "Copying values for preset $preset_name into job\n";
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