parent
ca0d52b36e
commit
45355c7303
|
@ -55,9 +55,9 @@ var Entities = Types{
|
||||||
"photos_keywords": &PhotoKeyword{},
|
"photos_keywords": &PhotoKeyword{},
|
||||||
"passwords": &Password{},
|
"passwords": &Password{},
|
||||||
"links": &Link{},
|
"links": &Link{},
|
||||||
Marker{}.TableName(): &Marker{},
|
|
||||||
Person{}.TableName(): &Person{},
|
Person{}.TableName(): &Person{},
|
||||||
PersonFace{}.TableName(): &PersonFace{},
|
Face{}.TableName(): &Face{},
|
||||||
|
Marker{}.TableName(): &Marker{},
|
||||||
}
|
}
|
||||||
|
|
||||||
type RowCount struct {
|
type RowCount struct {
|
||||||
|
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PeopleFaces []PersonFace
|
type Faces []Face
|
||||||
|
|
||||||
// PersonFace represents the face of a Person.
|
// Face represents the face of a Person.
|
||||||
type PersonFace struct {
|
type Face struct {
|
||||||
ID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;" json:"ID" yaml:"ID"`
|
ID string `gorm:"type:VARBINARY(42);primary_key;auto_increment:false;" json:"ID" yaml:"ID"`
|
||||||
PersonUID string `gorm:"type:VARBINARY(42);index;" json:"PersonUID" yaml:"PersonUID,omitempty"`
|
PersonUID string `gorm:"type:VARBINARY(42);index;" json:"PersonUID" yaml:"PersonUID,omitempty"`
|
||||||
Embedding string `gorm:"type:LONGTEXT;" json:"Embedding" yaml:"Embedding,omitempty"`
|
Embedding string `gorm:"type:LONGTEXT;" json:"Embedding" yaml:"Embedding,omitempty"`
|
||||||
|
@ -19,16 +19,16 @@ type PersonFace struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName returns the entity database table name.
|
// TableName returns the entity database table name.
|
||||||
func (PersonFace) TableName() string {
|
func (Face) TableName() string {
|
||||||
return "people_faces_dev"
|
return "faces_dev2"
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPersonFace returns a new face.
|
// NewPersonFace returns a new face.
|
||||||
func NewPersonFace(personUID, embedding string) *PersonFace {
|
func NewPersonFace(personUID, embedding string) *Face {
|
||||||
timeStamp := Timestamp()
|
timeStamp := Timestamp()
|
||||||
s := sha1.Sum([]byte(embedding))
|
s := sha1.Sum([]byte(embedding))
|
||||||
|
|
||||||
result := &PersonFace{
|
result := &Face{
|
||||||
ID: base32.StdEncoding.EncodeToString(s[:]),
|
ID: base32.StdEncoding.EncodeToString(s[:]),
|
||||||
PersonUID: personUID,
|
PersonUID: personUID,
|
||||||
Embedding: embedding,
|
Embedding: embedding,
|
||||||
|
@ -40,12 +40,12 @@ func NewPersonFace(personUID, embedding string) *PersonFace {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalEmbedding parses the face embedding JSON string.
|
// UnmarshalEmbedding parses the face embedding JSON string.
|
||||||
func (m *PersonFace) UnmarshalEmbedding() (result Embedding) {
|
func (m *Face) UnmarshalEmbedding() (result Embedding) {
|
||||||
return UnmarshalEmbedding(m.Embedding)
|
return UnmarshalEmbedding(m.Embedding)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save updates the existing or inserts a new face.
|
// Save updates the existing or inserts a new face.
|
||||||
func (m *PersonFace) Save() error {
|
func (m *Face) Save() error {
|
||||||
peopleMutex.Lock()
|
peopleMutex.Lock()
|
||||||
defer peopleMutex.Unlock()
|
defer peopleMutex.Unlock()
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ func (m *PersonFace) Save() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create inserts the face to the database.
|
// Create inserts the face to the database.
|
||||||
func (m *PersonFace) Create() error {
|
func (m *Face) Create() error {
|
||||||
peopleMutex.Lock()
|
peopleMutex.Lock()
|
||||||
defer peopleMutex.Unlock()
|
defer peopleMutex.Unlock()
|
||||||
|
|
||||||
|
@ -61,17 +61,17 @@ func (m *PersonFace) Create() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes the face from the database.
|
// Delete removes the face from the database.
|
||||||
func (m *PersonFace) Delete() error {
|
func (m *Face) Delete() error {
|
||||||
return Db().Delete(m).Error
|
return Db().Delete(m).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deleted returns true if the face is deleted.
|
// Deleted returns true if the face is deleted.
|
||||||
func (m *PersonFace) Deleted() bool {
|
func (m *Face) Deleted() bool {
|
||||||
return m.DeletedAt != nil
|
return m.DeletedAt != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore restores the face in the database.
|
// Restore restores the face in the database.
|
||||||
func (m *PersonFace) Restore() error {
|
func (m *Face) Restore() error {
|
||||||
if m.Deleted() {
|
if m.Deleted() {
|
||||||
return UnscopedDb().Model(m).Update("DeletedAt", nil).Error
|
return UnscopedDb().Model(m).Update("DeletedAt", nil).Error
|
||||||
}
|
}
|
||||||
|
@ -80,11 +80,11 @@ func (m *PersonFace) Restore() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update a face property in the database.
|
// Update a face property in the database.
|
||||||
func (m *PersonFace) Update(attr string, value interface{}) error {
|
func (m *Face) Update(attr string, value interface{}) error {
|
||||||
return UnscopedDb().Model(m).Update(attr, value).Error
|
return UnscopedDb().Model(m).Update(attr, value).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates face properties in the database.
|
// Updates face properties in the database.
|
||||||
func (m *PersonFace) Updates(values interface{}) error {
|
func (m *Face) Updates(values interface{}) error {
|
||||||
return UnscopedDb().Model(m).Updates(values).Error
|
return UnscopedDb().Model(m).Updates(values).Error
|
||||||
}
|
}
|
12
internal/entity/face_test.go
Normal file
12
internal/entity/face_test.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package entity
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFace_TableName(t *testing.T) {
|
||||||
|
m := &Face{}
|
||||||
|
assert.Contains(t, m.TableName(), "faces")
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ var UnknownMarker = NewMarker(0, "", SrcAuto, MarkerUnknown, 0, 0, 0, 0)
|
||||||
|
|
||||||
// TableName returns the entity database table name.
|
// TableName returns the entity database table name.
|
||||||
func (Marker) TableName() string {
|
func (Marker) TableName() string {
|
||||||
return "markers_dev"
|
return "markers_dev2"
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMarker creates a new entity.
|
// NewMarker creates a new entity.
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarker_TableName(t *testing.T) {
|
func TestMarker_TableName(t *testing.T) {
|
||||||
fileSync := &Marker{}
|
m := &Marker{}
|
||||||
assert.Equal(t, "markers_dev", fileSync.TableName())
|
assert.Contains(t, m.TableName(), "markers")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewMarker(t *testing.T) {
|
func TestNewMarker(t *testing.T) {
|
||||||
|
|
|
@ -15,31 +15,6 @@ var peopleMutex = sync.Mutex{}
|
||||||
|
|
||||||
type People []Person
|
type People []Person
|
||||||
|
|
||||||
// Relationships.
|
|
||||||
/*
|
|
||||||
const (
|
|
||||||
RelUnknown = ""
|
|
||||||
RelBaby = "baby"
|
|
||||||
RelWife = "wife"
|
|
||||||
RelHusband = "husband"
|
|
||||||
RelDad = "dad"
|
|
||||||
RelMom = "mom"
|
|
||||||
RelWorkmate = "workmate"
|
|
||||||
RelBestFriend = "best-friend"
|
|
||||||
RelFriend = "friend"
|
|
||||||
RelClassmate = "classmate"
|
|
||||||
RelBoyfriend = "boyfriend"
|
|
||||||
RelGirlfriend = "girlfriend"
|
|
||||||
RelFamily = "family"
|
|
||||||
RelGrandfather = "grandfather"
|
|
||||||
RelGrandmother = "grandmother"
|
|
||||||
RelBrother = "brother"
|
|
||||||
RelSister = "sister"
|
|
||||||
RelRelative = "relative"
|
|
||||||
RelOther = "other"
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Person represents a person on one or more photos.
|
// Person represents a person on one or more photos.
|
||||||
type Person struct {
|
type Person struct {
|
||||||
ID uint `gorm:"primary_key" json:"ID" yaml:"-"`
|
ID uint `gorm:"primary_key" json:"ID" yaml:"-"`
|
||||||
|
@ -80,7 +55,7 @@ func CreateUnknownPerson() {
|
||||||
|
|
||||||
// TableName returns the entity database table name.
|
// TableName returns the entity database table name.
|
||||||
func (Person) TableName() string {
|
func (Person) TableName() string {
|
||||||
return "people_dev"
|
return "people_dev2"
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
||||||
|
|
|
@ -7,6 +7,11 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestPerson_TableName(t *testing.T) {
|
||||||
|
m := &Person{}
|
||||||
|
assert.Contains(t, m.TableName(), "people")
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewPerson(t *testing.T) {
|
func TestNewPerson(t *testing.T) {
|
||||||
t.Run("Jens_Mander", func(t *testing.T) {
|
t.Run("Jens_Mander", func(t *testing.T) {
|
||||||
m := NewPerson("Jens Mander", SrcAuto, 0)
|
m := NewPerson("Jens Mander", SrcAuto, 0)
|
||||||
|
|
|
@ -82,7 +82,7 @@ func (w *Faces) Analyze() (err error) {
|
||||||
log.Infof("faces: max Ø %f < median %f < %f", maxMin, maxMedian, maxMax)
|
log.Infof("faces: max Ø %f < median %f < %f", maxMin, maxMedian, maxMax)
|
||||||
}
|
}
|
||||||
|
|
||||||
if known, err := query.PeopleFaces(); err != nil {
|
if known, err := query.Faces(); err != nil {
|
||||||
log.Errorf("faces: %s", err)
|
log.Errorf("faces: %s", err)
|
||||||
} else if len(known) == 0 {
|
} else if len(known) == 0 {
|
||||||
log.Infof("faces: no faces found")
|
log.Infof("faces: no faces found")
|
||||||
|
@ -233,7 +233,7 @@ func (w *Faces) Start() (err error) {
|
||||||
log.Errorf("faces: %s", err)
|
log.Errorf("faces: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
peopleFaces, err := query.PeopleFaces()
|
peopleFaces, err := query.Faces()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -298,7 +298,7 @@ func (w *Faces) Start() (err error) {
|
||||||
log.Errorf("faces: failed adding %s", txt.Quote(marker.MarkerLabel))
|
log.Errorf("faces: failed adding %s", txt.Quote(marker.MarkerLabel))
|
||||||
} else if f, ok := faceMap[faceId]; ok {
|
} else if f, ok := faceMap[faceId]; ok {
|
||||||
faceMap[faceId] = Face{Embedding: f.Embedding, PersonUID: person.PersonUID}
|
faceMap[faceId] = Face{Embedding: f.Embedding, PersonUID: person.PersonUID}
|
||||||
entity.Db().Model(&entity.PersonFace{}).Where("id = ?", faceId).Update("PersonUID", person.PersonUID)
|
entity.Db().Model(&entity.Face{}).Where("id = ?", faceId).Update("PersonUID", person.PersonUID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Existing person?
|
// Existing person?
|
||||||
|
|
|
@ -18,8 +18,8 @@ func People(limit, offset int, embeddings bool) (result entity.People, err error
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PeopleFaces finds a list of faces.
|
// Faces finds a list of faces.
|
||||||
func PeopleFaces() (result entity.PeopleFaces, err error) {
|
func Faces() (result entity.Faces, err error) {
|
||||||
stmt := Db().
|
stmt := Db().
|
||||||
Order("id")
|
Order("id")
|
||||||
|
|
||||||
|
@ -31,6 +31,6 @@ func PeopleFaces() (result entity.PeopleFaces, err error) {
|
||||||
// PurgeUnknownFaces removes unknown faces from the index.
|
// PurgeUnknownFaces removes unknown faces from the index.
|
||||||
func PurgeUnknownFaces() error {
|
func PurgeUnknownFaces() error {
|
||||||
return UnscopedDb().Delete(
|
return UnscopedDb().Delete(
|
||||||
entity.PersonFace{},
|
entity.Face{},
|
||||||
"person_uid = '' AND updated_at < ?", entity.Yesterday()).Error
|
"person_uid = '' AND updated_at < ?", entity.Yesterday()).Error
|
||||||
}
|
}
|
||||||
|
|
36
internal/query/people_test.go
Normal file
36
internal/query/people_test.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPeople(t *testing.T) {
|
||||||
|
results, err := People(3, 0, false)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.GreaterOrEqual(t, 1, len(results))
|
||||||
|
|
||||||
|
for _, val := range results {
|
||||||
|
assert.IsType(t, entity.Person{}, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFaces(t *testing.T) {
|
||||||
|
results, err := Faces()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.GreaterOrEqual(t, 1, len(results))
|
||||||
|
|
||||||
|
for _, val := range results {
|
||||||
|
assert.IsType(t, entity.Face{}, val)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue