Was macht photo-skill?
Die Skripte liegen in scripts/ neben dieser Datei; nachfolgend als $S bezeichnet, aber schreibe den absoluten Pfad in jedem Befehl aus, da Shell-Variablen nicht zwischen Bash-Aufrufen übertragen werden. Die Bibliothek ist ~/Pictures/Photos Library.photoslibrary, sofern PHOTOS_LIBRARY nichts anderes angibt.
Die Kernidee: Code bewertet und ruft ab, das Modell schaut und beurteilt. SQL, osxphotos und der Embedding-Index reduzieren eine Bibliothek mit 100.000 Fotos auf ein paar hundert Kandidaten; Kontaktbögen legen sie dir als beschriftete Raster vor; du (oder Unteragenten) beurteilen jede Kachel und schreiben ein Urteil pro ID; ein Skript prüft, dass jede ID genau eine Antwort erhalten hat.
Erstmalig auf einem Mac
Führe selbst sh $S/setup.sh aus (füge --mcp für den optionalen MCP-Server hinzu, --index für den nächtlichen Index-Job). Es prüft uv, sqlite3, die Command Line Tools und die Bibliothek, wärmt osxphotos und Pillow auf und beweist den Vollzugriff auf die Festplatte durch die Erstellung eines Schnappschusses. Der Vollzugriff auf die Festplatte ist das Einzige, was nur eine Person gewähren kann: Systemeinstellungen → Datenschutz & Sicherheit → Vollzugriff auf die Festplatte → schalte die App ein, in der Claude läuft (Terminal, Ghostty, die Claude-App...), beende sie dann und öffne sie erneut.
Regeln
- Nur lesend, es sei denn, der Besitzer bittet in diesem Gespräch um eine Änderung (importieren, Datum, ausblenden, favorisieren, Album). Schreibgeschützt schließt auch
osxphotos query --add-to-albumund osxphotosimport,batch-edit,timewarp,push-exif,sync,add-locationsaus. - Öffne niemals die live
Photos.sqlite, nicht einmal schreibgeschützt: SQLite schreibt seine-shm-Datei. Führe eine Abfrage auf einem APFS-Klon durch, der mitsh $S/snapshot.sh <verz>erstellt wurde (sofort, kostenlos). Schreibe niemals in das.photoslibrary. - Das Herunterladen von Originalen aus iCloud ist in Ordnung. Bei „Mac-Speicher optimieren“ sind die meisten Originale nur in iCloud (in einer Bibliothek mit ~100.000 Elementen waren 1,4 % auf der Festplatte), aber jedes Element hat eine lokale Vorschau. Vorschauen sind für Kontaktbögen und Ja/Nein-Entscheidungen geeignet, aber zu klein zum Lesen von Text oder Gesichtern; lade das Original mit
photokit downloadherunter (~1,7 s pro Stück). - Fotos sind privat. Arbeite im Sitzungs-Scratchpad oder in einem gitignorierten Ordner, übertrage sie niemals und veröffentliche keine Fotos von Personen (Webseite, PR, Upload), es sei denn, sie bitten darum. Entferne EXIF (GPS!) von allem, was den Rechner verlässt: erneutes Speichern durch Pillow erledigt das.
Wähle das Werkzeug
| Bedarf | Verwende | Geschwindigkeit |
|---|---|---|
| Zählungen, Daten, Alben, Personen, Papierkorb/ausgeblendet, GPS-Boxen, Apple-Bewertungen | sqlite3 auf dem Schnappschuss (reference.md enthält die Abfragen) | sofort |
| Was ist auf dem Bild („ein rotes Torii-Tor“), mehr davon, nahe Duplikate | photo_index.sh search / like / query (unten) | ~15 s pro Aufruf |
| Apples eigene Labels, KI-Bildunterschrift, Ortsnamen, benannte Personen, ästhetische Punktzahl | photo_meta.py einmal → grep das JSONL | 30 s + ms/Element |
| Ort, Label, Text-im-Foto, GPS-Radius von einem residenten Prozess | optionaler apple-photos MCP (setup.sh --mcp). Übergib immer library | 30-40 s erster Aufruf, dann <1 s |
| Dieselben Filter von einer Shell, JSON-Metadaten | uvx --from osxphotos osxphotos query --library "$HOME/Pictures/Photos Library.photoslibrary" --mute ... --json | ~30 s pro Aufruf |
| Viele osxphotos-Abfragen | ein Skript: uv run --no-project --with osxphotos, osxphotos.PhotosDB(dbfile=LIB) einmal geladen | 30 s einmal |
| Fotos ansehen | IDs → uuid_sheets.sh → die Blätter lesen | Sekunden |
| Dateien in voller Auflösung | photokit download (verwendet eine bereits auf der Festplatte befindliche Datei erneut) | ~1,7 s pro Stück aus iCloud |
| Ein einfacher Bildordner, nicht Apple Photos | sheets.py VERZ, und photo_index.sh build VERZ | |
| Die Bibliothek ändern | import.py, set_dates.py, set_flags.swift (unten) |
Gib osxphotos und dem MCP immer den Bibliothekspfad an (--library / "library" / PhotosDB(dbfile=...)). Ohne diesen suchen sie die „zuletzt geöffnete Bibliothek“ in den Einstellungen von Photos innerhalb des App-Containers; macOS blockiert diesen Lesevorgang hinter einer Aufforderung „auf Daten von anderen Apps zugreifen“, die in einem Terminal nie erscheint, und der Aufruf hängt bei 0 % CPU. Wenn --library auf den Schnappschuss zeigt, funktioniert es auch, aber dann fehlen Labels und die Dateipfade sind falsch.
Welche Elemente zählen
ZTRASHEDSTATE = 0immer.ZKIND = 0Fotos,1Videos.ZSAVEDASSETTYPE: 6 = die iCloud-Bibliothek des Besitzers (sein Telefon), 3 = importiert, 4 = das Foto einer anderen Person in einem geteilten Album, 12 = „Mit dir geteilt“ aus Nachrichten. „Fotos, die ich gemacht habe“ bedeutet 6 oder 3. Geteilte Elemente erscheinen in Datumsbereichen, erwähne es also, wenn du sie einschließt.ZHIDDEN = 1Elemente sind in Zählungen enthalten, es sei denn, sie werden herausgefiltert; erwähne sie, wenn welche auftauchen.- Daten:
>= 'YYYY-MM-DD'und< 'nächster Tag'aufdatetime(ZDATECREATED+978307200,'unixepoch','localtime').between '…-20' and '…-22'lässt den größten Teil des 22. weg. Gleicher Tag jedes Jahr:strftime('%m-%d', …) = '10-31'.'localtime'ist die Zone dieses Macs; fügeZADDITIONALASSETATTRIBUTES.ZTIMEZONEOFFSETfür die Zone hinzu, in der das Foto aufgenommen wurde. - Kein Standort ist
-180.0, daher verwirft ein GPS-Filter diese stillschweigend; gib an, wie viele ausgelassen wurden. - Kein erkanntes Gesicht:
not exists (select 1 from ZDETECTEDFACE f where f.ZASSETFORFACE = a.Z_PK). Es übersieht entfernte Menschenmengen und Hinterköpfe, schau also trotzdem nach, bevor du etwas veröffentlichst.
Fotos ansehen
sh $S/snapshot.sh <scratch>/db- Leite die ID-Abfrage direkt in den Blattersteller. Gib niemals ID-Listen in die Unterhaltung aus:
sqlite3 <scratch>/db/Photos.sqlite "select ZUUID from ZASSET where ZTRASHEDSTATE=0 and ZKIND=0 and datetime(ZDATECREATED+978307200,'unixepoch','localtime') >= '2024-10-31' and datetime(ZDATECREATED+978307200,'unixepoch','localtime') < '2024-11-01'" | sh $S/uuid_sheets.sh <scratch>/sheets
Das schreibt 6×5-Raster aus 236-Px-Kacheln, beschriftet mit 0001, 0002… (IDs sind global, nicht pro Blatt, daher können Kacheln von zwei Blättern nicht verwechselt werden), eine manifest.json (ID → Datei, plus was übersprungen wurde und warum) und missing.txt. Es verwendet das Original, wenn es auf der Festplatte ist, andernfalls die größte lokale Vorschau, und sucht auch in Ordnern für geteilte Alben und Nachrichten. Melde ein nicht-leeres missing.txt: Fotos verschwinden nie stillschweigend. Für einen Ordner: uv run --no-project --with pillow python -I $S/sheets.py OUT VERZ (--cols, --rows, --tile zum Ändern der Größe; ein Blatt sollte auf seiner langen Seite unter ~2.500 px bleiben).
- Ein paar Blätter: lies sie selbst.
- Viele Blätter: verteile sie an Unteragenten, denen jeweils ein Bereich von Blättern zugewiesen wird. Verwende ein Modell, das gut im Sehen ist (in Tests war das kleinste Modell zu nachsichtig). Der Prompt muss eine JSONL-Zeile pro Kachel-ID erfordern (
{"id": "0007", "verdict": ...}) und dem Unteragenten sagen, er solle Kachel für Kachel vorgehen und die Bilder selbst lesen, keinen Code schreiben. Führe dannpython3 -I $S/check_verdicts.py manifest.json out/*.jsonlaus: es schlägt fehl bei fehlenden, doppelten oder erfundenen IDs und markiert Blätter, bei denen jede Kachel dieselbe Antwort erhalten hat, was normalerweise bedeutet, dass der Unteragent überflogen hat; sieh dir dieses Blatt selbst an. - Für alles, was Dateien ändert, führe zwei Durchläufe durch und handle nur dort, wo sie übereinstimmen, dann sieh dir jede Kachel an, die du ändern willst.
- Notiere das Modell, die Anzahl der Agenten und die ungefähren Token im README des Projekts.
Suche nach dem, was auf dem Bild ist
Ein privater Index enthält ein Embedding pro Foto (standardmäßig SigLIP 2), erstellt aus lokalen Vorschauen, sodass nichts aus iCloud heruntergeladen wird. Er befindet sich in ~/Library/Application Support/photo-skill/index (PHOTO_INDEX_DIR), niemals in git. Gemessen: ~28 Fotos/s aus einer Photos-Bibliothek auf einem M4 (etwa eine Stunde pro 100.000, plus einmaliger Download eines ~400 MB großen Modells), ~70/s aus einem Ordner mit JPEGs.
sh $S/photo_index.sh status
sh $S/photo_index.sh build # Apple Photos; fügt nur Neues hinzu, fortsetzbar
sh $S/photo_index.sh build ~/Pictures/scans # oder ein beliebiger Ordner (verwendet eigenes PHOTO_INDEX_DIR)
sh $S/photo_index.sh search "a red torii gate" 60 | cut -f1 | sh $S/uuid_sheets.sh <scratch>/hits
sh $S/photo_index.sh like <scratch>/examples.txt 60 # nächste zu einer beliebigen Beispiel-ID
sh $S/photo_index.sh query --text "a bowl of ramen" --text "noodle soup" --minus "a menu" --within <scratch>/trip.txt --top 40
Ausgabe ist id<TAB>score, beste zuerst. Der Index bewertet; du beurteilst. Lies immer das Blatt: Eine Abfrage nach „einem Getränkeautomaten“ gibt auch Kühlschränke und Fahrkartenautomaten im Convenience-Store zurück.
- Bewertungen. Text→Foto-Cosinus-Werte von SigLIP sind klein (0,10–0,20 für eine gute Übereinstimmung); nur die Reihenfolge zählt, setze also niemals einen Schwellenwert. Foto→Foto mit dem Standardmodell: ≥ 0,97 gleiche Aufnahme, ≥ 0,95 gleicher Moment (Serien, Wiederholungen), ~0,90 gleicher Ort in einer anderen Aufnahme, ~0,85 die gleiche Art von Szene. Miss diese Werte nach einem Modellwechsel neu.
- Erst filtern, dann bewerten. Daten, Orte, Alben, Personen und geteilt vs. eigen stammen aus SQL auf dem Schnappschuss; schreibe diese IDs in eine Datei und übergib
--within. Das ist besser, als das Embedding nach „Fotos aus Kyoto im November“ zu fragen. - Formuliere wie eine Bildunterschrift („ein Foto einer Schüssel Ramen“ oder „eine Schüssel Ramen“), nicht wie Schlüsselwörter. Mehrere
--text-Formulierungen werden gemittelt;--minusdrückt ein Konzept weg. - Kombiniere mit Apples Metadaten.
photo_meta.pyschreibtmeta.jsonlmit Apples eigenen Labels, KI-Bildunterschrift, Ortsnamen, benannten Personen und ästhetischer Punktzahl. Es ist stark bei wo und wer (Ortsnamen wie „…-Schrein“), schwach bei wie es aussieht (Bildunterschriften sind allgemein: „eine Gruppe von Menschen, die vor einem Gebäude stehen“). Durchsuche es, vereinige es dann oder schneide es mit einer Indexsuche. Sein OCR-Feld (text) war in Tests auf macOS 26 leer; für Text in Fotos verwende die Textsuche des MCP oder lade das Original herunter und lies es selbst. - Nahe Duplikate:
likemit einem Beispiel und lies die Bewertungen; ≥ 0,95 Paare sind Wiederholungen. Dedupliziere niemals nach Name+Größe (Kopien werden umbenannt und neu codiert). - Modell wechseln (
PHOTO_INDEX_MODEL, jeder SigLIP/SigLIP 2-Checkpoint): Der Index zeichnet sein Modell inmodel.txtauf und weigert sich, zwei zu mischen; erstelle das neue in seinem eigenenPHOTO_INDEX_DIR.README.mdvergleicht Modelle.
Nächtliche Updates (optional): sh $S/photo_index.sh install-nightly (oder setup.sh --index) installiert einen launchd-Job, der die Fotos des Tages um 3:30 Uhr hinzufügt. launchd-Jobs erhalten nicht den Vollzugriff auf die Festplatte des Terminals, daher startet der Job einen winzigen Starter (photo-index-runner, erstellt aus index_runner.c), der seine eigene Berechtigung benötigt: Der Befehl öffnet die Systemeinstellungen bei Vollzugriff auf die Festplatte und zeigt die Datei im Finder an; die Person klickt auf +, wählt sie aus und schaltet sie ein. Überprüfe mit launchctl kickstart gui/$(id -u)/local.photo-skill.index, dann photo_index.sh status. „Operation not permitted“ im Log bedeutet, dass die Berechtigung fehlt. Die erneute Ausführung von install-nightly überschreibt den Job, behält aber den Starter, da ein Neuerstellen die Berechtigung verliert.
PhotoKit-Helfer
Einmal pro Sitzung kompilieren: swiftc -O $S/photokit.swift -o <scratch>/photokit. Sein erster Lauf fordert den Zugriff auf Fotos in einem macOS-Dialog an, den die Person am Mac erlauben muss. <scratch>/photokit download OUT_DIR < uuids.txt akzeptiert reine UUIDs, erlaubt iCloud, gibt jeden Pfad aus und meldet Fehler auf stderr. Nachrichten-Elemente („Mit dir geteilt“) sind für PhotoKit und osxphotos gleichermaßen unsichtbar: Ihre Vorschau ist alles, was es gibt.
import ALBUM FILE...überspringt die Photos.app-Skripting vollständig (keine Obergrenze pro Aufruf, kein Fehlerdialog, der hängen bleibt) und gibtfile<TAB>localIdentifieraus; protokolliere das. Leicht getestet: Bei Tausenden von Dateien zuerst ein paar hundert machen und anhand eines neuen Schnappschusses überprüfen.dateist ungetestet; verwendeset_dates.py, bis es ausgeführt wurde.
Die Bibliothek ändern (nur auf Anfrage)
- Kopiere Stufen außerhalb der Bibliothek (
cp -c-Klone sind kostenlos) und korrigiere sie zuerst: exiftool für Daten undOrientation, VideoRotation. python3 -I $S/import.py --album NAME --log LOG.jsonl [--dry-run] FILES...(oder Pfade auf stdin): 50 Dateien pro Aufruf, starte Photos alle 1.500 neu, stoppe bei der ersten kurzen Charge, fortsetzbar ab LOG. Wenn es anhält, hat Photos einen offenen Fehlerdialog: Bitte die Person, ihn zu schließen, dann erneut ausführen.- Daten für Dateien ohne eingebettete Daten:
python3 -I $S/set_dates.py < id<TAB>YYYY-MM-DD HH:MM:SS. - Ausgeblendet/Favorit (AppleScript kann nicht ausblenden):
swift $S/set_flags.swift < id<TAB>hide<TAB>fav. Ein ausgeblendetes Asset verschwindet aus Abfragen, also bedeutet „noch sichtbar: 0“, dass es funktioniert hat. - Überprüfe anhand eines neuen Schnappschusses: Jede protokollierte ID vorhanden, keine gelöscht, Album-Anzahl stimmt.
Fotos in einen Film
Für eine Diashow, Montage oder jedes Video, das aus Fotos erstellt wird, verwende das video-skill-Skill (github.com/kylefritz/video-skill; falls es nicht installiert ist, biete an, es in ~/.claude/skills/video-skill zu klonen). Der Teil dieses Skills: Finde und beurteile die Aufnahmen, lade die Originale mit photokit download herunter (Vorschauen sind für 1080p zu klein), speichere sie ohne EXIF neu und betrachte jedes in voller Größe auf Personen, bevor es in einen Film kommt. video-skill liest Videos genauso, wie dieses Skill Fotos liest, daher funktionieren seine Frame-Blätter mit check_verdicts.py.
Fallstricke
- Fotos-Daten sind Sekunden seit dem 01.01.2001: addiere
978307200. Vergleichedatetime(...)-Zeichenketten; der Vergleich einer Zahl mitstrftime('%s', ...)(Text) stimmt stillschweigend mit nichts überein. ZCLOUDLOCALSTATEbedeutet nicht, dass das Original auf der Festplatte ist; überprüfe die Datei.- Alte Kameras ohne Orientierungssensor speicherten Porträts seitwärts; korrigiere mit dem EXIF
Orientation-Tag (verlustfrei). - Gesichtskoordinaten in Photos haben ihren Ursprung unten links:
y = (1 - center_y) * height. - Das Öffnen eines Dropbox- oder iCloud Drive-Platzhalters blockiert für immer. Überprüfe zuerst
st_blocks > 0. - Eine veraltete
-walneben einer frischenPhotos.sqlitewird darüber abgespielt;snapshot.shlöscht aus diesem Grund zuerst die alten Dateien. /private/tmp-Scratchpads werden zwischen Sitzungen gelöscht; behalte länger laufende Arbeiten in einem Projektordner.