SCM — Screen Memories
在 macOS 上对任意文件夹中的每张照片和每一帧视频进行深度 AI 搜索。 本地优先——无需账户、无云端、无上传。推理在你的 Mac 上运行。
它有何不同
- 像思考一样搜索——用平实的语言描述一段记忆;剩下的交给本地 视觉模型。
- 视频,精确到瞬间——场景被分割并嵌入,因此你能 定位到具体镜头,而不只是文件。
- 文本和对话也可以——对可见文本进行 OCR;通过 Whisper 进行精确的口语台词搜索,各自作为独立模式。
- 你的标签页,你的提示词——将任意查询保存为标签页;Screenshots 和 Email 标签页可切换。
- 自我维护的媒体库——监视的文件夹自动导入,内容哈希 对重命名去重,模型切换时在后台重新嵌入,不会 阻塞搜索。
- 真正私密——你的媒体永远不会离开这台机器。权重只需下载 一次;之后一切都离线进行。
五种搜索方式
| 模式 | 查找内容 |
|---|---|
| 文件 | 按含义查找整张照片/视频——视觉排序,并带有文件名和短语加权 |
| 场景 | 视频中的瞬间——搜索某个镜头,跳转到其时间码 |
| OCR | 图像和帧中可见的文本,按字面匹配(Tesseract;eng + 35 种语言开关) |
| 对话 | 视频中确切的口语内容(Whisper),分级精确度 |
| LLM (可选启用) | 基于你的 Mac 已提取的对话、OCR 和文件名进行本地聊天——带引用的回答 |
要求
- macOS(使用 electron-builder 打包;菜单栏/托盘功能 仅限 macOS)
- Bun——该项目使用
bun作为包管理器和 运行器 - 已安装的 Node 模块:
bun install - 首次使用某个模型时会下载其权重(默认 CLIP 约 435MB); 之后完全离线。
下载
最简单的安装方式是通过 Homebrew(Apple Silicon,macOS 12+)。该 tap 的 cask 会在每次安装和 升级时自动清除 macOS 隔离标记,因此应用启动时无需手动执行 Gatekeeper 步骤:
brew tap allenv0/scm brew trust allenv0/scm brew install --cask allenv0/scm/scm
升级保持相同行为:
brew upgrade --cask allenv0/scm/scm
偏好最小权限?只信任该 cask 而非整个 tap:
brew tap allenv0/scm brew trust --cask allenv0/scm/scm brew install --cask scm
该 tap 位于allenv0/homebrew-scm。
开发
bun run dev #构建渲染器包,然后启动 Electron 应用 bun start #不重新构建直接启动 Electron 应用 bun run build #仅将渲染器包重新构建到 dist/
构建应用包(DMG / ZIP)
bun run dist #如果钥匙串中存在身份则签名 bun run dist:unsigned #跳过代码签名发现
这会按顺序运行两个步骤:
vite build— 将 React 渲染器编译为dist/(拾取所有 更改,位于src/)。electron-builder --mac— 打包应用。它将全新的dist/包与main.js,preload.js,main-lib/,以及indexer/(文件列表配置在build.files中的package.json),然后生成安装程序。
输出:安装程序位于dist-app/(参见build.directories.output中的package.json)— 查找SCM-0.2.4.dmg和SCM-0.2.4.zip。
功能
文件
输入即触发即时的文件名关键词预筛选,随后视觉模型接管:结果根据与图像嵌入的余弦相似度评分,带有门控短语和文件名加权、按模型校准的诚实下限,以及近重复多样性过滤器。每个图块都带有“为何匹配”徽章(视觉匹配 / 文件名匹配 / …)以及悬停提示,显示各组件得分明细。CJK 查询以重叠双字组进行搜索(“台北車站”也会匹配 台北、車站)。
场景
所有视频中的每个场景片段都会被评分,因此命中会落在确切的镜头上:图块显示场景海报并带有时间码徽章,打开视频会直接跳转到该时刻。噪声门会返回“无场景匹配”,而不是用乱码淹没网格,并且每个视频最多贡献 3 个场景。
OCR
匹配查询词元中在每个图像 OCR 文本里字面可见的比例 — 忽略文件名,且不涉及视觉模型,因此即使在 AI 引擎预热或离线时也能工作。匹配的词在图块和灯箱中以琥珀色框出。
对话
对 Whisper 转录进行精确字面检索 — 无嵌入、无阈值,在 AI 引擎关闭时也能工作。结果分为三个层级:精确行(单个话语中的连续短语),精确词(单个话语或 ≤8 秒窗口中的所有词),以及说出的词(同一视频中的所有词)。匹配的词在语音片段中高亮显示;打开结果会直接跳转到该行。
LLM(提问)
可选启用 — 在设置 → LLM 聊天中启用之前,不会下载或运行任何内容。一个绑定到回环地址的 llama.cpp 边车根据应用已提取的证据 — 对话行、OCR 文本和文件名关键词命中 — 回答你的问题,带有可点击的编号引用,逐词元流式输出并实时显示 tok/s。前导/screenshots,/videos,/email缩小语料库;停止会保留部分答案;空证据会在模型运行之前短路。
| 聊天模型 | 大小 | 备注 |
|---|---|---|
| Qwen3 1.7B(默认) | ~1.1GB | 快速日常聊天;适配 8GB Mac |
| Llama 3.2 3B | ~2GB | 更强的长回答;需要更多余量 |
标签页与图库视图
- 内置浏览标签页:全部,视频,以及截图和邮件——后两者可在 设置 → 智能标签页 中切换。选择 视频 会自动启用 场景 模式。
- 将任意查询保存为标签页:搜索栏下方的图钉胶囊会保存当前提示词及其模式(文件/场景/OCR/对话)——最多 20 个标签页,可重命名,每个都按保存时的状态精确恢复。
- 五个语义视图,位于可重新映射的快捷键之后(默认 ⌘1–⌘5),另有 ⌘I 导入 / AI 洞察,⌘, 打开设置。
- 搜索保持在所选标签页内:选择 截图、邮件、视频 或任意已保存的标签页,结果就会被过滤到该范围内——先限定范围,再搜索。在 LLMs 聊天中,同样的思路是显式的:前置的
/screenshots,/videos,/email在模型运行之前就缩小语料范围。
邮件标签页
呈现可见 OCR 文本中包含电子邮件地址的照片——这是一个重叠视图(照片仍保留其分类)。检测对 OCR 容错:它会重组 Tesseract 跨词框拆散的地址,并处理逗号代替点号的噪声("gmail,com")、拆分的顶级域名("gmail. com")、带括号的混淆写法("allen [at] gmail [dot] com")以及口述地址("allen at gmail dot com")。图块会显示一条联系人条;展开即可复制或撰写邮件。
截图标签页
截图分类可防重命名。四个信号,按优先级顺序:手动覆盖(右键任意图块)→ 文件名词汇(20+ 种语言中的 30+ 个本地化操作系统截图名称)→ PNG/JPEG 元数据探测(从 PNG 文本块 / EXIF UserComment 中读取 "screenshot",因此重命名后的 Bildschirmfoto 仍能被分类)→ 源文件夹提示。其余一切归入 项目。
视觉模型
通过 ONNX Runtime 提供四个可切换模型;每个图库选择当前启用的模型:
| 模型 | 角色 | 速度(CPU) | 下载 |
|---|---|---|---|
| CLIP ViT-L/14@336(默认) | 最佳真实世界视频场景搜索 | ~480–570ms/图 | ~435MB |
| SigLIP-2-B/16 | 最快的批量导入 | ~50–100ms/图 | ~412MB |
| SigLIP-2-L/16@256 | 高细节(1024 维)——小物体、标牌、屏幕文字 | ~200ms/图 | ~850MB |
| SigLIP-B/16@384 | 最大细节 | ~480ms/图 | ~214MB |
切换模型会重新嵌入整个库:切换会立即生效,尾部在后台补齐,在完成之前搜索会回退到文件名关键词。按模型进行文本均值中心化可以消除文本嵌入的偏差,使相似度分数在不同模型之间保持可靠。
视频搜索流水线
ffmpeg 扫描每个视频的镜头边界,并构建一个按你在设置 → 视频搜索中选择的密度采样的片段计划——每个预设都会在你确认之前显示其测得的耗时和磁盘占用:
| 预设 | 每个点的秒数 | 片段预算 |
|---|---|---|
| Eco | 60 | 4–32 |
| Balanced(默认) | 30 | 8–128 |
| Detailed | 15 | 12–256 |
| Ultra | 5 | 16–1024 |
| Ultra Pro | 2.5 | 24–2048(需要确认) |
每个片段会嵌入其中点帧并保留一张海报;镜头计划按文件缓存(路径 + 大小 + mtime + 配置指纹),因此重新导入时会完全跳过检测。
对话转录:Whispertiny.en(约 150MB,默认)或base.en(约 300MB)——切换会重新转录每个视频。整段视频嵌入三帧(20/50/80%)取平均;GIF 嵌入中间帧的平均值。
OCR
Tesseract 在自己的 worker 中运行,与视觉模型分开。英语始终开启;另外 35 种语言可在设置 → 照片搜索中切换(默认:简体 + 繁体中文、日语、韩语)。每种语言包下载一次(约 2.4–5MB;默认集合约 17MB),之后一切都在离线状态下运行。文字框会与文本一起存储,因此匹配项会就地高亮;CJK 文本会不带空格地拼接,而分散在多个文字框中的电子邮件片段会被重新组装。
导入与库管理
- 导入通过 ⌘I、拖放或监视文件夹——导入一个文件夹即开始监视它(实时
fs.watch外加每次启动时重新同步)。有问题的文件最多重试 3 次,之后会退出监视同步,直到它们发生变化。 - 防重命名去重:每个文件在复制前都会进行内容哈希(SHA-256),并且通过 MIME 嗅探来规范化伪造的扩展名。
- 命名嵌入版本(设置 → 库):整个可搜索状态的时点快照——索引、每个模型的嵌入分箱、场景和转录附属文件——支持恢复(先自动备份)以及一个 Fresh Start 危险区。上限:10。
- 一切都位于
~/Library/Application Support/scm(MEMORIES_DATA_DIR覆盖它):索引 JSON、每个模型的 Float32 嵌入分箱、场景和转录文本附属文件、缩略图和海报。
隐私源于设计
- 渲染器是一个沙箱化的
app://bundle ——contextIsolation、操作系统沙箱,以及一个 CSP 固定到'self'(+ Google Fonts CDN 用于展示字体,离线时使用等宽字体回退)。 - 只有主进程 worker 才会下载,每样东西只下载一次:视觉权重(Hugging Face)、OCR 语言包(Tesseract CDN)、Whisper 权重,以及——仅在你选择加入时——llama.cpp sidecar 和 GGUF 聊天模型(GitHub + Hugging Face),下载时进行 sha256 校验。
- 媒体被复制到应用管理的库中并从磁盘流式传输。无遥测、无账户、无上传。
设置与打磨
一个 macOS 风格的设置面板,包含十个面板(Library、Appearance、Grid、Smart Tabs、Photo Search、Video Search、LLMs Chat、Global Shortcut、Keyboard、Menu Bar)。围绕核心:浅色/深色/系统主题、用于灯箱的 CRT 屏幕效果、6 个备用应用图标、仅菜单栏模式、可录制的全局快捷键、首次运行引导导览、带全局暂停的后台工作托盘(场景 / 转录 / OCR),以及一个显示版本和已索引视频数量的状态栏。
备注
- 首次构建很慢:electron-builder 在首次运行时会下载 Electron 二进制文件和 ffmpeg 一次;后续构建会快得多。
- 代码签名:如果钥匙串中没有配置 Apple Developer 身份,DMG 会以未签名方式构建。应用仍可在本地运行,但 macOS 可能要求在首次启动时右键 → 打开。
- 重新构建会自动拾取更改:由于
main.js、preload.js、main-lib/、和indexer/是从源码打包(而非缓存),编辑其中任何一个后bun run dist都会生成新的包。
测试
bun run test:all #完整验证套件(单元 + 冒烟套件) bun run smoke:indexer #CLIP 索引器的无头冒烟测试 bun test test/ #单元测试(纯模块,无需 Electron) bun run lint #eslint bun run format:check #prettier
- 单元测试覆盖纯核心:排名、对话精确匹配、CJK 分词、Whisper 模型阶梯、MIME 嗅探、转录融合等(参见
test:*中的脚本package.json)。 - E2E 冒烟测试通过
ELECTRON_SMOKE_*环境变量在 Electron 应用内运行——从启动/协议检查到搜索矩阵、模型迁移和 Ask 模式的十几个以上场景(驱动位于scripts/e2e/,在main.js中调度;例如bun run smoke:ask、smoke:deep、smoke:grid)。 - 基准测试:
bun run bench:inference、bench:enrich、和bench:detect将 JSON 报告写入MDs/bench-*。
项目结构
main.js Electron main process: library, IPC, indexer worker pool, app:// protocol
preload.js contextBridge — exposes window.memories to the renderer
main-lib/ main-process modules split out of main.js (settings, library store,
rank search, Ask retrieval, LLM sidecar config, embedding versions, …)
indexer/ vision/OCR/ASR workers (utility processes) + video utils + model registry
src/ React renderer (grid, search modes, lightbox, tabs, settings, onboarding)
scripts/ bench scripts, E2E drivers, the smoke battery (smoke-all.sh)
test/ unit + integration tests
MDs/ design docs, bench reports, plans
dist/ vite build output (renderer bundle)
dist-app/ electron-builder output (DMG / ZIP)


