From 8c214a07cd8e643828cdefd4e8b26e72cb5eeb95 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Mon, 26 Apr 2021 13:28:08 +0200 Subject: [PATCH] Metadata: Add support for XMP sidecar CreateDate and Keywords #1151 --- internal/meta/xmp.go | 5 ++-- internal/meta/xmp_document.go | 34 ++++++++++++++++++----- internal/meta/xmp_test.go | 2 +- internal/photoprism/index_related_test.go | 16 +++++------ 4 files changed, 37 insertions(+), 20 deletions(-) diff --git a/internal/meta/xmp.go b/internal/meta/xmp.go index 0cb00c08b..96d05fdbb 100644 --- a/internal/meta/xmp.go +++ b/internal/meta/xmp.go @@ -57,13 +57,12 @@ func (data *Data) XMP(fileName string) (err error) { data.LensModel = doc.LensModel() } - if takenAt := doc.DateCreated(); !takenAt.IsZero() { + if takenAt := doc.TakenAt(); !takenAt.IsZero() { data.TakenAt = takenAt } if len(doc.Keywords()) != 0 { - data.Keywords += doc.Keywords() - data.Keywords = SanitizeMeta(data.Keywords) + data.AddKeywords(doc.Keywords()) } return nil diff --git a/internal/meta/xmp_document.go b/internal/meta/xmp_document.go index 4725897ea..a8a7a22b8 100644 --- a/internal/meta/xmp_document.go +++ b/internal/meta/xmp_document.go @@ -198,6 +198,7 @@ type XmpDocument struct { } `xml:"RDF" json:"rdf,omitempty"` } +// Load parses an XMP file and populates document values with its contents. func (doc *XmpDocument) Load(filename string) error { data, err := ioutil.ReadFile(filename) @@ -208,6 +209,7 @@ func (doc *XmpDocument) Load(filename string) error { return xml.Unmarshal(data, doc) } +// Title returns the XMP document title. func (doc *XmpDocument) Title() string { t := doc.RDF.Description.Title.Alt.Li.Text t2 := doc.RDF.Description.Title.Text @@ -219,10 +221,12 @@ func (doc *XmpDocument) Title() string { return "" } +// Artist returns the XMP document artist. func (doc *XmpDocument) Artist() string { return SanitizeString(doc.RDF.Description.Creator.Seq.Li) } +// Description returns the XMP document description. func (doc *XmpDocument) Description() string { d := doc.RDF.Description.Description.Alt.Li.Text d2 := doc.RDF.Description.Description.Text @@ -234,34 +238,50 @@ func (doc *XmpDocument) Description() string { return "" } +// Copyright returns the XMP document copyright info. func (doc *XmpDocument) Copyright() string { return SanitizeString(doc.RDF.Description.Rights.Alt.Li.Text) } +// CameraMake returns the XMP document camera make name. func (doc *XmpDocument) CameraMake() string { return SanitizeString(doc.RDF.Description.Make) } +// CameraModel returns the XMP document camera model name. func (doc *XmpDocument) CameraModel() string { return SanitizeString(doc.RDF.Description.Model) } +// LensModel returns the XMP document lens model name. func (doc *XmpDocument) LensModel() string { return SanitizeString(doc.RDF.Description.LensModel) } -func (doc *XmpDocument) DateCreated() time.Time { - s := doc.RDF.Description.DateCreated +// TakenAt returns the XMP document taken date. +func (doc *XmpDocument) TakenAt() time.Time { + taken := time.Time{} // Unknown - if tv, err := time.Parse(time.RFC3339, s); err == nil { - return tv - } else if tv2, err2 := time.Parse("2006-01-02T15:04:05.999999999", s); err2 == nil { - return tv2 + s := SanitizeString(doc.RDF.Description.DateCreated) + + if s == "" { + return taken } - return time.Time{} + if t, err := time.Parse(time.RFC3339, s); err == nil { + taken = t + } else if t, err := time.Parse("2006-01-02T15:04:05.999999999", s); err == nil { + taken = t + } else if t, err := time.Parse("2006-01-02T15:04:05-07:00", s); err == nil { + taken = t + } else if t, err := time.Parse("2006-01-02T15:04:05", s[:19]); err == nil { + taken = t + } + + return taken } +// Keywords returns the XMP document keywords. func (doc *XmpDocument) Keywords() string { s := doc.RDF.Description.Subject.Seq.Li diff --git a/internal/meta/xmp_test.go b/internal/meta/xmp_test.go index d8ecb7067..ac01cc3fb 100644 --- a/internal/meta/xmp_test.go +++ b/internal/meta/xmp_test.go @@ -18,7 +18,7 @@ func TestXMP(t *testing.T) { assert.Equal(t, "Botanischer Garten", data.Title) assert.Equal(t, time.Date(2021, 3, 24, 13, 07, 29, 0, time.FixedZone("", +3600)), data.TakenAt) assert.Equal(t, "Tulpen am See", data.Description) - assert.Equal(t, "Krokus, Blume, Schöne Wiese", data.Keywords) + assert.Equal(t, Keywords{"blume", "krokus", "schöne", "wiese"}, data.Keywords) }) t.Run("photoshop", func(t *testing.T) { diff --git a/internal/photoprism/index_related_test.go b/internal/photoprism/index_related_test.go index f516c8e8e..eac04f85a 100644 --- a/internal/photoprism/index_related_test.go +++ b/internal/photoprism/index_related_test.go @@ -13,7 +13,7 @@ import ( ) func TestIndexRelated(t *testing.T) { - t.Run("/2018-04-12 19_24_49.gif", func(t *testing.T) { + t.Run("2018-04-12 19_24_49.gif", func(t *testing.T) { conf := config.TestConfig() testFile, err := NewMediaFile("testdata/2018-04-12 19_24_49.gif") @@ -35,7 +35,7 @@ func TestIndexRelated(t *testing.T) { dest := filepath.Join(testPath, f.BaseName()) if err := f.Copy(dest); err != nil { - t.Fatalf("COPY FAILED: %s", err) + t.Fatalf("copying test file failed: %s", err) } } @@ -73,8 +73,7 @@ func TestIndexRelated(t *testing.T) { } }) - //TODO this test MUST run before PR 1151 can be merged - /*t.Run("/apple-test-2.jpg", func(t *testing.T) { + t.Run("apple-test-2.jpg", func(t *testing.T) { conf := config.TestConfig() testFile, err := NewMediaFile("testdata/apple-test-2.jpg") @@ -96,7 +95,7 @@ func TestIndexRelated(t *testing.T) { dest := filepath.Join(testPath, f.BaseName()) if err := f.Copy(dest); err != nil { - t.Fatalf("COPY FAILED: %s", err) + t.Fatalf("copying test file failed: %s", err) } } @@ -131,13 +130,12 @@ func TestIndexRelated(t *testing.T) { } else { assert.Equal(t, "Botanischer Garten", photo.PhotoTitle) assert.Equal(t, "Tulpen am See", photo.PhotoDescription) - assert.Contains(t, photo.Details.Keywords, "apple") - assert.Contains(t, photo.Details.Keywords, "green") assert.Contains(t, photo.Details.Keywords, "krokus") assert.Contains(t, photo.Details.Keywords, "blume") - assert.Contains(t, photo.Details.Keywords, "schöne wiese") + assert.Contains(t, photo.Details.Keywords, "schöne") + assert.Contains(t, photo.Details.Keywords, "wiese") assert.Equal(t, "2021-03-24 12:07:29 +0000 UTC", photo.TakenAt.String()) assert.Equal(t, "xmp", photo.TakenSrc) } - })*/ + }) }