source: extensions/piwigo_import_tree/piwigo_import_tree.pl @ 18694

Last change on this file since 18694 was 18694, checked in by plg, 12 years ago

descript.ion file contains photo descriptions, not photo titles

File size: 13.2 KB
Line 
1#!/usr/bin/perl
2
3# usage:
4# perl piwigo_import_tree.pl --directory=/Users/pierrick/piwigo/album1
5
6use strict;
7use warnings;
8
9# make it compatible with Windows, but breaks Linux
10#use utf8;
11
12use File::Find;
13use Data::Dumper;
14use File::Basename;
15use LWP::UserAgent;
16use JSON;
17use Getopt::Long;
18use Encode qw/is_utf8 decode/;
19use Time::HiRes qw/gettimeofday tv_interval/;
20use Digest::MD5 qw/md5 md5_hex/;
21
22my %opt = ();
23GetOptions(
24    \%opt,
25    qw/
26          base_url=s
27          username=s
28          password=s
29          directory=s
30          parent_album_id=s
31          define=s%
32          quiet
33          only_write_cache
34          reload_properties
35          debug
36          short_lines
37      /
38);
39
40my $album_dir = $opt{directory};
41$album_dir =~ s{^\./*}{};
42
43our $ua = LWP::UserAgent->new;
44$ua->agent('Mozilla/piwigo_remote.pl 1.25');
45$ua->cookie_jar({});
46
47my %conf;
48my %conf_default = (
49    base_url => 'http://localhost/plg/piwigo/salon',
50    username => 'plg',
51    password => 'plg',
52);
53
54foreach my $conf_key (keys %conf_default) {
55    $conf{$conf_key} = defined $opt{$conf_key} ? $opt{$conf_key} : $conf_default{$conf_key}
56}
57
58$ua->default_headers->authorization_basic(
59    $conf{username},
60    $conf{password}
61);
62
63my $result = undef;
64my $query = undef;
65
66binmode STDOUT, ":encoding(utf-8)";
67
68# Login to Piwigo
69piwigo_login();
70
71# Fill an "album path to album id" cache
72my %piwigo_albums = ();
73
74my $response = $ua->post(
75    $conf{base_url}.'/ws.php?format=json',
76    {
77        method => 'pwg.categories.getList',
78        recursive => 1,
79        fullname => 1,
80    }
81);
82
83my $albums_aref = from_json($response->content)->{result}->{categories};
84foreach my $album_href (@{$albums_aref}) {
85    $piwigo_albums{ $album_href->{name} } = $album_href->{id};
86}
87# print Dumper(\%piwigo_albums)."\n\n";
88
89if (defined $opt{parent_album_id}) {
90    foreach my $album_path (keys %piwigo_albums) {
91        if ($piwigo_albums{$album_path} == $opt{parent_album_id}) {
92            $conf{parent_album_id} = $opt{parent_album_id};
93            $conf{parent_album_path} = $album_path;
94        }
95    }
96
97    if (not defined $conf{parent_album_path}) {
98        print "Parent album ".$opt{parent_album_id}." does not exist\n";
99        exit();
100    }
101}
102
103# Initialize a cache with file names of existing photos, for related albums
104my %photos_of_album = ();
105
106# Synchronize local folder with remote Piwigo gallery
107find({wanted => \&add_to_piwigo, no_chdir => 1}, $album_dir);
108
109#---------------------------------------------------------------------
110# Functions
111#---------------------------------------------------------------------
112
113sub piwigo_login {
114    $ua->post(
115        $conf{base_url}.'/ws.php?format=json',
116        {
117            method => 'pwg.session.login',
118            username => $conf{username},
119            password => $conf{password},
120        }
121    );
122}
123
124sub fill_photos_of_album {
125    my %params = @_;
126
127    if (defined $photos_of_album{ $params{album_id} }) {
128        return 1;
129    }
130
131    piwigo_login();
132    my $response = $ua->post(
133        $conf{base_url}.'/ws.php?format=json',
134        {
135            method => 'pwg.categories.getImages',
136            cat_id => $params{album_id},
137            per_page => 1000000,
138        }
139    );
140
141    foreach my $image_href (@{from_json($response->content)->{result}{images}{_content}}) {
142        $photos_of_album{ $params{album_id} }{ $image_href->{file} } = $image_href->{id};
143    }
144}
145
146sub photo_exists {
147    my %params = @_;
148
149    fill_photos_of_album(album_id => $params{album_id});
150
151    if (defined $photos_of_album{ $params{album_id} }{ $params{file} }) {
152        return $photos_of_album{ $params{album_id} }{ $params{file} };
153    }
154    else {
155        return 0;
156    }
157}
158
159sub add_album {
160    my %params = @_;
161
162    my $form = {
163        method => 'pwg.categories.add',
164        name => $params{name},
165    };
166
167    if (defined $params{parent}) {
168        $form->{parent} = $params{parent};
169    }
170
171    piwigo_login();
172    my $response = $ua->post(
173        $conf{base_url}.'/ws.php?format=json',
174        $form
175    );
176
177    return from_json($response->content)->{result}{id};
178}
179
180sub set_album_properties {
181    my %params = @_;
182
183    print '[set_album_properties] for directory "'.$params{dir}.'"'."\n" if $opt{debug};
184
185    # avoid to load the readme.txt file 10 times if an album has 10
186    # sub-albums
187    our %set_album_properties_done;
188    if (defined $set_album_properties_done{ $params{id} }) {
189        print '[set_album_properties] already done'."\n" if $opt{debug};
190        return;
191    }
192    $set_album_properties_done{ $params{id} } = 1;
193
194    $params{dir} =~ s{ / }{/}g;
195
196    # is there a file "readme.txt" in the directory of the album?
197    my $desc_filepath = $params{dir}.'/readme.txt';
198
199    if (not -f $desc_filepath) {
200        print "no readme.txt for ".$params{dir}."\n" if $opt{debug};
201        return;
202    }
203
204    # example of readme.txt:
205    #
206    # Title: First public opening
207    # Date: 2009-09-26
208    # Copyright: John Connor
209    #
210    # Details:
211    # The first day Croome Court is opened to the public by the National Trust.
212    # And here is another line for details!
213
214    open(IN, '<', $desc_filepath);
215    my $title = undef;
216    my $date_string = undef;
217    my $is_details = 0;
218    my $details = '';
219    while (my $desc_line = <IN>) {
220        chomp($desc_line);
221
222        if ($is_details) {
223            $details.= $desc_line;
224        }
225        elsif ($desc_line =~ /^Date:\s*(.*)$/) {
226            $date_string = $1;
227        }
228        elsif ($desc_line =~ /^Title:\s*(.*)$/) {
229            $title = $1;
230        }
231        elsif ($desc_line =~ /^Details:/) {
232            # from now, all the remaining lines are "details"
233            $is_details = 1;
234        }
235    }
236    close(IN);
237
238    if (defined $date_string or $details ne '') {
239        my $comment = '';
240
241        if (defined $date_string) {
242            $comment = '<span class="albumDate">'.$date_string.'</span><br>';
243        }
244        $comment.= $details;
245
246        my $form = {
247            method => 'pwg.categories.setInfo',
248            category_id => $params{id},
249            comment => $comment,
250        };
251
252        if (defined $title) {
253            $form->{name} = $title;
254        }
255
256        piwigo_login();
257
258        my $response = $ua->post(
259            $conf{base_url}.'/ws.php?format=json',
260            $form
261        );
262    }
263}
264
265sub set_photo_properties {
266    my %params = @_;
267
268    print '[set_photo_properties] for "'.$params{path}.'"'."\n" if $opt{debug};
269
270    # is there any title defined in a descript.ion file?
271    my $desc_filepath = dirname($params{path}).'/descript.ion';
272
273    if (not -f $desc_filepath) {
274        print '[set_photo_properties] no descript.ion file'."\n" if $opt{debug};
275        return;
276    }
277
278    my $property = undef;
279    my $photo_filename = basename($params{path});
280    open(IN, '<', $desc_filepath);
281    while (my $desc_line = <IN>) {
282        if ($desc_line =~ /^$photo_filename/) {
283            chomp($desc_line);
284            $property = (split /\t/, $desc_line, 2)[1];
285        }
286    }
287    close(IN);
288
289    if (defined $property and $property ne '') {
290        print '[photo '.$params{id}.'] "';
291
292        if (defined $opt{short_lines}) {
293            print basename($params{path});
294        }
295        else {
296            print $params{path};
297        }
298
299        print '", set photo description "'.$property.'"'."\n";
300
301        my $form = {
302            method => 'pwg.images.setInfo',
303            image_id => $params{id},
304            single_value_mode => 'replace',
305            comment => $property,
306        };
307
308        piwigo_login();
309
310        my $response = $ua->post(
311            $conf{base_url}.'/ws.php?format=json',
312            $form
313        );
314    }
315}
316
317sub add_photo {
318    my %params = @_;
319
320    my $form = {
321        method => 'pwg.images.addSimple',
322        image => [$params{path}],
323        category => $params{album_id},
324    };
325
326    print '[album '.$params{album_id}.'] "';
327    if (defined $opt{short_lines}) {
328        print basename($params{path});
329    }
330    else {
331        print $params{path};
332    }
333    print '" upload starts... ';
334
335    $| = 1;
336    my $t1 = [gettimeofday];
337
338    piwigo_login();
339    my $response = $ua->post(
340        $conf{base_url}.'/ws.php?format=json',
341        $form,
342        'Content_Type' => 'form-data',
343    );
344
345    my $photo_id = from_json($response->content)->{result}{image_id};
346
347    my $elapsed = tv_interval($t1);
348    print ' completed ('.sprintf('%u ms', $elapsed * 1000).', photo '.$photo_id.')'."\n";
349
350    return $photo_id;
351}
352
353sub add_to_piwigo {
354    # print $File::Find::name."\n";
355    my $path = $File::Find::name;
356    my $parent_dir = dirname($album_dir);
357    if ($parent_dir ne '.') {
358        # print '$parent_dir = '.$parent_dir."\n";
359        $path =~ s{^$parent_dir/}{};
360    }
361    # print $path."\n";
362
363    if (-d) {
364        my $up_dir = '';
365        my $parent_id = undef;
366
367        if (defined $conf{parent_album_path}) {
368            $up_dir = $conf{parent_album_path}.' / ';
369            $parent_id = $conf{parent_album_id};
370        }
371
372        foreach my $dir (split '/', $path) {
373            my $is_new_album = 0;
374
375            if (not defined $piwigo_albums{$up_dir.$dir}) {
376                my $id = cached_album(dir => $up_dir.$dir);
377                # if the album is not in the cache OR if the id in the cache
378                # matches no album fetched by pwg.categories.getList, then
379                # we have to create the album first
380                if (not defined $id or not grep($_ eq $id, values(%piwigo_albums))) {
381                    print 'album "'.$up_dir.$dir.'" must be created'."\n";
382                    $is_new_album = 1;
383                    $id = add_album(name => $dir, parent => $parent_id);
384                    cache_add_album(dir => $up_dir.$dir, id => $id);
385                }
386                $piwigo_albums{$up_dir.$dir} = $id;
387            }
388
389            if ($is_new_album or defined $opt{reload_properties}) {
390                set_album_properties(dir => $up_dir.$dir, id => $piwigo_albums{$up_dir.$dir});
391            }
392
393            $parent_id = $piwigo_albums{$up_dir.$dir};
394            $up_dir.= $dir.' / ';
395        }
396    }
397
398    if (-f and $path =~ /\.(jpe?g|gif|png)$/i) {
399        my $album_key = join(' / ', split('/', dirname($path)));
400
401        if (defined $conf{parent_album_path}) {
402            $album_key = $conf{parent_album_path}.' / '.$album_key;
403        }
404
405        my $album_id = $piwigo_albums{$album_key};
406
407        my $image_id = photo_exists(album_id => $album_id, file => basename($File::Find::name));
408        if (not defined $image_id or $image_id < 1) {
409            $image_id = cached_photo(path => $File::Find::name, dir => $album_key);
410        }
411
412        if (defined $image_id and $image_id >= 1) {
413            if (not $opt{quiet}) {
414                print $File::Find::name.' already exists in Piwigo, skipped'."\n";
415            }
416
417            if (defined $opt{reload_properties}) {
418                set_photo_properties(path => $File::Find::name, id => $image_id);
419            }
420
421            return 1;
422        }
423
424        $image_id = add_photo(path => $File::Find::name, album_id => $album_id);
425        set_photo_properties(path => $File::Find::name, id => $image_id);
426        cache_add_photo(path => $File::Find::name, dir => $album_key, id => $image_id);
427    }
428}
429
430sub cache_add_photo {
431    my %params = @_;
432
433    if (cached_photo(path => $params{path}, dir => $params{dir})) {
434        if (not $opt{quiet}) {
435            print 'photo is in the cache, no upload'."\n";
436        }
437        return 1;
438    }
439
440    $params{dir} =~ s{ / }{/}g;
441
442    my $filepath = $params{dir}.'/.piwigo_import_tree.txt';
443
444    open(my $ofh, '>> '.$filepath) or die 'cannot open file "'.$filepath.'" for writing';
445    print {$ofh} $conf{base_url}.' '.md5_hex(basename($params{path}));
446
447    if (defined $params{id}) {
448        print {$ofh} ' [id='.$params{id}.']';
449    }
450
451    print {$ofh} "\n";
452    close($ofh);
453}
454
455sub cached_photo {
456    my %params = @_;
457
458    $params{dir} =~ s{ / }{/}g;
459
460    my $filepath = $params{dir}.'/.piwigo_import_tree.txt';
461
462    if (not -f $filepath) {
463        return undef;
464    }
465
466    my $photo_id = undef;
467    my $photo_filename_md5 = md5_hex(basename($params{path}));
468
469    open(my $ifh, '<'.$filepath) or die 'cannot open file "'.$filepath.'" for reading';
470    while (my $line = <$ifh>) {
471        chomp $line;
472        if ($line =~ m/$photo_filename_md5/) {
473            # TODO if needed : search the [id=(\d+)] for photo_id
474            if ($line =~ m/\[id=(\d+)\]/) {
475                return $1;
476            }
477            else {
478                return -1; # true, but not an image_id
479            }
480        }
481    }
482    close($ifh);
483
484    return undef;
485}
486
487sub cache_add_album {
488    my %params = @_;
489
490    $params{dir} =~ s{ / }{/}g;
491
492    my $filepath = $params{dir}.'/.piwigo_import_tree.txt';
493
494    open(my $ofh, '>> '.$filepath) or die 'cannot open file "'.$filepath.'" for writing';
495    print {$ofh} $conf{base_url}.' album_id = '.$params{id}."\n";
496    print $conf{base_url}.' album_id = '.$params{id}."\n";
497    close($ofh);
498}
499
500sub cached_album {
501    my %params = @_;
502
503    $params{dir} =~ s{ / }{/}g;
504
505    my $filepath = $params{dir}.'/.piwigo_import_tree.txt';
506
507    if (not -f $filepath) {
508        return undef;
509    }
510
511    my $album_id = undef;
512
513    open(my $ifh, '<'.$filepath) or die 'cannot open file "'.$filepath.'" for reading';
514    while (my $line = <$ifh>) {
515        chomp $line;
516        if ($line =~ m/album_id = (\d+)/) {
517            $album_id = $1;
518        }
519    }
520    close($ifh);
521
522    print 'directory "'.$params{dir}.'" was found as album '.$album_id."\n";
523
524    return $album_id;
525}
Note: See TracBrowser for help on using the repository browser.