#!/usr/bin/perl -w

use strict;
use warnings;

use DBI;
use File::Temp qw/tempdir/;
use File::Path qw/make_path/;
use SReview::CodecMap qw/detect_to_write/;
use SReview::Map;
use SReview::Talk;
use SReview::Video;
use SReview::Video::Concat;
use SReview::Videopipe;
use SReview::Config::Common;
use SReview::Files::Factory;

=head1 NAME

sreview-cut - cut a talk out of the raw recording data

=head1 SYNOPSIS

sreview-cut TALKID

=head1 DESCRIPTION

C<sreview-cut> performs the following actions:

=over

=item *

Look up the talk with id TALKID in the database.

=item *

From the raw files, extract the amount of video that, according to the
schedule, is part of the talk of which the event was given as the main video
(with adjustments as specified by the reviewer, if any, applied).

=item *

Extract the 20 minutes just before and the 20 minutes just after the
main video into the pre and post videos.

=item *

Apply A/V sync correction values if any exist by removing either some
video or some audio from the beginning of the extracted region.

=item *

Run C<bs1770gain> over the audio of the main video, and re-integrate it
with that main video.

=item *

Set the C<progress> field of the talk in the database to C<done>,
provided its C<state> field is still at C<cutting>.

=back

sreview-cut will B<never> re-encode the original video, and will
re-encode the original audio of the main video precisely once, after the
C<bs1770gain> call.

Any re-transcodes should be performed by C<sreview-transcode> (for
production) and/or C<sreview-previews> (for reviewers).

=head1 CONFIGURATION

C<sreview-cut> considers the following configuration values:

=over

=cut

my $config = SReview::Config::Common::setup;

=item workdir

The location where any temporary files are stored. Defaults to C</tmp>,
but can be overriden if necessary. These temporary files are removed
when C<sreview-cut> finishes.

=cut

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

=item dbistring

The DBI string used to connect to the database.

=cut

my $dbh = DBI->connect($config->get('dbistring'), '', '') or die "Cannot connect to database!";
my $talkid = $ARGV[0];

my $maptype;
my $primary_audio;
my $backup_audio;
my $both_audio;

if($config->get('audio_multiplex_mode') eq 'stereo') {
	$maptype = 'channel';
	$primary_audio = 'left';
	$backup_audio = 'right';
	$both_audio = 'both';
} else {
	$maptype = 'astream';
	$primary_audio = '0';
	$backup_audio = '1';
	$both_audio = '-1';
}

$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 = SReview::Talk->new(talkid => $talkid);

my $corrections = $talk->corrections;

my @segments_pre;
my @segments_main;
my @segments_post;


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

my $input_coll = SReview::Files::Factory->create("input", $config->get("inputglob"));
my $output_coll = SReview::Files::Factory->create("intermediate", $config->get("pubdir"));

foreach my $row (@{$talk->video_fragments}) {
	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};
	}
	my $input_file = $input_coll->get_file(fullname => $row->{raw_filename});
	my $input = SReview::Video->new(url => $input_file->filename);
	my $output = SReview::Video->new(url => "$tempdir/$target" . $row->{rawid} . ".mkv");
	if($row->{fragment_start} ne '0') {
		$output->fragment_start($row->{fragment_start});
	}
	if($row->{raw_length} ne $row->{raw_length_corrected}) {
		$output->duration($row->{raw_length_corrected});
	}
	SReview::Videopipe->new(inputs => [$input], "map" => [SReview::Map->new(input => $input, type => "allcopy")], output => $output, vcopy => 1, acopy => 1)->run();
	push @$segments, SReview::Video->new(url => $output->url);
}

my $full_input = SReview::Video::Concat->new(url => "$tempdir/full.txt", components => [@segments_pre, @segments_main, @segments_post]);
my $full_output = SReview::Video->new(url => "$tempdir/full.mkv");

SReview::Videopipe->new(inputs => [$full_input], "map" => [SReview::Map->new(input => $full_input, type => "allcopy")], output => $full_output)->run();

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();

=item pubdir

The directory where files available to the review system should be
stored. It should be made available to users as C</video/> under the
virtual host top level.

=cut

# Extract the correct audio
my $full = SReview::Video->new(url => $full_output->url);
my $map = SReview::Map->new(input => $full, type => $maptype);

if(exists($corrections->{audio_channel})) {
	if($corrections->{audio_channel} == 0) {
		$map->choice($primary_audio);
	} elsif($corrections->{audio_channel} == 1) {
		$map->choice($backup_audio);
	} elsif($corrections->{audio_channel} == 2) {
		$map->choice($both_audio);
	}
}

my $full_wav = SReview::Video->new(url => "$tempdir/full.wav");
SReview::Videopipe->new(inputs => [$full], output => $full_wav, "map" => [$map], acopy => 0, vcopy => 0)->run();

