#!/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 4.1 2017/10/06 14:42:18 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"; Exec("rm -rf $dir"); } } # 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 == 90) ? "transpose=clock" : ($Degrees == 180) ? "hflip,vflip" : ($Degrees == 270) ? "transpose=cclock" : ""; $Rotate .= ',' if ($Rotate); my $Background = '#000000@1'; print "orientation = '$Orientation' -> rotation = $Rotate\n" if ($Detailed); $Pict = EscapeMeta($Pict); $Mpeg = EscapeMeta($Mpeg); print "$Pict -> $Mpeg $Rotate\n" if $ListFiles; my $Cmd = "ffmpeg -i $Pict -vf '${Rotate}scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:$Background' -c:v libx264 -pix_fmt yuv420p -f mpegts -y $Mpeg " . ($Detailed ? "" : "2>/dev/null"); Exec($Cmd); $Cmd = "touch -r $Pict $Mpeg"; Exec($Cmd); } sub EscapeMeta { my $META = ' !"#$%&\'()*;<>?[\\]`{|}~'; my $s = shift; $s =~ s/([$META])/\\$1/g; return $s; } sub Exec { my $Cmd = shift; print "==> '$Cmd'\n" if ($Verbose); !system($Cmd) || die "$Cmd: $!\n"; }