#!/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 3.1 2013/05/23 10:00:23 kls Exp $ use File::Path; use File::Spec; use Getopt::Std; use Image::ExifTool qw(:Public); $Usage = qq{ Usage: $0 [options] picture-dir mpeg-dir $0 [options] picture-file mpeg-file Options: -f Force conversion -h print Help -o percent overscan in percent -s size Screen size (WIDTHxHEIGHT, default is 1920x1080) -v num Verbose (0=none, 1=list files, 2=detailed) -x dir[,...] eXclude the given directories }; getopts("fho:s:v:x:") || die $Usage; die $Usage if $opt_h; $Force = $opt_f; $Overscan = $opt_o || 0; $Size = $opt_s || "1920x1080"; $Verbose = $opt_v; @Exclude = split(',', $opt_x || ""); $ListFiles = $Verbose >= 1; $Detailed = $Verbose >= 2; # Supported picture types: %PICTYPES = ( bmp => 1, gif => 1, jpeg => 1, jpg => 1, png => 1, pnm => 1, tif => 1, tiff => 1, ); # 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]; $Extent = $Size; if ($Overscan > 0) { my ($x, $y) = $Size =~ /(.*)x(.*)/; my $r = (100 + $Overscan) / 100; $x = int($x * $r + 0.5); $y = int($y * $r + 0.5); $Extent = "${x}x$y"; } # 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 | sort`; chomp(@Pictures); PIC: for $pic (@Pictures) { for (@Exclude) { next PIC if ($pic =~ /\/$_\//); } 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) =~ /\.([^\.]*)$/; return if (!defined $PICTYPES{$Type}); my $Exif = ImageInfo($Pict); my $Orientation = $$Exif{"Orientation"}; my ($Degrees) = $Orientation =~ /Rotate ([0-9]+)/; my $Rotate = $Degrees ? "-rotate $Degrees" : ""; print "orientation = '$Orientation' -> rotation = $Rotate\n" if ($Detailed); $Pict = EscapeMeta($Pict); $Mpeg = EscapeMeta($Mpeg); print "$Pict -> $Mpeg $Rotate\n" if $ListFiles; my $Cmd = "convert $Pict -background '#000000' $Rotate -resize $Size -gravity center -extent $Extent ppm:- | " . "ffmpeg -f image2pipe -vcodec ppm -i pipe:0 -an -vcodec libx264 -vpre baseline -s $Size -qscale 2 -f mpegts -y $Mpeg " . ($Detailed ? "" : "2>/dev/null"); !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; }