1
0
mirror of https://github.com/kremalicious/blog.git synced 2024-12-22 09:13:35 +01:00

refactor exif

* add all exif & IPTC data to files
* create nodes for all exif data
This commit is contained in:
Matthias Kretschmann 2019-10-31 00:51:53 +01:00
parent 74ccd935a0
commit acfdba0a66
Signed by: m
GPG Key ID: 606EEEF3C479A91F
9 changed files with 202 additions and 135 deletions

View File

@ -45,7 +45,7 @@ The whole [blog](https://kremalicious.com) is a React-based Single Page App buil
### 🎆 EXIF extraction
Automatically extracts EXIF metadata from my photos on build time. For minimal overhead, [fast-exif](https://github.com/titarenko/fast-exif) parses every JPG file upon Gatsby file node creation and adds the extracted EXIF data as node fields.
Automatically extracts EXIF & IPTC metadata from my photos on build time. For minimal overhead, [fast-exif](https://github.com/titarenko/fast-exif) & [node-iptc](https://github.com/derekbaron/node-iptc) parse every JPG file upon Gatsby file node creation and add the extracted data as node fields.
This way, EXIF data is only extracted at build time and can be simply queried with GraphQL at run time.
@ -56,7 +56,7 @@ In the end looks like this, including location display with [pigeon-maps](https:
If you want to know how this works, have a look at the respective component under
- [`src/components/atoms/Exif.jsx`](src/components/atoms/Exif.jsx)
- the EXIF node fields creation [`gatsby/createExifFields.js`](gatsby/createExifFields.js) running in [`gatsby-node.js`](gatsby-node.js)
- the EXIF node fields creation [`gatsby/createExif.js`](gatsby/createExif.js) running in [`gatsby-node.js`](gatsby-node.js)
### 💰 Cryptocurrency donation via Web3/MetaMask

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,6 +1,6 @@
const webpack = require('webpack')
const { createMarkdownFields } = require('./gatsby/createMarkdownFields')
const { createExifFields } = require('./gatsby/createExifFields')
const { createExif } = require('./gatsby/createExif')
const {
generatePostPages,
generateTagPages,
@ -9,17 +9,15 @@ const {
const { generateJsonFeed } = require('./gatsby/feeds')
const { itemsPerPage } = require('./config')
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
exports.onCreateNode = ({ node, actions, getNode, createNodeId }) => {
// Markdown files
if (node.internal.type === 'MarkdownRemark') {
createMarkdownFields(node, createNodeField, getNode)
createMarkdownFields(node, actions, getNode)
}
// Image files
if (node.internal.mediaType === 'image/jpeg') {
createExifFields(node, createNodeField)
createExif(node, actions, createNodeId)
}
}

143
gatsby/createExif.js Normal file
View File

@ -0,0 +1,143 @@
const fs = require('fs')
const util = require('util')
const fastExif = require('fast-exif')
const Fraction = require('fraction.js')
const getCoordinates = require('dms2dec')
const iptc = require('node-iptc')
const readFile = util.promisify(fs.readFile)
exports.createExif = async (node, actions, createNodeId) => {
try {
// exif
const exifData = await fastExif.read(node.absolutePath, true)
if (!exifData) return
// iptc
const file = await readFile(node.absolutePath)
const iptcData = iptc(file)
createNodes(exifData, iptcData, node, actions, createNodeId)
} catch (error) {
console.error(`${node.name}: ${error.message}`)
}
}
function createNodes(exifData, iptcData, node, actions, createNodeId) {
const { createNodeField, createNode, createParentChildLink } = actions
const exifDataFormatted = formatExif(exifData)
const exif = {
...exifData,
iptc: {
...iptcData
},
formatted: {
...exifDataFormatted
}
}
const exifNode = {
id: createNodeId(`${node.id} >> ImageExif`),
children: [],
...exif,
parent: node.id,
internal: {
contentDigest: `${node.internal.contentDigest}`,
type: 'ImageExif'
}
}
// add exif fields to existing type file
createNodeField({
node,
name: 'exif',
value: exif
})
// create new nodes from all exif data
// allowing to be queried with imageExif & AllImageExif
createNode(exifNode)
createParentChildLink({
parent: node,
child: exifNode
})
}
function formatExif(exifData) {
if (!exifData.exif) return
const { Model } = exifData.image
const {
ISO,
FNumber,
ExposureTime,
FocalLength,
FocalLengthIn35mmFormat,
ExposureBiasValue,
ExposureMode,
LensModel
} = exifData.exif
const iso = `ISO ${ISO}`
const fstop = `ƒ ${FNumber}`
const focalLength = `${FocalLengthIn35mmFormat || FocalLength}mm`
// Shutter speed
const { n, d } = new Fraction(ExposureTime)
const shutterspeed = `${n}/${d}s`
// GPS
let latitude
let longitude
if (exifData.gps) ({ latitude, longitude } = formatGps(exifData.gps))
// Exposure
const exposure = formatExposure(ExposureBiasValue || ExposureMode)
return {
iso,
model: Model,
fstop,
shutterspeed,
focalLength,
lensModel: LensModel,
exposure,
gps: { latitude, longitude }
}
}
function formatGps(gpsData) {
if (!gpsData) return
const { GPSLatitudeRef, GPSLatitude, GPSLongitudeRef, GPSLongitude } = gpsData
const GPSdec = getCoordinates(
GPSLatitude,
GPSLatitudeRef,
GPSLongitude,
GPSLongitudeRef
)
const latitude = GPSdec[0]
const longitude = GPSdec[1]
return { latitude, longitude }
}
function formatExposure(exposureMode) {
if (!exposureMode) return
const exposureShortened = parseFloat(exposureMode.toFixed(2))
let exposure
if (exposureMode === 0) {
exposure = `+/- ${exposureShortened} ev`
} else if (exposureMode > 0) {
exposure = `+ ${exposureShortened} ev`
} else {
exposure = `${exposureShortened} ev`
}
return exposure
}

View File

@ -1,94 +0,0 @@
const fastExif = require('fast-exif')
const Fraction = require('fraction.js')
const dms2dec = require('dms2dec')
exports.createExifFields = async (node, createNodeField) => {
let exifData
try {
exifData = await fastExif.read(node.absolutePath, true)
if (!exifData) return
constructExifFields(exifData, createNodeField, node)
} catch (error) {
// console.error(`${node.name}: ${error.message}`)
return null // just silently fail when exif can't be extracted
}
}
const getGps = gpsData => {
if (!gpsData) return
const { GPSLatitudeRef, GPSLatitude, GPSLongitudeRef, GPSLongitude } = gpsData
const GPSdec = dms2dec(
GPSLatitude,
GPSLatitudeRef,
GPSLongitude,
GPSLongitudeRef
)
const latitude = GPSdec[0]
const longitude = GPSdec[1]
return { latitude, longitude }
}
const getExposure = exposureMode => {
const exposureShortened = parseFloat(exposureMode.toFixed(2))
let exposure
if (exposureMode === 0) {
exposure = `+/- ${exposureShortened} ev`
} else if (exposureMode > 0) {
exposure = `+ ${exposureShortened} ev`
} else {
exposure = `${exposureShortened} ev`
}
return exposure
}
const constructExifFields = (exifData, createNodeField, node) => {
const { Model } = exifData.image
const {
ISO,
FNumber,
ExposureTime,
FocalLength,
FocalLengthIn35mmFormat,
ExposureBiasValue,
ExposureMode,
LensModel
} = exifData.exif
const iso = `ISO ${ISO}`
const fstop = `ƒ ${FNumber}`
const focalLength = `${FocalLengthIn35mmFormat || FocalLength}mm`
// Shutter speed
const { n, d } = new Fraction(ExposureTime)
const shutterspeed = `${n}/${d}s`
// GPS
let latitude
let longitude
if (exifData.gps) ({ latitude, longitude } = getGps(exifData.gps))
// Exposure
const exposure = getExposure(ExposureBiasValue || ExposureMode)
// add exif fields to type File
createNodeField({
node,
name: 'exif',
value: {
iso,
model: Model,
fstop,
shutterspeed,
focalLength,
lensModel: LensModel,
exposure,
gps: { latitude, longitude }
}
})
}

View File

@ -2,6 +2,28 @@ const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')
const { repoContentPath } = require('../config')
// Create slug, date & github file link for posts from file path values
exports.createMarkdownFields = (node, actions, getNode) => {
const { createNodeField } = actions
const fileNode = getNode(node.parent)
const parsedFilePath = path.parse(fileNode.relativePath)
const slugOriginal = createFilePath({ node, getNode })
createSlug(node, createNodeField, slugOriginal, parsedFilePath)
createDate(node, createNodeField, slugOriginal)
// github file link
const type = fileNode.sourceInstanceName
const file = fileNode.relativePath
const githubLink = `${repoContentPath}/${type}/${file}`
createNodeField({
node,
name: 'githubLink',
value: githubLink
})
}
function createSlug(node, createNodeField, slugOriginal, parsedFilePath) {
let slug
@ -32,24 +54,3 @@ function createDate(node, createNodeField, slugOriginal) {
value: date
})
}
// Create slug, date & github file link for posts from file path values
exports.createMarkdownFields = (node, createNodeField, getNode) => {
const fileNode = getNode(node.parent)
const parsedFilePath = path.parse(fileNode.relativePath)
const slugOriginal = createFilePath({ node, getNode })
createSlug(node, createNodeField, slugOriginal, parsedFilePath)
createDate(node, createNodeField, slugOriginal)
// github file link
const type = fileNode.sourceInstanceName
const file = fileNode.relativePath
const githubLink = `${repoContentPath}/${type}/${file}`
createNodeField({
node,
name: 'githubLink',
value: githubLink
})
}

11
src/@types/Image.d.ts vendored
View File

@ -15,7 +15,7 @@ export interface ImageNode {
}
}
export interface Exif {
export interface ExifFormatted {
iso: string
model: string
fstop: string
@ -28,3 +28,12 @@ export interface Exif {
longitude: string
}
}
export interface Exif {
formatted: ExifFormatted
exif: any
image: any
thumbnail?: any
gps?: any
iptc?: any
}

View File

@ -4,7 +4,15 @@ import styles from './Exif.module.scss'
import { Exif as ExifMeta } from '../../@types/Image'
export default function Exif({ exif }: { exif: ExifMeta }) {
const { iso, model, fstop, shutterspeed, focalLength, exposure, gps } = exif
const {
iso,
model,
fstop,
shutterspeed,
focalLength,
exposure,
gps
} = exif.formatted
return (
<aside className={styles.exif}>

View File

@ -79,16 +79,18 @@ export const pageQuery = graphql`
}
fields {
exif {
iso
model
fstop
shutterspeed
focalLength
lensModel
exposure
gps {
latitude
longitude
formatted {
iso
model
fstop
shutterspeed
focalLength
lensModel
exposure
gps {
latitude
longitude
}
}
}
}