Create Live Photo
Turn Any Frame Sequence Into an iPhone Live Photo
- images
- audio
- photo
- video
- output_path
The name is the whole pitch. You give this node a batch of frames - from a video loader, an AnimateDiff pass, whatever - and it writes out an honest-to-goodness iPhone Live Photo you can AirDrop to your phone and set as a wallpaper. And unlike half the ComfyUI ecosystem, it calls no API, needs no key, and downloads no models. It's pure local video encoding.
A Live Photo is, mechanically, a boring trick: a still JPEG plus a short H.264 .MOV with the same base filename, zipped together by whatever phone handles it. That's exactly what this node produces - IMG.JPG and IMG.MOV in a timestamped livephoto_* folder under ComfyUI's output directory. Apple enforces a hard 1–5 second limit on the video part, which is why the duration input stops at 5.0.
How it works
The source is refreshingly small and easy to read. It writes every frame in your batch to a temp MP4 with OpenCV (mp4v codec), then shells out to ffmpeg to remux that into the final .MOV - H.264, scaled to 720px wide, trimmed to your duration with -t. If you feed it the optional audio, it tries to dump that to a WAV and mux it in as AAC. The key frame you pick gets saved as the JPEG at quality 95, and the whole thing lands in the output directory as IMG.JPG + IMG.MOV.
The example workflow wires VHS_LoadVideo straight in, which is the canonical setup: load a video, hand its frames to LivePhotoCreator, and out come the phone-ready files.
The inputs that actually matter
images(IMAGE) - your frame batch. Any sequence works.key_frame_index(INT, default 0) - which frame becomes the staticIMG.JPG. This is what people actually spend time on; it's the cover of your Live Photo. Pick the frame that looks best frozen.duration(FLOAT, 1–5, default 3) - how long the motion plays. Max is 5 because of the iPhone format; that's not negotiable, it's the spec.fps(FLOAT, 1–60, default 30) - output frame rate.audio(AUDIO, optional) - mux in sound if you've got it. Best-effort: if it can't be processed, the node prints a warning and ships video-only rather than dying.
The three outputs are photo (the key frame as an IMAGE, wire it into PreviewImage), video (the path to IMG.MOV, wire it into the pack's LivePhotoPreview node), and output_path (the folder, handy for a save or text node).
Where people get burned
Three things are easy to miss. First, the output is downscaled to 720px wide no matter what you feed in - the README frames it as "quality while keeping file size reasonable," but if you wanted 1080p on your phone, this silently gives you 720. Second, an out-of-range key_frame_index doesn't error, it clamps to the last frame, so a wrong index won't necessarily announce itself. Third, ffmpeg must be on your PATH; if it's missing, the node fails at the encode step, not at import. The console will tell you - the pack's startup check flags a missing ffmpeg explicitly.
Installing it
ComfyUI Manager is the easy path - search for "ComfyUI LivePhoto Creator" and restart. Or clone it in:
cd ComfyUI/custom_nodes
git clone https://github.com/stormcenter/ComfyUI-LivePhotoCreator
The runtime deps are light: opencv-python, pillow, and a system ffmpeg.
pip install opencv-python pillow
sudo apt-get install ffmpeg # or brew install ffmpeg / the Windows installer
Restart and it appears under image/animation. No model files to chase - which, for a node whose entire job is post-processing, is exactly how it should be.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| key_frame_index | INT | 0 | — |
| duration | FLOAT | 3.01–5 | — |
| fps | FLOAT | 301–60 | — |
| audioopt | AUDIO | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| photo | IMAGE | — |
| video | VIDEO | — |
| output_path | STRING | — |