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

Last change on this file since 25778 was 25778, checked in by ak19, 12 years ago

ex.ImageSize and ex.FileSize metadata were being set to the string unknown rather than the actual filesize returned by a call to imagick's identify. This was because the regex was too case-specific. Now ex.ImageSize and ex.FileSize are no longer the same: while ex.FileSize remains the bytevalue that was calculated, ex.ImageSize is now a display string.

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