Skip to content

fix(capture): end screencast videos at the stop time - #54

Merged
chhoumann merged 6 commits into
mainfrom
fix/46-still-ending
Oct 24, 2026
Merged

chhoumann merged 6 commits into
mainfrom
fix/46-still-ending

Conversation

@chhoumann

@chhoumann chhoumann commented Oct 24, 2026 •

Copy link
Copy Markdown
Owner

Fixes #46.

Problem

capture record --backend screencast rejected any take that ended on a still screen. A real take with 1s of changes followed by 3s of stillness failed with Encoded duration 7.7s does not match wall-clock 4.51s.

Root cause

The screencast backend builds an ffconcat list in which every frame gets its real on-screen duration. It then repeats the last frame so ffmpeg honors that frame's duration. On ffmpeg 7 and later, the frame after the last entry is timed like the gap before it, so the repeat adds the final still stretch a second time. ffmpeg 4.4 and 6.1 held the repeat for one 40 ms tick instead. Measured with ffmpeg 8.0.1:

ffconcat list encoded duration
a 0.5s, b 3s, repeat b 6.5s
a 0.5s, b 3s, no repeat 1.0s
a 3s, b 0.5s, repeat b 4.0s
a 0.4999s, b 3s, repeat b, input -t 3.5 6.5s
a 0.4999s, b 3s, repeat b, output -t 3.5 3.5s

Takes that kept changing until the end only went over by one short frame gap, which the check tolerates. A take whose screen never changed was also affected, because its single fallback screenshot was doubled the same way.

Fix

Trim the encoded output to max(stop - start, 1/fps) with an output-side -t. The repeat still makes ffmpeg hold the last frame, and the trim ends the video where the take ended. An input-side -t is not enough. It becomes a trim before the fps filter and is defeated when the per-frame durations, each rounded to 4 decimals, add up to one tick less than the cut (the 0.4999 row above). The duration check is unchanged. CapturedSource.inputArgs is renamed sourceArgs because it now carries that output option.

CI and the release job now install ffmpeg. The runner images ship none, and the new test runs a real encode.

Verification

Real screencast takes against a live Obsidian 1.13.7 window (capture launch plus capture record), using the packed tarball with the final fix:

still 1-5.webm (1s of changes, 3s still)  4.4/4.43, 4.5/4.46, 4.4/4.44, 4.5/4.47, 4.4/4.40 (duration/wall)
still.mp4      (same)                     4.4/4.43
busy.webm      (changes until the end)    1.4/1.42
idle.webm      (no change at all)         2.0/2.01
short.webm     (instant take)             0.1/0.00

The new test drives startRecording and stop with real JPEG frames and a still ending, then runs the real ffmpeg encode and duration check. Before the fix it fails with Encoded duration 3.8s does not match wall-clock 2.10s.

The screencast backend builds an ffconcat list and repeats the last frame
so ffmpeg honors that frame's duration. ffmpeg times the repeated entry
like the entry before it, so the repeat added the whole final still
stretch a second time. A take that ended on a still screen, or never
changed at all, encoded at about double its wall-clock length and failed
the duration check.

Cut the concat input at the stop time with -t. The repeat still carries
the last frame, and the video now ends where the take ended.

Fixes #46
…top time

An input-side -t becomes a trim before the fps filter. It drops the
repeated last frame only when the per-frame durations, each rounded to
4 decimals, add up to at least the cut. When they come out one tick
short, the repeat survives and ffmpeg 7+ doubles the still tail again
(0.4999 + 3.0 with input -t 3.5 encodes to 6.5s). Trimming the encoded
output holds the length at the cut for every list.
@chhoumann
chhoumann marked this pull request as ready for review October 24, 2026 15:05
@chhoumann
chhoumann merged commit fb9605e into main Oct 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Recording that ends on a still screen doubles the last frame's duration and fails the duration check

1 participant