#!/usr/bin/perl -w

use strict;
use warnings;

use DBI;
use File::Basename;
use File::Copy;
use File::Temp qw/tempdir/;
use Media::Convert::Asset;
use Media::Convert::Asset::Concat;
use Media::Convert::AvSync;
use Media::Convert::Map;
use Media::Convert::Normalizer;;
use Media::Convert::Pipe;;
use SReview::Config::Common;
use SReview::Files::Factory;
use SReview::Talk;

=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 minuts just after the main
video into the pre and post videos.

=item *

Apply A/V sync correction values if any exist.

=item *

Perform audio normalization, if enabled in the configuration

=item *

Extract sample videos from all three channels into preview audio
streams, if enabled in the configuration

=item *

Move the talk to the next state.

=back

sreview-cut will B<never> re-encode the original video, and will
re-encode the original audio of the main video at most once, after audio
normalization if that was enabled in the configuration.

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

=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 overridden 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 $talkid = $ARGV[0];

=item audio_multiplex_mode

The way in which the primary and backup audio channels are encoded in
the video. Can be one of:

=over

=item stereo

The primary audio is in the left channel, and the backup audio is in the
right channel.

=item stream

The primary audio is in the first audio stream (a mono stream), and the
backup audio is in the second audio stream (also a mono stream).

=item none

There is only one audio stream, and if it is a stereo channel then the
right channel is not the backup audio.

=back

=cut

my $multiplex_mode = $config->get("audio_multiplex_mode");
my $maptype;
my $primary_audio;
my $backup_audio;
my $both_audio;

if($multiplex_mode eq "stereo") {
	$maptype = "channel";
	$primary_audio = "left";
	$backup_audio = "right";
	$both_audio = "both";
} elsif($multiplex_mode eq "stream") {
	$maptype = "astream";
	$primary_audio = "0";
	$backup_audio = "1";
	$both_audio = "-1";
} else {
	$maptype = "none";
	$primary_audio = "both";
	$backup_audio = "both";
	$both_audio = "both";
}

my $dbh = DBI->connect($config->get("dbistring"));

$dbh->begin_work;

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

$dbh->commit;

my $talk = SReview::Talk->new(talkid => $talkid);

if($talk->get_flag("is_injected")) {
		$dbh->prepare("UPDATE talks SET progress='broken', comments='tried to re-cut an injected talk' WHERE id = ?")->execute($talkid);
		exit;
}

my $corrections = $talk->corrections;

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

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

=item inputglob

The location of the (raw) input files. These files should have been put
into the database by way of C<sreview-detect>

=item pubdir

The location of the intermediate files that are published to reviewers.

=item accessmethods

The L<SReview::Files> implementation used to access the input files
and/or the intermediate files.

=cut

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

$output_coll->delete_files(relnames => [dirname($talk->relative_name)]);

foreach my $row(@{$talk->avs_video_fragments}) {
	next if($row->{raw_length_corrected} <= 0.5 && $row->{talkid} < 0);
	my $start;
	my $stop;
	my $target;
	my $segments;

	if($row->{talkid} == -1) {
		$target = "pre";
		$segments = \@segments_pre;
		$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(relname => $row->{raw_filename});
	my $input = Media::Convert::Asset->new(url => $input_file->filename);
	my $output = Media::Convert::Asset->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});
	}
	Media::Convert::Pipe->new(inputs => [$input], "map" => [Media::Convert::Map->new(input => $input, type => "allcopy")], output => $output, vcopy => 1, acopy => 1)->run();
	push @$segments, Media::Convert::Asset->new(url => $output->url);
}

my $pre = undef;
my $main = undef;
my $post = undef;

if(scalar(@segments_pre)>0) {
	$pre = Media::Convert::Asset::Concat->new(url => "$tempdir/pre.txt", components => \@segments_pre);
}
$main = Media::Convert::Asset::Concat->new(url => "$tempdir/main.txt", components => \@segments_main);
if(scalar(@segments_post)>0) {
	$post = Media::Convert::Asset::Concat->new(url => "$tempdir/post.txt", components => \@segments_post);
}

my $pre_new = undef;
my $main_new = Media::Convert::Asset->new(url => "$tempdir/main.mkv");
my $post_new = undef;
my @videos;

if(defined($pre)) {
	$pre_new = Media::Convert::Asset->new(url => "$tempdir/pre.mkv");
	Media::Convert::Pipe->new(inputs => [$pre], output => $pre_new, map => [Media::Convert::Map->new(input => $pre, type => "allcopy")], vcopy => 1, acopy => 1)->run();
	push @videos, $pre_new;
}
Media::Convert::Pipe->new(inputs => [$main], output => $main_new, map => [Media::Convert::Map->new(input => $main, type => "allcopy")], vcopy => 1, acopy => 1)->run();
push @videos, $main;
if(defined($post)) {
	$post_new = Media::Convert::Asset->new(url => "$tempdir/post.mkv");
	Media::Convert::Pipe->new(inputs => [$post], output => $post_new, map => [Media::Convert::Map->new(input => $post, type => "allcopy")], vcopy => 1, acopy => 1)->run();
	push @videos, $post_new;
}

$pre = $pre_new;
$post = $post_new;
$main = $main_new;

sub reload {
	@videos = ();
	if(defined($pre)) {
		$pre = Media::Convert::Asset->new(url => $pre->url);
		push @videos, $pre;
	}
	$main = Media::Convert::Asset->new(url => $main->url);
	push @videos, $main;
	if(defined($post)) {
		$post = Media::Convert::Asset->new(url => $post->url);
		push @videos, $post;
	}
}

reload();

my $samplestart = ($mainlen - $prelen) / 2 + $prelen - 30;
my $samplelen = 60;
$samplestart = ($samplestart > 0) ? $samplestart : 0;
$samplelen = ($samplelen > $mainlen) ? $mainlen : $samplelen;
my @choices = ($primary_audio, $backup_audio, $both_audio);
foreach my $stream(0, 1, 2) {
	my $sample_wav_file = $output_coll->add_file(relname => $talk->relative_name . "/audio$stream.wav");
	my $sample_wav = Media::Convert::Asset->new(url => $sample_wav_file->filename, fragment_start => $samplestart, duration => $samplelen);
	Media::Convert::Pipe->new(inputs => [$main], output => $sample_wav, map => [Media::Convert::Map->new(input => $main, type => $maptype, choice => $choices[$stream])], acopy => 0, vskip => 1)->run();
	$sample_wav_file->store_file;
	foreach my $codec(qw/mp3 ogg/) {
		my $tmp = $output_coll->add_file(relname => $talk->relative_name . "/audio$stream.$codec");
		Media::Convert::Pipe->new(inputs => [$sample_wav], output => Media::Convert::Asset->new(url => $tmp->filename), acopy => 0, vskip => 1)->run();
		$tmp->store_file;
	}
}

if($corrections->{offset_audio} != 0) {
	for my $vid(@videos) {
		my $tmp = Media::Convert::Asset->new(url => "$tempdir/temp.mkv");
		Media::Convert::AvSync->new(input => $vid, output => $tmp, value => $corrections->{offset_audio})->run();
		move($tmp->url, $vid->url);
	}
	reload();
}

if(!$talk->get_flag("keep_audio")) {
	Media::Convert::Normalizer->new(input => $main, output => Media::Convert::Asset->new(url => "$tempdir/temp.mkv"))->run();
	move("$tempdir/temp.mkv", $main->url);
	reload();
}

my $postlen_db = 0;
my $prelen_db = 0;
if(defined($pre)) {
	$prelen_db = $pre->duration;
	my $pre_file = $output_coll->add_file(relname => $talk->relative_name . "/pre.mkv");
	move($pre->url, $pre_file->filename);
	$pre_file->store_file;
} else {
	$output_coll->delete_files(relnames => [$talk->relative_name . "/pre.mkv"]);
}
if(defined($post)) {
	$postlen_db = $post->duration;
	my $post_file = $output_coll->add_file(relname => $talk->relative_name . "/post.mkv");
	move($post->url, $post_file->filename);
	$post_file->store_file;
} else {
	$output_coll->delete_files(relnames => [$talk->relative_name . "/post.mkv"]);
}

my $final = $output_coll->add_file(relname => $talk->relative_name . "/main.mkv");

move($main->url, $final->filename);

$final->store_file;

$dbh->begin_work;

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

$dbh->commit;

=back

=head1 SEE ALSO

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

=cut
