LeoHsiao1 / pyexiv2

Read and write image metadata, including EXIF, IPTC, XMP, ICC Profile.
GNU General Public License v3.0
196 stars 39 forks source link

Modifying exif containing duplicate keys. #138

Closed CarlesLlobet closed 3 months ago

CarlesLlobet commented 3 months ago

I have an image with the following exif information:

$ exiftool original.jpg 
ExifTool Version Number         : 12.40
File Name                       : original.jpg
Directory                       : .
File Size                       : 2.7 MiB
File Modification Date/Time     : 2024:03:13 17:45:08+00:00
File Access Date/Time           : 2024:03:14 22:24:46+00:00
File Inode Change Date/Time     : 2024:03:14 22:24:38+00:00
File Permissions                : -rw-rw-r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
Exif Byte Order                 : Little-endian (Intel, II)
Image Description               : 
Make                            : samsung
Camera Model Name               : SM-A326U
Orientation                     : Rotate 90 CW
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Software                        : MediaTek Camera Application
Modify Date                     : 2023:11:20 15:46:23
Y Cb Cr Positioning             : Co-sited
Exposure Time                   : 1/24
F Number                        : 1.8
Exposure Program                : Program AE
ISO                             : 500
Sensitivity Type                : Unknown
Recommended Exposure Index      : 0
Exif Version                    : 0220
Date/Time Original              : 2023:11:20 15:46:23
Create Date                     : 2023:11:20 15:46:23
Components Configuration        : Y, Cb, Cr, -
Shutter Speed Value             : 1/24
Aperture Value                  : 1.9
Brightness Value                : 3
Exposure Compensation           : 0
Max Aperture Value              : 1.8
Metering Mode                   : Center-weighted average
Light Source                    : Other
Flash                           : On, Fired
Focal Length                    : 4.6 mm
Sub Sec Time                    : 703
Sub Sec Time Original           : 703
Sub Sec Time Digitized          : 703
Flashpix Version                : 0100
Color Space                     : sRGB
Exif Image Width                : 4000
Exif Image Height               : 3000
Interoperability Index          : R98 - DCF basic file (sRGB)
Interoperability Version        : 0100
Exposure Mode                   : Auto
White Balance                   : Auto
Digital Zoom Ratio              : 1
Focal Length In 35mm Format     : 25 mm
Scene Capture Type              : Standard
Compression                     : JPEG (old-style)
Thumbnail Offset                : 1408
Thumbnail Length                : 64000
Image Width                     : 4000
Image Height                    : 3000
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Time Stamp                      : 2023:11:20 20:46:21+00:00
MCC Data                        : 310
Aperture                        : 1.8
Image Size                      : 4000x3000
Megapixels                      : 12.0
Scale Factor To 35 mm Equivalent: 5.4
Shutter Speed                   : 1/24
Create Date                     : 2023:11:20 15:46:23.703
Date/Time Original              : 2023:11:20 15:46:23.703
Modify Date                     : 2023:11:20 15:46:23.703
Thumbnail Image                 : (Binary data 64000 bytes, use -b option to extract)
Circle Of Confusion             : 0.006 mm
Field Of View                   : 71.5 deg
Focal Length                    : 4.6 mm (35 mm equivalent: 25.0 mm)
Hyperfocal Distance             : 2.13 m
Light Value                     : 4.0

As you can see there are duplicate tags (ModifyDate, Create Date, DateTimeOriginal, ..).

The problem is that if I modify any of this (e.g: image.modify_exif({'Exif.Image.DateTime': '1970:01:01 00:00:00.001'})), It will override the metadata and just keep one of them, since dictionaries in python only keep the latest value.

How could I modify all dates without smashing duplicate keys?

LeoHsiao1 commented 3 months ago

Hi This issue has been discussed recently, see: https://github.com/LeoHsiao1/pyexiv2/issues/133 The source code of exiv2 supports writing duplicate exif keys. But there is rarely a need for this, as ordinary software cannot read duplicate exif keys.

