mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Clean missing captions during scan (#3240)
This commit is contained in:
parent
b5b9023b3e
commit
dc875ed5d7
4 changed files with 82 additions and 20 deletions
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/scene"
|
"github.com/stashapp/stash/pkg/scene"
|
||||||
"github.com/stashapp/stash/pkg/scene/generate"
|
"github.com/stashapp/stash/pkg/scene/generate"
|
||||||
|
"github.com/stashapp/stash/pkg/txn"
|
||||||
)
|
)
|
||||||
|
|
||||||
type scanner interface {
|
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.
|
// handlerRequiredFilter returns true if a File's handler needs to be executed despite the file not being updated.
|
||||||
type handlerRequiredFilter struct {
|
type handlerRequiredFilter struct {
|
||||||
extensionConfig
|
extensionConfig
|
||||||
|
txnManager txn.Manager
|
||||||
SceneFinder sceneFinder
|
SceneFinder sceneFinder
|
||||||
ImageFinder fileCounter
|
ImageFinder fileCounter
|
||||||
GalleryFinder galleryFinder
|
GalleryFinder galleryFinder
|
||||||
|
CaptionUpdater video.CaptionUpdater
|
||||||
|
|
||||||
FolderCache *lru.LRU
|
FolderCache *lru.LRU
|
||||||
|
|
||||||
|
|
@ -126,9 +129,11 @@ func newHandlerRequiredFilter(c *config.Instance) *handlerRequiredFilter {
|
||||||
|
|
||||||
return &handlerRequiredFilter{
|
return &handlerRequiredFilter{
|
||||||
extensionConfig: newExtensionConfig(c),
|
extensionConfig: newExtensionConfig(c),
|
||||||
|
txnManager: db,
|
||||||
SceneFinder: db.Scene,
|
SceneFinder: db.Scene,
|
||||||
ImageFinder: db.Image,
|
ImageFinder: db.Image,
|
||||||
GalleryFinder: db.Gallery,
|
GalleryFinder: db.Gallery,
|
||||||
|
CaptionUpdater: db.File,
|
||||||
FolderCache: lru.New(processes * 2),
|
FolderCache: lru.New(processes * 2),
|
||||||
videoFileNamingAlgorithm: c.GetVideoFileNamingAlgorithm(),
|
videoFileNamingAlgorithm: c.GetVideoFileNamingAlgorithm(),
|
||||||
}
|
}
|
||||||
|
|
@ -205,6 +210,15 @@ func (f *handlerRequiredFilter) Accept(ctx context.Context, ff file.File) bool {
|
||||||
return true
|
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
|
return false
|
||||||
|
|
@ -329,6 +343,7 @@ func getScanHandlers(options ScanMetadataInput, taskQueue *job.TaskQueue, progre
|
||||||
Handler: &scene.ScanHandler{
|
Handler: &scene.ScanHandler{
|
||||||
CreatorUpdater: db.Scene,
|
CreatorUpdater: db.Scene,
|
||||||
PluginCache: pluginCache,
|
PluginCache: pluginCache,
|
||||||
|
CaptionUpdater: db.File,
|
||||||
CoverGenerator: &coverGenerator{},
|
CoverGenerator: &coverGenerator{},
|
||||||
ScanGenerator: &sceneGenerators{
|
ScanGenerator: &sceneGenerators{
|
||||||
input: options,
|
input: options,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package video
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
@ -59,23 +60,6 @@ func IsLangInCaptions(lang string, ext string, captions []*models.VideoCaption)
|
||||||
return false
|
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
|
// getCaptionPrefix returns the prefix used to search for video files for the provided caption path
|
||||||
func getCaptionPrefix(captionPath string) string {
|
func getCaptionPrefix(captionPath string) string {
|
||||||
basename := strings.TrimSuffix(captionPath, filepath.Ext(captionPath)) // caption filename without the extension
|
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())
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stashapp/stash/pkg/file"
|
"github.com/stashapp/stash/pkg/file"
|
||||||
|
"github.com/stashapp/stash/pkg/file/video"
|
||||||
"github.com/stashapp/stash/pkg/logger"
|
"github.com/stashapp/stash/pkg/logger"
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/models/paths"
|
"github.com/stashapp/stash/pkg/models/paths"
|
||||||
|
|
@ -36,6 +37,7 @@ type ScanHandler struct {
|
||||||
|
|
||||||
CoverGenerator CoverGenerator
|
CoverGenerator CoverGenerator
|
||||||
ScanGenerator ScanGenerator
|
ScanGenerator ScanGenerator
|
||||||
|
CaptionUpdater video.CaptionUpdater
|
||||||
PluginCache *plugin.Cache
|
PluginCache *plugin.Cache
|
||||||
|
|
||||||
FileNamingAlgorithm models.HashAlgorithm
|
FileNamingAlgorithm models.HashAlgorithm
|
||||||
|
|
@ -52,6 +54,9 @@ func (h *ScanHandler) validate() error {
|
||||||
if h.ScanGenerator == nil {
|
if h.ScanGenerator == nil {
|
||||||
return errors.New("ScanGenerator is required")
|
return errors.New("ScanGenerator is required")
|
||||||
}
|
}
|
||||||
|
if h.CaptionUpdater == nil {
|
||||||
|
return errors.New("CaptionUpdater is required")
|
||||||
|
}
|
||||||
if !h.FileNamingAlgorithm.IsValid() {
|
if !h.FileNamingAlgorithm.IsValid() {
|
||||||
return errors.New("FileNamingAlgorithm is required")
|
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
|
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
|
// try to match the file to a scene
|
||||||
existing, err := h.CreatorUpdater.FindByFileID(ctx, f.Base().ID)
|
existing, err := h.CreatorUpdater.FindByFileID(ctx, f.Base().ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,6 @@
|
||||||
|
|
||||||
### 🎨 Improvements
|
### 🎨 Improvements
|
||||||
* Changed performer aliases to be a list, rather than a string field. ([#3113](https://github.com/stashapp/stash/pull/3113))
|
* 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))
|
||||||
Loading…
Reference in a new issue