Files
photoai/docs/NEXTGEN_REVIEW.md
T
koriweb 3e73967c7b darktable-inspired reskin + metadata/collections, map, easy mode, select/export
UI overhaul to a darktable tone-and-manner and a set of features adapted from
darktable's proven patterns (reimplemented in our Electron/TS stack; no GPL code).

Design reskin:
- Dark neutral-gray palette + amber accent, flat/squared corners, no card shadows,
  compact darktable-style top bar (logo + pipe-separated view tabs), denser 15px base
- Done via design tokens (Tailwind slate/brand/radius/shadow remap) — minimal churn

Metadata & collections (Phase A/B):
- exifr now captures GPS + camera; asset table ALTER-migrated (gpsLat/gpsLon/camera,
  metaVersion backfill on re-index)
- Collection facet bar (year timeline / camera / color-label) filters the grid

Map & relation finder (Phase C):
- Leaflet + online OSM map tab; geotagged photos as markers
- relationService: related photos by place (GPS<1km) + time (+/-2d) + CLIP similarity

Easy mode (Phase D):
- easyMode setting (menu / onboarding); scales the whole UI (rem) + bigger thumbnails
  + large icon nav with plain labels (4050 accessibility)

Library usability:
- Video thumbnails (representative frame capture in the inference worker)
- Media filter (All / Photos / Videos) to separate them
- Clearer culling labels ("Good shots" / "To cull") + explanation tooltip
- Multi-select tiles -> Export selected to a folder (copy, best-cut extraction) and
  Delete to Recycle Bin (shell.trashItem) behind a confirm dialog
- ONNX Runtime wasm bundled locally (offline) via copy-ort-wasm + asarUnpack

Docs: DARKTABLE_REVIEW (feasibility + roadmap A->D). All typecheck/tests/build green;
boot smoke verified each phase.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 19:22:19 +09:00

15 KiB

NextGen Photo AI — 확장 기획 타당성 검토 & 로드맵

상태: 검토(Review) / 계획 초안 · 작성: 2026-06-01 대상 기획: "NextGen Photo AI — 지능형 사진 관리 솔루션" 현재 베이스: ARCHITECTURE.md (Electron + face-api 폴더 정리기)


0. 한 줄 결론

기술적으로 전부 로컬(On-device)로 구현 가능합니다. 단, 이는 기능 추가가 아니라 제품의 성격 전환입니다: 현재 "폴더를 1회 스캔해 인물/날짜로 이동"하는 무상태(stateless) 정리기 → 사진을 지속적으로 색인(index)하고 검색·평가·그룹화하는 상태 기반 라이브러리(DAM). → 핵심은 새 기능들이 아니라 그 아래 깔릴 "라이브러리 인덱스" 기반(임베딩 + 메타 + 썸네일 DB) 입니다. 이것부터 만들어야 나머지가 전부 그 위에 올라갑니다.


1. 기능별 타당성 (로컬 구현 관점)

기능 핵심 기술 로컬 가능성 난이도 비고
[P1] AI 품질 평가 / Culling 초점=라플라시안 분산, 노출=히스토그램, 감은 눈=face-api 랜드마크(EAR), 미학=NIMA(ONNX) 높음 초점/노출/눈은 모델 없이 가능. 미학 점수만 ONNX 모델 필요
[P1] 자연어 검색 CLIP 이미지/텍스트 임베딩(transformers.js, ONNX, WebGPU) + 벡터 검색 가능 한국어 쿼리 처리 방식 결정 필요(§4-1)
[P1] 시각적 유사 검색 동일 CLIP 임베딩 코사인 유사도 높음 임베딩만 있으면 쉬움
[P2] 스마트 그룹화 임베딩 클러스터링(k-means/임계값) 높음 Phase 2 임베딩 재사용
[P2] 자가 정화(중복/저품질) 지각적 해시(pHash/dHash) + 품질 점수 높음 pHash는 순수 JS 구현 가능
(시나리오 A) RAW 파일 libraw/dcraw (WASM 또는 네이티브) ⚠️ 가능하나 무거움 범위 포함 여부 결정 필요(§4-2)

