#!/usr/bin/perl -w

# Extract the "interesting" parts of one or more raw input file(s)
# into a single "work" file named after the talk slug.

# This file is part of Sreview, a web-based video review and transcoding
# system
# Copyright (c) 2016-2017, Wouter Verhelst <w@uter.be>
#
# Sreview is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation; either version 3 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
# Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with this program.  If not, see
# <http://www.gnu.org/licenses/>.

use strict;
use warnings;

use DBI;
use File::Temp qw/tempdir/;
use File::Path qw/make_path/;

use Glib;
use Glib::Object::Introspection;

Glib::Object::Introspection->setup(
		basename => "Gst",
		version => "1.0",
		package => "Gst",
		);
Glib::Object::Introspection->setup(
		basename => "GstPbutils",
		version => "1.0",
		package => "Gst::Pbutils",
		);
Gst::init(\@ARGV);

sub system_v($) {
	my $command = shift;
	print $command . "\n";
	system($command);
}

our $config;
require './config.pl';

my $tempdir = tempdir( "cutXXXXXX", DIR => $config->{workdir}, CLEANUP => 1);

my $dbh = DBI->connect($config->{dbistring}, '', '') or die "Cannot connect to database!";

my $talkid = $ARGV[0];

$dbh->begin_work;

my $started = $dbh->prepare("UPDATE talks SET progress='running', state='cutting' WHERE id = ?");
$started->execute($talkid);

$dbh->commit;

$dbh->begin_work;

my $talk_data = $dbh->prepare("SELECT talkid, rawid, raw_filename, extract(epoch from fragment_start) AS fragment_start, extract(epoch from raw_length) as raw_length, extract(epoch from raw_length_corrected) as raw_length_corrected FROM adjusted_raw_talks(?, make_interval(secs :=?::numeric), make_interval(secs := ?::numeric)) ORDER BY talk_start, raw_start");
my $corrections_data = $dbh->prepare("SELECT corrections.talk, properties.name AS property, corrections.property_value FROM corrections LEFT JOIN properties ON corrections.property = properties.id WHERE talk = ?");
$corrections_data->execute($talkid);

my %corrections;
my @segments_pre;
my @segments_main;
my @segments_post;

while(my $row = $corrections_data->fetchrow_hashref()) {
	my $name = $row->{property};
	my $val = $row->{property_value};
	$corrections{$name} = $val;
}

foreach my $prop ("offset_start", "length_adj", "offset_audio") {
	if(!exists($corrections{$prop})) {
		$corrections{$prop} = 0;
	}
}

if(!exists($corrections{audio_channel})) {
	$corrections{audio_channel} = 2;
}

$talk_data->execute($talkid, $corrections{"offset_start"}, $corrections{"length_adj"});

my $prelen = 0;
my $mainlen = 0;
my $postlen = 0;

while(my $row = $talk_data->fetchrow_hashref()) {
	my $start;
	my $stop;
	my $target;
	my $segments;
	next if ($row->{raw_length_corrected} <= 0);
	if($row->{talkid} == -1) {
		$target = "pre";
		$segments = \@segments_pre;
		if($prelen == 0 && $corrections{offset_audio} > 0) {
			if($row->{fragment_start} > 0) {
				$row->{fragment_start} -= ($corrections{offset_audio} > $row->{fragment_start}) ? $row->{fragment_start} : $corrections{offset_audio};
			}
		}
		$prelen += $row->{raw_length_corrected};
	} elsif($row->{talkid} == -2) {
		$target = "post";
		$segments = \@segments_post;
		$postlen += $row->{raw_length_corrected};
	} else {
		$target = "main";
		$segments = \@segments_main;
		$mainlen += $row->{raw_length_corrected};
	}
	if($row->{fragment_start} ne '0') {
		$start = " -ss " . $row->{fragment_start};
	}
	if($row->{raw_length} ne $row->{raw_length_corrected}) {
		$stop = " -t " . $row->{raw_length_corrected};
	}
	system("ffmpeg -i " . $row->{raw_filename} . " -c copy -f mpegts $start $stop $tempdir/$target" . $row->{rawid} . ".ts");
	push @$segments, "$tempdir/$target" . $row->{rawid} . ".ts";
}

# ensure we have at least something
if(!scalar(@segments_main)) {
	die "did not find any segments for main video";
}

my @segments = (@segments_pre, @segments_main, @segments_post);

