mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 20:59:03 +00:00
Use exiftool for parsing exif data
Use exiftool to parse exif metadata. Signed-off-by: Kjeldgaard <Kjeldgaard@users.noreply.github.com>
This commit is contained in:
@@ -31,7 +31,7 @@ func SaveEXIF(tx *gorm.DB, media *models.Media) (*models.MediaEXIF, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var parser exifParser = &internalExifParser{}
|
||||
var parser exifParser = &externalExifParser{}
|
||||
|
||||
exif, err := parser.ParseExif(media)
|
||||
if err != nil {
|
||||
|
||||
206
api/scanner/exif/exif_parser_external.go
Normal file
206
api/scanner/exif/exif_parser_external.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package exif
|
||||
|
||||
import (
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/barasher/go-exiftool"
|
||||
"github.com/photoview/photoview/api/graphql/models"
|
||||
)
|
||||
|
||||
type externalExifParser struct{}
|
||||
|
||||
func (p *externalExifParser) ParseExif(media *models.Media) (returnExif *models.MediaEXIF, returnErr error) {
|
||||
// Init ExifTool
|
||||
et, err := exiftool.NewExiftool()
|
||||
if err != nil {
|
||||
log.Printf("Error initializing ExifTool: %s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
defer et.Close()
|
||||
|
||||
fileInfos := et.ExtractMetadata(media.Path)
|
||||
newExif := models.MediaEXIF{}
|
||||
|
||||
for _, fileInfo := range fileInfos {
|
||||
if fileInfo.Err != nil {
|
||||
log.Printf("Fileinfo error\n")
|
||||
continue
|
||||
}
|
||||
|
||||
// Get camera model
|
||||
model, err := fileInfo.GetString("Model")
|
||||
if err == nil {
|
||||
log.Printf("Camera model: %v", model)
|
||||
newExif.Camera = &model
|
||||
}
|
||||
|
||||
// Get Camera make
|
||||
make, err := fileInfo.GetString("Make")
|
||||
if err == nil {
|
||||
log.Printf("Camera make: %v", make)
|
||||
newExif.Maker = &make
|
||||
}
|
||||
|
||||
// Get lens
|
||||
lens, err := fileInfo.GetString("LensModel")
|
||||
if err == nil {
|
||||
log.Printf("Lens: %v", lens)
|
||||
newExif.Lens = &lens
|
||||
}
|
||||
|
||||
//Get time of photo
|
||||
date, err := fileInfo.GetString("DateTimeOriginal")
|
||||
if err == nil {
|
||||
log.Printf("Date shot: %s", date)
|
||||
layout := "2006:01:02 15:04:05"
|
||||
dateTime, err := time.Parse(layout, date)
|
||||
if err == nil {
|
||||
newExif.DateShot = &dateTime
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Get exposure time
|
||||
exposureTime, err := fileInfo.GetString("ExposureTime")
|
||||
if err == nil {
|
||||
log.Printf("Exposure time: %s", exposureTime)
|
||||
newExif.Exposure = &exposureTime
|
||||
}
|
||||
|
||||
// Get aperture
|
||||
aperture, err := fileInfo.GetFloat("Aperture")
|
||||
if err == nil {
|
||||
log.Printf("Aperture: %f", aperture)
|
||||
newExif.Aperture = &aperture
|
||||
}
|
||||
|
||||
// Get ISO
|
||||
iso, err := fileInfo.GetInt("ISO")
|
||||
if err == nil {
|
||||
log.Printf("ISO: %d", iso)
|
||||
newExif.Iso = &iso
|
||||
}
|
||||
|
||||
// Get focal length
|
||||
focalLen, err := fileInfo.GetString("FocalLength")
|
||||
if err == nil {
|
||||
log.Printf("Focal length: %s", focalLen)
|
||||
reg, err := regexp.Compile("[0-9.]+")
|
||||
focalLenStr := reg.FindString(focalLen)
|
||||
focalLenFloat, err := strconv.ParseFloat(focalLenStr, 64)
|
||||
if err == nil {
|
||||
newExif.FocalLength = &focalLenFloat
|
||||
}
|
||||
}
|
||||
|
||||
// Get flash info
|
||||
flash, err := fileInfo.GetString("Flash")
|
||||
if err == nil {
|
||||
log.Printf("Flash: %s", flash)
|
||||
newExif.Flash = &flash
|
||||
}
|
||||
|
||||
// Get orientation
|
||||
orientation, err := fileInfo.GetString("Orientation")
|
||||
log.Printf("Orientation: %s", orientation)
|
||||
if err == nil {
|
||||
if orientation == "Horizontal (normal)" {
|
||||
var orientationInt int64 = 0
|
||||
newExif.Orientation = &orientationInt
|
||||
} else {
|
||||
reg, err := regexp.Compile("CCW")
|
||||
if err == nil {
|
||||
if reg.MatchString(orientation) {
|
||||
//Counter clockwise
|
||||
reg, err := regexp.Compile("[0-9.]+")
|
||||
orientationStr := reg.FindString(orientation)
|
||||
orientationInt, err := strconv.ParseInt(orientationStr, 10, 64)
|
||||
if err == nil {
|
||||
log.Printf("Orientation: %d", orientationInt)
|
||||
newExif.Orientation = &orientationInt
|
||||
}
|
||||
} else {
|
||||
// Clockwise
|
||||
reg, err := regexp.Compile("[0-9.]+")
|
||||
orientationStr := reg.FindString(orientation)
|
||||
orientationInt, err := strconv.ParseInt(orientationStr, 10, 64)
|
||||
if err == nil {
|
||||
orientationInt = orientationInt * -1
|
||||
log.Printf("Orientation: %d", orientationInt)
|
||||
newExif.Orientation = &orientationInt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get exposure program
|
||||
expProgram, err := fileInfo.GetString("ExposureProgram")
|
||||
if err == nil {
|
||||
log.Printf("Exposure Program: %s", expProgram)
|
||||
newExif.ExposureProgram = &expProgram
|
||||
}
|
||||
|
||||
// GPS coordinates - longitude
|
||||
longitudeRaw, err := fileInfo.GetString("GPSLongitude")
|
||||
if err == nil {
|
||||
log.Printf("GPS longitude: %s", longitudeRaw)
|
||||
value, err := ConvertCoodinateToFloat(longitudeRaw)
|
||||
if err == nil {
|
||||
newExif.GPSLongitude = &value
|
||||
}
|
||||
}
|
||||
|
||||
// GPS coordinates - latitude
|
||||
latitudeRaw, err := fileInfo.GetString("GPSLatitude")
|
||||
if err == nil {
|
||||
log.Printf("GPS latitude: %s", latitudeRaw)
|
||||
value, err := ConvertCoodinateToFloat(latitudeRaw)
|
||||
if err == nil {
|
||||
newExif.GPSLatitude = &value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
returnExif = &newExif
|
||||
return
|
||||
}
|
||||
|
||||
func ConvertCoodinateToFloat(coordinate string) (value float64, err error) {
|
||||
reg, err := regexp.Compile("[0-9.]+")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
coordinateStr := reg.FindAllString(coordinate, -1)
|
||||
log.Printf("GPS: %s length: %d\n", coordinateStr, len(coordinateStr))
|
||||
if len(coordinateStr) != 3 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
deg, err := strconv.ParseFloat(coordinateStr[0], 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
minute, err := strconv.ParseFloat(coordinateStr[1], 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
second, err := strconv.ParseFloat(coordinateStr[2], 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var multiplier float64 = 1
|
||||
if deg < 0 {
|
||||
multiplier = -1
|
||||
}
|
||||
|
||||
value = (deg + minute / 60 + second / 3600) * multiplier
|
||||
return value, nil
|
||||
}
|
||||
@@ -1,188 +0,0 @@
|
||||
package exif
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"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{}
|
||||
|
||||
func (p *internalExifParser) ParseExif(media *models.Media) (returnExif *models.MediaEXIF, returnErr error) {
|
||||
photoFile, err := os.Open(media.Path)
|
||||
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 {
|
||||
return nil, errors.Wrap(err, "Could not decode EXIF")
|
||||
}
|
||||
|
||||
newExif := models.MediaEXIF{}
|
||||
|
||||
model, err := p.readStringTag(exifTags, exif.Model, media)
|
||||
if err == nil {
|
||||
newExif.Camera = model
|
||||
}
|
||||
|
||||
maker, err := p.readStringTag(exifTags, exif.Make, media)
|
||||
if err == nil {
|
||||
newExif.Maker = maker
|
||||
}
|
||||
|
||||
lens, err := p.readStringTag(exifTags, exif.LensModel, media)
|
||||
if err == nil {
|
||||
newExif.Lens = lens
|
||||
}
|
||||
|
||||
date, err := exifTags.DateTime()
|
||||
if err == nil {
|
||||
newExif.DateShot = &date
|
||||
}
|
||||
|
||||
exposure, err := p.readRationalTag(exifTags, exif.ExposureTime, media)
|
||||
if err == nil {
|
||||
exposureStr := exposure.RatString()
|
||||
newExif.Exposure = &exposureStr
|
||||
}
|
||||
|
||||
apertureRat, err := p.readRationalTag(exifTags, exif.FNumber, media)
|
||||
if err == nil {
|
||||
aperture, _ := apertureRat.Float64()
|
||||
newExif.Aperture = &aperture
|
||||
}
|
||||
|
||||
isoTag, err := exifTags.Get(exif.ISOSpeedRatings)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Could not read ISOSpeedRatings from EXIF: %s\n", media.Title)
|
||||
} else {
|
||||
iso, err := isoTag.Int(0)
|
||||
if err != nil {
|
||||
log.Printf("WARN: Could not parse EXIF ISOSpeedRatings as integer: %s\n", media.Title)
|
||||
} else {
|
||||
newExif.Iso = &iso
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
log.Printf("WARN: Could not parse EXIF FocalLength as rational or integer: %s\n%s\n", media.Title, err)
|
||||
} else {
|
||||
focalLenFloat := float64(focalLength)
|
||||
newExif.FocalLength = &focalLenFloat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flash, err := exifTags.Flash()
|
||||
if err == nil {
|
||||
newExif.Flash = &flash
|
||||
}
|
||||
|
||||
orientation, err := p.readIntegerTag(exifTags, exif.Orientation, media)
|
||||
if err == nil {
|
||||
newExif.Orientation = orientation
|
||||
}
|
||||
|
||||
exposureProgram, err := p.readIntegerTag(exifTags, exif.ExposureProgram, media)
|
||||
if err == nil {
|
||||
newExif.ExposureProgram = exposureProgram
|
||||
}
|
||||
|
||||
lat, long, err := exifTags.LatLong()
|
||||
if err == nil {
|
||||
newExif.GPSLatitude = &lat
|
||||
newExif.GPSLongitude = &long
|
||||
}
|
||||
|
||||
returnExif = &newExif
|
||||
return
|
||||
}
|
||||
|
||||
func (p *internalExifParser) readStringTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*string, error) {
|
||||
tag, err := tags.Get(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title)
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
value, err := tag.StringVal()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse %s from EXIF as string: %s", name, media.Title)
|
||||
}
|
||||
|
||||
return &value, nil
|
||||
}
|
||||
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media.Title)
|
||||
return nil, errors.New("exif tag returned null")
|
||||
}
|
||||
|
||||
func (p *internalExifParser) readRationalTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*big.Rat, error) {
|
||||
tag, err := tags.Get(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title)
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
value, err := tag.Rat(0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not parse %s from EXIF as rational: %s", name, media.Title)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media.Title)
|
||||
return nil, errors.New("exif tag returned null")
|
||||
}
|
||||
|
||||
func (p *internalExifParser) readIntegerTag(tags *exif.Exif, name exif.FieldName, media *models.Media) (*int, error) {
|
||||
tag, err := tags.Get(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not read %s from EXIF: %s", name, media.Title)
|
||||
}
|
||||
|
||||
if tag != nil {
|
||||
value, err := tag.Int(0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Could not parse %s from EXIF as integer: %s", name, media.Title)
|
||||
}
|
||||
|
||||
return &value, nil
|
||||
}
|
||||
|
||||
log.Printf("WARN: EXIF tag %s returned null: %s\n", name, media.Title)
|
||||
return nil, errors.New("exif tag returned null")
|
||||
}
|
||||
Reference in New Issue
Block a user