mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
@@ -88,6 +88,21 @@
|
|||||||
<td>{{ model.CameraSerial }}
|
<td>{{ model.CameraSerial }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr v-if="model.Stack < 1">
|
||||||
|
<td>
|
||||||
|
<translate>Stackable</translate>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<v-switch
|
||||||
|
@change="save"
|
||||||
|
hide-details
|
||||||
|
:true-value="0"
|
||||||
|
:false-value="-1"
|
||||||
|
v-model="model.Stack"
|
||||||
|
:label="model.Stack ? $gettext('Yes') : $gettext('No')"
|
||||||
|
></v-switch>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<translate>Favorite</translate>
|
<translate>Favorite</translate>
|
||||||
@@ -114,19 +129,6 @@
|
|||||||
></v-switch>
|
></v-switch>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<translate>Unstacked</translate>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<v-switch
|
|
||||||
@change="save"
|
|
||||||
hide-details
|
|
||||||
v-model="model.Single"
|
|
||||||
:label="model.Single ? $gettext('Yes') : $gettext('No')"
|
|
||||||
></v-switch>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<translate>Scan</translate>
|
<translate>Scan</translate>
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export class Photo extends RestModel {
|
|||||||
DocumentID: "",
|
DocumentID: "",
|
||||||
Type: TypeImage,
|
Type: TypeImage,
|
||||||
TypeSrc: "",
|
TypeSrc: "",
|
||||||
|
Stack: 0,
|
||||||
Favorite: false,
|
Favorite: false,
|
||||||
Private: false,
|
Private: false,
|
||||||
Scan: false,
|
Scan: false,
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ func StartIndexing(router *gin.RouterGroup) {
|
|||||||
Rescan: f.Rescan,
|
Rescan: f.Rescan,
|
||||||
Convert: conf.Settings().Index.Convert && conf.SidecarWritable(),
|
Convert: conf.Settings().Index.Convert && conf.SidecarWritable(),
|
||||||
Path: filepath.Clean(f.Path),
|
Path: filepath.Clean(f.Path),
|
||||||
Single: false,
|
Stack: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(indOpt.Path) > 1 {
|
if len(indOpt.Path) > 1 {
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
|
|||||||
files = related.Files
|
files = related.Files
|
||||||
}
|
}
|
||||||
|
|
||||||
newPhoto := entity.NewPhoto(true)
|
newPhoto := entity.NewPhoto(false)
|
||||||
newPhoto.PhotoPath = unstackFile.RootRelPath()
|
newPhoto.PhotoPath = unstackFile.RootRelPath()
|
||||||
newPhoto.PhotoName = unstackFile.BasePrefix(false)
|
newPhoto.PhotoName = unstackFile.BasePrefix(false)
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Re-index existing photo stack.
|
// Re-index existing photo stack.
|
||||||
if res := ind.FileName(photoprism.FileName(stackPrimary.FileRoot, stackPrimary.FileName), photoprism.IndexOptionsAll()); res.Failed() {
|
if res := ind.FileName(photoprism.FileName(stackPrimary.FileRoot, stackPrimary.FileName), photoprism.IndexOptionsSingle()); res.Failed() {
|
||||||
log.Errorf("photo: %s (unstack %s)", res.Err, txt.Quote(baseName))
|
log.Errorf("photo: %s (unstack %s)", res.Err, txt.Quote(baseName))
|
||||||
AbortSaveFailed(c)
|
AbortSaveFailed(c)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func indexAction(ctx *cli.Context) error {
|
|||||||
Path: subPath,
|
Path: subPath,
|
||||||
Rescan: ctx.Bool("all"),
|
Rescan: ctx.Bool("all"),
|
||||||
Convert: conf.Settings().Index.Convert && conf.SidecarWritable(),
|
Convert: conf.Settings().Index.Convert && conf.SidecarWritable(),
|
||||||
Single: false,
|
Stack: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
indexed := ind.Start(indOpt)
|
indexed := ind.Start(indOpt)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package entity
|
package entity
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Sort orders.
|
// Sort orders:
|
||||||
SortOrderAdded = "added"
|
SortOrderAdded = "added"
|
||||||
SortOrderNewest = "newest"
|
SortOrderNewest = "newest"
|
||||||
SortOrderOldest = "oldest"
|
SortOrderOldest = "oldest"
|
||||||
@@ -10,13 +10,13 @@ const (
|
|||||||
SortOrderRelevance = "relevance"
|
SortOrderRelevance = "relevance"
|
||||||
SortOrderEdited = "edited"
|
SortOrderEdited = "edited"
|
||||||
|
|
||||||
// Unknown values.
|
// Unknown values:
|
||||||
YearUnknown = -1
|
YearUnknown = -1
|
||||||
MonthUnknown = -1
|
MonthUnknown = -1
|
||||||
DayUnknown = -1
|
DayUnknown = -1
|
||||||
TitleUnknown = "Unknown"
|
TitleUnknown = "Unknown"
|
||||||
|
|
||||||
// Content types.
|
// Content types:
|
||||||
TypeDefault = ""
|
TypeDefault = ""
|
||||||
TypeImage = "image"
|
TypeImage = "image"
|
||||||
TypeLive = "live"
|
TypeLive = "live"
|
||||||
@@ -24,7 +24,7 @@ const (
|
|||||||
TypeRaw = "raw"
|
TypeRaw = "raw"
|
||||||
TypeText = "text"
|
TypeText = "text"
|
||||||
|
|
||||||
// Root directories.
|
// Root directories:
|
||||||
RootUnknown = ""
|
RootUnknown = ""
|
||||||
RootOriginals = "/"
|
RootOriginals = "/"
|
||||||
RootExamples = "examples"
|
RootExamples = "examples"
|
||||||
@@ -32,14 +32,19 @@ const (
|
|||||||
RootImport = "import"
|
RootImport = "import"
|
||||||
RootPath = "/"
|
RootPath = "/"
|
||||||
|
|
||||||
// Panorama projections.
|
// Panorama projections:
|
||||||
ProjectionDefault = ""
|
ProjectionDefault = ""
|
||||||
ProjectionEquirectangular = "equirectangular"
|
ProjectionEquirectangular = "equirectangular"
|
||||||
ProjectionCubestrip = "cubestrip"
|
ProjectionCubestrip = "cubestrip"
|
||||||
ProjectionCylindrical = "cylindrical"
|
ProjectionCylindrical = "cylindrical"
|
||||||
|
|
||||||
// Event names.
|
// Event names:
|
||||||
Updated = "updated"
|
Updated = "updated"
|
||||||
Created = "created"
|
Created = "created"
|
||||||
Deleted = "deleted"
|
Deleted = "deleted"
|
||||||
|
|
||||||
|
// Photo stacks:
|
||||||
|
IsStacked int8 = 1
|
||||||
|
IsStackable int8 = 0
|
||||||
|
IsUnstacked int8 = -1
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ func TestDetails_NoCopyright(t *testing.T) {
|
|||||||
|
|
||||||
func TestNewDetails(t *testing.T) {
|
func TestNewDetails(t *testing.T) {
|
||||||
t.Run("add to photo", func(t *testing.T) {
|
t.Run("add to photo", func(t *testing.T) {
|
||||||
p := NewPhoto(false)
|
p := NewPhoto(true)
|
||||||
|
|
||||||
assert.Equal(t, TitleUnknown, p.PhotoTitle)
|
assert.Equal(t, TitleUnknown, p.PhotoTitle)
|
||||||
|
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ type Photo struct {
|
|||||||
PhotoPath string `gorm:"type:VARBINARY(500);index:idx_photos_path_name;" json:"Path" yaml:"-"`
|
PhotoPath string `gorm:"type:VARBINARY(500);index:idx_photos_path_name;" json:"Path" yaml:"-"`
|
||||||
PhotoName string `gorm:"type:VARBINARY(255);index:idx_photos_path_name;" json:"Name" yaml:"-"`
|
PhotoName string `gorm:"type:VARBINARY(255);index:idx_photos_path_name;" json:"Name" yaml:"-"`
|
||||||
OriginalName string `gorm:"type:VARBINARY(755);" json:"OriginalName" yaml:"OriginalName,omitempty"`
|
OriginalName string `gorm:"type:VARBINARY(755);" json:"OriginalName" yaml:"OriginalName,omitempty"`
|
||||||
|
PhotoStack int8 `json:"Stack" yaml:"Stack"`
|
||||||
PhotoFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
|
PhotoFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
|
||||||
PhotoSingle bool `json:"Single" yaml:"Single,omitempty"`
|
|
||||||
PhotoPrivate bool `json:"Private" yaml:"Private,omitempty"`
|
PhotoPrivate bool `json:"Private" yaml:"Private,omitempty"`
|
||||||
PhotoScan bool `json:"Scan" yaml:"Scan,omitempty"`
|
PhotoScan bool `json:"Scan" yaml:"Scan,omitempty"`
|
||||||
PhotoPanorama bool `json:"Panorama" yaml:"Panorama,omitempty"`
|
PhotoPanorama bool `json:"Panorama" yaml:"Panorama,omitempty"`
|
||||||
@@ -102,11 +102,10 @@ type Photo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPhoto creates a photo entity.
|
// NewPhoto creates a photo entity.
|
||||||
func NewPhoto(single bool) Photo {
|
func NewPhoto(stackable bool) Photo {
|
||||||
return Photo{
|
m := Photo{
|
||||||
PhotoTitle: TitleUnknown,
|
PhotoTitle: TitleUnknown,
|
||||||
PhotoType: TypeImage,
|
PhotoType: TypeImage,
|
||||||
PhotoSingle: single,
|
|
||||||
PhotoCountry: UnknownCountry.ID,
|
PhotoCountry: UnknownCountry.ID,
|
||||||
CameraID: UnknownCamera.ID,
|
CameraID: UnknownCamera.ID,
|
||||||
LensID: UnknownLens.ID,
|
LensID: UnknownLens.ID,
|
||||||
@@ -117,6 +116,14 @@ func NewPhoto(single bool) Photo {
|
|||||||
Cell: &UnknownLocation,
|
Cell: &UnknownLocation,
|
||||||
Place: &UnknownPlace,
|
Place: &UnknownPlace,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if stackable {
|
||||||
|
m.PhotoStack = IsStackable
|
||||||
|
} else {
|
||||||
|
m.PhotoStack = IsUnstacked
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
// SavePhotoForm saves a model in the database using form data.
|
// SavePhotoForm saves a model in the database using form data.
|
||||||
|
|||||||
@@ -18,15 +18,15 @@ func (m *Photo) ResolvePrimary() error {
|
|||||||
|
|
||||||
// Identical returns identical photos that can be merged.
|
// Identical returns identical photos that can be merged.
|
||||||
func (m *Photo) Identical(includeMeta, includeUuid bool) (identical Photos, err error) {
|
func (m *Photo) Identical(includeMeta, includeUuid bool) (identical Photos, err error) {
|
||||||
if m.PhotoSingle || m.PhotoName == "" {
|
if m.PhotoStack == IsUnstacked || m.PhotoName == "" {
|
||||||
return identical, nil
|
return identical, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case includeMeta && includeUuid && m.HasLocation() && m.HasLatLng() && m.TakenSrc == SrcMeta && rnd.IsUUID(m.UUID):
|
case includeMeta && includeUuid && m.HasLocation() && m.HasLatLng() && m.TakenSrc == SrcMeta && rnd.IsUUID(m.UUID):
|
||||||
if err := Db().
|
if err := Db().
|
||||||
Where("(taken_at = ? AND taken_src = 'meta' AND photo_single = 0 AND cell_id = ? AND camera_serial = ? AND camera_id = ?) "+
|
Where("(taken_at = ? AND taken_src = 'meta' AND photo_stack > -1 AND cell_id = ? AND camera_serial = ? AND camera_id = ?) "+
|
||||||
"OR (uuid = ? AND photo_single = 0)"+
|
"OR (uuid = ? AND photo_stack > -1)"+
|
||||||
"OR (photo_path = ? AND photo_name = ?)",
|
"OR (photo_path = ? AND photo_name = ?)",
|
||||||
m.TakenAt, m.CellID, m.CameraSerial, m.CameraID, m.UUID, m.PhotoPath, m.PhotoName).
|
m.TakenAt, m.CellID, m.CameraSerial, m.CameraID, m.UUID, m.PhotoPath, m.PhotoName).
|
||||||
Order("id ASC").Find(&identical).Error; err != nil {
|
Order("id ASC").Find(&identical).Error; err != nil {
|
||||||
@@ -34,7 +34,7 @@ func (m *Photo) Identical(includeMeta, includeUuid bool) (identical Photos, err
|
|||||||
}
|
}
|
||||||
case includeMeta && m.HasLocation() && m.HasLatLng() && m.TakenSrc == SrcMeta:
|
case includeMeta && m.HasLocation() && m.HasLatLng() && m.TakenSrc == SrcMeta:
|
||||||
if err := Db().
|
if err := Db().
|
||||||
Where("(taken_at = ? AND taken_src = 'meta' AND photo_single = 0 AND cell_id = ? AND camera_serial = ? AND camera_id = ?) "+
|
Where("(taken_at = ? AND taken_src = 'meta' AND photo_stack > -1 AND cell_id = ? AND camera_serial = ? AND camera_id = ?) "+
|
||||||
"OR (photo_path = ? AND photo_name = ?)",
|
"OR (photo_path = ? AND photo_name = ?)",
|
||||||
m.TakenAt, m.CellID, m.CameraSerial, m.CameraID, m.PhotoPath, m.PhotoName).
|
m.TakenAt, m.CellID, m.CameraSerial, m.CameraID, m.PhotoPath, m.PhotoName).
|
||||||
Order("id ASC").Find(&identical).Error; err != nil {
|
Order("id ASC").Find(&identical).Error; err != nil {
|
||||||
@@ -42,7 +42,7 @@ func (m *Photo) Identical(includeMeta, includeUuid bool) (identical Photos, err
|
|||||||
}
|
}
|
||||||
case includeUuid && rnd.IsUUID(m.UUID):
|
case includeUuid && rnd.IsUUID(m.UUID):
|
||||||
if err := Db().
|
if err := Db().
|
||||||
Where("(uuid = ? AND photo_single = 0) OR (photo_path = ? AND photo_name = ?)",
|
Where("(uuid = ? AND photo_stack > -1) OR (photo_path = ? AND photo_name = ?)",
|
||||||
m.UUID, m.PhotoPath, m.PhotoName).
|
m.UUID, m.PhotoPath, m.PhotoName).
|
||||||
Order("id ASC").Find(&identical).Error; err != nil {
|
Order("id ASC").Find(&identical).Error; err != nil {
|
||||||
return identical, err
|
return identical, err
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ type Photo struct {
|
|||||||
PhotoDescription string `json:"Description"`
|
PhotoDescription string `json:"Description"`
|
||||||
DescriptionSrc string `json:"DescriptionSrc"`
|
DescriptionSrc string `json:"DescriptionSrc"`
|
||||||
Details Details `json:"Details"`
|
Details Details `json:"Details"`
|
||||||
|
PhotoStack int8 `json:"Stack"`
|
||||||
PhotoFavorite bool `json:"Favorite"`
|
PhotoFavorite bool `json:"Favorite"`
|
||||||
PhotoPrivate bool `json:"Private"`
|
PhotoPrivate bool `json:"Private"`
|
||||||
PhotoSingle bool `json:"Single"`
|
|
||||||
PhotoScan bool `json:"Scan"`
|
PhotoScan bool `json:"Scan"`
|
||||||
PhotoPanorama bool `json:"Panorama"`
|
PhotoPanorama bool `json:"Panorama"`
|
||||||
PhotoAltitude int `json:"Altitude"`
|
PhotoAltitude int `json:"Altitude"`
|
||||||
|
|||||||
@@ -6,60 +6,61 @@ import (
|
|||||||
|
|
||||||
// PhotoSearch represents search form fields for "/api/v1/photos".
|
// PhotoSearch represents search form fields for "/api/v1/photos".
|
||||||
type PhotoSearch struct {
|
type PhotoSearch struct {
|
||||||
Query string `form:"q"`
|
Query string `form:"q"`
|
||||||
Filter string `form:"filter"`
|
Filter string `form:"filter"`
|
||||||
ID string `form:"id"`
|
ID string `form:"id"`
|
||||||
Type string `form:"type"`
|
Type string `form:"type"`
|
||||||
Path string `form:"path"`
|
Path string `form:"path"`
|
||||||
Folder string `form:"folder"` // Alias for Path
|
Folder string `form:"folder"` // Alias for Path
|
||||||
Name string `form:"name"`
|
Name string `form:"name"`
|
||||||
Filename string `form:"filename"`
|
Filename string `form:"filename"`
|
||||||
Original string `form:"original"`
|
Original string `form:"original"`
|
||||||
Title string `form:"title"`
|
Title string `form:"title"`
|
||||||
Hash string `form:"hash"`
|
Hash string `form:"hash"`
|
||||||
Primary bool `form:"primary"`
|
Primary bool `form:"primary"`
|
||||||
Single bool `form:"single"`
|
Stack bool `form:"stack"`
|
||||||
Video bool `form:"video"`
|
Unstacked bool `form:"unstacked"`
|
||||||
Photo bool `form:"photo"`
|
Stackable bool `form:"stackable"`
|
||||||
Scan bool `form:"scan"`
|
Video bool `form:"video"`
|
||||||
Panorama bool `form:"panorama"`
|
Photo bool `form:"photo"`
|
||||||
Error bool `form:"error"`
|
Scan bool `form:"scan"`
|
||||||
Hidden bool `form:"hidden"`
|
Panorama bool `form:"panorama"`
|
||||||
Archived bool `form:"archived"`
|
Error bool `form:"error"`
|
||||||
Public bool `form:"public"`
|
Hidden bool `form:"hidden"`
|
||||||
Private bool `form:"private"`
|
Archived bool `form:"archived"`
|
||||||
Favorite bool `form:"favorite"`
|
Public bool `form:"public"`
|
||||||
Unsorted bool `form:"unsorted"`
|
Private bool `form:"private"`
|
||||||
Stack bool `form:"stack"`
|
Favorite bool `form:"favorite"`
|
||||||
Lat float32 `form:"lat"`
|
Unsorted bool `form:"unsorted"`
|
||||||
Lng float32 `form:"lng"`
|
Lat float32 `form:"lat"`
|
||||||
Dist uint `form:"dist"`
|
Lng float32 `form:"lng"`
|
||||||
Fmin float32 `form:"fmin"`
|
Dist uint `form:"dist"`
|
||||||
Fmax float32 `form:"fmax"`
|
Fmin float32 `form:"fmin"`
|
||||||
Chroma uint8 `form:"chroma"`
|
Fmax float32 `form:"fmax"`
|
||||||
Diff uint32 `form:"diff"`
|
Chroma uint8 `form:"chroma"`
|
||||||
Mono bool `form:"mono"`
|
Diff uint32 `form:"diff"`
|
||||||
Portrait bool `form:"portrait"`
|
Mono bool `form:"mono"`
|
||||||
Geo bool `form:"geo"`
|
Portrait bool `form:"portrait"`
|
||||||
Album string `form:"album"`
|
Geo bool `form:"geo"`
|
||||||
Label string `form:"label"`
|
Album string `form:"album"`
|
||||||
Category string `form:"category"` // Moments
|
Label string `form:"label"`
|
||||||
Country string `form:"country"` // Moments
|
Category string `form:"category"` // Moments
|
||||||
State string `form:"state"` // Moments
|
Country string `form:"country"` // Moments
|
||||||
Year int `form:"year"` // Moments
|
State string `form:"state"` // Moments
|
||||||
Month int `form:"month"` // Moments
|
Year int `form:"year"` // Moments
|
||||||
Day int `form:"day"` // Moments
|
Month int `form:"month"` // Moments
|
||||||
Color string `form:"color"`
|
Day int `form:"day"` // Moments
|
||||||
Quality int `form:"quality"`
|
Color string `form:"color"`
|
||||||
Review bool `form:"review"`
|
Quality int `form:"quality"`
|
||||||
Camera int `form:"camera"`
|
Review bool `form:"review"`
|
||||||
Lens int `form:"lens"`
|
Camera int `form:"camera"`
|
||||||
Before time.Time `form:"before" time_format:"2006-01-02"`
|
Lens int `form:"lens"`
|
||||||
After time.Time `form:"after" time_format:"2006-01-02"`
|
Before time.Time `form:"before" time_format:"2006-01-02"`
|
||||||
Count int `form:"count" binding:"required" serialize:"-"`
|
After time.Time `form:"after" time_format:"2006-01-02"`
|
||||||
Offset int `form:"offset" serialize:"-"`
|
Count int `form:"count" binding:"required" serialize:"-"`
|
||||||
Order string `form:"order" serialize:"-"`
|
Offset int `form:"offset" serialize:"-"`
|
||||||
Merged bool `form:"merged" serialize:"-"`
|
Order string `form:"order" serialize:"-"`
|
||||||
|
Merged bool `form:"merged" serialize:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *PhotoSearch) GetQuery() string {
|
func (f *PhotoSearch) GetQuery() string {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestNewPhoto(t *testing.T) {
|
|||||||
PhotoFavorite: false,
|
PhotoFavorite: false,
|
||||||
PhotoPrivate: false,
|
PhotoPrivate: false,
|
||||||
PhotoType: "image",
|
PhotoType: "image",
|
||||||
PhotoSingle: false,
|
PhotoStack: int8(1),
|
||||||
PhotoLat: 9.9999,
|
PhotoLat: 9.9999,
|
||||||
PhotoLng: 8.8888,
|
PhotoLng: 8.8888,
|
||||||
PhotoAltitude: 2,
|
PhotoAltitude: 2,
|
||||||
@@ -50,7 +50,7 @@ func TestNewPhoto(t *testing.T) {
|
|||||||
assert.Equal(t, false, r.PhotoFavorite)
|
assert.Equal(t, false, r.PhotoFavorite)
|
||||||
assert.Equal(t, false, r.PhotoPrivate)
|
assert.Equal(t, false, r.PhotoPrivate)
|
||||||
assert.Equal(t, "image", r.PhotoType)
|
assert.Equal(t, "image", r.PhotoType)
|
||||||
assert.Equal(t, false, r.PhotoSingle)
|
assert.Equal(t, int8(1), r.PhotoStack)
|
||||||
assert.Equal(t, float32(9.9999), r.PhotoLat)
|
assert.Equal(t, float32(9.9999), r.PhotoLat)
|
||||||
assert.Equal(t, float32(8.8888), r.PhotoLng)
|
assert.Equal(t, float32(8.8888), r.PhotoLng)
|
||||||
assert.Equal(t, 2, r.PhotoAltitude)
|
assert.Equal(t, 2, r.PhotoAltitude)
|
||||||
|
|||||||
@@ -89,10 +89,10 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||||||
|
|
||||||
file, primaryFile := entity.File{}, entity.File{}
|
file, primaryFile := entity.File{}, entity.File{}
|
||||||
|
|
||||||
photo := entity.NewPhoto(o.Single)
|
photo := entity.NewPhoto(o.Stack)
|
||||||
metaData := meta.NewData()
|
metaData := meta.NewData()
|
||||||
labels := classify.Labels{}
|
labels := classify.Labels{}
|
||||||
stripSequence := Config().Settings().StackSequences() && !o.Single
|
stripSequence := Config().Settings().StackSequences() && o.Stack
|
||||||
|
|
||||||
fileRoot, fileBase, filePath, fileName := m.PathNameInfo(stripSequence)
|
fileRoot, fileBase, filePath, fileName := m.PathNameInfo(stripSequence)
|
||||||
fullBase := m.BasePrefix(false)
|
fullBase := m.BasePrefix(false)
|
||||||
@@ -173,14 +173,14 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||||||
|
|
||||||
// Look for existing photo if file wasn't indexed yet...
|
// Look for existing photo if file wasn't indexed yet...
|
||||||
if !fileExists {
|
if !fileExists {
|
||||||
if photoQuery = entity.UnscopedDb().First(&photo, "photo_path = ? AND photo_name = ?", filePath, fullBase); photoQuery.Error == nil || fileBase == fullBase || o.Single {
|
if photoQuery = entity.UnscopedDb().First(&photo, "photo_path = ? AND photo_name = ?", filePath, fullBase); photoQuery.Error == nil || fileBase == fullBase || !o.Stack {
|
||||||
// Skip next query.
|
// Skip next query.
|
||||||
} else if photoQuery = entity.UnscopedDb().First(&photo, "photo_path = ? AND photo_name = ? AND photo_single = 0", filePath, fileBase); photoQuery.Error == nil {
|
} else if photoQuery = entity.UnscopedDb().First(&photo, "photo_path = ? AND photo_name = ? AND photo_stack > -1", filePath, fileBase); photoQuery.Error == nil {
|
||||||
fileStacked = true
|
fileStacked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stack file based on matching location and time metadata?
|
// Stack file based on matching location and time metadata?
|
||||||
if !o.Single && photoQuery.Error != nil && Config().Settings().StackMeta() && m.MetaData().HasTimeAndPlace() {
|
if o.Stack && photoQuery.Error != nil && Config().Settings().StackMeta() && m.MetaData().HasTimeAndPlace() {
|
||||||
metaData = m.MetaData()
|
metaData = m.MetaData()
|
||||||
photoQuery = entity.UnscopedDb().First(&photo, "photo_lat = ? AND photo_lng = ? AND taken_at = ? AND taken_src = 'meta' AND camera_serial = ?", metaData.Lat, metaData.Lng, metaData.TakenAt, metaData.CameraSerial)
|
photoQuery = entity.UnscopedDb().First(&photo, "photo_lat = ? AND photo_lng = ? AND taken_at = ? AND taken_src = 'meta' AND camera_serial = ?", metaData.Lat, metaData.Lng, metaData.TakenAt, metaData.CameraSerial)
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stack file based on the same unique ID?
|
// Stack file based on the same unique ID?
|
||||||
if !o.Single && photoQuery.Error != nil && Config().Settings().StackUUID() && m.MetaData().HasDocumentID() {
|
if o.Stack && photoQuery.Error != nil && Config().Settings().StackUUID() && m.MetaData().HasDocumentID() {
|
||||||
photoQuery = entity.UnscopedDb().First(&photo, "uuid <> '' AND uuid = ?", m.MetaData().DocumentID)
|
photoQuery = entity.UnscopedDb().First(&photo, "uuid <> '' AND uuid = ?", m.MetaData().DocumentID)
|
||||||
|
|
||||||
if photoQuery.Error == nil {
|
if photoQuery.Error == nil {
|
||||||
@@ -229,7 +229,10 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||||||
// Try to recover photo metadata from backup if not exists.
|
// Try to recover photo metadata from backup if not exists.
|
||||||
if !photoExists {
|
if !photoExists {
|
||||||
photo.PhotoQuality = -1
|
photo.PhotoQuality = -1
|
||||||
photo.PhotoSingle = o.Single
|
|
||||||
|
if o.Stack {
|
||||||
|
photo.PhotoStack = entity.IsStackable
|
||||||
|
}
|
||||||
|
|
||||||
if yamlName := fs.FormatYaml.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), stripSequence); yamlName != "" {
|
if yamlName := fs.FormatYaml.FindFirst(m.FileName(), []string{Config().SidecarPath(), fs.HiddenPath}, Config().OriginalsPath(), stripSequence); yamlName != "" {
|
||||||
if err := photo.LoadFromYaml(yamlName); err != nil {
|
if err := photo.LoadFromYaml(yamlName); err != nil {
|
||||||
@@ -250,7 +253,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||||||
|
|
||||||
photo.PhotoPath = filePath
|
photo.PhotoPath = filePath
|
||||||
|
|
||||||
if o.Single || photo.PhotoSingle || !stripSequence {
|
if !o.Stack || !stripSequence || photo.PhotoStack == entity.IsUnstacked {
|
||||||
photo.PhotoName = fullBase
|
photo.PhotoName = fullBase
|
||||||
} else {
|
} else {
|
||||||
photo.PhotoName = fileBase
|
photo.PhotoName = fileBase
|
||||||
@@ -823,7 +826,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||||||
log.Errorf("index: %s in %s (set download id)", err, logName)
|
log.Errorf("index: %s in %s (set download id)", err, logName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Single || photo.PhotoSingle {
|
if !o.Stack || photo.PhotoStack == entity.IsUnstacked {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
} else if original, merged, err := photo.Merge(Config().Settings().StackMeta(), Config().Settings().StackUUID()); err != nil {
|
} else if original, merged, err := photo.Merge(Config().Settings().StackMeta(), Config().Settings().StackUUID()); err != nil {
|
||||||
log.Errorf("index: %s in %s (merge)", err.Error(), logName)
|
log.Errorf("index: %s in %s (merge)", err.Error(), logName)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ type IndexOptions struct {
|
|||||||
Path string
|
Path string
|
||||||
Rescan bool
|
Rescan bool
|
||||||
Convert bool
|
Convert bool
|
||||||
Single bool
|
Stack bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *IndexOptions) SkipUnchanged() bool {
|
func (o *IndexOptions) SkipUnchanged() bool {
|
||||||
@@ -17,7 +17,7 @@ func IndexOptionsAll() IndexOptions {
|
|||||||
Path: "/",
|
Path: "/",
|
||||||
Rescan: true,
|
Rescan: true,
|
||||||
Convert: true,
|
Convert: true,
|
||||||
Single: false,
|
Stack: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -29,7 +29,7 @@ func IndexOptionsSingle() IndexOptions {
|
|||||||
Path: "/",
|
Path: "/",
|
||||||
Rescan: true,
|
Rescan: true,
|
||||||
Convert: true,
|
Convert: true,
|
||||||
Single: true,
|
Stack: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ type PhotoResult struct {
|
|||||||
PhotoMonth int `json:"Month"`
|
PhotoMonth int `json:"Month"`
|
||||||
PhotoDay int `json:"Day"`
|
PhotoDay int `json:"Day"`
|
||||||
PhotoCountry string `json:"Country"`
|
PhotoCountry string `json:"Country"`
|
||||||
|
PhotoStack int8 `json:"Stack"`
|
||||||
PhotoFavorite bool `json:"Favorite"`
|
PhotoFavorite bool `json:"Favorite"`
|
||||||
PhotoSingle bool `json:"Single"`
|
|
||||||
PhotoPrivate bool `json:"Private"`
|
PhotoPrivate bool `json:"Private"`
|
||||||
PhotoIso int `json:"Iso"`
|
PhotoIso int `json:"Iso"`
|
||||||
PhotoFocalLength int `json:"FocalLength"`
|
PhotoFocalLength int `json:"FocalLength"`
|
||||||
|
|||||||
@@ -201,8 +201,10 @@ func PhotoSearch(f form.PhotoSearch) (results PhotoResults, count int, err error
|
|||||||
s = s.Where("photos.photo_panorama = 1")
|
s = s.Where("photos.photo_panorama = 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Single {
|
if f.Stackable {
|
||||||
s = s.Where("photos.photo_single = 1")
|
s = s.Where("photos.photo_stack > -1")
|
||||||
|
} else if f.Unstacked {
|
||||||
|
s = s.Where("photos.photo_stack = -1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.Country != "" {
|
if f.Country != "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user