base64 support, ApiJson, Eval string inputs

This commit is contained in:
Michal Kolář
2024-11-15 11:44:00 +01:00
committed by Stanislav Fifik
parent d554eaaafb
commit 8e4637d117
4 changed files with 348 additions and 14 deletions

145
src/nodes/ApiJson.ts Normal file
View File

@@ -0,0 +1,145 @@
import {
NumberVar,
StringVar,
BooleanVar,
} from './io/AbstractIOSet';
import Node from './Node';
import _ from 'lodash';
const inputs = {
propertyPath0: {
type: 'String',
} as StringVar,
propertyPath1: {
type: 'String',
} as StringVar,
propertyPath2: {
type: 'String',
} as StringVar,
propertyPath3: {
type: 'String',
} as StringVar,
propertyPath4: {
type: 'String',
} as StringVar,
propertyPath5: {
type: 'String',
} as StringVar,
propertyPath6: {
type: 'String',
} as StringVar,
propertyPath7: {
type: 'String',
} as StringVar,
url: {
type: 'String',
} as StringVar,
payload: {
type: 'String',
} as StringVar,
method: {
type: 'String',
default: 'GET',
enum: ['GET', 'POST', 'PUT', 'DELETE'],
} as StringVar,
throttleMs: {
type: 'Number',
default: 10000,
} as NumberVar,
compress: {
type: 'Boolean',
default: false,
} as BooleanVar,
quality: {
type: 'Number',
default: 95,
min: 0,
max: 100,
} as NumberVar,
};
const outputs = {
value0: {
type: 'String',
} as StringVar,
value1: {
type: 'String',
} as StringVar,
value2: {
type: 'String',
} as StringVar,
value3: {
type: 'String',
} as StringVar,
value4: {
type: 'String',
} as StringVar,
value5: {
type: 'String',
} as StringVar,
value6: {
type: 'String',
} as StringVar,
value7: {
type: 'String',
} as StringVar,
};
export default class ApiJson extends Node<typeof inputs, typeof outputs> {
private fetchDataThrottled;
private throttleWaitMs;
constructor(options = {}) {
super('ApiJson', inputs, outputs);
this.throttleWaitMs = this.in.throttleMs.value;
this.fetchDataThrottled = _.throttle(this.fetchData, this.throttleWaitMs);
}
async _update() {
const url = this.in.url.value;
if (!url) {
return;
}
if (this.throttleWaitMs !== this.in.throttleMs.value) {
this.throttleWaitMs = this.in.throttleMs.value;
this.fetchDataThrottled = _.throttle(this.fetchData, this.throttleWaitMs);
}
let data: object;
try {
data = await this.fetchDataThrottled();
} catch (e) {
console.error(e);
await new Promise((resolve) => setTimeout(resolve, this.throttleWaitMs));
return;
}
this.out.value0.value = _.get(data, this.in.propertyPath0.value);
this.out.value1.value = _.get(data, this.in.propertyPath1.value);
this.out.value2.value = _.get(data, this.in.propertyPath2.value);
this.out.value3.value = _.get(data, this.in.propertyPath3.value);
this.out.value4.value = _.get(data, this.in.propertyPath4.value);
this.out.value5.value = _.get(data, this.in.propertyPath5.value);
this.out.value6.value = _.get(data, this.in.propertyPath6.value);
this.out.value7.value = _.get(data, this.in.propertyPath7.value);
}
async fetchData() {
const response = await fetch(this.in.url.value, {
method: this.in.method.value,
headers: {
'Content-Type': 'application/json',
},
...(this.in.payload.value ? {body: this.in.payload.value} : {}),
});
if (response.status >= 400) {
throw new Error(response.statusText);
}
return await response.json();
}
}

84
src/nodes/Base64ToImg.ts Normal file
View File

@@ -0,0 +1,84 @@
import {
ImageVar,
NumberVar,
StringVar,
} from './io/AbstractIOSet';
import Node from './Node';
import {canvasPool2D} from '../canvas/CanvasPool';
const inputs = {
base64: {
type: 'String',
} as StringVar,
};
const outputs = {
image: {
type: 'Image',
} as ImageVar,
width: {
type: 'Number',
} as NumberVar,
height: {
type: 'Number',
} as NumberVar,
};
/**
* TODO: after successful tests move this to the library
*/
export default class Base64ToImg extends Node<typeof inputs, typeof outputs> {
constructor(options = {}) {
super('Base64ToImg', inputs, outputs);
}
async _update() {
let base64String = this.in.base64.value;
if (!base64String) {
return;
}
if (!base64String.startsWith('data:image/')) {
base64String = `data:${this.estimateImageMimeType(base64String)};base64,${base64String}`;
}
const canvas = canvasPool2D.createCanvas();
canvas.acquire();
await new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
const canvasContext = canvas.getContext('2d');
canvasContext.drawImage(img, 0, 0);
resolve(img);
};
img.onerror = (e) => {
reject(new Error(e as string));
};
img.src = base64String;
});
this.out.image.value = canvas;
this.out.width.value = canvas.width;
this.out.height.value = canvas.height;
}
private estimateImageMimeType(base64String: string): string {
// Attempt to estimate the MIME type based on base64 signature.
if (base64String.startsWith('iVBORw0KGgo=')) {
return 'image/png';
} else if (base64String.startsWith('/9j/')) {
return 'image/jpeg';
} else if (base64String.startsWith('UklGR')) {
return 'image/webp';
} else {
// Default or unknown type
return 'application/octet-stream';
}
}
}

