File exiftool-minolta_A1-byte-exact-copy.v11.patch of Package perl-Image-ExifTool-minolta_A1-byte-exact.openSUSE_Tumbleweed

exiftool-minolta_A1-byte-exact-copy.v11.patch

This patch allows to make byte exact copies of the Minolta Dimage A1
camera original from a copy edited by exiftool >= 7.34.

This version is ported for Image-ExifTool-9.70.

If you set environment variable MINOLTA_BYTE_EXACT=4 and then remove all
tags you added, you will get a copy, that is equal to the original file
as it came from the camera.

Note: Patch still needs testing for images with edited ExifIFD/UserComment.

Author: Stanislav Brabec <utx@penguin.cz>

Variable MINOLTA_BYTE_EXACT meanings:
0 or undefined: default exiftool behavior
1: organize tags exactly as camera does
4: 1 + pad items exactly as camera does
Notes:
 - To get byte-exact copy of original, you have to strip custom tags and
   revert existing tags manually, as levels 2 and 3 are not yet
   implemented.
 - For exiftool >= 7.34 you can get byte exact copy of original even
   from files, that were written by with the default exiftool behavior.
 - exiftool <= 7.34 has an one obsolete pad byte in the MakerNotes, which
   causes a different result after reverting of all tags.

History:
2008-08-08: v1: initial version for Image-ExifTool-7.37
2009-11-23: v2: ported to Image-ExifTool-7.82
2010-07-11: v3: ported to Image-ExifTool-8.15
2010-08-27: v4: fixed byte exact padding with embedded ICC profile
2010-11-01: v5: fixed exact padding for exif size % 512 equal to 392 (support for negative padding)
2011-03-13: v6: ported to Image-ExifTool-8.40
2012-06-06: v7: fixed byte exact padding with Adobe RGB images without embedded ICC profile
2012-09-23: v8: ported to Image-ExifTool-8.65 (v8 was broken)
2013-03-24: v9: ported to Image-ExifTool-9.01
2014-12-08: v10: ported to Image-ExifTool-9.70
2015-03-21: v11: fixed compilation error

Index: Image-ExifTool-9.70/lib/Image/ExifTool/WriteExif.pl
===================================================================
--- Image-ExifTool-9.70.orig/lib/Image/ExifTool/WriteExif.pl
+++ Image-ExifTool-9.70/lib/Image/ExifTool/WriteExif.pl
@@ -1743,6 +1743,25 @@ sub WriteExif($$$)
     # set encoding for strings
     $strEnc = $et->Options('CharsetEXIF') if $$tagTablePtr{GROUPS}{0} eq 'EXIF';
 
+# Variable MINOLTA_BYTE_EXACT meanings:
+# 0 or undefined: default exiftool behavior
+# 1: organize tags exactly as camera does
+# 2: 1 + write only tags generated by the camera (not yet implemented)
+# 3: 2 + revert tag to values that camera can generate (not yet implemented)
+# 4: 3 + pad items exactly as camera does
+# Notes:
+# - To get byte-exact copy of original, you have to strip custom tags and
+#   revert existing tags manually, as levels 2 and 3 are not yet
+#   implemented.
+# - For exiftool >= 7.34 you can get byte exact copy of original even
+#   from files, that were written by with the default exiftool behavior.
+    my $minolta_byte_exact;
+    if (defined($ENV{MINOLTA_BYTE_EXACT})) {
+        $minolta_byte_exact = int($ENV{MINOLTA_BYTE_EXACT});
+    } else {
+        $minolta_byte_exact = 0;
+    }
+
     # allow multiple IFD's in IFD0-IFD1-IFD2... chain
     $$dirInfo{Multi} = 1 if $dirName =~ /^(IFD0|SubIFD)$/ and not defined $$dirInfo{Multi};
     $inMakerNotes = 1 if $$tagTablePtr{GROUPS}{0} eq 'MakerNotes';
@@ -1957,6 +1976,7 @@ sub WriteExif($$$)
         }
         my $dirBuff = '';   # buffer for directory data
         my $valBuff = '';   # buffer for value data
+        my $offsetValBuff = 0; # current offset to $valBuff
         my @valFixups;      # list of fixups for offsets in valBuff
         # fixup for offsets in dirBuff
         my $dirFixup = new Image::ExifTool::Fixup;
@@ -1968,6 +1988,23 @@ sub WriteExif($$$)
         my ($offList, $offHash, $ignoreCount, $fixCount);
         my $oldID = -1;
         my $newID = -1;
