Skip to content

Commit

Permalink
Improve handling of moved and added video files (stashapp#4598)
Browse files Browse the repository at this point in the history
* If old file path is not in library, treat as move
* Use existing phash if file with same oshash exists
  • Loading branch information
WithoutPants committed Feb 20, 2024
1 parent 8b1d4cc commit 76e5598
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 18 deletions.
7 changes: 5 additions & 2 deletions internal/manager/manager_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ import (
)

func useAsVideo(pathname string) bool {
if instance.Config.IsCreateImageClipsFromVideos() && config.StashConfigs.GetStashFromDirPath(instance.Config.GetStashPaths(), pathname).ExcludeVideo {
stash := config.StashConfigs.GetStashFromDirPath(instance.Config.GetStashPaths(), pathname)

if instance.Config.IsCreateImageClipsFromVideos() && stash != nil && stash.ExcludeVideo {
return false
}
return isVideo(pathname)
}

func useAsImage(pathname string) bool {
if instance.Config.IsCreateImageClipsFromVideos() && config.StashConfigs.GetStashFromDirPath(instance.Config.GetStashPaths(), pathname).ExcludeVideo {
stash := config.StashConfigs.GetStashFromDirPath(instance.Config.GetStashPaths(), pathname)
if instance.Config.IsCreateImageClipsFromVideos() && stash != nil && stash.ExcludeVideo {
return isImage(pathname) || isVideo(pathname)
}
return isImage(pathname)
Expand Down
63 changes: 56 additions & 7 deletions internal/manager/task_generate_phash.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,38 @@ func (t *GeneratePhashTask) Start(ctx context.Context) {
return
}

hash, err := videophash.Generate(instance.FFMpeg, t.File)
if err != nil {
logger.Errorf("error generating phash: %s", err.Error())
logErrorOutput(err)
return
var hash int64
set := false

// #4393 - if there is a file with the same oshash, we can use the same phash
// only use this if we're not overwriting
if !t.Overwrite {
existing, err := t.findExistingPhash(ctx)
if err != nil {
logger.Warnf("Error finding existing phash: %v", err)
} else if existing != nil {
logger.Infof("Using existing phash for %s", t.File.Path)
hash = existing.(int64)
set = true
}
}

if !set {
generated, err := videophash.Generate(instance.FFMpeg, t.File)
if err != nil {
logger.Errorf("Error generating phash: %v", err)
logErrorOutput(err)
return
}

hash = int64(*generated)
}

r := t.repository
if err := r.WithTxn(ctx, func(ctx context.Context) error {
hashValue := int64(*hash)
t.File.Fingerprints = t.File.Fingerprints.AppendUnique(models.Fingerprint{
Type: models.FingerprintTypePhash,
Fingerprint: hashValue,
Fingerprint: hash,
})

return r.File.Update(ctx, t.File)
Expand All @@ -46,6 +65,36 @@ func (t *GeneratePhashTask) Start(ctx context.Context) {
}
}

func (t *GeneratePhashTask) findExistingPhash(ctx context.Context) (interface{}, error) {
r := t.repository
var ret interface{}
if err := r.WithReadTxn(ctx, func(ctx context.Context) error {
oshash := t.File.Fingerprints.Get(models.FingerprintTypeOshash)

// find other files with the same oshash
files, err := r.File.FindByFingerprint(ctx, models.Fingerprint{
Type: models.FingerprintTypeOshash,
Fingerprint: oshash,
})
if err != nil {
return fmt.Errorf("finding files by oshash: %w", err)
}

// find the first file with a phash
for _, file := range files {
if phash := file.Base().Fingerprints.Get(models.FingerprintTypePhash); phash != nil {
ret = phash
return nil
}
}
return nil
}); err != nil {
return nil, err
}

return ret, nil
}

func (t *GeneratePhashTask) required() bool {
if t.Overwrite {
return true
Expand Down
13 changes: 6 additions & 7 deletions internal/manager/task_scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,12 @@ func (f *scanFilter) Accept(ctx context.Context, path string, info fs.FileInfo)
return false
}

s := f.stashPaths.GetStashFromDirPath(path)
if s == nil {
logger.Debugf("Skipping %s as it is not in the stash library", path)
return false
}

isVideoFile := useAsVideo(path)
isImageFile := useAsImage(path)
isZipFile := fsutil.MatchExtension(path, f.zipExt)
Expand All @@ -288,13 +294,6 @@ func (f *scanFilter) Accept(ctx context.Context, path string, info fs.FileInfo)
return false
}

s := f.stashPaths.GetStashFromDirPath(path)

if s == nil {
logger.Debugf("Skipping %s as it is not in the stash library", path)
return false
}

// shortcut: skip the directory entirely if it matches both exclusion patterns
// add a trailing separator so that it correctly matches against patterns like path/.*
pathExcludeTest := path + string(filepath.Separator)
Expand Down
10 changes: 8 additions & 2 deletions pkg/file/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -867,16 +867,22 @@ func (s *scanJob) handleRename(ctx context.Context, f models.File, fp []models.F
continue
}

if _, err := fs.Lstat(other.Base().Path); err != nil {
info, err := fs.Lstat(other.Base().Path)
switch {
case err != nil:
missing = append(missing, other)
} else if strings.EqualFold(f.Base().Path, other.Base().Path) {
case strings.EqualFold(f.Base().Path, other.Base().Path):
// #1426 - if file exists but is a case-insensitive match for the
// original filename, and the filesystem is case-insensitive
// then treat it as a move
if caseSensitive, _ := fs.IsPathCaseSensitive(other.Base().Path); !caseSensitive {
// treat as a move
missing = append(missing, other)
}
case !s.acceptEntry(ctx, other.Base().Path, info):
// #4393 - if the file is no longer in the configured library paths, treat it as a move
logger.Debugf("File %q no longer in library paths. Treating as a move.", other.Base().Path)
missing = append(missing, other)
}
}

Expand Down

0 comments on commit 76e5598

Please sign in to comment.