Memoria 开发记录 14:让故事可以重现——从临时缓存到可复现的数字资产
前言
Memoria 不只是浏览照片,还会把照片、音乐、节拍和渲染参数组合成故事视频。早期实现中,导出结果和音乐主要依赖文件路径与缓存目录。这样的方案在一次运行中可用,但应用清理缓存、路径变化或设备重启后,故事就可能无法再次播放。
这暴露出一个本质问题:
一个已经由用户保存的故事,究竟是一组临时文件引用,还是一个可以长期恢复和重新生成的数字资产?
围绕这个问题,项目完成了 ObjectBox 数据迁移、视频参数固化、音乐二进制保存、SHA256 去重和压缩。目标是让故事不依赖偶然存在的缓存环境。
数据库迁移:从替换存储引擎到重建事实来源
提交 40c9c4d 是一次大规模 ObjectBox 迁移,涉及实体模型、媒体资产同步、向量索引、照片服务、事件、故事和多个页面。单次改动新增约 2700 行、删除约 1200 行。
数据库迁移最危险的地方,是为了兼容旧接口而长期保留一层“假装还是旧数据库”的适配层。提交中曾加入 isar_compat.dart,随后 d8f671b 在修复 APK 构建时将其删除。
这说明迁移最终必须明确新的事实来源。兼容层适合帮助过渡,但如果长期存在,会让开发者无法判断某个查询究竟遵守哪套事务、索引和实体语义。
ObjectBox 在这里不仅保存普通业务字段,还承载:
- 照片和媒体资产身份;
- 图片与人脸嵌入向量;
- AI 分析状态;
- 故事、视频和音乐的固化信息;
- 可恢复任务状态。
它逐渐成为端侧数据资产的中心,而不只是一个更快的键值存储。
为什么缓存路径不能代表故事
一个视频缓存路径可能因为以下原因失效:
- 系统清理应用缓存;
- 用户手动执行缓存清理;
- 文件名或缓存键算法调整;
- 导出目录在不同平台上变化;
- 故事参数更新后旧缓存与新内容不一致。
如果 StoryEntity 只记录路径,数据库中保存的其实只是一个易失指针。真正能够描述故事的,是生成它所需的输入和参数。
提交 9ea5132 将以下信息固化到故事实体:
- 已缓存视频路径与缓存键;
- 自定义音乐路径;
- 动态节拍数据;
- 视频渲染参数。
播放和分享时优先复用已生成视频;如果视频不存在,仍可以依据参数重新生成。这使缓存从“唯一结果”降级为“可复用派生物”。
音乐为什么要保存二进制
用户选择的自定义音乐可能来自临时选择器路径、下载目录或其他应用提供的 URI。只记录路径,并不能保证未来仍有权限读取。
提交 b908ae2 将音乐文件二进制保存进 ObjectBox,并通过 SHA256 建立去重缓存:
选择音乐↓
读取二进制并计算 SHA256↓
保存到故事实体↓
需要播放或导出时,按哈希恢复到 MusicCache↓
同一内容不重复写入
SHA256 在这里不是为了密码学安全,而是作为内容寻址键。文件名可以变化,路径可以变化,只要内容相同,哈希就相同。这样多个故事引用同一首音乐时,无需反复恢复副本。
压缩:持久化能力也要控制成本
直接把音乐二进制放入数据库会增加体积。提交 d18b269 引入 Zstandard 压缩并优化音乐存储与去重逻辑。
Zstandard 的优势是压缩率和解压速度平衡较好。不过,音频文件本身往往已经压缩,因此实际收益取决于格式。工程上不能假设“加了压缩就一定更小”,而应记录压缩前后大小,并在收益不足时保留原数据。
更重要的是,压缩逻辑必须对业务透明:
- 保存时决定是否压缩;
- 实体记录编码方式;
- 读取时自动还原;
- 上层渲染和播放只接收正常文件或字节。
缓存键决定结果是否可复用
提交 013d389 重构视频缓存管理、缓存键生成和路径获取。一个正确的缓存键应该覆盖所有会影响输出的输入,例如:
照片顺序 + 转场参数 + 字幕 + 音乐内容 + 节拍数据 + 分辨率 + 帧率
如果缓存键漏掉音乐或渲染参数,用户修改设置后仍可能拿到旧视频;如果缓存键包含不稳定路径,相同内容又无法命中缓存。
因此,缓存键应尽量基于稳定内容身份和规范化参数,而不是临时路径或对象地址。
保存、导出、分享是一条完整链路
提交 bcaadd9 重构视频分享和文件管理。故事固化的最终价值,需要在用户操作中体现:
- 已有缓存视频时直接播放;
- 分享时优先复用已导出的文件;
- 缓存失效时能够重新生成;
- 用户手动保存的故事才进入长期故事列表;
- 导出完成后,故事实体同步记录最新结果。
这条链路将“生成一次视频”提升为“管理一项可长期使用的作品”。
总结
让故事可复现,本质上是一次数据所有权重构:数据库保存生成事实,缓存只负责加速,文件路径不再承担长期身份。
最终形成的原则是:
- 新数据库应成为明确事实来源,兼容层只用于短期迁移;
- 故事要保存可重建输出的参数,而不只是缓存路径;
- 外部音乐需要固化,避免未来权限和路径失效;
- 使用内容哈希去重并建立稳定缓存键;
- 压缩策略必须可识别、可还原并验证实际收益;
- 播放、导出和分享都围绕同一故事实体工作。
对应提交:40c9c4d、d8f671b、013d389、9ea5132、b908ae2、d18b269、bcaadd9。
