From f43154be93065f0694cb984eb63462d84c8c6514 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Wed, 22 Apr 2020 16:39:45 +0200 Subject: [PATCH] Convert: Case-insensitive related file search Signed-off-by: Michael Mayer --- internal/photoprism/convert.go | 13 +++--- pkg/fs/filetype.go | 75 +++++++++++++++++++++++++++++----- pkg/fs/filetype_test.go | 22 ++++++++++ 3 files changed, 91 insertions(+), 19 deletions(-) diff --git a/internal/photoprism/convert.go b/internal/photoprism/convert.go index c8abc224f..9eaece8b6 100644 --- a/internal/photoprism/convert.go +++ b/internal/photoprism/convert.go @@ -14,6 +14,7 @@ import ( "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/internal/thumb" + "github.com/photoprism/photoprism/pkg/fs" ) // Convert represents a converter that can convert RAW/HEIF images to JPEG. @@ -123,9 +124,7 @@ func (c *Convert) ToJpeg(image *MediaFile) (*MediaFile, error) { return image, nil } - base := image.AbsBase(c.conf.Settings().Library.GroupRelated) - - jpegName := base + ".jpg" + jpegName := fs.TypeJpeg.Find(image.FileName(), c.conf.Settings().Library.GroupRelated) mediaFile, err := NewMediaFile(jpegName) @@ -133,6 +132,8 @@ func (c *Convert) ToJpeg(image *MediaFile) (*MediaFile, error) { return mediaFile, nil } + jpegName = image.AbsBase(c.conf.Settings().Library.GroupRelated) + ".jpg" + if c.conf.ReadOnly() { return nil, fmt.Errorf("convert: disabled in read only mode (%s)", image.FileName()) } @@ -141,11 +142,7 @@ func (c *Convert) ToJpeg(image *MediaFile) (*MediaFile, error) { log.Infof("convert: %s -> %s", fileName, jpegName) - xmpName := base + ".xmp" - - if _, err := os.Stat(xmpName); err != nil { - xmpName = "" - } + xmpName := fs.TypeXMP.Find(image.FileName(), c.conf.Settings().Library.GroupRelated) event.Publish("index.converting", event.Data{ "fileType": image.FileType(), diff --git a/pkg/fs/filetype.go b/pkg/fs/filetype.go index 4b1d24b3a..e08890c13 100644 --- a/pkg/fs/filetype.go +++ b/pkg/fs/filetype.go @@ -4,6 +4,7 @@ import ( _ "image/gif" // Import for image. _ "image/jpeg" _ "image/png" + "os" "path/filepath" "strings" ) @@ -21,19 +22,22 @@ const ( TypeMov FileType = "mov" // Video files. TypeMP4 FileType = "mp4" TypeAvi FileType = "avi" - TypeXMP FileType = "xmp" // Adobe XMP sidecar file (XML). - TypeAAE FileType = "aae" // Apple sidecar file (XML). - TypeXML FileType = "xml" // XML metadata / config / sidecar file. - TypeYaml FileType = "yml" // YAML metadata / config / sidecar file. - TypeToml FileType = "toml" // Tom's Obvious, Minimal Language sidecar file. - TypeJson FileType = "json" // JSON metadata / config / sidecar file. - TypeText FileType = "txt" // Text config / sidecar file. - TypeMarkdown FileType = "md" // Markdown text sidecar file. - TypeOther FileType = "unknown" // Unknown file format. + TypeXMP FileType = "xmp" // Adobe XMP sidecar file (XML). + TypeAAE FileType = "aae" // Apple sidecar file (XML). + TypeXML FileType = "xml" // XML metadata / config / sidecar file. + TypeYaml FileType = "yml" // YAML metadata / config / sidecar file. + TypeToml FileType = "toml" // Tom's Obvious, Minimal Language sidecar file. + TypeJson FileType = "json" // JSON metadata / config / sidecar file. + TypeText FileType = "txt" // Text config / sidecar file. + TypeMarkdown FileType = "md" // Markdown text sidecar file. + TypeOther FileType = "" // Unknown file format. ) +type FileExtensions map[string]FileType +type TypeExtensions map[FileType][]string + // FileExt contains the filename extensions of file formats known to PhotoPrism. -var FileExt = map[string]FileType{ +var FileExt = FileExtensions{ ".bmp": TypeBitmap, ".gif": TypeGif, ".tif": TypeTiff, @@ -48,9 +52,14 @@ var FileExt = map[string]FileType{ ".avi": TypeAvi, ".mp4": TypeMP4, ".yml": TypeYaml, + ".yaml": TypeYaml, ".jpg": TypeJpeg, - ".thm": TypeJpeg, ".jpeg": TypeJpeg, + ".jpe": TypeJpeg, + ".jif": TypeJpeg, + ".jfif": TypeJpeg, + ".jfi": TypeJpeg, + ".thm": TypeJpeg, ".xmp": TypeXMP, ".aae": TypeAAE, ".heif": TypeHEIF, @@ -97,6 +106,50 @@ var FileExt = map[string]FileType{ ".json": TypeJson, } +func (m FileExtensions) TypeExt() TypeExtensions { + result := make(TypeExtensions) + + for ext, t := range m { + extUpper := strings.ToUpper(ext) + if _, ok := result[t]; ok { + result[t] = append(result[t], ext, extUpper) + } else { + result[t] = []string{ext, extUpper} + } + } + + return result +} + +var TypeExt = FileExt.TypeExt() + +// Find returns the first filename with the same base name and a given type. +func (t FileType) Find(fileName string, stripSequence bool) string { + base := Base(fileName, stripSequence) + dir := filepath.Dir(fileName) + + prefix := filepath.Join(dir, base) + prefixLower := filepath.Join(dir, strings.ToLower(base)) + prefixUpper := filepath.Join(dir, strings.ToUpper(base)) + + for _, ext := range TypeExt[t] { + if info, err := os.Stat(prefix + ext); err == nil && info.Mode().IsRegular() { + return filepath.Join(dir, info.Name()) + } + + if info, err := os.Stat(prefixLower + ext); err == nil && info.Mode().IsRegular() { + return filepath.Join(dir, info.Name()) + } + + if info, err := os.Stat(prefixUpper + ext); err == nil && info.Mode().IsRegular() { + return filepath.Join(dir, info.Name()) + } + } + + return "" +} + +// GetFileType returns the (expected) type for a given file name. func GetFileType(fileName string) FileType { fileExt := strings.ToLower(filepath.Ext(fileName)) result, ok := FileExt[fileExt] diff --git a/pkg/fs/filetype_test.go b/pkg/fs/filetype_test.go index 332b1cf28..080d94d5f 100644 --- a/pkg/fs/filetype_test.go +++ b/pkg/fs/filetype_test.go @@ -22,3 +22,25 @@ func TestGetFileType(t *testing.T) { assert.Equal(t, TypeOther, result) }) } + +func TestFileType_Find(t *testing.T) { + t.Run("find jpg", func(t *testing.T) { + result := TypeJpeg.Find("testdata/test.xmp", false) + assert.Equal(t, "testdata/test.jpg", result) + }) + + t.Run("upper ext", func(t *testing.T) { + result := TypeJpeg.Find("testdata/test.XMP", false) + assert.Equal(t, "testdata/test.jpg", result) + }) + + t.Run("with sequence", func(t *testing.T) { + result := TypeJpeg.Find("testdata/test (2).xmp", false) + assert.Equal(t, "", result) + }) + + t.Run("strip sequence", func(t *testing.T) { + result := TypeJpeg.Find("testdata/test (2).xmp", true) + assert.Equal(t, "testdata/test.jpg", result) + }) +}