Index: /gs3-extensions/structured-audio/trunk/bin/script/diarization.py
===================================================================
--- /gs3-extensions/structured-audio/trunk/bin/script/diarization.py	(revision 36211)
+++ /gs3-extensions/structured-audio/trunk/bin/script/diarization.py	(revision 36211)
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+from pyannote.audio import Pipeline
+from pathlib import Path
+import os, sys, time, csv, argparse
+
+# remove available graphic cards to trigger cpu default
+os.environ["CUDA_VISIBLE_DEVICES"] = "" 
+
+parser = argparse.ArgumentParser()
+parser.add_argument('inputfile', help="Audio input file")
+parser.add_argument('outputfile', nargs="?", help="Output file (.csv, optional)")
+parser.add_argument("--mingap", type=int, help="Minimum gap size in seconds between same-speaker segments", default=1)
+args = parser.parse_args()
+
+fileName = getattr(args, "inputfile")
+outputFile = getattr(args, "outputfile")
+gap_threshold = int(getattr(args, "mingap")); 
+
+# replace file extension with .csv
+p = Path(fileName)
+if (outputFile == None):
+    outputFile = "temp_" + str(p.with_suffix(".csv"))
+
+if (os.path.exists(fileName)):
+    print("starting pyannote pipeline with file: " + fileName)
+    timeStart = time.perf_counter() # timer for performance monitoring
+    FILEIN = {'audio': fileName}
+    fileName, fileExtension = os.path.splitext(fileName)
+    pipeline = Pipeline.from_pretrained('pyannote/speaker-diarization')
+    diarization = pipeline(FILEIN)
+    try:
+        with open(outputFile, mode="w") as out_file:
+            csv_writer = csv.writer(out_file, delimiter=',')
+            for turn, _, speaker in diarization.itertracks(yield_label=True):
+                csv_writer.writerow([speaker, round(turn.start, 1), round(turn.end, 1)])
+    except Exception as e:
+        print(e)
+
+    print("pipeline completed.")
+    print(f"processTime: {time.perf_counter()-timeStart:.1f}s")
+
+    print("starting gap-removal with file: " + outputFile)
+    print("minimum gap: " + str(gap_threshold) + "s")   
+    try:
+        with open(outputFile) as csv_file:
+            csv_reader = csv.reader(csv_file, delimiter=',')
+            names, starts, ends = [], [], []
+            for row in csv_reader: # convert csv to arrays
+                names.append(row[0])
+                starts.append(float(row[1]))
+                ends.append(float(row[2]))
+        os.remove(outputFile)
+    except Exception as e:
+        print(e)
+
+    try:
+        with open(outputFile.replace("temp_", ""), mode="w") as out_file:
+            num_items = len(names)
+            csv_writer = csv.writer(out_file, delimiter=',')
+            for i in range(1, num_items): # skip first line
+                if names[i] == names[i-1] and starts[i]-ends[i-1] < gap_threshold: # if prev and curr rows should be joined
+                    starts[i] = starts[i-1] # move prev start time to current
+                else:
+                    csv_writer.writerow([names[i-1], starts[i-1], ends[i-1]]) # write previous line to csv
+            csv_writer.writerow([names[num_items-1], starts[num_items-1], ends[num_items-1]]) # write last line
+    except Exception as e:
+        print(e) 
+
+    print("gap-removal completed.")
+
+else:
+    print("error: file " + fileName + "cannot be found")
Index: /gs3-extensions/structured-audio/trunk/perllib/plugins/StructuredAudioPlugin.pm
===================================================================
--- /gs3-extensions/structured-audio/trunk/perllib/plugins/StructuredAudioPlugin.pm	(revision 36211)
+++ /gs3-extensions/structured-audio/trunk/perllib/plugins/StructuredAudioPlugin.pm	(revision 36211)
@@ -0,0 +1,163 @@
+######################################################################
+#
+# 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 StructuredAudioPlugin;
+
+use strict;
+no strict 'refs'; # allow filehandles to be variables and viceversa
+no strict 'subs';
+
+use gsprintf;
+use FileUtils;
+
+use AudioPlugin;
+
+
+sub BEGIN {
+    @StructuredAudioPlugin::ISA = ('AudioPlugin');
+}
+
+my $arguments =
+    [ { 'name' => "segment_audio",
+	'desc' => "{StructuredAudioPlugin.segment_audio}",
+	'type' => "flag",
+	'reqd' => "no" }
+];
+
+my $options = { 'name'     => "StructuredAudioPlugin",
+		'desc'     => "{StructuredAudioPlugin.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 $self = new AudioPlugin($pluginlist, $inputargs, $hashArgOptLists);
+
+    return bless $self, $class;
+}
+
+
+sub begin {
+    my $self = shift (@_);
+    my ($pluginfo, $base_dir, $processor, $maxdocs) = @_;
+
+    $self->SUPER::begin(@_);
+}
+
+
+sub init {
+    my $self = shift (@_);
+    my ($verbosity, $outhandle, $failhandle) = @_;
+
+    $self->SUPER::init(@_);
+
+     my @pyannote_monitor   = ( 'monitor_init'   , "MultimediaConverter::unbuffered_monitor_init",
+                                'monitor_line'   , "StructuredAudioPlugin::pyannote_monitor_line",
+                                'monitor_deinit' , "MultimediaConverter::unbuffered_monitor_deinit" );
+                                        
+    $self->{'pyannote_monitor'} = \@pyannote_monitor;
+}
+
+
+sub pyannote_monitor_line
+{
+    my ($line) = @_;
+    
+    my $had_error = 0;
+    my $generate_dot = 0;
+        
+    if ($line =~ m/^.*$/) 
+    {
+        $generate_dot = 1;
+    }
+
+    return ($had_error,$generate_dot);
+}
+
+
+
+
+sub run_convert {
+    my $self = shift (@_);
+    my ($base_dir,$filename,$file,$doc_obj) = @_;
+    
+    my $section = $doc_obj->get_top_section();
+    
+    my $verbosity = $self->{'verbosity'};
+    my $outhandle = $self->{'outhandle'};
+
+   $self->SUPER::run_convert(@_);
+
+   if ($self->{'segment_audio'}) {
+
+        $self->init_cache_for_file($filename);
+        my $cached_audio_dir = $self->{'cached_dir'};
+	    # my $audio_root = $self->{'cached_file_root'};
+
+        # my $filename_no_path = &File::Basename::basename($filename);
+
+        my $ofile = "structured-audio.csv";
+        my $ofilename = &FileUtils::filenameConcatenate($cached_audio_dir,"structured-audio.csv");
+        my $pyannote_cmd = "diarization.py \"$filename\" \"$ofilename\"";
+
+        my $pyannote_regenerated;
+        my $pyannote_result;
+        my $pyannote_error;
+        
+        my $pyannote_options = { @{$self->{'pyannote_monitor'}},
+                    'message_prefix' => "pyannote",
+                    'message' => "Segmenting audio" }; 
+
+        ($pyannote_regenerated,$pyannote_result,$pyannote_error)
+            = $self->run_cached_general_cmd($pyannote_cmd,$filename,$ofilename,$pyannote_options);
+
+        $doc_obj->associate_file($ofilename,$ofile,"text/csv",$section);
+
+   }
+
+    return "csv";
+}
+
+
+1;
+
+
+
+
+
+
+
+
+
+
+
Index: /gs3-extensions/structured-audio/trunk/setup.bash
===================================================================
--- /gs3-extensions/structured-audio/trunk/setup.bash	(revision 36210)
+++ /gs3-extensions/structured-audio/trunk/setup.bash	(revision 36211)
@@ -48,8 +48,8 @@
   extdir=${GEXT_STRUCTURED_AUDIO##*/}
 
-  if [ "x$GSDL3EXTS" = "x" ] ; then
-    export GSDL3EXTS=$extdir
+  if [ "x$GSDLEXTS" = "x" ] ; then
+    export GSDLEXTS=$extdir
   else 
-    export GSDL3EXTS=$GSDL3EXTS:$extdir
+    export GSDLEXTS=$GSDLEXTS:$extdir
   fi
 
