Skip to content

Clips: a 1-minute clip beside the 30-second clip (plan 199) - #830

Merged
TheOrcDev merged 11 commits into
mainfrom
plan-199-clip-length
Oct 25, 2026
Merged

TheOrcDev merged 11 commits into
mainfrom
plan-199-clip-length

Conversation

@TheOrcDev

Copy link
Copy Markdown
Owner

Plan 199. The owner asked: "We currently have a 30-second clip. Let's also add a 60-second clip." Clips used to save one length for every press: 30 s by default, or whatever Output → Clips → Length said. Now there are two lengths, chosen at the moment of the press. The ring always keeps the last minute, and each press asks for 30 seconds (unchanged, the default) or 1 minute.

The owner took every D1–D9 recommendation ("Execute the entire plan and create a PR"). The plan and its As built are in plans/199-one-minute-clips.md.

What the user gets

Where 30 seconds 1 minute
Stream Manager status bar Clip 30 s 1 min (beside it; both are ⋯ items below 640 px)
Global shortcut (unbound by default) Save a 30-second clip (the existing key, so current bindings keep their meaning) Save a 1-minute clip (new)
⌘K Save a 30-second clip Save a 1-minute clip
Stream Manager composer /clip or /clip 30 /clip 60
Voice (captions or Buddy listening) "clip that", "clip it", … "clip the last minute", "long clip"
Remote / Stream Deck intent clipMark clipMark { seconds: 60 }
  • Output → Clips loses its Length row (D2). Its copy names both clips, and it gets a second shortcut recorder. A saved 15 s / 2 min / 5 min choice is dropped. Release notes should say so.
  • A 1-minute press during a recording also leaves a mark that spans the minute, so Buddy's report and Clean cut's moment cover what was asked for (D8).
  • Disk use roughly doubles (D3): 33 two-second segment files instead of 18. That is about 68 MB at 8 Mbps and 332 MB at 40 Mbps (4K), and arming needs 4 × 60 s × bitrate free. CPU and GPU don't change: same encode, more files kept.

Changes

