Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd9e3815a3 | ||
|
|
4c4583d4ab | ||
|
|
3d8955297f | ||
|
|
7b1ce0a607 | ||
|
|
649e3a1c64 | ||
|
|
cecb717940 | ||
|
|
671b385b94 | ||
|
|
a73e4e4a33 | ||
|
|
7b22b8e0bd | ||
|
|
61a0f2b6ae | ||
|
|
1ab38e6e1a | ||
|
|
5bf7a9f062 | ||
|
|
dbdb4a59cd | ||
|
|
0a77545c42 | ||
|
|
7dbceda8d9 | ||
|
|
581f17f1fc | ||
|
|
cd2b988e6e | ||
|
|
461edf21f4 |
@@ -2,7 +2,7 @@
|
||||
|
||||
**ComfyUI_FlipStreamViewer** is a tool that provides a viewer interface for flipping images with frame interpolation, allowing you to watch high-fidelity pseudo-videos without needing AnimateDiff.
|
||||
|
||||

|
||||

|
||||
|
||||
## Required Custom Nodes
|
||||
|
||||
@@ -24,18 +24,19 @@ Please install the following nodes via ComfyUI-Manager:
|
||||
|
||||
`http://localhost:8188/viewer`
|
||||
|
||||
3. To respond to viewer input:
|
||||
4. To respond to viewer input:
|
||||
- Check 'Extra options', 'Auto Queue', and 'instant' on the ComfyUI control panel.
|
||||
- Then click the 'Queue Prompt' button.
|
||||
|
||||
4. The 'preset' folder may be created in the current directory for saving and loading preset files. Sample presets can be found in the `presetsamples` folder.
|
||||
5. The 'preset' folder may be created in the current directory for saving and loading preset files. Sample presets can be found in the `presetsamples` folder.
|
||||
|
||||
## Prompt Format
|
||||
|
||||
Use the following format for input prompts. The 'pre text' and 'append text' sections apply to all frames. The separator `----` should consist of four hyphens, and each line of 'frameN text' applies to individual frames.
|
||||
Use the following format for input prompts. The 'pre text' and 'append text' sections apply to all frames. The separator `----` should consist of four hyphens, and each line of 'frameN text' applies to individual frames. The '{lora}' means it will be replaced by the LoRA input contents at the right pane.
|
||||
|
||||
```
|
||||
pre text,
|
||||
{lora}
|
||||
----
|
||||
- frame1 text
|
||||
- frame2 text
|
||||
@@ -45,9 +46,10 @@ append text
|
||||
|
||||
### Limitations
|
||||
|
||||
- Please note that this tool is not designed with security measures against third-party attacks. Therefore, it should only be used for personal purposes within a secure network.
|
||||
- There is no save video function available; please customize your workflow as needed.
|
||||
- Excessively long text may cause exceptions.
|
||||
- The keywords `BREAK`, `AND`, and `<lora:...>` cannot be used in the prompt.
|
||||
- The keywords `BREAK`, `AND` cannot be used in the prompt.
|
||||
|
||||
## Parameters
|
||||
|
||||
@@ -60,8 +62,8 @@ You can adjust the following parameters on the viewer:
|
||||
- **Nsta**: Start step for the second KSampler; increasing this value will stabilize the output between frames.
|
||||
- **Nfrm**: Number of frames; each frame undergoes x8 interpolation in the sample workflow.
|
||||
- **Nspf**: Seconds per frame displayed in the viewer stream.
|
||||
- **Nwth**: Threshold for WD14Tagger; A smaller value increases the level of detail.
|
||||
- **Ncth**: Character thresould for WD14Tagger
|
||||
- **Nwth**: Threshould for WD14Tagger.
|
||||
- **Ncth**: Character threshould for WD14Tagger.
|
||||
|
||||
## Inputs
|
||||
|
||||
@@ -80,36 +82,38 @@ Provide the following text inputs in the viewer:
|
||||
- **K**: Keep the seed to search for another good seed.
|
||||
- **B**: Go back to the previous seed.
|
||||
- **M**: Move the checkpoint file.
|
||||
- **L**: Load a preset.
|
||||
- **P**: Load a preset prompt only.
|
||||
- **S**: Save the current preset.
|
||||
- **D**: Delete a preset.
|
||||
|
||||
**Right Pane Buttons:**
|
||||
|
||||
- **L**: Load a preset.
|
||||
- **R**: Select random preset.
|
||||
- **P**: Load a preset prompt only.
|
||||
- **M**: Move the Preset file.
|
||||
- **S**: Save the current preset.
|
||||
- **T**: Toggle LoRA enable/disable.
|
||||
- **M**: Move the LoRA file.
|
||||
- **T**: Toggle tag enable/disable at the Prompt Input.
|
||||
- **R**: Switch to random tags at the Prompt Input.
|
||||
- **T**: Toggle tag enable/disable at the LoRA Input.
|
||||
- **R**: Switch to random tags at the LoRA Input.
|
||||
- **U**: Same as Update.
|
||||
- **W**: Apply WD14Tagger to the first frame; outputs tags to LoRA input.
|
||||
- **C**: Capture an image from another browser tab or desktop and set it as the first frame; useful with W.
|
||||
- **Clear Below**: Clear the LoRA input.
|
||||
|
||||
## Additional Features
|
||||
|
||||
- **LoRA Preview**: At the bottom of the right pane; click to jump to the Civitai LoRA page if found.
|
||||
- **Auto-hide**: Click to hide controls and images or the viewer auto-hides after 5 minutes of inactivity.
|
||||
|
||||
## Quality and Speed
|
||||
## For More Quality
|
||||
|
||||
- Use more higher-quality checkpoint models.
|
||||
- To enhance stability between frames, use a deliberate prompt in the 'pre text' to establish a consistent background, such as `on desk, ideal background`. Additionally, using LoRA can also improve stability.
|
||||
- To enhance stability between frames, use a deliberate prompt in the 'pre text' to establish a consistent background, such as `on desk, ideal background`.
|
||||
- Select the appropriate words in 'frameN text' to achieve the desired motion. This can be reused in other prompts.
|
||||
- Since 0sta is more flexible, it's better to change the Nsta only after testing those first.
|
||||
- Recommended speed setting: `4stp 1cfg 0sta dpmpp_2m,sgm_uniform` with `<lora:TCD-SDXL:1>`. You can download the TCD LoRA from [Civitai](https://civitai.com/models/395638). Rename the file to `TCD-SDXL.safetensors`.
|
||||
|
||||
## Customizing Workflow
|
||||
|
||||
You can customize the workflow for specific needs:
|
||||
|
||||
- **FlipStreamLoader**: Use the 'mode' parameter to switch between sub-directories for checkpoints and LoRAs, useful for managing models like sd15, pony, etc.
|
||||
- **FlipStreamViewer**: The 'allowip' parameter allows you to set IP addresses that can access the viewer. The 'w14exc' parameter is used to set exclude_tags for the WD14 Tagger.
|
||||
|
||||
+373
-270
@@ -6,6 +6,7 @@ import time
|
||||
from pathlib import Path
|
||||
from io import BytesIO
|
||||
|
||||
import requests
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw
|
||||
from aiohttp import web
|
||||
@@ -33,39 +34,63 @@ class AnyType(str):
|
||||
|
||||
any = AnyType("*")
|
||||
|
||||
HEAD="""
|
||||
HEAD=r"""
|
||||
<head>
|
||||
<style type="text/css">
|
||||
|
||||
html {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
body {
|
||||
color: lightgray;
|
||||
background: url(/stream) no-repeat top center local;
|
||||
margin: 0px;
|
||||
margin: 4px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
color: lightgray;
|
||||
background-color: black;
|
||||
width: 100%;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
color: lightgray;
|
||||
background-color: black;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
button {
|
||||
color: black;
|
||||
background-color: dimgray;
|
||||
font-size: 100%;
|
||||
border-radius: 4px;
|
||||
padding: 0px;
|
||||
min-width: 1.2em;
|
||||
}
|
||||
|
||||
select {
|
||||
color: lightgray;
|
||||
background-color: black;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
div.row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#leftPanel, #captureLeftPanel,
|
||||
#rightPanel, #captureRightPanel {
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
#centerPanel, captureCenterPanel {
|
||||
width: 76%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#captureDialog {
|
||||
display: none;
|
||||
position: fixed;
|
||||
@@ -76,12 +101,74 @@ select {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
#samplerSelect,
|
||||
#checkpointFolderSelect,
|
||||
#moveCheckpointSelect,
|
||||
#presetFolderSelect,
|
||||
#movePresetSelect,
|
||||
#loraFolderSelect,
|
||||
#moveLoraSelect {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#moveCheckpointSelect,
|
||||
#movePresetSelect,
|
||||
#moveLoraSelect {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#seedInput,
|
||||
#keepSeedInput,
|
||||
#checkpointFileSelect,
|
||||
#presetFileSelect,
|
||||
#loraFileSelect,
|
||||
#loraTagSelect {
|
||||
width: 4em;
|
||||
}
|
||||
|
||||
#presetTitleInput {
|
||||
width: 6em;
|
||||
}
|
||||
|
||||
#stepsRange,
|
||||
#cfgRange,
|
||||
#startstepRange,
|
||||
#framesRange,
|
||||
#framewaitRange,
|
||||
#wd14thRange,
|
||||
#wd14cthRange {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#toggleView {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#loraLink img {
|
||||
max-width: 100%;
|
||||
max-height: 10em;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#offsetXRange,
|
||||
#offsetYRange,
|
||||
#scaleRange {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
</style>
|
||||
<title>FlipStreamViewer</title>
|
||||
</head>
|
||||
"""
|
||||
|
||||
SCRIPT_PARAM="""
|
||||
SCRIPT_PARAM=r"""
|
||||
function getStateAsJson() {
|
||||
const autoUpdate = document.getElementById("autoUpdateCheckbox").checked;
|
||||
const presetTitle = document.getElementById("presetTitleInput").value;
|
||||
@@ -94,16 +181,16 @@ function getStateAsJson() {
|
||||
const loraTagOptions = JSON.stringify([...loraTagSelect.options].map(o => ({ value: o.value, text: o.text })));
|
||||
const loraTag = document.getElementById("loraTagSelect").value;
|
||||
const loraRate = document.getElementById("loraRate").value;
|
||||
const loraLinkHref = document.getElementById("loraLink").href;
|
||||
const loraPreviewSrc = document.getElementById("loraPreview").src;
|
||||
const loraLinkHref = document.getElementById("loraLink").getAttribute("href");
|
||||
const loraPreviewSrc = document.getElementById("loraPreview").getAttribute("src");
|
||||
const wd14th = parseFloat(document.getElementById("wd14thRange").value);
|
||||
const wd14cth = parseFloat(document.getElementById("wd14cthRange").value);
|
||||
return { autoUpdate: autoUpdate, presetTitle: presetTitle, presetFolder: presetFolder, presetFile: presetFile, checkpointFolder: checkpointFolder, loraRate: loraRate, loraFolder: loraFolder, loraFile: loraFile, loraTagOptions: loraTagOptions, loraTag: loraTag, loraLinkHref: loraLinkHref, loraPreviewSrc: loraPreviewSrc, loraTagOptions: loraTagOptions, wd14th: wd14th, wd14cth: wd14cth };
|
||||
}
|
||||
|
||||
function getParamAsJson() {
|
||||
const prompt = document.getElementById("promptInput").value;
|
||||
const negativePrompt = document.getElementById("negativePromptInput").value;
|
||||
const prompt = document.getElementById("promptInput").value.trim();
|
||||
const negativePrompt = document.getElementById("negativePromptInput").value.trim();
|
||||
const seed = parseInt(document.getElementById("seedInput").value) || 0;
|
||||
const keepSeed = parseInt(document.getElementById("keepSeedInput").value) || 0;
|
||||
const steps = parseInt(document.getElementById("stepsRange").value);
|
||||
@@ -111,7 +198,7 @@ function getParamAsJson() {
|
||||
const interval = parseInt(document.getElementById("intervalSelect").value);
|
||||
const sampler = document.getElementById("samplerSelect").value;
|
||||
const checkpoint = document.getElementById("checkpointFileSelect").value;
|
||||
const lora = document.getElementById("loraInput").value;
|
||||
const lora = document.getElementById("loraInput").value.trim();
|
||||
const startstep = parseInt(document.getElementById("startstepRange").value);
|
||||
const frames = parseFloat(document.getElementById("framesRange").value);
|
||||
const framewait = parseFloat(document.getElementById("framewaitRange").value);
|
||||
@@ -167,13 +254,11 @@ function updateParam(reload=false) {
|
||||
}
|
||||
"""
|
||||
|
||||
SCRIPT_PRESET="""
|
||||
function loadPreset(promptOnly=false) {
|
||||
var presetFile = document.getElementById("presetFileSelect");
|
||||
var presetTitle = document.getElementById("presetTitleInput")
|
||||
SCRIPT_PRESET=r"""
|
||||
function loadPreset(loraPromptOnly=false) {
|
||||
fetch("/load_preset", {
|
||||
method: "POST",
|
||||
body: JSON.stringify([getStateAsJson(), promptOnly]),
|
||||
body: JSON.stringify([getStateAsJson(), loraPromptOnly]),
|
||||
headers: {"Content-Type": "application/json"}
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
@@ -186,8 +271,34 @@ function loadPreset(promptOnly=false) {
|
||||
});
|
||||
}
|
||||
|
||||
function randomPreset() {
|
||||
const presetFileSelect = document.getElementById("presetFileSelect");
|
||||
presetFileSelect.selectedIndex = Math.floor(Math.random() * (presetFileSelect.options.length - 1)) + 1;
|
||||
}
|
||||
|
||||
function movePreset() {
|
||||
document.getElementById("movePresetSelect").style.display = "block";
|
||||
}
|
||||
|
||||
function movePresetFile() {
|
||||
const moveTo = document.getElementById("movePresetSelect").value;
|
||||
fetch("/move_presetfile", {
|
||||
method: "POST",
|
||||
body: JSON.stringify([getStateAsJson(), moveTo]),
|
||||
headers: {"Content-Type": "application/json"}
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
alert("Preset moved successfully!");
|
||||
} else {
|
||||
alert("Failed to move preset.");
|
||||
}
|
||||
}).catch(error => {
|
||||
alert("An error occurred while moving preset.");
|
||||
});
|
||||
}
|
||||
|
||||
function savePreset() {
|
||||
var title = document.getElementById("presetTitleInput").value;
|
||||
const title = document.getElementById("presetTitleInput").value;
|
||||
if (title == "") {
|
||||
alert("Please enter a title to save.");
|
||||
return;
|
||||
@@ -211,54 +322,23 @@ function savePreset() {
|
||||
alert("An error occurred while saving preset.");
|
||||
});
|
||||
}
|
||||
|
||||
function deletePreset() {
|
||||
var title = document.getElementById("presetTitleInput").value;
|
||||
if (title == "") {
|
||||
alert("Please enter a title to delete.");
|
||||
return;
|
||||
}
|
||||
if (!/^[a-zA-Z0-9-_]+$/.test(title)) {
|
||||
alert("Title can only contain [a-zA-Z0-9-_] characters.");
|
||||
return;
|
||||
}
|
||||
fetch("/delete_preset", {
|
||||
method: "POST",
|
||||
body: JSON.stringify([getStateAsJson(), getParamAsJson()]),
|
||||
headers: {"Content-Type": "application/json"}
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
alert("Preset deleted successfully!");
|
||||
location.reload();
|
||||
} else {
|
||||
alert("Failed to delete preset.");
|
||||
}
|
||||
}).catch(error => {
|
||||
alert("An error occurred while deleting preset.");
|
||||
});
|
||||
}
|
||||
"""
|
||||
|
||||
SCRIPT_CKPT="""
|
||||
SCRIPT_CKPT=r"""
|
||||
function moveCheckpoint() {
|
||||
const moveCheckpointSelect = document.getElementById("moveCheckpointSelect");
|
||||
moveCheckpointSelect.style.display = "block";
|
||||
document.getElementById("moveCheckpointSelect").style.display = "block";
|
||||
}
|
||||
|
||||
function moveCheckpointFile() {
|
||||
const moveCheckpointSelect = document.getElementById("moveCheckpointSelect");
|
||||
const checkpointFolder = document.getElementById("checkpointFolderSelect").value;
|
||||
const checkpoint = document.getElementById("checkpointFileSelect").value;
|
||||
const checkpointFolderTo = moveCheckpointSelect.value;
|
||||
|
||||
const moveFrom = document.getElementById("checkpointFileSelect").value;
|
||||
const moveTo = document.getElementById("moveCheckpointSelect").value;
|
||||
fetch("/move_checkpointfile", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({checkpoint: checkpoint, checkpointFolderTo: checkpointFolderTo}),
|
||||
body: JSON.stringify([getStateAsJson(), moveFrom, moveTo]),
|
||||
headers: {"Content-Type": "application/json"}
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
alert("Checkpoint moved successfully!");
|
||||
location.reload();
|
||||
} else {
|
||||
alert("Failed to move checkpoint.");
|
||||
}
|
||||
@@ -268,7 +348,7 @@ function moveCheckpointFile() {
|
||||
}
|
||||
"""
|
||||
|
||||
SCRIPT_LORA="""
|
||||
SCRIPT_LORA=r"""
|
||||
function selectLoraFile() {
|
||||
const loraFolder = document.getElementById("loraFolderSelect").value;
|
||||
const loraFileSelect = document.getElementById("loraFileSelect");
|
||||
@@ -281,9 +361,9 @@ function selectLoraFile() {
|
||||
const loraPreview = document.getElementById("loraPreview");
|
||||
|
||||
if (loraFile) {
|
||||
const re = new RegExp("<lora:" + loraName + ":[0-9.]+>[\\\\n]?", "g");
|
||||
const re = new RegExp("[\\n]?<lora:" + loraName + ":[0-9.]+>", "g");
|
||||
if (loraInput.value.search(re) == -1) {
|
||||
loraInput.value += "<lora:" + loraName + ":" + loraRate + ">\\n";
|
||||
loraInput.value += "\n<lora:" + loraName + ":" + loraRate + ">";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,16 +400,9 @@ function selectLoraFile() {
|
||||
loraTagSelect.appendChild(item);
|
||||
}
|
||||
}
|
||||
const hash = json._lorapreview_hash;
|
||||
if (hash) {
|
||||
fetch("https://civitai.com/api/v1/model-versions/by-hash/" + hash
|
||||
).then(response => response.json()).then(json => {
|
||||
loraLink.href = "https://civitai.com/models/" + json.modelId;
|
||||
loraPreview.src = json.images[0].url;
|
||||
}).catch(error => {
|
||||
loraPreview.src = "";
|
||||
});
|
||||
}
|
||||
|
||||
loraLink.href = json._lorapreview_href || "";
|
||||
loraPreview.src = json._lorapreview_src || "";
|
||||
});
|
||||
}
|
||||
|
||||
@@ -340,34 +413,28 @@ function toggleLora() {
|
||||
const loraRate = document.getElementById("loraRate").value;
|
||||
const loraInput = document.getElementById("loraInput");
|
||||
if (loraFile) {
|
||||
const re = new RegExp("<lora:" + loraName + ":[0-9.]+>[\\\\n]?", "g");
|
||||
const re = new RegExp("[\\n]?<lora:" + loraName + ":[0-9.]+>", "g");
|
||||
if (loraInput.value.search(re) >= 0) {
|
||||
loraInput.value = loraInput.value.replace(re, "");
|
||||
} else {
|
||||
loraInput.value += "<lora:" + loraName + ":" + loraRate + ">\\n";
|
||||
loraInput.value += "\n<lora:" + loraName + ":" + loraRate + ">";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function moveLora() {
|
||||
const moveLoraSelect = document.getElementById("moveLoraSelect");
|
||||
moveLoraSelect.style.display = "block";
|
||||
document.getElementById("moveLoraSelect").style.display = "block";
|
||||
}
|
||||
|
||||
function moveLoraFile() {
|
||||
const moveLoraSelect = document.getElementById("moveLoraSelect");
|
||||
const loraFolder = document.getElementById("loraFolderSelect").value;
|
||||
const loraFile = document.getElementById("loraFileSelect").value;
|
||||
const loraFolderTo = moveLoraSelect.value;
|
||||
|
||||
const moveTo = document.getElementById("moveLoraSelect").value;
|
||||
fetch("/move_lorafile", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({loraFolder: loraFolder, loraFile: loraFile, loraFolderTo: loraFolderTo}),
|
||||
body: JSON.stringify([getStateAsJson(), moveTo]),
|
||||
headers: {"Content-Type": "application/json"}
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
alert("Lora moved successfully!");
|
||||
location.reload();
|
||||
} else {
|
||||
alert("Failed to move lora.");
|
||||
}
|
||||
@@ -377,19 +444,19 @@ function moveLoraFile() {
|
||||
}
|
||||
"""
|
||||
|
||||
SCRIPT_TAG="""
|
||||
SCRIPT_TAG=r"""
|
||||
function toggleTag() {
|
||||
const loraTag = document.getElementById("loraTagSelect").value;
|
||||
const promptInput = document.getElementById("promptInput");
|
||||
const loraInput = document.getElementById("loraInput");
|
||||
if (loraTag) {
|
||||
const re = new RegExp(",\\\\s*" + loraTag, "g");
|
||||
if (promptInput.value.search(re) >= 0) {
|
||||
promptInput.value = promptInput.value.replace(re, "");
|
||||
const re = new RegExp("^\\s*" + loraTag + "\\s*(,\\s*|\n|$)|,\\s*" + loraTag + "\\s*(,|\n|$)", "g");
|
||||
if (loraInput.value.search(re) >= 0) {
|
||||
loraInput.value = loraInput.value.replace(re, "$2")
|
||||
} else {
|
||||
if (promptInput.value) {
|
||||
promptInput.value += ", " + loraTag;
|
||||
if (loraInput.value) {
|
||||
loraInput.value += ", " + loraTag;
|
||||
} else {
|
||||
promptInput.value = loraTag;
|
||||
loraInput.value = loraTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -397,38 +464,38 @@ function toggleTag() {
|
||||
|
||||
function randomTag() {
|
||||
const options = document.getElementById("loraTagSelect").options;
|
||||
const promptInput = document.getElementById("promptInput");
|
||||
const loraInput = document.getElementById("loraInput");
|
||||
const loraTag = options[Math.floor(Math.random() * options.length)].value;
|
||||
if (loraTag) {
|
||||
const re = new RegExp(",\\\\s*" + loraTag, "g");
|
||||
if (promptInput.value.search(re) < 0) {
|
||||
if (promptInput.value) {
|
||||
promptInput.value += ", " + loraTag;
|
||||
const re = new RegExp("^\\s*" + loraTag + "\\s*(,\\s*|\n|$)|,\\s*" + loraTag + "\\s*(,|\n|$)", "g");
|
||||
if (loraInput.value.search(re) < 0) {
|
||||
if (loraInput.value) {
|
||||
loraInput.value += ", " + loraTag;
|
||||
} else {
|
||||
promptInput.value = loraTag;
|
||||
loraInput.value = loraTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function addWD14Tag() {
|
||||
const loraInput = document.getElementById("loraInput");
|
||||
const wd14th = parseFloat(document.getElementById("wd14thRange").value);
|
||||
const wd14cth = parseFloat(document.getElementById("wd14cthRange").value);
|
||||
|
||||
fetch("/get_wd14tag", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({wd14th: wd14th, wd14cth: wd14cth}),
|
||||
body: JSON.stringify(getStateAsJson()),
|
||||
headers: {"Content-Type": "application/json"}
|
||||
}).then(response => response.json()).then(json => {
|
||||
loraInput.value += "\\n" + json.tags + "\\n";
|
||||
loraInput.value += "\n" + json.tags + "\n";
|
||||
}).catch(error => {
|
||||
alert(`Failed to fetch WD14 tags: ${error.stack}`);
|
||||
});
|
||||
}
|
||||
|
||||
function clearLoraInput() {
|
||||
document.getElementById("loraInput").value = "";
|
||||
}
|
||||
"""
|
||||
|
||||
SCRIPT_SEED="""
|
||||
SCRIPT_SEED=r"""
|
||||
function changeSeed(reload=false) {
|
||||
const seedInput = document.getElementById("seedInput");
|
||||
seedInput.value = Math.floor(Math.random() * 99999);
|
||||
@@ -465,7 +532,7 @@ function toggleAutoUpdate() {
|
||||
toggleAutoUpdate();
|
||||
"""
|
||||
|
||||
SCRIPT_VIEW="""
|
||||
SCRIPT_VIEW=r"""
|
||||
var toggleViewFlag = true;
|
||||
function toggleView() {
|
||||
const leftPanel = document.getElementById("leftPanel");
|
||||
@@ -496,7 +563,7 @@ function hideTimer() {
|
||||
hideTimer();
|
||||
"""
|
||||
|
||||
SCRIPT_CAPTURE="""
|
||||
SCRIPT_CAPTURE=r"""
|
||||
let capturedCanvas = document.createElement("canvas");
|
||||
async function capture() {
|
||||
try {
|
||||
@@ -524,9 +591,9 @@ function updateCanvas() {
|
||||
document.getElementById("captureDialog").style.display = "flex";
|
||||
const canvas = document.getElementById("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const scale = parseInt(document.getElementById("scaleRange").value, 10) / 100;
|
||||
const offsetX = parseInt(document.getElementById("offsetXRange").value, 10);
|
||||
const offsetY = parseInt(document.getElementById("offsetYRange").value, 10);
|
||||
const scale = parseInt(document.getElementById("scaleRange").value, 10) / 100;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.save();
|
||||
ctx.scale(scale, scale);
|
||||
@@ -539,6 +606,13 @@ function closeCapture() {
|
||||
document.getElementById("captureDialog").style.display = "none";
|
||||
}
|
||||
|
||||
function resetPos() {
|
||||
document.getElementById("offsetXRange").value = 0;
|
||||
document.getElementById("offsetYRange").value = 0;
|
||||
document.getElementById("scaleRange").value = 100;
|
||||
updateCanvas();
|
||||
}
|
||||
|
||||
function setFrame() {
|
||||
const canvas = document.getElementById("canvas");
|
||||
const dataURL = canvas.toDataURL("image/webp");
|
||||
@@ -608,146 +682,173 @@ async def viewer(request):
|
||||
raise HTTPForbidden()
|
||||
|
||||
return web.Response(text=f"""<html>{HEAD}<body>
|
||||
<div style="display: flex;">
|
||||
<div id="leftPanel" style="width: 12%;">
|
||||
<div class="row">
|
||||
<div id="leftPanel">
|
||||
<textarea id="promptInput" placeholder="Enter prompt" rows="15">{param["prompt"]}</textarea>
|
||||
<br>
|
||||
<textarea id="negativePromptInput" placeholder="Enter negativePrompt" rows="2">{param["negativePrompt"]}</textarea>
|
||||
<br>
|
||||
<button id="updateButton" onclick="updateParam(true)">Update</button>
|
||||
<button id="changeSeedButton" onclick="changeSeed(true)">Change</button>
|
||||
<br>
|
||||
Seed: <input id="seedInput" type="number" placeholder="Enter seed" value="{param["seed"]}" style="width: 5em;" />
|
||||
<br>
|
||||
<button id="keepSeedButton" onclick="keepSeed()">K</button>
|
||||
<button id="backSeedButton" onclick="backSeed()">B</button>
|
||||
<input id="keepSeedInput" type="number" placeholder="keepSeed" value="{param["keepSeed"]}" style="width: 5em;" />
|
||||
<br>
|
||||
Iv:<select id="intervalSelect" onchange="updateParam()">
|
||||
<option value="10" {"selected" if param["interval"] == 10 else ""}>10</option>
|
||||
<option value="15" {"selected" if param["interval"] == 15 else ""}>15</option>
|
||||
<option value="20" {"selected" if param["interval"] == 20 else ""}>20</option>
|
||||
<option value="25" {"selected" if param["interval"] == 25 else ""}>25</option>
|
||||
<option value="30" {"selected" if param["interval"] == 30 else ""}>30</option>
|
||||
</select>
|
||||
<input id="autoUpdateCheckbox" type="checkbox" onchange="toggleAutoUpdate()"{" checked" if state["autoUpdate"] else ""}>Auto</input>
|
||||
<br>
|
||||
<input id="stepsRange" type="range" min="1" max="25" step="1" value="{param["steps"]}" style="width: 50%;" oninput="stepsValue.innerText = this.value;" />
|
||||
<span id="stepsValue">{param["steps"]}</span>stp
|
||||
<br>
|
||||
<input id="cfgRange" type="range" min="1.0" max="7.0" step="0.1" value="{param["cfg"]}" style="width: 50%;" oninput="cfgValue.innerText = this.value;" />
|
||||
<span id="cfgValue">{param["cfg"]}</span>cfg
|
||||
<br>
|
||||
<input id="startstepRange" type="range" min="0" max="6" step="1" value="{param["startstep"]}" style="width: 50%;" oninput="startstepValue.innerText = this.value;" />
|
||||
<span id="startstepValue">{param["startstep"]}</span>sta
|
||||
<br>
|
||||
<input id="framesRange" type="range" min="1" max="8" step="1" value="{param["frames"]}" style="width: 50%;" oninput="framesValue.innerText = this.value;" />
|
||||
<span id="framesValue">{param["frames"]}</span>frm
|
||||
<br>
|
||||
<input id="framewaitRange" type="range" min="0" max="2" step="0.1" value="{param["framewait"]}" style="width: 50%;" oninput="framewaitValue.innerText = this.value;" />
|
||||
<span id="framewaitValue">{param["framewait"]}</span>spf
|
||||
<br>
|
||||
<select id="samplerSelect" onchange="updateParam()" style="width: 100%;">
|
||||
<div class="row">
|
||||
<button id="updateButton" onclick="updateParam(true)">Update</button>
|
||||
<button id="changeSeedButton" onclick="changeSeed(true)">Change</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
Seed: <input id="seedInput" type="number" placeholder="Enter seed" value="{param["seed"]}" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<button id="keepSeedButton" onclick="keepSeed()">K</button>
|
||||
<button id="backSeedButton" onclick="backSeed()">B</button>
|
||||
<input id="keepSeedInput" type="number" placeholder="keepSeed" value="{param["keepSeed"]}" />
|
||||
</div>
|
||||
<div class="row">
|
||||
Iv:<select id="intervalSelect" onchange="updateParam()">
|
||||
<option value="10" {"selected" if param["interval"] == 10 else ""}>10</option>
|
||||
<option value="15" {"selected" if param["interval"] == 15 else ""}>15</option>
|
||||
<option value="20" {"selected" if param["interval"] == 20 else ""}>20</option>
|
||||
<option value="25" {"selected" if param["interval"] == 25 else ""}>25</option>
|
||||
<option value="30" {"selected" if param["interval"] == 30 else ""}>30</option>
|
||||
</select>
|
||||
<input id="autoUpdateCheckbox" type="checkbox" onchange="toggleAutoUpdate()"{" checked" if state["autoUpdate"] else ""}>Auto</input>
|
||||
</div>
|
||||
<div class="row">
|
||||
<input id="stepsRange" type="range" min="1" max="25" step="1" value="{param["steps"]}" oninput="stepsValue.innerText = this.value;" />
|
||||
<span id="stepsValue">{param["steps"]}</span>stp
|
||||
</div>
|
||||
<div class="row">
|
||||
<input id="cfgRange" type="range" min="1.0" max="7.0" step="0.1" value="{param["cfg"]}" oninput="cfgValue.innerText = this.value;" />
|
||||
<span id="cfgValue">{param["cfg"]}</span>cfg
|
||||
</div>
|
||||
<div class="row">
|
||||
<input id="startstepRange" type="range" min="0" max="6" step="1" value="{param["startstep"]}" oninput="startstepValue.innerText = this.value;" />
|
||||
<span id="startstepValue">{param["startstep"]}</span>sta
|
||||
</div>
|
||||
<div class="row">
|
||||
<input id="framesRange" type="range" min="1" max="8" step="1" value="{param["frames"]}" oninput="framesValue.innerText = this.value;" />
|
||||
<span id="framesValue">{param["frames"]}</span>frm
|
||||
</div>
|
||||
<div class="row">
|
||||
<input id="framewaitRange" type="range" min="0" max="2" step="0.1" value="{param["framewait"]}" oninput="framewaitValue.innerText = this.value;" />
|
||||
<span id="framewaitValue">{param["framewait"]}</span>spf
|
||||
</div>
|
||||
<select id="samplerSelect" onchange="updateParam()">
|
||||
<option value="dpmpp_2m,sgm_uniform" {"selected" if param["sampler"] == "dpmpp_2m,sgm_uniform" else ""}>dpmpp_2m,sgm_uniform</option>
|
||||
<option value="ddim,ddim_uniform" {"selected" if param["sampler"] == "ddim,ddim_uniform" else ""}>ddim,ddim_uniform</option>
|
||||
<option value="eular,normal" {"selected" if param["sampler"] == "eular,normal" else ""}>eular,normal</option>
|
||||
</select>
|
||||
<br>
|
||||
<select id="checkpointFolderSelect" onchange="updateParam(true)" style="width: 100%;">
|
||||
<select id="checkpointFolderSelect" onchange="updateParam(true)">
|
||||
<option value="">checkpoint folder</option>
|
||||
{"".join([f'<option value="{dir.name}"{" selected" if state["checkpointFolder"] == dir.name else ""}>{dir.name}</option>' for dir in Path("ComfyUI/models/checkpoints", param["mode"]).glob("*/")])}
|
||||
</select>
|
||||
<select id="checkpointFileSelect" style="width: 50%;">
|
||||
<option value="">checkpoint</option>
|
||||
{f'<option value="{param["checkpoint"]}" selected>*{Path(param["checkpoint"]).stem}</option>' if param['checkpoint'] else ""}
|
||||
{"".join([f'<option value="{file.parent.name + "/" + file.name}">{file.stem}</option>' if param["checkpoint"] != file.parent.name + "/" + file.name else "" for file in Path("ComfyUI/models/checkpoints", param["mode"], state["checkpointFolder"]).glob("*.safetensors")])}
|
||||
</select>
|
||||
<button id="moveCheckpoint" onClick="moveCheckpoint()">M</button>
|
||||
<select id="moveCheckpointSelect" onchange="moveCheckpointFile()" style="width: 100%; display: none;">
|
||||
<div class="row">
|
||||
<select id="checkpointFileSelect">
|
||||
<option value="">checkpoint</option>
|
||||
{f'<option value="{param["checkpoint"]}" selected>*{Path(param["checkpoint"]).stem}</option>' if param['checkpoint'] else ""}
|
||||
{"".join([f'<option value="{file.relative_to("ComfyUI/models/checkpoints")}">{file.stem}</option>' if param["checkpoint"] != file.relative_to("ComfyUI/models/checkpoints") else "" for file in Path("ComfyUI/models/checkpoints", param["mode"], state["checkpointFolder"]).glob("*.safetensors")])}
|
||||
</select>
|
||||
<button id="moveCheckpointButton" onClick="moveCheckpoint()">M</button>
|
||||
</div>
|
||||
<select id="moveCheckpointSelect" onchange="moveCheckpointFile()">
|
||||
<option value="default" disabled selected>move to</option>
|
||||
{"".join([f'<option value="{dir.name}"{" selected" if state["checkpointFolder"] == dir.name else ""}>{dir.name}</option>' for dir in Path("ComfyUI/models/checkpoints", param["mode"]).glob("*/")])}
|
||||
{"".join([f'<option value="{dir.name}">{dir.name}</option>' for dir in Path("ComfyUI/models/checkpoints", param["mode"]).glob("*/")])}
|
||||
</select>
|
||||
<select id="presetFolderSelect" onchange="updateParam(true)" style="width: 100%;">
|
||||
</div>
|
||||
<div id="centerPanel">
|
||||
<button id="toggleView" onclick="toggleView()"></button>
|
||||
</div>
|
||||
<div id="rightPanel">
|
||||
<select id="presetFolderSelect" onchange="updateParam(true)">
|
||||
<option value="">preset folder</option>
|
||||
{"".join([f'<option value="{dir.name}"{" selected" if state["presetFolder"] == dir.name else ""}>{dir.name}</option>' for dir in Path("preset").glob("*/")])}
|
||||
</select>
|
||||
<select id="presetFileSelect" style="width: 50%;">
|
||||
<option value="" disabled selected>preset</option>
|
||||
{"".join([f'<option value="{file.name}"{" selected" if state["presetFile"] == file.name else ""}>{file.stem}</option>' for file in Path("preset", state["presetFolder"]).glob("*.json")])}
|
||||
<div class="row">
|
||||
<select id="presetFileSelect">
|
||||
<option value="" disabled selected>preset</option>
|
||||
{"".join([f'<option value="{file.name}"{" selected" if state["presetFile"] == file.name else ""}>{file.stem}</option>' for file in Path("preset", state["presetFolder"]).glob("*.json")])}
|
||||
</select>
|
||||
<button id="loadPresetButton" onclick="loadPreset()">L</button>
|
||||
<button id="randomPresetButton" onclick="randomPreset()">R</button>
|
||||
<button id="loadPresetLoraPromptButton" onclick="loadPreset(true)">P</button>
|
||||
<button id="movePresetButton" onclick="movePreset()">M</button>
|
||||
</div>
|
||||
<select id="movePresetSelect" onchange="movePresetFile()">
|
||||
<option value="default" disabled selected>move to</option>
|
||||
{"".join([f'<option value="{dir.name}">{dir.name}</option>' for dir in Path("preset").glob("*/")])}
|
||||
</select>
|
||||
<button id="loadPreset" onclick="loadPreset()">L</button>
|
||||
<button id="loadPresetPrompt" onclick="loadPreset(true)">P</button>
|
||||
<br>
|
||||
<input id="presetTitleInput" placeholder="preset title" value="{state["presetTitle"]}" style="width: 50%;" />
|
||||
<button id="savePreset" onclick="savePreset()">S</button>
|
||||
<button id="deletePreset" onclick="deletePreset()">D</button>
|
||||
</div>
|
||||
<div id="centerPanel" style="width: 76%;">
|
||||
<button id="toggleView" onclick="toggleView()" style="width: 100%; height: 100%; border: none; background: transparent;"></button>
|
||||
</div>
|
||||
<div id="rightPanel" style="width: 12%;">
|
||||
<select id="loraFolderSelect" onchange="updateParam(true)" style="width: 100%;">
|
||||
<div class="row">
|
||||
<input id="presetTitleInput" placeholder="preset title" value="{state["presetTitle"]}" />
|
||||
<button id="savePresetButton" onclick="savePreset()">S</button>
|
||||
</div>
|
||||
<select id="loraFolderSelect" onchange="updateParam(true)">
|
||||
<option value="">lora folder</option>
|
||||
{"".join([f'<option value="{dir.name}"{" selected" if state["loraFolder"] == dir.name else ""}>{dir.name}</option>' for dir in Path("ComfyUI/models/loras", param["mode"]).glob("*/")])}
|
||||
</select>
|
||||
<select id="loraFileSelect" onchange="selectLoraFile()" style="width: 50%;">
|
||||
<option value="" disabled selected>lora file</option>
|
||||
{"".join([f'<option value="{file.name}"{" selected" if state["loraFile"] == file.name else ""}>{file.stem}</option>' for file in Path("ComfyUI/models/loras", param["mode"], state["loraFolder"]).glob("*.safetensors")])}
|
||||
</select>
|
||||
<button id="toggleLora" onclick="toggleLora()">T</button>
|
||||
<button id="moveLora" onClick="moveLora()">M</button>
|
||||
<br>
|
||||
<select id="moveLoraSelect" onchange="moveLoraFile()" style="width: 100%; display: none;">
|
||||
<div class="row">
|
||||
<select id="loraFileSelect" onchange="selectLoraFile()">
|
||||
<option value="" disabled selected>lora file</option>
|
||||
{"".join([f'<option value="{file.name}"{" selected" if state["loraFile"] == file.name else ""}>{file.stem}</option>' for file in Path("ComfyUI/models/loras", param["mode"], state["loraFolder"]).glob("*.safetensors")])}
|
||||
</select>
|
||||
<button id="toggleLoraButton" onclick="toggleLora()">T</button>
|
||||
<button id="moveLoraButton" onClick="moveLora()">M</button>
|
||||
</div>
|
||||
<select id="moveLoraSelect" onchange="moveLoraFile()">
|
||||
<option value="default" disabled selected>move to</option>
|
||||
{"".join([f'<option value="{dir.name}"{" selected" if state["loraFolder"] == dir.name else ""}>{dir.name}</option>' for dir in Path("ComfyUI/models/loras", param["mode"]).glob("*/")])}
|
||||
{"".join([f'<option value="{dir.name}">{dir.name}</option>' for dir in Path("ComfyUI/models/loras", param["mode"]).glob("*/")])}
|
||||
</select>
|
||||
<select id="loraTagSelect" onchange="toggleTag()" style="width: 50%;">
|
||||
<option value="" disabled selected>tag</option>
|
||||
{"".join([f'<option value="{opt["value"]}"{" selected" if state["loraTag"] == opt["value"] else ""}>{opt["text"]}</option>' if opt["value"] else "" for opt in json.loads(state["loraTagOptions"])])}
|
||||
</select>
|
||||
<button id="toggleTag" onclick="toggleTag()">T</button>
|
||||
<button id="randomTag" onclick="randomTag()">R</button>
|
||||
<br>
|
||||
<button id="updateButton2" onclick="updateParam(true)">U</button>
|
||||
<button id="wd14Button" onclick="addWD14Tag()">W</button>
|
||||
<button id="captureButton" onclick="capture()">C</button>
|
||||
<select id="loraRate">
|
||||
<option value="1" {"selected" if state["loraRate"] == "1" else ""}>1</option>
|
||||
<option value="0.9" {"selected" if state["loraRate"] == "0.9" else ""}>0.9</option>
|
||||
<option value="0.8" {"selected" if state["loraRate"] == "0.8" else ""}>0.8</option>
|
||||
<option value="0.7" {"selected" if state["loraRate"] == "0.7" else ""}>0.7</option>
|
||||
<option value="0.6" {"selected" if state["loraRate"] == "0.6" else ""}>0.6</option>
|
||||
<option value="0.5" {"selected" if state["loraRate"] == "0.5" else ""}>0.5</option>
|
||||
<option value="0.4" {"selected" if state["loraRate"] == "0.4" else ""}>0.4</option>
|
||||
<option value="0.3" {"selected" if state["loraRate"] == "0.3" else ""}>0.3</option>
|
||||
<option value="0.2" {"selected" if state["loraRate"] == "0.2" else ""}>0.2</option>
|
||||
<option value="0.1" {"selected" if state["loraRate"] == "0.1" else ""}>0.1</option>
|
||||
</select>
|
||||
<br>
|
||||
<input id="wd14thRange" type="range" min="0" max="1" step="0.01" value="{state["wd14th"]}" style="width: 50%;" oninput="wd14thValue.innerText = this.value;" />
|
||||
<span id="wd14thValue">{state["wd14th"]}</span>wth
|
||||
<br>
|
||||
<input id="wd14cthRange" type="range" min="0" max="1" step="0.01" value="{state["wd14cth"]}" style="width: 50%;" oninput="wd14cthValue.innerText = this.value;" />
|
||||
<span id="wd14cthValue">{state["wd14cth"]}</span>cth
|
||||
<br>
|
||||
<div class="row">
|
||||
<select id="loraTagSelect" onchange="toggleTag()">
|
||||
<option value="" disabled selected>tag</option>
|
||||
{"".join([f'<option value="{opt["value"]}"{" selected" if state["loraTag"] == opt["value"] else ""}>{opt["text"]}</option>' if opt["value"] else "" for opt in json.loads(state["loraTagOptions"])])}
|
||||
</select>
|
||||
<button id="toggleTagButton" onclick="toggleTag()">T</button>
|
||||
<button id="randomTagButton" onclick="randomTag()">R</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button id="updateButton2" onclick="updateParam(true)">U</button>
|
||||
<button id="wd14Button" onclick="addWD14Tag()">W</button>
|
||||
<button id="captureButton" onclick="capture()">C</button>
|
||||
<select id="loraRate">
|
||||
<option value="1" {"selected" if state["loraRate"] == "1" else ""}>1</option>
|
||||
<option value="0.9" {"selected" if state["loraRate"] == "0.9" else ""}>0.9</option>
|
||||
<option value="0.8" {"selected" if state["loraRate"] == "0.8" else ""}>0.8</option>
|
||||
<option value="0.7" {"selected" if state["loraRate"] == "0.7" else ""}>0.7</option>
|
||||
<option value="0.6" {"selected" if state["loraRate"] == "0.6" else ""}>0.6</option>
|
||||
<option value="0.5" {"selected" if state["loraRate"] == "0.5" else ""}>0.5</option>
|
||||
<option value="0.4" {"selected" if state["loraRate"] == "0.4" else ""}>0.4</option>
|
||||
<option value="0.3" {"selected" if state["loraRate"] == "0.3" else ""}>0.3</option>
|
||||
<option value="0.2" {"selected" if state["loraRate"] == "0.2" else ""}>0.2</option>
|
||||
<option value="0.1" {"selected" if state["loraRate"] == "0.1" else ""}>0.1</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row">
|
||||
<input id="wd14thRange" type="range" min="0" max="1" step="0.01" value="{state["wd14th"]}" oninput="wd14thValue.innerText = this.value;" />
|
||||
<span id="wd14thValue">{state["wd14th"]}</span>wth
|
||||
</div>
|
||||
<div class="row">
|
||||
<input id="wd14cthRange" type="range" min="0" max="1" step="0.01" value="{state["wd14cth"]}" oninput="wd14cthValue.innerText = this.value;" />
|
||||
<span id="wd14cthValue">{state["wd14cth"]}</span>cth
|
||||
</div>
|
||||
<div class="row">
|
||||
<button id="clearLoraInputButton" onclick="clearLoraInput()">Clear Below</button>
|
||||
</div>
|
||||
<textarea id="loraInput" placeholder="Enter lora" rows="15">{param["lora"]}</textarea>
|
||||
<br>
|
||||
<a id="loraLink" href="{state["loraLinkHref"]}" target="_blank">
|
||||
<img id="loraPreview" src="{state["loraPreviewSrc"]}" style="max-width: 100%; max-height: 15em; width:auto; height:auto; margin:auto; display:block;" />
|
||||
</a>
|
||||
<div class="row">
|
||||
<a id="loraLink" href="{state["loraLinkHref"] or "javascript:void(0)"}" target="_blank">
|
||||
<img id="loraPreview" src="{state["loraPreviewSrc"]}" alt onerror="this.onerror = null; this.src='';" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="captureDialog">
|
||||
<div style="width: 12%;"></div>
|
||||
<div style="width: 76%; text-align: center;">
|
||||
<canvas id="canvas" width="960" height="600"/>
|
||||
<div id="captureLeftPanel"></div>
|
||||
<div id="captureCenterPanel">
|
||||
<canvas id="canvas" width="960" height="600" />
|
||||
</div>
|
||||
<div style="width: 12%;">
|
||||
X <input id="offsetXRange" type="range" min="-960" max="960" value="{param["offsetX"]}" onchange="updateCanvas()" style="width: 80%;">
|
||||
<div id="captureRightPanel">
|
||||
<button id="resetPosButton" onclick="resetPos()">Reset</button>
|
||||
<br>
|
||||
Y <input id="offsetYRange" type="range" min="-600" max="600" value="{param["offsetY"]}" onchange="updateCanvas()" style="width: 80%;">
|
||||
X <input id="offsetXRange" type="range" min="-960" max="960" value="{param["offsetX"]}" onchange="updateCanvas()">
|
||||
<br>
|
||||
S <input id="scaleRange" type="range" min="50" max="400" value="{param["scale"]}" onchange="updateCanvas()" style="width: 80%;">
|
||||
Y <input id="offsetYRange" type="range" min="-600" max="600" value="{param["offsetY"]}" onchange="updateCanvas()">
|
||||
<br>
|
||||
S <input id="scaleRange" type="range" min="50" max="400" value="{param["scale"]}" onchange="updateCanvas()">
|
||||
<br>
|
||||
<button id="setframeButton" onclick="setFrame()">SetFrame</button>
|
||||
<button id="closeButton" onclick="closeCapture()">Close</button>
|
||||
@@ -799,7 +900,12 @@ async def get_lorainfo(request):
|
||||
|
||||
header_json = json.loads(header)
|
||||
lorainfo = header_json["__metadata__"] if "__metadata__" in header_json else {}
|
||||
lorainfo["_lorapreview_hash"] = hash
|
||||
|
||||
if hash:
|
||||
res = requests.get("https://civitai.com/api/v1/model-versions/by-hash/" + hash).json()
|
||||
lorainfo["_lorapreview_href"] = "https://civitai.com/models/" + str(res["modelId"])
|
||||
lorainfo["_lorapreview_src"] = res["images"][0]["url"]
|
||||
|
||||
return web.json_response(lorainfo)
|
||||
|
||||
|
||||
@@ -808,12 +914,13 @@ async def get_wd14tag(request):
|
||||
if request.remote not in allowed_ips:
|
||||
raise HTTPForbidden()
|
||||
|
||||
prm = await request.json()
|
||||
stt = await request.json()
|
||||
state.update(stt);
|
||||
tags = []
|
||||
if frame_buffer:
|
||||
with BytesIO(frame_buffer[0]) as buf:
|
||||
image = Image.open(buf)
|
||||
tags = await wd14tagger.tag(image, "wd-v1-4-moat-tagger-v2.onnx", prm["wd14th"], prm["wd14cth"], exclude_tags, trailing_comma=True)
|
||||
tags = await wd14tagger.tag(image, "wd-v1-4-moat-tagger-v2.onnx", state["wd14th"], state["wd14cth"], exclude_tags)
|
||||
return web.json_response({"tags": tags})
|
||||
|
||||
|
||||
@@ -834,11 +941,26 @@ async def move_lorafile(request):
|
||||
if request.remote not in allowed_ips:
|
||||
raise HTTPForbidden()
|
||||
|
||||
req = await request.json()
|
||||
loraPathFrom = Path("ComfyUI/models/checkpoints", param["mode"], req["checkpoint"])
|
||||
loraPathTo = Path("ComfyUI/models/checkpoints", param["mode"], req["checkpointFolderTo"], loraPathFrom.name)
|
||||
Path(loraPathFrom).rename(loraPathTo)
|
||||
state["checkpointFolder"] = req["checkpointFolderTo"]
|
||||
stt, moveFrom, moveTo = await request.json()
|
||||
state.update(stt)
|
||||
pathFrom = Path("ComfyUI/models/checkpoints", param["mode"], state["checkpointFolder"], Path(moveFrom).name)
|
||||
pathTo = Path("ComfyUI/models/checkpoints", param["mode"], moveTo, pathFrom.name)
|
||||
Path(pathFrom).rename(pathTo)
|
||||
state["checkpointFolder"] = moveTo
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@server.PromptServer.instance.routes.post("/move_presetfile")
|
||||
async def move_presetfile(request):
|
||||
if request.remote not in allowed_ips:
|
||||
raise HTTPForbidden()
|
||||
|
||||
stt, moveTo = await request.json()
|
||||
state.update(stt)
|
||||
pathFrom = Path("preset", state["presetFolder"], state["presetFile"])
|
||||
pathTo = Path("preset", moveTo, pathFrom.name)
|
||||
Path(pathFrom).rename(pathTo)
|
||||
state["presetFolder"] = moveTo
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@@ -847,12 +969,12 @@ async def move_lorafile(request):
|
||||
if request.remote not in allowed_ips:
|
||||
raise HTTPForbidden()
|
||||
|
||||
req = await request.json()
|
||||
loraPathFrom = Path("ComfyUI/models/loras", param["mode"], req["loraFolder"], req["loraFile"])
|
||||
loraPathTo = Path("ComfyUI/models/loras", param["mode"], req["loraFolderTo"], req["loraFile"])
|
||||
Path(loraPathFrom).rename(loraPathTo)
|
||||
state["loraFolder"] = req["loraFolderTo"]
|
||||
state["loraFile"] = req["loraFile"]
|
||||
stt, moveTo = await request.json()
|
||||
state.update(stt)
|
||||
pathFrom = Path("ComfyUI/models/loras", param["mode"], state["loraFolder"], state["loraFile"])
|
||||
pathTo = Path("ComfyUI/models/loras", param["mode"], moveTo, pathFrom.name)
|
||||
Path(pathFrom).rename(pathTo)
|
||||
state["loraFolder"] = moveTo
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@@ -861,28 +983,20 @@ async def load_preset(request):
|
||||
if request.remote not in allowed_ips:
|
||||
raise HTTPForbidden()
|
||||
|
||||
stt, promptOnly = await request.json()
|
||||
stt, loraPromptOnly = await request.json()
|
||||
state.update(stt)
|
||||
data = param
|
||||
with open(Path("preset", state["presetFolder"], state["presetFile"]), "r") as file:
|
||||
buf = json.load(file)
|
||||
if promptOnly:
|
||||
data["prompt"] = buf["prompt"]
|
||||
if "postfix" in buf:
|
||||
data["postfix"] = buf["postfix"]
|
||||
if "negativePrompt" in buf:
|
||||
data["negativePrompt"] = buf["negativePrompt"]
|
||||
if loraPromptOnly:
|
||||
if "lora" in buf:
|
||||
data["lora"] = base64.b64decode(buf["lora"]).decode("utf-8")
|
||||
|
||||
else:
|
||||
data = buf
|
||||
|
||||
data["prompt"] = base64.b64decode(data["prompt"]).decode("utf-8")
|
||||
if "postfix" in buf:
|
||||
data["prompt"] += base64.b64decode(data["postfix"]).decode("utf-8")
|
||||
data["negativePrompt"] = base64.b64decode(data["negativePrompt"]).decode("utf-8") if "negativePrompt" in data else ""
|
||||
try:
|
||||
data["lora"] = base64.b64decode(data["lora"]).decode("utf-8")
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
data["prompt"] = base64.b64decode(buf["prompt"]).decode("utf-8")
|
||||
data["negativePrompt"] = base64.b64decode(buf["negativePrompt"]).decode("utf-8") if "negativePrompt" in buf else ""
|
||||
data["lora"] = base64.b64decode(buf["lora"]).decode("utf-8")
|
||||
|
||||
param.update(data)
|
||||
state["presetTitle"] = Path(state["presetFile"]).stem
|
||||
@@ -905,18 +1019,6 @@ async def save_preset(request):
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
@server.PromptServer.instance.routes.post("/delete_preset")
|
||||
async def delete_preset(request):
|
||||
if request.remote not in allowed_ips:
|
||||
raise HTTPForbidden()
|
||||
|
||||
[stt, data] = await request.json()
|
||||
state.update(stt)
|
||||
param.update(data)
|
||||
Path("preset", state["presetFolder"], state["presetTitle"] + ".json").unlink(missing_ok=True)
|
||||
return web.Response(status=200)
|
||||
|
||||
|
||||
class FlipStreamLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
@@ -935,13 +1037,14 @@ class FlipStreamLoader:
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return param["checkpoint"]
|
||||
|
||||
def loader(self, mode, default_ckpt, **kwargs):
|
||||
def loader(self, mode, default_ckpt):
|
||||
global frame_updating
|
||||
frame_updating = True
|
||||
param["mode"] = mode
|
||||
ckpt_path = None
|
||||
if param["checkpoint"]:
|
||||
ckpt_path = folder_paths.get_full_path("checkpoints", param["mode"] + "/" + param["checkpoint"])
|
||||
else:
|
||||
ckpt_path = folder_paths.get_full_path("checkpoints", param["checkpoint"])
|
||||
if ckpt_path is None:
|
||||
ckpt_path = folder_paths.get_full_path("checkpoints", default_ckpt)
|
||||
out = comfy.sd.load_checkpoint_guess_config(ckpt_path, output_vae=True, output_clip=True, embedding_directory=folder_paths.get_folder_paths("embeddings"))
|
||||
return out[:3]
|
||||
@@ -952,26 +1055,25 @@ class FlipStreamUpdate:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {}}
|
||||
|
||||
RETURN_TYPES = ("STRING","STRING","STRING","STRING","INT","INT","FLOAT","STRING",comfy.samplers.KSampler.SAMPLERS,comfy.samplers.KSampler.SCHEDULERS)
|
||||
RETURN_NAMES = ("prompt","batchPrompt","appPrompt","negativePrompt","seed","steps","cfg","lora","sampler","scheduler")
|
||||
RETURN_TYPES = ("STRING","STRING","STRING","STRING")
|
||||
RETURN_NAMES = ("prompt","batchPrompt","appPrompt","negativePrompt")
|
||||
FUNCTION = "update"
|
||||
CATEGORY = "FlipStreamViewer"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return (param["prompt"], param["negativePrompt"], param["seed"], param["steps"], param["cfg"], param["lora"], param["sampler"])
|
||||
return (param["prompt"], param["negativePrompt"], param["lora"])
|
||||
|
||||
def update(self, *kwargs):
|
||||
def update(self, **kwargs):
|
||||
global frame_updating
|
||||
frame_updating = True
|
||||
|
||||
buf = param["prompt"].split("----\n")
|
||||
prompt = buf[0]
|
||||
prompt = buf[0].replace("{lora}", param["lora"])
|
||||
batchPrompt = buf[1] if len(buf) > 1 else "-\n-\n"
|
||||
appPrompt = buf[2] if len(buf) > 2 else ""
|
||||
batchPrompt = ",\n".join([f'"{n}":"{item.lstrip("-").strip()}"' for n, item in enumerate(batchPrompt.strip().split("\n"))])
|
||||
sampler, scheduler = param["sampler"].split(",")
|
||||
return (prompt, batchPrompt, appPrompt, param["negativePrompt"], param["seed"], param["steps"], param["cfg"], param["lora"], sampler, scheduler)
|
||||
return (prompt, batchPrompt, appPrompt, param["negativePrompt"])
|
||||
|
||||
|
||||
class FlipStreamOption:
|
||||
@@ -979,21 +1081,22 @@ class FlipStreamOption:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {}}
|
||||
|
||||
RETURN_TYPES = ("INT","INT",["enable", "disable"])
|
||||
RETURN_NAMES = ("startstep","frames","start_noise")
|
||||
RETURN_TYPES = ("INT","INT",["enable", "disable"],"INT","INT","FLOAT",comfy.samplers.KSampler.SAMPLERS,comfy.samplers.KSampler.SCHEDULERS)
|
||||
RETURN_NAMES = ("startstep","frames","start_noise","seed","steps","cfg","sampler","scheduler")
|
||||
FUNCTION = "option"
|
||||
CATEGORY = "FlipStreamViewer"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return (param["startstep"], param["frames"])
|
||||
return (param["startstep"], param["frames"], param["seed"], param["steps"], param["cfg"], param["sampler"])
|
||||
|
||||
def option(self, **kwargs):
|
||||
if param["startstep"] == 0:
|
||||
start_noise = "enable"
|
||||
else:
|
||||
start_noise = "disable"
|
||||
return (param["startstep"], param["frames"], start_noise)
|
||||
sampler, scheduler = param["sampler"].split(",")
|
||||
return (param["startstep"], param["frames"], start_noise, param["seed"], param["steps"], param["cfg"], sampler, scheduler)
|
||||
|
||||
|
||||
class FlipStreamViewer:
|
||||
@@ -1002,8 +1105,8 @@ class FlipStreamViewer:
|
||||
return {
|
||||
"required": {
|
||||
"tensor": ("IMAGE",),
|
||||
"allowip": ("STRING",),
|
||||
"wd14exc": ("STRING",),
|
||||
"allowip": ("STRING", {"default": ""}),
|
||||
"wd14exc": ("STRING", {"default": ""}),
|
||||
"idle": ("FLOAT", {"default": 1.0}),
|
||||
},
|
||||
}
|
||||
@@ -1022,7 +1125,7 @@ class FlipStreamViewer:
|
||||
FUNCTION = "update_frame"
|
||||
CATEGORY = "FlipStreamViewer"
|
||||
|
||||
def update_frame(self, tensor, idle, **kwargs):
|
||||
def update_frame(self, tensor, **kwargs):
|
||||
global frame_updating
|
||||
global frame_buffer
|
||||
buffer = []
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui_flipstreamviewer"
|
||||
description = "ComfyUI_FlipStreamViewer is a tool that provides a viewer interface for flipping images with frame interpolation, allowing you to watch high-fidelity pseudo-videos without needing AnimateDiff."
|
||||
version = "1.0.1"
|
||||
description = "ComfyUI_FlipStreamViewer is a tool that provides a viewer interface for flipping images with frame interpolation, allowing you to watch high-fidelity pseudo-videos without needing AnimateDiff. Please note that this tool is not designed with security measures against third-party attacks. Therefore, it should only be used for personal purposes within a secure network."
|
||||
version = "1.1.0"
|
||||
license = {file = "LICENSE"}
|
||||
|
||||
[project.urls]
|
||||
|
||||
+453
-467
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 347 KiB After Width: | Height: | Size: 343 KiB |
Reference in New Issue
Block a user