######################################################################
#
# AudioPlugin.pm -- plugin for processing video largely based on ImagePlug
# A component of the Greenstone digital library software
# from the New Zealand Digital Library Project at the 
# University of Waikato, New Zealand.
#
# Copyright (C) 1999 New Zealand Digital Library Project
#
# 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 AudioPlugin;

use strict;
no strict 'refs'; # allow filehandles to be variables and viceversa
no strict 'subs';

use gsprintf;

use MultimediaPlugin;
use AudioConverter;

sub BEGIN {
    @AudioPlugin::ISA = ('MultimediaPlugin', 'AudioConverter');
}


my $enable_streaming_list =
    [{'name' => "disabled", 'desc' => "Do not create any audio file optimised for streaming over the Internet."}, # Don't move this entry - it's used as a sentinel value below
     {'name' => "flv", 'desc' => "Uses the FLV format for streaming media. Better to target old computers."},
     {'name' => "mp4", 'desc' => "Uses the MP4 container with H264 and AAC codecs. Better quality at very low bitrates but more ressources intensive."},
     {'name' => "mp3", 'desc' => "(audio only) Uses MP3 for psuedo streaming."}
     ];

my $streaming_mime_types = { "flv" => "video/flv",
			     "mp4" => "video/mp4",
			     "mp3" => "audio/mpeg" };

my $arguments =
    [ { 'name' => "process_exp",
	'desc' => "{BasePlugin.process_exp}",
	'type' => "regexp",
	'deft' => &get_default_process_exp(),
	'reqd' => "no" },
      { 'name' => "converttotype",
	'desc' => "{AudioPlugin.converttotype}",
	'type' => "string",
	'deft' => "",
	'reqd' => "no" },
      { 'name' => "converttobitrate",
	'desc' => "{AudioPlugin.converttobitrate}",
	'type' => "string",
	'deft' => "128k",
	'reqd' => "no" },
      { 'name' => "streamingbitrate",
	'desc' => "{AudioPlugin.streamingbitrate}",
	'type' => "string",
	'deft' => "200k",
	'reqd' => "no" },

      { 'name' => "enable_streaming",
        'desc' => "{MultimediaPlug.enable_streaming}",
        'type' => "enum",
        'list' => $enable_streaming_list,
	'deft' => @{$enable_streaming_list}[0]->{'name'},
	'reqd' => "no" }

];

my $options = { 'name'     => "AudioPlugin",
		'desc'     => "{AudioPlugin.desc}",
		'abstract' => "no",
		'inherits' => "yes",
		'args'     => $arguments };


sub new {
    my ($class) = shift (@_);
    my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
    push(@$pluginlist, $class);

    push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
    push(@{$hashArgOptLists->{"OptList"}},$options);

    new AudioConverter($pluginlist, $inputargs, $hashArgOptLists);
    my $self = new MultimediaPlugin($pluginlist, $inputargs, $hashArgOptLists);


    return bless $self, $class;
}


sub begin {
    my $self = shift (@_);
    my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;

    $self->SUPER::begin(@_);
    $self->AudioConverter::begin(@_); 
}


sub init {
    my $self = shift (@_);
    my ($verbosity, $outhandle, $failhandle) = @_;

    if ($self->{'enable_streaming'} eq @{$enable_streaming_list}[0]->{'name'})
    {
      print $outhandle "!Warning! AudioPlugin will generate no streamable audio as format set to \'disabled\'\n";
    }

    $self->SUPER::init(@_);
    $self->AudioConverter::init(@_); 
}


sub get_default_process_exp {
    my $self = shift (@_);

    return q^(?i)\.(mp3|au|aiff?|aifc|wav|ogg|flac|shn)$^; 
}



# Create the keyframes, thumbnail and screenview images, and discover
# the Video's size, width, and height using the ffmpeg utility.

