Index: /gs3-extensions/mars-src/trunk/lib/python/pessentia.py
===================================================================
--- /gs3-extensions/mars-src/trunk/lib/python/pessentia.py	(revision 34360)
+++ /gs3-extensions/mars-src/trunk/lib/python/pessentia.py	(revision 34360)
@@ -0,0 +1,104 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+
+import essentia
+import essentia.standard
+import essentia.streaming
+
+argc = len(sys.argv)
+
+input_audio_filename     = None
+output_features_filename = None
+
+if argc <= 1:
+    print("Usage: "+sys.argv[0] +" input_file [output_file]\n",file=sys.stderr)    
+    sys.exit(1)
+else:
+    input_audio_filename    = sys.argv[1]
+    if argc == 2:
+        output_features_filename = os.path.splitext(input_audio_filename)[0]+'-features.json'
+    else:
+        output_features_filename = sys.argv[2]
+
+# Prepare to process the file        
+loader = essentia.standard.MonoLoader(filename=input_audio_filename)
+
+audio = loader()
+
+
+#from pylab import plot, show, figure, imshow
+#%matplotlib inline
+#import matplotlib.pyplot as plt
+#plt.rcParams['figure.figsize'] = (15, 6) # set plot sizes to something larger than default
+#
+#plot(audio[1*44100:2*44100])
+#plt.title("This is how the 2nd second of this audio looks like:")
+#show() # unnecessary if you started "ipython --pylab"
+
+
+from essentia.standard import *
+w = Windowing(type = 'hann')
+spectrum = Spectrum()  # FFT() would return the complex FFT, here we just want the magnitude spectrum
+mfcc = MFCC()
+
+logNorm = UnaryOperator(type='log')
+
+mfccs = []
+melbands = []
+melbands_log = []
+
+print("Processing Frames")
+
+for frame in FrameGenerator(audio, frameSize=1024, hopSize=512, startFromZero=True):
+    mfcc_bands, mfcc_coeffs = mfcc(spectrum(w(frame)))
+    mfccs.append(mfcc_coeffs)
+    melbands.append(mfcc_bands)
+    melbands_log.append(logNorm(mfcc_bands))
+
+# transpose to have it in a better shape
+# we need to convert the list to an essentia.array first (== numpy.array of floats)
+mfccs = essentia.array(mfccs).T
+melbands = essentia.array(melbands).T
+melbands_log = essentia.array(melbands_log).T
+
+
+## and plot
+#imshow(melbands[:,:], aspect = 'auto', origin='lower', interpolation='none')
+#plt.title("Mel band spectral energies in frames")
+#show()
+
+#imshow(melbands_log[:,:], aspect = 'auto', origin='lower', interpolation='none')
+#plt.title("Log-normalized mel band spectral energies in frames")
+#show()
+
+#imshow(mfccs[1:,:], aspect='auto', origin='lower', interpolation='none')
+#plt.title("MFCCs in frames")
+#show()
+
+
+pool = essentia.Pool()
+
+for frame in FrameGenerator(audio, frameSize = 1024, hopSize = 512, startFromZero=True):
+    mfcc_bands, mfcc_coeffs = mfcc(spectrum(w(frame)))
+    pool.add('lowlevel.mfcc', mfcc_coeffs)
+    pool.add('lowlevel.mfcc_bands', mfcc_bands)
+    pool.add('lowlevel.mfcc_bands_log', logNorm(mfcc_bands))
+
+#imshow(pool['lowlevel.mfcc_bands'].T, aspect = 'auto', origin='lower', interpolation='none')
+#plt.title("Mel band spectral energies in frames")
+#show()
+
+#imshow(pool['lowlevel.mfcc_bands_log'].T, aspect = 'auto', origin='lower', interpolation='none')
+#plt.title("Log-normalized mel band spectral energies in frames")
+#show()
+
+#imshow(pool['lowlevel.mfcc'].T[1:,:], aspect='auto', origin='lower', interpolation='none')
+#plt.title("MFCCs in frames")
+#show()
+
+
+output = YamlOutput(filename = output_features_filename, format = 'json' ) # otherwise defaults to YAML (.sig)
+output(pool)
+
Index: /gs3-extensions/mars-src/trunk/perllib/plugins/EssentiaPlugin.pm
===================================================================
--- /gs3-extensions/mars-src/trunk/perllib/plugins/EssentiaPlugin.pm	(revision 34360)
+++ /gs3-extensions/mars-src/trunk/perllib/plugins/EssentiaPlugin.pm	(revision 34360)
@@ -0,0 +1,174 @@
+###########################################################################
+#
+# EssentiaPlugin.pm -- for augmenting audio files with Essentia computed
+#                      features
+# 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 EssentiaPlugin;
+
+use BaseImporter;
+use pEssentiaExtractor;
+
+use strict;
+no strict 'refs'; # allow filehandles to be variables and viceversa
+no strict 'subs';
+
+use gsprintf 'gsprintf';
+
+sub BEGIN {
+    @EssentiaPlugin::ISA = ('BaseImporter', 'pEssentiaExtractor');
+}
+
+my $arguments =
+    [ 
+      { 'name' => "compute_mir_features",
+	'desc' => "{BaseImporter.compute_mir_features}",
+	'type' => "enum",
+	'list' => [{'name' => "true",  'desc' => "{common.true}"},
+		   {'name' => "false", 'desc' => "{common.false}"}],
+	'deft' => "false",
+	'reqd' => "no" },
+      { 'name' => "compute_essentia_features",
+	'desc' => "{BaseImporter.compute_essentia_features}",
+	'type' => "enum",
+	'list' => [{'name' => "true",  'desc' => "{common.true}"},
+		   {'name' => "false", 'desc' => "{common.false}"}],
+	'deft' => "false",
+	'reqd' => "no" },
+      { 'name' => "retrieve_mir_metadata",
+	'desc' => "{BaseImporter.retrieve_mir_metadata}",
+	'type' => "enum",
+	'list' => [{'name' => "true",  'desc' => "{common.true}"},
+		   {'name' => "false", 'desc' => "{common.false}"}],
+	'deft' => "false",
+	'reqd' => "no" },
+      { 'name' => "process_exp",
+	'desc' => "{BaseImporter.process_exp}",
+	'type' => "regexp",
+	'deft' => &get_default_process_exp(),
+	'reqd' => "no" }
+      ];
+
+my $options = { 'name'     => "EssentiaPlugin",
+		'desc'     => "{EssentiaPlugin.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);
+
+    
+    my $pee_self = new pEssentiaExtractor($pluginlist, $inputargs, $hashArgOptLists,1);
+
+    my $base_self = new BaseImporter($pluginlist, $inputargs, $hashArgOptLists);
+
+    my $self = BaseImporter::merge_inheritance($pee_self,$base_self);
+
+    return bless $self, $class;
+}
+
+sub get_default_process_exp {
+    my $self = shift (@_);
+    
+    return q^(?i)(\.mp3|\.wave?|\.aif[fc]?|\.au|\.snd|\.og[ga]|\.m4a)$^; 
+}
+
+sub begin {
+    my $self = shift (@_);
+    my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;
+
+    $self->SUPER::begin(@_);
+    $self->pEssentiaExtractor::begin(@_);
+}
+
+
+
+
+sub process_features {
+    my $self = shift (@_);
+    my ($base_dir, $file, $doc_obj) = @_;
+
+    my $outhandle = $self->{'outhandle'};
+    my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
+
+    my $top_section = $doc_obj->get_top_section();
+
+    if ($self->{'compute_essentia_features'} eq "true")
+    {
+	my $utf8_filename_no_path = $self->filepath_to_utf8($filename_no_path);
+	my $url_encoded_filename = &util::rename_file($utf8_filename_no_path, $self->{'file_rename_method'});
+	
+	my $features_filename = $self->compute_essentia_features($filename_full_path);
+	my ($features_ext) = ($features_filename =~ m/\.([^.]+)$/);
+
+	
+	my $mime_type = ($features_ext eq "json") ? "application/json" : "text/plain";
+	$doc_obj->associate_file($features_filename, "pessentia.$features_ext", 
+				 $mime_type, $top_section);
+
+    }
+
+}
+
+# do plugin specific processing of doc_obj
+sub process {
+    my $self = shift (@_);
+    my ($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli) = @_;
+
+    my $top_section = $doc_obj->get_top_section();
+
+    $self->process_features($base_dir,$file,$doc_obj);
+    
+    #we have no text - adds dummy text and NoText metadata
+    $self->add_dummy_text($doc_obj, $doc_obj->get_top_section());
+
+    my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
+    $doc_obj->associate_file($filename_full_path, "audio.mp3", "audio/mpeg", $top_section);
+
+    return 1;
+
+}
+
+#sub post_process_doc_obj {
+#    my $self = shift (@_);  
+#    my ($pluginfo, $base_dir, $file, $metadata, $doc_obj, $gli) = @_;
+#
+#    my $outhandle = $self->{'outhandle'};
+#    my ($filename_full_path, $filename_no_path) = &util::get_full_filenames($base_dir, $file);
+#
+#    return 1;
+#}
+
+
+1;
+
+
+
Index: /gs3-extensions/mars-src/trunk/perllib/plugins/pEssentiaExtractor.pm
===================================================================
--- /gs3-extensions/mars-src/trunk/perllib/plugins/pEssentiaExtractor.pm	(revision 34360)
+++ /gs3-extensions/mars-src/trunk/perllib/plugins/pEssentiaExtractor.pm	(revision 34360)
@@ -0,0 +1,285 @@
+###########################################################################
+#
+# pEssentiaExtractor - helper plugin that computers audio features for 
+#                        music information retrieval use, using python bindings of Essentia
+#
+# A component of the Greenstone digital library software
+# from the New Zealand Digital Library Project at the 
+# University of Waikato, New Zealand.
+#
+# Copyright (C) 2010 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 pEssentiaExtractor;
+
+use BaseMediaConverter;
+
+use Cwd;
+
+use FileUtils;
+
+use strict;
+no strict 'refs'; # allow filehandles to be variables and viceversa
+
+
+BEGIN {
+    @pEssentiaExtractor::ISA = ('BaseMediaConverter');
+}
+
+
+my $arguments = [
+    { 'name' => "window_size",
+      'desc' => "{jAudioExtractor.window_size}",
+      'type' => "int",
+      'range' => "128,",
+      'deft' => '512',
+      'reqd' => "no" },
+    { 'name' => "window_overlap",
+      'desc' => "{jAudioExtractor.window_overlap}",
+      'type' => "string",
+      'range' => "0.0",
+      'deft' => '0.0',
+      'reqd' => "no" },
+    { 'name' => "sample_rate",
+      'desc' => "{jAudioExtractor.sample_rate}",
+      'type' => "enum",
+      'list' => [{'name' => "8 kHz",      'desc' => "{jAudioExtractor.8000Hz}"},
+		 {'name' => "11.025 kHz", 'desc' => "{jAudioExtractor.11025Hz}"},
+		 {'name' => "16 kHz",     'desc' => "{jAudioExtractor.16000Hz}"},
+		 {'name' => "22.05 kHz",  'desc' => "{jAudioExtractor.22050Hz}"},
+		 {'name' => "44.1 kHz",   'desc' => "{jAudioExtractor.44100Hz}"} ],
+      'deft' => '16 kHz',
+      'reqd' => "no" },
+    { 'name' => "extracted_data",
+      'desc' => "{pEssentiaExtractor.extracted_data}", # ****** delete or change
+      'type' => "enum",
+      'list' => [{'name' => "Overall and Windowed", 'desc' => "{jAudioExtractor.overall_and_windowed}"},
+		 {'name' => "Windowed only", 'desc' => "{jAudioExtractor.windowed_only}"},
+		 {'name' => "Overall only", 'desc' => "{jAudioExtractor.overall_only}"}],
+      'deft' => 'Overall and Windowed',
+      'reqd' => "no" },
+    { 'name' => "output_type",
+      'desc' => "{pEssentiaExtractor.output_type}",
+      'type' => "enum",
+      'list' => [
+	  {'name' => "JSON", 'desc' => "{pEssentiaExtractor.json}"},
+	  {'name' => "CSV",  'desc' => "{pEssentiaExtractor.csv}"},
+	  {'name' => "YAML", 'desc' => "{pEssentiaExtractor.yaml}"},
+	  {'name' => "Weka ARFF", 'desc' => "{jAudioExtractor.weka_arff}"}
+	  ],
+      'deft' => 'JSON',
+      'reqd' => "no" }
+    ];
+
+
+
+my $options = { 'name' => "pEssentiaExtractor",
+		'desc' => "{pEssentiaExtractor.desc}",
+		'abstract' => "yes",
+		'inherits' => "yes",
+		'args' => $arguments };
+
+sub new {
+    my ($class) = shift (@_);
+    my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
+    push(@$pluginlist, $class);
+
+    push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
+    push(@{$hashArgOptLists->{"OptList"}},$options);
+
+    my $self = new BaseMediaConverter($pluginlist, $inputargs, $hashArgOptLists, 1);
+
+    # Set controlling variables
+    my $gsdl_home = $ENV{'GSDLHOME'};
+    my $music_ir_home = $ENV{'GEXT_MUSICIR'};
+
+    my $essentia_output_directory = &util::filename_cat($gsdl_home,"tmp"); # Set the directory to save the the ACE XML output files in
+    if (!FileUtils::directoryExists($essentia_output_directory)) {
+	FileUtils::makeDirectory($essentia_output_directory);
+    }
+    $self->{'essentia_output_directory'} = $essentia_output_directory;
+
+
+    return bless $self, $class;
+}
+
+
+# Create and save a temporary pEssentia batch file referring to a file to extract
+# features from
+sub prepareTempPEssentiaBatchFile 
+{
+    # ARG Ob1: $new_batch_file_path refers to the path of the temporary batch file to create
+    # ARG Ob2: $model_batch_file_path refers to the path of the model batch file to base the temporary one on
+    # ARG Ob3: $input_music_file_path refers to the file to extract features with
+    # ARG Ob4: $feature_values_file_path refers to the path of the ACE XML Feature Values file that the jMIR component will output to
+    # ARG Ob5: $feature_values_file_path refers to the path of the ACE XML Feature Descriptions file that the jMIR component will output to
+    my ( $self, $new_batch_file_path, $model_batch_file_path, $input_music_file_path, $feature_values_file_path, $feature_descriptions_file_path ) = @_;
+
+    # **** TODO
+    
+    # Retrieve settings for jAudioPlugin and use in batch file that is generated
+    my $sample_rate = $self->{'sample_rate'}; # sample of how to get parameter from Greenstone/GLI
+
+
+    # Read the contents of the model batch file
+    my $batch_file_contents;
+    local $/=undef;
+    open (INPUT, "$model_batch_file_path") or die "Could not read the model jAudio batch file $model_batch_file_path";
+    binmode INPUT;
+    $batch_file_contents = <INPUT>;
+    close INPUT;
+
+    # Set the batch ID tag in the temporary file
+    $batch_file_contents =~ s/<batch ID="SampleJAudioBatch">/<batch ID="$input_music_file_path">/;
+
+    # Set the input file name in the file tag in the temporary file
+    if ($^O eq "cygwin") {
+	$input_music_file_path = `cygpath -m "$input_music_file_path"`;
+	$input_music_file_path=~ s/\s+$//;
+    }
+    $batch_file_contents =~ s/<file><\/file>/<file>$input_music_file_path<\/file>/;
+
+    # Set the feature vales save path in the temporary file
+    if ($^O eq "cygwin") {
+	$feature_descriptions_file_path = `cygpath -m "$feature_descriptions_file_path"`;
+	$feature_descriptions_file_path=~ s/\s+$//;
+    }
+
+    $batch_file_contents =~ s/<destination><\/destination>/<destination>$feature_descriptions_file_path<\/destination>/;
+
+    # Set the feature vales save path in the temporary file
+    if ($^O eq "cygwin") {
+	$feature_values_file_path = `cygpath -m "$feature_values_file_path"`;
+	$feature_values_file_path=~ s/\s+$//;
+    }
+    $batch_file_contents =~ s/<destination><\/destination>/<destination>$feature_values_file_path<\/destination>/;
+
+    # Save the temporary batch file
+    open (OUTPUT, ">$new_batch_file_path") or die "Could not create the temporary jAudio batch file $new_batch_file_path";
+    print OUTPUT "$batch_file_contents";
+    close OUTPUT;
+    
+    # Done
+    return 0;
+}
+
+
+sub compute_essentia_features
+{
+    my $self = shift(@_);
+    my $source_file_path = shift(@_);
+    my $convert_options  = shift(@_) || "";
+
+    my $outhandle = $self->{'outhandle'};
+    my $verbosity = $self->{'verbosity'};
+
+    my $source_file_no_path = &File::Basename::basename($source_file_path);
+
+    print STDERR "******* Essentia: source_file_path = $source_file_path\n\n\n";
+    
+    $self->init_cache_for_file($source_file_path);
+
+    # Determine the full name and path of the output file
+    my $target_file_path;
+    my $feature_values_file_path;
+    my $feature_descriptions_file_path;
+
+    my $target_file_type;
+    if ($self->{'output_type'} eq "CSV") {
+	$target_file_type="csv";
+    }
+    elsif ($self->{'output_type'} eq "YAML") {
+	$target_file_type="xml";
+    }
+    elsif ($self->{'output_type'} eq "ARFF") {
+	$target_file_type="arff";
+    }
+    else {
+	# JSON
+	$target_file_type="json";
+    }
+
+    if ($self->{'enable_cache'}) {
+	my $cached_dir = $self->{'cached_dir'};
+	my $file_root = $self->{'cached_file_root'};
+
+	my $target_file = "$file_root.$target_file_type";
+
+	$target_file_path = &util::filename_cat($cached_dir,$target_file);
+    }
+    else {
+	$target_file_path = &util::get_tmp_filename($target_file_type);
+    }
+
+    # **** Do-over?
+#    if ($self->{'output_type'} eq "ACE XML") {
+#	$feature_values_file_path = $target_file_path;
+#	$feature_values_file_path =~ s/\.xml$/_FV.xml/;
+#
+#	$feature_descriptions_file_path = $target_file_path;
+#	$feature_descriptions_file_path =~ s/\.xml$/_FD.xml/;
+#
+#	# Make target_file_path be the principle file generated by jAudio when using ACE XML
+#	$target_file_path = $feature_values_file_path;
+#    }
+
+    my $essentia_output_directory = $self->{'essentia_output_directory'};
+
+    # **** Do-over
+
+	# Run the feature extraction.
+
+	# Specify the name for a temporary pEssentia batch file
+#	my $template_batch_file_path = &util::filename_cat($jmir_directory,"SampleJAudioBatchFile.xml.in");
+#	my $batch_file_path = &util::filename_cat($essentia_output_directory,"tempjaudiobatchfile.xml");
+	
+#	# Create the batch file
+#	$self->prepareTempPEssentiaBatchFile( $batch_file_path, $template_batch_file_path, 
+#					      $source_file_path, $feature_values_file_path, $feature_descriptions_file_path );
+	
+	# Input and Output files to use are stored in the batch_file
+#	my $batch_file_path_os = $batch_file_path;
+
+#	$batch_file_path_os = &util::makeFilenameJavaCygwinCompatible($batch_file_path_os);
+##	if ($^O eq "cygwin") {
+##	    $batch_file_path_os = `cygpath -w "$batch_file_path"`;
+##	    $batch_file_path_os =~ s/\s+$//;
+##	}
+
+	# ****
+	# my $pessentia_cmd = "java -Xmx1024M -jar jaudio.jar $convert_options -b \"$batch_file_path_os\"";
+	my $pessentia_cmd = "pessentia.sh $convert_options \"$source_file_path\" \"$target_file_path\"";
+		
+	# Test the execution path
+	print("EXECUTION CMD: $pessentia_cmd\n");
+    
+	my $print_info = { 'message_prefix' => "pEssentia",
+			   'message' => "Extracting audio features from $source_file_no_path" };
+	
+	my ($regenerated,$result,$had_error) 
+	    = $self->autorun_general_cmd($pessentia_cmd,$source_file_path,$target_file_path,$print_info);
+	
+#	# Delete the pEssentia batch file
+#	unlink($batch_file_path);
+
+    return ($target_file_path);
+}
+    
+
+
+
+1;	