+        my $sizeUserComment = 0;
+
+        if (($minolta_byte_exact > 0) and ($dirName eq 'ExifIFD')) {
+            $index = 0;
+            EntryUserComment:
+            for (;$index < $numEntries;++$index) {
+                 $entry = $dirStart + 2 + 12 * $index;
+                 $oldID = Get16u($dataPt, $entry);
+                 $readCount = Get32u($dataPt, $entry+4);
+                 # UserComment
+                 if ($oldID == 37510) {
+                     $sizeUserComment=$readCount;
+                 }
+            }
+            $index = 0;
+            $oldID = -1;
+        }
 
         # patch for Canon EOS 40D firmware 1.0.4 bug (incorrect directory counts)
         if ($inMakerNotes and $$et{Model} eq 'Canon EOS 40D') {
@@ -3068,10 +3105,42 @@ NoOverwrite:            next if $isNew >
                 my $entryBased;
                 if ($$dirInfo{EntryBased} or ($newInfo and $$newInfo{EntryBased})) {
                     $entryBased = 1;
-                    $offsetVal = Set32u(length($valBuff) - length($dirBuff));
+                    $offsetValBuff = length($valBuff) - length($dirBuff);
                 } else {
-                    $offsetVal = Set32u(length $valBuff);
+                    if (($minolta_byte_exact > 0) and ($dirName eq 'ExifIFD')) {
+                        CASE:{
+                            # DateTimeOriginal
+                             $newID == 36867 and do{$offsetValBuff = 0; last CASE;};
+                            # CreateDate
+                             $newID == 36868 and do{$offsetValBuff = 20; last CASE;};
+                            # ExposureTime
+                             $newID == 33434 and do{$offsetValBuff = 40; last CASE;};
+                            # FNumber
+                             $newID == 33437 and do{$offsetValBuff = 48; last CASE;};
+                            # ExposureCompensation
+                             $newID == 37380 and do{$offsetValBuff = 56; last CASE;};
+                            # MaxApertureValue
+                             $newID == 37381 and do{$offsetValBuff = 64; last CASE;};
+                            # FocalLength
+                             $newID == 37386 and do{$offsetValBuff = 72; last CASE;};
+                            # DigitalZoomRatio
+                             $newID == 41988 and do{$offsetValBuff = 80; last CASE;};
+                            # BrightnessValue
+                             $newID == 37379 and do{$offsetValBuff = 88; last CASE;};
+                            # SubjectLocation
+                             $newID == 37396 and do{$offsetValBuff = 96; last CASE;};
+                            # These two have defined position as well, but editing UserComment, MakerNoteMinolta will change.
+                            # As MakerNote is processed first, we must add UserComment size
+                            # UserComment
+                             $newID == 37510 and do{$offsetValBuff = 104; last CASE;};
+                            # MakerNote 37500
+                             $newID == 37500 and do{$offsetValBuff = 104 + $sizeUserComment; last CASE;};
+                            # Unknown entries
+                            do{$offsetValBuff = length $valBuff;};
+                        }
+                    } else {$offsetValBuff = length $valBuff;};
                 }
+                $offsetVal = Set32u($offsetValBuff);
                 my ($dataTag, $putFirst);
                 ($dataTag, $putFirst) = @$newInfo{'DataTag','PutFirst'} if $newInfo;
                 if ($dataTag) {
@@ -3100,7 +3169,11 @@ NoOverwrite:            next if $isNew >
                     $offsetVal = Set32u(length $$hdrPtr);
                     $$hdrPtr .= $$newValuePt;
                 } else {
-                    $valBuff .= $$newValuePt;       # add value data to buffer
+                    # pad $valBuff if value starts after current buffer end (in byte exact mode)
+                    if ($offsetValBuff > length $valBuff) {
+                        $valBuff .= "\0" x ($offsetValBuff - length $valBuff);
+                    }
+                    (substr $valBuff, $offsetValBuff, length $$newValuePt) = $$newValuePt; # add value data to buffer
                     # must save a fixup pointer for every pointer in the directory
                     if ($entryBased) {
                         $entryBasedFixup or $entryBasedFixup = new Image::ExifTool::Fixup;
@@ -3250,6 +3323,10 @@ NoOverwrite:            next if $isNew >
         my $valFixup;
         foreach $valFixup (@valFixups) {
             $$valFixup{Start} += $valPos;
+            if (($minolta_byte_exact > 0) and ($dirName eq 'ExifIFD')) {
+                # Fix MakerNotes fixups to refer to a known fixed position
+                $valFixup->{Start} += $sizeUserComment;
+            }
             $fixup->AddFixup($valFixup);
         }
         # stop if no next IFD pointer
@@ -3652,9 +3729,10 @@ NoOverwrite:            next if $isNew >
             my $pt = \$$previewInfo{Data}; # image data or 'LOAD_PREVIEW' flag
             # now that we know the size of the EXIF data, first test to see if our new image fits
             # inside the EXIF segment (remember about the TIFF and EXIF headers: 8+6 bytes)
-            if (($$pt ne 'LOAD_PREVIEW' and length($$pt) + length($newData) + 14 <= 0xfffd and
+            if (($minolta_byte_exact == 0) and
+                 (($$pt ne 'LOAD_PREVIEW' and length($$pt) + length($newData) + 14 <= 0xfffd and
                 not $$previewInfo{IsTrailer}) or
-                $$previewInfo{IsShort}) # must fit in this segment if using short pointers
+                $$previewInfo{IsShort})) # must fit in this segment if using short pointers
             {
                 # It fits! (or must exist in EXIF segment), so fixup the
                 # PreviewImage pointers and stuff the preview image in here
@@ -3702,6 +3780,23 @@ NoOverwrite:            next if $isNew >
     # (could be up to 10 bytes and still be empty)
     $newData = '' if defined $newData and length($newData) < 12;
 
+    if (($minolta_byte_exact > 3) and ($dirName eq 'IFD1')) {
+        my $padSize;
+        # is ICC profile included?
+        # In this time we have no better hint than file extension:
+        # FIXME: Use of RAF would be better.
+        if ($$et{FILE_EXT} eq "JPE") {
+            $padSize = 510 - ((length($newData) + 119) % 512);
+        } else {
+            $padSize = 511 - ((length($newData) + 94) % 512);
+        }
+        if ($padSize == -1) {
+            $newData = substr($newData, 0, length($newData) - 1);
+        } else {
+            $newData .= "\0" x $padSize;
+        }
+    }
+
     return $newData;    # return our directory data
 }