1
0
Fork 0

exif solution on each post during build

This commit is contained in:
Matthias Kretschmann 2023-09-05 03:15:01 +01:00
parent edea2b320a
commit 59334b90a1
Signed by: m
GPG Key ID: 606EEEF3C479A91F
7 changed files with 59 additions and 98 deletions

View File

@ -52,10 +52,11 @@ export const schemaPhotos = (image: ImageFunction) =>
z
.object({
...schemaShared,
image: image()
image: image(),
// .refine((img) => img.width >= 1040, {
// message: 'Cover image must be at least 1040 pixels wide!'
// })
exif: z.object({}).optional()
})
.strict()

View File

@ -7,7 +7,7 @@
"license": "MIT",
"type": "module",
"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",
"build": "npm run prebuild && astro build --config .config/astro.config.mjs",
"preview": "astro preview",
@ -24,7 +24,6 @@
"new": "ts-node --esm scripts/new/index.ts",
"create:icons": "ts-node --esm scripts/create-icons/index.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"
},
"dependencies": {

View File

@ -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)
}

View File

@ -1,5 +1,6 @@
import { getCollection, type CollectionEntry } from 'astro:content'
import { slugifyAll } from './slugify'
import { readOutExif } from './exif'
export function getSortedPosts(
posts: CollectionEntry<'articles' | 'links' | 'photos'>[]
@ -8,8 +9,8 @@ export function getSortedPosts(
.filter(({ data }) => !data.draft)
.sort(
(a, b) =>
Math.floor((b.data.date as Date).getTime() / 1000) -
Math.floor((a.data.date as Date).getTime() / 1000)
Math.floor((b.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)
postsCollection.forEach(
(post: CollectionEntry<'articles' | 'links' | 'photos'>) => {
// remove date from slug
const slug = post.slug.substring(11) as CollectionEntry<
'articles' | 'links' | 'photos'
>['slug']
for await (const post of postsCollection) {
// remove date from slug
const slug = post.slug.substring(11) as CollectionEntry<
'articles' | 'links' | 'photos'
>['slug']
// use date from frontmatter, or grab from file path
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}`
// use date from frontmatter, or grab from file path
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}`
post.slug = slug
post.data.date = new Date(date)
post.data.githubLink = githubLink
// extract exif & iptc data from photos
if (post.collection === 'photos') {
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)
return posts

35
src/lib/exif/index.ts Normal file
View 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}`)
}
}