##########################################################################
#
# videoconvert.pm -- utility to help convert video file
#
# Copyright (C) 1999 DigiLib Systems Limited, NZ
#
# 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; either version 2 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
###########################################################################


package videoconvert;

use strict;
no strict 'refs'; # allow filehandles to be variables and viceversa

use baseconvert;

sub BEGIN {
    @videoconvert::ISA = ('baseconvert');
}


# Discover the characteristics of a video file.
#  Equivalent step to that in ImagePlugin that uses ImageMagicks's 'indentify' utility
#  Here we use 'ffmpeg' for video but for consistency keep the Perl method name the same 
#  as before


sub identify { 
    my ($video, $outhandle, $verbosity) = @_;

    # Use the ffmpeg command to get the file specs
    my $command = "ffmpeg -i \"$video\"";

    print $outhandle "  $command\n" if ($verbosity > 2);
    my $result = '';
    $result = `$command 2>&1`;
    print $outhandle "  $result\n" if ($verbosity > 4);

    # Read the type, width, and height etc.
    my $vtype =  'unknown';
    my $vcodec = 'unknown';
    my $width =  'unknown';
    my $height = 'unknown';
    my $fps    = 'unknown';

    my $atype =  'unknown';
    my $afreq =  'unknown';
    my $achan =  'unknown';
    my $arate =  'unknown';

    my $video_safe = quotemeta $video;

    # strip off everything up to filename
    $result =~ s/^.*\'$video_safe\'://s;

    if ($result =~ m/Video: (.*?) fps/m) {
	my $video_info = $1;
	if ($video_info =~ m/([^,]+),(?: ([^,]+),)? (\d+)x(\d+),.*?(\d+\.\d+)/)
	{
	    $vtype = $1;
	    $vcodec = $2 if defined $2;
	    $width = $3;
	    $height = $4;
	    $fps = $5;
	}
    }

##    if ($result =~ m/Video: (\w+), (\w+), (\d+)x(\d+),.*?(\d+\.\d+) fps/m) {
#    if ($result =~ m/Video: ([^,]+),(?: ([^,]+),)? (\d+)x(\d+),.*?(\d+\.\d+) fps/m) {
#	$vtype = $1;
#	$vcodec = $2;
#	$width = $3;
#	$height = $4;
#	$fps = $5;
#    }

    if ($result =~ m/Audio: (\w+), (\d+) Hz, (\w+)(?:, (\d+.*))?/m) {
	$atype = $1;
	$afreq = $2;
	$achan = $3;
	$arate = $4 if (defined $4);
    }

    # Read the duration
    my $duration = "unknown";
    if ($result =~ m/Duration: (\d+:\d+:\d+\.\d+)/m) {
	$duration = $1;
    }
    print $outhandle "  file: $video:\t $vtype, $width, $height, $duration\n" 
	if ($verbosity > 2);

    if ($verbosity >3) {
	print $outhandle "\t video codec=$vcodec, fps = $fps\n";
	print $outhandle "\t audio codec=$atype, freq = $afreq Hz, $achan, $arate\n";
    }

    # Return the specs
    return ($vtype, $width, $height, $duration, -s $video,
	    $vcodec,$fps,$atype,$afreq,$achan,$arate);
}


sub vob_durations
{
    my ($media_base_dir,$title_num,$outhandle) = @_;

    my $filter_re = sprintf("^VTS_%02d_[1-9]\\.VOB\$",$title_num);

    my $duration_info = {};

    if (opendir(VTSIN,$media_base_dir)) {
	my @vts_title_vobs = grep { $_ =~ m/$filter_re/ } readdir(VTSIN);
	closedir(VTSIN);

	foreach my $v (@vts_title_vobs) {
	    my $full_v = &util::filename_cat($media_base_dir,$v);

	    my ($vtype, $width, $height, $duration, $vsize,
		$vcodec,$fps,$atype,$afreq,$achan,$arate) = identify($full_v,$outhandle,0);

	    my ($vob_num) = ($v =~ m/^VTS_\d\d_(\d)\.VOB$/);

	    $duration_info->{$vob_num} = $duration;
	    print STDERR "**** $title_num: $title_num, storing {$vob_num} => $duration\n";

	}

    }
    else {
	print $outhandle "Warning: unable to read files in directory $media_base_dir\n";
    }
    
    return $duration_info;

}



