Compare commits

...
18 Commits
Author SHA1 Message Date
sakura1bgx dd9e3815a3 Update pyproject.toml 2024-09-03 22:01:20 +09:00
sakura1bgx 4c4583d4ab Add files via upload 2024-09-03 21:54:54 +09:00
sakura1bgx 3d8955297f Update README.md 2024-09-03 21:51:18 +09:00
sakura1bgx 7b1ce0a607 Update README.md 2024-09-03 21:47:32 +09:00
sakura1bgx 649e3a1c64 Update flipstreamviewer.py
- Refined button design and style managements.
- Refined nodes interface.
- Move preset control to the right pane and refined buttons.
- Add WD14Tagger threshould inputs.
2024-09-03 21:47:04 +09:00
sakura1bgx cecb717940 Update flipstreamviewer.py
- Fix random tags generation.
2024-08-28 06:44:36 +09:00
sakura1bgx 671b385b94 Update flipstreamviewer.py
- Fix toggle lora and tags.
2024-08-28 06:42:10 +09:00
sakura1bgx a73e4e4a33 Update README.md 2024-08-28 04:52:38 +09:00
sakura1bgx 7b22b8e0bd Update README.md 2024-08-28 04:49:38 +09:00
sakura1bgx 61a0f2b6ae Update pyproject.toml 2024-08-28 04:44:09 +09:00
sakura1bgx 1ab38e6e1a Update README.md 2024-08-28 04:42:34 +09:00
sakura1bgx 5bf7a9f062 Add files via upload 2024-08-28 04:41:44 +09:00
sakura1bgx dbdb4a59cd Merge pull request #5 from sakura1bgx/sakura1bgx-patch-1-4
Update pyproject.toml
2024-08-28 04:39:09 +09:00
sakura1bgx 0a77545c42 Merge pull request #4 from sakura1bgx/sakura1bgx-patch-1-1
Update workflow.json
2024-08-28 04:38:55 +09:00
sakura1bgx 7dbceda8d9 Merge pull request #3 from sakura1bgx/sakura1bgx-patch-1
Update flipstreamviewer.py
2024-08-28 04:38:38 +09:00
sakura1bgx 581f17f1fc Update pyproject.toml 2024-08-28 04:34:02 +09:00
sakura1bgx cd2b988e6e Update workflow.json 2024-08-28 04:28:24 +09:00
sakura1bgx 461edf21f4 Update flipstreamviewer.py
- Add the '{lora}' keyword to the prompt input. This keyword can be replaced with the contents of the LoRA input. If you don't include this keyword in the prompt, the LoRA input will be ignored.
- Fix the toggle buttons for LoRA and tags so that they correctly toggle within the LoRA input section.
2024-08-28 04:24:13 +09:00
5 changed files with 848 additions and 755 deletions
+20 -16
View File
@@ -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.
![viewer_snapshot](https://github.com/user-attachments/assets/61e79e55-111e-40b9-8417-f93acb90aed7)
![viewer_snapshot](https://github.com/user-attachments/assets/60a0b749-1f92-4287-935a-02f1a31767de)
## 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
View File
@@ -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
View File
@@ -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
View File
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