Immich XMP Sidecar 完全指南:元数据读取、写回机制与 Sidecar 队列管理
【免费下载链接】immichHigh performance self-hosted photo and video management solution.项目地址: https://gitcode.com/GitHub_Trending/im/immich
Immich 通过 XMP Sidecar(旁车文件)机制,在不改动原始媒体文件的前提下,与 Lightroom、Darktable、digiKam 等专业工具共享元数据:上传时从.xmp文件导入描述、评分、时间、位置和标签,之后在 Web UI 中修改这些字段时再写回.xmp文件。本文基于官方文档与服务器端源码,完整讲解 Sidecar 的字段映射规则、文件命名约定、内外部库两条工作流,以及 Sidecar 队列在管理后台的运行原理。
什么是 XMP Sidecar
XMP 是一种以 XML 格式存储元数据的标准。Sidecar 是与媒体文件同名、以.xmp结尾的外部元数据文件,例如IMG_0001.jpg.xmp。与直接写入文件内嵌 EXIF 不同,Sidecar 方式完全不修改原始文件——这正是 Lightroom 等工具默认采用的"非破坏性编辑"模式。
Immich 在两个环节使用 Sidecar:
- 元数据提取任务(Metadata Extraction job):读取并导入
.xmp文件中的元数据到数据库; - Sidecar Write 任务:将数据库中的元数据写回
.xmp文件。
文档还指出一个重要的互操作性:Lightroom、Darktable、digiKam 等应用都可以配置为把修改写入.xmp文件而非原图,这样 Immich 与这些工具就能通过同一份 Sidecar 文件交换元数据,而原始媒体文件始终保持原样。
支持的元数据字段:读取与写入映射
Immich 并不支持所有 XMP 字段。文档给出了明确的"读/写"字段映射表,并且强调:写操作不会替换整个文件内容,而是与文件中已有字段合并(merge)。
| 元数据 | Immich 写入 XMP 的字段 | Immich 从 XMP 读取的字段 |
|---|---|---|
| Description | dc:description,tiff:ImageDescription | dc:description,tiff:ImageDescription |
| Rating | xmp:Rating | xmp:Rating |
| DateTime | exif:DateTimeOriginal,photoshop:DateCreated | 按优先级依次尝试:exif:SubSecDateTimeOriginal、exif:DateTimeOriginal、xmp:SubSecCreateDate、xmp:CreateDate、xmp:CreationDate、xmp:MediaCreateDate、xmp:SubSecMediaCreateDate、xmp:DateTimeCreated |
| Location | exif:GPSLatitude,exif:GPSLongitude | exif:GPSLatitude,exif:GPSLongitude |
| Tags | digiKam:TagsList | 按优先级依次尝试:digiKam:TagsList、lr:HierarchicalSubject、IPTC:Keywords |
映射表之外的字段(如Creator、Source、IPTC 其他字段、Lightroom 的编辑记录等)会原样保留在.xmp文件中,但不会被 Immich 索引、不可搜索。
源码印证:读取合并与写回实现
读取阶段的实现在 metadata.service.ts 的getExifTags方法中(约 L565-L622):它对媒体文件本身和 Sidecar 文件分别调用readTags,然后按{ ...mediaTags, ...videoResult?.tags, ...sidecarTags }的顺序合并——Sidecar 标签覆盖媒体文件内嵌标签。其中日期字段有专门处理:如果 Sidecar 中解析出有效日期(firstDateTime,按EXIF_DATE_TAGS优先级列表匹配),会先把媒体文件的所有日期类标签删除,再让 Sidecar 的日期生效,从而保证"Sidecar 优先"的语义。
写回实现在handleSidecarWrite(约 L475-L527):它从资产的exifInfo中取出description、dateTimeOriginal、latitude、longitude、rating、tags,组装为Description、ImageDescription、DateTimeOriginal、GPSLatitude、GPSLongitude、Rating、TagsList等标签对象,最后调用 metadata.repository.ts 的writeTags(L136-L146)。
这里有一个关键细节解释了"写入是合并而非替换":writeTags内部给每个标签名追加^前缀(如Description^)后再交给 exiftool-vendored 写入,对应 exiftool 的"追加/合并"写模式,因此.xmp文件中已有但 Immich 不认识的字段不会被清掉。
此外,写回时机由事件驱动:handleTagAsset/handleUntagAsset(L465-L473)监听AssetTag/AssetUntag事件并自动把SidecarWrite任务投入队列。也就是说,在 Web UI 中编辑描述、评分或更新标签后,Immich 会自动排队一次 Sidecar Write,与文档中"自动排队 Sidecar Write"的提示一致。
文件命名规则
Sidecar 必须与媒体文件共享基础名。文档给出的规则:
- ✅
IMG_0001.jpg.xmp(推荐) - ✅
IMG_0001.xmp(兜底) - ❌
myphoto_meta.xmp(不会被识别)
若IMG_0001.jpg.xmp和IMG_0001.xmp同时存在,Immich 使用.jpg.xmp版本。
这段规则在源码中有精确对应。metadata.service.ts 的getSidecarCandidates(L529-L547)按顺序生成候选路径:
- 已关联的
sidecarFile.path(数据库中已记录的 Sidecar 路径,如存在); ${originalPath}.xmp—— 即IMG_123.jpg.xmp;${join(assetPath.dir, assetPath.name)}.xmp—— 即IMG_123.xmp。
SidecarCheck任务(handleSidecarCheck,L424-L463)逐个用storageRepository.checkFileExists(candidate, constants.R_OK)探测候选文件是否可读,取第一个命中的路径;若与数据库已记录路径不同,则通过assetRepository.upsertFile({ type: AssetFileType.Sidecar, path })更新关联,否则删除旧关联。这解释了"优先.jpg.xmp"——候选列表的顺序即优先级。
上传工作流:Detect → Copy → Extract → Write-back
对于内部库(用户上传的资产),Sidecar 经历四个阶段:
- Detect(检测)——上传时 Immich 检查每个媒体文件旁边是否放置了
.xmp文件。上传接口的字段映射在 asset-media.service.ts 中定义:UploadFieldName.SIDECAR_DATA对应扩展名.xmp(约 L72、L97),即客户端可以随媒体文件同时提交一个 Sidecar 字段。 - Copy(复制)——媒体文件和 Sidecar 一起被复制进 Immich 内部库目录。Sidecar 会按内部存储模板重命名,与媒体文件保持同目录同名关系,例如:
upload/library/<user>/YYYY/YYYY-MM-DD/IMG_0001.jpg upload/library/<user>/YYYY/YYYY-MM-DD/IMG_0001.jpg.xmp存储模板迁移时 Sidecar 的跟随重命名逻辑见 storage-template.service.ts(约 L253,
newPath:${newPath}.xmp``)。 - Extract(提取)——从 Sidecar 中解析选定的元数据(标题、描述、日期、评分、标签)并存入数据库,即上文
getExifTags的合并逻辑。 - Write-back(写回)——之后如果在 Web UI 中更新标签、评分或描述,Immich 会同时更新数据库和被复制的
.xmp文件,保持两边同步。
外部库(挂载文件夹)工作流
对外部库(外部库通过IMMICH_EXTERNAL_LIBRARY挂载已有文件夹,见 外部库文档),流程有所不同:
- Detect——外部库的
DISCOVER任务自动把挂载目录中与既有媒体文件相邻的.xmp文件建立关联,不移动、不重命名任何文件; - Extract——从 Sidecar 读取并保存与上文相同的一组元数据字段;
- Write-back——只要 Immich 对该挂载点有写权限,之后对评分、标签等元数据的编辑也会写回磁盘上的原始
.xmp文件。
:::danger 注意:只读挂载的陷阱
如果挂载是只读的,Immich 既无法更新 Sidecar 也无法更新数据库——元数据编辑会静默失败且不产生任何警告。官方文档将这一已知问题链接到上游 issue #10538。生产部署中若 Sidecar 写入不生效,应优先检查挂载点的读写权限。 :::
Sidecar 管理队列(Admin Jobs)
Immich 提供两个管理员任务来管理 Sidecar:
| 任务 | 作用 |
|---|---|
DISCOVER | 找到媒体文件旁边新出现的.xmp文件(媒体尚未关联任何 Sidecar 时建立关联) |
SYNC | 重新读取已有的.xmp文件并刷新数据库中的元数据(例如外部工具在 Immich 之外编辑过 Sidecar 之后) |
从源码结构看,这两个入口对应服务器端Sidecar队列:enum.ts 中定义了QueueName.Sidecar以及JobName.SidecarQueueAll、JobName.SidecarCheck、JobName.SidecarWrite(约 L906-L908)。管理端触发时,queue.service.ts 的runCommand对QueueName.Sidecar投递SidecarQueueAll任务(L220-L221),后者流式取出待处理资产,为每个资产排队一个SidecarCheck任务——即上文的"检测/发现"环节;而元数据的重新读取则由元数据提取任务完成(可用force参数强制重新执行)。
Web 端在管理后台的"队列"与"任务设置"页面中暴露了该队列:queue.service.ts(L181-L185)将其映射为带 XML 图标的队列项,JobSettings.svelte 中可调整 Sidecar 任务的并发配置。
实践要点小结
- 让 Lightroom/Darktable/digiKam 以 Sidecar 模式工作,Immich 即可与其共享描述、评分、日期、坐标、标签五类可搜索元数据;
- Sidecar 命名务必使用
媒体文件名.xmp(首选带原始扩展名的.jpg.xmp),其他命名不会被识别; - 写回是字段级合并:Immich 不会破坏 Sidecar 中其他工具留下的字段(如 Lightroom 编辑记录);
- 外部库场景下确保挂载点可写,否则元数据编辑会静默失败;
- 外部工具离线修改 Sidecar 后,通过管理后台触发
SYNC让数据库刷新到最新状态。
【免费下载链接】immichHigh performance self-hosted photo and video management solution.项目地址: https://gitcode.com/GitHub_Trending/im/immich
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考