9b044449a0
- 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>
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
import { join, extname, basename } from 'node:path'
|
|
import type { CaptureDate } from '@shared/types'
|
|
import { UNMATCHED_FOLDER } from '@shared/constants'
|
|
|
|
/**
|
|
* 인물/미정 + 연/월 기준의 대상 디렉터리 경로를 생성한다.
|
|
* 실제 파일명 충돌 해소는 fileOps에서 수행 (여기서는 디렉터리 + 원본 파일명까지).
|
|
*
|
|
* @param who 인물 폴더명, 또는 미검출이면 null → [미정]
|
|
*/
|
|
export function buildTargetPath(
|
|
outputRoot: string,
|
|
who: string | null,
|
|
date: CaptureDate,
|
|
sourceFile: string
|
|
): string {
|
|
const folder = who ?? UNMATCHED_FOLDER
|
|
const filename = basename(sourceFile)
|
|
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부터 시작하는 충돌 회피 인덱스
|
|
*/
|
|
export function withCollisionSuffix(targetPath: string, index: number): string {
|
|
const dir = targetPath.slice(0, targetPath.length - basename(targetPath).length)
|
|
const ext = extname(targetPath)
|
|
const stem = basename(targetPath, ext)
|
|
return join(dir, `${stem}_${index}${ext}`)
|
|
}
|