#!/usr/bin/perl

# Crée une vidéo timelapse à partir d'un sous-répertoire et de spécification de période de temps
#   usage : ../mkvideo.pl --video videos/_Test/New_jour_09_acrotere_garage --base webcam/2012/01/26/ --cam ext --from '2012/01/26 08-00-00' --to '2012/01/26 10-00-00'
# Autres options :
#   --video videos/_Test/Chantier_jour_09_acrotere_garage
#   --base webcam/2012/01/26/
#   --from '2012/01/26 08-00-00'
#   --to '2012/01/26 10-00-00'
#   --cam ext
#   --interval 15
#   --timestamp '%H:%M'
# Nommage des images source :
#   webcam/2012/04/30/00/ext_00-02-58_i.jpg
#   webcam/2012/04/30/00/int_00-02-58_i.jpg

# Exemples :
#   ../mkvideo.pl --video videos/_Test/avril_int --base webcam/2012/04/ --cam int --from '2012/04/01 00-00-00' --to '2012/04/30 23-59-59' --interval 300 --rate 10 --timestamp '%d/%m/%Y %H:%M'
#   ../mkvideo.pl --video videos/_Test/mai_int --base webcam/2012/05 --cam int --from '2012/05/17 00-00-00' --to '2012/05/31 23-59-59' --interval 200 --rate 20 --timestamp '%d/%m/%Y %H:%M' --preview
#   ../mkvideo.pl --video videos/_Test/mai_int --base webcam/2012/05 --cam int --from '2012/05/17 00-00-00' --to '2012/05/31 23-59-59' --interval 200 --rate 20 --timestamp '%d/%m/%Y %H:%M' --preview --exclude '*/2012/??/??/[02][012345]/*' --force


use strict;
use Getopt::Long;
use Data::Dumper;
use Date::Calc qw(Date_to_Time);
use Graphics::Magick;
use Date::Format;


# Lecture des options de ligne de commande
my $option = {
    verbose => 0,
    interval => 1,
    rate => 20,
    cam => 'ext',
    timestamp => '%H:%M:%S', # selon Date::Format
    pipeformat => 'ppm', # format intermédiaire commun entre Graphics::Magick et avconv (PPM/ppm : non compressé mais 200% CPU $coté Perl ; JPEG/mjpeg : un peu plus lent, toujours 200%, fichier MP4 10% plus gros)
    force => 0,
};
my %formats = (
    preview => [
        ['preview.flv', qw(-b 600k -s 640x400)],
    ],
    full => [
        # le FLV doit être le premier, sinon ses paramètres d'encodage sont influencés par ceux des encodages précédents
        ['640.flv',  qw(-qmin 2 -qmax 31 -b 1000k -s 640x400)],
        ['1280.mp4', qw(-qscale 8 -s 1280x800)],
        ['640.mp4',  qw(-qscale 8 -s 640x400)],
        ['320.mp4',  qw(-qmin 2 -qmax 31 -b 300k -s 320x200)],
    ],
);
my %stats;
$stats{date_debut} = scalar(localtime);
$stats{duree} = time;

GetOptions($option,
    'video=s',          # nom de base des fichiers vidéo à créer
    'base=s@',          # répertoire(s) de base des images individuelles
    'from=s',           # date/heure de début des images, inclusive
    'to=s',             # date/heure de fin des images, inclusive
    'cam=s',            # nom de la caméra concernée
    'interval=i',       # intervalle entre deux images source, en secondes
    'rate=i',           # framerate en sortie (image/s)
    'timestamp=s',      # format du timestamp, selon Date::Format
    'preview',          # sort un unique flux basse résolution
    'force',            # force l'écrasement d'une vidéo déjà créée sous ce nom
    'exclude=s@',       # exclue une (des) sous-arborescence(s) (option ! -path de find, éventuellement répétée)
    #'verbose|v+',       # add more -v to increase verbosity (up to 2)
    #'quiet|q',          # reduce verbosity to 0
    #'debug|d',          # increase verbosity to the max (4)
) or die("Erreur de lecture des options");

# Adaptation des bornes sous forme de timestamp
if (exists($option->{from})) {
    $option->{from} = Date_to_Time($option->{from} =~ m[(\d{4})/(\d{2})/(\d{2}) (\d{2})-(\d{2})-(\d{2})]);
} else {
    $option->{from} = 0;
}
if (exists($option->{to})) {
    $option->{to} = Date_to_Time($option->{to} =~ m[(\d{4})/(\d{2})/(\d{2}) (\d{2})-(\d{2})-(\d{2})]);
} else {
    $option->{to} = 2**31;
}
print Data::Dumper->Dump([$option], ['option']);
print "Nombre de frames attendues : ", ($option->{to} - $option->{from}) / $option->{interval}, "\n";
print "Durée attendue : ", ($option->{to} - $option->{from}) / $option->{interval} / $option->{rate}, "s\n";

