AFCP.pm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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. package Image::ExifTool::AFCP;
  11. use strict;
  12. use vars qw($VERSION);
  13. use Image::ExifTool qw(:DataAccess :Utils);
  14. $VERSION = '1.07';
  15. sub ProcessAFCP($$);
  16. %Image::ExifTool::AFCP::Main = (
  17. PROCESS_PROC => \&ProcessAFCP,
  18. NOTES => q{
  19. AFCP stands for AXS File Concatenation Protocol, and is a poorly designed
  20. protocol for appending information to the end of files. This can be used as
  21. an auxiliary technique to store IPTC information in images, but is
  22. incompatible with some file formats.
  23. ExifTool will read and write (but not create) AFCP IPTC information in JPEG
  24. and TIFF images.
  25. See
  26. L<http://web.archive.org/web/20080828211305/http://www.tocarte.com/media/axs_afcp_spec.pdf>
  27. for the AFCP specification.
  28. },
  29. IPTC => { SubDirectory => { TagTable => 'Image::ExifTool::IPTC::Main' } },
  30. TEXT => 'Text',
  31. Nail => {
  32. Name => 'ThumbnailImage',
  33. Groups => { 2 => 'Preview' },
  34. # (the specification allows for a variable amount of padding before
  35. # the image after a 10-byte header, so look for the JPEG SOI marker,
  36. # otherwise assume a fixed 8 bytes of padding)
  37. RawConv => q{
  38. pos($val) = 10;
  39. my $start = ($val =~ /\xff\xd8\xff/g) ? pos($val) - 3 : 18;
  40. my $img = substr($val, $start);
  41. return $self->ValidateImage(\$img, $tag);
  42. },
  43. },
  44. PrVw => {
  45. Name => 'PreviewImage',
  46. Groups => { 2 => 'Preview' },
  47. RawConv => q{
  48. pos($val) = 10;
  49. my $start = ($val =~ /\xff\xd8\xff/g) ? pos($val) - 3 : 18;
  50. my $img = substr($val, $start);
  51. return $self->ValidateImage(\$img, $tag);
  52. },
  53. },
  54. );
  55. #------------------------------------------------------------------------------
  56. # Read/write AFCP information in a file
  57. # Inputs: 0) ExifTool object reference, 1) dirInfo reference
  58. # (Set 'ScanForAFCP' member in dirInfo to scan from current position for AFCP)
  59. # Returns: 1 on success, 0 if this file didn't contain AFCP information
  60. # -1 on write error or if the offsets were incorrect on reading
  61. # - updates DataPos to point to actual AFCP start if ScanForAFCP is set
  62. # - updates DirLen to trailer length
  63. # - returns Fixup reference in dirInfo hash when writing
  64. sub ProcessAFCP($$)
  65. {
  66. my ($et, $dirInfo) = @_;
  67. my $raf = $$dirInfo{RAF};
  68. my $curPos = $raf->Tell();
  69. my $offset = $$dirInfo{Offset} || 0; # offset from end of file
  70. my $rtnVal = 0;
  71. NoAFCP: for (;;) {
  72. my ($buff, $fix, $dirBuff, $valBuff, $fixup, $vers);
  73. # look for AXS trailer
  74. last unless $raf->Seek(-12-$offset, 2) and
  75. $raf->Read($buff, 12) == 12 and
  76. $buff =~ /^(AXS(!|\*))/;
  77. my $endPos = $raf->Tell();
  78. my $hdr = $1;
  79. SetByteOrder($2 eq '!' ? 'MM' : 'II');
  80. my $startPos = Get32u(\$buff, 4);
  81. if ($raf->Seek($startPos, 0) and $raf->Read($buff, 12) == 12 and $buff =~ /^$hdr/) {
  82. $fix = 0;
  83. } else {
  84. $rtnVal = -1;
  85. # look for start of AXS trailer if 'ScanForAFCP'
  86. last unless $$dirInfo{ScanForAFCP} and $raf->Seek($curPos, 0);
  87. my $actualPos = $curPos;
  88. # first look for header right at current position
  89. for (;;) {
  90. last if $raf->Read($buff, 12) == 12 and $buff =~ /^$hdr/;
  91. last NoAFCP if $actualPos != $curPos;
  92. # scan for AXS header (could be after preview image)
  93. for (;;) {
  94. my $buf2;
  95. $raf->Read($buf2, 65536) or last NoAFCP;
  96. $buff .= $buf2;
  97. if ($buff =~ /$hdr/g) {
  98. $actualPos += pos($buff) - length($hdr);
  99. last; # ok, now go back and re-read header
  100. }
  101. $buf2 = substr($buf2, -3); # only need last 3 bytes for next test
  102. $actualPos += length($buff) - length($buf2);
  103. $buff = $buf2;
  104. }
  105. last unless $raf->Seek($actualPos, 0); # seek to start of AFCP
  106. }
  107. # calculate shift for fixing AFCP offsets
  108. $fix = $actualPos - $startPos;
  109. }
  110. # set variables returned in dirInfo hash
  111. $$dirInfo{DataPos} = $startPos + $fix; # actual start position
  112. $$dirInfo{DirLen} = $endPos - ($startPos + $fix);
  113. $rtnVal = 1;
  114. my $verbose = $et->Options('Verbose');
  115. my $out = $et->Options('TextOut');
  116. my $outfile = $$dirInfo{OutFile};
  117. if ($outfile) {
  118. # allow all AFCP information to be deleted
  119. if ($$et{DEL_GROUP}{AFCP}) {
  120. $verbose and print $out " Deleting AFCP\n";
  121. ++$$et{CHANGED};
  122. last;
  123. }
  124. $dirBuff = $valBuff = '';
  125. require Image::ExifTool::Fixup;
  126. $fixup = $$dirInfo{Fixup};
  127. $fixup or $fixup = $$dirInfo{Fixup} = new Image::ExifTool::Fixup;
  128. $vers = substr($buff, 4, 2); # get version number
  129. } else {
  130. $et->DumpTrailer($dirInfo) if $verbose or $$et{HTML_DUMP};
  131. }
  132. # read AFCP directory data
  133. my $numEntries = Get16u(\$buff, 6);
  134. my $dir;
  135. unless ($raf->Read($dir, 12 * $numEntries) == 12 * $numEntries) {
  136. $et->Error('Error reading AFCP directory', 1);
  137. last;
  138. }
  139. if ($verbose > 2 and not $outfile) {
  140. my $dat = $buff . $dir;
  141. print $out " AFCP Directory:\n";
  142. HexDump(\$dat, undef,
  143. Addr => $$dirInfo{DataPos},
  144. Width => 12,
  145. Prefix => $$et{INDENT},
  146. Out => $out,
  147. );
  148. }
  149. $fix and $et->Warn("Adjusted AFCP offsets by $fix", 1);
  150. #
  151. # process AFCP directory
  152. #
  153. my $tagTablePtr = GetTagTable('Image::ExifTool::AFCP::Main');
  154. my ($index, $entry);
  155. for ($index=0; $index<$numEntries; ++$index) {
  156. my $entry = 12 * $index;
  157. my $tag = substr($dir, $entry, 4);
  158. my $size = Get32u(\$dir, $entry + 4);
  159. my $offset = Get32u(\$dir, $entry + 8);
  160. if ($size < 0x80000000 and
  161. $raf->Seek($offset+$fix, 0) and
  162. $raf->Read($buff, $size) == $size)
  163. {
  164. if ($outfile) {
  165. # rewrite this information
  166. my $tagInfo = $et->GetTagInfo($tagTablePtr, $tag);
  167. if ($tagInfo and $$tagInfo{SubDirectory}) {
  168. my %subdirInfo = (
  169. DataPt => \$buff,
  170. DirStart => 0,
  171. DirLen => $size,
  172. DataPos => $offset + $fix,
  173. Parent => 'AFCP',
  174. );
  175. my $subTable = GetTagTable($tagInfo->{SubDirectory}->{TagTable});
  176. my $newDir = $et->WriteDirectory(\%subdirInfo, $subTable);
  177. if (defined $newDir) {
  178. $size = length $newDir;
  179. $buff = $newDir;
  180. }
  181. }
  182. $fixup->AddFixup(length($dirBuff) + 8);
  183. $dirBuff .= $tag . Set32u($size) . Set32u(length $valBuff);
  184. $valBuff .= $buff;
  185. } else {
  186. # extract information
  187. $et->HandleTag($tagTablePtr, $tag, $buff,
  188. DataPt => \$buff,
  189. Size => $size,
  190. Index => $index,
  191. DataPos => $offset + $fix,
  192. );
  193. }
  194. } else {
  195. $et->Warn("Bad AFCP directory");
  196. $rtnVal = -1 if $outfile;
  197. last;
  198. }
  199. }
  200. if ($outfile and length($dirBuff)) {
  201. my $outPos = Tell($outfile); # get current outfile position
  202. # apply fixup to directory pointers
  203. my $valPos = $outPos + 12; # start of value data
  204. $fixup->{Shift} += $valPos + length($dirBuff);
  205. $fixup->ApplyFixup(\$dirBuff);
  206. # write the AFCP header, directory, value data and EOF record (with zero checksums)
  207. Write($outfile, $hdr, $vers, Set16u(length($dirBuff)/12), Set32u(0),
  208. $dirBuff, $valBuff, $hdr, Set32u($outPos), Set32u(0)) or $rtnVal = -1;
  209. # complete fixup so the calling routine can apply further shifts
  210. $fixup->AddFixup(length($dirBuff) + length($valBuff) + 4);
  211. $fixup->{Start} += $valPos;
  212. $fixup->{Shift} -= $valPos;
  213. }
  214. last;
  215. }
  216. return $rtnVal;
  217. }
  218. 1; # end
  219. __END__
  220. =head1 NAME
  221. Image::ExifTool::AFCP - Read/write AFCP trailer
  222. =head1 SYNOPSIS
  223. This module is used by Image::ExifTool
  224. =head1 DESCRIPTION
  225. This module contains definitions required by Image::ExifTool to extract
  226. information from the AFCP trailer. Although the AFCP specification is
  227. compatible with various file formats, ExifTool currently only processes AFCP
  228. in JPEG images.
  229. =head1 NOTES
  230. AFCP is a specification which allows meta information (including IPTC) to be
  231. appended to the end of a file.
  232. It is a poorly designed protocol because (like TIFF) it uses absolute
  233. offsets to specify data locations. This is a huge blunder because it makes
  234. the AFCP information dependent on the file length, so it is easily
  235. invalidated by image editing software which doesn't recognize the AFCP
  236. trailer to fix up these offsets when the file length changes. ExifTool will
  237. attempt to fix these invalid offsets if possible.
  238. Scanning for AFCP information may be time consuming, especially when reading
  239. from a sequential device, since the information is at the end of the file.
  240. In these instances, the ExifTool FastScan option may be used to disable
  241. scanning for AFCP information.
  242. =head1 AUTHOR
  243. Copyright 2003-2016, Phil Harvey (phil at owl.phy.queensu.ca)
  244. This library is free software; you can redistribute it and/or modify it
  245. under the same terms as Perl itself.
  246. =head1 REFERENCES
  247. =over 4
  248. =item L<http://www.tocarte.com/media/axs_afcp_spec.pdf>
  249. =back
  250. =head1 SEE ALSO
  251. L<Image::ExifTool::TagNames/AFCP Tags>,
  252. L<Image::ExifTool(3pm)|Image::ExifTool>
  253. =cut