Skip to main content

macOS 사진 라이브러리에서 "주요 이벤트" 자동 추출 실패기

4 min read

AI 92 / Human 8

AI-drafted, edited

AI가 거의 다 쓰고, kimtoma가 일부 수정.

실험 설계와 결정은 사람이, 클러스터링 로직과 도구 통합은 AI가. 결과를 보고 폐기 결정도 사람이.

Photos.app에 12만 장 가까이 쌓여 있는 사진을 사이트의 /photos 메뉴로 옮기는 일을 어떻게 줄일까. 매일 찍는 일상 사진 중에서 "기억하고 싶은 이벤트"만 자동으로 골라낼 수 있다면 — 만 장 단위 큐레이션이 한 시간 단위로 줄어들 것이다. 이걸 한 번 만들어봤다. 그리고 한 시간 만에 폐기했다.

문제 정의

내 사이트의 /photos는 마크다운 frontmatter + 폴더 한 개당 이벤트 한 개 구조다. 한 이벤트는 보통 사진 5–20장 + 짧은 본문. 직접 큐레이션하면 한 이벤트당 10–15분.

2006년부터 쌓인 사진이 12만 장. 그중 2026년만 1,651장. 이걸 손으로 정리하면 며칠이 걸린다. "Photos.app이 이미 자동 클러스터링은 해주잖아? 그걸 가져다 쓰면 되지 않을까?" — 여기서 시작했다.

어떻게 만들었나

도구 셋업은 단순했다.

  • osxphotos — Photos 라이브러리에서 메타데이터 추출 (Python CLI)
  • sips — macOS 내장 도구. HEIC → JPEG 변환에 사용 (sharp의 libvips가 HEIC 미지원이라 우회)
  • sharp — JPEG → WebP + thumb + blur dataURL 파이프라인 (이미 사이트에서 쓰던 거)

흐름:

osxphotos query  →  Python 필터링 + 클러스터링  →  sips로 HEIC→JPG  →  sharp로 WebP  →  마크다운 frontmatter 생성

핵심은 "어떤 사진 군집을 이벤트로 칠 것인가" — 클러스터링 휴리스틱이다. 처음 시도는 이랬다:

  1. 시간 갭이 18시간 미만인 연속 사진들을 한 클러스터로 묶기 (간단)
  2. 클러스터당 점수 매기기:
    • 사진 수 (많을수록 가산)
    • 며칠짜리 (다일이면 ×1.6, 4일+면 ×1.3 추가)
    • 비-홈 위치 비율 (집 밖일수록 ×1.5)
    • 얼굴 검출 수 (사람이 등장하는 사진은 사회적 이벤트)
    • 명명된 앨범에 속한 사진 비율 (사용자가 이미 큐레이션한 흔적)
  3. 위 점수 상위 8개 클러스터를 후보 이벤트로 추출
  4. 각 클러스터에서 Photos.app의 curation_score 기준 상위 10장만 사진으로 채택

place.ishome 플래그 같은 게 이미 Photos에 박혀 있어서 "집 vs 바깥" 구분도 거저 얻을 수 있었다. Photos.app이 ML로 이미 다 분류해놓은 셈이라, 코드 작성은 250줄 정도로 끝났다.

무엇이 잘못됐나

테스트 출력은 그럴듯해 보였다.

#    175 photos · 05-05~05-10  · 개봉1동       score= 733.8
#    166 photos · 04-22~04-25  · 서울특별시     score= 604.0
#    124 photos · 04-04         · 성남시        score= 418.6
...

위치 다양, 다일 이벤트 포함, 점수 분포도 합리적. "오, 잘 잡혔네." 하고 88장을 import해서 사이트에 띄웠다.

그런데 결과를 보니 — 8개 다 일상이었다.

  • 5일짜리 "이벤트"는 그냥 어머니 댁에서 보낸 가족 시간
  • 4일짜리는 그냥 동네 출퇴근 + 평일 일상
  • 단일일 124장짜리는 아이 데리고 카페 갔던 평범한 토요일

진짜 기억하고 싶은 이벤트들 — 누군가의 생일, 의미 있는 외출, 특정 사건 — 은 사진 수가 오히려 적었다. 30~40장이면 "정성껏 찍은 한 자리"이고, 100장 넘어가면 "그냥 폰을 계속 들고 다닌 시간"이 더 많다.

왜 그랬을까

휴리스틱은 "양"을 신호로 본다. 사람은 "맥락"을 본다.

내가 휴리스틱에 넣은 시그널들:

  • 사진 수가 많다 → 중요한 이벤트일 것이다 ❌
  • 며칠짜리다 → 여행이거나 이벤트일 것이다 ❌
  • 집 밖이다 → 외출이니까 의미가 있을 것이다 ❌
  • 얼굴이 많다 → 사람이 있는 자리는 사회적 이벤트일 것이다 ❌

각각은 합리적으로 보이지만 — 결국 측정한 것은 "얼마나 많이 찍었나"이지 "왜 찍었나"가 아니었다. 일상에서 폰을 자주 꺼내는 시간대(아이와 보낸 주말, 부모님 댁 방문)는 어떤 이벤트보다도 사진 수가 많다. 진짜 이벤트는 종종 정신없어서 사진을 적게 찍는다.

Photos.app이 이미 갖고 있는 "Memories" 클러스터는 더 잘했을지도 모른다. 그건 단순 시간 갭이 아니라 ML로 장면/사물/얼굴/위치를 종합 평가한 결과니까. 그런데 osxphotos의 query API에는 Memories를 직접 노출하는 옵션이 없었다 — 한 단계 더 깊이 들어가야 한다.

의외의 깨달음

실험 자체는 1시간이 안 걸렸다 — 청소까지 포함해서. 코드 짜는 데 30분, 결과 보고 폐기 결정 5분, 정리 10분.

이게 가장 큰 수확이다. 휴리스틱은 빨리 만들고 빨리 버리는 게 핵심. 처음부터 "잘 만들어야지" 하고 ML 모델 학습이나 사람 라벨링 데이터를 모았다면 며칠 깎였을 것이다. 250줄짜리 휴리스틱은 1시간으로 충분히 명확한 답("안 됨")을 줬다.

또 하나 — 자동화가 안 되는 영역을 명확히 했다. 사진을 "왜 찍었나"는 메타데이터에 안 박혀 있다. 사진 시각 분석으로 추론 가능한 신호도 아니다. 그건 결국 사람의 기억에만 있는 정보다. 큐레이션은 위임 가능하지 않다는 걸 확인했다.

다음 시도

지금은 손으로 정리한다. 8장의 클러스터를 보면서 진짜 이벤트 후보를 추리는 건 5초도 안 걸린다. 인간 눈은 그걸 무료로 한다.

다음에 자동화를 다시 시도한다면 — Photos.app의 Memories를 직접 읽거나 (sqlite 직조회), 명시적 사용자 신호(즐겨찾기, 별도 앨범에 직접 분류, 캡션 작성)만 쓰는 방향이 맞을 것 같다. "사진을 많이 찍은 시간"은 신호가 아니다.

코드는 폐기했지만 메모만 남긴다. git logauto-2026-* 자취도 없이 깨끗이.