CommunityImagemgithub.com

kylefritz/photo-skill

Encontre, examine, avalie e organize fotos na biblioteca do Apple Photos (ou qualquer pasta de imagens) no Mac.

O que é photo-skill?

Este skill permite que agentes de IA interajam com a biblioteca de fotos do Apple Photos no Mac, ou com pastas comuns de imagens. Ele combina consultas SQL rápidas e o utilitário osxphotos para filtrar grandes bibliotecas (por data, álbum, pessoa, local, etc.) com um índice de embeddings on-device para pesquisa por conteúdo visual (por exemplo, 'encontre uma foto de um portão torii vermelho') e identificação de quase-duplicatas. O fluxo de trabalho principal delega a análise visual a subagentes: após reduzir o conjunto de candidatos, o skill gera 'contact sheets' (grades de miniaturas) que subagentes examinam, atribuindo um veredito a cada imagem, validado por um script separado. O skill também oferece suporte à manipulação segura da biblioteca (importar, definir datas, ocultar/favoritar) apenas sob solicitação, ao download de originais do iCloud e ao gerenciamento de um índice de embeddings noturno. Ele prioriza privacidade e segurança, nunca escrevendo diretamente na biblioteca e trabalhando em snapshots APFS para evitar corrupção.

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/kylefritz/photo-skill/tree/HEAD

Installed? Explore more Imagem skills: steipete/songsee, affaan-m/frontend-design, mattpocock/improve-codebase-architecture · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O que photo-skill faz?

Os scripts ficam em scripts/ ao lado deste arquivo; escreva como $S abaixo, mas escreva o caminho absoluto em cada comando, porque as variáveis de shell não persistem entre chamadas Bash. A biblioteca é ~/Pictures/Photos Library.photoslibrary a menos que PHOTOS_LIBRARY diga o contrário.

A ideia central: o código ranqueia e busca, o modelo olha e julga. SQL, osxphotos e o índice de embeddings reduzem uma biblioteca de 100 mil fotos a algumas centenas de candidatos; contact sheets as colocam diante de você como grades rotuladas; você (ou subagentes) julga cada miniatura e escreve um veredito por id; um script verifica se cada id recebeu exatamente uma resposta.

Primeira vez em um Mac

Execute sh $S/setup.sh você mesmo (adicione --mcp para o servidor MCP opcional, --index para o trabalho de índice noturno). Ele verifica uv, sqlite3, as Ferramentas de Linha de Comando e a biblioteca, aquece osxphotos e Pillow, e prova o Acesso Total ao Disco tirando um snapshot. O Acesso Total ao Disco é a única coisa que só uma pessoa pode conceder: Ajustes do Sistema → Privacidade e Segurança → Acesso Total ao Disco → ative o aplicativo em que o Claude executa (Terminal, Ghostty, o aplicativo Claude...), depois saia e reabra.

Regras

  • Somente leitura, a menos que o proprietário peça uma alteração nesta conversa (importar, data, ocultar, favoritar, álbum). Somente leitura também descarta osxphotos query --add-to-album e osxphotos import, batch-edit, timewarp, push-exif, sync, add-locations.
  • Nunca abra o Photos.sqlite ao vivo, nem somente leitura: SQLite escreve seu arquivo -shm. Consulte um clone APFS feito por sh $S/snapshot.sh <dir> (instantâneo, gratuito). Nunca escreva dentro do .photoslibrary.
  • Baixar originais do iCloud é permitido. Com "Otimizar Armazenamento do Mac", a maioria dos originais está apenas no iCloud (em uma biblioteca de ~100 mil itens, 1,4% estavam no disco), mas cada item tem uma prévia local. As prévias são boas para contact sheets e decisões sim/não, pequenas demais para ler texto ou rostos; baixe o original com photokit download (~1,7 s cada).
  • Fotos são privadas. Trabalhe no scratchpad da sessão ou em uma pasta ignorada pelo git, nunca as comite e não publique as fotos de alguém (uma página web, um PR, um upload) a menos que eles peçam. Remova EXIF (GPS!) de qualquer coisa que saia da máquina: re-salvar pelo Pillow faz isso.

Escolha a ferramenta

