ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LibrePhotos Memories 功能解析:从“一年前的今天”回忆页到后端纪念日窗口算法

LibrePhotos Memories 功能解析:从“一年前的今天”回忆页到后端纪念日窗口算法 LibrePhotos Memories 功能解析从“一年前的今天”回忆页到后端纪念日窗口算法【免费下载链接】librephotosA self-hosted open source photo management service.项目地址: https://gitcode.com/GitHub_Trending/li/librephotosMemories回忆功能回答的是一个很朴素的问题“一年前的今天我在做什么”它会收集你前几年在相近日期拍摄的照片以年度磁贴Tile的形式呈现并支持以幻灯片方式回放。本文基于 LibrePhotos 的用户指南 memories.md 展开结合后端视图 memories.py、前端路由 memories.tsx 及其测试用例讲清楚这一功能的界面结构、选片算法、过滤规则和幻灯片回放机制帮助你理解它“为什么这样设计”以及如何在源码层面验证每一处行为。:::note Memories 尚未进入正式发布的镜像当前位于dev分支将在下一个版本中发布。 :::功能入口有两个侧边栏的Memories以及在聚光灯搜索spotlight search中输入 memories或 on this day、years ago。页面结构磁贴、封面与 Gallery 视图打开 Memories 页面后会为每个在“今天前后”有照片的往年年份展示一个磁贴最近的年份排最前。每个磁贴携带四类信息封面照片——如果当天有“收藏favorite”照片则优先选用否则取当天最早的静态照片视频永远不会被用作封面。这里“收藏”指的是设置中Minimum image rating to interpret as favorite设定的评分阈值距今多久——例如 “1 year ago”“4 years ago”具体日期与地点——例如Jul 14, 2022 · Lisbon地点名仅在反向地理编码reverse geocoding已填充时才会显示照片数量——这条回忆包含多少张照片。点击磁贴即以幻灯片形式播放该年份页面顶部的Play all则把所有年份按“最早的年份在前、照片按拍摄顺序”依次连播。页面头部还有一个Tiles / Gallery切换开关Tiles默认每年只展示一张封面适合快速浏览全年景Gallery把所有回忆中的全部照片放进常规照片网格此时可以像在任何其他位置一样选中、收藏、分享或加入相册。切换到 Gallery 或按 Play all 时前端才会去请求完整照片集Tiles 视图只需要封面数据因此在大型图库上依然很快。从前端源码可以确认这一“两级加载”策略的实现方式。memories.tsx 同时挂了两个查询summary默认分页大小的封面级数据和everyPhotosize取MAX_MEMORY_ITEMS且仅在wantsEveryPhoto为真时才enabled。常量MAX_MEMORY_ITEMS 200定义在 types.ts对应后端的MAX_ITEMS_PER_MEMORY。切换回 Tiles 时零成本因为两种尺寸的数据各自缓存在独立的 query key 下见 hooks.ts 的MemoriesQueryKeys.list(size)。注意单条回忆最多返回 200 张照片/年。如果某天的照片超过这个上限磁贴上显示的数量仍然是真实总数页面会额外注明“仅显示每年前 200 张照片”。选片算法纪念日窗口如何生成回忆的选取基于时间线timeline已经在分组的天数因此一条回忆覆盖的日期范围与时间线展示的完全一致。规则如下纪念日窗口大多数图库在“精确同一天”上查不到照片所以一条回忆覆盖纪念日前后各 3 天。这个宽度足以把一个周末短途旅行保持在一起又不会把一整周不相关的照片都拉进来。整月回退如果窗口内所有年份都没有照片页面会放宽到整个月显示“August 2019”风格的磁贴——宁可给一条对月份的回忆也不给用户一个空页面。排除今年今年的纪念日照片还停留在时间线顶部不算“回忆”。2 月 29 日在没有该日期的年份回退到 28 日。以你的时区为准纪念日取自你自己的时区Settings → Metadata Options → Default timezone因此回忆在“你的午夜”而不是服务器午夜时切换。后端接口GET /api/memories路由注册见 urls.py暴露了四个查询参数与上述规则一一对应参数文档由 memories.py 中的extend_schema声明参数类型默认值上限含义dateYYYY-MM-DD用户时区的今天—要查询的纪念日windowint330纪念日前后各包含的天数fallbackbooltrue—窗口为空时是否放宽到整月传false可强制严格的纪念日查询sizeint30200每条回忆返回的照片数numberOfItems始终报告真实总数关键实现细节“今天”是用户的今天。today_for_user()memories.py用ZoneInfo(user.default_timezone or UTC)换算当前日期时区名无效时回退到 UTC。这与AlbumDate.date是“本地日历天”的语义一致——否则回忆会在服务器午夜而不是用户午夜时翻转。窗口生成。day_windows()memories.py从图库最早年份由AlbumDate的最小日期决定逐年生成(anchor ± window)的窗口跳过当前年份再反转成“最近年份在前”。闰日回退由anniversary()处理datetime.date(year, 2, 29)抛ValueError时退到 28 日。整月回退是第二道查询。仅当日期窗口完全没有结果且fallback为真时才会调用month_windows()memories.py生成整月窗口返回类型标记为month_years_ago前端据此把磁贴标题渲染为“August 2019”样式见 memoryLabels.ts。被排除的照片候选集与过滤规则回忆取材于时间线展示的同一批照片——你自己的照片、未隐藏、不在回收站中且只取 Stack文件变体 的主照片这样 RAW/JPEG 配对不会重复出现。在此之上被检测为截图和文档的照片也会被跳过——四年前的一张截图并不是任何事的回忆。这一过滤在 memories.py 的memory_candidates()中实现先复用时间线同款查询构造器build_photo_queryset(user, {})再追加.filter(removedFalse, is_screenshotFalse, is_documentFalse)。地点名来自反向地理编码——如果你没有运行过Add Geolocation任务磁贴上就只显示地点为空参考Places 文档。AlbumDate.location里存的地点由album_date_place()安全读取结构异常时降级为“无地点”而不是报错。封面选择逻辑pick_cover()memories.py用一行表达式实现了磁贴规则按(photo.video, -(photo.rating or 0))取最小值——静态照片优先于视频、评分高者优先、评分相同时取当天最早的一张。回放幻灯片与键盘快捷键播放某条回忆会打开常规灯箱lightbox并直接进入幻灯片模式因此你熟悉的一切交互都可用间隔下拉框3、5、10、15 或 30 秒默认取Settings → Slideshow interval的设置、s停止/开始、方向键手动逐张切换、f全屏、Esc关闭。“选择不同回忆总是从头开始播放”这一行为在前端通过 Reactkey实现memories.tsx 中的MemorySlideshow key{playing.id} ...会在换回忆时强制卸载重建组件状态自然归零。而 MemorySlideshow.tsx 本身只是把一组照片交给Lightbox并置startSlideshow单年连播还是全部连播完全由调用方传入的items决定。接口契约与数据模型为什么查询成本与照片数无关接口返回结构由前端 zod schema 精确描述types.ts{ date: string, // 实际查询的纪念日YYYY-MM-DD window_days: number, // 生效的窗口天数 results: [ { id: string, // 形如 years_ago-2022 type: years_ago | month_years_ago, years_ago: number, // 距今年数 year: number, date: string, // 最值得命名的那一天离纪念日最近 start_date: string, // 该回忆实际覆盖的日期区间 end_date: string, location: string, // 没有地点时为空串 numberOfItems: number, // 真实总数不受 size 截断 cover: PigPhoto, // 封面照片摘要 items: PigPhoto[] // 按时间顺序、最多 size 张 } ] }值得注意的是type字段目前只有日期型回忆但它为将来增加“按地点”“按人物”的回忆类型预留了扩展位前端无需猜测每条回忆的主题。从数据模型看整个端点刻意不走Photo.exif_timestamp逐张匹配而是基于AlbumDate每天每主人一行查询AlbumDate.date是时间线分组用的普通日历天不存在时区换算把照片挪到邻日的风险每个日期主人只有一行因此端点成本与图库跨越的年数成正比而不是与照片数成正比——20 万张照片的图库与 200 张的图库走的是同样少数的索引日期范围查询模块 docstring 见 memories.py每天的“规模统计”由数据库聚合完成Count(id, distinctTrue)memories.py2000 张连拍的一天与 2 张的一天代价相同每个年份窗口只读取前size个照片 idorder_by(exif_timestamp, image_hash)后切片超大日期的剩余照片从不离开数据库。这些行为都有对应的测试用例覆盖集中在 test_memories_timeline.py闰日回退test_anniversary_of_a_leap_day_in_a_common_year、窗口配置test_the_window_is_configurable、关闭回退test_the_fallback_can_be_turned_off、今年不算回忆test_this_years_photos_are_not_memories_yet、年份倒序test_years_are_ordered_from_the_nearest_backwards、size截断但总数不截断test_size_caps_the_items_but_not_the_count、截图/文档排除test_screenshots_and_documents_are_not_memories、整月回退test_the_month_is_the_fallback_when_the_day_is_empty等可以作为阅读后端实现时的最佳验证清单。页面为空时意味着什么如果你看到“No memories yet”说明你的图库从今天往回没有超过一年的照片——回忆只看更早的年份。这对只扫描了近期照片的新图库来说是预期行为当所有候选照片都被过滤掉例如某天只有截图时也会出现同样提示。最后值得一提的是回忆不需要任何预生成或定时任务——它们在你打开页面时即时计算因此新扫描进来的照片会立即出现在回忆中。【免费下载链接】librephotosA self-hosted open source photo management service.项目地址: https://gitcode.com/GitHub_Trending/li/librephotos创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表