line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
########################################################### |
2
|
|
|
|
|
|
|
# A Perl package for showing/modifying JPEG (meta)data. # |
3
|
|
|
|
|
|
|
# Copyright (C) 2004,2005,2006 Stefano Bettelli # |
4
|
|
|
|
|
|
|
# See the COPYING and LICENSE files for license terms. # |
5
|
|
|
|
|
|
|
########################################################### |
6
|
|
|
|
|
|
|
package Image::MetaData::JPEG; |
7
|
14
|
|
|
14
|
|
57
|
use Image::MetaData::JPEG::data::Tables qw(:Lookups :TagsAPP13); |
|
14
|
|
|
|
|
20
|
|
|
14
|
|
|
|
|
2107
|
|
8
|
14
|
|
|
14
|
|
74
|
use Image::MetaData::JPEG::Segment; |
|
14
|
|
|
|
|
23
|
|
|
14
|
|
|
|
|
239
|
|
9
|
14
|
|
|
14
|
|
45
|
no integer; |
|
14
|
|
|
|
|
20
|
|
|
14
|
|
|
|
|
59
|
|
10
|
14
|
|
|
14
|
|
270
|
use strict; |
|
14
|
|
|
|
|
15
|
|
|
14
|
|
|
|
|
325
|
|
11
|
14
|
|
|
14
|
|
46
|
use warnings; |
|
14
|
|
|
|
|
21
|
|
|
14
|
|
|
|
|
27549
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
########################################################### |
14
|
|
|
|
|
|
|
# This method returns a reference to the $index-th (the # |
15
|
|
|
|
|
|
|
# first, if $index is undefined) Photoshop-like APP13 # |
16
|
|
|
|
|
|
|
# segment which contains information matching the $what # |
17
|
|
|
|
|
|
|
# argument (see is_app13_ok() for details). If $index is # |
18
|
|
|
|
|
|
|
# undefined, it defaults to zero (i.e., first segment). # |
19
|
|
|
|
|
|
|
# If no suitable segment is available, undef is returned. # |
20
|
|
|
|
|
|
|
# If $index is (-1), this method returns the number of # |
21
|
|
|
|
|
|
|
# available suitable APP13 segments (which is >= 0). If # |
22
|
|
|
|
|
|
|
# $what is invalid, an exception is thrown. Beware!, the # |
23
|
|
|
|
|
|
|
# meaning of $index is influenced by the value of $what. # |
24
|
|
|
|
|
|
|
########################################################### |
25
|
|
|
|
|
|
|
sub retrieve_app13_segment { |
26
|
69
|
|
|
69
|
1
|
6472
|
my ($this, $index, $what) = @_; |
27
|
|
|
|
|
|
|
# $index defaults to zero if undefined |
28
|
69
|
100
|
|
|
|
193
|
$index = 0 unless defined $index; |
29
|
|
|
|
|
|
|
# select all segments compatible with $what |
30
|
69
|
|
|
|
|
228
|
my @references = grep { $_->is_app13_ok($what) } $this->get_segments(); |
|
787
|
|
|
|
|
1036
|
|
31
|
|
|
|
|
|
|
# if $index is -1, return the size of @references |
32
|
67
|
100
|
|
|
|
213
|
return scalar @references if $index == -1; |
33
|
|
|
|
|
|
|
# return the $index-th such segment, or undef if absent |
34
|
53
|
100
|
|
|
|
177
|
return exists $references[$index] ? $references[$index] : undef; |
35
|
|
|
|
|
|
|
} |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
########################################################### |
38
|
|
|
|
|
|
|
# This method forces an appropriate Photoshop-like APP13 # |
39
|
|
|
|
|
|
|
# segment to be present in the file, and returns its # |
40
|
|
|
|
|
|
|
# reference. If at least one segment matching $what is # |
41
|
|
|
|
|
|
|
# present, the first one is returned. Otherwise, the 1st # |
42
|
|
|
|
|
|
|
# Photoshop-like APP13 is adapted by inserting an appro- # |
43
|
|
|
|
|
|
|
# priate subdir record (update() is called automatically).# |
44
|
|
|
|
|
|
|
# If not such segment exists, it is first created and # |
45
|
|
|
|
|
|
|
# inserted. If $what is invalid, an exception is thrown. # |
46
|
|
|
|
|
|
|
########################################################### |
47
|
|
|
|
|
|
|
sub provide_app13_segment { |
48
|
72
|
|
|
72
|
1
|
2441
|
my ($this, $what) = @_; |
49
|
|
|
|
|
|
|
# get the list of segments selected by $what |
50
|
72
|
|
|
|
|
219
|
my @what_refs = grep { $_->is_app13_ok($what) } $this->get_segments(); |
|
851
|
|
|
|
|
1062
|
|
51
|
|
|
|
|
|
|
# if the list is not empty, return the first element |
52
|
70
|
100
|
|
|
|
205
|
return $what_refs[0] if @what_refs; |
53
|
|
|
|
|
|
|
# get the list of Photoshop-like segments (this only looks |
54
|
|
|
|
|
|
|
# for the Photoshop identifier, special case of $what = undef); |
55
|
|
|
|
|
|
|
# then extract the first element. |
56
|
9
|
|
|
|
|
29
|
my @refs = grep { $_->is_app13_ok(undef) } $this->get_segments(); |
|
111
|
|
|
|
|
141
|
|
57
|
9
|
100
|
|
|
|
35
|
my $app13_segment = @refs ? $refs[0] : undef; |
58
|
|
|
|
|
|
|
# if no segment is found, we surely need to generate a new |
59
|
|
|
|
|
|
|
# one, and store it in an appropriate position in the file; |
60
|
|
|
|
|
|
|
# remember that at least the Photoshop string must be there |
61
|
9
|
100
|
|
|
|
25
|
unless ($app13_segment) { |
62
|
4
|
|
|
|
|
33
|
$app13_segment = new Image::MetaData::JPEG::Segment |
63
|
|
|
|
|
|
|
('APP13', \ "$$APP13_PHOTOSHOP_IDS[0]"); |
64
|
|
|
|
|
|
|
# insert it into the list of JPEG segments |
65
|
|
|
|
|
|
|
# (the position is chosen automatically) |
66
|
4
|
|
|
|
|
19
|
$this->insert_segments($app13_segment); } |
67
|
|
|
|
|
|
|
# ok, we must adapt the Photoshop-like segment (automatic update()) |
68
|
9
|
|
|
|
|
28
|
$app13_segment->provide_app13_subdir($what); |
69
|
|
|
|
|
|
|
# return the modified segment |
70
|
9
|
|
|
|
|
37
|
return $app13_segment; |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
########################################################### |
74
|
|
|
|
|
|
|
# This method removes all traces of IPTC/non-IPTC infor- # |
75
|
|
|
|
|
|
|
# mation (depending on $what) from the $index-th APP13 # |
76
|
|
|
|
|
|
|
# Photoshop-style Segment. If, after this, the segment is # |
77
|
|
|
|
|
|
|
# empty, it is eliminated from the list of segments in # |
78
|
|
|
|
|
|
|
# the file. If $index is (-1), all segments are affected # |
79
|
|
|
|
|
|
|
# at once. If $what is invalid an exception is thrown. # |
80
|
|
|
|
|
|
|
# The meaning of $index depends on $what. # |
81
|
|
|
|
|
|
|
########################################################### |
82
|
|
|
|
|
|
|
sub remove_app13_info { |
83
|
10
|
|
|
10
|
1
|
6196
|
my ($this, $index, $what) = @_; |
84
|
|
|
|
|
|
|
# this is the list of segments to be purged (initially empty) |
85
|
10
|
|
|
|
|
20
|
my @purgeme = (); |
86
|
|
|
|
|
|
|
# call the selection routine and store the segment reference |
87
|
10
|
|
|
|
|
29
|
push @purgeme, $this->retrieve_app13_segment($index, $what); |
88
|
|
|
|
|
|
|
# if $index is -1, retrieve_... returned the number of |
89
|
|
|
|
|
|
|
# segments to be purged, not a segment reference! In this |
90
|
|
|
|
|
|
|
# case, the selection routine is repeated with every index. |
91
|
10
|
100
|
|
|
|
37
|
@purgeme = map { $this->retrieve_app13_segment($_, $what) |
|
3
|
|
|
|
|
7
|
|
92
|
|
|
|
|
|
|
} (0..($purgeme[$#purgeme]-1)) if $index == -1; |
93
|
|
|
|
|
|
|
# for each segment in the purge list, apply the purge routine |
94
|
|
|
|
|
|
|
# (but don't be fooled by undefined references, i.e. invalid |
95
|
|
|
|
|
|
|
# indexes). If only one record remains in the segment (presumably |
96
|
|
|
|
|
|
|
# the Identifier), the segment is marked for a later deletion. |
97
|
10
|
|
|
|
|
23
|
for (@purgeme) { |
98
|
10
|
100
|
|
|
|
27
|
next unless defined $_; |
99
|
9
|
|
|
|
|
28
|
$_->remove_app13_info($what); |
100
|
9
|
100
|
|
|
|
11
|
$_->{name} = 'deleteme' if scalar @{$_->{records}} <= 1; } |
|
9
|
|
|
|
|
44
|
|
101
|
|
|
|
|
|
|
# remove the marked segments from the file |
102
|
10
|
|
|
|
|
48
|
$this->drop_segments('deleteme'); |
103
|
|
|
|
|
|
|
} |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
########################################################### |
106
|
|
|
|
|
|
|
# This method is an interface to the method with the same # |
107
|
|
|
|
|
|
|
# name in the Segment class. To begin with, the first # |
108
|
|
|
|
|
|
|
# suitable APP13 segment is retrieved (if there is no # |
109
|
|
|
|
|
|
|
# such segment, undef is returned). Then, get_app13_data # |
110
|
|
|
|
|
|
|
# is called on this segment, passing all the arguments # |
111
|
|
|
|
|
|
|
# through. If $what is invalid an exception is thrown # |
112
|
|
|
|
|
|
|
# out. For further details, have a look at # |
113
|
|
|
|
|
|
|
# Segment::get_app13_data() and retrieve_app13_segment(). # |
114
|
|
|
|
|
|
|
########################################################### |
115
|
|
|
|
|
|
|
sub get_app13_data { |
116
|
22
|
|
|
22
|
1
|
8008
|
my ($this, $type, $what) = @_; |
117
|
|
|
|
|
|
|
# get the first suitable APP13 segment in the current JPEG |
118
|
|
|
|
|
|
|
# file (this returns undef if no segment is present). |
119
|
22
|
|
|
|
|
61
|
my $segment = $this->retrieve_app13_segment(undef, $what); |
120
|
|
|
|
|
|
|
# return undef if no segment is present |
121
|
22
|
50
|
|
|
|
47
|
return undef unless defined $segment; |
122
|
|
|
|
|
|
|
# pass all arguments to the Segment method |
123
|
22
|
|
|
|
|
56
|
return $segment->get_app13_data($type, $what); |
124
|
|
|
|
|
|
|
} |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
########################################################### |
127
|
|
|
|
|
|
|
# This method is an interface to the method with the same # |
128
|
|
|
|
|
|
|
# name in the Segment class. To begin with, the first # |
129
|
|
|
|
|
|
|
# suitable APP13 segment is retrieved (if there is no # |
130
|
|
|
|
|
|
|
# such segment, one is created and initialised). Then the # |
131
|
|
|
|
|
|
|
# set_app13_data is called on this segment passing the # |
132
|
|
|
|
|
|
|
# arguments through. For further details, have a look at # |
133
|
|
|
|
|
|
|
# Segment::set_app13_data() and provide_app13_segment(). # |
134
|
|
|
|
|
|
|
########################################################### |
135
|
|
|
|
|
|
|
sub set_app13_data { |
136
|
60
|
|
|
60
|
1
|
28738
|
my ($this, $data, $action, $what) = @_; |
137
|
|
|
|
|
|
|
# get the first suitable APP13 segment in the current JPEG file |
138
|
|
|
|
|
|
|
# (if there is no such segment, initialise one; therefore, this |
139
|
|
|
|
|
|
|
# call cannot fail unless $what is invalid [mhh ...]). |
140
|
60
|
|
|
|
|
131
|
my $segment = $this->provide_app13_segment($what); |
141
|
|
|
|
|
|
|
# pass all arguments to the Segment method |
142
|
59
|
|
|
|
|
131
|
return $segment->set_app13_data($data, $action, $what); |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
########################################################### |
146
|
|
|
|
|
|
|
# The following routines best fit as Segment methods. # |
147
|
|
|
|
|
|
|
########################################################### |
148
|
|
|
|
|
|
|
package Image::MetaData::JPEG::Segment; |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
########################################################### |
151
|
|
|
|
|
|
|
# These helper functions have a single argument. They fix # |
152
|
|
|
|
|
|
|
# it to some standard value, if it is undefined, then # |
153
|
|
|
|
|
|
|
# they check that its value is a legal string and throw # |
154
|
|
|
|
|
|
|
# an exception out if not so. 'IPTC' is treated like a # |
155
|
|
|
|
|
|
|
# synonym of 'IPTC_2' for backward compatibility. Same # |
156
|
|
|
|
|
|
|
# thing for 'PHOTOSHOP', a synonym for 'PS_8BIM'. # |
157
|
|
|
|
|
|
|
# ------------------------------------------------------- # |
158
|
|
|
|
|
|
|
# sanitise: 0=this, 1=var, 2=name, 3=regex(1st=default) # |
159
|
|
|
|
|
|
|
########################################################### |
160
|
2465
|
|
|
2465
|
0
|
3556
|
sub sanitise_what { sanitise(@_, 'what' , 'IPTC|IPTC_2|IPTC_1|'. |
161
|
|
|
|
|
|
|
'PHOTOSHOP|PS_8BIM|PS_8BPS|PS_PHUT') }; |
162
|
90
|
|
|
90
|
0
|
162
|
sub sanitise_type { sanitise(@_, 'type' , 'TEXTUAL|NUMERIC' ) }; |
163
|
79
|
|
|
79
|
0
|
133
|
sub sanitise_action { sanitise(@_, 'action', 'REPLACE|ADD|UPDATE' ) }; |
164
|
2634
|
100
|
|
2634
|
0
|
5134
|
sub sanitise { ($_[1] = $_[3]) =~ s/^([^\|]*)\|.*$/$1/ unless defined $_[1]; |
165
|
2634
|
100
|
|
|
|
17712
|
($_[1] =~/^($_[3])$/) ?1: $_[0]->die("Unknown '$_[2]': $_[1]")}; |
166
|
|
|
|
|
|
|
my $what2dir = {'IPTC' => $APP13_IPTC_DIRNAME . '_2', # synonym |
167
|
|
|
|
|
|
|
'IPTC_1' => $APP13_IPTC_DIRNAME . '_1', |
168
|
|
|
|
|
|
|
'IPTC_2' => $APP13_IPTC_DIRNAME . '_2', |
169
|
|
|
|
|
|
|
'PHOTOSHOP' => $APP13_PHOTOSHOP_DIRNAME . '_8BIM', # synonym |
170
|
|
|
|
|
|
|
'PS_8BIM' => $APP13_PHOTOSHOP_DIRNAME . '_8BIM', |
171
|
|
|
|
|
|
|
'PS_8BPS' => $APP13_PHOTOSHOP_DIRNAME . '_8BPS', |
172
|
|
|
|
|
|
|
'PS_PHUT' => $APP13_PHOTOSHOP_DIRNAME . '_PHUT', }; |
173
|
1959
|
|
100
|
1959
|
0
|
14309
|
sub subdir_name { $_[0] eq $_ && return $$what2dir{$_} for keys %$what2dir; } |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
########################################################### |
176
|
|
|
|
|
|
|
# This method inspects a segments, and return "ok" if the # |
177
|
|
|
|
|
|
|
# segment shows the required features, undef otherwise. # |
178
|
|
|
|
|
|
|
# The features are selected by the value of $what: # |
179
|
|
|
|
|
|
|
# 1) ($what is undefined) the segment is an APP13 segment # |
180
|
|
|
|
|
|
|
# and it contains the correct 'Identifier' record. # |
181
|
|
|
|
|
|
|
# 2) ($what has a value) the segment matches 1), and # |
182
|
|
|
|
|
|
|
# $what is accepted by sanitise_what and the segment # |
183
|
|
|
|
|
|
|
# contains the subdir_name($what) subdirectory. # |
184
|
|
|
|
|
|
|
# 3) (everything else) the routine dies. # |
185
|
|
|
|
|
|
|
########################################################### |
186
|
|
|
|
|
|
|
sub is_app13_ok { |
187
|
2025
|
|
|
2025
|
0
|
1864
|
my ($this, $what) = @_; |
188
|
|
|
|
|
|
|
# intercept and die on unknown $what's (don't set a default!) |
189
|
2025
|
|
|
|
|
2478
|
$this->sanitise_what(my $temp_what = $what); |
190
|
|
|
|
|
|
|
# return undef if this segment is not APP13 |
191
|
2021
|
100
|
|
|
|
4589
|
return undef unless $this->{name} eq 'APP13'; |
192
|
|
|
|
|
|
|
# return undef if there is no 'Identifier' or it is not Photoshop |
193
|
433
|
|
|
|
|
1104
|
my $id = $this->search_record_value('Identifier'); |
194
|
433
|
100
|
66
|
|
|
1051
|
return undef unless $id && grep { /^$id$/ } @$APP13_PHOTOSHOP_IDS; |
|
866
|
|
|
|
|
3811
|
|
195
|
|
|
|
|
|
|
# if $what is undefined we are happy |
196
|
431
|
100
|
|
|
|
897
|
return 'ok' unless defined $what; |
197
|
|
|
|
|
|
|
# return "ok" if $what is defined and the appropriate subdir is there |
198
|
317
|
100
|
|
|
|
490
|
return 'ok' if defined $this->search_record(subdir_name($what)); |
199
|
|
|
|
|
|
|
# fallback |
200
|
36
|
|
|
|
|
98
|
return undef; |
201
|
|
|
|
|
|
|
} |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
########################################################### |
204
|
|
|
|
|
|
|
# This method returns the appropriate subdirectory record # |
205
|
|
|
|
|
|
|
# reference for the current APP13 Photoshop-like segment # |
206
|
|
|
|
|
|
|
# (undef is returned if it is not present). # |
207
|
|
|
|
|
|
|
########################################################### |
208
|
|
|
|
|
|
|
sub retrieve_app13_subdir { |
209
|
176
|
|
|
176
|
0
|
201
|
my ($this, $what) = @_; |
210
|
|
|
|
|
|
|
# die on unknown $what's |
211
|
176
|
|
|
|
|
248
|
$this->sanitise_what($what); |
212
|
|
|
|
|
|
|
# return immediately if the segment is not suitable |
213
|
176
|
100
|
|
|
|
256
|
return undef unless $this->is_app13_ok($what); |
214
|
|
|
|
|
|
|
# return the appropriate subdirectory reference |
215
|
166
|
|
|
|
|
312
|
return $this->search_record_value(subdir_name($what)); |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
########################################################### |
219
|
|
|
|
|
|
|
# This method returns the appropriate subdirectory record # |
220
|
|
|
|
|
|
|
# reference for the current Photoshop-style APP13 segment.# |
221
|
|
|
|
|
|
|
# If the subdirectory is not there, it is first created # |
222
|
|
|
|
|
|
|
# and initialised. The routine can fail (returns undef) # |
223
|
|
|
|
|
|
|
# only if the segment isn't a Photoshop-style one. If the # |
224
|
|
|
|
|
|
|
# subdirectory is created, the segment is updated. # |
225
|
|
|
|
|
|
|
#---------------------------------------------------------# |
226
|
|
|
|
|
|
|
# The initialisation of a subdirectory can include manda- # |
227
|
|
|
|
|
|
|
# tory records, which are now read from tables and not # |
228
|
|
|
|
|
|
|
# hardcoded here as it used to be. # |
229
|
|
|
|
|
|
|
########################################################### |
230
|
|
|
|
|
|
|
sub provide_app13_subdir { |
231
|
88
|
|
|
88
|
0
|
99
|
my ($this, $what) = @_; |
232
|
|
|
|
|
|
|
# die on unknown $what's |
233
|
88
|
|
|
|
|
138
|
$this->sanitise_what($what); |
234
|
|
|
|
|
|
|
# don't try to mess up non-APP13 segments! |
235
|
88
|
50
|
|
|
|
157
|
return undef unless $this->is_app13_ok(undef); |
236
|
|
|
|
|
|
|
# be positive, call retrieve first |
237
|
88
|
|
|
|
|
177
|
my $subdir = $this->retrieve_app13_subdir($what); |
238
|
|
|
|
|
|
|
# return this value, if it is not undef |
239
|
88
|
100
|
|
|
|
253
|
return $subdir if defined $subdir; |
240
|
|
|
|
|
|
|
# create the appropriate subdir in the main record directory |
241
|
10
|
|
|
|
|
22
|
$subdir = $this->provide_subdirectory(subdir_name($what)); |
242
|
|
|
|
|
|
|
# there might be a mandatory records table; act consequently |
243
|
10
|
|
|
|
|
36
|
my $mandatory = JPEG_lookup('APP13', subdir_name($what), '__mandatory'); |
244
|
10
|
100
|
|
|
|
52
|
$this->set_app13_data($mandatory, 'ADD', $what) if $mandatory; |
245
|
|
|
|
|
|
|
# obviously, update the segment |
246
|
10
|
|
|
|
|
41
|
$this->update(); |
247
|
|
|
|
|
|
|
# return the subdirectory reference |
248
|
10
|
|
|
|
|
24
|
return $subdir; |
249
|
|
|
|
|
|
|
} |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
########################################################### |
252
|
|
|
|
|
|
|
# This method removes all traces of IPTC/non-IPTC infor- # |
253
|
|
|
|
|
|
|
# mation (depending on $what) from the $index-th APP13 # |
254
|
|
|
|
|
|
|
# Photoshop-style Segment. This routine cannot fail, # |
255
|
|
|
|
|
|
|
# unless $what is invalid. The segment gets updated if # |
256
|
|
|
|
|
|
|
# the modification is made. # |
257
|
|
|
|
|
|
|
########################################################### |
258
|
|
|
|
|
|
|
sub remove_app13_info { |
259
|
9
|
|
|
9
|
0
|
17
|
my ($this, $what) = @_; |
260
|
|
|
|
|
|
|
# die on unknown $what's |
261
|
9
|
|
|
|
|
21
|
$this->sanitise_what($what); |
262
|
|
|
|
|
|
|
# return if there is nothing to erase |
263
|
9
|
50
|
|
|
|
22
|
return unless $this->is_app13_ok($what); |
264
|
|
|
|
|
|
|
# these approach is simple and crude |
265
|
9
|
|
|
|
|
117
|
@{$this->{records}} = |
|
23
|
|
|
|
|
42
|
|
266
|
9
|
|
|
|
|
18
|
grep { $_->{key} ne subdir_name($what) } @{$this->{records}}; |
|
9
|
|
|
|
|
20
|
|
267
|
|
|
|
|
|
|
# update the data area of the segment |
268
|
9
|
|
|
|
|
31
|
$this->update(); |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
########################################################### |
272
|
|
|
|
|
|
|
# This method returns a reference to a hash containing a # |
273
|
|
|
|
|
|
|
# copy of the list of records selected by $what in the # |
274
|
|
|
|
|
|
|
# current segment, if the corresponding subdirectory is # |
275
|
|
|
|
|
|
|
# present, undef otherwise. Each hash element is a (key, # |
276
|
|
|
|
|
|
|
# arrayref) pair, where 'key' is a tag and 'arrayref' # |
277
|
|
|
|
|
|
|
# points to an array with the record values. The output # |
278
|
|
|
|
|
|
|
# format is selected by the $type argument: # |
279
|
|
|
|
|
|
|
# - NUMERIC: hash with native numeric keys # |
280
|
|
|
|
|
|
|
# - TEXTUAL: hash with translated textual keys (default) # |
281
|
|
|
|
|
|
|
# If $type or $what is invalid, an exception is thrown. # |
282
|
|
|
|
|
|
|
# If a numerical key (tag) is not known, a custom textual # |
283
|
|
|
|
|
|
|
# key is created with 'Unknown_tag_' followed by the nu- # |
284
|
|
|
|
|
|
|
# merical value (solving problem with non-standard tags). # |
285
|
|
|
|
|
|
|
# ------------------------------------------------------- # |
286
|
|
|
|
|
|
|
# Since an IPTC tag can be repeateable, @$arrayref can # |
287
|
|
|
|
|
|
|
# actually contain more than one value. Moreover, if # |
288
|
|
|
|
|
|
|
# $what is "non-IPTC", resource block names are appended # |
289
|
|
|
|
|
|
|
# (so, the @$arrayref length is always even in this case, # |
290
|
|
|
|
|
|
|
# and almost always equal to two). # |
291
|
|
|
|
|
|
|
# ------------------------------------------------------- # |
292
|
|
|
|
|
|
|
# Note that there is no check at all on the validity of # |
293
|
|
|
|
|
|
|
# the Photoshop/IPTC record values: their format is not # |
294
|
|
|
|
|
|
|
# checked and one or multiple values can be attached to # |
295
|
|
|
|
|
|
|
# a single key independently of its repeatability. This # |
296
|
|
|
|
|
|
|
# is, in some sense, consistent with the fact that also # |
297
|
|
|
|
|
|
|
# "unknown" tags are included in the output. # |
298
|
|
|
|
|
|
|
########################################################### |
299
|
|
|
|
|
|
|
sub get_app13_data { |
300
|
90
|
|
|
90
|
0
|
5092
|
my ($this, $type, $what) = @_; |
301
|
|
|
|
|
|
|
# die on unknown $type's |
302
|
90
|
|
|
|
|
164
|
$this->sanitise_type($type); |
303
|
|
|
|
|
|
|
# die on unknown $what's |
304
|
88
|
|
|
|
|
230
|
$this->sanitise_what($what); |
305
|
|
|
|
|
|
|
# retrieve the appropriate records list |
306
|
88
|
|
|
|
|
261
|
my $records = $this->retrieve_app13_subdir($what); |
307
|
|
|
|
|
|
|
# return undef if the directory is not present |
308
|
88
|
50
|
|
|
|
212
|
return undef unless $records; |
309
|
|
|
|
|
|
|
# this is the data hash to be filled |
310
|
88
|
|
|
|
|
107
|
my $data = {}; |
311
|
|
|
|
|
|
|
# create a hash, where the keys are the numeric keys of @$records |
312
|
|
|
|
|
|
|
# and the values are references to (initially empty) arrays. |
313
|
88
|
|
|
|
|
133
|
$$data{$_} = [] for map { $_->{key} } @$records; |
|
866
|
|
|
|
|
1529
|
|
314
|
|
|
|
|
|
|
# These arrays are then filled with the record values, |
315
|
|
|
|
|
|
|
# accumulated according to the tag. |
316
|
88
|
|
|
|
|
202
|
push @{$$data{$_->{key}}}, $_->get_value() for @$records; |
|
866
|
|
|
|
|
1699
|
|
317
|
|
|
|
|
|
|
# if $what is "non-IPTC", append the "extra" values for each |
318
|
|
|
|
|
|
|
# record, according to the tag (this is undef, mostly). |
319
|
88
|
100
|
|
|
|
284
|
if ($what !~ /IPTC/) { |
320
|
22
|
|
|
|
|
45
|
push @{$$data{$_->{key}}}, $_->{extra} for @$records; } |
|
281
|
|
|
|
|
360
|
|
321
|
|
|
|
|
|
|
# if the type is textual, the tags must be translated; |
322
|
|
|
|
|
|
|
# if there is no positive match from JPEG_lookup, create a tag |
323
|
|
|
|
|
|
|
# carrying 'Unknown_tag_' followed by the key numerical value. |
324
|
88
|
100
|
|
|
|
238
|
%$data = map { my $match = JPEG_lookup('APP13', subdir_name($what), $_); |
|
252
|
|
|
|
|
300
|
|
325
|
252
|
100
|
|
|
|
726
|
(defined $match ? $match : "Unknown_tag_$_") |
326
|
|
|
|
|
|
|
=> $$data{$_} } keys %$data if $type eq 'TEXTUAL'; |
327
|
|
|
|
|
|
|
# return the magic scalar |
328
|
88
|
|
|
|
|
211
|
return $data; |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
########################################################### |
332
|
|
|
|
|
|
|
# This method accepts Photoshop data in various formats # |
333
|
|
|
|
|
|
|
# and updates the content of a Photoshop-style APP13 # |
334
|
|
|
|
|
|
|
# segment. The key type of each entry in the input %$data # |
335
|
|
|
|
|
|
|
# hash can be numeric or textual, independently of the # |
336
|
|
|
|
|
|
|
# others (the same key can appear in both forms, the # |
337
|
|
|
|
|
|
|
# corresponding values will be put together). The value # |
338
|
|
|
|
|
|
|
# of each entry can be an array reference or a scalar # |
339
|
|
|
|
|
|
|
# (you can use this as a shortcut for value arrays with # |
340
|
|
|
|
|
|
|
# only one value). The $action argument can be: # |
341
|
|
|
|
|
|
|
# - ADD : new records are added and nothing is deleted; # |
342
|
|
|
|
|
|
|
# however, if you try to add a non-repeatable record # |
343
|
|
|
|
|
|
|
# which is already present, the newly supplied value # |
344
|
|
|
|
|
|
|
# replaces the pre-existing value. # |
345
|
|
|
|
|
|
|
# - UPDATE : new records replace those characterised by # |
346
|
|
|
|
|
|
|
# the same tags, but the others are preserved. This # |
347
|
|
|
|
|
|
|
# makes it possible to modify repeatable records. # |
348
|
|
|
|
|
|
|
# - REPLACE : [default] all records in the relevant # |
349
|
|
|
|
|
|
|
# subdir are deleted before inserting the new ones. # |
350
|
|
|
|
|
|
|
# The return value is a reference to a hash containing # |
351
|
|
|
|
|
|
|
# the rejected key-values entries. The entries of %$data # |
352
|
|
|
|
|
|
|
# are not modified. # |
353
|
|
|
|
|
|
|
# ------------------------------------------------------- # |
354
|
|
|
|
|
|
|
# If $what implies some mandatory datasets, they are read # |
355
|
|
|
|
|
|
|
# and from tables and added, unless already present. # |
356
|
|
|
|
|
|
|
# If $what is "non-IPTC", UPDATE is a synonim of 'ADD', # |
357
|
|
|
|
|
|
|
# and the second value is used as data block name. # |
358
|
|
|
|
|
|
|
# ------------------------------------------------------- # |
359
|
|
|
|
|
|
|
# At the end, the segment data area is updated. An entry # |
360
|
|
|
|
|
|
|
# in the %$data hash may be rejected for various reasons: # |
361
|
|
|
|
|
|
|
# - the tag is undefined or not known; # |
362
|
|
|
|
|
|
|
# - the entry value is undef or points to an empty array;# |
363
|
|
|
|
|
|
|
# [IPTC only]: # |
364
|
|
|
|
|
|
|
# - the non-repeatable property is violated; # |
365
|
|
|
|
|
|
|
# - the tag is marked as invalid; # |
366
|
|
|
|
|
|
|
# - a value is undefined; # |
367
|
|
|
|
|
|
|
# - the length of a value is invalid; # |
368
|
|
|
|
|
|
|
# - a value does not match its mandatory regular expr. # |
369
|
|
|
|
|
|
|
########################################################### |
370
|
|
|
|
|
|
|
sub set_app13_data { |
371
|
79
|
|
|
79
|
0
|
5462
|
my ($this, $data, $action, $what) = @_; |
372
|
|
|
|
|
|
|
# die on unknown $action's |
373
|
79
|
|
|
|
|
151
|
$this->sanitise_action($action); |
374
|
|
|
|
|
|
|
# die on unknown $what's |
375
|
79
|
|
|
|
|
216
|
$this->sanitise_what($what); |
376
|
|
|
|
|
|
|
# return immediately if $data is not a hash reference |
377
|
79
|
100
|
|
|
|
272
|
return unless ref $data eq 'HASH'; |
378
|
|
|
|
|
|
|
# collapse UPDATE into ADD if $what is "non-IPTC" |
379
|
78
|
100
|
100
|
|
|
312
|
$action = 'ADD' if $what !~ /IPTC/ && $action eq 'UPDATE'; |
380
|
|
|
|
|
|
|
# this is the name of the target subdirectory |
381
|
78
|
|
|
|
|
136
|
my $subdir = subdir_name($what); |
382
|
|
|
|
|
|
|
# prepare two hash references and initialise them |
383
|
|
|
|
|
|
|
# with accepted and rejected data |
384
|
78
|
|
|
|
|
197
|
my ($data_accepted, $data_rejected) = screen_data($data, $what); |
385
|
|
|
|
|
|
|
# if $action is not 'REPLACE', old records need to be merged in; |
386
|
|
|
|
|
|
|
# take a copy of all current records if necessary |
387
|
78
|
100
|
|
|
|
223
|
my $oldrecs = $action eq 'REPLACE' ? {} : |
388
|
|
|
|
|
|
|
$this->get_app13_data('NUMERIC', $what); |
389
|
|
|
|
|
|
|
# loop over all entries in the %$oldrecs hash and insert them into the |
390
|
|
|
|
|
|
|
# new hash if necessary (the "old hash" is of course empty if $action |
391
|
|
|
|
|
|
|
# corresponds to 'REPLACE', so we are dealing with 'ADD' or 'UPDATE' here). |
392
|
78
|
|
|
|
|
271
|
while (my ($tag, $oldarrayref) = each %$oldrecs) { |
393
|
|
|
|
|
|
|
# a pre-existing tag must always remain, prepare a slot. |
394
|
309
|
100
|
|
|
|
536
|
$$data_accepted{$tag} = [] unless exists $$data_accepted{$tag}; |
395
|
|
|
|
|
|
|
# if the tag is already covered by the new values and the |
396
|
|
|
|
|
|
|
# $action is 'UPDATE' or $what is "non-IPTC", do nothing |
397
|
|
|
|
|
|
|
# (I am assuming that "non-IPTC" is non-repeatable) |
398
|
309
|
|
|
|
|
254
|
my $newarrayref = $$data_accepted{$tag}; |
399
|
309
|
100
|
100
|
|
|
517
|
next if @$newarrayref && ($action eq 'UPDATE' || $what !~ /IPTC/); |
|
|
|
66
|
|
|
|
|
400
|
|
|
|
|
|
|
# ... otherwise (i.e., if $action is 'ADD' or $action is 'UPDATE' |
401
|
|
|
|
|
|
|
# but the tag is not overwritten by new values) insert the old |
402
|
|
|
|
|
|
|
# values at the beginning of the value array. |
403
|
299
|
|
|
|
|
721
|
unshift @$newarrayref, @$oldarrayref; } |
404
|
|
|
|
|
|
|
# if a mandatory dataset hash is present, and the mandatory |
405
|
|
|
|
|
|
|
# datasets are note there, some more work is needed. |
406
|
78
|
100
|
|
|
|
196
|
if (my $mandatory = JPEG_lookup('APP13', $subdir, '__mandatory')) { |
407
|
65
|
|
|
|
|
116
|
my ($mand_datasets, $impossible) = screen_data($mandatory, $what); |
408
|
|
|
|
|
|
|
# If mandatory datasets are rejected, there is a big mess |
409
|
65
|
50
|
|
|
|
134
|
$this->die('Mandatory datasets rejected') if %$impossible; |
410
|
65
|
|
|
|
|
202
|
while (my ($tag, $val) = each %$mand_datasets) { |
411
|
65
|
100
|
|
|
|
318
|
$$data_accepted{$tag}=$val unless exists $$data_accepted{$tag}; }} |
412
|
|
|
|
|
|
|
# overwrite the appropriate subdir content with accepted datasets |
413
|
78
|
|
|
|
|
199
|
$this->insert_accepted($what, $data_accepted); |
414
|
|
|
|
|
|
|
# remember to commit these changes to the data area |
415
|
78
|
|
|
|
|
245
|
$this->update(); |
416
|
|
|
|
|
|
|
# return the reference of rejected tags/values |
417
|
78
|
|
|
|
|
504
|
return $data_rejected; |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
########################################################### |
421
|
|
|
|
|
|
|
# This routine actually overwrites the appropriate subdir # |
422
|
|
|
|
|
|
|
# content with accepted datasets. Keys are guaranteed to # |
423
|
|
|
|
|
|
|
# be numerically sorted (increasing). # |
424
|
|
|
|
|
|
|
########################################################### |
425
|
|
|
|
|
|
|
sub insert_accepted { |
426
|
78
|
|
|
78
|
0
|
113
|
my ($this, $what, $data) = @_; |
427
|
|
|
|
|
|
|
# get and clear the appropriate records directory |
428
|
78
|
|
|
|
|
153
|
my $dirref = $this->provide_app13_subdir($what); @$dirref = (); |
|
78
|
|
|
|
|
455
|
|
429
|
|
|
|
|
|
|
# Remember to keep only the last value for non-repeatable records. |
430
|
78
|
|
|
|
|
145
|
shift_non_repeatables($data, $what); |
431
|
|
|
|
|
|
|
# loop on datasets in increasing numeric order on tags |
432
|
78
|
|
|
|
|
285
|
for my $key (sort {$a<=>$b} keys %$data) { |
|
1044
|
|
|
|
|
872
|
|
433
|
|
|
|
|
|
|
# $what is "non-IPTC". For each key, create a resource data block |
434
|
|
|
|
|
|
|
# with the first value. If there is a second value, set "extra"; |
435
|
461
|
100
|
|
|
|
1384
|
if ($what !~ /IPTC/) { |
|
|
50
|
|
|
|
|
|
436
|
131
|
|
|
|
|
135
|
my $arrayref = $$data{$key}; |
437
|
|
|
|
|
|
|
# resource data block value (the Record obj. is in @$dirref) |
438
|
131
|
|
|
|
|
125
|
my $vref = \ $$arrayref[0]; |
439
|
131
|
|
|
|
|
290
|
$this->store_record($dirref, $key, $UNDEF, $vref, length $$vref); |
440
|
|
|
|
|
|
|
# resource data block extra (the Record obj. is in @$dirref) |
441
|
131
|
100
|
|
|
|
351
|
$this->search_record('LAST_RECORD', $dirref)->{extra} = |
442
|
|
|
|
|
|
|
$$arrayref[1] if exists $$arrayref[1]; } |
443
|
|
|
|
|
|
|
# $what is IPTC_something. For each element in the hash, create |
444
|
|
|
|
|
|
|
# one or more Records corresponding to a dataset and insert them |
445
|
|
|
|
|
|
|
# into the appropriate subdirectory. |
446
|
|
|
|
|
|
|
elsif ($what =~ /^IPTC/) { |
447
|
|
|
|
|
|
|
# each element of the array creates a new Record |
448
|
330
|
|
|
|
|
993
|
$this->store_record($dirref, $key, $ASCII, \ $_, length $_) |
449
|
330
|
|
|
|
|
252
|
for @{$$data{$key}}; } |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
########################################################### |
454
|
|
|
|
|
|
|
# This function takes a hash of candidate inputs to the # |
455
|
|
|
|
|
|
|
# APP13 segment record list and decides whether to accept # |
456
|
|
|
|
|
|
|
# or reject them. It returns two references to two hashes # |
457
|
|
|
|
|
|
|
# with accepted and rejected data. All keys of accepted # |
458
|
|
|
|
|
|
|
# records are forced to numeric form. The actual data # |
459
|
|
|
|
|
|
|
# screening is done by value_is_OK(). # |
460
|
|
|
|
|
|
|
########################################################### |
461
|
|
|
|
|
|
|
sub screen_data { |
462
|
143
|
|
|
143
|
0
|
159
|
my ($data, $what) = @_; |
463
|
|
|
|
|
|
|
# prepare repositories for good and bad guys |
464
|
143
|
|
|
|
|
201
|
my ($data_accepted, $data_rejected) = ({}, {}); |
465
|
|
|
|
|
|
|
# this is the name of the target subdirectory |
466
|
143
|
|
|
|
|
219
|
my $subdir = subdir_name($what); |
467
|
|
|
|
|
|
|
# Force an ordering on %$data; this is necessary because the same key |
468
|
|
|
|
|
|
|
# can be present twice, in numeric and textual form, and we want the |
469
|
|
|
|
|
|
|
# corresponding value merging to be stable (numeric goes first). |
470
|
143
|
|
|
|
|
456
|
for (sort keys %$data) { |
471
|
|
|
|
|
|
|
# get copies, do not manipulate original data! |
472
|
263
|
|
|
|
|
369
|
my ($tag, $value) = ($_, $$data{$_}); |
473
|
|
|
|
|
|
|
# accept both array references and plain scalars |
474
|
263
|
100
|
|
|
|
551
|
$value = (ref $value) ? [ @$value ] : [ $value ]; |
475
|
|
|
|
|
|
|
# if $tag is not numeric, try a textual to numeric |
476
|
|
|
|
|
|
|
# translation; (but don't set it to an undefined value yet) |
477
|
263
|
100
|
66
|
|
|
1406
|
if (defined $tag && $tag !~ /^\d*$/) { |
478
|
198
|
|
|
|
|
444
|
my $num_tag = JPEG_lookup('APP13', $subdir, $tag); |
479
|
198
|
100
|
|
|
|
1209
|
$tag = $num_tag if defined $num_tag; } |
480
|
|
|
|
|
|
|
# get a reference to the correct repository: an entry is |
481
|
|
|
|
|
|
|
# accepted if it passes the value_is_OK test, rejected otherwise. |
482
|
263
|
100
|
|
|
|
411
|
my $repository = value_is_OK($tag, $value, $what) ? |
483
|
|
|
|
|
|
|
$data_accepted : $data_rejected; |
484
|
|
|
|
|
|
|
# add data to the repository (do not overwrite!) |
485
|
263
|
100
|
|
|
|
688
|
$$repository{$tag} = [ ] unless exists $$repository{$tag}; |
486
|
263
|
|
|
|
|
238
|
push @{$$repository{$tag}}, @$value; } |
|
263
|
|
|
|
|
625
|
|
487
|
|
|
|
|
|
|
# return references to the two repositories |
488
|
143
|
|
|
|
|
270
|
return ($data_accepted, $data_rejected); |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
########################################################### |
492
|
|
|
|
|
|
|
# This function "corrects" a hash of records violating # |
493
|
|
|
|
|
|
|
# some non-repeatable constraint. If a non-repeatable # |
494
|
|
|
|
|
|
|
# record is found with multiple values, only the last one # |
495
|
|
|
|
|
|
|
# is retained. $what is needed to retrieve syntax tables. # |
496
|
|
|
|
|
|
|
########################################################### |
497
|
|
|
|
|
|
|
sub shift_non_repeatables { |
498
|
78
|
|
|
78
|
0
|
88
|
my ($hashref, $what) = @_; |
499
|
|
|
|
|
|
|
# loop over all elements in the hash |
500
|
78
|
|
|
|
|
308
|
while (my ($tag, $arrayref) = each %$hashref) { |
501
|
|
|
|
|
|
|
# get the constraints of this record |
502
|
461
|
|
|
|
|
551
|
my $constraints = JPEG_lookup |
503
|
|
|
|
|
|
|
('APP13', subdir_name($what), '__syntax', $tag); |
504
|
|
|
|
|
|
|
# skip unknown tags (this shouldn't happen) and repeatable records |
505
|
461
|
100
|
100
|
|
|
2010
|
next unless $constraints && $$constraints[1] eq 'N'; |
506
|
|
|
|
|
|
|
# retain only the last element of this non-repeatable record |
507
|
246
|
100
|
|
|
|
973
|
$$hashref{$tag} = [ $$arrayref[$#$arrayref] ] if @$arrayref != 1; |
508
|
|
|
|
|
|
|
} |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
########################################################### |
512
|
|
|
|
|
|
|
# This function return true if a given value fits a given # |
513
|
|
|
|
|
|
|
# tag definition, false otherwise. The input arguments are# |
514
|
|
|
|
|
|
|
# a numeric tag and an array reference, as usual. + $what # |
515
|
|
|
|
|
|
|
########################################################### |
516
|
|
|
|
|
|
|
sub value_is_OK { |
517
|
263
|
|
|
263
|
0
|
330
|
my ($tag, $arrayref, $what) = @_; |
518
|
|
|
|
|
|
|
# $tag must be defined |
519
|
263
|
50
|
|
|
|
413
|
return undef unless defined $tag; |
520
|
|
|
|
|
|
|
# $tag must be a numeric value |
521
|
263
|
100
|
|
|
|
785
|
return undef unless $tag =~ /^\d*$/; |
522
|
|
|
|
|
|
|
# $arrayref must be an array reference |
523
|
256
|
50
|
33
|
|
|
975
|
return undef unless ref $arrayref && ref $arrayref eq 'ARRAY'; |
524
|
|
|
|
|
|
|
# the referenced array must contain at least one element |
525
|
256
|
100
|
|
|
|
385
|
return undef unless @$arrayref; |
526
|
|
|
|
|
|
|
# if the tag is not known, it is not acceptable |
527
|
253
|
100
|
|
|
|
370
|
return undef unless JPEG_lookup('APP13', subdir_name($what), $tag); |
528
|
|
|
|
|
|
|
# it $what is "non-IPTC", the number of values can be only 1 or 2 |
529
|
247
|
100
|
100
|
|
|
852
|
return undef if $what !~ /IPTC/ && scalar @$arrayref > 2; |
530
|
|
|
|
|
|
|
# the following tests are applied only if a syntax def. is present |
531
|
246
|
|
|
|
|
343
|
my $constraints = JPEG_lookup('APP13',subdir_name($what),'__syntax',$tag); |
532
|
246
|
50
|
|
|
|
429
|
return 1 unless defined $constraints; |
533
|
|
|
|
|
|
|
# if the tag is non-repeatable, accept exactly one element |
534
|
246
|
100
|
100
|
|
|
899
|
return undef if $$constraints[1] eq 'N' && @$arrayref != 1; |
535
|
|
|
|
|
|
|
# get the mandatory "regular expression" for this tag |
536
|
242
|
|
|
|
|
255
|
my $regex = $$constraints[4]; |
537
|
|
|
|
|
|
|
# if $regex matches 'invalid', inhibit this tag |
538
|
242
|
100
|
|
|
|
408
|
return undef if $regex =~ /invalid/; |
539
|
|
|
|
|
|
|
# run the following tests on all values |
540
|
239
|
|
|
|
|
281
|
for (@$arrayref) { |
541
|
|
|
|
|
|
|
# the second value for "non-IPTC" should not be tested |
542
|
291
|
100
|
100
|
|
|
742
|
next if $what !~ /IPTC/ && ($_||1) ne ($$arrayref[0]||1); |
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
543
|
|
|
|
|
|
|
# each value must be defined |
544
|
277
|
100
|
|
|
|
389
|
return undef unless defined $_; |
545
|
|
|
|
|
|
|
# each value length must fit the appropriate range |
546
|
275
|
100
|
100
|
|
|
933
|
return undef if (length $_ < $$constraints[2] || |
547
|
|
|
|
|
|
|
length $_ > $$constraints[3] ); |
548
|
|
|
|
|
|
|
# each value must match the mandatory regular expression; |
549
|
|
|
|
|
|
|
# but, if $regex matches 'binary', everything is permitted |
550
|
272
|
100
|
100
|
|
|
3150
|
return undef unless /$regex/ || $regex =~ /binary/; } |
551
|
|
|
|
|
|
|
# all tests were successful! return success |
552
|
226
|
|
|
|
|
545
|
return 1; |
553
|
|
|
|
|
|
|
} |
554
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
# successful package load |
556
|
|
|
|
|
|
|
1; |