commit d2c1d252d16a48b53484ba21902684172bef8009 Author: Royce Schultz Date: Mon Apr 29 21:40:33 2024 -0600 init commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..80e9bfa --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# ComfyUI Notifications + +This package provides custom nodes to ComfyUI to notify users when workflows have completed. + +These tools are also available via [ComfyUI-Custom-Scripts](https://github.com/royceschultz/ComfyUI-Custom-Scripts), but this package comes without the bloat of other tools. + +## Send Notification + +Sends a system notification via the browser. + +## Play Sound + +Plays a chime sound to notify the user. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..861603b --- /dev/null +++ b/__init__.py @@ -0,0 +1,14 @@ +from .nodes.play_sound import PlaySound +from .nodes.system_notification import SystemNotification + +NODE_CLASS_MAPPINGS = { + "Notif-PlaySound": PlaySound, + "Notif-SystemNotification": SystemNotification, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "Notif-PlaySound": "Play Sound", + "Notif-SystemNotification": "System Notification", +} + +WEB_DIRECTORY = "./web" diff --git a/nodes/play_sound.py b/nodes/play_sound.py new file mode 100644 index 0000000..f27592f --- /dev/null +++ b/nodes/play_sound.py @@ -0,0 +1,21 @@ +from .util import ComfyAnyType + +class PlaySound: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "any": (ComfyAnyType('*'), {}), + "mode": (["always", "on empty queue"], {}), + "volume": ("FLOAT", {"min": 0, "max": 1, "step": 0.1, "default": 0.5}), + "file": ("STRING", { "default": "notify.mp3" }) + }, + } + + FUNCTION = "nop" + OUTPUT_NODE = True + RETURN_TYPES = tuple() + CATEGORY = "notifications" + + def nop(self, any, mode, volume, file): + return {"ui": {"a": []}, "result": (0, )} diff --git a/nodes/system_notification.py b/nodes/system_notification.py new file mode 100644 index 0000000..ba67755 --- /dev/null +++ b/nodes/system_notification.py @@ -0,0 +1,19 @@ +from .util import ComfyAnyType + +class SystemNotification: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "any": (ComfyAnyType("*"), {}), + "mode": (["always", "on empty queue"], {}), + }, + } + + FUNCTION = "nop" + OUTPUT_NODE = True + RETURN_TYPES = tuple() + CATEGORY = "notifications" + + def nop(self, any, mode): + return {"ui": {"a": []}, "result": (0, )} diff --git a/nodes/util.py b/nodes/util.py new file mode 100644 index 0000000..cc986b3 --- /dev/null +++ b/nodes/util.py @@ -0,0 +1,5 @@ +# Hack: string type that is always equal in not equal comparisons +# Thanks to https://github.com/pythongosssss/ComfyUI-Custom-Scripts +class ComfyAnyType(str): + def __ne__(self, __value: object) -> bool: + return False diff --git a/web/assets/notify.mp3 b/web/assets/notify.mp3 new file mode 100644 index 0000000..5e3fdab Binary files /dev/null and b/web/assets/notify.mp3 differ diff --git a/web/play_sound.js b/web/play_sound.js new file mode 100644 index 0000000..f482f79 --- /dev/null +++ b/web/play_sound.js @@ -0,0 +1,35 @@ +import { app } from "../../../scripts/app.js"; + +app.registerExtension({ + name: "Notifications.PlaySound", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "Notif-PlaySound") { + const onExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = async function () { + onExecuted?.apply(this, arguments); + if (this.widgets[0].value === "on empty queue") { + if (app.ui.lastQueueSize !== 0) { + await new Promise((r) => setTimeout(r, 500)); + } + if (app.ui.lastQueueSize !== 0) { + return; + } + } + let file = this.widgets[2].value; + if (!file) { + file = "notify.mp3"; + } + if (!file.startsWith("http")) { + if (!file.includes("/")) { + file = "assets/" + file; + } + file = new URL(file, import.meta.url) + } + const url = new URL(file); + const audio = new Audio(url); + audio.volume = this.widgets[1].value; + audio.play(); + }; + } + }, +}); diff --git a/web/system_notification.js b/web/system_notification.js new file mode 100644 index 0000000..f09e92d --- /dev/null +++ b/web/system_notification.js @@ -0,0 +1,46 @@ +import { app } from "../../../scripts/app.js"; + +const notificationSetup = () => { + if (!("Notification" in window)) { + console.log("This browser does not support notifications."); + alert("This browser does not support notifications."); + return; + } + if (Notification.permission === "denied") { + console.log("Notifications are blocked. Please enable them in your browser settings."); + alert("Notifications are blocked. Please enable them in your browser settings."); + return; + } + if (Notification.permission !== "granted") { + Notification.requestPermission() + } + return true; +} + +app.registerExtension({ + name: "Notifications.SystemNotification", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "Notif-SystemNotification") { + const onExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = async function () { + onExecuted?.apply(this, arguments); + if (this.widgets[0].value === "on empty queue") { + if (app.ui.lastQueueSize !== 0) { + await new Promise((r) => setTimeout(r, 500)); + } + if (app.ui.lastQueueSize !== 0) { + return; + } + } + if (!notificationSetup()) return; + const notification = new Notification("ComfyUI", { body: "Your notification has triggered." }) + }; + + const onNodeCreated = nodeType.prototype.onNodeCreated; + nodeType.prototype.onNodeCreated = function () { + onNodeCreated?.apply(this, arguments); + notificationSetup(); + } + } + }, +});