Token导航 LogoToken导航TokenDH.com
研究检索权限需确认clawhub未标认证来源可访问clear审计提醒

organise-photos整理照片

Agent Skill

用于辅助图像生成、图片编辑、视觉素材处理或图像模型工作流。它适合让 Agent 根据文本生成图片、处理背景、整理视觉提示词或调用相关图像工具。使用时需要确认输入图片、版权来源、输出格式和模型限制;涉及人物、品牌、商品或公开展示素材时,应额外核对授权、真实性和内容合规边界。

总安装

8,767

周安装

369

GitHub Stars

公开资料未说明

下载量

3,070
OpenClaw

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

MIT-0

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:organise-photos(整理照片)
来源仓库:https://github.com/lemondepat/organise-photos
安装命令:
openclaw skills install organise-photos
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 OpenClaw 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

ClawHubOpenClaw
openclaw skills install organise-photos

简介

organise-photos 自动清理无效图片文件并按编号整理照片至子文件夹中。

  • 适用于个人相册归档、批量图像预处理或存储空间优化任务。
  • 可过滤模糊连拍、删除曝光不良文件并排除非图片内容。
  • 操作前请备份原始素材以防误删重要数据。
  • 需注意版权归属问题,尤其涉及人物肖像或商业用途时应额外审核授权。

SKILL.md

name
organise-photos
description
Organize a photo folder by cleaning non-photo files, removing bad exposures, detecting blur and burst shots, and classifying photos into numbered subfolders using AI vision analysis.

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):

# List all files with sizes
ls -la "$FOLDER"

# Find photo files (common extensions)
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 non-photo files
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" \
\)

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"
mkdir -p "$FOLDER/_misc"
mv [non-photo files] "$FOLDER/_misc/"

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:

