Index: Refactor JSON sidecar creation #439 #3588

A JSON sidecar should now exist in the MediaFile.RelatedFiles() method
so that the information can subsequently be used to extract an embedded
video from the media file, if necessary.

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer 2023-08-14 12:51:22 +02:00
parent c82a4b2287
commit cb66022925
7 changed files with 83 additions and 36 deletions

View file

@ -57,8 +57,10 @@ func (c *Convert) ToJson(f *MediaFile, force bool) (jsonName string, err error)
}
// Check if file exists.
if !fs.FileExists(jsonName) {
return "", fmt.Errorf("exiftool: failed creating %s", filepath.Base(jsonName))
if fs.FileExists(jsonName) {
log.Debugf("cache: created %s", filepath.Base(jsonName))
} else {
return "", fmt.Errorf("exiftool: failed to create %s", filepath.Base(jsonName))
}
return jsonName, err

View file

@ -40,15 +40,8 @@ func ImportWorker(jobs <-chan ImportJob) {
}
// Extract metadata to a JSON file with Exiftool.
if related.Main.NeedsExifToolJson() {
if jsonName, err := imp.convert.ToJson(related.Main, false); err != nil {
log.Tracef("exiftool: %s", clean.Log(err.Error()))
log.Debugf("exiftool: failed parsing %s", clean.Log(related.Main.RootRelName()))
} else if err := related.Main.ReadExifToolJson(); err != nil {
log.Errorf("import: %s in %s (read metadata)", clean.Log(err.Error()), clean.Log(related.Main.BaseName()))
} else {
log.Debugf("import: created %s", filepath.Base(jsonName))
}
if jsonErr := related.Main.CreateExifToolJson(); jsonErr != nil {
log.Errorf("import: %s", clean.Log(jsonErr.Error()))
}
originalName := related.Main.RelName(src)
@ -134,13 +127,8 @@ func ImportWorker(jobs <-chan ImportJob) {
}
// Extract metadata to a JSON file with Exiftool.
if f.NeedsExifToolJson() {
if jsonName, err := imp.convert.ToJson(f, false); err != nil {
log.Tracef("exiftool: %s", clean.Log(err.Error()))
log.Debugf("exiftool: failed parsing %s", clean.Log(f.RootRelName()))
} else {
log.Debugf("import: created %s", filepath.Base(jsonName))
}
if jsonErr := f.CreateExifToolJson(); jsonErr != nil {
log.Errorf("import: %s", clean.Log(jsonErr.Error()))
}
// Create JPEG sidecar for media files in other formats so that thumbnails can be created.

View file

@ -2,7 +2,6 @@ package photoprism
import (
"fmt"
"path/filepath"
"github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/clean"
@ -31,13 +30,8 @@ func IndexMain(related *RelatedFiles, ind *Index, o IndexOptions) (result IndexR
}
// Extract metadata to a JSON file with Exiftool.
if f.NeedsExifToolJson() {
if jsonName, err := ind.convert.ToJson(f, false); err != nil {
log.Tracef("exiftool: %s", clean.Log(err.Error()))
log.Debugf("exiftool: failed parsing %s", clean.Log(f.RootRelName()))
} else {
log.Debugf("index: created %s", filepath.Base(jsonName))
}
if jsonErr := f.CreateExifToolJson(); jsonErr != nil {
log.Errorf("index: %s", clean.Log(jsonErr.Error()))
}
// Create JPEG sidecar for media files in other formats so that thumbnails can be created.

View file

@ -2,7 +2,6 @@ package photoprism
import (
"fmt"
"path/filepath"
"github.com/dustin/go-humanize/english"
@ -58,13 +57,8 @@ func IndexRelated(related RelatedFiles, ind *Index, o IndexOptions) (result Inde
}
// Extract metadata to a JSON file with Exiftool.
if f.NeedsExifToolJson() {
if jsonName, err := ind.convert.ToJson(f, false); err != nil {
log.Tracef("exiftool: %s", clean.Log(err.Error()))
log.Debugf("exiftool: failed parsing %s", clean.Log(f.RootRelName()))
} else {
log.Debugf("index: created %s", filepath.Base(jsonName))
}
if jsonErr := f.CreateExifToolJson(); jsonErr != nil {
log.Errorf("index: %s", clean.Log(jsonErr.Error()))
}
// Create JPEG sidecar for media files in other formats so that thumbnails can be created.

View file

@ -4,7 +4,6 @@ import (
"fmt"
"path/filepath"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/meta"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
@ -41,7 +40,7 @@ func (m *MediaFile) ExifToolJsonName() (string, error) {
// NeedsExifToolJson tests if an ExifTool JSON file needs to be created.
func (m *MediaFile) NeedsExifToolJson() bool {
if m.Root() == entity.RootSidecar || !m.IsMedia() || m.Empty() {
if m.InSidecar() && m.IsImage() || !m.IsMedia() || m.Empty() {
return false
}
@ -54,6 +53,20 @@ func (m *MediaFile) NeedsExifToolJson() bool {
return !fs.FileExists(jsonName)
}
// CreateExifToolJson extracts metadata to a JSON file using Exiftool.
func (m *MediaFile) CreateExifToolJson() error {
if !m.NeedsExifToolJson() {
return nil
} else if jsonName, jsonErr := NewConvert(Config()).ToJson(m, false); jsonErr != nil {
log.Tracef("exiftool: %s", clean.Log(jsonErr.Error()))
log.Debugf("exiftool: failed parsing %s", clean.Log(m.RootRelName()))
} else if jsonErr = m.metaData.JSON(jsonName, ""); jsonErr != nil {
return fmt.Errorf("%s in %s (read json sidecar)", clean.Log(jsonErr.Error()), clean.Log(m.BaseName()))
}
return nil
}
// ReadExifToolJson reads metadata from a cached ExifTool JSON file.
func (m *MediaFile) ReadExifToolJson() error {
jsonName, err := m.ExifToolJsonName()

View file

@ -4,11 +4,13 @@ import (
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/meta"
"github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/projection"
)
@ -109,6 +111,53 @@ func TestMediaFile_NeedsExifToolJson(t *testing.T) {
})
}
func TestMediaFile_CreateExifToolJson(t *testing.T) {
conf := config.TestConfig()
t.Run("gopher-video.mp4", func(t *testing.T) {
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/gopher-video.mp4")
if err != nil {
t.Fatal(err)
}
jsonName, err := mediaFile.ExifToolJsonName()
if fs.FileExists(jsonName) {
if err = os.Remove(jsonName); err != nil {
t.Error(err)
}
}
assert.True(t, mediaFile.NeedsExifToolJson())
err = mediaFile.CreateExifToolJson()
if err != nil {
t.Fatal(err)
}
data := mediaFile.MetaData()
assert.Empty(t, err)
assert.IsType(t, meta.Data{}, data)
assert.Equal(t, "2020-05-11 14:18:35 +0000 UTC", data.TakenAt.String())
assert.Equal(t, "2020-05-11 14:18:35 +0000 UTC", data.TakenAtLocal.String())
assert.Equal(t, time.Duration(2410000000), data.Duration)
assert.Equal(t, meta.CodecAvc1, data.Codec)
assert.Equal(t, 270, data.Width)
assert.Equal(t, 480, data.Height)
assert.Equal(t, false, data.Flash)
assert.Equal(t, "", data.Description)
if err = os.Remove(jsonName); err != nil {
t.Error(err)
}
})
}
func TestMediaFile_Exif_JPEG(t *testing.T) {
conf := config.TestConfig()

View file

@ -13,6 +13,13 @@ import (
// RelatedFiles returns files which are related to this file.
func (m *MediaFile) RelatedFiles(stripSequence bool) (result RelatedFiles, err error) {
// Create JSON sidecar file?
if m.IsSidecar() {
// Do nothing.
} else if err = m.CreateExifToolJson(); err != nil {
log.Errorf("media: %s", clean.Log(err.Error()), clean.Log(result.Main.BaseName()))
}
// File path and name without any extensions.
prefix := m.AbsPrefix(stripSequence)