Skip to content

Google Motion Photos: Android 11+ format and grid hover playback - #4900

Merged
ildyria merged 11 commits into
LycheeOrg:masterfrom
mlapaglia:master
Oct 19, 2026
Merged

ildyria merged 11 commits into
LycheeOrg:masterfrom
mlapaglia:master

Conversation

@mlapaglia

@mlapaglia mlapaglia commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

fixes #753, fixes #1283

What this enables

  • Motion photos from Android 11+ phones now import as live photos. That includes Pixel Ultra HDR shots. Until now Lychee only understood the old MicroVideoOffset format, so these photos lost their video. The video is cut out of the JPEG and stored next to the original, like Apple Live Photos. The existing LivePhotosKit player in the lightbox then plays it with no change.
  • Old-format motion photos also work without exiftool. Previously only the exiftool reader exposed MicroVideoOffset.
  • php artisan lychee:extract_motion_photos recovers the video of photos uploaded before this change. The original file still contains the video. Options: --dry-run, --album=<id>, --after=<id> to continue in batches, and --reencode to convert clips already stored as HEVC to H.264.
  • Motion clips play in every browser. Current Pixel phones record the clip in HEVC, which Firefox and others can't decode. Clips in H.264 are copied as before; any other codec is transcoded to H.264 (libx264, CRF 23) in the same .mov, with the rotation applied. On the Pixel sample that took 0.17 s and shrank the clip from 3.69 MB to 1.15 MB.
  • The v8 grid shows which photos move and plays them on hover. Every live photo, Apple or Google, gets a motion badge (translated in all 23 languages). With a mouse, resting on a thumbnail for 250 ms plays the clip muted and looping over the still. On touch devices, or with "reduce motion" turned on, only the badge shows, and tapping opens the photo as before.

