← All topicsOpen Media →

/media is the gallery of everything that has already been ingested, from Reddit, Instagram, and Telegram alike. It is where you find a clip and then do something to it: caption it, queue a generation job, trim it, tag it, favorite it, or archive it out of the way. Everything is one infinite-scrolling grid; there are no page numbers.

The one thing to know before anything else: the landing view is already filtered. Opening /media with no URL params does not show you the library — it shows today's media, under 20 seconds, that has no ComfyUI job yet. See "Filters" below for how to widen it.

Implementation detail — SSR strategy, page-size sizing, and the index scripts the queries depend on — lives in docs/architecture/media-gallery.md.

Filters

All filtering lives behind the Filters button at the top right of the grid, which opens a sheet. To its left is the active-filter row: one pill per filter that is currently narrowing the results, and tapping a pill clears that one filter. When nothing is narrowing the view it reads "No filters active".

Three filters are on by default and they are the reason a fresh /media looks emptier than the library really is:

Filter Ships as Widen it to
Day TODAY ALL
Comfy NO COMFY JOB ALL
Duration ≤20s Any duration

Day shows a pill even at its default value, precisely so a fresh landing does not read as unfiltered when it is not. Clearing the Day pill jumps to All days rather than back to Today — the pill is a "widen this" control, not an undo.

The full set of sections in the sheet:

  • Day — TODAY / YESTERDAY / ALL.
  • Source — REDDIT / INSTAGRAM / TELEGRAM, multi-select. Turning all three off does not empty the grid; it snaps back to all three.
  • Subreddit and Channel — sub-pickers that only appear once Reddit (respectively Telegram) is on and the current day actually contains some. Turning a source back off clears its sub-picks, so you cannot re-enable Reddit later and find a stale subreddit quietly constraining the view.
  • Caption — ALL / CAPTIONED / NOT CAPTIONED. NOT CAPTIONED is the filter for "what have I not captioned yet" — and remember to widen Day and Duration alongside it, or you are only seeing today's short clips.
  • Comfy — ALL / WITH COMFY JOB / NO COMFY JOB.
  • Favorites — ALL / FAVORITES.
  • Tags — one horizontally-scrolling row per namespace. Selecting more than one tag matches any of them, so more selections means more results, not fewer. Full explanation on the Tags page.
  • Confidence — High / Needs review / All, filtering on tag confidence rather than on the tags themselves.
  • Duration — ≤15s / ≤20s / ≤30s / ≤60s / Any duration.
  • Aspect — Portrait / Landscape / Any aspect.
  • Sort — Newest first / Oldest first.

Filter changes take two seconds to apply. The chips highlight the instant you tap them, but the actual refetch waits for a two-second quiet window, so a burst of taps costs one fetch instead of one per tap. Nothing is broken if the grid sits still for a moment after your last tap — the whole bar dims slightly while a commit is in flight.

Every filter is in the URL, so a view worth returning to is worth bookmarking. Hand-edited URLs are safe: an unrecognized value silently falls back to that filter's default rather than throwing an error page.

What a card gives you

Cards load 36 at a time and the next page prefetches as you scroll. While anything on screen is mid-flight — captioning, generating — the grid re-polls itself every few seconds, but only while the browser tab is actually visible.

The card footer carries four small metadata fields: ingested date, id, caption jobs, and comfyui jobs. Two of them are links. The external-link icon beside id opens that clip's full detail page at /detail/media/<id> in a new tab — that is where the per-clip tag editor with provenance lives. The comfyui jobs count links to every generation job made from this clip.

Under the footer sit quick tag chips: the clip's top three tags by confidence with a "+N" overflow. Tapping a chip removes that tag. They only ever show tags already applied, so there is nothing to add by tapping — adding is the detail page, bulk-select, or the rapid tagger.

Actions on one clip

On a phone the action row is already visible — the overlay auto-reveals on any coarse-pointer device, so there is nothing to tap first. On a desktop with a mouse, hover the card to bring it up.

Action What it does
Add to Jobs Creates a pending generation job for this clip. It shows up on Jobs with pending status.
Caption / Recaption Runs captioning. The button reads Recaption on a clip that already has a caption, and that variant forces a fresh one rather than skipping. It reads Captioning… / Recaptioning… while in flight.
View Caption Opens the structured caption in a modal. Only present once the clip has one.
Trim Set start/end timecodes or cut sections out of the middle. On a clip you have already trimmed, the modal also offers Revert to drop back to the untrimmed source.
Favorite / Unfavorite Toggles the star. It fills in once the clip is favorited. Favorited clips are what the Favorites filter and the /favorites screen show.
Archive Takes the clip out of the gallery. The record survives in the database — this hides, it does not delete.

