diff --git a/go.mod b/go.mod index df9b6dd72..5bd678893 100644 --- a/go.mod +++ b/go.mod @@ -63,9 +63,9 @@ require ( github.com/ugorji/go v1.2.6 // indirect github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 github.com/urfave/cli v1.22.5 - golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 + golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect - golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d + golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d gonum.org/v1/gonum v0.9.3 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/photoprism/go-tz.v2 v2.1.1 diff --git a/go.sum b/go.sum index 664d9f479..d68ada929 100644 --- a/go.sum +++ b/go.sum @@ -314,6 +314,8 @@ golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ= +golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -378,6 +380,8 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/internal/commands/faces.go b/internal/commands/faces.go index ae38c3853..45871d591 100644 --- a/internal/commands/faces.go +++ b/internal/commands/faces.go @@ -21,7 +21,7 @@ var FacesCommand = cli.Command{ }, { Name: "index", - Usage: "Performs face clustering and recognition", + Usage: "Performs face clustering and matching", Action: facesIndexAction, }, }, @@ -58,7 +58,7 @@ func facesStatsAction(ctx *cli.Context) error { return nil } -// facesIndexAction performs face clustering and recognition. +// facesIndexAction performs face clustering and matching. func facesIndexAction(ctx *cli.Context) error { start := time.Now() diff --git a/internal/photoprism/faces.go b/internal/photoprism/faces.go index adac8428f..bbd9818b3 100644 --- a/internal/photoprism/faces.go +++ b/internal/photoprism/faces.go @@ -16,7 +16,7 @@ import ( "github.com/mpraski/clusters" ) -// Faces represents a worker for face clustering and recognition. +// Faces represents a worker for face clustering and matching. type Faces struct { conf *config.Config } @@ -143,19 +143,22 @@ func (w *Faces) Analyze() (err error) { return nil } -// Start face clustering and recognition. +// Disabled tests if facial recognition is disabled. +func (w *Faces) Disabled() bool { + return !(w.conf.Experimental() && w.conf.Settings().Features.People) +} + +// Start face clustering and matching. func (w *Faces) Start() (err error) { defer func() { if r := recover(); r != nil { - err = fmt.Errorf("faces: %s (panic)\nstack: %s", r, debug.Stack()) - log.Error(err) + err = fmt.Errorf("%s (panic)\nstack: %s", r, debug.Stack()) + log.Errorf("faces: %s", err) } }() - if !w.conf.Experimental() { - return fmt.Errorf("faces: experimental features disabled") - } else if !w.conf.Settings().Features.People { - return fmt.Errorf("faces: disabled in settings") + if w.Disabled() { + return fmt.Errorf("facial recognition is disabled") } if err := mutex.MainWorker.Start(); err != nil { @@ -266,7 +269,7 @@ func (w *Faces) Start() (err error) { for _, marker := range markers { if mutex.MainWorker.Canceled() { - return fmt.Errorf("faces: worker canceled") + return fmt.Errorf("worker canceled") } var faceId string diff --git a/internal/photoprism/moments.go b/internal/photoprism/moments.go index bad858dd3..d8a45ec50 100644 --- a/internal/photoprism/moments.go +++ b/internal/photoprism/moments.go @@ -32,8 +32,8 @@ func NewMoments(conf *config.Config) *Moments { func (w *Moments) Start() (err error) { defer func() { if r := recover(); r != nil { - err = fmt.Errorf("moments: %s (panic)\nstack: %s", r, debug.Stack()) - log.Error(err) + err = fmt.Errorf("%s (panic)\nstack: %s", r, debug.Stack()) + log.Errorf("moments: %s", err) } }() diff --git a/internal/workers/meta.go b/internal/workers/meta.go index de9f82673..9afe79bb6 100644 --- a/internal/workers/meta.go +++ b/internal/workers/meta.go @@ -14,23 +14,23 @@ import ( "github.com/photoprism/photoprism/internal/query" ) -// Meta represents a background metadata maintenance worker. +// Meta represents a background metadata optimization worker. type Meta struct { conf *config.Config } -// NewMeta returns a new background metadata maintenance worker. +// NewMeta returns a new Meta worker. func NewMeta(conf *config.Config) *Meta { return &Meta{conf: conf} } // originalsPath returns the original media files path as string. -func (worker *Meta) originalsPath() string { - return worker.conf.OriginalsPath() +func (m *Meta) originalsPath() string { + return m.conf.OriginalsPath() } -// Start starts the metadata worker. -func (worker *Meta) Start(delay time.Duration) (err error) { +// Start metadata optimization routine. +func (m *Meta) Start(delay time.Duration) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("metadata: %s (panic)\nstack: %s", r, debug.Stack()) @@ -46,13 +46,14 @@ func (worker *Meta) Start(delay time.Duration) (err error) { log.Debugf("metadata: starting routine check") - settings := worker.conf.Settings() + settings := m.conf.Settings() done := make(map[string]bool) limit := 50 offset := 0 optimized := 0 + // Run index optimization. for { photos, err := query.PhotosCheck(limit, offset, delay) @@ -86,9 +87,9 @@ func (worker *Meta) Start(delay time.Duration) (err error) { log.Debugf("metadata: optimized photo %s", photo.String()) } - for _, m := range merged { - log.Infof("metadata: merged %s", m.PhotoUID) - done[m.PhotoUID] = true + for _, p := range merged { + log.Infof("metadata: merged %s", p.PhotoUID) + done[p.PhotoUID] = true } } @@ -105,20 +106,33 @@ func (worker *Meta) Start(delay time.Duration) (err error) { log.Infof("metadata: optimized %d photos", optimized) } + // Explicitly set quality of photos without primary file to -1. if err := query.ResetPhotoQuality(); err != nil { log.Warnf("metadata: %s (reset photo quality)", err.Error()) } + // Update photo counts for labels and places. if err := entity.UpdatePhotoCounts(); err != nil { log.Warnf("metadata: %s (update photo counts)", err.Error()) } - moments := photoprism.NewMoments(worker.conf) - - if err := moments.Start(); err != nil { + // Run moments worker. + if w := photoprism.NewMoments(m.conf); w == nil { + log.Errorf("moments: failed creating worker") + } else if err := w.Start(); err != nil { log.Warnf("moments: %s", err) } + // Run faces worker. + if w := photoprism.NewFaces(m.conf); w == nil { + log.Errorf("faces: failed creating worker") + } else if w.Disabled() { + // Do nothing. + } else if err := w.Start(); err != nil { + log.Warnf("faces: %s", err) + } + + // Run garbage collection. runtime.GC() return nil