diff --git a/js/toast_handler.js b/js/toast_handler.js new file mode 100644 index 0000000..7e5af0f --- /dev/null +++ b/js/toast_handler.js @@ -0,0 +1,32 @@ +// Copyright (c) 2025 Salvador E. Tropea +// Copyright (c) 2025 Instituto Nacional de TecnologĂ­a Industrial +// License: GPLv3 +// Project: ComfyUI-AudioBatch + +// This script adds an event named "set-audiobatch-toast" +// Used to notify the user in the GUI using the Toast API + +import { app } from "/scripts/app.js"; + +// Register a new extension +app.registerExtension({ + name: "SET.AudioBatch.ToastHandler", // Unique name + + // The setup function is executed when the extension is loaded + setup() { + // Add a listener for our custom event + app.api.addEventListener("set-audiobatch-toast", (event) => { + // The data from Python is in event.detail + const { message, summary, severity } = event.detail; + + // Use the ComfyUI toast API to show the message + // app.ui.toast.addMessage is the modern way to do this + app.extensionManager.toast.add({ + severity: severity, + summary: summary, + detail: message, + life: 6000 + }); + }); + }, +}); diff --git a/source/nodes/nodes_audio.py b/source/nodes/nodes_audio.py index 8bfbffb..0cd22d1 100644 --- a/source/nodes/nodes_audio.py +++ b/source/nodes/nodes_audio.py @@ -14,6 +14,7 @@ from .utils.logger import main_logger from .utils.misc import parse_time_to_seconds, parse_note_to_frequency from .utils.downmix import spectral_downmix from .utils.downloader import download_model +from .utils.comfy_notification import send_toast_notification try: from folder_paths import get_input_directory # To get the ComfyUI input directory except ModuleNotFoundError: @@ -1083,13 +1084,14 @@ class AudioDownload: if not base_url.endswith('/'): base_url += '/' download_url = base_url + filename + send_toast_notification(f"Downloading `{filename}`", "Download") - # --- Use your existing download_model function --- try: download_model(url=download_url, save_dir=save_dir, file_name=filename) except Exception as e: logger.error(f"Download failed for {download_url}: {e}", exc_info=True) raise # Re-raise to stop the workflow and show the error + send_toast_notification("Finished downloading", "Download", 'success') else: logger.info(f"Found existing file, skipping download: '{local_filepath}'") diff --git a/source/nodes/utils/comfy_notification.py b/source/nodes/utils/comfy_notification.py new file mode 100644 index 0000000..f5d6f99 --- /dev/null +++ b/source/nodes/utils/comfy_notification.py @@ -0,0 +1,47 @@ +# Copyright (c) 2025 Salvador E. Tropea +# Copyright (c) 2025 Instituto Nacional de TecnologĂ­a Industrial +# License: GPLv3 +# Project: ComfyUI-AudioBatch +# +# ComfyUI Toast API messages +# Original code from Gemini 2.5 Pro, which was really outdated +# Took ideas from Easy Use nodes and looking at ComfyUI code +import logging +from typing import Optional +# ComfyUI imports +try: + from server import PromptServer + with_comfy = True +except Exception: + with_comfy = False +# Local imports +from .misc import NODES_NAME + +logger = logging.getLogger(f"{NODES_NAME}.comfy_notification") + + +def send_toast_notification(message: str, summary: str = "Warning", severity: str = "warn", sid: Optional[str] = None): + """ + Sends a toast notification event to the ComfyUI client. + + Args: + message (str): The message content of the toast. + severity (str): The type of toast. Can be 'success' | 'info' | 'warn' | 'error' | 'secondary' | 'contrast' + summary (str): Short explanation + sid (str, optional): The session ID of the client to send to. + If None, broadcasts to all clients. Defaults to None. + """ + if not with_comfy: + return + try: + PromptServer.instance.send_sync( + "set-audioseparation-toast", # This is our custom event name + { + 'message': message, + 'summary': summary, + 'severity': severity + }, + sid + ) + except Exception as e: + logger.error(f"when trying to use ComfyUI PromptServer: {e}") diff --git a/source/nodes/utils/logger.py b/source/nodes/utils/logger.py index d63c596..4d284bb 100644 --- a/source/nodes/utils/logger.py +++ b/source/nodes/utils/logger.py @@ -6,8 +6,9 @@ from __future__ import annotations # Good practice import logging import os import sys -from typing import Any +from typing import Any, Callable from .misc import NODES_NAME, NODES_DEBUG_VAR +from .comfy_notification import send_toast_notification # 1. Initialize variables with the `Any` type. @@ -56,10 +57,56 @@ class CustomFormatter(logging.Formatter): return formatter.format(record) +def on_log_error_or_warning(record: logging.LogRecord) -> None: + """ + This function is called whenever a log with level WARNING or higher is emitted. + The 'record' object contains all information about the log event. + """ + if record.levelno == logging.WARNING: + summary = "Warning" + severity = "warn" + else: + summary = "Error" + severity = "error" + send_toast_notification(record.getMessage(), summary=summary, severity=severity) + + +class WarningAndErrorFilter(logging.Filter): + """ + A custom log filter that intercepts logs of a certain level. + """ + def __init__(self, callback: Callable, level: int = logging.WARNING): + """ + Initializes the filter. + + Args: + callback: The function to call when a log record meets the level criteria. + level: The minimum level to trigger the callback. + """ + super().__init__() + self._callback = callback + self._level = level + + def filter(self, record: logging.LogRecord) -> bool: + """ + This method is called for every log record. + """ + # Check if the log level is WARNING or higher + if record.levelno >= self._level: + self._callback(record) + + # Always return True to ensure the log is always processed + # by the handlers after this filter. + return True + + # Create a new logger logger = logging.getLogger(NODES_NAME) logger.propagate = False +# Add the custom filter to the logger. +logger.addFilter(WarningAndErrorFilter(callback=on_log_error_or_warning)) + # Add handler if we don't have one. if not logger.handlers: handler = logging.StreamHandler(sys.stdout)