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   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__