SCM — Screen Memories

Local-first: geen accounts, geen cloud en geen uploads. De inference draait volledig op je Mac.

Wat SCM onderscheidt

  • Zoek zoals je denkt – Beschrijf een herinnering in gewone taal; een lokaal vision-model doet de rest.
  • Video, tot op het moment – Scènes worden gesegmenteerd en voorzien van embeddings, zodat je direct bij het juiste shot uitkomt en niet alleen bij het videobestand.
  • Tekst en dialoog ook – OCR voor zichtbare tekst en exacte zoekopdrachten naar gesproken regels via Whisper, elk als een eigen modus.
  • Je eigen tabbladen en prompts – Sla elke zoekopdracht op als een tabblad; tabbladen voor screenshots en e-mail kunnen worden in- en uitgeschakeld.
  • Zelfonderhoudende bibliotheek – Gemonitorde mappen importeren automatisch, content-hashes voorkomen duplicaten bij het hernoemen van bestanden, en modelwissels zorgen voor re-embedding op de achtergrond zonder het zoeken te blokkeren.
  • Echt privé – Je media verlaat nooit de machine. Modellen worden één keer gedownload; daarna werkt alles offline.

Vijf manieren om te zoeken

ModusWat wordt gevonden
BestandenComplete foto's/video's op basis van betekenis — vision ranking met boosts voor bestandsnaam en frases.
ScènesMomenten binnen video's — zoek naar een specifiek shot en spring direct naar de tijdcode.
OCRTekst die zichtbaar is in afbeeldingen en frames, letterlijk gematcht (Tesseract; Engels + 35 taalopties).
DialoogExacte gesproken woorden in video's (Whisper), met verschillende niveaus van exactheid.
LLM's (optioneel)Lokale chat over de dialoog, OCR en bestandsnamen die je Mac al heeft geëxtraheerd — inclusief geciteerde antwoorden.

Vereisten

  • macOS (gepakket met electron-builder; menu-bar/tray functies zijn uitsluitend voor macOS).
  • Bun — het project gebruikt Bun als package manager en runner.
  • Node modules geïnstalleerd: bun install.
  • Modellen: Bij het eerste gebruik van een model worden de weights gedownload (~435MB voor het standaard CLIP-model); daarna werkt het volledig offline.

Downloaden

De eenvoudigste installatie is via Homebrew (Apple Silicon, macOS 12+). De cask van de tap verwijdert automatisch de macOS-quarantinelabel bij elke installatie en upgrade, zodat de app start zonder handmatige Gatekeeper-stappen:

brew tap allenv0/scm
brew trust allenv0/scm
brew install --cask allenv0/scm/scm

Upgrades behouden hetzelfde gedrag:

brew upgrade --cask allenv0/scm/scm

Voor wie de voorkeur geeft aan minimale rechten (trust alleen de cask in plaats van de hele tap):

brew tap allenv0/scm
brew trust --cask allenv0/scm/scm
brew install --cask scm

De tap bevindt zich op allenv0/homebrew-scm.

Ontwikkeling

bun run dev     # bouwt de renderer bundle en start de Electron-app
bun start       # start de Electron-app zonder opnieuw te bouwen
bun run build   # bouwt enkel de renderer bundle in dist/

Het app-pakket bouwen (DMG / ZIP)

bun run dist            # ondertekend als er een identity in de keychain staat
bun run dist:unsigned   # slaat code-sign detectie over

Dit proces voert twee stappen sequentieel uit:

  1. vite build — compileert de React renderer naar dist/ (verwerkt alle wijzigingen onder src/).
  2. electron-builder --mac — pakket de app. Het bundelt de nieuwe dist/ bundle samen met main.js, preload.js, main-lib/ en indexer/ (de bestandenlijst is geconfigureerd onder build.files in package.json), en produceert vervolgens de installers.

Output: De installers komen in dist-app/ (zie build.directories.output in package.json) — zoek naar SCM-0.2.4.dmg en SCM-0.2.4.zip.

Functies

Bestanden

Het typen start een directe pre-pass op bestandsnaam-trefwoorden, waarna het vision-model het overneemt. Resultaten worden gescoord via cosinus-gelijkenis tegen image embeddings, met gated phrase- en bestandsnaam-boosts, een 'honesty floor' die per model is gekalibreerd, en een diversiteitsfilter voor bijna-duplicaten. Elke tegel bevat een badge "waarom het matchte" (Visual match / Filename match / …) en een hover-tooltip met de score-uitsplitsing per component. CJK-zoekopdrachten zoeken als overlappende bigrams ("台北車站" matcht ook 台北 en 車站).

Scènes

Elk scènesegment in alle video's wordt gescoord, zodat een hit direct op het exacte shot landt. Tegels tonen de scène-poster met een tijdcode-badge; het openen van de video springt direct naar dat moment. Een noise gate geeft "geen scène-match" terug in plaats van het raster te vullen met onzin, en elke video draagt maximaal 3 scènes bij.

OCR

Matcht het fractie van de query-tokens die letterlijk zichtbaar zijn in de OCR-tekst van elke afbeelding. De bestandsnaam wordt genegeerd en er is geen vision-model bij betrokken, waardoor het werkt zelfs als de AI-engine nog opwarmt of offline is. Gematchte woorden worden in amber omkaderd op de tegels en in de lightbox.

Dialoog

Exacte letterlijke retrieval via Whisper-transcripties — geen embeddings, geen drempelwaarden, werkt ook als de AI-engine uitstaat. Resultaten komen in drie niveaus:

  • Exacte regel (aaneengesloten frase in één uiting).
  • Exacte woorden (alle woorden in één uiting of een venster van ≤8s).
  • Gesproken woorden (alle woorden in dezelfde video).

Gematchte woorden worden gemarkeerd in een tekstfragment; het openen van een resultaat zoekt direct naar de regel.

LLM's (Ask)

Optioneel — er wordt niets gedownload of uitgevoerd totdat dit is ingeschakeld in Settings → LLMs Chat. Een llama.cpp sidecar verbonden met loopback beantwoordt vragen op basis van bewijs dat de app al heeft geëxtraheerd (dialoogregels, OCR-tekst en bestandsnaam-trefwoorden), met genummerde citaten waarop je kunt klikken. Antwoorden worden token-voor-token gestreamd met een live tok/s readout.

Prefixen zoals /screenshots, /videos en /email vernauwen het corpus. De knop 'Stop' behoudt het gedeeltelijke antwoord; bij ontbrekend bewijs wordt het proces afgebroken voordat het model überhaupt start.

Chat-modelGrootteNotities
Qwen3 1.7B (standaard)~1.1GBSnelle dagelijkse chat; past op 8GB Macs
Llama 3.2 3B~2GBSterkere lange antwoorden; vereist meer headroom

Tabbladen & bibliotheekweergaven

Ingebouwde browse-tabbladen: All, Videos, plus Screenshots en Email (de laatste twee zijn togglebaar in Settings → Smart Tabs). Het selecteren van Videos activeert automatisch de Scènes-modus.

Elke zoekopdracht kan als tabblad worden opgeslagen: de 'pin pill' onder de zoekbalk slaat de huidige prompt op met de bijbehorende modus (Files/Scenes/OCR/Dialogue) — maximaal 20 tabbladen, hernoembaar en exact herstelbaar.

Er zijn vijf semantische weergaven achter herdefinieerbare sneltoetsen (standaard ⌘1–⌘5), plus ⌘I voor import / AI Insights, en ⌘, voor Instellingen. Zoekopdrachten blijven in het geselecteerde tabblad: kies eerst de scope (bijv. Screenshots), en zoek dan. In de LLM-chat is dit expliciet via /screenshots, /videos of /email.

Email-tabblad

Toont foto's waarvan de zichtbare OCR-tekst een e-mailadres bevat. Dit is een overlappende weergave (een foto behoudt ook zijn eigen categorie). De detectie is OCR-tolerant: het voegt adressen samen die door Tesseract zijn gesplitst over word-boxes, en handelt ruis aan zoals komma's in plaats van punten ("gmail,com"), gesplitste TLD's ("gmail. com"), bracket-obfuscatie ("allen [at] gmail [dot] com") en gedicteerde adressen ("allen at gmail dot com"). Tegels tonen een contactstrip; uitvouwen maakt kopiëren of mailen mogelijk.

