게시물 목록 카드 썸네일 생성 추가
This commit is contained in:
98
scripts/backfill-post-thumbnails.js
Normal file
98
scripts/backfill-post-thumbnails.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import { mkdir, readdir, stat } from 'node:fs/promises'
|
||||
import { extname, join } from 'node:path'
|
||||
import sharp from 'sharp'
|
||||
import {
|
||||
POST_THUMBNAIL_HEIGHT,
|
||||
POST_THUMBNAIL_QUALITY,
|
||||
POST_THUMBNAIL_WIDTH,
|
||||
getPostThumbnailDirectoryPath,
|
||||
getPostThumbnailPathForFile,
|
||||
isPostThumbnailSource
|
||||
} from '../server/utils/post-thumbnail-image.js'
|
||||
|
||||
const uploadRoot = join(process.cwd(), 'public', 'uploads', 'posts')
|
||||
|
||||
/**
|
||||
* 게시물 업로드 이미지 파일 목록을 수집한다.
|
||||
* @param {string} directoryPath - 탐색 디렉터리
|
||||
* @returns {Promise<string[]>} 이미지 파일 경로 목록
|
||||
*/
|
||||
const collectPostImages = async (directoryPath) => {
|
||||
let entries = []
|
||||
|
||||
try {
|
||||
entries = await readdir(directoryPath, { withFileTypes: true })
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
|
||||
const files = []
|
||||
|
||||
for (const entry of entries) {
|
||||
const entryPath = join(directoryPath, entry.name)
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name === 'thumbs') {
|
||||
continue
|
||||
}
|
||||
|
||||
files.push(...await collectPostImages(entryPath))
|
||||
continue
|
||||
}
|
||||
|
||||
if (entry.isFile() && isPostThumbnailSource('', entry.name)) {
|
||||
files.push(entryPath)
|
||||
}
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
/**
|
||||
* 게시물 이미지 카드 썸네일을 생성한다.
|
||||
* @param {string} imagePath - 원본 이미지 경로
|
||||
* @returns {Promise<boolean>} 새로 생성했는지 여부
|
||||
*/
|
||||
const createPostThumbnail = async (imagePath) => {
|
||||
const thumbnailPath = getPostThumbnailPathForFile(imagePath)
|
||||
|
||||
try {
|
||||
await stat(thumbnailPath)
|
||||
return false
|
||||
} catch {
|
||||
await mkdir(getPostThumbnailDirectoryPath(imagePath), { recursive: true })
|
||||
}
|
||||
|
||||
await sharp(imagePath)
|
||||
.rotate()
|
||||
.resize({
|
||||
width: POST_THUMBNAIL_WIDTH,
|
||||
height: POST_THUMBNAIL_HEIGHT,
|
||||
fit: 'cover',
|
||||
position: 'centre',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({ quality: POST_THUMBNAIL_QUALITY })
|
||||
.toFile(thumbnailPath)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const images = await collectPostImages(uploadRoot)
|
||||
let createdCount = 0
|
||||
let skippedCount = 0
|
||||
|
||||
for (const imagePath of images) {
|
||||
if (!isPostThumbnailSource('', imagePath) || extname(imagePath).toLowerCase() === '.gif') {
|
||||
skippedCount += 1
|
||||
continue
|
||||
}
|
||||
|
||||
if (await createPostThumbnail(imagePath)) {
|
||||
createdCount += 1
|
||||
} else {
|
||||
skippedCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout.write(`게시물 카드 썸네일 생성 완료: 생성 ${createdCount}개, 건너뜀 ${skippedCount}개\n`)
|
||||
Reference in New Issue
Block a user