CarlesLlobet commented 3 months ago

HI @LeoHsiao1,

Yes, I saw that issue but the implemented function only allows to copy all the tags from one image to the other as they are. I'd need to also modify some of the Date tags while copying.

Would that be possible with the copy function implemented in that issue to modify some of them during copy?

LeoHsiao1 commented 3 months ago

In addition to COPY, I offer another option: https://github.com/LeoHsiao1/pyexiv2/issues/133#issuecomment-1905995016

Write duplicate exif key by list type value:

>>> img.modify_exif({'Exif.Image.DateTime': ['2024:03:15 17:35:02', '2024:00:00 10:00:00']})
>>> img.read_exif()['Exif.Image.DateTime']
['2024:03:15 17:35:02', '2024:00:00 10:00:00']

But the order of the list may be reversed. Does this meet your needs?

CarlesLlobet commented 3 months ago

That does not seem to do what I need, no.

Lets say we have this code example:

import pyexiv2
from datetime import datetime

def modify_exif_timestamps(image_path):
    # Open the image file
    image = pyexiv2.Image(image_path)

    # Define the new timestamp
    new_timestamp = datetime(1970, 1, 1, 0, 0, 0, 1)

    image.modify_exif({'Exif.Image.DateTime': ['1970:01:01 00:00:00.001', '1970:01:01 00:00:00.001']})
    image.close()

# Replace 'original_modified.jpg' with the path to your image file
modify_exif_timestamps('original_modified.jpg')

As you can see, my original image has several dates duplicated (Modify Date, Create Date, Date/Time Original). Whenever I execute the sample code, the modified tag (Modify Date) does get modified correctly but theres only 1 instance now: image

LeoHsiao1 commented 3 months ago

I have a suspicion.The Modify Date output by exiftool does not necessarily correspond to the Exif.Image.DateTime of exiv2. May be affected by multiple keys of exiv2. Could you please test writing few keys?

image.clear_exif()
image.modify_exif({'Exif.Image.DateTime': ['1970:01:01 00:00:00.001', '1971:01:01 00:00:00.001']})
image.read_exif()

And then view that image with exiftool?

CarlesLlobet commented 3 months ago

Sure, here's the code:

import pyexiv2
from datetime import datetime

def modify_exif_timestamps(image_path):
    # Open the image file
    image = pyexiv2.Image(image_path)

    # Define the new timestamp
    new_timestamp = datetime(1970, 1, 1, 0, 0, 0, 1)

    image.clear_exif()
    image.modify_exif({'Exif.Image.DateTime': ['1970:01:01 00:00:00.001', '1970:01:01 00:00:00.001']})
    image.read_exif()
    image.close()

# Replace 'original_modified.jpg' with the path to your image file
modify_exif_timestamps('original_modified.jpg')

and results: image

CarlesLlobet commented 3 months ago

You also can have the original image if you want to do some tests: original

LeoHsiao1 commented 3 months ago