Screenshots-tabblad

De classificatie van screenshots is ongevoelig voor hernoemen. Vier signalen worden gebruikt in volgorde van prioriteit:

  1. Handmatige override (rechtermuisknop op tegel).
  2. Bestandsnaam-vocabulaire (30+ gelokaliseerde OS-screenshotnamen in 20+ talen).
  3. PNG/JPEG metadata probe (leest "screenshot" uit PNG text chunks / EXIF UserComment).
  4. Source-folder hint.

Alles wat niet matcht, komt terecht in Projects.

Visuele modellen

Vier schakelbare modellen via ONNX Runtime; het actieve model wordt per bibliotheek gekozen:

ModelRolSnelheid (CPU)Download
CLIP ViT-L/14@336 (standaard)Beste real-world video scène-search~480–570ms/img~435MB
SigLIP-2-B/16Snelste bulk-import~50–100ms/img~412MB
SigLIP-2-L/16@256Hoog detail (1024-dim) — kleine objecten, borden, on-screen tekst~200ms/img~850MB
SigLIP-B/16@384Maximum detail~480ms/img~214MB

Het wisselen van model zorgt voor een re-embedding van de hele bibliotheek. De overstap is direct; de rest wordt op de achtergrond ingevuld. Tot die tijd valt de zoekfunctie terug op trefwoorden in de bestandsnaam. Per-model text-mean centering voorkomt bias in text embeddings, zodat similarity scores eerlijk blijven over verschillende modellen.

Video zoek-pijplijn

ffmpeg scant elke video op shot-grenzen en bouwt een segmentplan, gesampled volgens de dichtheid die je kiest in Settings → Video Search. Elk preset toont de gemeten tijd en schijfkosten:

PresetSeconden per puntSegmentbudget
Eco604–32
Balanced (standaard)308–128
Detailed1512–256
Ultra516–1024
Ultra Pro2.524–2048 (bevestiging vereist)

Elk segment embedt het midpoint-frame en bewaart een poster. Shot-plannen worden per bestand gecached (pad + grootte + mtime + config fingerprint), zodat re-imports de detectie overslaan.

Dialoog-transcriptie: Gebruikt Whisper tiny.en (~150MB, standaard) of base.en (~300MB). Het wisselen van model zorgt voor een nieuwe transcriptie van alle video's. Complete video's embedden drie frames (20/50/80%) gemiddeld; GIF's embedden een gemiddelde van de middelste frames.

OCR

Tesseract draait in een eigen worker, gescheiden van het vision-model. Engels staat altijd aan; 35 extra talen zijn schakelbaar in Settings → Photo Search (standaard: Vereenvoudigd + Traditioneel Chinees, Japans, Koreaans). Elk talenpakket wordt één keer gedownload (~2.4–5MB; ~17MB voor de standaardset) en werkt daarna offline. Word-boxes worden opgeslagen met de tekst, zodat matches ter plekke gemarkeerd kunnen worden; CJK-tekst wordt zonder spaties samengevoegd en e-mailfragmenten die over word-boxes verspreid zijn, worden gereconstrueerd.

Import & bibliotheekbeheer

Import kan via ⌘I, drag-and-drop, of gemonitorde mappen. Het importeren van een map start de monitoring (live fs.watch plus een re-sync bij elke start). Probleembestanden worden tot 3 keer opnieuw geprobeerd, daarna worden ze overgeslagen bij syncs totdat ze wijzigen.

Renoem-bestendige deduplicatie: Elk bestand wordt content-hashed (SHA-256) vóór kopieeracties, en onjuiste extensies worden genormaliseerd via MIME sniffing.

Genummerde embedding-versies (Settings → Library): Point-in-time snapshots van de gehele doorzoekbare staat (index, embeddings van elk model, scènes en transcriptie-sidecars), met een herstelfunctie (automatische back-up eerst) en een 'Fresh Start' danger zone. Maximum: 10 versies.

