API: Rename /batch/photos endpoint to /batch/photos/edit #271

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2025-05-04 14:09:23 +02:00
parent b423b1980b
commit dd6e17e97e
27 changed files with 1937 additions and 1083 deletions

View File

@@ -3,7 +3,8 @@ package batch
type Action = string
const (
ActionNone Action = "none"
ActionUpdate Action = "update"
ActionAdd Action = "add"
ActionRemove Action = "remove"
ActionKeep Action = "keep"
ActionChange Action = "change"
)

View File

@@ -1,11 +1,11 @@
package batch
import (
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/entity/search"
)
// PhotoForm represents photo batch edit form values.
type PhotoForm struct {
// PhotosForm represents photo batch edit form values.
type PhotosForm struct {
PhotoType String `json:"Type"`
PhotoTitle String `json:"Title"`
PhotoCaption String `json:"Caption"`
@@ -30,207 +30,223 @@ type PhotoForm struct {
CameraID UInt `json:"CameraID"`
LensID UInt `json:"LensID"`
DetailsSubject String `json:"Subject"`
DetailsArtist String `json:"Artist"`
DetailsCopyright String `json:"Copyright"`
DetailsLicense String `json:"License"`
DetailsKeywords String `json:"DetailsKeywords"`
DetailsSubject String `json:"DetailsSubject"`
DetailsArtist String `json:"DetailsArtist"`
DetailsCopyright String `json:"DetailsCopyright"`
DetailsLicense String `json:"DetailsLicense"`
}
func NewPhotoForm(photos entity.Photos) *PhotoForm {
frm := &PhotoForm{}
func NewPhotosForm(photos search.PhotoResults) *PhotosForm {
frm := &PhotosForm{}
for _, photo := range photos {
if photo.PhotoType != "" && frm.PhotoType.Value == "" {
for i, photo := range photos {
if i == 0 {
frm.PhotoType.Value = photo.PhotoType
frm.PhotoType.Matches = true
} else if photo.PhotoType != frm.PhotoType.Value {
frm.PhotoType.Matches = false
frm.PhotoType.Mixed = true
frm.PhotoType.Value = ""
}
if photo.PhotoTitle != "" && frm.PhotoTitle.Value == "" {
if i == 0 {
frm.PhotoTitle.Value = photo.PhotoTitle
frm.PhotoTitle.Matches = true
} else if photo.PhotoTitle != frm.PhotoTitle.Value {
frm.PhotoTitle.Matches = false
frm.PhotoTitle.Mixed = true
frm.PhotoTitle.Value = ""
}
if photo.PhotoCaption != "" && frm.PhotoCaption.Value == "" {
if i == 0 {
frm.PhotoCaption.Value = photo.PhotoCaption
frm.PhotoTitle.Matches = true
} else if photo.PhotoCaption != frm.PhotoCaption.Value {
frm.PhotoCaption.Matches = false
frm.PhotoCaption.Mixed = true
frm.PhotoCaption.Value = ""
}
if !photo.TakenAt.IsZero() && frm.TakenAt.Value.IsZero() {
if i == 0 {
frm.TakenAt.Value = photo.TakenAt
frm.TakenAt.Matches = true
} else if photo.TakenAt != frm.TakenAt.Value {
frm.TakenAt.Matches = false
frm.TakenAt.Mixed = true
}
if !photo.TakenAtLocal.IsZero() && frm.TakenAtLocal.Value.IsZero() {
if i == 0 {
frm.TakenAtLocal.Value = photo.TakenAtLocal
frm.TakenAtLocal.Matches = true
} else if photo.TakenAtLocal != frm.TakenAtLocal.Value {
frm.TakenAtLocal.Matches = false
frm.TakenAtLocal.Mixed = true
}
if photo.PhotoDay > 0 && frm.PhotoDay.Value == 0 {
if i == 0 {
frm.PhotoDay.Value = photo.PhotoDay
frm.PhotoDay.Matches = true
} else if photo.PhotoDay != frm.PhotoDay.Value {
frm.PhotoDay.Matches = false
frm.PhotoDay.Mixed = true
frm.PhotoDay.Value = 0
}
if photo.PhotoMonth > 0 && frm.PhotoMonth.Value == 0 {
if i == 0 {
frm.PhotoMonth.Value = photo.PhotoMonth
frm.PhotoMonth.Matches = true
} else if photo.PhotoMonth != frm.PhotoMonth.Value {
frm.PhotoMonth.Matches = false
frm.PhotoMonth.Mixed = true
frm.PhotoMonth.Value = 0
}
if photo.PhotoYear > 0 && frm.PhotoYear.Value == 0 {
if i == 0 {
frm.PhotoYear.Value = photo.PhotoYear
frm.PhotoYear.Matches = true
} else if photo.PhotoYear != frm.PhotoYear.Value {
frm.PhotoYear.Matches = false
frm.PhotoYear.Mixed = true
frm.PhotoYear.Value = 0
}
if photo.TimeZone != "" && frm.TimeZone.Value == "" {
if i == 0 {
frm.TimeZone.Value = photo.TimeZone
frm.TimeZone.Matches = true
} else if photo.TimeZone != frm.TimeZone.Value {
frm.TimeZone.Matches = false
frm.TimeZone.Mixed = true
frm.TimeZone.Value = "Local"
}
if photo.PhotoCountry != "" && frm.PhotoCountry.Value == "" {
if i == 0 {
frm.PhotoCountry.Value = photo.PhotoCountry
frm.PhotoCountry.Matches = true
} else if photo.PhotoCountry != frm.PhotoCountry.Value {
frm.PhotoCountry.Matches = false
frm.PhotoCountry.Mixed = true
frm.PhotoCountry.Value = "zz"
}
if photo.PhotoAltitude != 0 && frm.PhotoAltitude.Value == 0 {
if i == 0 {
frm.PhotoAltitude.Value = photo.PhotoAltitude
frm.PhotoAltitude.Matches = true
} else if photo.PhotoAltitude != frm.PhotoAltitude.Value {
frm.PhotoAltitude.Matches = false
frm.PhotoAltitude.Mixed = true
frm.PhotoAltitude.Value = 0
}
if photo.PhotoLat != 0.0 && frm.PhotoLat.Value == 0.0 {
if i == 0 {
frm.PhotoLat.Value = photo.PhotoLat
frm.PhotoLat.Matches = true
} else if photo.PhotoLat != frm.PhotoLat.Value {
frm.PhotoLat.Matches = false
frm.PhotoLat.Mixed = true
frm.PhotoLat.Value = 0.0
}
if photo.PhotoLng != 0.0 && frm.PhotoLng.Value == 0.0 {
if i == 0 {
frm.PhotoLng.Value = photo.PhotoLng
frm.PhotoLng.Matches = true
} else if photo.PhotoLng != frm.PhotoLng.Value {
frm.PhotoLng.Matches = false
frm.PhotoLng.Mixed = false
frm.PhotoLng.Value = 0.0
}
if photo.PhotoIso != 0 && frm.PhotoIso.Value == 0 {
if i == 0 {
frm.PhotoIso.Value = photo.PhotoIso
frm.PhotoIso.Matches = true
} else if photo.PhotoIso != frm.PhotoIso.Value {
frm.PhotoIso.Matches = false
frm.PhotoIso.Mixed = true
frm.PhotoIso.Value = 0
}
if photo.PhotoFocalLength != 0 && frm.PhotoFocalLength.Value == 0 {
if i == 0 {
frm.PhotoFocalLength.Value = photo.PhotoFocalLength
frm.PhotoFocalLength.Matches = true
} else if photo.PhotoFocalLength != frm.PhotoFocalLength.Value {
frm.PhotoFocalLength.Matches = false
frm.PhotoFocalLength.Mixed = true
frm.PhotoFocalLength.Value = 0
}
if photo.PhotoFNumber != 0.0 && frm.PhotoFNumber.Value == 0.0 {
if i == 0 {
frm.PhotoFNumber.Value = photo.PhotoFNumber
frm.PhotoFNumber.Matches = true
} else if photo.PhotoFNumber != frm.PhotoFNumber.Value {
frm.PhotoFNumber.Matches = false
frm.PhotoFNumber.Mixed = true
frm.PhotoFNumber.Value = 0
}
if photo.PhotoExposure != "" && frm.PhotoExposure.Value == "" {
if i == 0 {
frm.PhotoExposure.Value = photo.PhotoExposure
frm.PhotoExposure.Matches = true
} else if photo.PhotoExposure != frm.PhotoExposure.Value {
frm.PhotoExposure.Matches = false
frm.PhotoExposure.Mixed = true
frm.PhotoExposure.Value = ""
}
if photo.PhotoFavorite && !frm.PhotoFavorite.Value {
if i == 0 {
frm.PhotoFavorite.Value = photo.PhotoFavorite
frm.PhotoFavorite.Matches = true
} else if photo.PhotoFavorite != frm.PhotoFavorite.Value {
frm.PhotoFavorite.Matches = false
frm.PhotoFavorite.Mixed = true
frm.PhotoFavorite.Value = false
}
if photo.PhotoPrivate && !frm.PhotoPrivate.Value {
if i == 0 {
frm.PhotoPrivate.Value = photo.PhotoPrivate
frm.PhotoPrivate.Matches = true
} else if photo.PhotoPrivate != frm.PhotoPrivate.Value {
frm.PhotoPrivate.Matches = false
frm.PhotoPrivate.Mixed = true
frm.PhotoPrivate.Value = false
}
if photo.PhotoScan && !frm.PhotoScan.Value {
if i == 0 {
frm.PhotoScan.Value = photo.PhotoScan
frm.PhotoScan.Matches = true
} else if photo.PhotoScan != frm.PhotoScan.Value {
frm.PhotoScan.Matches = false
frm.PhotoScan.Mixed = true
frm.PhotoScan.Value = false
}
if photo.PhotoPanorama && !frm.PhotoPanorama.Value {
if i == 0 {
frm.PhotoPanorama.Value = photo.PhotoPanorama
frm.PhotoPanorama.Matches = true
} else if photo.PhotoPanorama != frm.PhotoPanorama.Value {
frm.PhotoPanorama.Matches = false
frm.PhotoPanorama.Mixed = true
frm.PhotoPanorama.Value = false
}
if photo.CameraID != 0 && frm.CameraID.Value == 0 {
if i == 0 {
frm.CameraID.Value = photo.CameraID
frm.CameraID.Matches = true
} else if photo.CameraID != frm.CameraID.Value {
frm.CameraID.Matches = false
frm.CameraID.Mixed = true
frm.CameraID.Value = 1
}
if photo.LensID != 0 && frm.LensID.Value == 0 {
if i == 0 {
frm.LensID.Value = photo.LensID
frm.LensID.Matches = true
} else if photo.LensID != frm.LensID.Value {
frm.LensID.Matches = false
frm.LensID.Mixed = true
frm.LensID.Value = 1
}
if photo.Details != nil {
if photo.Details.Subject != "" && frm.DetailsSubject.Value == "" {
frm.DetailsSubject.Value = photo.Details.Subject
frm.DetailsSubject.Matches = true
} else if photo.Details.Subject != frm.DetailsSubject.Value {
frm.DetailsSubject.Matches = false
}
if photo.Details.Artist != "" && frm.DetailsArtist.Value == "" {
frm.DetailsArtist.Value = photo.Details.Artist
frm.DetailsArtist.Matches = true
} else if photo.Details.Artist != frm.DetailsArtist.Value {
frm.DetailsArtist.Matches = false
}
if photo.Details.Copyright != "" && frm.DetailsCopyright.Value == "" {
frm.DetailsCopyright.Value = photo.Details.Copyright
frm.DetailsCopyright.Matches = true
} else if photo.Details.Copyright != frm.DetailsCopyright.Value {
frm.DetailsCopyright.Matches = false
}
if photo.Details.License != "" && frm.DetailsLicense.Value == "" {
frm.DetailsLicense.Value = photo.Details.License
frm.DetailsLicense.Matches = true
} else if photo.Details.License != frm.DetailsLicense.Value {
frm.DetailsLicense.Matches = false
}
if i == 0 {
frm.DetailsKeywords.Value = photo.DetailsKeywords
} else if photo.DetailsKeywords != frm.DetailsKeywords.Value {
frm.DetailsKeywords.Mixed = true
frm.DetailsKeywords.Value = ""
}
if i == 0 {
frm.DetailsSubject.Value = photo.DetailsSubject
} else if photo.DetailsSubject != frm.DetailsSubject.Value {
frm.DetailsSubject.Mixed = true
frm.DetailsSubject.Value = ""
}
if i == 0 {
frm.DetailsArtist.Value = photo.DetailsArtist
} else if photo.DetailsArtist != frm.DetailsArtist.Value {
frm.DetailsArtist.Mixed = true
frm.DetailsArtist.Value = ""
}
if i == 0 {
frm.DetailsCopyright.Value = photo.DetailsCopyright
} else if photo.DetailsCopyright != frm.DetailsCopyright.Value {
frm.DetailsCopyright.Mixed = true
frm.DetailsCopyright.Value = ""
}
if i == 0 {
frm.DetailsLicense.Value = photo.DetailsLicense
} else if photo.DetailsLicense != frm.DetailsLicense.Value {
frm.DetailsLicense.Mixed = true
frm.DetailsLicense.Value = ""
}
}
// Use defaults for the following values if they are empty:
if frm.PhotoCountry.Value == "" {
frm.PhotoCountry.Value = "zz"
}
if frm.CameraID.Value < 1 {
frm.CameraID.Value = 1
}
if frm.LensID.Value < 1 {
frm.LensID.Value = 1
}
return frm

View File

@@ -0,0 +1,61 @@
package batch
import (
"encoding/json"
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/internal/entity/search"
"github.com/photoprism/photoprism/pkg/fs"
)
func TestNewPhotosForm(t *testing.T) {
t.Run("Success", func(t *testing.T) {
var photos search.PhotoResults
dataFile := fs.Abs("./testdata/photos.json")
data, dataErr := os.ReadFile(dataFile)
if dataErr != nil {
t.Fatal(dataErr)
}
jsonErr := json.Unmarshal(data, &photos)
if jsonErr != nil {
t.Fatal(jsonErr)
}
frm := NewPhotosForm(photos)
// Photo metadata.
assert.Equal(t, "", frm.PhotoType.Value)
assert.Equal(t, true, frm.PhotoType.Mixed)
assert.Equal(t, "", frm.PhotoTitle.Value)
assert.Equal(t, true, frm.PhotoTitle.Mixed)
assert.Equal(t, "", frm.PhotoCaption.Value)
assert.Equal(t, true, frm.PhotoCaption.Mixed)
assert.Equal(t, false, frm.PhotoFavorite.Value)
assert.Equal(t, true, frm.PhotoFavorite.Mixed)
assert.Equal(t, false, frm.PhotoPrivate.Value)
assert.Equal(t, false, frm.PhotoPrivate.Mixed)
assert.Equal(t, uint(1000003), frm.CameraID.Value)
assert.Equal(t, false, frm.CameraID.Mixed)
assert.Equal(t, uint(1000000), frm.LensID.Value)
assert.Equal(t, false, frm.LensID.Mixed)
// Additional details.
assert.Equal(t, "", frm.DetailsKeywords.Value)
assert.Equal(t, true, frm.DetailsKeywords.Mixed)
assert.Equal(t, "", frm.DetailsSubject.Value)
assert.Equal(t, true, frm.DetailsSubject.Mixed)
assert.Equal(t, "", frm.DetailsArtist.Value)
assert.Equal(t, true, frm.DetailsArtist.Mixed)
assert.Equal(t, "", frm.DetailsCopyright.Value)
assert.Equal(t, true, frm.DetailsCopyright.Mixed)
assert.Equal(t, "", frm.DetailsLicense.Value)
assert.Equal(t, true, frm.DetailsLicense.Mixed)
})
}

View File

@@ -0,0 +1,31 @@
package batch
import "strings"
// PhotosRequest represents items selected in the user interface.
type PhotosRequest struct {
Return bool `json:"return,omitempty"`
Filter string `json:"filter,omitempty"`
Photos []string `json:"photos"`
Values *PhotosForm `json:"values,omitempty"`
}
// Empty checks if any specific items were selected.
func (f PhotosRequest) Empty() bool {
switch {
case len(f.Photos) > 0:
return false
}
return true
}
// Get returns a string slice with the selected item UIDs.
func (f PhotosRequest) Get() []string {
return f.Photos
}
// String returns a string containing all selected item UIDs.
func (f PhotosRequest) String() string {
return strings.Join(f.Get(), ", ")
}

442
internal/form/batch/testdata/photos.json vendored Normal file
View File

@@ -0,0 +1,442 @@
[
{
"ID": "1000003-1000011",
"UID": "ps6sg6be2lvl0yh0",
"Type": "video",
"TypeSrc": "",
"TakenAt": "1990-04-18T01:00:00Z",
"TakenAtLocal": "1990-04-18T01:00:00Z",
"TakenSrc": "meta",
"TimeZone": "Local",
"Path": "",
"Name": "",
"Title": "",
"Caption": "",
"Year": 1990,
"Month": 4,
"Day": 18,
"Country": "za",
"Stack": 0,
"Favorite": false,
"Private": false,
"Iso": 400,
"FocalLength": 84,
"FNumber": 4.5,
"Exposure": "",
"Faces": 1,
"Quality": 4,
"Resolution": 45,
"Duration": 7200000000000,
"Color": 12,
"Scan": false,
"Panorama": false,
"CameraID": 1000003,
"CameraMake": "Canon",
"CameraModel": "EOS 6D",
"LensID": 1000000,
"LensMake": "Apple",
"LensModel": "F380",
"Altitude": -100,
"Lat": 48.519234,
"Lng": 9.057997,
"CellID": "",
"PlaceID": "",
"PlaceSrc": "",
"PlaceLabel": "",
"PlaceCity": "",
"PlaceState": "",
"PlaceCountry": "",
"InstanceID": "",
"FileUID": "fs6sg6bw15bnlqdw",
"FileRoot": "/",
"FileName": "1990/04/bridge2.jpg",
"OriginalName": "",
"Hash": "pcad9168fa6acc5c5c2965adf6ec465ca42fd818",
"Width": 1200,
"Height": 1600,
"Portrait": true,
"Merged": true,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"EditedAt": "0001-01-01T00:00:00Z",
"CheckedAt": "0001-01-01T00:00:00Z",
"DetailsKeywords": "bridge, nature",
"DetailsSubject": "Bridge",
"DetailsArtist": "Jens Mander",
"DetailsCopyright": "Copyright 2020",
"DetailsLicense": "n/a",
"Files": [
{
"UID": "fs6sg6bw15bnlqdw",
"PhotoUID": "ps6sg6be2lvl0yh0",
"Name": "1990/04/bridge2.jpg",
"Root": "/",
"Hash": "pcad9168fa6acc5c5c2965adf6ec465ca42fd818",
"Size": 921858,
"Primary": true,
"Codec": "jpeg",
"FileType": "jpg",
"MediaType": "image",
"Mime": "image/jpg",
"Portrait": true,
"Width": 1200,
"Height": 1600,
"Orientation": 6,
"AspectRatio": 0.75,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"Markers": [
{
"UID": "ms6sg6b1wowuy777",
"FileUID": "fs6sg6bw15bnlqdw",
"Type": "face",
"Src": "image",
"Name": "",
"Review": false,
"Invalid": false,
"FaceID": "TOSCDXCS4VI3PGIUTCNIQCNI6HSFXQVZ",
"FaceDist": 0.6,
"SubjUID": "",
"SubjSrc": "",
"X": 0.404687,
"Y": 0.249707,
"W": 0.214062,
"H": 0.321219,
"Size": 200,
"Score": 74,
"Thumb": "pcad9168fa6acc5c5c2965ddf6ec465ca42fd818",
"CreatedAt": "2025-05-04T11:49:42.529705909Z"
}
]
},
{
"UID": "fs6sg6bwhhbnlqdn",
"PhotoUID": "ps6sg6be2lvl0yh0",
"Name": "London/bridge3.jpg",
"Root": "/",
"Hash": "pcad9168fa6acc5c5ba965adf6ec465ca42fd818",
"Size": 921851,
"Primary": false,
"Codec": "jpeg",
"FileType": "jpg",
"MediaType": "image",
"Mime": "image/jpg",
"Portrait": true,
"Width": 1200,
"Height": 1600,
"Orientation": 6,
"AspectRatio": 0.75,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"Markers": [
{
"UID": "ms6sg6b1wowu1000",
"FileUID": "fs6sg6bwhhbnlqdn",
"Type": "face",
"Src": "image",
"Name": "Actress A",
"Review": false,
"Invalid": false,
"FaceID": "GMH5NISEEULNJL6RATITOA3TMZXMTMCI",
"FaceDist": 0.4507357278575355,
"SubjUID": "js6sg6b1h1njaaac",
"SubjSrc": "",
"X": 0.464844,
"Y": 0.449531,
"W": 0.434375,
"H": 0.652582,
"Size": 556,
"Score": 155,
"Thumb": "pcad9168fa6acc5c5c2965ddf6ec465ca42fd818-046045043065",
"CreatedAt": "2025-05-04T11:49:42.442163583Z"
}
]
},
{
"UID": "fs6sg6bwhhbnlqdy",
"PhotoUID": "ps6sg6be2lvl0yh0",
"Name": "1990/04/bridge2.mp4",
"Root": "/",
"Hash": "pcad9168fa6acc5c5ba965adf6ec465ca42fd819",
"Size": 921851,
"Primary": false,
"Codec": "avc1",
"FileType": "mp4",
"MediaType": "video",
"Mime": "image/mp4",
"Portrait": true,
"Video": true,
"Duration": 17000000000,
"Width": 1200,
"Height": 1600,
"Orientation": 6,
"AspectRatio": 0.75,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"Markers": []
}
]
},
{
"ID": "1000001-1000001",
"UID": "ps6sg6be2lvl0yh8",
"Type": "raw",
"TypeSrc": "",
"TakenAt": "2006-01-01T02:00:00Z",
"TakenAtLocal": "2006-01-01T02:00:00Z",
"TakenSrc": "meta",
"TimeZone": "Europe/Berlin",
"Path": "",
"Name": "",
"Title": "",
"Caption": "photo caption non-photographic",
"Year": 2790,
"Month": 2,
"Day": 12,
"Country": "de",
"Stack": 0,
"Favorite": true,
"Private": false,
"Iso": 305,
"FocalLength": 28,
"FNumber": 3.5,
"Exposure": "",
"Quality": 3,
"Resolution": 2,
"Color": 3,
"Scan": false,
"Panorama": false,
"CameraID": 1000003,
"CameraMake": "Canon",
"CameraModel": "EOS 6D",
"LensID": 1000000,
"LensMake": "Apple",
"LensModel": "F380",
"Altitude": -10,
"Lat": 48.519234,
"Lng": 9.057997,
"CellID": "",
"PlaceID": "",
"PlaceSrc": "",
"PlaceLabel": "",
"PlaceCity": "",
"PlaceState": "",
"PlaceCountry": "",
"InstanceID": "",
"FileUID": "fs6sg6bw45bn0001",
"FileRoot": "/",
"FileName": "2790/02/Photo01.dng",
"OriginalName": "",
"Hash": "3cad9168fa6acc5c5c2965ddf6ec465ca42fd818",
"Width": 1200,
"Height": 1600,
"Portrait": true,
"Merged": false,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"EditedAt": "0001-01-01T00:00:00Z",
"CheckedAt": "0001-01-01T00:00:00Z",
"DetailsKeywords": "screenshot, info",
"DetailsSubject": "Non Photographic",
"DetailsArtist": "Hans",
"DetailsCopyright": "copy",
"DetailsLicense": "MIT",
"Files": [
{
"UID": "fs6sg6bw45bn0001",
"PhotoUID": "ps6sg6be2lvl0yh8",
"Name": "2790/02/Photo01.dng",
"Root": "/",
"Hash": "3cad9168fa6acc5c5c2965ddf6ec465ca42fd818",
"Size": 661858,
"Primary": false,
"Codec": "jpeg",
"FileType": "raw",
"MediaType": "raw",
"Mime": "image/DNG",
"Portrait": true,
"Width": 1200,
"Height": 1600,
"Orientation": 6,
"AspectRatio": 0.75,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"Markers": []
}
]
},
{
"ID": "1000000-1000000",
"UID": "ps6sg6be2lvl0yh7",
"Type": "image",
"TypeSrc": "",
"TakenAt": "2008-07-01T10:00:00Z",
"TakenAtLocal": "2008-07-01T12:00:00Z",
"TakenSrc": "meta",
"TimeZone": "Europe/Berlin",
"Path": "",
"Name": "",
"Title": "Lake / 2790",
"Caption": "photo caption lake",
"Year": 2790,
"Month": 7,
"Day": 4,
"Country": "zz",
"Stack": 0,
"Favorite": false,
"Private": false,
"Iso": 200,
"FocalLength": 50,
"FNumber": 5,
"Exposure": "1/80",
"Faces": 3,
"Quality": 3,
"Resolution": 2,
"Color": 9,
"Scan": false,
"Panorama": false,
"CameraID": 1000003,
"CameraSrc": "meta",
"CameraMake": "Canon",
"CameraModel": "EOS 6D",
"LensID": 1000000,
"LensMake": "Apple",
"LensModel": "F380",
"Lat": 0,
"Lng": 0,
"CellID": "",
"PlaceID": "",
"PlaceSrc": "",
"PlaceLabel": "",
"PlaceCity": "",
"PlaceState": "",
"PlaceCountry": "",
"InstanceID": "",
"FileUID": "fs6sg6bw45bnlqdw",
"FileRoot": "/",
"FileName": "2790/07/27900704_070228_D6D51B6C.jpg",
"OriginalName": "Vacation/exampleFileNameOriginal.jpg",
"Hash": "2cad9168fa6acc5c5c2965ddf6ec465ca42fd818",
"Width": 3648,
"Height": 2736,
"Portrait": false,
"Merged": false,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"EditedAt": "0001-01-01T00:00:00Z",
"CheckedAt": "0001-01-01T00:00:00Z",
"DetailsKeywords": "nature, frog",
"DetailsSubject": "Lake",
"DetailsArtist": "Hans",
"DetailsCopyright": "copy",
"DetailsLicense": "MIT",
"Files": [
{
"UID": "fs6sg6bw45bnlqdw",
"PhotoUID": "ps6sg6be2lvl0yh7",
"Name": "2790/07/27900704_070228_D6D51B6C.jpg",
"Root": "/",
"Hash": "2cad9168fa6acc5c5c2965ddf6ec465ca42fd818",
"Size": 4278906,
"Primary": true,
"OriginalName": "Vacation/exampleFileNameOriginal.jpg",
"Codec": "jpeg",
"FileType": "jpg",
"MediaType": "image",
"Mime": "image/jpg",
"Width": 3648,
"Height": 2736,
"Projection": "equirectangular",
"AspectRatio": 1.33333,
"CreatedAt": "0001-01-01T00:00:00Z",
"UpdatedAt": "0001-01-01T00:00:00Z",
"Markers": [
{
"UID": "ms6sg6b14ahkyd24",
"FileUID": "fs6sg6bw45bnlqdw",
"Type": "face",
"Src": "image",
"Name": "",
"Review": false,
"Invalid": false,
"FaceID": "VF7ANLDET2BKZNT4VQWJMMC6HBEFDOG6",
"FaceDist": 0.3139983399779298,
"SubjUID": "",
"SubjSrc": "",
"X": 0.1,
"Y": 0.229688,
"W": 0.246334,
"H": 0.29707,
"Size": 209,
"Score": 55,
"Thumb": "acad9168fa6acc5c5c2965ddf6ec465ca42fd818",
"CreatedAt": "2025-05-04T11:49:42.485807442Z"
},
{
"UID": "ms6sg6b1wowu1005",
"FileUID": "fs6sg6bw45bnlqdw",
"Type": "face",
"Src": "image",
"Name": "Actor A",
"Review": false,
"Invalid": false,
"FaceID": "PI6A2XGOTUXEFI7CBF4KCI5I2I3JEJHS",
"FaceDist": 0.3139983399779298,
"SubjUID": "js6sg6b1h1njaaad",
"SubjSrc": "",
"X": 0.5,
"Y": 0.429688,
"W": 0.746334,
"H": 0.49707,
"Size": 509,
"Score": 100,
"Thumb": "pcad9168fa6acc5c5c2965ddf6ec465ca42fd818",
"CreatedAt": "2025-05-04T11:49:42.626934696Z"
},
{
"UID": "ms6sg6b1wowuy888",
"FileUID": "fs6sg6bw45bnlqdw",
"Type": "face",
"Src": "image",
"Name": "",
"Review": false,
"Invalid": false,
"FaceID": "TOSCDXCS4VI3PGIUTCNIQCNI6HSFXQVZ",
"FaceDist": 0.6,
"SubjUID": "",
"SubjSrc": "",
"X": 0.528125,
"Y": 0.240328,
"W": 0.3625,
"H": 0.543962,
"Size": 200,
"Score": 56,
"Thumb": "pcad9168fa6acc5c5c2965ddf6ec465ca42fd818",
"CreatedAt": "2025-05-04T11:49:42.427572061Z"
},
{
"UID": "ms6sg6b1wowu1001",
"FileUID": "fs6sg6bw45bnlqdw",
"Type": "face",
"Src": "image",
"Name": "Actress A",
"Review": false,
"Invalid": false,
"FaceID": "GMH5NISEEULNJL6RATITOA3TMZXMTMCI",
"FaceDist": 0.5099754448545762,
"SubjUID": "js6sg6b1h1njaaac",
"SubjSrc": "",
"X": 0.547656,
"Y": 0.330986,
"W": 0.402344,
"H": 0.60446,
"Size": 515,
"Score": 102,
"Thumb": "pcad9168fa6acc5c5c2965ddf6ec465ca42fd818-05403304060446",
"CreatedAt": "2025-05-04T11:49:42.457213555Z"
}
]
}
]
}
]

View File

@@ -6,49 +6,49 @@ import (
// String represents batch edit form value.
type String struct {
Value string `json:"value"`
Matches bool `json:"matches"`
Action Action `json:"action"`
Value string `json:"value"`
Mixed bool `json:"mixed"`
Action Action `json:"action"`
}
// Bool represents batch edit form value.
type Bool struct {
Value bool `json:"value"`
Matches bool `json:"matches"`
Action Action `json:"action"`
Value bool `json:"value"`
Mixed bool `json:"mixed"`
Action Action `json:"action"`
}
// Time represents batch edit form value.
type Time struct {
Value time.Time `json:"value"`
Matches bool `json:"matches"`
Action Action `json:"action"`
Value time.Time `json:"value"`
Mixed bool `json:"mixed"`
Action Action `json:"action"`
}
// Int represents batch edit form value.
type Int struct {
Value int `json:"value"`
Matches bool `json:"matches"`
Action Action `json:"action"`
Value int `json:"value"`
Mixed bool `json:"mixed"`
Action Action `json:"action"`
}
// UInt represents batch edit form value.
type UInt struct {
Value uint `json:"value"`
Matches bool `json:"matches"`
Action Action `json:"action"`
Value uint `json:"value"`
Mixed bool `json:"mixed"`
Action Action `json:"action"`
}
// Float32 represents batch edit form value.
type Float32 struct {
Value float32 `json:"value"`
Matches bool `json:"matches"`
Action Action `json:"action"`
Value float32 `json:"value"`
Mixed bool `json:"mixed"`
Action Action `json:"action"`
}
// Float64 represents batch edit form value.
type Float64 struct {
Value float64 `json:"value"`
Matches bool `json:"matches"`
Action Action `json:"action"`
Value float64 `json:"value"`
Mixed bool `json:"mixed"`
Action Action `json:"action"`
}

View File

@@ -99,6 +99,7 @@ type SearchPhotos struct {
Offset int `form:"offset" serialize:"-"` // Result FILE offset
Order string `form:"order" serialize:"-"` // Sort order
Merged bool `form:"merged" serialize:"-"` // Merge FILES in response
Details bool `form:"-" serialize:"-"` // Include additional information from details table
}
func (f *SearchPhotos) GetQuery() string {

View File

@@ -5,6 +5,7 @@ import "strings"
// Selection represents items selected in the user interface.
type Selection struct {
All bool `json:"all"`
Filter string `json:"filter"`
Files []string `json:"files"`
Photos []string `json:"photos"`
Albums []string `json:"albums"`