source: extensions/piwigo_import_tree/piwigo_import_tree.pl @ 18600

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

If a file readme.txt is available in the directory, use it to define album
description.

With option --reload_properties, ability to reload album/photo properties even
if the photos are already uploaded.

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