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
|
|
3635
|
use strict; |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
176
|
|
14
|
5
|
|
|
5
|
|
26
|
use vars qw($VERSION); |
|
5
|
|
|
|
|
9
|
|
|
5
|
|
|
|
|
241
|
|
15
|
5
|
|
|
5
|
|
25
|
use Image::ExifTool qw(:DataAccess :Utils); |
|
5
|
|
|
|
|
8
|
|
|
5
|
|
|
|
|
5861
|
|
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
|
136
|
my ($et, $dirInfo) = @_; |
75
|
29
|
|
|
|
|
73
|
my $raf = $$dirInfo{RAF}; |
76
|
29
|
|
|
|
|
99
|
my $curPos = $raf->Tell(); |
77
|
29
|
|
100
|
|
|
111
|
my $offset = $$dirInfo{Offset} || 0; # offset from end of file |
78
|
29
|
|
|
|
|
55
|
my $rtnVal = 0; |
79
|
|
|
|
|
|
|
|
80
|
29
|
|
|
|
|
50
|
NoAFCP: for (;;) { |
81
|
29
|
|
|
|
|
60
|
my ($buff, $fix, $dirBuff, $valBuff, $fixup, $vers); |
82
|
|
|
|
|
|
|
# look for AXS trailer |
83
|
29
|
50
|
33
|
|
|
105
|
last unless $raf->Seek(-12-$offset, 2) and |
|
|
|
33
|
|
|
|
|
84
|
|
|
|
|
|
|
$raf->Read($buff, 12) == 12 and |
85
|
|
|
|
|
|
|
$buff =~ /^(AXS(!|\*))/; |
86
|
29
|
|
|
|
|
106
|
my $endPos = $raf->Tell(); |
87
|
29
|
|
|
|
|
91
|
my $hdr = $1; |
88
|
29
|
50
|
|
|
|
152
|
SetByteOrder($2 eq '!' ? 'MM' : 'II'); |
89
|
29
|
|
|
|
|
126
|
my $startPos = Get32u(\$buff, 4); |
90
|
29
|
50
|
33
|
|
|
114
|
if ($raf->Seek($startPos, 0) and $raf->Read($buff, 12) == 12 and $buff =~ /^$hdr/) { |
|
|
|
33
|
|
|
|
|
91
|
29
|
|
|
|
|
68
|
$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
|
|
|
|
|
88
|
$$dirInfo{DataPos} = $startPos + $fix; # actual start position |
121
|
29
|
|
|
|
|
81
|
$$dirInfo{DirLen} = $endPos - ($startPos + $fix); |
122
|
|
|
|
|
|
|
|
123
|
29
|
|
|
|
|
50
|
$rtnVal = 1; |
124
|
29
|
|
|
|
|
105
|
my $verbose = $et->Options('Verbose'); |
125
|
29
|
|
|
|
|
90
|
my $out = $et->Options('TextOut'); |
126
|
29
|
|
|
|
|
82
|
my $outfile = $$dirInfo{OutFile}; |
127
|
29
|
100
|
|
|
|
78
|
if ($outfile) { |
128
|
|
|
|
|
|
|
# allow all AFCP information to be deleted |
129
|
8
|
50
|
|
|
|
32
|
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
|
|
|
|
|
21
|
$dirBuff = $valBuff = ''; |
135
|
8
|
|
|
|
|
91
|
require Image::ExifTool::Fixup; |
136
|
8
|
|
|
|
|
22
|
$fixup = $$dirInfo{Fixup}; |
137
|
8
|
50
|
|
|
|
64
|
$fixup or $fixup = $$dirInfo{Fixup} = new Image::ExifTool::Fixup; |
138
|
8
|
|
|
|
|
27
|
$vers = substr($buff, 4, 2); # get version number |
139
|
|
|
|
|
|
|
} else { |
140
|
21
|
50
|
33
|
|
|
117
|
$et->DumpTrailer($dirInfo) if $verbose or $$et{HTML_DUMP}; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
# read AFCP directory data |
143
|
29
|
|
|
|
|
104
|
my $numEntries = Get16u(\$buff, 6); |
144
|
29
|
|
|
|
|
59
|
my $dir; |
145
|
29
|
50
|
|
|
|
109
|
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
|
|
|
122
|
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
|
|
|
|
83
|
$fix and $et->Warn("Adjusted AFCP offsets by $fix", 1); |
155
|
|
|
|
|
|
|
# |
156
|
|
|
|
|
|
|
# process AFCP directory |
157
|
|
|
|
|
|
|
# |
158
|
29
|
|
|
|
|
84
|
my $tagTablePtr = GetTagTable('Image::ExifTool::AFCP::Main'); |
159
|
29
|
|
|
|
|
69
|
my ($index, $entry); |
160
|
29
|
|
|
|
|
109
|
for ($index=0; $index<$numEntries; ++$index) { |
161
|
58
|
|
|
|
|
102
|
my $entry = 12 * $index; |
162
|
58
|
|
|
|
|
128
|
my $tag = substr($dir, $entry, 4); |
163
|
58
|
|
|
|
|
156
|
my $size = Get32u(\$dir, $entry + 4); |
164
|
58
|
|
|
|
|
168
|
my $offset = Get32u(\$dir, $entry + 8); |
165
|
58
|
50
|
33
|
|
|
294
|
if ($size < 0x80000000 and |
|
|
|
33
|
|
|
|
|
166
|
|
|
|
|
|
|
$raf->Seek($offset+$fix, 0) and |
167
|
|
|
|
|
|
|
$raf->Read($buff, $size) == $size) |
168
|
|
|
|
|
|
|
{ |
169
|
58
|
100
|
|
|
|
135
|
if ($outfile) { |
170
|
|
|
|
|
|
|
# rewrite this information |
171
|
16
|
|
|
|
|
65
|
my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag); |
172
|
16
|
50
|
66
|
|
|
68
|
if ($tagInfo and $$tagInfo{SubDirectory}) { |
173
|
8
|
|
|
|
|
50
|
my %subdirInfo = ( |
174
|
|
|
|
|
|
|
DataPt => \$buff, |
175
|
|
|
|
|
|
|
DirStart => 0, |
176
|
|
|
|
|
|
|
DirLen => $size, |
177
|
|
|
|
|
|
|
DataPos => $offset + $fix, |
178
|
|
|
|
|
|
|
Parent => 'AFCP', |
179
|
|
|
|
|
|
|
); |
180
|
8
|
|
|
|
|
29
|
my $subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable}); |
181
|
8
|
|
|
|
|
35
|
my $newDir = $et->WriteDirectory(\%subdirInfo, $subTable); |
182
|
8
|
100
|
|
|
|
50
|
if (defined $newDir) { |
183
|
1
|
|
|
|
|
2
|
$size = length $newDir; |
184
|
1
|
|
|
|
|
3
|
$buff = $newDir; |
185
|
|
|
|
|
|
|
} |
186
|
|
|
|
|
|
|
} |
187
|
16
|
|
|
|
|
89
|
$fixup->AddFixup(length($dirBuff) + 8); |
188
|
16
|
|
|
|
|
47
|
$dirBuff .= $tag . Set32u($size) . Set32u(length $valBuff); |
189
|
16
|
|
|
|
|
70
|
$valBuff .= $buff; |
190
|
|
|
|
|
|
|
} else { |
191
|
|
|
|
|
|
|
# extract information |
192
|
42
|
|
|
|
|
204
|
$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
|
|
|
145
|
if ($outfile and length($dirBuff)) { |
206
|
8
|
|
|
|
|
27
|
my $outPos = Tell($outfile); # get current outfile position |
207
|
|
|
|
|
|
|
# apply fixup to directory pointers |
208
|
8
|
|
|
|
|
22
|
my $valPos = $outPos + 12; # start of value data |
209
|
8
|
|
|
|
|
22
|
$fixup->{Shift} += $valPos + length($dirBuff); |
210
|
8
|
|
|
|
|
40
|
$fixup->ApplyFixup(\$dirBuff); |
211
|
|
|
|
|
|
|
# write the AFCP header, directory, value data and EOF record (with zero checksums) |
212
|
8
|
50
|
|
|
|
51
|
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
|
|
|
|
|
40
|
$fixup->AddFixup(length($dirBuff) + length($valBuff) + 4); |
216
|
8
|
|
|
|
|
23
|
$fixup->{Start} += $valPos; |
217
|
8
|
|
|
|
|
21
|
$fixup->{Shift} -= $valPos; |
218
|
|
|
|
|
|
|
} |
219
|
29
|
|
|
|
|
98
|
last; |
220
|
|
|
|
|
|
|
} |
221
|
29
|
|
|
|
|
82
|
return $rtnVal; |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
1; # end |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
__END__ |