"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); require("dotenv/config"); const log_1 = require("./log"); const promises_1 = require("fs/promises"); const path_1 = require("path"); const util_1 = require("util"); const uuid_1 = require("uuid"); const crypto_1 = require("crypto"); const os_1 = require("os"); const moment_1 = __importDefault(require("moment")); const argparse_1 = require("argparse"); const shell_1 = require("./shell"); const hash_1 = require("./hash"); const files3_1 = require("./files3"); const env_1 = require("./env"); const db_1 = require("./db"); const geocode_1 = require("./geocode"); const sizeOf = (0, util_1.promisify)(require('image-size')); class Generate { constructor() { this.inbox = (0, env_1.envString)('INBOX', '~/Photos/toprocess'); this.photos = (0, env_1.envString)('PHOTOS', '~/Photos/processed'); this.artist = (0, env_1.envString)('ARTIST', 'Unknown'); this.tmp = (0, os_1.tmpdir)(); this.log = (0, log_1.createLog)('generate'); const parser = new argparse_1.ArgumentParser({ description: 'Generate script' }); parser.add_argument('-s', '--score', { type: 'int', default: 0, help: 'Starting score' }); const args = parser.parse_args(); this.log.info(`Generating site: ${new Date()}`); this.db = new db_1.DB(); this.s3 = new files3_1.Files3((0, env_1.envString)('S3_BUCKET', 's3bucket'), true); this.geocode = new geocode_1.Geocode(this.db); this.generate(); this.score = args.score; } async generate() { let inbox; let images; let filename; let meta; let photo; let exif; try { inbox = await (0, promises_1.realpath)(this.inbox); } catch (err) { this.log.error(err); return; } try { images = await (0, promises_1.readdir)(inbox); } catch (err) { this.log.error(err); return; } images = images.filter((el) => { if (el.toLowerCase().indexOf('.jpg') !== -1 || el.toLowerCase().indexOf('.jpeg') !== -1 || el.toLowerCase().indexOf('.tif') !== -1 || el.toLowerCase().indexOf('.tiff') !== -1) { return true; } return false; }); if (images.length === 0) { this.log.info(`No new images found`); return; } images = await Promise.all(images.map(async (el) => { return await (0, promises_1.realpath)((0, path_1.join)(inbox, el)); })); for (let image of images) { this.log.info(image); filename = (0, path_1.basename)(image); meta = this.parseFilename(filename); try { photo = await this.createPhoto(image, meta); } catch (err) { this.log.error(`Error creating photo record metadata`, err); continue; } if (await this.db.existsName(filename)) { this.log.info(`Image ${filename} already exists`); if (await this.db.existsHash(photo.hash)) { this.log.warn(`Image ${name} already exists, moving...`); await this.move(image); continue; } } if (await this.db.existsHash(photo.hash)) { this.log.warn(`Image exists under different name, update?`); await this.move(image); continue; } try { await this.db.create(photo); this.log.info(JSON.stringify(photo, null, '\t')); } catch (err) { this.log.error(`Error inserting photo into database`, err); continue; } try { await this.upload(image); } catch (err) { this.log.error(`Error uploading image`, err); continue; } try { exif = await this.exif(photo); } catch (err) { this.log.error(`Error building EXIF data`, err); } try { await this.img(image, photo.id, exif); } catch (err) { this.log.error(`Error running img.sh script`, err); } await this.move(image); } } //Artist string //ImageTitle string //ImageUniqueID string //ISO int16u[n] //DateTimeOriginal string (YYYY:MM:DD HH:MM:SS) // //GPSLatitude rational64u[3] //GPSLongitude async exif(photo) { const filePath = await this.mktemp('photosite_exif'); const created = moment_1.default.unix(photo.created / 1000).format('YYYY:MM:DD HH:mm:ss'); let exif = `-Artist=${this.artist} -Title=${photo.description} -ImageUniqueId=${photo.id} -DateTimeOriginal=${created}`; const iso = photo.filmstock.split(' ').filter(el => this.isOnlyNumbers(el)).map(el => parseInt(el)).filter(el => el > 25); if (iso.length > 0) { exif += ` -ISO=${iso}`; } if (photo.latitude !== null && photo.longitude !== null) { exif += ` -GPSLatitude=${photo.latitude} -GPSLongitude=${photo.longitude}`; } try { await (0, promises_1.writeFile)(filePath, exif, 'utf8'); } catch (err) { this.log.error(`Error writing EXIF data`, err); } return filePath; } async img(file, id, exif) { const cmd = ['bash', 'scripts/img.sh', file, id, exif]; const shell = new shell_1.Shell(cmd); try { await shell.execute(); } catch (err) { this.log.error(err); return; } this.log.info(`Processed image file for ${file}`); } async getImageDimensions(imagePath) { let dimensions; try { dimensions = await sizeOf(imagePath); return dimensions; } catch (err) { this.log.error('Error getting image dimensions:', err); } } capitalize(str) { return (str.substring(0, 1).toUpperCase()) + str.substring(1); } formatProperNouns(str) { let parts = str.split('-'); parts = parts.map(el => this.capitalize(el)); return parts.join(' '); } //year //month //day //format //filmstock //location //description //original //2024_12_02_35mm_Kodak-Gold-200_Somerville-MA_Walk-towards-Harvard-Square#000061280009.tif parseFilename(filename) { const halves = filename.split('#'); const parts = halves[0].split('_'); let meta = {}; for (let i = 0; i < parts.length; i++) { switch (i) { case 0: meta.year = parseInt(parts[i]); break; case 1: meta.month = parseInt(parts[i]); break; case 2: meta.day = parseInt(parts[i]); break; case 3: meta.format = parts[i]; break; case 4: meta.filmstock = parts[i].split('-').join(' '); break; case 5: meta.location = parts[i].split('-').join(' '); break; case 6: meta.description = parts[i].split('-').join(' '); break; } } meta.original = halves[1]; return meta; } async createPhoto(image, meta) { const hash = await hash_1.Hashes.fileHash(image); const dimensions = await this.getImageDimensions(image); const now = Date.now(); const latlng = await this.geocode.query(meta.location); return { id: (0, uuid_1.v4)(), name: (0, path_1.basename)(image), description: meta.description, original: meta.original, hash, width: dimensions.width, height: dimensions.height, format: meta.format, filmstock: meta.filmstock, location: meta.location, latitude: latlng === null ? null : latlng.latitude, longitude: latlng === null ? null : latlng.longitude, discovered: now, updated: now, created: +new Date(meta.year, meta.month, meta.day), score: this.score }; } async upload(image) { const name = (0, path_1.basename)(image); return this.s3.createFromPath(image, name); } async move(image) { const name = (0, path_1.basename)(image); const dest = (0, path_1.join)(this.photos, name); try { await (0, promises_1.rename)(image, dest); this.log.info(`Moved image ${name} to outbox`); } catch (err) { this.log.error(`Error moving image`, err); } } async mktemp(prefix = 'tmp') { const uniqueId = (0, crypto_1.randomBytes)(16).toString('hex'); const tempFilePath = (0, path_1.join)(this.tmp, `${prefix}-${uniqueId}`); try { await (0, promises_1.writeFile)(tempFilePath, '', { flag: 'wx' }); return tempFilePath; } catch (err) { if (err.code === 'EEXIST') { return this.mktemp(prefix); } else { throw err; } } } isOnlyNumbers(str) { return /^[0-9]+$/.test(str); } } new Generate(); //# sourceMappingURL=generate.js.map