2021-09-18 15:32:39 +02:00
|
|
|
package search
|
2020-05-08 15:41:01 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/gosimple/slug"
|
|
|
|
"github.com/ulule/deepcopier"
|
2022-03-30 20:36:25 +02:00
|
|
|
|
|
|
|
"github.com/photoprism/photoprism/internal/entity"
|
2020-05-08 15:41:01 +02:00
|
|
|
)
|
|
|
|
|
2021-09-18 15:32:39 +02:00
|
|
|
// Photo represents a photo search result.
|
|
|
|
type Photo struct {
|
2022-03-30 20:36:25 +02:00
|
|
|
ID uint `json:"-" select:"photos.id"`
|
|
|
|
CompositeID string `json:"ID" select:"files.photo_id AS composite_id"`
|
|
|
|
UUID string `json:"DocumentID,omitempty" select:"photos.uuid"`
|
|
|
|
PhotoUID string `json:"UID" select:"photos.photo_uid"`
|
|
|
|
PhotoType string `json:"Type" select:"photos.photo_type"`
|
|
|
|
TypeSrc string `json:"TypeSrc" select:"photos.taken_src"`
|
|
|
|
TakenAt time.Time `json:"TakenAt" select:"photos.taken_at"`
|
|
|
|
TakenAtLocal time.Time `json:"TakenAtLocal" select:"photos.taken_at_local"`
|
|
|
|
TakenSrc string `json:"TakenSrc" select:"photos.taken_src"`
|
|
|
|
TimeZone string `json:"TimeZone" select:"photos.time_zone"`
|
|
|
|
PhotoPath string `json:"Path" select:"photos.photo_path"`
|
|
|
|
PhotoName string `json:"Name" select:"photos.photo_name"`
|
|
|
|
OriginalName string `json:"OriginalName" select:"photos.original_name"`
|
|
|
|
PhotoTitle string `json:"Title" select:"photos.photo_title"`
|
|
|
|
PhotoDescription string `json:"Description" select:"photos.photo_description"`
|
|
|
|
PhotoYear int `json:"Year" select:"photos.photo_year"`
|
|
|
|
PhotoMonth int `json:"Month" select:"photos.photo_month"`
|
|
|
|
PhotoDay int `json:"Day" select:"photos.photo_day"`
|
|
|
|
PhotoCountry string `json:"Country" select:"photos.photo_country"`
|
|
|
|
PhotoStack int8 `json:"Stack" select:"photos.photo_stack"`
|
|
|
|
PhotoFavorite bool `json:"Favorite" select:"photos.photo_favorite"`
|
|
|
|
PhotoPrivate bool `json:"Private" select:"photos.photo_private"`
|
|
|
|
PhotoIso int `json:"Iso" select:"photos.photo_iso"`
|
|
|
|
PhotoFocalLength int `json:"FocalLength" select:"photos.photo_focal_length"`
|
|
|
|
PhotoFNumber float32 `json:"FNumber" select:"photos.photo_f_number"`
|
|
|
|
PhotoExposure string `json:"Exposure" select:"photos.photo_exposure"`
|
|
|
|
PhotoFaces int `json:"Faces,omitempty" select:"photos.photo_faces"`
|
|
|
|
PhotoQuality int `json:"Quality" select:"photos.photo_quality"`
|
|
|
|
PhotoResolution int `json:"Resolution" select:"photos.photo_resolution"`
|
|
|
|
PhotoColor uint8 `json:"Color" select:"photos.photo_color"`
|
|
|
|
PhotoScan bool `json:"Scan" select:"photos.photo_scan"`
|
|
|
|
PhotoPanorama bool `json:"Panorama" select:"photos.photo_panorama"`
|
|
|
|
CameraID uint `json:"CameraID" select:"photos.camera_id"` // Camera
|
|
|
|
CameraSrc string `json:"CameraSrc,omitempty" select:"photos.camera_src"`
|
|
|
|
CameraSerial string `json:"CameraSerial,omitempty" select:"photos.camera_serial"`
|
|
|
|
CameraModel string `json:"CameraModel,omitempty" select:"cameras.camera_model"`
|
|
|
|
CameraMake string `json:"CameraMake,omitempty" select:"cameras.camera_make"`
|
|
|
|
LensID uint `json:"LensID" select:"photos.lens_id"` // Lens
|
|
|
|
LensModel string `json:"LensModel,omitempty" select:"lenses.lens_make"`
|
|
|
|
LensMake string `json:"LensMake,omitempty" select:"lenses.lens_model"`
|
|
|
|
PhotoAltitude int `json:"Altitude,omitempty" select:"photos.photo_altitude"`
|
|
|
|
PhotoLat float32 `json:"Lat" select:"photos.photo_lat"`
|
|
|
|
PhotoLng float32 `json:"Lng" select:"photos.photo_lng"`
|
|
|
|
CellID string `json:"CellID" select:"photos.cell_id"` // Cell
|
|
|
|
CellAccuracy int `json:"CellAccuracy,omitempty" select:"photos.cell_accuracy"`
|
|
|
|
PlaceID string `json:"PlaceID" select:"photos.place_id"`
|
|
|
|
PlaceSrc string `json:"PlaceSrc" select:"photos.place_src"`
|
|
|
|
PlaceLabel string `json:"PlaceLabel" select:"places.place_label"`
|
|
|
|
PlaceCity string `json:"PlaceCity" select:"places.place_city"`
|
|
|
|
PlaceState string `json:"PlaceState" select:"places.place_state"`
|
|
|
|
PlaceCountry string `json:"PlaceCountry" select:"places.place_country"`
|
|
|
|
InstanceID string `json:"InstanceID" select:"files.instance_id"`
|
|
|
|
FileID uint `json:"-" select:"files.id AS file_id"` // File
|
|
|
|
FileUID string `json:"FileUID" select:"files.file_uid"`
|
|
|
|
FileRoot string `json:"FileRoot" select:"files.file_root"`
|
|
|
|
FileName string `json:"FileName" select:"files.file_name"`
|
|
|
|
FileHash string `json:"Hash" select:"files.file_hash"`
|
|
|
|
FileWidth int `json:"Width" select:"files.file_width"`
|
|
|
|
FileHeight int `json:"Height" select:"files.file_height"`
|
|
|
|
FilePortrait bool `json:"Portrait" select:"files.file_portrait"`
|
|
|
|
FilePrimary bool `json:"-" select:"files.file_primary"`
|
|
|
|
FileSidecar bool `json:"-" select:"files.file_sidecar"`
|
|
|
|
FileMissing bool `json:"-" select:"files.file_missing"`
|
|
|
|
FileVideo bool `json:"-" select:"files.file_video"`
|
|
|
|
FileDuration time.Duration `json:"-" select:"files.file_duration"`
|
|
|
|
FileCodec string `json:"-" select:"files.file_codec"`
|
|
|
|
FileType string `json:"-" select:"files.file_type"`
|
|
|
|
FileMime string `json:"-" select:"files.file_mime"`
|
|
|
|
FileSize int64 `json:"-" select:"files.file_size"`
|
|
|
|
FileOrientation int `json:"-" select:"files.file_orientation"`
|
|
|
|
FileProjection string `json:"-" select:"files.file_projection"`
|
|
|
|
FileAspectRatio float32 `json:"-" select:"files.file_aspect_ratio"`
|
|
|
|
FileColors string `json:"-" select:"files.file_colors"`
|
|
|
|
FileChroma uint8 `json:"-" select:"files.file_chroma"`
|
|
|
|
FileLuminance string `json:"-" select:"files.file_luminance"`
|
|
|
|
FileDiff uint32 `json:"-" select:"files.file_diff"`
|
|
|
|
Merged bool `json:"Merged" select:"-"`
|
|
|
|
CreatedAt time.Time `json:"CreatedAt" select:"photos.created_at"`
|
|
|
|
UpdatedAt time.Time `json:"UpdatedAt" select:"photos.updated_at"`
|
|
|
|
EditedAt time.Time `json:"EditedAt,omitempty" select:"photos.edited_at"`
|
|
|
|
CheckedAt time.Time `json:"CheckedAt,omitempty" select:"photos.checked_at"`
|
|
|
|
DeletedAt time.Time `json:"DeletedAt,omitempty" select:"photos.deleted_at"`
|
2020-05-23 20:58:58 +02:00
|
|
|
|
|
|
|
Files []entity.File `json:"Files"`
|
2020-05-08 15:41:01 +02:00
|
|
|
}
|
|
|
|
|
2022-03-30 20:36:25 +02:00
|
|
|
// ShareBase returns a meaningful file name for sharing.
|
|
|
|
func (photo *Photo) ShareBase(seq int) string {
|
|
|
|
var name string
|
|
|
|
|
|
|
|
if photo.PhotoTitle != "" {
|
|
|
|
name = strings.Title(slug.MakeLang(photo.PhotoTitle, "en"))
|
|
|
|
} else {
|
|
|
|
name = photo.PhotoUID
|
|
|
|
}
|
|
|
|
|
|
|
|
taken := photo.TakenAtLocal.Format("20060102-150405")
|
|
|
|
|
|
|
|
if seq > 0 {
|
|
|
|
return fmt.Sprintf("%s-%s (%d).%s", taken, name, seq, photo.FileType)
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%s-%s.%s", taken, name, photo.FileType)
|
|
|
|
}
|
|
|
|
|
2021-09-18 15:32:39 +02:00
|
|
|
type PhotoResults []Photo
|
2020-05-08 15:41:01 +02:00
|
|
|
|
2020-06-14 11:39:53 +02:00
|
|
|
// UIDs returns a slice of photo UIDs.
|
2022-03-30 20:36:25 +02:00
|
|
|
func (photos PhotoResults) UIDs() []string {
|
|
|
|
result := make([]string, len(photos))
|
2020-06-14 11:39:53 +02:00
|
|
|
|
2022-03-30 20:36:25 +02:00
|
|
|
for i, el := range photos {
|
2020-06-14 11:39:53 +02:00
|
|
|
result[i] = el.PhotoUID
|
|
|
|
}
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2022-03-29 00:21:50 +02:00
|
|
|
// Merge consecutive file results that belong to the same photo.
|
2022-03-30 20:36:25 +02:00
|
|
|
func (photos PhotoResults) Merge() (merged PhotoResults, count int, err error) {
|
|
|
|
count = len(photos)
|
|
|
|
merged = make(PhotoResults, 0, count)
|
2020-05-08 15:41:01 +02:00
|
|
|
|
|
|
|
var i int
|
2022-03-29 00:21:50 +02:00
|
|
|
var photoId uint
|
2020-05-08 15:41:01 +02:00
|
|
|
|
2022-03-30 20:36:25 +02:00
|
|
|
for _, photo := range photos {
|
2020-05-08 15:41:01 +02:00
|
|
|
file := entity.File{}
|
|
|
|
|
2022-03-29 00:21:50 +02:00
|
|
|
if err = deepcopier.Copy(&file).From(photo); err != nil {
|
2022-03-30 20:36:25 +02:00
|
|
|
return merged, count, err
|
2020-05-08 15:41:01 +02:00
|
|
|
}
|
|
|
|
|
2022-03-29 00:21:50 +02:00
|
|
|
file.ID = photo.FileID
|
2020-05-08 15:41:01 +02:00
|
|
|
|
2022-03-29 00:21:50 +02:00
|
|
|
if photoId == photo.ID && i > 0 {
|
2022-03-30 20:36:25 +02:00
|
|
|
merged[i-1].Files = append(merged[i-1].Files, file)
|
|
|
|
merged[i-1].Merged = true
|
2020-05-08 15:41:01 +02:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
i++
|
2022-03-29 00:21:50 +02:00
|
|
|
photoId = photo.ID
|
|
|
|
photo.CompositeID = fmt.Sprintf("%d-%d", photoId, file.ID)
|
|
|
|
photo.Files = append(photo.Files, file)
|
|
|
|
|
2022-03-30 20:36:25 +02:00
|
|
|
merged = append(merged, photo)
|
2021-01-27 21:30:10 +01:00
|
|
|
}
|
2020-05-08 15:41:01 +02:00
|
|
|
|
2022-03-30 20:36:25 +02:00
|
|
|
return merged, count, nil
|
2020-05-08 15:41:01 +02:00
|
|
|
}
|