diff --git a/api/src/scanner/Scanner.js b/api/src/scanner/Scanner.js
index 9d45e911..e5cdeafa 100644
--- a/api/src/scanner/Scanner.js
+++ b/api/src/scanner/Scanner.js
@@ -64,7 +64,7 @@ class PhotoScanner {
await _processImage(
{
driver: this.driver,
- addFinishedImage: this.markFinishedImage,
+ markFinishedImage: this.markFinishedImage,
},
id
)
diff --git a/api/src/scanner/processImage.js b/api/src/scanner/processImage.js
index 038c4e56..bb588e12 100644
--- a/api/src/scanner/processImage.js
+++ b/api/src/scanner/processImage.js
@@ -3,8 +3,48 @@ import path from 'path'
import { exiftool } from 'exiftool-vendored'
import sharp from 'sharp'
import { isRawImage, imageSize, getImageCachePath } from './utils'
+import { DateTime as NeoDateTime } from 'neo4j-driver/lib/v1/temporal-types.js'
-export default async function processImage({ driver, addFinishedImage }, id) {
+async function addExifTags({ session, photo }) {
+ const exifResult = await session.run(
+ 'MATCH (p:Photo { id: {id} })-[:EXIF]->(exif:PhotoEXIF) RETURN exif',
+ {
+ id: photo.id,
+ }
+ )
+
+ if (exifResult.records.length > 0) return
+
+ const rawTags = await exiftool.read(photo.path)
+
+ const photoExif = {
+ camera: rawTags.Model,
+ maker: rawTags.Make,
+ lens: rawTags.LensType,
+ dateShot:
+ rawTags.DateTimeOriginal &&
+ NeoDateTime.fromStandardDate(rawTags.DateTimeOriginal.toDate()),
+ fileSize: rawTags.FileSize,
+ exposure: rawTags.ShutterSpeedValue,
+ aperture: rawTags.ApertureValue,
+ iso: rawTags.ISO,
+ focalLength: rawTags.FocalLength,
+ flash: rawTags.Flash,
+ }
+
+ const result = await session.run(
+ `MATCH (p:Photo { id: {id} })
+ CREATE (p)-[:EXIF]->(exif:PhotoEXIF {exifProps})`,
+ {
+ id: photo.id,
+ exifProps: photoExif,
+ }
+ )
+
+ console.log('Added exif tags to photo', photo.path)
+}
+
+export default async function processImage({ driver, markFinishedImage }, id) {
const session = driver.session()
const result = await session.run(
@@ -14,18 +54,32 @@ export default async function processImage({ driver, addFinishedImage }, id) {
}
)
+ const photo = result.records[0].get('p').properties
+ const albumId = result.records[0].get('a.id')
+
+ const imagePath = getImageCachePath(id, albumId)
+
+ // Verify that processing is needed
+ if (await fs.exists(path.resolve(imagePath, 'thumbnail.jpg'))) {
+ const urlResult = await session.run(
+ `MATCH (p:Photo { id: {id} })-->(urls:PhotoURL) RETURN urls`,
+ { id }
+ )
+
+ if (urlResult.records.length == 2) {
+ markFinishedImage()
+
+ console.log('Skipping image', photo.path)
+ return
+ }
+ }
+
+ // Begin processing
await session.run(
`MATCH (p:Photo { id: {id} })-->(urls:PhotoURL) DETACH DELETE urls`,
{ id }
)
- const photo = result.records[0].get('p').properties
- const albumId = result.records[0].get('a.id')
-
- // console.log('Processing photo', photo.path)
-
- const imagePath = getImageCachePath(id, albumId)
-
await fs.remove(imagePath)
await fs.mkdirp(imagePath)
@@ -106,7 +160,9 @@ export default async function processImage({ driver, addFinishedImage }, id) {
console.log('Create photo url failed', e)
}
+ await addExifTags({ session, photo })
+
session.close()
- addFinishedImage()
+ markFinishedImage()
}
diff --git a/api/src/scanner/scanAlbum.js b/api/src/scanner/scanAlbum.js
index 17ef1480..e0da59a2 100644
--- a/api/src/scanner/scanAlbum.js
+++ b/api/src/scanner/scanAlbum.js
@@ -42,11 +42,7 @@ export default async function scanAlbum(
'thumbnail.jpg'
)
- if (!(await fs.exists(thumbnailPath))) {
- processingImagePromises.push(processImage(photoId))
- } else {
- markFinishedImage()
- }
+ processingImagePromises.push(processImage(photoId))
} else {
console.log(`Found new image at ${itemPath}`)
const imageId = uuid()
diff --git a/api/src/scanner/scanUser.js b/api/src/scanner/scanUser.js
index 28c9bf84..3152f0b5 100644
--- a/api/src/scanner/scanUser.js
+++ b/api/src/scanner/scanUser.js
@@ -134,7 +134,7 @@ export default async function scanUser({ driver, scanAlbum }, user) {
OPTIONAL MATCH (a)-[:CONTAINS]->(p:Photo)-->(photoTail)
WITH a, p, photoTail, a.id AS albumId
DETACH DELETE a, p, photoTail
- RETURN albumId`,
+ RETURN DISTINCT albumId`,
{ userId: user.id, foundAlbums: foundAlbumIds }
)
diff --git a/api/src/schema.graphql b/api/src/schema.graphql
index a9f2ebe1..4892eb00 100644
--- a/api/src/schema.graphql
+++ b/api/src/schema.graphql
@@ -31,6 +31,20 @@ type PhotoURL {
height: Int
}
+type PhotoEXIF {
+ photo: Photo @relation(name: "EXIF", direction: "IN")
+ camera: String
+ maker: String
+ lens: String
+ dateShot: DateTime
+ fileSize: String
+ exposure: String
+ aperture: Float
+ iso: Int
+ focalLength: String
+ flash: String
+}
+
type Photo {
id: ID!
title: String
@@ -42,6 +56,7 @@ type Photo {
thumbnail: PhotoURL @relation(name: "THUMBNAIL_URL", direction: "OUT")
# The album that holds the photo
album: Album! @relation(name: "CONTAINS", direction: "IN")
+ exif: PhotoEXIF @relation(name: "EXIF", direction: "OUT")
}
type SiteInfo {
diff --git a/ui/src/Pages/AlbumPage/AlbumPage.js b/ui/src/Pages/AlbumPage/AlbumPage.js
index f2540d14..71ae6d1b 100644
--- a/ui/src/Pages/AlbumPage/AlbumPage.js
+++ b/ui/src/Pages/AlbumPage/AlbumPage.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react'
import gql from 'graphql-tag'
import { Query } from 'react-apollo'
import Layout from '../../Layout'
-import AlbumSidebar from './AlbumSidebar'
+import PhotoSidebar from '../../components/sidebar/PhotoSidebar'
import PhotoGallery from '../../PhotoGallery'
import AlbumGallery from '../AllAlbumsPage/AlbumGallery'
@@ -92,7 +92,7 @@ class AlbumPage extends Component {
this.setActiveImage(index, data.album.photos[index].id)
}}
/>
- {album.title}