FS: Improve matching of related media files #2983
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
a912383ede
commit
54f281a425
8 changed files with 500 additions and 368 deletions
|
@ -305,19 +305,6 @@ func (m *MediaFile) Checksum() string {
|
||||||
return m.checksum
|
return m.checksum
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditedName returns the corresponding edited image file name as used by Apple (e.g. IMG_E12345.JPG).
|
|
||||||
func (m *MediaFile) EditedName() string {
|
|
||||||
basename := filepath.Base(m.fileName)
|
|
||||||
|
|
||||||
if strings.ToUpper(basename[:4]) == "IMG_" && strings.ToUpper(basename[:5]) != "IMG_E" {
|
|
||||||
if filename := filepath.Dir(m.fileName) + string(os.PathSeparator) + basename[:4] + "E" + basename[4:]; fs.FileExists(filename) {
|
|
||||||
return filename
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathNameInfo returns file name infos for indexing.
|
// PathNameInfo returns file name infos for indexing.
|
||||||
func (m *MediaFile) PathNameInfo(stripSequence bool) (fileRoot, fileBase, relativePath, relativeName string) {
|
func (m *MediaFile) PathNameInfo(stripSequence bool) (fileRoot, fileBase, relativePath, relativeName string) {
|
||||||
fileRoot = m.Root()
|
fileRoot = m.Root()
|
||||||
|
@ -444,11 +431,29 @@ func (m *MediaFile) SubDir(dir string) string {
|
||||||
return filepath.Join(filepath.Dir(m.fileName), dir)
|
return filepath.Join(filepath.Dir(m.fileName), dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AbsPrefix returns the directory and base filename without any extensions.
|
||||||
|
func (m *MediaFile) AbsPrefix(stripSequence bool) string {
|
||||||
|
return fs.AbsPrefix(m.FileName(), stripSequence)
|
||||||
|
}
|
||||||
|
|
||||||
// BasePrefix returns the filename base without any extensions and path.
|
// BasePrefix returns the filename base without any extensions and path.
|
||||||
func (m *MediaFile) BasePrefix(stripSequence bool) string {
|
func (m *MediaFile) BasePrefix(stripSequence bool) string {
|
||||||
return fs.BasePrefix(m.FileName(), stripSequence)
|
return fs.BasePrefix(m.FileName(), stripSequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EditedName returns the corresponding edited image file name as used by Apple (e.g. IMG_E12345.JPG).
|
||||||
|
func (m *MediaFile) EditedName() string {
|
||||||
|
basename := filepath.Base(m.fileName)
|
||||||
|
|
||||||
|
if strings.ToUpper(basename[:4]) == "IMG_" && strings.ToUpper(basename[:5]) != "IMG_E" {
|
||||||
|
if filename := filepath.Dir(m.fileName) + string(os.PathSeparator) + basename[:4] + "E" + basename[4:]; fs.FileExists(filename) {
|
||||||
|
return filename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// Root returns the file root directory.
|
// Root returns the file root directory.
|
||||||
func (m *MediaFile) Root() string {
|
func (m *MediaFile) Root() string {
|
||||||
if m.fileRoot != entity.RootUnknown {
|
if m.fileRoot != entity.RootUnknown {
|
||||||
|
@ -484,11 +489,6 @@ func (m *MediaFile) Root() string {
|
||||||
return m.fileRoot
|
return m.fileRoot
|
||||||
}
|
}
|
||||||
|
|
||||||
// AbsPrefix returns the directory and base filename without any extensions.
|
|
||||||
func (m *MediaFile) AbsPrefix(stripSequence bool) string {
|
|
||||||
return fs.AbsPrefix(m.FileName(), stripSequence)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MimeType returns the mime type.
|
// MimeType returns the mime type.
|
||||||
func (m *MediaFile) MimeType() string {
|
func (m *MediaFile) MimeType() string {
|
||||||
if m.mimeType != "" {
|
if m.mimeType != "" {
|
||||||
|
|
|
@ -11,10 +11,16 @@ import (
|
||||||
"github.com/photoprism/photoprism/pkg/fs"
|
"github.com/photoprism/photoprism/pkg/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RelatedFilePathPrefix returns the absolute file path and name prefix without file extensions
|
||||||
|
// and suffixes to be ignored.
|
||||||
|
func (m *MediaFile) RelatedFilePathPrefix(stripSequence bool) (s string) {
|
||||||
|
return fs.RelatedFilePathPrefix(m.FileName(), stripSequence)
|
||||||
|
}
|
||||||
|
|
||||||
// RelatedFiles returns files which are related to this file.
|
// RelatedFiles returns files which are related to this file.
|
||||||
func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err error) {
|
func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err error) {
|
||||||
// File path and name without any extensions.
|
// Related file path prefix without ignored file name extensions and suffixes.
|
||||||
prefix := m.AbsPrefix(stripSequence)
|
filePathPrefix := m.RelatedFilePathPrefix(stripSequence)
|
||||||
|
|
||||||
// Storage folder path prefixes.
|
// Storage folder path prefixes.
|
||||||
sidecarPrefix := Config().SidecarPath() + "/"
|
sidecarPrefix := Config().SidecarPath() + "/"
|
||||||
|
@ -30,34 +36,36 @@ func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err e
|
||||||
skipVectors := Config().DisableVectors()
|
skipVectors := Config().DisableVectors()
|
||||||
|
|
||||||
// Replace sidecar with originals path in search prefix.
|
// Replace sidecar with originals path in search prefix.
|
||||||
if len(sidecarPrefix) > 1 && sidecarPrefix != originalsPrefix && strings.HasPrefix(prefix, sidecarPrefix) {
|
if len(sidecarPrefix) > 1 && sidecarPrefix != originalsPrefix && strings.HasPrefix(filePathPrefix, sidecarPrefix) {
|
||||||
prefix = strings.Replace(prefix, sidecarPrefix, originalsPrefix, 1)
|
filePathPrefix = strings.Replace(filePathPrefix, sidecarPrefix, originalsPrefix, 1)
|
||||||
log.Debugf("media: replaced sidecar with originals path in related file matching pattern")
|
log.Debugf("media: replaced sidecar with originals path in related file matching pattern")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quote path for glob.
|
// globPattern specifies the escaped naming pattern to find related files.
|
||||||
|
var globPattern string
|
||||||
|
|
||||||
|
// Strip common name sequences like "copy 2" or "(3)"?
|
||||||
if stripSequence {
|
if stripSequence {
|
||||||
// Strip common name sequences like "copy 2" and escape meta characters.
|
globPattern = regexp.QuoteMeta(filePathPrefix) + "*"
|
||||||
prefix = regexp.QuoteMeta(prefix)
|
|
||||||
} else {
|
} else {
|
||||||
// Use strict file name matching and escape meta characters.
|
globPattern = regexp.QuoteMeta(filePathPrefix+".") + "*"
|
||||||
prefix = regexp.QuoteMeta(prefix + ".")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find related files.
|
// Find files that match the pattern.
|
||||||
matches, err := filepath.Glob(prefix + "*")
|
matches, err := filepath.Glob(globPattern)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for related edited image file name (as used by Apple) and add it to the list of files, if found.
|
// Additionally include edited version in the file matches, if exists.
|
||||||
if name := m.EditedName(); name != "" {
|
if name := m.EditedName(); name != "" {
|
||||||
matches = append(matches, name)
|
matches = append(matches, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
isHEIC := false
|
isHEIC := false
|
||||||
|
|
||||||
|
// Process files that matched the pattern.
|
||||||
for _, fileName := range matches {
|
for _, fileName := range matches {
|
||||||
f, fileErr := NewMediaFile(fileName)
|
f, fileErr := NewMediaFile(fileName)
|
||||||
|
|
||||||
|
@ -65,7 +73,7 @@ func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err e
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore file format?
|
// Skip file if its format must be ignored based on the configuration.
|
||||||
switch {
|
switch {
|
||||||
case skipRaw && f.IsRaw():
|
case skipRaw && f.IsRaw():
|
||||||
log.Debugf("media: skipped related raw image %s", clean.Log(f.RootRelName()))
|
log.Debugf("media: skipped related raw image %s", clean.Log(f.RootRelName()))
|
||||||
|
|
256
internal/photoprism/mediafile_related_test.go
Normal file
256
internal/photoprism/mediafile_related_test.go
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
package photoprism
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMediaFile_RelatedFilePathPrefix(t *testing.T) {
|
||||||
|
t.Run("IMG_1234_HEVC.JPEG", func(t *testing.T) {
|
||||||
|
fileName := "testdata/related/IMG_1234_HEVC (3).JPEG"
|
||||||
|
f, err := NewMediaFile(fileName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, fileName, f.FileName())
|
||||||
|
assert.Equal(t, "testdata/related/IMG_1234_HEVC", f.AbsPrefix(true))
|
||||||
|
assert.Equal(t, "testdata/related/IMG_1234_HEVC (3)", f.AbsPrefix(false))
|
||||||
|
assert.Equal(t, "testdata/related/IMG_1234", f.RelatedFilePathPrefix(true))
|
||||||
|
assert.Equal(t, "testdata/related/IMG_1234_HEVC (3)", f.RelatedFilePathPrefix(false))
|
||||||
|
})
|
||||||
|
t.Run("fern_green.jpg", func(t *testing.T) {
|
||||||
|
f, err := NewMediaFile(conf.ExamplesPath() + "/fern_green.jpg")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := conf.ExamplesPath() + "/fern_green"
|
||||||
|
|
||||||
|
assert.Equal(t, expected, f.RelatedFilePathPrefix(true))
|
||||||
|
assert.Equal(t, expected, f.RelatedFilePathPrefix(false))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMediaFile_RelatedFiles(t *testing.T) {
|
||||||
|
c := config.TestConfig()
|
||||||
|
|
||||||
|
t.Run("example.tif", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/example.tif")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
related, err := mediaFile.RelatedFiles(true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Len(t, related.Files, 6)
|
||||||
|
assert.True(t, related.HasPreview())
|
||||||
|
|
||||||
|
for _, result := range related.Files {
|
||||||
|
t.Logf("FileName: %s", result.FileName())
|
||||||
|
|
||||||
|
filename := result.FileName()
|
||||||
|
|
||||||
|
if len(filename) < 2 {
|
||||||
|
t.Fatalf("filename not be longer: %s", filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension := result.Extension()
|
||||||
|
|
||||||
|
if len(extension) < 2 {
|
||||||
|
t.Fatalf("extension should be longer: %s", extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
relativePath := result.RelPath(c.ExamplesPath())
|
||||||
|
|
||||||
|
if len(relativePath) > 0 {
|
||||||
|
t.Fatalf("relative path should be empty: %s", relativePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("canon_eos_6d.dng", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/canon_eos_6d.dng")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedBaseFilename := c.ExamplesPath() + "/canon_eos_6d"
|
||||||
|
|
||||||
|
related, err := mediaFile.RelatedFiles(true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Len(t, related.Files, 3)
|
||||||
|
assert.False(t, related.HasPreview())
|
||||||
|
|
||||||
|
for _, result := range related.Files {
|
||||||
|
t.Logf("FileName: %s", result.FileName())
|
||||||
|
|
||||||
|
filename := result.FileName()
|
||||||
|
|
||||||
|
extension := result.Extension()
|
||||||
|
|
||||||
|
baseFilename := filename[0 : len(filename)-len(extension)]
|
||||||
|
|
||||||
|
assert.Equal(t, expectedBaseFilename, baseFilename)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("iphone_7.heic", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/iphone_7.heic")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedBaseFilename := c.ExamplesPath() + "/iphone_7"
|
||||||
|
|
||||||
|
related, err := mediaFile.RelatedFiles(true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.GreaterOrEqual(t, len(related.Files), 3)
|
||||||
|
|
||||||
|
for _, result := range related.Files {
|
||||||
|
t.Logf("FileName: %s", result.FileName())
|
||||||
|
|
||||||
|
filename := result.FileName()
|
||||||
|
extension := result.Extension()
|
||||||
|
baseFilename := filename[0 : len(filename)-len(extension)]
|
||||||
|
|
||||||
|
if result.IsJpeg() {
|
||||||
|
assert.Contains(t, expectedBaseFilename, "examples/iphone_7")
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, expectedBaseFilename, baseFilename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("2015-02-04.jpg", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile("testdata/2015-02-04.jpg")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
related, err := mediaFile.RelatedFiles(true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if related.Main == nil {
|
||||||
|
t.Fatal("main file must not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(related.Files) != 4 {
|
||||||
|
t.Fatalf("length is %d, should be 4", len(related.Files))
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("FILE: %s, %s", related.Main.FileType(), related.Main.MimeType())
|
||||||
|
|
||||||
|
assert.Equal(t, "2015-02-04.jpg", related.Main.BaseName())
|
||||||
|
|
||||||
|
assert.Equal(t, "2015-02-04.jpg", related.Files[0].BaseName())
|
||||||
|
assert.Equal(t, "2015-02-04(1).jpg", related.Files[1].BaseName())
|
||||||
|
assert.Equal(t, "2015-02-04.jpg.json", related.Files[2].BaseName())
|
||||||
|
assert.Equal(t, "2015-02-04.jpg(1).json", related.Files[3].BaseName())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("2015-02-04(1).jpg", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile("testdata/2015-02-04(1).jpg")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
related, err := mediaFile.RelatedFiles(false)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if related.Main == nil {
|
||||||
|
t.Fatal("main file must not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(related.Files) != 1 {
|
||||||
|
t.Fatalf("length is %d, should be 1", len(related.Files))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "2015-02-04(1).jpg", related.Main.BaseName())
|
||||||
|
|
||||||
|
assert.Equal(t, "2015-02-04(1).jpg", related.Files[0].BaseName())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("2015-02-04(1).jpg stacked", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile("testdata/2015-02-04(1).jpg")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
related, err := mediaFile.RelatedFiles(true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if related.Main == nil {
|
||||||
|
t.Fatal("main file must not be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(related.Files) != 4 {
|
||||||
|
t.Fatalf("length is %d, should be 4", len(related.Files))
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "2015-02-04.jpg", related.Main.BaseName())
|
||||||
|
|
||||||
|
assert.Equal(t, "2015-02-04.jpg", related.Files[0].BaseName())
|
||||||
|
assert.Equal(t, "2015-02-04(1).jpg", related.Files[1].BaseName())
|
||||||
|
assert.Equal(t, "2015-02-04.jpg.json", related.Files[2].BaseName())
|
||||||
|
assert.Equal(t, "2015-02-04.jpg(1).json", related.Files[3].BaseName())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMediaFile_RelatedFiles_Ordering(t *testing.T) {
|
||||||
|
c := config.TestConfig()
|
||||||
|
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/IMG_4120.JPG")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
related, err := mediaFile.RelatedFiles(true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Len(t, related.Files, 5)
|
||||||
|
|
||||||
|
assert.Equal(t, c.ExamplesPath()+"/IMG_4120.AAE", related.Files[0].FileName())
|
||||||
|
assert.Equal(t, c.ExamplesPath()+"/IMG_4120.JPG", related.Files[1].FileName())
|
||||||
|
|
||||||
|
for _, result := range related.Files {
|
||||||
|
filename := result.FileName()
|
||||||
|
t.Logf("FileName: %s", filename)
|
||||||
|
}
|
||||||
|
}
|
|
@ -293,19 +293,17 @@ func TestMediaFile_LensMake(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMediaFile_FocalLength(t *testing.T) {
|
func TestMediaFile_FocalLength(t *testing.T) {
|
||||||
t.Run("/cat_brown.jpg", func(t *testing.T) {
|
c := config.TestConfig()
|
||||||
conf := config.TestConfig()
|
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/cat_brown.jpg")
|
t.Run("/cat_brown.jpg", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/cat_brown.jpg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, 29, mediaFile.FocalLength())
|
assert.Equal(t, 29, mediaFile.FocalLength())
|
||||||
})
|
})
|
||||||
t.Run("/elephants.jpg", func(t *testing.T) {
|
t.Run("/elephants.jpg", func(t *testing.T) {
|
||||||
conf := config.TestConfig()
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/elephants.jpg")
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -314,19 +312,17 @@ func TestMediaFile_FocalLength(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMediaFile_FNumber(t *testing.T) {
|
func TestMediaFile_FNumber(t *testing.T) {
|
||||||
t.Run("/cat_brown.jpg", func(t *testing.T) {
|
c := config.TestConfig()
|
||||||
conf := config.TestConfig()
|
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/cat_brown.jpg")
|
t.Run("/cat_brown.jpg", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/cat_brown.jpg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, float32(2.2), mediaFile.FNumber())
|
assert.Equal(t, float32(2.2), mediaFile.FNumber())
|
||||||
})
|
})
|
||||||
t.Run("/elephants.jpg", func(t *testing.T) {
|
t.Run("/elephants.jpg", func(t *testing.T) {
|
||||||
conf := config.TestConfig()
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/elephants.jpg")
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -335,19 +331,17 @@ func TestMediaFile_FNumber(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMediaFile_Iso(t *testing.T) {
|
func TestMediaFile_Iso(t *testing.T) {
|
||||||
t.Run("/cat_brown.jpg", func(t *testing.T) {
|
c := config.TestConfig()
|
||||||
conf := config.TestConfig()
|
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/cat_brown.jpg")
|
t.Run("/cat_brown.jpg", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/cat_brown.jpg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, 32, mediaFile.Iso())
|
assert.Equal(t, 32, mediaFile.Iso())
|
||||||
})
|
})
|
||||||
t.Run("/elephants.jpg", func(t *testing.T) {
|
t.Run("/elephants.jpg", func(t *testing.T) {
|
||||||
conf := config.TestConfig()
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/elephants.jpg")
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -356,19 +350,18 @@ func TestMediaFile_Iso(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMediaFile_Exposure(t *testing.T) {
|
func TestMediaFile_Exposure(t *testing.T) {
|
||||||
t.Run("/cat_brown.jpg", func(t *testing.T) {
|
c := config.TestConfig()
|
||||||
conf := config.TestConfig()
|
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/cat_brown.jpg")
|
t.Run("/cat_brown.jpg", func(t *testing.T) {
|
||||||
|
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/cat_brown.jpg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, "1/50", mediaFile.Exposure())
|
assert.Equal(t, "1/50", mediaFile.Exposure())
|
||||||
})
|
})
|
||||||
t.Run("/elephants.jpg", func(t *testing.T) {
|
t.Run("/elephants.jpg", func(t *testing.T) {
|
||||||
conf := config.TestConfig()
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/elephants.jpg")
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/elephants.jpg")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -377,9 +370,9 @@ func TestMediaFile_Exposure(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMediaFileCanonicalName(t *testing.T) {
|
func TestMediaFileCanonicalName(t *testing.T) {
|
||||||
conf := config.TestConfig()
|
c := config.TestConfig()
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/beach_wood.jpg")
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/beach_wood.jpg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -408,28 +401,27 @@ func TestMediaFileCanonicalNameFromFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMediaFile_CanonicalNameFromFileWithDirectory(t *testing.T) {
|
func TestMediaFile_CanonicalNameFromFileWithDirectory(t *testing.T) {
|
||||||
conf := config.TestConfig()
|
c := config.TestConfig()
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/beach_wood.jpg")
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/beach_wood.jpg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, conf.ExamplesPath()+"/beach_wood", mediaFile.CanonicalNameFromFileWithDirectory())
|
assert.Equal(t, c.ExamplesPath()+"/beach_wood", mediaFile.CanonicalNameFromFileWithDirectory())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMediaFile_EditedFilename(t *testing.T) {
|
func TestMediaFile_EditedFilename(t *testing.T) {
|
||||||
conf := config.TestConfig()
|
c := config.TestConfig()
|
||||||
|
|
||||||
t.Run("IMG_4120.JPG", func(t *testing.T) {
|
t.Run("IMG_4120.JPG", func(t *testing.T) {
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120.JPG")
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/IMG_4120.JPG")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, conf.ExamplesPath()+"/IMG_E4120.JPG", mediaFile.EditedName())
|
assert.Equal(t, c.ExamplesPath()+"/IMG_E4120.JPG", mediaFile.EditedName())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("fern_green.jpg", func(t *testing.T) {
|
t.Run("fern_green.jpg", func(t *testing.T) {
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/fern_green.jpg")
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/fern_green.jpg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -437,228 +429,10 @@ func TestMediaFile_EditedFilename(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMediaFile_RelatedFiles(t *testing.T) {
|
|
||||||
conf := config.TestConfig()
|
|
||||||
|
|
||||||
t.Run("example.tif", func(t *testing.T) {
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/example.tif")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
related, err := mediaFile.RelatedFiles(true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Len(t, related.Files, 6)
|
|
||||||
assert.True(t, related.HasPreview())
|
|
||||||
|
|
||||||
for _, result := range related.Files {
|
|
||||||
t.Logf("FileName: %s", result.FileName())
|
|
||||||
|
|
||||||
filename := result.FileName()
|
|
||||||
|
|
||||||
if len(filename) < 2 {
|
|
||||||
t.Fatalf("filename not be longer: %s", filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension := result.Extension()
|
|
||||||
|
|
||||||
if len(extension) < 2 {
|
|
||||||
t.Fatalf("extension should be longer: %s", extension)
|
|
||||||
}
|
|
||||||
|
|
||||||
relativePath := result.RelPath(conf.ExamplesPath())
|
|
||||||
|
|
||||||
if len(relativePath) > 0 {
|
|
||||||
t.Fatalf("relative path should be empty: %s", relativePath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("canon_eos_6d.dng", func(t *testing.T) {
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/canon_eos_6d.dng")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedBaseFilename := conf.ExamplesPath() + "/canon_eos_6d"
|
|
||||||
|
|
||||||
related, err := mediaFile.RelatedFiles(true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Len(t, related.Files, 3)
|
|
||||||
assert.False(t, related.HasPreview())
|
|
||||||
|
|
||||||
for _, result := range related.Files {
|
|
||||||
t.Logf("FileName: %s", result.FileName())
|
|
||||||
|
|
||||||
filename := result.FileName()
|
|
||||||
|
|
||||||
extension := result.Extension()
|
|
||||||
|
|
||||||
baseFilename := filename[0 : len(filename)-len(extension)]
|
|
||||||
|
|
||||||
assert.Equal(t, expectedBaseFilename, baseFilename)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("iphone_7.heic", func(t *testing.T) {
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/iphone_7.heic")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedBaseFilename := conf.ExamplesPath() + "/iphone_7"
|
|
||||||
|
|
||||||
related, err := mediaFile.RelatedFiles(true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.GreaterOrEqual(t, len(related.Files), 3)
|
|
||||||
|
|
||||||
for _, result := range related.Files {
|
|
||||||
t.Logf("FileName: %s", result.FileName())
|
|
||||||
|
|
||||||
filename := result.FileName()
|
|
||||||
extension := result.Extension()
|
|
||||||
baseFilename := filename[0 : len(filename)-len(extension)]
|
|
||||||
|
|
||||||
if result.IsJpeg() {
|
|
||||||
assert.Contains(t, expectedBaseFilename, "examples/iphone_7")
|
|
||||||
} else {
|
|
||||||
assert.Equal(t, expectedBaseFilename, baseFilename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("2015-02-04.jpg", func(t *testing.T) {
|
|
||||||
mediaFile, err := NewMediaFile("testdata/2015-02-04.jpg")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
related, err := mediaFile.RelatedFiles(true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if related.Main == nil {
|
|
||||||
t.Fatal("main file must not be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(related.Files) != 4 {
|
|
||||||
t.Fatalf("length is %d, should be 4", len(related.Files))
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Logf("FILE: %s, %s", related.Main.FileType(), related.Main.MimeType())
|
|
||||||
|
|
||||||
assert.Equal(t, "2015-02-04.jpg", related.Main.BaseName())
|
|
||||||
|
|
||||||
assert.Equal(t, "2015-02-04.jpg", related.Files[0].BaseName())
|
|
||||||
assert.Equal(t, "2015-02-04(1).jpg", related.Files[1].BaseName())
|
|
||||||
assert.Equal(t, "2015-02-04.jpg.json", related.Files[2].BaseName())
|
|
||||||
assert.Equal(t, "2015-02-04.jpg(1).json", related.Files[3].BaseName())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("2015-02-04(1).jpg", func(t *testing.T) {
|
|
||||||
mediaFile, err := NewMediaFile("testdata/2015-02-04(1).jpg")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
related, err := mediaFile.RelatedFiles(false)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if related.Main == nil {
|
|
||||||
t.Fatal("main file must not be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(related.Files) != 1 {
|
|
||||||
t.Fatalf("length is %d, should be 1", len(related.Files))
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "2015-02-04(1).jpg", related.Main.BaseName())
|
|
||||||
|
|
||||||
assert.Equal(t, "2015-02-04(1).jpg", related.Files[0].BaseName())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("2015-02-04(1).jpg stacked", func(t *testing.T) {
|
|
||||||
mediaFile, err := NewMediaFile("testdata/2015-02-04(1).jpg")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
related, err := mediaFile.RelatedFiles(true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if related.Main == nil {
|
|
||||||
t.Fatal("main file must not be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(related.Files) != 4 {
|
|
||||||
t.Fatalf("length is %d, should be 4", len(related.Files))
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "2015-02-04.jpg", related.Main.BaseName())
|
|
||||||
|
|
||||||
assert.Equal(t, "2015-02-04.jpg", related.Files[0].BaseName())
|
|
||||||
assert.Equal(t, "2015-02-04(1).jpg", related.Files[1].BaseName())
|
|
||||||
assert.Equal(t, "2015-02-04.jpg.json", related.Files[2].BaseName())
|
|
||||||
assert.Equal(t, "2015-02-04.jpg(1).json", related.Files[3].BaseName())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMediaFile_RelatedFiles_Ordering(t *testing.T) {
|
|
||||||
conf := config.TestConfig()
|
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120.JPG")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
related, err := mediaFile.RelatedFiles(true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Len(t, related.Files, 5)
|
|
||||||
|
|
||||||
assert.Equal(t, conf.ExamplesPath()+"/IMG_4120.AAE", related.Files[0].FileName())
|
|
||||||
assert.Equal(t, conf.ExamplesPath()+"/IMG_4120.JPG", related.Files[1].FileName())
|
|
||||||
|
|
||||||
for _, result := range related.Files {
|
|
||||||
filename := result.FileName()
|
|
||||||
t.Logf("FileName: %s", filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMediaFile_SetFilename(t *testing.T) {
|
func TestMediaFile_SetFilename(t *testing.T) {
|
||||||
conf := config.TestConfig()
|
c := config.TestConfig()
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/turtle_brown_blue.jpg")
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/turtle_brown_blue.jpg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -824,29 +598,57 @@ func TestMediaFile_Directory(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMediaFile_Basename(t *testing.T) {
|
func TestMediaFile_AbsPrefix(t *testing.T) {
|
||||||
t.Run("/limes.jpg", func(t *testing.T) {
|
c := config.TestConfig()
|
||||||
conf := config.TestConfig()
|
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/limes.jpg")
|
t.Run("/limes.jpg", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/limes.jpg")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := c.ExamplesPath() + "/limes"
|
||||||
|
assert.Equal(t, expected, mediaFile.AbsPrefix(true))
|
||||||
|
})
|
||||||
|
t.Run("/IMG_4120 copy.JPG", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/IMG_4120 copy.JPG")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := c.ExamplesPath() + "/IMG_4120"
|
||||||
|
assert.Equal(t, expected, mediaFile.AbsPrefix(true))
|
||||||
|
})
|
||||||
|
t.Run("/IMG_4120 (1).JPG", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/IMG_4120 (1).JPG")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := c.ExamplesPath() + "/IMG_4120"
|
||||||
|
assert.Equal(t, expected, mediaFile.AbsPrefix(true))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMediaFile_BasePrefix(t *testing.T) {
|
||||||
|
c := config.TestConfig()
|
||||||
|
|
||||||
|
t.Run("/limes.jpg", func(t *testing.T) {
|
||||||
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/limes.jpg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, "limes", mediaFile.BasePrefix(true))
|
assert.Equal(t, "limes", mediaFile.BasePrefix(true))
|
||||||
})
|
})
|
||||||
t.Run("/IMG_4120 copy.JPG", func(t *testing.T) {
|
t.Run("/IMG_4120 copy.JPG", func(t *testing.T) {
|
||||||
conf := config.TestConfig()
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/IMG_4120 copy.JPG")
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120 copy.JPG")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, "IMG_4120", mediaFile.BasePrefix(true))
|
assert.Equal(t, "IMG_4120", mediaFile.BasePrefix(true))
|
||||||
})
|
})
|
||||||
t.Run("/IMG_4120 (1).JPG", func(t *testing.T) {
|
t.Run("/IMG_4120 (1).JPG", func(t *testing.T) {
|
||||||
conf := config.TestConfig()
|
mediaFile, err := NewMediaFile(c.ExamplesPath() + "/IMG_4120 (1).JPG")
|
||||||
|
|
||||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120 (1).JPG")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
BIN
internal/photoprism/testdata/related/IMG_1234_HEVC (3).JPEG
vendored
Normal file
BIN
internal/photoprism/testdata/related/IMG_1234_HEVC (3).JPEG
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
|
@ -1,55 +0,0 @@
|
||||||
package fs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StripSequence removes common sequence patterns at the end of file names.
|
|
||||||
func StripSequence(name string) string {
|
|
||||||
// Strip numeric extensions like .00000, .00001, .4542353245,.... (at least 5 digits).
|
|
||||||
if dot := strings.LastIndex(name, "."); dot != -1 && len(name[dot+1:]) >= 5 {
|
|
||||||
if i, err := strconv.Atoi(name[dot+1:]); err == nil && i >= 0 {
|
|
||||||
name = name[:dot]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Other common sequential naming schemes.
|
|
||||||
if end := strings.Index(name, "("); end != -1 {
|
|
||||||
// Copies created by Chrome & Windows, example: IMG_1234 (2).
|
|
||||||
name = name[:end]
|
|
||||||
} else if end := strings.Index(name, " copy"); end != -1 {
|
|
||||||
// Copies created by OS X, example: IMG_1234 copy 2.
|
|
||||||
name = name[:end]
|
|
||||||
}
|
|
||||||
|
|
||||||
name = strings.TrimSpace(name)
|
|
||||||
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
// BasePrefix returns the filename base without any extensions and path.
|
|
||||||
func BasePrefix(fileName string, stripSequence bool) string {
|
|
||||||
name := StripKnownExt(StripExt(filepath.Base(fileName)))
|
|
||||||
|
|
||||||
if !stripSequence {
|
|
||||||
return name
|
|
||||||
}
|
|
||||||
|
|
||||||
return StripSequence(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RelPrefix returns the relative filename.
|
|
||||||
func RelPrefix(fileName, dir string, stripSequence bool) string {
|
|
||||||
if name := RelName(fileName, dir); name != "" {
|
|
||||||
return AbsPrefix(name, stripSequence)
|
|
||||||
}
|
|
||||||
|
|
||||||
return BasePrefix(fileName, stripSequence)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AbsPrefix returns the directory and base filename without any extensions.
|
|
||||||
func AbsPrefix(fileName string, stripSequence bool) string {
|
|
||||||
return filepath.Join(filepath.Dir(fileName), BasePrefix(fileName, stripSequence))
|
|
||||||
}
|
|
78
pkg/fs/filepath.go
Normal file
78
pkg/fs/filepath.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RelatedMediaFileSuffix is a regular expression that matches suffixes of related media files,
|
||||||
|
// see https://github.com/photoprism/photoprism/issues/2983 (Support Live Photos downloaded with "iCloudPD").
|
||||||
|
var RelatedMediaFileSuffix = regexp.MustCompile(`(?i)_(jpg|jpeg|hevc)$`)
|
||||||
|
|
||||||
|
// StripSequence removes common sequence patterns at the end of file names.
|
||||||
|
func StripSequence(fileName string) string {
|
||||||
|
if fileName == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip numeric extensions like .00000, .00001, .4542353245,.... (at least 5 digits).
|
||||||
|
if dot := strings.LastIndex(fileName, "."); dot != -1 && len(fileName[dot+1:]) >= 5 {
|
||||||
|
if i, err := strconv.Atoi(fileName[dot+1:]); err == nil && i >= 0 {
|
||||||
|
fileName = fileName[:dot]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other common sequential naming schemes.
|
||||||
|
if end := strings.Index(fileName, "("); end != -1 {
|
||||||
|
// Copies created by Chrome & Windows, example: IMG_1234 (2).
|
||||||
|
fileName = fileName[:end]
|
||||||
|
} else if end := strings.Index(fileName, " copy"); end != -1 {
|
||||||
|
// Copies created by OS X, example: IMG_1234 copy 2.
|
||||||
|
fileName = fileName[:end]
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName = strings.TrimSpace(fileName)
|
||||||
|
|
||||||
|
return fileName
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasePrefix returns the filename base without any extensions and path.
|
||||||
|
func BasePrefix(fileName string, stripSequence bool) string {
|
||||||
|
fileBase := StripKnownExt(StripExt(filepath.Base(fileName)))
|
||||||
|
|
||||||
|
if !stripSequence {
|
||||||
|
return fileBase
|
||||||
|
}
|
||||||
|
|
||||||
|
return StripSequence(fileBase)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelPrefix returns the relative filename.
|
||||||
|
func RelPrefix(fileName, dir string, stripSequence bool) string {
|
||||||
|
if name := RelName(fileName, dir); name != "" {
|
||||||
|
return AbsPrefix(name, stripSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
return BasePrefix(fileName, stripSequence)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AbsPrefix returns the directory and base filename without any extensions.
|
||||||
|
func AbsPrefix(fileName string, stripSequence bool) string {
|
||||||
|
if fileName == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Join(filepath.Dir(fileName), BasePrefix(fileName, stripSequence))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelatedFilePathPrefix returns the absolute file path and name prefix without file extensions and media file
|
||||||
|
// suffixes to be ignored for comparison, see https://github.com/photoprism/photoprism/issues/2983.
|
||||||
|
func RelatedFilePathPrefix(fileName string, stripSequence bool) string {
|
||||||
|
if fileName == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return RelatedMediaFileSuffix.ReplaceAllString(AbsPrefix(fileName, stripSequence), "")
|
||||||
|
}
|
|
@ -6,39 +6,37 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBase(t *testing.T) {
|
func TestBasePrefix(t *testing.T) {
|
||||||
|
t.Run("Empty", func(t *testing.T) {
|
||||||
|
assert.Equal(t, "", BasePrefix("", true))
|
||||||
|
assert.Equal(t, "", BasePrefix("", false))
|
||||||
|
})
|
||||||
t.Run("Screenshot 2019-05-21 at 10.45.52.png", func(t *testing.T) {
|
t.Run("Screenshot 2019-05-21 at 10.45.52.png", func(t *testing.T) {
|
||||||
regular := BasePrefix("Screenshot 2019-05-21 at 10.45.52.png", false)
|
regular := BasePrefix("Screenshot 2019-05-21 at 10.45.52.png", false)
|
||||||
assert.Equal(t, "Screenshot 2019-05-21 at 10.45.52", regular)
|
assert.Equal(t, "Screenshot 2019-05-21 at 10.45.52", regular)
|
||||||
stripped := BasePrefix("Screenshot 2019-05-21 at 10.45.52.png", true)
|
stripped := BasePrefix("Screenshot 2019-05-21 at 10.45.52.png", true)
|
||||||
assert.Equal(t, "Screenshot 2019-05-21 at 10.45.52", stripped)
|
assert.Equal(t, "Screenshot 2019-05-21 at 10.45.52", stripped)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test.jpg", func(t *testing.T) {
|
t.Run("Test.jpg", func(t *testing.T) {
|
||||||
result := BasePrefix("/testdata/Test.jpg", true)
|
result := BasePrefix("/testdata/Test.jpg", true)
|
||||||
assert.Equal(t, "Test", result)
|
assert.Equal(t, "Test", result)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test.jpg.json", func(t *testing.T) {
|
t.Run("Test.jpg.json", func(t *testing.T) {
|
||||||
result := BasePrefix("/testdata/Test.jpg.json", true)
|
result := BasePrefix("/testdata/Test.jpg.json", true)
|
||||||
assert.Equal(t, "Test", result)
|
assert.Equal(t, "Test", result)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
||||||
result := BasePrefix("/testdata/Test copy 3.jpg", true)
|
result := BasePrefix("/testdata/Test copy 3.jpg", true)
|
||||||
assert.Equal(t, "Test", result)
|
assert.Equal(t, "Test", result)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test (3).jpg", func(t *testing.T) {
|
t.Run("Test (3).jpg", func(t *testing.T) {
|
||||||
result := BasePrefix("/testdata/Test (3).jpg", true)
|
result := BasePrefix("/testdata/Test (3).jpg", true)
|
||||||
assert.Equal(t, "Test", result)
|
assert.Equal(t, "Test", result)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test.jpg", func(t *testing.T) {
|
t.Run("Test.jpg", func(t *testing.T) {
|
||||||
result := BasePrefix("/testdata/Test.jpg", false)
|
result := BasePrefix("/testdata/Test.jpg", false)
|
||||||
assert.Equal(t, "Test", result)
|
assert.Equal(t, "Test", result)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test.3453453.jpg", func(t *testing.T) {
|
t.Run("Test.3453453.jpg", func(t *testing.T) {
|
||||||
regular := BasePrefix("/testdata/Test.3453453.jpg", false)
|
regular := BasePrefix("/testdata/Test.3453453.jpg", false)
|
||||||
assert.Equal(t, "Test.3453453", regular)
|
assert.Equal(t, "Test.3453453", regular)
|
||||||
|
@ -46,7 +44,6 @@ func TestBase(t *testing.T) {
|
||||||
stripped := BasePrefix("/testdata/Test.3453453.jpg", true)
|
stripped := BasePrefix("/testdata/Test.3453453.jpg", true)
|
||||||
assert.Equal(t, "Test", stripped)
|
assert.Equal(t, "Test", stripped)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("/foo/bar.0000.ZIP", func(t *testing.T) {
|
t.Run("/foo/bar.0000.ZIP", func(t *testing.T) {
|
||||||
regular := BasePrefix("/foo/bar.0000.ZIP", false)
|
regular := BasePrefix("/foo/bar.0000.ZIP", false)
|
||||||
assert.Equal(t, "bar.0000", regular)
|
assert.Equal(t, "bar.0000", regular)
|
||||||
|
@ -54,7 +51,6 @@ func TestBase(t *testing.T) {
|
||||||
stripped := BasePrefix("/foo/bar.0000.ZIP", true)
|
stripped := BasePrefix("/foo/bar.0000.ZIP", true)
|
||||||
assert.Equal(t, "bar.0000", stripped)
|
assert.Equal(t, "bar.0000", stripped)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("/foo/bar.00001.ZIP", func(t *testing.T) {
|
t.Run("/foo/bar.00001.ZIP", func(t *testing.T) {
|
||||||
regular := BasePrefix("/foo/bar.00001.ZIP", false)
|
regular := BasePrefix("/foo/bar.00001.ZIP", false)
|
||||||
assert.Equal(t, "bar.00001", regular)
|
assert.Equal(t, "bar.00001", regular)
|
||||||
|
@ -62,12 +58,10 @@ func TestBase(t *testing.T) {
|
||||||
stripped := BasePrefix("/foo/bar.00001.ZIP", true)
|
stripped := BasePrefix("/foo/bar.00001.ZIP", true)
|
||||||
assert.Equal(t, "bar", stripped)
|
assert.Equal(t, "bar", stripped)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
||||||
result := BasePrefix("/testdata/Test copy 3.jpg", false)
|
result := BasePrefix("/testdata/Test copy 3.jpg", false)
|
||||||
assert.Equal(t, "Test copy 3", result)
|
assert.Equal(t, "Test copy 3", result)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test (3).jpg", func(t *testing.T) {
|
t.Run("Test (3).jpg", func(t *testing.T) {
|
||||||
result := BasePrefix("/testdata/Test (3).jpg", false)
|
result := BasePrefix("/testdata/Test (3).jpg", false)
|
||||||
assert.Equal(t, "Test (3)", result)
|
assert.Equal(t, "Test (3)", result)
|
||||||
|
@ -98,7 +92,11 @@ func TestBase(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRelBase(t *testing.T) {
|
func TestRelPrefix(t *testing.T) {
|
||||||
|
t.Run("Empty", func(t *testing.T) {
|
||||||
|
assert.Equal(t, "", RelPrefix("", "", true))
|
||||||
|
assert.Equal(t, "", RelPrefix("", "", false))
|
||||||
|
})
|
||||||
t.Run("/foo/bar.0000.ZIP", func(t *testing.T) {
|
t.Run("/foo/bar.0000.ZIP", func(t *testing.T) {
|
||||||
regular := RelPrefix("/foo/bar.0000.ZIP", "/bar", false)
|
regular := RelPrefix("/foo/bar.0000.ZIP", "/bar", false)
|
||||||
assert.Equal(t, "/foo/bar.0000", regular)
|
assert.Equal(t, "/foo/bar.0000", regular)
|
||||||
|
@ -106,7 +104,6 @@ func TestRelBase(t *testing.T) {
|
||||||
stripped := RelPrefix("/foo/bar.0000.ZIP", "/bar", true)
|
stripped := RelPrefix("/foo/bar.0000.ZIP", "/bar", true)
|
||||||
assert.Equal(t, "/foo/bar.0000", stripped)
|
assert.Equal(t, "/foo/bar.0000", stripped)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("/foo/bar.00001.ZIP", func(t *testing.T) {
|
t.Run("/foo/bar.00001.ZIP", func(t *testing.T) {
|
||||||
regular := RelPrefix("/foo/bar.00001.ZIP", "/bar", false)
|
regular := RelPrefix("/foo/bar.00001.ZIP", "/bar", false)
|
||||||
assert.Equal(t, "/foo/bar.00001", regular)
|
assert.Equal(t, "/foo/bar.00001", regular)
|
||||||
|
@ -114,33 +111,79 @@ func TestRelBase(t *testing.T) {
|
||||||
stripped := RelPrefix("/foo/bar.00001.ZIP", "/bar", true)
|
stripped := RelPrefix("/foo/bar.00001.ZIP", "/bar", true)
|
||||||
assert.Equal(t, "/foo/bar", stripped)
|
assert.Equal(t, "/foo/bar", stripped)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
||||||
result := RelPrefix("/testdata/foo/Test copy 3.jpg", "/testdata", false)
|
result := RelPrefix("/testdata/foo/Test copy 3.jpg", "/testdata", false)
|
||||||
assert.Equal(t, "foo/Test copy 3", result)
|
assert.Equal(t, "foo/Test copy 3", result)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test (3).jpg", func(t *testing.T) {
|
t.Run("Test (3).jpg", func(t *testing.T) {
|
||||||
result := RelPrefix("/testdata/foo/Test (3).jpg", "/testdata", false)
|
result := RelPrefix("/testdata/foo/Test (3).jpg", "/testdata", false)
|
||||||
assert.Equal(t, "foo/Test (3)", result)
|
assert.Equal(t, "foo/Test (3)", result)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test (3).jpg", func(t *testing.T) {
|
t.Run("Test (3).jpg", func(t *testing.T) {
|
||||||
result := RelPrefix("/testdata/foo/Test (3).jpg", "/testdata/foo/Test (3).jpg", false)
|
result := RelPrefix("/testdata/foo/Test (3).jpg", "/testdata/foo/Test (3).jpg", false)
|
||||||
assert.Equal(t, "Test (3)", result)
|
assert.Equal(t, "Test (3)", result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBaseAbs(t *testing.T) {
|
func TestAbsPrefix(t *testing.T) {
|
||||||
|
t.Run("Empty", func(t *testing.T) {
|
||||||
|
assert.Equal(t, "", AbsPrefix("", true))
|
||||||
|
assert.Equal(t, "", AbsPrefix("", false))
|
||||||
|
})
|
||||||
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
||||||
result := AbsPrefix("/testdata/Test (4).jpg", true)
|
result := AbsPrefix("/testdata/Test (4).jpg", true)
|
||||||
|
|
||||||
assert.Equal(t, "/testdata/Test", result)
|
assert.Equal(t, "/testdata/Test", result)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Test (3).jpg", func(t *testing.T) {
|
t.Run("Test (3).jpg", func(t *testing.T) {
|
||||||
result := AbsPrefix("/testdata/Test (4).jpg", false)
|
result := AbsPrefix("/testdata/Test (4).jpg", false)
|
||||||
|
|
||||||
assert.Equal(t, "/testdata/Test (4)", result)
|
assert.Equal(t, "/testdata/Test (4)", result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRelatedFilePathPrefix(t *testing.T) {
|
||||||
|
t.Run("Empty", func(t *testing.T) {
|
||||||
|
assert.Equal(t, "", RelatedFilePathPrefix("", true))
|
||||||
|
assert.Equal(t, "", RelatedFilePathPrefix("", false))
|
||||||
|
})
|
||||||
|
t.Run("IMG_4120", func(t *testing.T) {
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_4120", RelatedFilePathPrefix("/foo/bar/IMG_4120.JPG", false))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_E4120", RelatedFilePathPrefix("/foo/bar/IMG_E4120.JPG", false))
|
||||||
|
})
|
||||||
|
t.Run("LivePhoto", func(t *testing.T) {
|
||||||
|
assert.Equal(t, "IMG_1722", RelatedFilePathPrefix("IMG_1722_HEVC.MOV", false))
|
||||||
|
assert.Equal(t, "IMG_1722", RelatedFilePathPrefix("IMG_1722_HEVC.MOV", true))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722", RelatedFilePathPrefix("/foo/bar/IMG_1722_HevC", false))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722", RelatedFilePathPrefix("/foo/bar/IMG_1722_HEVC.MOV", false))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722", RelatedFilePathPrefix("/foo/bar/IMG_1722_HEVC.MOV", true))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722", RelatedFilePathPrefix("/foo/bar/IMG_1722_hevc.MOV", false))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722_hevc_", RelatedFilePathPrefix("/foo/bar/IMG_1722_hevc_.MOV", false))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722", RelatedFilePathPrefix("/foo/bar/IMG_1722_HEVC.AVC", true))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722_MOV", RelatedFilePathPrefix("/foo/bar/IMG_1722_MOV.MOV", true))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722_AVC", RelatedFilePathPrefix("/foo/bar/IMG_1722_AVC.MOV", true))
|
||||||
|
|
||||||
|
assert.Equal(t, "IMG_1722", RelatedFilePathPrefix("IMG_1722_HEVC.JPEG", false))
|
||||||
|
assert.Equal(t, "IMG_1722", RelatedFilePathPrefix("IMG_1722_HEVC.JPEG", true))
|
||||||
|
assert.Equal(t, "IMG_1722", RelatedFilePathPrefix("IMG_1722_HEVC (1).JPEG", true))
|
||||||
|
assert.Equal(t, "IMG_1722", RelatedFilePathPrefix("IMG_1722_HEVC (2).JPEG", true))
|
||||||
|
assert.Equal(t, "IMG_1722", RelatedFilePathPrefix("IMG_1722_JPEG (1).JPEG", true))
|
||||||
|
assert.Equal(t, "IMG_1722", RelatedFilePathPrefix("IMG_1722_JPG (2).JPEG", true))
|
||||||
|
assert.Equal(t, "IMG_1722_JPG (2)", RelatedFilePathPrefix("IMG_1722_JPG (2).JPEG", false))
|
||||||
|
assert.Equal(t, "IMG_1722_AVC", RelatedFilePathPrefix("IMG_1722_AVC (3).JPEG", true))
|
||||||
|
assert.Equal(t, "IMG_1722_AVC (3)", RelatedFilePathPrefix("IMG_1722_AVC (3).JPEG", false))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722", RelatedFilePathPrefix("/foo/bar/IMG_1722_Jpeg", false))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722", RelatedFilePathPrefix("/foo/bar/IMG_1722_JPEG.MOV", false))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722", RelatedFilePathPrefix("/foo/bar/IMG_1722_JPEG.MOV", true))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722", RelatedFilePathPrefix("/foo/bar/IMG_1722_jpeg.MOV", false))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722_jpeg_", RelatedFilePathPrefix("/foo/bar/IMG_1722_jpeg_.MOV", false))
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_1722", RelatedFilePathPrefix("/foo/bar/IMG_1722_JPEG.JPEG", false))
|
||||||
|
})
|
||||||
|
t.Run("Sequence", func(t *testing.T) {
|
||||||
|
assert.Equal(t, "/foo/bar/Test", RelatedFilePathPrefix("/foo/bar/Test (4).jpg", true))
|
||||||
|
assert.Equal(t, "/foo/bar/Test (4)", RelatedFilePathPrefix("/foo/bar/Test (4).jpg", false))
|
||||||
|
})
|
||||||
|
t.Run("LowerCase", func(t *testing.T) {
|
||||||
|
assert.Equal(t, "/foo/bar/IMG_E4120", RelatedFilePathPrefix("/foo/bar/IMG_E4120.JPG", false))
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue