Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
351 changes: 265 additions & 86 deletions protogen/gen/opencloud/messages/search/v0/search.pb.go

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions protogen/gen/opencloud/messages/search/v0/search.pb.web.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions protogen/gen/opencloud/services/search/v0/search.swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@
"items": {
"type": "string"
}
},
"video": {
"$ref": "#/definitions/v0Video"
}
}
},
Expand Down Expand Up @@ -493,6 +496,49 @@
"format": "int32"
}
}
},
"v0Video": {
"type": "object",
"properties": {
"audioBitsPerSample": {
"type": "integer",
"format": "int32"
},
"audioChannels": {
"type": "integer",
"format": "int32"
},
"audioFormat": {
"type": "string"
},
"audioSamplesPerSecond": {
"type": "integer",
"format": "int32"
},
"bitrate": {
"type": "integer",
"format": "int32"
},
"duration": {
"type": "string",
"format": "int64"
},
"fourCC": {
"type": "string"
},
"frameRate": {
"type": "number",
"format": "double"
},
"height": {
"type": "integer",
"format": "int32"
},
"width": {
"type": "integer",
"format": "int32"
}
}
}
},
"externalDocs": {
Expand Down
14 changes: 14 additions & 0 deletions protogen/proto/opencloud/messages/search/v0/search.proto
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ message Photo {
optional google.protobuf.Timestamp takenDateTime = 9;
}

message Video {
optional int32 audioBitsPerSample = 1;
optional int32 audioChannels = 2;
optional string audioFormat = 3;
optional int32 audioSamplesPerSecond = 4;
optional int32 bitrate = 5;
optional int64 duration = 6;
optional string fourCC = 7;
optional double frameRate = 8;
optional int32 height = 9;
optional int32 width = 10;
}

message Entity {
Reference ref = 1;
ResourceID id = 2;
Expand All @@ -80,6 +93,7 @@ message Entity {
Image image = 18;
Photo photo = 19;
repeated string favorites = 20;
Video video = 22;
}

message Match {
Expand Down
1 change: 1 addition & 0 deletions services/graph/pkg/service/v0/driveitems.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ func cs3ResourceToDriveItem(logger *log.Logger, publicBaseURL *url.URL, res *sto
driveItem.Image = metadataToFacet[libregraph.Image](metadata, "image")
driveItem.Location = metadataToFacet[libregraph.GeoCoordinates](metadata, "location")
driveItem.Photo = metadataToFacet[libregraph.Photo](metadata, "photo")
driveItem.Video = metadataToFacet[libregraph.Video](metadata, "video")
}

return driveItem, nil
Expand Down
1 change: 1 addition & 0 deletions services/search/pkg/bleve/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ func (b *Backend) Search(_ context.Context, sir *searchService.SearchIndexReques
Image: hitToFacet[searchMessage.Image](hit.Fields, "image"),
Location: hitToFacet[searchMessage.GeoCoordinates](hit.Fields, "location"),
Photo: hitToFacet[searchMessage.Photo](hit.Fields, "photo"),
Video: hitToFacet[searchMessage.Video](hit.Fields, "video"),
},
}

Expand Down
1 change: 1 addition & 0 deletions services/search/pkg/content/content.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Document struct {
Image *libregraph.Image `json:"image,omitempty"`
Location *libregraph.GeoCoordinates `json:"location,omitempty"`
Photo *libregraph.Photo `json:"photo,omitempty"`
Video *libregraph.Video `json:"video,omitempty"`
}

func CleanString(content, langCode string) string {
Expand Down
1 change: 1 addition & 0 deletions services/search/pkg/content/tika.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ func (t Tika) Extract(ctx context.Context, ri *provider.ResourceInfo) (Document,
doc.Image = t.getImage(meta)
doc.Photo = t.getPhoto(meta)
doc.Audio = t.getAudio(meta)
doc.Video = t.getVideo(meta)
}

if langCode, _ := t.tika.LanguageString(ctx, doc.Content); langCode != "" && t.CleanStopWords {
Expand Down
7 changes: 7 additions & 0 deletions services/search/pkg/content/tika_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@ package content

import (
"strconv"
"strings"

libregraph "github.com/opencloud-eu/libre-graph-api-go"
)

func (t Tika) getImage(meta map[string][]string) *libregraph.Image {
// tiff:ImageWidth/Length are also set for videos; the content type is what
// tells an image apart.
if ct, err := getFirstValue(meta, "Content-Type"); err != nil || !strings.HasPrefix(ct, "image/") {
return nil
}

var image *libregraph.Image
initImage := func() {
if image == nil {
Expand Down
9 changes: 9 additions & 0 deletions services/search/pkg/content/tika_image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
var _ = Describe("getImage", func() {
It("maps the image dimensions to the image facet", func() {
image := Tika{}.getImage(map[string][]string{
"Content-Type": {"image/jpeg"},
"tiff:ImageWidth": {"100"},
"tiff:ImageLength": {"200"},
})
Expand All @@ -17,6 +18,14 @@ var _ = Describe("getImage", func() {
Expect(image.Height).To(Equal(libregraph.PtrInt32(200)))
})

It("returns nil for a video even though it carries the same tiff dimensions", func() {
Expect(Tika{}.getImage(map[string][]string{
"Content-Type": {"video/mp4"},
"tiff:ImageWidth": {"1920"},
"tiff:ImageLength": {"1080"},
})).To(BeNil())
})

It("returns nil when no image metadata is present", func() {
Expect(Tika{}.getImage(map[string][]string{})).To(BeNil())
})
Expand Down
93 changes: 93 additions & 0 deletions services/search/pkg/content/tika_video.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package content

import (
"math"
"strconv"
"strings"

libregraph "github.com/opencloud-eu/libre-graph-api-go"
)

func (t Tika) getVideo(meta map[string][]string) *libregraph.Video {
// the tiff:/xmpDM: keys below are shared with images and audio; the file's
// content type is what tells a video apart.
if ct, err := getFirstValue(meta, "Content-Type"); err != nil || !strings.HasPrefix(ct, "video/") {
return nil
}

var video *libregraph.Video
initVideo := func() {
if video == nil {
video = libregraph.NewVideo()
}
}

if v, err := getFirstValue(meta, "tiff:ImageWidth"); err == nil {
if i, err := strconv.ParseInt(v, 0, 32); err == nil {
initVideo()
video.SetWidth(int32(i))
}
}

if v, err := getFirstValue(meta, "tiff:ImageLength"); err == nil {
if i, err := strconv.ParseInt(v, 0, 32); err == nil {
initVideo()
video.SetHeight(int32(i))
}
}

if v, err := getFirstValue(meta, "xmpDM:duration"); err == nil {
// Tika emits fractional seconds.
if f, err := strconv.ParseFloat(v, 64); err == nil {
initVideo()
video.SetDuration(int64(math.Round(f * 1000)))
}
}

if v, err := getFirstValue(meta, "xmpDM:videoCompressor"); err == nil {
initVideo()
video.SetFourCC(v)
}

// TODO: video.Bitrate: not provided by tika (no bitrate tag; needs computing)
// TODO: video.FrameRate: not provided by tika (metadata-extractor exposes no frame-rate tag)

if v, err := getFirstValue(meta, "xmpDM:audioSampleRate"); err == nil {
if i, err := strconv.ParseInt(v, 0, 32); err == nil {
initVideo()
video.SetAudioSamplesPerSecond(int32(i))
}
}

if c, ok := audioChannelCount(meta); ok {
initVideo()
video.SetAudioChannels(c)
}

// TODO: video.AudioBitsPerSample: not provided by tika (Mp4SoundDirectory.TAG_AUDIO_SAMPLE_SIZE is unmapped)
// TODO: video.AudioFormat: tika only sets xmpDM:audioCompressor for audio-typed
// files (and to the container major brand, not the codec), so it never fires for
// a video/* file. Needs tika to expose the audio track's codec.

return video
}

// audioChannelCount maps tika's xmpDM:audioChannelType enum to a channel count.
// A numeric channel count from tika would drop this mapping and cover >2 channels.
func audioChannelCount(meta map[string][]string) (int32, bool) {
v, err := getFirstValue(meta, "xmpDM:audioChannelType")
if err != nil {
return 0, false
}
switch v {
case "Mono":
return 1, true
case "Stereo":
return 2, true
case "5.1":
return 6, true
case "7.1":
return 8, true
}
return 0, false
}
42 changes: 42 additions & 0 deletions services/search/pkg/content/tika_video_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package content

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
libregraph "github.com/opencloud-eu/libre-graph-api-go"
)

var _ = Describe("getVideo", func() {
It("maps the video metadata to the video facet", func() {
video := Tika{}.getVideo(map[string][]string{
"Content-Type": {"video/mp4"},
"xmpDM:videoCompressor": {"avc1"},
"tiff:ImageWidth": {"1920"},
"tiff:ImageLength": {"1080"},
"xmpDM:duration": {"12.5"},
"xmpDM:audioSampleRate": {"48000"},
"xmpDM:audioChannelType": {"Stereo"},
})
Expect(video).ToNot(BeNil())

Expect(video.Width).To(Equal(libregraph.PtrInt32(1920)))
Expect(video.Height).To(Equal(libregraph.PtrInt32(1080)))
Expect(video.Duration).To(Equal(libregraph.PtrInt64(12500)))
Expect(video.FourCC).To(Equal(libregraph.PtrString("avc1")))
Expect(video.AudioSamplesPerSecond).To(Equal(libregraph.PtrInt32(48000)))
Expect(video.AudioChannels).To(Equal(libregraph.PtrInt32(2)))
// not provided by tika for video/* files yet, see the TODOs in tika_video.go:
// Expect(video.Bitrate).To(Equal(libregraph.PtrInt32(...)))
// Expect(video.FrameRate).To(Equal(libregraph.PtrFloat64(...)))
// Expect(video.AudioBitsPerSample).To(Equal(libregraph.PtrInt32(16)))
// Expect(video.AudioFormat).To(Equal(libregraph.PtrString(...)))
})

It("returns nil for a non-video file (image content type)", func() {
Expect(Tika{}.getVideo(map[string][]string{
"Content-Type": {"image/jpeg"},
"tiff:ImageWidth": {"1920"},
"tiff:ImageLength": {"1080"},
})).To(BeNil())
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func OpenSearchHitToMatch(hit opensearchgoAPI.SearchHit) (*searchMessage.Match,
Image: copyFacet[searchMessage.Image](resource.Image),
Location: copyFacet[searchMessage.GeoCoordinates](resource.Location),
Photo: copyFacet[searchMessage.Photo](resource.Photo),
Video: copyFacet[searchMessage.Video](resource.Video),
},
}

Expand Down
1 change: 1 addition & 0 deletions services/search/pkg/search/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,7 @@ func (s *Service) doUpsertItem(ref *provider.Reference, batch BatchOperator) {
facetToMetadata(metadata, doc.Image, "libre.graph.image.")
facetToMetadata(metadata, doc.Location, "libre.graph.location.")
facetToMetadata(metadata, doc.Photo, "libre.graph.photo.")
facetToMetadata(metadata, doc.Video, "libre.graph.video.")
if len(metadata) == 0 {
return
}
Expand Down