source: main/trunk/greenstone2/perllib/plugins/ImageConverter.pm@ 23335

Last change on this file since 23335 was 23335, checked in by davidb, 13 years ago

Work done on improving handing of filenames when the actualy filename encoding used is not necesarrily known. Tested for Linux. Work currently includes some debug statements that will be removed once testing for Windows and Mac is done.

  • Property svn:executable set to *
File size: 18.0 KB
Line 
1###########################################################################
2#
3# ImageConverter - helper plugin that does image conversion using ImageMagick
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 ImageConverter;
27
28use BaseMediaConverter;
29
30
31use strict;
32no strict 'refs'; # allow filehandles to be variables and viceversa
33
34use gsprintf 'gsprintf';
35
36BEGIN {
37 @ImageConverter::ISA = ('BaseMediaConverter');
38}
39
40my $arguments = [
41 { 'name' => "create_thumbnail",
42 'desc' => "{ImageConverter.create_thumbnail}",
43 'type' => "enum",
44 'list' => [{'name' => "true", 'desc' => "{common.true}"},
45 {'name' => "false", 'desc' => "{common.false}"}],
46 'deft' => "true",
47 'reqd' => "no" },
48 { 'name' => "thumbnailsize",
49 'desc' => "{ImageConverter.thumbnailsize}",
50 'type' => "int",
51 'deft' => "100",
52 'range' => "1,",
53 'reqd' => "no" },
54 { 'name' => "thumbnailtype",
55 'desc' => "{ImageConverter.thumbnailtype}",
56 'type' => "string",
57 'deft' => "gif",
58 'reqd' => "no" },
59 { 'name' => "noscaleup",
60 'desc' => "{ImageConverter.noscaleup}",
61 'type' => "flag",
62 'reqd' => "no" },
63 { 'name' => "create_screenview",
64 'desc' => "{ImageConverter.create_screenview}",
65 'type' => "enum",
66 'list' => [{'name' => "true", 'desc' => "{common.true}"},
67 {'name' => "false", 'desc' => "{common.false}"}],
68 'deft' => "true",
69 'reqd' => "no" },
70 { 'name' => "screenviewsize",
71 'desc' => "{ImageConverter.screenviewsize}",
72 'type' => "int",
73 'deft' => "500",
74 'range' => "1,",
75 'reqd' => "no" },
76 { 'name' => "screenviewtype",
77 'desc' => "{ImageConverter.screenviewtype}",
78 'type' => "string",
79 'deft' => "jpg",
80 'reqd' => "no" },
81 { 'name' => "converttotype",
82 'desc' => "{ImageConverter.converttotype}",
83 'type' => "string",
84 'deft' => "",
85 'reqd' => "no" },
86 { 'name' => "minimumsize",
87 'desc' => "{ImageConverter.minimumsize}",
88 'type' => "int",
89 'deft' => "100",
90 'range' => "1,",
91 'reqd' => "no" }
92 ];
93
94my $options = { 'name' => "ImageConverter",
95 'desc' => "{ImageConverter.desc}",
96 'abstract' => "yes",
97 'inherits' => "yes",
98 'args' => $arguments };
99
100sub new {
101 my ($class) = shift (@_);
102 my ($pluginlist,$inputargs,$hashArgOptLists) = @_;
103 push(@$pluginlist, $class);
104
105 push(@{$hashArgOptLists->{"ArgList"}},@{$arguments});
106 push(@{$hashArgOptLists->{"OptList"}},$options);
107
108 my $self = new BaseMediaConverter($pluginlist, $inputargs, $hashArgOptLists, 1);
109
110
111 return bless $self, $class;
112
113}
114
115# needs to be called after BasePlugin init, so that outhandle is set up.
116sub init {
117 my $self = shift(@_);
118
119 $self->{'tmp_file_paths'} = ();
120
121 # Check that ImageMagick is installed and available on the path
122 my $image_conversion_available = 1;
123 my $no_image_conversion_reason = "";
124 # None of this works very well on Windows 95/98...
125 if ($ENV{'GSDLOS'} eq "windows" && !Win32::IsWinNT()) {
126 $image_conversion_available = 0;
127 $no_image_conversion_reason = "win95notsupported";
128 } else {
129 my $result = `identify -help 2>&1`;
130 my $return_value = $?;
131 if ( ($ENV{'GSDLOS'} eq "windows" && $return_value == 256) || $return_value == -1) { # Linux and Windows return different values for "program not found"
132 $image_conversion_available = 0;
133 $no_image_conversion_reason = "imagemagicknotinstalled";
134 }
135 }
136 $self->{'image_conversion_available'} = $image_conversion_available;
137 $self->{'no_image_conversion_reason'} = $no_image_conversion_reason;
138
139 if ($self->{'image_conversion_available'} == 0) {
140 my $outhandle = $self->{'outhandle'};
141 &gsprintf($outhandle, "ImageConverter: {ImageConverter.noconversionavailable} ({ImageConverter.".$self->{'no_image_conversion_reason'}."})\n");
142 }
143
144}
145
146
147# convert image to new type if converttotype is set
148# generate thumbnails if required
149# generate screenview if required
150# discover image metadata
151# filename_no_path must be in utf8 and url-encoded
152sub generate_images {
153 my $self = shift(@_);
154 my ($filename_full_path, $filename_no_path, $doc_obj, $section, $filename_encoding) = @_;
155
156 # check image magick status
157 return 0 if $self->{'image_conversion_available'} == 0;
158
159 # check the filenames
160 return 0 if ($filename_no_path eq "" || !-f $filename_full_path);
161
162 if ($self->{'enable_cache'}) {
163 $self->init_cache_for_file($filename_full_path);
164 }
165 if ($self->{'store_file_paths'}) {
166 $self->{'orig_file'} = "";
167 $self->{'thumb_file'} = "";
168 $self->{'screen_file'} = "";
169 }
170 my $verbosity = $self->{'verbosity'};
171 my $outhandle = $self->{'outhandle'};
172
173 # check the size of the image against minimum size if specified
174 my $minimumsize = $self->{'minimumsize'};
175 if (defined $minimumsize && (-s $filename_full_path < $minimumsize)) {
176 print $outhandle "ImageConverter: \"$filename_full_path\" too small, skipping\n"
177 if ($verbosity > 1);
178 return 0; # or is there a better return value??
179 }
180
181 my $filehead = $filename_no_path;
182 $filehead =~ s/\.([^\.]*)$//; # filename with no extension
183 my $assocfilemeta = "[assocfilepath]";
184 if ($section ne $doc_obj->get_top_section()) {
185 $assocfilemeta = "[parent(Top):assocfilepath]";
186 }
187
188 # The images that will get generated may contain percent signs in their src filenames
189 # Encode those percent signs themselves so that urls to the imgs refer to them correctly
190 my $url_to_filehead = &unicode::filename_to_url($filehead);
191 my $url_to_filename_no_path = &unicode::filename_to_url($filename_no_path);
192
193 # Convert the image to a new type (if required).
194 my $converttotype = $self->{'converttotype'};
195 my $type = "unknown";
196
197 if ($converttotype ne "" && $filename_full_path !~ m/$converttotype$/) {
198# # $doc_obj->add_utf8_metadata($section, "Image", $utf8_filename_meta);
199
200 my ($result, $converted_filename_full_path)
201 = $self->convert($filename_full_path, $converttotype, "", "CONVERTTYPE");
202
203 $type = $converttotype;
204 $filename_full_path = $converted_filename_full_path;
205 $filename_no_path = "$filehead.$type";
206 $url_to_filename_no_path = "$url_to_filehead.$type";
207 if ($self->{'store_file_paths'}) {
208 $self->{'orig_file'} = $converted_filename_full_path;
209 }
210 }
211
212 # add Image metadata
213 $doc_obj->add_utf8_metadata($section, "Image", $url_to_filename_no_path); # url to generated image
214
215 # here we overwrite the original with the potentially converted one
216# $doc_obj->set_utf8_metadata_element($section, "Source", &unicode::url_decode($filename_no_path)); # displayname of generated image
217# $doc_obj->set_utf8_metadata_element($section, "SourceFile", $url_to_filename_no_path); # displayname of generated image
218
219# $self->set_Source_metadata($doc_obj,$url_to_filename_no_path,undef);
220
221 $self->set_Source_metadata($doc_obj,&unicode::url_decode($filename_no_path),
222 $filename_encoding);
223
224
225 # use identify to get info about the (possibly converted) image
226 my ($image_type, $image_width, $image_height, $image_size)
227 = &identify($filename_full_path, $outhandle, $verbosity);
228
229 if ($image_type ne " ") {
230 $type = $self->correct_mime_type($image_type);
231 }
232
233 #overwrite the ones added in BasePlugin
234 $doc_obj->set_metadata_element ($section, "FileFormat", $type);
235 $doc_obj->set_metadata_element ($section, "FileSize", $image_size);
236
237 $doc_obj->add_metadata ($section, "ImageType", $image_type);
238 $doc_obj->add_metadata ($section, "ImageWidth", $image_width);
239 $doc_obj->add_metadata ($section, "ImageHeight", $image_height);
240 $doc_obj->add_metadata ($section, "ImageSize", $image_size);
241
242 if ((defined $self->{'MaxImageWidth'})
243 && ($image_width > $self->{'MaxImageWidth'})) {
244 $self->{'MaxImageWidth'} = $image_width;
245 }
246 if ((defined $self->{'MaxImageHeight'})
247 && ($image_height > $self->{'MaxImageHeight'})) {
248 $self->{'MaxImageHeight'} = $image_height;
249 }
250
251 $doc_obj->add_metadata ($section, "srclink_file", $url_to_filename_no_path);
252 $doc_obj->add_metadata ($section, "srcicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/$assocfilemeta/[srclink_file]\" width=\"[ImageWidth]\" height=\"[ImageHeight]\">");
253
254 # Add the image as an associated file
255 $doc_obj->associate_file($filename_full_path, $filename_no_path, "image/$type", $section);
256
257 if ($self->{'create_thumbnail'} eq "true") {
258 $self->create_thumbnail($filename_full_path, $filehead, $doc_obj, $section, $assocfilemeta, $url_to_filehead);
259 }
260 if ($self->{'create_screenview'} eq "true") {
261 $self->create_screenview($filename_full_path, $filehead, $doc_obj, $section, $assocfilemeta, $url_to_filehead, $image_width, $image_height);
262 }
263
264 return 1;
265}
266
267sub create_thumbnail {
268 my $self = shift(@_);
269 my ($original_file, $filehead, $doc_obj, $section, $assocfilemeta, $url_to_filehead) = @_;
270 $url_to_filehead = $filehead unless defined $url_to_filehead;
271
272 my $thumbnailsize = $self->{'thumbnailsize'};
273 my $thumbnailtype = $self->correct_mime_type($self->{'thumbnailtype'});
274
275 # Generate the thumbnail with convert
276 my ($result,$thumbnailfile)
277 = $self->convert($original_file, $thumbnailtype, "-geometry $thumbnailsize" . "x$thumbnailsize", "THUMB");
278
279 # Add the thumbnail as an associated file ...
280 if (-e "$thumbnailfile") {
281 $doc_obj->associate_file("$thumbnailfile", $filehead."_thumb.$thumbnailtype",
282 "image/$thumbnailtype",$section); # name of generated image
283 $doc_obj->add_metadata ($section, "ThumbType", $thumbnailtype);
284 $doc_obj->add_utf8_metadata ($section, "Thumb", $url_to_filehead."_thumb.$thumbnailtype"); # url to generated image
285
286 $doc_obj->add_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/$assocfilemeta/[Thumb]\" width=[ThumbWidth] height=[ThumbHeight]>");
287
288
289 # Extract Thumbnail metadata from convert output
290 if ($result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
291 $doc_obj->add_metadata ($section, "ThumbWidth", $1);
292 $doc_obj->add_metadata ($section, "ThumbHeight", $2);
293 }
294 if ($self->{'store_file_paths'}) {
295 $self->{'thumb_file'} = $thumbnailfile;
296 }
297
298 } else {
299 my $outhandle = $self->{'outhandle'};
300 print $outhandle "Couldn't find thumbnail $thumbnailfile\n";
301
302 }
303}
304
305sub create_screenview {
306
307 my $self = shift(@_);
308 my ($original_file, $filehead, $doc_obj, $section, $assocfilemeta, $url_to_filehead, $image_width, $image_height) = @_;
309 $url_to_filehead = $filehead unless defined $url_to_filehead;
310
311 my $screenviewsize = $self->{'screenviewsize'};
312 my $screenviewtype = $self->correct_mime_type($self->{'screenviewtype'});
313
314 # Scale the image, unless the original image is smaller than the screenview size and -noscaleup is set
315 my $scale_option = "-geometry $screenviewsize" . "x$screenviewsize";
316 if ($self->{'noscaleup'} && $image_width < $screenviewsize && $image_height < $screenviewsize)
317 {
318 $scale_option = "";
319 }
320
321 # make the screenview image
322 my ($result,$screenviewfilename)
323 = $self->convert($original_file, $screenviewtype, $scale_option, "SCREEN");
324
325 #add the screenview as an associated file ...
326 if (-e "$screenviewfilename") {
327 $doc_obj->associate_file("$screenviewfilename", $filehead."_screen.$screenviewtype", "image/$screenviewtype",$section); # name of generated image
328 $doc_obj->add_metadata ($section, "ScreenType", $screenviewtype);
329 $doc_obj->add_utf8_metadata ($section, "Screen", $url_to_filehead."_screen.$screenviewtype"); # url to generated image
330
331 $doc_obj->add_metadata ($section, "screenicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/$assocfilemeta/[Screen]\" width=[ScreenWidth] height=[ScreenHeight]>");
332
333 # get screenview dimensions, size and type
334 if ($result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
335 $doc_obj->add_metadata ($section, "ScreenWidth", $1);
336 $doc_obj->add_metadata ($section, "ScreenHeight", $2);
337 } elsif ($result =~ m/([0-9]+)x([0-9]+)/) {
338 #if the image hasn't changed size, the previous regex doesn't match
339 $doc_obj->add_metadata ($section, "ScreenWidth", $1);
340 $doc_obj->add_metadata ($section, "ScreenHeight", $2);
341 }
342
343 if ($self->{'store_file_paths'}) {
344 $self->{'screen_file'} = $screenviewfilename;
345 }
346
347 } else {
348 my $outhandle = $self->{'outhandle'};
349 print $outhandle "Couldn't find screenview file $screenviewfilename\n";
350
351 }
352
353}
354
355
356
357sub convert {
358 my $self = shift(@_);
359 my $source_file_path = shift(@_);
360 my $target_file_type = shift(@_);
361 my $convert_options = shift(@_) || "";
362 my $convert_id = shift(@_) || "";
363 my $cache_mode = shift(@_) || "";
364
365 my $outhandle = $self->{'outhandle'};
366 my $verbosity = $self->{'verbosity'};
367
368 my $source_file_no_path = &File::Basename::basename($source_file_path);
369
370 # Determine the full name and path of the output file
371 my $target_file_path;
372 if ($self->{'enable_cache'}) {
373 my $cached_image_dir = $self->{'cached_dir'};
374 my $image_root = $self->{'cached_file_root'};
375 $image_root .= "_$convert_id" if ($convert_id ne "");
376 my $image_file = "$image_root.$target_file_type";
377 $target_file_path = &util::filename_cat($cached_image_dir,$image_file);
378 }
379 else {
380 $target_file_path = &util::get_tmp_filename($target_file_type);
381 push(@{$self->{'tmp_file_paths'}}, $target_file_path);
382
383 # Output filename used to be parsed from result line:
384 # my ($ofilename) = ($result =~ m/=>(.*\.$target_file_type)/);
385 # by the function that called 'convert'
386 # but this is no longer needed, as output filename is now
387 # explicitly passed back
388 }
389
390 # Generate and run the convert command
391 my $convert_command = "convert -interlace plane -verbose $convert_options \"$source_file_path\" \"$target_file_path\"";
392
393 my $print_info = { 'message_prefix' => $convert_id,
394 'message' => "Converting image $source_file_no_path to: $convert_id $target_file_type" };
395 $print_info->{'cache_mode'} = $cache_mode if ($cache_mode ne "");
396
397 my ($regenerated,$result,$had_error)
398 = $self->autorun_general_cmd($convert_command,$source_file_path,$target_file_path,$print_info);
399
400 return ($result,$target_file_path);
401}
402
403sub convert_without_result {
404 my $self = shift(@_);
405
406 my $source_file_path = shift(@_);
407 my $target_file_type = shift(@_);
408 my $convert_options = shift(@_) || "";
409 my $convert_id = shift(@_) || "";
410
411 return $self->convert($source_file_path,$target_file_type,
412 $convert_options,$convert_id,"without_result");
413}
414
415
416# Discover the characteristics of an image file with the ImageMagick
417# "identify" command.
418
419sub identify {
420 my ($image, $outhandle, $verbosity) = @_;
421
422 # Use the ImageMagick "identify" command to get the file specs
423 my $command = "identify \"$image\" 2>&1";
424 print $outhandle "$command\n" if ($verbosity > 2);
425 my $result = '';
426 $result = `$command`;
427 print $outhandle "$result\n" if ($verbosity > 3);
428
429 # Read the type, width, and height
430 my $type = 'unknown';
431 my $width = 'unknown';
432 my $height = 'unknown';
433
434 my $image_safe = quotemeta $image;
435 if ($result =~ /^$image_safe (\w+) (\d+)x(\d+)/) {
436 $type = $1;
437 $width = $2;
438 $height = $3;
439 }
440
441 # Read the size
442 my $size = "unknown";
443 if ($result =~ m/^.* ([0-9]+)b/) {
444 $size = $1;
445 }
446 elsif ($result =~ m/^.* ([0-9]+)(\.([0-9]+))?kb?/) {
447 $size = 1024 * $1;
448 if (defined($2)) {
449 $size = $size + (1024 * $2);
450 # Truncate size (it isn't going to be very accurate anyway)
451 $size = int($size);
452 }
453 }
454 elsif ($result =~ m/^.* ([0-9]+)(\.([0-9]+))?mb?/) {
455 $size = 1024 * 1024 * $1;
456 if (defined($2)) {
457 $size = $size + (1024 * 1024 * $2);
458 # Truncate size (it isn't going to be very accurate anyway)
459 $size = int($size);
460 }
461 }
462 elsif ($result =~ m/^.* (([0-9]+)(\.([0-9]+))?e\+([0-9]+))(kb|b)?/) {
463 # Deals with file sizes on Linux of type "3.4e+02kb" where e+02 is 1*10^2.
464 # 3.4e+02 therefore evaluates to 3.4 x 1 x 10^2 = 340kb.
465 # Programming languages including Perl know how that 3.4e+02 is a number,
466 # so we don't need to do any calculations.
467 $size = $1*1; # turn the string into a number by multiplying it by 1
468 #if we did $size = $1; $size would be merely the string "3.4e+02"
469 $size = int($size); # truncate size
470 }
471 print $outhandle "file: $image:\t $type, $width, $height, $size\n"
472 if ($verbosity > 2);
473
474 # Return the specs
475 return ($type, $width, $height, $size);
476}
477
478sub clean_up_temporary_files {
479 my $self = shift(@_);
480
481 foreach my $tmp_file_path (@{$self->{'tmp_file_paths'}}) {
482 if (-e $tmp_file_path) {
483 &util::rm($tmp_file_path);
484 }
485 }
486
487}
488
489# image/jpg is not a valid mime-type, it ought to be image/jpeg.
490# Sometimes JPEG is passed in also, want to keep things lowercase just in case.
491sub correct_mime_type {
492 my $self = shift(@_);
493 my ($file_extension) = @_;
494
495 $file_extension = lc($file_extension);
496 $file_extension =~ s/jpg/jpeg/s;
497
498 return $file_extension;
499}
500
5011;
Note: See TracBrowser for help on using the repository browser.