#!/usr/bin/env python3
"""Detect near-black or near-white photos using 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% = near-black
HIGH_THRESH = float(sys.argv[3]) if len(sys.argv) > 3 else 0.95  # > 95% = near-white

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:
            # Convert to grayscale for brightness analysis
            gray = img.convert('L')
            arr = np.array(gray, dtype=np.float32) / 255.0
            mean = float(arr.mean())
            if mean < LOW_THRESH or mean > HIGH_THRESH:
                label = "near-black" if mean < LOW_THRESH else "near-white"
                bad.append((f.name, mean, label))
    except Exception as e:
        print(f"Skipping {f.name}: {e}", file=sys.stderr)

if not bad:
    print("No severely bad exposure photos found.")
else:
    print(f"Found {len(bad)} problematic photo(s):")
    for name, mean, label in bad:
        print(f"  {label}  brightness={mean:.3f}  {name}")

Run: python3 /tmp/detect_bad_exposure.py "$FOLDER" 0.05 0.95

Show the list to user, then confirm before deleting:

rm "$BAD_PHOTO"
# or
mkdir -p "$FOLDER/_rejected_exposure"
mv "$BAD_PHOTO" "$FOLDER/_rejected_exposure/"

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)
  2. Detects burst groups (photos taken within 3 seconds of each other OR with near-identical perceptual hash)
#!/usr/bin/env python3
"""Analyze photos for blur and burst grouping."""
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  # Laplacian variance below this = blurry
BURST_SECONDS = int(sys.argv[3]) if len(sys.argv) > 3 else 3         # seconds between shots = same burst
PHASH_DIST = int(sys.argv[4]) if len(sys.argv) > 4 else 8            # perceptual hash distance threshold

PHOTO_EXTS = {'.jpg', '.jpeg', '.png', '.heic', '.heif', '.bmp',
              '.tiff', '.tif', '.webp'}

def get_exif_datetime(img_path):
    """Extract DateTimeOriginal from EXIF."""
    try:
        with Image.open(img_path) as img:
            exif_data = img._getexif()
            if exif_data:
                for tag_id, value in exif_data.items():
                    tag = TAGS.get(tag_id, tag_id)
                    if tag == 'DateTimeOriginal':
                        return datetime.strptime(value, "%Y:%m:%d %H:%M:%S")
    except Exception:
        pass
    # Fall back to file modification time
    return datetime.fromtimestamp(Path(img_path).stat().st_mtime)

def laplacian_sharpness(img_path):
    """Compute Laplacian variance as sharpness score. Higher = sharper."""
    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())

# Collect all photos
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"Analyzing {len(photos)} photos...", flush=True)

results = []
for i, f in enumerate(photos):
    print(f"  [{i+1}/{len(photos)}] {f.name}", flush=True)
    dt = get_exif_datetime(f)
    sharp = laplacian_sharpness(f)
    try:
        ph = imagehash.phash(Image.open(f))
    except Exception:
        ph = None
    results.append({
        "file": f.name,
        "path": str(f),
        "datetime": dt.isoformat(),
        "sharpness": sharp,
        "blurry": sharp < BLUR_THRESHOLD,
        "phash": str(ph) if ph else None,
    })

# Detect burst groups: same timestamp (within N seconds) OR similar phash
groups = []
used = set()
for i, r in enumerate(results):
    if i in used:
        continue
    group = [i]
    used.add(i)
    t1 = datetime.fromisoformat(r["datetime"])
    for j, r2 in enumerate(results):
        if j <= i or j in used:
            continue
        t2 = datetime.fromisoformat(r2["datetime"])
        time_close = abs((t2 - t1).total_seconds()) <= BURST_SECONDS
        hash_close = (r["phash"] and r2["phash"] and
                      imagehash.hex_to_hash(r["phash"]) - imagehash.hex_to_hash(r2["phash"]) <= PHASH_DIST)
        if time_close or hash_close:
            group.append(j)
            used.add(j)
    if len(group) > 1:
        groups.append(group)

# Mark burst membership
for g_idx, group in enumerate(groups):
    # Find sharpest in group
    best_idx = max(group, key=lambda i: results[i]["sharpness"])
    for idx in group:
        results[idx]["burst_group"] = g_idx + 1
        results[idx]["burst_best"] = (idx == best_idx)
        results[idx]["burst_size"] = len(group)

# Output JSON for further processing
output = {
    "photos": results,
    "burst_groups": len(groups),
    "blurry_count": sum(1 for r in results if r.get("blurry")),
    "blur_threshold": BLUR_THRESHOLD,
}
with open("/tmp/photo_analysis.json", "w") as fout:
    json.dump(output, fout, indent=2, ensure_ascii=False)

# Print summary
blurry = [r for r in results if r.get("blurry")]
burst_photos = [r for r in results if "burst_group" in r]
print(f"\
=== Analysis Complete ===")
print(f"Blurry photos (sharpness < {BLUR_THRESHOLD}): {len(blurry)}")
print(f"Burst groups: {len(groups)} group(s) ({len(burst_photos)} photos total)")
if blurry:
    print("\
Blurry photo list:")
    for r in blurry:
        print(f"  sharpness={r['sharpness']:.1f}  {r['file']}")
if groups:
    print(f"\
Burst group details:")
    for g_idx, group in enumerate(groups):
        print(f"  Group {g_idx+1} ({len(group)} photos):")
        for idx in group:
            r = results[idx]
            best_mark = "★ sharpest" if r.get("burst_best") else ""
            print(f"    {r['file']}  sharpness={r['sharpness']:.1f} {best_mark}")
print("\
Full analysis saved to /tmp/photo_analysis.json")

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 _rejected_blur subfolder"
  • "Keep them, do nothing"

Execute using /tmp/photo_analysis.json:

import json, shutil, os
from pathlib import Path

data = json.load(open("/tmp/photo_analysis.json"))
FOLDER = "PATH_TO_FOLDER"

for r in data["photos"]:
    if r.get("blurry"):
        src = Path(r["path"])
        # delete: src.unlink()
        # move: shutil.move(str(src), os.path.join(FOLDER, "_rejected_blur", src.name))

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 _burst_extras"
  • "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:

import json, shutil, os
from pathlib import Path

data = json.load(open("/tmp/photo_analysis.json"))

# Group photos by burst_group
groups = {}
for r in data["photos"]:
    g = r.get("burst_group")
    if g:
        groups.setdefault(g, []).append(r)

for g_idx, members in groups.items():
    for r in members:
        if not r.get("burst_best"):
            src = Path(r["path"])
            # delete: src.unlink()
            # or move to _burst_extras/

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)
  2. 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
  2. Sort by count (most photos first)
  3. Assign two-digit numbers: 01_, 02_, etc.

Show proposed structure (folder names in the user's language):

Proposed folder structure:
01_landscape   (45 photos)
02_portrait    (30 photos)
03_architecture (20 photos)

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:

mkdir -p "$FOLDER/01_landscape"
mv "$PHOTO" "$FOLDER/01_landscape/"

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, imagehash
  • Install: pip install Pillow numpy opencv-python-headless imagehash
  • 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_bad_exposure.py for RAW files

Temp Files

  • /tmp/photo_analysis.json — full analysis results; clean up after: rm /tmp/photo_analysis.json

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

适合场景

01

OpenClaw 用户查找和安装 Skill 时

02

用户想查找某类 Agent Skill 时

03

需要根据任务场景推荐可安装能力包时

04

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

补充不同宿主或平台的使用分布数据

能力 5

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

OpenClaw

96.78%
按下载量换算2,971

安全审计

VirusTotal

可疑

ClawScan

可疑

Static analysis

通过

权限和风险

权限需确认

当前来源未能明确判断权限范围,默认进入异常复核队列。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills