Generate done.

This commit is contained in:
mmcwilliams 2024-12-09 15:15:27 -05:00
parent 222a3c2ddc
commit ed022f7a55
24 changed files with 632 additions and 71 deletions

View File

@ -9,3 +9,4 @@ S3_ENDPOINT=""
UMAMI=""
DB="data/site.db"
ARTIST="Unknown"
GEOCODE_API_KEY=""

70
dist/db/index.js vendored
View File

@ -9,7 +9,7 @@ class DB {
constructor() {
this.log = (0, log_1.createLog)('db');
this.db = new sqlite3_1.Database((0, env_1.envString)('DB', 'data/site.db'));
this.db.run('PRAGMA journal_mode = WAL;');
//this.db.run( 'PRAGMA journal_mode = WAL;');
}
async run(query, args = null) {
return new Promise((resolve, reject) => {
@ -20,6 +20,15 @@ class DB {
});
});
}
async all(query, args = null) {
return new Promise((resolve, reject) => {
return this.db.all(query, args, (err, rows) => {
if (err)
return reject(err);
return resolve(rows);
});
});
}
toBoolean(val) {
return val === 1 ? true : false;
}
@ -41,12 +50,69 @@ class DB {
}
try {
await this.run(query, values);
this.log.info(`Inserted new photo`);
this.log.info(`Inserted new photo ${photo.name}`);
}
catch (err) {
throw err;
}
}
async existsName(name) {
const query = `SELECT id FROM photos WHERE name = ? LIMIT 1;`;
let rows = [];
let exists = false;
try {
rows = await this.all(query, [name]);
}
catch (err) {
this.log.error(`Error finding photo by name ${name}`, err);
}
if (rows.length > 0) {
exists = true;
}
return exists;
}
async existsHash(hash) {
const query = `SELECT id FROM photos WHERE hash = ? LIMIT 1;`;
let rows = [];
let exists = false;
try {
rows = await this.all(query, [hash]);
}
catch (err) {
this.log.error(`Error finding photo by hash ${hash}`, err);
}
if (rows.length > 0) {
exists = true;
}
return exists;
}
async cacheLocation(location, latlng) {
const query = `INSERT OR IGNORE INTO geocode (location, latitude, longitude) VALUES (?, ?, ?);`;
try {
await this.run(query, [location, latlng.latitude, latlng.longitude]);
}
catch (err) {
//ignore
}
}
async getLocation(location) {
const query = `SELECT latitude, longitude FROM geocode WHERE location = ? LIMIT 1;`;
let rows = [];
let res = null;
try {
rows = await this.all(query, [location]);
}
catch (err) {
//ignore
}
if (rows.length > 0) {
res = {
latitude: rows[0].latitude,
longitude: rows[0].longitude
};
}
return res;
}
}
exports.DB = DB;
module.exports = { DB };

View File