# Prépare les commandes d'exploration et de conversion en vidéo
# FIXME : ajouter un sort
my @find = ('find', @{$option->{base}});
push(@find, map {('!', '-path', $_)} @{$option->{exclude}}) if (exists($option->{exclude}));
push(@find, '-type', 'f', '!', '-empty', '-name', "$option->{cam}_*_i.jpg", '-print');
my @avconv = ('ffmpeg', '-f', 'image2pipe', '-vcodec', $option->{pipeformat}, '-r', $option->{rate}, '-i', 'pipe:'); #, '-y');
push(@avconv, '-y') if ($option->{force});
foreach my $format (@{$formats{$option->{preview} ? 'preview' : 'full'}}) {
    my $ext = shift @$format;
    push(@avconv, @$format);
    push(@avconv, "$option->{video}-$ext");
};
$stats{find} = join(' ', @find);
$stats{avconv} = join(' ', @avconv);
print Data::Dumper->Dump([\%stats], ['stats']);
my $image = new Graphics::Magick;

# Parcourre l'arborescence et sélectionne les images à la bonne fréquence
my $next_ts = $option->{from};
my $status = undef;
open(FIND, '-|', @find) or die ("Can't open $find[0]: $!");
open(AVCONV, '|-', @avconv) or die ("Can't open $avconv[0]: $!");
#open(AVCONV, ">/srv/shared-data/temp/mkvideo.$option->{pipeformat}") or die ("Can't open avconv: $!");
while (my $file = <FIND>) {
    chomp $file;
    my $timestamp = Date_to_Time($file =~ m[(\d{4})/(\d{2})/(\d{2})/../\w+_(\d{2})-(\d{2})-(\d{2})_i.jpg]);

    # Exclue les images qui ne font pas partie de la période considérée
    next if (($timestamp <= $option->{from}) or ($timestamp >= $option->{to}));
    $stats{images}++;

    # Recherche la prochaine image située juste après le prochain timestamp désiré
    next if ($timestamp < $next_ts);

    # Ajuste le timestamp désiré de l'image suivante, qui doit au moins être le timestamp de l'image courante
    $next_ts += $option->{interval};
    $next_ts = $timestamp if ($timestamp > $next_ts);

    # Traitement de l'image trouvée
    $stats{frames}++;
    my $text = time2str($option->{timestamp}, $timestamp, 'UTC');

    # Equivalent de : gm convert $SRC -stroke yellow -fill yellow -strokewidth 0.3 -font x:fixed -draw 'scale 4,4; text 10,190 "'"$STAMP"'"' $TMP/$DEST
    #   http://www.graphicsmagick.org/perl.html
    $status = $image->Read($file);
    warn $status, next if $status;
    #my ($width, $height, $magick) = $image->Get('width', 'height', 'magick');
    my ($height) = $image->Get('height');
    #$image->Annotate(text=>string, font=>string, family=>string, style=>{Normal, Italic, Oblique, Any}, stretch=> {Normal, UltraCondensed, ExtraCondensed, Condensed, SemiCondensed, SemiExpanded, Expanded, ExtraExpanded, UltraExpanded}, weight=>integer, pointsize=>integer, density=> geometry, stroke=> color name, strokewidth=>integer, fill=>color name, undercolor=>color name, geometry=>geometry, gravity=> {NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast}, antialias=> {true, false}, x=>integer, y=> integer, affine=>array of float values, translate=>float, float, scale=>float, float, rotate=> float. skewX=>float, skewY=> float, align=>{Left, Center, Right}, encoding=>{UTF-8});
    $status = $image->Annotate(
        text => $text,
        #font => 'x:fixed',
        #family => string,
        #style => {Normal, Italic, Oblique, Any},
        #stretch =>  {Normal, UltraCondensed, ExtraCondensed, Condensed, SemiCondensed, SemiExpanded, Expanded, ExtraExpanded, UltraExpanded},
        #weight => integer,
        pointsize => 45,
        #density =>  geometry,
        stroke => 'yellow',
        strokewidth => '1',
        fill => 'yellow',
        #undercolor => color name,
        #geometry => geometry,
        #gravity =>  {NorthWest, North, NorthEast, West, Center, East, SouthWest, South, SouthEast},
        antialias => 'True',#true,#, false},
        x => 40,
        y => $height - 40,
        #affine => array of float values,
        #translate => float, float,
        #scale => [4, 4],
        #rotate =>  float,
        #skewX => float,
        #skewY =>  float,
        #align => {Left, Center, Right},
        #encoding => {UTF-8},
    );
    warn $status, next if $status;

    # Passe l'image dans le pipe, à avconv
    $image->Set(magick => $option->{pipeformat}); # enregistre l'image sans compression, dans un format compatible avec avconv
    my @blobs = $image->ImageToBlob();
    print AVCONV $blobs[0];
    #$image->Write('/srv/shared-data/temp/mkvideo.jpg');
    @$image = (); # supprime l'image en mémoire, pour pouvoir réutiliser l'objet

    #print "$next_ts, $timestamp : $file ($width, $height, $magick), $text\n";
}
close(FIND);
close(AVCONV);

# Fin du traitement
$stats{date_fin} = scalar(localtime);
$stats{duree} = time - $stats{duree};

print Data::Dumper->Dump([\%stats], ['stats']);
