Markers feature — visual mockup

Click a tab. Hover the tiles to see the static→animated swap. Click a tile or the timeline pips to simulate seeking. Press N while the Scene Detail tab is active to open the create-marker dialog.
all doggy missionary cowgirl shower +12 tags
static thumbnail (idle)
animated preview (hover)
click → jump to timestamp
14:32 / 1:42:18 Press N to mark this moment

Code: SSIS-456

part 1 · 1080p · 1h 42m · 5 markers

What a marker actually is in the database

-- markers table — one row per labeled timestamp
CREATE TABLE markers (
  id             INTEGER PRIMARY KEY,
  image_id       INTEGER REFERENCES images(id) ON DELETE CASCADE,
  part_idx       INTEGER NOT NULL DEFAULT 0,    -- multi-part videos
  seconds        REAL NOT NULL,                  -- 872.3 = 14:32
  end_seconds    REAL,                           -- optional, for ranges
  title          TEXT,                           -- optional free-text
  primary_tag_id INTEGER REFERENCES tags(id),
  created_at     INTEGER NOT NULL,
  updated_at     INTEGER NOT NULL
);

-- secondary tags (many-to-many)
CREATE TABLE marker_tags (
  marker_id  INTEGER REFERENCES markers(id) ON DELETE CASCADE,
  tag_id     INTEGER REFERENCES tags(id) ON DELETE CASCADE,
  PRIMARY KEY (marker_id, tag_id)
);

Example row + derived files on disk

-- row in markers table
{
  id: 142,
  image_id: 4711,           -- the cover for SSIS-456
  part_idx: 0,
  seconds: 872.3,           -- 14:32
  end_seconds: null,
  title: "close-up · bedroom",
  primary_tag_id: 38,        -- "doggy"
  created_at: 1735776300000,
  updated_at: 1735776300000
}

-- secondary tags from marker_tags
[ 12 "bedroom", 19 "close-up", 22 "POV" ]

-- derived assets generated by ffmpeg, keyed by marker.id
data/marker-thumbs/142.webp     -- 320×180 static, ~40 KB
                                   ffmpeg -ss 872.3 -i video.mp4 -frames:v 1 -vf scale=320:-1 ...
data/marker-previews/142.webp   -- 5s animated, no audio, ~280 KB
                                   ffmpeg -ss 872.3 -t 5 -i video.mp4 -vf "scale=320:-1,fps=12" -loop 0 ...

-- both are caches. delete them and they regenerate on next request.
-- generation is queued, not blocking — same pattern as whisperjav.

Wall tile rendering — how the hover swap works

<div class="tile">
  <div class="tile-media">
    <img src="/api/marker-thumb/142"     // static, always loaded
         class="static">
    <img src="/api/marker-preview/142"   // animated webp, lazy-loaded on hover
         class="animated"
         loading="lazy">
    <span class="timestamp">14:32</span>
  </div>
  <div class="tile-meta">
    <span class="primary-tag">doggy</span>
    <span class="scene">SSIS-456 · part 1</span>
  </div>
</div>

// CSS handles the swap purely with hover + opacity transition.
// No JS needed for the swap; image fetch is browser-lazy.

Create marker at 14:32.300

Snapshot this moment so you can find it again. ffmpeg will queue a thumbnail and a 5s animated preview after save.
doggy × bedroom × close-up × POV ×