@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":";;;AAAA,yBAAuB;AAEvB,gCAAmC;AAEnC,qCAAmC;AACnC,gCAAmC;AAmBnC,MAAa,EAAE;IAId;QACC,IAAI,CAAC,GAAG,GAAG,IAAA,eAAS,EAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,EAAE,GAAG,IAAI,kBAAQ,CAAC,IAAA,eAAS,EAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC;QACxD,IAAI,CAAC,EAAE,CAAC,GAAG,CAAE,4BAA4B,CAAC,CAAC;IAC5C,CAAC;IAEO,KAAK,CAAC,GAAG,CAAE,KAAc,EAAE,OAAe,IAAI;QACrD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAkB,EAAE,MAAiB,EAAE,EAAE;YAC5D,OAAO,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,GAAW,EAAE,IAAY,EAAE,EAAE;gBAC7D,IAAI,GAAG;oBAAE,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC5B,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;YACtB,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;IACJ,CAAC;IAEO,SAAS,CAAE,GAAY;QAC9B,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;IACjC,CAAC;IAEO,WAAW,CAAE,GAAa;QACjC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,mEAAmE;IAC5D,KAAK,CAAC,MAAM,CAAE,KAAa;QACjC,MAAM,IAAI,GAAc,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAY,uBAAuB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,IAAI,CAAC,GAAG,CAAC,EAAE,CAAA,EAAE,CAAA,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;QACzG,MAAM,MAAM,GAAW,EAAE,CAAC;QAC1B,KAAK,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;YACtB,IAAI,OAAQ,KAAa,CAAC,GAAG,CAAC,KAAK,SAAS,EAAE,CAAC;gBAC9C,MAAM,CAAC,IAAI,CAAE,IAAI,CAAC,WAAW,CAAG,KAAa,CAAC,GAAG,CAAC,CAAE,CAAC,CAAC;YACvD,CAAC;iBAAM,CAAC;gBACP,MAAM,CAAC,IAAI,CAAE,KAAa,CAAC,GAAG,CAAC,CAAC,CAAC;YAClC,CAAC;QACF,CAAC;QACD,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC9B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;QACpC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,MAAM,GAAG,CAAC;QACX,CAAC;IACF,CAAC;CAED;AA/CD,gBA+CC;AAED,MAAM,CAAC,OAAO,GAAG,EAAE,EAAE,EAAE,CAAC"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":"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"}

File diff suppressed because one or more lines are too long

122
dist/generate.js vendored
View File

