source: gs2-extensions/gsdl-audio/trunk/perllib/plugins/AudioConverter.pm@ 21012

Last change on this file since 21012 was 16659, checked in by davidb, 16 years ago

Start of Greenstone extension for audio support

File size: 9.3 KB
Line 
1###########################################################################
2#
3# AudioConverter - helper plugin that does audio conversion using sox
4#
5# A component of the Greenstone digital library software
6# from the New Zealand Digital Library Project at the
7# University of Waikato, New Zealand.
8#
9# Copyright (C) 2008 New Zealand Digital Library Project
10#
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation; either version 2 of the License, or
14# (at your option) any later version.
15#
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24#
25###########################################################################
26package AudioConverter;
27
28use MediaConverter;
29
30
31use strict;
32no strict 'refs'; # allow filehandles to be variables and viceversa
33
34use gsprintf 'gsprintf';
35
36BEGIN {
37 @AudioConverter::ISA = ('MediaConverter');
38}
39
40my $arguments = [
41 { 'name' => "cache_generated_audio",
42 'desc' => "{AudioConverter.cache_generated_audio}",
43 'type' => "flag",
44 'reqd' => "no",
45 'hiddengli' => "yes" # option to delete cache from GLI not yet implemented
46 }
47 ];
48
49my $options = { 'name' => "AudioConverter",
50 'desc' => "{AudioConverter.desc}",
51 'abstract' => "yes",
52 'inherits' => "yes",
53 'args' => $arguments };
54
55sub new {
56 my ($class) = shift (@_);
57 my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
58 push(@$pluginlist, $class);
59
60 push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
61 push(@{$hashArgOptLists->{"OptList"}},$options);
62
63 my $self = new MediaConverter($pluginlist, $inputargs, $hashArgOptLists, 1);
64
65
66 return bless $self, $class;
67
68}
69
70# needs to be called after BasePlugin init, so that outhandle is set up.
71sub init {
72 my $self = shift(@_);
73
74 $self->{'tmp_file_paths'} = ();
75
76 # Check that sox is installed and available on the path
77 my $audio_conversion_available = 1;
78 my $no_audio_conversion_reason = "";
79
80 # Conversion does not work very well on Windows 95/98...
81 # (because of distinction needed between stdout and stderr???)
82
83 if ($ENV{'GSDLOS'} eq "windows" && !Win32::IsWinNT()) {
84 $audio_conversion_available = 0;
85 $no_audio_conversion_reason = "win95notsupported";
86 } else {
87 my $result = `sox -h 2>&1`;
88 if ($? == -1 || $? == 256) { # Linux and Windows return different values for "program not found"
89 $audio_conversion_available = 0;
90 $no_audio_conversion_reason = "soxnotinstalled";
91 }
92 }
93 $self->{'audio_conversion_available'} = $audio_conversion_available;
94 $self->{'no_audio_conversion_reason'} = $no_audio_conversion_reason;
95
96 if ($self->{'audio_conversion_available'} == 0) {
97 my $outhandle = $self->{'outhandle'};
98 &gsprintf($outhandle, "AudioConverter: {AudioConverter.noconversionavailable} ({AudioConverter.".$self->{'no_audio_conversion_reason'}."})\n");
99 }
100}
101
102
103# convert audio to new type if converttotype is set
104# discover audio metadata
105sub generate_audio {
106 my $self = shift(@_);
107
108 my ($filename_full_path, $filename_no_path, $doc_obj, $section) = @_;
109
110 # check sox status
111 return 0 if $self->{'audio_conversion_available'} == 0;
112 # check the filenames
113 return 0 if ($filename_no_path eq "" || !-f $filename_full_path);
114
115 if ($self->{'cache_generated_audio'}) {
116 $self->init_cache_for_file($filename_full_path);
117 }
118
119 my $verbosity = $self->{'verbosity'};
120 my $outhandle = $self->{'outhandle'};
121
122
123 my $filehead = $filename_no_path;
124 $filehead =~ s/\.([^\.]*)$//; # filename with no extension
125 my $assocfilemeta = "[assocfilepath]";
126 if ($section ne $doc_obj->get_top_section()) {
127 $assocfilemeta = "[parent(Top):assocfilepath]";
128 }
129
130 # Convert the audio to a new type (if required).
131 my $converttotype = $self->{'converttotype'};
132 my $type = "unknown";
133
134 if ($converttotype ne "" && $filename_full_path !~ m/$converttotype$/) {
135
136 my ($result,$filename_full_path)
137 = $self->convert($filename_full_path, $converttotype, "", "CONVERTTYPE");
138
139 $type = $converttotype;
140 $filename_no_path = "$filehead.$type";
141 }
142
143 # add Audio metadata
144 $doc_obj->add_metadata($section, "Audio", $filename_no_path);
145
146 # Source and SourceUTF8 - should this be converted filename or original?
147 # here we overwrite the originals with converted ones
148 $self->set_Source_metadata($doc_obj, $filename_no_path);
149
150 # use identify to get info about the (possibly converted) audio
151
152 my ($audio_type, $audio_srate, $audio_quant, $audio_chans, $audio_dur, $audio_samples)
153 = &identify($filename_full_path, $outhandle, $verbosity);
154
155 if ($audio_type ne " ") {
156 $type = $audio_type;
157 }
158
159 my $file_size = -s $filename_full_path;
160 #overwrite the ones added in BasePlugin
161 $doc_obj->set_metadata_element ($section, "FileFormat", $type);
162 $doc_obj->set_metadata_element ($section, "FileSize", $file_size);
163
164 $doc_obj->add_metadata ($section, "AudioType", $audio_type);
165 $doc_obj->add_metadata ($section, "AudioSampleRate", $audio_srate);
166 $doc_obj->add_metadata ($section, "AudioQuantization", $audio_quant);
167 $doc_obj->add_metadata ($section, "AudioNumChannels", $audio_chans);
168 $doc_obj->add_metadata ($section, "AudioDuration", $audio_dur);
169 $doc_obj->add_metadata ($section, "AudioNumSamples", $audio_samples);
170
171 $doc_obj->add_metadata ($section, "srclink", "<a href=\"_httpprefix_/collect/[collection]/index/assoc/$assocfilemeta/[Audio]\">");
172 $doc_obj->add_metadata ($section, "/srclink", "</a>");
173 $doc_obj->add_metadata ($section, "srcicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/$assocfilemeta/_icon${type}_\" width=\"100\">");
174
175 # Add the audio as an associated file
176 $doc_obj->associate_file($filename_full_path, $filename_no_path, "audio/$type", $section);
177
178}
179
180
181
182sub convert {
183 my $self = shift(@_);
184 my $source_file_path = shift(@_);
185 my $target_file_type = shift(@_);
186 my $convert_options = shift(@_) || "";
187 my $convert_id = shift(@_) || "";
188
189 my $outhandle = $self->{'outhandle'};
190 my $verbosity = $self->{'verbosity'};
191
192 my $source_file_no_path = &File::Basename::basename($source_file_path);
193
194 # Determine the full name and path of the output file
195 my $target_file_path;
196 if ($self->{'cache_generated_audio'}) {
197 my $cached_audio_dir = $self->{'cached_dir'};
198 my $audio_root = $self->{'cached_file_root'};
199 $audio_root .= "_$convert_id" if ($convert_id ne "");
200 my $audio_file = "$audio_root.$target_file_type";
201 $target_file_path = &util::filename_cat($cached_audio_dir,$audio_file);
202 }
203 else {
204 $target_file_path = &util::get_tmp_filename($target_file_type);
205 push(@{$self->{'tmp_file_paths'}}, $target_file_path);
206 }
207
208 # Generate and run the convert command
209 my $vl = int($verbosity/2);
210
211 my $convert_command = "sox -V$vl --show-progress $convert_options \"$source_file_path\" \"$target_file_path\"";
212
213 my $print_info = { 'message_prefix' => $convert_id,
214 'message' => "Converting audio $source_file_no_path to: $convert_id $target_file_type" };
215
216 my ($regenerated,$result,$had_error)
217 = $self->autorun_general_cmd($convert_command,$target_file_path,$print_info);
218
219 return ($result,$target_file_path);
220}
221
222
223# Discover the characteristics of an audio file with sox
224# Name 'indentify' relates to original operation written for images using
225# ImageMagick's 'indentify'
226sub identify {
227 my ($audio, $outhandle, $verbosity) = @_;
228
229 # Use the 'sox -V3' command to get the file specs
230 my $command = "sox -V3 \"$audio\" 2>&1";
231 print $outhandle "$command\n" if ($verbosity > 2);
232 my $result = '';
233 $result = `$command`;
234 print $outhandle "$result\n" if ($verbosity > 3);
235
236 # Extract useful metadata about audio files
237 ## my $type = 'unknown'; # ... set this to something related to mime type?
238 ## allow plugin to specify mime-type info?
239
240 my $encod = 'unknown';
241 my $srate = 'unknown';
242 my $qsize = 'unknown';
243 my $chans = 'unknown';
244 my $durat = 'unknown';
245 my $sampl = 'unknown';
246
247 ($encod) = ($result =~ m/Sample Encoding\s*:\s+(.*)$/m);
248 ($srate) = ($result =~ m/Sample Rate\s*:\s+(.*)$/m);
249 ($qsize) = ($result =~ m/Sample Size\s*:\s+(\d+)$/m);
250 ($chans) = ($result =~ m/Channels\s*:\s+(.*)$/m);
251
252 my ($durat_full) = ($result =~ m/Duration\s*:\s+(.*)$/m);
253 ($durat,$sampl) = ($durat_full =~ m/^(.*?)\s+=\s+(\d+)/m);
254
255 print $outhandle "file: $audio:\t $encod, $srate, $qsize, $chans, $durat\n"
256 if ($verbosity > 2);
257
258 # Return the specs
259 return ($encod, $srate, $qsize, $chans, $durat, $sampl);
260}
261
262### Can this be moved into MediaConverter ???
263
264sub clean_up_temporary_files {
265 my $self = shift(@_);
266
267 foreach my $tmp_file_path (@{$self->{'tmp_file_paths'}}) {
268 if (-e $tmp_file_path) {
269 &util::rm($tmp_file_path);
270 }
271 }
272
273}
274
2751;
Note: See TracBrowser for help on using the repository browser.