All work on generate project. Write to db. Start EXIF work

This commit is contained in:
mmcwilliams 2024-12-08 16:51:53 -05:00
parent 8915c9e1c6
commit 222a3c2ddc
14 changed files with 233 additions and 35 deletions

View File

@ -7,4 +7,5 @@ S3_ACCESS_SECRET=""
S3_BUCKET="" S3_BUCKET=""
S3_ENDPOINT="" S3_ENDPOINT=""
UMAMI="" UMAMI=""
DB="data/site.db" DB="data/site.db"
ARTIST="Unknown"

11
dist/db/index.js vendored
View File

@ -9,6 +9,7 @@ class DB {
constructor() { constructor() {
this.log = (0, log_1.createLog)('db'); this.log = (0, log_1.createLog)('db');
this.db = new sqlite3_1.Database((0, env_1.envString)('DB', 'data/site.db')); this.db = new sqlite3_1.Database((0, env_1.envString)('DB', 'data/site.db'));
this.db.run('PRAGMA journal_mode = WAL;');
} }
async run(query, args = null) { async run(query, args = null) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -19,6 +20,12 @@ class DB {
}); });
}); });
} }
toBoolean(val) {
return val === 1 ? true : false;
}
fromBoolean(val) {
return val ? 1 : 0;
}
//CASE WHEN LOWER(active) = 'true' THEN 1 ELSE 0 END AS active_bool //CASE WHEN LOWER(active) = 'true' THEN 1 ELSE 0 END AS active_bool
async create(photo) { async create(photo) {
const keys = Object.keys(photo); const keys = Object.keys(photo);
@ -26,7 +33,7 @@ class DB {
const values = []; const values = [];
for (let key of keys) { for (let key of keys) {
if (typeof photo[key] === 'boolean') { if (typeof photo[key] === 'boolean') {
values.push(photo[key] ? 1 : 0); values.push(this.fromBoolean(photo[key]));
} }
else { else {
values.push(photo[key]); values.push(photo[key]);
@ -37,7 +44,7 @@ class DB {
this.log.info(`Inserted new photo`); this.log.info(`Inserted new photo`);
} }
catch (err) { catch (err) {
this.log.error(`Error inserting record into photos`, err); throw err;
} }
} }
} }

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;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"}

14
dist/files3/index.js vendored
View File

@ -107,7 +107,7 @@ class Files3 {
async create(file, keyName) { async create(file, keyName) {
if (!this.writeable) if (!this.writeable)
return false; return false;
const id = await this.hashFile(file); const id = (0, uuid_1.v4)();
const ext = mime.extension(file.mimetype); const ext = mime.extension(file.mimetype);
const key = typeof keyName !== 'undefined' ? keyName : `${id}.${ext}`; const key = typeof keyName !== 'undefined' ? keyName : `${id}.${ext}`;
const webPath = (0, path_1.join)('/files/', this.bucket, key); const webPath = (0, path_1.join)('/files/', this.bucket, key);
@ -132,9 +132,9 @@ class Files3 {
record.hash = this.hash(file.buffer); record.hash = this.hash(file.buffer);
record.size = file.buffer.byteLength; record.size = file.buffer.byteLength;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
return this.s3.putObject(params, function (err, data) { return this.s3.putObject(params, (err, data) => {
if (err) { if (err) {
this.log.error(err); this.log.error('create', err);
return reject(err); return reject(err);
} }
else { else {
@ -321,6 +321,7 @@ class Files3 {
upload.concurrentParts(5); upload.concurrentParts(5);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
upload.on('error', (err) => { upload.on('error', (err) => {
this.log.error('createStreamFromPath', err);
return reject(err); return reject(err);
}); });
upload.on('part', (details) => { upload.on('part', (details) => {
@ -351,6 +352,7 @@ class Files3 {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
return this.s3.getObject(params, (err, data) => { return this.s3.getObject(params, (err, data) => {
if (err) { if (err) {
this.log.error('read', err);
return reject(err); return reject(err);
} }
return resolve(data.Body); //buffer return resolve(data.Body); //buffer
@ -383,6 +385,7 @@ class Files3 {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
return this.s3.getSignedUrl('putObject', s3Params, (err, url) => { return this.s3.getSignedUrl('putObject', s3Params, (err, url) => {
if (err) { if (err) {
this.log.error('signedPutKey', err);
return reject(err); return reject(err);
} }
return resolve(url); return resolve(url);
@ -407,6 +410,7 @@ class Files3 {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
return this.s3.getSignedUrl('getObject', s3Params, (err, url) => { return this.s3.getSignedUrl('getObject', s3Params, (err, url) => {
if (err) { if (err) {
this.log.error('signedGetKey', err);
return reject(err); return reject(err);
} }
return resolve(url); return resolve(url);
@ -457,7 +461,7 @@ class Files3 {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
return this.s3.deleteObject(params, (err, data) => { return this.s3.deleteObject(params, (err, data) => {
if (err) { if (err) {
this.log.error(err); this.log.error('delete', err);
return reject(err); return reject(err);
} }
return resolve(true); //buffer return resolve(true); //buffer
@ -475,7 +479,7 @@ class Files3 {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
return this.s3.listObjectsV2(params, (err, data) => { return this.s3.listObjectsV2(params, (err, data) => {
if (err) { if (err) {
this.log.error(err); this.log.error('list', err);
return reject(err); return reject(err);
} }
return resolve(data); return resolve(data);

File diff suppressed because one or more lines are too long

32
dist/generate.js vendored
View File

@ -67,8 +67,28 @@ 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); try {
console.dir(meta); photo = await this.createPhoto(image, meta);
}
catch (err) {
this.log.error(`Error creating photo record metadata`, err);
continue;
}
try {
await this.db.create(photo);
}
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;
}
//await this.move(image);
} }
} }
async img(file, exif) { async img(file, exif) {
@ -145,6 +165,7 @@ class Generate {
async createPhoto(image, meta) { async createPhoto(image, meta) {
const hash = await hash_1.Hashes.fileHash(image); const hash = await hash_1.Hashes.fileHash(image);
const dimensions = await this.getImageDimensions(image); const dimensions = await this.getImageDimensions(image);
const now = Date.now();
return { return {
name: (0, path_1.basename)(image), name: (0, path_1.basename)(image),
original: meta.original, original: meta.original,
@ -154,10 +175,15 @@ class Generate {
format: meta.format, format: meta.format,
filmstock: meta.filmstock, filmstock: meta.filmstock,
location: meta.location, location: meta.location,
discovered: Date.now(), discovered: now,
updated: now,
created: +new Date(meta.year, meta.month, meta.day) created: +new Date(meta.year, meta.month, meta.day)
}; };
} }
async upload(image) {
const name = (0, path_1.basename)(image);
return this.s3.createFromPath(image, name);
}
} }
new Generate(); new Generate();
//# sourceMappingURL=generate.js.map //# sourceMappingURL=generate.js.map

File diff suppressed because one or more lines are too long

8
notes/exit_test.txt Normal file
View File

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

View File

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

View File

@ -5,12 +5,12 @@ set -e
source .env source .env
INPUT="${1}" INPUT="${1}"
EXIF="${2}" ID="${2}"
EXIF="${3}"
SIZES=( SIZES=(
"home:420" "home:420"
"full:1920" "full:2000"
"bsky:2000"
) )
function img () { function img () {
@ -26,7 +26,7 @@ for sizeRaw in ${SIZES[@]}; do
size=$(echo $sizeRaw | awk -F':' '{print $2}') size=$(echo $sizeRaw | awk -F':' '{print $2}')
name=$(basename "${1}") name=$(basename "${1}")
name=${name%.*} name=${name%.*}
output="${WWW}/img/${name}_${size}.jpg" output="${WWW}/img/${ID}_${size}.jpg"
img "${1}" "${output}" "${size}" img "${1}" "${output}" "${size}"
exiftool -overwrite_original -@ "${EXIF}" "${output}" exiftool -overwrite_original -@ "${EXIF}" "${output}"
done done

View File

@ -1,9 +1,11 @@
CREATE TABLE IF NOT EXISTS photos ( CREATE TABLE IF NOT EXISTS photos (
id TEXT PRIMARY KEY,
name TEXT UNIQUE, name TEXT UNIQUE,
original TEXT UNIQUE, original TEXT UNIQUE,
hash TEXT UNIQUE, hash TEXT UNIQUE,
width INTEGER, width INTEGER,
height INTEGER, height INTEGER,
format TEXT,
filmstock TEXT, filmstock TEXT,
location TEXT, location TEXT,
discovered INTEGER, discovered INTEGER,

View File

@ -6,6 +6,7 @@ import { Database } from 'sqlite3';
import { envString } from '../env'; import { envString } from '../env';
interface Photo { interface Photo {
id : string;
name : string; name : string;
original? : string; original? : string;
hash : string; hash : string;
@ -29,6 +30,7 @@ export class DB {
constructor () { constructor () {
this.log = createLog('db'); this.log = createLog('db');
this.db = new Database(envString('DB', 'data/site.db')); this.db = new Database(envString('DB', 'data/site.db'));
this.db.run( 'PRAGMA journal_mode = WAL;');
} }
private async run (query : string, args : any[] = null) { private async run (query : string, args : any[] = null) {
@ -40,26 +42,73 @@ export class DB {
}); });
} }
//CASE WHEN LOWER(active) = 'true' THEN 1 ELSE 0 END AS active_bool private async all (query : string, args : any[] = null) : Promise<any[]> {
return new Promise((resolve : Function, reject : Function) => {
return this.db.all(query, args, (err : Error, rows : any[]) => {
if (err) return reject(err);
return resolve(rows);
});
});
}
private toBoolean (val : number) : boolean {
return val === 1 ? true : false;
}
private fromBoolean (val : boolean) : number {
return val ? 1 : 0;
}
//CASE WHEN LOWER(active) = 'true' THEN 1 ELSE 0 END AS active_bool
public async create (photo : Photo) { public async create (photo : Photo) {
const keys : string[] = Object.keys(photo); const keys : string[] = Object.keys(photo);
const query : string = `INSERT INTO photos (${keys.join(',')}) VALUES (${keys.map(el=>'?').join(',')});`; const query : string = `INSERT INTO photos (${keys.join(',')}) VALUES (${keys.map(el=>'?').join(',')});`;
const values : any[] = []; const values : any[] = [];
for (let key of keys) { for (let key of keys) {
if (typeof (photo as any)[key] === 'boolean') { if (typeof (photo as any)[key] === 'boolean') {
values.push((photo as any)[key] ? 1 : 0); values.push( this.fromBoolean( (photo as any)[key] ));
} else { } else {
values.push((photo as any)[key]); values.push((photo as any)[key]);
} }
} }
try { try {
await this.run(query, values); await this.run(query, values);
this.log.info(`Inserted new photo`) this.log.info(`Inserted new photo ${photo.name}`);
} catch (err) { } catch (err) {
this.log.error(`Error inserting record into photos`, err); throw err;
} }
} }
public async existsName (name : string) : Promise<boolean> {
const query : string = `SELECT id FROM photos WHERE name = ? LIMIT 1;`;
let rows : any[] = [];
let exists : boolean = 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;
}
public async existsHash (hash : string) : Promise<boolean> {
const query : string = `SELECT id FROM photos WHERE hash = ? LIMIT 1;`;
let rows : any[] = [];
let exists : boolean = 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;
}
} }
module.exports = { DB }; module.exports = { DB };

View File

@ -51,6 +51,7 @@ export class Files3 {
endpoint: spacesEndpoint as unknown as string, endpoint: spacesEndpoint as unknown as string,
signatureVersion: 'v4' signatureVersion: 'v4'
}; };
this.endpoint = S3_ENDPOINT; this.endpoint = S3_ENDPOINT;
this.s3 = new S3(s3Config); this.s3 = new S3(s3Config);
this.s3Stream = s3Stream(this.s3); this.s3Stream = s3Stream(this.s3);
@ -97,11 +98,12 @@ export class Files3 {
**/ **/
public async create (file : any, keyName? : string) { public async create (file : any, keyName? : string) {
if (!this.writeable) return false; if (!this.writeable) return false;
const id : string = await this.hashFile(file); const id : string = uuid();
const ext : string | false = mime.extension(file.mimetype); const ext : string | false = mime.extension(file.mimetype);
const key : string = typeof keyName !== 'undefined' ? keyName : `${id}.${ext}`; const key : string = typeof keyName !== 'undefined' ? keyName : `${id}.${ext}`;
const webPath : string = pathJoin('/files/', this.bucket, key); const webPath : string = pathJoin('/files/', this.bucket, key);
const publicPath : string = pathJoin(`${this.bucket}.${this.endpoint}`, key); const publicPath : string = pathJoin(`${this.bucket}.${this.endpoint}`, key);
const record : FileRecord = { const record : FileRecord = {
id, id,
created : +new Date(), created : +new Date(),
@ -113,6 +115,7 @@ export class Files3 {
type : file.mimetype, type : file.mimetype,
size : null size : null
}; };
const params : S3.PutObjectRequest = { const params : S3.PutObjectRequest = {
Bucket: this.bucket, Bucket: this.bucket,
Key: key, Key: key,
@ -124,10 +127,10 @@ export class Files3 {
record.size = file.buffer.byteLength; record.size = file.buffer.byteLength;
return new Promise((resolve : Function, reject : Function) => { return new Promise((resolve : Function, reject : Function) => {
return this.s3.putObject(params, function (err : Error, data : any) { return this.s3.putObject(params, (err : Error, data : any) => {
if (err) { if (err) {
this.log.error(err) this.log.error('create', err);
return reject(err) return reject(err);
} else { } else {
this.log.info(`Saved file ${record.path}`); this.log.info(`Saved file ${record.path}`);
return resolve(record); return resolve(record);
@ -323,6 +326,7 @@ export class Files3 {
return new Promise((resolve : Function, reject : Function) => { return new Promise((resolve : Function, reject : Function) => {
upload.on('error', (err : Error) => { upload.on('error', (err : Error) => {
this.log.error('createStreamFromPath', err);
return reject(err); return reject(err);
}); });
upload.on('part', (details : any) => { upload.on('part', (details : any) => {
@ -355,6 +359,7 @@ export class Files3 {
return new Promise((resolve : Function, reject : Function) => { return new Promise((resolve : Function, reject : Function) => {
return this.s3.getObject(params, (err : Error, data : any) => { return this.s3.getObject(params, (err : Error, data : any) => {
if (err) { if (err) {
this.log.error('read', err);
return reject(err); return reject(err);
} }
return resolve(data.Body) //buffer return resolve(data.Body) //buffer
@ -390,6 +395,7 @@ export class Files3 {
return new Promise((resolve : Function, reject : Function) => { return new Promise((resolve : Function, reject : Function) => {
return this.s3.getSignedUrl('putObject', s3Params, (err : Error, url : string) => { return this.s3.getSignedUrl('putObject', s3Params, (err : Error, url : string) => {
if (err) { if (err) {
this.log.error('signedPutKey', err);
return reject(err); return reject(err);
} }
return resolve(url); return resolve(url);
@ -415,6 +421,7 @@ export class Files3 {
return new Promise((resolve : Function, reject : Function) => { return new Promise((resolve : Function, reject : Function) => {
return this.s3.getSignedUrl('getObject', s3Params, (err : Error, url : string) => { return this.s3.getSignedUrl('getObject', s3Params, (err : Error, url : string) => {
if (err) { if (err) {
this.log.error('signedGetKey', err);
return reject(err); return reject(err);
} }
return resolve(url); return resolve(url);
@ -467,10 +474,10 @@ export class Files3 {
return new Promise((resolve : Function, reject : Function) => { return new Promise((resolve : Function, reject : Function) => {
return this.s3.deleteObject(params, (err : Error, data : any) => { return this.s3.deleteObject(params, (err : Error, data : any) => {
if (err) { if (err) {
this.log.error(err); this.log.error('delete', err);
return reject(err); return reject(err);
} }
return resolve(true) //buffer return resolve(true); //buffer
}); });
}); });
} }
@ -487,7 +494,7 @@ export class Files3 {
return new Promise((resolve : Function, reject : Function) => { return new Promise((resolve : Function, reject : Function) => {
return this.s3.listObjectsV2(params, (err : Error, data : any) => { return this.s3.listObjectsV2(params, (err : Error, data : any) => {
if (err) { if (err) {
this.log.error(err); this.log.error('list', err);
return reject(err); return reject(err);
} }
return resolve(data); return resolve(data);

View File

@ -1,9 +1,12 @@
import 'dotenv/config'; import 'dotenv/config';
import { createLog } from './log'; import { createLog } from './log';
import type { Logger } from 'winston'; import type { Logger } from 'winston';
import { readFile, readdir, realpath } from 'fs/promises'; import { readFile, writeFile, readdir, realpath, rename } from 'fs/promises';
import { join, basename } from 'path'; import { join, basename } from 'path';
import { promisify } from 'util'; import { promisify } from 'util';
import { v4 as uuid } from 'uuid';
import { randomBytes } from 'crypto';
import { tmpdir } from 'os';
import { Shell } from './shell'; import { Shell } from './shell';
import { Hashes } from './hash'; import { Hashes } from './hash';
import { Files3 } from './files3' import { Files3 } from './files3'
@ -29,14 +32,16 @@ class Generate {
private files : string[]; private files : string[];
private inbox : string = envString('INBOX', '~/Photos/toprocess'); private inbox : string = envString('INBOX', '~/Photos/toprocess');
private photos : string = envString('PHOTOS', '~/Photos/processed'); private photos : string = envString('PHOTOS', '~/Photos/processed');
private artist : string = envString('ARTIST', 'Unknown');
private s3 : Files3; private s3 : Files3;
private db : DB; private db : DB;
private tmp : string = tmpdir();
constructor () { constructor () {
this.log = createLog('generate'); this.log = createLog('generate');
this.log.info(`Generating site: ${new Date()}`); this.log.info(`Generating site: ${new Date()}`);
this.db = new DB(); this.db = new DB();
this.s3 = new Files3(envString('S3_BUCKET', 'mmcwilliamsphotos'), true); this.s3 = new Files3(envString('S3_BUCKET', 's3bucket'), true);
this.generate(); this.generate();
} }
@ -90,15 +95,67 @@ class Generate {
); );
for (let image of images) { for (let image of images) {
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);
console.dir(meta) 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);
} 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;
}
await this.move(image);
} }
} }
private async img (file : string, exif : string) { //Artist
//ImageTitle
//ImageUniqueID
//ISOSpeed
//DateTimeOriginal
private async exif (photo : Photo) : Promise<string> {
const filePath : string = await this.mktemp('photosite_exif');
try {
} catch (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, exif];
const shell : Shell = new Shell(cmd); const shell : Shell = new Shell(cmd);
try { try {
@ -177,7 +234,9 @@ class Generate {
private async createPhoto (image : string, meta : Metadata) : Promise<Photo> { private async createPhoto (image : string, meta : Metadata) : Promise<Photo> {
const hash : string = await Hashes.fileHash(image); const hash : string = await Hashes.fileHash(image);
const dimensions : any = await this.getImageDimensions(image); const dimensions : any = await this.getImageDimensions(image);
const now : number = Date.now();
return { return {
id : uuid(),
name : basename(image), name : basename(image),
original: meta.original, original: meta.original,
hash, hash,
@ -186,10 +245,44 @@ class Generate {
format : meta.format, format : meta.format,
filmstock : meta.filmstock, filmstock : meta.filmstock,
location : meta.location, location : meta.location,
discovered : Date.now(), discovered : now,
updated : now,
created : + new Date(meta.year, meta.month, meta.day) created : + new Date(meta.year, meta.month, meta.day)
} }
} }
private async upload (image: string) {
const name : string = basename(image);
return this.s3.createFromPath(image, name);
}
private async move (image : string) {
const name : string = basename(image);
const dest : string = 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 : string = 'tmp') : Promise<string> {
const uniqueId = randomBytes(16).toString('hex');
const tempFilePath = join(this.tmp, `${prefix}-${uniqueId}`);
try {
await writeFile(tempFilePath, '', { flag: 'wx' });
return tempFilePath;
} catch (err) {
if (err.code === 'EEXIST') {
return this.mktemp(prefix);
} else {
throw err;
}
}
}
} }
new Generate(); new Generate();