Skip to content

Commit

Permalink
enhance exif subsecond and offset handling. (#1033)
Browse files Browse the repository at this point in the history
* enhance exif subsecond and offset handling.

* don't warn for legal empty offset tags.
  • Loading branch information
tsteven4 committed Mar 11, 2023
1 parent 71e60e9 commit b33c59a
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 13 deletions.
32 changes: 26 additions & 6 deletions exif.cc
Original file line number Diff line number Diff line change
Expand Up @@ -677,7 +677,7 @@ ExifFormat::exif_find_tag(ExifApp* app, const uint16_t ifd_nr, const uint16_t ta
}

QDateTime
ExifFormat::exif_get_exif_time(ExifApp* app)
ExifFormat::exif_get_exif_time(ExifApp* app) const
{
QDateTime res;

Expand All @@ -700,20 +700,24 @@ ExifFormat::exif_get_exif_time(ExifApp* app)
// Exif 2.31 added offset tags to record the offset to UTC.
// If these are present use them, otherwise assume local time.
ExifTag* offset_tag = nullptr;
ExifTag* subsec_tag = nullptr;
switch (tag->id) {
case 0x9003:
offset_tag = exif_find_tag(app, EXIF_IFD, 0x9011); /* OffsetTimeOriginal from EXIF */
subsec_tag = exif_find_tag(app, EXIF_IFD, 0x9291); /* SubSecTimeOriginal from EXIF */
break;
case 0x0132:
offset_tag = exif_find_tag(app, EXIF_IFD, 0x9010); /* OffsetTime from EXIF */
subsec_tag = exif_find_tag(app, EXIF_IFD, 0x9290); /* SubSecTime from EXIF */
break;
case 0x9004:
offset_tag = exif_find_tag(app, EXIF_IFD, 0x9012); /* OffsetTimeDigitized from EXIF */
subsec_tag = exif_find_tag(app, EXIF_IFD, 0x9292); /* SubSecTimeDigitized from EXIF */
break;
}

if (offset_tag) {
QByteArray time_tag = exif_read_str(offset_tag);
if (offset_tag || opt_offsettime) {
QByteArray time_tag = opt_offsettime? QByteArray(opt_offsettime) : exif_read_str(offset_tag);
// string should be +HH:MM or -HH:MM
static const QRegularExpression re(R"(^([+-])(\d{2}):(\d{2})$)");
assert(re.isValid());
Expand All @@ -723,10 +727,24 @@ ExifFormat::exif_get_exif_time(ExifApp* app)
int offset_hours = match.captured(1).append(match.captured(2)).toInt();
int offset_mins = match.captured(1).append(match.captured(3)).toInt();
res.setOffsetFromUtc(((offset_hours * 60) + offset_mins) * 60);
} else if (opt_offsettime) {
// Only warn for user supplied offsets.
// Offset tags may indicate the offset was unknown, e.g. " : ".
warning(MYNAME ": OffsetTime is expected to be +HH:MM or -HH:MM, but was %s.\n", time_tag.constData());
}
}

if (subsec_tag) {
QByteArray subsec = exif_read_str(subsec_tag);
bool ok;
double ss = subsec.prepend("0.").toDouble(&ok);
if (ok) {
res = res.addMSecs(lround(1000.0 * ss));
}
}

}
return res;
return res.toUTC();
}

Waypoint*
Expand Down Expand Up @@ -895,7 +913,7 @@ ExifFormat::exif_waypt_from_exif_app(ExifApp* app) const
gps_datetime = QDateTime(datestamp, timestamp, Qt::UTC);
if (gps_datetime.isValid()) {
if (global_opts.debug_level >= 3) {
printf(MYNAME "-GPSTimeStamp = %s\n", qPrintable(gps_datetime.toString(Qt::ISODate)));
printf(MYNAME "-GPSTimeStamp = %s\n", qPrintable(gps_datetime.toString(Qt::ISODateWithMs)));
}
wpt->SetCreationTime(gps_datetime);
} else {
Expand Down Expand Up @@ -1555,7 +1573,9 @@ ExifFormat::write()

exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 0, dt.time().hour());
exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 1, dt.time().minute());
exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 2, dt.time().second());
exif_put_double(GPS_IFD, GPS_IFD_TAG_TIMESTAMP, 2,
static_cast<double>(dt.time().second()) +
static_cast<double>(dt.time().msec())/1000.0);

