Add video sorting, reference thumbnails, theme/i18n, menu, DnD/paste, presets

Feature work on top of the initial organizer:

- Videos: .mp4/.mov/.avi/.mkv/.webm/.m4v sorted into output/Movie/YYYY/MM
- Profiles: reference-image thumbnail cards via a secure photoai-media:// protocol
  (serves only registered reference images); per-thumbnail delete; add via
  click, drag & drop, or paste (Ctrl+V) using webUtils.getPathForFile + a
  byte-based addReferenceData path for clipboard images
- Presets: local person library (max 5) saved to userData; one-click apply into
  active profiles; reusing stored descriptors (no recompute)
- Theme: dark/light with dark default (Tailwind class strategy)
- i18n: ko/en table-based localization; first-run onboarding to pick
  language + theme; English-neutral "Unsorted" folder (was [미정])
- App menu: File/Edit/View/Window/Help, localized; Help opens a detailed,
  theme-aware user guide window
- UI: History block scrolls internally (no whole-window scroll);
  threshold/concurrency tooltips; generic example name
- Settings persisted to userData/settings.json; menu + renderer kept in sync
- Docs: NextGen Photo AI feasibility review + Phase 0/1 roadmap

All typecheck/tests (12) /build green; boot smoke verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 15:40:44 +09:00
parent 8a8c10248c
commit 6dce580846
38 changed files with 1916 additions and 212 deletions
+15 -2
View File
@@ -12,12 +12,25 @@
| 3 | EXIF 촬영날짜 누락 시 | **파일 수정일(mtime)로 대체**하여 YYYY/MM 추출 | 대부분의 사진을 연/월 구조에 편입 가능. |
| 4 | 다중 인물 사진의 Move 1순위 | **프로필 등록 순서** (1번 인물=이동, 2·3번=복사) | PRD 명세 "첫 번째 프로필 기준 이동 후 복사"와 일치. |
## 1-1. 영상 파일 처리 (2026-06-01 추가)
| 항목 | 결정 |
|------|------|
| 대상 확장자 | `.mp4 .mov .avi .mkv .webm .m4v` |
| 처리 방식 | **얼굴인식 없이** 날짜 기준으로 **이동만** |
| 대상 폴더 | 출력 루트 하위 **`Movie/YYYY/MM/`** |
| 날짜 기준 | 파일 수정일(mtime). 영상은 EXIF 미적용 → mtime 직행 |
| 재처리 방지 | `Movie` 폴더는 스캔 제외 디렉터리에 포함 |
| 향후(비고) | "영상에 누가 나오나"가 필요하면 ffmpeg 프레임 샘플링으로 인물 매칭(phase 2) |
> 근거: 영상은 프레임이 많아 정지 이미지 엔진으로 바로 매칭 불가. v1은 날짜 기준 분리로 안전·신속 처리하고, 사진 정리 결과를 어지럽히지 않도록 전용 `Movie` 폴더로 격리.
## 2. 기본 가정 (Claude 기본값, 이의 없으면 채택)
- **이동(Move) 구현 방식**: `복사 → 무결성 검증 → 원본 삭제` 순서로 수행 (Atomic 보장, 데이터 무결성 0 Error).
- **유사도 임계값**: face-api Euclidean distance `< 0.5`를 매칭으로 간주 (튜닝 가능 파라미터로 노출).
- **Reference 등록**: 인물당 다수 사진 등록 허용 → 평균 descriptor 사용으로 정확도 향상 (KPI ≥98% 대응).
- **미검출/인식 실패 처리**: `[미정]/YYYY/MM/` 으로 이동 (삭제 금지, Scenario 03).
- **미검출/인식 실패 처리**: `Unsorted/YYYY/MM/` 으로 이동 (삭제 금지, Scenario 03). 폴더명은 언어 중립을 위해 영문 `Unsorted` 사용(구 `[미정]`).
- **대상 확장자**: `.jpg .jpeg .png .webp` 자동 감지. (`.heic`는 향후 확장 검토.)
- **프로필 최대 인원**: 3명 (PRD 명세).
@@ -26,7 +39,7 @@
```
<출력루트>/
<프로필명>/YYYY/MM/... # 인물 매칭 사진
[미정]/YYYY/MM/... # 미검출·인식실패 사진
Unsorted/YYYY/MM/... # 미검출·인식실패 사진
```
## 4. KPI / 비기능 요구
+180
View File
@@ -0,0 +1,180 @@
# NextGen Photo AI — 확장 기획 타당성 검토 & 로드맵
> 상태: **검토(Review) / 계획 초안** · 작성: 2026-06-01
> 대상 기획: "NextGen Photo AI — 지능형 사진 관리 솔루션"
> 현재 베이스: [ARCHITECTURE.md](./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 ~150~350MB + 런타임). 로컬·오프라인 정책상 동봉 또는 최초 실행 시 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** (네이티브) — 인덱스 DB. electron-builder의 `@electron/rebuild`로 ABI 재빌드(현재 빌드 파이프라인이 이미 수행).
- **썸네일은 네이티브(sharp) 없이** AI Worker(렌더러)의 canvas로 생성 → 바이트를 Main이 캐시. 네이티브 의존 최소화.
- 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: better-sqlite3 도입 + `indexDb` 스키마/마이그레이션 + 빌드(ABI 재빌드) 검증
- [ ] Phase 0-b: contentHash 해셔 + 라이브러리 폴더 지정 UI + `indexer` 워크/재개 + 진행률 IPC
- [ ] Phase 0-c: AI Worker에 썸네일 생성 + 캐시 + 그리드 표시(빈 품질로 우선)
- [ ] Phase 1-a: `qualityEngine` 초점/노출 점수 → DB 저장
- [ ] Phase 1-b: 감은 눈(EAR, face-api 랜드마크) 점수 → flag 산출
- [ ] Phase 1-c: CullingView(후보/제외 그리드, 점수, 임계값 설정, 오버라이드)
- [ ] Phase 1-d: (옵션) 제외 사진 내보내기/이동 액션
- [ ] 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 토대)**부터 착수.