3e73967c7b
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>
130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
// 전 프로세스 공유 상수
|
|
|
|
/** 처리 대상 이미지 확장자 (소문자, 점 포함) */
|
|
export const SUPPORTED_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.webp'] as const
|
|
|
|
/** 처리 대상 영상 확장자 (소문자, 점 포함) */
|
|
export const SUPPORTED_VIDEO_EXTENSIONS = [
|
|
'.mp4',
|
|
'.mov',
|
|
'.avi',
|
|
'.mkv',
|
|
'.webm',
|
|
'.m4v'
|
|
] as const
|
|
|
|
/** 미검출/인식실패 사진이 들어가는 폴더명 (언어 중립 — 영어 모드에서 한글 노출 방지) */
|
|
export const UNMATCHED_FOLDER = 'Unsorted'
|
|
|
|
/** 영상 파일이 들어가는 폴더명 (얼굴인식 없이 날짜 기준 이동) */
|
|
export const MOVIE_FOLDER = 'Movie'
|
|
|
|
/** 로컬 참조 이미지를 UI 창에 안전하게 표시하기 위한 커스텀 프로토콜 스킴 */
|
|
export const MEDIA_SCHEME = 'photoai-media'
|
|
|
|
/** 로그 폴더명 (출력 루트 하위) */
|
|
export const LOG_FOLDER = '_PhotoAI_logs'
|
|
|
|
/** 프로필 영속화 파일명 (userData 하위) */
|
|
export const PROFILE_STORE_FILE = 'profiles.json'
|
|
|
|
/** 앱 설정 영속화 파일명 (userData 하위) */
|
|
export const SETTINGS_FILE = 'settings.json'
|
|
|
|
/** 프리셋(인물 라이브러리) 영속화 파일명 (userData 하위, 로컬 전용) */
|
|
export const PRESET_STORE_FILE = 'presets.json'
|
|
|
|
/** 최대 프로필 인원 (PRD) */
|
|
export const MAX_PROFILES = 3
|
|
|
|
/** 최대 프리셋(저장 인물) 수 */
|
|
export const MAX_PRESETS = 5
|
|
|
|
/** 기본 잡 옵션 */
|
|
export const DEFAULT_JOB_OPTIONS = {
|
|
matchThreshold: 0.5,
|
|
concurrency: 3,
|
|
detector: 'ssd' as const
|
|
}
|
|
|
|
/** 추론 시 이미지 장변 최대 픽셀 (다운스케일 기준) */
|
|
export const MAX_IMAGE_DIMENSION = 1024
|
|
|
|
/** 썸네일 장변 픽셀 */
|
|
export const THUMBNAIL_SIZE = 256
|
|
|
|
/** 품질 분석 시 이미지 장변 픽셀 (초점/노출/얼굴 계산용) */
|
|
export const ANALYZE_SIZE = 512
|
|
|
|
/** 품질 판정 기본 임계값 (Phase 1) */
|
|
export const QUALITY_THRESHOLDS = {
|
|
/** 라플라시안 분산이 이 값 미만이면 흐림 (512px 기준) */
|
|
focus: 60,
|
|
/** 노출 점수(0~1)가 이 값 미만이면 노출 불량 */
|
|
exposure: 0.35,
|
|
/** EAR(눈 종횡비)이 이 값 미만이면 눈 감음 */
|
|
eyes: 0.18
|
|
}
|
|
|
|
/** IPC 채널명 */
|
|
export const IPC = {
|
|
// UI → Main (invoke)
|
|
PROFILES_LIST: 'profiles:list',
|
|
PROFILES_UPSERT: 'profiles:upsert',
|
|
PROFILES_REMOVE: 'profiles:remove',
|
|
PROFILES_ADD_REFERENCE: 'profiles:addReference',
|
|
PROFILES_ADD_REFERENCE_DATA: 'profiles:addReferenceData',
|
|
PROFILES_REMOVE_REFERENCE: 'profiles:removeReference',
|
|
PROFILES_APPLY_PRESET: 'profiles:applyPreset',
|
|
// 프리셋(인물 라이브러리)
|
|
PRESETS_LIST: 'presets:list',
|
|
PRESETS_SAVE_FROM: 'presets:saveFrom',
|
|
PRESETS_REMOVE: 'presets:remove',
|
|
DIALOG_PICK_SOURCE: 'dialog:pickSource',
|
|
DIALOG_PICK_OUTPUT: 'dialog:pickOutput',
|
|
DIALOG_PICK_IMAGES: 'dialog:pickImages',
|
|
JOB_RUN: 'job:run',
|
|
JOB_CANCEL: 'job:cancel',
|
|
// 설정
|
|
SETTINGS_GET: 'settings:get',
|
|
SETTINGS_SET: 'settings:set',
|
|
SETTINGS_CHANGED: 'settings:changed',
|
|
// 라이브러리 / 색인 (Phase 0)
|
|
LIBRARY_LIST: 'library:list',
|
|
LIBRARY_ADD: 'library:add',
|
|
LIBRARY_REMOVE: 'library:remove',
|
|
INDEX_RUN: 'index:run',
|
|
INDEX_CANCEL: 'index:cancel',
|
|
INDEX_PROGRESS: 'index:progress',
|
|
INDEX_DONE: 'index:done',
|
|
INDEX_ASSETS: 'index:assets',
|
|
INDEX_ASSET_IDS: 'index:assetIds',
|
|
INDEX_FACETS: 'index:facets',
|
|
INDEX_EXPORT: 'index:export',
|
|
INDEX_SET_RATING: 'index:setRating',
|
|
INDEX_SET_LABEL: 'index:setLabel',
|
|
// 검색 (Phase 2)
|
|
SEARCH_BUILD: 'search:build',
|
|
SEARCH_CANCEL: 'search:cancel',
|
|
SEARCH_STATUS: 'search:status',
|
|
SEARCH_QUERY: 'search:query',
|
|
SEARCH_PROGRESS: 'search:progress',
|
|
SEARCH_DONE: 'search:done',
|
|
// 그룹화 / 자가정화 (Phase 3)
|
|
GROUPS_BUILD: 'groups:build',
|
|
GROUPS_TRASH: 'groups:trash',
|
|
// 지도 / 연관 탐색 (Phase C)
|
|
MAP_ASSETS: 'map:assets',
|
|
MAP_RELATED: 'map:related',
|
|
// Main → UI (send)
|
|
JOB_PROGRESS: 'job:progress',
|
|
JOB_FILE_PROCESSED: 'job:fileProcessed',
|
|
JOB_DONE: 'job:done',
|
|
JOB_ERROR: 'job:error',
|
|
// Main ↔ Inference
|
|
INFER_READY: 'infer:ready',
|
|
INFER_DETECT: 'infer:detect',
|
|
INFER_DESCRIBE: 'infer:describe',
|
|
INFER_INIT: 'infer:init'
|
|
} as const
|