NecessidadeUseVelocidade
Contagens, datas, álbuns, pessoas, lixeira/ocultos, caixas GPS, pontuações da Applesqlite3 no snapshot (reference.md tem as consultas)instantâneo
O que está na foto ("um portão torii vermelho"), mais como estas, quase-duplicatasphoto_index.sh search / like / query (abaixo)~15 s por chamada
Rótulos da própria Apple, legenda IA, nomes de lugares, pessoas nomeadas, pontuação estéticaphoto_meta.py uma vez → filtre o JSONL30 s + ms/item
Lugar, rótulo, texto em foto, raio GPS a partir de um processo residenteMCP apple-photos opcional (setup.sh --mcp). Sempre passe library30-40 s na primeira chamada, depois <1 s
Os mesmos filtros do shell, metadados JSONuvx --from osxphotos osxphotos query --library "$HOME/Pictures/Photos Library.photoslibrary" --mute ... --json~30 s por chamada
Muitas consultas osxphotosum script: uv run --no-project --with osxphotos, carregue osxphotos.PhotosDB(dbfile=LIB) uma vez30 s uma vez
Olhar fotosids → uuid_sheets.sh → leia as planilhassegundos
Arquivos em resolução totalphotokit download (reutiliza um arquivo já no disco)~1,7 s cada do iCloud
Uma pasta simples de imagens, não Apple Photossheets.py DIR, e photo_index.sh build DIR
Alterar a bibliotecaimport.py, set_dates.py, set_flags.swift (abaixo)

Sempre forneça o caminho da biblioteca para osxphotos e para o MCP (--library / "library" / PhotosDB(dbfile=...)). Sem ele, eles procuram a "última biblioteca aberta" nas preferências do Fotos dentro de seu contêiner do aplicativo; o macOS bloqueia essa leitura atrás de um prompt "acessar dados de outros aplicativos" que nunca aparece em um terminal, e a chamada trava para sempre com 0% de CPU. Apontar --library para o snapshot também funciona, mas então os rótulos estão faltando e os caminhos dos arquivos estão errados.

Quais itens contam

  • ZTRASHEDSTATE = 0 sempre. ZKIND = 0 fotos, 1 vídeos.
  • ZSAVEDASSETTYPE: 6 = a biblioteca iCloud do proprietário (o celular dele), 3 = importado, 4 = foto de outra pessoa em um álbum compartilhado, 12 = Compartilhado com Você do Mensagens. "Fotos que tirei" significa 6 ou 3. Itens compartilhados aparecem em intervalos de data, então mencione se você os incluir.
  • Itens ZHIDDEN = 1 estão nas contagens a menos que filtrados; mencione-os se aparecerem.
  • Datas: >= 'YYYY-MM-DD' e < 'próximo dia' em datetime(ZDATECREATED+978307200,'unixepoch','localtime'). between '…-20' and '…-22' perde a maior parte do dia 22. Mesmo dia todos os anos: strftime('%m-%d', …) = '10-31'. 'localtime' é o fuso deste Mac; adicione ZADDITIONALASSETATTRIBUTES.ZTIMEZONEOFFSET para o fuso onde foi tirada.
  • Nenhuma localização é -180.0, então um filtro GPS silenciosamente descarta esses; diga quantos foram deixados de fora.
  • Nenhum rosto detectado: not exists (select 1 from ZDETECTEDFACE f where f.ZASSETFORFACE = a.Z_PK). Ele perde multidões distantes e nucas, então ainda olhe antes de publicar algo.

Olhando fotos

  1. sh $S/snapshot.sh <scratch>/db
  2. Pipe a consulta de id diretamente no construtor de planilhas. Nunca imprima listas de ids na conversa:
    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
    

