源码解读③:不依赖 PIL 手写 EXIF 解析——前任.skill 照片时间线提取原理
源码解读③不依赖 PIL 手写 EXIF 解析——前任.skill 照片时间线提取原理【免费下载链接】ex-skill前任 skill项目地址: https://gitcode.com/gh_mirrors/exsk/ex-skill前任.skillex-skill是一个把前任蒸馏成可对话 AI Skill 的开源项目而 photo_analyzer.py 负责从照片的 EXIF 元数据中提取拍摄时间、生成一张照片时间线。本篇源码解读带你读懂它如何不依赖 PIL / Pillow仅用 Python 标准库手写 EXIF 解析几十行代码就从 JPEG 的原始字节里钓出了拍摄日期 为什么要手写 EXIF 解析大多数同学提取照片拍摄时间第一反应是from PIL import Image或装个piexif。但前任.skill 选择了另一条路只 import 标准库os、struct、re、datetime、pathlib。这样设计带来三个好处零安装用户 clone 下来就能跑photo_analyzer.py不需要任何pip install。跨平台稳不挑 Python 版本也不受 Pillow 编译、架构兼容的影响。职责单一它只关心这张照片是什么时候拍的不解析照片像素内容内容分析交给 Claude 的Read工具所以能保持极轻量。隐私说明该工具只提取 EXIF 中的日期等元数据不读取、不上传照片内容所有处理都在本地完成。requirements.txt里其实把 Pillow 作为可选依赖注释掉了仅在需要更好支持 HEIC / TIFF 时才启用# Optional: Better EXIF support for more image formats (HEIC, TIFF, etc.) # Pillow10.0.0 参见 requirements.txt先看懂 JPEG 结构SOI、APP1 与 EOI手写 EXIF 解析的前提是理解 JPEG 文件的分段布局。一个 JPEG 由若干**段segment**拼接而成每段都以0xFF开头的标记marker开始标记字节名称含义FF D8SOIStart Of Image文件起始标记FF E1APP1应用段EXIF 信息通常藏在这里FF DASOSStart Of Scan真正的图像数据从这里开始FF D9EOIEnd Of Image文件结束标记每段标记之后紧跟一个2 字节大端长度它包含自身这 2 个字节。所以一段的实际载荷长度是length - 2。理解了这个结构整个解析逻辑就清晰了从 SOI 开始一段一段往后跳直到跳进 APP1把里面的字节读出来找日期。核心逐段扫描的 while 循环整个找 EXIF的过程浓缩在 extract_exif_date() 里。先看它如何校验文件头并逐段游走with open(file_path, rb) as f: # 1) 校验 JPEG 起始标记不是 JPEG 直接放弃 if f.read(2) ! b\xff\xd8: return None while True: marker f.read(2) if marker[0] ! 0xFF: break if marker[1] 0xE1: # 2) 命中 APP1EXIF 在这 length struct.unpack(H, f.read(2))[0] exif_data f.read(length - 2) ... # 3) 在 exif_data 里找日期 elif marker[1] in (0xD9, 0xDA): # 4) 到 EOI / SOS结束 break else: # 5) 其他段读长度并跳过 length struct.unpack(H, f.read(2))[0] f.seek(length - 2, 1)三个关键细节struct.unpack(H, ...)表示大端网络字节序H表示无符号短整型2 字节正好读出段的长度。f.seek(length - 2, 1)1代表相对当前位置把文件指针一次性跳过无关段效率很高。外层try/except直接pass遇到任何异常比如非 JPEG、文件损坏都优雅地返回None绝不中断整个扫描。这段逻辑是全文的骨架对应 photo_analyzer.py。用正则从原始字节里钓出拍摄日期有意思的地方来了。拿到exif_data后代码并没有老老实实去解析 TIFF 的 IFD 目录结构而是直接在原始字节里正则匹配日期串# DateTimeOriginal 在 EXIF 里就是 YYYY:MM:DD HH:MM:SS date_pattern rb\d{4}:\d{2}:\d{2} \d{2}:\d{2}:\d{2} matches re.findall(date_pattern, exif_data) if matches: return matches[0].decode(ascii) 参见 photo_analyzer.py为什么这样能成因为 EXIF 里的拍摄时间标签DateTimeOriginaltag0x9003本身就是按YYYY:MM:DD HH:MM:SS这个 ASCII 格式存储的。与其费劲解析二进制结构不如用一行正则钓出第一处匹配——简单、鲁棒、零依赖。这是典型的够用就好工程取舍。EXIF 拿不到时的兜底文件修改时间现实里很多照片会被压缩、转发EXIF 信息被抹掉。get_photo_date() 为此设计了一个降级兜底EXIF 优先拿不到就退回文件的修改时间。exif_date extract_exif_date(str(file_path)) if exif_date: return datetime.strptime(exif_date, %Y:%m:%d %H:%M:%S).strftime(%Y-%m-%d) # 兜底文件修改时间 mtime file_path.stat().st_mtime return datetime.fromtimestamp(mtime).strftime(%Y-%m-%d)这一层保证了时间线永远不会空——即便所有 EXIF 都丢了照片依然能按修改时间排进时间轴里。按日期分组生成 Markdown 时间线日期拿到后剩下的就是整理成人类能读的表格scan_photos() 递归遍历目录按扩展名过滤出图片支持的格式 覆盖 jpg / heic / webp / tiff 等。group_by_date() 用defaultdict按日期聚合并排序。format_output() 输出 Markdown每天最多列 10 张避免一天几百张刷屏。跑完你得到的时间线长这样# 照片时间线 目录~/Photos/with_her 总照片数128 跨度2022-03-14 ~ 2024-09-02 --- ## 2022-03-145 张 - IMG_0021.jpg3102 KB - IMG_0022.jpg2871 KB ... ## 2022-08-0212 张 - ... 还有 2 张时间线本身不含任何照片内容只回答一个问题这些照片分别属于哪一天。后续再由 AI 按日期回看具体照片补全记忆。三条命令跑通照片时间线 想亲眼看看它工作克隆仓库后对着一个照片文件夹执行即可# 克隆仓库本地只读使用 git clone https://gitcode.com/gh_mirrors/exsk/ex-skill # 扫描照片目录把时间线写到文件 python3 ex-skill/tools/photo_analyzer.py --dir ~/Photos/with_her --output /tmp/photo_timeline.txt # 直接打印到终端 python3 ex-skill/tools/photo_analyzer.py --dir ~/Photos/with_her在 Claude Code 里它被 SKILL.md 的方式 C照片流程调用时间线生成后再由Read工具读回来。相关源码地图photo_analyzer.py 只是 tools/ 下七把瑞士军刀之一它们共同把各类原材料解析成 AI 可用的文本模块作用tools/photo_analyzer.py照片 EXIF 元数据 → 时间线本篇主角tools/wechat_parser.py微信聊天记录解析tools/imessage_parser.pyiMessage / chat.db 解析tools/sms_parser.py短信解析tools/social_media_parser.py微博 / 豆瓣 / 小红书 / Instagram 解析tools/skill_writer.py生成与管理 Skill 文件tools/version_manager.py版本存档与回滚时间线最终会汇入 prompts/memories_analyzer.md 的关系时间线维度成为共同记忆的一部分示例见 exes/example_xiaomei/memories.md。小结不依赖 PIL 的手写 EXIF 解析靠的是一条清晰思路——按 JPEG 标记逐段游走 → 在 APP1 里正则钓日期 → 拿不到就用修改时间兜底 → 按日期分组出时间线。代码很短却把零依赖、鲁棒、够用三件事都做到了这正是它值得被单独解读的原因 ✨【免费下载链接】ex-skill前任 skill项目地址: https://gitcode.com/gh_mirrors/exsk/ex-skill创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

