Watch at Ease看见世界,读懂生活。

科技 · 实用指南

在 darktable 中把 JPEG 评级复制给不同文件夹的 RAW

保留按日期整理的 RAW 文件夹及其 Jpgs 子文件夹。先检查执行计划,再把明确记录的星级写入已有的 darktable 旁车文件,最后将更改读回图库。

日期文件夹布局概念图:RAW 及其已有 XMP 位于父文件夹,配对的 JPEG 和可选的原生 JPEG XMP 位于 Jpgs。明确选定一个评级来源,只在直接父目录内按相同主文件名配对,再转移 Rating,同时保留 RAW 旁车中的其他元数据和编辑。
原创示意图,使用虚构文件名和四星示例。明确选择需要的 JPEG 内嵌评级或原生 JPEG 旁车作为来源,再将每张 JPEG 与直接上一级目录中的 RAW 配对。脚本要求已有 RAW 旁车,只更改其中的 XMP Rating,不会把 JPEG 编辑历史复制给 RAW。 Watch at Ease

给 JPEG 打好星,可以让大型照片库更容易整理。麻烦在于,对应的 RAW 往往放在上一级文件夹:照片已经选好了,真正要后期处理的文件却还没有这些筛选结果。把评级带过去,不应以重新整理多年的文件夹为前提。

本指南提供适用于这种目录布局的完整辅助脚本。它在每个日期文件夹内配对,只更新已有 RAW 旁车中的一个评级字段,并备份原旁车。之后还需要让 darktable 读回更改,整个流程才算完成。先从一个较小的日期文件夹开始,逐对检查并确认结果,再继续处理其余照片。

1. 配对范围限定在各自的日期文件夹内

支持的布局是:RAW 直接放在日期文件夹中,对应 JPEG 直接放在该文件夹的 Jpgs 子文件夹中。例如,Photos/2024/2024-05/Jpgs/DSC01234.JPG 与 Photos/2024/2024-05/DSC01234.ARW 配对。实际接收评级的文件是 Photos/2024/2024-05/DSC01234.ARW.xmp。

这两个图像文件共有的主文件名是 DSC01234,拼写和大小写都要一致。另一个月份文件夹中的 DSC01234.ARW 属于另一项候选,脚本不会用它补上当前缺失的配对。脚本会遍历你选定的照片目录树,但每一对都受这个父子目录关系约束。它不能判断两份文件是否来自同一次拍摄;你需要确认文件命名和目录安排确实能对应起来。

默认 RAW 扩展名为 ARW,JPEG 子文件夹名为 Jpgs。可用 --raw-ext 指定其他 RAW 扩展名,用 --jpeg-subdir 指定另一个精确的子文件夹名称。如果多个 JPEG 共用主文件名、同时匹配多种 RAW 格式、存在大小写歧义,或发现可能属于编号编辑版本的旁车,脚本会阻止处理并留待检查。Jpgs 内继续嵌套的文件夹不在这条配对规则内。

目标旁车必须已经含有 darktable 元数据。脚本不会创建一个空文件来代替缺失的编辑历史,也不会猜测 Lightroom 风格的 DSC01234.xmp 是否才是目标。darktable 导入时可以读取某些其他命名方式的旁车,但其原生旁车会保留输入图像的扩展名。这次转移应使用实际存在的原生文件。

2. 先确定 JPEG 评级以哪里为准

在 darktable 中做任何准备之前,先确定想要的评级目前存放在哪里。常见情况有三种,准备方式各不相同,虽然脚本只提供两种需要明确指定的来源模式。

如果评级内嵌在原始 JPEG 中,使用 --rating-source jpeg,并保持这些 JPEG 不变。不要仅为本指南准备来源,就把它们导入 darktable 或生成 JPEG 旁车。脚本读取的是其中明确记录的 XMP-xmp:Rating;它不会假定其他应用显示的评级都已写入这个内嵌字段。

如果所需评级目前保存在 darktable 数据库的 JPEG 条目中,在 lighttable 选中相关 JPEG,然后使用 history stack > write sidecar files。这会把当前信息写入 Jpgs/DSC01234.JPG.xmp 这样的原生旁车。关闭 darktable 后,选择 --rating-source sidecar。

如果已有的 JPEG 旁车已经包含所需评级,就保留这份文件并以它为准。不要为了照做上一段的步骤,而用较旧的 darktable 数据库内容覆盖它。旁车模式要求同一文件夹中存在由完整 JPEG 文件名加 .xmp 组成的精确名称;脚本不会寻找省略了 JPEG 扩展名的同主文件名旁车。

