From 4006344505bf7cf06c3ede890282a056f20db6d4 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Sat, 3 Oct 2026 11:12:17 +0200 Subject: [PATCH] fix(search): read the tika 4 live photo keys Tika 4 prefixes its metadata keys (mp4:, img:), so the live photo facet was never built there. Live photo support needs tika 4: tika 3 got the video keys only in 3.3.2 and never the still image time. The content key is read through one lookup, marked tika 4 and tika 3 legacy. --- services/search/pkg/content/tika.go | 8 ++--- .../search/pkg/content/tika_live_photo.go | 11 +++---- .../pkg/content/tika_live_photo_test.go | 32 +++++++++---------- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/services/search/pkg/content/tika.go b/services/search/pkg/content/tika.go index e225f2ff18..1d941abf01 100644 --- a/services/search/pkg/content/tika.go +++ b/services/search/pkg/content/tika.go @@ -95,10 +95,10 @@ func (t Tika) Extract(ctx context.Context, ri *provider.ResourceInfo) (Document, doc.Title = strings.TrimSpace(fmt.Sprintf("%s %s", doc.Title, title)) } - // tika 4 renamed the meta prefix from X-TIKA: to tk: - if content, err := getFirstValue(meta, "tk:content"); err == nil { - doc.Content = strings.TrimSpace(fmt.Sprintf("%s %s", doc.Content, content)) - } else if content, err := getFirstValue(meta, "X-TIKA:content"); err == nil { + if content, err := getFirstValue(meta, + "tk:content", // tika 4 + "X-TIKA:content", // tika 3 legacy + ); err == nil { doc.Content = strings.TrimSpace(fmt.Sprintf("%s %s", doc.Content, content)) } } diff --git a/services/search/pkg/content/tika_live_photo.go b/services/search/pkg/content/tika_live_photo.go index fdf90a4481..b06e2f7d93 100644 --- a/services/search/pkg/content/tika_live_photo.go +++ b/services/search/pkg/content/tika_live_photo.go @@ -10,9 +10,8 @@ import ( func (t Tika) getLivePhoto(meta map[string][]string) *libregraph.LivePhoto { // ContentId pairs the two halves, without it there is no live photo. The // video carries it in the QuickTime item list, the still image in the Apple - // maker note, which tika surfaces as "Content Identifier". A file is only - // ever one half, so both keys are read. - contentID, err := getFirstValue(meta, "com.apple.quicktime.content.identifier", "Content Identifier") + // maker note. A file is only ever one half, so both keys are read. + contentID, err := getFirstValue(meta, "mp4:com.apple.quicktime.content.identifier", "img:Content Identifier") if err != nil || contentID == "" { return nil } @@ -25,19 +24,19 @@ func (t Tika) getLivePhoto(meta map[string][]string) *libregraph.LivePhoto { } } - if v, err := getFirstValue(meta, "com.apple.quicktime.live-photo.auto"); err == nil { + if v, err := getFirstValue(meta, "mp4:com.apple.quicktime.live-photo.auto"); err == nil { if b, err := strconv.ParseBool(v); err == nil { livePhoto.SetAuto(b) } } - if v, err := getFirstValue(meta, "com.apple.quicktime.live-photo.vitality-score"); err == nil { + if v, err := getFirstValue(meta, "mp4:com.apple.quicktime.live-photo.vitality-score"); err == nil { if f, err := strconv.ParseFloat(v, 64); err == nil { livePhoto.SetVitalityScore(f) } } - if v, err := getFirstValue(meta, "com.apple.quicktime.live-photo.vitality-scoring-version"); err == nil { + if v, err := getFirstValue(meta, "mp4:com.apple.quicktime.live-photo.vitality-scoring-version"); err == nil { if i, err := strconv.ParseInt(v, 10, 64); err == nil { livePhoto.SetVitalityScoringVersion(i) } diff --git a/services/search/pkg/content/tika_live_photo_test.go b/services/search/pkg/content/tika_live_photo_test.go index ac5d421776..ebf3d43ab7 100644 --- a/services/search/pkg/content/tika_live_photo_test.go +++ b/services/search/pkg/content/tika_live_photo_test.go @@ -9,12 +9,12 @@ import ( var _ = Describe("getLivePhoto", func() { It("maps the video half of a live photo", func() { livePhoto := Tika{}.getLivePhoto(map[string][]string{ - "Content-Type": {"video/quicktime"}, - "com.apple.quicktime.content.identifier": {"6F1A2B3C-1234-4E5F-9A8B-0011223344CC"}, - "quicktime:still-image-time": {"1500000"}, - "com.apple.quicktime.live-photo.auto": {"1"}, - "com.apple.quicktime.live-photo.vitality-score": {"0.75"}, - "com.apple.quicktime.live-photo.vitality-scoring-version": {"4"}, + "Content-Type": {"video/quicktime"}, + "mp4:com.apple.quicktime.content.identifier": {"6F1A2B3C-1234-4E5F-9A8B-0011223344CC"}, + "quicktime:still-image-time": {"1500000"}, + "mp4:com.apple.quicktime.live-photo.auto": {"1"}, + "mp4:com.apple.quicktime.live-photo.vitality-score": {"0.75"}, + "mp4:com.apple.quicktime.live-photo.vitality-scoring-version": {"4"}, }) Expect(livePhoto).ToNot(BeNil()) @@ -27,8 +27,8 @@ var _ = Describe("getLivePhoto", func() { It("maps the still half via the Apple maker-note content identifier", func() { livePhoto := Tika{}.getLivePhoto(map[string][]string{ - "Content-Type": {"image/heic"}, - "Content Identifier": {"6F1A2B3C-1234-4E5F-9A8B-0011223344CC"}, + "Content-Type": {"image/heic"}, + "img:Content Identifier": {"6F1A2B3C-1234-4E5F-9A8B-0011223344CC"}, }) Expect(livePhoto).ToNot(BeNil()) Expect(livePhoto.ContentId).To(Equal("6F1A2B3C-1234-4E5F-9A8B-0011223344CC")) @@ -39,15 +39,15 @@ var _ = Describe("getLivePhoto", func() { "Content-Type": {"image/jpeg"}, })).To(BeNil()) Expect(Tika{}.getLivePhoto(map[string][]string{ - "Content-Type": {"image/heic"}, - "Content Identifier": {""}, + "Content-Type": {"image/heic"}, + "img:Content Identifier": {""}, })).To(BeNil(), "an empty pairing id is no live photo") }) It("rounds a fractional still-image-time", func() { livePhoto := Tika{}.getLivePhoto(map[string][]string{ - "com.apple.quicktime.content.identifier": {"6F1A2B3C"}, - "quicktime:still-image-time": {"1500000.7"}, + "mp4:com.apple.quicktime.content.identifier": {"6F1A2B3C"}, + "quicktime:still-image-time": {"1500000.7"}, }) Expect(livePhoto).ToNot(BeNil()) Expect(livePhoto.StillImageTimeUs).To(Equal(libregraph.PtrInt64(1500001))) @@ -55,9 +55,9 @@ var _ = Describe("getLivePhoto", func() { It("keeps the facet when optional values are malformed", func() { livePhoto := Tika{}.getLivePhoto(map[string][]string{ - "com.apple.quicktime.content.identifier": {"6F1A2B3C"}, - "com.apple.quicktime.live-photo.auto": {"maybe"}, - "com.apple.quicktime.live-photo.vitality-score": {"very"}, + "mp4:com.apple.quicktime.content.identifier": {"6F1A2B3C"}, + "mp4:com.apple.quicktime.live-photo.auto": {"maybe"}, + "mp4:com.apple.quicktime.live-photo.vitality-score": {"very"}, }) Expect(livePhoto).ToNot(BeNil()) Expect(livePhoto.Auto).To(BeNil()) @@ -66,7 +66,7 @@ var _ = Describe("getLivePhoto", func() { It("leaves the video-only fields empty on the still half", func() { livePhoto := Tika{}.getLivePhoto(map[string][]string{ - "Content Identifier": {"6F1A2B3C"}, + "img:Content Identifier": {"6F1A2B3C"}, }) Expect(livePhoto).ToNot(BeNil()) Expect(livePhoto.StillImageTimeUs).To(BeNil())