Compare commits

..

No commits in common. "8915c9e1c627eef66e51ce2aacead2646169cf9d" and "175dca167002fccb759ab0fa676a512a3275b7fa" have entirely different histories.

11 changed files with 44 additions and 158 deletions

View File

@ -15,7 +15,7 @@ Create a `.env` file by copying the `default.env` file and modifying the values
The following command will sync the data with s3, generate metadata for each new photo and produce all required thumbnails required for the site. The following command will sync the data with s3, generate metadata for each new photo and produce all required thumbnails required for the site.
```bash ```bash
bash scripts/generate.sh npm run generate
``` ```
### Build Site ### Build Site
@ -23,26 +23,5 @@ bash scripts/generate.sh
This command will build the website using the sqlite + photo data available at the time it is run. This command will build the website using the sqlite + photo data available at the time it is run.
```bash ```bash
bash scripts/build.sh npm run build
```
### File Naming
Photos should be named with relavent metadata in the filename so that the scripts can parse out the data.
The underscore character (`_`) is the delimiter between elements and dash (`-`) should be used in place of spaces.
The hash character (`#`) should be used to split between the metadata filename and the original file, so that it can be searched for if needed.
Elements are expected in the following order:
1. year
1. month
1. day
1. format
1. filmstock
1. location
1. description
1. original
```
2024_12_02_35mm_Kodak-Gold-200_Somerville-MA_Walk-towards-Harvard-Square#000061280009.tif
``` ```

21
dist/db/index.js vendored
View File

@ -19,27 +19,6 @@ class DB {
}); });
}); });
} }
//CASE WHEN LOWER(active) = 'true' THEN 1 ELSE 0 END AS active_bool
async create(photo) {
const keys = Object.keys(photo);
const query = `INSERT INTO photos (${keys.join(',')}) VALUES (${keys.map(el => '?').join(',')});`;
const values = [];
for (let key of keys) {
if (typeof photo[key] === 'boolean') {
values.push(photo[key] ? 1 : 0);
}
else {
values.push(photo[key]);
}
}
try {
await this.run(query, values);
this.log.info(`Inserted new photo`);
}
catch (err) {
this.log.error(`Error inserting record into photos`, err);
}
}
} }
exports.DB = DB; exports.DB = DB;
module.exports = { 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;IACzD,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;IAEF,mEAAmE;IAC3D,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,KAAa,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1C,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,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,oCAAoC,EAAE,GAAG,CAAC,CAAC;QAC3D,CAAC;IACF,CAAC;CAED;AAtCD,gBAsCC;AAED,MAAM,CAAC,OAAO,GAAG,EAAE,EAAE,EAAE,CAAC"} {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/db/index.ts"],"names":[],"mappings":";;;AAAA,yBAAuB;AAEvB,gCAAmC;AAEnC,qCAAmC;AACnC,gCAAmC;AAYnC,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;IACzD,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;CAED;AAlBD,gBAkBC;AAED,MAAM,CAAC,OAAO,GAAG,EAAE,EAAE,EAAE,CAAC"}

25
dist/generate.js vendored
View File

@ -6,7 +6,6 @@ const promises_1 = require("fs/promises");
const path_1 = require("path"); const path_1 = require("path");
const util_1 = require("util"); const util_1 = require("util");
const shell_1 = require("./shell"); const shell_1 = require("./shell");
const hash_1 = require("./hash");
const files3_1 = require("./files3"); const files3_1 = require("./files3");
const env_1 = require("./env"); const env_1 = require("./env");
const db_1 = require("./db"); const db_1 = require("./db");
@ -32,7 +31,7 @@ class Generate {
let images; let images;
let filename; let filename;
let meta; let meta;
let photo; let dimensions;
try { try {
inbox = await (0, promises_1.realpath)(this.inbox); inbox = await (0, promises_1.realpath)(this.inbox);
} }
@ -67,7 +66,9 @@ class Generate {
this.log.info(image); this.log.info(image);
filename = (0, path_1.basename)(image); filename = (0, path_1.basename)(image);
meta = this.parseFilename(filename); meta = this.parseFilename(filename);
photo = await this.createPhoto(image, meta); dimensions = await this.getImageDimensions(image);
meta.width = dimensions.width;
meta.height = dimensions.height;
console.dir(meta); console.dir(meta);
} }
} }
@ -109,7 +110,7 @@ class Generate {
//location //location
//description //description
//original //original
//2024_12_02_35mm_Kodak-Gold-200_Somerville-MA_Walk-towards-Harvard-Square#000061280009.tif //2024_12_02_35mm_Kodak-Gold-200_Somerville-MA_Walk-with-Charlie#000061280009.tif
parseFilename(filename) { parseFilename(filename) {
const halves = filename.split('#'); const halves = filename.split('#');
const parts = halves[0].split('_'); const parts = halves[0].split('_');
@ -142,22 +143,6 @@ class Generate {
meta.original = halves[1]; meta.original = halves[1];
return meta; return meta;
} }
async createPhoto(image, meta) {
const hash = await hash_1.Hashes.fileHash(image);
const dimensions = await this.getImageDimensions(image);
return {
name: (0, path_1.basename)(image),
original: meta.original,
hash,
width: dimensions.width,
height: dimensions.height,
format: meta.format,
filmstock: meta.filmstock,
location: meta.location,
discovered: Date.now(),
created: +new Date(meta.year, meta.month, meta.day)
};
}
} }
new Generate(); new Generate();
//# sourceMappingURL=generate.js.map //# sourceMappingURL=generate.js.map

File diff suppressed because one or more lines are too long

29
dist/hash/index.js vendored
View File

@ -1,23 +1,16 @@
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.Hashes = void 0; exports.hash = hash;
const crypto_1 = require("crypto");
const fs_1 = require("fs"); const fs_1 = require("fs");
class Hashes { const crypto_1 = require("crypto");
static async fileHash(path) { function hash(path) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const hashSum = (0, crypto_1.createHash)('sha256'); const hashSum = (0, crypto_1.createHash)('sha256');
const stream = (0, fs_1.createReadStream)(path); const stream = (0, fs_1.createReadStream)(path);
stream.on('error', (err) => reject(err)); stream.on('error', (err) => reject(err));
stream.on('data', (chunk) => hashSum.update(chunk)); stream.on('data', (chunk) => hashSum.update(chunk));
stream.on('end', () => resolve(hashSum.digest('hex'))); stream.on('end', () => resolve(hashSum.digest('hex')));
}); });
}
static stringHash(str) {
const sha = (0, crypto_1.createHash)('sha256').update(str);
return sha.digest('hex');
}
} }
exports.Hashes = Hashes; module.exports = { hash };
module.exports = { Hashes };
//# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map

View File

@ -1 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hash/index.ts"],"names":[],"mappings":";;;AAAA,mCAA0C;AAC1C,2BAAsC;AAEtC,MAAa,MAAM;IAClB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAE,IAAa;QACnC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAkB,EAAE,MAAiB,EAAE,EAAE;YAC5D,MAAM,OAAO,GAAU,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC;YAC5C,MAAM,MAAM,GAAS,IAAA,qBAAgB,EAAC,IAAI,CAAC,CAAC;YAC5C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9C,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YAC7D,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,UAAU,CAAE,GAAY;QAC9B,MAAM,GAAG,GAAU,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpD,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;CACD;AAfD,wBAeC;AAED,MAAM,CAAC,OAAO,GAAG,EAAE,MAAM,EAAE,CAAC"} {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hash/index.ts"],"names":[],"mappings":";;AAGA,oBAQC;AAXD,2BAAsC;AACtC,mCAA0C;AAE1C,SAAgB,IAAI,CAAE,IAAa;IAClC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAkB,EAAE,MAAiB,EAAE,EAAE;QAC5D,MAAM,OAAO,GAAU,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAS,IAAA,qBAAgB,EAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7D,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,OAAO,GAAG,EAAE,IAAI,EAAE,CAAC"}

View File

@ -5,11 +5,10 @@ CREATE TABLE IF NOT EXISTS photos (
width INTEGER, width INTEGER,
height INTEGER, height INTEGER,
filmstock TEXT, filmstock TEXT,
location TEXT,
discovered INTEGER, discovered INTEGER,
created INTEGER, created INTEGER,
updated INTEGER, updated INTEGER,
bsky INTEGER DEFAULT 0, posted INTEGER DEFAULT 0,
score INTEGER DEFAULT 0, score INTEGER DEFAULT 0,
deleted INTEGER DEFAULT 0 deleted INTEGER DEFAULT 0
); );

View File

@ -5,20 +5,13 @@ import type { Logger } from 'winston';
import { Database } from 'sqlite3'; import { Database } from 'sqlite3';
import { envString } from '../env'; import { envString } from '../env';
interface Photo { interface Photos {
name : string; name : string;
original? : string;
hash : string; hash : string;
width : number; width : number;
height : number; height : number;
format? : string; discovered ?: number;
filmstock? : string; posted? : boolean;
location? : string;
discovered ? : number;
created? : number;
updated? : number;
bsky? : boolean;
deleted? : boolean;
score? : number; score? : number;
} }
@ -40,27 +33,7 @@ export class DB {
}); });
} }
//CASE WHEN LOWER(active) = 'true' THEN 1 ELSE 0 END AS active_bool
public async create (photo : Photo) {
const keys : string[] = Object.keys(photo);
const query : string = `INSERT INTO photos (${keys.join(',')}) VALUES (${keys.map(el=>'?').join(',')});`;
const values : any[] = [];
for (let key of keys) {
if (typeof (photo as any)[key] === 'boolean') {
values.push((photo as any)[key] ? 1 : 0);
} else {
values.push((photo as any)[key]);
}
}
try {
await this.run(query, values);
this.log.info(`Inserted new photo`)
} catch (err) {
this.log.error(`Error inserting record into photos`, err);
}
}
} }
module.exports = { DB }; module.exports = { DB };
export type { Photo }; export type { Photos };

View File

@ -5,11 +5,10 @@ import { readFile, readdir, realpath } from 'fs/promises';
import { join, basename } from 'path'; import { join, basename } from 'path';
import { promisify } from 'util'; import { promisify } from 'util';
import { Shell } from './shell'; import { Shell } from './shell';
import { Hashes } from './hash'; import { hash } from './hash';
import { Files3 } from './files3' import { Files3 } from './files3'
import { envString } from './env'; import { envString } from './env';
import { DB } from './db'; import { DB } from './db';
import type { Photo } from './db';
const sizeOf = promisify(require('image-size')); const sizeOf = promisify(require('image-size'));
@ -21,6 +20,8 @@ interface Metadata {
filmstock?: string; filmstock?: string;
location? : string; location? : string;
description? : string; description? : string;
width? : number;
height? : number;
original?: string; original?: string;
} }
@ -53,7 +54,7 @@ class Generate {
let images : string[]; let images : string[];
let filename : string; let filename : string;
let meta : Metadata; let meta : Metadata;
let photo : Photo; let dimensions : any;
try { try {
inbox = await realpath(this.inbox); inbox = await realpath(this.inbox);
@ -92,8 +93,9 @@ class Generate {
this.log.info(image); this.log.info(image);
filename = basename(image); filename = basename(image);
meta = this.parseFilename(filename); meta = this.parseFilename(filename);
photo = await this.createPhoto(image, meta); dimensions = await this.getImageDimensions(image);
meta.width = dimensions.width;
meta.height = dimensions.height;
console.dir(meta) console.dir(meta)
} }
} }
@ -139,7 +141,7 @@ class Generate {
//description //description
//original //original
//2024_12_02_35mm_Kodak-Gold-200_Somerville-MA_Walk-towards-Harvard-Square#000061280009.tif //2024_12_02_35mm_Kodak-Gold-200_Somerville-MA_Walk-with-Charlie#000061280009.tif
private parseFilename (filename : string) : Metadata { private parseFilename (filename : string) : Metadata {
const halves : string[] = filename.split('#') const halves : string[] = filename.split('#')
@ -173,23 +175,6 @@ class Generate {
meta.original = halves[1]; meta.original = halves[1];
return meta; return meta;
} }
private async createPhoto (image : string, meta : Metadata) : Promise<Photo> {
const hash : string = await Hashes.fileHash(image);
const dimensions : any = await this.getImageDimensions(image);
return {
name : basename(image),
original: meta.original,
hash,
width : dimensions.width,
height : dimensions.height,
format : meta.format,
filmstock : meta.filmstock,
location : meta.location,
discovered : Date.now(),
created : + new Date(meta.year, meta.month, meta.day)
}
}
} }
new Generate(); new Generate();

View File

@ -1,21 +1,14 @@
import { createHash, Hash } from 'crypto';
import { createReadStream } from 'fs'; import { createReadStream } from 'fs';
import { createHash, Hash } from 'crypto';
export class Hashes { export function hash (path : string) : Promise<string> {
static async fileHash (path : string) : Promise<string> { return new Promise((resolve : Function, reject : Function) => {
return new Promise((resolve : Function, reject : Function) => { const hashSum : Hash = createHash('sha256');
const hashSum : Hash = createHash('sha256'); const stream : any = createReadStream(path);
const stream : any = createReadStream(path); stream.on('error', (err : Error) => reject(err));
stream.on('error', (err : Error) => reject(err)); stream.on('data', (chunk : Buffer) => hashSum.update(chunk));
stream.on('data', (chunk : Buffer) => hashSum.update(chunk)); stream.on('end', () => resolve(hashSum.digest('hex')));
stream.on('end', () => resolve(hashSum.digest('hex'))); });
});
}
static stringHash (str : string) : string {
const sha : Hash = createHash('sha256').update(str);
return sha.digest('hex');
}
} }
module.exports = { Hashes }; module.exports = { hash };