内嵌评级和旁车评级都可读取时,计划会同时列出两者。若不一致,警告会指出差异,并以你明确指定的来源模式为准。脚本不会比较修改时间来替你决定。如果选定来源没有评级,即使另一来源有值,这一对仍会跳过。应选择正确模式并重新生成计划,不要把信息缺失当成零星。

3. 确认当前 RAW 旁车,再关闭 darktable

确认每个目标旁车都包含你真正想保留的当前 RAW 编辑状态。如果这些编辑保存在 darktable 数据库中,在 lighttable 选中本批照片,再使用 history stack > write sidecar files;这一步应先于任何对旧旁车的重新导入。如果 RAW 旁车本身已经包含所需的较新编辑,就保留它来生成计划,不要从较旧的数据库覆盖它;执行后再按 XMP 到数据库的方向读回。

对于确实尚未加入图库的 RAW,使用 import > add to library 从其现有位置加入,随后确认原生旁车已经写出。JPEG 元数据只按所选来源分支的需要准备。转移之前,备份重要的图库数据和旁车;脚本之后创建的备份只覆盖它实际更改的 RAW 旁车。

退出前,打开 preferences > storage > XMP sidecar files,启用 look for updated XMP files on startup。在这套流程中,将 create XMP files 设为 on import,不要设为 never。after edit 策略不能确保每个尚未处理的 RAW 都已拥有旁车。

关闭 darktable,并在生成计划、检查和执行期间保持关闭。也暂停其他可能重写这些文件的应用,让准备好的状态保持稳定:图像加入 darktable 后,数据库内容可能优先生效,将外部对 XMP 的更改覆盖掉。

四步概念流程:保留选定评级来源,确认 RAW XMP 包含所需的当前编辑并关闭照片编辑器;检查来源选择和每一项配对计划;备份原 XMP 并更改 Rating;将更改后的 XMP 读入 darktable,检查星级和编辑。零是明确的零星评级,负一表示已拒绝,缺失则跳过。RAW 已有星级或拒绝标记与来源不一致时,默认受到保护。
原创流程概念示意图。保留选定评级来源,确认 RAW XMP 包含所需的当前编辑,再关闭编辑器,然后检查并执行计划。只有 darktable 数据库持有所需来源状态时,才从数据库写出旁车。最后将更改后的 RAW XMP 重新读入 darktable,检查评级和编辑。缺失、零星和已拒绝各不相同;处理冲突需要明确另建新计划。 Watch at Ease

4. 准备 Python、RDFLib 和 ExifTool

需要 Python 3.10 或更高版本、RDFLib 7.6.0,以及 ExifTool 可执行文件。为脚本和计划文件新建一个可写文件夹。示例使用 python3;在 Windows 上,如果你的 Python 命令是 py -3,后续就统一使用它。

把 RDFLib 安装到实际运行脚本的同一个 Python 环境中。虚拟环境便于将这些依赖与其他项目分开。如果使用虚拟环境,请把下文所有命令中的 python3 替换为该环境的 Python 可执行文件。

python3 -m pip install rdflib==7.6.0

python3 -c "import sys,rdflib; print(sys.version); print(rdflib.__version__)"

从 ExifTool 官方发行渠道获取程序,并按其安装说明操作。在 Windows 上,为命令行使用,将 exiftool(-k).exe 改名为 exiftool.exe,并把随附的 exiftool_files 文件夹保留在旁边。若在 macOS 或 Linux 上使用便携的 Perl 发行版,则需将 lib 目录与 exiftool 脚本放在同一位置,并确保 Perl 可用。

如果 ExifTool 已在 PATH 中,脚本可通过 exiftool 找到它。否则,用 --exiftool 指定可执行文件路径。Windows 示例为 --exiftool "C:/Tools/ExifTool/exiftool.exe";macOS 或 Linux 则填写实际的可执行文件路径。这个参数接受文件路径,不接受 "perl exiftool" 这样的 shell 命令。

生成计划和执行计划时,必须使用同一可执行文件及版本。如果期间更新或移动了 ExifTool,请重新生成计划,使记录的运行环境与实际一致。

5. 保存完整脚本并检查代码

下面用一个紧凑的单行代码包提供完整脚本。把整段复制到工作文件夹内名为 unpack_rating_helper.py 的纯文本文件中。保留为一个逻辑行;编辑器仅在显示时自动折行没有关系。不要加上引号、Markdown 代码围栏或周围的文章文字。

解包程序离线工作:它解压内含的源码、校验 SHA-256,然后以只允许新建的方式创建 sync_jpeg_ratings.py,供你检查。它不会运行辅助脚本,也不会下载任何内容。如果同名文件已存在,它会拒绝覆盖。

