#! /usr/bin/perl
#
# vcodec_copy_helper - convert TS stream to PS and adjust time in DB
#
#####################
#    Copyright (C) 2010-2012 Michael A. Kangin <mak@complife.ru>
#
#    Copyright: Vargus is under GNU GPL, the GNU General Public License.
#
#    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; version 2 dated June, 1991.
#
#    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.
#
#    http://www.gnu.org/licenses/gpl-2.0.html
#


use strict;
use warnings;

use Getopt::Long;
use DBI;
use Sys::Syslog qw(LOG_DAEMON LOG_ERR LOG_WARNING LOG_NOTICE LOG_INFO LOG_DEBUG);
use Sys::Hostname;
use File::Temp qw(tempfile);
use Time::HiRes qw(gettimeofday);
use File::Basename;

use Vargus::Common;

my (
	$id,
	$start,
	$end,
	$duration,
	$filename,
	$camera,
	$host,
	$out_filename, 
	$file_path,
	$fail_cnt
);

my ($dbh, $sth);
my %sql_access;
my $video_storage;
my ($orig_duration, $actual_duration, $difference);
my $orig_extension;

my ($pp_host, $pp_pid, $db_hostid);
my %vargus_servers = ();
my ($tmp1, $tmp2, $tmp3);
my $executor_pid;

my $tmp_dir = '/var/tmp';
our $conf_dir = "/etc/vargus";
our $informer_port = 9165;
our $obj_name;


sub do_cleanup {
	$sth->finish if $sth;
	$dbh->disconnect if $dbh;
	unlink $tmp2 if $tmp2 && -e $tmp2;
	unlink $tmp3 if $tmp3 && -e $tmp3;
	if ($tmp1 && -e $tmp1 && ! -e $filename) {
		rename $tmp1, $filename;
		$dbh = DBI->connect($sql_access{dsn}, $sql_access{user}, $sql_access{password}) and
			$dbh->do("delete from postproc where tmpname='$tmp1';");
		$dbh->disconnect;
	}
	unlink $tmp1 if $tmp1 && -e $tmp1;
}

sub prepare_to_die {
	do_cleanup;
	die;
}

sub cleanup {
	do_cleanup;
	exit;
}


$SIG{TERM} = $SIG{INT} = $SIG{PIPE} = sub {
	s_log(LOG_DEBUG, "Preprocessor $$ prepare to die...");
	kill(9, $executor_pid) if $executor_pid;
	cleanup;
};



sub configure {
	my @cfg_body = ();
	my @main_section = ();
	my $config_file = shift;

	open(CFG, $config_file) or log_n_die("Error reading config file $config_file");
	chomp(@cfg_body = <CFG>);
	close(CFG);

	@main_section = expand_macroses(get_cfg_section("vargus", @cfg_body));

	$sql_access{host} = get_option("sql-host", @main_section);
	$sql_access{db} = get_option("sql-db", @main_section) 
		or log_n_die("No SQL database specified");
	$sql_access{user} = get_option("sql-user", @main_section) 
		or log_n_die("No SQL user specified");
	$sql_access{password} = get_option("sql-password", @main_section) 
		or log_n_die("No SQL password specified");

	$sql_access{dsn} = "DBI:mysql:$sql_access{db}";
	$sql_access{dsn} .= ":$sql_access{host}" if $sql_access{host};

	$video_storage = get_option("video-storage", @main_section)
		or log_n_die("No video storage location specified");
}



sub get_hostid {
	my $query;

	my $dbh = DBI->connect($sql_access{dsn}, $sql_access{user}, $sql_access{password});
	unless ($dbh) {
		s_log(LOG_WARNING, "Error connect to SQL database");
		return;
	}

	my $query_ok = 1;
	$query = "select id from servers where hostname = '" . hostname .  "';";
	s_log(LOG_DEBUG, "try to execute query $query");
	my $sth = $dbh->prepare($query) or $query_ok = 0;;
	$sth->execute or $query_ok = 0;

	if ($query_ok) {
		($db_hostid) = $sth->fetchrow_array;
	}

	$sth->finish;
	$dbh->disconnect;

	s_log(LOG_WARNING, "Error get hostid from db") unless $query_ok;
}









GetOptions('filename=s' => \$filename);

unless ($filename) {
	log_n_die("Error in parameters, need filename");
}

unless ( -r $filename ) {
	log_n_die("File $filename is not readable");
}

if ($filename =~ /\.mp4$/) { 
	log_n_die("Can't work with this file's type ($filename)");
}

($orig_extension = $filename) =~ s/.*\.//;

# Try to detect DB parameters
my $postproc_conf;
($postproc_conf) = glob("$conf_dir/[0-9][0-9]-postprocess");
unless ($postproc_conf && -r $postproc_conf) {
	s_log(LOG_ERR, "Can't get readable postprocess config");
	exit 1;
}

configure($postproc_conf);


# Connect to DB and get fileinfo
$dbh = DBI->connect($sql_access{dsn}, $sql_access{user}, $sql_access{password})
	or log_n_die("Can't connect to mysql base");

my $sql_query = "select id,UNIX_TIMESTAMP(start_time),UNIX_TIMESTAMP(end_time),duration,camera,hostid,fail_cnt " . 
		"from archive where filename='$filename'";
$sth = $dbh->prepare($sql_query) or log_n_die("Can't prepare SQL query $sql_query");
$sth->execute or log_n_die("Can't execute SQL query $sql_query");

($id, $start, $end, $duration, $camera, $host, $fail_cnt) = $sth->fetchrow_array;
$sth->finish if $sth;


$sth = $dbh->prepare("select id, hostname, storage from servers;");
$sth->execute;

