#!/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/; # 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"); my $rip_filename = $rip_options{output_filename}; $rip_filename =~ s/\.([^\.]+)$/\.$uuid\.$1/; # Generate deinterlace options my @deinterlace_options; push @deinterlace_options, '-d' if ($rip_options{deinterlace} == 1); 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 my @handbrake_cmd = ( 'nice', '-n', $rip_options{nice}, $options{handbrake}, '-i', $rip_options{input_filename}, '-o', $rip_filename, @title_options, '-f', $rip_options{format}, '-e', $rip_options{video_codec}, '-q', $rip_options{quantizer}, '-w', $rip_options{video_width}, '-l', $rip_options{video_height}, @deinterlace_options, '-a', $rip_options{audio_tracks}, '-E', $rip_options{audio_codec}, '-A', $rip_options{audio_names}, '-s', $rip_options{subtitle_tracks}, ); $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_filename"); return $rip_filename; } 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