sub run_convert {
    my $self = shift (@_);
    my $base_dir = shift (@_);
    my $filename = shift (@_);   # filename with full path
    my $file = shift (@_);       # filename without path
    my $doc_obj = shift (@_);

    my $section = $doc_obj->get_top_section();
    
    my $verbosity = $self->{'verbosity'};
    my $outhandle = $self->{'outhandle'};

    # check the filename is okay
    return 0 if ($file eq "" || $filename eq "");

    my $minimumsize = $self->{'minimumsize'};
    if (defined $minimumsize && (-s $filename < $minimumsize)) {
        print $outhandle "AudioPlugin: \"$filename\" too small, skipping\n"
	    if ($verbosity > 1);
    }

    my ($aduration,$asize,$atype,$afreq,$achan,$arate) 
	= &AudioConverter::identify($filename, $outhandle, $verbosity);

    if ($aduration eq "N/A") {
	print $outhandle "Unable to determine duration of $file\n";
	$aduration = undef;
    }

    my ($dur_hour,$dur_min,$dur_sec) 
	= ($aduration =~ m/(\d+):(\d+):(\d+\.\d+)/);

    my $total_dur_secs = undef;

    if (defined $dur_hour && defined $dur_min && defined *dur_sec) {
	$total_dur_secs = $dur_hour*3600 + $dur_min*60 + $dur_sec;
    }


    # Convert the audio to a new type (if required).
    my $converttotype = $self->{'converttotype'};
    my $converttosize = $self->{'converttosize'};

    # shorten duration prcessed for experimentation purposes
    my $exp_duration = undef;
    
    my $excerpt_duration = $self->{'excerpt_duration'};

    if ((defined $excerpt_duration) && ($excerpt_duration ne "")) {
	$exp_duration = $excerpt_duration;
	my ($hh,$mm,$ss,$ms) = ($exp_duration =~ m/^(\d\d):(\d\d):(\d\d)\.?(\d\d)?/);
	my $excerpt_dur_in_secs = $hh * 3600 + $mm * 60 + $ss;

	if (defined $total_dur_secs) {
	    if ($excerpt_dur_in_secs > $total_dur_secs) {
		# clip is already shorter than requested video excerpt duration
		# set exp_duration back to undefined
		$exp_duration = undef;
	    }
	}
	else {
	    $exp_duration = undef;
	}
    }


    if (defined $exp_duration)
    {
	print $outhandle "Only encoding first $exp_duration of video.\n";
	$self->{'exp_duration'} = $exp_duration;
    }

    my $ascii_only_filenames = $self->{'use_ascii_only_filenames'};

    $self->init_cache_for_file($filename);

    my $originalfilename = undef;
    my $type = "unknown";

    my $output_dir = $self->{'cached_dir'};
    my $iaudio_root = $self->{'cached_file_root'};

    my $convertto_regenerated = 0;
    if (($converttotype ne "" && $filename !~ m/$converttotype$/i)) {

	$originalfilename = $filename;

	my ($convertto_command,$ofilename,$ofile) = $self->audio_convertto_cmd($filename,$converttotype);

	my $convertto_result;
	my $convertto_error;
	
	my $convertto_options = { @{$self->{'ffmpeg_monitor'}},
				  'message_prefix' => "Convert to",
				  'message' => "Converting audio to $converttotype" }; 

	($convertto_regenerated,$convertto_result,$convertto_error)
	    = $self->run_cached_general_cmd($convertto_command,$filename,$ofilename,$convertto_options);
						    
	$type = $converttotype;
	$filename = $ofilename;
	$file = $ofile;

	($aduration,$asize,$atype,$afreq,$achan,$arate) 
	    = &AudioConverter::identify($ofilename, $outhandle, $verbosity);

	if ($aduration eq "N/A") {
	    print $outhandle "Unable to determine duration of converted $ofile\n";
	    $aduration = undef;
	}
    }
    

    # Add the audio metadata

    my $file_unicode = pack("U0C*", map { ord($_) } split(//,$file)); # force explicitly to unicode

    $file_unicode =~ s/\x{2018}|\x{2019}|\x{201C}|\x{201D}//g; # remove smart quotes as cause problem in URL for video server
    $file_unicode =~ s/\x{2013}/\-/g; # change en-dash to '-' as again causes problems for video server

##    print STDERR "**** file metadata = ", &gsprintf::debug_unicode_string($file_unicode), "\n";

    
    # split filename on char if specified
    if ($file_unicode =~ m/\-/) {

	my @file_split = split(/\s*\-\s*/,$file_unicode);
	my $creator = shift @file_split;
	my $title = shift @file_split;

	$title =~ s/\..*?$//;
	$title =~ s/^(.)/\u$1/;

	$doc_obj->add_utf8_metadata($section,"Title",$title);

	my @creator_split = split(/\s+and\s+/,$creator);
	foreach my $c (@creator_split) {
	    $doc_obj->add_utf8_metadata($section,"Creator",$c);
	}
    }

    $file = $file_unicode;
#    my $filemeta = $self->filename_to_utf8_metadata($file);
#    my $filemeta_url_safe = $self->url_safe($filemeta);

    my $filemeta_url_safe = &unicode::filename_to_url($file);

    $doc_obj->add_utf8_metadata ($section, "Audio", $filemeta_url_safe);

    # Also want to set filename as 'Source' metadata to be
    # consistent with other plugins
#    $doc_obj->add_utf8_metadata ($section, "Source", $filemeta_url_safe);


    if ($atype ne " ") {
	$type = $atype;
    }
    
    $doc_obj->add_metadata ($section, "FileFormat", $type);
    $doc_obj->add_metadata ($section, "FileSize",   $asize);

    $doc_obj->add_metadata ($section, "AudioType",     $atype);
    $doc_obj->add_metadata ($section, "AudioDuration", $aduration);

    $doc_obj->add_metadata ($section, "AudioFreq",     $afreq);
    $doc_obj->add_metadata ($section, "AudioChannels", $achan);
    $doc_obj->add_metadata ($section, "AudioRate",     $arate);

    $doc_obj->add_utf8_metadata ($section, "srclink", 
			    "<a href=\"_httpprefix_/collect/[collection]/index/assoc/[assocfilepath]/[Audio]\">");
    $doc_obj->add_utf8_metadata ($section, "/srclink", "</a>");
    $doc_obj->add_metadata ($section, "srcicon", "[AudioType]");

    # Add the image as an associated file
    $doc_obj->associate_file($filename,$file,"audio/$type",$section);




    my $streamable_regenerated = 0;
    my $optionally_run_general_cmd 
	= ($convertto_regenerated) ? "regenerate_general_cmd" : "run_cached_general_cmd";

    if ($self->{'enable_streaming'})
    {
      # The first entry in the "enabled_streaming_list" array should be "disabled"
      if ($self->{'enable_streaming'} ne @{$enable_streaming_list}[0]->{'name'})
      {
	my $enable_streaming = $self->{'enable_streaming'};

	# Even if the original file or 'converttotype' is set to MP3
	# we'll still go ahead and generate this MP3 as it might very
	# well have different settings (such as a lower sample rate)

	my $streaming_bitrate = $self->{'streamingbitrate'};
	my $streaming_size    = $self->{'streamingsize'};
	
	my $ifilename = $originalfilename || $filename;
	
	my ($stream_cmd,$mp_filename,$mp_file);
	if ($enable_streaming eq "mp4") {
	    ($stream_cmd,$mp_filename,$mp_file) 
		= $self->audio_mp4_stream_cmd($ifilename, $streaming_size,
					  $streaming_bitrate);
	}
	else {
	    ($stream_cmd,$mp_filename,$mp_file) 
		= $self->audio_stream_ffmpeg_cmd($ifilename, $enable_streaming,
					  $streaming_bitrate, $streaming_size,);

	}
	
	
	my $streamable_options = { @{$self->{'ffmpeg_monitor'}},
				   'message_prefix' => "Stream",
				   'message' => "Generating streamable audio: $mp_file" }; 
	
	my $streamable_result;
	my $streamable_had_error;
	($streamable_regenerated,$streamable_result,$streamable_had_error)
	    = $self->$optionally_run_general_cmd($stream_cmd,$ifilename,$mp_filename,$streamable_options);
	
	if (!$streamable_had_error)
        {
          my $streamable_url = $mp_file;
          my $streamable_url_safe = $self->url_safe($streamable_url);

          $doc_obj->add_utf8_metadata ($section, "streamableaudio", $streamable_url_safe);
          $doc_obj->associate_file($mp_filename,$mp_file,"audio/mpeg", $section);
	}

	# The following aren't currently used
	$self->{'mp_file'} = $mp_file;
	$self->{'mp_filename'} = $mp_filename;
      }
    }
    return $type;
}




sub read_into_doc_obj {
    my $self = shift (@_);  
    my ($pluginfo, $base_dir, $file, $block_hash, $metadata, $processor, $maxdocs, $total_count, $gli) = @_;

    $self->{'media_type'} = "audio";

    my ($rv,$doc_obj) = $self->SUPER::read_into_doc_obj(@_);

    if ($rv != 1) {
	return ($rv,$doc_obj);
    }
    
    $self->{'media_type'} = undef;

    return ($rv,$doc_obj);
}



1;