품질 평가/Culling이 모델 리스크가 가장 낮고 가치가 높음 → 1순위로 출시 권장.


2. 필요한 기술 스택 추가

영역 후보 메모
ML 런타임 transformers.js v3 (@huggingface/transformers) ONNX Runtime Web, WebGPU 가속 + WASM 폴백. 전부 로컬
임베딩 모델 CLIP ViT-B/32(영어, ~경량) 또는 다국어 CLIP(jina-clip-v2 등, 무거움) §4-1 결정에 따름
한국어 처리(옵션) opus-mt-ko-en(쿼리 번역, 소형) 영어 CLIP + 쿼리만 KO→EN 번역하는 절충안
미학 점수(옵션) NIMA류 ONNX 없으면 초점/노출/눈만으로 v1
인덱스 DB better-sqlite3 (메타+점수+pHash) + 임베딩(BLOB 또는 별도 바이너리) JSON은 수천 장에서 한계 → SQLite 필요
벡터 검색 초기 브루트포스 코사인(~5만 장까지 OK) → 대규모 시 sqlite-vec / hnswlib-wasm 단계적
썸네일 sharp(네이티브) 또는 canvas 리사이즈 + 디스크 캐시 그리드 UI 필수
지각 해시 dHash/aHash 순수 JS 중복 탐지
RAW(옵션) libraw-wasm / dcraw §4-2 결정

모델 동봉 시 앱 용량이 수백 MB 증가합니다(CLIP 150350MB + 런타임). 로컬·오프라인 정책상 동봉 또는 최초 실행 시 1회 다운로드 방식 결정 필요.


3. 아키텍처 변화 (가장 중요한 부분)

현재 3-프로세스(Main / UI / 추론창)에 2개 축을 추가:

추가 1) Library Index (영속 상태)
  SQLite DB: 파일별 { contentHash, path, exif, faces,
                      clipEmbedding(512d), qualityScores, pHash, thumbnailRef }
  - 파일은 contentHash로 식별 → 경로가 바뀌어도 추적, 중복 인식
  - "이동/복사" 정리기와 공존: 정리기도 이 인덱스를 활용

추가 2) AI Worker (백그라운드 색인기)
  - 기존 '숨김 추론창' 패턴 확장 또는 Web Worker(+WebGPU)
  - 신규/변경 파일을 배치로: CLIP 임베딩 + 품질 점수 + pHash 계산
  - 재개 가능(resumable), UI 프리징 0 (별 프로세스/워커)

새 데이터 흐름:

임포트/스캔 → (AI Worker) 임베딩·점수·해시 계산 → Index DB 저장
검색:   쿼리 → (번역?) → 텍스트 임베딩 → DB 벡터 코사인 Top-K → 결과 그리드
Culling: Index의 품질 점수로 필터 → 후보군/제외 뷰
그룹화: 임베딩 클러스터링 → 클러스터 뷰
정화:   pHash 근접쌍 + 저품질 → 정리 제안

비파괴(non-destructive) 색인이 핵심: 검색/평가/그룹화는 파일을 옮기지 않고 "제자리에서" 인덱싱합니다. 현재의 이동 기반 정리기와는 철학이 다르므로, 둘을 어떻게 공존시킬지(§4-3)가 설계 관건입니다.


4. 열린 질문 — 확정됨 (2026-06-01)

# 항목 결정
1 한국어 자연어 검색 영어 CLIP + 쿼리 KO→EN 번역(경량·빠름). Phase 2에서 정확도 실측
2 RAW 지원 차후(Phase 4). 우선 JPEG/PNG/WebP
3 라이브러리 모델 인덱스 기반 통합 — 비파괴 색인을 기본으로, 기존 이동/복사 정리기도 인덱스 활용
4 진행 범위 Phase 0 + Phase 1 먼저
5 모델 배포 (미정 — Phase 1은 모델 거의 불필요. Phase 2 진입 시 결정)
6 타깃 규모 초기 수천~수만 장 가정(브루트포스 벡터검색으로 충분), 대규모는 Phase 4

