2026-03-13 17:33:39 +00:00
|
|
|
package platform
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"image"
|
|
|
|
|
_ "image/gif" // register GIF format
|
|
|
|
|
_ "image/jpeg" // register JPEG format
|
|
|
|
|
_ "image/png" // register PNG format
|
|
|
|
|
"io"
|
2026-04-30 15:34:08 +00:00
|
|
|
"math"
|
2026-03-13 17:33:39 +00:00
|
|
|
"time"
|
|
|
|
|
|
2026-05-07 10:39:17 +00:00
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/db"
|
|
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/db/gen/nidus-sync/publicreport/model"
|
|
|
|
|
querypublicreport "github.com/Gleipnir-Technology/nidus-sync/db/query/publicreport"
|
|
|
|
|
"github.com/Gleipnir-Technology/nidus-sync/geomutil"
|
2026-03-13 17:33:39 +00:00
|
|
|
"github.com/google/uuid"
|
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
|
"github.com/rwcarlsen/goexif/exif"
|
|
|
|
|
"github.com/rwcarlsen/goexif/tiff"
|
2026-05-07 10:39:17 +00:00
|
|
|
"github.com/twpayne/go-geom"
|
2026-03-13 17:33:39 +00:00
|
|
|
//exif "github.com/rwcarlsen/goexif/exif"
|
|
|
|
|
//"github.com/dsoprea/go-exif-extra/format"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type GPS struct {
|
|
|
|
|
Latitude float64
|
|
|
|
|
Longitude float64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ExifCollection struct {
|
|
|
|
|
GPS *GPS
|
|
|
|
|
Tags map[string]string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ImageUpload struct {
|
|
|
|
|
Bounds image.Rectangle
|
|
|
|
|
ContentType string
|
|
|
|
|
Exif *ExifCollection
|
|
|
|
|
Format string
|
|
|
|
|
|
|
|
|
|
UploadFilesize int
|
|
|
|
|
UploadFilename string
|
|
|
|
|
UUID uuid.UUID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (e *ExifCollection) Walk(name exif.FieldName, tag *tiff.Tag) error {
|
|
|
|
|
e.Tags[string(name)] = tag.String()
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
func ImageExtractExif(content_type string, file_bytes []byte) (result *ExifCollection, err error) {
|
|
|
|
|
/*
|
|
|
|
|
Using "github.com/evanoberholster/imagemeta"
|
|
|
|
|
meta, err := imagemeta.Decode(bytes.NewReader(file_bytes))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return result, fmt.Errorf("Failed to decode image meta: %w", err)
|
|
|
|
|
}
|
|
|
|
|
result.GPS = &GPS{
|
|
|
|
|
Latitude: meta.GPS.Latitude(),
|
|
|
|
|
Longitude: meta.GPS.Longitude(),
|
|
|
|
|
}
|
|
|
|
|
return result, err
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
e, err := exif.Decode(bytes.NewReader(file_bytes))
|
|
|
|
|
if err != nil {
|
|
|
|
|
if err.Error() == "exif: failed to find exif intro marker" {
|
|
|
|
|
return nil, nil
|
|
|
|
|
} else if errors.Is(err, io.EOF) {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
return nil, fmt.Errorf("Failed to decode image meta: %w", err)
|
|
|
|
|
}
|
|
|
|
|
lat, lng, _ := e.LatLong()
|
|
|
|
|
result = &ExifCollection{
|
|
|
|
|
GPS: &GPS{
|
|
|
|
|
Latitude: lat,
|
|
|
|
|
Longitude: lng,
|
|
|
|
|
},
|
|
|
|
|
Tags: make(map[string]string, 0),
|
|
|
|
|
}
|
|
|
|
|
err = e.Walk(result)
|
|
|
|
|
return result, err
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-07 10:39:17 +00:00
|
|
|
func saveImageUploads(ctx context.Context, txn db.Ex, uploads []ImageUpload) ([]model.Image, error) {
|
|
|
|
|
images := make([]model.Image, 0)
|
2026-03-13 17:33:39 +00:00
|
|
|
for _, u := range uploads {
|
2026-05-07 10:39:17 +00:00
|
|
|
var location *geom.T
|
|
|
|
|
if u.Exif != nil && u.Exif.GPS != nil && !(math.IsNaN(u.Exif.GPS.Longitude) || math.IsNaN(u.Exif.GPS.Latitude)) {
|
|
|
|
|
l := geomutil.PointFromLngLat(u.Exif.GPS.Longitude, u.Exif.GPS.Latitude)
|
|
|
|
|
location = &l
|
|
|
|
|
}
|
|
|
|
|
image := model.Image{
|
|
|
|
|
// ID:
|
|
|
|
|
ContentType: u.ContentType,
|
|
|
|
|
Created: time.Now(),
|
|
|
|
|
Location: location,
|
|
|
|
|
ResolutionX: int32(u.Bounds.Max.X),
|
|
|
|
|
ResolutionY: int32(u.Bounds.Max.Y),
|
|
|
|
|
StorageUUID: u.UUID,
|
|
|
|
|
StorageSize: int64(u.UploadFilesize),
|
|
|
|
|
UploadedFilename: u.UploadFilename,
|
|
|
|
|
}
|
|
|
|
|
image, err := querypublicreport.ImageInsert(ctx, txn, image)
|
2026-03-13 17:33:39 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return images, fmt.Errorf("Failed to create photo records: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: figure out how to do this via the setter...?
|
|
|
|
|
if u.Exif != nil {
|
2026-05-07 10:39:17 +00:00
|
|
|
exif_models := make([]model.ImageExif, len(u.Exif.Tags))
|
|
|
|
|
i := 0
|
2026-03-13 17:33:39 +00:00
|
|
|
for k, v := range u.Exif.Tags {
|
|
|
|
|
to_save := trimQuotes(v)
|
2026-05-07 10:39:17 +00:00
|
|
|
exif_models[i] = model.ImageExif{
|
|
|
|
|
ImageID: image.ID,
|
|
|
|
|
Name: k,
|
|
|
|
|
Value: to_save,
|
|
|
|
|
}
|
2026-03-13 17:33:39 +00:00
|
|
|
}
|
2026-05-07 10:39:17 +00:00
|
|
|
if len(exif_models) > 0 {
|
|
|
|
|
_, err = querypublicreport.ImageExifInserts(ctx, txn, exif_models)
|
2026-03-13 17:33:39 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return images, fmt.Errorf("Failed to create photo exif records: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Info().Int32("id", image.ID).Int("tags", len(u.Exif.Tags)).Msg("Saved an uploaded file to the database")
|
|
|
|
|
} else {
|
|
|
|
|
log.Info().Int32("id", image.ID).Int("tags", 0).Msg("Saved an uploaded file without EXIF data")
|
|
|
|
|
}
|
|
|
|
|
images = append(images, image)
|
|
|
|
|
}
|
|
|
|
|
return images, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Given a string like "\"foo\"" return "foo".
|
|
|
|
|
func trimQuotes(s string) string {
|
|
|
|
|
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
|
|
|
|
|
return s[1 : len(s)-1]
|
|
|
|
|
}
|
|
|
|
|
return s
|
|
|
|
|
}
|