package exif import ( "fmt" "math" "strconv" "strings" "time" "github.com/dsoprea/go-logging" "github.com/dsoprea/go-exif/v2/common" "github.com/dsoprea/go-exif/v2/undefined" ) var ( utilityLogger = log.NewLogger("exif.utility") ) // ParseExifFullTimestamp parses dates like "2018:11:30 13:01:49" into a UTC // `time.Time` struct. func ParseExifFullTimestamp(fullTimestampPhrase string) (timestamp time.Time, err error) { defer func() { if state := recover(); state != nil { err = log.Wrap(state.(error)) } }() parts := strings.Split(fullTimestampPhrase, " ") datestampValue, timestampValue := parts[0], parts[1] dateParts := strings.Split(datestampValue, ":") year, err := strconv.ParseUint(dateParts[0], 10, 16) if err != nil { log.Panicf("could not parse year") } month, err := strconv.ParseUint(dateParts[1], 10, 8) if err != nil { log.Panicf("could not parse month") } day, err := strconv.ParseUint(dateParts[2], 10, 8) if err != nil { log.Panicf("could not parse day") } timeParts := strings.Split(timestampValue, ":") hour, err := strconv.ParseUint(timeParts[0], 10, 8) if err != nil { log.Panicf("could not parse hour") } minute, err := strconv.ParseUint(timeParts[1], 10, 8) if err != nil { log.Panicf("could not parse minute") } second, err := strconv.ParseUint(timeParts[2], 10, 8) if err != nil { log.Panicf("could not parse second") } timestamp = time.Date(int(year), time.Month(month), int(day), int(hour), int(minute), int(second), 0, time.UTC) return timestamp, nil } // ExifFullTimestampString produces a string like "2018:11:30 13:01:49" from a // `time.Time` struct. It will attempt to convert to UTC first. func ExifFullTimestampString(t time.Time) (fullTimestampPhrase string) { t = t.UTC() return fmt.Sprintf("%04d:%02d:%02d %02d:%02d:%02d", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) } // ExifTag is one simple representation of a tag in a flat list of all of them. type ExifTag struct { // IfdPath is the fully-qualified IFD path (even though it is not named as // such). IfdPath string `json:"ifd_path"` // TagId is the tag-ID. TagId uint16 `json:"id"` // TagName is the tag-name. This is never empty. TagName string `json:"name"` // UnitCount is the recorded number of units constution of the value. UnitCount uint32 `json:"unit_count"` // TagTypeId is the type-ID. TagTypeId exifcommon.TagTypePrimitive `json:"type_id"` // TagTypeName is the type name. TagTypeName string `json:"type_name"` // Value is the decoded value. Value interface{} `json:"value"` // ValueBytes is the raw, encoded value. ValueBytes []byte `json:"value_bytes"` // Formatted is the human representation of the first value (tag values are // always an array). FormattedFirst string `json:"formatted_first"` // Formatted is the human representation of the complete value. Formatted string `json:"formatted"` // ChildIfdPath is the name of the child IFD this tag represents (if it // represents any). Otherwise, this is empty. ChildIfdPath string `json:"child_ifd_path"` } // String returns a string representation. func (et ExifTag) String() string { return fmt.Sprintf("ExifTag= degreesRightBound { return false } else if gi2.Minutes < gi1.Minutes || gi2.Minutes >= minutesRightBound { return false } else if gi2.Seconds < gi1.Seconds || gi2.Seconds >= secondsRightBound { return false } return true } // FqIfdPath returns a fully-qualified IFD path expression. func FqIfdPath(parentFqIfdName, ifdName string, ifdIndex int) string { var currentIfd string if parentFqIfdName != "" { currentIfd = fmt.Sprintf("%s/%s", parentFqIfdName, ifdName) } else { currentIfd = ifdName } if ifdIndex > 0 { currentIfd = fmt.Sprintf("%s%d", currentIfd, ifdIndex) } return currentIfd }