5. 단계별 로드맵 (권장 시퀀스)

각 Phase는 독립 출시 가능하도록 설계.

  • Phase 0 — 라이브러리 인덱스 기반 (필수 선행) SQLite 인덱스 + contentHash 식별 + 썸네일 캐시 + AI Worker 스캐폴드(재개 가능 배치). 화면 변화 없음, 토대만.
  • Phase 1 — [P1] 품질 평가 & 스마트 Culling (첫 출시 권장) 초점(라플라시안)·노출(히스토그램)·감은 눈(face-api 랜드마크) 점수 → '고품질 후보 / 제외' 뷰. 모델 리스크 최저, 가치 즉시.
  • Phase 2 — [P1] CLIP 임베딩 + 자연어/유사 검색 임베딩 색인 + 검색 UI(쿼리바·결과 그리드·"이 사진과 비슷한"). §4-1 결정 반영.
  • Phase 3 — [P2] 스마트 그룹화 + 자가 정화 임베딩 클러스터링 + pHash 중복/저품질 정리 제안. Phase 1·2 산출물 재사용.
  • Phase 4 — 옵션/결정 의존 RAW 지원, 미학(NIMA) 점수, 대규모용 ANN 인덱스(sqlite-vec/HNSW).

6. 리스크 & 솔직한 평가

  • 규모: 이 확장은 현재 앱과 비슷하거나 더 큰 작업량입니다(수 주 단위, 다중 Phase). "정리기"에서 "지능형 라이브러리"로의 제품 전환입니다.
  • 한국어 검색 품질: CLIP 계열은 영어 중심 → 번역/다국어 모델 선택에 따라 KPI(Search Success Rate)가 크게 좌우됨. 초기 POC로 실측 검증 필요.
  • 성능/하드웨어: 수천 장 임베딩은 GPU(WebGPU)에서도 수 분~수십 분. 저사양(WASM)에서는 훨씬 느림 → 백그라운드·재개·진행률 UX 필수.
  • 앱 용량/배포: 모델 동봉 시 수백 MB. 배포 방식 결정 필요.
  • RAW: 브라우저 환경 RAW 디코딩은 까다로움 → 범위 신중 결정.

7. 제안

Phase 0 + Phase 1(인덱스 기반 + 품질/Culling)을 1차 목표로 삼는 것을 권장합니다. 모델 리스크가 낮고, 자연어 검색(Phase 2)의 토대(인덱스·워커)를 그대로 재사용합니다. 자연어 검색은 §4-1 결정 후 소규모 POC로 한국어 정확도부터 실측하고 본 구현에 들어가는 것을 권합니다.


8. Phase 0 + Phase 1 상세 실행 계획 (확정 범위)

8.1 기술 추가 (이 단계 한정)

  • better-sqlite3(네이티브)sql.js(WASM SQLite)로 확정. 이유: 이 환경(Node 24 + Python 3.12)에서 네이티브 컴파일이 실패(distutils 제거)했고, 네이티브 모듈은 사용자 PC마다 ABI/빌드툴 문제가 재발한다. WASM은 빌드/재빌드가 전혀 없어 Windows+macOS 배포가 단순. 인메모리 DB를 userData/index.db로 export 영속화. (수천~수만 장 메타데이터 규모에 충분. 추후 필요 시 indexDb 추상화 뒤에서 네이티브로 교체 가능.)
  • 썸네일은 네이티브(sharp) 없이 AI Worker(렌더러)의 canvas로 생성 → 바이트를 Main이 캐시. 네이티브 의존 0.