exif_put_str(GPS_IFD, GPS_IFD_TAG_DATESTAMP, CSTR(dt.toString(u"yyyy:MM:dd")));
} else {
Expand Down
4 changes: 3 additions & 1 deletion exif.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class ExifFormat : public Format
static void exif_examine_app(ExifApp* app);
static ExifIfd* exif_find_ifd(ExifApp* app, uint16_t ifd_nr);
static ExifTag* exif_find_tag(ExifApp* app, uint16_t ifd_nr, uint16_t tag_id);
static QDateTime exif_get_exif_time(ExifApp* app);
QDateTime exif_get_exif_time(ExifApp* app) const;
Waypoint* exif_waypt_from_exif_app(ExifApp* app) const;
static Rational<int> exif_dec2frac(double val, double tolerance);
ExifTag* exif_put_value(int ifd_nr, uint16_t tag_id, uint16_t type, int count, int index, const void* data) const;
Expand Down Expand Up @@ -205,12 +205,14 @@ class ExifFormat : public Format
char* opt_overwrite{};
char* opt_frame{};
char* opt_name{};
char* opt_offsettime{};

QVector<arglist_t> exif_args = {
{ "filename", &opt_filename, "Set waypoint name to source filename", "Y", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr },
{ "frame", &opt_frame, "Time-frame (in seconds)", "10", ARGTYPE_INT, "0", nullptr, nullptr },
{ "name", &opt_name, "Locate waypoint for tagging by this name", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
{ "overwrite", &opt_overwrite, "!OVERWRITE! the original file. Default=N", "N", ARGTYPE_BOOL, ARG_NOMINMAX, nullptr },
{ "offset", &opt_offsettime, "Image Offset Time (+HH:MM or -HH:MM)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr },
};
};
#endif // EXIF_H_INCLUDED_
2 changes: 2 additions & 0 deletions reference/format3.txt
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ option exif name Locate waypoint for tagging by this name string https://www.

option exif overwrite !OVERWRITE! the original file. Default=N boolean N https://www.gpsbabel.org/WEB_DOC_DIR/fmt_exif.html#fmt_exif_o_overwrite

option exif offset Image Offset Time (+HH:MM or -HH:MM) string https://www.gpsbabel.org/WEB_DOC_DIR/fmt_exif.html#fmt_exif_o_offset

file rwrwrw shape shp ESRI shapefile shape
https://www.gpsbabel.org/WEB_DOC_DIR/fmt_shape.html
option shape name Source for name field in .dbf string 0 https://www.gpsbabel.org/WEB_DOC_DIR/fmt_shape.html#fmt_shape_o_name
Expand Down
1 change: 1 addition & 0 deletions reference/help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ File Types (-i and -o options):
frame Time-frame (in seconds)
name Locate waypoint for tagging by this name
overwrite (0/1) !OVERWRITE! the original file. Default=N
offset Image Offset Time (+HH:MM or -HH:MM)
shape ESRI shapefile
name Source for name field in .dbf
url Source for URL field in .dbf
Expand Down
8 changes: 2 additions & 6 deletions xmldoc/formats/options/exif-frame.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
<userinput>gpsbabel -i gpx -f holiday.gpx -o exif,frame=60 -F IMG0784.JPG</userinput>
</para>
<para>
If the camera time wasn't adjusted, you should move the track(s) by the this difference.
I.e. if the camera time is five minutes behind your time, the track(s) should be shifted
five minutes back.
</para>
<para>
<userinput>gpsbabel -i gpx -f holiday.gpx -x track,move=-5m -o exif,frame=60 -F IMG0784.JPG</userinput>
If the camera time wasn't adjusted, you should use the offset option. You may also need to use the frame option, or
the interpolate filter.
</para>
10 changes: 10 additions & 0 deletions xmldoc/formats/options/exif-offset.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<para>
Uses the given value instead of the value from the tag OffsetTime, OffsetTimeOriginal or OffsetTimeDigitized.
This is useful when the image doesn't contain an OffsetTime* tag and the offset is different from the local time, or when the image contains a tag that is incorrect.
The format of this option should match that of the tag OffsetTime*, specifcally it must be "+HH:MM" or "-HH:MM".
</para>
<para>
If the camera was using China Standard Time, e.g. in the winter in Taiwan, then you should supply
an offset of "+8:00".
<userinput>gpsbabel -i gpx -f holiday.gpx -o exif,offset=+08:00 -F IMG0784.JPG</userinput>
</para>

0 comments on commit b33c59a

Please sign in to comment.