import base64,zlib,hashlib,sys; from pathlib import Path; data=zlib.decompress(base64.b64decode('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')); f=Path("sync_jpeg_ratings.py").open("xb") if hashlib.sha256(data).hexdigest()=='ba1136a7f667973bf1fef54682971bf964f4518825c99d45955ed871fdb88158' else sys.exit("SHA-256 mismatch; no helper file written"); f.write(data); f.close(); print("Created sync_jpeg_ratings.py for inspection; it has not been run.")

python3 unpack_rating_helper.py

使用前,先在文本编辑器中打开生成的 sync_jpeg_ratings.py。还原后的文件为 34,478 字节,SHA-256 为 ba1136a7f667973bf1fef54682971bf964f4518825c99d45955ed871fdb88158。如果哈希不匹配,就不会写出脚本文件;应重新复制原始代码段,不要删掉校验步骤。

可读的脚本只会在已有 RAW 旁车的临时副本中更改 XMP-xmp:Rating。它用 RDFLib 比较其余全部 RDF 元数据,因此语义等价的 XML 写法可以接受,编辑数据被改动则不能通过。它不会写入图像像素、访问 darktable 数据库,也不会复制 JPEG 的整块 XMP。

6. 生成计划并逐对检查

先从一个较小的日期文件夹,或选定年份的目录树开始。将命令中的照片和 ExifTool 路径换成自己的路径。默认操作只会写出一个新的 JSON 计划文件,照片和旁车都保持不变。

python3 sync_jpeg_ratings.py "/path/to/Photos/2024" --rating-source sidecar --manifest "ratings-plan.json" --exiftool "/path/to/exiftool"

如果要读取 JPEG 内嵌评级,把 sidecar 改为 jpeg。需要时,可添加 --jpeg-subdir "Jpgs" 或 --raw-ext ARW,并填入适合自己的值。在 Windows 上,完整的等效命令可以这样写:

py -3 sync_jpeg_ratings.py "D:/Photos/2024" --rating-source sidecar --manifest "ratings-plan.json" --exiftool "C:/Tools/ExifTool/exiftool.exe"

打开 ratings-plan.json。先查看 counts,再逐一检查每个 ready 条目的 jpeg、raw、raw_sidecar、rating_source、target_rating 和 new_rating。确认路径、预期的旧值到新值变化,以及所有关于内嵌评级与旁车不一致的警告。试运行成功,并不表示每张照片都已准备好执行。

可执行的配对才会进入执行阶段。值相同的配对已经拥有所需评级,会保持不变。跳过的配对是因为选定来源没有明确评级。被阻止的配对需要处理,原因可能是旁车缺失、评级未知、文件名冲突、疑似其他编辑版本,或另一个不支持的情况。解决实际问题后,再生成新的计划文件。

计划记录文件标识、修改时间、完整文件的哈希值,以及相关相邻文件的名称。即使不编辑像素,计算大型 RAW 的哈希仍需要时间,因此应选择规模可控的目录树。空计划可能只是选定文件夹中没有符合条件的 Jpgs 子文件夹,并不能证明照片库中没有已评级的 JPEG。

7. 分清零星、已拒绝和已有评级冲突

来源评级必须是 -1 到 5 之间的整数。零是真实的零星评级,-1 是真实的拒绝标记。字段缺失与这两者都不同。选定来源缺失评级时,脚本会保留 RAW 的原评级;无法读取、小数、超出范围或存在歧义的评级都会被阻止。

默认情况下,有效来源可以填入缺失或零星的目标评级;已有值相同则不作更改。如果目标已有 1–5 星或 -1,而来源值与其不同,就会受到保护,包括来源打算把它清为零星的情况。darktable 文档给出的初始导入评级默认值为 1,所以刚导入的 RAW 也可能触发这项保护。

如果确实要替换这些已有值,先检查受影响的配对,再用 --replace-rated 生成一份单独的计划。这个设置会作用于新计划中所有符合条件的评级冲突,因此应选取足够小的目录树,不要把它当成只勾选某一行的开关。

python3 sync_jpeg_ratings.py "/path/to/Photos/2024" --rating-source sidecar --replace-rated --manifest "ratings-replace-plan.json" --exiftool "/path/to/exiftool"

执行前再次检查新计划。不要手动编辑 JSON 中的值或状态来强行执行。校验和用于发现已审核计划的意外变更;执行阶段还会核对配对、来源、冲突策略和当前文件。允许替换的选项不会使无效评级或有歧义的文件配对变得可接受。

8. 执行已确认的计划并保留回执

保持 darktable 关闭,执行刚刚检查过的那一份计划。只有当你确认的新替换计划就是 ratings-replace-plan.json 时,才在此处使用该文件。ExifTool 路径继续沿用之前的设置。

