commit 3af2cf56797bb848d2d9a75e7cc3856f68cccec8 Author: sakura1bgx Date: Sun Aug 25 23:54:48 2024 +0900 Add files via upload diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..f57e875 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 sakura1bgx + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8b7fcda --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# ComfyUI_FlipStreamViewer + +**ComfyUI_FlipStreamViewer** is a tool that provides a viewer interface for flipping images with frame interpolation, allowing you to create high-fidelity pseudo-videos without needing AnimateDiff. + +## Required Custom Nodes + +Please install the following nodes via ComfyUI-Manager: + +- ComfyUI Impact Pack +- ComfyUI Frame Interpolation +- ComfyUI WD 1.4 Tagger +- FizzNodes +- LoRA Tag Loader for ComfyUI + +## Getting Started + +1. **Run ComfyUI** and load the `workflow.json` file located in the `workflows` folder. +2. Access the viewer at the following URL if ComfyUI is running at `http://localhost:8188`: + +http://localhost:8188/viewer + + +3. To respond to viewer input: +- Check 'Extra options', 'Auto Queue', and 'instant'. +- Click the 'Queue Prompt' button on the ComfyUI control panel. + +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. + +## 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. + +''' +pre text, +---- +- frame1 text +- frame2 text +---- +append text +''' + +### Limitations + +- Excessively long text may cause exceptions. +- The keywords `BREAK`, `AND`, and `` cannot be used in the prompt. + +## Parameters + +You can adjust the following parameters on the viewer: + +- **Seed**: Random seed for KSampler. +- **Iv**: Interval in seconds for auto-changing the random seed. +- **Nstp**: Steps for KSampler. +- **Ncfg**: CFG for KSampler. +- **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. + +## Inputs + +Provide the following text inputs in the viewer: + +- **Prompt Input**: Located at the top of the left pane (refer to the Prompt Format section above). +- **Negative Prompt Input**: Located below the Prompt Input; this applies to all frames. +- **LoRA Input**: Located in the middle of the right pane; input LoRA such as ``. + +## Controls + +**Left Pane Buttons:** + +- **Update**: Apply input data to the workflow. +- **Change**: Change the seed and update. +- **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:** + +- **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. +- **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. + +## 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; the viewer auto-hides after 5 minutes of inactivity. + +## Quality and Speed + +- Use more higher-quality checkpoint models. +- Recommended speed setting: `4stp 1cfg 0sta dpmpp_2m,sgm_uniform` with ``. 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. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..5f80308 --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +from .flipstreamviewer import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS + +__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] diff --git a/flipstreamviewer.py b/flipstreamviewer.py new file mode 100644 index 0000000..fb56545 --- /dev/null +++ b/flipstreamviewer.py @@ -0,0 +1,1034 @@ +import asyncio +import base64 +import hashlib +import json +import time +from pathlib import Path +from io import BytesIO + +import numpy as np +from PIL import Image, ImageDraw +from aiohttp import web +from aiohttp.web_exceptions import HTTPForbidden, HTTPError + +import server +import folder_paths +import comfy + +wd14tagger = __import__("ComfyUI-WD14-Tagger").wd14tagger + +checkpoints_list = folder_paths.get_filename_list("checkpoints") + +allowed_ips = ["127.0.0.1"] +param = {"mode": "", "prompt": "", "negativePrompt": "", "seed": 0, "keepSeed": 0, "steps": 13, "cfg": 4, "interval": 30, "sampler": "dpmpp_2m,sgm_uniform", "checkpoint": "", "lora": "", "startstep": 0, "frames": 1, "framewait": 1, "offsetX": 0, "offsetY": 0, "scale": 100} +state = {"autoUpdate": False, "presetTitle": time.strftime("%Y%m%d-%H%M"), "presetFolder": "", "presetFile": "", "loraRate": "1.0", "checkpointFolder": "", "loraFolder": "", "loraFile": "", "loraTagOptions": "[]", "loraTag": "", "loraLinkHref": "", "loraPreviewSrc": ""} +frame_updating = False +frame_buffer = [] + +class AnyType(str): + def __ne__(self, __value: object) -> bool: + return False + +any = AnyType("*") + +HEAD=""" + + +FlipStreamViewer + +""" + +SCRIPT_PARAM=""" +function getStateAsJson() { + const autoUpdate = document.getElementById("autoUpdateCheckbox").checked; + const presetTitle = document.getElementById("presetTitleInput").value; + const presetFolder = document.getElementById("presetFolderSelect").value; + const presetFile = document.getElementById("presetFileSelect").value; + const checkpointFolder = document.getElementById("checkpointFolderSelect").value; + const loraFolder = document.getElementById("loraFolderSelect").value; + const loraFile = document.getElementById("loraFileSelect").value; + const loraTagSelect = document.getElementById("loraTagSelect"); + 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; + 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 }; +} + +function getParamAsJson() { + const prompt = document.getElementById("promptInput").value; + const negativePrompt = document.getElementById("negativePromptInput").value; + const seed = parseInt(document.getElementById("seedInput").value) || 0; + const keepSeed = parseInt(document.getElementById("keepSeedInput").value) || 0; + const steps = parseInt(document.getElementById("stepsRange").value); + const cfg = parseFloat(document.getElementById("cfgRange").value); + 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 startstep = parseInt(document.getElementById("startstepRange").value); + const frames = parseFloat(document.getElementById("framesRange").value); + const framewait = parseFloat(document.getElementById("framewaitRange").value); + const offsetX = parseInt(document.getElementById("offsetXRange").value); + const offsetY = parseInt(document.getElementById("offsetYRange").value); + const scale = parseInt(document.getElementById("scaleRange").value); + return { prompt: prompt, negativePrompt: negativePrompt, seed: seed, keepSeed: keepSeed, steps: steps, cfg: cfg, interval: interval, sampler: sampler, checkpoint: checkpoint, lora: lora, startstep: startstep, frames: frames, framewait: framewait, offsetX: offsetX, offsetY: offsetY, scale: scale }; +} + +function setParam(param) { + document.getElementById("promptInput").value = param.prompt || ""; + document.getElementById("negativePromptInput").value = param.negativePrompt || ""; + document.getElementById("seedInput").value = param.seed || 0; + document.getElementById("keepSeedInput").value = param.keepSeed || 0; + document.getElementById("stepsRange").value = param.steps || 13; + document.getElementById("stepsValue").innerText = param.steps || 13; + document.getElementById("cfgRange").value = param.cfg || 4; + document.getElementById("cfgValue").innerText = param.cfg || 4; + document.getElementById("intervalSelect").value = param.interval || 30; + document.getElementById("samplerSelect").value = param.sampler || "dpmpp_2m,sgm_uniform"; + document.getElementById("checkpointFileSelect").value = param.checkpoint || ""; + document.getElementById("loraInput").value = param.lora || ""; + document.getElementById("startstepRange").value = param.startstep || 0; + document.getElementById("startstepValue").innerText = param.startstep || 0; + document.getElementById("framesRange").value = param.frames || 1; + document.getElementById("framesValue").innerText = param.frames || 1; + document.getElementById("framewaitRange").value = param.framewait || 1; + document.getElementById("framewaitValue").innerText = param.framewait || 1; + document.getElementById("offsetXRange").value = param.offsetX || 0; + document.getElementById("offsetYRange").value = param.offsetY || 0; + document.getElementById("scaleRange").value = param.scale || 100; +} + +function updateParam(reload=false) { + var updateButton = document.getElementById("updateButton"); + updateButton.disabled = true; + fetch("/update_param", { + method: "POST", + body: JSON.stringify([getStateAsJson(), getParamAsJson()]), + headers: {"Content-Type": "application/json"} + }).then(response => { + if (response.ok) { + if (reload) { + location.reload(); + } + } else { + alert("Failed to update parameter."); + } + }).catch(error => { + alert("An error occurred while updating parameter."); + }); + updateButton.disabled = false; +} +""" + +SCRIPT_PRESET=""" +function loadPreset(promptOnly=false) { + var presetFile = document.getElementById("presetFileSelect"); + var presetTitle = document.getElementById("presetTitleInput") + fetch("/load_preset", { + method: "POST", + body: JSON.stringify([getStateAsJson(), promptOnly]), + headers: {"Content-Type": "application/json"} + }).then(response => { + if (response.ok) { + location.reload(); + } else { + alert("Failed to load preset."); + } + }).catch(error => { + alert("An error occurred while loading preset: " + error); + }); +} + +function savePreset() { + var title = document.getElementById("presetTitleInput").value; + if (title == "") { + alert("Please enter a title to save."); + return; + } + if (!/^[a-zA-Z0-9-_]+$/.test(title)) { + alert("Title can only contain [a-zA-Z0-9-_] characters."); + return; + } + fetch("/save_preset", { + method: "POST", + body: JSON.stringify([getStateAsJson(), getParamAsJson()]), + headers: {"Content-Type": "application/json"} + }).then(response => { + if (response.ok) { + alert("Preset saved successfully!"); + location.reload(); + } else { + alert("Failed to save preset."); + } + }).catch(error => { + 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=""" +function moveCheckpoint() { + const moveCheckpointSelect = document.getElementById("moveCheckpointSelect"); + 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; + + fetch("/move_checkpointfile", { + method: "POST", + body: JSON.stringify({checkpoint: checkpoint, checkpointFolderTo: checkpointFolderTo}), + headers: {"Content-Type": "application/json"} + }).then(response => { + if (response.ok) { + alert("Checkpoint moved successfully!"); + location.reload(); + } else { + alert("Failed to move checkpoint."); + } + }).catch(error => { + alert("An error occurred while moving checkpoint."); + }); +} +""" + +SCRIPT_LORA=""" +function selectLoraFile() { + const loraFolder = document.getElementById("loraFolderSelect").value; + const loraFileSelect = document.getElementById("loraFileSelect"); + const loraName = loraFileSelect.options[loraFileSelect.selectedIndex].text; + const loraFile = loraFileSelect.value; + const loraRate = document.getElementById("loraRate").value; + const loraInput = document.getElementById("loraInput"); + const loraTagSelect = document.getElementById("loraTagSelect"); + const loraLink = document.getElementById("loraLink"); + const loraPreview = document.getElementById("loraPreview"); + + if (loraFile) { + const re = new RegExp("[\\\\n]?", "g"); + if (loraInput.value.search(re) == -1) { + loraInput.value += "\\n"; + } + } + + fetch("/get_lorainfo", { + method: "POST", + body: JSON.stringify({loraFolder: loraFolder, loraFile: loraFile}), + headers: {"Content-Type": "application/json"} + }).then(response => response.json()).then(json => { + loraTagSelect.innerHTML = ""; + + item = document.createElement("option"); + item.value = ""; + item.text = "tag"; + loraTagSelect.appendChild(item); + + if (json.ss_tag_frequency) { + const datasets = JSON.parse(json.ss_tag_frequency); + const tags = {}; + for (const setName in datasets) { + const set = datasets[setName]; + for (const t in set) { + if (t in tags) { + tags[t] += set[t]; + } else { + tags[t] = set[t]; + } + } + } + const sorted_tags = Object.entries(tags).sort((a, b) => b[1] - a[1]); + for (const i in sorted_tags) { + item = document.createElement("option"); + item.value = sorted_tags[i][0].trim(); + item.text = sorted_tags[i][0].trim() + ":" + sorted_tags[i][1]; + 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 = ""; + }); + } + }); +} + +function toggleLora() { + const loraFileSelect = document.getElementById("loraFileSelect"); + const loraName = loraFileSelect.options[loraFileSelect.selectedIndex].text; + const loraFile = loraFileSelect.value; + const loraRate = document.getElementById("loraRate").value; + const loraInput = document.getElementById("loraInput"); + if (loraFile) { + const re = new RegExp("[\\\\n]?", "g"); + if (loraInput.value.search(re) >= 0) { + loraInput.value = loraInput.value.replace(re, ""); + } else { + loraInput.value += "\\n"; + } + } +} + +function moveLora() { + const moveLoraSelect = document.getElementById("moveLoraSelect"); + 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; + + fetch("/move_lorafile", { + method: "POST", + body: JSON.stringify({loraFolder: loraFolder, loraFile: loraFile, loraFolderTo: loraFolderTo}), + headers: {"Content-Type": "application/json"} + }).then(response => { + if (response.ok) { + alert("Lora moved successfully!"); + location.reload(); + } else { + alert("Failed to move lora."); + } + }).catch(error => { + alert("An error occurred while moving lora."); + }); +} +""" + +SCRIPT_TAG=""" +function toggleTag() { + const loraTag = document.getElementById("loraTagSelect").value; + const promptInput = document.getElementById("promptInput"); + if (loraTag) { + const re = new RegExp(",\\\\s*" + loraTag, "g"); + if (promptInput.value.search(re) >= 0) { + promptInput.value = promptInput.value.replace(re, ""); + } else { + if (promptInput.value) { + promptInput.value += ", " + loraTag; + } else { + promptInput.value = loraTag; + } + } + } +} + +function randomTag() { + const options = document.getElementById("loraTagSelect").options; + const promptInput = document.getElementById("promptInput"); + 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; + } else { + promptInput.value = loraTag; + } + } + } +} + +async function addWD14Tag() { + const loraInput = document.getElementById("loraInput"); + + fetch("/get_wd14tag", { + method: "GET", + }).then(response => response.json()).then(json => { + loraInput.value += "\\n" + json.tags + "\\n"; + }).catch(error => { + alert(`Failed to fetch WD14 tags: ${error.stack}`); + }); +} +""" + +SCRIPT_SEED=""" +function changeSeed(reload=false) { + const seedInput = document.getElementById("seedInput"); + seedInput.value = Math.floor(Math.random() * 99999); + updateParam(reload); +} + +function keepSeed() { + const seed = parseInt(document.getElementById("seedInput").value) || 0; + const keepSeedInput = document.getElementById("keepSeedInput"); + keepSeedInput.value = seed; +} + +function backSeed() { + const keepSeed = parseInt(document.getElementById("keepSeedInput").value) || 0; + const seedInput = document.getElementById("seedInput"); + seedInput.value = keepSeed; +} + +var intervalId = 0; +function toggleAutoUpdate() { + const autoUpdate = document.getElementById("autoUpdateCheckbox").checked; + const interval = parseInt(document.getElementById("intervalSelect").value); + if (autoUpdate) { + intervalId = setInterval(function() { + changeSeed(); + }, interval * 1000); + } else { + if (intervalId) { + clearInterval(intervalId); + intervalId = 0; + } + } +} +toggleAutoUpdate(); +""" + +SCRIPT_VIEW=""" +var toggleViewFlag = true; +function toggleView() { + const leftPanel = document.getElementById("leftPanel"); + const rightPanel = document.getElementById("rightPanel"); + if (toggleViewFlag) { + leftPanel.style.visibility = "hidden"; + rightPanel.style.visibility = "hidden"; + toggleViewFlag = false; + } else { + document.body.style.backgroundImage = "none"; + leftPanel.style.visibility = "visible"; + rightPanel.style.visibility = "visible"; + toggleViewFlag = true; + } +} + +function hideTimer() { + setTimeout(function() { + document.getElementById("autoUpdateCheckbox").checked = false; + toggleAutoUpdate(); + closeCapture(); + document.body.style.backgroundImage = "none"; + leftPanel.style.visibility = "hidden"; + rightPanel.style.visibility = "hidden"; + toggleViewFlag = false; + }, 300 * 1000); +} +hideTimer(); +""" + +SCRIPT_CAPTURE=""" +let capturedCanvas = document.createElement("canvas"); +async function capture() { + try { + const video = document.createElement("video"); + video.srcObject = await navigator.mediaDevices.getDisplayMedia({ video: true }); + video.style.display = "none"; + video.play(); + video.addEventListener("loadedmetadata", () => { + const { videoWidth, videoHeight } = video; + capturedCtx = capturedCanvas.getContext("2d"); + capturedCtx.clearRect(0, 0, capturedCanvas.width, capturedCanvas.height); + capturedCanvas.width = videoWidth; + capturedCanvas.height = videoHeight; + capturedCtx.drawImage(video, 0, 0); + video.srcObject.getTracks().forEach(track => track.stop()); + video.srcObject = null; + updateCanvas(); + }); + } catch (error) { + alert("An error occurred while capture."); + } +} + +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); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + ctx.scale(scale, scale); + ctx.translate(offsetX, offsetY); + ctx.drawImage(capturedCanvas, 0, 0); + ctx.restore(); +} + +function closeCapture() { + document.getElementById("captureDialog").style.display = "none"; +} + +function setFrame() { + const canvas = document.getElementById("canvas"); + const dataURL = canvas.toDataURL("image/webp"); + fetch("/set_frame", { + method: "POST", + body: JSON.stringify([getStateAsJson(), getParamAsJson(), dataURL]), + headers: { "Content-Type": "application/json" } + }).then(response => { + if (response.ok) { + alert("Set frame successfully!"); + location.reload(); + } else { + alert("Failed to set frame."); + } + }).catch(error => { + alert("An error occurred while set frame."); + }); +} +""" + + +@server.PromptServer.instance.routes.get("/stream") +async def stream(request): + if request.remote not in allowed_ips: + raise HTTPForbidden() + + response = web.StreamResponse() + response.content_type = "multipart/x-mixed-replace; boundary=frame" + await response.prepare(request) + + update_start = None + while request.transport and not request.transport.is_closing(): + if frame_buffer: + if len(frame_buffer) > 1: + frame = frame_buffer.pop(0) + frame_buffer.append(frame) + else: + frame = frame_buffer[0] + + if frame_updating: + if update_start is None: + update_start = time.time() + with BytesIO(frame) as inbuf: + image = Image.open(inbuf) + w, h = image.size + s = (time.time() - update_start + 1) * 4 + draw = ImageDraw.Draw(image) + draw.rectangle((int(w / 2 - s), 16, int(w / 2 + s), 32), fill="blue", outline="blue", width=1) + with BytesIO() as outbuf: + image.save(outbuf, format="WEBP", quality=100) + frame = outbuf.getvalue() + else: + update_start = None + + await response.write(b"--frame\r\nContent-Type: image/webp\r\n\r\n" + frame + b"\r\n") + await response.write(b"--frame\r\nContent-Type: image/webp\r\n\r\n" + frame + b"\r\n") + + if param["framewait"] > 0: + await asyncio.sleep(param["framewait"] * 0.1) + + return response + + +@server.PromptServer.instance.routes.get("/viewer") +async def viewer(request): + if request.remote not in allowed_ips: + print(request.remote) + raise HTTPForbidden() + + return web.Response(text=f"""{HEAD} +
+
+ +
+ +
+ + +
+ Seed: +
+ + + +
+ Iv: + Auto +
+ + {param["steps"]}stp +
+ + {param["cfg"]}cfg +
+ + {param["startstep"]}sta +
+ + {param["frames"]}frm +
+ + {param["framewait"]}spf +
+ +
+ + + + + + + + +
+ + + +
+
+ +
+
+ + + + +
+ + + + +
+ + + + +
+ +
+ + + +
+
+
+
+
+ +
+
+ X +
+ Y +
+ S +
+ + +
+
+ + """, content_type="text/html") + + +@server.PromptServer.instance.routes.post("/update_param") +async def update_param(request): + if request.remote not in allowed_ips: + raise HTTPForbidden() + + stt, prm = await request.json() + state.update(stt) + param.update(prm) + return web.Response(status=200) + + +@server.PromptServer.instance.routes.post("/get_lorainfo") +async def get_lorainfo(request): + if request.remote not in allowed_ips: + raise HTTPForbidden() + + req = await request.json() + loraPath = Path("ComfyUI/models/loras", param["mode"], req["loraFolder"], req["loraFile"]) + hash = None + with open(loraPath, "rb") as file: + header_size = int.from_bytes(file.read(8), "little", signed=False) + if header_size <= 0: + raise HTTPError("get_lorainfo: Invalid header size") + + header = file.read(header_size) + if header_size <= 0: + raise HTTPError("get_lorainfo: Invalid header") + + file.seek(0) + hash = hashlib.sha256(file.read()).hexdigest() + + header_json = json.loads(header) + lorainfo = header_json["__metadata__"] if "__metadata__" in header_json else {} + lorainfo["_lorapreview_hash"] = hash + return web.json_response(lorainfo) + + +@server.PromptServer.instance.routes.get("/get_wd14tag") +async def get_wd14tag(request): + if request.remote not in allowed_ips: + raise HTTPForbidden() + + 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") + return web.json_response({"tags": tags}) + + +@server.PromptServer.instance.routes.post("/set_frame") +async def set_frame(request): + global frame_buffer + if request.remote not in allowed_ips: + raise HTTPForbidden() + + stt, prm, dataURL = await request.json() + state.update(stt) + param.update(prm) + _, data = dataURL.split(",", 1) + frame_buffer = [base64.b64decode(data)] + return web.Response(status=200) + + +@server.PromptServer.instance.routes.post("/move_checkpointfile") +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"] + return web.Response(status=200) + + +@server.PromptServer.instance.routes.post("/move_lorafile") +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"] + return web.Response(status=200) + + +@server.PromptServer.instance.routes.post("/load_preset") +async def load_preset(request): + if request.remote not in allowed_ips: + raise HTTPForbidden() + + stt, promptOnly = 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"] + 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) + + param.update(data) + state["presetTitle"] = Path(state["presetFile"]).stem + return web.Response(status=200) + + +@server.PromptServer.instance.routes.post("/save_preset") +async def save_preset(request): + if request.remote not in allowed_ips: + raise HTTPForbidden() + + [stt, data] = await request.json() + state.update(stt) + param.update(data) + data["prompt"] = base64.b64encode(bytes(data["prompt"], "utf-8")).decode("utf-8") + data["negativePrompt"] = base64.b64encode(bytes(data["negativePrompt"], "utf-8")).decode("utf-8") + data["lora"] = base64.b64encode(bytes(data["lora"], "utf-8")).decode("utf-8") + with open(Path("preset", state["presetFolder"], state["presetTitle"] + ".json"), "w") as file: + json.dump(data, file) + 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): + return { + "required": { + "mode": ("STRING", {"default": param["mode"]}), + "default_ckpt": (checkpoints_list,), + } + } + + RETURN_TYPES = ("MODEL", "CLIP", "VAE") + FUNCTION = "loader" + CATEGORY = "FlipStreamViewer" + + @classmethod + def IS_CHANGED(cls, **kwargs): + return param["checkpoint"] + + def loader(self, mode, default_ckpt, **kwargs): + global frame_updating + frame_updating = True + param["mode"] = mode + if param["checkpoint"]: + ckpt_path = folder_paths.get_full_path("checkpoints", param["mode"] + "/" + param["checkpoint"]) + else: + 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] + + +class FlipStreamUpdate: + @classmethod + 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") + 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"]) + + def update(self, *kwargs): + global frame_updating + frame_updating = True + + buf = param["prompt"].split("----\n") + prompt = buf[0] + 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) + + +class FlipStreamOption: + @classmethod + def INPUT_TYPES(s): + return {"required": {}} + + RETURN_TYPES = ("INT","INT",["enable", "disable"]) + RETURN_NAMES = ("startstep","frames","start_noise") + FUNCTION = "option" + CATEGORY = "FlipStreamViewer" + + @classmethod + def IS_CHANGED(cls, **kwargs): + return (param["startstep"], param["frames"]) + + def option(self, **kwargs): + if param["startstep"] == 0: + start_noise = "enable" + else: + start_noise = "disable" + return (param["startstep"], param["frames"], start_noise) + + +class FlipStreamViewer: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "tensor": ("IMAGE",), + "idle": ("FLOAT", {"default": 1.0}), + }, + } + + @classmethod + def IS_CHANGED(cls, idle, **kwargs): + time.sleep(idle) + return None + + RETURN_TYPES = () + OUTPUT_NODE = True + FUNCTION = "update_frame" + CATEGORY = "FlipStreamViewer" + + def update_frame(self, tensor, idle): + global frame_updating + global frame_buffer + buffer = [] + for i in range(tensor.shape[0]): + image = Image.fromarray(np.clip(255. * tensor[i,:,:,:].cpu().numpy().squeeze(), 0, 255).astype(np.uint8)) + with BytesIO() as image_bytes: + image.save(image_bytes, format="WEBP", quality=100) + buffer.append(image_bytes.getvalue()) + buffer += buffer[::-1] + frame_updating = False + frame_buffer = buffer + return () + + +NODE_CLASS_MAPPINGS = { + "FlipStreamLoader": FlipStreamLoader, + "FlipStreamUpdate": FlipStreamUpdate, + "FlipStreamOption": FlipStreamOption, + "FlipStreamViewer": FlipStreamViewer, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "FlipStreamLoader": "FlipStreamLoader", + "FlipStreamUpdate": "FlipStreamUpdate", + "FlipStreamOption": "FlipStreamOption", + "FlipStreamViewer": "FlipStreamViewer", +} diff --git a/presetsamples/cat.json b/presetsamples/cat.json new file mode 100644 index 0000000..e360593 --- /dev/null +++ b/presetsamples/cat.json @@ -0,0 +1 @@ +{"prompt": "aGlnaHJlcywgb24gdGFibGUsIGlkZWFsIGJhY2tncm91bmQsIHNvbG8sIGJsYWNrIGNhdCwKLS0tLQotIAotIHNpdHRpbmcKLS0tLQo=", "negativePrompt": "", "seed": 0, "keepSeed": 0, "steps": 13, "cfg": 4, "interval": 30, "sampler": "dpmpp_2m,sgm_uniform", "checkpoint": "", "lora": "", "startstep": 0, "frames": 2, "framewait": 1, "offsetX": 0, "offsetY": 0, "scale": 100} \ No newline at end of file diff --git a/workflows/workflow.json b/workflows/workflow.json new file mode 100644 index 0000000..a7a7892 --- /dev/null +++ b/workflows/workflow.json @@ -0,0 +1,1543 @@ +{ + "last_node_id": 349, + "last_link_id": 1093, + "nodes": [ + { + "id": 88, + "type": "RepeatLatentBatch", + "pos": [ + -670.4625255718743, + -857.6950814796875 + ], + "size": { + "0": 210, + "1": 54 + }, + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 819 + }, + { + "name": "amount", + "type": "INT", + "link": 1069, + "widget": { + "name": "amount" + } + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 851 + ], + "slot_index": 0, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "RepeatLatentBatch" + }, + "widgets_values": [ + 16 + ] + }, + { + "id": 149, + "type": "KSamplerAdvanced", + "pos": [ + -415, + -756 + ], + "size": { + "0": 210, + "1": 294 + }, + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 931 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 1093 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 954 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 910 + }, + { + "name": "noise_seed", + "type": "INT", + "link": 1051, + "widget": { + "name": "noise_seed" + } + }, + { + "name": "steps", + "type": "INT", + "link": 1053, + "widget": { + "name": "steps" + } + }, + { + "name": "cfg", + "type": "FLOAT", + "link": 1055, + "widget": { + "name": "cfg" + } + }, + { + "name": "start_at_step", + "type": "INT", + "link": 1065, + "widget": { + "name": "start_at_step" + } + }, + { + "name": "add_noise", + "type": "COMBO", + "link": 1073, + "widget": { + "name": "add_noise" + } + }, + { + "name": "sampler_name", + "type": "COMBO", + "link": 1060, + "widget": { + "name": "sampler_name" + } + }, + { + "name": "scheduler", + "type": "COMBO", + "link": 1061, + "widget": { + "name": "scheduler" + } + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 579 + ], + "slot_index": 0, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "KSamplerAdvanced" + }, + "widgets_values": [ + "disable", + 231567818888929, + "randomize", + 9, + 7, + "dpmpp_2m", + "sgm_uniform", + 3, + 10000, + "disable" + ] + }, + { + "id": 150, + "type": "VAEDecode", + "pos": [ + -408.46252557187427, + -416.6950814796875 + ], + "size": { + "0": 196.5991973876953, + "1": 46 + }, + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 579 + }, + { + "name": "vae", + "type": "VAE", + "link": 1078 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 991 + ], + "slot_index": 0, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "VAEDecode" + } + }, + { + "id": 154, + "type": "CLIPTextEncode", + "pos": [ + -1202.9145114203122, + -640.0866598906147 + ], + "size": { + "0": 210, + "1": 54 + }, + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 913 + }, + { + "name": "text", + "type": "STRING", + "link": 1049, + "widget": { + "name": "text" + } + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 785, + 954 + ], + "slot_index": 0, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 173, + "type": "EmptyLatentImage", + "pos": [ + -936.8961622465587, + -859.9747392674933 + ], + "size": { + "0": 210, + "1": 106 + }, + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 791 + ], + "slot_index": 0, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "EmptyLatentImage" + }, + "widgets_values": [ + 960, + 600, + 1 + ] + }, + { + "id": 176, + "type": "LoraTagLoader", + "pos": [ + -1200.9145114203122, + -856.0866598906147 + ], + "size": { + "0": 210, + "1": 74 + }, + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 1075 + }, + { + "name": "clip", + "type": "CLIP", + "link": 1076 + }, + { + "name": "text", + "type": "STRING", + "link": 1057, + "widget": { + "name": "text" + } + } + ], + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 931, + 932 + ], + "slot_index": 0, + "shape": 3 + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 913, + 914, + 1084 + ], + "slot_index": 1, + "shape": 3 + }, + { + "name": "STRING", + "type": "STRING", + "links": [], + "slot_index": 2, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoraTagLoader" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 249, + "type": "KSamplerAdvanced", + "pos": [ + -939, + -708 + ], + "size": { + "0": 210, + "1": 294 + }, + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "model", + "type": "MODEL", + "link": 932 + }, + { + "name": "positive", + "type": "CONDITIONING", + "link": 845 + }, + { + "name": "negative", + "type": "CONDITIONING", + "link": 785 + }, + { + "name": "latent_image", + "type": "LATENT", + "link": 791 + }, + { + "name": "noise_seed", + "type": "INT", + "link": 1050, + "widget": { + "name": "noise_seed" + } + }, + { + "name": "steps", + "type": "INT", + "link": 1052, + "widget": { + "name": "steps" + } + }, + { + "name": "cfg", + "type": "FLOAT", + "link": 1054, + "widget": { + "name": "cfg" + } + }, + { + "name": "end_at_step", + "type": "INT", + "link": 1063, + "widget": { + "name": "end_at_step" + } + }, + { + "name": "sampler_name", + "type": "COMBO", + "link": 1058, + "widget": { + "name": "sampler_name" + } + }, + { + "name": "scheduler", + "type": "COMBO", + "link": 1059, + "widget": { + "name": "scheduler" + } + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 819 + ], + "slot_index": 0, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "KSamplerAdvanced" + }, + "widgets_values": [ + "enable", + 745303733003524, + "randomize", + 31, + 7, + "dpmpp_2m", + "sgm_uniform", + 0, + 15, + "enable" + ] + }, + { + "id": 279, + "type": "LatentBatchSeedBehavior", + "pos": [ + -443.46252557187427, + -858.6950814796875 + ], + "size": { + "0": 210, + "1": 58 + }, + "flags": {}, + "order": 12, + "mode": 0, + "inputs": [ + { + "name": "samples", + "type": "LATENT", + "link": 851 + } + ], + "outputs": [ + { + "name": "LATENT", + "type": "LATENT", + "links": [ + 910 + ], + "slot_index": 0, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LatentBatchSeedBehavior" + }, + "widgets_values": [ + "fixed" + ] + }, + { + "id": 309, + "type": "CLIPTextEncode", + "pos": [ + -1200.9145114203122, + -739.0866598906147 + ], + "size": { + "0": 210, + "1": 54 + }, + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 914 + }, + { + "name": "text", + "type": "STRING", + "link": 1047, + "widget": { + "name": "text" + } + } + ], + "outputs": [ + { + "name": "CONDITIONING", + "type": "CONDITIONING", + "links": [ + 845 + ], + "slot_index": 0, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "CLIPTextEncode" + }, + "widgets_values": [ + "" + ] + }, + { + "id": 323, + "type": "RIFE VFI", + "pos": [ + -148.9615528845685, + -630.6951425148437 + ], + "size": { + "0": 295.6000061035156, + "1": 198 + }, + "flags": {}, + "order": 16, + "mode": 0, + "inputs": [ + { + "name": "frames", + "type": "IMAGE", + "link": 992 + }, + { + "name": "optional_interpolation_states", + "type": "INTERPOLATION_STATES", + "link": null + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1081 + ], + "slot_index": 0, + "shape": 3 + } + ], + "title": "RIFE VFI", + "properties": { + "Node name for S&R": "RIFE VFI" + }, + "widgets_values": [ + "rife49.pth", + 10, + 8, + false, + false, + 1 + ] + }, + { + "id": 328, + "type": "ImpactControlBridge", + "pos": [ + -149, + -756 + ], + "size": { + "0": 210, + "1": 78 + }, + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "value", + "type": "IMAGE", + "link": 991 + }, + { + "name": "mode", + "type": "BOOLEAN", + "link": 1012, + "widget": { + "name": "mode" + } + } + ], + "outputs": [ + { + "name": "value", + "type": "IMAGE", + "links": [ + 992 + ], + "slot_index": 0, + "shape": 3, + "label": "IMAGE" + } + ], + "properties": { + "Node name for S&R": "ImpactControlBridge" + }, + "widgets_values": [ + true, + false + ] + }, + { + "id": 336, + "type": "ImpactCompare", + "pos": [ + 84, + -856 + ], + "size": { + "0": 210, + "1": 78 + }, + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "a", + "type": "INT", + "link": 1071 + }, + { + "name": "b", + "type": "INT", + "link": 1013 + } + ], + "outputs": [ + { + "name": "BOOLEAN", + "type": "BOOLEAN", + "links": [ + 1012 + ], + "slot_index": 0, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "ImpactCompare" + }, + "widgets_values": [ + "a >= b" + ] + }, + { + "id": 337, + "type": "ImpactInt", + "pos": [ + -151, + -856 + ], + "size": { + "0": 210, + "1": 58 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "INT", + "type": "INT", + "links": [ + 1013 + ], + "slot_index": 0, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "ImpactInt" + }, + "widgets_values": [ + 2 + ] + }, + { + "id": 342, + "type": "FlipStreamUpdate", + "pos": [ + -1463, + -689 + ], + "size": { + "0": 206.01165771484375, + "1": 206 + }, + "flags": {}, + "order": 2, + "mode": 0, + "outputs": [ + { + "name": "prompt", + "type": "STRING", + "links": [ + 1047, + 1086 + ], + "slot_index": 0, + "shape": 3 + }, + { + "name": "batchPrompt", + "type": "STRING", + "links": [ + 1090 + ], + "slot_index": 1, + "shape": 3 + }, + { + "name": "appPrompt", + "type": "STRING", + "links": [ + 1088 + ], + "slot_index": 2, + "shape": 3 + }, + { + "name": "negativePrompt", + "type": "STRING", + "links": [ + 1049 + ], + "slot_index": 3, + "shape": 3 + }, + { + "name": "seed", + "type": "INT", + "links": [ + 1050, + 1051 + ], + "slot_index": 4, + "shape": 3 + }, + { + "name": "steps", + "type": "INT", + "links": [ + 1052, + 1053 + ], + "slot_index": 5, + "shape": 3 + }, + { + "name": "cfg", + "type": "FLOAT", + "links": [ + 1054, + 1055 + ], + "slot_index": 6, + "shape": 3 + }, + { + "name": "lora", + "type": "STRING", + "links": [ + 1057 + ], + "slot_index": 7, + "shape": 3 + }, + { + "name": "sampler", + "type": "COMBO", + "links": [ + 1058, + 1060 + ], + "slot_index": 8, + "shape": 3 + }, + { + "name": "scheduler", + "type": "COMBO", + "links": [ + 1059, + 1061 + ], + "slot_index": 9, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "FlipStreamUpdate" + } + }, + { + "id": 343, + "type": "FlipStreamOption", + "pos": [ + -1462, + -435 + ], + "size": { + "0": 202.96011352539062, + "1": 75.58403778076172 + }, + "flags": {}, + "order": 3, + "mode": 0, + "outputs": [ + { + "name": "startstep", + "type": "INT", + "links": [ + 1063, + 1065 + ], + "slot_index": 0, + "shape": 3 + }, + { + "name": "frames", + "type": "INT", + "links": [ + 1069, + 1071, + 1091, + 1092 + ], + "slot_index": 1, + "shape": 3 + }, + { + "name": "start_noise", + "type": "COMBO", + "links": [ + 1073 + ], + "slot_index": 2, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "FlipStreamOption" + } + }, + { + "id": 345, + "type": "FlipStreamLoader", + "pos": [ + -1467, + -856 + ], + "size": { + "0": 210, + "1": 122 + }, + "flags": {}, + "order": 4, + "mode": 0, + "outputs": [ + { + "name": "MODEL", + "type": "MODEL", + "links": [ + 1075 + ], + "slot_index": 0, + "shape": 3 + }, + { + "name": "CLIP", + "type": "CLIP", + "links": [ + 1076 + ], + "slot_index": 1, + "shape": 3 + }, + { + "name": "VAE", + "type": "VAE", + "links": [ + 1078 + ], + "slot_index": 2, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "FlipStreamLoader" + }, + "widgets_values": [ + "", + "sd_xl_base_1.0.safetensors" + ] + }, + { + "id": 347, + "type": "FlipStreamViewer", + "pos": [ + 167.0384471154315, + -629.6951425148437 + ], + "size": { + "0": 210, + "1": 58 + }, + "flags": {}, + "order": 17, + "mode": 0, + "inputs": [ + { + "name": "tensor", + "type": "IMAGE", + "link": 1081 + } + ], + "properties": { + "Node name for S&R": "FlipStreamViewer" + }, + "widgets_values": [ + 1 + ] + }, + { + "id": 348, + "type": "BatchPromptSchedule", + "pos": [ + -666.4625255718743, + -757.6950814796875 + ], + "size": { + "0": 235.1999969482422, + "1": 262 + }, + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "clip", + "type": "CLIP", + "link": 1084 + }, + { + "name": "pre_text", + "type": "STRING", + "link": 1086, + "widget": { + "name": "pre_text" + } + }, + { + "name": "app_text", + "type": "STRING", + "link": 1088, + "widget": { + "name": "app_text" + } + }, + { + "name": "pw_a", + "type": "FLOAT", + "link": null, + "widget": { + "name": "pw_a" + } + }, + { + "name": "pw_b", + "type": "FLOAT", + "link": null, + "widget": { + "name": "pw_b" + } + }, + { + "name": "pw_c", + "type": "FLOAT", + "link": null, + "widget": { + "name": "pw_c" + } + }, + { + "name": "pw_d", + "type": "FLOAT", + "link": null, + "widget": { + "name": "pw_d" + } + }, + { + "name": "text", + "type": "STRING", + "link": 1090, + "widget": { + "name": "text" + } + }, + { + "name": "max_frames", + "type": "INT", + "link": 1091, + "widget": { + "name": "max_frames" + } + }, + { + "name": "end_frame", + "type": "INT", + "link": 1092, + "widget": { + "name": "end_frame" + } + } + ], + "outputs": [ + { + "name": "POS", + "type": "CONDITIONING", + "links": [ + 1093 + ], + "slot_index": 0, + "shape": 3 + }, + { + "name": "NEG", + "type": "CONDITIONING", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "BatchPromptSchedule" + }, + "widgets_values": [ + "\"0\" :\"\",\n\"11\" :\"\",\n\"23\" :\"\",\n\"35\" :\"\",\n\"47\" :\"\",\n\"59\" :\"\",\n\"71\" :\"\",\n\"83\" :\"\",\n\"95\" :\"\",\n\"107\" :\"\",\n\"119\" :\"\"\n", + 120, + false, + "", + "", + 0, + 0, + 0, + 0, + 0, + 0 + ] + } + ], + "links": [ + [ + 579, + 149, + 0, + 150, + 0, + "LATENT" + ], + [ + 785, + 154, + 0, + 249, + 2, + "CONDITIONING" + ], + [ + 791, + 173, + 0, + 249, + 3, + "LATENT" + ], + [ + 819, + 249, + 0, + 88, + 0, + "LATENT" + ], + [ + 845, + 309, + 0, + 249, + 1, + "CONDITIONING" + ], + [ + 851, + 88, + 0, + 279, + 0, + "LATENT" + ], + [ + 910, + 279, + 0, + 149, + 3, + "LATENT" + ], + [ + 913, + 176, + 1, + 154, + 0, + "CLIP" + ], + [ + 914, + 176, + 1, + 309, + 0, + "CLIP" + ], + [ + 931, + 176, + 0, + 149, + 0, + "MODEL" + ], + [ + 932, + 176, + 0, + 249, + 0, + "MODEL" + ], + [ + 954, + 154, + 0, + 149, + 2, + "CONDITIONING" + ], + [ + 991, + 150, + 0, + 328, + 0, + "*" + ], + [ + 992, + 328, + 0, + 323, + 0, + "IMAGE" + ], + [ + 1012, + 336, + 0, + 328, + 1, + "BOOLEAN" + ], + [ + 1013, + 337, + 0, + 336, + 1, + "*" + ], + [ + 1047, + 342, + 0, + 309, + 1, + "STRING" + ], + [ + 1049, + 342, + 3, + 154, + 1, + "STRING" + ], + [ + 1050, + 342, + 4, + 249, + 4, + "INT" + ], + [ + 1051, + 342, + 4, + 149, + 4, + "INT" + ], + [ + 1052, + 342, + 5, + 249, + 5, + "INT" + ], + [ + 1053, + 342, + 5, + 149, + 5, + "INT" + ], + [ + 1054, + 342, + 6, + 249, + 6, + "FLOAT" + ], + [ + 1055, + 342, + 6, + 149, + 6, + "FLOAT" + ], + [ + 1057, + 342, + 7, + 176, + 2, + "STRING" + ], + [ + 1058, + 342, + 8, + 249, + 8, + "COMBO" + ], + [ + 1059, + 342, + 9, + 249, + 9, + "COMBO" + ], + [ + 1060, + 342, + 8, + 149, + 9, + "COMBO" + ], + [ + 1061, + 342, + 9, + 149, + 10, + "COMBO" + ], + [ + 1063, + 343, + 0, + 249, + 7, + "INT" + ], + [ + 1065, + 343, + 0, + 149, + 7, + "INT" + ], + [ + 1069, + 343, + 1, + 88, + 1, + "INT" + ], + [ + 1071, + 343, + 1, + 336, + 0, + "INT" + ], + [ + 1073, + 343, + 2, + 149, + 8, + "COMBO" + ], + [ + 1075, + 345, + 0, + 176, + 0, + "MODEL" + ], + [ + 1076, + 345, + 1, + 176, + 1, + "CLIP" + ], + [ + 1078, + 345, + 2, + 150, + 1, + "VAE" + ], + [ + 1081, + 323, + 0, + 347, + 0, + "IMAGE" + ], + [ + 1084, + 176, + 1, + 348, + 0, + "CLIP" + ], + [ + 1086, + 342, + 0, + 348, + 1, + "STRING" + ], + [ + 1088, + 342, + 2, + 348, + 2, + "STRING" + ], + [ + 1090, + 342, + 1, + 348, + 7, + "STRING" + ], + [ + 1091, + 343, + 1, + 348, + 8, + "INT" + ], + [ + 1092, + 343, + 1, + 348, + 9, + "INT" + ], + [ + 1093, + 348, + 0, + 149, + 1, + "CONDITIONING" + ] + ], + "groups": [ + { + "title": "Input", + "bounding": [ + -1483, + -935, + 246, + 592 + ], + "color": "#3f789e", + "font_size": 24 + }, + { + "title": "Base latent", + "bounding": [ + -956, + -936, + 246, + 543 + ], + "color": "#3f789e", + "font_size": 24 + }, + { + "title": "LoRA and CLIP", + "bounding": [ + -1217, + -936, + 242, + 369 + ], + "color": "#3f789e", + "font_size": 24 + }, + { + "title": "Frames", + "bounding": [ + -689, + -935, + 499, + 579 + ], + "color": "#3f789e", + "font_size": 24 + }, + { + "title": "Interpolation and Output", + "bounding": [ + -171, + -935, + 563, + 511 + ], + "color": "#3f789e", + "font_size": 24 + } + ], + "config": {}, + "extra": { + "ds": { + "scale": 0.8979276645799553, + "offset": [ + 1491.2821079458877, + 949.6256302407462 + ] + }, + "groupNodes": {} + }, + "version": 0.4 +} \ No newline at end of file