Skip to content

M1: GAM as the library - #2

Merged
davior merged 1 commit into
mainfrom
claude/funny-knuth-en5fly
Oct 16, 2026
Merged

davior merged 1 commit into
mainfrom
claude/funny-knuth-en5fly

Conversation

@davior

@davior davior commented Oct 16, 2026

Copy link
Copy Markdown
Owner

Second milestone (plan of attack, M1). GAM becomes GVC's media library.

What you can do with it

  • Find footage for a scene in GAM, including where something is said. Search "people talking about water" and a hit on a transcript line comes back as that moment. Matched words are highlighted, and the hit shows the time range and a preview of the clip.

  • Place a moment two ways:

    • Use as b-roll: muted under the scene's narration.
    • Play with its sound: the scene becomes a clip, and its narration is set aside. Discard brings it back.

    Either way the moment becomes the visual's trim. It's padded by 0.5 s so the cut doesn't clip a word, sized by a placeholder's moment when one asks for it, and kept inside the asset.

  • Pictures are placed whole.

  • Each scene shows its picture or footage: thumbnail, name, trim, or why there isn't one ("Still in the note", "No longer in the library", "To find: …").

  • "Footage still to find" lists the unresolved search placeholders. Resolving one trims every scene that shows it.

  • "Copy into GAM" finishes a push from Notes. Every file the spec still points at inside the note moves into your library, under the same key, as one new version. Failures stay to retry.

How

  • GVC calls GAM as you. The server forwards the caller's own Notes token (CallerToken), only after verifying it, so GAM's ownership checks apply and there's no service account.
  • Failures are explicit. GAM down gives 502 gam_unavailable. GAM refusing the session gives 502 gam_rejected_session, which names the JWT_SECRET_KEY mismatch.
  • GAM's answers are normalised into GVC's own models (schemas_gam.py), so a field GAM renames breaks one file.
  • Signed URLs are never stored. They expire, so names and thumbnails are looked up per page load (POST /api/gam/assets/lookup). The browser loads them straight from GAM: img-src and media-src now carry ${GAM_BASE_URL}, templated at container start like the Notes origin, and csp.test.ts guards it.
  • Copying note media only fetches /media/ paths from the configured Notes, since a pasted spec is untrusted input. The size cap (NOTE_MEDIA_MAX_MB) is enforced while the file streams.
  • GN-V2 is dropped. Notes serves /media/* without auth, so GVC fetches the files directly. That is Notes' existing behaviour; the plan doc now says so.
  • Works with or without davior/gam#36. Without end_time, a moment is taken as start + 5 s.

New settings

  • GAM_TIMEOUT_SECONDS (default 30) and NOTE_MEDIA_MAX_MB (default 4096), in .env.example and compose.
  • The frontend container now also takes GAM_BASE_URL, for the CSP.

Tests

  • Backend: pytest -q passes, 61 tests. GAM and Notes are faked behind one httpx.MockTransport (tests/fake_siblings.py). The tests cover:
    • the token reaching GAM, and a token this app did not verify never being forwarded;
    • absolute URLs, and a missing end_time;
    • documents filtered out;
    • lookup returning null for a deleted asset;
    • GAM down and GAM refusing the session;
    • copy-note-media: same keys, one version, partial failure then retry, SSRF attempts refused with no request made, the size cap, and ownership.
  • Frontend: format check, lint, npm test (76 tests: momentTrim, placeHit, resolveSearch, the footage search and the visual strip) and build all pass.
  • End to end against a real GAM. I ran GAM from gam#36 locally with a 60-second test video and a three-line transcript, a stand-in Notes media server, and GVC with Vite. In a browser:
    1. Searched "water running clear". It found the line at 0:21–0:27.
    2. Used it as b-roll and saved. The spec has trim: {in: 20.5, out: 28}, audio: "mute".
    3. Pushed the Notes fixture and ran "Copy into GAM". All 3 files became from: "gam", pending_note_media went to 0, and the thumbnails show.

Not in this PR

  • Rendering, and the byte cache it needs. Both are M2; the plan doc moves the cache there.
  • Audio for soundtracks: that's the lanes in M3.
  • Notes' "Make video" button (GN-V1).

🤖 Generated with Claude Code

https://claude.ai/code/session_016fiRJAt34ag62iPiuMkNhm


Generated by Claude Code

Find footage for a scene in GAM, including where something is said in
a video, and place that moment in the scene. Either as muted b-roll
under the narration, or as a clip that plays its own sound. The moment
becomes the visual's trim:
- padded so a cut does not clip a word;
- sized by a search placeholder's `moment` when it asks for one;
- kept inside the asset.

Search placeholders resolve the same way. Every scene showing one gets
the trim.

GVC's server calls GAM as the signed-in user by forwarding their own
Notes token, so GAM's ownership checks hold and there is no service
account. A token is only forwarded after this app has verified it.
GAM down is a 502 gam_unavailable. GAM refusing the session is
gam_rejected_session, which names the JWT secret mismatch.

Thumbnails and names are looked up per page load, because GAM's
signed URLs expire. They load straight from GAM, so the CSP's img-src
and media-src carry GAM_BASE_URL, templated like the Notes origin.

A note pushed from Notes can now finish the hand-off: "Copy into GAM"
moves each file the spec still points at inside the note into the
user's library. Each ref is replaced under the same key, so nothing
that used it changes. It saves one version. Failures stay to retry.
Only /media/ paths on the configured Notes are fetched, since a pasted
spec is untrusted input, and the size cap is enforced while the file
streams.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016fiRJAt34ag62iPiuMkNhm
@davior
davior marked this pull request as ready for review October 16, 2026 04:36
@davior
davior merged commit 659ccf6 into main Oct 16, 2026
3 checks 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.

2 participants