2021-03-23 21:36:03 +01:00
|
|
|
package exif
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"math/big"
|
|
|
|
"os"
|
2021-04-22 22:46:52 +02:00
|
|
|
"time"
|
2021-03-23 21:36:03 +01:00
|
|
|
|
|
|
|
"github.com/photoview/photoview/api/graphql/models"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/xor-gate/goexif2/exif"
|
|
|
|
"github.com/xor-gate/goexif2/mknote"
|
|
|
|
)
|
|
|
|
|
|
|
|
// internalExifParser is an exif parser that parses the media without the use of external tools
|
|
|
|
type internalExifParser struct{}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
func (p *internalExifParser) ParseExif(media_path string) (returnExif *models.MediaEXIF, returnErr error) {
|
|
|
|
photoFile, err := os.Open(media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
exif.RegisterParsers(mknote.All...)
|
|
|
|
|
|
|
|
// Recover if exif.Decode panics
|
|
|
|
defer func() {
|
|
|
|
if err := recover(); err != nil {
|
|
|
|
log.Printf("Recovered from panic: Exif decoding: %s\n", err)
|
|
|
|
returnErr = errors.New(fmt.Sprintf("Exif decoding panicked: %s\n", err))
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
exifTags, err := exif.Decode(photoFile)
|
|
|
|
if err != nil {
|
2021-04-22 22:46:52 +02:00
|
|
|
return nil, nil
|
|
|
|
// return nil, errors.Wrap(err, "Could not decode EXIF")
|
2021-03-23 21:36:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
newExif := models.MediaEXIF{}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
model, err := p.readStringTag(exifTags, exif.Model, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
if err == nil {
|
|
|
|
newExif.Camera = model
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
maker, err := p.readStringTag(exifTags, exif.Make, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
if err == nil {
|
|
|
|
newExif.Maker = maker
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
lens, err := p.readStringTag(exifTags, exif.LensModel, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
if err == nil {
|
|
|
|
newExif.Lens = lens
|
|
|
|
}
|
|
|
|
|
|
|
|
date, err := exifTags.DateTime()
|
|
|
|
if err == nil {
|
2021-04-22 22:46:52 +02:00
|
|
|
_, tz := date.Zone()
|
|
|
|
date_utc := date.Add(time.Duration(tz) * time.Second).UTC()
|
|
|
|
newExif.DateShot = &date_utc
|
2021-03-23 21:36:03 +01:00
|
|
|
}
|
|
|
|
|
2021-05-18 23:48:48 +02:00
|
|
|
exposureTag, err := exifTags.Get(exif.ExposureTime)
|
2021-03-23 21:36:03 +01:00
|
|
|
if err == nil {
|
2021-05-18 23:48:48 +02:00
|
|
|
exposureRat, err := exposureTag.Rat(0)
|
2021-03-31 23:35:14 +02:00
|
|
|
if err == nil {
|
2021-05-18 23:48:48 +02:00
|
|
|
exposure, _ := exposureRat.Float64()
|
|
|
|
newExif.Exposure = &exposure
|
2021-03-31 23:35:14 +02:00
|
|
|
}
|
2021-03-23 21:36:03 +01:00
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
apertureRat, err := p.readRationalTag(exifTags, exif.FNumber, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
if err == nil {
|
|
|
|
aperture, _ := apertureRat.Float64()
|
|
|
|
newExif.Aperture = &aperture
|
|
|
|
}
|
|
|
|
|
|
|
|
isoTag, err := exifTags.Get(exif.ISOSpeedRatings)
|
|
|
|
if err != nil {
|
2021-04-22 21:37:59 +02:00
|
|
|
log.Printf("WARN: Could not read ISOSpeedRatings from EXIF: %v\n", media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
} else {
|
|
|
|
iso, err := isoTag.Int(0)
|
|
|
|
if err != nil {
|
2021-04-22 21:37:59 +02:00
|
|
|
log.Printf("WARN: Could not parse EXIF ISOSpeedRatings as integer: %v\n", media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
} else {
|
|
|
|
iso64 := int64(iso)
|
|
|
|
newExif.Iso = &iso64
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
focalLengthTag, err := exifTags.Get(exif.FocalLength)
|
|
|
|
if err == nil {
|
|
|
|
focalLengthRat, err := focalLengthTag.Rat(0)
|
|
|
|
if err == nil {
|
|
|
|
focalLength, _ := focalLengthRat.Float64()
|
|
|
|
newExif.FocalLength = &focalLength
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// For some photos, the focal length cannot be read as a rational value,
|
|
|
|
// but is instead the second value read as an integer
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
focalLength, err := focalLengthTag.Int(1)
|
|
|
|
if err != nil {
|
2021-04-22 21:37:59 +02:00
|
|
|
log.Printf("WARN: Could not parse EXIF FocalLength as rational or integer: %v\n%s\n", media_path, err)
|
2021-03-23 21:36:03 +01:00
|
|
|
} else {
|
|
|
|
focalLenFloat := float64(focalLength)
|
|
|
|
newExif.FocalLength = &focalLenFloat
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
flash, err := p.readIntegerTag(exifTags, exif.Flash, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
if err == nil {
|
2021-03-28 23:52:51 +02:00
|
|
|
flash64 := int64(*flash)
|
|
|
|
newExif.Flash = &flash64
|
2021-03-23 21:36:03 +01:00
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
orientation, err := p.readIntegerTag(exifTags, exif.Orientation, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
if err == nil {
|
|
|
|
orientation64 := int64(*orientation)
|
|
|
|
newExif.Orientation = &orientation64
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
exposureProgram, err := p.readIntegerTag(exifTags, exif.ExposureProgram, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
if err == nil {
|
|
|
|
exposureProgram64 := int64(*exposureProgram)
|
|
|
|
newExif.ExposureProgram = &exposureProgram64
|
|
|
|
}
|
|
|
|
|
|
|
|
lat, long, err := exifTags.LatLong()
|
|
|
|
if err == nil {
|
|
|
|
newExif.GPSLatitude = &lat
|
|
|
|
newExif.GPSLongitude = &long
|
|
|
|
}
|
|
|
|
|
|
|
|
returnExif = &newExif
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
func (p *internalExifParser) readStringTag(tags *exif.Exif, name exif.FieldName, media_path string) (*string, error) {
|
2021-03-23 21:36:03 +01:00
|
|
|
tag, err := tags.Get(name)
|
|
|
|
if err != nil {
|
2021-04-22 21:37:59 +02:00
|
|
|
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if tag != nil {
|
|
|
|
value, err := tag.StringVal()
|
|
|
|
if err != nil {
|
2021-04-22 21:37:59 +02:00
|
|
|
return nil, errors.Wrapf(err, "could not parse %s from EXIF as string: %s", name, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return &value, nil
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
return nil, errors.New("exif tag returned null")
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
func (p *internalExifParser) readRationalTag(tags *exif.Exif, name exif.FieldName, media_path string) (*big.Rat, error) {
|
2021-03-23 21:36:03 +01:00
|
|
|
tag, err := tags.Get(name)
|
|
|
|
if err != nil {
|
2021-04-22 21:37:59 +02:00
|
|
|
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if tag != nil {
|
|
|
|
value, err := tag.Rat(0)
|
|
|
|
if err != nil {
|
2021-04-22 21:37:59 +02:00
|
|
|
return nil, errors.Wrapf(err, "could not parse %s from EXIF as rational: %s", name, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return value, nil
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
return nil, errors.New("exif tag returned null")
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
func (p *internalExifParser) readIntegerTag(tags *exif.Exif, name exif.FieldName, media_path string) (*int, error) {
|
2021-03-23 21:36:03 +01:00
|
|
|
tag, err := tags.Get(name)
|
|
|
|
if err != nil {
|
2021-04-22 21:37:59 +02:00
|
|
|
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if tag != nil {
|
|
|
|
value, err := tag.Int(0)
|
|
|
|
if err != nil {
|
2021-04-22 21:37:59 +02:00
|
|
|
return nil, errors.Wrapf(err, "Could not parse %s from EXIF as integer: %s", name, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return &value, nil
|
|
|
|
}
|
|
|
|
|
2021-04-22 21:37:59 +02:00
|
|
|
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media_path)
|
2021-03-23 21:36:03 +01:00
|
|
|
return nil, errors.New("exif tag returned null")
|
|
|
|
}
|