# Create preview audio streams
my $samplestart = ($mainlen - $prelen) / 2 + $prelen - 30;
my $samplelen = 60;
$samplestart = ($samplestart > 0) ? $samplestart : 0;
$samplelen = ($samplelen > $mainlen) ? $mainlen : $samplelen;
my @audionames = (
	$output_coll->add_file(relname => $talk->relative_name . "-audio0.wav"),
	$output_coll->add_file(relname => $talk->relative_name . "-audio1.wav"),
	$output_coll->add_file(relname => $talk->relative_name . "-audio2.wav"),
);
my @samples = (
        SReview::Video->new(url => $audionames[0]->filename, fragment_start => $samplestart, duration => $samplelen),
        SReview::Video->new(url => $audionames[1]->filename, fragment_start => $samplestart, duration => $samplelen),
        SReview::Video->new(url => $audionames[2]->filename, fragment_start => $samplestart, duration => $samplelen),
);
SReview::Videopipe->new(inputs => [$full], output => $samples[0], "map" => [SReview::Map->new(input => $full, type => $maptype, choice => $primary_audio)], acopy => 0, vcopy => 0)->run();
SReview::Videopipe->new(inputs => [$full], output => $samples[1], "map" => [SReview::Map->new(input => $full, type => $maptype, choice => $backup_audio)], acopy => 0, vcopy => 0)->run();
SReview::Videopipe->new(inputs => [$full], output => $samples[2], "map" => [SReview::Map->new(input => $full, type => $maptype, choice => $both_audio)], acopy => 0, vcopy => 0)->run();
foreach my $stream(0, 1, 2) {
	$audionames[$stream]->store_file;
	foreach my $codec(qw/mp3 ogg/) {
		my $temp_output = $output_coll->add_file(relname => $talk->relative_name . "-audio$stream.$codec");
                SReview::Videopipe->new(inputs => [$samples[$stream]], output => SReview::Video->new(url => $temp_output->filename), acopy => 0, vcopy => 0)->run();
		$temp_output->store_file;
        }
}

# If necessary, fickle with A/V sync values
if($corrections->{offset_audio} != 0) {
	if($corrections->{offset_audio} > 0) {
		# Delay audio by cutting off some of the video at the start
		my $full_corrected = SReview::Video->new(url => "$tempdir/full-new.mkv", fragment_start => $corrections->{offset_audio});
		$prelen -= $corrections->{offset_audio};
		if($prelen < 0) {
			$mainlen -= abs($prelen);
			$prelen = 0;
		}
		SReview::Videopipe->new(inputs => [$full], output => $full_corrected)->run();
		unlink("$tempdir/full.mkv");
		rename("$tempdir/full-new.mkv", "$tempdir/full.mkv");
	} else {
		# Delay video by cutting off some of the audio at the start
		my $full_corrected = SReview::Video->new(url => "$tempdir/full-new.wav", fragment_start => abs($corrections->{offset_audio}));
		$postlen -= abs($corrections->{offset_audio});
		if($postlen < 0) {
			$mainlen -= abs($postlen);
			$postlen = 0;
		}
		SReview::Videopipe->new(inputs => [$full_wav], output => $full_corrected)->run();
		unlink("$tempdir/full.wav");
		rename("$tempdir/full-new.wav", "$tempdir/full.wav");
	}
}

# Now split and merge the full video and audio files back into three video files with the audio reattached
my $map_v = SReview::Map->new(input => $full, type => 'stream', choice => 'video');
my $map_a = SReview::Map->new(input => $full_wav, type => 'stream', choice => 'audio');
my $prelen_db = 0;
my $postlen_db = 0;
if($prelen > 0) {
	my $prefile = $output_coll->add_file(relname => $talk->relative_name . "-pre.mkv");
	SReview::Videopipe->new(inputs => [$full, $full_wav], output => SReview::Video->new(url => $prefile->filename, audio_codec => detect_to_write($full->audio_codec), duration => $prelen), "map" => [$map_v, $map_a], vcopy => 1, acopy => 0)->run();
	$prelen_db = SReview::Video->new(url => $prefile->filename)->duration();
	$prefile->store_file;
} else {
	print "Skipping generation of pre video, no data\n";
}
if($postlen > 0) {
	my $postfile = $output_coll->add_file(relname => $talk->relative_name . "-post.mkv");
	SReview::Videopipe->new(inputs => [$full, $full_wav], output => SReview::Video->new(url => $postfile->filename, audio_codec => detect_to_write($full->audio_codec), fragment_start => ($prelen + $mainlen), duration => $postlen), "map" => [$map_v, $map_a], vcopy => 1, acopy => 0)->run();
	$postlen_db = SReview::Video->new(url => $postfile->filename)->duration();
	$postfile->store_file;
} else {
	print "Skipping generation of post video, no data\n";
}
SReview::Videopipe->new(inputs => [$full_wav], output => SReview::Video->new(url => "$tempdir/audio.wav", fragment_start => $prelen, duration => $mainlen), acopy => 1)->run();
SReview::Videopipe->new(inputs => [$full], output => SReview::Video->new(url => "$tempdir/preaudio.mkv", fragment_start => $prelen, duration => $mainlen), vcopy => 1)->run();
# Run bs1770gain (no class, maybe do that later)
system("bs1770gain -a -o $tempdir $tempdir/audio.wav");
my $preaudio = SReview::Video->new(url => "$tempdir/preaudio.mkv");
my $flac = SReview::Video->new(url => "$tempdir/audio.flac");
$map_v->input($preaudio);
$map_a->input($flac);
my $finalname = $output_coll->add_file(relname => $talk->relative_name . ".mkv");
SReview::Videopipe->new(inputs => [$preaudio, $flac], output => SReview::Video->new(url => $finalname->filename, audio_codec => detect_to_write($full->audio_codec)), vcopy => 1, acopy => 0, "map" => [$map_v, $map_a])->run();

my $update = $dbh->prepare("UPDATE talks SET progress='done', prelen = ?::interval, postlen = ?::interval WHERE id = ? AND state='cutting'");
$update->execute("$prelen_db seconds", "$postlen_db seconds", $talkid);

$finalname->store_file;

$dbh->commit;

=back

=head1 SEE ALSO

L<sreview-transcode>, L<sreview-previews>, L<sreview-skip>, L<sreview-config>

=cut