Alle gegevens staan onder ~/Library/Application Support/scm (overschrijfbaar via MEMORIESDATADIR): de index JSON, Float32 embedding bins per model, scène- en transcriptie-sidecars, thumbnails en posters.

Privacy door ontwerp

De renderer is een gesandboxte app:// bundle — met contextIsolation, OS sandbox, en een CSP gepind op 'self' (+ Google Fonts CDN voor display-type, met monospace fallback wanneer offline).

Alleen main-process workers downloaden bestanden, één keer per item: vision weights (Hugging Face), OCR-talenpakketten (Tesseract CDN), Whisper weights, en — indien geactiveerd — de llama.cpp sidecar en GGUF chat-modellen (GitHub + Hugging Face), sha256-geverifieerd bij download.

Media wordt gekopieerd naar de door de app beheerde bibliotheek en vanaf schijf gestreamd. Geen telemetrie, geen accounts, geen uploads.

Instellingen & Details

Een macOS-stijl instellingenvenster met tien panelen (Library, Appearance, Grid, Smart Tabs, Photo Search, Video Search, LLMs Chat, Global Shortcut, Keyboard, Menu Bar).

Extra's:

  • Thema's: light/dark/system.
  • CRT-schermeffect voor de lightbox.
  • 6 alternatieve app-iconen.
  • Menu-bar-only modus.
  • Opneembaar global shortcut.
  • First-run onboarding tour.
  • Background-work trays (scènes / transcripties / OCR) met een globale pauzeknop.
  • Statusbalk met versie en aantal geïndexeerde video's.

Opmerkingen

  • Eerste build is traag: electron-builder downloadt de Electron-binary en ffmpeg bij de eerste run; volgende builds zijn veel sneller.
  • Code signing: Zonder een Apple Developer identity in de keychain worden DMG's ongetekend gebouwd. De app draait lokaal nog steeds, maar macOS kan bij de eerste start een rechtermuisklik → Open vereisen.
  • Rebuilds: Omdat main.js, preload.js, main-lib/ en indexer/ vanuit broncode worden gepakket (niet gecached), produceert een bun run dist na bewerkingen direct een nieuw pakket.

Testen

bun run test:all         # volledige verificatie batterij (unit + smoke suites)
bun run smoke:indexer    # headless smoke test van de CLIP indexer
bun test test/           # unit tests (pure modules, geen Electron nodig)
bun run lint             # eslint
bun run format:check     # prettier

Unit tests dekken de pure cores: ranking, dialogue exact-match, CJK tokens, de Whisper model ladder, MIME sniffing, transcript fusion, etc.

E2E smoke tests draaien in de Electron-app via ELECTRONSMOKE* omgevingsvariabelen — meer dan een dozijn scenario's van boot/protocol checks tot search matrices, modelmigratie en Ask-modus (drivers in scripts/e2e/, aangeroepen in main.js; bijv. bun run smoke:ask, smoke:deep, smoke:grid).

Benchmarks: bun run bench:inference, bench:enrich, en bench:detect schrijven JSON-rapporten naar MDs/bench-*.

Projectstructuur

  • main.js: Electron main process: bibliotheek, IPC, indexer worker pool, app:// protocol.
  • preload.js: contextBridge — stelt window.memories beschikbaar voor de renderer.
  • main-lib/: Main-process modules gesplitst uit main.js (instellingen, library store, rank search, Ask retrieval, LLM sidecar config, embedding versies, …).
  • indexer/: vision/OCR/ASR workers (utility processes) + video utils + model registry.
  • src/: React renderer (grid, zoekmodi, lightbox, tabbladen, instellingen, onboarding).
  • scripts/: bench scripts, E2E drivers, de smoke batterij (smoke-all.sh).
  • test/: unit + integratietests.
  • MDs/: design docs, bench rapporten, plannen.
  • dist/: vite build output (renderer bundle).
  • dist-app/: electron-builder output (DMG / ZIP).