File Coverage

blib/lib/Image/ExifTool/AFCP.pm
Criterion Covered Total %
statement 74 103 71.8
branch 20 48 41.6
condition 14 38 36.8
subroutine 4 4 100.0
pod 0 1 0.0
total 112 194 57.7


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__