Windows系统下基于Python从Google Takeout JSON文件批量恢复照片创建日期及修复EXIF元数据
解决Google Takeout照片日期与文件名匹配问题的Python方案
我之前帮朋友处理过一模一样的Google Takeout归档问题,手动改几千张照片根本不现实,下面这套Python脚本完美解决你的所有需求,包括文件名截断的匹配小坑。
一、环境准备
首先安装依赖库,打开Windows命令提示符(CMD)或者PowerShell,运行:
pip install pywin32 pillow
pywin32:用来修改Windows系统级的文件创建/修改时间(Python内置方法改不了创建时间)Pillow:用来读取和写入JPEG的EXIF元数据
二、核心功能实现
1. 解析JSON获取原始拍摄时间
每个图片对应的JSON文件里,photoTakenTime.timestamp是UTC时间戳,我们会把它转换成Windows本地时间的datetime对象,方便后续操作。
2. 同步系统文件时间
用win32file和win32con API直接修改文件的创建时间和修改时间,这样Windows资源管理器就能正确按日期排序了。
3. 注入EXIF元数据到JPEG
对于JPG/JPEG文件,我们用Pillow把原始拍摄时间写入EXIF的DateTimeOriginal(原始拍摄时间)、DateTimeDigitized(数字化时间)和DateTime(文件修改时间)字段,这样其他看图软件也能识别到正确的拍摄日期。
4. 处理文件名截断匹配问题
Takeout有时候会截断长文件名(比如"2023-05-01 12-30-45 我的旅行照片.jpg"变成"2023-05-01 12-30-45 我的旅行照~1.jpg"),这时候我们用模糊匹配+前缀匹配的方式,优先找完全匹配的,找不到就找前缀最接近的文件,确保能正确对应。
三、完整代码
import os import json import datetime import win32file import win32con from PIL import Image from difflib import get_close_matches def get_photo_taken_time(json_path): """从JSON文件读取原始拍摄时间,返回本地datetime对象""" try: with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) timestamp = int(data.get('photoTakenTime', {}).get('timestamp', 0)) if not timestamp: return None # 转换UTC时间戳为本地datetime utc_dt = datetime.datetime.utcfromtimestamp(timestamp) local_dt = utc_dt.astimezone() return local_dt except Exception as e: print(f"解析JSON失败 {json_path}: {str(e)}") return None def set_file_times(file_path, target_dt): """设置Windows文件的创建时间和修改时间""" try: # 转换datetime为Windows文件时间格式(100纳秒间隔的整数) file_time = win32file.TimeToFileTime(target_dt.timetuple()) handle = win32file.CreateFile( file_path, win32con.GENERIC_WRITE, win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE | win32con.FILE_SHARE_DELETE, None, win32con.OPEN_EXISTING, win32con.FILE_ATTRIBUTE_NORMAL, None ) # 设置创建时间、最后访问时间、最后修改时间 win32file.SetFileTime(handle, file_time, file_time, file_time) handle.close() print(f"已同步系统时间: {file_path} -> {target_dt.strftime('%Y-%m-%d %H:%M:%S')}") except Exception as e: print(f"设置文件时间失败 {file_path}: {str(e)}") def update_exif_date(file_path, target_dt): """更新JPEG文件的EXIF拍摄日期""" if not file_path.lower().endswith(('.jpg', '.jpeg')): # PNG等格式EXIF支持有限,暂不处理 return try: exif_date_str = target_dt.strftime('%Y:%m:%d %H:%M:%S') with Image.open(file_path) as img: exif_data = img.getexif() # 更新EXIF日期字段 exif_data[36867] = exif_date_str # DateTimeOriginal exif_data[36868] = exif_date_str # DateTimeDigitized exif_data[306] = exif_date_str # DateTime img.save(file_path, exif=exif_data) print(f"已更新EXIF数据: {file_path}") except Exception as e: print(f"更新EXIF失败 {file_path}: {str(e)}") def find_matching_image(json_filename, image_extensions, search_dir): """根据JSON文件名找到对应的图片文件,处理截断问题""" base_name = os.path.splitext(json_filename)[0] # 先找完全匹配的 for ext in image_extensions: candidate = os.path.join(search_dir, base_name + ext) if os.path.exists(candidate): return candidate # 找不到完全匹配的,找模糊匹配的(处理文件名截断) all_files = [f for f in os.listdir(search_dir) if os.path.splitext(f)[1].lower() in image_extensions] matches = get_close_matches(base_name, all_files, n=1, cutoff=0.7) if matches: return os.path.join(search_dir, matches[0]) print(f"未找到匹配的图片文件: {base_name}") return None def process_takeout_photos(root_dir): """遍历目录处理所有Takeout照片和JSON""" image_extensions = ('.jpg', '.jpeg', '.png', '.webp') # 支持的图片格式 for dirpath, _, filenames in os.walk(root_dir): for filename in filenames: if filename.lower().endswith('.json'): json_path = os.path.join(dirpath, filename) # 获取拍摄时间 taken_dt = get_photo_taken_time(json_path) if not taken_dt: continue # 找匹配的图片文件 image_path = find_matching_image(filename, image_extensions, dirpath) if not image_path: continue # 同步系统时间 set_file_times(image_path, taken_dt) # 更新EXIF(仅JPEG) update_exif_date(image_path, taken_dt) if __name__ == "__main__": import sys # 支持命令行指定目录,默认当前目录 target_dir = sys.argv[1] if len(sys.argv) > 1 else os.getcwd() print(f"开始处理目录: {target_dir}") process_takeout_photos(target_dir) print("处理完成!")
四、使用说明
- 把上面的代码保存为
fix_takeout_dates.py,放到你的Google Takeout照片归档目录里(或者随便放,运行时指定目录) - 打开CMD/PowerShell,切换到代码所在目录,运行:
# 处理当前目录 python fix_takeout_dates.py # 或者指定具体目录 python fix_takeout_dates.py "C:\Users\YourName\Downloads\Takeout\Google Photos" - 脚本会自动遍历所有子目录,处理每个JSON对应的图片文件,输出操作日志。
五、注意事项
- 运行前最好备份照片!虽然脚本不会删除文件,但操作EXIF和文件时间有风险,备份更稳妥。
- 对于PNG/WebP等格式,EXIF写入可能不生效(这些格式的EXIF支持不如JPEG完善),但系统文件时间会正常同步。
- 文件名截断匹配的相似度阈值是0.7,如果匹配不准,可以调整
get_close_matches里的cutoff参数(比如调到0.6更宽松)。
内容的提问来源于stack exchange,提问作者LinusNeedTechTips
相关产品推荐
相关产品推荐

