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.
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user