feat: Cut an exact frame or a range sheet out of a finished recording, even with Unity closed
Merged
HA
8d ago+1026+1026+0123456789012345678901234567890123456789-11-11-01234567890123456789
Summary
- After a recording stops, an agent can cut the exact frame at a moment, or a sheet of the frames around it, out of the video with one
record-videocommand. It needs ffmpeg but does not need the Unity Editor to be running.
User Impact
- Before: the burst and timeline sheets were fixed when the recording stopped. To look closer at another moment, or at full size, an agent had to drive ffmpeg itself and guess how the seconds in the response map to video time.
- After:
uloop record-video --video <OutputPath> --frame-at <seconds>writes the full-size frame beside the video.--sheet-from <s> --sheet-to <s>writes a sheet of up to 40 consecutive frames of that range (5 columns, 320 px tiles).- The seconds are the ones the responses already report (
MarkSeconds,BurstFrames,TimelineFrames). Each image reportsEncodedFrame,CapturedIndex, andSeconds, so it lines up with the burst and timeline sheets frame for frame. - Without ffmpeg the command fails with
FFMPEG_NOT_FOUNDand says the sheets written at stop are still available. An ffmpeg failure, or a time past the end, fails withFFMPEG_FAILEDand the end of ffmpeg's stderr.
Changes
- Times are read on the capture clock and mapped through the
frames.jsonsamples written at stop. Each maps to the frame showing the last capture at or before that time, clamped to the recording.- The video's own clock (frame number / frame rate) drifts from the capture clock. A first version that let ffmpeg's
fpsfilter pick frames by time put the first changed tile two frames away from the burst's.
- The video's own clock (frame number / frame rate) drifts from the capture clock. A first version that let ffmpeg's
- ffmpeg seeks half a frame before the target frame. A sheet reads exactly frames A..B with
-ss/-tand keeps every step-th frame withselect, so frames after B never fill the empty slots. - A frame the encoder repeated to keep pace reports the capture it repeats.
- The flags are CLI-only, declared in the shared table that feeds help, suggestions, and the catalog check. The extraction rejects any other option, because Unity never sees them and they would be silently ignored.
- Only recordings with a
frames.jsonbeside them, made by this version, can be cut.
Verification
go testfor the project runner: the extraction tests pass. They cover time-to-frame boundaries (exact capture time, before the first capture, after the last), a repeated frame, step and rows, the-ss/-t/selectarguments, argument errors, missing or emptyframes.json, a missing ffmpeg, ffmpeg failure, and a stale output file.golangci-lint: 0 issues. In the sandbox the only failing test is an unrelated one that binds a Unix socket in the system temporary folder.- Mutations caught:
>→>=at the time boundary, reporting the next capture for a repeated frame, seeking without the half frame, an off-by-one read length, a floor instead of a ceiling for the step, and ignoring the step. Earlier ones also caught: not deleting a stale output, not checking that ffmpeg wrote the image, and ignoring a failed ffmpeg lookup. check-release-triggersandsync-tool-docs.sh --checkpass.- Live check, macOS only (Windows resolution of
ffmpeg.exewas not checked):- On a recording whose trigger lit a key 3.8 s in,
--sheet-from 3.506 --sheet-to 4.106gave 19 tiles. The first lit tile is EncodedFrame 111 / CapturedIndex 109 / Seconds 3.740, the same frame as in the burst sheet, and the next lit tile also matches. --frame-at 3.806returned frame 113, and its pixels are identical to frame 113 decoded directly from the video.- A
.webmrecording (VP8, millisecond timestamps):--frame-at 1.5returned frame 43, and its pixels are identical to frame 43 decoded directly. - With the Unity Editor quit (
status: NotRunning), the same--frame-atthrough the dispatcher succeeded.
- On a recording whose trigger lit a key 3.8 s in,