diff --git a/README.md b/README.md index 4b5cf11..2337cd7 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# πŸ”— Comfyui : Bjornulf_custom_nodes v0.22 πŸ”— +# πŸ”— Comfyui : Bjornulf_custom_nodes v0.23 πŸ”— # ☁ Usage in cloud : @@ -65,6 +65,7 @@ wget --content-disposition -P /workspace/ComfyUI/models/checkpoints "https://civ - **v0.20**: Changes for lobechat save image : include the code of free VRAM hack + ignore missing images filenames - **v0.21**: Add a new write text node that also display the text in the comfyui console (good for debugging) - **v0.22**: Allow write text node to use random selection like this {hood|helmet} will randomly choose between hood or helmet. +- **v0.23**: And a new node: Pause, resume or stop workflow. # πŸ“ Nodes descriptions @@ -325,4 +326,16 @@ I don't think there is a clean way to do that, so I'm using a hacky way. So, not perfect but better than being stuck at 6GB of VRAM used if I know I won't be using it again... Just connect this node with your workflow, it takes an image as input and return the same image without any changes. ❗ Comfyui is using cache to run faster (like not reloading checkpoints), so only use this free VRAM node when you need it. -❗ For this node to work properly, you need to enable the dev/api mode in ComfyUI. (You can do that in the settings) \ No newline at end of file +❗ For this node to work properly, you need to enable the dev/api mode in ComfyUI. (You can do that in the settings) + +### 35 - ⏸️ Paused. Resume or Stop ? + +![pause resume stop](screenshots/pause1.png) +![pause resume stop](screenshots/pause2.png) +![pause resume stop](screenshots/pause3.png) + +**Description:** +Automatically pause the workflow, and rings a bell when it does. (play the audio `bell.m4a` file provided) +You can then manually resume or stop the workflow by clicking on the node's buttons. +I do that let's say for example if I have a very long upscaling process, I can check if the input is good before continuing. Sometimes I might stop the workflow and restart it with another seed. +You can connect any type of node to the pause node, above is an example with text, but you can send an IMAGE or whatever else, in the node `input = output`. (Of course you need to send the output to something that has the correct format...) \ No newline at end of file diff --git a/__init__.py b/__init__.py index 1772760..4ecbbcd 100644 --- a/__init__.py +++ b/__init__.py @@ -40,7 +40,7 @@ from .character_description import CharacterDescriptionGenerator from .text_to_speech import TextToSpeech from .loop_combine_texts_by_lines import CombineTextsByLines from .free_vram_hack import FreeVRAM -# from .pause_resume import PauseResume +from .pause_resume_stop import PauseResume # from .check_black_image import CheckBlackImage # from .clear_vram import ClearVRAM @@ -49,7 +49,7 @@ from .free_vram_hack import FreeVRAM NODE_CLASS_MAPPINGS = { # "Bjornulf_CustomStringType": CustomStringType, "Bjornulf_ollamaLoader": ollamaLoader, - # "Bjornulf_PauseResume": PauseResume, + "Bjornulf_PauseResume": PauseResume, "Bjornulf_FreeVRAM": FreeVRAM, "Bjornulf_CombineTextsByLines": CombineTextsByLines, "Bjornulf_TextToSpeech": TextToSpeech, @@ -98,7 +98,7 @@ NODE_CLASS_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = { # "Bjornulf_CustomStringType": "!!! CUSTOM STRING TYPE !!!", "Bjornulf_ollamaLoader": "πŸ¦™ Ollama (Description)", - # "Bjornulf_PauseResume": "⏸️ Pause/Resume", + "Bjornulf_PauseResume": "⏸️ Paused. Resume or Stop ?", "Bjornulf_FreeVRAM": "🧹 Free VRAM hack", "Bjornulf_CombineTextsByLines": "β™» Loop (All Lines from input πŸ”— combine by lines)", "Bjornulf_TextToSpeech": "πŸ”Š TTS - Text to Speech", diff --git a/bell.m4a b/bell.m4a new file mode 100644 index 0000000..fbea635 Binary files /dev/null and b/bell.m4a differ diff --git a/pause_resume_stop.py b/pause_resume_stop.py new file mode 100644 index 0000000..069b9c8 --- /dev/null +++ b/pause_resume_stop.py @@ -0,0 +1,63 @@ +import time +from aiohttp import web +from server import PromptServer +import logging +from pydub import AudioSegment +from pydub.playback import play +import os + +class Everything(str): + def __ne__(self, __value: object) -> bool: + return False + +class PauseResume: + is_paused = True + should_stop = False + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "input": (Everything("*"), {"forceInput": True}) + } + } + + RETURN_TYPES = (Everything("*"),) + RETURN_NAMES = ("output",) + FUNCTION = "loop_resume_or_stop" + CATEGORY = "Bjornulf" + + def play_audio(self): + audio_file = os.path.join(os.path.dirname(__file__), 'bell.m4a') + sound = AudioSegment.from_file(audio_file, format="m4a") + play(sound) + + def loop_resume_or_stop(self, input): + self.play_audio() + self.input = input + while PauseResume.is_paused and not PauseResume.should_stop: + logging.info(f"PauseResume.is_paused: {PauseResume.is_paused}, PauseResume.should_stop: {PauseResume.should_stop}") + time.sleep(1) # Sleep to prevent busy waiting + + if PauseResume.should_stop: + PauseResume.should_stop = False # Reset for next run + PauseResume.is_paused = True + PauseResume.should_stop = False + raise Exception("Workflow stopped by user") + + PauseResume.is_paused = True + PauseResume.should_stop = False + return (self.input,) + +@PromptServer.instance.routes.get("/bjornulf_resume") +async def resume_node(request): + logging.info("Resume node called") + PauseResume.is_paused = False + return web.Response(text="Node resumed") + +@PromptServer.instance.routes.get("/bjornulf_stop") +async def stop_node(request): + logging.info("Stop node called") + PauseResume.should_stop = True + PauseResume.is_paused = False # Ensure the loop exits + return web.Response(text="Workflow stopped") \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f4f2b30..da98d4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "bjornulf_custom_nodes" description = "Nodes: Ollama, Text to Speech, Save image for Bjornulf LobeChat, Text with random Seed, Random line from input, Combine images (Background + Overlay alpha), Image to grayscale (black & white), Remove image Transparency (alpha), Resize Image, ..." -version = "0.21" +version = "0.23" license = {file = "LICENSE"} [project.urls] diff --git a/screenshots/pause1.png b/screenshots/pause1.png new file mode 100644 index 0000000..728d4cc Binary files /dev/null and b/screenshots/pause1.png differ diff --git a/screenshots/pause2.png b/screenshots/pause2.png new file mode 100644 index 0000000..64eae32 Binary files /dev/null and b/screenshots/pause2.png differ diff --git a/screenshots/pause3.png b/screenshots/pause3.png new file mode 100644 index 0000000..36bf259 Binary files /dev/null and b/screenshots/pause3.png differ diff --git a/web/js/pauseresume.js b/web/js/pauseresume.js new file mode 100644 index 0000000..18dc805 --- /dev/null +++ b/web/js/pauseresume.js @@ -0,0 +1,37 @@ +import { app } from "../../../scripts/app.js"; + +app.registerExtension({ + name: "Bjornulf.PauseResume", + async nodeCreated(node) { + if (node.comfyClass === "Bjornulf_PauseResume") { + const resumeButton = node.addWidget("button", "Resume", "Resume", () => { + fetch('/bjornulf_resume', { method: 'GET' }) + .then(response => response.text()) + .then(data => { + console.log('Resume response:', data); + // You can update the UI here if needed + }) + .catch(error => console.error('Error:', error)); + }); + const stopButton = node.addWidget("button", "Stop", "Stop", () => { + fetch('/bjornulf_stop', { method: 'GET' }) + .then(response => response.text()) + .then(data => { + console.log('Stop response:', data); + // You can update the UI here if needed + }) + .catch(error => console.error('Error:', error)); + }); + } + } +}); + +// BASIC BUTTON +// app.registerExtension({ +// name: "Bjornulf.PauseResume", +// async nodeCreated(node) { +// if (node.comfyClass === "Bjornulf_PauseResume") { +// node.addWidget("button","Resume","Resume", (...args) => { console.log("lol"); } ) +// } +// } +// }); \ No newline at end of file