Isso escreve grades 6×5 de miniaturas de 236 px rotuladas 0001, 0002… (os ids são globais, não por planilha, então as miniaturas de duas planilhas não podem ser confundidas), um manifest.json (id → arquivo, mais qualquer coisa ignorada e o motivo) e missing.txt. Ele usa o original quando está no disco, senão a maior prévia local, e também procura em pastas de álbuns compartilhados e Mensagens. Reporte um missing.txt não vazio: fotos nunca desaparecem silenciosamente. Para uma pasta: uv run --no-project --with pillow python -I $S/sheets.py OUT DIR (--cols, --rows, --tile para redimensionar; uma planilha deve ficar abaixo de ~2.500 px no lado maior).

  • Poucas planilhas: leia você mesmo.
  • Muitas planilhas: distribua para subagentes, cada um recebendo um intervalo de planilhas. Use um modelo bom em visão (em testes, o menor modelo era muito tolerante). O prompt deve exigir uma linha JSONL por id de miniatura ({"id": "0007", "verdict": ...}) e dizer ao subagente para ir miniatura por miniatura e ler as imagens ele mesmo, não escrever código. Depois python3 -I $S/check_verdicts.py manifest.json out/*.jsonl: ele falha em ids ausentes, duplicados ou inventados e sinaliza planilhas onde toda miniatura recebeu a mesma resposta, o que geralmente significa que o subagente passou por cima; olhe essa planilha você mesmo.
  • Para qualquer coisa que vá alterar arquivos, execute duas passagens e aja apenas onde elas concordam, depois olhe cada miniatura que você está prestes a alterar.
  • Registre o modelo, o número de agentes e os tokens aproximados no README do projeto.

Pesquisa pelo que está na foto

Um índice privado mantém um embedding por foto (SigLIP 2 por padrão), construído a partir de prévias locais, então nada é baixado do iCloud. Ele fica em ~/Library/Application Support/photo-skill/index (PHOTO_INDEX_DIR), nunca no git. Medido: ~28 fotos/s de uma biblioteca do Fotos em um M4 (cerca de uma hora para 100k, mais um download de modelo de ~400 MB uma vez), ~70/s de uma pasta de JPEGs.

sh $S/photo_index.sh status
sh $S/photo_index.sh build                          # Apple Photos; adiciona apenas o que é novo, retomável
sh $S/photo_index.sh build ~/Pictures/scans         # ou qualquer pasta (use seu próprio PHOTO_INDEX_DIR)
sh $S/photo_index.sh search "um portão torii vermelho" 60 | cut -f1 | sh $S/uuid_sheets.sh <scratch>/hits
sh $S/photo_index.sh like <scratch>/examples.txt 60                 # mais próximo de qualquer id de exemplo
sh $S/photo_index.sh query --text "uma tigela de ramen" --text "sopa de macarrão" --minus "um cardápio" --within <scratch>/trip.txt --top 40

A saída é id<TAB>score, melhores primeiro. O índice ranqueia; você julga. Sempre leia a planilha: uma consulta por "uma máquina de venda automática" também retorna geladeiras de loja de conveniência e máquinas de bilhetes.

  • Pontuações. Os cossenos texto→foto do SigLIP são pequenos (0,10–0,20 para uma boa correspondência); apenas a ordem importa, então nunca os limite. Foto→foto com o modelo padrão: ≥ 0,97 mesma foto, ≥ 0,95 mesmo momento (rajadas, retomadas), ~0,90 mesmo lugar em uma foto diferente, ~0,85 o mesmo tipo de cena. Remeça estes após alterar o modelo.
  • Filtre primeiro, depois ranqueie. Datas, lugares, álbuns, pessoas e compartilhado vs. próprio vêm do SQL no snapshot; escreva esses ids em um arquivo e passe --within. Isso é melhor do que pedir ao embedding por "fotos de Kyoto em novembro".
  • Formule como uma legenda ("uma foto de uma tigela de ramen" ou "uma tigela de ramen"), não palavras-chave. Várias formulações --text são calculadas em média; --minus afasta um conceito.
  • Combine com metadados da Apple. photo_meta.py escreve um meta.jsonl com os rótulos da própria Apple, legenda IA, nome do lugar, pessoas nomeadas e pontuação estética. É forte em onde e quem (nomes de lugares como "… Santuário"), fraco em como parece (legendas são genéricas: "um grupo de pessoas em pé na frente de um prédio"). Filtre e depois faça união ou interseção com uma pesquisa do índice. Seu campo OCR (text) retornou vazio no macOS 26 em testes; para texto em fotos, use a pesquisa de texto do MCP ou baixe o original e leia você mesmo.
  • Quase-duplicatas: like com um exemplo e leia as pontuações; pares ≥ 0,95 são retomadas. Nunca desduplique por nome+tamanho (cópias são renomeadas e recodificadas).
  • Alterar o modelo (PHOTO_INDEX_MODEL, qualquer checkpoint SigLIP/SigLIP 2): o índice registra seu modelo em model.txt e se recusa a misturar dois; construa o novo em seu próprio PHOTO_INDEX_DIR. README.md compara modelos.

Atualizações noturnas (opcional): sh $S/photo_index.sh install-nightly (ou setup.sh --index) instala um job launchd que adiciona as fotos do dia às 3:30. Jobs launchd não recebem o Acesso Total ao Disco do terminal, então o job inicia um pequeno lançador (photo-index-runner, construído a partir de index_runner.c) que precisa de sua própria concessão: o comando abre Ajustes do Sistema em Acesso Total ao Disco e revela o arquivo no Finder; a pessoa clica +, seleciona-o, ativa. Verifique com launchctl kickstart gui/$(id -u)/local.photo-skill.index, depois photo_index.sh status. "Operação não permitida" no log significa que a concessão está faltando. Reexecutar install-nightly reescreve o job, mas mantém o lançador, porque reconstruí-lo perde a concessão.

Auxiliar PhotoKit

Compile uma vez por sessão: swiftc -O $S/photokit.swift -o <scratch>/photokit. Sua primeira execução pede acesso ao Fotos em uma caixa de diálogo do macOS que a pessoa no Mac deve permitir. <scratch>/photokit download OUT_DIR < uuids.txt recebe UUIDs simples, permite iCloud, imprime cada caminho e reporta falhas em stderr. Itens do Mensagens (Compartilhado com Você) são invisíveis tanto para o PhotoKit quanto para o osxphotos: a prévia deles é tudo o que existe.

  • import ALBUM FILE... pula toda a scriptagem do aplicativo Fotos (sem limite por lançamento, sem caixa de diálogo de erro para ficar travado) e imprime file<TAB>localIdentifier; registre isso. Levemente testado: para milhares de arquivos, faça algumas centenas primeiro e verifique-os com um snapshot novo.
  • date não foi testado; use set_dates.py até que tenha sido executado.

Alterando a biblioteca (apenas quando solicitado)

  1. Copie para fora da biblioteca (clones cp -c são gratuitos) e corrija-as primeiro: exiftool para datas e Orientation, Rotation de vídeo.
  2. python3 -I $S/import.py --album NOME --log LOG.jsonl [--dry-run] ARQUIVOS... (ou caminhos no stdin): 50 arquivos por chamada, reinicia o Fotos a cada 1.500, para no primeiro lote curto, retomável a partir de LOG. Se parar, o Fotos tem uma caixa de diálogo de erro aberta: peça à pessoa para fechá-la e execute novamente.
  3. Datas para arquivos sem nenhuma incorporada: python3 -I $S/set_dates.py < id<TAB>YYYY-MM-DD HH:MM:SS.
  4. Oculto/Favorito (AppleScript não pode ocultar): swift $S/set_flags.swift < id<TAB>ocultar<TAB>fav. Um ativo oculto desaparece das buscas, então "ainda visível: 0" significa que funcionou.
  5. Verifique com um snapshot novo: cada id registrado presente, nenhum na lixeira, contagens de álbum corretas.

Fotos em um filme

Para um slideshow, montagem ou qualquer vídeo feito de fotos, use o skill video-skill (github.com/kylefritz/video-skill; se não estiver instalado, ofereça-se para cloná-lo em ~/.claude/skills/video-skill). A parte deste skill: encontrar e julgar as fotos, photokit download os originais (prévias são pequenas demais para 1080p), re-salvá-los sem EXIF e olhar cada um em tamanho real para pessoas antes de colocá-lo em um filme. O video-skill lê vídeo da mesma forma que este skill lê fotos, então suas planilhas de quadro funcionam com check_verdicts.py.

Pegadinhas

  • As datas das fotos são segundos desde 2001-01-01: adicione 978307200. Compare strings datetime(...); comparar um número a strftime('%s', ...) (texto) silenciosamente não corresponde a nada.
  • ZCLOUDLOCALSTATE não significa que o original está no disco; verifique o arquivo.
  • Câmeras antigas sem sensor de orientação armazenavam retratos de lado; corrija com a tag EXIF Orientation (sem perdas).
  • Coordenadas de rosto no Fotos têm origem no canto inferior esquerdo: y = (1 - center_y) * height.
  • Abrir um placeholder do Dropbox ou iCloud Drive trava para sempre. Verifique st_blocks > 0 primeiro.
  • Um -wal desatualizado ao lado de um Photos.sqlite novo é reproduzido sobre ele; snapshot.sh limpa os arquivos antigos primeiro por esse motivo.
  • Scratchpads /private/tmp são limpos entre sessões; mantenha trabalhos longos em uma pasta de projeto.

Habilidades Relacionadas