InDesign.pm 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. #------------------------------------------------------------------------------
  2. # File: InDesign.pm
  3. #
  4. # Description: Read/write meta information in Adobe InDesign files
  5. #
  6. # Revisions: 2009-06-17 - P. Harvey Created
  7. #
  8. # References: 1) http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf
  9. #------------------------------------------------------------------------------
  10. package Image::ExifTool::InDesign;
  11. use strict;
  12. use vars qw($VERSION);
  13. use Image::ExifTool qw(:DataAccess :Utils);
  14. $VERSION = '1.04';
  15. # map for writing metadata to InDesign files (currently only write XMP)
  16. my %indMap = (
  17. XMP => 'IND',
  18. );
  19. # GUID's used in InDesign files
  20. my $masterPageGUID = "\x06\x06\xed\xf5\xd8\x1d\x46\xe5\xbd\x31\xef\xe7\xfe\x74\xb7\x1d";
  21. my $objectHeaderGUID = "\xde\x39\x39\x79\x51\x88\x4b\x6c\x8E\x63\xee\xf8\xae\xe0\xdd\x38";
  22. my $objectTrailerGUID = "\xfd\xce\xdb\x70\xf7\x86\x4b\x4f\xa4\xd3\xc7\x28\xb3\x41\x71\x06";
  23. #------------------------------------------------------------------------------
  24. # Read or write meta information in an InDesign file
  25. # Inputs: 0) ExifTool object reference, 1) dirInfo reference
  26. # Returns: 1 on success, 0 if this wasn't a valid InDesign file, or -1 on write error
  27. sub ProcessIND($$)
  28. {
  29. my ($et, $dirInfo) = @_;
  30. my $raf = $$dirInfo{RAF};
  31. my $outfile = $$dirInfo{OutFile};
  32. my ($hdr, $buff, $buf2, $err, $writeLen, $foundXMP);
  33. # validate the InDesign file
  34. return 0 unless $raf->Read($hdr, 16) == 16;
  35. return 0 unless $hdr eq $masterPageGUID;
  36. return 0 unless $raf->Read($buff, 8) == 8;
  37. $et->SetFileType($buff eq 'DOCUMENT' ? 'INDD' : 'IND'); # set the FileType tag
  38. # read the master pages
  39. $raf->Seek(0, 0) or $err = 'Seek error', goto DONE;
  40. unless ($raf->Read($buff, 4096) == 4096 and
  41. $raf->Read($buf2, 4096) == 4096)
  42. {
  43. $err = 'Unexpected end of file';
  44. goto DONE; # (goto's can be our friend)
  45. }
  46. SetByteOrder('II');
  47. unless ($buf2 =~ /^\Q$masterPageGUID/) {
  48. $err = 'Second master page is invalid';
  49. goto DONE;
  50. }
  51. my $seq1 = Get64u(\$buff, 264);
  52. my $seq2 = Get64u(\$buf2, 264);
  53. # take the most current master page
  54. my $curPage = $seq2 > $seq1 ? \$buf2 : \$buff;
  55. # byte order of stream data may be different than headers
  56. my $streamInt32u = Get8u($curPage, 24);
  57. if ($streamInt32u == 1) {
  58. $streamInt32u = 'V'; # little-endian int32u
  59. } elsif ($streamInt32u == 2) {
  60. $streamInt32u = 'N'; # big-endian int32u
  61. } else {
  62. $err = 'Invalid stream byte order';
  63. goto DONE;
  64. }
  65. my $pages = Get32u($curPage, 280);
  66. $pages < 2 and $err = 'Invalid page count', goto DONE;
  67. my $pos = $pages * 4096;
  68. if ($pos > 0x7fffffff and not $et->Options('LargeFileSupport')) {
  69. $err = 'InDesign files larger than 2 GB not supported (LargeFileSupport not set)';
  70. goto DONE;
  71. }
  72. if ($outfile) {
  73. # make XMP the preferred group for writing
  74. $et->InitWriteDirs(\%indMap, 'XMP');
  75. Write($outfile, $buff, $buf2) or $err = 1, goto DONE;
  76. my $result = Image::ExifTool::CopyBlock($raf, $outfile, $pos - 8192);
  77. unless ($result) {
  78. $err = defined $result ? 'Error reading InDesign database' : 1;
  79. goto DONE;
  80. }
  81. $writeLen = 0;
  82. } else {
  83. $raf->Seek($pos, 0) or $err = 'Seek error', goto DONE;
  84. }
  85. # scan through the contiguous objects for XMP
  86. my $verbose = $et->Options('Verbose');
  87. my $out = $et->Options('TextOut');
  88. for (;;) {
  89. $raf->Read($hdr, 32) or last;
  90. unless (length($hdr) == 32 and $hdr =~ /^\Q$objectHeaderGUID/) {
  91. # this must be null padding or we have an error
  92. $hdr =~ /^\0+$/ or $err = 'Corrupt file or unsupported InDesign version';
  93. last;
  94. }
  95. my $len = Get32u(\$hdr, 24);
  96. if ($verbose) {
  97. printf $out "Contiguous object at offset 0x%x (%d bytes):\n", $raf->Tell(), $len;
  98. if ($verbose > 2) {
  99. my $len2 = $len < 1024000 ? $len : 1024000;
  100. my %parms = (Addr => $raf->Tell());
  101. $parms{MaxLen} = $verbose > 3 ? 1024 : 96 if $verbose < 5;
  102. $raf->Seek(-$raf->Read($buff, $len2), 1) or $err = 1;
  103. HexDump(\$buff, undef, %parms);
  104. }
  105. }
  106. # check for XMP if stream data is long enough
  107. # (56 bytes is just enough for XMP header)
  108. if ($len > 56) {
  109. $raf->Read($buff, 56) == 56 or $err = 'Unexpected end of file', last;
  110. if ($buff =~ /^(....)<\?xpacket begin=(['"])\xef\xbb\xbf\2 id=(['"])W5M0MpCehiHzreSzNTczkc9d\3/s) {
  111. my $lenWord = $1; # save length word for writing later
  112. $len -= 4; # get length of XMP only
  113. $foundXMP = 1;
  114. # I have a sample where the XMP is 107 MB, and ActivePerl may run into
  115. # memory troubles (with its apparent 1 GB limit) if the XMP is larger
  116. # than about 400 MB, so guard against this
  117. if ($len > 300 * 1024 * 1024) {
  118. my $msg = sprintf('Insanely large XMP (%.0f MB)', $len / (1024 * 1024));
  119. if ($outfile) {
  120. $et->Error($msg, 2) and $err = 1, last;
  121. } elsif ($et->Options('IgnoreMinorErrors')) {
  122. $et->Warn($msg);
  123. } else {
  124. $et->Warn("$msg. Ignored.", 1);
  125. $err = 1;
  126. last;
  127. }
  128. }
  129. # load and parse the XMP data
  130. unless ($raf->Seek(-52, 1) and $raf->Read($buff, $len) == $len) {
  131. $err = 'Error reading XMP stream';
  132. last;
  133. }
  134. my %dirInfo = (
  135. DataPt => \$buff,
  136. Parent => 'IND',
  137. NoDelete => 1, # do not allow this to be deleted when writing
  138. );
  139. my $tagTablePtr = GetTagTable('Image::ExifTool::XMP::Main');
  140. if ($outfile) {
  141. # validate xmp data length (should be same as length in header - 4)
  142. my $xmpLen = unpack($streamInt32u, $lenWord);
  143. unless ($xmpLen == $len) {
  144. $err = "Incorrect XMP stream length ($xmpLen should be $len)";
  145. last;
  146. }
  147. # make sure that XMP is writable
  148. my $classID = Get32u(\$hdr, 20);
  149. $classID & 0x40000000 or $err = 'XMP stream is not writable', last;
  150. my $xmp = $et->WriteDirectory(\%dirInfo, $tagTablePtr);
  151. if ($xmp and length $xmp) {
  152. # write new xmp with leading length word
  153. $buff = pack($streamInt32u, length $xmp) . $xmp;
  154. # update header with new length and invalid checksum
  155. Set32u(length($buff), \$hdr, 24);
  156. Set32u(0xffffffff, \$hdr, 28);
  157. } else {
  158. $$et{CHANGED} = 0; # didn't change anything
  159. $et->Warn("Can't delete XMP as a block from InDesign file") if defined $xmp;
  160. # put length word back at start of stream
  161. $buff = $lenWord . $buff;
  162. }
  163. } else {
  164. $et->ProcessDirectory(\%dirInfo, $tagTablePtr);
  165. }
  166. $len = 0; # we got the full stream (nothing left to read)
  167. } else {
  168. $len -= 56; # we got 56 bytes of the stream
  169. }
  170. } else {
  171. $buff = ''; # must reset this for writing later
  172. }
  173. if ($outfile) {
  174. # write object header and data
  175. Write($outfile, $hdr, $buff) or $err = 1, last;
  176. my $result = Image::ExifTool::CopyBlock($raf, $outfile, $len);
  177. unless ($result) {
  178. $err = defined $result ? 'Truncated stream data' : 1;
  179. last;
  180. }
  181. $writeLen += 32 + length($buff) + $len;
  182. } elsif ($len) {
  183. # skip over remaining stream data
  184. $raf->Seek($len, 1) or $err = 'Seek error', last;
  185. }
  186. $raf->Read($buff, 32) == 32 or $err = 'Unexpected end of file', last;
  187. unless ($buff =~ /^\Q$objectTrailerGUID/) {
  188. $err = 'Invalid object trailer';
  189. last;
  190. }
  191. if ($outfile) {
  192. # make sure object UID and ClassID are the same in the trailer
  193. substr($hdr,16,8) eq substr($buff,16,8) or $err = 'Non-matching object trailer', last;
  194. # write object trailer
  195. Write($outfile, $objectTrailerGUID, substr($hdr,16)) or $err = 1, last;
  196. $writeLen += 32;
  197. }
  198. }
  199. if ($outfile) {
  200. # write null padding if necessary
  201. # (InDesign files must be an even number of 4096-byte blocks)
  202. my $part = $writeLen % 4096;
  203. Write($outfile, "\0" x (4096 - $part)) or $err = 1 if $part;
  204. }
  205. DONE:
  206. if (not $err) {
  207. $et->Warn('No XMP stream to edit') if $outfile and not $foundXMP;
  208. return 1; # success!
  209. } elsif (not $outfile) {
  210. # issue warning on read error
  211. $et->Warn($err) unless $err eq '1';
  212. } elsif ($err ne '1') {
  213. # set error and return success code
  214. $et->Error($err);
  215. } else {
  216. return -1; # write error
  217. }
  218. return 1;
  219. }
  220. 1; # end
  221. __END__
  222. =head1 NAME
  223. Image::ExifTool::InDesign - Read/write meta information in Adobe InDesign files
  224. =head1 SYNOPSIS
  225. This module is used by Image::ExifTool
  226. =head1 DESCRIPTION
  227. This module contains routines required by Image::ExifTool to read XMP
  228. meta information from Adobe InDesign (.IND, .INDD and .INDT) files.
  229. =head1 LIMITATIONS
  230. 1) Only XMP meta information is processed.
  231. 2) A new XMP stream may not be created, so XMP tags may only be written to
  232. InDesign files which previously contained XMP.
  233. 3) File sizes of greater than 2 GB and are currently not supported because
  234. the ability to handle large files like this is system dependent.
  235. =head1 AUTHOR
  236. Copyright 2003-2016, Phil Harvey (phil at owl.phy.queensu.ca)
  237. This library is free software; you can redistribute it and/or modify it
  238. under the same terms as Perl itself.
  239. =head1 REFERENCES
  240. =over 4
  241. =item L<http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf>
  242. =back
  243. =head1 SEE ALSO
  244. L<Image::ExifTool(3pm)|Image::ExifTool>
  245. =cut