#!/usr/bin/perl -w
#
# makempeg1.pl
#
# Generate an MPEG-1 movie with the UCB mpeg_encode utility out of JPEG
# snapshots.  Since the MPEG-1 standard doesn't support less than 23.976
# frames/second, we fudge lower frame rates by specifying each JPEG image
# multiple times.  It's a lousy hack, but it works. :-)
#
# This program makes use of the following utility:
#
# mpeg_encode
# the UCB Parallel MPEG-1 Encoder
# http://bmrc.berkeley.edu/frame/research/mpeg/
#
# Copyright (C) 2000
# All rights reserved.
#
# BackWatcher, Inc.
# Information Security Solutions
# http://www.backwatcher.com/
# support@backwatcher.com
# 813-979-1633
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# Change history:
#
# 0.10    12/24/00        Alpha release

require 5.004;
use strict;
use Getopt::Long;
use POSIX qw(locale_h);
use POSIX qw(strftime);

#
# User defined variables
#

my $mpencode	= "/usr/local/bin/mpeg_encode";		# mpeg_encode binary
my $mpeopts	= "-no_frame_summary";			# mpeg_encode options
my $paramfile	= "mpeg_encode.par";			# mpeg_encode parameter file
#my $fpattern	= "IBBPBBPBBPBB";			# frame pattern
my $fpattern	= "IBPB";				# frame pattern
my $tmp		= "/tmp";				# tmp dir
my $path	= "/bin:/usr/bin:/usr/sbin";		# path
my $locale	= "en_US.ISO-8859-1";			# locale

#
# Options requiring arguments
#

my $src		= '';
my $dst		= '';
my $cam		= '';
my $ips		= '';  		# encode 1,2,3,4,6,8,12 and 24 ips @ 24 fps
                   		# encode 5 and 25 ips @ 25 fps
                   		# encode 10,15 and 30 ips @ 30 fps

# Frame rates available in MPEG-1 and the currently defined set of
# Profiles and Levels in MPEG-2 are: 23.976 Hz (3-2 pulldown NTSC),
# 24 Hz (Film), 25 Hz (PAL/SECAM or 625/60 video), 29.97 (NTSC),
# 30 Hz (drop-frame NTSC or component 525/60), 50 Hz (double-rate PAL),         
# 59.97 Hz (double rate NTSC), and 60 Hz (double-rate, drop-frame
# NTSC/component 525/60 video). Only 23.976, 24, 25, 29.97, and 30 Hz
# are within the conformance space of Constrained Parameter Bitstreams
# and Main Level.

#
# Main symbol declerations
#

my (
     $fps, $today, @files, $file, $dir,
     $gopsize, $dstcpy, $frame, $count,
     $date, $help, $debug
);

main();
exit;

#
# Main subroutine
#

sub main {

  #
  # Secure the environment
  #

  delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};		# make %ENV safer
  $ENV{PATH} = "$path";					# set PATH

  #
  # Process command line arguments
  #

  getopts();

  #
  # Get local date
  #

  getdate();

  #
  # Generate mpeg_encode parameter file
  #

  genparamfile();

  #
  # Encode movie
  #

  encode();

  #
  # Tidy up
  #

  tidyup();

}

sub getopts {

  #
  # Process arguments
  #

  GetOptions ('src=s' => \$src,
              'dst=s' => \$dst,
              'cam=s' => \$cam,
              'ips=s' => \$ips,
              'date' => \$date,
              'help' => \$help,
              'debug' => \$debug);

  #
  # Ensure that --src, --dst and --cam were specified
  #

  if (($src eq '') || ($dst eq '') || ($cam eq '')) {
    help();
  }

  #
  # Ensure that an argument to --ips is a valid value
  #

  if ($ips) {

    use integer;			# use integer operations to get modulus

    print "m24 = ", (24 % $ips), " ; m25 = ", (25 % $ips), " ; m30 = ", (30 % $ips), "\n" if ($debug);	# debug info

    if ((30 % $ips) != 0) {
      if ((24 % $ips) != 0) {
        if ((25 % $ips) != 0) {
          print "error: argument to --ips must be on of 1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 24, 25 or 30.\n";
          exit;
        }
      }
    }

    no integer;				# don't use integer operations anymore

    print "ips = $ips\n" if ($debug);				# debug info

  }

  print "src = $src\n" if ($debug);				# debug info
  print "dst = $dst\n" if ($debug);				# debug info
  print "cam = $cam\n" if ($debug);				# debug info

}

sub getdate {

  #
  # Get local date 
  #

  setlocale(LC_TIME, "$locale");
  $today = ( strftime "%m%d%y", localtime);

  print "today = $today\n" if ($debug);			# debug info

}