while (my @entry = $sth->fetchrow_array()) {
	$vargus_servers{$entry[0]}{hostname} = $entry[1];
	$vargus_servers{$entry[0]}{storage} = $entry[2] if $entry[2];
}
$sth->finish; 

unless(%vargus_servers) {
	log_n_die("No vargus server hostnames found.")
}

$dbh->disconnect if $dbh;

my $camera_info = do_remote_query($vargus_servers{$host}{hostname}, "query !expand camera;$camera;write:filename,write:path");
unless ($camera_info) {
	log_n_die("Can't acquire information for $camera from host " . $vargus_servers{$host}{hostname});
}
chomp($camera_info);
($out_filename, $file_path) = split(';', $camera_info);


$pp_host = hostname;
$pp_pid = $$;
$tmp1 = (tempfile("$vargus_servers{$host}{storage}/postprocess.$pp_host.$pp_pid.infile.XXXXX", OPEN => 0))[1] . ".$orig_extension";
$tmp2 = (tempfile("$vargus_servers{$host}{storage}/postprocess.$pp_host.$pp_pid.outfile.XXXXX", OPEN => 0))[1];
$tmp3 = (tempfile("$vargus_servers{$host}{storage}/postprocess.$pp_host.$pp_pid.outfile.XXXXX", OPEN => 0))[1];

unless (-e $filename) {
	s_log(LOG_INFO, "File for preprocess $filename is suddenly vanished");
	cleanup;
}

if (rename($filename, $tmp1)) {
	if ($dbh = DBI->connect($sql_access{dsn}, $sql_access{user}, $sql_access{password})) {
		my $sql_query = "insert into postproc (filename, tmpname) values ('$filename', '$tmp1');";
		$dbh->do($sql_query);
		$dbh->disconnect;
	} else {
		s_log(LOG_WARNING, "Error connect to SQL database $sql_access{db}");
	}
} else {
	s_log(LOG_INFO, "Error renaming $filename to $tmp1, skip");
	cleanup;
}


$orig_duration = `mediainfo --Inform="General;%Duration%" $tmp1`;
chomp($orig_duration);

my $copy_commandline;
$copy_commandline = "/usr/bin/avconv -i $tmp1 -vcodec copy -acodec copy -f mp4 $tmp2";
$copy_commandline = "/usr/bin/MP4Box -add $tmp1 $tmp2" if $fail_cnt == 1;
$copy_commandline = "cvlc --sout '#std{access=file,mux=mp4,dst=$tmp2}' $tmp1 vlc://quit;" .
			"/bin/mv $tmp2 $tmp3; /usr/bin/MP4Box -add $tmp3 $tmp2" if $fail_cnt >= 2;

$executor_pid = fork();

defined($executor_pid) or do {
	s_log(LOG_INFO, "Error run preprocessor executor process ($!)");
	cleanup;
};

unless ($executor_pid) {
	exec("/bin/nice /usr/bin/ionice -c 3 $copy_commandline");
}

s_log(LOG_DEBUG, "Start preprocess executor $executor_pid for $filename with command $copy_commandline (failcnt: $fail_cnt)");

my ($run_start_time, $run_stop_time);
($run_start_time) = gettimeofday();


my $max_exec_time = 600;
eval {
	local $SIG{ALRM} = sub { die "alarm\n" };
	alarm($max_exec_time);
	waitpid($executor_pid, 0);
	alarm(0);
};

if ($@ and $@ eq "alarm\n") {
	s_log(LOG_WARNING, "Executor $executor_pid was run more than $max_exec_time seconds; will be terminated by timeout");
	kill(9, $executor_pid);
	cleanup;
} else {
	($run_stop_time) = gettimeofday();
	s_log(LOG_DEBUG, "Preprocessor executor $executor_pid for " . basename($filename) . 
		" was running " . ($run_stop_time - $run_start_time) . " seconds");
}

$actual_duration = `mediainfo --Inform="General;%Duration%" $tmp2`;
chomp($actual_duration);
$difference = ($orig_duration - $actual_duration) / 1000;

if ($difference > 30) {
	s_log(LOG_WARNING, "Very big duration difference while preprocess " . basename($filename) . ", failed ($orig_duration - $actual_duration = $difference)");
	cleanup;
}

s_log(LOG_DEBUG, "Stat info for " . basename($filename) . ": Difference: $difference, old start: $start, new start: " . ($start + $difference));
if ($fail_cnt == 1) {
	s_log(LOG_NOTICE, "Warning! $filename was decoded with other method, please check");
}
if ($fail_cnt >= 2) {
	s_log(LOG_NOTICE, "Warning! $filename was decoded with third method, please check");
}

$start += $difference;
$duration = $actual_duration / 1000;
$end = $start + $duration;

$out_filename = "$vargus_servers{$host}{storage}/$file_path/$out_filename";
$out_filename =~ s/\(date\)/(date_of:$start)/g; 
$out_filename =~ s/\(time\)/(time_of:$start)/g;
$obj_name = $camera;
$out_filename = expand_macroses($out_filename);
$out_filename =~ s/\..*//;
$out_filename .= ".mp4";

$dbh = DBI->connect($sql_access{dsn}, $sql_access{user}, $sql_access{password})
	or log_n_die("Can't connect to mysql base");
$sql_query = "update archive set filename='$out_filename', start_time=FROM_UNIXTIME($start), end_time=FROM_UNIXTIME($end), " .
		"duration=$duration where id=$id";
$dbh->do($sql_query);
$dbh->do("delete from postproc where tmpname='$tmp1';");
$dbh->disconnect;

rename $tmp2, $out_filename or s_log(LOG_WARNING, "Error rename $tmp2 to $out_filename ($!)");
unlink $tmp1 or s_log(LOG_WARNING, "Error delete $tmp1 ($!)");

cleanup;
