source: extras/pLoader/trunk/src/Uploader/ImageList.pm @ 2730

Last change on this file since 2730 was 2730, checked in by ronosman, 16 years ago

Feature added: improved user feedback at the end of image processing.
Bug fixed : rotate resized images only once when images are uploaded several times.

  • Property svn:eol-style set to LF
File size: 23.2 KB
Line 
1# +-----------------------------------------------------------------------+
2# | pLoader - a Perl photo uploader for Piwigo                            |
3# +-----------------------------------------------------------------------+
4# | Copyright(C) 2008      Piwigo Team                  http://piwigo.org |
5# +-----------------------------------------------------------------------+
6# | This program is free software; you can redistribute it and/or modify  |
7# | it under the terms of the GNU General Public License as published by  |
8# | the Free Software Foundation                                          |
9# |                                                                       |
10# | This program is distributed in the hope that it will be useful, but   |
11# | WITHOUT ANY WARRANTY; without even the implied warranty of            |
12# | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
13# | General Public License for more details.                              |
14# |                                                                       |
15# | You should have received a copy of the GNU General Public License     |
16# | along with this program; if not, write to the Free Software           |
17# | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
18# | USA.                                                                  |
19# +-----------------------------------------------------------------------+
20package Uploader::ImageList;
21use strict;
22use Carp;
23use base qw/Uploader::Object/;
24use Image::ExifTool qw(:Public);
25use Image::Magick;
26use File::Spec;
27use Uploader::Image;
28use Data::Dumper;
29use Storable;
30# this class implements a collection of image files with associated data
31$|=1;
32__PACKAGE__->mk_accessors( 
33    qw/
34                thumb_size
35                preview_ratio
36                categories
37                type
38                filter
39                blur
40                quality
41                prefix
42                author
43                count
44                resize_w
45                resize_h
46                new_files
47                storable_file
48                wx_thumb_size
49                current_image
50                images
51                image_selection
52                exif_metadata
53                wx_thumb_imglist
54                wx_thumb_dir
55                preview_dir
56                site_resized_dir
57                site_thumb_dir
58                userdata_dir
59                progress_msg
60                last_error_msg
61                default_name_prefix
62                SetNewFilesViewerRefreshCallback
63                SetNewFilesProgressCallback
64                SetNewFilesDisplayEndInfoCallback
65                UploadImagesViewerCallback
66                progress_thumbnail_refresh
67                progress_msg_refresh
68                progressbar_refresh
69                progress_endinfo_refresh
70                ResizeCallback
71                upload_rejects
72                pwg
73                upload_high
74     /
75);
76
77
78sub Init {
79    my ( $self ) = @_; 
80}
81
82
83
84# save exif preview image if available
85# otherwise create a preview image
86sub _write_preview_image {
87    my ( $self, $imagedata ) = @_;
88
89
90    # If PreviewImage is available, we use it
91    if(defined $imagedata ) {
92        eval {
93            open PREVIEW_FILE, ">", $self->current_image->preview_file ;
94            binmode PREVIEW_FILE;
95            print PREVIEW_FILE $$imagedata;
96            close PREVIEW_FILE;
97        };
98        $self->last_error_msg($@) if $@;
99    }
100   
101}
102
103# Rotate exif preview if needed
104sub _rotate_image {
105    my ( $self, $file ) = @_;   
106
107}
108
109sub _set_exif_tag {
110    my ( $self, $file, $tag, $newValue ) = @_; 
111
112  my $options = {};
113  # Create a new Image::ExifTool object
114  my $exifTool = new Image::ExifTool;
115
116  # Extract meta information from an image
117  $exifTool->ExtractInfo($file, $options);
118
119  # Set a new value for a tag
120  $exifTool->SetNewValue($tag, $newValue);
121
122  # Write new meta information to a file
123  $exifTool->WriteInfo($file);
124
125}
126
127sub _set_current_image_filepaths {
128    my ( $self ) = @_;
129
130    my ( $vol, $dir, $file ) = File::Spec->splitpath(
131        $self->current_image->file
132    );
133
134    my ( $filename, $ext ) = split /\./, $file ;
135
136    $self->current_image->wx_thumb_file( 
137        File::Spec->catfile(
138            $self->wx_thumb_dir,
139            sprintf(
140                "%s.%s",
141                $filename,
142                $self->type,
143            )
144        )
145    );
146
147    $self->current_image->preview_file( 
148        File::Spec->catfile(
149            $self->preview_dir,
150            sprintf(
151                "%s.%s",
152                $filename,
153                $self->type,
154            )
155        )
156    );
157
158
159    $self->current_image->site_thumb_file( 
160        File::Spec->catfile(
161            $self->site_thumb_dir,
162            sprintf(
163                "%s.%s",
164                $filename,
165                $self->type,
166            )
167        )
168    );
169
170       
171}
172
173sub SetCurrentImage {
174    my ( $self, $indx ) = @_;   
175
176    $self->current_image(
177        $self->images->[$indx]
178    );
179}
180
181
182sub SetNewFiles {
183    my ( $self, $files ) = @_;
184
185    $self->new_files( $files );
186
187    # if some files have been previously selected
188    my $i = scalar @{$self->images};
189    my $count = 0;
190    $self->count($count);
191    my $errors = 0;
192    map {
193        # read exif metadata
194        my $info;
195
196
197        eval {
198            $info = ImageInfo( $_ );
199        };
200
201        $info = {} if($@);
202
203        $self->_add_image($_, $info, $i);       
204        $self->SetCurrentImage($i);
205        $self->_set_current_image_filepaths();
206
207       
208        # an exif preview is available. we use it
209        if(defined $info->{PreviewImage}){
210            $self->_write_preview_image( $info->{PreviewImage} );
211        }
212        # have to create a preview file
213        else {
214            eval {
215                if(!$self->CreateGUIPreview()){
216                    # use method provided by the caller
217                    # source, target, type, ratio
218                    $self->ResizeCallback->(
219                        $self->current_image->file,
220                        $self->current_image->preview_file,
221                        $self->type,
222                        $self->preview_ratio,
223                        undef,
224                        undef,
225                        $self->quality,
226                    );
227                }
228            };# create a preview file
229        }   
230
231        $self->RotateImage(
232            $self->current_image->preview_file,
233        );
234
235        $self->_set_exif_tag(
236            $self->current_image->preview_file,
237            'Orientation',
238            'Horizontal (normal)',
239        );
240
241
242        # Now, we should have a valid preview image.
243        # try to thumbnail it
244        eval {
245            # use the preview image to create a gui display thumbnail
246            $self->CreateGUIThumbnail();
247        };
248        # ok
249        if(!$@){
250            $self->progress_msg("Thumbnail and preview created for %s");
251            $i++;
252            $count++;
253            $self->count($count);
254        }
255        else {
256            $self->progress_msg("An error has occured when processing %s\n$@");
257            # remove from list
258            splice @{$self->images}, $i, 1;
259            $errors++;
260        }
261       
262        $self->SetNewFilesProgressCallback->();
263        $self->SetNewFilesViewerRefreshCallback->();
264
265    }
266    @{$files};
267    $self->SetNewFilesDisplayEndInfoCallback->(
268        sprintf(
269            "%s images added to the selection\n\n%s errors",
270            $self->count,
271            $errors
272           
273        )
274    );
275   
276    $self->Store;
277   
278}
279
280# key is file path
281sub _add_image {
282    my ( $self, $file, $info, $i ) = @_;       
283
284    my $image = Uploader::Image->new(
285        {
286            file              => $_,
287            site_name         => sprintf(
288                                     "%s %s", 
289                                     $self->default_name_prefix, 
290                                     $i,
291                                 ),
292            site_author       => $self->author,
293            exif_metadata     => $self->_select_exif_data($info),
294            add_rank          => $i,
295            site_categories   => [],
296            site_tags         => [],
297            site_high_file    => $_,
298        }
299    );
300
301    # append to image list
302    $self->images->[$i] = $image ;
303
304}
305
306
307sub RemoveImageSelection {
308    my ( $self ) = @_;
309   
310    return if (! scalar @{$self->images} );
311    return if (! defined $self->image_selection );
312   
313    # higher first, to keep same indexes during remove
314    my @images = reverse @{$self->image_selection};     
315    map {
316        my $image = $self->images->[$_]->file;
317        splice @{$self->images}, $_, 1 ;
318        $self->wx_thumb_imglist->Remove($_);
319        shift @images;
320    }
321    @images;
322   
323    # clear image selection
324    $self->image_selection([]);
325}
326
327# used for display in GUI. has to fit a square box ( wxImageList )
328sub CreateGUIThumbnail {
329    my ( $self ) = @_;
330
331    return 1 if( -e $self->current_image->wx_thumb_file );
332    my $rval = 0;
333
334    my $image = new Image::Magick;
335
336    my $size = $self->wx_thumb_size;
337
338    my $status = $image->Set(size=>sprintf("%sx%s", 3*$size, 3*$size));
339    warn "$status" if $status ;
340
341    $status = $image->ReadImage(
342        $self->current_image->preview_file
343    );
344    warn "$status" if $status;
345    return $rval if $status;
346
347    $status = $image->Thumbnail(
348        geometry=>sprintf("%s%s>", $size*$size, '@')
349    );
350    warn "$status" if $status;
351    return $rval if $status;
352
353    $status = $image->Set(background=>"white");
354    warn "$status" if $status ;
355
356    $status = $image->Set(Gravity=>"Center");
357    warn "$status" if $status ;
358
359    $image->Extent(
360        geometry=>sprintf("%sx%s", $size, $size),
361        gravity=>'center',
362    );
363
364    $image->Set(quality=>100);
365
366    $status = $image->Strip();
367    warn "$status" if $status ;
368   
369
370    $image->Write(
371        sprintf(
372            "%s:%s",
373            $self->type,
374            $self->current_image->wx_thumb_file,
375        )
376    );
377
378    undef $image;
379   
380    $rval = 1;
381   
382    return $rval;
383}
384
385
386sub CreateGUIPreview {
387    my ( $self ) = @_;
388
389    return 1 if( -e $self->current_image->preview_file );
390   
391    my $rval = 1;
392
393    my $image = Image::Magick->new();
394
395    my $ratio = $self->preview_ratio;
396
397
398    my $status = $image->Read(
399        $self->current_image->file
400    );
401    warn "$status ", $self->current_image->file, "\n" if $status ;
402    return 0 if $status;
403
404    $status = $image->Thumbnail(
405        geometry=>sprintf(
406                              "%s%%x%s%%>", 
407                              $ratio, 
408                              $ratio
409                         )
410    );
411    warn "$status" if $status ;
412    return 0 if $status;
413
414
415    $status = $image->Set(background=>"white");
416    warn "$status" if $status ;
417
418    $status = $image->Set(Gravity=>"Center");
419    warn "$status" if $status ;
420
421
422    $image->Set(quality=>$self->quality);
423
424
425    $status = $image->Write(
426        sprintf(
427            "%s:%s",
428            $self->type,
429            $self->current_image->preview_file,
430        )
431    );
432    warn "$status" if $status ;
433    return 0 if $status;
434   
435    undef $image;
436
437    return $rval;
438}
439
440
441sub CreateResized {
442    my ( $self ) = @_;
443   
444    my $rval = 1 ;
445    return $rval if( -e $self->current_image->site_resized_file );
446   
447
448    my $image = new Image::Magick;
449
450    my $status = $image->ReadImage(
451        $self->current_image->file
452    );
453    warn "$status" if $status ;
454    return 0 if $status;
455
456    my $w = $image->Get('width');
457    my $h = $image->Get('height');
458       
459    # should calculate the aspect ratio
460    my $resize_w = $self->resize_w;
461    my $resize_h = $self->resize_h;
462       
463    if( $w < $h ){
464        my $resize_w_ = $resize_w;
465        $resize_w = $resize_h;
466        $resize_h = $resize_w_;
467    }
468   
469    $status = $image->Resize(
470        geometry => sprintf("%sx%s>", $resize_w, $resize_h), 
471        filter => sprintf("%s", $self->filter), 
472        blur => $self->blur
473    );
474    warn "$status" if $status ;
475    return 0 if $status;
476
477    $status = $image->Set(Gravity=>"Center");
478    warn "$status" if $status ;
479
480    # exif from original image
481    my $orientation = $self->current_image->exif_metadata->{Orientation};
482   
483    # Valid for Rotate 180, Rotate 90 CW, Rotate 270 CW
484    if( $orientation =~ m/Rotate (\d+)/ ){
485        printf(
486            "Rotate %s\n",
487            $1
488        );
489   
490        $image->Rotate( degrees=>$1 ); 
491    }
492
493    $status = $image->Set(quality=>$self->quality);
494    warn "$status" if $status ;
495
496    $image->Write(
497        sprintf(
498            "%s:%s",
499            $self->type,
500            $self->current_image->site_resized_file,
501        )
502    );
503    warn "$status" if $status ;
504    return 0 if $status;
505   
506    undef $image;
507
508   
509    $rval = 0 if $status;
510
511    return $rval;
512}
513
514sub CreateThumbnail {
515    my ( $self ) = @_;
516   
517    return 1 if( -e $self->current_image->site_thumb_file );
518   
519    my $rval = 1;
520
521    my $image = new Image::Magick;
522
523    my $status = $image->ReadImage(
524        $self->current_image->site_resized_file
525    );
526    warn "$status" if $status ;
527
528   
529    $status = $image->Resize(
530        geometry => sprintf(
531                                "%sx%s>", 
532                                $self->thumb_size, 
533                                $self->thumb_size
534                           ),
535    );
536    warn "$status" if $status ;
537
538    $status = $image->Set(Gravity=>"Center");
539    warn "$status" if $status ;
540
541    $status = $image->Set(quality=>$self->quality);
542    warn "$status" if $status ;
543
544    $status = $image->Strip();
545    warn "$status" if $status ;
546
547
548    $image->Write(
549        sprintf(
550            "%s:%s",
551            $self->type,
552            $self->current_image->site_thumb_file,
553        )
554    );
555   
556    undef $image;
557
558
559    $rval = 0 if $status;
560
561    return $rval;
562}
563
564
565
566sub _select_exif_data {
567    my ( $self, $exif ) = @_;
568
569    return {
570        map {
571            $_ => $exif->{$_},
572        }
573        qw/
574            CreateDate
575            ImageWidth
576            ImageHeight
577            Orientation
578            DateTimeOriginal
579            ISO
580            ExposureTime
581            ApertureValue
582            FocalLength
583            Lens
584            Exposure
585            Make
586            Model
587        /
588    };   
589}
590
591sub Store {
592    my ( $self ) = @_;
593   
594    my $data = $self->get_storable(
595        [ 
596            qw/
597                images
598                thumb_size
599                preview_ratio
600                type
601                filter
602                blur
603                quality
604                prefix
605                author
606                count
607                resize_w
608                resize_h
609                new_files
610                storable_file
611                wx_thumb_size
612                current_image
613                images
614                exif_metadata
615                wx_thumb_dir
616                preview_dir
617                site_resized_dir
618                site_thumb_dir
619                userdata_dir
620                progress_msg
621                default_name_prefix
622                upload_high
623            /
624        ] 
625   
626    );
627    eval {
628        store $data, $self->storable_file;
629    };
630    if($@){
631        print $@, "\n"; 
632    }
633}
634
635
636
637sub UploadSelection {
638    my ( $self ) = @_; 
639
640    my $viewer_callback = $self->UploadImagesViewerCallback ;
641
642
643    $self->upload_rejects(
644        []
645    );
646
647    my $count = 1;
648    my $msg;
649    $self->count(
650        $count
651    );
652    my $uploaded = 0;
653    my $rejected = 0;
654    my $time_begin = time;
655    my $last_error;
656    map {
657        # current image object         
658        $self->current_image(
659            $self->images->[$_]
660        );
661
662        my ( $vol, $dir, $file ) = File::Spec->splitpath(
663            $self->current_image->file
664        );
665       
666        my $site_name = $self->current_image->site_name;
667   
668        my ( $filename, $ext ) = split /\./, $file ;
669
670        # lately defined to make sure we have the last global properties ( resize_w, resize_h )
671        $self->current_image->site_resized_file( 
672            File::Spec->catfile(
673                $self->site_resized_dir,
674                sprintf(
675                    "%s_%sx%s.%s",
676                    $filename,
677                    $self->resize_w,
678                    $self->resize_h,
679                    $self->type,
680                )
681            )
682        );
683       
684        $msg = sprintf(
685            "Preparing resized image for %s - %s",
686            $site_name,
687            $file,
688        ); 
689
690        eval {
691            # set current image thumbnail
692            $self->progress_thumbnail_refresh->();
693
694            $self->progress_msg_refresh->($msg);
695   
696            # update upload progress dialog
697            $self->progressbar_refresh->(0.25);
698        };
699        # user cancelled : dialog box is destroyed
700        croak "Upload cancelled." if $@ ;
701
702        eval {
703            if(!$self->CreateResized()){
704                printf("CreateResized failed %s. Use ResizeCallback\n", $@);
705                # use method provided by the caller
706                # source, target, type, ratio, width, $height
707                $self->ResizeCallback->(
708                    $self->current_image->file,
709                    $self->current_image->site_resized_file,
710                    $self->type,
711                    undef,
712                    $self->resize_w,
713                    $self->resize_h,
714                    $self->quality,
715                );
716               
717                $self->RotateImage(
718                    $self->current_image->site_resized_file,
719                );
720            }
721        };
722
723        $self->_set_exif_tag(
724            $self->current_image->site_resized_file,
725            'Orientation',
726            'Horizontal (normal)',
727        );
728
729
730        # if upload high, rotate a copy of original file
731        if($self->upload_high){
732            $self->CreateHigh();
733        }
734
735
736
737
738        $msg = sprintf(
739            "Preparing thumbnail for %s - %s",
740            $site_name,
741            $file,
742        );
743
744        eval {
745            $self->progress_msg_refresh->($msg);
746        };
747        croak "Upload cancelled." if $@ ;
748
749        eval {
750            $self->CreateThumbnail();
751        };
752
753        if($@){
754            $msg = sprintf(
755                "An error has occured %s - %s\n$@",
756                $site_name,
757                $file
758            );
759        }
760        else{
761            $msg = sprintf(
762                "Uploading %s - %s",
763                $site_name,
764                $file
765            );
766        }
767        eval {
768            $self->progress_msg_refresh->($msg);
769            $self->progressbar_refresh->(0.50);
770        };
771        croak "Upload cancelled." if $@ ;
772
773        # photo metadata
774        $self->_prepare_upload_properties();           
775        my ( $status, $status_msg ) = $self->pwg->UploadImage();
776
777        if ( $status ){
778            $msg = sprintf(
779                "%s : %s upload succcessful.",
780                $site_name,
781                $file
782            );
783            $uploaded++;
784        } else {
785            $msg = sprintf(
786                "An error has occured.\n%s : %s upload is cancelled.\n$status_msg",
787                $site_name,
788                $file
789            );
790            $rejected++;
791            $last_error = $status_msg;
792        }       
793       
794        $count++;
795        $self->count(
796            $count
797        );
798        # update upload progress dialog
799        eval {
800            $self->progress_msg_refresh->($msg);
801            $self->progressbar_refresh->(1);
802        };
803        croak "Upload cancelled." if $@ ;
804       
805    }
806    @{$self->image_selection} if defined 
807        $self->image_selection;
808
809    my $time_end = time;
810    my $duration = $time_end - $time_begin;
811    $msg = sprintf(
812        "%s images processed\n\n%s images uploaded\n\n%s images in errors and not uploaded - $last_error\n\nDuration : %s seconds",
813        $self->count - 1,
814        $uploaded,
815        $rejected,
816        $duration,
817    );
818    $self->progress_endinfo_refresh->($msg);
819}
820
821# if we need to rotate
822sub CreateHigh {
823    my ( $self ) = @_;
824
825    my $orientation = $self->current_image->exif_metadata->{Orientation};
826   
827    # Valid for Rotate 180, Rotate 90 CW, Rotate 270 CW
828    if( $orientation =~ m/Rotate (\d+)/ ){
829
830        my ( $vol, $dir, $file ) = File::Spec->splitpath(
831            $self->current_image->file
832        );
833   
834        my ( $filename, $ext ) = split /\./, $file ;
835   
836        # high_file is a copy of original
837        $self->current_image->site_high_file( 
838            File::Spec->catfile(
839                $self->site_resized_dir,
840                sprintf(
841                    "%s_high.%s",
842                    $filename,
843                    $self->type,
844                )
845            )
846        );
847
848        my $image = Image::Magick->new();
849        # we read original
850        my $status = $image->Read(
851            $self->current_image->file
852        );
853        warn "$status ", $self->current_image->file, "\n" if $status ;
854        return 0 if $status;
855
856        $image->Rotate( degrees=>$1 ); 
857       
858        $image->Write(
859            filename=>$self->current_image->site_high_file
860        );
861        warn "$status ", $self->current_image->site_high_file, "\n" if $status ;
862        return 0 if $status;
863       
864        undef $image;
865
866        $self->_set_exif_tag(
867            $self->current_image->site_high_file,
868            'Orientation',
869            'Horizontal (normal)',
870        );
871
872
873        # Now all images that need to be rotated are done. Update exif
874        $self->current_image->exif_metadata->{Orientation} = 'Horizontal (normal)';
875    }
876    else{
877        # high file is the original file
878        $self->current_image->site_high_file(
879            $self->current_image->file
880        );
881    }
882
883    return 1;
884}
885
886sub _prepare_upload_properties {
887    my ( $self ) = @_;
888   
889    $self->pwg->upload_high(
890        $self->upload_high
891    );
892
893    $self->pwg->site_high_file(
894        $self->current_image->site_high_file
895    );
896
897    $self->pwg->site_resized_file(
898        $self->current_image->site_resized_file
899    );
900
901    $self->pwg->site_thumb_file(
902        $self->current_image->site_thumb_file
903    );
904
905    $self->pwg->site_author(
906        $self->current_image->site_author
907    );
908
909    $self->pwg->site_comment(
910        $self->current_image->site_comment
911    );
912
913    $self->pwg->site_image_name(
914        $self->current_image->site_name
915    );
916
917    $self->pwg->site_img_date_creation(
918        $self->current_image->create_date
919    );
920
921    $self->current_image->site_categories(
922        $self->categories
923    );
924
925    $self->pwg->categories(
926        sprintf(
927            "%s",
928            join(';', @{$self->categories})
929        )
930    );
931
932    $self->pwg->tags(
933        #join(',', @{$self->current_image->site_tags})
934    );
935       
936}
937
938# read Orientation exif tag from original image
939# apply rotation to image ( preview or resize )
940sub RotateImage {
941    my ( $self, $file ) = @_;
942   
943    # exif from original image
944    my $orientation = $self->current_image->exif_metadata->{Orientation};
945   
946    # Valid for Rotate 180, Rotate 90 CW, Rotate 270 CW
947    if( $orientation =~ m/Rotate (\d+)/ ){
948        printf(
949            "Rotate %s\n",
950            $1
951        );
952
953        my $image = Image::Magick->new();
954       
955        # read resized file
956        my $status = $image->Read(
957            $file
958        );
959        warn "$status ", $file, "\n" if $status ;
960        return 0 if $status;
961   
962        $image->Rotate( degrees=>$1 ); 
963       
964        # write resizd file
965        $image->Write(
966            filename=>$file
967        );
968        warn "$status ", $file, "\n" if $status ;
969        return 0 if $status;
970       
971        undef $image;
972   
973    }   
974    return 1;
975}
976
9771;
Note: See TracBrowser for help on using the repository browser.