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.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.*.swp
|
||||||
40
config.yml
Normal file
40
config.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
options:
|
||||||
|
verbose: 1
|
||||||
|
log_file: /tmp/handbrake-cluster-client.log
|
||||||
|
job_servers:
|
||||||
|
- build0.example.com
|
||||||
|
- build1.example.com
|
||||||
|
report_email: me@example.com
|
||||||
|
|
||||||
|
presets:
|
||||||
|
tvseries:
|
||||||
|
nice: 15
|
||||||
|
format: mkv
|
||||||
|
video_codec: x264
|
||||||
|
video_width: 720
|
||||||
|
video_height: 480
|
||||||
|
quantizer: 0.61
|
||||||
|
deinterlace: 2
|
||||||
|
audio_tracks: 1
|
||||||
|
audio_codec: ac3
|
||||||
|
audio_names: English
|
||||||
|
subtitle_tracks: 1,5
|
||||||
|
disk1:
|
||||||
|
input_filename: /dev/sr1
|
||||||
|
disk2:
|
||||||
|
input_filename: /tmp/dvds/disk2/VIDEO_TS
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- presets:
|
||||||
|
- tvseries
|
||||||
|
- disk1
|
||||||
|
output_filename: /tmp/title1.mkv
|
||||||
|
title: 1
|
||||||
|
|
||||||
|
- presets:
|
||||||
|
- tvseries
|
||||||
|
- disk2
|
||||||
|
output_filename: /tmp/title2.mkv
|
||||||
|
title: -1
|
||||||
|
|
||||||
@@ -7,28 +7,35 @@ use Data::Dumper;
|
|||||||
use Gearman::Client;
|
use Gearman::Client;
|
||||||
use Getopt::Long;
|
use Getopt::Long;
|
||||||
use Log::Handler;
|
use Log::Handler;
|
||||||
|
use MIME::Lite::TT::HTML;
|
||||||
use Pod::Usage;
|
use Pod::Usage;
|
||||||
use Storable qw/freeze thaw/;
|
use Storable qw/freeze thaw/;
|
||||||
|
use YAML::Any;
|
||||||
|
|
||||||
# Handle interrupts, and term signals
|
# Handle interrupts, and term signals
|
||||||
$SIG{'INT'} = 'INT_handler';
|
$SIG{'INT'} = 'INT_handler';
|
||||||
$SIG{'TERM'} = 'TERM_handler';
|
$SIG{'TERM'} = 'TERM_handler';
|
||||||
|
|
||||||
# Globals
|
# Globals
|
||||||
our %options = (
|
our %default_options = (
|
||||||
verbose => 0,
|
verbose => 0,
|
||||||
|
debug => 0,
|
||||||
quiet => 0,
|
quiet => 0,
|
||||||
log_file => '/tmp/handbrake-cluster-client.log',
|
log_file => '/tmp/handbrake-cluster-client.log',
|
||||||
silent => 0,
|
silent => 0,
|
||||||
help => 0,
|
help => 0,
|
||||||
pretend => 0,
|
pretend => 0,
|
||||||
job_servers => ['build0.sihnon.net', 'build1.sihnon.net', 'build2.sihnon.net'],
|
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 = (
|
our %rip_options = (
|
||||||
nice => 15,
|
nice => 15,
|
||||||
input_filename => '/dev/sr0',
|
input_filename => '/dev/sr0',
|
||||||
output_filename => 'rip-output.mkv',
|
output_filename => 'rip-output.mkv',
|
||||||
title => -1,
|
title => 0,
|
||||||
format => 'mkv',
|
format => 'mkv',
|
||||||
video_codec => 'x264',
|
video_codec => 'x264',
|
||||||
video_width => 720, # DVD resolution
|
video_width => 720, # DVD resolution
|
||||||
@@ -51,6 +58,8 @@ GetOptions(
|
|||||||
'help|h' => \$options{help},
|
'help|h' => \$options{help},
|
||||||
'pretend|n' => \$options{pretend},
|
'pretend|n' => \$options{pretend},
|
||||||
'job-servers|j=s@' => \$options{job_servers},
|
'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},
|
'nice|N=i' => \$rip_options{nice},
|
||||||
'input|i=s' => \$rip_options{input_filename},
|
'input|i=s' => \$rip_options{input_filename},
|
||||||
'output|o=s' => \$rip_options{output_filename},
|
'output|o=s' => \$rip_options{output_filename},
|
||||||
@@ -68,6 +77,19 @@ GetOptions(
|
|||||||
) or pod2usage(-verbose => 0);
|
) or pod2usage(-verbose => 0);
|
||||||
pod2usage(-verbose => 1) if ($options{help});
|
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
|
# Setup logging
|
||||||
my $log = Log::Handler->new();
|
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 $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
|
||||||
@@ -96,19 +118,39 @@ if ( $options{log_file}) {
|
|||||||
my $client = Gearman::Client->new;
|
my $client = Gearman::Client->new;
|
||||||
$client->job_servers($options{job_servers});
|
$client->job_servers($options{job_servers});
|
||||||
|
|
||||||
# Add new ripping task
|
# Add new ripping task for each job to run
|
||||||
my $taskset = $client->new_task_set;
|
my $taskset = $client->new_task_set;
|
||||||
$taskset->add_task("rip", freeze(\%rip_options),
|
foreach my $job (@jobs) {
|
||||||
{
|
$taskset->add_task("rip", freeze($job),
|
||||||
on_complete => \&on_complete_handler,
|
{
|
||||||
on_fail => \&on_fail_handler,
|
on_complete => \&on_complete_handler,
|
||||||
}
|
on_fail => \&on_fail_handler,
|
||||||
);
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
$taskset->wait;
|
$taskset->wait;
|
||||||
|
|
||||||
sub on_complete_handler {
|
sub on_complete_handler {
|
||||||
my $result_ref = shift or die;
|
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");
|
$log->notice("Completed rip to $$result_ref");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +158,38 @@ sub on_fail_handler {
|
|||||||
$log->error("Rip failed");
|
$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 {
|
sub INT_handler {
|
||||||
$log->error("Caught interrupt, exiting.");
|
$log->error("Caught interrupt, exiting.");
|
||||||
|
|||||||
@@ -87,13 +87,20 @@ sub do_rip {
|
|||||||
push @deinterlace_options, '-d' if ($rip_options{deinterlace} == 1);
|
push @deinterlace_options, '-d' if ($rip_options{deinterlace} == 1);
|
||||||
push @deinterlace_options, '-5' if ($rip_options{deinterlace} == 2);
|
push @deinterlace_options, '-5' if ($rip_options{deinterlace} == 2);
|
||||||
|
|
||||||
|
my @title_options;
|
||||||
|
if ($rip_options{title} < 0) {
|
||||||
|
push @title_options, '-L';
|
||||||
|
} else {
|
||||||
|
push @title_options, '-t', $rip_options{title};
|
||||||
|
}
|
||||||
|
|
||||||
# Generate the command line for handbrake
|
# Generate the command line for handbrake
|
||||||
my @handbrake_cmd = (
|
my @handbrake_cmd = (
|
||||||
'nice', '-n', $rip_options{nice},
|
'nice', '-n', $rip_options{nice},
|
||||||
$options{handbrake},
|
$options{handbrake},
|
||||||
'-i', $rip_options{input_filename},
|
'-i', $rip_options{input_filename},
|
||||||
'-o', $rip_filename,
|
'-o', $rip_filename,
|
||||||
'-t', $rip_options{title},
|
@title_options.
|
||||||
'-f', $rip_options{format},
|
'-f', $rip_options{format},
|
||||||
'-e', $rip_options{video_codec},
|
'-e', $rip_options{video_codec},
|
||||||
'-q', $rip_options{quantizer},
|
'-q', $rip_options{quantizer},
|
||||||
|
|||||||
1
rip-completed.html
Normal file
1
rip-completed.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
A rip has completed successfully
|
||||||
1
rip-completed.txt
Normal file
1
rip-completed.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
A rip has completed successfully
|
||||||
Reference in New Issue
Block a user