diff --git a/api/onnx_web/output.py b/api/onnx_web/output.py
index 79fc863c..6b051854 100644
--- a/api/onnx_web/output.py
+++ b/api/onnx_web/output.py
@@ -203,9 +203,9 @@ def save_image(
exif = PngImagePlugin.PngInfo()
if params is not None:
- exif.add_text("make", "onnx-web")
+ exif.add_text("Make", "onnx-web")
exif.add_text(
- "maker note",
+ "Maker Note",
dumps(
json_params(
[output],
@@ -217,9 +217,9 @@ def save_image(
)
),
)
- exif.add_text("model", server.server_version)
+ exif.add_text("Model", server.server_version)
exif.add_text(
- "parameters",
+ "Parameters",
str_params(server, params, size, inversions=inversions, loras=loras),
)
diff --git a/gui/package.json b/gui/package.json
index f721bb40..527c2639 100644
--- a/gui/package.json
+++ b/gui/package.json
@@ -17,6 +17,7 @@
"@types/lodash": "^4.14.192",
"@types/node": "^18.15.11",
"browser-bunyan": "^1.8.0",
+ "exifreader": "^4.13.0",
"i18next": "^22.4.14",
"i18next-browser-languagedetector": "^7.0.1",
"lodash": "^4.17.21",
diff --git a/gui/src/components/Profiles.tsx b/gui/src/components/Profiles.tsx
index 99c03c6c..fdf63ad4 100644
--- a/gui/src/components/Profiles.tsx
+++ b/gui/src/components/Profiles.tsx
@@ -1,28 +1,26 @@
-import * as React from 'react';
-import { useContext } from 'react';
import { doesExist, mustExist } from '@apextoaster/js-utils';
-import { useStore } from 'zustand';
-import { useTranslation } from 'react-i18next';
+import { Delete as DeleteIcon, ImageSearch, Save as SaveIcon } from '@mui/icons-material';
import {
+ Autocomplete,
Button,
- IconButton,
Dialog,
- DialogTitle,
- DialogContent,
DialogActions,
- TextField,
+ DialogContent,
+ DialogTitle,
+ IconButton,
ListItem,
ListItemText,
- Autocomplete,
Stack,
+ TextField,
} from '@mui/material';
-import {
- Delete as DeleteIcon,
- Save as SaveIcon,
-} from '@mui/icons-material';
+import * as ExifReader from 'exifreader';
+import * as React from 'react';
+import { useContext } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useStore } from 'zustand';
+import { BaseImgParams, Txt2ImgParams } from '../client/types.js';
import { StateContext } from '../state.js';
-import { BaseImgParams } from '../client/types.js';
export interface ProfilesProps {
params: BaseImgParams;
@@ -78,6 +76,29 @@ export function Profiles(props: ProfilesProps) {
+
)}
onChange={(event, value) => {
@@ -124,3 +145,95 @@ export function Profiles(props: ProfilesProps) {
>;
}
+
+export async function loadParamsFromFile(file: File): Promise> {
+ const parts = file.name.toLocaleLowerCase().split('.');
+ const ext = parts[parts.length - 1];
+
+ switch (ext) {
+ case 'jpg':
+ case 'jpeg':
+ case 'png':
+ return parseImageParams(file);
+ case 'json':
+ {
+ const params = JSON.parse(await file.text());
+ return params.params as Txt2ImgParams;
+ }
+ case 'txt':
+ default:
+ return parseAutoComment(await file.text());
+ }
+}
+
+export async function parseImageParams(file: File): Promise> {
+ const tags = await ExifReader.load(file);
+
+ // handle lowercase variation from my earlier mistakes
+ const makerNote = tags.MakerNote || tags['maker note'];
+ // eslint-disable-next-line dot-notation, @typescript-eslint/strict-boolean-expressions
+ const userComment = tags.UserComment || tags['Parameters'] || tags['parameters'];
+
+ if (doesExist(makerNote) && isProbablyJSON(makerNote.value)) {
+ const params = JSON.parse(makerNote.value);
+ return params.params as Txt2ImgParams; // TODO: enforce schema and do some error handling
+ }
+
+ if (doesExist(userComment) && typeof userComment.value === 'string') {
+ return parseAutoComment(userComment.value);
+ }
+
+ return {};
+}
+
+export function isProbablyJSON(maybeJSON: unknown): maybeJSON is string {
+ return typeof maybeJSON === 'string' && maybeJSON[0] === '{' && maybeJSON[maybeJSON.length - 1] === '}';
+}
+
+export const NEGATIVE_PROMPT_TAG = 'Negative prompt:';
+
+export function parseAutoComment(comment: string): Partial {
+ const lines = comment.split('\n');
+ const [prompt, maybeNegative, ...otherLines] = lines;
+
+ const params: Partial = {
+ prompt,
+ };
+
+ // check if maybeNegative is the negative prompt
+ if (maybeNegative.startsWith(NEGATIVE_PROMPT_TAG)) {
+ params.negativePrompt = maybeNegative.substring(NEGATIVE_PROMPT_TAG.length).trim();
+ } else {
+ otherLines.unshift(maybeNegative);
+ }
+
+ // join rest and split on commas
+ const other = otherLines.join(' ');
+ const otherParams = other.split(',');
+
+ for (const param of otherParams) {
+ const [key, value] = param.split(':');
+
+ switch (key.toLocaleLowerCase().trim()) {
+ case 'steps':
+ params.steps = parseInt(value, 10);
+ break;
+ case 'sampler':
+ params.scheduler = value;
+ break;
+ case 'cfg scale':
+ params.cfg = parseInt(value, 10);
+ break;
+ case 'seed':
+ params.seed = parseInt(value, 10);
+ break;
+ case 'size':
+ // TODO: parse size
+ break;
+ default:
+ // unknown param
+ }
+ }
+
+ return params;
+}
diff --git a/gui/yarn.lock b/gui/yarn.lock
index 0ab674c3..f93b5e70 100644
--- a/gui/yarn.lock
+++ b/gui/yarn.lock
@@ -776,6 +776,11 @@
"@typescript-eslint/types" "5.59.0"
eslint-visitor-keys "^3.3.0"
+"@xmldom/xmldom@^0.8.8":
+ version "0.8.9"
+ resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.9.tgz#b6ef7457e826be8049667ae673eda7876eb049be"
+ integrity sha512-4VSbbcMoxc4KLjb1gs96SRmi7w4h1SF+fCoiK0XaQX62buCc1G5d0DC5bJ9xJBNPDSVCmIrcl8BiYxzjrqaaJA==
+
"@xobotyi/scrollbar-width@^1.9.5":
version "1.9.5"
resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d"
@@ -1563,6 +1568,13 @@ esutils@^2.0.2:
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+exifreader@^4.13.0:
+ version "4.13.0"
+ resolved "https://registry.yarnpkg.com/exifreader/-/exifreader-4.13.0.tgz#f380b33cfc85630a0dbd56edd41e28710a9e9679"
+ integrity sha512-IhJBpyXDLbCdgzVHkthadOvrMiZOR2XS7POVp0b5JoVfScRoCJ6YazZ+stTkbDTE5TRTP44bE5RKsujckAs45Q==
+ optionalDependencies:
+ "@xmldom/xmldom" "^0.8.8"
+
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"