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.
71 lines
3.8 KiB
Markdown
71 lines
3.8 KiB
Markdown
# 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.creationdate` tags 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).
|