sub genparamfile {

  #
  # Generate mpeg_encode parameter file
  #

  open (PARAMS, "> $tmp/$paramfile") || die "cannot open: $!";

  print PARAMS "BASE_FILE_FORMAT        JPEG\n";
  print PARAMS "INPUT_CONVERT   *\n";
  print PARAMS "PATTERN         $fpattern\n";

  $gopsize = length $fpattern;					# get GOP SIZE

  print PARAMS "GOP_SIZE        $gopsize\n";
  print PARAMS "SLICES_PER_FRAME        1\n";

  $dstcpy = $dst if ($date);					# copy original destination
  $dstcpy =~ s/\/.*\/// if ($date);				# get destination file
  $dst = ($dst . $today . "_" . $dstcpy) if ($date);	# add datestamped file
  $dst =~ s/$dstcpy// if ($date);						# strip original file

  print "dst = $dst\n" if ($debug);				# debug info

  print PARAMS "OUTPUT          $dst\n";
  print PARAMS "INPUT_DIR       $src\n";

  print PARAMS "INPUT\n";

  @files = scandir ("$src");					# get source directory file list

  #
  # Disregard matching files that don't end in .jpg
  #

  foreach $file (@files) {
    $_ = $file;
    if (/\.jpg/) {
      # noop!
    } else {
      shift @files;
    }
  }

  #
  # Get fps if ips specified
  #

  if ($ips) {

    use integer;			# use integer operations to get modulus

    if (($ips == 10) || ($ips == 15) || ($ips == 30)) {
      $fps = 30;
    } elsif (($ips == 5) || ($ips == 25)) {
      $fps = 25;
    } else {
      $fps = 24;
    }

    no integer;				# don't use integer operations anymore
  
    print "fps = $fps\n" if ($debug);				# debug info
  }

    $frame = 0;					# frame counter for debug info

    foreach $file (@files) {

      print "\nfile = $file\n\n" if ($debug);		# debug info

      if ($ips) {
        for ($count = 0; $count < ($fps / $ips); $count++) {	# fps fudge
          print "adding frame $frame: file $file \#", ($count + 1), "\n" if ($debug);	# debug info
          print PARAMS "$file\n";					# add file to paramater file
          $frame++;				# increment frame counter
        }
      } else {
          print "adding frame $frame: file $file", "\n" if ($debug);	# debug info
          print PARAMS "$file\n";					# add file to paramater file
          $frame++;				# increment frame counter
      }
    }

  print PARAMS "END_INPUT\n";

  # Motion Vector Search Techniques
  # P-frame Techniques: TWOLEVEL, LOGARITHMIC, SUBSAMPLE, EXHAUSTIVE
  # B-frame Techniques: CROSS2, SIMPLE, EXHAUSTIVE
  # Little quality difference, large compression and speed difference.
  print PARAMS "PSEARCH_ALG     LOGARITHMIC\n";
  print PARAMS "BSEARCH_ALG     SIMPLE\n";

  # Motion Vector Search Window
  print PARAMS "PIXEL           HALF\n";
  print PARAMS "RANGE           8\n";

  print PARAMS "REFERENCE_FRAME ORIGINAL\n";

  # Q-Scale Values (1-31)
  # Values can be set separately for I, P and B frames.
  # Larger values give better compression, but worse quality.
  # Different values have very little effect on speed.
  print PARAMS "IQSCALE         8\n";	# 8
  print PARAMS "PQSCALE         31\n";	# 10
  print PARAMS "BQSCALE         31\n";	# 25

  close PARAMS;

}

sub scandir {

  #
  # Scan directory for image files
  #

  $dir = $_[0];
  opendir(DIR, $dir) || die "can't opendir $dir: $!";
  @files = grep { /$cam/ && -f "$dir/$_" } readdir(DIR);
  closedir DIR;
  return @files;

}

sub encode {

  #
  # Encode movie
  #

  system "$mpencode", "$mpeopts", "$tmp/$paramfile"
    || die "$mpencode failed: $!\n";

}

sub tidyup {

  #
  # Be tidy
  #

  unlink "$tmp/$paramfile" if !($debug);	# remove paramater file if no debug

##  foreach $file (@files) {
##    unlink "$src/$file" if !($debug);	# remove image files if no debug
##  }

}

sub help {
  print "usage: makempeg1.pl --src sourcedir --dst destfile --cam camstring\n";
  print "	      [--ips {1-4,5-6,8,10,12,15,24,25,30}] [--date] [--help] [--debug]\n\n";
  print "	--src		source directory\n";
  print "	--dst		destination file\n";
  print "	--cam		string to match for input file selection\n";
  print "	--ips		images/second source JPEG files generated at\n";
  print "	--date		date stamp destination file\n";
  print "	--help		display this help\n";
  print "	--debug		print debugging information\n\n";
  print "  makempeg1.pl makes MPEG-1 movies from the JPEG snapshots of Axis\n";
  print "  network cameras.\n\n";
  print "  Example:\n\n";
  print "  makempeg1.pl --src /some/dir --dst /tmp/movie.mpg --cam cam1 --ips 4 --date\n\n";
  print "  would make an MPEG-1 movie in file, /tmp/mmddyy_movie.mpg, out of\n";
  print "  JPEG files beginning with the string, cam1, in directory, /some/dir,\n";
  print "  where the source JPEG files were generated at 4 images/second.\n";
  exit;
}