python3 sync_jpeg_ratings.py --apply "ratings-plan.json" --exiftool "/path/to/exiftool"

每次写入之前,脚本都会重新读取选定评级,核实计划中的文件和目录清单。它在计划文件旁创建名称唯一的 rating-sync-backup-<plan-id> 文件夹,将原 RAW 旁车保存在其中,并在 sidecars 内保留目标的相对目录路径。receipt.json 会记录每个目标、对应备份、旧评级、新评级和哈希值。

ExifTool 在暂存旁车中更改那个明确指定的评级字段。随后,脚本核对评级和除评级外的完整 RDF 数据,再次检查输入状态,最后替换目标文件。提交后,它会验证旁车,并确认 RAW、JPEG 和任何已记录的 JPEG 旁车仍符合各自的文件指纹。XML 排版或工具包标识可能变化,但备份保留原始字节。

文件发生变化、相关文件名清单变化,或计划文件被修改,都会让已过时的操作停止。如果一批处理到中途报错,之前已完成的配对仍然完成,后续配对则不会尝试。查看回执中的 result_counts 和 unattempted_ready_pairs。这是逐个文件提交的过程,不是整批全部成功或全部撤销的事务。

留意 committed_verification_failed 和 commit_state_requires_inspection。出现这些状态时,必须把当前文件与记录的哈希进行核对;替换完成后报错,并不代表什么都没改。中断后,如果最后保存的条目状态是 prepared,也需要检查。将计划、回执和备份放在一起保留。

唯一命名的备份目录会阻止盲目重复执行同一计划。处理好停止的批次后,为剩余工作重新生成计划。不要删除之前的备份目录来绕过这项保护。即使回执显示执行完成,darktable_reload 仍会标记为等待处理。

9. 让 darktable 读回旁车并确认结果

启动 darktable。在发现旁车更新的对话框中,选择本次要处理的 RAW 条目,并点击 keep the XMP edit,即把 XMP 内容读入数据库的操作。不要选 keep the database edit,那会把旧数据库状态写回旁车。这些按钮文字已在当前官方源码中核对过,具体版本或语言的措辞可能不同。

如果没有列出预期条目,不要据此认定转移失败或成功。启动检查受设置和时间戳影响。可使用 lighttable 中的 import > add to library 作为后备方式:找到原 RAW 文件夹,取消勾选 select only new images,并明确选中本批已有的 RAW 文件。该复选框控制的是初始选择,取消勾选不能代替实际选择目标文件。完成添加操作后,它们的 XMP 数据会重新读入。

在原位置使用这些文件。copy & import 走的是另一种导入流程,对已有旁车的处理方式不同。无需从图库移除或删除图像。refresh exif 会重读源图像,可能覆盖评级;把 JPEG 旁车作为 RAW 的历史堆栈加载,则可能替换编辑。这些都不是本次转移所需的读回步骤。

检查界面中 RAW 的星级、已拒绝状态和原有编辑,也检查你依赖的自定义顺序。自定义排序位置不会写入 XMP,因此仅保留旁车,无法保证重建所有只存在于数据库中的细节。如果这些状态很重要,就保留数据库备份。

若要在继续编辑前撤销某一项已确认的转移,先关闭 darktable,在回执中找到那一对准确的 raw_sidecar 与 backup。用 backup_sha256 校验备份,再用 new_sidecar_sha256 核对当前旁车。下面的只读命令会输出文件的 SHA-256;依次换成每个文件的实际路径:

python3 -c "import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" "/full/path/to/sidecar.xmp"

如果当前目标仍与记录中的转移后版本一致,就用文件管理器把这一份原始备份复制回那个精确对应的 RAW 旁车,随后让 darktable 读入恢复后的旁车并验证。如果当前文件在执行后又发生了变化,应先另行保存它并检查这些后续编辑,再决定如何恢复。不要动无关目标。确认本批的评级和编辑状态后,再继续下一个日期文件夹。

验证范围:提供的脚本使用 Python 3.12、RDFLib 7.6.0,以及 13.59.3 供应方发行包中的 ExifTool 13.59,通过了 27 个完整 CLI 测试案例。测试使用生成的 JPEG、含编辑历史的合成 darktable 风格 XMP,以及不解析内容的 RAW 占位文件,核对了受控评级变更、图像字节与其他元数据不变、备份、冲突、过时输入、部分完成和明确选定单个目标后的恢复。解包程序还通过了四种还原测试;Python 开启优化时哈希校验仍然有效,已有的输出文件也不会被覆盖。这些结果不包含 darktable GUI/数据库读回、真实相机 RAW 解码或跨平台执行。界面操作说明依据官方 5.6 手册和当前源码核对,最终结果仍需在自己的图库中确认。