From 8e8408b5100b2100dfd2bb30d7193fffb378a9ed Mon Sep 17 00:00:00 2001 From: AlekPet Date: Wed, 7 Jan 2026 12:14:24 +0300 Subject: [PATCH] Add new nodes CogViewImageGenerateNode, CogVideoXGenerateNode --- ChatGLMNode/README.md | 8 +- ChatGLMNode/chatglm_node.py | 276 ++++++++++++++++++++++++++++++-- ChatGLMNode/config.json.example | 2 + __init__.py | 8 + pyproject.toml | 4 +- 5 files changed, 279 insertions(+), 19 deletions(-) diff --git a/ChatGLMNode/README.md b/ChatGLMNode/README.md index df1d0cd..6bef41e 100644 --- a/ChatGLMNode/README.md +++ b/ChatGLMNode/README.md @@ -156,7 +156,9 @@ Optional: You can also set initial language values ​​when creating nodes in "from_translate": "ru", "to_translate": "en", "default_language_model": "glm-4.5-flash", - "default_multimodal_model": "glm-4v-flash", + "default_multimodal_model": "glm-4.6v-flash", + "default_image_generate_model": "cogview-3-flash", + "default_video_generate_model": "cogvideox-flash", "ZHIPUAI_API_KEY": "your_api_key" } ``` @@ -170,6 +172,10 @@ Optional: You can also set initial language values ​​when creating nodes in > **ChatGLM4InstructNode** - Generate prompt from instruct > > **ChatGLM4InstructMediaNode** - Generate prompt from instruct to describe what is shown in the media +> +> **CogViewImageGenerateNode** - Generate prompt from instruct to describe what is shown in the media +> +> **CogVideoXGenerateNode** - Generate prompt from instruct to describe what is shown in the media ## Image: diff --git a/ChatGLMNode/chatglm_node.py b/ChatGLMNode/chatglm_node.py index 1db86f5..e9d8ca6 100644 --- a/ChatGLMNode/chatglm_node.py +++ b/ChatGLMNode/chatglm_node.py @@ -1,17 +1,26 @@ import os import json import requests - import base64 from io import BytesIO import numpy as np from server import PromptServer -from PIL import Image +from PIL import Image, ImageOps, ImageSequence +import time +import torch +import node_helpers +from comfy_api_nodes.util import ( + download_url_to_video_output, +) + ALL_CODES_LANGS = ['af', 'sq', 'am', 'ar', 'hy', 'as', 'ay', 'az', 'bm', 'eu', 'be', 'bn', 'bho', 'bs', 'bg', 'ca', 'ceb', 'ny', 'zh-CN', 'zh-TW', 'co', 'hr', 'cs', 'da', 'dv', 'doi', 'nl', 'en', 'eo', 'et', 'ee', 'tl', 'fi', 'fr', 'fy', 'gl', 'ka', 'de', 'el', 'gn', 'gu', 'ht', 'ha', 'haw', 'iw', 'hi', 'hmn', 'hu', 'is', 'ig', 'ilo', 'id', 'ga', 'it', 'ja', 'jw', 'kn', 'kk', 'km', 'rw', 'gom', 'ko', 'kri', 'ku', 'ckb', 'ky', 'lo', 'la', 'lv', 'ln', 'lt', 'lg', 'lb', 'mk', 'mai', 'mg', 'ms', 'ml', 'mt', 'mi', 'mr', 'mni-Mtei', 'lus', 'mn', 'my', 'ne', 'no', 'or', 'om', 'ps', 'fa', 'pl', 'pt', 'pa', 'qu', 'ro', 'ru', 'sm', 'sa', 'gd', 'nso', 'sr', 'st', 'sn', 'sd', 'si', 'sk', 'sl', 'so', 'es', 'su', 'sw', 'sv', 'tg', 'ta', 'tt', 'te', 'th', 'ti', 'ts', 'tr', 'tk', 'ak', 'uk', 'ur', 'ug', 'uz', 'vi', 'cy', 'xh', 'yi', 'yo', 'zu'] - +# Endpoints ENDPOINT_URL = "https://open.bigmodel.cn/api/paas/v4/chat/completions" +ENDPOINT_IMAGE_URL = "https://open.bigmodel.cn/api/paas/v4/images/generations" +ENDPOINT_VIDEO_URL = "https://open.bigmodel.cn/api/paas/v4/videos/generations" +ENDPOINT_VIDEO_CHECK_URL = "https://open.bigmodel.cn/api/paas/v4/async-result/" # Language models: https://docs.bigmodel.cn/api-reference/%E6%A8%A1%E5%9E%8B-api/%E5%AF%B9%E8%AF%9D%E8%A1%A5%E5%85%A8#%E6%96%87%E6%9C%AC%E6%A8%A1%E5%9E%8B LIST_LANGUAGE_MODELS = [ @@ -57,13 +66,6 @@ LIST_MULTIMODAL_MODELS = [ "autoglm-phone", ] -# Multimodal models: https://docs.bigmodel.cn/api-reference/%E6%A8%A1%E5%9E%8B-api/%E5%AF%B9%E8%AF%9D%E8%A1%A5%E5%85%A8#%E8%A7%86%E8%A7%89%E6%A8%A1%E5%9E%8B -LIST_IMAGE_GENERATION_MODELS = [ - "cogview-4-250304", - "cogview-4", - "cogview-3-flash" -] - # CogView: https://docs.bigmodel.cn/api-reference/%E6%A8%A1%E5%9E%8B-api/%E5%9B%BE%E5%83%8F%E7%94%9F%E6%88%90 LIST_IMAGE_GENERATION_MODELS = [ "cogview-4-250304", @@ -74,7 +76,6 @@ LIST_IMAGE_GENERATION_MODELS = [ # CogVideo: https://docs.bigmodel.cn/api-reference/%E6%A8%A1%E5%9E%8B-api/%E7%94%9F%E6%88%90%E8%A7%86%E9%A2%91%E5%BC%82%E6%AD%A5 LIST_VIDEO_GENERATION_MODELS = [ "cogvideox-3", - "cogvideox3-flash", "cogvideox-2", "cogvideox-flash", ] @@ -89,6 +90,8 @@ def getConfigData(): "to_translate": "en", "default_language_model": "glm-4.5-flash", "default_multimodal_model": "glm-4.6v-flash", + "default_image_generate_model": "cogview-3-flash", + "default_video_generate_model": "cogvideox-flash", "ZHIPUAI_API_KEY": "your_api_key" } @@ -119,7 +122,7 @@ def checkPropValue(obj, key, not_include = []): CONFIG = getConfigData() -def createRequest(payload): +def createRequest(payload, generate = "text", method = "POST", params = {}): global CONFIG if checkPropValue(CONFIG, "ZHIPUAI_API_KEY", ["your_api_key"]): @@ -133,15 +136,30 @@ def createRequest(payload): "Content-Type": "application/json", } + if generate == "image": + endpoint = ENDPOINT_IMAGE_URL + elif generate == "video": + endpoint = ENDPOINT_VIDEO_URL + headers.update({'Accept-Language': "en-US,en"}) + elif generate == "video-check": + endpoint = ENDPOINT_VIDEO_CHECK_URL + params["id"] + headers.update({'Accept-Language': "en-US,en"}) + else: + endpoint = ENDPOINT_URL + try: - response = requests.post(ENDPOINT_URL, headers=headers, json=payload) + response = requests.post(endpoint, headers=headers, json=payload) if method == "POST" else requests.get(endpoint, headers=headers) response.raise_for_status() if response.status_code == 200: json_data = response.json() - response_text = json_data.get("choices")[0]["message"]["content"].strip() - - return response_text + + if generate == "text": + return json_data.get("choices")[0]["message"]["content"].strip() + elif generate == "image": + return json_data.get("data")[0]["url"] + elif generate == "video" or generate == "video-check": + return json_data except requests.HTTPError as e: print(f"Error request ChatGLM: {response.status_code}, {response.text}") @@ -512,3 +530,229 @@ class ChatGLM4InstructMediaNode: answer = createRequest(payload) return (answer,) + +# Generate Image & Video nodes +IMAGE_SUPPORTS_RESOLUTIONS = ["768x1344", "864x1152", "1024x1024", "1344x768", "1152x864", "1440x720", "720x1440"] +VIDEO_SUPPORTS_RESOLUTIONS = ["720x1280", "1024x1024", "1080x1920", "1280x720", "1920x1080", "2048x1080", "3840x2160"] + + +# List sizes to str +def getStrListSizes(list_values, indexVal): + return ", ".join(map(str, sorted(int(w.split("x")[indexVal]) for w in list_values))) + + +# Function set correct size value +def setCorrectSize(value, minMax, nodeName): + if type(value) == str: + value = int(value) + + if value < minMax[0]: + value = minMax[0] + print(f"[{nodeName}] The value is less than {minMax[0]}, we set it to the correct value {minMax[0]}.") + elif value > minMax[1]: + value = minMax[1] + print(f"[{nodeName}] The value is greater than {minMax[1]}, we set it to the correct value {minMax[1]}.") + + return value + +class CogViewImageGenerateNode: + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "model": ( + LIST_IMAGE_GENERATION_MODELS, + { + "default": CONFIG.get("default_image_generate_model", "cogvideox-flash"), + "tooltip": "The model code to be called. Models with text 'flash' should be free!", + }, + ), + "prompt": ( + "STRING", + { + "multiline": True, + "placeholder": "Input prompt text", + "default": "", + "tooltip": "Enter the prompt for generated image", + }, + ), + }, + "optional": { + "quality": ( + ["standard", "hd"], + { + "default": "standard", + "tooltip": "Image generation quality, default is 'standard'. This parameter is only supported by cogview-4-250304.", + }, + ), + "width": ("INT", {"default": 1024, "tooltip":f"Image width, default value 1024. Recommended width values: {getStrListSizes(IMAGE_SUPPORTS_RESOLUTIONS, 0)}."}), + "height": ("INT", {"default": 1024, "tooltip":f"Image height, default value 1024. Recommended height values: {getStrListSizes(IMAGE_SUPPORTS_RESOLUTIONS, 1)}."}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "image_generate" + DESCRIPTION = ( + "This is a node that generates an image based on a text prompt." + ) + CATEGORY = "AlekPet Nodes/image" + + def image_generate(self, model, prompt, quality="standard", width=1024, height=1024): + if prompt is None and not prompt.strip(): + raise ValueError("Prompt value is empty!") + + width = setCorrectSize(width, [512, 2048], "CogViewImageGenerateNode") + height = setCorrectSize(height, [512, 2048], "CogViewImageGenerateNode") + + size = f"{width}x{height}" + + # Create body request + payload = { + "model": model, + "prompt": prompt, + "quality": quality, + "size": size, + } + + image_url = createRequest(payload, "image") + response = requests.get(image_url) + img = node_helpers.pillow(Image.open, BytesIO(response.content)) + + output_images = [] + w, h = None, None + + excluded_formats = ['MPO'] + + for i in ImageSequence.Iterator(img): + i = node_helpers.pillow(ImageOps.exif_transpose, i) + + if i.mode == 'I': + i = i.point(lambda i: i * (1 / 255)) + image = i.convert("RGB") + + if len(output_images) == 0: + w = image.size[0] + h = image.size[1] + + if image.size[0] != w or image.size[1] != h: + continue + + image = np.array(image).astype(np.float32) / 255.0 + image = torch.from_numpy(image)[None,] + output_images.append(image) + + if len(output_images) > 1 and img.format not in excluded_formats: + output_image = torch.cat(output_images, dim=0) + else: + output_image = output_images[0] + + return (output_image,) + +# Video generate +async def execute_gen_video(model, prompt, image, quality, with_audio, watermark, width, height, fps, duration): + + width = setCorrectSize(width, [512, 2048], "CogVideoXGenerateNode") + height = setCorrectSize(height, [512, 2048], "CogVideoXGenerateNode") + + size = f"{width}x{height}" + + # Create body request + payload = { + "model": model, + "prompt": prompt, + "quality": quality, + "watermark_enabled": watermark, + "with_audio": with_audio, + "size": size, + "fps": int(fps), + "duration": int(duration), + } + + if image is not None: + img = 255.0 * image.cpu().numpy() + img = np.squeeze(img) + img = Image.fromarray(np.clip(img, 0, 255).astype(np.uint8)) + img = toBase64ImgUrl(img) + # Add to payload + payload.update({"image_url": img}) + + + video_task = createRequest(payload, "video") + + # Data task generate video + idTask = video_task.get("id") + + if not idTask or not idTask.strip(): + raise ValueError("Video get result task fail! Video generate task ID is not valid!") + + video_generated = None + for _ in range(400): + check_video_task = createRequest(payload, "video-check", "GET", {"id": idTask}) + task_status = check_video_task.get("task_status") + + if task_status == "SUCCESS": + video_generated = check_video_task.get("video_result") + break + + elif task_status == "FAIL": + raise ValueError("The video generation task failed!") + + time.sleep(0.3) + + if video_generated is None or not len(video_generated): + raise ValueError("Genereated video is not valid!") + + return await download_url_to_video_output(str(video_generated[0]["url"])) + +class CogVideoXGenerateNode: + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "model": ( + LIST_VIDEO_GENERATION_MODELS, + { + "default": CONFIG.get("default_video_generate_model", "cogvideox-3"), + "tooltip": "The model code to be called. Models with text 'flash' should be free!", + }, + ), + "prompt": ( + "STRING", + { + "multiline": True, + "placeholder": "Input prompt text", + "default": "", + "tooltip": "Enter the prompt for generated image", + }, + ), + }, + "optional": { + "image": ("IMAGE",), + "quality": ( + ["speed", "quality"], + { + "default": "speed", + "tooltip": "Output mode, defaults to speed. quality: Quality priority, generates higher quality output. speed: Speed ​​priority, generates faster output, but with slightly lower quality.", + }, + ), + "with_audio": ("BOOLEAN", {"default": False, "tooltip": "Whether to generate AI sound effects. Default: False (do not generate sound effects)."},), + "watermark": ("BOOLEAN", {"default": True, "tooltip": "Add watermark, default: True. Watermark off allow only customers who have signed a disclaimer to use the service. Signature path: Personal Center>Security Management>Remove Watermark Management"},), + "width": ("INT", {"default": 1920, "tooltip":f"Video width, default value 1920. Recommended width values: {getStrListSizes(VIDEO_SUPPORTS_RESOLUTIONS, 0)}"}), + "height": ("INT", {"default": 1080, "tooltip":f"Video height, default value 1080. Recommended height values: {getStrListSizes(VIDEO_SUPPORTS_RESOLUTIONS, 1)}"}), + "fps": ([30, 60], {"default": 30, "tooltip":"Video frame rate (FPS), default value is 30 frame rate"}), + "duration": ([5, 10], {"default": 5, "tooltip":"Video duration, default is 5 seconds"}), + } + } + + RETURN_TYPES = ("VIDEO",) + FUNCTION = "video_generate" + DESCRIPTION = ( + "This is a node that generates an video based on a text prompt or image." + ) + CATEGORY = "AlekPet Nodes/video" + + async def video_generate(self, model, prompt, image=None, quality="speed", with_audio=False, watermark=True, width=1920, height=1080, fps=30, duration=5): + if prompt is None and not prompt.strip(): + raise ValueError("Prompt value is empty!") + + return (await execute_gen_video(model, prompt, image, quality, with_audio, watermark, width, height, fps, duration),) \ No newline at end of file diff --git a/ChatGLMNode/config.json.example b/ChatGLMNode/config.json.example index 3f43032..1641574 100644 --- a/ChatGLMNode/config.json.example +++ b/ChatGLMNode/config.json.example @@ -4,5 +4,7 @@ "to_translate": "en", "default_language_model": "glm-4.5-flash", "default_multimodal_model": "glm-4.6v-flash", + "default_image_generate_model": "cogview-3-flash", + "default_video_generate_model": "cogvideox-flash", "ZHIPUAI_API_KEY": "your_api_key" } diff --git a/__init__.py b/__init__.py index 4406750..35a5a2e 100644 --- a/__init__.py +++ b/__init__.py @@ -364,6 +364,8 @@ try: ChatGLM4TranslateTextNode, ChatGLM4InstructNode, ChatGLM4InstructMediaNode, + CogViewImageGenerateNode, + CogVideoXGenerateNode ) NODE_CLASS_MAPPINGS.update( @@ -372,6 +374,9 @@ try: "ChatGLM4TranslateTextNode": ChatGLM4TranslateTextNode, "ChatGLM4InstructNode": ChatGLM4InstructNode, "ChatGLM4InstructMediaNode": ChatGLM4InstructMediaNode, + # Generate + "CogViewImageGenerateNode": CogViewImageGenerateNode, + "CogVideoXGenerateNode": CogVideoXGenerateNode, } ) @@ -381,6 +386,9 @@ try: "ChatGLM4TranslateTextNode": "ChatGLM-4 Translate Text Node", "ChatGLM4InstructNode": "ChatGLM-4 Instruct Node", "ChatGLM4InstructMediaNode": "ChatGLM-4 Instruct Media Node", + # Generate + "CogViewImageGenerateNode": "CogView Image Generate Node", + "CogVideoXGenerateNode": "CogVideoX Video Generate Node", } ) diff --git a/pyproject.toml b/pyproject.toml index 1ee341b..c77cf6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui_custom_nodes_alekpet" -description = "Nodes: PoseNode, PainterNode, TranslateTextNode, TranslateCLIPTextEncodeNode, DeepTranslatorTextNode, DeepTranslatorCLIPTextEncodeNode, ArgosTranslateTextNode, ArgosTranslateCLIPTextEncodeNode, ChatGLM4TranslateCLIPTextEncodeNode, ChatGLM4TranslateTextNode, ChatGLM4InstructNode, ChatGLM4InstructMediaNode, PreviewTextNode, HexToHueNode, ColorsCorrectNode, IDENode." -version = "1.0.90" +description = "Nodes: PoseNode, PainterNode, TranslateTextNode, TranslateCLIPTextEncodeNode, DeepTranslatorTextNode, DeepTranslatorCLIPTextEncodeNode, ArgosTranslateTextNode, ArgosTranslateCLIPTextEncodeNode, ChatGLM4TranslateCLIPTextEncodeNode, ChatGLM4TranslateTextNode, ChatGLM4InstructNode, ChatGLM4InstructMediaNode, CogViewImageGenerateNode, CogVideoXGenerateNode, PreviewTextNode, HexToHueNode, ColorsCorrectNode, IDENode." +version = "1.0.91" license = { file = "LICENSE" } [project.urls]