Phase 0-a 완료(2026-06-01): indexDb(sql.js) + asset/quality 스키마 + 영속화. Electron 부팅 시 userData/index.db 생성 확인. typecheck/build/스모크 통과.

  • Phase 1 품질 점수는 모델 거의 불필요: 초점=라플라시안 분산, 노출=휘도 히스토그램, 감은 눈=face-api 랜드마크(EAR). face-api는 이미 추론창에 로드됨.

8.2 데이터 모델 (SQLite)

asset(
  id INTEGER PK, contentHash TEXT UNIQUE, path TEXT, ext TEXT,
  sizeBytes INT, mtime INT, width INT, height INT,
  exifYear TEXT, exifMonth TEXT, indexedAt INT
)
quality(
  assetId FK, focus REAL, exposure REAL, eyesOpen REAL,
  flag TEXT  -- 'candidate' | 'blurry' | 'eyesClosed' | 'badExposure'
)
-- Phase 2 예약: embedding(assetId, vec BLOB), phash(assetId, hash)
  • contentHash(파일 내용 해시)로 식별 → 경로가 바뀌어도 추적, 정확 중복 인식. (contentHash, mtime) 동일하면 재색인 스킵(재개 가능).
  • 썸네일: userData/thumbs/<contentHash>.webp.

8.3 프로세스/모듈 추가

Main
  indexDb.ts        SQLite 래퍼 (better-sqlite3)
  libraryStore.ts   색인 대상 라이브러리 폴더 관리(settings)
  indexer.ts        오케스트레이터: 라이브러리 워크 → 워커 디스패치 → DB upsert → 진행률
  cullingService.ts Phase1 결과 조회/뷰 데이터 제공

AI Worker (기존 숨김 추론창 확장 or 신규 'indexer' 창)
  qualityEngine.ts  초점/노출/EAR 점수 + 썸네일 생성 (canvas + face-api 재사용)

UI (신규 화면)
  LibraryView       라이브러리 폴더 지정 + 색인 진행률
  CullingView       '고품질 후보 / 제외(흐림·눈감음·노출)' 그리드 + 점수/오버라이드
  • 비파괴: 색인은 파일을 옮기지 않음. Culling 결과로 '제외'를 태그만 하고, 원하면 사용자가 별도 폴더로 내보내기(기존 fileOps 재사용, 선택).
  • 기존 정리기와 통합: 정리 잡도 인덱스(EXIF/얼굴)를 재사용하도록 점진 연결.

8.4 작업 순서 (체크리스트)

  • Phase 0-a 완료: indexDb(sql.js로 확정) 스키마/마이그레이션 + Electron 부팅 시 DB 생성 검증
  • Phase 0-b 완료: contentHash(샘플 sha1) + 라이브러리 폴더 지정 UI(라이브러리 탭) + indexer 워크/재개(경로·mtime 스킵)/진행률 IPC/배치 영속화/취소. 헤드리스 파이프라인 검증(중복 0) + 부팅 스모크 통과
  • Phase 0-c 완료: AI Worker(추론창) canvas로 썸네일(webp) 생성 → userData/thumbs/<hash>.webp 캐시(비파괴), 색인 시 자동 생성 + 원본 치수 저장, photoai-media://thumb 해시 기반 보안 제공, 라이브러리 탭 썸네일 그리드(페이지네이션). typecheck/build/부팅 스모크 통과
  • Phase 1-a 완료: qualityEngine 초점(라플라시안 분산)/노출(히스토그램 클리핑) 점수 → DB
  • Phase 1-b 완료: 감은 눈(EAR, face-api 랜드마크, Tiny 검출기) 점수 → classifyFlag로 종합 분류(candidate/blurry/eyesClosed/badExposure). 색인 시 infer:analyze로 썸네일+품질 1회 로드 통합
  • Phase 1-c (기본) 완료: 라이브러리 그리드에 컬링 필터(전체/고품질 후보/제외) + 품질 배지. 임계값 튜닝 UI · 사진별 수동 오버라이드는 후속으로 보류
  • Phase 1-d(옵션): 제외 사진 내보내기/이동 액션 (보류)