Slice Change
S1 replay_buffer.rs: CLIP_LENGTHS = [30, 60]. clip.mark / replay.save take { seconds? }; null or absent means 30 s, and any other length is refused with invalid-clip-length before any work. The length is clamped to the ring. ReplaySaveAccepted.seconds. A press that joins a queued save raises it to the longer length. RemoteIntent::ClipMark { seconds }, still {"kind":"clipMark"}-compatible.
S2 Voice phrases carry their length. "clip the last minute" and "long clip" save 60 s, and TAIL_WORDS is computed from the table. clip_marks.lookback_seconds (nullable, via ensure_column) feeds moments.rs; NULL reads as 30 s.
S3 TS contracts for every new field (strict schemas, protocol fixtures both sides). Old payloads still parse.
S4 Renderer: REPLAY_RING_SECONDS = 60. markClip(seconds?). A new global action clip-mark-long. ClipContext.seconds is now the 30-s length (never the ring's) plus longSeconds / longShortcutLabel. ClipMarkCommand.seconds is checked in main.
S5 UI (design skill, shadcn primitives only): Output → Clips, Settings → Shortcuts, ⌘K, the Stream Manager's two StatusBarActions and ⋯ items, the composer's /clip 60 completion, help and notice.
S6 smoke:replay-buffer proves both lengths, the refusal and the 60-s mark. Docs: docs/replay-buffer.md, CONTEXT.md, docs/remote-control.md. The live-lab phrase check reads the new phrase-table rows.

Rebased onto main after plan 198 (#828), #817, #819 and #827. #827's "let the ring roll a window past the analysis" rule is kept: the 30-s and 1-min presses go back to back before either clip is analyzed, and the remote 1-min save waits a full window after.

Verification (this Mac)

Local gates are running on the rebased tree; this section is filled in, and the PR marked ready, when they finish. Before the rebase: focused cargo tests (110), the real-FFmpeg save, clippy, fmt, typecheck, lint, format, the desktop suite (5,423), test:scripts (2,552), smoke:replay-buffer, smoke:remote-control and smoke:multistream with the 15-s ring all passed.

Owed

  • Owner by-eye in light and dark:
    • Output → Clips
    • Settings → Shortcuts
    • ⌘K with both items
    • the Stream Manager status bar, wide and below 640 px
    • the /cl completions
    • "Clip saved · Last 1:00"
  • Voice "clip the last minute" on a real take, with captions or Buddy listening.
  • Windows and Linux runtime: CI covers compile and unit tests only.
  • Release notes: the Length setting is gone; the ring keeps a minute.

@coderabbitai

coderabbitai Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The backend half of the second clip length. The ring's length and a
press's length are now two numbers:

- `CLIP_LENGTHS = [30, 60]`, `DEFAULT_CLIP_SECONDS = 30` and
  `clip_seconds(requested, ring)`: no length is today's 30 s, anything
  else is refused with `invalid-clip-length` ("A clip is 30 seconds or 1
  minute."), and the result is clamped to the ring, so a smoke's 15-s
  ring still saves 15 s and says so.
- `request_save(state, source, seconds)`: the queue keeps the length, and
  a press that joins a queued save raises it to the longer of the two.
  `ReplaySaveAccepted` gains the required `seconds`.
- `SaveSnapshot.seconds` is the press's length; `select_segments` and
  `replay.saved` follow it. The ring's prune keeps using the ring's.
- `clip.mark` and `replay.save` take optional `{ seconds?: 30 | 60 }`;
  absent or null params are `{}`.
- `RemoteIntent::ClipMark { seconds }`: `{"kind":"clipMark"}` still
  parses, a bad length is refused before the relay.
- `DEFAULT_SECONDS` (the ring's fallback while off) moves to 60.

Voice still saves 30 s here; S2 gives the phrases a length. The shared
fixture gains `seconds` on the accepted save and the new params; the TS
schemas follow in S3.
…th (plan 199 S2)

- The phrase table carries a length. "clip the last minute" and "long
  clip" save 1 min (D7); "clip that" and the others stay 30 s. The match
  reports its length and the voice mark saves it.
- `TAIL_WORDS` is derived from the table (longest phrase − 1, now 3), so
  a longer phrase can never outgrow the tail; a test pins it.
- `ClipMark.lookback_seconds` (wire `lookbackSeconds`, omitted when None)
  and a nullable `clip_marks.lookback_seconds` column through
  `ensure_column`. Every press stores its length; older rows stay NULL
  and read as 30 s (D8).
- `mark_moments` reaches back the mark's length, capped by the longest
  moment, so Buddy's report and Clean cut span the minute for a 1-min
  press.
- `LONG_CLIP_SECONDS = 60`; `CLIP_LENGTHS` and the ring's fallback length
  are built from it.
- Types: `ClipLength = 30 | 60`, `ClipMarkParams`, `ReplaySaveParams`,
  the required `ReplaySaveAccepted.seconds`, `ClipMark.lookbackSeconds?`
  and `ClipMarkCommand.seconds?`.
- `shared/clip-marks.ts` mirrors the Rust constants
  (`DEFAULT_CLIP_SECONDS`, `LONG_CLIP_SECONDS`, `CLIP_LENGTHS`, the
  `invalid-clip-length` code and sentence) with `isClipLength`,
  `parseClipLength` and `clipMarkParams` (no params without a length, so
  every existing caller sends what it sent before).
- RPC contract: `clip.mark` and `replay.save` take an optional
  `{ seconds?: 30 | 60 }` (absent stays valid); the accepted save's
  `seconds` is required; `clipMarkSchema` gains the optional
  `lookbackSeconds`.
- Marker context: `ClipContext.longSeconds` and `longShortcutLabel?` in
  the strict schema. Studio pushes `seconds: 30` and `longSeconds: 60`,
  never the ring's length.
- The Stream Manager relay keeps the length through main (main rebuilt
  the command from its request id alone); an odd length is refused
  there with the backend's sentence.
- The remote `clipMark` passes `seconds` to `markClip`; any other length
  is acked with the same sentence.
- The ring is fixed at `REPLAY_RING_SECONDS = 60` (D2). `ReplayBufferSettings`
  loses `seconds`; the normalizer drops a stored Length, `session-params`
  sends the constant, and the reconcile's "ring shape" no longer follows a
  setting. `__videorcSmokeSetReplayBuffer(armed, seconds)` still shortens
  the ring for a smoke through a renderer-only `smokeRingSeconds` that
  never persists. The Output page's Length row goes (its setting is gone);
  Disk use and the description read the fixed ring.
- `markClip(seconds?: ClipLength)`: with none the request carries no
  params, exactly as before. The Stream Manager relay and the remote
  `clipMark` pass the length through.
- A second global action `clip-mark-long` (config `clipMarkLong`, unbound,
  D5) saves the minute. `clip-mark` keeps its key and becomes "Save a
  30-second clip"; the new label is "Save a 1-minute clip". Main's
  registration and the IPC schema pick the key up through
  `globalShortcutEntries` and `isGlobalShortcutConfigKey`.
- The marker context carries `longShortcutLabel` beside `shortcutLabel`.
- `/clip` and `/clip 30` are 30 s, `/clip 60` the minute; anything else
  is "A clip is 30 seconds or 1 minute: /clip or /clip 60." (D6). The
  composer's `onClip(seconds)` and the window's `saveClip(seconds)` pass
  the asked length, and the Saving toast names it, not the ring's.
- Eager budget: `clipLengthWords` and `clipSavingToast` (Stream Manager
  only) and the length checks move to the lazy `shared/clip-lengths.ts`,
  so the eager gzip lands at 394,858 (base 394,848; ceiling 395,000).

`// plan 199 S5:` notes mark where the UI changes: two Stream Manager
buttons, the second shortcut recorder and the Output copy, the composer
notice.
Output → Clips names both clips and records a key for each; Settings →
Shortcuts gains Save a 1-minute clip; ⌘K offers Save a 30-second clip and
Save a 1-minute clip; the Stream Manager shows Clip 30 s and 1 min (two
items in the narrow ⋯ menu); the composer completes /clip 60 and names the
length asked for. The Length helper the removed row used is gone.
…e clip (plan 199 S6)

smoke:replay-buffer now expects the 1-minute ring at launch, saves a 30-s
clip (no length) and 1-minute clips (clip.mark and remote clipMark with
seconds 60), proves seconds 45 is refused and writes nothing, saves the
minute mid-recording with a 60-s mark, and watches the disk bound until 30 s
after the ring is full. CONTEXT.md, docs/replay-buffer.md and
docs/remote-control.md describe the two lengths.
The lab's phrase check reads clip_marks.rs's phrase table, whose rows now
carry the clip length a phrase saves; a lab phrase without seconds is 30 s.
@TheOrcDev
TheOrcDev marked this pull request as ready for review October 25, 2026 21:50
@TheOrcDev
TheOrcDev force-pushed the plan-199-clip-length branch from dbae543 to f269640 Compare October 25, 2026 21:50
@TheOrcDev
TheOrcDev merged commit 54721e5 into main Oct 25, 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.

1 participant