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

Last change on this file since 3159 was 3159, checked in by ronosman, 15 years ago

I18n support.

  • Property svn:eol-style set to LF
File size: 23.6 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            printf("use preview\n");
211            $self->_write_preview_image( $info->{PreviewImage} );
212        }
213        # have to create a preview file
214        else {
215            printf("have to create a preview\n");
216            eval {
217                if(!$self->CreateGUIPreview()){
218            printf("use preview callback\n");
219                    # use method provided by the caller
220                    # source, target, type, ratio
221                    $self->ResizeCallback->(
222                        $self->current_image->file,
223                        $self->current_image->preview_file,
224                        $self->type,
225                        $self->preview_ratio,
226                        undef,
227                        undef,
228                        $self->quality,
229                    );
230                }
231            };# create a preview file
232        }   
233
234        $self->RotateImage(
235            $self->current_image->preview_file,
236        );
237
238        $self->_set_exif_tag(
239            $self->current_image->preview_file,
240            'Orientation',
241            'Horizontal (normal)',
242        );
243
244
245        # Now, we should have a valid preview image.
246        # try to thumbnail it
247        eval {
248            printf("create gui thumbnail\n");
249            # use the preview image to create a gui display thumbnail
250            $self->CreateGUIThumbnail();
251        };
252        # ok
253        if(!$@){
254            $self->progress_msg("Thumbnail and preview created for %s");
255            $i++;
256            $count++;
257            $self->count($count);
258        }
259        else {
260            $self->progress_msg("An error has occured when processing %s\n$@");
261            # remove from list
262            splice @{$self->images}, $i, 1;
263            $errors++;
264        }
265       
266        $self->SetNewFilesProgressCallback->();
267        $self->SetNewFilesViewerRefreshCallback->();
268
269    }
270    @{$files};
271    $self->SetNewFilesDisplayEndInfoCallback->(
272        sprintf(
273            "%s images added to the selection\n\n%s errors",
274            $self->count,
275            $errors
276           
277        )
278    );
279   
280    $self->Store;
281   
282}
283
284# key is file path
285sub _add_image {
286    my ( $self, $file, $info, $i ) = @_;       
287
288    my $image = Uploader::Image->new(
289        {
290            file              => $_,
291            site_name         => sprintf(
292                                     "%s %s", 
293                                     $self->default_name_prefix, 
294                                     $i,
295                                 ),
296            site_author       => $self->author,
297            exif_metadata     => $self->_select_exif_data($info),
298            add_rank          => $i,
299            site_categories   => [],
300            site_tags         => [],
301            site_high_file    => $_,
302        }
303    );
304
305    # append to image list
306    $self->images->[$i] = $image ;
307
308}
309
310
311sub RemoveImageSelection {
312    my ( $self ) = @_;
313   
314    return if (! scalar @{$self->images} );
315    return if (! defined $self->image_selection );
316   
317    # higher first, to keep same indexes during remove
318    my @images = reverse @{$self->image_selection};     
319    map {
320        my $image = $self->images->[$_]->file;
321        splice @{$self->images}, $_, 1 ;
322        $self->wx_thumb_imglist->Remove($_);
323        shift @images;
324    }
325    @images;
326   
327    # clear image selection
328    $self->image_selection([]);
329}
330
331# used for display in GUI. has to fit a square box ( wxImageList )
332sub CreateGUIThumbnail {
333    my ( $self ) = @_;
334
335    return 1 if( -e $self->current_image->wx_thumb_file );
336    my $rval = 0;
337print "CreateGUIThumbnail ", $self->current_image->wx_thumb_file, "\n";
338    my $image = new Image::Magick;
339
340    my $size = $self->wx_thumb_size;
341
342    my $status = $image->Set(size=>sprintf("%sx%s", 3*$size, 3*$size));
343    warn "$status" if $status ;
344
345    $status = $image->ReadImage(
346        $self->current_image->preview_file
347    );
348    warn "$status" if $status;
349    return $rval if $status;
350
351    $status = $image->Thumbnail(
352        geometry=>sprintf("%s%s>", $size*$size, '@')
353    );
354    warn "$status" if $status;
355    return $rval if $status;
356
357    $status = $image->Set(background=>"white");
358    warn "$status" if $status ;
359
360    $status = $image->Set(Gravity=>"Center");
361    warn "$status" if $status ;
362
363    $image->Extent(
364        geometry=>sprintf("%sx%s", $size, $size),
365        gravity=>'center',
366    );
367
368    $image->Set(quality=>100);
369
370    $status = $image->Strip();
371    warn "$status" if $status ;
372   
373
374    $image->Write(
375        sprintf(
376            "%s:%s",
377            $self->type,
378            $self->current_image->wx_thumb_file,
379        )
380    );
381
382    undef $image;
383   
384    $rval = 1;
385   
386    return $rval;
387}
388
389
390sub CreateGUIPreview {
391    my ( $self ) = @_;
392
393    return 1 if( -e $self->current_image->preview_file );
394   
395    my $rval = 1;
396
397    my $image = Image::Magick->new();
398
399    my $ratio = $self->preview_ratio;
400
401
402    my $status = $image->Read(
403        sprintf(
404            "'%s'",
405            $self->current_image->file,
406        )
407    );
408    warn "$status ", $self->current_image->file, "\n" if $status ;
409    return 0 if $status;
410
411    $status = $image->Thumbnail(
412        geometry=>sprintf(
413                              "%s%%x%s%%>", 
414                              $ratio, 
415                              $ratio
416                         )
417    );
418    warn "$status" if $status ;
419    return 0 if $status;
420
421
422    $status = $image->Set(background=>"white");
423    warn "$status" if $status ;
424
425    $status = $image->Set(Gravity=>"Center");
426    warn "$status" if $status ;
427
428
429    $image->Set(quality=>$self->quality);
430
431
432    $status = $image->Write(
433        sprintf(
434            "%s:%s",
435            $self->type,
436            $self->current_image->preview_file,
437        )
438    );
439    warn "$status" if $status ;
440    return 0 if $status;
441   
442    undef $image;
443
444    return $rval;
445}
446
447
448sub CreateResized {
449    my ( $self ) = @_;
450   
451    my $rval = 1 ;
452    return $rval if( -e $self->current_image->site_resized_file );
453
454printf(
455    "Create resized %s\n",
456    $self->current_image->file,
457);     
458
459    my $image = new Image::Magick;
460
461    my $status = $image->ReadImage(
462        $self->current_image->file
463    );
464    warn "$status" if $status ;
465    return 0 if $status;
466
467    my $w = $image->Get('width');
468    my $h = $image->Get('height');
469       
470    # should calculate the aspect ratio
471    my $resize_w = $self->resize_w;
472    my $resize_h = $self->resize_h;
473       
474    if( $w < $h ){
475        my $resize_w_ = $resize_w;
476        $resize_w = $resize_h;
477        $resize_h = $resize_w_;
478    }
479   
480    $status = $image->Resize(
481        geometry => sprintf("%sx%s>", $resize_w, $resize_h), 
482        filter => sprintf("%s", $self->filter), 
483        blur => $self->blur
484    );
485    warn "$status" if $status ;
486    return 0 if $status;
487
488    $status = $image->Set(Gravity=>"Center");
489    warn "$status" if $status ;
490
491    # exif from original image
492    my $orientation = $self->current_image->exif_metadata->{Orientation};
493   
494    # Valid for Rotate 180, Rotate 90 CW, Rotate 270 CW
495    if( $orientation =~ m/Rotate (\d+)/ ){
496        printf(
497            "Rotate %s\n",
498            $1
499        );
500   
501        $image->Rotate( degrees=>$1 ); 
502    }
503
504    $status = $image->Set(quality=>$self->quality);
505    warn "$status" if $status ;
506
507    $status = $image->Set(interlace=>'Line');
508    warn "$status" if $status ;
509
510    $image->Write(
511        sprintf(
512            "%s:%s",
513            $self->type,
514            $self->current_image->site_resized_file,
515        )
516    );
517    warn "$status" if $status ;
518    return 0 if $status;
519   
520    undef $image;
521
522   
523    $rval = 0 if $status;
524
525    return $rval;
526}
527
528sub CreateThumbnail {
529    my ( $self ) = @_;
530   
531    return 1 if( -e $self->current_image->site_thumb_file );
532   
533    my $rval = 1;
534
535    my $image = new Image::Magick;
536
537    my $status = $image->ReadImage(
538        $self->current_image->site_resized_file
539    );
540    warn "$status" if $status ;
541
542   
543    $status = $image->Resize(
544        geometry => sprintf(
545                                "%sx%s>", 
546                                $self->thumb_size, 
547                                $self->thumb_size
548                           ),
549    );
550    warn "$status" if $status ;
551
552    $status = $image->Set(Gravity=>"Center");
553    warn "$status" if $status ;
554
555    $status = $image->Set(quality=>$self->quality);
556    warn "$status" if $status ;
557
558    $status = $image->Strip();
559    warn "$status" if $status ;
560
561
562    $image->Write(
563        sprintf(
564            "%s:%s",
565            $self->type,
566            $self->current_image->site_thumb_file,
567        )
568    );
569   
570    undef $image;
571
572
573    $rval = 0 if $status;
574
575    return $rval;
576}
577
578
579
580sub _select_exif_data {
581    my ( $self, $exif ) = @_;
582
583    return {
584        map {
585            $_ => $exif->{$_},
586        }
587        qw/
588            CreateDate
589            ImageWidth
590            ImageHeight
591            Orientation
592            DateTimeOriginal
593            ISO
594            ExposureTime
595            ApertureValue
596            FocalLength
597            Lens
598            Exposure
599            Make
600            Model
601        /
602    };   
603}
604
605sub Store {
606    my ( $self ) = @_;
607   
608    my $data = $self->get_storable(
609        [ 
610            qw/
611                images
612                thumb_size
613                preview_ratio
614                type
615                filter
616                blur
617                quality
618                prefix
619                author
620                count
621                resize_w
622                resize_h
623                new_files
624                storable_file
625                wx_thumb_size
626                current_image
627                images
628                exif_metadata
629                wx_thumb_dir
630                preview_dir
631                site_resized_dir
632                site_thumb_dir
633                userdata_dir
634                progress_msg
635                default_name_prefix
636                upload_high
637            /
638        ] 
639   
640    );
641    eval {
642        store $data, $self->storable_file;
643    };
644    if($@){
645        print $@, "\n"; 
646    }
647}
648
649
650
651sub UploadSelection {
652    my ( $self ) = @_; 
653
654    my $viewer_callback = $self->UploadImagesViewerCallback ;
655
656
657    $self->upload_rejects(
658        []
659    );
660
661    my $count = 1;
662    my $msg;
663    $self->count(
664        $count
665    );
666    my $uploaded = 0;
667    my $rejected = 0;
668    my $time_begin = time;
669    my $last_error;
670    map {
671        # current image object         
672        $self->current_image(
673            $self->images->[$_]
674        );
675
676        my ( $vol, $dir, $file ) = File::Spec->splitpath(
677            $self->current_image->file
678        );
679       
680        my $site_name = $self->current_image->site_name;
681   
682        my ( $filename, $ext ) = split /\./, $file ;
683
684        # lately defined to make sure we have the last global properties ( resize_w, resize_h )
685        $self->current_image->site_resized_file( 
686            File::Spec->catfile(
687                $self->site_resized_dir,
688                sprintf(
689                    "%s_%sx%s.%s",
690                    $filename,
691                    $self->resize_w,
692                    $self->resize_h,
693                    $self->type,
694                )
695            )
696        );
697       
698        $msg = sprintf(
699            "Preparing resized image for %s - %s",
700            $site_name,
701            $file,
702        ); 
703
704        eval {
705            # set current image thumbnail
706            $self->progress_thumbnail_refresh->();
707
708            $self->progress_msg_refresh->($msg);
709   
710            # update upload progress dialog
711            $self->progressbar_refresh->(0.25);
712        };
713        # user cancelled : dialog box is destroyed
714        croak "Upload cancelled." if $@ ;
715
716        eval {
717            if(!$self->CreateResized()){
718                printf("CreateResized failed %s. Use ResizeCallback\n", $@);
719                # use method provided by the caller
720                # source, target, type, ratio, width, $height
721                $self->ResizeCallback->(
722                    $self->current_image->file,
723                    $self->current_image->site_resized_file,
724                    $self->type,
725                    undef,
726                    $self->resize_w,
727                    $self->resize_h,
728                    $self->quality,
729                );
730               
731                $self->RotateImage(
732                    $self->current_image->site_resized_file,
733                );
734            }
735        };
736
737        $self->_set_exif_tag(
738            $self->current_image->site_resized_file,
739            'Orientation',
740            'Horizontal (normal)',
741        );
742
743
744        # if upload high, rotate a copy of original file
745        if($self->upload_high){
746            $self->CreateHigh();
747        }
748
749
750
751
752        $msg = sprintf(
753            "Preparing thumbnail for %s - %s",
754            $site_name,
755            $file,
756        );
757
758        eval {
759            $self->progress_msg_refresh->($msg);
760        };
761        croak "Upload cancelled." if $@ ;
762
763        eval {
764            $self->CreateThumbnail();
765        };
766
767        if($@){
768            $msg = sprintf(
769                "An error has occured %s - %s\n$@",
770                $site_name,
771                $file
772            );
773        }
774        else{
775            $msg = sprintf(
776                "Uploading %s - %s",
777                $site_name,
778                $file
779            );
780        }
781        eval {
782            $self->progress_msg_refresh->($msg);
783            $self->progressbar_refresh->(0.50);
784        };
785        croak "Upload cancelled." if $@ ;
786
787        # photo metadata
788        $self->_prepare_upload_properties();           
789        my ( $status, $status_msg ) = $self->pwg->UploadImage();
790
791        if ( $status ){
792            $msg = sprintf(
793                "%s : %s upload succcessful.",
794                $site_name,
795                $file
796            );
797            $uploaded++;
798        } else {
799            $msg = sprintf(
800                "An error has occured.\n%s : %s upload is cancelled.\n$status_msg",
801                $site_name,
802                $file
803            );
804            $rejected++;
805            $last_error = $status_msg;
806        }       
807       
808        $count++;
809        $self->count(
810            $count
811        );
812        # update upload progress dialog
813        eval {
814            $self->progress_msg_refresh->($msg);
815            $self->progressbar_refresh->(1);
816        };
817        croak "Upload cancelled." if $@ ;
818       
819    }
820    @{$self->image_selection} if defined 
821        $self->image_selection;
822
823    my $time_end = time;
824    my $duration = $time_end - $time_begin;
825    $msg = sprintf(
826        "%s images processed\n\n%s images uploaded\n\n%s images in errors and not uploaded - $last_error\n\nDuration : %s seconds",
827        $self->count - 1,
828        $uploaded,
829        $rejected,
830        $duration,
831    );
832    $self->progress_endinfo_refresh->($msg);
833}
834
835# if we need to rotate
836sub CreateHigh {
837    my ( $self ) = @_;
838
839    my $orientation = $self->current_image->exif_metadata->{Orientation};
840   
841    # Valid for Rotate 180, Rotate 90 CW, Rotate 270 CW
842    if( $orientation =~ m/Rotate (\d+)/ ){
843
844        my ( $vol, $dir, $file ) = File::Spec->splitpath(
845            $self->current_image->file
846        );
847   
848        my ( $filename, $ext ) = split /\./, $file ;
849   
850        # high_file is a copy of original
851        $self->current_image->site_high_file( 
852            File::Spec->catfile(
853                $self->site_resized_dir,
854                sprintf(
855                    "%s_high.%s",
856                    $filename,
857                    $self->type,
858                )
859            )
860        );
861
862        my $image = Image::Magick->new();
863        # we read original
864        my $status = $image->Read(
865            $self->current_image->file
866        );
867        warn "$status ", $self->current_image->file, "\n" if $status ;
868        return 0 if $status;
869
870        $image->Rotate( degrees=>$1 ); 
871       
872        $image->Write(
873            filename=>$self->current_image->site_high_file
874        );
875        warn "$status ", $self->current_image->site_high_file, "\n" if $status ;
876        return 0 if $status;
877       
878        undef $image;
879
880        $self->_set_exif_tag(
881            $self->current_image->site_high_file,
882            'Orientation',
883            'Horizontal (normal)',
884        );
885
886
887        # Now all images that need to be rotated are done. Update exif
888        $self->current_image->exif_metadata->{Orientation} = 'Horizontal (normal)';
889    }
890    else{
891        # high file is the original file
892        $self->current_image->site_high_file(
893            $self->current_image->file
894        );
895    }
896
897    return 1;
898}
899
900sub _prepare_upload_properties {
901    my ( $self ) = @_;
902   
903    $self->pwg->upload_high(
904        $self->upload_high
905    );
906
907    $self->pwg->site_high_file(
908        $self->current_image->site_high_file
909    );
910
911    $self->pwg->site_resized_file(
912        $self->current_image->site_resized_file
913    );
914
915    $self->pwg->site_thumb_file(
916        $self->current_image->site_thumb_file
917    );
918
919    $self->pwg->site_author(
920        $self->current_image->site_author
921    );
922
923    $self->pwg->site_comment(
924        $self->current_image->site_comment
925    );
926
927    $self->pwg->site_image_name(
928        $self->current_image->site_name
929    );
930
931    $self->pwg->site_img_date_creation(
932        $self->current_image->create_date
933    );
934
935    $self->current_image->site_categories(
936        $self->categories
937    );
938
939    $self->pwg->categories(
940        sprintf(
941            "%s",
942            join(';', @{$self->categories})
943        )
944    );
945
946    $self->pwg->tags(
947        #join(',', @{$self->current_image->site_tags})
948    );
949       
950}
951
952# read Orientation exif tag from original image
953# apply rotation to image ( preview or resize )
954sub RotateImage {
955    my ( $self, $file ) = @_;
956   
957    # exif from original image
958    my $orientation = $self->current_image->exif_metadata->{Orientation};
959   
960    # Valid for Rotate 180, Rotate 90 CW, Rotate 270 CW
961    if( $orientation =~ m/Rotate (\d+)/ ){
962        printf(
963            "Rotate %s\n",
964            $1
965        );
966
967        my $image = Image::Magick->new();
968       
969        # read resized file
970        my $status = $image->Read(
971            $file
972        );
973        warn "$status ", $file, "\n" if $status ;
974        return 0 if $status;
975   
976        $image->Rotate( degrees=>$1 ); 
977       
978        # write resizd file
979        $image->Write(
980            filename=>$file
981        );
982        warn "$status ", $file, "\n" if $status ;
983        return 0 if $status;
984       
985        undef $image;
986   
987    }   
988    return 1;
989}
990
9911;
Note: See TracBrowser for help on using the repository browser.