PIL.save()报错:填充字符需为Unicode而非字节的问题排查
解决EXIF元数据修改时的"The fill character must be a unicode character, not bytes"错误
问题背景
- 需求:依据
YYYYMMDD_hhmmdd格式的图片文件名,修改照片的拍摄日期、创建日期等元数据。 - 异常情况:代码处理含二进制GPS信息的图片(20190803_212800.jpeg)时正常,但处理无GPS信息的图片(20190805_120100.jpeg)时,抛出错误:
The fill character must be a unicode character, not bytes。 - 定位结论:问题与图片是否包含GPS信息直接相关,无GPS信息的图片EXIF结构缺失引发编码冲突。
原代码
import os from datetime import datetime from PIL import Image Date_dt_Format = "%Y:%m:%d %H:%M:%S" Name_format = "%Y%m%d_%H%M%S" def Change_Property_picture(File_Name_dt: datetime, file_path: str): # read the image data using PIL image = Image.open(file_path) DateTime_import = File_Name_dt.strftime(Date_dt_Format) # extract EXIF data exif1 = image.getexif() # Update TAGs (34665 --> Exif) exif1.get_ifd(34665)[306] = DateTime_import exif1.get_ifd(34665)[36867] = DateTime_import exif1.get_ifd(34665)[36868] = DateTime_import exif1.get_ifd(34665)[50971] = DateTime_import creation_time = File_Name_dt.timestamp() modification_time = File_Name_dt.timestamp() # Save image.save(fp=f"{file_path}", exif=exif1) os.utime(file_path, (creation_time, modification_time)) picture_path = "path to picture" Pict1_dt = datetime.strptime("20190803_212800", Name_format) Pict2_dt = datetime.strptime("20190805_120100", Name_format) Change_Property_picture(File_Name_dt=Pict1_dt ,file_path=f"{picture_path}20190803_212800.jpeg") Change_Property_picture(File_Name_dt=Pict2_dt ,file_path=f"{picture_path}20190805_120100.jpeg")
错误原因
原代码直接操作PIL的get_ifd(34665)获取Exif子IFD,但无GPS信息的图片EXIF结构不完整,PIL在序列化EXIF数据时,会出现字节与Unicode字符的编码冲突,触发填充字符类型错误。而含GPS信息的图片EXIF结构完整,序列化流程无异常。
解决方案
方案1:修复PIL代码逻辑
通过提前检查并初始化必要的EXIF子IFD,避免结构缺失引发的编码问题:
import os from datetime import datetime from PIL import Image Date_dt_Format = "%Y:%m:%d %H:%M:%S" Name_format = "%Y%m%d_%H%M%S" def Change_Property_picture(File_Name_dt: datetime, file_path: str): image = Image.open(file_path) DateTime_import = File_Name_dt.strftime(Date_dt_Format) # 获取EXIF数据,若不存在则初始化空字典 exif = image.getexif() # 确保Exif子IFD(标签34665)存在,不存在则创建空字典 if 34665 not in exif: exif[34665] = {} exif_ifd = exif.get_ifd(34665) # 更新日期相关标签 exif_ifd[306] = DateTime_import # DateTime(图片修改日期) exif_ifd[36867] = DateTime_import # DateTimeOriginal(拍摄日期) exif_ifd[36868] = DateTime_import # DateTimeDigitized(数字化日期) exif_ifd[50971] = DateTime_import # SubsecTimeOriginal(拍摄日期子秒) # 更新文件系统时间戳 timestamp = File_Name_dt.timestamp() os.utime(file_path, (timestamp, timestamp)) # 保存时明确指定格式,避免编码歧义 image.save(file_path, exif=exif, format="JPEG") picture_path = "path to picture" Pict1_dt = datetime.strptime("20190803_212800", Name_format) Pict2_dt = datetime.strptime("20190805_120100", Name_format) Change_Property_picture(Pict1_dt, f"{picture_path}20190803_212800.jpeg") Change_Property_picture(Pict2_dt, f"{picture_path}20190805_120100.jpeg")
方案2:使用piexif库(更稳定)
piexif是专门处理EXIF数据的库,对边缘情况支持更完善,避免PIL的EXIF序列化bug:
- 先安装库:
pip install piexif - 替换代码:
import os import piexif from datetime import datetime from PIL import Image Date_dt_Format = "%Y:%m:%d %H:%M:%S" Name_format = "%Y%m%d_%H%M%S" def Change_Property_picture(File_Name_dt: datetime, file_path: str): image = Image.open(file_path) DateTime_import = File_Name_dt.strftime(Date_dt_Format) # 读取现有EXIF数据,无数据则初始化空结构 try: exif_dict = piexif.load(image.info["exif"]) except KeyError: exif_dict = {"Exif": {}, "0th": {}, "GPS": {}, "1st": {}} # 更新各日期标签 exif_dict["0th"][piexif.ImageIFD.DateTime] = DateTime_import # 图片修改日期 exif_dict["Exif"][piexif.ExifIFD.DateTimeOriginal] = DateTime_import # 拍摄日期 exif_dict["Exif"][piexif.ExifIFD.DateTimeDigitized] = DateTime_import # 数字化日期 exif_dict["Exif"][50971] = DateTime_import # 拍摄日期子秒 # 将字典转换为EXIF字节流 exif_bytes = piexif.dump(exif_dict) # 保存图片并写入新EXIF数据 image.save(file_path, exif=exif_bytes, format="JPEG") # 更新文件系统时间戳 timestamp = File_Name_dt.timestamp() os.utime(file_path, (timestamp, timestamp)) picture_path = "path to picture" Pict1_dt = datetime.strptime("20190803_212800", Name_format) Pict2_dt = datetime.strptime("20190805_120100", Name_format) Change_Property_picture(Pict1_dt, f"{picture_path}20190803_212800.jpeg") Change_Property_picture(Pict2_dt, f"{picture_path}20190805_120100.jpeg")
内容的提问来源于stack exchange,提问作者Jan Vaško
相关产品推荐
相关产品推荐