How it works

  • Detection lives in php-exif. As suggested in review, the reader moved to lychee-org/php-exif (feat: Google Motion Photo support for every adapter php-exif#83, released in 1.5.0), and this PR bumps php-exif to ^1.5.0. Reader::read() now reports the video as MicroVideoOffset for every reader (exiftool, Imagick or Native), in both the legacy and the Motion Photo 1.0 format (ADR-100-01).
  • How php-exif finds the video. It reads the XMP from the JPEG itself rather than mapping exiftool's output. exiftool flattens Container:Directory into lists that misalign on real Pixel files, whose primary item has no Item:Length. It takes the length of the MotionPhoto container item, or the legacy offset, and trusts a length only if an MP4 ftyp box starts where it points. If the file says MotionPhoto="1" with no usable length, it scans after the primary image's end-of-image marker, in 64 KiB chunks.
  • Reuse of the upload path. Extractor copies getMicroVideoOffset() into the existing micro_video_offset, so the upload pipes are unchanged. GoogleMotionPictureHandler::saveVideoStream() now picks the output format by codec: MOVFormat (stream copy) for H.264, the new H264MOVFormat otherwise. lychee:extract_motion_photos calls php-exif's MotionPhotoLocator directly.
  • Video URLs in the grid. v3 grid tiles don't carry live_photo_url until their details are loaded. On hover, a thumbnail asks the view's existing details loader for its own photo. That's one request the first time, cached after that, and needs no API change.

Why it doesn't break existing features

  • No migration, and no API or resource change. The only dependency change is lychee-org/php-exif ^1.4.0 → ^1.5.0; no new composer or npm package.
  • php-exif's locator never throws. On any read error it reports no video, so the photo imports as a still, the same fallback as today.
  • The existing Google motion photo tests are unchanged and pass. The full upload suites pass for the GD, Imagick, raw and watermarker handlers.
  • PlaceGoogleMotionVideo now uses a shared path helper, GoogleMotionPictureHandler::videoPathNextTo(). The stored path is the same as before.
  • In the grid, no <video> element exists until hover intent, so pages load no video. ThumbBadge gains an optional label prop. v7 is untouched.

Tests

New:

  • The unit tests for the reader (JPEG walk, XMP parser, locator, Reader with every adapter) are in php-exif under tests/PHPExif/MotionPhoto/.
  • In BaseImageHandler, run by every handler test class: uploads of the new format, Ultra HDR, a real Pixel photo, no FFmpeg, and an old-format file without exiftool.
  • ExtractMotionPhotosTest (13), including --reencode, failed photos and the resume cursor, and a failed write that must leave the stored clip intact.
  • Stored clip codec after upload: H.264 and upright for the Pixel (HEVC) sample, stream-copied for the v1 (H.264) sample.
  • useMotionHover.test.ts (18, Vitest).

Samples:

  • google_motion_photo_pixel.jpg is a real Pixel 11 Pro motion photo. GPS, the C2PA manifest and the HDR+ debug maker note were removed. The gain map and video bytes are unchanged.
  • google_motion_photo_v1.jpg and google_motion_photo_ultrahdr.jpg are built by tests/Samples/build_motion_photo_fixtures.php.

Run locally:

  • make phpstan: no errors.
  • php-cs-fixer: no changes.
  • rector process --dry-run: no changes (its two internal errors are in the existing telemetry files).
  • npx vp check: no errors.
  • npx vp run typecheck, npx vp test: pass.
  • Related PHP test classes pass: the four upload suites, ExtractMotionPhotosTest, PhotoAddTest, DownloadTest, Detect360Test, and the other Extractor users.

Manual: Chrome on a scratch instance with v8 and the v3 grid enabled. The badges show, nothing loads before hover, one details request on the first hover, playback, unload on leave, badges only with reduced motion, and the still is kept if the clip can't play. The lightbox shows the LIVE button for the Pixel photo.

Notes for reviewers

  • Transcoding applies only to clips embedded in Google motion photos. Apple Live Photo videos are separate uploads and are left as they are.
  • The backfill command writes each clip to <path>.part and moves it into place, so an interrupted write never truncates a clip already in use.
  • Spec, plan, tasks, decisions and the drift gate report are in docs/specs/4-architecture/features/100-google-motion-photos/.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Google Motion Photos are recognized during import, with embedded video extracted and stored alongside the original photo when available.
    • Live photos display a badge in v8 gallery grids and play muted video on hover where supported, respecting device capabilities and reduced-motion preferences.
    • Added a command to extract motion-photo videos from photos already in your library, with album filtering, batch limits, dry-run, and optional re-encoding.
    • Added translated motion-photo labels across supported gallery languages.
  • Documentation
    • Updated photo and architecture guides to cover Google Motion Photos and extraction.

@mlapaglia
mlapaglia requested a review from a team as a code owner October 18, 2026 12:19
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

This change adds Google Motion Photo upload handling and a command to extract or re-encode embedded videos in existing photos. It adds v8 gallery badges and hover playback for live photos, with supporting tests, fixtures, translations, and documentation.

Changes: Google Motion Photo Support

Layer / File(s) Summary
Upload extraction and video storage
app/Actions/Photo/Pipes/Standalone/PlaceGoogleMotionVideo.php, app/Image/Handlers/GoogleMotionPictureHandler.php, app/ModelFunctions/H264MOVFormat.php, app/Metadata/Extractor.php, composer.json, tests/Constants/TestConstants.php, tests/ImageProcessing/Image/Handlers/BaseImageHandler.php, tests/Samples/build_motion_photo_fixtures.php, tests/Traits/ProbesStoredVideo.php, docs/specs/4-architecture/..., docs/specs/6-decisions/...
The upload pipe uses a shared helper to build the video path beside the original. The handler copies H.264 streams and transcodes other codecs with the new H.264 MOV format. The php-exif constraint changes to ^1.5.0. Fixtures and upload tests cover Motion Photo 1.0, Ultra HDR, Pixel, legacy, and no-FFmpeg cases.
Backfill extraction and re-encoding
app/Console/Commands/ImageProcessing/ExtractMotionPhotos.php, tests/ImageProcessing/Commands/ExtractMotionPhotosTest.php, docs/specs/4-architecture/features/100-google-motion-photos/...
The command selects JPEGs for extraction or re-encoding. It supports album and cursor filters, limits, and dry runs. It extracts videos for matching photos, reports failures, and dispatches PhotoSaved after detections. Tests cover command results, options, and failure cases.
Gallery hover playback
resources/js/v8/composables/photo/*, resources/js/v8/components/gallery/albumModule/..., resources/js/v8/views/gallery-panels/{Search,Timeline}.vue, lang/*/gallery.php, docs/specs/1-concepts/photos.md, docs/specs/4-architecture/knowledge-map.md
The v8 gallery loads photo details when a motion URL is missing. Live-photo thumbnails show a localized badge and play muted, looping video after a hover delay when device and motion-preference checks allow it. The composable tests cover playback and URL-loading behavior.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🔵 Low · up to 343a9

A rare interrupted re-encode could leave a truncated motion clip, though it is recoverable by rerunning the command. The feature spec also still partly describes the superseded in-house reader. Neither blocks the merge.

Pre-merge checks: Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 35.21% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 142 functions across 46 files. (6 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
Full details: Docstring Coverage

Explanation

Docstring coverage is 35.21% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 142 functions across 46 files. (6 skipped: 6 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit watched the still frame glow,
Then saw its hidden cliplet flow.
A badge appeared; the hover stirred,
The tiny movie hopped, undeterred.
“Motion photos!” the rabbit cried,
And tucked a moving frame inside.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a0531c06-273f-4d1d-8f7a-18c7209fe887
📥 Commits

Reviewing files that changed from the base of the PR and between f4a3197 and c0aa039.

⛔ Files ignored due to path filters: 3
  • tests/Samples/google_motion_photo_pixel.jpg is excluded by !**/*.jpg
  • tests/Samples/google_motion_photo_ultrahdr.jpg is excluded by !**/*.jpg
  • tests/Samples/google_motion_photo_v1.jpg is excluded by !**/*.jpg
📒 Files selected for processing: 59
  • app/Actions/Photo/Pipes/Standalone/PlaceGoogleMotionVideo.php
  • app/Console/Commands/ImageProcessing/ExtractMotionPhotos.php
  • app/Image/Handlers/GoogleMotionPictureHandler.php
  • app/Metadata/Extractor.php
  • app/Metadata/MotionPhoto/FtypBox.php
  • app/Metadata/MotionPhoto/JpegHeader.php
  • app/Metadata/MotionPhoto/JpegXmpReader.php
  • app/Metadata/MotionPhoto/MotionPhotoLocator.php
  • app/Metadata/MotionPhoto/MotionPhotoXmp.php
  • app/Metadata/MotionPhoto/MotionPhotoXmpHandler.php
  • docs/specs/1-concepts/photos.md
  • docs/specs/4-architecture/features/098-google-motion-photos/open-questions.md
  • docs/specs/4-architecture/features/098-google-motion-photos/plan.md
  • docs/specs/4-architecture/features/098-google-motion-photos/spec.md
  • docs/specs/4-architecture/features/098-google-motion-photos/tasks.md
  • docs/specs/4-architecture/knowledge-map.md
  • docs/specs/4-architecture/request-lifecycle-photo-upload.md
  • docs/specs/4-architecture/roadmap.md
  • docs/specs/6-decisions/ADR-098-01-in-house-motion-photo-xmp-reader.md
  • docs/specs/6-decisions/README.md
  • lang/ar/gallery.php
  • lang/bg/gallery.php
  • lang/cz/gallery.php
  • lang/de/gallery.php
  • lang/el/gallery.php
  • lang/en/gallery.php
  • lang/es/gallery.php
  • lang/fa/gallery.php
  • lang/fr/gallery.php
  • lang/hu/gallery.php
  • lang/it/gallery.php
  • lang/ja/gallery.php
  • lang/nl/gallery.php
  • lang/no/gallery.php
  • lang/pl/gallery.php
  • lang/pt/gallery.php
  • lang/ru/gallery.php
  • lang/sk/gallery.php
  • lang/sv/gallery.php
  • lang/tr/gallery.php
  • lang/vi/gallery.php
  • lang/zh_CN/gallery.php
  • lang/zh_TW/gallery.php
  • resources/js/v8/components/gallery/albumModule/AlbumPanel.vue
  • resources/js/v8/components/gallery/albumModule/Virtualized/PhotoThumbVirtual.vue
  • resources/js/v8/components/gallery/albumModule/thumbs/PhotoThumb.vue
  • resources/js/v8/components/gallery/albumModule/thumbs/ThumbBadge.vue
  • resources/js/v8/composables/photo/motionUrlLoader.ts
  • resources/js/v8/composables/photo/useMotionHover.test.ts
  • resources/js/v8/composables/photo/useMotionHover.ts
  • resources/js/v8/views/gallery-panels/Search.vue
  • resources/js/v8/views/gallery-panels/Timeline.vue
  • tests/Constants/TestConstants.php
  • tests/ImageProcessing/Commands/ExtractMotionPhotosTest.php
  • tests/ImageProcessing/Image/Handlers/BaseImageHandler.php
  • tests/Samples/build_motion_photo_fixtures.php
  • tests/Unit/Metadata/MotionPhoto/JpegXmpReaderTest.php
  • tests/Unit/Metadata/MotionPhoto/MotionPhotoLocatorTest.php
  • tests/Unit/Metadata/MotionPhoto/MotionPhotoXmpTest.php

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread docs/specs/4-architecture/roadmap.md Outdated
Comment thread resources/js/v8/composables/photo/useMotionHover.ts Outdated
…detail requests

Feature 098 was taken upstream by Tag Albums Category, so the spec folder, ADR and all identifiers move to 100. Repeated hover now reuses the in-flight details request instead of sending a duplicate.

Spec impact: Feature 100 spec FR-100-05 and S-100-19 describe the reused request, and the plan records the review follow-up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: fb011b8e-47dc-4de8-96ed-366ceb8a0c2e
📥 Commits

Reviewing files that changed from the base of the PR and between c0aa039 and 2cdecef.

📒 Files selected for processing: 30
  • app/Console/Commands/ImageProcessing/ExtractMotionPhotos.php
  • app/Metadata/Extractor.php
  • app/Metadata/MotionPhoto/FtypBox.php
  • app/Metadata/MotionPhoto/JpegXmpReader.php
  • app/Metadata/MotionPhoto/MotionPhotoLocator.php
  • app/Metadata/MotionPhoto/MotionPhotoXmp.php
  • docs/specs/1-concepts/photos.md
  • docs/specs/4-architecture/features/100-google-motion-photos/open-questions.md
  • docs/specs/4-architecture/features/100-google-motion-photos/plan.md
  • docs/specs/4-architecture/features/100-google-motion-photos/spec.md
  • docs/specs/4-architecture/features/100-google-motion-photos/tasks.md
  • docs/specs/4-architecture/knowledge-map.md
  • docs/specs/4-architecture/request-lifecycle-photo-upload.md
  • docs/specs/4-architecture/roadmap.md
  • docs/specs/6-decisions/ADR-100-01-in-house-motion-photo-xmp-reader.md
  • docs/specs/6-decisions/README.md
  • resources/js/v8/components/gallery/albumModule/AlbumPanel.vue
  • resources/js/v8/components/gallery/albumModule/Virtualized/PhotoThumbVirtual.vue
  • resources/js/v8/components/gallery/albumModule/thumbs/PhotoThumb.vue
  • resources/js/v8/composables/photo/motionUrlLoader.ts
  • resources/js/v8/composables/photo/useMotionHover.test.ts
  • resources/js/v8/composables/photo/useMotionHover.ts
  • resources/js/v8/views/gallery-panels/Search.vue
  • resources/js/v8/views/gallery-panels/Timeline.vue
  • tests/ImageProcessing/Commands/ExtractMotionPhotosTest.php
  • tests/ImageProcessing/Image/Handlers/BaseImageHandler.php
  • tests/Samples/build_motion_photo_fixtures.php
  • tests/Unit/Metadata/MotionPhoto/JpegXmpReaderTest.php
  • tests/Unit/Metadata/MotionPhoto/MotionPhotoLocatorTest.php
  • tests/Unit/Metadata/MotionPhoto/MotionPhotoXmpTest.php
🚧 Files skipped from review as they are similar to previous changes: 25
  • resources/js/v8/components/gallery/albumModule/AlbumPanel.vue
  • docs/specs/4-architecture/roadmap.md
  • resources/js/v8/views/gallery-panels/Timeline.vue
  • docs/specs/6-decisions/README.md
  • resources/js/v8/views/gallery-panels/Search.vue
  • docs/specs/1-concepts/photos.md
  • app/Metadata/Extractor.php
  • resources/js/v8/components/gallery/albumModule/Virtualized/PhotoThumbVirtual.vue
  • resources/js/v8/components/gallery/albumModule/thumbs/PhotoThumb.vue
  • resources/js/v8/composables/photo/motionUrlLoader.ts
  • docs/specs/4-architecture/knowledge-map.md
  • docs/specs/4-architecture/request-lifecycle-photo-upload.md
  • app/Metadata/MotionPhoto/MotionPhotoXmp.php
  • resources/js/v8/composables/photo/useMotionHover.ts
  • resources/js/v8/composables/photo/useMotionHover.test.ts
  • tests/Unit/Metadata/MotionPhoto/JpegXmpReaderTest.php
  • tests/ImageProcessing/Image/Handlers/BaseImageHandler.php
  • app/Metadata/MotionPhoto/FtypBox.php
  • app/Metadata/MotionPhoto/JpegXmpReader.php
  • app/Console/Commands/ImageProcessing/ExtractMotionPhotos.php
  • app/Metadata/MotionPhoto/MotionPhotoLocator.php
  • tests/Samples/build_motion_photo_fixtures.php
  • tests/ImageProcessing/Commands/ExtractMotionPhotosTest.php
  • tests/Unit/Metadata/MotionPhoto/MotionPhotoXmpTest.php
  • tests/Unit/Metadata/MotionPhoto/MotionPhotoLocatorTest.php

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.

Comment thread docs/specs/4-architecture/features/100-google-motion-photos/open-questions.md Outdated
Comment thread docs/specs/4-architecture/features/100-google-motion-photos/plan.md
Comment thread docs/specs/4-architecture/features/100-google-motion-photos/tasks.md Outdated
The fallback scan started at the start-of-scan marker, so compressed image data that looked like an MP4 header could be taken for the video. It now starts after the end-of-image marker of the primary image. The plan and tasks no longer prescribe vp check --fix and no longer list Flow.vue as a loader provider.

Spec impact: FR-100-01, NFR-100-02, SVC-100-04, ADR-100-01 and the knowledge map describe the new scan start, and the plan records the second review round.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ildyria

ildyria commented Oct 18, 2026

Copy link
Copy Markdown
Member

Hi, thank you for your contribution!

You say:

Why not php-exif. The XMP is read in Lychee rather than through php-exif (ADR-098-01). php-exif maps these tags only for exiftool, and exiftool flattens the directory into parallel lists. A real Pixel file shows why that breaks: its primary item has no Item:Length at all, so the flattened lengths no longer line up with their items.

Why not make a PR on https://github.com/LycheeOrg/php-exif to add the support for it ?

@mlapaglia

Copy link
Copy Markdown
Contributor Author

LycheeOrg/php-exif#83

@codecov

codecov Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.31%. Comparing base (f4a3197) to head (1a08abf).
⚠️ Report is 9 commits behind head on master.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ildyria

ildyria commented Oct 19, 2026

Copy link
Copy Markdown
Member

Pixel clips are HEVC. Browsers that can't decode HEVC (e.g. Firefox) keep the still in the grid and show no error.

Should we transcode them with ffmpeg then?

mlapaglia and others added 2 commits October 19, 2026 07:16
php-exif 1.5.0 now locates the video of Google Motion Photos for every reader (LycheeOrg/php-exif#83). Bump the dependency, drop Lychee's own reader and its unit tests, and let the backfill command call the php-exif locator.

Spec impact: ADR-100-02 supersedes ADR-100-01, Q-100-08 records the decision, and FR-100-01, the services catalogue, test strategy, knowledge map and upload lifecycle now point at php-exif.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mlapaglia

Copy link
Copy Markdown
Contributor Author

Why not make a PR on https://github.com/LycheeOrg/php-exif to add the support for it ?

Good call, done. The reader now lives in php-exif (LycheeOrg/php-exif#83, released in 1.5.0). Reader::read() reports the video as MicroVideoOffset for every adapter, in both the legacy and the Motion Photo 1.0 format.

This PR now bumps lychee-org/php-exif to ^1.5.0 and drops Lychee's own copy of the reader and its unit tests. Extractor reads getMicroVideoOffset() as before, and lychee:extract_motion_photos calls PHPExif\MotionPhoto\MotionPhotoLocator directly. The description and ADR-100-02 are updated to match.

@socket-security

socket-security Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedcomposer/​lychee-org/​php-exif@​1.4.0.0 ⏵ 1.5.0.010010090100100

View full report

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 56ce5f28-1c05-470e-a25d-d4d1081c87de
📥 Commits

Reviewing files that changed from the base of the PR and between e9bd499 and f639e06.

⛔ Files ignored due to path filters: 4
  • composer.lock is excluded by !**/*.lock
  • tests/Samples/google_motion_photo_pixel.jpg is excluded by !**/*.jpg
  • tests/Samples/google_motion_photo_ultrahdr.jpg is excluded by !**/*.jpg
  • tests/Samples/google_motion_photo_v1.jpg is excluded by !**/*.jpg
📒 Files selected for processing: 13
  • app/Console/Commands/ImageProcessing/ExtractMotionPhotos.php
  • app/Metadata/Extractor.php
  • composer.json
  • docs/specs/4-architecture/features/100-google-motion-photos/open-questions.md
  • docs/specs/4-architecture/features/100-google-motion-photos/plan.md
  • docs/specs/4-architecture/features/100-google-motion-photos/spec.md
  • docs/specs/4-architecture/features/100-google-motion-photos/tasks.md
  • docs/specs/4-architecture/knowledge-map.md
  • docs/specs/4-architecture/request-lifecycle-photo-upload.md
  • docs/specs/4-architecture/roadmap.md
  • docs/specs/6-decisions/ADR-100-01-in-house-motion-photo-xmp-reader.md
  • docs/specs/6-decisions/ADR-100-02-motion-photo-reader-in-php-exif.md
  • docs/specs/6-decisions/README.md
🚧 Files skipped from review as they are similar to previous changes: 7
  • docs/specs/4-architecture/request-lifecycle-photo-upload.md
  • app/Metadata/Extractor.php
  • docs/specs/4-architecture/roadmap.md
  • docs/specs/4-architecture/knowledge-map.md
  • docs/specs/6-decisions/ADR-100-01-in-house-motion-photo-xmp-reader.md
  • docs/specs/4-architecture/features/100-google-motion-photos/plan.md
  • docs/specs/4-architecture/features/100-google-motion-photos/tasks.md

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread app/Console/Commands/ImageProcessing/ExtractMotionPhotos.php
Comment thread app/Console/Commands/ImageProcessing/ExtractMotionPhotos.php
Comment thread docs/specs/4-architecture/features/100-google-motion-photos/spec.md Outdated
mlapaglia and others added 2 commits October 19, 2026 10:04
Current Pixel phones record motion photo clips in HEVC, which several browsers cannot play. Copy H.264 clips as before and transcode any other codec to H.264 (libx264, CRF 23) in the same .mov container, with the rotation applied. lychee:extract_motion_photos --reencode re-extracts clips stored before this change.

Spec impact: Q-100-09 resolved, the Non-Goals, FR-100-02, FR-100-04 and S-100-21 to S-100-23 describe the transcoding and --reencode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A full batch with a failed photo printed an --after cursor that skips it. Print each failure and, after the cursor, ask to run the same batch again first.

Spec impact: FR-100-04 describes the failure lines and the retry hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2f6af084-3946-468b-9e9f-c2866cd53a59
📥 Commits

Reviewing files that changed from the base of the PR and between f639e06 and 343a97c.

📒 Files selected for processing: 12
  • app/Console/Commands/ImageProcessing/ExtractMotionPhotos.php
  • app/Image/Handlers/GoogleMotionPictureHandler.php
  • app/ModelFunctions/H264MOVFormat.php
  • docs/specs/4-architecture/features/100-google-motion-photos/open-questions.md
  • docs/specs/4-architecture/features/100-google-motion-photos/plan.md
  • docs/specs/4-architecture/features/100-google-motion-photos/spec.md
  • docs/specs/4-architecture/features/100-google-motion-photos/tasks.md
  • docs/specs/4-architecture/knowledge-map.md
  • docs/specs/4-architecture/roadmap.md
  • tests/ImageProcessing/Commands/ExtractMotionPhotosTest.php
  • tests/ImageProcessing/Image/Handlers/BaseImageHandler.php
  • tests/Traits/ProbesStoredVideo.php
🚧 Files skipped from review as they are similar to previous changes: 5
  • docs/specs/4-architecture/features/100-google-motion-photos/tasks.md
  • docs/specs/4-architecture/knowledge-map.md
  • docs/specs/4-architecture/features/100-google-motion-photos/plan.md
  • docs/specs/4-architecture/features/100-google-motion-photos/open-questions.md
  • docs/specs/4-architecture/roadmap.md

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread app/Console/Commands/ImageProcessing/ExtractMotionPhotos.php
@mlapaglia

Copy link
Copy Markdown
Contributor Author

Should we transcode them with ffmpeg then?

Yes, done in 43af3bb. Embedded clips in H.264 are still copied as is; any other codec (HEVC from current Pixels) is transcoded to H.264 with libx264 at CRF 23 in the same .mov, with the rotation applied, so Firefox plays them too. On the Pixel sample it takes 0.17 s and the clip goes from 3.69 MB to 1.15 MB.

Clips stored before this change can be converted with php artisan lychee:extract_motion_photos --reencode, which extracts them again from the original. Apple Live Photo videos are separate uploads and are not touched.

Comment thread app/Metadata/Extractor.php Outdated
…ment

Remove the in-house XMP reader ADR and renumber the php-exif reader ADR from ADR-100-02 to ADR-100-01, folding the context and alternatives it still needs into it.

Drop the micro_video_offset doc comment in Extractor, left over from the removed in-house locator call.

Spec impact: ADR index, Feature 100 spec (DO-100-01), plan, tasks and open questions, roadmap and knowledge map now cite ADR-100-01

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ildyria

ildyria commented Oct 19, 2026

Copy link
Copy Markdown
Member

@coderabbitai approve please

@ildyria
ildyria enabled auto-merge (squash) October 19, 2026 18:43
@coderabbitai

coderabbitai Bot commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

@ildyria, submitting your approval request for this PR.


🧠 Learnings used
Learnt from: mlapaglia
Repo: LycheeOrg/Lychee PR: 4900
File: app/Console/Commands/ImageProcessing/ExtractMotionPhotos.php:202-223
Timestamp: 2026-10-19T17:57:34.666Z
Learning: In Lychee's PHP command app/Console/Commands/ImageProcessing/ExtractMotionPhotos.php, normal extraction uses GoogleMotionPictureHandler::videoPathNextTo() to derive a deterministic video path from the original. If Photo::save() fails after the file write, the database live_photo_short_path remains NULL. The photo remains eligible for extraction, and a retry replaces the same target rather than creating another file. This retry behavior is intentional; immediate target cleanup is not required by this design.
✅ Action performed

Changes approved. Review threads were not changed.

@ildyria
ildyria merged commit a7894ea into LycheeOrg:master Oct 19, 2026
72 of 106 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Lychee does not support modern Google Motion Pictures (aka Live Photos) [Enhancement] Support new Google motion photos Android 11

2 participants