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
|
||||
.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()
|
||||
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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 { 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
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