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