用 exiftool 修复 Google Takeout 照片日期:完整命令和坑
把 Takeout .json 文件里的日期和 GPS 复制进照片的 exiftool 命令,它在哪些情况下会失败,以及什么时候该换图形界面工具。
exiftool 免费,哪个系统都能跑,也确实能修 Google Takeout 的导出。大多数教程给你一条命令就结束了。那条命令对五分之四的照片有效。这篇讲第五张会怎样。
安装
Mac 上 brew install exiftool。Debian 或 Ubuntu 上 sudo apt install libimage-exiftool-perl。Fedora 上 sudo dnf install perl-Image-ExifTool。Windows 上从 exiftool.org 下载 zip,解压,把 exiftool(-k).exe 改名为 exiftool.exe。然后在放着解压后 Takeout 文件夹的目录里打开终端。
第一次在副本上操作。exiftool 会直接写你的文件。
主命令
每张照片旁边都有一个附属文件,2024 年以后的导出叫 IMG_2034.JPG.supplemental-metadata.json,更早的叫 IMG_2034.JPG.json。附属文件里的 photoTakenTime.timestamp 是真正的拍摄时间,以 1970 年以来的秒数记。这条命令把它复制进照片:
exiftool -r -d %s -tagsfromfile "%d/%F.supplemental-metadata.json" \
"-DateTimeOriginal<PhotoTakenTimeTimestamp" \
"-CreateDate<PhotoTakenTimeTimestamp" \
"-FileModifyDate<PhotoTakenTimeTimestamp" \
-ext jpg -ext jpeg -ext png -ext heic -ext gif \
-overwrite_original -progress "Takeout/Google Photos"
逐段解释:-r 递归子文件夹。-d %s 告诉 exiftool 读到的日期是一个时间戳数字。-tagsfromfile 用 %d(照片所在文件夹)和 %F(带扩展名的文件名)拼出附属文件名。三行带 < 的把时间戳分别复制进 EXIF 拍摄时间、EXIF 创建时间和文件修改时间。-overwrite_original 让 exiftool 不要给每个文件留一个 _original 备份。
旧格式的导出,把模式换成 "%d/%F.json" 再跑一遍。第一遍已经写过日期的文件只是被重写成相同的值。
想同时写 GPS,再加四行:
"-GPSLatitude<GeoDataLatitude" "-GPSLatitudeRef<GeoDataLatitude" \
"-GPSLongitude<GeoDataLongitude" "-GPSLongitudeRef<GeoDataLongitude" \
Google 给没有位置的照片写 0.0,所以要包一层 -if '$GeoDataLatitude ne 0',否则几百张照片会被放到大西洋上。详见 Takeout 照片没有位置信息。
跑完之后,exiftool 会打印更新了多少文件、跳过了多少。被跳过的那些,就是一个晚上耗掉的地方。
在哪里失败
被截断的附属文件名。 Google 会把过长的附属文件名截断到 46 个字符左右。PXL_20230812_193044123.RAW-01.COVER.jpg 的附属文件叫 PXL_20230812_193044123.RAW-01.COVER.supplemen.json 或更短。%F 模式永远匹配不到。长的相机文件名、微信和 WhatsApp 的文件名、文件名里带说明文字的,都会撞上。你得把剩下的列出来,手动或者写脚本匹配。
带 (1) 的重复文件。 同一个文件名上传了两次,照片叫 IMG_1234(1).jpg,附属文件却叫 IMG_1234.jpg(1).json。括号挪位置了。跑主命令之前先给附属文件改名:
for j in "Takeout/Google Photos"/*/*"("*").json"; do
mv "$j" "$(echo "$j" | sed -E 's/\.([a-zA-Z0-9]+)\(([0-9]+)\)(\.supplemental-metadata)?\.json$/(\2).\1\3.json/')"
done
编辑过的副本。 IMG_1234-edited.jpg 没有自己的附属文件,和原始文件共用。原始文件处理完之后,从原始文件复制:
for f in "Takeout/Google Photos"/*/*-edited.*; do
exiftool -tagsfromfile "${f/-edited/}" -AllDates -GPS:all -overwrite_original "$f"
done
视频。 MP4 和 MOV 不用 EXIF。它们的日期存在 QuickTime 容器的另一组标签里,而且按 UTC 读取:
exiftool -r -d %s -api QuickTimeUTC -tagsfromfile "%d/%F.supplemental-metadata.json" \
"-CreateDate<PhotoTakenTimeTimestamp" "-ModifyDate<PhotoTakenTimeTimestamp" \
"-TrackCreateDate<PhotoTakenTimeTimestamp" "-MediaCreateDate<PhotoTakenTimeTimestamp" \
"-FileModifyDate<PhotoTakenTimeTimestamp" \
-ext mp4 -ext mov -overwrite_original "Takeout/Google Photos"
实况照片的视频片段完全没有附属文件,跑完这条它们还是没日期。实况照片的 MP4/MOV 配对那篇有处理它们的循环。
时区的坑
附属文件里的时间戳是 UTC 的精确时刻。EXIF 的 DateTimeOriginal 是不带时区的挂钟时间。exiftool 用你电脑当前的时区在两者之间换算。在上海 11:05 拍的照片,电脑设成北京时间就得到 11:05,设成纽约时间就变成前一天 22:05。
跑之前把时区设成大多数照片的拍摄地:
TZ=Asia/Shanghai exiftool ...
在别的时区拍的旅行照片还是会差几个小时。单靠附属文件没有办法避免;只有相机自己写的 EXIF(如果还在)记录了当地时间。所以不要「修」那些已经有正确 EXIF 日期的照片。加上 -if 'not $DateTimeOriginal' 让它们保持原样。
这个晚上值不值
几千张相机照片的干净导出,包括读这篇文章,exiftool 一个小时搞定。真实的相册里有微信图片、截图、(1) 重复、HEIC、视频和实况照片,预算一个晚上,并且准备多跑几次 exiftool -r -if 'not $DateTimeOriginal' -filename 看还有什么没修上。
如果你还想按日期重命名、按年/月分文件夹、HEIC 转 JPG、实况照片保持配对,那还要再串四条命令和一个循环。喜欢折腾的话没问题。GooglePhotosTakeoutHelper 是一个免费脚本,替你处理命名上的怪事,如果你习惯终端但不想自己写 sed,它是个不错的中间选项。
用软件
Takeout JSON Metadata Fixer 一次运行做完整个清单,先试运行,支持 Windows、Linux 和 Mac。
它把每张照片和附属文件配对,包括截断的文件名、(1) 重复和 -edited 副本,把 photoTakenTime 和 GPS 写进文件。视频写 QuickTime 标签。实况照片的视频片段继承静态照片的日期。没有附属文件的照片保留 EXIF 日期,两者都没有的用文件时间,所以不会有照片没日期。
命名和文件夹模板是可选的,「保留文件名」加「保留我的文件夹」就只修日期。开始前检查磁盘空间,全程离线。前 1000 张照片免费。无限制一次性 19.99 美元,比一个晚上便宜。
常见问题
exiftool 报 "Warning: No writable tags set from" 某个附属文件,什么意思?
附属文件存在但没有 photoTakenTime,或者文件名模式匹配到了错误的文件。打开那个 .json 看看。相册文件夹里的 metadata.json 也会触发这个警告,可以忽略。
能不解压直接对 zip 跑吗?
不能。exiftool 需要文件在磁盘上。先把所有分卷解压到同一个文件夹,见 Takeout 分成很多个 zip 怎么办。
写 EXIF 会改变图片吗?
不会。exiftool 重写元数据区块,图像数据不动。文件可能大几百字节。
该复制 photoTakenTime 还是 creationTime?
photoTakenTime。creationTime 是上传到 Google 的时间,经常晚好几年。