| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
2
|
|
|
|
|
|
|
# File: AFCP.pm |
|
3
|
|
|
|
|
|
|
# |
|
4
|
|
|
|
|
|
|
# Description: Read/write AFCP trailer |
|
5
|
|
|
|
|
|
|
# |
|
6
|
|
|
|
|
|
|
# Revisions: 12/26/2005 - P. Harvey Created |
|
7
|
|
|
|
|
|
|
# |
|
8
|
|
|
|
|
|
|
# References: 1) http://web.archive.org/web/20080828211305/http://www.tocarte.com/media/axs_afcp_spec.pdf |
|
9
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
package Image::ExifTool::AFCP; |
|
12
|
|
|
|
|
|
|
|
|
13
|
5
|
|
|
5
|
|
4448
|
use strict; |
|
|
5
|
|
|
|
|
12
|
|
|
|
5
|
|
|
|
|
201
|
|
|
14
|
5
|
|
|
5
|
|
27
|
use vars qw($VERSION); |
|
|
5
|
|
|
|
|
15
|
|
|
|
5
|
|
|
|
|
269
|
|
|
15
|
5
|
|
|
5
|
|
41
|
use Image::ExifTool qw(:DataAccess :Utils); |
|
|
5
|
|
|
|
|
11
|
|
|
|
5
|
|
|
|
|
7009
|
|
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
$VERSION = '1.08'; |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
sub ProcessAFCP($$); |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
%Image::ExifTool::AFCP::Main = ( |
|
22
|
|
|
|
|
|
|
PROCESS_PROC => \&ProcessAFCP, |
|
23
|
|
|
|
|
|
|
NOTES => q{ |
|
24
|
|
|
|
|
|
|
AFCP stands for AXS File Concatenation Protocol, and is a poorly designed |
|
25
|
|
|
|
|
|
|
protocol for appending information to the end of files. This can be used as |
|
26
|
|
|
|
|
|
|
an auxiliary technique to store IPTC information in images, but is |
|
27
|
|
|
|
|
|
|
incompatible with some file formats. |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
ExifTool will read and write (but not create) AFCP IPTC information in JPEG |
|
30
|
|
|
|
|
|
|
and TIFF images. |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
See |
|
33
|
|
|
|
|
|
|
L |
|
34
|
|
|
|
|
|
|
for the AFCP specification. |
|
35
|
|
|
|
|
|
|
}, |
|
36
|
|
|
|
|
|
|
IPTC => { SubDirectory => { TagTable => 'Image::ExifTool::IPTC::Main' } }, |
|
37
|
|
|
|
|
|
|
TEXT => 'Text', |
|
38
|
|
|
|
|
|
|
Nail => { |
|
39
|
|
|
|
|
|
|
Name => 'ThumbnailImage', |
|
40
|
|
|
|
|
|
|
Groups => { 2 => 'Preview' }, |
|
41
|
|
|
|
|
|
|
# (the specification allows for a variable amount of padding before |
|
42
|
|
|
|
|
|
|
# the image after a 10-byte header, so look for the JPEG SOI marker, |
|
43
|
|
|
|
|
|
|
# otherwise assume a fixed 8 bytes of padding) |
|
44
|
|
|
|
|
|
|
RawConv => q{ |
|
45
|
|
|
|
|
|
|
pos($val) = 10; |
|
46
|
|
|
|
|
|
|
my $start = ($val =~ /\xff\xd8\xff/g) ? pos($val) - 3 : 18; |
|
47
|
|
|
|
|
|
|
my $img = substr($val, $start); |
|
48
|
|
|
|
|
|
|
return $self->ValidateImage(\$img, $tag); |
|
49
|
|
|
|
|
|
|
}, |
|
50
|
|
|
|
|
|
|
}, |
|
51
|
|
|
|
|
|
|
PrVw => { |
|
52
|
|
|
|
|
|
|
Name => 'PreviewImage', |
|
53
|
|
|
|
|
|
|
Groups => { 2 => 'Preview' }, |
|
54
|
|
|
|
|
|
|
RawConv => q{ |
|
55
|
|
|
|
|
|
|
pos($val) = 10; |
|
56
|
|
|
|
|
|
|
my $start = ($val =~ /\xff\xd8\xff/g) ? pos($val) - 3 : 18; |
|
57
|
|
|
|
|
|
|
my $img = substr($val, $start); |
|
58
|
|
|
|
|
|
|
return $self->ValidateImage(\$img, $tag); |
|
59
|
|
|
|
|
|
|
}, |
|
60
|
|
|
|
|
|
|
}, |
|
61
|
|
|
|
|
|
|
); |
|
62
|
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
#------------------------------------------------------------------------------ |
|
64
|
|
|
|
|
|
|
# Read/write AFCP information in a file |
|
65
|
|
|
|
|
|
|
# Inputs: 0) ExifTool object reference, 1) dirInfo reference |
|
66
|
|
|
|
|
|
|
# (Set 'ScanForAFCP' member in dirInfo to scan from current position for AFCP) |
|
67
|
|
|
|
|
|
|
# Returns: 1 on success, 0 if this file didn't contain AFCP information |
|
68
|
|
|
|
|
|
|
# -1 on write error or if the offsets were incorrect on reading |
|
69
|
|
|
|
|
|
|
# - updates DataPos to point to actual AFCP start if ScanForAFCP is set |
|
70
|
|
|
|
|
|
|
# - updates DirLen to trailer length |
|
71
|
|
|
|
|
|
|
# - returns Fixup reference in dirInfo hash when writing |
|
72
|
|
|
|
|
|
|
sub ProcessAFCP($$) |
|
73
|
|
|
|
|
|
|
{ |
|
74
|
29
|
|
|
29
|
0
|
108
|
my ($et, $dirInfo) = @_; |
|
75
|
29
|
|
|
|
|
100
|
my $raf = $$dirInfo{RAF}; |
|
76
|
29
|
|
|
|
|
116
|
my $curPos = $raf->Tell(); |
|
77
|
29
|
|
100
|
|
|
176
|
my $offset = $$dirInfo{Offset} || 0; # offset from end of file |
|
78
|
29
|
|
|
|
|
82
|
my $rtnVal = 0; |
|
79
|
|
|
|
|
|
|
|
|
80
|
29
|
|
|
|
|
77
|
NoAFCP: for (;;) { |
|
81
|
29
|
|
|
|
|
79
|
my ($buff, $fix, $dirBuff, $valBuff, $fixup, $vers); |
|
82
|
|
|
|
|
|
|
# look for AXS trailer |
|
83
|
29
|
50
|
33
|
|
|
125
|
last unless $raf->Seek(-12-$offset, 2) and |
|
|
|
|
33
|
|
|
|
|
|
84
|
|
|
|
|
|
|
$raf->Read($buff, 12) == 12 and |
|
85
|
|
|
|
|
|
|
$buff =~ /^(AXS(!|\*))/; |
|
86
|
29
|
|
|
|
|
148
|
my $endPos = $raf->Tell(); |
|
87
|
29
|
|
|
|
|
148
|
my $hdr = $1; |
|
88
|
29
|
50
|
|
|
|
257
|
SetByteOrder($2 eq '!' ? 'MM' : 'II'); |
|
89
|
29
|
|
|
|
|
214
|
my $startPos = Get32u(\$buff, 4); |
|
90
|
29
|
50
|
33
|
|
|
174
|
if ($raf->Seek($startPos, 0) and $raf->Read($buff, 12) == 12 and $buff =~ /^$hdr/) { |
|
|
|
|
33
|
|
|
|
|
|
91
|
29
|
|
|
|
|
89
|
$fix = 0; |
|
92
|
|
|
|
|
|
|
} else { |
|
93
|
0
|
|
|
|
|
0
|
$rtnVal = -1; |
|
94
|
|
|
|
|
|
|
# look for start of AXS trailer if 'ScanForAFCP' |
|
95
|
0
|
0
|
0
|
|
|
0
|
last unless $$dirInfo{ScanForAFCP} and $raf->Seek($curPos, 0); |
|
96
|
0
|
|
|
|
|
0
|
my $actualPos = $curPos; |
|
97
|
|
|
|
|
|
|
# first look for header right at current position |
|
98
|
0
|
|
|
|
|
0
|
for (;;) { |
|
99
|
0
|
0
|
0
|
|
|
0
|
last if $raf->Read($buff, 12) == 12 and $buff =~ /^$hdr/; |
|
100
|
0
|
0
|
|
|
|
0
|
last NoAFCP if $actualPos != $curPos; |
|
101
|
|
|
|
|
|
|
# scan for AXS header (could be after preview image) |
|
102
|
0
|
|
|
|
|
0
|
for (;;) { |
|
103
|
0
|
|
|
|
|
0
|
my $buf2; |
|
104
|
0
|
0
|
|
|
|
0
|
$raf->Read($buf2, 65536) or last NoAFCP; |
|
105
|
0
|
|
|
|
|
0
|
$buff .= $buf2; |
|
106
|
0
|
0
|
|
|
|
0
|
if ($buff =~ /$hdr/g) { |
|
107
|
0
|
|
|
|
|
0
|
$actualPos += pos($buff) - length($hdr); |
|
108
|
0
|
|
|
|
|
0
|
last; # ok, now go back and re-read header |
|
109
|
|
|
|
|
|
|
} |
|
110
|
0
|
|
|
|
|
0
|
$buf2 = substr($buf2, -3); # only need last 3 bytes for next test |
|
111
|
0
|
|
|
|
|
0
|
$actualPos += length($buff) - length($buf2); |
|
112
|
0
|
|
|
|
|
0
|
$buff = $buf2; |
|
113
|
|
|
|
|
|
|
} |
|
114
|
0
|
0
|
|
|
|
0
|
last unless $raf->Seek($actualPos, 0); # seek to start of AFCP |
|
115
|
|
|
|
|
|
|
} |
|
116
|
|
|
|
|
|
|
# calculate shift for fixing AFCP offsets |
|
117
|
0
|
|
|
|
|
0
|
$fix = $actualPos - $startPos; |
|
118
|
|
|
|
|
|
|
} |
|
119
|
|
|
|
|
|
|
# set variables returned in dirInfo hash |
|
120
|
29
|
|
|
|
|
126
|
$$dirInfo{DataPos} = $startPos + $fix; # actual start position |
|
121
|
29
|
|
|
|
|
144
|
$$dirInfo{DirLen} = $endPos - ($startPos + $fix); |
|
122
|
|
|
|
|
|
|
|
|
123
|
29
|
|
|
|
|
72
|
$rtnVal = 1; |
|
124
|
29
|
|
|
|
|
189
|
my $verbose = $et->Options('Verbose'); |
|
125
|
29
|
|
|
|
|
133
|
my $out = $et->Options('TextOut'); |
|
126
|
29
|
|
|
|
|
109
|
my $outfile = $$dirInfo{OutFile}; |
|
127
|
29
|
100
|
|
|
|
118
|
if ($outfile) { |
|
128
|
|
|
|
|
|
|
# allow all AFCP information to be deleted |
|
129
|
8
|
50
|
|
|
|
37
|
if ($$et{DEL_GROUP}{AFCP}) { |
|
130
|
0
|
0
|
|
|
|
0
|
$verbose and print $out " Deleting AFCP\n"; |
|
131
|
0
|
|
|
|
|
0
|
++$$et{CHANGED}; |
|
132
|
0
|
|
|
|
|
0
|
last; |
|
133
|
|
|
|
|
|
|
} |
|
134
|
8
|
|
|
|
|
20
|
$dirBuff = $valBuff = ''; |
|
135
|
8
|
|
|
|
|
47
|
require Image::ExifTool::Fixup; |
|
136
|
8
|
|
|
|
|
19
|
$fixup = $$dirInfo{Fixup}; |
|
137
|
8
|
50
|
|
|
|
64
|
$fixup or $fixup = $$dirInfo{Fixup} = new Image::ExifTool::Fixup; |
|
138
|
8
|
|
|
|
|
32
|
$vers = substr($buff, 4, 2); # get version number |
|
139
|
|
|
|
|
|
|
} else { |
|
140
|
21
|
50
|
33
|
|
|
141
|
$et->DumpTrailer($dirInfo) if $verbose or $$et{HTML_DUMP}; |
|
141
|
|
|
|
|
|
|
} |
|
142
|
|
|
|
|
|
|
# read AFCP directory data |
|
143
|
29
|
|
|
|
|
137
|
my $numEntries = Get16u(\$buff, 6); |
|
144
|
29
|
|
|
|
|
75
|
my $dir; |
|
145
|
29
|
50
|
|
|
|
157
|
unless ($raf->Read($dir, 12 * $numEntries) == 12 * $numEntries) { |
|
146
|
0
|
|
|
|
|
0
|
$et->Error('Error reading AFCP directory', 1); |
|
147
|
0
|
|
|
|
|
0
|
last; |
|
148
|
|
|
|
|
|
|
} |
|
149
|
29
|
50
|
33
|
|
|
177
|
if ($verbose > 2 and not $outfile) { |
|
150
|
0
|
|
|
|
|
0
|
my $dat = $buff . $dir; |
|
151
|
0
|
|
|
|
|
0
|
print $out " AFCP Directory:\n"; |
|
152
|
0
|
|
|
|
|
0
|
$et->VerboseDump(\$dat, Addr => $$dirInfo{DataPos}, Width => 12); |
|
153
|
|
|
|
|
|
|
} |
|
154
|
29
|
50
|
|
|
|
89
|
$fix and $et->Warn("Adjusted AFCP offsets by $fix", 1); |
|
155
|
|
|
|
|
|
|
# |
|
156
|
|
|
|
|
|
|
# process AFCP directory |
|
157
|
|
|
|
|
|
|
# |
|
158
|
29
|
|
|
|
|
101
|
my $tagTablePtr = GetTagTable('Image::ExifTool::AFCP::Main'); |
|
159
|
29
|
|
|
|
|
101
|
my ($index, $entry); |
|
160
|
29
|
|
|
|
|
154
|
for ($index=0; $index<$numEntries; ++$index) { |
|
161
|
58
|
|
|
|
|
145
|
my $entry = 12 * $index; |
|
162
|
58
|
|
|
|
|
166
|
my $tag = substr($dir, $entry, 4); |
|
163
|
58
|
|
|
|
|
239
|
my $size = Get32u(\$dir, $entry + 4); |
|
164
|
58
|
|
|
|
|
182
|
my $offset = Get32u(\$dir, $entry + 8); |
|
165
|
58
|
50
|
33
|
|
|
330
|
if ($size < 0x80000000 and |
|
|
|
|
33
|
|
|
|
|
|
166
|
|
|
|
|
|
|
$raf->Seek($offset+$fix, 0) and |
|
167
|
|
|
|
|
|
|
$raf->Read($buff, $size) == $size) |
|
168
|
|
|
|
|
|
|
{ |
|
169
|
58
|
100
|
|
|
|
193
|
if ($outfile) { |
|
170
|
|
|
|
|
|
|
# rewrite this information |
|
171
|
16
|
|
|
|
|
73
|
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); |
|
172
|
16
|
50
|
66
|
|
|
99
|
if ($tagInfo and $$tagInfo{SubDirectory}) { |
|
173
|
8
|
|
|
|
|
59
|
my %subdirInfo = ( |
|
174
|
|
|
|
|
|
|
DataPt => \$buff, |
|
175
|
|
|
|
|
|
|
DirStart => 0, |
|
176
|
|
|
|
|
|
|
DirLen => $size, |
|
177
|
|
|
|
|
|
|
DataPos => $offset + $fix, |
|
178
|
|
|
|
|
|
|
Parent => 'AFCP', |
|
179
|
|
|
|
|
|
|
); |
|
180
|
8
|
|
|
|
|
34
|
my $subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); |
|
181
|
8
|
|
|
|
|
96
|
my $newDir = $et->WriteDirectory(\%subdirInfo, $subTable); |
|
182
|
8
|
100
|
|
|
|
48
|
if (defined $newDir) { |
|
183
|
1
|
|
|
|
|
2
|
$size = length $newDir; |
|
184
|
1
|
|
|
|
|
4
|
$buff = $newDir; |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
} |
|
187
|
16
|
|
|
|
|
94
|
$fixup->AddFixup(length($dirBuff) + 8); |
|
188
|
16
|
|
|
|
|
83
|
$dirBuff .= $tag . Set32u($size) . Set32u(length $valBuff); |
|
189
|
16
|
|
|
|
|
80
|
$valBuff .= $buff; |
|
190
|
|
|
|
|
|
|
} else { |
|
191
|
|
|
|
|
|
|
# extract information |
|
192
|
42
|
|
|
|
|
211
|
$et->HandleTag($tagTablePtr, $tag, $buff, |
|
193
|
|
|
|
|
|
|
DataPt => \$buff, |
|
194
|
|
|
|
|
|
|
Size => $size, |
|
195
|
|
|
|
|
|
|
Index => $index, |
|
196
|
|
|
|
|
|
|
DataPos => $offset + $fix, |
|
197
|
|
|
|
|
|
|
); |
|
198
|
|
|
|
|
|
|
} |
|
199
|
|
|
|
|
|
|
} else { |
|
200
|
0
|
|
|
|
|
0
|
$et->Warn("Bad AFCP directory"); |
|
201
|
0
|
0
|
|
|
|
0
|
$rtnVal = -1 if $outfile; |
|
202
|
0
|
|
|
|
|
0
|
last; |
|
203
|
|
|
|
|
|
|
} |
|
204
|
|
|
|
|
|
|
} |
|
205
|
29
|
100
|
66
|
|
|
185
|
if ($outfile and length($dirBuff)) { |
|
206
|
8
|
|
|
|
|
37
|
my $outPos = Tell($outfile); # get current outfile position |
|
207
|
|
|
|
|
|
|
# apply fixup to directory pointers |
|
208
|
8
|
|
|
|
|
25
|
my $valPos = $outPos + 12; # start of value data |
|
209
|
8
|
|
|
|
|
31
|
$fixup->{Shift} += $valPos + length($dirBuff); |
|
210
|
8
|
|
|
|
|
47
|
$fixup->ApplyFixup(\$dirBuff); |
|
211
|
|
|
|
|
|
|
# write the AFCP header, directory, value data and EOF record (with zero checksums) |
|
212
|
8
|
50
|
|
|
|
48
|
Write($outfile, $hdr, $vers, Set16u(length($dirBuff)/12), Set32u(0), |
|
213
|
|
|
|
|
|
|
$dirBuff, $valBuff, $hdr, Set32u($outPos), Set32u(0)) or $rtnVal = -1; |
|
214
|
|
|
|
|
|
|
# complete fixup so the calling routine can apply further shifts |
|
215
|
8
|
|
|
|
|
65
|
$fixup->AddFixup(length($dirBuff) + length($valBuff) + 4); |
|
216
|
8
|
|
|
|
|
22
|
$fixup->{Start} += $valPos; |
|
217
|
8
|
|
|
|
|
25
|
$fixup->{Shift} -= $valPos; |
|
218
|
|
|
|
|
|
|
} |
|
219
|
29
|
|
|
|
|
98
|
last; |
|
220
|
|
|
|
|
|
|
} |
|
221
|
29
|
|
|
|
|
94
|
return $rtnVal; |
|
222
|
|
|
|
|
|
|
} |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
1; # end |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
__END__ |