2020-01-07 17:36:49 +01:00
|
|
|
package meta
|
|
|
|
|
|
|
|
import (
|
2020-05-14 11:57:26 +02:00
|
|
|
"math"
|
2020-01-07 17:36:49 +01:00
|
|
|
"time"
|
2020-07-23 15:34:20 +02:00
|
|
|
|
|
|
|
"github.com/photoprism/photoprism/pkg/rnd"
|
2021-11-27 18:41:10 +01:00
|
|
|
"github.com/photoprism/photoprism/pkg/s2"
|
2020-01-07 17:36:49 +01:00
|
|
|
)
|
|
|
|
|
2022-01-05 16:37:19 +01:00
|
|
|
const (
|
|
|
|
ImageTypeHDR = 3 // see https://exiftool.org/TagNames/Apple.html
|
|
|
|
)
|
|
|
|
|
2022-03-28 15:57:29 +02:00
|
|
|
// Data represents image metadata.
|
2020-01-07 17:36:49 +01:00
|
|
|
type Data struct {
|
2022-07-22 11:46:53 +02:00
|
|
|
FileName string `meta:"FileName"`
|
|
|
|
DocumentID string `meta:"BurstUUID,MediaGroupUUID,ImageUniqueID,OriginalDocumentID,DocumentID,DigitalImageGUID"`
|
|
|
|
InstanceID string `meta:"InstanceID,DocumentID"`
|
2022-08-24 21:16:16 +02:00
|
|
|
CreatedAt time.Time `meta:"SubSecCreateDate,CreationDate,CreateDate,MediaCreateDate,ContentCreateDate,TrackCreateDate"`
|
2022-08-24 17:50:22 +02:00
|
|
|
TakenAt time.Time `meta:"SubSecDateTimeOriginal,SubSecDateTimeCreated,DateTimeOriginal,CreationDate,DateTimeCreated,DateTime,DateTimeDigitized" xmp:"DateCreated"`
|
|
|
|
TakenAtLocal time.Time `meta:"SubSecDateTimeOriginal,SubSecDateTimeCreated,DateTimeOriginal,CreationDate,DateTimeCreated,DateTime,DateTimeDigitized"`
|
2022-07-22 11:46:53 +02:00
|
|
|
TakenGps time.Time `meta:"GPSDateTime,GPSDateStamp"`
|
|
|
|
TakenNs int `meta:"-"`
|
|
|
|
TimeZone string `meta:"-"`
|
|
|
|
Duration time.Duration `meta:"Duration,MediaDuration,TrackDuration"`
|
|
|
|
FPS float64 `meta:"VideoFrameRate,VideoAvgFrameRate"`
|
|
|
|
Frames int `meta:"FrameCount"`
|
|
|
|
Codec string `meta:"CompressorID,VideoCodecID,CodecID,FileType"`
|
|
|
|
Title string `meta:"Headline,Title" xmp:"dc:title" dc:"title,title.Alt"`
|
|
|
|
Subject string `meta:"Subject,PersonInImage,ObjectName,HierarchicalSubject,CatalogSets" xmp:"Subject"`
|
|
|
|
Keywords Keywords `meta:"Keywords"`
|
|
|
|
Notes string `meta:"Comment"`
|
|
|
|
Artist string `meta:"Artist,Creator,By-line,OwnerName,Owner" xmp:"Creator"`
|
|
|
|
Description string `meta:"Description,Caption-Abstract" xmp:"Description,Description.Alt"`
|
|
|
|
Copyright string `meta:"Rights,Copyright,CopyrightNotice,WebStatement" xmp:"Rights,Rights.Alt"`
|
|
|
|
License string `meta:"UsageTerms,License"`
|
|
|
|
Projection string `meta:"ProjectionType"`
|
|
|
|
ColorProfile string `meta:"ICCProfileName,ProfileDescription"`
|
|
|
|
CameraMake string `meta:"CameraMake,Make" xmp:"Make"`
|
|
|
|
CameraModel string `meta:"CameraModel,Model" xmp:"Model"`
|
|
|
|
CameraOwner string `meta:"OwnerName"`
|
|
|
|
CameraSerial string `meta:"SerialNumber"`
|
|
|
|
LensMake string `meta:"LensMake"`
|
|
|
|
LensModel string `meta:"Lens,LensModel" xmp:"LensModel"`
|
|
|
|
Software string `meta:"Software,HistorySoftwareAgent,ProcessingSoftware"`
|
|
|
|
Flash bool `meta:"FlashFired"`
|
|
|
|
FocalLength int `meta:"FocalLength,FocalLengthIn35mmFormat"`
|
|
|
|
FocalDistance float64 `meta:"HyperfocalDistance"`
|
|
|
|
Exposure string `meta:"ExposureTime,ShutterSpeedValue,ShutterSpeed,TargetExposureTime"`
|
|
|
|
Aperture float32 `meta:"ApertureValue,Aperture"`
|
|
|
|
FNumber float32 `meta:"FNumber"`
|
|
|
|
Iso int `meta:"ISO"`
|
|
|
|
ImageType int `meta:"HDRImageType"`
|
|
|
|
GPSPosition string `meta:"GPSPosition"`
|
|
|
|
GPSLatitude string `meta:"GPSLatitude"`
|
|
|
|
GPSLongitude string `meta:"GPSLongitude"`
|
|
|
|
Lat float32 `meta:"-"`
|
|
|
|
Lng float32 `meta:"-"`
|
|
|
|
Altitude int `meta:"GlobalAltitude,GPSAltitude"`
|
|
|
|
Width int `meta:"ImageWidth,PixelXDimension,ExifImageWidth,SourceImageWidth"`
|
|
|
|
Height int `meta:"ImageHeight,ImageLength,PixelYDimension,ExifImageHeight,SourceImageHeight"`
|
|
|
|
Orientation int `meta:"-"`
|
|
|
|
Rotation int `meta:"Rotation"`
|
|
|
|
Views int `meta:"-"`
|
|
|
|
Albums []string `meta:"-"`
|
|
|
|
Error error `meta:"-"`
|
|
|
|
json map[string]string
|
|
|
|
exif map[string]string
|
2020-01-07 17:36:49 +01:00
|
|
|
}
|
2020-05-14 11:57:26 +02:00
|
|
|
|
2022-07-06 23:01:54 +02:00
|
|
|
// New returns a new metadata struct.
|
|
|
|
func New() Data {
|
2022-04-09 19:56:38 +02:00
|
|
|
return Data{}
|
2020-07-11 16:46:29 +02:00
|
|
|
}
|
|
|
|
|
2020-05-14 11:57:26 +02:00
|
|
|
// AspectRatio returns the aspect ratio based on width and height.
|
|
|
|
func (data Data) AspectRatio() float32 {
|
2022-04-14 10:49:56 +02:00
|
|
|
w := float64(data.ActualWidth())
|
|
|
|
h := float64(data.ActualHeight())
|
2020-05-14 11:57:26 +02:00
|
|
|
|
2022-04-14 10:49:56 +02:00
|
|
|
if w <= 0 || h <= 0 {
|
2020-05-14 11:57:26 +02:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2022-04-14 10:49:56 +02:00
|
|
|
return float32(math.Round((w/h)*100) / 100)
|
2020-05-14 11:57:26 +02:00
|
|
|
}
|
|
|
|
|
2022-01-05 11:40:44 +01:00
|
|
|
// Portrait returns true if it is a portrait picture or video based on width and height.
|
2020-05-14 11:57:26 +02:00
|
|
|
func (data Data) Portrait() bool {
|
2020-08-28 09:27:25 +02:00
|
|
|
return data.ActualWidth() < data.ActualHeight()
|
2020-05-14 11:57:26 +02:00
|
|
|
}
|
|
|
|
|
2022-01-05 16:37:19 +01:00
|
|
|
// IsHDR tests if it is a high dynamic range file.
|
|
|
|
func (data Data) IsHDR() bool {
|
|
|
|
return data.ImageType == ImageTypeHDR
|
|
|
|
}
|
|
|
|
|
2020-05-14 11:57:26 +02:00
|
|
|
// Megapixels returns the resolution in megapixels.
|
|
|
|
func (data Data) Megapixels() int {
|
|
|
|
return int(math.Round(float64(data.Width*data.Height) / 1000000))
|
|
|
|
}
|
2020-05-27 13:40:21 +02:00
|
|
|
|
|
|
|
// HasDocumentID returns true if a DocumentID exists.
|
|
|
|
func (data Data) HasDocumentID() bool {
|
2022-09-28 09:01:17 +02:00
|
|
|
return rnd.IsUUID(data.DocumentID)
|
2020-05-27 13:40:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// HasInstanceID returns true if an InstanceID exists.
|
|
|
|
func (data Data) HasInstanceID() bool {
|
2022-09-28 09:01:17 +02:00
|
|
|
return rnd.IsUUID(data.InstanceID)
|
2020-05-27 13:40:21 +02:00
|
|
|
}
|
|
|
|
|
2021-02-05 09:45:28 +01:00
|
|
|
// HasTimeAndPlace if data contains a time and GPS position.
|
2020-05-27 13:40:21 +02:00
|
|
|
func (data Data) HasTimeAndPlace() bool {
|
|
|
|
return !data.TakenAt.IsZero() && data.Lat != 0 && data.Lng != 0
|
|
|
|
}
|
2020-06-04 14:56:27 +02:00
|
|
|
|
|
|
|
// ActualWidth is the width after rotating the media file if needed.
|
|
|
|
func (data Data) ActualWidth() int {
|
|
|
|
if data.Orientation > 4 {
|
|
|
|
return data.Height
|
|
|
|
}
|
|
|
|
|
|
|
|
return data.Width
|
|
|
|
}
|
|
|
|
|
|
|
|
// ActualHeight is the height after rotating the media file if needed.
|
|
|
|
func (data Data) ActualHeight() int {
|
|
|
|
if data.Orientation > 4 {
|
|
|
|
return data.Width
|
|
|
|
}
|
|
|
|
|
|
|
|
return data.Height
|
|
|
|
}
|
2020-12-04 19:51:51 +01:00
|
|
|
|
|
|
|
// CellID returns the S2 cell ID.
|
|
|
|
func (data Data) CellID() string {
|
|
|
|
return s2.PrefixedToken(float64(data.Lat), float64(data.Lng))
|
|
|
|
}
|