From d3f9d2019039ffb967620d5c2803d5732dfeebce Mon Sep 17 00:00:00 2001 From: Fisto Date: Wed, 16 Sep 2026 13:05:31 +0200 Subject: [PATCH] Deduplicate media across scrapes and polish the viewer controls --- CURRENT.md | 6 ++ docs/media-scraper.md | 2 + docs/media.md | 14 +++- media-scrape/src/download.ts | 34 +++++++--- media-scrape/src/engine.ts | 12 ++-- media-scrape/src/extract.ts | 6 +- media-scrape/src/store.ts | 24 +++++-- media-scrape/test/run.ts | 6 +- poc/public/index.html | 1 + poc/public/media.css | 7 ++ poc/public/media.js | 45 +++++++------ poc/src/media.ts | 126 +++++++++++++++++++++++------------ 12 files changed, 198 insertions(+), 85 deletions(-) diff --git a/CURRENT.md b/CURRENT.md index d686e82..ba94d40 100644 --- a/CURRENT.md +++ b/CURRENT.md @@ -40,6 +40,12 @@ mobile draft at 390px. - [x] C11 preview caching: Discord-hosted image/GIF previews (WebP 320px, hash-addressed, 5 MiB cap, one attempt) run after every walk in both modes. Live run `...-20260916090722`: `previewed: 2`; thumbs served over `/api/media///thumb` (200). + - [x] C20 Cross-scrape dedupe and viewer polish. `queryMedia` and `stats` deduplicate by URL + (newest scrape wins), so overlapping scrapes no longer duplicate cards or counters (live: 17 + items / 17 unique). Viewer: big play icon top-right; play embeds a provider player or a direct + mp4/webm/m4v, otherwise opens the external warning; **View full** opens the embedded + media/player in a new tab while the link button opens the original page; duration is stored + (Discord attachment duration, ffprobe for downloads, 0 normalized to unknown); favicon added. - [x] C19 External embed previews and MIME fixes. Embeds with no thumbnail in Discord's data are resolved via provider oEmbed/fxtwitter and an `og:image` fallback; preview bytes keep their real extension (`preview_ext`), so JPEG thumbnails serve as `image/jpeg` instead of mislabeled diff --git a/docs/media-scraper.md b/docs/media-scraper.md index 5626d48..b21d34f 100644 --- a/docs/media-scraper.md +++ b/docs/media-scraper.md @@ -111,6 +111,8 @@ Downloads run after a finished walk when the scrape used `mode: "download"`: `//thumbnails/` while the local copy stays for the Media tab. - Downloaded videos get a poster frame (`ffmpeg -ss 0.5`, 480px WebP) cached in `thumbs/`, because Discord serves no thumbnails for uploaded video files. +- The ffprobe pass fills missing dimensions and `duration_secs` after a download. Attachment + durations come from Discord; the 0 that Discord sends for non-audio files is treated as unknown. - Progress counters: `downloaded`, `downloadErrors`. Previews run after every walk: diff --git a/docs/media.md b/docs/media.md index 99ea1ac..36004f7 100644 --- a/docs/media.md +++ b/docs/media.md @@ -67,6 +67,10 @@ media-scrape/db/ - The database is an index. It holds the scrape state, the messages seen, the media items, and the link between a media item and its source messages. Source channel, author, and timestamp are indexed so filters can be added later without a re-scrape. +- Scrapes stay self-contained, but the API and UI **deduplicate by URL across scrapes**: one card + per URL, the newest scrape wins; stats count unique URLs too. Re-scraping a channel does not + duplicate anything in the grid or the counters. (Files are still stored per scrape, so the + on-disk size doubles with overlapping scrapes; a shared content store is a possible later step.) - Storage uses `node:sqlite` (experimental in Node 22, prints a warning). If its API changes, `better-sqlite3` is the fallback; it needs a native build, so that switch is an owner install. - The file name in `media/` is the SHA-256 of the bytes, never the original name. The original @@ -147,9 +151,13 @@ that deletion is irreversible. The delete button is disabled for a 5 second coun tiles carry a translucent play icon top-right. Discord serves no thumbnails for uploaded videos, so a video without a cached preview renders a first-frame `