Photo Folder Organizer
Intelligently organize a photo folder: clean non-photo files, remove bad exposures locally, detect blur/burst via Python, analyze content with AI vision, and sort into categorized subfolders.
Usage
User wants to organize a photo folder: $ARGUMENTS
If the user has not provided a folder path, ask them to provide one.
Language note: Detect the language the user is writing in and respond in that language throughout the entire session. Category folder names should also be in the user's language.
Step 1: Scan the Folder
Scan the folder for all files (non-recursive at root level):
CODEBLOCK0
Report to user:
- - Total files found
- Number of photo files
- Number of non-photo files (list them)
Step 2: Handle Non-Photo Files
Use AskUserQuestion (only if non-photo files exist):
Question: "Found N non-photo file(s). How would you like to handle them?"
Options:
- - "Move to _misc subfolder (Recommended)"
- "Delete all non-photo files"
- "Leave them as-is"
CODEBLOCK1
Step 3: Remove Bad Exposure Photos (Local Python)
Use AskUserQuestion:
Question: "Would you like to remove photos with severely bad exposure (near-black or near-white)?"
Options:
- - "Yes, use default thresholds (brightness mean < 5% or > 95%) (Recommended)"
- "Yes, let me specify custom thresholds"
- "No, keep all photos"
Run a Python script to detect and report bad exposures without deleting yet:
CODEBLOCK2
Run: INLINECODE0
Show the list to user, then confirm before deleting:
CODEBLOCK3
Step 4: Detect Blur and Burst Shots (Local Python)
Run a comprehensive Python analysis script on all remaining photos. This step:
- 1. Scores each photo's sharpness (Laplacian variance)
- Detects burst groups (photos taken within 3 seconds of each other OR with near-identical perceptual hash)
CODEBLOCK4
Run: python3 /tmp/analyze_photos.py "$FOLDER" 80 3 8
Step 5: Handle Blurry Photos
If any blurry photos were found:
Use AskUserQuestion:
Question: "Found N blurry photo(s) (out of focus). How would you like to handle them?"
Options:
- - "Delete all blurry photos"
- "Move to rejectedblur subfolder"
- "Keep them, do nothing"
Execute using /tmp/photo_analysis.json:
CODEBLOCK5
Step 6: Handle Burst Series
If burst groups were found:
Use AskUserQuestion:
Question: "Found N burst group(s) (M photos total). How would you like to handle them?"
Options:
- - "Keep only the sharpest photo per group, delete the rest (Recommended)"
- "Keep only the sharpest photo per group, move the rest to burstextras"
- "Keep all, do nothing"
- "Decide group by group"
If "Decide group by group", for each burst group: show filenames + sharpness scores, use AskUserQuestion with: Keep sharpest / Keep all / Decide photo by photo
Execute using /tmp/photo_analysis.json:
CODEBLOCK6
Step 7: AI Vision Analysis and Classification
For AI classification, read photo images directly using the Read tool (no frame extraction needed — photos are already images).
For large folders (>100 photos): Read photos in batches of 20-30, analyzing all at once.
Analyze each photo and produce:
- 1. Category: A short label describing content, in the user's language (e.g. landscape, portrait, architecture, food, interior, events, night scene, animals, street, nature, travel)
- Quality note: any notable issue not already caught (strong motion blur, extreme overexposure, poor composition) — mark as "deletable" if clearly low value
After analyzing all photos, present a summary table:
Filename Category Notes
IMG_001.jpg landscape none
IMG_002.jpg portrait none
IMG_003.jpg architecture none
...
Step 8: Classify Photos into Numbered Subfolders
Based on AI analysis categories:
- 1. Collect all unique categories
- Sort by count (most photos first)
- Assign two-digit numbers:
01_, 02_, etc.
Show proposed structure (folder names in the user's language):
CODEBLOCK8
Use AskUserQuestion:
Question: "Does the proposed folder structure look good?"
Options:
- - "Looks good, proceed"
- "I need to rename some categories"
- "I need to merge some categories"
Execute file moves:
CODEBLOCK9
Show final structure after moving:
find "$FOLDER" -type d | sort
for d in "$FOLDER"/*/; do echo "$d: $(ls "$d" | wc -l) photos"; done
Step 9: Refine a Category (Optional)
Use AskUserQuestion:
Question: "Would you like to further organize any category folder?"
Options:
- - "No, all done"
- "Yes, let me choose a category"
If user wants to refine, list created folders as options.
Then ask:
Question: "How would you like to organize this folder?"
Options:
- - "Group by date"
- "Group by quality (picks / normal)"
- "Group by person"
- "Let me describe how"
Execute the requested sub-organization using AI analysis data or re-read images if needed.
After completing, loop back to Step 9 to ask if any other category needs refinement.
Technical Notes
Prerequisites
- - Python 3 with packages:
Pillow, numpy, opencv-python-headless, INLINECODE9 - Install: INLINECODE10
- Or use
uv / uvx for isolated environments
Blur Detection
- - Uses Laplacian variance: measures edge sharpness in the image
- Threshold ~80 works well for typical photos; adjust lower (e.g. 50) for lenient mode
- Very high-resolution photos may need higher threshold (~150)
- Note: intentionally blurred/bokeh backgrounds don't trigger this — it analyzes the whole image
Burst Detection Logic
- - Time-based: Photos taken within 3 seconds → likely burst
- Hash-based: Perceptual hash distance ≤ 8 → nearly identical composition
- Both conditions are checked independently (OR logic)
- Sharpest photo = highest Laplacian variance score → selected as "best" in group
Supported Formats
- - JPEG, PNG, HEIC/HEIF, WebP, BMP, TIFF
- RAW formats (CR2, CR3, NEF, ARW, DNG) require
rawpy package:
pip install rawpy — add rawpy support to detect
badexposure.py for RAW files
Temp Files
- -
/tmp/photo_analysis.json — full analysis results; clean up after: INLINECODE16
Folder Safety
- - Never delete files without explicit user confirmation
- Always show what will be deleted/moved before executing
- Prefer moving to
_rejected_* subfolder over permanent deletion
照片文件夹整理工具
智能整理照片文件夹:清理非照片文件,本地删除曝光不佳的照片,通过Python检测模糊/连拍,使用AI视觉分析内容,并分类到子文件夹中。
使用方法
用户想要整理一个照片文件夹:$ARGUMENTS
如果用户没有提供文件夹路径,请要求他们提供一个。
语言提示:检测用户使用的语言,并在整个会话过程中使用该语言回复。分类文件夹名称也应使用用户的语言。
步骤1:扫描文件夹
扫描文件夹中的所有文件(仅在根层级,不递归):
bash
列出所有文件及其大小
ls -la $FOLDER
查找照片文件(常见扩展名)
find $FOLDER -maxdepth 1 -type f \( \
-iname
.jpg -o -iname .jpeg -o -iname *.png \
-o -iname
.heic -o -iname .heif \
-o -iname
.raw -o -iname .cr2 -o -iname *.cr3 \
-o -iname
.nef -o -iname .arw -o -iname *.dng \
-o -iname
.tiff -o -iname .tif -o -iname *.bmp \
-o -iname
.webp -o -iname .gif \
\)
查找非照片文件
find $FOLDER -maxdepth 1 -type f ! \( \
-iname
.jpg -o -iname .jpeg -o -iname *.png \
-o -iname
.heic -o -iname .heif \
-o -iname
.raw -o -iname .cr2 -o -iname *.cr3 \
-o -iname
.nef -o -iname .arw -o -iname *.dng \
-o -iname
.tiff -o -iname .tif -o -iname *.bmp \
-o -iname
.webp -o -iname .gif \
\)
向用户报告:
- - 找到的文件总数
- 照片文件数量
- 非照片文件数量(列出它们)
步骤2:处理非照片文件
使用AskUserQuestion(仅当存在非照片文件时):
问题:找到N个非照片文件。您希望如何处理它们?
选项:
- - 移动到_misc子文件夹(推荐)
- 删除所有非照片文件
- 保持原样
bash
mkdir -p $FOLDER/_misc
mv [非照片文件] $FOLDER/_misc/
步骤3:删除曝光不佳的照片(本地Python)
使用AskUserQuestion:
问题:您是否希望删除严重曝光不佳的照片(接近全黑或全白)?
选项:
- - 是,使用默认阈值(亮度平均值 < 5% 或 > 95%)(推荐)
- 是,让我指定自定义阈值
- 否,保留所有照片
运行Python脚本检测并报告曝光不佳的照片,但暂不删除:
python
#!/usr/bin/env python3
使用Pillow检测接近全黑或全白的照片。
import sys
import os
from pathlib import Path
try:
from PIL import Image
import numpy as np
except ImportError:
os.system(pip install Pillow numpy -q)
from PIL import Image
import numpy as np
FOLDER = sys.argv[1]
LOW_THRESH = float(sys.argv[2]) if len(sys.argv) > 2 else 0.05 # < 5% = 接近全黑
HIGH_THRESH = float(sys.argv[3]) if len(sys.argv) > 3 else 0.95 # > 95% = 接近全白
PHOTO_EXTS = {.jpg, .jpeg, .png, .heic, .heif, .bmp,
.tiff, .tif, .webp, .gif}
bad = []
for f in sorted(Path(FOLDER).iterdir()):
if f.suffix.lower() not in PHOTO_EXTS:
continue
try:
with Image.open(f) as img:
# 转换为灰度图进行亮度分析
gray = img.convert(L)
arr = np.array(gray, dtype=np.float32) / 255.0
mean = float(arr.mean())
if mean < LOWTHRESH or mean > HIGHTHRESH:
label = 接近全黑 if mean < LOW_THRESH else 接近全白
bad.append((f.name, mean, label))
except Exception as e:
print(f跳过 {f.name}: {e}, file=sys.stderr)
if not bad:
print(未发现严重曝光不佳的照片。)
else:
print(f发现 {len(bad)} 张有问题的照片:)
for name, mean, label in bad:
print(f {label} 亮度={mean:.3f} {name})
运行:python3 /tmp/detectbadexposure.py $FOLDER 0.05 0.95
向用户显示列表,然后确认后再删除:
bash
rm $BAD_PHOTO
或
mkdir -p $FOLDER/
rejectedexposure
mv $BAD
PHOTO $FOLDER/rejected_exposure/
步骤4:检测模糊和连拍照片(本地Python)
对所有剩余照片运行全面的Python分析脚本。此步骤:
- 1. 为每张照片的清晰度评分(拉普拉斯方差)
- 检测连拍组(3秒内拍摄的照片或感知哈希几乎相同的照片)
python
#!/usr/bin/env python3
分析照片的模糊度和连拍分组。
import sys
import os
import json
from pathlib import Path
from datetime import datetime
try:
import cv2
import numpy as np
from PIL import Image
from PIL.ExifTags import TAGS
import imagehash
except ImportError:
os.system(pip install opencv-python-headless Pillow imagehash numpy -q)
import cv2
import numpy as np
from PIL import Image
from PIL.ExifTags import TAGS
import imagehash
FOLDER = sys.argv[1]
BLUR_THRESHOLD = float(sys.argv[2]) if len(sys.argv) > 2 else 80.0 # 拉普拉斯方差低于此值 = 模糊
BURST_SECONDS = int(sys.argv[3]) if len(sys.argv) > 3 else 3 # 拍摄间隔秒数 = 同一连拍
PHASH_DIST = int(sys.argv[4]) if len(sys.argv) > 4 else 8 # 感知哈希距离阈值
PHOTO_EXTS = {.jpg, .jpeg, .png, .heic, .heif, .bmp,
.tiff, .tif, .webp}
def getexifdatetime(img_path):
从EXIF中提取原始日期时间。
try:
with Image.open(img_path) as img:
exifdata = img.getexif()
if exif_data:
for tagid, value in exifdata.items():
tag = TAGS.get(tagid, tagid)
if tag == DateTimeOriginal:
return datetime.strptime(value, %Y:%m:%d %H:%M:%S)
except Exception:
pass
# 回退到文件修改时间
return datetime.fromtimestamp(Path(imgpath).stat().stmtime)
def laplaciansharpness(imgpath):
计算拉普拉斯方差作为清晰度评分。数值越高越清晰。
img = cv2.imread(str(img_path))
if img is None:
return 0.0
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
return float(cv2.Laplacian(gray, cv2.CV_64F).var())
收集所有照片
photos = []
for f in sorted(Path(FOLDER).iterdir()):
if f.suffix.lower() not in PHOTO_EXTS or f.name.startswith(.):
continue
photos.append(f)
print(f正在分析 {len(photos)} 张照片..., flush=True)
results = []
for i, f in enumerate(photos):
print(f [{i+1}/{len(photos)}] {f.name}, flush=True)
dt = getexifdatetime(f)
sharp = laplacian_sharpness(f)
try:
ph = imagehash.phash(Image.open(f))
except Exception:
ph = None
results.append