语音识别项目实战:数据增强、微调、热词和流式部署

语音识别项目实战:数据增强、微调、热词和流式部署

摘要:一篇能直接复用的项目实战 这是一篇可以直接照着做的语音与音频长文项目实战。项目面向“会议转写、客服质检、字幕生成和语音工单录入”,核心方法是“Conformer/Whisper、SpecAugment、语言模型融合、热词偏置”,技术栈以Python、Whisper、FunASR、PyTorch、ONNX Run…

2026/11/11 8:00:00 阅读更多 →
微信聊天记录导出的避坑指南:5 个真实场景,每个都有坑

微信聊天记录导出的避坑指南:5 个真实场景,每个都有坑

前言微信聊天记录导出这件事,很多人是"到用的时候才想起导出",然后一头扎进坑里。而且不同场景的坑还不一样:取证的坑在证明力,电商客服的坑在附件和检索,个人留念的坑在附件不落地,工作交接的坑…

2026/10/31 0:43:02 阅读更多 →
旧房改造整装内容写作框架:用户痛点与叙述结构

旧房改造整装内容写作框架:用户痛点与叙述结构

摘要旧房改造整装与新房整装有着本质区别。新房整装是在相对可控的毛坯状态下进行规划,而旧房改造面对的是结构老化、水电线路陈旧、原有装修拆除、邻里关系协调等一系列复杂问题。用户对旧房改造的核心关切,也不同于新房——他们更担心结构安全、水电隐…

2026/11/4 10:19:28 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/10 23:32:01 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/10 23:32:03 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/11 2:31:33 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/10 16:14:15 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/10 12:51:50 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/10 23:32:10 阅读更多 →

日新闻

周新闻

月新闻