###################################################################### # # 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."}, {'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' => "disabled", '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) = @_; $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", ""); $doc_obj->add_utf8_metadata ($section, "/srclink", ""); $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'}) { 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;