# Concat files losslessly
open CONCAT, ">$tempdir/concat.txt";
foreach my $segment(@segments) {
	print CONCAT "file '$segment'\n";
}
close CONCAT;
system_v("ffmpeg -f concat -safe 0 -i $tempdir/concat.txt -c copy -y $tempdir/full.ts");

my $eventname = $dbh->prepare("SELECT events.id AS eventid, events.name AS event, rooms.name AS room, talks.starttime::date, talks.slug FROM talks JOIN events ON talks.event = events.id JOIN rooms ON rooms.id = talks.room WHERE talks.id = ?");
$eventname->execute($talkid);
my $row = $eventname->fetchrow_hashref();
my $workdir = $config->{pubdir} . "/video/" . $row->{eventid} . "/" . substr($row->{room}, 0, 1);
make_path($workdir);
my $outname = "$workdir/" . $row->{slug};
# Figure out what the correct thing to do with is
my $channel = "-map_channel 0.1.0";
if(exists($corrections{audio_channel})) {
	if ($corrections{audio_channel} =~ /A/i || $corrections{audio_channel} == 1) {
		$channel = "-map_channel 0.1.1";
	} elsif($corrections{audio_channel} == 2) {
		$channel = "-ac 1";
	}
}
system_v("ffmpeg -i $tempdir/full.ts $channel -y $tempdir/full.wav");
# if there is an A/V sync correction value, shift the audio around a bit
if($corrections{offset_audio} != 0) {
	print "adjusting A/V desync...\n";
	# Extract audio
	if($corrections{offset_audio} > 0) {
		# delay audio by cutting off some of the video at the start
		system_v("ffmpeg -ss " . abs($corrections{offset_audio}) . " -i $tempdir/full.ts -c copy -y $tempdir/full-new.ts");
		$prelen -= $corrections{offset_audio};
		if($prelen < 0) {
			$mainlen -= abs($prelen);
			$prelen = 0;
		}
		unlink("$tempdir/full.ts");
		rename("$tempdir/full-new.ts", "$tempdir/full.ts");
	} else {
		# delay video by cutting off some of the audio at the start
		system_v("ffmpeg -ss " . abs($corrections{offset_audio}) . " -i $tempdir/full.wav -c copy -y $tempdir/full-new.wav");
		$postlen -= $corrections{offset_audio};
		if($postlen < 0) {
			$mainlen -= abs($postlen);
			$postlen = 0;
		}
		unlink("$tempdir/full.wav");
		rename("$tempdir/full-new.wav", "$tempdir/full.wav");
	};
}
# Now split and merge the full video and audio files into three video files with the audio reattached
system_v("ffmpeg -i $tempdir/full.ts -i $tempdir/full.wav -t $prelen -c:v copy -b:a 128k -map 0:v -map 1:a -y $outname-pre.ts");
system_v("ffmpeg -i $tempdir/full.ts -i $tempdir/full.wav -ss " . ($prelen + $mainlen) . " -c:v copy -b:a 128k -map 0:v -map 1:a -shortest -y $outname-post.ts");
system_v("ffmpeg -i $tempdir/full.wav -ss $prelen -t $mainlen -c copy $tempdir/audio.wav");
system_v("ffmpeg -i $tempdir/full.ts -ss $prelen -t $mainlen -c copy $tempdir/preaudio.ts");
# - Run bs1770gain (which creates a FLAC file)
system_v("bs1770gain -a -o $tempdir $tempdir/audio.wav");
# - Replace original audio stream with normalized one
system_v("ffmpeg -i $tempdir/preaudio.ts -i $tempdir/audio.flac -b:a 128k -c:v copy -map 0:v -map 1:a -shortest -y $outname.ts");

# Detect the size of pre and post videos to be sure they're correct
my $disc = Gst::Pbutils::Discoverer->new(1_000_000_000);
if(scalar(@segments_pre) > 0) {
	my $info = $disc->discover_uri(Glib->filename_to_uri("$outname-pre.ts", undef));
	$prelen = $info->get_duration() / 1_000_000_000;
};
if(scalar(@segments_post) > 0) {
	my $info = $disc->discover_uri(Glib->filename_to_uri("$outname-post.ts", undef));
	$postlen = $info->get_duration() / 1_000_000_000;
};
# Now update the database
my $update = $dbh->prepare("UPDATE talks SET progress = 'done', prelen = ?::interval, postlen = ?::interval WHERE id = ? AND state = 'cutting'");
$update->execute("$prelen seconds", "$postlen seconds", $talkid);

$dbh->commit;
