Documents things a future session would otherwise have to rediscover: no test suite/fixtures exist (verification requires real sample clips and ffprobe checks), the libvmaf stderr spam is a dev-machine quirk not a bug, detection thresholds were empirically tuned on one real dataset, the -r 30 normalization is load-bearing for bitrate targeting and shouldn't be removed casually, and a cross-platform GUI was considered and deliberately deferred.
3.8 KiB
CLAUDE.md
Guidance for working in this repo that isn't already covered by README.md or the scripts' own docstrings/comments.
There is no test suite, and no fixture files in the repo
.gitignore excludes *.mov/*.MOV, so there are no sample clips checked
in. The overlap-detection logic depends on real iPhone HEVC/PCM quirks
(variable frame rate, the exact multi-track Live Photo container structure,
genuinely-correlated real-world audio at clip boundaries) that aren't
practical to fake convincingly with synthetic ffmpeg test inputs. To verify
a change to concat_live_clips.py, ask the user for a real set of
sequential Live Photo .MOV files (or reuse ones from earlier in the
conversation if still on disk) and check results with ffprobe:
ffprobe -show_entries format=duration— output duration should equal the sum of (each clip's duration − its detected overlap).ffprobe -select_streams v -show_entries frame=pts_time— verify strictly increasing timestamps (no reordering artifacts at the joins).ffprobe -show_entries format_tags— confirm GPS/device/Live-Photo tags made it through from the source clip.- Cross-check detected overlap/gap values against the clips'
com.apple.quicktime.creationdatetags by hand for a sanity read before trusting a change to the correlation math.
The libvmaf stderr spam is an environment quirk, not a bug
Every libx265 encode on this dev machine prints repeated
libvmaf ERROR could not read model from path: ... / problem loading model file lines. This reproduces with a trivial synthetic
-c:v libx265 encode with zero flags, and -x265-params log-level=none
does not suppress it — it comes from libvmaf itself, not from anything
this script requests, and doesn't affect output correctness (exit code 0,
correct output every time this was checked). Don't spend time chasing it as
if it were caused by this codebase; the user has explicitly said they're
fine with it showing.
Detection thresholds are empirically tuned on one real dataset, not derived analytically
--confidence-threshold (0.9), --min-signal-rms (25),
--bitrate-multiplier (1.2), and --bitrate-floor (10M) were chosen by
testing against two real clip sets from one iPhone (see conversation
history / commit messages for the actual numbers observed: correlation
scores landed either ~1.000 or well below ~0.6, RMS values were in the
hundreds for real audio content). They aren't validated across other
devices, iOS versions, or recording environments. If a user reports a false
abort or a false pass, prefer surfacing the actual score/RMS numbers
(already printed) over silently loosening a default.
Rate-control depends on the -r 30 normalization — don't remove it
concat_live_clips.py forces constant 30fps output (see the comment above
video_args += ["-r", "30", ...]). This was added after the bitrate-target
mode was found to silently undershoot by >10x (target ~13Mbps, actual
~1Mbps, x265 hitting max QP) because the source clips' irregular VFR
timestamps confused x265's ABR rate control once concatenated. If touching
the encoding args, keep this normalization or re-verify bitrate targeting
actually hits its target (check ffprobe -show_entries stream=bit_rate on the output, not just that the encode succeeds).
A cross-platform GUI was considered and explicitly deferred
The user asked about a GUI, wanted it to look good on Mac/Windows/Linux and possibly iOS, then decided against it once the iOS scope implication became clear (iOS would require either a full native rewrite or a client-server model, not just a new frontend on the same script). Don't reintroduce that scope unprompted; if asked again, the open question is specifically desktop-only vs. desktop+mobile, since that decision determines the whole architecture (e.g. Tkinter/PySide6 vs. a local web app).