source: extensions/piwigo_import_tree/piwigo_import_tree.pl @ 27153

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

add Copyright: from readme.txt as <span class=albumCopyright> in album description

File size: 13.5 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 $copyright = undef;
218    my $is_details = 0;
219    my $details = '';
220    while (my $desc_line = <IN>) {
221        chomp($desc_line);
222
223        if ($is_details) {
224            $details.= $desc_line;
225        }
226        elsif ($desc_line =~ /^Date:\s*(.*)$/) {
227            $date_string = $1;
228        }
229        elsif ($desc_line =~ /^Title:\s*(.*)$/) {
230            $title = $1;
231        }
232        elsif ($desc_line =~ /^Copyright:\s*(.*)$/) {
233            $copyright = $1;
234        }
235        elsif ($desc_line =~ /^Details:/) {
236            # from now, all the remaining lines are "details"
237            $is_details = 1;
238        }
239    }
240    close(IN);
241
242    if (defined $date_string or $details ne '') {
243        my $comment = '';
244
245        if (defined $date_string) {
246            $comment.= '<span class="albumDate">'.$date_string.'</span><br>';
247        }
248        if (defined $copyright) {
249            $comment.= '<span class="albumCopyright">'.$copyright.'</span><br>';
250        }
251        $comment.= $details;
252
253        my $form = {
254            method => 'pwg.categories.setInfo',
255            category_id => $params{id},
256            comment => $comment,
257        };
258
259        if (defined $title) {
260            $form->{name} = $title;
261        }
262
263        piwigo_login();
264
265        my $response = $ua->post(
266            $conf{base_url}.'/ws.php?format=json',
267            $form
268        );
269    }
270}
271
272sub set_photo_properties {
273    my %params = @_;
274
275    print '[set_photo_properties] for "'.$params{path}.'"'."\n" if $opt{debug};
276
277    # is there any title defined in a descript.ion file?
278    my $desc_filepath = dirname($params{path}).'/descript.ion';
279
280    if (not -f $desc_filepath) {
281        print '[set_photo_properties] no descript.ion file'."\n" if $opt{debug};
282        return;
283    }
284
285    my $property = undef;
286    my $photo_filename = basename($params{path});
287    open(IN, '<', $desc_filepath);
288    while (my $desc_line = <IN>) {
289        if ($desc_line =~ /^$photo_filename/) {
290            chomp($desc_line);
291            $property = (split /\t/, $desc_line, 2)[1];
292        }
293    }
294    close(IN);
295
296    if (defined $property and $property ne '') {
297        print '[photo '.$params{id}.'] "';
298
299        if (defined $opt{short_lines}) {
300            print basename($params{path});
301        }
302        else {
303            print $params{path};
304        }
305
306        print '", set photo description "'.$property.'"'."\n";
307
308        my $form = {
309            method => 'pwg.images.setInfo',
310            image_id => $params{id},
311            single_value_mode => 'replace',
312            comment => $property,
313        };
314
315        piwigo_login();
316
317        my $response = $ua->post(
318            $conf{base_url}.'/ws.php?format=json',
319            $form
320        );
321    }
322}
323
324sub add_photo {
325    my %params = @_;
326
327    my $form = {
328        method => 'pwg.images.addSimple',
329        image => [$params{path}],
330        category => $params{album_id},
331    };
332
333    print '[album '.$params{album_id}.'] "';
334    if (defined $opt{short_lines}) {
335        print basename($params{path});
336    }
337    else {
338        print $params{path};
339    }
340    print '" upload starts... ';
341
342    $| = 1;
343    my $t1 = [gettimeofday];
344
345    piwigo_login();
346    my $response = $ua->post(
347        $conf{base_url}.'/ws.php?format=json',
348        $form,
349        'Content_Type' => 'form-data',
350    );
351
352    my $photo_id = from_json($response->content)->{result}{image_id};
353
354    my $elapsed = tv_interval($t1);
355    print ' completed ('.sprintf('%u ms', $elapsed * 1000).', photo '.$photo_id.')'."\n";
356
357    return $photo_id;
358}
359
360sub add_to_piwigo {
361    # print $File::Find::name."\n";
362    my $path = $File::Find::name;
363    my $parent_dir = dirname($album_dir);
364    if ($parent_dir ne '.') {
365        # print '$parent_dir = '.$parent_dir."\n";
366        $path =~ s{^$parent_dir/}{};
367    }
368    # print $path."\n";
369
370    if (-d) {
371        my $up_dir = '';
372        my $parent_id = undef;
373
374        if (defined $conf{parent_album_path}) {
375            $up_dir = $conf{parent_album_path}.' / ';
376            $parent_id = $conf{parent_album_id};
377        }
378
379        foreach my $dir (split '/', $path) {
380            my $is_new_album = 0;
381
382            if (not defined $piwigo_albums{$up_dir.$dir}) {
383                my $id = cached_album(dir => $up_dir.$dir);
384                # if the album is not in the cache OR if the id in the cache
385                # matches no album fetched by pwg.categories.getList, then
386                # we have to create the album first
387                if (not defined $id or not grep($_ eq $id, values(%piwigo_albums))) {
388                    print 'album "'.$up_dir.$dir.'" must be created'."\n";
389                    $is_new_album = 1;
390                    $id = add_album(name => $dir, parent => $parent_id);
391                    cache_add_album(dir => $up_dir.$dir, id => $id);
392                }
393                $piwigo_albums{$up_dir.$dir} = $id;
394            }
395
396            if ($is_new_album or defined $opt{reload_properties}) {
397                set_album_properties(dir => $up_dir.$dir, id => $piwigo_albums{$up_dir.$dir});
398            }
399
400            $parent_id = $piwigo_albums{$up_dir.$dir};
401            $up_dir.= $dir.' / ';
402        }
403    }
404
405    if (-f and $path =~ /\.(jpe?g|gif|png)$/i) {
406        my $album_key = join(' / ', split('/', dirname($path)));
407
408        if (defined $conf{parent_album_path}) {
409            $album_key = $conf{parent_album_path}.' / '.$album_key;
410        }
411
412        my $album_id = $piwigo_albums{$album_key};
413
414        my $image_id = photo_exists(album_id => $album_id, file => basename($File::Find::name));
415        if (not defined $image_id or $image_id < 1) {
416            $image_id = cached_photo(path => $File::Find::name, dir => $album_key);
417        }
418
419        if (defined $image_id and $image_id >= 1) {
420            if (not $opt{quiet}) {
421                print $File::Find::name.' already exists in Piwigo, skipped'."\n";
422            }
423
424            if (defined $opt{reload_properties}) {
425                set_photo_properties(path => $File::Find::name, id => $image_id);
426            }
427
428            return 1;
429        }
430
431        $image_id = add_photo(path => $File::Find::name, album_id => $album_id);
432        set_photo_properties(path => $File::Find::name, id => $image_id);
433        cache_add_photo(path => $File::Find::name, dir => $album_key, id => $image_id);
434    }
435}
436
437sub cache_add_photo {
438    my %params = @_;
439
440    if (cached_photo(path => $params{path}, dir => $params{dir})) {
441        if (not $opt{quiet}) {
442            print 'photo is in the cache, no upload'."\n";
443        }
444        return 1;
445    }
446
447    $params{dir} =~ s{ / }{/}g;
448
449    my $filepath = $params{dir}.'/.piwigo_import_tree.txt';
450
451    open(my $ofh, '>> '.$filepath) or die 'cannot open file "'.$filepath.'" for writing';
452    print {$ofh} $conf{base_url}.' '.md5_hex(basename($params{path}));
453
454    if (defined $params{id}) {
455        print {$ofh} ' [id='.$params{id}.']';
456    }
457
458    print {$ofh} "\n";
459    close($ofh);
460}
461
462sub cached_photo {
463    my %params = @_;
464
465    $params{dir} =~ s{ / }{/}g;
466
467    my $filepath = $params{dir}.'/.piwigo_import_tree.txt';
468
469    if (not -f $filepath) {
470        return undef;
471    }
472
473    my $photo_id = undef;
474    my $photo_filename_md5 = md5_hex(basename($params{path}));
475
476    open(my $ifh, '<'.$filepath) or die 'cannot open file "'.$filepath.'" for reading';
477    while (my $line = <$ifh>) {
478        chomp $line;
479        if ($line =~ m/$photo_filename_md5/) {
480            # TODO if needed : search the [id=(\d+)] for photo_id
481            if ($line =~ m/\[id=(\d+)\]/) {
482                return $1;
483            }
484            else {
485                return -1; # true, but not an image_id
486            }
487        }
488    }
489    close($ifh);
490
491    return undef;
492}
493
494sub cache_add_album {
495    my %params = @_;
496
497    $params{dir} =~ s{ / }{/}g;
498
499    my $filepath = $params{dir}.'/.piwigo_import_tree.txt';
500
501    open(my $ofh, '>> '.$filepath) or die 'cannot open file "'.$filepath.'" for writing';
502    print {$ofh} $conf{base_url}.' album_id = '.$params{id}."\n";
503    print $conf{base_url}.' album_id = '.$params{id}."\n";
504    close($ofh);
505}
506
507sub cached_album {
508    my %params = @_;
509
510    $params{dir} =~ s{ / }{/}g;
511
512    my $filepath = $params{dir}.'/.piwigo_import_tree.txt';
513
514    if (not -f $filepath) {
515        return undef;
516    }
517
518    my $album_id = undef;
519
520    open(my $ifh, '<'.$filepath) or die 'cannot open file "'.$filepath.'" for reading';
521    while (my $line = <$ifh>) {
522        chomp $line;
523        if ($line =~ m/album_id = (\d+)/) {
524            $album_id = $1;
525        }
526    }
527    close($ifh);
528
529    print 'directory "'.$params{dir}.'" was found as album '.$album_id."\n";
530
531    return $album_id;
532}
Note: See TracBrowser for help on using the repository browser.