Spacedrive Photos 扩展深度解析:基于扩展 SDK 的人脸识别、地点聚类与智能相册实现

Spacedrive Photos 扩展深度解析:基于扩展 SDK 的人脸识别、地点聚类与智能相册实现 Spacedrive Photos 扩展深度解析基于扩展 SDK 的人脸识别、地点聚类与智能相册实现【免费下载链接】spacedriveSpacedrive is an open source cross-platform file explorer, powered by a virtual distributed filesystem written in Rust.项目地址: https://gitcode.com/gh_mirrors/sp/spacedrive导读Photos 是 Spacedrive 官方提供的照片管理扩展目标是在本地复刻 Apple Photos 与 Google Photos 的核心能力自动人脸检测与聚类、EXIF GPS 地点识别、时间地点驱动的 Moment 自动生成、基于 ResNet50 的场景理解与智能搜索。本文以 extensions/photos/README.md 为骨架结合该扩展在仓库内的真实源码模型定义、Job/Task、Action、Query、Agent 记忆、AI 提示词模板与权限清单逐层拆解其架构设计与实现方式读者可据此掌握 Spacedrive 扩展 SDK 的完整用法并理解如何用#[extension]、#[model]、#[job]、#[agent]等宏搭建一个具有持久化、权限隔离、跨设备同步能力的复杂扩展。一、扩展定位与功能总览Spacedrive 的 Photos 扩展扩展 IDcom.spacedrive.photos版本1.0.0将自己定位为带有人脸识别、地点识别和智能组织的进阶照片管理方案声明的最低 Core 版本为2.0.0见 manifest.json。它的核心特性可归为五类能力域具体功能依赖技术人脸识别自动人脸检测、DBSCAN 聚类、人物命名、按人脸搜索、跨设备同步聚类RetinaFace 模型 DBSCAN 算法地点识别EXIF GPS 提取、500 米半径地理聚类、AI 逆地理编码、按地点搜索、地图视图EXIF 元数据 本地 LLMMoments时间地点自动聚类成集、AI 生成标题如 Summer in Paris、每周回忆定时任务、时间线视图LLM 聚类工具函数场景理解ResNet50/Places365 场景分类、智能标签#beach、#sunset、按场景搜索、美学质量评分场景分类模型相册与整理手动相册、规则智能相册、收藏、隐藏照片、共享相册模型同步与冲突合并二、架构模型层设计扩展的五个核心模型定义在 src/models/ 目录下分别对应五个 Rust 结构体全部通过#[model]宏注册Photo // 关联图片文件 EXIF 人脸/场景 sidecar Person // 带名称与嵌入向量的人脸聚类 Place // 带半径的地理位置 Album // 照片集合 Moment // 基于时间/地点的照片分组2.1 Photo 模型扩展自有 sidecar 的典型范式src/models/photo.rs 是理解扩展数据模型的最佳入口。Photo同时使用了#[entry]绑定磁盘文件、#[metadata]读取 Core 的 EXIF、#[sidecar(kind ..., extension_owned)]写入扩展自有 sidecar、#[custom_field]写入扩展命名空间的自定义字段与#[computed]派生字段#[model(version 1.0.0)] pub struct Photo { pub id: Uuid, #[entry(filter *.{jpg,jpeg,png,heic,heif,raw,cr2,nef,dng})] pub file: Entry, #[metadata] pub exif: OptionExifData, #[sidecar(kind faces, extension_owned)] pub detected_faces: OptionVecFaceDetection, #[sidecar(kind scene, extension_owned)] pub scene_tags: OptionVecSceneTag, #[sidecar(kind aesthetics, extension_owned)] pub quality_score: Optionf32, #[user_metadata] pub tags: VecTag, #[custom_field] pub identified_people: VecPersonId, #[custom_field] pub place_id: OptionPlaceId, #[custom_field] pub moment_id: OptionMomentId, #[computed] pub has_faces: bool, #[computed] pub taken_at: OptionDateTimeUtc, }值得注意的工程细节extension_ownedsidecarfaces、scene、aesthetics三类 sidecar 归扩展所有写入路径位于库目录 sidecar 树的extensions/photos/命名空间下详见后文数据存储章节避免与 Core 自身及其他扩展的数据互相污染custom_field命名空间隔离identified_people、place_id、moment_id写入photos命名空间与 manifest.json 中write_custom_fields: [photos]权限对应ExifData结构体完整覆盖相机信息品牌、型号、镜头、焦距、光圈、ISO、快门、拍摄时间taken_at与GpsCoordinates经纬度海拔taken_at与has_faces作为#[computed]派生字段供搜索索引使用FaceDetection包含边界框BoundingBoxx/y/width/height、置信度、embedding: Vecf32人脸嵌入向量以及identified_as: OptionPersonId关联到 Person。2.2 Person / Place可同步的持久化模型src/models/person.rs 使用#[persist_strategy(always)]强制持久化并通过#[sync]属性声明跨设备同步策略#[model(version 1.0.0)] #[persist_strategy(always)] pub struct Person { pub id: PersonId, #[sync(shared, conflict last_writer_wins)] pub name: OptionString, #[sync(shared)] pub thumbnail_photo_id: OptionUuid, #[sidecar(kind face_embeddings)] pub embeddings: VecVecf32, #[sync(device_owned)] pub photo_count: usize, #[vectorized(strategy average, model registered:face_embedding)] pub representative_embedding: Vecf32, }同步语义的设计意图清晰name、缩略图等共享字段多设备一致名字冲突采用last_writer_wins而photo_count是device_owned每台设备各自统计representative_embedding通过#[vectorized]声明为对嵌入向量的平均值聚合供向量检索使用——这正是人脸聚类跨设备同步的底层机制。src/models/place.rs 的Place同样为persist_strategy(always)共享字段为名称、经纬度、radius_meters聚类半径与缩略图photo_count为设备本地统计。2.3 Album / Moment共享集合与冲突合并src/models/album.rs 的Album通过#[sync(shared, conflict union_merge)]声明photo_ids在多设备编辑时按并集合并两端各自新增的照片都会保留album_type为枚举AlbumType::{Manual, Smart, Shared, Favorites, Hidden}覆盖手动相册、智能相册、共享相册、收藏、隐藏五种形态。src/models/moment.rs 的Moment记录标题、起止日期、可选地点PlaceId与照片 ID 列表辅助结构MomentGroup携带place_name、common_scenes共同场景标签是 AI 生成标题的输入素材。三、Agent 记忆系统PhotosMind扩展在 src/agent/memory.rs 中定义了一个照片大脑PhotosMind通过#[agent_memory]与#[memory_config]配置记忆衰减与总结阈值#[agent_memory] #[memory_config(decay_rate 0.01, summarization_trigger 500)] pub struct PhotosMind { pub history: TemporalMemoryPhotoEvent, pub knowledge: AssociativeMemoryPhotoKnowledge, pub plan: WorkingMemoryAnalysisPlan, }三种记忆类型的职责分工TemporalMemoryPhotoEvent分析时间线记录PhotoAnalyzed、PersonIdentified、MomentCreated三类事件构成可回溯的分析历史AssociativeMemoryPhotoKnowledge人脸/地点知识图存储FaceCluster人物代表嵌入照片列表、PlaceCluster地点中心照片列表、ScenePattern场景类型典型时间常见地点是搜索与回忆的关联数据源WorkingMemoryAnalysisPlan待办计划AnalysisPlan记录待分析的目录pending_locations、待做人脸检测的照片、待聚类的照片与待生成的 Moment 时间范围供 Agent 生命周期钩子消费。PhotoEvent与PhotoKnowledge均实现MemoryVarianttrait 的variant_name()这是基于枚举变体的记忆变体机制。PhotosMind还提供三个自定义记忆查询方法直接服务于 Query 层photos_of_person按person_id字段过滤知识、photos_at_placetop_k(1000)限制返回规模、similar_scenesquery_similarmin_similarity(0.8)做语义相似检索。四、后台处理Jobs 与 TasksREADME 列出的六个 Job 全部落在 src/jobs/ 目录此外还有 src/tasks/ 下的detect_faces与classify_scene两个 Task。Job 与 Task 的分工Job 是有状态的长时间运行工作可持久化、可中断恢复Task 是可重试的原子单元Job 通过ctx.run(...)编排 Task。Job文件作用analyze_photos_batchsrc/jobs/analyze.rs逐张照片做人脸检测并写 sidecar随后触发聚类与打标签identify_places_in_locationsrc/jobs/places.rs目录内 GPS 聚类、命名、回写地点analyze_scenessrc/jobs/scenes.rs场景分类create_momentssrc/jobs/moments.rs时间地点聚类生成 Moment 并 AI 命名cluster_faces_into_peoplesrc/jobs/clustering.rs人脸嵌入聚类为人generate_face_tagssrc/jobs/clustering.rs从 sidecar 生成#person:xxx标签4.1 analyze_photos_batch带进度与断点续跑的批处理src/jobs/analyze.rs 是完整流程的主控AnalyzePhotosState保存photo_ids与current_indexJob 状态可持久化因此天然支持中断恢复。核心逻辑要点幂等跳过对每张照片先检查ctx.sidecar_exists(content_uuid, faces)已分析的直接跳过避免重复计算Task 编排ctx.run(detect_faces_in_photo, photo.clone())执行单张人脸检测结果通过ctx.save_sidecar(content_uuid, faces, photos, faces)写入photos命名空间 sidecar可中断 进度上报每张照片后调用ctx.check_interrupt()并以Progress::simple(idx/total, Analyzed {}/{} photos)上报百分比进度最后Progress::complete(Face analysis complete)收尾收尾编排全部检测完成后依次ctx.run(cluster_faces_into_people, ...)与ctx.run(generate_face_tags, ...)完成聚类与打标签。4.2 cluster_faces_into_peopleDBSCAN 聚类的真实调用src/jobs/clustering.rs 中该 Task 标注#[task(retries 1, timeout_ms 60000)]最多重试 1 次、超时 60 秒。流程为从每个照片的facessidecar 读取全部FaceDetection汇聚成Vec(Uuid, FaceDetection)再调用工具函数dbscan_clustering(all_faces, threshold)——threshold 直接取自扩展配置ctx.config::PhotosConfig().face_clustering_threshold默认 0.6即 README 所述 DBSCAN 算法的真实接线点。每个聚类调用find_or_create_person当前实现为todo!占位后将 person_id 回写照片的identified_people自定义字段。generate_face_tags则读取照片的identified_people字段对每个已命名的 Person 调用ctx.vdfs().add_tag(photo.metadata_id(), format!(#person:{}, name))这就是 README 中#person:alice标签搜索的实现来源。4.3 identify_places_in_locationGPS 聚类 AI 逆地理编码src/jobs/places.rs 完整展示了 Job 如何与 VDFS 查询和本地 LLM 协作通过ctx.vdfs().query_entries().in_location(location).of_type::Image().where_metadata(exif.gps, is_not_null())筛选指定目录下含 GPS 元数据的图片cluster_by_location(photos, 500.0)做 500 米半径地理聚类对应 README 的 groups photos within 500m每个聚类find_or_create_place获取/创建Place若名称为Unknown Location则调用ctx.run(reverse_geocode, ...)Task用ctx.ai().from_registered(llm:local)prompt_template(identify_place.jinja)做逆地理编码最后把place_id写入照片custom_field并给照片追加#place:{name}标签。reverse_geocode任务使用 prompts/identify_place.jinja 模板提示 LLM 根据经纬度输出规范地点名城市格式Paris, France、地标Eiffel Tower, Paris、自然景观Yosemite National Park, California、街区Greenwich Village, New York保证结果格式可用于标签与搜索。4.4 create_moments时间地点聚类的 Moment 生成src/jobs/moments.rs 接收photo_events来自TemporalMemory的PhotoEvent列表调用工具函数cluster_into_moments生成MomentGroup再通过 prompts/generate_moment_title.jinja 让本地 LLM 基于地点名 共同场景 月份如 July 2026生成标题如 Summer in Paris最后ctx.vdfs().create_model(moment)持久化。源码注释明确说明记忆更新应由 Agent 处理器完成而非 Job 直接写记忆体现职责分离设计。五、用户操作Actions 的 preview-execute 模式README 列出的 Actioncreate_album、identify_person、remove_photo_from_album、hide_photo中create_album的完整实现见 src/actions/create_album.rs它演示了 SDK 的preview-execute 双阶段模式#[action] pub async fn create_album(ctx: ActionContext, name: String, photo_ids: VecUuid) - ActionResultActionPreview { Ok(ActionPreview { title: Create Album.to_string(), description: format!(Create album {} with {} photos, name, photo_ids.len()), changes: vec![Change::CreateModel { model_type: Album.to_string(), data: serde_json::to_value(Album { /* ... */ })?, }], reversible: true, }) } #[action_execute] pub async fn create_album_execute(ctx: ActionContext, preview: ActionPreview) - ActionResultExecutionResult { // 遍历 preview.changes对 Change::CreateModel 调用 ctx.vdfs().create_model(album) }第一阶段只返回预览ActionPreview标题、描述、将执行的变更列表、reversible标记第二阶段才真正执行。该模式让 UI 可以在执行前展示影响范围、支持撤销reversible: trueidentify_person、hide_photo等隐私/组织类操作可复用同一安全路径。actions 目录下还有 manage_album.rs对应remove_photo_from_album与 identify_person.rs。六、自然语言查询Query 层查询定义在 src/queries/ 目录README 中的三条搜索能力Show me photos of Alice、Photos from Paris、sunsets 场景分别对应search_person、search_place、search_scene。以 src/queries/search_person.rs 为例#[query(photos of {person_name})] pub async fn search_person(ctx: QueryContextPhotosMind, person_name: String) - QueryResultVecPhoto { let person ctx.vdfs().query_models::Person() .where_field(name, equals(person_name)).first().await? .ok_or(QueryError::NotFound)?; let photo_ids ctx.memory().read().await.photos_of_person(person.id).await; // 逐个 get_model::Photo 组装结果 }该 Query 的调用链路清晰展示了模型查询 记忆查询的协作先用 VDFS 按姓名找到Person模型再调用PhotosMind.photos_of_person走AssociativeMemory的知识图查询拿到照片 ID 列表最后回查Photo模型。#[query(photos of {person_name})]声明的自然语言模式即为用户在搜索框输入的语句模板search_placephotos from Paris与search_scene海滩/日落遵循同样的模式匹配 → 记忆/模型查询范式。七、配置与权限manifest.json 与 PhotosConfig7.1 扩展清单与权限模型manifest.json 是扩展的权限与资源声明中枢包含四类关键信息必需 Core 特性exif_extractionEXIF 读取、ai_modelsAI 模型、semantic_search语义搜索Core 版本低于2.0.0时扩展无法安装。细粒度权限权限声明含义读取条目read_entriesglob**/*.{jpg,jpeg,png,heic,heif,raw,cr2,nef,dng,webp}只可读图片类型文件读取 sidecarread_sidecars: [exif, thumbnail]可读 Core 的 EXIF 与缩略图写入 sidecarwrite_sidecars: [faces, places, scene, aesthetics]只可写扩展自有分析结果标签/字段write_tags: true、write_custom_fields: [photos]写标签与photos命名空间字段任务调度dispatch_jobs: true允许分发后台 Job模型使用use_models三项均preference: local人脸检测、场景分类、LLM 均优先本地执行模型声明photos_face_detection_v1RetinaFace12MB ONNX与photos_scene_v1ResNet50 基于 Places365 训练95MB ONNXsource 类型为download由扩展首次安装/分析时按需拉取对应 README Extension downloads AI models (~107MB total) 的 1295107MB 出处。入口声明wasm_file: photos.wasm——扩展以 WASM 形式运行因此 Cargo.toml 中crate-type [cdylib]且 release profile 使用opt-level z体积优化与lto true、codegen-units 1最大化内联与瘦身。7.2 用户可调设置PhotosConfigsrc/config.rs 的PhotosConfig定义四个用户面向设置均有#[serde(default)]默认值与设计中的 UI 标签/取值范围设置项字段默认值取值范围/说明启用人脸识别face_recognitiontrue布尔开关启用地点识别place_identificationtrue布尔开关自动创建回忆auto_memoriestrue布尔开关关联每周回忆 Agent 任务场景检测置信度scene_confidence_threshold0.70.0 ~ 1.0低于阈值的场景标签被丢弃人脸聚类阈值face_clustering_threshold0.60.0 ~ 1.0越高聚类越严格其中face_clustering_threshold在 4.2 节已确认被cluster_faces_into_peopleTask 实际读取ctx.config::PhotosConfig().face_clustering_thresholdscene_confidence_threshold预计在 src/tasks/classify_scene.rs 中用于过滤低置信度场景标签。八、安装、使用与完整分析流程8.1 安装步骤按 README 的安装流程用户需要从 Spacedrive 扩展商店安装 Photos 扩展入口为 extensions/README.md 描述的扩展机制为扩展授权具体的照片目录例如/Users/alice/Photos/Volumes/External/Family Photos权限为 user-scoped只分析用户主动选择的目录不扫描全盘扩展按需下载 AI 模型人脸检测 12MB 场景分类 95MB ≈ 107MB来源见 manifest.json 的models段用户在界面点击 Analyze for Faces 按钮触发分析 Job。8.2 分析流水线README 给出的端到端流程与源码调用链逐一对齐如下用户对 /My Photos 启用 Photos 扩展 ↓ [lib.rs] #[extension] 注册 权限校验 扩展分发 analyze_photos_batch Job ↓ [jobs/analyze.rs] 遍历 state.photo_ids 对每张照片: - 从 Core 读取 EXIF#[metadata] 权限 exif_extraction - detect_faces_in_photo → 写入 faces sidecarRetinaFace - classify_scene → 写入 scene sidecarResNet50/Places365 ↓ [jobs/clustering.rs] cluster_faces_into_people 将人脸嵌入聚成 PersonDBSCAN阈值 0.6 ↓ [jobs/clustering.rs] generate_face_tags 生成 #person:alice 标签 ↓ [queries/] 用户可搜索 #person:alice 或 photos from beach其中每张照片处理都带幂等检查已有 faces sidecar 则跳过与check_interrupt中断点Job 状态AnalyzePhotosState持久化在 Core 中支持进度显示与断点续跑。九、数据存储布局README 的存储结构对应 manifest.json 的write_sidecars与write_custom_fields权限以及代码中save_sidecar(content_uuid, kind, photos, ...)的命名空间参数~/.spacedrive/ # 用户级模型缓存 └── models/ ├── face_detection/ │ └── photos_v1.onnx # RetinaFace 人脸检测模型12MB └── scene_classification/ └── resnet50.onnx # Places365 场景分类模型95MB .sdlibrary/ # 库目录 └── sidecars/ ├── content/{uuid}/ # 按内容 UUID 组织的 sidecar │ └── extensions/photos/ # 扩展命名空间extension_owned │ ├── faces.json # 人脸检测结果边界框置信度嵌入 │ ├── scene.json # 场景分类结果 │ └── aesthetics.json # 美学质量评分 └── extension/photos/ └── memory/ # Agent 记忆持久化 ├── history.db # 照片分析事件TemporalMemory └── knowledge.vss # 人脸/地点知识图AssociativeMemory要点分析结果人脸、场景、评分全部以 sidecar 形式落在库目录内随库同步而不随云端上传模型权重缓存在用户级~/.spacedrive/models/按类别分子目录与 manifest 中models声明的category字段对应。faces.json中每个FaceDetection的embedding向量与Person.embeddings的 sidecar 存储src/models/person.rs 中#[sidecar(kind face_embeddings)]共同构成跨设备人脸匹配的向量底座。十、SDK 特性清单哪些已实现、哪些是愿景10.1 示例中已实现并可在源码中核实的 SDK 特性SDK 特性源码证据#[extension] 权限与依赖声明src/lib.rsrequired_features、permissions#[model]五个模型src/models/version 1.0.0#[agent]生命周期钩子与事件处理器src/agent/handlers.rs#[agent_memory]枚举型记忆src/agent/memory.rsTemporalMemory/AssociativeMemory/WorkingMemory#[job]/#[task]持久化处理src/jobs/、src/tasks/含 retries/timeout#[action]preview-execute 模式src/actions/create_album.rs#[query]用户搜索src/queries/search_person.rs扩展自有 sidecar#[sidecar(kind faces, extension_owned)]photo.rs虚拟模型与持久化#[persist_strategy(always)]person.rs、place.rs用户范围权限manifest.jsonread_entriesglob安装时模型注册manifest.jsonmodels段AI Jinja 模板prompts/ 三个模板 ctx.ai()调用从 sidecar 生成标签generate_face_tagsclustering.rs自定义记忆查询方法PhotosMind::photos_of_person等memory.rs10.2 尚未在 Core 落地的部分README 明确声明Most features here are aspirational - the SDK is still being built. This serves as a comprehensive reference implementation.多数特性是愿景性的SDK 仍在建设本扩展作为完整参考实现。源码中也有两处todo!(Implement ...)占位find_or_create_place与find_or_create_person从源码结构看这两处是人员/地点匹配去重的挂起点当前尚未完成。因此本扩展的价值更多在于向 SDK 使用者展示全套宏与 API 的标准用法而非开箱即用的生产级分析流水线。十一、能力对比本地优先的差异点README 将扩展能力与两大商业产品对齐并给出 Spacedrive Photos 的差异化定位对标 Apple Photos人脸识别与聚类、地点识别、Memories、手动/智能相册、收藏与隐藏、按人/地点/场景搜索、地图视图共享相册需等待 P2P 实现扩展中AlbumType::Shared已预留对标 Google Photos人脸分组、地点检测、内容搜索、相册、自动创作Moments、场景/物体检测云备份则由 Spacedrive 自身的分布式文件系统以不同方式处理扩展本身不承担备份职责本地优先优势README 原文要点100% 本地、隐私优先人脸数据不出设备、通过 P2P 而非云端实现多设备同步、零订阅成本、user-scoped 只分析授权目录、基于开放 SDK 可扩展。需要说明的是上述优势是扩展在 README 中的自我定位属于产品设计目标尚需结合 10.2 节的实现状态理解其成熟度。十二、构建与运行在仓库根目录下按 README 构建 WASM 产物cd extensions/photos cargo build --target wasm32-unknown-unknown --release cp target/wasm32-unknown-unknown/release/photos_extension.wasm ./photos.wasm构建产物photos.wasm与 manifest.json 中的wasm_file字段对应安装时将两者一并提交给 Spacedrive Coremin_core_version: 2.0.0。构建前需确认已安装wasm32-unknown-unknowntarget可参考 scripts/utils/rustup.mjs 的 toolchain 管理方式Cargo.toml将spacedrive-sdk以路径依赖指向 crates/sdk且[workspace]为空确保该扩展独立于根工作区构建。十三、总结Spacedrive Photos 扩展是一份麻雀虽小、五脏俱全的扩展 SDK 全景示例manifest.json演示了权限与模型声明五个#[model]演示了 sidecar/自定义字段/同步冲突/向量化等数据建模能力六个 Job 演示了带进度、可中断、可重试的后台处理create_album演示了 preview-execute 安全执行模式PhotosMind演示了三类 Agent 记忆的协作Jinja 模板演示了本地 AI 的无缝接入。阅读它的源码从 src/lib.rs 的#[extension]声明开始按models → jobs → actions → queries → agent的顺序是学习 Spacedrive 扩展开发的最高效路径之一同时应留意todo!()占位与 README 中aspirational的说明将参考实现与生产可用区分对待。【免费下载链接】spacedriveSpacedrive is an open source cross-platform file explorer, powered by a virtual distributed filesystem written in Rust.项目地址: https://gitcode.com/gh_mirrors/sp/spacedrive创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考