mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-04 00:39:01 +00:00
265 lines
6.7 KiB
JavaScript
265 lines
6.7 KiB
JavaScript
import fs from 'fs-extra'
|
|
import path from 'path'
|
|
import { resolve as pathResolve, basename as pathBasename } from 'path'
|
|
import { PubSub } from 'apollo-server'
|
|
import uuid from 'uuid'
|
|
import { exiftool } from 'exiftool-vendored'
|
|
import sharp from 'sharp'
|
|
import readChunk from 'read-chunk'
|
|
import imageType from 'image-type'
|
|
import config from './config'
|
|
|
|
export const EVENT_SCANNER_PROGRESS = 'SCANNER_PROGRESS'
|
|
|
|
const isImage = async path => {
|
|
const buffer = await readChunk(path, 0, 12)
|
|
const type = imageType(buffer)
|
|
|
|
return type != null
|
|
}
|
|
|
|
const isRawImage = async path => {
|
|
const buffer = await readChunk(path, 0, 12)
|
|
const { ext } = imageType(buffer)
|
|
|
|
const rawTypes = ['cr2', 'arw', 'crw', 'dng']
|
|
|
|
return rawTypes.includes(ext)
|
|
}
|
|
|
|
class PhotoScanner {
|
|
constructor(driver) {
|
|
this.driver = driver
|
|
this.isRunning = false
|
|
this.pubsub = new PubSub()
|
|
|
|
this.scanAll = this.scanAll.bind(this)
|
|
this.scanAlbum = this.scanAlbum.bind(this)
|
|
this.scanUser = this.scanUser.bind(this)
|
|
this.processImage = this.processImage.bind(this)
|
|
}
|
|
|
|
async scanAll() {
|
|
this.isRunning = true
|
|
|
|
this.pubsub.publish(EVENT_SCANNER_PROGRESS, {
|
|
scannerStatusUpdate: {
|
|
progress: 0,
|
|
finished: false,
|
|
error: false,
|
|
errorMessage: '',
|
|
},
|
|
})
|
|
|
|
let session = this.driver.session()
|
|
|
|
session.run('MATCH (u:User) return u').subscribe({
|
|
onNext: record => {
|
|
const user = record.toObject().u.properties
|
|
|
|
console.log('USER', user)
|
|
|
|
if (!user.rootPath) {
|
|
console.log(`User ${user.username}, has no root path, skipping`)
|
|
return
|
|
}
|
|
|
|
this.scanUser(user)
|
|
|
|
console.log(`Scanning ${user.username}...`)
|
|
},
|
|
onCompleted: () => {
|
|
session.close()
|
|
this.isRunning = false
|
|
console.log('Done scanning')
|
|
|
|
this.pubsub.publish(EVENT_SCANNER_PROGRESS, {
|
|
scannerStatusUpdate: {
|
|
progress: 100,
|
|
finished: true,
|
|
error: false,
|
|
errorMessage: '',
|
|
},
|
|
})
|
|
},
|
|
onError: error => {
|
|
console.error(error)
|
|
|
|
this.pubsub.publish(EVENT_SCANNER_PROGRESS, {
|
|
scannerStatusUpdate: {
|
|
progress: 0,
|
|
finished: false,
|
|
error: true,
|
|
errorMessage: error.message,
|
|
},
|
|
})
|
|
},
|
|
})
|
|
}
|
|
|
|
async scanUser(user) {
|
|
console.log('Scanning path', user.rootPath)
|
|
|
|
const driver = this.driver
|
|
const scanAlbum = this.scanAlbum
|
|
|
|
async function scanPath(path) {
|
|
const list = fs.readdirSync(path)
|
|
|
|
let foundImage = false
|
|
|
|
for (const item of list) {
|
|
const itemPath = pathResolve(path, item)
|
|
// console.log(`Scanning item ${itemPath}...`)
|
|
const stat = fs.statSync(itemPath)
|
|
|
|
if (stat.isDirectory()) {
|
|
// console.log(`Entering directory ${itemPath}`)
|
|
const imagesInDirectory = await scanPath(itemPath)
|
|
|
|
if (imagesInDirectory) {
|
|
console.log(`Found album at ${itemPath}`)
|
|
const session = driver.session()
|
|
|
|
const findAlbumResult = await session.run(
|
|
'MATCH (a:Album { path: {path} }) RETURN a',
|
|
{
|
|
path: itemPath,
|
|
}
|
|
)
|
|
|
|
console.log('FIND ALBUM RESULT', findAlbumResult.records)
|
|
|
|
if (findAlbumResult.records.length != 0) {
|
|
console.log('Album already exists')
|
|
|
|
const album = findAlbumResult.records[0].toObject().a.properties
|
|
scanAlbum(album)
|
|
|
|
continue
|
|
}
|
|
|
|
console.log('Adding album')
|
|
const albumId = uuid()
|
|
const albumResult = await session.run(
|
|
`MATCH (u:User { id: {userId} })
|
|
CREATE (a:Album { id: {id}, title: {title}, path: {path} })
|
|
CREATE (u)-[own:OWNS]->(a)
|
|
RETURN a`,
|
|
{
|
|
id: albumId,
|
|
userId: user.id,
|
|
title: item,
|
|
path: itemPath,
|
|
}
|
|
)
|
|
|
|
const album = albumResult.records[0].toObject().a.properties
|
|
scanAlbum(album)
|
|
|
|
session.close()
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
if (!foundImage && (await isImage(itemPath))) {
|
|
foundImage = true
|
|
}
|
|
}
|
|
|
|
return foundImage
|
|
}
|
|
|
|
await scanPath(user.rootPath)
|
|
console.log('User scan complete')
|
|
}
|
|
|
|
async scanAlbum(album) {
|
|
const { title, path, id } = album
|
|
console.log('Scanning album', title)
|
|
|
|
const list = fs.readdirSync(path)
|
|
|
|
for (const item of list) {
|
|
const itemPath = pathResolve(path, item)
|
|
|
|
if (await isImage(itemPath)) {
|
|
const session = this.driver.session()
|
|
|
|
const photoResult = await session.run(
|
|
`MATCH (p:Photo {path: {imgPath} })<--(a:Album {id: {albumId}}) RETURN p`,
|
|
{
|
|
imgPath: itemPath,
|
|
albumId: id,
|
|
}
|
|
)
|
|
|
|
if (photoResult.records.length != 0) {
|
|
console.log(`Photo already exists ${item}`)
|
|
} else {
|
|
console.log(`Found new image at ${itemPath}`)
|
|
const imageId = uuid()
|
|
await session.run(
|
|
`MATCH (a:Album { id: {albumId} })
|
|
CREATE (p:Photo {id: {id}, path: {path}, title: {title} })
|
|
CREATE (a)-[:CONTAINS]->(p)`,
|
|
{
|
|
id: imageId,
|
|
path: itemPath,
|
|
title: item,
|
|
albumId: id,
|
|
}
|
|
)
|
|
|
|
this.processImage(imageId)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async processImage(id) {
|
|
console.log('Processing image')
|
|
const session = this.driver.session()
|
|
|
|
const result = await session.run('MATCH (p:Photo { id: {id} }) return p', {
|
|
id,
|
|
})
|
|
const photo = result.records[0].get('p').properties
|
|
|
|
console.log('PHOTO', photo.path)
|
|
|
|
const imagePath = path.resolve(config.cachePath, 'images', id)
|
|
|
|
await fs.remove(imagePath)
|
|
await fs.mkdirp(imagePath)
|
|
|
|
let resizeBaseImg = photo.path
|
|
|
|
if (await isRawImage(photo.path)) {
|
|
console.log('Processing RAW image')
|
|
|
|
const extractedPath = path.resolve(imagePath, 'extracted.jpg')
|
|
await exiftool.extractPreview(photo.path, extractedPath)
|
|
|
|
resizeBaseImg = extractedPath
|
|
}
|
|
|
|
// Resize image
|
|
console.log('Resizing image', resizeBaseImg)
|
|
await sharp(resizeBaseImg)
|
|
.resize(1440, 1080, { fit: 'inside', withoutEnlargement: true })
|
|
.toFile(path.resolve(imagePath, 'thumbnail.jpg'))
|
|
|
|
await sharp(resizeBaseImg)
|
|
.jpeg({ quality: 85 })
|
|
.toFile(path.resolve(imagePath, 'original.jpg'))
|
|
|
|
session.close()
|
|
|
|
console.log('Processing done')
|
|
}
|
|
}
|
|
|
|
export default PhotoScanner
|