You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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("处理完成!")

四、使用说明

  1. 把上面的代码保存为fix_takeout_dates.py,放到你的Google Takeout照片归档目录里(或者随便放,运行时指定目录)
  2. 打开CMD/PowerShell,切换到代码所在目录,运行:
    # 处理当前目录
    python fix_takeout_dates.py
    # 或者指定具体目录
    python fix_takeout_dates.py "C:\Users\YourName\Downloads\Takeout\Google Photos"
    
  3. 脚本会自动遍历所有子目录,处理每个JSON对应的图片文件,输出操作日志。

五、注意事项

  • 运行前最好备份照片!虽然脚本不会删除文件,但操作EXIF和文件时间有风险,备份更稳妥。
  • 对于PNG/WebP等格式,EXIF写入可能不生效(这些格式的EXIF支持不如JPEG完善),但系统文件时间会正常同步。
  • 文件名截断匹配的相似度阈值是0.7,如果匹配不准,可以调整get_close_matches里的cutoff参数(比如调到0.6更宽松)。

内容的提问来源于stack exchange,提问作者LinusNeedTechTips

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 06:40:06