LibrePhotos 的 Stacks 与文件变体机制:RAW+JPEG、Live Photo 分组与连拍检测的实现原理
LibrePhotos 的 Stacks 与文件变体机制RAWJPEG、Live Photo 分组与连拍检测的实现原理【免费下载链接】librephotosA self-hosted open source photo management service.项目地址: https://gitcode.com/GitHub_Trending/li/librephotosLibrePhotos 通过两套互补的机制自动组织相关照片**文件变体File Variants**把同一次拍摄的不同格式文件折叠为一张照片**堆叠Stacks**把多次但相关的拍摄归为一组。读完本文你将理解扫描阶段两阶段分组的实现、Repair File Variants 修复任务的运作方式、基于规则引擎的连拍Burst检测原理以及 Stacks 的完整数据模型与 API 调用链。两个核心概念文件变体 vs 堆叠这是理解 LibrePhotos 照片分组的第一分界二者解决的是不同层面的问题文件变体File Variants同一次拍摄产生的不同格式文件如IMG_001.CR2和IMG_001.jpg在时间线上只显示一条但底层挂多个文件。它走Photo.files多对多关系在扫描阶段自动建立。堆叠Stacks不同的、但逻辑上属于一组的照片连拍、手动归组等组内只有封面照片显示在时间线上。它走PhotoStack模型与Photo.stacks多对多关系在检测阶段或手动操作时建立。这一设计在 PhotoStack 模型 的注释中有明确说明RAWJPEG 对与 Live Photo 不再作为堆叠处理而是使用 Photo.files 存储文件变体PhotoPrism 风格的模型模型中RAW_JPEG_PAIR和LIVE_PHOTO两个旧堆叠类型仅保留用于迁移兼容并已标记弃用。文件变体RAWJPEG 对相机以 RAWJPEG 模式拍摄时会为每张照片生成两个文件如IMG_001.CR2和IMG_001.jpg。LibrePhotos 在扫描时自动将两者归为一组——时间线上只显示一张照片缩略图上叠加RAW 徽标表示存在 RAW 变体。Live PhotosLive Photo 由一张静帧加一段短视频构成。从 Live Photo 检测模块 的源码看LibrePhotos 实际覆盖三种形态形态特征检测方式Google Pixel Motion PhotoMP4 数据直接内嵌在 JPEG 的 EOI 标记之后在文件内存映射中搜索ftypmp42/ftypisom/ftypiso2签名ftyp前 4 字节即 MP4 头部Samsung Motion PhotoJPEG 后附带MotionPhoto_Data标记搜索MotionPhoto_Data字节串其后即为视频数据Apple Live Photo独立的同名.mov伴生文件按图片名 .mov在同目录查找且通过_path_as_stored()匹配文件系统实际存储的大小写拼写避免在 SMB 等大小写不敏感挂载上创建重复 File 记录对于 Google/Samsung 的内嵌视频extract_embedded_motion_video()会把视频从 JPEG 中抽出并写入MEDIA_ROOT/embedded_media/{hash}_motion.mp4再作为embedded_media关联到原文件——该过程受FEATURE_PROCESS_EMBEDDED_MEDIA功能开关控制。两阶段扫描变体分组如何完成LibrePhotos 使用两阶段扫描来避免并发处理的竞态条件核心实现在 scan_jobs.py 的scan_photos()与 file_grouping.py阶段一 — 分组Grouping遍历扫描目录收集全部文件_partition_scan_paths()按目录不含扩展名的全小写基本名这一分组键把文件切分成组。IMG_001.jpg与IMG_001.CR2落在同一组XMP sidecar 在此阶段被单独暂存因为它需要先找到归属的照片才能处理。阶段二 — 处理Processing每个文件组作为整体被处理handle_file_group创建一个 Photo 实体并把组内所有文件挂为变体。主显示文件main_file按类型优先级自动选择见 file_grouping.py 中的FILE_TYPE_PRIORITY优先级文件类型说明1IMAGEJPEG/HEIC/PNG/TIFF最高优先级2VIDEO独立视频或 Live Photo 运动片段3RAW_FILERAW 永远作为变体不作主文件4METADATA_FILEXMP sidecar最低优先级5UNKNOWN其他select_main_file()在同类型内按路径字母序取第一个。所有图像组处理完毕后暂存的 XMP sidecar 才通过一个哨兵任务wait_for_group_and_process_metadata按组完成顺序处理并挂接到所属照片上。Repair File Variants 修复任务每次扫描结束后scan_jobs.py 的_queue_followup_jobs()会异步触发一次Repair File Variants任务JOB_REPAIR_FILE_VARIANTS编号 16实现见 repair_jobs.py。它处理的是历次扫描中因竞态条件或增量添加产生的孤儿变体找出所有main_file为 RAW 类型的 Photo即 RAW 自成一张照片的情况先用find_matching_jpeg_photo()在同目录、同基本名下查找已有的 JPEG/HEIC/PNG/TIFF 对应照片扩展名大小写都尝试——找到则把 RAW 文件并入该照片并删除孤立的 RAW Photo找不到对应照片但自身已带图像变体时由_promote_image_main_file()把主文件纠正为图像文件已作为独立照片扫入的 Live Photo 视频不会被该任务合并避免误伤独立视频。查看文件变体照片网格中带 RAW 变体的缩略图显示RAW 徽标灯箱侧栏中文件名旁分辨率与文件大小旁边出现N format(s)链接展开后列出所有非主变体每个带格式徽标JPG、RAW、VIDEO、META或FILE以 zip 下载照片时每张照片的全部文件变体会自动包含在内。相关设置遗留的 Stack RAWJPEG 开关设置中有一个Stack RAWJPEG开关它是遗留项——源自早期将 RAWJPEG 对建模为堆叠stack_raw_jpeg字段User 模型 中默认True。迁移 0109 曾把旧的skip_raw_files语义反向迁移到该字段。由于 RAWJPEG 现在总是在扫描阶段作为文件变体分组当前关闭该开关已无实际效果。Stacks堆叠堆叠把不同但相关的照片归组。注意照片一旦入栈时间线上只显示堆叠的封面照片其余照片仍保留在图库中、折叠在堆叠之后——可以打开堆叠访问它们、用Set Cover更换封面或Unstack解组恢复全部显示。堆叠类型类型说明检测方式Burstburst连拍模式下的快速序列自动规则引擎默认基于 EXIF 连拍/序列标签与文件名模式BracketbracketHDR 曝光包围预留——当前没有包围检测逻辑Create Stack始终创建 Manual 堆叠Manualmanual任意手动归组手动类型定义在 PhotoStack.StackType。模型上还保留两个弃用类型raw_jpeg/live_photo仅用于迁移可见性。自动检测规则引擎如何工作连拍检测基于每用户独立存储的规则列表JSON 存在用户配置的burst_detection_rules字段中实现在 burst_detection_rules.py 与 stack_detection.py。规则分为两类默认启用硬标准确定性判定EXIF Burst Mode Tag— 读取MakerNotes:BurstMode/MakerNotes:ContinuousDrive值为1/On/True/Yes/Continuous即命中按相机型号 秒级时间戳生成分组键EXIF Sequence Number— 相机写入的序列号MakerNotes:SequenceNumber等有有效序列号即判定为连拍成员同样按秒级时间戳分组Filename Burst Pattern— 文件名命名约定。内置五组预定义正则模式见 burst_detection_rules.py 的BURST_FILENAME_PATTERNS模式名正则示例burst_suffix_BURST\dIMG_001_BURST001.jpgsequence_suffix_\d{3,}$以 3 位以上序号结尾的文件bracketed_sequence\(\d\)$photo (1).jpg、photo (2).jpgsamsung_burst_\d{3}_COVER三星连拍封面图iphone_burstIMG_\d{4}_\diPhone 连拍序列默认禁用软标准估计性判定——可能对无关照片误分组Timestamp Proximity— 间隔在interval_ms内默认 2000 毫秒且默认要求同一相机require_same_camera: true以相机品牌_型号比对的连续照片归为一组算法见group_photos_by_timestamp()Visual Similarity— 对时间序上相邻照片做感知哈希比较汉明距离 ≤similarity_threshold默认 15则同组算法见group_photos_by_visual_similarity()。此外源码中还预置了可选的额外规则OTHER_RULES仅匹配_BURST后缀的窄化规则、自定义正则文件名规则custom_pattern以及一条更宽松的 5 秒时间邻近规则interval_ms: 5000且不要求同相机——对应设置界面中可添加的额外预设。每条规则还支持condition_path/condition_filename/condition_exif格式为TAG_NAME//正则三个附加过滤条件。在Settings的Burst Detection Rules面板中可以启用/禁用/重排/增删规则前端预定义规则列表来自api/defaultburstrules与api/predefinedburstrules两个端点。规则修改在下一次运行Detect Stacks时生效。检测流程的调用链从Organizing → Stacks页点击Detect Stacks触发检测完整链路为前端调用POST /api/stacks/detect/DetectStacksView可选参数detect_bursts默认 true视图通过async_task(batch_detect_stacks, user, options)将检测入队为后台任务立即返回202 Accepted{status: queued}batch_detect_stacks() 创建JOB_SCAN_PHOTOS类型的LongRunningJob用于进度跟踪回调中上报{stage: burst_sequences, current, total, found}detect_burst_sequences()先清空该用户既有的BURST_SEQUENCE堆叠保证重新检测不产生重复再分两阶段执行硬标准规则逐张读 EXIF 并归组随后软标准规则在时间序照片上做邻近/相似度分组每个含 ≥2 张照片的组经PhotoStack.create_or_merge()创建堆叠——若组内照片已属于同类型堆叠则自动并入并对连拍堆叠记录sequence_start/sequence_end时间跨度封面自动选择auto_select_primary()连拍/包围取时间中点那张手动堆叠取分辨率最高宽×高最大那张。Organizing 页面与手动堆叠Organizing页导航可达是管理堆叠与重复照片的中心枢纽含两个标签页Stacks 标签页浏览全部检测出的堆叠按类型过滤下拉框提供All Types及你实际存在的每种类型列表接口GET /api/stacks/PhotoStackListView支持stack_type、page、page_size默认 20上限 100参数只返回含 ≥2 张照片的堆叠每页附带前 4 张预览缩略图与封面信息点击堆叠打开Stack Modal展示组内全部照片及细节分辨率、文件大小、相机、时间——详情接口GET /api/stacks/{id}/还返回每张照片的file_variants列表Set CoverPOST /api/stacks/{id}/primary/传入photo_hash更换封面UnstackPOST /api/stacks/{id}/remove/移除照片剩余不足 2 张时堆叠自动删除DELETE /api/stacks/{id}/直接删除整个堆叠只解除关联不删照片View in Lightbox在全屏灯箱中浏览堆叠照片。创建手动堆叠在任意视图中多选照片打开选中操作菜单三点菜单点击Create Stack选中照片被归入一个 manual 堆叠时间线上仅留封面照片。后端 CreateManualStackView 要求至少 2 张去重后的唯一照片若其中任一张已属于手动堆叠新照片会并入该既有堆叠而非新建——这保证了一张照片同一时间只在一个手动堆叠里。管理堆叠选中操作菜单还提供Merge Stacks—POST /api/stacks/merge/把包含选中照片的多个 manual 堆叠合并为一个MergeStacksView会先找到相关 manual 堆叠逐个调用模型的merge_with()迁移照片关联并删除空堆叠Break Apart Stacks— 把选中照片从所属的每个 manual 堆叠中移出堆叠剩余不足 2 张时自动删除RemoveFromStackView中可见该逻辑移除封面后会触发auto_select_primary()重选。Lightbox 中的堆叠查看属于堆叠的照片时侧栏Stacks区块显示堆叠内其他照片的缩略图预览点击即可切换点击View full stack打开 Stack Modal若照片同时属于多个堆叠会按类型分组的折叠面板accordion展示。验证与延伸阅读堆叠与文件变体的行为有完整的测试覆盖位于 tests/stacks/test_burst_detection.py连拍检测、test_burst_rules_engine.py规则引擎、test_live_photo_detection.pyLive Photo、test_stack_api_endpoints.pyAPI 端点等RAW 合并修复逻辑的测试见 test_repair_ungrouped_file_variants.py。API 路由定义集中在 urls.py^api/stacks/...一组路径。适用前提本文基于当前仓库的扫描架构与规则引擎源码。连拍检测依赖用户 EXIF 中实际存在的相机厂商标签MakerNotes不同相机厂商对 BurstMode/SequenceNumber 的写入支持不一此时可启用软标准规则或自定义文件名正则作为补充包围Bracket堆叠目前仅有类型预留尚无可自动检测的创建路径。【免费下载链接】librephotosA self-hosted open source photo management service.项目地址: https://gitcode.com/GitHub_Trending/li/librephotos创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考