Group related files #283
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
855781658b
commit
96ec67f868
15 changed files with 96 additions and 45 deletions
|
@ -16,5 +16,5 @@ library:
|
|||
rescan: false
|
||||
raw: false
|
||||
thumbs: true
|
||||
related: true
|
||||
group: true
|
||||
move: false
|
||||
|
|
|
@ -43,9 +43,9 @@
|
|||
<v-checkbox
|
||||
@change="save"
|
||||
class="ma-0 pa-0"
|
||||
v-model="settings.library.related"
|
||||
v-model="settings.library.group"
|
||||
color="secondary-dark"
|
||||
:label="labels.related"
|
||||
:label="labels.group"
|
||||
hint="Files with sequential names like 'IMG_1234 (2)' or 'IMG_1234 copy 2' belong to the same photo."
|
||||
prepend-icon="file_copy"
|
||||
persistent-hint
|
||||
|
@ -286,7 +286,7 @@
|
|||
thumbs: this.$gettext("Create thumbnails"),
|
||||
raw: this.$gettext("Convert RAW files"),
|
||||
move: this.$gettext("Remove imported files"),
|
||||
related: this.$gettext("Find related files"),
|
||||
group: this.$gettext("Group related files"),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -23,7 +23,7 @@ type LibrarySettings struct {
|
|||
CompleteRescan bool `json:"rescan" yaml:"rescan"`
|
||||
ConvertRaw bool `json:"raw" yaml:"raw"`
|
||||
CreateThumbs bool `json:"thumbs" yaml:"thumbs"`
|
||||
FindRelated bool `json:"related" yaml:"related"`
|
||||
GroupRelated bool `json:"group" yaml:"group"`
|
||||
MoveImported bool `json:"move" yaml:"move"`
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ func NewSettings() *Settings {
|
|||
CompleteRescan: false,
|
||||
ConvertRaw: false,
|
||||
CreateThumbs: true,
|
||||
FindRelated: true,
|
||||
GroupRelated: true,
|
||||
MoveImported: false,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -94,6 +94,7 @@ func NewTestConfig() *Config {
|
|||
log.SetLevel(logrus.DebugLevel)
|
||||
|
||||
c := &Config{params: NewTestParams()}
|
||||
c.initSettings()
|
||||
err := c.Init(context.Background())
|
||||
if err != nil {
|
||||
log.Fatalf("failed init config: %v", err)
|
||||
|
|
2
internal/config/testdata/configEmpty.yml
vendored
2
internal/config/testdata/configEmpty.yml
vendored
|
@ -16,5 +16,5 @@ library:
|
|||
rescan: false
|
||||
raw: false
|
||||
thumbs: true
|
||||
related: true
|
||||
group: true
|
||||
move: false
|
||||
|
|
|
@ -102,7 +102,7 @@ func (c *Convert) ConvertCommand(image *MediaFile, jpegName string, xmpName stri
|
|||
result = exec.Command(c.conf.DarktableBin(), image.fileName, jpegName)
|
||||
}
|
||||
} else {
|
||||
return nil, useMutex, fmt.Errorf("convert: no raw to jpeg converter installed (%s)", image.Base())
|
||||
return nil, useMutex, fmt.Errorf("convert: no raw to jpeg converter installed (%s)", image.Base(c.conf.Settings().Library.GroupRelated))
|
||||
}
|
||||
} else if image.IsHEIF() {
|
||||
result = exec.Command(c.conf.HeifConvertBin(), image.fileName, jpegName)
|
||||
|
@ -123,7 +123,7 @@ func (c *Convert) ToJpeg(image *MediaFile) (*MediaFile, error) {
|
|||
return image, nil
|
||||
}
|
||||
|
||||
base := image.AbsBase()
|
||||
base := image.AbsBase(c.conf.Settings().Library.GroupRelated)
|
||||
|
||||
jpegName := base + ".jpg"
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ func (imp *Import) Start(opt ImportOptions) {
|
|||
return nil
|
||||
}
|
||||
|
||||
related, err := mf.RelatedFiles()
|
||||
related, err := mf.RelatedFiles(imp.conf.Settings().Library.GroupRelated)
|
||||
|
||||
if err != nil {
|
||||
event.Error(fmt.Sprintf("import: %s", err.Error()))
|
||||
|
|
|
@ -93,7 +93,7 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
}
|
||||
}
|
||||
|
||||
related, err := importedMainFile.RelatedFiles()
|
||||
related, err := importedMainFile.RelatedFiles(imp.conf.Settings().Library.GroupRelated)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("import: could not index \"%s\" (%s)", destinationMainFilename, err.Error())
|
||||
|
|
|
@ -120,7 +120,7 @@ func (ind *Index) Start(options IndexOptions) map[string]bool {
|
|||
return nil
|
||||
}
|
||||
|
||||
related, err := mf.RelatedFiles()
|
||||
related, err := mf.RelatedFiles(ind.conf.Settings().Library.GroupRelated)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("index: %s", err.Error())
|
||||
|
|
|
@ -61,7 +61,7 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
var locKeywords []string
|
||||
|
||||
labels := classify.Labels{}
|
||||
fileBase := m.Base()
|
||||
fileBase := m.Base(ind.conf.Settings().Library.GroupRelated)
|
||||
filePath := m.RelativePath(ind.originalsPath())
|
||||
fileName := m.RelativeName(ind.originalsPath())
|
||||
fileHash := ""
|
||||
|
|
|
@ -269,8 +269,8 @@ func (m *MediaFile) EditedName() string {
|
|||
}
|
||||
|
||||
// RelatedFiles returns files which are related to this file.
|
||||
func (m *MediaFile) RelatedFiles() (result RelatedFiles, err error) {
|
||||
baseFilename := m.AbsBase()
|
||||
func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err error) {
|
||||
baseFilename := m.AbsBase(stripSequence)
|
||||
// escape any meta characters in the file name
|
||||
baseFilename = regexp.QuoteMeta(baseFilename)
|
||||
matches, err := filepath.Glob(baseFilename + "*")
|
||||
|
@ -357,12 +357,12 @@ func (m MediaFile) RelativePath(directory string) string {
|
|||
}
|
||||
|
||||
// RelativeBase returns the relative filename.
|
||||
func (m MediaFile) RelativeBase(directory string) string {
|
||||
func (m MediaFile) RelativeBase(directory string, stripSequence bool) string {
|
||||
if relativePath := m.RelativePath(directory); relativePath != "" {
|
||||
return relativePath + string(os.PathSeparator) + m.Base()
|
||||
return filepath.Join(relativePath, m.Base(stripSequence))
|
||||
}
|
||||
|
||||
return m.Base()
|
||||
return m.Base(stripSequence)
|
||||
}
|
||||
|
||||
// Directory returns the directory
|
||||
|
@ -371,13 +371,13 @@ func (m MediaFile) Directory() string {
|
|||
}
|
||||
|
||||
// Base returns the filename base without any extensions and path.
|
||||
func (m MediaFile) Base() string {
|
||||
return fs.Base(m.FileName())
|
||||
func (m MediaFile) Base(stripSequence bool) string {
|
||||
return fs.Base(m.FileName(), stripSequence)
|
||||
}
|
||||
|
||||
// AbsBase returns the directory and base filename without any extensions.
|
||||
func (m MediaFile) AbsBase() string {
|
||||
return m.Directory() + string(os.PathSeparator) + m.Base()
|
||||
func (m MediaFile) AbsBase(stripSequence bool) string {
|
||||
return fs.AbsBase(m.FileName(), stripSequence)
|
||||
}
|
||||
|
||||
// MimeType returns the mime type.
|
||||
|
@ -573,7 +573,7 @@ func (m *MediaFile) Jpeg() (*MediaFile, error) {
|
|||
return m, nil
|
||||
}
|
||||
|
||||
jpegFilename := fmt.Sprintf("%s.%s", m.AbsBase(), fs.TypeJpeg)
|
||||
jpegFilename := fmt.Sprintf("%s.%s", m.AbsBase(false), fs.TypeJpeg)
|
||||
|
||||
if !fs.FileExists(jpegFilename) {
|
||||
return nil, fmt.Errorf("jpeg file does not exist: %s", jpegFilename)
|
||||
|
@ -712,11 +712,11 @@ func (m *MediaFile) ResampleDefault(thumbPath string, force bool) (err error) {
|
|||
defer func() {
|
||||
switch count {
|
||||
case 0:
|
||||
log.Info(capture.Time(start, fmt.Sprintf("mediafile: no new thumbnails created for %s", m.Base())))
|
||||
log.Info(capture.Time(start, fmt.Sprintf("mediafile: no new thumbnails created for %s", m.Base(false))))
|
||||
case 1:
|
||||
log.Info(capture.Time(start, fmt.Sprintf("mediafile: one thumbnail created for %s", m.Base())))
|
||||
log.Info(capture.Time(start, fmt.Sprintf("mediafile: one thumbnail created for %s", m.Base(false))))
|
||||
default:
|
||||
log.Info(capture.Time(start, fmt.Sprintf("mediafile: %d thumbnails created for %s", count, m.Base())))
|
||||
log.Info(capture.Time(start, fmt.Sprintf("mediafile: %d thumbnails created for %s", count, m.Base(false))))
|
||||
}
|
||||
}()
|
||||
|
||||
|
|
|
@ -257,7 +257,7 @@ func TestMediaFile_RelatedFiles(t *testing.T) {
|
|||
|
||||
expectedBaseFilename := conf.ExamplesPath() + "/canon_eos_6d"
|
||||
|
||||
related, err := mediaFile.RelatedFiles()
|
||||
related, err := mediaFile.RelatedFiles(true)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
@ -283,7 +283,7 @@ func TestMediaFile_RelatedFiles(t *testing.T) {
|
|||
|
||||
expectedBaseFilename := conf.ExamplesPath() + "/iphone_7"
|
||||
|
||||
related, err := mediaFile.RelatedFiles()
|
||||
related, err := mediaFile.RelatedFiles(true)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
@ -310,7 +310,7 @@ func TestMediaFile_RelatedFiles_Ordering(t *testing.T) {
|
|||
|
||||
assert.Nil(t, err)
|
||||
|
||||
related, err := mediaFile.RelatedFiles()
|
||||
related, err := mediaFile.RelatedFiles(true)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
@ -393,15 +393,15 @@ func TestMediaFile_RelativeBasename(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
t.Run("directory with end slash", func(t *testing.T) {
|
||||
basename := mediaFile.RelativeBase("/go/src/github.com/photoprism/photoprism/assets/resources/")
|
||||
basename := mediaFile.RelativeBase("/go/src/github.com/photoprism/photoprism/assets/resources/", true)
|
||||
assert.Equal(t, "examples/tree_white", basename)
|
||||
})
|
||||
t.Run("directory without end slash", func(t *testing.T) {
|
||||
basename := mediaFile.RelativeBase("/go/src/github.com/photoprism/photoprism/assets/resources")
|
||||
basename := mediaFile.RelativeBase("/go/src/github.com/photoprism/photoprism/assets/resources", true)
|
||||
assert.Equal(t, "examples/tree_white", basename)
|
||||
})
|
||||
t.Run("directory equals example path", func(t *testing.T) {
|
||||
basename := mediaFile.RelativeBase("/go/src/github.com/photoprism/photoprism/assets/resources/examples/")
|
||||
basename := mediaFile.RelativeBase("/go/src/github.com/photoprism/photoprism/assets/resources/examples/", true)
|
||||
assert.Equal(t, "tree_white", basename)
|
||||
})
|
||||
|
||||
|
@ -423,21 +423,21 @@ func TestMediaFile_Basename(t *testing.T) {
|
|||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/limes.jpg")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "limes", mediaFile.Base())
|
||||
assert.Equal(t, "limes", mediaFile.Base(true))
|
||||
})
|
||||
t.Run("/IMG_4120 copy.JPG", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120 copy.JPG")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "IMG_4120", mediaFile.Base())
|
||||
assert.Equal(t, "IMG_4120", mediaFile.Base(true))
|
||||
})
|
||||
t.Run("/IMG_4120 (1).JPG", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/IMG_4120 (1).JPG")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "IMG_4120", mediaFile.Base())
|
||||
assert.Equal(t, "IMG_4120", mediaFile.Base(true))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ func (s *Sync) relatedDownloads(a entity.Account) (result Downloads, err error)
|
|||
|
||||
// Group results by directory and base name
|
||||
for i, file := range files {
|
||||
k := fs.AbsBase(file.RemoteName)
|
||||
k := fs.AbsBase(file.RemoteName, s.conf.Settings().Library.GroupRelated)
|
||||
|
||||
result[k] = append(result[k], file)
|
||||
|
||||
|
@ -124,7 +124,7 @@ func (s *Sync) download(a entity.Account) (complete bool, err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
related, err := mf.RelatedFiles()
|
||||
related, err := mf.RelatedFiles(s.conf.Settings().Library.GroupRelated)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("sync: %s", err.Error())
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Base returns the filename base without any extensions and path.
|
||||
func Base(fileName string) string {
|
||||
func Base(fileName string, stripSequence bool) string {
|
||||
basename := filepath.Base(fileName)
|
||||
|
||||
if end := strings.Index(basename, "."); end != -1 {
|
||||
|
@ -15,6 +14,11 @@ func Base(fileName string) string {
|
|||
basename = basename[:end]
|
||||
}
|
||||
|
||||
if !stripSequence {
|
||||
return basename
|
||||
}
|
||||
|
||||
// common sequential naming schemes
|
||||
if end := strings.Index(basename, " ("); end != -1 {
|
||||
// copies created by Chrome & Windows, example: IMG_1234 (2)
|
||||
basename = basename[:end]
|
||||
|
@ -27,6 +31,6 @@ func Base(fileName string) string {
|
|||
}
|
||||
|
||||
// AbsBase returns the directory and base filename without any extensions.
|
||||
func AbsBase(fileName string) string {
|
||||
return filepath.Dir(fileName) + string(os.PathSeparator) + Base(fileName)
|
||||
func AbsBase(fileName string, stripSequence bool) string {
|
||||
return filepath.Join(filepath.Dir(fileName), Base(fileName, stripSequence))
|
||||
}
|
||||
|
|
|
@ -7,13 +7,59 @@ import (
|
|||
)
|
||||
|
||||
func TestBase(t *testing.T) {
|
||||
result := Base("/testdata/test.jpg")
|
||||
t.Run("Test.jpg", func(t *testing.T) {
|
||||
result := Base("/testdata/Test.jpg", true)
|
||||
assert.Equal(t, "Test", result)
|
||||
})
|
||||
|
||||
assert.Equal(t, "test", result)
|
||||
t.Run("Test.3453453.jpg", func(t *testing.T) {
|
||||
result := Base("/testdata/Test.3453453.jpg", true)
|
||||
assert.Equal(t, "Test", result)
|
||||
})
|
||||
|
||||
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
||||
result := Base("/testdata/Test copy 3.jpg", true)
|
||||
assert.Equal(t, "Test", result)
|
||||
})
|
||||
|
||||
t.Run("Test (3).jpg", func(t *testing.T) {
|
||||
result := Base("/testdata/Test (3).jpg", true)
|
||||
assert.Equal(t, "Test", result)
|
||||
})
|
||||
|
||||
t.Run("Test.jpg", func(t *testing.T) {
|
||||
result := Base("/testdata/Test.jpg", false)
|
||||
assert.Equal(t, "Test", result)
|
||||
})
|
||||
|
||||
t.Run("Test.3453453.jpg", func(t *testing.T) {
|
||||
result := Base("/testdata/Test.3453453.jpg", false)
|
||||
assert.Equal(t, "Test", result)
|
||||
})
|
||||
|
||||
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
||||
result := Base("/testdata/Test copy 3.jpg", false)
|
||||
assert.Equal(t, "Test copy 3", result)
|
||||
})
|
||||
|
||||
t.Run("Test (3).jpg", func(t *testing.T) {
|
||||
result := Base("/testdata/Test (3).jpg", false)
|
||||
assert.Equal(t, "Test (3)", result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBaseAbs(t *testing.T) {
|
||||
result := AbsBase("/testdata/test.jpg")
|
||||
t.Run("Test copy 3.jpg", func(t *testing.T) {
|
||||
result := AbsBase("/testdata/Test (4).jpg", true)
|
||||
|
||||
assert.Equal(t, "/testdata/Test", result)
|
||||
})
|
||||
|
||||
t.Run("Test (3).jpg", func(t *testing.T) {
|
||||
result := AbsBase("/testdata/Test (4).jpg", false)
|
||||
|
||||
assert.Equal(t, "/testdata/Test (4)", result)
|
||||
})
|
||||
|
||||
|
||||
assert.Equal(t, "/testdata/test", result)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue