Combine parsed metadata object into single photo record. Maybe move dimensions code out of the for loop into that async
This commit is contained in:
parent
175dca1670
commit
606df7a9dc
25
README.md
25
README.md
|
@ -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.
|
||||
|
||||
```bash
|
||||
npm run generate
|
||||
bash scripts/generate.sh
|
||||
```
|
||||
|
||||
### Build Site
|
||||
|
@ -23,5 +23,26 @@ npm run generate
|
|||
This command will build the website using the sqlite + photo data available at the time it is run.
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
bash scripts/build.sh
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
|
@ -19,6 +19,27 @@ 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;
|
||||
module.exports = { DB };
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"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"}
|
||||
{"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"}
|
|
@ -6,6 +6,7 @@ const promises_1 = require("fs/promises");
|
|||
const path_1 = require("path");
|
||||
const util_1 = require("util");
|
||||
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");
|
||||
|
@ -32,6 +33,7 @@ class Generate {
|
|||
let filename;
|
||||
let meta;
|
||||
let dimensions;
|
||||
let photo;
|
||||
try {
|
||||
inbox = await (0, promises_1.realpath)(this.inbox);
|
||||
}
|
||||
|
@ -69,6 +71,7 @@ class Generate {
|
|||
dimensions = await this.getImageDimensions(image);
|
||||
meta.width = dimensions.width;
|
||||
meta.height = dimensions.height;
|
||||
photo = await this.createPhoto(image, meta);
|
||||
console.dir(meta);
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +113,7 @@ class Generate {
|
|||
//location
|
||||
//description
|
||||
//original
|
||||
//2024_12_02_35mm_Kodak-Gold-200_Somerville-MA_Walk-with-Charlie#000061280009.tif
|
||||
//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('_');
|
||||
|
@ -143,6 +146,21 @@ class Generate {
|
|||
meta.original = halves[1];
|
||||
return meta;
|
||||
}
|
||||
async createPhoto(image, meta) {
|
||||
const hash = await hash_1.Hashes.fileHash(image);
|
||||
return {
|
||||
name: (0, path_1.basename)(image),
|
||||
original: meta.original,
|
||||
hash,
|
||||
width: meta.width,
|
||||
height: meta.height,
|
||||
format: meta.format,
|
||||
filmstock: meta.filmstock,
|
||||
location: meta.location,
|
||||
discovered: Date.now(),
|
||||
created: +new Date(meta.year, meta.month, meta.day)
|
||||
};
|
||||
}
|
||||
}
|
||||
new Generate();
|
||||
//# sourceMappingURL=generate.js.map
|
File diff suppressed because one or more lines are too long
|
@ -1,9 +1,10 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.hash = hash;
|
||||
const fs_1 = require("fs");
|
||||
exports.Hashes = void 0;
|
||||
const crypto_1 = require("crypto");
|
||||
function hash(path) {
|
||||
const fs_1 = require("fs");
|
||||
class Hashes {
|
||||
static async fileHash(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hashSum = (0, crypto_1.createHash)('sha256');
|
||||
const stream = (0, fs_1.createReadStream)(path);
|
||||
|
@ -11,6 +12,12 @@ function hash(path) {
|
|||
stream.on('data', (chunk) => hashSum.update(chunk));
|
||||
stream.on('end', () => resolve(hashSum.digest('hex')));
|
||||
});
|
||||
}
|
||||
static stringHash(str) {
|
||||
const sha = (0, crypto_1.createHash)('sha256').update(str);
|
||||
return sha.digest('hex');
|
||||
}
|
||||
}
|
||||
module.exports = { hash };
|
||||
exports.Hashes = Hashes;
|
||||
module.exports = { Hashes };
|
||||
//# sourceMappingURL=index.js.map
|
|
@ -1 +1 @@
|
|||
{"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"}
|
||||
{"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"}
|
|
@ -5,10 +5,11 @@ CREATE TABLE IF NOT EXISTS photos (
|
|||
width INTEGER,
|
||||
height INTEGER,
|
||||
filmstock TEXT,
|
||||
location TEXT,
|
||||
discovered INTEGER,
|
||||
created INTEGER,
|
||||
updated INTEGER,
|
||||
posted INTEGER DEFAULT 0,
|
||||
bsky INTEGER DEFAULT 0,
|
||||
score INTEGER DEFAULT 0,
|
||||
deleted INTEGER DEFAULT 0
|
||||
);
|
||||
|
|
|
@ -5,13 +5,20 @@ import type { Logger } from 'winston';
|
|||
import { Database } from 'sqlite3';
|
||||
import { envString } from '../env';
|
||||
|
||||
interface Photos {
|
||||
interface Photo {
|
||||
name : string;
|
||||
original? : string;
|
||||
hash : string;
|
||||
width : number;
|
||||
height : number;
|
||||
discovered ?: number;
|
||||
posted? : boolean;
|
||||
format? : string;
|
||||
filmstock? : string;
|
||||
location? : string;
|
||||
discovered ? : number;
|
||||
created? : number;
|
||||
updated? : number;
|
||||
bsky? : boolean;
|
||||
deleted? : boolean;
|
||||
score? : number;
|
||||
}
|
||||
|
||||
|
@ -33,7 +40,27 @@ 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 };
|
||||
export type { Photos };
|
||||
export type { Photo };
|
|
@ -5,10 +5,11 @@ import { readFile, readdir, realpath } from 'fs/promises';
|
|||
import { join, basename } from 'path';
|
||||
import { promisify } from 'util';
|
||||
import { Shell } from './shell';
|
||||
import { hash } from './hash';
|
||||
import { Hashes } from './hash';
|
||||
import { Files3 } from './files3'
|
||||
import { envString } from './env';
|
||||
import { DB } from './db';
|
||||
import type { Photo } from './db';
|
||||
|
||||
const sizeOf = promisify(require('image-size'));
|
||||
|
||||
|
@ -55,6 +56,7 @@ class Generate {
|
|||
let filename : string;
|
||||
let meta : Metadata;
|
||||
let dimensions : any;
|
||||
let photo : Photo;
|
||||
|
||||
try {
|
||||
inbox = await realpath(this.inbox);
|
||||
|
@ -96,6 +98,9 @@ class Generate {
|
|||
dimensions = await this.getImageDimensions(image);
|
||||
meta.width = dimensions.width;
|
||||
meta.height = dimensions.height;
|
||||
|
||||
photo = await this.createPhoto(image, meta);
|
||||
|
||||
console.dir(meta)
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +146,7 @@ class Generate {
|
|||
//description
|
||||
//original
|
||||
|
||||
//2024_12_02_35mm_Kodak-Gold-200_Somerville-MA_Walk-with-Charlie#000061280009.tif
|
||||
//2024_12_02_35mm_Kodak-Gold-200_Somerville-MA_Walk-towards-Harvard-Square#000061280009.tif
|
||||
|
||||
private parseFilename (filename : string) : Metadata {
|
||||
const halves : string[] = filename.split('#')
|
||||
|
@ -175,6 +180,22 @@ class Generate {
|
|||
meta.original = halves[1];
|
||||
return meta;
|
||||
}
|
||||
|
||||
private async createPhoto (image : string, meta : Metadata) : Promise<Photo> {
|
||||
const hash : string = await Hashes.fileHash(image);
|
||||
return {
|
||||
name : basename(image),
|
||||
original: meta.original,
|
||||
hash,
|
||||
width : meta.width,
|
||||
height : meta.height,
|
||||
format : meta.format,
|
||||
filmstock : meta.filmstock,
|
||||
location : meta.location,
|
||||
discovered : Date.now(),
|
||||
created : + new Date(meta.year, meta.month, meta.day)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Generate();
|
|
@ -1,7 +1,8 @@
|
|||
import { createReadStream } from 'fs';
|
||||
import { createHash, Hash } from 'crypto';
|
||||
import { createReadStream } from 'fs';
|
||||
|
||||
export function hash (path : string) : Promise<string> {
|
||||
export class Hashes {
|
||||
static async fileHash (path : string) : Promise<string> {
|
||||
return new Promise((resolve : Function, reject : Function) => {
|
||||
const hashSum : Hash = createHash('sha256');
|
||||
const stream : any = createReadStream(path);
|
||||
|
@ -9,6 +10,12 @@ export function hash (path : string) : Promise<string> {
|
|||
stream.on('data', (chunk : Buffer) => hashSum.update(chunk));
|
||||
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 = { hash };
|
||||
module.exports = { Hashes };
|
Loading…
Reference in New Issue