Files
live-photo-concat/README.md
T
ergosteur 302afee062 Initial commit: iPhone Live Photo clip concatenator
Detects genuine audio/video overlap between consecutive Live Photo
clips via audio cross-correlation, trims the duplicated footage, and
re-encodes a seamless, metadata-preserving output.
2026-07-26 12:41:19 -04:00

81 lines
3.3 KiB
Markdown

# live-photo-concat
Concatenate sequential iPhone Live Photo `.MOV` clips into a single seamless,
metadata-preserving video — with the real duplicate footage between clips
removed.
## Why
Each iPhone Live Photo `.MOV` captures roughly 1.5 seconds before and after
its key moment. When several Live Photos are taken in quick succession (e.g.
rapid-fire shutter presses), consecutive clips genuinely overlap: the same
seconds of real-world video and audio get captured twice, once at the end of
clip *N* and again at the start of clip *N+1*. Naively concatenating the
clips repeats that footage, which shows up as an odd "loop" at each clip
boundary.
This script:
1. Extracts the real video+audio streams from each clip (iPhone Live Photo
files bundle a handful of extra HDR/depth/metadata tracks that aren't
needed here).
2. Cross-correlates the audio at each clip boundary to measure the *actual*
overlap duration from the content itself (not just filename order or
whole-second creation timestamps, which aren't precise enough).
3. Trims the duplicated span off the start of each subsequent clip.
4. Re-encodes the video across the joins (trimming mid-GOP HEVC can't be done
with a plain stream copy) and concatenates everything into one file.
5. Carries over the original container metadata (GPS, device info, creation
time, Live Photo identifiers) from one of the source clips.
Audio stays uncompressed PCM throughout, so it never loses quality. Video
quality defaults to a bitrate slightly above the source clips' own bitrate
(configurable — see below).
## Requirements
- `ffmpeg` / `ffprobe` on `PATH`
- Python 3 with the packages in `requirements.txt`:
```bash
pip install --user -r requirements.txt
```
## Usage
```bash
./concat_live_clips.py IMG_2441.MOV IMG_2442.MOV IMG_2443.MOV ...
./concat_live_clips.py -o myvideo.mov clip1.MOV clip2.MOV ...
```
If `-o/--output` is omitted, the output filename is derived from the source
filenames' numbering, e.g. `IMG_2441.MOV .. IMG_2445.MOV` produces
`IMG_2441-2445.mov`.
### Numbered sequence shortcuts
```bash
# bash / zsh
./concat_live_clips.py IMG_{2441..2445}.MOV
# PowerShell
python .\concat_live_clips.py (2441..2445 | ForEach-Object { "IMG_$_.MOV" })
```
### Options
| Flag | Description |
|---|---|
| `-o, --output` | Output file path (default: derived from source filenames) |
| `--crf N` | Use CRF (quality-based) encoding instead of the default bitrate target. Lower = higher quality; ~14-18 is near-transparent |
| `--bitrate RATE` | Explicit target video bitrate, e.g. `12M` or `12000k` |
| `--lossless` | Mathematically lossless video encoding (much larger output) |
| `--bitrate-multiplier N` | When auto-selecting a bitrate, multiply the source clips' peak bitrate by this (default: `1.2`) |
| `--bitrate-floor RATE` | When auto-selecting a bitrate, never go below this (default: `10M`) |
| `--preset` | x265 preset (default: `medium`) |
| `--metadata-from N` | Take container metadata from the Nth input clip, 0-indexed (default: `0`, the first clip) |
| `--confidence-threshold N` | Minimum audio cross-correlation confidence required to trust an overlap detection before falling back to 0 (default: `0.9`) |
`--crf`, `--bitrate`, and `--lossless` are mutually exclusive; the default
(no flag) auto-computes a bitrate target from the source clips.