@ -1,25 +1,43 @@
"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', 'mmcwilliamsphotos'), true);
this.s3 = new files3_1.Files3((0, env_1.envString)('S3_BUCKET', 's3bucket'), true);
this.geocode = new geocode_1.Geocode();
this.generate();
this.score = args.score;
}
async generate() {
//check version
@ -33,6 +51,7 @@ class Generate {
let filename;
let meta;
let photo;
let exif;
try {
inbox = await (0, promises_1.realpath)(this.inbox);
}
@ -74,6 +93,19 @@ class Generate {
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);
}
@ -88,11 +120,56 @@ class Generate {
this.log.error(`Error uploading image`, err);
continue;
}
//await this.move(image);
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);
}
}
async img(file, exif) {
const cmd = ['bash', 'scripts/img.sh', file, exif];
//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}
-ImageTitle=${photo.name}
-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();
@ -166,7 +243,9 @@ class Generate {
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),
original: meta.original,
hash,
@ -175,15 +254,48 @@ class Generate {
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)
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 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

File diff suppressed because one or more lines are too long

74
dist/geocode/index.js vendored Normal file
View File

@ -0,0 +1,74 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Geocode = void 0;
require("dotenv/config");
const url_1 = require("url");
const node_fetch_1 = __importDefault(require("node-fetch"));
const log_1 = require("../log");
const env_1 = require("../env");
const db_1 = require("../db");
class Geocode {
constructor() {
this.log = (0, log_1.createLog)('geocode');
this.baseUrl = 'https://geocode.maps.co/search';
this.apiKey = (0, env_1.envString)('GEOCODE_API_KEY', null);
this.db = new db_1.DB();
}
async query(location) {
let res = await this.db.getLocation(location);
if (res === null) {
res = await this.api(location);
}
return res;
}
toLatLng(obj) {
return {
latitude: parseFloat(obj.lat),
longitude: parseFloat(obj.lon)
};
}
//https://geocode.maps.co/search?q=&api_key=675738aa38619885468998kehbf6458
async api(location) {
const url = new url_1.URL(this.baseUrl);
let response;
let json;
let res = null;
url.searchParams.append('q', location);
this.log.info(`Querying API: ${url.href}`);
url.searchParams.append('api_key', this.apiKey);
await this.delay(1000); //rate limit to 1/sec
try {
response = await (0, node_fetch_1.default)(url.href);
}
catch (err) {
this.log.error('Error getting response', err);
return null;
}
try {
json = await response.json();
}
catch (err) {
this.log.error('Error parsing json', err);
return null;
}
if (json.length < 1) {
return null;
}
res = this.toLatLng(json[0]);
await this.db.cacheLocation(location, res);
return res;
}
async delay(ms) {
return new Promise((resolve, reject) => {
return setTimeout(resolve, ms);
});
}
cache(location, latitude, longitude) {
}
}
exports.Geocode = Geocode;
module.exports = { Geocode };
//# sourceMappingURL=index.js.map

1
dist/geocode/index.js.map vendored Normal file
View File

@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/geocode/index.ts"],"names":[],"mappings":";;;;;;AAAA,yBAAuB;AAEvB,6BAA0B;AAC1B,4DAA+B;AAG/B,gCAAmC;AAEnC,gCAAmC;AACnC,8BAA2B;AAG3B,MAAa,OAAO;IAMnB;QALQ,QAAG,GAAY,IAAA,eAAS,EAAC,SAAS,CAAC,CAAC;QACpC,YAAO,GAAY,gCAAgC,CAAC;QACpD,WAAM,GAAY,IAAA,eAAS,EAAC,iBAAiB,EAAE,IAAI,CAAC,CAAC;QACrD,OAAE,GAAQ,IAAI,OAAE,EAAE,CAAC;IAI3B,CAAC;IAEM,KAAK,CAAC,KAAK,CAAE,QAAiB;QACpC,IAAI,GAAG,GAAY,MAAM,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QACvD,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YAClB,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;QACD,OAAO,GAAG,CAAC;IACZ,CAAC;IAEO,QAAQ,CAAE,GAAS;QAC1B,OAAO;YACN,QAAQ,EAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;YAC9B,SAAS,EAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;SAC/B,CAAC;IACH,CAAC;IAED,2EAA2E;IACnE,KAAK,CAAC,GAAG,CAAE,QAAiB;QACnC,MAAM,GAAG,GAAS,IAAI,SAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxC,IAAI,QAAmB,CAAC;QACxB,IAAI,IAAU,CAAC;QACf,IAAI,GAAG,GAAY,IAAI,CAAC;QAExB,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAEvC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3C,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAEhD,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,qBAAqB;QAE7C,IAAI,CAAC;YACJ,QAAQ,GAAG,MAAM,IAAA,oBAAK,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;YAC9C,OAAO,IAAI,CAAC;QACb,CAAC;QAED,IAAI,CAAC;YACJ,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;YAC1C,OAAO,IAAI,CAAC;QACb,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC;QACb,CAAC;QAED,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAE7B,MAAM,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QAE3C,OAAO,GAAG,CAAC;IAEZ,CAAC;IAEO,KAAK,CAAC,KAAK,CAAE,EAAW;QAC/B,OAAO,IAAI,OAAO,CAAE,CAAC,OAAkB,EAAE,MAAiB,EAAE,EAAE;YAC7D,OAAO,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACJ,CAAC;IAEO,KAAK,CAAE,QAAiB,EAAE,QAAiB,EAAE,SAAkB;IAEvE,CAAC;CACD;AA1ED,0BA0EC;AAED,MAAM,CAAC,OAAO,GAAG,EAAE,OAAO,EAAE,CAAC"}

6
dist/shell/index.js vendored
View File

@ -24,7 +24,8 @@ class Shell {
return new Promise((resolve, reject) => {
this.child = (0, child_process_1.spawn)(this.bin, this.args);
this.log.info(`Shell: ${this.bin} ${this.args.join(' ')}`);
this.child.stdout.on('data', (data) => {
this.child.stdout.on('data', (output) => {
const data = output.toString();
if (!this.silent)
this.log.info(data);
if (this.after !== null)
@ -33,7 +34,8 @@ class Shell {
this.stdio(data);
}
});
this.child.stderr.on('data', (data) => {
this.child.stderr.on('data', (output) => {
const data = output.toString();
if (!this.silent)
this.log.warn(data);
if (this.stderr !== null) {

View File

@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shell/index.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shell/index.ts"],"names":[],"mappings":"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"}

BIN
notes/exif_test.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

3
notes/exif_test.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/bash
exiftool -overwrite_original -@ exif_test.txt exif_test.jpg

7
notes/exif_test.txt Normal file
View File

@ -0,0 +1,7 @@
-Artist=Test Artist
-ImageTitle=A title that I selected
-ImageUniqueID=11111111111111
-ISOSpeed=400
-DateTimeOriginal=2024:12:08 12:00:00
-GPSLatitude=42.000000
-GPSLongitude=71.000000

View File

@ -1,8 +0,0 @@
-XPTitle=Title here
-XPSubject=Subject here
-XPComment=Comments here
-XPArtist=Test Artist
-XPImageTitle
-XPImageUniqueID
-XPISOSpeed
-XPDateTimeOriginal

158
package-lock.json generated
View File

@ -10,14 +10,16 @@
"license": "MIT",
"dependencies": {
"@atproto/api": "^0.13.18",
"argparse": "^2.0.1",
"aws-sdk": "^2.1692.0",
"dotenv": "^16.3.1",
"handlebars": "^4.7.8",
"handlebars-helpers": "^0.10.0",
"image-size": "^1.1.1",
"lodash": "^4.17.21",
"mime": "^4.0.1",
"mime-types": "^2.1.35",
"moment": "^2.30.1",
"node-fetch": "^2.7.0",
"s3-cli": "^0.13.0",
"s3-upload-stream": "^1.0.7",
"sqlite3": "^5.1.7",
@ -26,10 +28,13 @@
"winston": "^3.11.0"
},
"devDependencies": {
"@types/argparse": "^2.0.17",
"@types/handlebars-helpers": "^0.5.6",
"@types/lodash": "^4.14.202",
"@types/mime-types": "^2.1.4",
"@types/moment": "^2.13.0",
"@types/node": "^20.10.6",
"@types/node-fetch": "^2.6.11",
"@types/s3-upload-stream": "^1.0.7",
"@types/sqlite3": "^3.1.11",
"@types/triple-beam": "^1.3.5",
@ -188,6 +193,13 @@
"node": ">= 6"
}
},
"node_modules/@types/argparse": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-2.0.17.tgz",
"integrity": "sha512-fueJssTf+4dW4HODshEGkIZbkLKHzgu1FvCI4cTc/MKum/534Euo3SrN+ilq8xgyHnOjtmg33/hee8iXLRg1XA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/aws-sdk2-types": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@types/aws-sdk2-types/-/aws-sdk2-types-0.0.5.tgz",
@ -222,6 +234,17 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/moment": {
"version": "2.13.0",
"resolved": "https://registry.npmjs.org/@types/moment/-/moment-2.13.0.tgz",
"integrity": "sha512-DyuyYGpV6r+4Z1bUznLi/Y7HpGn4iQ4IVcGn8zrr1P4KotKLdH0sbK1TFR6RGyX6B+G8u83wCzL+bpawKU/hdQ==",
"deprecated": "This is a stub types definition for Moment (https://github.com/moment/moment). Moment provides its own type definitions, so you don't need @types/moment installed!",
"dev": true,
"license": "MIT",
"dependencies": {
"moment": "*"
}
},
"node_modules/@types/node": {
"version": "20.17.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz",
@ -232,6 +255,17 @@
"undici-types": "~6.19.2"
}
},
"node_modules/@types/node-fetch": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz",
"integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.0"
}
},
"node_modules/@types/s3-upload-stream": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/s3-upload-stream/-/s3-upload-stream-1.0.7.tgz",
@ -741,13 +775,10 @@
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/arr-diff": {
"version": "4.0.0",
@ -823,6 +854,13 @@
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true,
"license": "MIT"
},
"node_modules/atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
@ -1254,6 +1292,19 @@
"text-hex": "1.0.x"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/component-emitter": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
@ -1442,6 +1493,16 @@
"node": ">=0.10.0"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
@ -1785,6 +1846,21 @@
"node": ">=0.10.0"
}
},
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"dev": true,
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fragment-cache": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
@ -3065,21 +3141,6 @@
"node": ">=0.10.0"
}
},
"node_modules/mime": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/mime/-/mime-4.0.4.tgz",
"integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==",
"funding": [
"https://github.com/sponsors/broofa"
],
"license": "MIT",
"bin": {
"mime": "bin/cli.js"
},
"engines": {
"node": ">=16"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@ -3401,6 +3462,26 @@
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"license": "MIT"
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-gyp": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz",
@ -3864,6 +3945,15 @@
"node": ">= 0.10.0"
}
},
"node_modules/remarkable/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/repeat-element": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz",
@ -4837,6 +4927,12 @@
"node": ">=0.10.0"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/triple-beam": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
@ -5081,6 +5177,22 @@
"node": ">=0.10.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -17,10 +17,13 @@
"author": "Matthew McWilliams",
"license": "MIT",
"devDependencies": {
"@types/argparse": "^2.0.17",
"@types/handlebars-helpers": "^0.5.6",
"@types/lodash": "^4.14.202",
"@types/mime-types": "^2.1.4",
"@types/moment": "^2.13.0",
"@types/node": "^20.10.6",
"@types/node-fetch": "^2.6.11",
"@types/s3-upload-stream": "^1.0.7",
"@types/sqlite3": "^3.1.11",
"@types/triple-beam": "^1.3.5",
@ -30,14 +33,16 @@
},
"dependencies": {
"@atproto/api": "^0.13.18",
"argparse": "^2.0.1",
"aws-sdk": "^2.1692.0",
"dotenv": "^16.3.1",
"handlebars": "^4.7.8",
"handlebars-helpers": "^0.10.0",
"image-size": "^1.1.1",
"lodash": "^4.17.21",
"mime": "^4.0.1",
"mime-types": "^2.1.35",
"moment": "^2.30.1",
"node-fetch": "^2.7.0",
"s3-cli": "^0.13.0",
"s3-upload-stream": "^1.0.7",
"sqlite3": "^5.1.7",

View File

@ -2,5 +2,5 @@
set -e
node dist/generate
node dist/build
bash scripts/generate.sh
bash scripts/build.sh

View File

@ -6,6 +6,7 @@ source .env
rm -rf data/site.db
mkdir -p data
mkdir -p www
cat "sql/setup.sql" | sqlite3 "${DB}"

View File

@ -9,7 +9,7 @@ ID="${2}"
EXIF="${3}"
SIZES=(
"home:420"
"thumb:420"
"full:2000"
)
@ -26,9 +26,7 @@ for sizeRaw in ${SIZES[@]}; do
size=$(echo $sizeRaw | awk -F':' '{print $2}')
name=$(basename "${1}")
name=${name%.*}
output="${WWW}/img/${ID}_${size}.jpg"
output="${WWW}/img/${ID}_${sizeName}.jpg"
img "${1}" "${output}" "${size}"
exiftool -overwrite_original -@ "${EXIF}" "${output}"
done
mv "${1}" "${PHOTOS}/"

View File

@ -8,6 +8,8 @@ CREATE TABLE IF NOT EXISTS photos (
format TEXT,
filmstock TEXT,
location TEXT,
latitude REAL,
longitude REAL,
discovered INTEGER,
created INTEGER,
updated INTEGER,
@ -16,7 +18,8 @@ CREATE TABLE IF NOT EXISTS photos (
deleted INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS version (
id TEXT PRIMARY KEY,
updated INTEGER UNIQUE
)
CREATE TABLE IF NOT EXISTS geocode (
location TEXT PRIMARY KEY,
latitude REAL,
longitude REAL
);

View File

@ -5,6 +5,11 @@ import type { Logger } from 'winston';
import { Database } from 'sqlite3';
import { envString } from '../env';
interface LatLng {
latitude : number;
longitude : number;
}
interface Photo {
id : string;
name : string;
@ -15,6 +20,8 @@ interface Photo {
format? : string;
filmstock? : string;
location? : string;
latitude? : number;
longitude? : number;
discovered ? : number;
created? : number;
updated? : number;
@ -30,7 +37,7 @@ export class DB {
constructor () {
this.log = createLog('db');
this.db = new Database(envString('DB', 'data/site.db'));
this.db.run( 'PRAGMA journal_mode = WAL;');
//this.db.run( 'PRAGMA journal_mode = WAL;');
}
private async run (query : string, args : any[] = null) {
@ -109,7 +116,37 @@ export class DB {
return exists;
}
public async cacheLocation (location : string, latlng : LatLng) {
const query : string = `INSERT OR IGNORE INTO geocode (location, latitude, longitude) VALUES (?, ?, ?);`;
try {
await this.run(query, [location, latlng.latitude, latlng.longitude]);
} catch (err) {
//ignore
}
}
public async getLocation (location : string) : Promise<LatLng> {
const query : string = `SELECT latitude, longitude FROM geocode WHERE location = ? LIMIT 1;`;
let rows : any[] = [];
let res : LatLng = null;
try {
rows = await this.all(query, [location]);
} catch (err) {
//ignore
}
if (rows.length > 0) {
res = {
latitude : rows[0].latitude,
longitude : rows[0].longitude
}
}
return res;
}
}
module.exports = { DB };
export type { Photo };
export type { Photo, LatLng };

View File

@ -7,12 +7,15 @@ import { promisify } from 'util';
import { v4 as uuid } from 'uuid';
import { randomBytes } from 'crypto';
import { tmpdir } from 'os';
import moment from 'moment';
import { ArgumentParser } from 'argparse';
import { Shell } from './shell';
import { Hashes } from './hash';
import { Files3 } from './files3'
import { envString } from './env';
import { DB } from './db';
import type { Photo } from './db';
import type { Photo, LatLng } from './db';
import { Geocode } from './geocode';
const sizeOf = promisify(require('image-size'));
@ -35,14 +38,24 @@ class Generate {
private artist : string = envString('ARTIST', 'Unknown');
private s3 : Files3;
private db : DB;
private geocode : Geocode;
private tmp : string = tmpdir();
private score : number;
constructor () {
this.log = createLog('generate');
const parser = new ArgumentParser({
description: 'Generate script'
});
parser.add_argument('-s', '--score', { type : 'int', default : 0, help: 'Starting score' });
const args : any = parser.parse_args();
this.log.info(`Generating site: ${new Date()}`);
this.db = new DB();
this.s3 = new Files3(envString('S3_BUCKET', 's3bucket'), true);
this.geocode = new Geocode();
this.generate();
this.score = args.score;
}
private async generate () {
@ -59,6 +72,7 @@ class Generate {
let filename : string;
let meta : Metadata;
let photo : Photo;
let exif : string;
try {
inbox = await realpath(this.inbox);
@ -136,27 +150,60 @@ class Generate {
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
//ImageTitle
//ImageUniqueID
//ISOSpeed
//DateTimeOriginal
//Artist string
//ImageTitle string
//ImageUniqueID string
//ISO int16u[n]
//DateTimeOriginal string (YYYY:MM:DD HH:MM:SS)
//
//GPSLatitude rational64u[3]
//GPSLongitude
private async exif (photo : Photo) : Promise<string> {
const filePath : string = await this.mktemp('photosite_exif');
const created : string = moment.unix(photo.created / 1000).format('YYYY:MM:DD HH:mm:ss');
let exif : string = `-Artist=${this.artist}
-ImageTitle=${photo.name}
-ImageUniqueId=${photo.id}
-DateTimeOriginal=${created}`
const iso : number[] = 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 writeFile(filePath, exif, 'utf8');
} catch (err) {
this.log.error(`Error writing EXIF data`, err);
}
return filePath;
}
private async img (file : string, id : string, exif : string) {
const cmd : string[] = ['bash', 'scripts/img.sh', file, exif];
const cmd : string[] = ['bash', 'scripts/img.sh', file, id, exif];
const shell : Shell = new Shell(cmd);
try {
await shell.execute();
@ -235,6 +282,8 @@ class Generate {
const hash : string = await Hashes.fileHash(image);
const dimensions : any = await this.getImageDimensions(image);
const now : number = Date.now();
const latlng : LatLng = await this.geocode.query(meta.location);
return {
id : uuid(),
name : basename(image),
@ -245,9 +294,12 @@ class Generate {
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)
created : + new Date(meta.year, meta.month, meta.day),
score : this.score
}
}
@ -261,7 +313,7 @@ class Generate {
const dest : string = join(this.photos, name);
try {
await rename(image, dest);
//await rename(image, dest);
this.log.info(`Moved image ${name} to outbox`);
} catch (err) {
this.log.error(`Error moving image`, err);
@ -283,6 +335,10 @@ class Generate {
}
}
}
private isOnlyNumbers(str : string) : boolean {
return /^[0-9]+$/.test(str);
}
}
new Generate();

89
src/geocode/index.ts Normal file
View File

@ -0,0 +1,89 @@
import 'dotenv/config';
import { URL } from 'url';
import fetch from 'node-fetch';
import type { Response } from 'node-fetch';
import { createLog } from '../log';
import type { Logger } from 'winston';
import { envString } from '../env';
import { DB } from '../db';
import type { LatLng } from '../db';
export class Geocode {
private log : Logger = createLog('geocode');
private baseUrl : string = 'https://geocode.maps.co/search';
private apiKey : string = envString('GEOCODE_API_KEY', null);
private db : DB = new DB();
constructor () {
}
public async query (location : string) : Promise<LatLng> {
let res : LatLng = await this.db.getLocation(location);
if (res === null) {
res = await this.api(location);
}
return res;
}
private toLatLng (obj : any) : LatLng {
return {
latitude : parseFloat(obj.lat),
longitude : parseFloat(obj.lon)
};
}
//https://geocode.maps.co/search?q=&api_key=675738aa38619885468998kehbf6458
private async api (location : string) : Promise<LatLng> {
const url : URL = new URL(this.baseUrl);
let response : Response;
let json : any;
let res : LatLng = null;
url.searchParams.append('q', location);
this.log.info(`Querying API: ${url.href}`);
url.searchParams.append('api_key', this.apiKey);
await this.delay(1000); //rate limit to 1/sec
try {
response = await fetch(url.href);
} catch (err) {
this.log.error('Error getting response', err);
return null;
}
try {
json = await response.json();
} catch (err) {
this.log.error('Error parsing json', err);
return null;
}
if (json.length < 1) {
return null;
}
res = this.toLatLng(json[0]);
await this.db.cacheLocation(location, res);
return res;
}
private async delay (ms : number) {
return new Promise ((resolve : Function, reject : Function) => {
return setTimeout(resolve, ms);
});
}
private cache (location : string, latitude : number, longitude : number) {
}
}
module.exports = { Geocode };

View File

@ -31,7 +31,8 @@ export class Shell {
this.log.info(`Shell: ${this.bin} ${this.args.join(' ')}`);
this.child.stdout.on('data', (data : string) => {
this.child.stdout.on('data', (output : Object) => {
const data : string = output.toString();
if (!this.silent) this.log.info(data);
if (this.after !== null) this.lines.push(data);
if (this.stdio !== null) {
@ -39,7 +40,8 @@ export class Shell {
}
});
this.child.stderr.on('data', (data : string) => {
this.child.stderr.on('data', (output : Object) => {
const data : string = output.toString();
if (!this.silent) this.log.warn(data);
if (this.stderr !== null) {
this.stderr(data);