diff --git a/content/_schemas.ts b/content/_schemas.ts index eb15c5d8..889db699 100644 --- a/content/_schemas.ts +++ b/content/_schemas.ts @@ -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() diff --git a/package.json b/package.json index 5e2100d6..afed1f2b 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/scripts/create-exif/index.ts b/scripts/create-exif/index.ts deleted file mode 100644 index 0b4e134b..00000000 --- a/scripts/create-exif/index.ts +++ /dev/null @@ -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 { - 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 { - 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) -} diff --git a/src/lib/astro.ts b/src/lib/astro.ts index f8a6f8f0..de63b868 100644 --- a/src/lib/astro.ts +++ b/src/lib/astro.ts @@ -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 diff --git a/scripts/create-exif/format.ts b/src/lib/exif/format.ts similarity index 100% rename from scripts/create-exif/format.ts rename to src/lib/exif/format.ts diff --git a/src/lib/exif/index.ts b/src/lib/exif/index.ts new file mode 100644 index 00000000..1aa03bd8 --- /dev/null +++ b/src/lib/exif/index.ts @@ -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 { + 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}`) + } +} diff --git a/scripts/create-exif/types.ts b/src/lib/exif/types.ts similarity index 100% rename from scripts/create-exif/types.ts rename to src/lib/exif/types.ts