82d61d1f93
Animated GIFs are transcoded to AVC because it is much smaller and thus also suitable for long/large animations. In addition, this commit adds support for more metadata fields such as frame rate, number of frames, file capture timestamp (unix milliseconds), media type, and software version. Support for SVG files can later be implemented in a similar way.
161 lines
3.4 KiB
Go
161 lines
3.4 KiB
Go
package meta
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"runtime/debug"
|
|
"time"
|
|
|
|
"gopkg.in/photoprism/go-tz.v2/tz"
|
|
)
|
|
|
|
type GPhoto struct {
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
Views int `json:"imageViews,string"`
|
|
Geo GGeo `json:"geoData"`
|
|
TakenAt GTime `json:"photoTakenTime"`
|
|
CreatedAt GTime `json:"creationTime"`
|
|
UpdatedAt GTime `json:"modificationTime"`
|
|
}
|
|
|
|
func (m GPhoto) SanitizedTitle() string {
|
|
return SanitizeTitle(m.Title)
|
|
}
|
|
|
|
func (m GPhoto) SanitizedDescription() string {
|
|
return SanitizeDescription(m.Description)
|
|
}
|
|
|
|
type GMeta struct {
|
|
Album GAlbum `json:"albumData"`
|
|
}
|
|
|
|
type GAlbum struct {
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
Access string `json:"access"`
|
|
Location string `json:"location"`
|
|
Date GTime `json:"date"`
|
|
Geo GGeo `json:"geoData"`
|
|
}
|
|
|
|
func (m GAlbum) Exists() bool {
|
|
return m.Title != ""
|
|
}
|
|
|
|
type GGeo struct {
|
|
Lat float64 `json:"latitude"`
|
|
Lng float64 `json:"longitude"`
|
|
Altitude float64 `json:"altitude"`
|
|
}
|
|
|
|
func (m GGeo) Exists() bool {
|
|
return m.Lat != 0.0 && m.Lng != 0.0
|
|
}
|
|
|
|
type GTime struct {
|
|
Unix int64 `json:"timestamp,string"`
|
|
Formatted string `json:"formatted"`
|
|
}
|
|
|
|
func (m GTime) Exists() bool {
|
|
return m.Unix > 0
|
|
}
|
|
|
|
func (m GTime) Time() time.Time {
|
|
return time.Unix(m.Unix, 0).UTC()
|
|
}
|
|
|
|
// GMeta parses JSON sidecar data as created by Google Photos.
|
|
func (data *Data) GMeta(jsonData []byte) (err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = fmt.Errorf("metadata: %s (gmeta panic)\nstack: %s", e, debug.Stack())
|
|
}
|
|
}()
|
|
|
|
p := GMeta{}
|
|
|
|
if err := json.Unmarshal(jsonData, &p); err != nil {
|
|
return err
|
|
}
|
|
|
|
if p.Album.Exists() {
|
|
data.Albums = append(data.Albums, p.Album.Title)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GPhoto parses JSON photo sidecar data as created by Google Photos.
|
|
func (data *Data) GPhoto(jsonData []byte) (err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = fmt.Errorf("metadata: %s (gphoto panic)\nstack: %s", e, debug.Stack())
|
|
}
|
|
}()
|
|
|
|
p := GPhoto{}
|
|
|
|
if err := json.Unmarshal(jsonData, &p); err != nil {
|
|
return err
|
|
}
|
|
|
|
if s := p.SanitizedTitle(); s != "" && data.Title == "" {
|
|
data.Title = s
|
|
}
|
|
|
|
if s := p.SanitizedDescription(); s != "" && data.Description == "" {
|
|
data.Description = s
|
|
}
|
|
|
|
if p.Views > 0 && data.Views == 0 {
|
|
data.Views = p.Views
|
|
}
|
|
|
|
if p.TakenAt.Exists() {
|
|
if data.TakenAt.IsZero() {
|
|
data.TakenAt = p.TakenAt.Time()
|
|
}
|
|
|
|
if data.TakenAtLocal.IsZero() {
|
|
data.TakenAtLocal = p.TakenAt.Time()
|
|
}
|
|
}
|
|
|
|
if p.Geo.Exists() {
|
|
if data.Lat == 0 && data.Lng == 0 {
|
|
data.Lat = float32(p.Geo.Lat)
|
|
data.Lng = float32(p.Geo.Lng)
|
|
}
|
|
|
|
if data.Altitude == 0 {
|
|
data.Altitude = int(p.Geo.Altitude)
|
|
}
|
|
}
|
|
|
|
// Set time zone and calculate UTC time.
|
|
if data.Lat != 0 && data.Lng != 0 {
|
|
zones, err := tz.GetZone(tz.Point{
|
|
Lat: float64(data.Lat),
|
|
Lon: float64(data.Lng),
|
|
})
|
|
|
|
if err == nil && len(zones) > 0 {
|
|
data.TimeZone = zones[0]
|
|
}
|
|
|
|
if !data.TakenAtLocal.IsZero() {
|
|
if loc, err := time.LoadLocation(data.TimeZone); err != nil {
|
|
log.Warnf("metadata: unknown time zone %s (gphotos)", data.TimeZone)
|
|
} else if tl, err := time.ParseInLocation("2006:01:02 15:04:05", data.TakenAtLocal.Format("2006:01:02 15:04:05"), loc); err == nil {
|
|
data.TakenAt = tl.Truncate(time.Second).UTC()
|
|
} else {
|
|
log.Errorf("metadata: %s (gphotos)", err.Error()) // this should never happen
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|