Acting on many clips at once

Each card has a select checkbox in its corner. Selecting anything raises a bar across the bottom of the screen with three rows: the count plus Select all (N) / Unselect all, then the quick actions Add to Jobs, Caption, and Archive, then tag chips grouped by namespace. Tag chips apply or remove a tag across the whole selection in one call. Select all adds the currently loaded clips to what is already selected rather than replacing it, so it never silently drops an out-of-view selection.

One click, one pass. Every control that performs something — a quick action or a tag chip — lands across the whole selection and then dismisses the bar back to the normal view. Select all and Unselect all are the exceptions, since they shape the selection rather than acting on it. The tradeoff: putting a second tag on the same batch means selecting it again.

Two behaviors here have bitten before:

  • The selection deliberately survives filter and scroll changes. If part of your selection is no longer on screen, the bar says so — "N selected" with an out-of-view count beside it. It clears when you press Done or Unselect all, and any action dismisses the bar on the click — including when that action later fails, so a failed Archive leaves you to reselect. Check that count before pressing anything: the action applies to the full selection, not to what you can see.
  • Tagging No face archives. It is the one tag that does more than label: it flags the media isArchived and archives its generation jobs, across the whole selection. One-way — restore from the media detail page. See the Tags page.
  • Applying a tag in bulk pins it. A hand-applied tag freezes that whole namespace against future auto-tagging on those clips, permanently. That is what you want when correcting; it is expensive when careless. The rule is spelled out under "Manual pinning" on the Tags page.

The bar goes busy as a whole while any write is in flight — a tag write and an Archive both mutate the same selection, and letting them race is not worth the saved spinner.

The rest of the Media menu

The Media nav item's submenu holds six screens that all reuse the same grid and the same card actions:

  • Browse by Tag (/media/tags) — a focused, one-tag-at-a-time view. Pick a single tag from the chip rows (grouped by namespace, each chip showing how many clips carry it) and the grid fills with every clip that has it — all-time, not just today, and across every confidence tier so the count on the chip matches what you see. It is single-select: picking another tag replaces the last, and tapping the active one clears back to the picker. Selecting a tag collapses the picker to a compact bar to give the grid room; Change brings it back. This is the discoverable front door to tag filtering that otherwise hides inside the Filters sheet; to combine tags, or narrow by day/source/duration, use /media proper. Not to be confused with Tags (/tags), which edits the vocabulary rather than browsing media. The bottom bulk-action bar and quick tag chips work here exactly as they do on /media — removing a clip's tag drops it from the view on the next refresh, since it no longer matches.
  • Recently Captioned (/media/recently-captioned) — newest captions first. The screen for eyeballing what a caption run just produced.
  • Reddit (/media/reddit) — the subreddit list, with per-subreddit media counts and recent activity. Each row can be synced on demand (re-fetch that subreddit now) or swept for captions. Tapping through opens that subreddit's gallery.
  • Instagram (/media/instagram) — the same for tracked profiles, searchable by username, with a per-profile caption sweep.
  • Telegram (/media/telegram) — the chat list. The per-chat action here is Browse, not Sync, because Telegram ingestion is opt-in per message rather than chat-wide; you preview and pick.
  • Subject Library (/media/subjects) — the reference faces captioning uses to describe people consistently across clips. Upload by drag-drop anywhere on the page or through the Upload Subject modal, crop the face, Recaption to re-run subject analysis, and filter by processing status. Deleting a subject is a hard delete, unlike Archive everywhere else in the app: it permanently removes the subject record, its caption jobs, describe runs and captions, and its stored images from R2. Generation jobs that used the subject are not deleted — they survive, still referencing a subject that no longer exists.
  • Prioritize (next to Archive on each subject) — makes one subject the exclusive target for all automatic generation until you turn it off. Prioritizing reassigns the existing pending queue (pending/captioning/captioned jobs) to that subject, and every new or refilled job and the whole automation window then serve only it; other subjects pause rather than lose their work. It is single-subject and reversible: prioritizing one subject un-prioritizes any other, and unprioritizing restores each reassigned job to the subject it came from (jobs that already generated stay put). The per-job + ("Add to Jobs") override bypasses priority, and captioning stays subject-agnostic. A gold Prioritized badge marks the active subject.