diff --git a/internal/entity/marker_fixtures.go b/internal/entity/marker_fixtures.go index ec57e8051..112791ca9 100644 --- a/internal/entity/marker_fixtures.go +++ b/internal/entity/marker_fixtures.go @@ -71,6 +71,7 @@ var MarkerFixtures = MarkerMap{ "1000003-5": Marker{ ID: 5, FileID: 1000003, + FaceID: FaceFixtures.Get("unknown").ID, RefUID: "", MarkerSrc: SrcImage, MarkerType: MarkerFace, diff --git a/internal/photoprism/faces.go b/internal/photoprism/faces.go index 5b515f4ee..c827a0161 100644 --- a/internal/photoprism/faces.go +++ b/internal/photoprism/faces.go @@ -173,12 +173,18 @@ func (w *Faces) Start() (err error) { // Skip clustering if index contains no new face markers. if n := query.CountNewFaceMarkers(); n < 1 { - log.Debugf("faces: no new markers, matching known faces") + log.Debugf("faces: no new markers, matching people and faces") - if m, err := query.MatchKnownFaces(); err != nil { + if affected, err := query.MatchMarkersWithPeople(); err != nil { + log.Errorf("faces: %s (create people from markers)", err) + } else if affected > 0 { + log.Infof("faces: matched %d markers with people", affected) + } + + if matched, err := query.MatchKnownFaces(); err != nil { return err - } else if m > 0 { - log.Infof("faces: matched %d markers", m) + } else if matched > 0 { + log.Infof("faces: matched %d markers to known faces", matched) } return nil @@ -313,7 +319,7 @@ func (w *Faces) Start() (err error) { log.Errorf("faces: failed adding %s", txt.Quote(marker.MarkerLabel)) } else if f, ok := faceMap[faceId]; ok { faceMap[faceId] = faceMatch{Embedding: f.Embedding, PersonUID: person.PersonUID} - entity.Db().Model(&entity.Face{}).Where("id = ?", faceId).Update("PersonUID", person.PersonUID) + entity.Db().Model(&entity.Face{}).Where("id = ? AND person_uid = ''", faceId).Update("PersonUID", person.PersonUID) } // Existing person? diff --git a/internal/query/markers.go b/internal/query/markers.go index c328b0b7b..a62bb0124 100644 --- a/internal/query/markers.go +++ b/internal/query/markers.go @@ -2,6 +2,7 @@ package query import ( "github.com/photoprism/photoprism/internal/entity" + "github.com/photoprism/photoprism/pkg/txt" ) // MarkerByID returns a Marker based on the ID. @@ -16,22 +17,23 @@ func MarkerByID(id uint) (marker entity.Marker, err error) { // Markers finds a list of file markers filtered by type, embeddings, and sorted by id. func Markers(limit, offset int, markerType string, embeddings, unmatched bool) (result entity.Markers, err error) { - stmt := Db() + db := Db() if markerType != "" { - stmt = stmt.Where("marker_type = ?", markerType) + db = db.Where("marker_type = ?", markerType) } if embeddings { - stmt = stmt.Where("embeddings <> ''") + db = db.Where("embeddings <> ''") } if unmatched { - stmt = stmt.Where("ref_uid = ''") + db = db.Where("ref_uid = ''") } - stmt = stmt.Order("id").Limit(limit).Offset(offset) - err = stmt.Find(&result).Error + db = db.Order("id").Limit(limit).Offset(offset) + + err = db.Find(&result).Error return result, err } @@ -64,3 +66,34 @@ func Embeddings(single bool) (result entity.Embeddings, err error) { return result, nil } + +func MatchMarkersWithPeople() (affected int, err error) { + var markers entity.Markers + + if err := Db(). + Where("face_id <> '' AND ref_uid = '' AND ref_src = ''"). + Where("marker_invalid = 0 AND marker_type = ?", entity.MarkerFace). + Where("marker_label <> ''"). + Order("marker_label"). + Find(&markers).Error; err != nil { + return affected, err + } else if len(markers) == 0 { + return affected, nil + } + + for _, m := range markers { + if p := entity.NewPerson(m.MarkerLabel, entity.SrcMarker, 1); p == nil { + log.Errorf("faces: person should not be nil - bug?") + } else if p = entity.FirstOrCreatePerson(p); p == nil { + log.Errorf("faces: failed adding person %s for marker %d", txt.Quote(m.MarkerLabel), m.ID) + } else if err := m.Updates(entity.Val{"RefUID": p.PersonUID, "RefSrc": entity.SrcPeople, "FaceID": ""}); err != nil { + return affected, err + } else if err := Db().Model(&entity.Face{}).Where("id = ? AND person_uid = ''", m.FaceID).Update("PersonUID", p.PersonUID).Error; err != nil { + return affected, err + } else { + affected++ + } + } + + return affected, nil +} diff --git a/internal/query/markers_test.go b/internal/query/markers_test.go new file mode 100644 index 000000000..5a2eb1f97 --- /dev/null +++ b/internal/query/markers_test.go @@ -0,0 +1,43 @@ +package query + +import ( + "testing" + + "github.com/photoprism/photoprism/internal/entity" + "github.com/stretchr/testify/assert" +) + +func TestMarkers(t *testing.T) { + results, err := Markers(3, 0, entity.MarkerFace, false, false) + + if err != nil { + t.Fatal(err) + } + + assert.GreaterOrEqual(t, len(results), 1) + + for _, val := range results { + assert.IsType(t, entity.Marker{}, val) + } +} + +func TestEmbeddings(t *testing.T) { + results, err := Embeddings(false) + + if err != nil { + t.Fatal(err) + } + + assert.GreaterOrEqual(t, len(results), 1) + + for _, val := range results { + assert.IsType(t, entity.Embedding{}, val) + } +} + +func TestMatchMarkersWithPeople(t *testing.T) { + affected, err := MatchMarkersWithPeople() + + assert.NoError(t, err) + assert.GreaterOrEqual(t, affected, 1) +}