Problem with tag qualifiers

Started by Tom Cunningham, July 05, 2025, 08:07:07 PM

Previous topic - Next topic

Tom Cunningham

I'm using ExifTool::ImageInfo to extract dates from a file. If I do:

my $info = $ExifTool::ImageInfo('pic.jpg');
my $date = $info->{'ModifyDate'};

I get back a valid date. However, if I change the second line to:

my $date = $info->{'XMP:ModifyDate'};

I get back nothing. Using the same file on the command line I get:

$ exiftool -ModifyDate pic.jpg
Modify Date                     : 2025:06:16 19:41:49-0700
$ exiftool -XMP:ModifyDate pic.jpg
Modify Date                     : 2025:06:16 21:41:49-07:00

Which is what I expect. Any clues as to why it works on the command line, but not in the script? Thanks.


Phil Harvey

The "info" hash uses tag keys, not fully qualified tag names.  You have to do something like this if you want to find the XMP:ModifyDate:

my $et = Image::ExifTool->new;
$et->ImageInfo('pic.jpg');
my $date = $et->GetValue('XMP:ModifyDate');

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Tom Cunningham


Tom Cunningham

BTW, I am using code generated by Google API. I can't recall my original prompt, but it was something like:

using the exiftool api, write perl code to adjust all time fields in the metadata to the photo's location coordinates relative to central standard time

and this is what it came up with:

#!/usr/bin/perl

use strict;
use warnings;
use Image::ExifTool qw(:Public);
use DateTime::TimeZone; # You'll need to install this Perl module

# Define the directory containing your image files
my $image_directory = "/path/to/your/images";

# Create a new ExifTool object
my $exifTool = new Image::ExifTool;

# Get a list of image files in the directory (you can adjust this to suit your needs)
my @image_files = <$image_directory/*.jpg>;

foreach my $file (@image_files) {
    # Extract metadata including GPS coordinates and timestamps
    my $info = $exifTool->ImageInfo($file);

    # Check if GPS coordinates are available
    if ($info->{GPSLatitude} && $info->{GPSLongitude}) {
        my $latitude = $info->{GPSLatitude};
        my $longitude = $info->{GPSLongitude};

        # Attempt to get the timezone from coordinates using DateTime::TimeZone
        # You'll need a way to map coordinates to a timezone name (e.g., using a library or service)
        # This example uses a placeholder, you'll need to implement the actual logic
        my $timezone_name = get_timezone_from_coordinates($latitude, $longitude);

        if ($timezone_name) {
            # Create DateTime::TimeZone object
            my $location_timezone = DateTime::TimeZone->new(name => $timezone_name);

            # Iterate through time-related tags that might have CST timestamps
            my @time_tags = qw(DateTimeOriginal CreateDate ModifyDate); # Adjust the list of tags as needed

            foreach my $tag (@time_tags) {
                if ($info->{$tag}) {
                    # Get the original timestamp string (assuming it's in CST, you might need to adjust this)
                    my $timestamp_str = $info->{$tag};

                    # Assuming timestamp format "YYYY:MM:DD HH:MM:SS" (adjust if needed)
                    if ($timestamp_str =~ /(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})/) {
                        my ($year, $month, $day, $hour, $minute, $second) = ($1, $2, $3, $4, $5, $6);

                        # Create a DateTime object in the original timezone (CST, adjust if needed)
                        my $original_dt = DateTime->new(
                            year      => $year,
                            month     => $month,
                            day       => $day,
                            hour      => $hour,
                            minute    => $minute,
                            second    => $second,
                            time_zone => 'America/Chicago' # Assuming CST is America/Chicago
                        );

                        # Convert the DateTime object to the location's timezone
                        $original_dt->set_time_zone($location_timezone);

                        # Format the adjusted timestamp for ExifTool (YYYY:MM:DD HH:MM:SS[+-]HH:MM)
                        my $adjusted_timestamp = $original_dt->ymd(':') . ' ' . $original_dt->hms(':') . $original_dt->strftime('%z');

                        # Update the timestamp tag with the adjusted value and timezone
                        $exifTool->SetNewValue($tag, $adjusted_timestamp);
                    }
                }
            }

            # Write the changes to the file
            $exifTool->WriteInfo($file);
            print "Adjusted timestamps for $file to $timezone_name\n";
        } else {
            print "Could not determine timezone for $file from GPS coordinates\n";
        }
    } else {
        print "No GPS coordinates found for $file\n";
    }
}

# Subroutine to get timezone name from latitude and longitude (placeholder)
# You'll need to implement this logic using a library or external service
sub get_timezone_from_coordinates {
    my ($latitude, $longitude) = @_;
    # Example placeholder: replace with your timezone lookup logic
    # You might use a library like Geo::Timezone or an API call to a geolocation service
    return "Europe/Berlin"; # Replace with actual timezone name based on coordinates
}

As you can see, the culprit is:

    my $timestamp_str = $info->{$tag};

which, as you point out, doesn't support fully qualified tags. Anyway, I was impressed that it could generate ExifTool API code.  8)


Phil Harvey

Surprisingly reasonable code attempt.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Tom Cunningham

Quote from: Tom Cunningham on July 06, 2025, 11:17:06 AMBTW, I am using code generated by Google API.

Should be Google AI, not API.

Phil Harvey

...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Tom Cunningham

Still having some issues. Here's a debug run:

Loading DB routines from perl5db.pl version 1.80
Editor support available.

Enter h or 'h h' for help, or 'man perldebug' for more help.

IO::Socket::SSL::CODE(0xa037ee6d0)(/usr/share/perl5/vendor_perl/5.40/IO/Socket/SSL.pm:276):
276:            INIT { init() }
277:            init();
  DB<1> n
main::(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:41):
41:     my $exifTool = new Image::ExifTool;
  DB<1>
main::(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:42):
42:     my $geo = My::GeoNames->new(username => USERNAME);
  DB<1> b 1125
  DB<2> c
main::TzUpdate(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:1125):
1125:                   my $timestamp_str = $exifTool->GetValue($tag);
  DB<2> p $tag
XMP:DateTimeOriginal
  DB<3> n
main::TzUpdate(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:1126):
1126:                   if ($timestamp_str) {
  DB<3> p $timestamp_str
2025:06:16 21:41:49-07:00
  DB<4> n
main::TzUpdate(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:1129):
1129:                       if ($timestamp_str =~ /(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})/) {
  DB<4>
main::TzUpdate(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:1130):
1130:                           my ($year, $month, $day, $hour, $minute, $second) = ($1, $2, $3, $4, $5, $6);
  DB<4>
main::TzUpdate(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:1133):
1133:                           my $original_dt = DateTime->new(
1134:                               year      => $year,
1135:                               month     => $month,
1136:                               day       => $day,
1137:                               hour      => $hour,
1138:                               minute    => $minute,
1139:                               second    => $second,
1140:                               time_zone => 'America/Chicago' # Assume Central time
1141:                           );
  DB<4>
main::TzUpdate(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:1144):
1144:                           $original_dt->set_time_zone($location_timezone);
  DB<4>
main::TzUpdate(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:1147):
1147:                           my $adjusted_timestamp = $original_dt->ymd(':') . ' ' . $original_dt->hms(':') . $original_dt->strftime('%z');
  DB<4>
main::TzUpdate(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:1150):
1150:                           $exifTool->SetNewValue($tag, $adjusted_timestamp);
  DB<4> p $adjusted_timestamp
2025:06:16 19:41:49-0700
  DB<5> p $tag
XMP:DateTimeOriginal
  DB<6> n
main::TzUpdate(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:1154):
1154:               $updates = 1;  # we'll always update
  DB<6>
main::(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:91):
91:             if ($unique) {
  DB<6>
main::(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:94):
94:             if ($updates && !$try) {
  DB<6>
main::(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:95):
95:                 $exifTool->WriteInfo($fn);
  DB<6>
main::(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:96):
96:                 $updates = 0;
  DB<6>
main::(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:98):
98:             if (defined $geoError) {
  DB<6>
main::(/cygdrive/c/Users/Tom/Pictures/Metadata/EXIF/ExifTool/exiftidy.pl:125):
125:        exit;
  DB<6>
Debugged program terminated.  Use q to quit or R to restart,
use o inhibit_exit to avoid stopping after program termination,
S<h q>, S<h R> or S<h o> to get additional info.
  DB<6>

This all looks hunky-dory, but then when I do:

$ exiftool -time:all -G1 -a -s pic.jpg
[System]        FileModifyDate                  : 2025:07:06 22:41:13+01:00
[System]        FileAccessDate                  : 2025:07:06 23:09:20+01:00
[System]        FileCreateDate                  : 2025:07:06 20:09:53+01:00
[IFD0]          ModifyDate                      : 2025:06:16 19:41:49-0700
[ExifIFD]       DateTimeOriginal                : 2025:06:16 19:41:49-0700
[ExifIFD]       CreateDate                      : 2025:06:16 19:41:49-0700
[ExifIFD]       OffsetTime                      : -05:00
[ExifIFD]       OffsetTimeOriginal              : -05:00
[ExifIFD]       OffsetTimeDigitized             : -05:00
[Sony]          SonyDateTime                    : 2025:06:16 21:41:49
[GPS]           GPSTimeStamp                    : 04:41:49
[GPS]           GPSDateStamp                    : 2025:06:17
[IFD1]          ModifyDate                      : 2025:06:16 19:41:49-0700
[XMP-exif]      DateTimeDigitized               : 2025:06:16 19:41:49-07:00
[XMP-exif]      DateTimeOriginal                : 2025:06:16 21:41:49-07:00
[XMP-exif]      GPSDateTime                     : 2025:06:17 04:41:49Z
[XMP-xmp]       ModifyDate                      : 2025:06:16 21:41:49-07:00
[Composite]     GPSDateTime                     : 2025:06:17 04:41:49Z

Huh? I thought I changed the XMP DateTimeOriginal, but it doesn't appear to have changed. I'm stumped.

Phil Harvey

You should be looking at the return value of WriteInfo to see if everything went OK.  Also to debug this you could do $exifTool->Options(Verbose => 2); to see what it is doing when writing.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Tom Cunningham

I get a return value of 2 from WriteInfo, which means the file was written but no changes were made. Using $exifTool->Options(Verbose => 2), the only places I see mention of DateTimeOriginal are here:

  | + [ExifIFD directory with 40 entries]
  | | 0)  ExposureTime = 0.02 (1/50)
  | |     - Tag 0x829a (8 bytes, rational64u[1])
  | | 1)  FNumber = 4 (40/10)
  | |     - Tag 0x829d (8 bytes, rational64u[1])
  | | 2)  ExposureProgram = 2
  | |     - Tag 0x8822 (2 bytes, int16u[1])
  | | 3)  ISO = 6400
  | |     - Tag 0x8827 (2 bytes, int16u[1])
  | | 4)  TimeZoneOffset = -7
  | |     - Tag 0x882a (2 bytes, int16s[1])
  | | 5)  SensitivityType = 2
  | |     - Tag 0x8830 (2 bytes, int16u[1])
  | | 6)  RecommendedExposureIndex = 6400
  | |     - Tag 0x8832 (4 bytes, int32u[1])
  | | 7)  ExifVersion = 0231
  | |     - Tag 0x9000 (4 bytes, undef[4])
  | | 8)  DateTimeOriginal = 2025:06:16 19:41:49
  | |     - Tag 0x9003 (20 bytes, string[20])
  | | 9)  CreateDate = 2025:06:16 19:41:49-0700
  | |     - Tag 0x9004 (25 bytes, string[25])

and here:

  + [XMP directory, 5526 bytes]
  | XMPToolkit = XMP Core 4.4.0-Exiv2
  | Warning = [minor] Fixed incorrect URI for xmlns:MicrosoftPhoto
  | CountryCode = USA
  | - Tag 'x:xmpmeta/rdf:RDF/rdf:Description/Iptc4xmpCore:CountryCode'
  | Location = Bow Lake Residential Mobile Home Community
  | - Tag 'x:xmpmeta/rdf:RDF/rdf:Description/Iptc4xmpCore:Location'
  | ColorLabel = 0
  | - Tag 'x:xmpmeta/rdf:RDF/rdf:Description/digiKam:ColorLabel'
  | DateTimeDigitized = 2025-06-16T19:41:49-07:00
  | - Tag 'x:xmpmeta/rdf:RDF/rdf:Description/exif:DateTimeDigitized'
  | DateTimeOriginal = 2025-06-16T19:41:49
  | - Tag 'x:xmpmeta/rdf:RDF/rdf:Description/exif:DateTimeOriginal'
  | GPSAltitude = 104/1
  | - Tag 'x:xmpmeta/rdf:RDF/rdf:Description/exif:GPSAltitude'

The latter value is what I want to write to the file, but it looks like it doesn't happen. No other errors or warnings from WriteInfo.

Phil Harvey

I meant to use the Verbose option when writing.

Here is what I get from the command line when trying to write your value:

% exiftool a.jpg -xmp:datetimeoriginal'=2025:06:16 19:41:49-0700' -v2
Writing XMP-exif:DateTimeOriginal
======== a.jpg
Rewriting a.jpg...
  Editing tags in: APP1 XMP
  Creating tags in: APP1 XMP
JPEG APP1 (11914 bytes)
JPEG APP1 (1119 bytes)
  Rewriting XMP
    + XMP-exif:DateTimeOriginal = '2025-06-16T19:41:49-07:00'
JPEG APP5 (49950 bytes)
JPEG DQT (130 bytes)
JPEG SOF0:
JPEG DHT (416 bytes)
JPEG SOS
    1 image files updated

Also, looking at your post #7, doesn't XMP-exif:DateTimeOriginal have the value you were trying to write?  I don't see the problem.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Tom Cunningham

No, in post #7 (at least the one I'm looking at), DateTimeOriginal is still 2025:06:16 21:41:49-07:00; it should be 2025:06:16 19:41:49-07:00:

[XMP-exif]      DateTimeDigitized              : 2025:06:16 19:41:49-07:00
[XMP-exif]      DateTimeOriginal                : 2025:06:16 21:41:49-07:00

So I tried putting $exifTool->Options(Verbose => 2) just before the write:

            $exifTool->Options(Verbose => 2);
            my $result = $exifTool->WriteInfo($fn);
            $exifTool->Options(Verbose => 0);

And I got no debug output. Am I doing this correctly?

Phil Harvey

I see the difference in the date now.

You set Verbose correctly.  The output should go to stdout.

- Phil
...where DIR is the name of a directory/folder containing the images.  On Mac/Linux/PowerShell, use single quotes (') instead of double quotes (") around arguments containing a dollar sign ($).

Tom Cunningham

I obviously don't understand how these date/time tags work.

            # Create DateTime::TimeZone object
            my $location_timezone = DateTime::TimeZone->new(name => $timezone_name);

            # adjust time-related tags as needed
            my @time_tags = qw(
                              ModifyDate
                              DateTimeOriginal
                              CreateDate
                              Sony:SonyDateTime
                              XMP:DateTimeDigitized
                              XMP:DateTimeOriginal
                              XMP:ModifyDate
                              XMP:CreatDate
                              );

            # Iterate through time-related tags
            foreach my $tag (@time_tags) {
                # Get the original timestamp string
                my $timestamp_str = $exifTool->GetValue($tag);
                if ($timestamp_str) {

                    # Assuming timestamp format "YYYY:MM:DD HH:MM:SS" (adjust if needed)
                    if ($timestamp_str =~ /(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})/) {
                        my ($year, $month, $day, $hour, $minute, $second) = ($1, $2, $3, $4, $5, $6);

                        # Create a DateTime object in the original timezone
                        my $original_dt = DateTime->new(
                            year      => $year,
                            month    => $month,
                            day      => $day,
                            hour      => $hour,
                            minute    => $minute,
                            second    => $second,
                            time_zone => 'America/Chicago' # Assume Central time
                        );

                        # Convert the DateTime object to the location's timezone
                        $original_dt->set_time_zone($location_timezone);
                        # Format the adjusted timestamp for ExifTool (YYYY:MM:DD HH:MM:SS[+-]HH:MM)
                        my $adjusted_timestamp = $original_dt->ymd(':') . ' ' . $original_dt->hms(':') . $original_dt->strftime('%z');

                        # Update the timestamp tag with the adjusted value and timezone
                        ($success, $errStr) = $exifTool->SetNewValue($tag, $adjusted_timestamp);
                  }
                }
            }

On the first iteration of the foreach loop, $tag is 'ModifyDate' and $adjusted_timestamp is '2025:06:16 19:41:49-700'. After the call to SetNewValue, $success is 6. I would have expected 1. Is it modifying other tags? Totally confused.

BTW, when I do exiftool -ver from the command line, I get 13.30, but when I run my script with $exifTool->Options(Verbose => 2) I get 12.64. How can I sync these up?

StarGeek

Quote from: Tom Cunningham on July 08, 2025, 06:46:03 PMBTW, when I do exiftool -ver from the command line, I get 13.30, but when I run my script with $exifTool->Options(Verbose => 2) I get 12.64. How can I sync these up?

First, find where you have the exiftool Perl libraries at. They probably are in one of the perl lib directories, with the directory names of File and Image

On the front page download the link that says
"Download Version 13.32 (7.5 MB) - July 2, 2025"
Extract the lib directory from that archive. Inside of that there is a file directory and Image directory. Drop those into the same directory as the old directories so they are overwritten.
"It didn't work" isn't helpful. What was the exact command used and the output.
Read FAQ #3 and use that cmd
Please use the Code button for exiftool output

Please include your OS/Exiftool version/filetype