Skip to content

feat(search): motion photo facet - #3200

Merged
dschmidt merged 10 commits into
mainfrom
feat/search-motion-photo-facet
Sep 3, 2026
Merged

feat(search): motion photo facet#3200
dschmidt merged 10 commits into
mainfrom
feat/search-motion-photo-facet

Conversation

@dschmidt

@dschmidt dschmidt commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Stacked on #3198 (tika facets).

Adds the Google Motion Photo facet (version, presentationTimestampUs, videoSize) end to end.

  • reads the Motion Photo XMP that Tika flattens into Camera: keys, covering the current MotionPhoto scheme and the legacy MicroVideo scheme. Per the spec only a marker value of 1 counts, anything else is treated as a still image. The marker is required: without it a picture that merely carries a video would look like a motion photo.
  • takes videoSize from the video itself, the Content-Length of the document Tika extracted. No XMP container arithmetic.
  • confirms the video is actually present (a Google Photos share keeps the XMP but strips the appended video) by letting Tika extract it: the facet survives only when the recursive metadata carries an embedded video/* document. Verified against all three cases with a Tika built from TIKA-4869: Emit the video of a motion photo as an embedded document apache/tika#3115: MotionPhoto and MicroVideo both yield the video as a second document, the stripped file yields one document and no facet.
  • requires Tika 4 for the Camera: keys, and TIKA-4869: Emit the video of a motion photo as an embedded document apache/tika#3115 for the extracted video and its Content-Length. That one is not released yet, so until it ships the facet always stays empty: no released Tika extracts the video.
  • adds the MotionPhoto search proto message + facet, wires it through the bleve and OpenSearch backends and the graph driveItem (@libre.graph.motionPhoto).

One preparatory commit first: getFirstValue now tries multiple metadata keys (Motion Photo vs legacy MicroVideo).

Needs a separate reva change, not includable here: the WebDAV PROPFIND handler must know the motionPhoto keys to expose the facet over WebDAV. It lives in vendored reva, so it needs its own reva PR + dependency bump. The required diff:

diff --git a/vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go b/vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go
index d86367a2c5..f40f4ec082 100644
--- a/vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go
+++ b/vendor/github.com/opencloud-eu/reva/v2/internal/http/services/owncloud/ocdav/propfind/propfind.go
@@ -107,6 +107,11 @@ var (
 		"orientation",
 		"takenDateTime",
 	}
+	motionPhotoKeys = []string{
+		"version",
+		"presentationTimestampUs",
+		"videoSize",
+	}
 )
 
 type countingReader struct {
@@ -887,6 +892,8 @@ func metadataKeys(pf XML) ([]string, []string) {
 					metadataKeys = append(metadataKeys, metadataKeysWithPrefix("libre.graph.image", imageKeys)...)
 				case "http://owncloud.org/ns/photo":
 					metadataKeys = append(metadataKeys, metadataKeysWithPrefix("libre.graph.photo", photoKeys)...)
+				case "http://owncloud.org/ns/motionPhoto":
+					metadataKeys = append(metadataKeys, metadataKeysWithPrefix("libre.graph.motionPhoto", motionPhotoKeys)...)
 				default:
 					metadataKeys = append(metadataKeys, key)
 				}
@@ -944,7 +951,7 @@ func requiresExplicitFetching(n *xml.Name) bool {
 		}
 	case net.NsOwncloud:
 		switch n.Local {
-		case "favorite", "share-types", "checksums", "size", "tags", "audio", "location", "image", "photo":
+		case "favorite", "share-types", "checksums", "size", "tags", "audio", "location", "image", "photo", "motionPhoto":
 			return true
 		default:
 			return false
@@ -1292,6 +1299,7 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p
 			appendMetadataProp(k, "oc", "location", "libre.graph.location", locationKeys)
 			appendMetadataProp(k, "oc", "image", "libre.graph.image", imageKeys)
 			appendMetadataProp(k, "oc", "photo", "libre.graph.photo", photoKeys)
+			appendMetadataProp(k, "oc", "motionPhoto", "libre.graph.motionPhoto", motionPhotoKeys)
 		}
 
 		if md.Type == provider.ResourceType_RESOURCE_TYPE_CONTAINER {
@@ -1579,6 +1587,10 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p
 					if k := md.GetArbitraryMetadata().GetMetadata(); k != nil {
 						appendMetadataProp(k, "oc", "photo", "libre.graph.photo", photoKeys)
 					}
+				case "motionPhoto":
+					if k := md.GetArbitraryMetadata().GetMetadata(); k != nil {
+						appendMetadataProp(k, "oc", "motionPhoto", "libre.graph.motionPhoto", motionPhotoKeys)
+					}
 				case "name":
 					appendToOK(prop.Escaped("oc:name", md.Name))
 				case "shareid":

@codacy-production

codacy-production Bot commented Jul 29, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 25 complexity · 4 duplication

Metric Results
Complexity 25
Duplication 4

View in Codacy

🟢 Coverage 40.00% diff coverage

Metric Results
Coverage variation Report missing for f5a06431
Diff coverage 40.00% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (f5a0643) Report Missing Report Missing Report Missing
Head commit (3515594) 88120 20605 23.38%

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#3200) 130 52 40.00%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

1 Codacy didn't receive coverage data for the commit, or there was an error processing the received data. Check your integration for errors and validate that your coverage setup is correct.

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@dschmidt
dschmidt force-pushed the feat/search-motion-photo-facet branch from 1a7afbc to 303e84e Compare August 18, 2026 16:30
@dschmidt
dschmidt force-pushed the feat/search-motion-photo-facet branch from 303e84e to 716f480 Compare August 31, 2026 15:20
@dschmidt
dschmidt force-pushed the feat/search-motion-photo-facet branch 5 times, most recently from 1aa5615 to e71f348 Compare August 31, 2026 18:06
@dschmidt
dschmidt marked this pull request as ready for review August 31, 2026 18:07
@dschmidt
dschmidt force-pushed the feat/search-motion-photo-facet branch from e71f348 to 2c1de3d Compare August 31, 2026 18:37
@fschade
fschade force-pushed the feat/search-motion-photo-facet branch from 2c1de3d to 5c9ad8f Compare September 1, 2026 05:17
@dschmidt
dschmidt force-pushed the feat/search-motion-photo-facet branch from 5c9ad8f to c9996ae Compare September 1, 2026 07:11
Base automatically changed from feat/search-tika-facets to main September 1, 2026 09:45
@dschmidt
dschmidt force-pushed the feat/search-motion-photo-facet branch 3 times, most recently from 6256b09 to 48c5f11 Compare September 1, 2026 14:27
@butonic
butonic force-pushed the feat/search-motion-photo-facet branch from 44cbe0b to b4a9b62 Compare September 2, 2026 22:15
@dschmidt
dschmidt force-pushed the feat/search-motion-photo-facet branch from b4a9b62 to 3515594 Compare September 2, 2026 23:10
@dschmidt
dschmidt merged commit e6102c0 into main Sep 3, 2026
66 checks passed
@dschmidt
dschmidt deleted the feat/search-motion-photo-facet branch September 3, 2026 01:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants