Since I got an iPhone back when they were new, I've been using a commercial application to convert movies and TV shows from the web for it. It runs on my old laptop, and converting any given video takes about five times longer than the duration of the video. I lived with it since it worked well and I didn't want to try to figure out how to use a command line tool on my faster Linux box.
One big problem I had was to get rid of the (Danish) subtitles, so that's part of this code's behavior. Otherwise, it simply takes what you give it and scales it down to the iPhone's resolution and reencodes it to an .m4v file. The gory details are in POD, readily available via Pod::Usage.
#!/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:globa
+l_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 $cm
+d\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 <kyle@cpan.org>
=cut