mirror of
https://github.com/kremalicious/blog.git
synced 2024-06-16 09:33:13 +02:00
exif solution on each post during build
This commit is contained in:
parent
edea2b320a
commit
59334b90a1
|
@ -52,10 +52,11 @@ export const schemaPhotos = (image: ImageFunction) =>
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
...schemaShared,
|
...schemaShared,
|
||||||
image: image()
|
image: image(),
|
||||||
// .refine((img) => img.width >= 1040, {
|
// .refine((img) => img.width >= 1040, {
|
||||||
// message: 'Cover image must be at least 1040 pixels wide!'
|
// message: 'Cover image must be at least 1040 pixels wide!'
|
||||||
// })
|
// })
|
||||||
|
exif: z.object({}).optional()
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prebuild": "npm run create:icons && npm run create:redirects && npm run move:downloads && npm run create:exif",
|
"prebuild": "npm run create:icons && npm run create:redirects && npm run move:downloads",
|
||||||
"start": "npm run prebuild && astro dev --config .config/astro.config.mjs",
|
"start": "npm run prebuild && astro dev --config .config/astro.config.mjs",
|
||||||
"build": "npm run prebuild && astro build --config .config/astro.config.mjs",
|
"build": "npm run prebuild && astro build --config .config/astro.config.mjs",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
|
@ -24,7 +24,6 @@
|
||||||
"new": "ts-node --esm scripts/new/index.ts",
|
"new": "ts-node --esm scripts/new/index.ts",
|
||||||
"create:icons": "ts-node --esm scripts/create-icons/index.ts",
|
"create:icons": "ts-node --esm scripts/create-icons/index.ts",
|
||||||
"create:redirects": "ts-node --esm scripts/redirect-from.ts",
|
"create:redirects": "ts-node --esm scripts/redirect-from.ts",
|
||||||
"create:exif": "ts-node --esm scripts/create-exif/index.ts",
|
|
||||||
"move:downloads": "ts-node --esm scripts/move-downloads.ts"
|
"move:downloads": "ts-node --esm scripts/move-downloads.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
import fs from 'node:fs/promises'
|
|
||||||
import path from 'node:path'
|
|
||||||
import { read } from 'fast-exif'
|
|
||||||
import iptc from 'node-iptc'
|
|
||||||
import ora from 'ora'
|
|
||||||
import type { Exif, ExifFormatted } from './types.ts'
|
|
||||||
import { formatExif } from './format.ts'
|
|
||||||
|
|
||||||
const imageFolder = path.join(process.cwd(), 'content', 'photos')
|
|
||||||
const outputFilePath = '.config/exif.json'
|
|
||||||
|
|
||||||
const spinner = ora('Extracting EXIF metadata from all photos').start()
|
|
||||||
|
|
||||||
async function readOutExif(filePath: string): Promise<Exif | undefined> {
|
|
||||||
if (!filePath) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
// exif
|
|
||||||
const exifData = await read(filePath, true)
|
|
||||||
if (!exifData) return
|
|
||||||
|
|
||||||
// iptc
|
|
||||||
const file = await fs.readFile(filePath)
|
|
||||||
const iptcData = iptc(file)
|
|
||||||
|
|
||||||
// format before output
|
|
||||||
const exifDataFormatted = formatExif(exifData)
|
|
||||||
const imageId = path.basename(filePath, path.extname(filePath))
|
|
||||||
|
|
||||||
const exif = {
|
|
||||||
image: imageId,
|
|
||||||
exif: { ...exifDataFormatted } as ExifFormatted,
|
|
||||||
iptc: { ...iptcData }
|
|
||||||
}
|
|
||||||
|
|
||||||
return exif
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error(`${filePath}: ${error.message}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function processImages(folderPath: string): Promise<Exif[]> {
|
|
||||||
const allExif: Exif[] = []
|
|
||||||
|
|
||||||
try {
|
|
||||||
const files = await fs.readdir(folderPath, { recursive: true })
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const filePath = path.join(folderPath, file)
|
|
||||||
const stats = await fs.stat(filePath)
|
|
||||||
|
|
||||||
if (stats.isFile()) {
|
|
||||||
// Check if it's an image file based on its file extension
|
|
||||||
const fileExtension = path.extname(filePath).toLowerCase()
|
|
||||||
|
|
||||||
if (fileExtension === '.jpg' || fileExtension === '.jpeg') {
|
|
||||||
const exif = await readOutExif(filePath)
|
|
||||||
if (!exif) continue
|
|
||||||
allExif.push(exif)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error:', (err as Error).message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return allExif
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const allExif = await processImages(imageFolder)
|
|
||||||
const allExifJSON = JSON.stringify(allExif, null, 2)
|
|
||||||
|
|
||||||
// Write the redirects object to the output file
|
|
||||||
fs.writeFile(outputFilePath, allExifJSON, 'utf-8')
|
|
||||||
|
|
||||||
spinner.succeed(`Extracted EXIF data from ${allExif.length} photos`)
|
|
||||||
} catch (error: any) {
|
|
||||||
spinner.fail((error as Error).message)
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { getCollection, type CollectionEntry } from 'astro:content'
|
import { getCollection, type CollectionEntry } from 'astro:content'
|
||||||
import { slugifyAll } from './slugify'
|
import { slugifyAll } from './slugify'
|
||||||
|
import { readOutExif } from './exif'
|
||||||
|
|
||||||
export function getSortedPosts(
|
export function getSortedPosts(
|
||||||
posts: CollectionEntry<'articles' | 'links' | 'photos'>[]
|
posts: CollectionEntry<'articles' | 'links' | 'photos'>[]
|
||||||
|
@ -8,8 +9,8 @@ export function getSortedPosts(
|
||||||
.filter(({ data }) => !data.draft)
|
.filter(({ data }) => !data.draft)
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
Math.floor((b.data.date as Date).getTime() / 1000) -
|
Math.floor((b.data.date as Date)?.getTime() / 1000) -
|
||||||
Math.floor((a.data.date as Date).getTime() / 1000)
|
Math.floor((a.data.date as Date)?.getTime() / 1000)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,22 +28,27 @@ export async function loadAndFormatCollection(
|
||||||
) {
|
) {
|
||||||
const postsCollection = await getCollection(name)
|
const postsCollection = await getCollection(name)
|
||||||
|
|
||||||
postsCollection.forEach(
|
for await (const post of postsCollection) {
|
||||||
(post: CollectionEntry<'articles' | 'links' | 'photos'>) => {
|
// remove date from slug
|
||||||
// remove date from slug
|
const slug = post.slug.substring(11) as CollectionEntry<
|
||||||
const slug = post.slug.substring(11) as CollectionEntry<
|
'articles' | 'links' | 'photos'
|
||||||
'articles' | 'links' | 'photos'
|
>['slug']
|
||||||
>['slug']
|
|
||||||
|
|
||||||
// use date from frontmatter, or grab from file path
|
// use date from frontmatter, or grab from file path
|
||||||
const date = post.data.date ? post.data.date : slug.substring(1, 11)
|
const date = post.data.date ? post.data.date : slug.substring(1, 11)
|
||||||
const githubLink = `https://github.com/kremalicious/blog/blob/main/content/${post.collection}/${post.id}`
|
const githubLink = `https://github.com/kremalicious/blog/blob/main/content/${post.collection}/${post.id}`
|
||||||
|
|
||||||
post.slug = slug
|
// extract exif & iptc data from photos
|
||||||
post.data.date = new Date(date)
|
if (post.collection === 'photos') {
|
||||||
post.data.githubLink = githubLink
|
const imagePath = post.data.image.src.split('?')[0].split('/@fs')[1]
|
||||||
|
const exif = await readOutExif(imagePath)
|
||||||
|
post.data.exif = exif
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
post.slug = slug
|
||||||
|
post.data.date = new Date(date)
|
||||||
|
post.data.githubLink = githubLink
|
||||||
|
}
|
||||||
|
|
||||||
const posts = getSortedPosts(postsCollection)
|
const posts = getSortedPosts(postsCollection)
|
||||||
return posts
|
return posts
|
||||||
|
|
35
src/lib/exif/index.ts
Normal file
35
src/lib/exif/index.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { read } from 'fast-exif'
|
||||||
|
import iptc from 'node-iptc'
|
||||||
|
import type { Exif, ExifFormatted } from './types.ts'
|
||||||
|
import { formatExif } from './format.ts'
|
||||||
|
|
||||||
|
export async function readOutExif(filePath: string): Promise<Exif | undefined> {
|
||||||
|
if (!filePath) return
|
||||||
|
|
||||||
|
const imageId = path.basename(filePath, path.extname(filePath))
|
||||||
|
|
||||||
|
try {
|
||||||
|
// exif
|
||||||
|
const exifData = await read(filePath, true)
|
||||||
|
if (!exifData) return
|
||||||
|
|
||||||
|
// iptc
|
||||||
|
const file = fs.readFileSync(filePath)
|
||||||
|
const iptcData = iptc(file)
|
||||||
|
|
||||||
|
// format before output
|
||||||
|
const exifDataFormatted = formatExif(exifData)
|
||||||
|
|
||||||
|
const exif = {
|
||||||
|
image: imageId,
|
||||||
|
exif: { ...exifDataFormatted } as ExifFormatted,
|
||||||
|
iptc: { ...iptcData }
|
||||||
|
}
|
||||||
|
|
||||||
|
return exif
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(`${imageId}: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user