Subjects: Do not use or count people tagged on private pictures #4238

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2024-05-08 15:35:30 +02:00
parent 9d04275d18
commit 22cb0ac1a9
9 changed files with 77 additions and 44 deletions

View File

@@ -64,7 +64,7 @@ export class Subject extends RestModel {
return { name: view, query: { q: `person:${this.Slug}` } };
}
return { name: view, query: { q: "subject:" + this.UID } };
return { name: view, query: { q: `subject:${this.UID}` } };
}
classes(selected) {

View File

@@ -142,11 +142,11 @@ func CreateMarker(router *gin.RouterGroup) {
AbortSaveFailed(c)
return
} else if changed {
if updateErr := query.UpdateSubjectCovers(); updateErr != nil {
if updateErr := query.UpdateSubjectCovers(true); updateErr != nil {
log.Errorf("faces: %s (update covers)", updateErr)
}
if updateErr := entity.UpdateSubjectCounts(); updateErr != nil {
if updateErr := entity.UpdateSubjectCounts(true); updateErr != nil {
log.Errorf("faces: %s (update counts)", updateErr)
}
}
@@ -234,11 +234,11 @@ func UpdateMarker(router *gin.RouterGroup) {
}
}
if updateErr := query.UpdateSubjectCovers(); updateErr != nil {
if updateErr := query.UpdateSubjectCovers(true); updateErr != nil {
log.Errorf("faces: %s (update covers)", updateErr)
}
if updateErr := entity.UpdateSubjectCounts(); updateErr != nil {
if updateErr := entity.UpdateSubjectCounts(true); updateErr != nil {
log.Errorf("faces: %s (update counts)", updateErr)
}
}
@@ -298,9 +298,9 @@ func ClearMarkerSubject(router *gin.RouterGroup) {
log.Errorf("faces: %s (clear marker subject)", err)
AbortSaveFailed(c)
return
} else if err := query.UpdateSubjectCovers(); err != nil {
} else if err := query.UpdateSubjectCovers(true); err != nil {
log.Errorf("faces: %s (update covers)", err)
} else if err := entity.UpdateSubjectCounts(); err != nil {
} else if err := entity.UpdateSubjectCounts(true); err != nil {
log.Errorf("faces: %s (update counts)", err)
}

View File

@@ -72,7 +72,7 @@ func UpdatePlacesCounts() (err error) {
}
// UpdateSubjectCounts updates the subject file counts.
func UpdateSubjectCounts() (err error) {
func UpdateSubjectCounts(public bool) (err error) {
mutex.Index.Lock()
defer mutex.Index.Unlock()
@@ -81,36 +81,49 @@ func UpdateSubjectCounts() (err error) {
var res *gorm.DB
subjTable := Subject{}.TableName()
filesTable := File{}.TableName()
markerTable := Marker{}.TableName()
var photosJoin *gorm.SqlExpr
// Count people tagged on private pictures?
// see https://github.com/photoprism/photoprism/issues/4238
// and https://github.com/photoprism/photoprism/issues/2570#issuecomment-1231690056
if public {
photosJoin = gorm.Expr("p.id = f.photo_id AND p.deleted_at IS NULL AND p.photo_private = 0")
} else {
photosJoin = gorm.Expr("p.id = f.photo_id AND p.deleted_at IS NULL")
}
condition := gorm.Expr("subj_type = ?", SubjPerson)
switch DbDialect() {
case MySQL:
res = Db().Exec(`UPDATE ? LEFT JOIN (
SELECT m.subj_uid, COUNT(DISTINCT f.id) AS subj_files, COUNT(DISTINCT f.photo_id) AS subj_photos FROM ? f
JOIN ? m ON f.file_uid = m.file_uid AND m.subj_uid IS NOT NULL AND m.subj_uid <> '' AND m.subj_uid IS NOT NULL
SELECT m.subj_uid, COUNT(DISTINCT f.id) AS subj_files, COUNT(DISTINCT f.photo_id) AS subj_photos
FROM files f
JOIN photos p ON ?
JOIN markers m ON f.file_uid = m.file_uid AND m.subj_uid IS NOT NULL AND m.subj_uid <> '' AND m.subj_uid IS NOT NULL
WHERE m.marker_invalid = 0 AND f.deleted_at IS NULL GROUP BY m.subj_uid
) b ON b.subj_uid = subjects.subj_uid
SET subjects.file_count = CASE WHEN b.subj_files IS NULL THEN 0 ELSE b.subj_files END,
subjects.photo_count = CASE WHEN b.subj_photos IS NULL THEN 0 ELSE b.subj_photos END
WHERE ?`, gorm.Expr(subjTable), gorm.Expr(filesTable), gorm.Expr(markerTable), condition)
WHERE ?`, gorm.Expr(subjTable), photosJoin, condition)
case SQLite3:
// Update files count.
res = Db().Table(subjTable).
UpdateColumn("file_count", gorm.Expr("(SELECT COUNT(DISTINCT f.id) FROM files f "+
fmt.Sprintf("JOIN %s m ON f.file_uid = m.file_uid AND m.subj_uid = %s.subj_uid ",
markerTable, subjTable)+" WHERE m.marker_invalid = 0 AND f.deleted_at IS NULL) WHERE ?", condition))
UpdateColumn("file_count", gorm.Expr("(SELECT COUNT(DISTINCT f.id)"+
" FROM files f JOIN photos p ON ?"+
" JOIN markers m ON f.file_uid = m.file_uid AND m.subj_uid = subjects.subj_uid"+
" WHERE m.marker_invalid = 0 AND f.deleted_at IS NULL) WHERE ?", photosJoin, condition))
// Update photo count.
if res.Error != nil {
return res.Error
} else {
photosRes := Db().Table(subjTable).
UpdateColumn("photo_count", gorm.Expr("(SELECT COUNT(DISTINCT f.photo_id) FROM files f "+
fmt.Sprintf("JOIN %s m ON f.file_uid = m.file_uid AND m.subj_uid = %s.subj_uid ",
markerTable, subjTable)+" WHERE m.marker_invalid = 0 AND f.deleted_at IS NULL) WHERE ?", condition))
UpdateColumn("photo_count", gorm.Expr("(SELECT COUNT(DISTINCT f.photo_id)"+
" FROM files f JOIN photos p ON ?"+
" JOIN markers m ON f.file_uid = m.file_uid AND m.subj_uid = subjects.subj_uid"+
" WHERE m.marker_invalid = 0 AND f.deleted_at IS NULL) WHERE ?", photosJoin, condition))
res.RowsAffected += photosRes.RowsAffected
}
default:
@@ -195,7 +208,7 @@ func UpdateCounts() (err error) {
return fmt.Errorf("%s while updating places counts", err)
}
if err = UpdateSubjectCounts(); err != nil {
if err = UpdateSubjectCounts(true); err != nil {
if strings.Contains(err.Error(), "Error 1054") {
log.Errorf("counts: failed to update subjects, potentially incompatible database version")
log.Errorf("%s see https://jira.mariadb.org/browse/MDEV-25362", err)

View File

@@ -16,7 +16,7 @@ func TestLabelCounts(t *testing.T) {
}
}
func TestUpdatePhotoCounts(t *testing.T) {
func TestUpdateCounts(t *testing.T) {
err := UpdateCounts()
if err != nil {

View File

@@ -87,7 +87,7 @@ func NewMarker(file File, area crop.Area, subjUID, markerSrc, markerType string,
Y: area.Y,
W: area.W,
H: area.H,
Q: int(float32(math.Log(float64(score))) * float32(size) * area.W),
Q: int(math.Log(float64(score)) * ((float64(size) * float64(area.W)) / 2)),
Size: size,
Score: score,
Thumb: area.Thumb(file.FileHash),

View File

@@ -52,7 +52,7 @@ func TestNewMarker(t *testing.T) {
assert.Equal(t, "2cad9168fa6acc5c5c2965ddf6ec465ca42fd818-1340ce163163", m.Thumb)
assert.Equal(t, "ls6sg6b1wowuy3c3", m.SubjUID)
assert.True(t, m.MarkerReview)
assert.Equal(t, 119, m.Q)
assert.Equal(t, 59, m.Q)
assert.Equal(t, 29, m.Score)
assert.Equal(t, SrcImage, m.MarkerSrc)
assert.Equal(t, MarkerLabel, m.MarkerType)

View File

@@ -245,37 +245,57 @@ func UpdateLabelCovers() (err error) {
}
// UpdateSubjectCovers updates subject cover thumbs.
func UpdateSubjectCovers() (err error) {
func UpdateSubjectCovers(public bool) (err error) {
mutex.Index.Lock()
defer mutex.Index.Unlock()
start := time.Now()
var res *gorm.DB
var photosJoin *gorm.SqlExpr
subjTable := entity.Subject{}.TableName()
markerTable := entity.Marker{}.TableName()
// Use faces tagged on private pictures as cover images?
// see https://github.com/photoprism/photoprism/issues/4238
// and https://github.com/photoprism/photoprism/issues/2570#issuecomment-1231690056
if public {
photosJoin = gorm.Expr("p.id = f.photo_id AND p.deleted_at IS NULL AND p.photo_private = 0")
} else {
photosJoin = gorm.Expr("p.id = f.photo_id AND p.deleted_at IS NULL")
}
condition := gorm.Expr(
fmt.Sprintf("%s.subj_type = ? AND thumb_src = ?", subjTable),
entity.SubjPerson, entity.SrcAuto)
condition := gorm.Expr("subjects.subj_type = ? AND thumb_src = ?", entity.SubjPerson, entity.SrcAuto)
// TODO: Avoid using private photos as subject covers.
// See https://github.com/photoprism/photoprism/issues/2570#issuecomment-1231690056
// Compose SQL update query.
switch DbDialect() {
case MySQL:
res = Db().Exec(`UPDATE ? LEFT JOIN (
SELECT m.subj_uid, m.q, MAX(m.thumb) AS marker_thumb FROM ? m
res = Db().Exec(`UPDATE subjects LEFT JOIN (
SELECT m.subj_uid, m.q, MAX(m.thumb) AS marker_thumb
FROM markers m
JOIN files f ON f.file_uid = m.file_uid AND f.deleted_at IS NULL
JOIN photos p ON ?
WHERE m.subj_uid <> '' AND m.subj_uid IS NOT NULL
AND m.marker_invalid = 0 AND m.thumb IS NOT NULL AND m.thumb <> ''
GROUP BY m.subj_uid, m.q
) b ON b.subj_uid = subjects.subj_uid
SET thumb = marker_thumb WHERE ?`, gorm.Expr(subjTable), gorm.Expr(markerTable), condition)
SET thumb = marker_thumb WHERE ?`,
photosJoin,
condition,
)
case SQLite3:
from := gorm.Expr(fmt.Sprintf("%s m WHERE m.subj_uid = %s.subj_uid ", markerTable, subjTable))
res = Db().Table(entity.Subject{}.TableName()).UpdateColumn("thumb", gorm.Expr(`(
SELECT m.thumb FROM ? AND m.thumb <> '' ORDER BY m.subj_src DESC, m.q DESC LIMIT 1
) WHERE ?`, from, condition))
// from := gorm.Expr(fmt.Sprintf("%s m WHERE m.subj_uid = %s.subj_uid ", markerTable, subjTable))
res = Db().Table(entity.Subject{}.TableName()).UpdateColumn("thumb",
gorm.Expr(`(
SELECT m.thumb
FROM markers m
JOIN files f ON f.file_uid = m.file_uid AND f.deleted_at IS NULL
JOIN photos p ON ?
WHERE m.subj_uid = subjects.subj_uid AND m.thumb <> ''
ORDER BY m.subj_src DESC, m.q DESC LIMIT 1
) WHERE ?`,
photosJoin,
condition,
),
)
default:
log.Warnf("sql: unsupported dialect %s", DbDialect())
return nil
@@ -309,7 +329,7 @@ func UpdateCovers() (err error) {
}
// Update Subjects.
if err = UpdateSubjectCovers(); err != nil {
if err = UpdateSubjectCovers(true); err != nil {
return fmt.Errorf("%s while updating subject covers", err)
}

View File

@@ -27,7 +27,8 @@ func TestUpdateLabelCovers(t *testing.T) {
}
func TestUpdateSubjectCovers(t *testing.T) {
assert.NoError(t, UpdateSubjectCovers())
assert.NoError(t, UpdateSubjectCovers(false))
assert.NoError(t, UpdateSubjectCovers(true))
}
func TestUpdateCovers(t *testing.T) {

View File

@@ -4,13 +4,12 @@ import (
"fmt"
"strings"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt"
"github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/txt"
)
// Subjects searches subjects and returns them.