Skip to content

Commit 44cbe0b

Browse files
committed
refactor(search): take the motion photo video size from the video tika extracted
1 parent b543ed6 commit 44cbe0b

4 files changed

Lines changed: 41 additions & 73 deletions

File tree

services/search/pkg/content/tika.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,10 @@ func (t Tika) Extract(ctx context.Context, ri *provider.ResourceInfo) (Document,
118118
// a motion photo is the xmp on the file itself plus the video tika extracted
119119
// from it. The xmp alone proves nothing: a share can keep it and strip the
120120
// appended video.
121-
if len(metas) > 0 && slices.ContainsFunc(metas[1:], isVideo) {
122-
doc.MotionPhoto = t.getMotionPhoto(metas[0])
121+
if len(metas) > 0 {
122+
if i := slices.IndexFunc(metas[1:], isVideo); i >= 0 {
123+
doc.MotionPhoto = t.getMotionPhoto(metas[0], metas[i+1])
124+
}
123125
}
124126

125127
if langCode := t.detectLanguage(ctx, doc.Content); langCode != "" && t.CleanStopWords {

services/search/pkg/content/tika_motion_photo.go

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@ package content
22

33
import (
44
libregraph "github.com/opencloud-eu/libre-graph-api-go"
5-
"sort"
65
"strconv"
76
"strings"
87
)
98

109
// getMotionPhoto reads Google Motion Photo XMP, which Tika exposes under the
1110
// canonical Camera/Container prefixes. It covers both the current MotionPhoto
12-
// scheme and the legacy MicroVideo scheme. videoSize (the embedded video's byte
13-
// length, needed to range-fetch it) is required, so the facet is dropped without it.
14-
func (t Tika) getMotionPhoto(meta map[string][]string) *libregraph.MotionPhoto {
15-
// per the spec only a MotionPhoto/MicroVideo marker of 1 means motion
16-
// photo, every other value is "treat as a still image". An absent marker
17-
// is tolerated on purpose, the byte-level video check below decides.
18-
if v, err := getFirstValue(meta, "Camera:MotionPhoto", "Camera:MicroVideo"); err == nil && v != "1" {
11+
// scheme and the legacy MicroVideo scheme. videoSize (needed to range-fetch the
12+
// video) comes from the video tika extracted, and is required: without it the
13+
// facet is dropped.
14+
func (t Tika) getMotionPhoto(meta, video map[string][]string) *libregraph.MotionPhoto {
15+
// the marker is what makes this a motion photo rather than a picture that
16+
// happens to carry a video: per the spec only a value of 1 counts, every
17+
// other value means "treat as a still image".
18+
if v, err := getFirstValue(meta, "Camera:MotionPhoto", "Camera:MicroVideo"); err != nil || v != "1" {
1919
return nil
2020
}
2121

@@ -40,9 +40,11 @@ func (t Tika) getMotionPhoto(meta map[string][]string) *libregraph.MotionPhoto {
4040
}
4141
}
4242

43-
if size, ok := motionPhotoVideoSize(meta); ok {
44-
initMotionPhoto()
45-
motionPhoto.SetVideoSize(size)
43+
if v, err := getFirstValue(video, "Content-Length"); err == nil {
44+
if i, err := strconv.ParseInt(v, 10, 64); err == nil {
45+
initMotionPhoto()
46+
motionPhoto.SetVideoSize(i)
47+
}
4648
}
4749

4850
if motionPhoto == nil || !motionPhoto.HasVideoSize() {
@@ -51,35 +53,6 @@ func (t Tika) getMotionPhoto(meta map[string][]string) *libregraph.MotionPhoto {
5153
return motionPhoto
5254
}
5355

54-
// motionPhotoVideoSize returns the embedded video's byte length: the length of
55-
// the Container item whose semantic is "MotionPhoto", or for legacy files the
56-
// MicroVideo offset (bytes from EOF to the video start, which equals its
57-
// length). The current scheme wins when both are present, like everywhere else.
58-
func motionPhotoVideoSize(meta map[string][]string) (int64, bool) {
59-
keys := make([]string, 0, len(meta))
60-
for k := range meta {
61-
keys = append(keys, k)
62-
}
63-
// map order is random, the first matching container item must be stable
64-
sort.Strings(keys)
65-
for _, k := range keys {
66-
if vals := meta[k]; !strings.HasSuffix(k, "/Item:Semantic") || len(vals) == 0 || vals[0] != "MotionPhoto" {
67-
continue
68-
}
69-
if v, err := getFirstValue(meta, strings.TrimSuffix(k, "/Item:Semantic")+"/Item:Length"); err == nil {
70-
if i, err := strconv.ParseInt(v, 10, 64); err == nil {
71-
return i, true
72-
}
73-
}
74-
}
75-
if v, err := getFirstValue(meta, "Camera:MicroVideoOffset"); err == nil {
76-
if i, err := strconv.ParseInt(v, 10, 64); err == nil {
77-
return i, true
78-
}
79-
}
80-
return 0, false
81-
}
82-
8356
// isVideo reports whether meta describes a video. Tika emits the video appended
8457
// to a motion photo as an embedded document, and it only does so when the bytes
8558
// the xmp advertises are really there: a shared motion photo can keep the xmp

services/search/pkg/content/tika_motion_photo_test.go

Lines changed: 22 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,70 +7,63 @@ import (
77
)
88

99
var _ = Describe("getMotionPhoto", func() {
10-
It("maps the current MotionPhoto XMP scheme (container item length)", func() {
10+
It("maps the current MotionPhoto XMP scheme", func() {
1111
mp := Tika{}.getMotionPhoto(map[string][]string{
12-
"Camera:MotionPhotoVersion": {"1"},
13-
"Camera:MotionPhotoPresentationTimestampUs": {"1500000"},
14-
"xmp-raw:Container:Directory[2]/Container:Item/Item:Semantic": {"MotionPhoto"},
15-
"xmp-raw:Container:Directory[2]/Container:Item/Item:Length": {"1048576"},
16-
})
12+
"Camera:MotionPhoto": {"1"},
13+
"Camera:MotionPhotoVersion": {"1"},
14+
"Camera:MotionPhotoPresentationTimestampUs": {"1500000"},
15+
}, map[string][]string{"Content-Length": {"1048576"}, "Content-Type": {"video/mp4"}})
1716
Expect(mp).ToNot(BeNil())
1817
Expect(mp.Version).To(Equal(libregraph.PtrInt32(1)))
1918
Expect(mp.PresentationTimestampUs).To(Equal(libregraph.PtrInt64(1500000)))
2019
Expect(mp.VideoSize).To(Equal(libregraph.PtrInt64(1048576)))
2120
})
2221

23-
It("maps the legacy MicroVideo XMP scheme (offset is the length)", func() {
22+
It("maps the legacy MicroVideo XMP scheme", func() {
2423
mp := Tika{}.getMotionPhoto(map[string][]string{
24+
"Camera:MicroVideo": {"1"},
2525
"Camera:MicroVideoVersion": {"1"},
2626
"Camera:MicroVideoPresentationTimestampUs": {"1500000"},
27-
"Camera:MicroVideoOffset": {"2097152"},
28-
})
27+
}, map[string][]string{"Content-Length": {"1048576"}, "Content-Type": {"video/mp4"}})
2928
Expect(mp).ToNot(BeNil())
3029
Expect(mp.Version).To(Equal(libregraph.PtrInt32(1)))
3130
Expect(mp.PresentationTimestampUs).To(Equal(libregraph.PtrInt64(1500000)))
32-
Expect(mp.VideoSize).To(Equal(libregraph.PtrInt64(2097152)))
31+
Expect(mp.VideoSize).To(Equal(libregraph.PtrInt64(1048576)))
3332
})
3433

35-
It("drops the facet without a video size", func() {
34+
It("drops the facet when the video reports no length", func() {
3635
Expect(Tika{}.getMotionPhoto(map[string][]string{
36+
"Camera:MotionPhoto": {"1"},
3737
"Camera:MotionPhotoVersion": {"1"},
38-
})).To(BeNil())
38+
}, map[string][]string{"Content-Type": {"video/mp4"}})).To(BeNil())
3939
})
4040

41-
It("returns nil when no motion photo metadata is present", func() {
42-
Expect(Tika{}.getMotionPhoto(map[string][]string{})).To(BeNil())
41+
It("returns nil without the marker, a picture may just carry a video", func() {
42+
Expect(Tika{}.getMotionPhoto(map[string][]string{}, map[string][]string{"Content-Length": {"1048576"}, "Content-Type": {"video/mp4"}})).To(BeNil())
4343
})
4444

4545
It("treats a zero MotionPhoto marker as a still image", func() {
4646
Expect(Tika{}.getMotionPhoto(map[string][]string{
4747
"Camera:MotionPhoto": {"0"},
4848
"Camera:MotionPhotoVersion": {"1"},
49-
"xmp-raw:Container:Directory[2]/Container:Item/Item:Semantic": {"MotionPhoto"},
50-
"xmp-raw:Container:Directory[2]/Container:Item/Item:Length": {"1048576"},
51-
})).To(BeNil())
49+
}, map[string][]string{"Content-Length": {"1048576"}, "Content-Type": {"video/mp4"}})).To(BeNil())
5250
Expect(Tika{}.getMotionPhoto(map[string][]string{
53-
"Camera:MicroVideo": {"0"},
54-
"Camera:MicroVideoOffset": {"2097152"},
55-
})).To(BeNil())
51+
"Camera:MicroVideo": {"0"},
52+
}, map[string][]string{"Content-Length": {"1048576"}, "Content-Type": {"video/mp4"}})).To(BeNil())
5653
})
5754

5855
It("treats undefined marker values as a still image", func() {
5956
Expect(Tika{}.getMotionPhoto(map[string][]string{
6057
"Camera:MotionPhoto": {"2"},
61-
"xmp-raw:Container:Directory[2]/Container:Item/Item:Semantic": {"MotionPhoto"},
62-
"xmp-raw:Container:Directory[2]/Container:Item/Item:Length": {"1048576"},
63-
})).To(BeNil())
58+
}, map[string][]string{"Content-Length": {"1048576"}, "Content-Type": {"video/mp4"}})).To(BeNil())
6459
})
6560

6661
It("prefers the current scheme when both are present", func() {
6762
mp := Tika{}.getMotionPhoto(map[string][]string{
68-
"Camera:MotionPhotoVersion": {"2"},
69-
"Camera:MicroVideoVersion": {"1"},
70-
"Camera:MicroVideoOffset": {"2097152"},
71-
"xmp-raw:Container:Directory[2]/Container:Item/Item:Semantic": {"MotionPhoto"},
72-
"xmp-raw:Container:Directory[2]/Container:Item/Item:Length": {"1048576"},
73-
})
63+
"Camera:MotionPhoto": {"1"},
64+
"Camera:MotionPhotoVersion": {"2"},
65+
"Camera:MicroVideoVersion": {"1"},
66+
}, map[string][]string{"Content-Length": {"1048576"}, "Content-Type": {"video/mp4"}})
7467
Expect(mp).ToNot(BeNil())
7568
Expect(mp.Version).To(Equal(libregraph.PtrInt32(2)))
7669
Expect(mp.VideoSize).To(Equal(libregraph.PtrInt64(1048576)))

services/search/pkg/content/tika_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ var _ = Describe("Tika", func() {
205205
})
206206

207207
It("keeps the motion photo facet when tika emits the video", func() {
208-
fullResponse = `[{"Camera:MotionPhotoVersion": "1", "xmp-raw:Container:Directory[2]/Container:Item/Item:Semantic": "MotionPhoto", "xmp-raw:Container:Directory[2]/Container:Item/Item:Length": "40"}, {"Content-Type": "video/mp4"}]`
208+
fullResponse = `[{"Camera:MotionPhoto": "1", "Camera:MotionPhotoVersion": "1"}, {"Content-Type": "video/mp4", "Content-Length": "40"}]`
209209

210210
doc, err := tika.Extract(context.TODO(), &provider.ResourceInfo{
211211
Type: provider.ResourceType_RESOURCE_TYPE_FILE,
@@ -217,7 +217,7 @@ var _ = Describe("Tika", func() {
217217
})
218218

219219
It("drops the motion photo facet when the advertised video is gone", func() {
220-
fullResponse = `[{"Camera:MotionPhotoVersion": "1", "xmp-raw:Container:Directory[2]/Container:Item/Item:Semantic": "MotionPhoto", "xmp-raw:Container:Directory[2]/Container:Item/Item:Length": "40"}]`
220+
fullResponse = `[{"Camera:MotionPhoto": "1", "Camera:MotionPhotoVersion": "1"}]`
221221

222222
doc, err := tika.Extract(context.TODO(), &provider.ResourceInfo{
223223
Type: provider.ResourceType_RESOURCE_TYPE_FILE,

0 commit comments

Comments
 (0)