package workers import ( "fmt" "path" "runtime/debug" "slices" "time" "github.com/dustin/go-humanize/english" "github.com/photoprism/photoprism/internal/ai/vision" "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity/query" "github.com/photoprism/photoprism/internal/entity/search" "github.com/photoprism/photoprism/internal/entity/sortby" "github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/pkg/clean" "github.com/photoprism/photoprism/pkg/txt" ) // Reset removes data generated by the specified model types for photos matching the search filter. func (w *Vision) Reset(filter string, count int, models []string, provider string) (err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("vision: %s (worker panic)\nstack: %s", r, debug.Stack()) log.Error(err) } }() if err = mutex.VisionWorker.Start(); err != nil { return err } defer mutex.VisionWorker.Stop() resetLabels := slices.Contains(models, vision.ModelTypeLabels) resetCaptions := slices.Contains(models, vision.ModelTypeCaption) if n := len(models); n == 0 { log.Warnf("vision: no models were specified") return nil } else { log.Infof("vision: resetting %s model data", txt.JoinAnd(models)) } provider = clean.ShortTypeLower(provider) if provider == "" { provider = vision.DefaultSrc } start := time.Now() done := make(map[string]bool) if count < 1 || count > search.MaxResults { count = search.MaxResults } frm := form.SearchPhotos{ Query: filter, Primary: true, Merged: false, Count: count, Offset: 0, Order: sortby.Added, } photos, _, queryErr := search.Photos(frm) if queryErr != nil { return queryErr } if len(photos) == 0 { log.Info("vision: no pictures to reset") return nil } var ( updated int removedLabels int64 resetCaptionsCount int ) for _, photo := range photos { if done[photo.PhotoUID] { continue } done[photo.PhotoUID] = true m, loadErr := query.PhotoByUID(photo.PhotoUID) if loadErr != nil { log.Errorf("vision: failed to load %s (%s)", photo.PhotoUID, loadErr) continue } changed := false if resetCaptions { if changedCaption, capErr := m.ResetCaption(provider); capErr != nil { log.Warnf("vision: %s (reset caption)", clean.Error(capErr)) } else if changedCaption { resetCaptionsCount++ changed = true } } if resetLabels { if removed, lblErr := m.ResetLabels(provider); lblErr != nil { log.Warnf("vision: %s (reset labels)", clean.Error(lblErr)) } else if removed > 0 { removedLabels += removed changed = true log.Debugf("vision: removed %d labels from %s", removed, clean.Log(path.Join(m.PhotoPath, m.PhotoName))) } } if !changed { continue } updated++ if err := m.GenerateAndSaveTitle(); err != nil { log.Warnf("vision: %s (generate title)", clean.Error(err)) } } if removedLabels > 0 { entity.FlushPhotoLabelCache() } if updated > 0 { if moments := photoprism.NewMoments(w.conf); moments == nil { log.Errorf("vision: failed to update moments") } else if err = moments.Start(); err != nil { log.Warnf("moments: %s in optimization worker", err) } if err = entity.UpdateCounts(); err != nil { log.Warnf("vision: %s in optimization worker", err) } if err = query.UpdateCovers(); err != nil { log.Warnf("vision: %s in optimization worker", err) } } log.Infof("vision: reset %s [%s]", english.Plural(updated, "picture", "pictures"), time.Since(start)) return nil }