Files
2026-07-13 12:30:10 +08:00

135 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""照片元信息分析器
提取照片 EXIF 信息(拍摄时间、地点),构建关系时间线和常去地点。
Usage:
python3 photo_analyzer.py --dir <photo_dir> --output <output_path>
"""
import argparse
import os
import sys
import json
from pathlib import Path
from datetime import datetime
try:
from PIL import Image
from PIL.ExifTags import TAGS, GPSTAGS
HAS_PIL = True
except ImportError:
HAS_PIL = False
def get_exif_data(image_path: str) -> dict:
"""提取单张图片的 EXIF 信息"""
if not HAS_PIL:
return {'error': 'Pillow 未安装,无法读取 EXIF'}
try:
img = Image.open(image_path)
exif_raw = img._getexif()
if not exif_raw:
return {}
exif = {}
for tag_id, value in exif_raw.items():
tag = TAGS.get(tag_id, tag_id)
exif[tag] = value
result = {
'file': os.path.basename(image_path),
'path': image_path,
}
# 拍摄时间
date_taken = exif.get('DateTimeOriginal') or exif.get('DateTime')
if date_taken:
result['date_taken'] = str(date_taken)
# GPS 信息
gps_info = exif.get('GPSInfo')
if gps_info:
gps_data = {}
for key in gps_info:
decode = GPSTAGS.get(key, key)
gps_data[decode] = gps_info[key]
if 'GPSLatitude' in gps_data and 'GPSLongitude' in gps_data:
lat = _convert_to_degrees(gps_data['GPSLatitude'])
lon = _convert_to_degrees(gps_data['GPSLongitude'])
if gps_data.get('GPSLatitudeRef') == 'S':
lat = -lat
if gps_data.get('GPSLongitudeRef') == 'W':
lon = -lon
result['gps'] = {'lat': lat, 'lon': lon}
return result
except Exception as e:
return {'file': os.path.basename(image_path), 'error': str(e)}
def _convert_to_degrees(value):
"""将 GPS 坐标转换为十进制度"""
d, m, s = value
return float(d) + float(m) / 60 + float(s) / 3600
def main():
parser = argparse.ArgumentParser(description='照片元信息分析器')
parser.add_argument('--dir', required=True, help='照片目录')
parser.add_argument('--output', required=True, help='输出文件路径')
args = parser.parse_args()
if not os.path.isdir(args.dir):
print(f"错误:目录不存在 {args.dir}", file=sys.stderr)
sys.exit(1)
image_exts = {'.jpg', '.jpeg', '.png', '.heic', '.heif'}
photos = []
for root, dirs, files in os.walk(args.dir):
for fname in sorted(files):
if Path(fname).suffix.lower() in image_exts:
fpath = os.path.join(root, fname)
exif = get_exif_data(fpath)
photos.append(exif)
# 按时间排序
dated_photos = [p for p in photos if p.get('date_taken')]
dated_photos.sort(key=lambda x: x['date_taken'])
undated_photos = [p for p in photos if not p.get('date_taken')]
os.makedirs(os.path.dirname(args.output) or '.', exist_ok=True)
with open(args.output, 'w', encoding='utf-8') as f:
f.write(f"# 照片时间线分析\n\n")
f.write(f"扫描目录:{args.dir}\n")
f.write(f"总照片数:{len(photos)}\n")
f.write(f"有时间信息:{len(dated_photos)}\n")
f.write(f"有位置信息:{len([p for p in photos if p.get('gps')])}\n\n")
if dated_photos:
f.write("## 时间线\n\n")
for p in dated_photos:
line = f"- **{p['date_taken'][:10]}** — {p['file']}"
if p.get('gps'):
line += f" (GPS: {p['gps']['lat']:.4f}, {p['gps']['lon']:.4f})"
f.write(line + "\n")
f.write("\n")
if undated_photos:
f.write(f"## 无时间信息的照片({len(undated_photos)} 张)\n\n")
for p in undated_photos:
f.write(f"- {p.get('file', p.get('path', 'unknown'))}\n")
if not HAS_PIL:
f.write("\n⚠️ Pillow 未安装,仅列出文件。安装方法:pip3 install Pillow\n")
print(f"分析完成,结果已写入 {args.output}")
if __name__ == '__main__':
main()