diff --git a/internal/manager/task_scan.go b/internal/manager/task_scan.go index 5a2a07653..04234239f 100644 --- a/internal/manager/task_scan.go +++ b/internal/manager/task_scan.go @@ -21,6 +21,7 @@ import ( "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" "github.com/stashapp/stash/pkg/scene/generate" + "github.com/stashapp/stash/pkg/txn" ) type scanner interface { @@ -111,9 +112,11 @@ type sceneFinder interface { // handlerRequiredFilter returns true if a File's handler needs to be executed despite the file not being updated. type handlerRequiredFilter struct { extensionConfig - SceneFinder sceneFinder - ImageFinder fileCounter - GalleryFinder galleryFinder + txnManager txn.Manager + SceneFinder sceneFinder + ImageFinder fileCounter + GalleryFinder galleryFinder + CaptionUpdater video.CaptionUpdater FolderCache *lru.LRU @@ -126,9 +129,11 @@ func newHandlerRequiredFilter(c *config.Instance) *handlerRequiredFilter { return &handlerRequiredFilter{ extensionConfig: newExtensionConfig(c), + txnManager: db, SceneFinder: db.Scene, ImageFinder: db.Image, GalleryFinder: db.Gallery, + CaptionUpdater: db.File, FolderCache: lru.New(processes * 2), videoFileNamingAlgorithm: c.GetVideoFileNamingAlgorithm(), } @@ -205,6 +210,15 @@ func (f *handlerRequiredFilter) Accept(ctx context.Context, ff file.File) bool { return true } } + + // clean captions - scene handler handles this as well, but + // unchanged files aren't processed by the scene handler + videoFile, _ := ff.(*file.VideoFile) + if videoFile != nil { + if err := video.CleanCaptions(ctx, videoFile, f.txnManager, f.CaptionUpdater); err != nil { + logger.Errorf("Error cleaning captions: %v", err) + } + } } return false @@ -329,6 +343,7 @@ func getScanHandlers(options ScanMetadataInput, taskQueue *job.TaskQueue, progre Handler: &scene.ScanHandler{ CreatorUpdater: db.Scene, PluginCache: pluginCache, + CaptionUpdater: db.File, CoverGenerator: &coverGenerator{}, ScanGenerator: &sceneGenerators{ input: options, diff --git a/pkg/file/video/caption.go b/pkg/file/video/caption.go index 8c10d0d1c..2820caee4 100644 --- a/pkg/file/video/caption.go +++ b/pkg/file/video/caption.go @@ -2,6 +2,7 @@ package video import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -59,23 +60,6 @@ func IsLangInCaptions(lang string, ext string, captions []*models.VideoCaption) return false } -// CleanCaptions removes non existent/accessible language codes from captions -func CleanCaptions(scenePath string, captions []*models.VideoCaption) (cleanedCaptions []*models.VideoCaption, changed bool) { - changed = false - for _, caption := range captions { - found := false - f := caption.Path(scenePath) - if _, er := os.Stat(f); er == nil { - cleanedCaptions = append(cleanedCaptions, caption) - found = true - } - if !found { - changed = true - } - } - return -} - // getCaptionPrefix returns the prefix used to search for video files for the provided caption path func getCaptionPrefix(captionPath string) string { basename := strings.TrimSuffix(captionPath, filepath.Ext(captionPath)) // caption filename without the extension @@ -148,3 +132,52 @@ func AssociateCaptions(ctx context.Context, captionPath string, txnMgr txn.Manag logger.Error(err.Error()) } } + +// CleanCaptions removes non existent/accessible language codes from captions +func CleanCaptions(ctx context.Context, f *file.VideoFile, txnMgr txn.Manager, w CaptionUpdater) error { + captions, err := w.GetCaptions(ctx, f.ID) + if err != nil { + return fmt.Errorf("getting captions for file %s: %w", f.Path, err) + } + + if len(captions) == 0 { + return nil + } + + filePath := f.Path + + changed := false + var newCaptions []*models.VideoCaption + + for _, caption := range captions { + captionPath := caption.Path(filePath) + _, err := os.Stat(captionPath) + if errors.Is(err, os.ErrNotExist) { + logger.Infof("Removing non existent caption %s for %s", caption.Filename, f.Path) + changed = true + } else { + // other errors are ignored for the purposes of cleaning + newCaptions = append(newCaptions, caption) + } + } + + if changed { + fn := func(ctx context.Context) error { + return w.UpdateCaptions(ctx, f.ID, newCaptions) + } + + // possible that we are already in a transaction and txnMgr is nil + // in that case just call the function directly + if txnMgr == nil { + err = fn(ctx) + } else { + err = txn.WithTxn(ctx, txnMgr, fn) + } + + if err != nil { + return fmt.Errorf("updating captions for file %s: %w", f.Path, err) + } + } + + return nil +} diff --git a/pkg/scene/scan.go b/pkg/scene/scan.go index b0f9ef3d4..8abbfc2d3 100644 --- a/pkg/scene/scan.go +++ b/pkg/scene/scan.go @@ -7,6 +7,7 @@ import ( "time" "github.com/stashapp/stash/pkg/file" + "github.com/stashapp/stash/pkg/file/video" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/models/paths" @@ -36,6 +37,7 @@ type ScanHandler struct { CoverGenerator CoverGenerator ScanGenerator ScanGenerator + CaptionUpdater video.CaptionUpdater PluginCache *plugin.Cache FileNamingAlgorithm models.HashAlgorithm @@ -52,6 +54,9 @@ func (h *ScanHandler) validate() error { if h.ScanGenerator == nil { return errors.New("ScanGenerator is required") } + if h.CaptionUpdater == nil { + return errors.New("CaptionUpdater is required") + } if !h.FileNamingAlgorithm.IsValid() { return errors.New("FileNamingAlgorithm is required") } @@ -72,6 +77,12 @@ func (h *ScanHandler) Handle(ctx context.Context, f file.File, oldFile file.File return ErrNotVideoFile } + if oldFile != nil { + if err := video.CleanCaptions(ctx, videoFile, nil, h.CaptionUpdater); err != nil { + return fmt.Errorf("cleaning captions: %w", err) + } + } + // try to match the file to a scene existing, err := h.CreatorUpdater.FindByFileID(ctx, f.Base().ID) if err != nil { diff --git a/ui/v2.5/src/docs/en/Changelog/v0190.md b/ui/v2.5/src/docs/en/Changelog/v0190.md index 1fe661e20..5bb6669e9 100644 --- a/ui/v2.5/src/docs/en/Changelog/v0190.md +++ b/ui/v2.5/src/docs/en/Changelog/v0190.md @@ -7,3 +7,6 @@ ### 🎨 Improvements * Changed performer aliases to be a list, rather than a string field. ([#3113](https://github.com/stashapp/stash/pull/3113)) + +### 🐛 Bug fixes +* Fixed missing captions not being removed during scan. ([#3240](https://github.com/stashapp/stash/pull/3240)) \ No newline at end of file