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

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

Forgot to commit the use of the perl function to find the filesize and set ex.FileSize meta.

  • Property svn:executable set to *
File size: 19.9 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 my $sys_file_size = -s $filename_full_path;
260 $doc_obj->set_metadata_element ($section, "FileSize", $sys_file_size); #$image_size);
261
262 $doc_obj->add_metadata ($section, "ImageType", $image_type);
263 $doc_obj->add_metadata ($section, "ImageWidth", $image_width);
264 $doc_obj->add_metadata ($section, "ImageHeight", $image_height);
265 $doc_obj->add_metadata ($section, "ImageSize", $size_str);
266
267 if ((defined $self->{'MaxImageWidth'})
268 && ($image_width > $self->{'MaxImageWidth'})) {
269 $self->{'MaxImageWidth'} = $image_width;
270 }
271 if ((defined $self->{'MaxImageHeight'})
272 && ($image_height > $self->{'MaxImageHeight'})) {
273 $self->{'MaxImageHeight'} = $image_height;
274 }
275
276 # srclink_file is now deprecated because of the "_" in the metadataname. Use srclinkFile
277 $doc_obj->add_metadata ($section, "srclink_file", $url_to_filename_no_path);
278 $doc_obj->add_metadata ($section, "srclinkFile", $url_to_filename_no_path);
279 $doc_obj->add_metadata ($section, "srcicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/$assocfilemeta/[srclinkFile]\" width=\"[ImageWidth]\" height=\"[ImageHeight]\">");
280
281 # Add the image as an associated file
282 $doc_obj->associate_file($filename_full_path, $filename_no_path, "image/$type", $section);
283
284 if ($self->{'create_thumbnail'} eq "true") {
285 $self->create_thumbnail($filename_full_path, $filehead, $doc_obj, $section, $assocfilemeta, $url_to_filehead);
286 }
287 if ($self->{'create_screenview'} eq "true") {
288 $self->create_screenview($filename_full_path, $filehead, $doc_obj, $section, $assocfilemeta, $url_to_filehead, $image_width, $image_height);
289 }
290
291 return 1;
292}
293
294sub create_thumbnail {
295 my $self = shift(@_);
296 my ($original_file, $filehead, $doc_obj, $section, $assocfilemeta, $url_to_filehead) = @_;
297 $url_to_filehead = $filehead unless defined $url_to_filehead;
298
299 my $thumbnailsize = $self->{'thumbnailsize'};
300 my $thumbnailtype = $self->correct_mime_type($self->{'thumbnailtype'});
301
302 # Generate the thumbnail with convert
303 my ($result,$thumbnailfile)
304 = $self->convert($original_file, $thumbnailtype, "-geometry $thumbnailsize" . "x$thumbnailsize", "THUMB");
305
306 # Add the thumbnail as an associated file ...
307 if (-e "$thumbnailfile") {
308 $doc_obj->associate_file("$thumbnailfile", $filehead."_thumb.$thumbnailtype",
309 "image/$thumbnailtype",$section); # name of generated image
310 $doc_obj->add_metadata ($section, "ThumbType", $thumbnailtype);
311 $doc_obj->add_utf8_metadata ($section, "Thumb", $url_to_filehead."_thumb.$thumbnailtype"); # url to generated image
312
313 $doc_obj->add_metadata ($section, "thumbicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/$assocfilemeta/[Thumb]\" width=[ThumbWidth] height=[ThumbHeight]>");
314
315
316 # Extract Thumbnail metadata from convert output
317 if ($result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
318 $doc_obj->add_metadata ($section, "ThumbWidth", $1);
319 $doc_obj->add_metadata ($section, "ThumbHeight", $2);
320 }
321 if ($self->{'store_file_paths'}) {
322 $self->{'thumb_file'} = $thumbnailfile;
323 }
324
325 } else {
326 my $outhandle = $self->{'outhandle'};
327 print $outhandle "Couldn't find thumbnail $thumbnailfile\n";
328
329 }
330}
331
332sub create_screenview {
333
334 my $self = shift(@_);
335 my ($original_file, $filehead, $doc_obj, $section, $assocfilemeta, $url_to_filehead, $image_width, $image_height) = @_;
336 $url_to_filehead = $filehead unless defined $url_to_filehead;
337
338 my $screenviewsize = $self->{'screenviewsize'};
339 my $screenviewtype = $self->correct_mime_type($self->{'screenviewtype'});
340
341 # Scale the image, unless the original image is smaller than the screenview size and -noscaleup is set
342 my $scale_option = "-geometry $screenviewsize" . "x$screenviewsize";
343 if ($self->{'noscaleup'} && $image_width < $screenviewsize && $image_height < $screenviewsize)
344 {
345 $scale_option = "";
346 }
347
348 # make the screenview image
349 my ($result,$screenviewfilename)
350 = $self->convert($original_file, $screenviewtype, $scale_option, "SCREEN");
351
352 #add the screenview as an associated file ...
353 if (-e "$screenviewfilename") {
354 $doc_obj->associate_file("$screenviewfilename", $filehead."_screen.$screenviewtype", "image/$screenviewtype",$section); # name of generated image
355 $doc_obj->add_metadata ($section, "ScreenType", $screenviewtype);
356 $doc_obj->add_utf8_metadata ($section, "Screen", $url_to_filehead."_screen.$screenviewtype"); # url to generated image
357
358 $doc_obj->add_metadata ($section, "screenicon", "<img src=\"_httpprefix_/collect/[collection]/index/assoc/$assocfilemeta/[Screen]\" width=[ScreenWidth] height=[ScreenHeight]>");
359
360 # get screenview dimensions, size and type
361 if ($result =~ m/[0-9]+x[0-9]+=>([0-9]+)x([0-9]+)/) {
362 $doc_obj->add_metadata ($section, "ScreenWidth", $1);
363 $doc_obj->add_metadata ($section, "ScreenHeight", $2);
364 } elsif ($result =~ m/([0-9]+)x([0-9]+)/) {
365 #if the image hasn't changed size, the previous regex doesn't match
366 $doc_obj->add_metadata ($section, "ScreenWidth", $1);
367 $doc_obj->add_metadata ($section, "ScreenHeight", $2);
368 }
369
370 if ($self->{'store_file_paths'}) {
371 $self->{'screen_file'} = $screenviewfilename;
372 }
373
374 } else {
375 my $outhandle = $self->{'outhandle'};
376 print $outhandle "Couldn't find screenview file $screenviewfilename\n";
377
378 }
379
380}
381
382
383
384sub convert {
385 my $self = shift(@_);
386 my $source_file_path = shift(@_);
387 my $target_file_type = shift(@_);
388 my $convert_options = shift(@_) || "";
389 my $convert_id = shift(@_) || "";
390 my $cache_mode = shift(@_) || "";
391
392 my $outhandle = $self->{'outhandle'};
393 my $verbosity = $self->{'verbosity'};
394
395 my $source_file_no_path = &File::Basename::basename($source_file_path);
396
397 # Determine the full name and path of the output file
398 my $target_file_path;
399 if ($self->{'enable_cache'}) {
400 my $cached_image_dir = $self->{'cached_dir'};
401 my $image_root = $self->{'cached_file_root'};
402 $image_root .= "_$convert_id" if ($convert_id ne "");
403 my $image_file = "$image_root.$target_file_type";
404 $target_file_path = &util::filename_cat($cached_image_dir,$image_file);
405 }
406 else {
407 $target_file_path = &util::get_tmp_filename($target_file_type);
408 push(@{$self->{'tmp_file_paths'}}, $target_file_path);
409
410 # Output filename used to be parsed from result line:
411 # my ($ofilename) = ($result =~ m/=>(.*\.$target_file_type)/);
412 # by the function that called 'convert'
413 # but this is no longer needed, as output filename is now
414 # explicitly passed back
415 }
416
417 # Generate and run the convert command
418 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\"";
419
420 my $print_info = { 'message_prefix' => $convert_id,
421 'message' => "Converting image $source_file_no_path to: $convert_id $target_file_type" };
422 $print_info->{'cache_mode'} = $cache_mode if ($cache_mode ne "");
423
424 my ($regenerated,$result,$had_error)
425 = $self->autorun_general_cmd($convert_command,$source_file_path,$target_file_path,$print_info);
426
427 return ($result,$target_file_path);
428}
429
430sub convert_without_result {
431 my $self = shift(@_);
432
433 my $source_file_path = shift(@_);
434 my $target_file_type = shift(@_);
435 my $convert_options = shift(@_) || "";
436 my $convert_id = shift(@_) || "";
437
438 return $self->convert($source_file_path,$target_file_type,
439 $convert_options,$convert_id,"without_result");
440}
441
442
443# Discover the characteristics of an image file with the ImageMagick
444# "identify" command.
445
446sub identify {
447 my ($image, $outhandle, $verbosity) = @_;
448
449 # Use the ImageMagick "identify" command to get the file specs
450 my $command = "\"".&util::get_perl_exec()."\" -S gs-magick.pl identify \"$image\" 2>&1";
451 print $outhandle "$command\n" if ($verbosity > 2);
452 my $result = '';
453 $result = `$command`;
454 print $outhandle "$result\n" if ($verbosity > 3);
455
456 # Read the type, width, and height
457 my $type = 'unknown';
458 my $width = 'unknown';
459 my $height = 'unknown';
460
461 my $image_safe = quotemeta $image;
462 if ($result =~ /^$image_safe (\w+) (\d+)x(\d+)/) {
463 $type = $1;
464 $width = $2;
465 $height = $3;
466 }
467
468 # Read the size
469 my $size = "unknown";
470 my $size_str="unknown";
471
472 if ($result =~ m/^.* ([0-9]+)b/i) {
473 $size_str="$1B"; # display string
474 $size = $1;
475 }
476 elsif ($result =~ m/^.* ([0-9]+)(\.([0-9]+))?kb?/i) {
477 # display string stays about the same
478 $size_str="$1";
479 $size_str.="$2" if defined $2;
480 $size_str.="KB";
481
482 $size = 1024 * $1;
483 if (defined($2)) {
484 $size = $size + (1024 * $2);
485 # Truncate size (it isn't going to be very accurate anyway)
486 $size = int($size);
487 }
488 }
489 elsif ($result =~ m/^.* ([0-9]+)(\.([0-9]+))?mb?/i) {
490 # display string stays about the same
491 $size_str="$1";
492 $size_str.="$2" if defined $2;
493 $size_str.="MB";
494
495 $size = 1024 * 1024 * $1;
496 if (defined($2)) {
497 $size = $size + (1024 * 1024 * $2);
498 # Truncate size (it isn't going to be very accurate anyway)
499 $size = int($size);
500 }
501 }
502 elsif ($result =~ m/^.* ((([0-9]+)(\.([0-9]+))?e\+([0-9]+))(kb|b)?)/i) {
503 # display string stays the same
504 $size_str="$1";
505
506 # Deals with file sizes on Linux of type "3.4e+02kb" where e+02 is 1*10^2.
507 # 3.4e+02 therefore evaluates to 3.4 x 1 x 10^2 = 340kb.
508 # Programming languages including Perl know how that 3.4e+02 is a number,
509 # so we don't need to do any calculations.
510 # $2 is just the number without the kb/b at the end.
511 $size = $2*1; # turn the string into a number by multiplying it by 1
512 #if we did $size = $1; $size would be merely the string "3.4e+02"
513 $size = int($size); # truncate size
514 }
515 print $outhandle "file: $image:\t $type, $width, $height, $size, $size_str\n"
516 if ($verbosity > 2);
517
518 # Return the specs
519 return ($type, $width, $height, $size, $size_str);
520}
521
522sub clean_up_temporary_files {
523 my $self = shift(@_);
524
525 foreach my $tmp_file_path (@{$self->{'tmp_file_paths'}}) {
526 if (-e $tmp_file_path) {
527 &util::rm($tmp_file_path);
528 }
529 }
530
531}
532
533# image/jpg is not a valid mime-type, it ought to be image/jpeg.
534# Sometimes JPEG is passed in also, want to keep things lowercase just in case.
535sub correct_mime_type {
536 my $self = shift(@_);
537 my ($file_extension) = @_;
538
539 $file_extension = lc($file_extension);
540 $file_extension =~ s/jpg/jpeg/s;
541
542 return $file_extension;
543}
544
5451;
Note: See TracBrowser for help on using the repository browser.