Files

172 lines
3.3 KiB
Go

package exiftool
import (
"fmt"
"math"
"strings"
"time"
)
// GPS stores gps-related tags.
type GPS struct {
GPSLatitude *float64
GPSLongitude *float64
}
// IsValid returns true when GPS data is valid.
func (gps GPS) IsValid() bool {
if gps.GPSLongitude == nil || gps.GPSLatitude == nil {
return false
}
if math.IsNaN(*gps.GPSLatitude) {
return false
}
if math.IsNaN(*gps.GPSLongitude) {
return false
}
if math.Abs(*gps.GPSLatitude) > 90 || math.Abs(*gps.GPSLongitude) > 180 {
return false
}
return true
}
func (gps GPS) String() string {
if !gps.IsValid() {
return "GPS(invalid)"
}
return fmt.Sprintf("GPS(%.9f, %.9f)", *gps.GPSLatitude, *gps.GPSLongitude)
}
// TimeAll stores tags returned by -time:all.
type TimeAll struct {
SubSecDateTimeOriginal *string
SubSecCreateDate *string
DateTimeOriginal *string
CreateDate *string
TrackCreateDate *string
MediaCreateDate *string
FileModifyDate *string
OffsetTimeOriginal *string
OffsetTime *string
TimeZone *int
GPSDateTime *string
}
const layout = "2006:01:02 15:04:05.999"
const layoutWithTimezone = "2006:01:02 15:04:05.999Z07:00"
// TimeInLocal returns most likely time. The date and time are in local. The timezone is meaningless and always be in UTC. Use `OffsetSecs()` to determine the timezone.
func (t TimeAll) TimeInLocal() time.Time {
for _, dateP := range []*string{
// Keep the order for the priority to generate DateShot
t.SubSecDateTimeOriginal,
t.SubSecCreateDate,
t.DateTimeOriginal,
t.CreateDate,
t.TrackCreateDate,
t.MediaCreateDate,
t.FileModifyDate,
} {
if dateP == nil {
continue
}
date := *dateP
// Ignore timezone
if zoneIndex := strings.IndexAny(date, "+-Z"); zoneIndex >= 0 {
date = date[:zoneIndex]
}
if date, err := time.ParseInLocation(layout, date, time.UTC); err == nil {
return date
}
}
return time.Time{}
}
// OffsetSecs returns seconds offset by UTC.
func (t TimeAll) OffsetSecs(local time.Time) (int, bool) {
for _, offsetP := range []*string{
t.OffsetTimeOriginal,
t.OffsetTime,
} {
if offsetP == nil {
continue
}
if t, err := time.Parse("-07:00", *offsetP); err == nil {
_, offsetSecs := t.Zone()
return offsetSecs, true
}
}
// TimeZone is in minutes
if t.TimeZone != nil {
return *t.TimeZone * 60, true
}
// Calculate offset sec with GPS time and local time.
if local.IsZero() {
return 0, false
}
if t.GPSDateTime == nil {
return 0, false
}
gpsDate, err := time.Parse(layoutWithTimezone, *t.GPSDateTime)
if err != nil {
return 0, false
}
gpsDate = gpsDate.UTC()
// GPS time is always UTC per EXIF spec
// offset = local time (in UTC) - GPS UTC time
offset := int(local.Sub(gpsDate).Seconds())
return offset, true
}
type PhotoMeta struct {
ImageDescription *string
Model *string
Make *string
LensModel *string
ISO *int64
Flash *int64
Orientation *int64
ExposureProgram *int64
ExposureTime *float64
Aperture *float64
FocalLength *float64
}
func (m *PhotoMeta) SanitizeFloats() {
for _, value := range []**float64{
&m.ExposureTime,
&m.Aperture,
&m.FocalLength,
} {
if *value == nil {
continue
}
if math.IsNaN(**value) || math.IsInf(**value, 0) {
*value = nil
}
}
}
type MIMEType struct {
MIMEType *string
}