1
0
Fork 0

feat(gui): load and parse most parameters from image, text, or json

This commit is contained in:
Sean Sube 2023-07-19 22:42:49 -05:00
parent 10fa36c1c1
commit 079d4aa602
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
4 changed files with 144 additions and 18 deletions

View File

@ -203,9 +203,9 @@ def save_image(
exif = PngImagePlugin.PngInfo() exif = PngImagePlugin.PngInfo()
if params is not None: if params is not None:
exif.add_text("make", "onnx-web") exif.add_text("Make", "onnx-web")
exif.add_text( exif.add_text(
"maker note", "Maker Note",
dumps( dumps(
json_params( json_params(
[output], [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( exif.add_text(
"parameters", "Parameters",
str_params(server, params, size, inversions=inversions, loras=loras), str_params(server, params, size, inversions=inversions, loras=loras),
) )

View File

@ -17,6 +17,7 @@
"@types/lodash": "^4.14.192", "@types/lodash": "^4.14.192",
"@types/node": "^18.15.11", "@types/node": "^18.15.11",
"browser-bunyan": "^1.8.0", "browser-bunyan": "^1.8.0",
"exifreader": "^4.13.0",
"i18next": "^22.4.14", "i18next": "^22.4.14",
"i18next-browser-languagedetector": "^7.0.1", "i18next-browser-languagedetector": "^7.0.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",

View File

@ -1,28 +1,26 @@
import * as React from 'react';
import { useContext } from 'react';
import { doesExist, mustExist } from '@apextoaster/js-utils'; import { doesExist, mustExist } from '@apextoaster/js-utils';
import { useStore } from 'zustand'; import { Delete as DeleteIcon, ImageSearch, Save as SaveIcon } from '@mui/icons-material';
import { useTranslation } from 'react-i18next';
import { import {
Autocomplete,
Button, Button,
IconButton,
Dialog, Dialog,
DialogTitle,
DialogContent,
DialogActions, DialogActions,
TextField, DialogContent,
DialogTitle,
IconButton,
ListItem, ListItem,
ListItemText, ListItemText,
Autocomplete,
Stack, Stack,
TextField,
} from '@mui/material'; } from '@mui/material';
import { import * as ExifReader from 'exifreader';
Delete as DeleteIcon, import * as React from 'react';
Save as SaveIcon, import { useContext } from 'react';
} from '@mui/icons-material'; import { useTranslation } from 'react-i18next';
import { useStore } from 'zustand';
import { BaseImgParams, Txt2ImgParams } from '../client/types.js';
import { StateContext } from '../state.js'; import { StateContext } from '../state.js';
import { BaseImgParams } from '../client/types.js';
export interface ProfilesProps { export interface ProfilesProps {
params: BaseImgParams; params: BaseImgParams;
@ -78,6 +76,29 @@ export function Profiles(props: ProfilesProps) {
<Button type="button" variant="contained" onClick={() => setDialogOpen(true)}> <Button type="button" variant="contained" onClick={() => setDialogOpen(true)}>
<SaveIcon /> <SaveIcon />
</Button> </Button>
<Button component='label' variant="contained">
<ImageSearch />
<input
hidden
accept={'*.json,*.jpg,*.jpeg,*.png,*.txt'}
type='file'
onChange={(event) => {
const { files } = event.target;
if (doesExist(files) && files.length > 0) {
const file = mustExist(files[0]);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
loadParamsFromFile(file).then((newParams) => {
if (doesExist(props.setParams) && doesExist(newParams)) {
props.setParams({
...props.params,
...newParams,
});
}
});
}
}}
/>
</Button>
</Stack> </Stack>
)} )}
onChange={(event, value) => { onChange={(event, value) => {
@ -124,3 +145,95 @@ export function Profiles(props: ProfilesProps) {
</Dialog> </Dialog>
</>; </>;
} }
export async function loadParamsFromFile(file: File): Promise<Partial<Txt2ImgParams>> {
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<Partial<Txt2ImgParams>> {
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<Txt2ImgParams> {
const lines = comment.split('\n');
const [prompt, maybeNegative, ...otherLines] = lines;
const params: Partial<Txt2ImgParams> = {
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;
}

View File

@ -776,6 +776,11 @@
"@typescript-eslint/types" "5.59.0" "@typescript-eslint/types" "5.59.0"
eslint-visitor-keys "^3.3.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": "@xobotyi/scrollbar-width@^1.9.5":
version "1.9.5" version "1.9.5"
resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" 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" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== 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: fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"