View File

@@ -5,11 +5,11 @@ import {
StringVar,
BooleanVar,
} from './io/AbstractIOSet';
import get from 'lodash/get';
import _ from 'lodash';
const inputs = {
image: {
type: 'Image'
type: 'Image',
} as ImageVar,
i0: {
type: 'Number',
@@ -43,6 +43,38 @@ const inputs = {
type: 'Number',
default: 0,
} as NumberVar,
i8: {
type: 'String',
default: undefined,
} as StringVar,
i9: {
type: 'String',
default: undefined,
} as StringVar,
i10: {
type: 'String',
default: undefined,
} as StringVar,
i11: {
type: 'String',
default: undefined,
} as StringVar,
i12: {
type: 'String',
default: undefined,
} as StringVar,
i13: {
type: 'String',
default: undefined,
} as StringVar,
i14: {
type: 'String',
default: undefined,
} as StringVar,
i15: {
type: 'String',
default: undefined,
} as StringVar,
formula: {
type: 'String',
default: '',
@@ -50,41 +82,52 @@ const inputs = {
waitForInput: {
type: 'Boolean',
default: true,
} as BooleanVar
} as BooleanVar,
};
const outputs = {
image: {
type: 'Image'
type: 'Image',
} as ImageVar,
result: {
type: 'Number'
type: 'Number',
} as NumberVar,
}
resultString: {
type: 'String',
} as StringVar,
};
export default class Eval extends Node<typeof inputs, typeof outputs> {
constructor() {
super('Eval', inputs, outputs);
}
interpolate() {
return this.in.formula.value.replace(/\{(\w+)\}/g, (_, paramName) =>
get(this.in, [paramName, 'value'], 0)
)
interpolate(defaultValue: any) {
return this.in.formula.value.replace(/\{((i\d)+)}/g, (originalString, paramName) => {
return _.get(this.in, [paramName, 'value'], defaultValue);
});
}
hasAllConnectedInputsValue() {
if (this.in.waitForInput.value) {
return super.hasAllConnectedInputsValue();
if (!this.in.waitForInput.value) {
return true;
}
for (const inputName of Object.keys(this.in.variables)) {
// allow empty string
if (this.in[inputName].output && !this.in[inputName].value && !_.isString(this.in[inputName].value)) {
return false;
}
}
return true;
}
evaluate() {
try {
this.out.result.value = eval(this.interpolate());
this.out.result.value = eval(this.interpolate(0));
this.out.resultString.value = eval(this.interpolate(''));
} catch (e) {
console.error(e);
console.error(e, this.in);
}
this.out.image.value = this.in.image.value;
}

62
src/nodes/ImgToBase64.ts Normal file
View File

@@ -0,0 +1,62 @@
import {
ImageVar,
StringVar,
} from './io/AbstractIOSet';
import Node from './Node';
import {canvasPool2D} from '../canvas/CanvasPool';
import {waitForMedia} from '../utils';
import {mediaSize} from './canvas';
const inputs = {
image: {
type: 'Image',
} as ImageVar,
type: {
type: 'String',
enum: ['image/png', 'image/jpeg', 'image/webp', 'image/gif'],
default: 'image/png',
} as StringVar,
};
const outputs = {
base64: {
type: 'String',
} as StringVar,
};
export default class ImgToBase64 extends Node<typeof inputs, typeof outputs> {
constructor(options = {}) {
super('ImgToBase64', inputs, outputs);
}
async _update() {
const media = this.in.image.value;
if (!media) {
return;
}
await waitForMedia(media);
const {width, height} = mediaSize(media);
const canvas = canvasPool2D.createCanvas();
canvas.acquire();
canvas.width = width;
canvas.height = height;
// @ts-ignore
canvas.getContext('2d').drawImage(media, 0, 0, width, height);
const blob = await canvas.convertToBlob({type: this.in.type.value});
this.out.base64.value = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(reader.result as string);
};
reader.onerror = (e) => {
reject(e);
};
reader.readAsDataURL(blob);
});
}
}