aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2024-12-11 13:11:43 -0500
committerbobzel <zzzman@gmail.com>2024-12-11 13:11:43 -0500
commita5a7769e0c400f0a58a9b53ae13b338a26eaa919 (patch)
treec6705b180714b0cbe03a23b36f4b6014d34aca9a
parent48c3b802a3c8fd446ecbd33747fe702b11170dfe (diff)
fixes for uploading remote images to sample and save them properly. extnesions fo firefly manager to query text in images, expand images, and tweaks to generate images.
-rw-r--r--package-lock.json23
-rw-r--r--package.json3
-rw-r--r--src/client/views/nodes/ImageBox.tsx31
-rw-r--r--src/server/ApiManagers/FireflyManager.ts114
-rw-r--r--src/server/ApiManagers/UploadManager.ts8
-rw-r--r--src/server/DashUploadUtils.ts37
6 files changed, 183 insertions, 33 deletions
diff --git a/package-lock.json b/package-lock.json
index 05f6d540f..46057f615 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -100,6 +100,7 @@
"depcheck": "^1.4.7",
"dompurify": "^3.1.7",
"dotenv": "^16.4.5",
+ "dropbox": "^10.34.0",
"eslint-webpack-plugin": "^4.1.0",
"exif": "^0.6.0",
"exifr": "^7.1.3",
@@ -128,7 +129,7 @@
"google-auth-library": "^9.4.1",
"googleapis": "^144.0.0",
"googlephotos": "^0.3.5",
- "got": "^14.0.0",
+ "got": "^14.4.5",
"howler": "^2.2.4",
"html-to-image": "^1.11.11",
"html-to-text": "^9.0.5",
@@ -19701,6 +19702,20 @@
"resolved": "https://registry.npmjs.org/double-bits/-/double-bits-1.1.1.tgz",
"integrity": "sha512-BCLEIBq0O/DWoA7BsCu/R+RP0ZXiowP8BhtJT3qeuuQEBpnS8LK/Wo6UTJQv6v8mK1fj8n90YziHLwGdM5whSg=="
},
+ "node_modules/dropbox": {
+ "version": "10.34.0",
+ "resolved": "https://registry.npmjs.org/dropbox/-/dropbox-10.34.0.tgz",
+ "integrity": "sha512-5jb5/XzU0fSnq36/hEpwT5/QIep7MgqKuxghEG44xCu7HruOAjPdOb3x0geXv5O/hd0nHpQpWO+r5MjYTpMvJg==",
+ "dependencies": {
+ "node-fetch": "^2.6.1"
+ },
+ "engines": {
+ "node": ">=0.10.3"
+ },
+ "peerDependencies": {
+ "@types/node-fetch": "^2.5.7"
+ }
+ },
"node_modules/dynamic-dedupe": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz",
@@ -22149,9 +22164,9 @@
}
},
"node_modules/got": {
- "version": "14.4.4",
- "resolved": "https://registry.npmjs.org/got/-/got-14.4.4.tgz",
- "integrity": "sha512-tqiF7eSgTBwQkxb1LxsEpva8TaMYVisbhplrFVmw9GQE3855Z+MH/mnsXLLOkDxR6hZJRFMj5VTAZ8lmTF8ZOA==",
+ "version": "14.4.5",
+ "resolved": "https://registry.npmjs.org/got/-/got-14.4.5.tgz",
+ "integrity": "sha512-sq+uET8TnNKRNnjEOPJzMcxeI0irT8BBNmf+GtZcJpmhYsQM1DSKmCROUjPWKsXZ5HzwD5Cf5/RV+QD9BSTxJg==",
"dependencies": {
"@sindresorhus/is": "^7.0.1",
"@szmarczak/http-timer": "^5.0.1",
diff --git a/package.json b/package.json
index 949f76599..7ca0c8b38 100644
--- a/package.json
+++ b/package.json
@@ -183,6 +183,7 @@
"depcheck": "^1.4.7",
"dompurify": "^3.1.7",
"dotenv": "^16.4.5",
+ "dropbox": "^10.34.0",
"eslint-webpack-plugin": "^4.1.0",
"exif": "^0.6.0",
"exifr": "^7.1.3",
@@ -211,7 +212,7 @@
"google-auth-library": "^9.4.1",
"googleapis": "^144.0.0",
"googlephotos": "^0.3.5",
- "got": "^14.0.0",
+ "got": "^14.4.5",
"howler": "^2.2.4",
"html-to-image": "^1.11.11",
"html-to-text": "^9.0.5",
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ff879a2ab..8c7ec959e 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -38,6 +38,8 @@ import { FieldView, FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import './ImageBox.scss';
import { OpenWhere } from './OpenWhere';
+import { Upload } from '../../../server/SharedMediaTypes';
+import { ImageUtils } from '../../util/Import & Export/ImageUtils';
export class ImageEditorData {
// eslint-disable-next-line no-use-before-define
@@ -309,6 +311,35 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' });
funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' });
funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
+ funcs.push({
+ description: 'GetImageText',
+ event: () => {
+ Networking.PostToServer('/queryFireflyImageText', {
+ file: (file => {
+ const ext = extname(file);
+ return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
+ })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
+ }).then(text => alert(text));
+ },
+ icon: 'expand-arrows-alt',
+ });
+ funcs.push({
+ description: 'Expand Image',
+ event: () => {
+ Networking.PostToServer('/expandImage', {
+ prompt: 'sunny skies',
+ file: (file => {
+ const ext = extname(file);
+ return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
+ })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
+ }).then((info: Upload.ImageInformation) => {
+ const img = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { title: 'expand:' + this.Document.title });
+ DocUtils.assignImageInfo(info, img);
+ this._props.addDocTab(img, OpenWhere.addRight);
+ });
+ },
+ icon: 'expand-arrows-alt',
+ });
funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' });
funcs.push({
description: 'Open Image Editor',
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index cc4c218bf..d757a23fe 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -1,7 +1,11 @@
+import { Dropbox, files } from 'dropbox';
+import * as fs from 'fs';
+import * as multipart from 'parse-multipart-data';
+import * as path from 'path';
import { DashUploadUtils } from '../DashUploadUtils';
-import { _invalid, _success, Method } from '../RouteManager';
+import { _error, _invalid, _success, Method } from '../RouteManager';
+import { Directory, filesDirectory } from '../SocketData';
import ApiManager, { Registration } from './ApiManager';
-import * as multipart from 'parse-multipart-data';
export default class FireflyManager extends ApiManager {
getBearerToken = () =>
@@ -13,11 +17,11 @@ export default class FireflyManager extends ApiManager {
body: `grant_type=client_credentials&client_id=${process.env._CLIENT_FIREFLY_CLIENT_ID}&client_secret=${process.env._CLIENT_FIREFLY_SECRET}&scope=openid,AdobeID,session,additional_info,read_organizations,firefly_api,ff_apis`,
}).catch(error => {
console.error('Error:', error);
- return '';
+ return undefined;
});
- askFirefly = (prompt: string = 'a realistic illustration of a cat coding') => {
+ generateImage = (prompt: string = 'a realistic illustration of a cat coding') => {
const fetched = this.getBearerToken().then(response =>
- (response as Response).json().then((data: { access_token: string }) =>
+ response?.json().then((data: { access_token: string }) =>
fetch('https://firefly-api.adobe.io/v3/images/generate', {
method: 'POST',
headers: [
@@ -28,20 +32,66 @@ export default class FireflyManager extends ApiManager {
],
body: `{ "prompt": "${prompt}" }`,
})
- .then(response2 => response2.json().then(json => JSON.stringify((json.outputs?.[0] as { image: { url: string } })?.image)))
+ .then(response2 => response2.json().then(json => (json.outputs?.[0] as { image: { url: string } })?.image.url))
.catch(error => {
console.error('Error:', error);
- return '';
+ return undefined;
})
)
);
return fetched;
};
- getImageText = (testshotpng: Blob) => {
+ expandImage = (imgUrl: string, prompt?: string) => {
+ const dropboxImgUrl = imgUrl;
+ const fetched = this.getBearerToken().then(response =>
+ response
+ ?.json()
+ .then((data: { access_token: string }) => {
+ return fetch('https://firefly-api.adobe.io/v3/images/expand', {
+ method: 'POST',
+ headers: [
+ ['Content-Type', 'application/json'],
+ ['Accept', 'application/json'],
+ ['x-api-key', process.env._CLIENT_FIREFLY_CLIENT_ID ?? ''],
+ ['Authorization', `Bearer ${data.access_token}`],
+ ],
+ body: JSON.stringify({
+ image: {
+ source: {
+ url: dropboxImgUrl,
+ },
+ },
+ numVariations: 1,
+ seeds: [0],
+ size: {
+ width: 3048,
+ height: 2048,
+ },
+ prompt: prompt ?? 'cloudy skies',
+ placement: {
+ inset: {
+ left: 0,
+ top: 0,
+ right: 0,
+ bottom: 0,
+ },
+ alignment: {
+ horizontal: 'center',
+ vertical: 'center',
+ },
+ },
+ }),
+ });
+ })
+ .then(resp => resp.json())
+ );
+ return fetched;
+ };
+ getImageText = (imageBlob: Blob) => {
const inputFileVarName = 'infile';
const outputVarName = 'result';
const fetched = this.getBearerToken().then(response =>
- (response as Response).json().then((data: { access_token: string }) => {
+ response?.json().then((data: { access_token: string }) => {
return fetch('https://sensei.adobe.io/services/v2/predict', {
method: 'POST',
headers: [
@@ -51,7 +101,7 @@ export default class FireflyManager extends ApiManager {
['Authorization', `Bearer ${data.access_token}`],
],
body: ((form: FormData) => {
- form.set(inputFileVarName, testshotpng);
+ form.set(inputFileVarName, imageBlob);
form.set(
'contentAnalyzerRequests',
JSON.stringify({
@@ -98,7 +148,7 @@ export default class FireflyManager extends ApiManager {
multipart
.parse(Buffer.from(arrayBuffer), 'Boundary' + (response2.headers.get('content-type')?.match(/=Boundary(.*);/)?.[1] ?? ''))
.filter(part => part.name === outputVarName)
- .map(part => JSON.parse(part.data.toString()[0]))
+ .map(part => JSON.parse(part.data.toString())[0])
.reduce((text, json) => text + (json?.is_text_present ? json.tags.map((tag: { text: string }) => tag.text).join(' ') : ''), '')
)
.catch(error => {
@@ -117,10 +167,10 @@ export default class FireflyManager extends ApiManager {
method: Method.POST,
subscription: '/queryFireflyImage',
secureHandler: ({ req, res }) =>
- this.askFirefly(req.body.prompt).then(fire =>
- DashUploadUtils.UploadImage(JSON.parse(fire).url).then(info => {
+ this.generateImage(req.body.prompt).then(url =>
+ DashUploadUtils.UploadImage(url ?? '').then(info => {
if (info instanceof Error) _invalid(res, info.message);
- else _success(res, info.accessPaths.agnostic.client);
+ else _success(res, info);
})
),
});
@@ -128,6 +178,7 @@ export default class FireflyManager extends ApiManager {
register({
method: Method.POST,
subscription: '/queryFireflyImageText',
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
secureHandler: ({ req, res }) =>
fetch('http://localhost:1050/files/images/testshot.png').then(json =>
json.blob().then(file =>
@@ -137,5 +188,40 @@ export default class FireflyManager extends ApiManager {
)
),
});
+ register({
+ method: Method.POST,
+ subscription: '/expandImage',
+ secureHandler: ({ req, res }) =>
+ new Promise<void>((resolve, reject) => {
+ const dbx = new Dropbox({ accessToken: process.env.DROPBOX_TOKEN });
+ fs.readFile(path.join(filesDirectory, `${Directory.images}/${path.basename(req.body.file)}`), undefined, (err, contents) => {
+ if (err) {
+ console.log('Error: ', err);
+ reject();
+ } else {
+ dbx.filesUpload({ path: `/Apps/browndash/${path.basename(req.body.file)}`, contents })
+ .then(response => {
+ dbx.filesGetTemporaryLink({ path: response.result.path_display ?? '' }).then(link => {
+ console.log(link.result);
+ this.expandImage(link.result.link, req.body.prompt).then(text => {
+ if (text.error_code) _error(res, text.message);
+ else
+ DashUploadUtils.UploadImage(text.outputs[0].image.url).then(info => {
+ if (info instanceof Error) _invalid(res, info.message);
+ else _success(res, info);
+ resolve();
+ });
+ });
+ });
+ })
+ .catch(uploadErr => {
+ console.log(uploadErr);
+ _error(res, 'upload to dropbox failed');
+ reject();
+ });
+ }
+ });
+ }),
+ });
}
}
diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts
index 7bfdd5aec..5a880901b 100644
--- a/src/server/ApiManagers/UploadManager.ts
+++ b/src/server/ApiManagers/UploadManager.ts
@@ -70,10 +70,16 @@ export default class UploadManager extends ApiManager {
]);
} else {
fileguids.split(';').map(guid => DashUploadUtils.uploadProgress.set(guid, `resampling images`));
+ // original filenames with '.'s, such as a Macbook screenshot, can be a problem - their extension is not kept in formidable's newFilename.
+ // This makes sure that the extension is preserved in the newFilename.
+ const fixNewFilename = (f: formidable.File) => {
+ if (path.extname(f.originalFilename ?? '') !== path.extname(f.newFilename)) f.newFilename = f.newFilename + path.extname(f.originalFilename ?? '');
+ return f;
+ };
const results = (
await Promise.all(
Array.from(Object.keys(files)).map(
- async key => (!files[key] ? undefined : DashUploadUtils.upload(files[key]![0] /* , key */)) // key is the guid used by the client to track upload progress.
+ async key => (!files[key] ? undefined : DashUploadUtils.upload(fixNewFilename(files[key][0]) /* , key */)) // key is the guid used by the client to track upload progress.
)
)
).filter(result => result && !(result.result instanceof Error));
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 028116779..a06fa3b6e 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -22,7 +22,6 @@ import { AzureManager } from './ApiManagers/AzureManager';
import { AcceptableMedia, Upload } from './SharedMediaTypes';
import { Directory, clientPathToFile, filesDirectory, pathToDirectory, publicDirectory, serverPathToFile } from './SocketData';
import { resolvedServerUrl } from './server_Initialization';
-
import { Worker, isMainThread, parentPort } from 'worker_threads';
// Create an array to store worker threads
@@ -55,7 +54,8 @@ if (isMainThread) {
.then(img =>
sizes.forEach(({ width, suffix }) =>
img.resize({ w: width || img.bitmap.width })
- .write(InjectSize(outputPath, suffix) as `${string}.${string}`)
+ .write(InjectSize(outputPath, suffix) as `${string}.${string}`)
+ .catch(e => console.log("Jimp error:", e))
))
.catch(e => console.log('Error Jimp:', e))
.finally(() => unlinkSource && unlinkSync(imgSourcePath));
@@ -343,15 +343,24 @@ export namespace DashUploadUtils {
const outputPath = path.resolve(pathToDirectory(Directory.images), outputFileName);
const sizes = imageResampleSizes(path.extname(outputFileName));
- const imgReadStream = new Duplex();
- imgReadStream.push(fs.readFileSync(imgSourcePath));
- imgReadStream.push(null);
- await Promise.all(
- sizes.map(({ suffix }) =>
- new Promise<unknown>(res =>
- imgReadStream.pipe(createWriteStream(writtenFiles[suffix] = InjectSize(outputPath, suffix))).on('close', res)
- )
- )); // prettier-ignore
+ if (unlinkSource) {
+ const imgReadStream = new Duplex();
+ imgReadStream.push(fs.readFileSync(imgSourcePath));
+ imgReadStream.push(null);
+ await Promise.all(
+ sizes.map(({ suffix }) =>
+ new Promise<unknown>(res =>
+ imgReadStream.pipe(createWriteStream(writtenFiles[suffix] = InjectSize(outputPath, suffix))).on('close', res)
+ )
+ )); // prettier-ignore
+ } else {
+ await Promise.all(
+ sizes.map(({ suffix }) =>
+ new Promise<unknown>(res =>
+ request.get(imgSourcePath).pipe(createWriteStream(writtenFiles[suffix] = InjectSize(outputPath, suffix))).on('close', res)
+ )
+ )); // prettier-ignore
+ }
workerResample(imgSourcePath, outputPath, SizeSuffix.Original, unlinkSource);
return writtenFiles;
@@ -450,7 +459,7 @@ export namespace DashUploadUtils {
}
const outputFile = filename || result.filename || '';
- return UploadInspectedImage(result, outputFile, prefix);
+ return UploadInspectedImage(result, outputFile, prefix, isLocal().exec(source) ? true : false);
};
type md5 = 'md5';
@@ -568,7 +577,9 @@ export namespace DashUploadUtils {
switch (category) {
case 'image':
if (imageFormats.includes(format)) {
- const result = await UploadImage(filepath, basename(filepath));
+ const outputName = basename(filepath);
+ const extname = path.extname(originalFilename ?? '');
+ const result = await UploadImage(filepath, outputName.endsWith(extname) ? outputName : outputName + extname, undefined);
return { source: file, result };
}
fs.unlink(filepath, () => {});