#!/usr/bin/perl use warnings; use strict; use Data::Dumper; use Gearman::Worker; use Getopt::Long; use IPC::Open2; use Log::Handler; use Pod::Usage; use String::Random qw/random_string/; use Storable qw/thaw/; use Switch; # Handle interrupts, and term signals $SIG{'INT'} = 'INT_handler'; $SIG{'TERM'} = 'TERM_handler'; # 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( '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}, '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(rip => \&do_rip); $worker->work while 1; sub do_rip { my $job = shift; my %rip_options = %{ thaw($job->arg) }; $log->notice("Beginning new rip to $rip_options{output_filename}"); my $uuid = random_string('cccccc'); $log->debug("Using $uuid as unique identifier for this job"); $rip_options{unique_output_filename} = $rip_options{output_filename}; $rip_options{unique_output_filename} =~ s/\.([^\.]+)$/\.$uuid\.$1/; # 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, 'unique_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'), ); $log->debug("Beginning ripping process with command:\n" . join(' ', @handbrake_cmd)); # Execute the ripping process my ($child_in, $child_out); my $child_pid = open2($child_out, $child_in, @handbrake_cmd); # Don't need to write from the child close($child_in); my $line; while ($line = <$child_out>) { $log->debug($line); } close($child_out); # If the rip process failed, report an error status here waitpid($child_pid, 0); my $child_exit_status = $? >> 8; if ($child_exit_status) { $log->warning("Ripping process returned error status: $child_exit_status"); return undef; } $log->notice("Finished rip to $rip_options{unique_output_filename}"); return $rip_options{unique_output_filename}; } 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}) : ()); } } 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-worker - Processes DVD rips farmed out by gearman =head1 SYNOPSIS handbrake-cluster-worker.pl -h handbrake-cluster-worker.pl [-v [-d]|-q|-s] [-l logfile] [-n] =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 =cut