Repository navigation
Making vivo / iQOO dual-file live photos work in Immich (merge + link, no fork) #32598
duruonanni
started this conversation in
Show and tell
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Sharing a small tool in case anyone else has a phone that splits live photos into two files.
The problem
My vivo X100 Ultra (also iQOO, and some OPPO/realme builds) saves a live photo as two loose files:
They're joined only by a private
vivo Live Photo IDthat Immich never reads, so both detectionpaths in
metadata.service.jsmiss them — Apple'sContentIdentifierpairing and the AndroidCamera:MotionPhoto/MicroVideosingle-file formats.The consequences:
The black frame is not an Immich bug
This one cost me a while, so it's worth stating: the clips are full-range
yuvj420pwithcolor_range=pc, and the Windows hardware decoding path renders them black. The very same fileplays fine in Chromium on Linux (software decoding), which is what made it look like an Immich
problem. Re-encoding to
yuv420p/tv/bt709fixes it, and also cuts the file size roughlyin half.
What I ended up building
https://github.com/duruonanni/motion-photo-bridge (MIT, Python 3.10+,
ffmpeg)Container:DirectoryXMP.The photo's pixels and the entire EXIF segment are copied byte for byte (verified by hashing the
segment before and after).
livePhotoVideoIdon the image andvisibility=hiddenon theclip — exactly what Immich does for a phone it recognises.
For photos already in the library there's no need to delete and re-upload:
tools/apply_batch.pyreplaces the image file in place and moves the clip underencoded-video/,so asset IDs, albums, tags, favourites and dates all survive. I ran it over 878 pairs (~55 min,
every pair backed up first). The download archive is now a single
.jpg, and the timeline showsone picture with a motion button.
Integration: workflow + webhook, no fork
I deliberately didn't fork Immich. It uses a Workflow (
AssetCreate, filename matches\.mp4$)that POSTs to a small receiver, which then calls the official API. Two details that cost me time
and might save someone else:
workflowRepository.search({userId: asset.ownerId}).The workflow must be created by the account that uploads the photos; an admin creating it on their
behalf will never fire.
http://172.17.0.1:<port>.host.docker.internalisn't configured in the stock compose file.defer; the next upload or a periodicsweep catches it.
Why not a plugin?
The plugin system is clearly coming along —
@immich/plugin-sdk, the Extism JS PDK, and hostfunctions including
httpRequestthat do exactly what my receiver does. But on v3.3.3/api/pluginsonly exposes GET routes (/,/:id,/methods,/templates); there's noinstall/upload endpoint yet, so a workflow webhook is still the only supported way in from outside.
Once plugins can be installed, this maps onto one almost directly.
Happy to answer questions — and I'd love to hear from anyone whose phone uses a different pairing
scheme, since same-stem
JPG+MP4may not be the only convention out there.All reactions