aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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, () => {});