#!/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.4 2012/01/08 13:27:17 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)
};

getopts("fho:s:v:") || die $Usage;

die $Usage if $opt_h;

$Force     = $opt_f;
$Overscan  = $opt_o || 0;
$Size      = $opt_s || "1920x1080";
$Verbose   = $opt_v;

$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);

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) =~ /\.([^\.]*)$/;
  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;
}