Birthday & anniversary photo collections

- profiles get an optional birthday (MM-DD); photos of that person taken on the
  date are also copied into Birthdays/<person>/<year>/
- app-wide anniversaries (label + MM-DD); any photo taken on the date is copied
  into Anniversaries/<label>/<year>/ (including faceless photos and videos)
- copy (not move) so normal person/date sorting is preserved
- CaptureDate gains day; new collection path builder; scanner skips the new folders
- UI: birthday input in profile create/edit + new Anniversaries manager

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 20:11:29 +09:00
parent d73e11f0fd
commit 9b044449a0
16 changed files with 287 additions and 27 deletions
+19
View File
@@ -19,6 +19,25 @@ export function buildTargetPath(
return join(outputRoot, folder, date.year, date.month, filename)
}
/** 폴더명으로 안전하지 않은 문자 제거 (라벨에 / : * 등이 들어오는 경우 대비) */
function safeFolder(name: string): string {
return name.replace(/[\\/:*?"<>|]/g, '_').trim() || '_'
}
/**
* 컬렉션(생일/기념일) 폴더 경로: <출력>/<카테고리>/<하위>/<연도>/<파일명>
* 예: 출력/Birthdays/Alex/2024/IMG_0001.jpg, 출력/Anniversaries/결혼기념일/2024/IMG_0001.jpg
*/
export function buildCollectionPath(
outputRoot: string,
category: string,
sub: string,
year: string,
sourceFile: string
): string {
return join(outputRoot, category, safeFolder(sub), year, basename(sourceFile))
}
/**
* 파일명 충돌 시 사용할 후보 경로를 생성 (name_1.ext, name_2.ext ...).
* @param index 1부터 시작하는 충돌 회피 인덱스