From be83ed19e3a63c02325e46a02f348251847d4bc4 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Tue, 8 Sep 2026 11:38:38 +0200 Subject: [PATCH] fix(search): take extraction facets from the main document only The recursive tika response lists the file first, then its embedded resources (cover art, thumbnails, the clip appended to a motion photo). The loop applied getImage/getPhoto/getLocation/getAudio/getLivePhoto to every part, so an mp3's embedded cover art leaked a 200x200 image facet onto the track (and an embedded EXIF image would leak photo/location). Read those facets from metas[0] only, like the video facet already does; the loop now only concatenates title/content and detects the motion photo clip. --- services/search/pkg/content/tika.go | 48 ++++++++---------------- services/search/pkg/content/tika_test.go | 6 ++- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/services/search/pkg/content/tika.go b/services/search/pkg/content/tika.go index 7b918e9ab9..e225f2ff18 100644 --- a/services/search/pkg/content/tika.go +++ b/services/search/pkg/content/tika.go @@ -82,6 +82,9 @@ func (t Tika) Extract(ctx context.Context, ri *provider.ResourceInfo) (Document, if err != nil { return doc, err } + if len(metas) == 0 { + return doc, nil + } for _, meta := range metas { title, err := getFirstValue(meta, "dc:title") @@ -98,40 +101,21 @@ func (t Tika) Extract(ctx context.Context, ri *provider.ResourceInfo) (Document, } else if content, err := getFirstValue(meta, "X-TIKA:content"); err == nil { doc.Content = strings.TrimSpace(fmt.Sprintf("%s %s", doc.Content, content)) } - - // keep facets from earlier entries, an embedded resource's meta - // (e.g. cover art) must not reset them - if v := t.getLocation(meta); v != nil { - doc.Location = v - } - if v := t.getImage(meta); v != nil { - doc.Image = v - } - if v := t.getPhoto(meta); v != nil { - doc.Photo = v - } - if v := t.getAudio(meta); v != nil { - doc.Audio = v - } - if v := t.getLivePhoto(meta); v != nil { - doc.LivePhoto = v - } } - if len(metas) > 0 { - // the video facet says the file is a video, so it comes from the file - // itself: the clip tika extracts from a motion photo must not make its - // image look like one - doc.Video = t.getVideo(metas[0]) - } - - // a motion photo is the xmp on the file itself plus the video tika extracted - // from it. The xmp alone proves nothing: a share can keep it and strip the - // appended video. - if len(metas) > 0 { - if i := slices.IndexFunc(metas[1:], isVideo); i >= 0 { - doc.MotionPhoto = t.getMotionPhoto(metas[0], metas[i+1]) - } + // facets describe the file itself, not its embedded parts (cover art, clips) + m0 := metas[0] + doc.Location = t.getLocation(m0) + doc.Image = t.getImage(m0) + doc.Photo = t.getPhoto(m0) + doc.Audio = t.getAudio(m0) + doc.LivePhoto = t.getLivePhoto(m0) + doc.Video = t.getVideo(m0) + + // a motion photo is the file's own xmp plus the video tika extracted from + // it; the xmp alone proves nothing, a share can strip the appended clip + if i := slices.IndexFunc(metas[1:], isVideo); i >= 0 { + doc.MotionPhoto = t.getMotionPhoto(m0, metas[i+1]) } if langCode := t.detectLanguage(ctx, doc.Content); langCode != "" && t.CleanStopWords { diff --git a/services/search/pkg/content/tika_test.go b/services/search/pkg/content/tika_test.go index 5a224a9e33..f0b1850879 100644 --- a/services/search/pkg/content/tika_test.go +++ b/services/search/pkg/content/tika_test.go @@ -170,7 +170,9 @@ var _ = Describe("Tika", func() { Expect(doc.Content).To(Equal("body test stop words!!!")) }) - It("keeps the audio facet when an embedded resource follows", func() { + It("takes facets from the main document, not from an embedded resource", func() { + // metas[0] is the file (audio), metas[1] its embedded cover art. The + // cover must not give the track an image facet. fullResponse = `[{"Content-Type": "audio/mpeg", "dc:title": "Sucker", "tk:content": "lyrics"}, {"Content-Type": "image/jpeg", "tiff:ImageWidth": "500"}]` doc, err := tika.Extract(context.TODO(), &provider.ResourceInfo{ @@ -180,7 +182,7 @@ var _ = Describe("Tika", func() { Expect(err).ToNot(HaveOccurred()) Expect(doc.Audio).ToNot(BeNil()) Expect(doc.Audio.Title).To(Equal(libregraph.PtrString("Sucker"))) - Expect(doc.Image).ToNot(BeNil()) + Expect(doc.Image).To(BeNil()) }) It("adds no audio facet to non-audio documents", func() {