diff --git a/README.md b/README.md index 8e931040..53c917c8 100644 --- a/README.md +++ b/README.md @@ -220,13 +220,15 @@ npm run new "Hello World" npm run new "Hello World" 2017-12-27 ``` +Create a new photo post with date, title & description pre-filled from EXIF/IPTC data of a given image file: + ```bash -npm run new "Hello World" photo -npm run new "Hello World" photo 2017-12-27 +npm run new photo /path/to/photo.jpg ``` - [`scripts/new.js`](scripts/new.js) - [`scripts/new.md`](scripts/new.md) +- [`scripts/new-photo.md`](scripts/new-photo.md) ## 🚚 Deployment diff --git a/content/photos/2019-01-27-all-work-and-no-play.jpg b/content/photos/2019-01-27-all-work-and-no-play.jpg new file mode 100644 index 00000000..c6ae65c8 Binary files /dev/null and b/content/photos/2019-01-27-all-work-and-no-play.jpg differ diff --git a/content/photos/2019-01-27-all-work-and-no-play.md b/content/photos/2019-01-27-all-work-and-no-play.md new file mode 100644 index 00000000..715ad580 --- /dev/null +++ b/content/photos/2019-01-27-all-work-and-no-play.md @@ -0,0 +1,9 @@ +--- +type: photo +date: 2019-01-27T00:00:00.000Z + +title: All Work And No Play +image: 2019-01-27-all-work-and-no-play.jpg +--- + +Inside the [“Stanley Kubrick” exhibition](https://www.cccb.org/en/exhibitions/file/stanley-kubrick/228237) in the [Centre de Cultura Contemporània de Barcelona (CCCB)](https://www.cccb.org) in Barcelona, Catalonia, Spain. diff --git a/content/photos/2019-02-25-edificio-italia.jpg b/content/photos/2019-02-25-edificio-italia.jpg new file mode 100644 index 00000000..0d10bcb3 Binary files /dev/null and b/content/photos/2019-02-25-edificio-italia.jpg differ diff --git a/content/photos/2019-02-25-edificio-italia.md b/content/photos/2019-02-25-edificio-italia.md new file mode 100644 index 00000000..1a338940 --- /dev/null +++ b/content/photos/2019-02-25-edificio-italia.md @@ -0,0 +1,9 @@ +--- +type: photo +date: 2019-02-25T22:51:00.000Z + +title: Edifício Itália +image: 2019-02-25-edificio-italia.jpg +--- + +View over São Paulo from the 46th floor of Edifício Itália. diff --git a/content/photos/2019-03-06-brazilian-museum-of-sculpture-and-ecology.jpg b/content/photos/2019-03-06-brazilian-museum-of-sculpture-and-ecology.jpg new file mode 100644 index 00000000..8283581d Binary files /dev/null and b/content/photos/2019-03-06-brazilian-museum-of-sculpture-and-ecology.jpg differ diff --git a/content/photos/2019-03-06-brazilian-museum-of-sculpture-and-ecology.md b/content/photos/2019-03-06-brazilian-museum-of-sculpture-and-ecology.md new file mode 100644 index 00000000..6aa6be35 --- /dev/null +++ b/content/photos/2019-03-06-brazilian-museum-of-sculpture-and-ecology.md @@ -0,0 +1,9 @@ +--- +type: photo +date: 2019-03-06T17:52:46.000Z + +title: Brazilian Museum of Sculpture and Ecology +image: 2019-03-06-brazilian-museum-of-sculpture-and-ecology.jpg +--- + +On the grounds of the Brazilian Museum of Sculpture and Ecology (MuBE) in São Paulo, Brazil. diff --git a/content/photos/2019-03-13-paraty-mirim.jpg b/content/photos/2019-03-13-paraty-mirim.jpg new file mode 100644 index 00000000..32517aa1 Binary files /dev/null and b/content/photos/2019-03-13-paraty-mirim.jpg differ diff --git a/content/photos/2019-03-13-paraty-mirim.md b/content/photos/2019-03-13-paraty-mirim.md new file mode 100644 index 00000000..30662075 --- /dev/null +++ b/content/photos/2019-03-13-paraty-mirim.md @@ -0,0 +1,9 @@ +--- +type: photo +date: 2019-03-13T00:00:00.000Z + +title: Paraty-Mirim +image: 2019-03-13-paraty-mirim.jpg +--- + +At the Paraty-Mirim beach in Paraty, Rio de Janeiro, Brazil. diff --git a/content/photos/2019-09-29-arco-di-costantino.jpg b/content/photos/2019-09-29-arco-di-costantino.jpg new file mode 100644 index 00000000..f64f8c8f Binary files /dev/null and b/content/photos/2019-09-29-arco-di-costantino.jpg differ diff --git a/content/photos/2019-09-29-arco-di-costantino.md b/content/photos/2019-09-29-arco-di-costantino.md new file mode 100644 index 00000000..0475d1ab --- /dev/null +++ b/content/photos/2019-09-29-arco-di-costantino.md @@ -0,0 +1,9 @@ +--- +type: photo +date: 2019-09-29T13:57:00.000Z + +title: Arco di Costantino +image: 2019-09-29-arco-di-costantino.jpg +--- + +The Arch of Constantine seen through the Collosseum walls in Rome, Italy. diff --git a/content/photos/2019-09-29-foro-di-cesare.jpg b/content/photos/2019-09-29-foro-di-cesare.jpg new file mode 100644 index 00000000..663667bf Binary files /dev/null and b/content/photos/2019-09-29-foro-di-cesare.jpg differ diff --git a/content/photos/2019-09-29-foro-di-cesare.md b/content/photos/2019-09-29-foro-di-cesare.md new file mode 100644 index 00000000..36b7a434 --- /dev/null +++ b/content/photos/2019-09-29-foro-di-cesare.md @@ -0,0 +1,9 @@ +--- +type: photo +date: 2019-09-29T15:31:00.000Z + +title: Foro di Cesare +image: 2019-09-29-foro-di-cesare.jpg +--- + +View over the [Forum of Caesar](https://en.wikipedia.org/wiki/Forum_of_Caesar) within the [Imperial Forum](https://en.wikipedia.org/wiki/Imperial_fora) in Rome, Italy. diff --git a/gatsby/createExifFields.js b/gatsby/createExifFields.js index c17312b0..656e16b7 100644 --- a/gatsby/createExifFields.js +++ b/gatsby/createExifFields.js @@ -54,6 +54,7 @@ const constructExifFields = (exifData, createNodeField, node) => { FNumber, ExposureTime, FocalLength, + FocalLengthIn35mmFormat, ExposureBiasValue, ExposureMode, LensModel @@ -61,7 +62,7 @@ const constructExifFields = (exifData, createNodeField, node) => { const iso = `ISO ${ISO}` const fstop = `ƒ ${FNumber}` - const focalLength = `${FocalLength}mm` + const focalLength = `${FocalLengthIn35mmFormat || FocalLength}mm` // Shutter speed const { n, d } = new Fraction(ExposureTime) diff --git a/package.json b/package.json index be0f346c..e4b72ec9 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "identity-obj-proxy": "^3.0.0", "jest": "^24.9.0", "markdownlint-cli": "^0.18.0", + "node-iptc": "^1.0.5", "node-sass": "^4.12.0", "npm-run-all": "^4.1.5", "ora": "^4.0.0", diff --git a/scripts/new-photo.md b/scripts/new-photo.md index af526209..4ea3a5d9 100644 --- a/scripts/new-photo.md +++ b/scripts/new-photo.md @@ -6,4 +6,4 @@ title: TITLE image: DATE_SHORT-SLUG.jpg --- -Beep Boop. +DESCRIPTION diff --git a/scripts/new.js b/scripts/new.js index 1ce0433d..b244e445 100644 --- a/scripts/new.js +++ b/scripts/new.js @@ -2,6 +2,8 @@ import fs from 'fs-extra' import path from 'path' import slugify from 'slugify' import ora from 'ora' +import fastExif from 'fast-exif' +import iptc from 'node-iptc' const templatePath = path.join(__dirname, 'new.md') const templatePathPhoto = path.join(__dirname, 'new-photo.md') @@ -14,24 +16,55 @@ if (!process.argv[2]) { spinner.fail('Use the format `npm run new "Title of post"`') } -const title = process.argv[2] -const isPhoto = process.argv[3] === 'photo' +let title = process.argv[2] +const isPhoto = process.argv[2] === 'photo' spinner.text = `Adding '${title}'.` -const titleSlug = slugify(title, { lower: true }) +let titleSlug = slugify(title, { lower: true }) const postsPath = path.join('.', 'content', 'posts') const photosPath = path.join('.', 'content', 'photos') let date = new Date().toISOString() -if (isPhoto) { - if (process.argv[4]) { - date = new Date(process.argv[4]).toISOString() +async function getIptc(imagePath) { + return new Promise((resolve, reject) => { + fs.readFile(imagePath, (err, data) => { + if (err) reject(err) + const iptcData = iptc(data) + return resolve(iptcData) + }) + }) +} + +async function getExif(imagePath) { + let exifData + try { + exifData = await fastExif.read(imagePath, true) + } catch (error) { + return null } + let iptcData + try { + iptcData = await getIptc(imagePath) + } catch (error) { + return null + } + + return { ...exifData, iptc: { ...iptcData } } +} + +async function createPhotoPost() { + const photo = process.argv[3] + const exifData = await getExif(photo) + title = exifData.iptc.object_name || exifData.iptc.title + titleSlug = slugify(title, { lower: true }) + date = new Date(exifData.exif.DateTimeOriginal).toISOString() const dateShort = date.slice(0, 10) - const filePhoto = `${photosPath}/${dateShort}-${titleSlug}.md` + const description = exifData.iptc.caption + const fileName = `${dateShort}-${titleSlug}` + const postPhoto = `${photosPath}/${fileName}.md` const newContentsPhoto = templatePhoto .split('TITLE') @@ -42,13 +75,23 @@ if (isPhoto) { .join(date) .split('DATE_SHORT') .join(dateShort) + .split('DESCRIPTION') + .join(description) - fs.appendFile(filePhoto, newContentsPhoto, err => { - if (err) spinner.fail(`Error creating photo post: ${err}`) - spinner.succeed( - `New photo post '${title}' created.\n\n Use ${dateShort}-${titleSlug}.jpg as the photo file name.` - ) + // copy photo file in place + fs.copyFile(photo, `${photosPath}/${fileName}.jpg`, err => { + if (err) spinner.fail(`Error copying photo file: ${err}`) }) + + // create photo post file + fs.appendFile(postPhoto, newContentsPhoto, err => { + if (err) spinner.fail(`Error creating photo post: ${err}`) + spinner.succeed(`New photo post '${title}' as '${fileName}.md' created.`) + }) +} + +if (isPhoto) { + createPhotoPost() } else { if (process.argv[3]) { date = new Date(process.argv[3]).toISOString()