8.6 Phase 1 마무리 (임계값 튜닝 + 수동 오버라이드) — 완료(2026-06-01)

  • 품질 임계값(초점/노출/눈)을 설정에 저장 + 슬라이더 UI. 임계값 변경 시 저장된 원본 점수로 SQL CASE 실시간 재분류(재분석 없음)
  • 별점(0~5) + 색라벨(5색) 수동 메타(usermeta 테이블), 썸네일 호버 편집, 별점 최소 필터

8.7 Phase 2 (CLIP 자연어/유사 검색) — 기본 완료(2026-06-01)

  • @huggingface/transformers(WASM/WebGPU, 네이티브 빌드 0) 도입
  • CLIP(Xenova/clip-vit-base-patch32) 이미지/텍스트 임베딩 — 추론창 lazy-load
  • 한국어 쿼리 자동 번역(Xenova/opus-mt-ko-en) → 영어 CLIP
  • 임베딩 SQLite BLOB 저장 + "검색 색인 생성"(임베딩 배치, 진행률/취소) — 기본 색인과 분리
  • 브루트포스 코사인 검색 + 검색 탭(임베딩 상태/생성 + 검색바 + 결과 그리드)
  • typecheck/build/부팅 스모크 통과

⚠️ Phase 2 런타임 미검증 항목: CLIP/번역 모델은 최초 사용 시 HF Hub에서 다운로드(온라인 1회 필요, 이후 캐시). 한국어 검색 정확도 실측은 후속. 임베딩 생성은 이미지당 수백 ms(WASM) → 대량은 시간 소요(백그라운드).

8.8 Phase 2 마무리 — 완료(2026-06-01)

  • ONNX Runtime WASM 오프라인 동봉: transformers.js의 ort-wasm-simd-threaded.jsep.wasm(20.6MB)을 public/ort로 복사(scripts/copy-ort-wasm, postinstall/prebuild) → env.backends.onnx.wasm.wasmPaths를 로컬 경로로 지정(CDN 불필요). electron-builder asarUnpack 처리. (모델 가중치 자체는 여전히 최초 1회 다운로드)
  • 쿼리 템플릿: 텍스트 임베딩 시 "a photo of {query}" 템플릿 적용(CLIP 정확도 향상), 단일 스레드(numThreads=1)로 COEP 미보장 환경 대응

8.9 Phase 3 (스마트 그룹화 + 자가정화) — 완료(2026-06-01)

  • 그룹화: 임베딩 코사인 유사도 그리디 클러스터링(groupingService), 유사도 임계값 슬라이더, 그룹별 보관 추천(가장 선명) 자동 표시
  • 자가정화: 그룹의 보관 추천 외 항목을 선택 → OS 휴지통으로 이동(shell.trashItem, 복구 가능) + 인덱스에서 삭제(연관 메타/임베딩 cascade)
  • 그룹·정화 탭 신설(확인 다이얼로그 포함), typecheck/build/부팅 스모크 통과
  • 비고: 저품질 정화는 라이브러리 탭의 '제외 후보' 필터로 이미 접근 가능. 근접중복이 본 단계의 핵심.
  • i18n(ko/en) · 다크모드 · 검증(typecheck/test/build/스모크)

8.5 리스크 (이 단계)

  • better-sqlite3 ABI: Electron 버전별 재빌드 필요 → CI/빌드에서 electron-builder가 처리하나, dev 실행 시 electron-rebuild 1회 필요할 수 있음.
  • 대량 색인 UX: 수천 장 썸네일/점수 계산은 시간 소요 → 백그라운드·재개·진행률 필수(이미 설계 반영).
  • EAR 기반 눈감음은 휴리스틱 → 임계값 튜닝 및 오버라이드 UI로 보완.

본 계획 승인 시 **Phase 0-a(인덱스 DB 토대)**부터 착수.