#!/usr/bin/perl use strict; use warnings; use 5.010; use File::Find; use English '-no_match_vars'; use Getopt::Long; use Pod::Usage; my %OPT = ( unlink => 0, loud => 0, quiet => 0, abits => 128, vbits => 800, jobs => 1, ); Getopt::Long::Configure('bundling'); GetOptions( 'quiet|q' => \$OPT{quiet}, 'loud' => \$OPT{loud}, 'unlink|delete' => \$OPT{unlink}, 'audio-bitrate=i' => \$OPT{abits}, 'video-bitrate=i' => \$OPT{vbits}, 'jobs|j=i' => \$OPT{jobs}, 'help|h|?' => sub { pod2usage(1) }, 'man' => sub { pod2usage( -exitstatus => 0, -verbose => 2 ); }, ) or pod2usage(2); my @dirlist = grep -d, @ARGV; my @filelist = grep -f, @ARGV; if ( ! @ARGV ) { @dirlist = ( '.' ); } if ( @dirlist ) { find( \&wanted, @dirlist ); } if ( $OPT{jobs} > 1 ) { convert_many( $OPT{jobs}, sort @filelist ); } else { foreach my $filename ( sort @filelist ) { convert( $filename ); } } exit; # http://perlmonks.org/?node_id=28870 sub convert_many { my ( $jobs_wanted, @filelist ) = @_; my %running; while ( @filelist || %running ) { if ( @filelist && $jobs_wanted > scalar keys %running ) { my $file = shift @filelist; my $pid = fork // die "Can't fork: $!"; if ( $pid ) { # parent $running{ $pid } = $file; } else { # child convert( $file ); exit; } } else { my $pid = wait; if ( ! defined delete $running{ $pid } ) { die "reaped unknown child PID '$pid'"; } if ( $CHILD_ERROR ) { die "child exited with status ($CHILD_ERROR)"; } } } return; } # From the mplayer man page: # It plays most MPEG/VOB, AVI, ASF/WMA/WMV, RM, QT/MOV/MP4, Ogg/OGM, # MKV, VIVO, FLI, NuppelVideo, yuv4mpeg, FILM and RoQ files, # supported by many native and binary codecs. You can watch VCD, # SVCD, DVD, 3ivx, DivX 3/4/5, WMV and even H.264 movies, too. sub wanted { return if ! -f; return if ! m{ \. (?: avi | mkv | mpe?g | wmv | asf | rm | qt | mov | mp4 | ogm | fli ) \z }xmsi; push @filelist, $File::Find::name; return; } sub convert { my ( $src_file ) = @_; my $src_file_quoted = shell_quote( $src_file ); ( my $new_file = $src_file ) =~ s{ \. [^.]+ \z }{.m4v}xms; if ( $new_file eq $src_file ) { die "renamed '$src_file' to the same filename"; } my $new_file_quoted = shell_quote( $new_file ); # # Many of these options came from a web page I've since lost. # To remove subtitles, I added -noass and -noautosub, but that # didn't work. What worked was to add 'expand' to the end of # the -vf filter chain and add -noautoexpand to keep mencoder # from putting its own expand at the end (which added subtitles). # my $cmd = join q{ }, qq{mencoder $src_file_quoted -o $new_file_quoted}, qq{-vf dsize=480:320:0,scale=0:0,expand -noautoexpand}, qq{-oac faac -faacopts mpeg=4:object=2:raw:br=$OPT{abits}}, qq{-ovc x264 -x264encopts nocabac:level_idc=30:bframes=0:global_header:bitrate=$OPT{vbits}:frameref=6:partitions=all}, q{-of lavf -lavfopts format=mp4}, q{-noass -noautosub}, (q{-really-quiet > /dev/null 2> /dev/null}) x! $OPT{loud} ; if ( ! $OPT{quiet} ) { say scalar localtime(), " $new_file_quoted"; } system( "$cmd" ) == 0 or die "MENCODER GAVE ERROR EXIT STATUS ($CHILD_ERROR) for $cmd\n"; if ( $OPT{unlink} ) { unlink $src_file or die "Can't unlink '$src_file': $!"; } return; } sub shell_quote { my ($s) = @_; $s //= q{}; $s =~ s{ ([\\"`\$]) }{\\$1}xmsg; return qq{"$s"}; } __END__ =pod =head1 NAME mk-iphone-vid.pl - Convert videos for the iPhone =head1 SYNOPSIS mk-iphone-vid.pl [options] [files/directories] Options: -h, --help brief help message --man full documentation --unlink delete originals after converting -j, --jobs number of conversions to run at once --audio-bitrate set output audio bitrate --video-bitrate set output video bitrate -q, --quiet output only errors --loud output progress and more =head1 DESCRIPTION This uses mencoder to convert videos it can read into videos that can play on an Apple iPhone. =head1 USAGE Just run this with a list of files and/or directories as arguments. It will traverse directories recursively to find video files. If no arguments are given, it will default to looking in the current directory. All files it finds are converted to .m4v files suitable to play on an Apple iPhone. By default it will output one line per file with the date and output filename when processing of that file starts. Any file explicitly listed on the command line will be processed regardless of its name. Files discovered through directory traversal must match a list of file extensions. Output file names are the same as the input file names with the extension changed .m4v. =head1 OPTIONS =over 8 =item B<--help> =item B<-h> Print a brief help message and exit. =item B<--man> Print the full documentation and exit. =item B<--delete> =item B<--unlink> Delete the original file after a successful conversion. =item B<--jobs> =item B<-j> Specifies the number of conversions to run simultaneously. =item B<--audio-bitrate> Default: 128 Set the bitrate for audio output. =item B<--video-bitrate> Default: 800 Set the bitrate for video output. =item B<--quiet> =item B<-q> Output only errors during processing. =item B<--loud> This allows the normal output from mencoder during encoding. =back =head1 DEPENDENCIES This uses mencoder to do the real work, so that has to be in your path. The mencoder invocation uses AAC for audio encoding and libavformat for the container format, so those have to be available. =head1 BUGS AND LIMITATIONS There really is not nearly the control available that mencoder provides if used directly. In particular, the code is written explicitly to try to strip subtitles where possible. The list of file extensions that it will recognize as videos during searches is hard coded and might not be comprehensive. =head1 DIAGNOSTICS If mencoder exits with a non-zero exit status, processing will halt. Without the B<--loud> option, you might not know why (mencoder's output is normally supressed). =over =item MENCODER GAVE ERROR EXIT STATUS ($CHILD_ERROR) for $cmd This is the general error any time mencoder fails for any reason. The mencoder command line is given at the end in case you want to try it manually or look for obvious problems there. =item renamed '$src_file' to the same filename This probably means that the source file name ends in .m4v. It's refusing to overwrite it. =item Can't fork Try running without the B<--jobs> option. =item reaped unknown child PID '$pid' A call to wait() got back a child process ID for a child it does not remember spawning. This should never happen. =item child exited with status ($CHILD_ERROR) If running with multiple B<--jobs>, this is the error that appears if a job fails for some reason. Hopefully there's a more descriptive message above this one. =item Can't unlink '$src_file' Try running without the B<--unlink> option. For some reason, it can't delete the original files when it's done with them. =back =head1 COPYRIGHT AND LICENSE Copyright (c) 2009 Kyle Hasselbacher. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR Kyle Hasselbacher =cut