mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
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:
@@ -64,7 +64,7 @@ export class Subject extends RestModel {
|
|||||||
return { name: view, query: { q: `person:${this.Slug}` } };
|
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) {
|
classes(selected) {
|
||||||
|
|||||||
@@ -142,11 +142,11 @@ func CreateMarker(router *gin.RouterGroup) {
|
|||||||
AbortSaveFailed(c)
|
AbortSaveFailed(c)
|
||||||
return
|
return
|
||||||
} else if changed {
|
} else if changed {
|
||||||
if updateErr := query.UpdateSubjectCovers(); updateErr != nil {
|
if updateErr := query.UpdateSubjectCovers(true); updateErr != nil {
|
||||||
log.Errorf("faces: %s (update covers)", updateErr)
|
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)
|
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)
|
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)
|
log.Errorf("faces: %s (update counts)", updateErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,9 +298,9 @@ func ClearMarkerSubject(router *gin.RouterGroup) {
|
|||||||
log.Errorf("faces: %s (clear marker subject)", err)
|
log.Errorf("faces: %s (clear marker subject)", err)
|
||||||
AbortSaveFailed(c)
|
AbortSaveFailed(c)
|
||||||
return
|
return
|
||||||
} else if err := query.UpdateSubjectCovers(); err != nil {
|
} else if err := query.UpdateSubjectCovers(true); err != nil {
|
||||||
log.Errorf("faces: %s (update covers)", err)
|
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)
|
log.Errorf("faces: %s (update counts)", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func UpdatePlacesCounts() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSubjectCounts updates the subject file counts.
|
// UpdateSubjectCounts updates the subject file counts.
|
||||||
func UpdateSubjectCounts() (err error) {
|
func UpdateSubjectCounts(public bool) (err error) {
|
||||||
mutex.Index.Lock()
|
mutex.Index.Lock()
|
||||||
defer mutex.Index.Unlock()
|
defer mutex.Index.Unlock()
|
||||||
|
|
||||||
@@ -81,36 +81,49 @@ func UpdateSubjectCounts() (err error) {
|
|||||||
var res *gorm.DB
|
var res *gorm.DB
|
||||||
|
|
||||||
subjTable := Subject{}.TableName()
|
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)
|
condition := gorm.Expr("subj_type = ?", SubjPerson)
|
||||||
|
|
||||||
switch DbDialect() {
|
switch DbDialect() {
|
||||||
case MySQL:
|
case MySQL:
|
||||||
res = Db().Exec(`UPDATE ? LEFT JOIN (
|
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
|
SELECT m.subj_uid, COUNT(DISTINCT f.id) AS subj_files, COUNT(DISTINCT f.photo_id) AS subj_photos
|
||||||
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
|
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
|
WHERE m.marker_invalid = 0 AND f.deleted_at IS NULL GROUP BY m.subj_uid
|
||||||
) b ON b.subj_uid = subjects.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,
|
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
|
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:
|
case SQLite3:
|
||||||
// Update files count.
|
// Update files count.
|
||||||
res = Db().Table(subjTable).
|
res = Db().Table(subjTable).
|
||||||
UpdateColumn("file_count", gorm.Expr("(SELECT COUNT(DISTINCT f.id) FROM files f "+
|
UpdateColumn("file_count", gorm.Expr("(SELECT COUNT(DISTINCT f.id)"+
|
||||||
fmt.Sprintf("JOIN %s m ON f.file_uid = m.file_uid AND m.subj_uid = %s.subj_uid ",
|
" FROM files f JOIN photos p ON ?"+
|
||||||
markerTable, subjTable)+" WHERE m.marker_invalid = 0 AND f.deleted_at IS NULL) WHERE ?", condition))
|
" 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.
|
// Update photo count.
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
return res.Error
|
return res.Error
|
||||||
} else {
|
} else {
|
||||||
photosRes := Db().Table(subjTable).
|
photosRes := Db().Table(subjTable).
|
||||||
UpdateColumn("photo_count", gorm.Expr("(SELECT COUNT(DISTINCT f.photo_id) FROM files f "+
|
UpdateColumn("photo_count", gorm.Expr("(SELECT COUNT(DISTINCT f.photo_id)"+
|
||||||
fmt.Sprintf("JOIN %s m ON f.file_uid = m.file_uid AND m.subj_uid = %s.subj_uid ",
|
" FROM files f JOIN photos p ON ?"+
|
||||||
markerTable, subjTable)+" WHERE m.marker_invalid = 0 AND f.deleted_at IS NULL) WHERE ?", condition))
|
" 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
|
res.RowsAffected += photosRes.RowsAffected
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -195,7 +208,7 @@ func UpdateCounts() (err error) {
|
|||||||
return fmt.Errorf("%s while updating places counts", err)
|
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") {
|
if strings.Contains(err.Error(), "Error 1054") {
|
||||||
log.Errorf("counts: failed to update subjects, potentially incompatible database version")
|
log.Errorf("counts: failed to update subjects, potentially incompatible database version")
|
||||||
log.Errorf("%s see https://jira.mariadb.org/browse/MDEV-25362", err)
|
log.Errorf("%s see https://jira.mariadb.org/browse/MDEV-25362", err)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func TestLabelCounts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdatePhotoCounts(t *testing.T) {
|
func TestUpdateCounts(t *testing.T) {
|
||||||
err := UpdateCounts()
|
err := UpdateCounts()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ func NewMarker(file File, area crop.Area, subjUID, markerSrc, markerType string,
|
|||||||
Y: area.Y,
|
Y: area.Y,
|
||||||
W: area.W,
|
W: area.W,
|
||||||
H: area.H,
|
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,
|
Size: size,
|
||||||
Score: score,
|
Score: score,
|
||||||
Thumb: area.Thumb(file.FileHash),
|
Thumb: area.Thumb(file.FileHash),
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func TestNewMarker(t *testing.T) {
|
|||||||
assert.Equal(t, "2cad9168fa6acc5c5c2965ddf6ec465ca42fd818-1340ce163163", m.Thumb)
|
assert.Equal(t, "2cad9168fa6acc5c5c2965ddf6ec465ca42fd818-1340ce163163", m.Thumb)
|
||||||
assert.Equal(t, "ls6sg6b1wowuy3c3", m.SubjUID)
|
assert.Equal(t, "ls6sg6b1wowuy3c3", m.SubjUID)
|
||||||
assert.True(t, m.MarkerReview)
|
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, 29, m.Score)
|
||||||
assert.Equal(t, SrcImage, m.MarkerSrc)
|
assert.Equal(t, SrcImage, m.MarkerSrc)
|
||||||
assert.Equal(t, MarkerLabel, m.MarkerType)
|
assert.Equal(t, MarkerLabel, m.MarkerType)
|
||||||
|
|||||||
@@ -245,37 +245,57 @@ func UpdateLabelCovers() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateSubjectCovers updates subject cover thumbs.
|
// UpdateSubjectCovers updates subject cover thumbs.
|
||||||
func UpdateSubjectCovers() (err error) {
|
func UpdateSubjectCovers(public bool) (err error) {
|
||||||
mutex.Index.Lock()
|
mutex.Index.Lock()
|
||||||
defer mutex.Index.Unlock()
|
defer mutex.Index.Unlock()
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
var res *gorm.DB
|
var res *gorm.DB
|
||||||
|
var photosJoin *gorm.SqlExpr
|
||||||
|
|
||||||
subjTable := entity.Subject{}.TableName()
|
// Use faces tagged on private pictures as cover images?
|
||||||
markerTable := entity.Marker{}.TableName()
|
// 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(
|
condition := gorm.Expr("subjects.subj_type = ? AND thumb_src = ?", entity.SubjPerson, entity.SrcAuto)
|
||||||
fmt.Sprintf("%s.subj_type = ? AND thumb_src = ?", subjTable),
|
|
||||||
entity.SubjPerson, entity.SrcAuto)
|
|
||||||
|
|
||||||
// TODO: Avoid using private photos as subject covers.
|
// Compose SQL update query.
|
||||||
// See https://github.com/photoprism/photoprism/issues/2570#issuecomment-1231690056
|
|
||||||
switch DbDialect() {
|
switch DbDialect() {
|
||||||
case MySQL:
|
case MySQL:
|
||||||
res = Db().Exec(`UPDATE ? LEFT JOIN (
|
res = Db().Exec(`UPDATE subjects LEFT JOIN (
|
||||||
SELECT m.subj_uid, m.q, MAX(m.thumb) AS marker_thumb FROM ? m
|
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
|
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 <> ''
|
AND m.marker_invalid = 0 AND m.thumb IS NOT NULL AND m.thumb <> ''
|
||||||
GROUP BY m.subj_uid, m.q
|
GROUP BY m.subj_uid, m.q
|
||||||
) b ON b.subj_uid = subjects.subj_uid
|
) 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:
|
case SQLite3:
|
||||||
from := gorm.Expr(fmt.Sprintf("%s m WHERE m.subj_uid = %s.subj_uid ", markerTable, subjTable))
|
// 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(`(
|
res = Db().Table(entity.Subject{}.TableName()).UpdateColumn("thumb",
|
||||||
SELECT m.thumb FROM ? AND m.thumb <> '' ORDER BY m.subj_src DESC, m.q DESC LIMIT 1
|
gorm.Expr(`(
|
||||||
) WHERE ?`, from, condition))
|
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:
|
default:
|
||||||
log.Warnf("sql: unsupported dialect %s", DbDialect())
|
log.Warnf("sql: unsupported dialect %s", DbDialect())
|
||||||
return nil
|
return nil
|
||||||
@@ -309,7 +329,7 @@ func UpdateCovers() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update Subjects.
|
// Update Subjects.
|
||||||
if err = UpdateSubjectCovers(); err != nil {
|
if err = UpdateSubjectCovers(true); err != nil {
|
||||||
return fmt.Errorf("%s while updating subject covers", err)
|
return fmt.Errorf("%s while updating subject covers", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ func TestUpdateLabelCovers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateSubjectCovers(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) {
|
func TestUpdateCovers(t *testing.T) {
|
||||||
|
|||||||
@@ -4,13 +4,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
|
||||||
|
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/form"
|
"github.com/photoprism/photoprism/internal/form"
|
||||||
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Subjects searches subjects and returns them.
|
// Subjects searches subjects and returns them.
|
||||||
|
|||||||
Reference in New Issue
Block a user