sub new {
    my ($class) = shift @_;

    my ($base_dir,$video_filename, $verbosity,$outhandle,
	$exp_duration,$ascii_only_filenames) = @_;

    my $self = new baseconvert($base_dir,$video_filename,$verbosity,$outhandle);

    $self->{'exp_duration'} = $exp_duration;

    if (defined $ascii_only_filenames && ($ascii_only_filenames)) {
	my $file_root = $self->{'file_root'};
	$self->{'file_root'} = ascii_only_filename($file_root);
    }
				  
    my @ffmpeg_monitor   = ( 'monitor_init'   , "videoconvert::ffmpeg_monitor_init",
			     'monitor_line'   , "videoconvert::ffmpeg_monitor_line",
			     'monitor_deinit' , "videoconvert::ffmpeg_monitor_deinit" );
					
    my @flvtool2_monitor = ( 'monitor_init'   ,"monitor_init_unbuffered",
			     'monitor_line'   , "videoconvert::flvtool2_monitor_line",
			     'monitor_deinit' , "monitor_deinit_unbuffered" );
					
    $self->{'ffmpeg_monitor'} = \@ffmpeg_monitor;
    $self->{'flvtool2_monitor'} = \@flvtool2_monitor;


    return bless $self, $class;
}


sub ascii_only_filename
{
    my ($file) = @_;

    my $file_unicode = pack("U0C*", map { ord($_) } split(//,$file)); # force explicitly to unicode

    my @ascii_only_chars 
	= map { $_ >= 128 # if non-ascii
		    ? "" 
		    :  chr($_) } unpack("U*", $file_unicode); # unpack Unicode characters

    my $ascii_file = join("",@ascii_only_chars);

    if ($ascii_file eq "") {
	print STDERR "Warning: filename includes no ASCII characters\n";
	print STDERR "         Keeping as original filename\n";
	$ascii_file = $file;
    }

    return $ascii_file;
}


sub remove_difficult_chars
{
    my $self = shift @_;

    my ($file) = @_;

    # remove problematic characters from filename that make using it in a URL difficult

    my $file_unicode = pack("U0C*", map { ord($_) } split(//,$file)); # force explicitly to unicode

    my $url = $file_unicode;
    $url =~ s/\x{2018}|\x{2019}|\x{201C}|\x{201D}//g; # remove smart quotes as cause problem in URL for video server
    $url =~ s/\x{2013}/\-/g; # change en-dash to '-' as again causes problems for video server
    
    return $url;
}


sub url_safe
{
    my $self = shift @_;

    my ($file) = @_;
    
    my @url_utf8_chars 
	= map { $_ >= 128 # if non-ascii
		    ? "%" . sprintf("%02X", $_) 
		    :  chr($_) } unpack("U*", $file); # unpack Unicode characters

    my $url = join("",@url_utf8_chars);


    return $url;
}





sub optional_frame_scale
{
    my $self = shift (@_);
    my ($orig_size,$video_width,$video_height) = @_;

    my $s_opt = "";
    if ($video_width > $video_height) {
	if ($video_width > $orig_size) {
	    my $scale_factor = $orig_size/$video_width;
	    my $scaled_width = int($video_width * $scale_factor);
	    my $scaled_height = int($video_height * $scale_factor);

	    # round to be ensure multiple of 2 (needed by some codecs)
	    $scaled_width  = int($scaled_width/2)*2;
	    $scaled_height = int($scaled_height/2)*2;

	    $s_opt = "-s ${scaled_width}x${scaled_height}";
	}
	# else, video is smaller than requested size, don't scale up
    }
    else {
	if ($video_height > $orig_size) {
	    my $scale_factor = $orig_size/$video_height;
	    my $scaled_width = int($video_width * $scale_factor);
	    my $scaled_height = int($video_height * $scale_factor);

	    # round to be ensure multiple of 2 (needed by some codecs)
	    $scaled_width  = int($scaled_width/2)*2;
	    $scaled_height = int($scaled_height/2)*2;

	    $s_opt = "-s ${scaled_width}x${scaled_height}";
	}
	# else, video is smaller than requested size, don't scale up
	
    }
    
    return $s_opt;
}


sub keyframe_cmd
{
    my $self = shift (@_);
    my ($ivideo_filename) = @_;

    my $video_ext_dir = &util::filename_cat($ENV{'GSDLHOME'},"ext","video");

    my $output_dir = $self->{'cached_dir'};
    my $ivideo_root = $self->{'file_root'};

    my $oshot_filename = &util::filename_cat($output_dir,"shots.xml");

    my $exp_duration = $self->{'exp_duration'};
    my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";

    my $main_opts = "-y $t_opt";

    my $hive = &util::filename_cat($video_ext_dir,"lib","vhook","hive.so");

    my $oflash_filename = &util::filename_cat($output_dir,"$ivideo_root\_keyframe.flv");

    my $vhook_opts = "$hive  -o $oshot_filename -k $output_dir $ivideo_filename";

    my $ffmpeg_cmd = "ffkeyframe  $main_opts -vhook \"$vhook_opts\"   -i \"$ivideo_filename\" -an -y \"$oflash_filename\"";


    return ($ffmpeg_cmd,$oflash_filename);
}


sub stream_cmd
{
    my $self = shift (@_);
    my ($ivideo_filename,$video_width,$video_height,
	$streaming_quality,
	$streaming_bitrate,$streaming_size, 
	$opt_streaming_achan, $opt_streaming_arate) = @_;

    my $streaming_achan 
	= (defined $opt_streaming_achan) ? $opt_streaming_achan : 2;

    my $streaming_arate 
	= (defined $opt_streaming_arate) ? $opt_streaming_arate : 22050;

    my $output_dir = $self->{'cached_dir'};
    my $ivideo_root = $self->{'file_root'};

    my $oflash_file = "${ivideo_root}_stream.flv";
    my $oflash_filename = &util::filename_cat($output_dir,$oflash_file);

    my $s_opt = $self->optional_frame_scale($streaming_size,$video_width,$video_height);

    my $exp_duration = $self->{'exp_duration'};
    my $t_opt = (defined $exp_duration) ? "-t $exp_duration" : "";

    my $main_opts = "-y $t_opt";

    my $bitrate_opt = "-b $streaming_bitrate";
    ### my $stream_opts = "-r 25 $s_opt";
    my $stream_opts .= " $s_opt -ac $streaming_achan -ar $streaming_arate";

    # -flags +ilme+ildct' and maybe '-flags +alt' for interlaced material, and try '-top 0/1'

    my $all_opts = "$main_opts $stream_opts"; 

    my $ffmpeg_cmd;

    if ($streaming_quality eq "high") {

	my $pass_log_file = &util::filename_cat($output_dir,"$ivideo_root-logpass.txt");
	if (-e $pass_log_file) {
	    &util::rm($pass_log_file);
	}

	$all_opts .= " -passlogfile \"$pass_log_file\"";

	my $ffmpeg_cmd_pass1 = "ffmpeg -pass 1 -i \"$ivideo_filename\" $all_opts -y \"$oflash_filename\"";

	my $ffmpeg_cmd_pass2 = "ffmpeg -pass 2 -i \"$ivideo_filename\" $all_opts $bitrate_opt -y \"$oflash_filename\"";
	$ffmpeg_cmd = "( $ffmpeg_cmd_pass1 ; $ffmpeg_cmd_pass2 )";
    }
    else {
	# single pass

	$ffmpeg_cmd = "ffmpeg -i \"$ivideo_filename\" $all_opts -y \"$oflash_filename\"";
    }

    return ($ffmpeg_cmd,$oflash_filename,$oflash_file);
}



sub audio_excerpt_cmd
{
    my $self = shift (@_);
    my ($ivoa_filename,$hh,$mm,$ss,$opt_excerpt_len) = @_;

    # ivoa = input video or audio

    my $time_encoded = "$hh:$mm:$ss";
    my $time_encoded_file = "$hh$mm$ss";
    
    
    my $output_dir = $self->{'cached_dir'};
    my $ivoa_root = $self->{'file_root'};

    my $omp3_file = "${ivoa_root}_$time_encoded_file.mp3";
    my $omp3_filename = &util::filename_cat($output_dir,$omp3_file);

    my $all_opts = "-y -acodec mp3 -ss $time_encoded ";

    if (defined $opt_excerpt_len) {
	$all_opts .= "-t $opt_excerpt_len ";
    }


    my $ffmpeg_cmd = "ffmpeg -i \"$ivoa_filename\" $all_opts  \"$omp3_filename\"";

    return ($ffmpeg_cmd,$omp3_filename,$omp3_file);
}



sub streamseekable_cmd
{
    my $self = shift (@_);
    my ($oflash_filename) = @_;

    my $output_dir = $self->{'cached_dir'};
    my $ivideo_root = $self->{'file_root'};

    my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");

    my $flvtool_cmd = "flvtool2 -vUP \"$oflash_filename\"";

    return ($flvtool_cmd,$oflash_filename);
}


sub streamkeyframes_cmd
{
    my $self = shift (@_);
    my ($oflash_filename,$doc_obj,$section) = @_;

    my $assocfilepath 
	= $doc_obj->get_metadata_element($section,"assocfilepath");

    my $output_dir = $self->{'cached_dir'};

    my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");

    my $video_server = $ENV{'GEXT_VIDEO_SERVER'}; 
    my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};

    my $collect = $ENV{'GSDLCOLLECTION'};
 
    my $flvtool_cmd = "flvtool2 -vAUtP \"$cue_filename\" -thumbLocation:$video_server$video_prefix/collect/$collect/index/assoc/$assocfilepath \"$oflash_filename\"";


    return ($flvtool_cmd,$oflash_filename);
}


sub streamcuepts_cmd
{
    my $self = shift (@_);
    my ($oflash_filename) = @_;

    my $output_dir = $self->{'cached_dir'};

    my $cue_filename = &util::filename_cat($output_dir,"on_cue.xml");

    my $video_server = $ENV{'GEXT_VIDEO_SERVER'}; 
    my $video_prefix = $ENV{'GEXT_VIDEO_PREFIX'};

    my $collect = $ENV{'GSDLCOLLECTION'};
    my $thumbloc = "$video_server$video_prefix/collect/$collect";


#    my $flvtool_cmd = "flvtool2 -vUAtP \"$cue_filename\" -thumbLocation:$thumbloc \"$oflash_filename\"";

#    my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";



#    my $flvtool_cmd = "flvtool2 -vUAt \"$cue_filename\" \"$oflash_filename\"";


##    my $flvtool_cmd = "flvtool2 -vAt \"$cue_filename\" -UP \"$oflash_filename\" \"$output_dir/updated.flv\"";

##    my $flvtool_cmd = "flvtool2 -vAtU \"$cue_filename\" \"$oflash_filename\" \"$output_dir/updated.flv\"";

    my $flvtool_cmd = "flvtool2 -vAtUP \"$cue_filename\" \"$oflash_filename\"";

    return ($flvtool_cmd,$oflash_filename);
}


sub keyframe_thumbnail_cmd
{
    my $self = shift (@_);
    my ($ivideo_filename,$thumbnailfile,$thumbnailsize) = @_;

    my $output_dir = $self->{'cached_dir'};
    my $ivideo_root = $self->{'file_root'};

    my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);


    # Try for 4th keyframe, but fall back to 1st if doesn't exist
    my $key_filename = "${key_filename_prefix}_0003.jpg";
    $key_filename = "${key_filename_prefix}_0000.jpg" if (!-e $key_filename);

    my $command;

    if (-e $key_filename) {
	$command = "convert -interlace plane -verbose -geometry $thumbnailsize" 
	    . "x$thumbnailsize \"$key_filename\" \"$thumbnailfile\"";
    }
    else {
	# extractkeyframe has either not been switched on, or else had
	# a problem when running
	# => extract a from 
	# my $frame_rate = 1.0 / 60.0;


	$command = "ffmpeg -i \"$ivideo_filename\"  -ss 5.5 -vframes 1 -f image2 -s ${thumbnailsize}x${thumbnailsize} -y \"$thumbnailfile\"";

	# fmpeg -i input.dv -r 1 -f image2 -s 120x96 images%05d.png
    }

    return ($command,$thumbnailfile);
}


sub keyframe_montage_cmd
{
    my $self = shift (@_);
    my ($ivideo_filename,$montagefile) = @_;

    my $output_dir = $self->{'cached_dir'};
    my $ivideo_root = $self->{'file_root'};

    my $key_filename_prefix = &util::filename_cat($output_dir,$ivideo_root);

    my $options = "-tile 10  -geometry 75x62+2+2";

    my $command = "montage $options ${key_filename_prefix}_*.jpg \"$montagefile\"";

    return ($command,$montagefile);
}



sub parse_shot_xml
{
    my ($self) = shift(@_);

    my ($plugin) = @_;

    my $outhandle = $self->{'outhandle'};
    my $output_dir = $self->{'cached_dir'};

    my $shots_filename = &util::filename_cat($output_dir,"shots.xml");

    eval {
	$plugin->{'parser'}->parsefile($shots_filename);
    };
    
    if ($@) {
	print $outhandle "videoconvert.pm: skipping $shots_filename as not conformant to Hive shot syntax\n" if ($self->{'verbosity'} > 1);
	print $outhandle "\n Perl Error:\n $@\n" if ($self->{'verbosity'}>2);
	return 0;
    }

}

sub associate_keyframes_old
{
    my ($self) = shift(@_);

    my ($doc_obj,$section,$plugin) = @_;

    my $output_dir = $self->{'cached_dir'};

    my $count = 1;
    foreach my $kframe_file (@{$plugin->{'keyframe_fnames'}}) {

	my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
	$doc_obj->associate_file($kframe_filename,"keyframe$count.jpg","image/jpeg",
				 $section);
	$count++;
    }

    $doc_obj->add_utf8_metadata($section,"NumKeyframes",scalar(@{$plugin->{'keyframe_fnames'}}));


    # *****
    # $doc_obj->add_metadata ($section, "thumblist", $plugin->{'flowplayer_thumblist'});

}

sub associate_keyframes
{
    my ($self) = shift(@_);

    my ($doc_obj,$section,$plugin) = @_;

    my $output_dir = $self->{'cached_dir'};
    my $timeline = $plugin->{'keyframe_timeline'};
    
    my $count = 1;

    foreach my $t (sort { $timeline->{$a}->{'keyframeindex'} <=> $timeline->{$b}->{'keyframeindex'} } keys %$timeline)
    {
	my $kframe_file = $timeline->{$t}->{'thumb'};
	my $timestamp = $timeline->{$t}->{'timestamp'};

	my $kframe_filename = &util::filename_cat($output_dir,$kframe_file);
	$doc_obj->associate_file($kframe_filename,"keyframe$count.jpg","image/jpeg",
				 $section);
	$doc_obj->add_utf8_metadata($section,"KeyframeTimestamp",$timestamp);

	$count++;
    }

    $doc_obj->add_utf8_metadata($section,"NumKeyframes",scalar(@{$plugin->{'keyframe_fnames'}}));


    # *****
    # $doc_obj->add_metadata ($section, "thumblist", $plugin->{'flowplayer_thumblist'});
}



sub ffmpeg_monitor_init
{
    my $saved_record_sep = $/;
    $/ = "\r";

    my $saved_buffer_len = $|;
    $| = 1;

    my $saved_rec = { 'saved_record_sep' => $saved_record_sep,
		      'saved_buffer_len' => $saved_buffer_len };

    return $saved_rec;
}



sub ffmpeg_monitor_deinit
{
    my ($saved_rec) = @_;

    my $saved_record_sep = $saved_rec->{'saved_record_sep'};
    my $saved_buffer_len = $saved_rec->{'saved_buffer_len'};

    $/ = $saved_record_sep;
    $| = $saved_buffer_len;
}


sub ffmpeg_monitor_line
{
    my ($line) = @_;
    
    my $had_error = 0;
    my $generate_dot = 0;

    if ($line =~ m/^frame=/) 
    {
	$generate_dot = 1;
    }

    return ($had_error,$generate_dot);
}


sub flvtool2_monitor_line
{
    my ($line) = @_;

    my $had_error = 0;
    my $generate_dot = 1;

    if ($line =~ m/\s+\- /) {
	# ignore tabulated output printed at end of command
	$generate_dot = 0;
    }
    
    if ($line =~ m/^Error:/i) {
	$had_error = 1;
    }

    return ($had_error,$generate_dot);
}





1;