Ok, the cause of the problem is still there: The Modify Date output by exiftool does not necessarily correspond to the Exif.Image.DateTime of exiv2. After analyzing, I don't think your EXIF key is duplicated.

  1. Use exiftool -v to show the data structure of the metadata:

    $ exiftool -v original.jpg
      ExifToolVersion = 12.78
      FileName = 1-raw.jpg
      Directory = ..
      FileSize = 2851929
      FileModifyDate = 1710512547
      FileAccessDate = 1710512636
      FileInodeChangeDate = 1710513088
      FilePermissions = 33279
      FileType = JPEG
      FileTypeExtension = JPG
      MIMEType = image/jpeg
    JPEG APP1 (65402 bytes):
      ExifByteOrder = II
      + [IFD0 directory with 19 entries]
      | 0)  ImageWidth = 4000
      | 1)  ImageHeight = 3000
      | 2)  ImageDescription =
      | 3)  Make = samsung
      | 4)  Model = SM-A326U
      | 5)  Orientation = 6
      | 6)  XResolution = 72 (72/1)
      | 7)  YResolution = 72 (72/1)
      | 8)  ResolutionUnit = 2
      | 9)  Software = MediaTek Camera Application
      | 10) ModifyDate = 2023:11:20 15:46:23
      | 11) YCbCrPositioning = 2
      | 12) Exif_0x0220 = 0
      | 13) Exif_0x0221 = 0
      | 14) Exif_0x0222 = 0
      | 15) Exif_0x0223 = 0
      | 16) Exif_0x0224 = 0
      | 17) Exif_0x0225 =
      | 18) ExifOffset (SubDirectory) -->
      | + [ExifIFD directory with 32 entries]
      | | 0)  ExposureTime = 0.041662 (41662000/1000000000)
      | | 1)  FNumber = 1.8 (1800/1000)
      | | 2)  ExposureProgram = 2
      | | 3)  ISO = 500
      | | 4)  SensitivityType = 0
      | | 5)  RecommendedExposureIndex = 0
      | | 6)  ExifVersion = 0220
      | | 7)  DateTimeOriginal = 2023:11:20 15:46:23
      | | 8)  CreateDate = 2023:11:20 15:46:23
      | | 9)  ComponentsConfiguration = 1 2 3 0
      | | 10) ShutterSpeedValue = 4.585 (4585/1000)
      | | 11) ApertureValue = 1.8 (1800/1000)
      | | 12) BrightnessValue = 3 (30/10)
      | | 13) ExposureCompensation = 0 (0/10)
      | | 14) MaxApertureValue = 1.69 (169/100)
      | | 15) MeteringMode = 2
      | | 16) LightSource = 255
      | | 17) Flash = 9
      | | 18) FocalLength = 4.6 (4600/1000)
      | | 19) SubSecTime = 703
      | | 20) SubSecTimeOriginal = 703
      | | 21) SubSecTimeDigitized = 703
    ...

    It can be seen that only ModifyDate = 2023:11:20 15:46:23 and SubSecTime = 703 are stored in the image.

  2. Use exiftool -exif:* to show only EXIF metadata:

    $ exiftool -exif:* original.jpg | grep Date
    Modify Date                     : 2023:11:20 15:46:23
    Date/Time Original              : 2023:11:20 15:46:23
    Create Date                     : 2023:11:20 15:46:23

    There are no two ModifyDate here.

  3. So why do you see two ModifyDates?

    $ exiftool original.jpg | grep Date
    File Modification Date/Time     : 2024:03:15 22:22:27+08:00
    File Access Date/Time           : 2024:03:15 22:23:56+08:00
    File Inode Change Date/Time     : 2024:03:15 22:31:28+08:00
    Modify Date                     : 2023:11:20 15:46:23
    Date/Time Original              : 2023:11:20 15:46:23
    Create Date                     : 2023:11:20 15:46:23
    Create Date                     : 2023:11:20 15:46:23.703
    Date/Time Original              : 2023:11:20 15:46:23.703
    Modify Date                     : 2023:11:20 15:46:23.703

    My guess is that exiftool shows two values with different precision when there is SubSecTime = 703.

LeoHsiao1 commented 3 months ago

So, to follow the principles of exiftool, you need to change

img.modify_exif({'Exif.Image.DateTime': '1970:01:01 00:00:00.703'})

to

img.modify_exif({'Exif.Image.DateTime': '1970:01:01 00:00:00', "Exif.Photo.SubSecTime": "703"})

Then execute exiftool, it shows two Modify Date:

$ exiftool 1.jpg | grep Modify
Modify Date                     : 1970:01:01 00:00:00
Modify Date                     : 1970:01:01 00:00:00.703
CarlesLlobet commented 3 months ago

Wow thank you so much for digging into this and finding the root cause!

Thats weird from exiftool and it was driving me crazy!

Thanks again, I'll close the ticket 🙏