Metadata: Use file mod time instead of birth time as fallback #4157

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2024-04-08 20:44:57 +02:00
parent e28a1a4537
commit e5c5ce2348
9 changed files with 55 additions and 55 deletions

View File

@@ -147,7 +147,7 @@ func (imp *Import) Start(opt ImportOptions) fs.Done {
directories = append(directories, fileName)
}
folder := entity.NewFolder(entity.RootImport, fs.RelName(fileName, imp.conf.ImportPath()), fs.BirthTime(fileName))
folder := entity.NewFolder(entity.RootImport, fs.RelName(fileName, imp.conf.ImportPath()), fs.ModTime(fileName))
if err := folder.Create(); err == nil {
log.Infof("import: added folder /%s", folder.Path)

View File

@@ -67,7 +67,7 @@ func ImportWorker(jobs <-chan ImportJob) {
} else {
destDirRel := fs.RelName(destDir, imp.originalsPath())
folder := entity.NewFolder(entity.RootOriginals, destDirRel, fs.BirthTime(destDir))
folder := entity.NewFolder(entity.RootOriginals, destDirRel, fs.ModTime(destDir))
if createErr := folder.Create(); createErr == nil {
log.Infof("import: created folder /%s", folder.Path)

View File

@@ -169,7 +169,7 @@ func (ind *Index) Start(o IndexOptions) (found fs.Done, updated int) {
}
if !errors.Is(result, filepath.SkipDir) {
folder := entity.NewFolder(entity.RootOriginals, relName, fs.BirthTime(fileName))
folder := entity.NewFolder(entity.RootOriginals, relName, fs.ModTime(fileName))
if err := folder.Create(); err == nil {
log.Infof("index: added folder /%s", folder.Path)

View File

@@ -158,21 +158,23 @@ func (m *MediaFile) FileSize() int64 {
return fileSize
}
// DateCreated returns only the date on which the media file was probably taken in UTC.
// DateCreated returns the media creation time in UTC.
func (m *MediaFile) DateCreated() time.Time {
takenAt, _ := m.TakenAt()
return takenAt
}
// TakenAt returns the date on which the media file was taken in UTC and the source of this information.
// TakenAt returns the media creation time in UTC and the source from which it originates.
func (m *MediaFile) TakenAt() (time.Time, string) {
// Check if creation time has been cached.
if !m.takenAt.IsZero() {
return m.takenAt, m.takenAtSrc
}
m.takenAt = time.Now().UTC()
// First try to extract the creation time from the file metadata,
data := m.MetaData()
if data.Error == nil && !data.TakenAt.IsZero() && data.TakenAt.Year() > 1000 {
@@ -184,6 +186,7 @@ func (m *MediaFile) TakenAt() (time.Time, string) {
return m.takenAt, m.takenAtSrc
}
// Otherwiese, try to determine creation time from file name and path.
if nameTime := txt.DateFromFilePath(m.fileName); !nameTime.IsZero() {
m.takenAt = nameTime
m.takenAtSrc = entity.SrcName
@@ -198,19 +201,16 @@ func (m *MediaFile) TakenAt() (time.Time, string) {
fileInfo, err := times.Stat(m.FileName())
if err != nil {
log.Warnf("media: %s (file stat)", err.Error())
log.Infof("media: %s was taken at %s (now)", clean.Log(filepath.Base(m.fileName)), m.takenAt.String())
log.Warnf("media: %s (stat call failed)", err.Error())
log.Infof("media: %s was taken at %s (unknown mod time)", clean.Log(filepath.Base(m.fileName)), m.takenAt.String())
return m.takenAt, m.takenAtSrc
}
if fileInfo.HasBirthTime() {
m.takenAt = fileInfo.BirthTime().UTC()
log.Infof("media: %s was taken at %s (file birth time)", clean.Log(filepath.Base(m.fileName)), m.takenAt.String())
} else {
// Use file modification time as fallback.
m.takenAt = fileInfo.ModTime().UTC()
log.Infof("media: %s was taken at %s (file mod time)", clean.Log(filepath.Base(m.fileName)), m.takenAt.String())
}
return m.takenAt, m.takenAtSrc
}

View File

@@ -26,7 +26,7 @@ func FoldersByPath(rootName, rootPath, path string, recursive bool) (folders ent
folders = make(entity.Folders, len(dirs))
for i, dir := range dirs {
newFolder := entity.NewFolder(rootName, filepath.Join(path, dir), fs.BirthTime(filepath.Join(rootPath, dir)))
newFolder := entity.NewFolder(rootName, filepath.Join(path, dir), fs.ModTime(filepath.Join(rootPath, dir)))
if err = newFolder.Create(); err == nil {
folders[i] = newFolder

View File

@@ -1,22 +0,0 @@
package fs
import (
"time"
"github.com/djherbis/times"
)
// BirthTime returns the creation time of a file or folder.
func BirthTime(fileName string) time.Time {
s, err := times.Stat(fileName)
if err != nil {
return time.Now()
}
if s.HasBirthTime() {
return s.BirthTime()
}
return s.ModTime()
}

View File

@@ -1,18 +0,0 @@
package fs
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestBirthTime(t *testing.T) {
t.Run("time now", func(t *testing.T) {
result := BirthTime("/testdata/Test.jpg")
assert.NotEmpty(t, result)
})
t.Run("mod time", func(t *testing.T) {
result := BirthTime("./testdata/CATYELLOW.jpg")
assert.NotEmpty(t, result)
})
}

20
pkg/fs/modtime.go Normal file
View File

@@ -0,0 +1,20 @@
package fs
import (
"time"
"github.com/djherbis/times"
)
// ModTime returns the last modification time of a file or directory in UTC.
func ModTime(filePath string) time.Time {
stat, err := times.Stat(filePath)
// Return the current time if Stat() call failed.
if err != nil {
return time.Now().UTC()
}
// Return modification time as reported by Stat().
return stat.ModTime().UTC()
}

20
pkg/fs/modtime_test.go Normal file
View File

@@ -0,0 +1,20 @@
package fs
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestModTime(t *testing.T) {
t.Run("ValidFilepath", func(t *testing.T) {
result := ModTime("./testdata/CATYELLOW.jpg")
assert.NotEmpty(t, result)
})
t.Run("InvalidFilePath", func(t *testing.T) {
result := ModTime("/testdata/Test.jpg")
assert.NotEmpty(t, result)
assert.True(t, result.Before(time.Now()))
})
}