#!/usr/bin/perl
#
# This script will create a montage from a collection
# of images, this montage will then be sent to a list of
# users defined in /etc/motionmail.d/settings.conf
#
# typically called from motion
# motion detection software for linux 
# (http://www.lavrsen.dk/twiki/bin/view/Motion/WebHome)
# use the following in motion.conf:
# 'on_event_end /home/cam/motionmail.pl -e%v -q'

use Getopt::Long;
use Pod::Usage;
my $help    = '';
my $man     = '';
my $event   = '';
my $ver     = '';
my $quiet   = '';
my $dbg     = '';     # enable verbose output
$now        = time;
$version    = '0.02';
undef $err;

GetOptions ( 'help|h'                => \$help,
             'man'                   => \$man,
             'e=s'                   => \$event,
             'version|V'             => \$ver,
             'q'                     => \$quiet,
	     'debug'                 => \$dbg   # option '-debug' will enable verbose output
	   );                                   # only used for development
if((!$event)&&(!$man)&&($ver)){ $help=1;}	   
if($help)    { pod2usage( -verbose =>  1) && exit;               }
if($man)     { pod2usage({-verbose =>  2, -output => \*STDOUT}); }
if($ver)     { print "motionmail version $version\n"; exit;  }

if(!-e "/etc/motionmail.d/settings.conf"){
  print "\n\t/etc/motionmail.d/settings.conf not found!\n" if(!$quiet);
  print "\tsee 'motionmail -man' for instructions.\n\n"    if(!$quiet);
  exit;
}else{
  $excl = get_settings();
  create_montage();
  if($excl){
    # do not notify when the event was within an exclusion time block
    print "(".(get_date($now))[2].") Within exclusion time, not sending.\n" if(!$quiet);
    exit;
  }
  notify("rmontage$event.png");
}

sub create_montage {
  # create a montage from the images captured during this event
  opendir(DIR,$setting{'file_dir'}) || die "Can't open file_dir ($setting{'file_dir'}): $!\n"; 
  @files=readdir(DIR);
  close(DIR);
  @files=grep(/$event-/,@files);
  chdir $setting{'file_dir'};
  # Sort the files so the tiles appear in order 
  @files=sort(@files);
  $files=join " ",@files;
  # failsave: if no images are found, don't try to run the montage command
  if(!$files){
    print "no files found, exiting...\n" if(!$quiet);
    exit;
  }
  # exec the montage command
  # command for email thumbnails
  $err = `montage -verbose -geometry 35% -adjoin $files rmontage$event.png` if (($setting{'make_montage'})&&(!$excl));
  print $err if($dbg);
  # command for montage that gets left behind on the server
  $err = `montage -verbose -label %f -frame 5 -geometry 100% -adjoin $files montage$event.png` if (($setting{'make_montage'})&&(!$excl));
  print $err if($dbg);
  # move event images to the history dir
  if(!-e "./history"){ mkdir "./history", 0755; }
  foreach $file(@files){
    $err = `mv --verbose $file ./history` if($setting{'keep_history'});
    print $err if($dbg);
  }
}

sub notify {
  my($f)=shift;
  #this stuff would be good to move into configuration varibales
  my $message = "Motion was detected ".(get_date($now))[3]; 
  send_mail($setting{'send_to'},"Motion Detected ($event)",$message,"$setting{'file_dir'}/$f");
}

sub send_mail{ 
  my($to,$subj,$message,$file)=@_;
  my $html=<<EOF;
<html>
<body>
$message<br>
<a href="http://path.to.net/location/montage$event.png">
<img src="cid:event_$event.png">
</a>
</body>
</html>
EOF

  use MIME::Lite;
  my $mime_msg = MIME::Lite->new( From    => "$setting{'app_name'} <$setting{'app_email'}>",
                                  To      => $to,
                                  CC      => $cc,
                                  Subject => $subj,
                                  'X-Application' => "motionmail $version by Taco Scheltema <taco\@scheltema.org>",
                                  Type    => 'multipart/related'
                                ) || die("Error creating MIME message: $!");
               $mime_msg->attach( Type     => 'text/html',
                                  Data     =>$html );
               $mime_msg->attach( Type     => 'image/png',
	                          Path     =>$file,
	                          Id       =>"event_$event.png"
	                          ) if($file);
  MIME::Lite->send('smtp', $setting{'mail_server'}) if ($setting{'mail_server'});
  $mime_msg->send() || die("Error sending message: $!");
  unlink $file;
}

sub get_settings {
  open(SETTINGS,"</etc/motionmail.d/settings.conf") || die "Can't open /etc/motionmail.d/settings.conf: $!\n";
  @lines=<SETTINGS>;
  close(SETTINGS);
  # the following hash is only used to sort the settings properly
  # very ugly solution. it is only used with the verbose (-debug)
  # option so it probably can be removed altogether to reduce the
  # size of this script
  %wd=(
	'sunday'    => 0,
        'monday'    => 1,
	'tuesday'   => 2,
	'wednesday' => 3,
	'thursday'  => 4,
	'friday'    => 5,
	'saturday'  => 6,
	'send_to'   => 7,
	'file_dir'  => 8
	); 
  # get rid of irrelevant lines
  @lines = grep(!/^#/,@lines);
  @lines = grep(!/^\s/,@lines);
  @lines = grep(!/^\n/,@lines);
  # fill setting hash with values from the settings.conf file
  foreach $line(@lines) {
    $line=~s/\s//g;
    ($key,$val)=split(/=/,$line);
    $setting{$key}=$val;
    if($val=~m/(\d{4})-(\d{4})/) {
      $begin{$key}=$1; $end{$key}=$2;
    }
  }
  # print a neatly formatted list of settings
  # (only with -debug)
  print "Settings:\n" if(($dbg)&&(!$quiet));
  for $key (sort { $wd{$a} <=> $wd{$b} } keys %setting){
    printf ("%14s = %-40s\n",$key,$setting{$key}) if(($dbg)&&(!$quiet));
  }
  # check if current event was detected within an exclusion time block
  # if it is, do not send anything. Events will still be saved.
  my($day,$time)=(get_date($now))[1,2];
  if(($time>=$begin{$day})&&($time<$end{$day})){
    $dont_send=1;
  }
}

sub get_date {
   my $time = shift;
      $time or ($time = time);
   my @weekday = qw/sunday monday tuesday wednesday thursday friday saturday/;
   my ($min, $hr, $day, $mon, $yr, $wday) = (localtime($time))[1,2,3,4,5,6];
   $yr = $yr + 1900;
   $mon=$mon+1;
   ($min < 10) and ($min = "0$min");
   ($hr  < 10) and ($hr  = "0$hr");
   ($day < 10) and ($day = "0$day");
   ($mon < 10) and ($mon = "0$mon");
   return "$day$mon$yr","$weekday[$wday]","$hr$min","$weekday[$wday] $day/$mon/$yr $hr:$min";
}

__END__

=pod

=head1 NAME

motionmail - creates a montage from a collection of images and sends it
              to a list of email addresses.

=head1 SYNOPSIS

motionmail -e<EVENTNR> [-q]

=head1 OPTIONS

=over 8

=item B<-help> B<-h>

displays a brief help message

=item B<-man>

displays the full manual

=item B<-version> B<-V>

displays version information

=item B<-e> <EVENTNR>

sets the event number. motionmail will look for images for this event in
the image directory, it will then create a montage from these images and
send the montage to the email addresses defined in the settings.conf file

=item B<-q>

quiet operation, this will prevent any output from being displayed. Be
aware that this will also prevent errors from being displayed.

=back

=head1 DESCRIPTION

motionmail will search for images that were stored by motion for the event
given. It will create a montage of these images and subsequently send this 
montage to the email addresses defined in /etc/motionmail.d/settings.conf.
The event images will be moved to a subdirectory called 'history', the 
montage image will be deleted after sending.
a separate cronjob should be defined to delete old events to prevent an
excess of data.

This script can be called by adding:
on_event_end /usr/local/bin/motionmail.pl -e=%v -q

To your motion.conf


=head1 FILES

B</etc/motionmail.d/settings.conf>

Configuration settings.
this file should contain the following:

 #---------- settings.conf ------------
 
 # settings file for motionmail
 # (see motionmail -man for instructions)
 # comment lines start with #
 # blank lines, lines starting with a space and
 # lines starting with # will be ignored
 
 # comma separated list of email addresses
 send_to      = taco@cannex.com.au
 # directory where event images are stored by motion
 # (see the 'target_dir' setting in motion.conf)
 file_dir     = /home/cam/public_html
 # make the montage.png image (1=yes,0=no)
 # (nothing will be sent if this is set to 0)
 make_montage = 1
 # keep event images (1=yes,0=no)
 # this will store all event images in a subdir of
 # file_dir called 'history'
 keep_history = 1
 # application name
 # this name will appear in the 'From' field of all email
 app_name     = Motion
 # application email
 # this will be the sender address of all email
 app_email    = motion@cannex.com.au
 # the 'From:' field will be formed like this:
 # From: app_name <app_email>
 
 # for each weekday an exclusion time can be set
 # this is done in 24h notation in the form of <begin time hhmm>-<end time hhmm>,
 # events will only be sent outside the defined time block, when left empty
 # events will be sent for the full 24 hours. see example below
 
 monday   = 0800-1700
 tuesday  = 0800-1700
 wednesday= 0800-1700
 thursday = 0800-1700
 friday   = 0800-1600
 saturday =
 sunday   =
 
 #---------- settings.conf ------------

=head1 REQUIREMENTS

 Getopt::Long, Pod::Usage, MIME::Lite


=head1 VERSION

motionmail version 0.01 ( Mon Jun  6 08:03:03 EST 2005 )

=head1 AUTHOR

Taco Scheltema <taco@scheltema.org>, <taco@cannex.com.au>

=cut
