#!/usr/bin/perl # pic2mpg: Convert picture files to MPEG still frames # # Converts either a single picture file or all files in a # given directory (recursively) to MPEG still frames. # # See the README file for copyright information and how to reach the author. # # $Id: pic2mpg 2.0 2008/02/29 14:34:22 kls Exp $ ## TODO implement HDTV (1920 x 1080) use File::Path; use File::Spec; use Getopt::Std; use Image::Size; $Usage = qq{ Usage: $0 [options] picture-dir mpeg-dir $0 [options] picture-file mpeg-file Options: -a Aspect ratio 4:3 (default is 16:9) -f Force conversion -h print Help -i Ignore unknown file types -n NTSC (default is PAL) -v num Verbose (0=none, 1=list files, 2=detailed) -x percent X overscan in percent -y percent Y overscan in percent }; getopts("afhinv:x:y:") || die $Usage; die $Usage if $opt_h; $Aspect = $opt_a; $Force = $opt_f; $Ignore = $opt_i; $NTSC = $opt_n; $Verbose = $opt_v; $OverscanX = $opt_x; $OverscanY = $opt_y; $ListFiles = $Verbose >= 1; $Detailed = $Verbose >= 2; # Screen size: $SW = $NTSC ? 720 : 720; $SH = $NTSC ? 480 : 576; $ScreenRatio = $Aspect ? 4 / 3 : 16 / 9; # Converter programs: %PNMCONV = ( bmp => "bmptopnm", gif => "giftopnm", jpeg => "jpegtopnm", jpg => "jpegtopnm", png => "pngtopnm", pnm => "cat", tif => "tifftopnm", tiff => "tifftopnm", ); # Command options: die "$0: missing parameter\n" unless $ARGV[0] && $ARGV[1]; die "$0: file or directory not found: $ARGV[0]\n" unless -e $ARGV[0]; die "$0: source and destination must be different\n" if $ARGV[0] eq $ARGV[1]; $verbose1 = $Detailed ? "--verbose" : ""; $verbose2 = $Detailed ? "-v 2" : "-v 0"; $system1 = $NTSC ? "" : "--pal"; $system2 = $NTSC ? "n" : "p"; $framerate = $NTSC ? "30000:1001" : "25:1"; $aspect = $Aspect ? "2" : "3"; # Convert a single file: if (-f $ARGV[0]) { die "$0: mixed file and directory ('$ARGV[0]' <-> '$ARGV[1]')\n" unless !-e $ARGV[1] || -f $ARGV[1]; ConvertFile($ARGV[0], $ARGV[1]); exit; } die "$0: mixed directory and file ('$ARGV[0]' <-> '$ARGV[1]')\n" unless !-e $ARGV[1] || -d $ARGV[1]; $PICDIR = File::Spec->rel2abs($ARGV[0]); $MPGDIR = File::Spec->rel2abs($ARGV[1]); # Convert pictures to mpegs: chdir($PICDIR) || die "$PICDIR: $!\n"; @Pictures = `find -type f`; chomp(@Pictures); for $pic (@Pictures) { my $mpg = "$MPGDIR/$pic.mpg"; if ($Force || !-e $mpg || -M $mpg > -M $pic) { (my $dir = $mpg) =~ s/\/[^\/]*$//; mkpath($dir); ConvertFile($pic, $mpg); } } # Remove mpegs without pictures: chdir($MPGDIR) || die "$MPGDIR: $!\n"; @Mpegs = `find -type f`; chomp(@Mpegs); for $mpg (@Mpegs) { my $pic = "$PICDIR/$mpg"; $pic =~ s/\.mpg$//; if (!-e $pic) { print "removing $mpg\n"; unlink($mpg); } } # Remove empty directories: chdir($MPGDIR) || die "$MPGDIR: $!\n"; for ($i = 0; $i < 10; $i++) { # dirs might become empty when removing empty subdirs @Dirs = `find -type d -empty`; chomp(@Dirs); last unless @Dirs; for $dir (@Dirs) { $dir = EscapeMeta($dir); print "removing $dir\n"; !system("rm -rf $dir") || die "$dir: $!\n"; } } # Actual file conversion: sub ConvertFile { my ($Pict, $Mpeg) = @_; (my $Type) = lc($Pict) =~ /\.([^\.]*)$/; if (!defined $PNMCONV{$Type}) { return if ($Ignore); die "unknown file type '$Type': '$Pict'\n"; } my ($w, $h) = imgsize($Pict); print "image size is $w x $h\n" if ($Detailed); if ($w / $h <= $ScreenRatio) { $w = $h * $ScreenRatio; } else { $h = $w / $ScreenRatio; } my $ScaleW = $SW / $w * (100 - 2 * $OverscanX) / 100; my $ScaleH = $SH / $h * (100 - 2 * $OverscanY) / 100; $Pict = EscapeMeta($Pict); $Mpeg = EscapeMeta($Mpeg); print "$Pict -> $Mpeg\n" if $ListFiles; my $Cmd = "$PNMCONV{$Type} $Pict 2> /dev/null |" . "pnmscale $verbose1 --xscale=$ScaleW --yscale=$ScaleH |" . "pnmpad $verbose1 --black --width $SW --height $SH |" . "ppmntsc $verbose1 $system1 |" . "ppmtoy4m $verbose2 -F $framerate -I p -S 420mpeg2 |" . "mpeg2enc $verbose2 -f 3 -b 12500 -a $aspect -q 1 -n $system2 -o $Mpeg"; !system($Cmd) || die "$Cmd: $!\n"; $Cmd = "touch -r $Pict $Mpeg"; !system($Cmd) || die "$Cmd: $!\n"; } sub EscapeMeta { my $META = ' !"#$%&\'()*;<>?[\\]`{|}~'; my $s = shift; $s =~ s/([$META])/\\$1/g; return $s; }