This commit is contained in:
AlekPet
2025-07-23 09:45:12 +03:00
21 changed files with 1760 additions and 2415 deletions
-3
View File
@@ -1,3 +0,0 @@
go/
DeepLX/
config.json
-134
View File
@@ -1,134 +0,0 @@
# DeepLX nodes for ComfyUI, for translating prompts into other languages.
Powerful Free DeepL API, No Token Required. Used module [DeepLX](https://github.com/OwO-Network/DeepLX) .
### Languages supported:
[List languages supported](https://developers.deepl.com/docs/getting-started/supported-languages)
<details>
<summary><strong>Source Languages</strong></summary>
- Arabic: AR
- Bulgarian: BG
- Czech: CS
- Danish: DA
- German: DE
- Greek: EL
- English: EN
- Spanish: ES
- Estonian: ET
- Finnish: FI
- French: FR
- Hungarian: HU
- Indonesian: ID
- Italian: IT
- Japanese: JA
- Korean: KO
- Lithuanian: LT
- Latvian: LV
- Norwegian Bokmål: NB
- Dutch: NL
- Polish: PL
- Portuguese: PT
- Romanian: RO
- Russian: RU
- Slovak: SK
- Slovenian: SL
- Swedish: SV
- Turkish: TR
- Ukrainian: UK
- Chinese: ZH
</details>
<details>
<summary><strong>Target Languages</strong></summary>
- Arabic: AR
- Bulgarian: BG
- Czech: CS
- Danish: DA
- German: DE
- Greek: EL
- English: EN
- English (British): EN-GB
- English (American): EN-US
- Spanish: ES
- Estonian: ET
- Finnish: FI
- French: FR
- Hungarian: HU
- Indonesian: ID
- Italian: IT
- Japanese: JA
- Korean: KO
- Lithuanian: LT
- Latvian: LV
- Norwegian Bokmål: NB
- Dutch: NL
- Polish: PL
- Portuguese: PT
- Portuguese (Brazilian): PT-BR
- Portuguese (all Portuguese variants excluding Brazilian Portuguese): PT-PT
- Romanian: RO
- Russian: RU
- Slovak: SK
- Slovenian: SL
- Swedish: SV
- Turkish: TR
- Ukrainian: UK
- Chinese (simplified): ZH-HANS
- Chinese (traditional): ZH-HANT
</details>
### Autoinstaller
**Golang (Go)** and **DeepLX** installation occurs automatically if the **Go** or **DeepLX** directory is missing.
You can also call the automatic installer by running shell `run_install_deeplx_windows.cmd` or `run_install_deeplx_linux.sh` for your platform.
**_Note: If you encounter any problems, you can use manual installation._**
### Manual install and use:
1. Install my custom nodes in ComfyUI, used [HERE](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet#installing)
2. Download zip archive Golang (programming language) from your system (Widnows, Linux ...) [Download Golang](https://go.dev/dl/)
3. Extract archive (inside archive folder `go`) in the folder `\ComfyUI\custom_nodes\ComfyUI_Custom_Nodes_AlekPet\DeepLXTranslateNode`
4. Open shell in the folder `\ComfyUI\custom_nodes\ComfyUI_Custom_Nodes_AlekPet\DeepLXTranslateNode` and run command `git clone https://github.com/OwO-Network/DeepLX` (you need install Git if not exists [Git](https://git-scm.com/downloads))
**Structure folders:**
![DeepLXTranslateNode structures folders](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/raw/master/DeepLXTranslateNode/image_deeplx_structures_folders.png)
5. Run comfyui (**see note after**) and add node `DeepLXTranslateCLIPTextEncodeNode` or `DeepLXTranslateTextNode` context menu in the category `CONDITIONING`.
6. [Optional] If you are in **CH**, you need in the `\ComfyUI\custom_nodes\ComfyUI_Custom_Nodes_AlekPet\DeepLXTranslateNode\go`, edit file **go.env**, modify this line `GOPROXY=https://proxy.golang.org,direct` to `GOPROXY=https://goproxy.cn,direct` [Thanks tongpeng1988](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/issues/136#issuecomment-2804126821) .
**_Note: The first launch may fail, Golang is installing packages for DeepLX, just restart ComfyUI!_**
### Note
On first launch, a `config.json` file will be created where you can specify the initial values ​​from which language to translate and to which language. You can also disable DeepLXTranslate nodes using the 'run_deeplx' property.
**Default config.json values:**
```json
{
"settings": {
"__commnet": "Please check the list of available languages ​​before specifying, especially target_lang! Property run_deeplx disable DeepLXTranslate nodes. See README file",
"source_lang": "Russian",
"target_lang": "English",
"run_deeplx": true
}
}
```
> Includes:
> **DeepLXTranslateCLIPTextEncodeNode** - translate text, and return CONDITIONING
>
> **DeepLXTranslateTextNode** - translate text and return text (STRING)
## Image:
![DeepLXTranslateNode](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/raw/master/DeepLXTranslateNode/image_deeplx_translate_node.jpg)
@@ -1,386 +0,0 @@
# Title: DeepLX Translate promts nodes
# Author: AlekPet (https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet)
# Github DeepLX: https://github.com/OwO-Network/DeepLX
import os
import json
import subprocess
import requests
import time
from . import install_deeplx
LANGUAGES_CODES = {
"source": {
"Arabic": "AR",
"Bulgarian": "BG",
"Czech": "CS",
"Danish": "DA",
"German": "DE",
"Greek": "EL",
"English": "EN",
"Spanish": "ES",
"Estonian": "ET",
"Finnish": "FI",
"French": "FR",
"Hungarian": "HU",
"Indonesian": "ID",
"Italian": "IT",
"Japanese": "JA",
"Korean": "KO",
"Lithuanian": "LT",
"Latvian": "LV",
"Norwegian Bokmål": "NB",
"Dutch": "NL",
"Polish": "PL",
"Portuguese": "PT",
"Romanian": "RO",
"Russian": "RU",
"Slovak": "SK",
"Slovenian": "SL",
"Swedish": "SV",
"Turkish": "TR",
"Ukrainian": "UK",
"Chinese": "ZH",
},
"target": {
"Arabic": "AR",
"Bulgarian": "BG",
"Czech": "CS",
"Danish": "DA",
"German": "DE",
"Greek": "EL",
"English": "EN",
"English (British)": "EN-GB",
"English (American)": "EN-US",
"Spanish": "ES",
"Estonian": "ET",
"Finnish": "FI",
"French": "FR",
"Hungarian": "HU",
"Indonesian": "ID",
"Italian": "IT",
"Japanese": "JA",
"Korean": "KO",
"Lithuanian": "LT",
"Latvian": "LV",
"Norwegian Bokmål": "NB",
"Dutch": "NL",
"Polish": "PL",
"Portuguese": "PT",
"Portuguese (Brazilian)": "PT-BR",
"Portuguese (all Portuguese variants excluding Brazilian Portuguese)": "PT-PT",
"Romanian": "RO",
"Russian": "RU",
"Slovak": "SK",
"Slovenian": "SL",
"Swedish": "SV",
"Turkish": "TR",
"Ukrainian": "UK",
"Chinese (simplified)": "ZH-HANS",
"Chinese (traditional)": "ZH-HANT",
},
}
# Colors
class ColPrint:
RED = "\033[1;31;40m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
MAGNETA = "\033[95m"
BLUE = "\033[94m"
CLEAR = "\033[0m"
# Node directory
NODE_DIR = os.path.dirname(os.path.abspath(__file__))
SETTINGS = {}
# Directory node and config file
config_path = os.path.join(NODE_DIR, "config.json")
# Load config.js file
if not os.path.exists(config_path):
print(
f"{ColPrint.YELLOW}[DeepLXTranslateNode] {ColPrint.MAGNETA}File config.js file not found! Create default!{ColPrint.CLEAR}"
)
with open(config_path, "w", encoding="utf-8") as f:
defaultJSON = {
"settings": {
"__commnet": "Please check the list of available languages ​​before specifying, especially target_lang! Property run_deeplx disable DeepLXTranslate nodes. See README file",
"source_lang": "Russian",
"target_lang": "English",
"run_deeplx": True
}
}
json.dump(defaultJSON, f, ensure_ascii=False, indent=4)
else:
with open(config_path, "r", encoding="utf-8") as f:
config = json.load(f)
SETTINGS = config.get("settings", {})
if SETTINGS.keys():
reset_lang = False
# Getting user settings for config.json
source_lang = SETTINGS.get("source_lang")
target_lang = SETTINGS.get("target_lang")
if source_lang not in LANGUAGES_CODES["source"]:
SETTINGS.update({"source_lang": "Russian"})
reset_lang = True
if target_lang not in LANGUAGES_CODES["target"]:
SETTINGS.update({"target_lang": "English"})
reset_lang = True
if reset_lang:
print(
f"{ColPrint.YELLOW}[DeepLXTranslateNode]{ColPrint.MAGNETA} Source or target language not found in list of available languages, check config.js!{ColPrint.CLEAR}"
)
# DeepLX and go constants
DEEPLX_SERVER_RUNNING = False
DEEPLX_SERVER_URL = "http://127.0.0.1:1188/"
DEEPLX_SERVER_URL_TRANSLATE = "http://127.0.0.1:1188/translate"
RETRY_CHECK_SERVER = 10
PATH_TO_DEEPLX_SERVER = os.path.join(NODE_DIR, "DeepLX")
PATH_TO_GO = os.path.join(NODE_DIR, "go", "bin")
# We check whether the user has disabled the launch of the DeepLX server.
if not SETTINGS.get("run_deeplx", True): # Setting whether to run DeepLX server
DEEPLX_SERVER_RUNNING = False
raise Exception("User disabled DeepLX server startup in config.json (proprety: 'run_deeplx')")
### Automatical install Golang (Go) and DeepLX ###
try:
# Checking exists path to Go and install Golang (Go)
if not os.path.exists(PATH_TO_GO):
print(f"{ColPrint.YELLOW}[DeepLXTranslateNode]{ColPrint.MAGNETA} Path to 'Golang (Go)' not exists, try install...{ColPrint.CLEAR}")
# Get platform
platform = install_deeplx.getPlatform()
if platform is not None:
print(f"{ColPrint.YELLOW}[DeepLXTranslateNode]{ColPrint.GREEN} Your platform is: {'macOS (Darwin)' if platform == 'darwin' else platform.capitalize()}{ColPrint.CLEAR}")
# Get link
go_url = install_deeplx.GO_URLS[platform]
# Download file
file_name, path_to_file = install_deeplx.download(go_url, NODE_DIR)
# Extract file
install_deeplx.extractArchive(path_to_file)
else:
print(f"{ColPrint.RED}[DeepLXTranslateNode] Unable to determine identifying the underlying platform! Use manual installation!{ColPrint.CLEAR} ")
# # Checking exists path to Go and install DeepLX
if not os.path.exists(PATH_TO_DEEPLX_SERVER):
print(
f"{ColPrint.YELLOW}[DeepLXTranslateNode]{ColPrint.MAGNETA} Path to DeepLX server folder not exists, try install...{ColPrint.CLEAR}")
# Get link
deeplx_url = install_deeplx.FILES_DOWNLOAD["DeepLX"].get("url")
# Download file
file_name, path_to_file = install_deeplx.download(deeplx_url, NODE_DIR)
# Extract file
install_deeplx.extractArchive(path_to_file)
# Rename DeepLX-master to DeepLX
install_deeplx.otherOperations("DeepLX", NODE_DIR)
except Exception as e:
raise e
### end - Automatical install Golang (Go) and DeepLX ###
# Checking pathes variables
if not os.path.exists(PATH_TO_GO):
raise FileNotFoundError(f"Error path to 'Golang (Go)' not exists: {PATH_TO_GO}")
if not os.path.exists(PATH_TO_DEEPLX_SERVER):
raise FileNotFoundError(f"Error path to DeepLX server folder not exists: {PATH_TO_DEEPLX_SERVER}")
# Detect platform
go_executable = os.path.join(PATH_TO_GO, "go")
if os.name == "nt":
go_executable += ".exe"
# Try run DeepLX server
try:
print(f"{ColPrint.YELLOW}[DeepLXTranslateNode] {ColPrint.BLUE}Running server DeepLX...{ColPrint.CLEAR}")
proc_deeplx = subprocess.Popen([go_executable, "run", "main.go"], cwd=PATH_TO_DEEPLX_SERVER)
DEEPLX_SERVER_RUNNING = True
time.sleep(2)
except Exception as e:
DEEPLX_SERVER_RUNNING = False
raise Exception(
f"{ColPrint.RED}[DeepLXTranslateNode] Error running server DeepLX: {ColPrint.MAGNETA}{e}{ColPrint.CLEAR}"
)
if DEEPLX_SERVER_RUNNING:
for retry in range(RETRY_CHECK_SERVER, 0, -1):
print(
f"{ColPrint.YELLOW}[DeepLXTranslateNode] {ColPrint.BLUE}Checking DeepLX server. Attempt {retry}...{ColPrint.CLEAR}"
)
try:
response = requests.get(DEEPLX_SERVER_URL)
response.raise_for_status()
print(
f"{ColPrint.YELLOW}[DeepLXTranslateNode]{ColPrint.GREEN} Server responded successfully: {ColPrint.CLEAR}",
response.text,
)
DEEPLX_SERVER_RUNNING = True
break
except requests.HTTPError as e:
print(
f"{ColPrint.RED}[DeepLXTranslateNode] HTTP error while connecting to DeepLX: {ColPrint.MAGNETA}{e}{ColPrint.CLEAR}"
)
if retry == 1:
DEEPLX_SERVER_RUNNING = False
raise
except Exception as e:
if retry == 1:
DEEPLX_SERVER_RUNNING = False
raise Exception(
f"{ColPrint.RED}[DeepLXTranslateNode] Unexpected error while checking DeepLX server: {ColPrint.MAGNETA}{e}{ColPrint.CLEAR}"
)
time.sleep(2)
else:
DEEPLX_SERVER_RUNNING = False
print(
f"{ColPrint.RED}[DeepLXTranslateNode] All attempts to reach the DeepLX server have failed.{ColPrint.CLEAR}"
)
def createRequest(payload):
"""Request function"""
try:
response = requests.post(DEEPLX_SERVER_URL_TRANSLATE, json=payload)
response.raise_for_status()
if response.status_code == 200:
json_data = response.json()
response_text = json_data.get("data").strip()
return response_text
except requests.HTTPError as e:
print(
f"DeepLX server is not running! Please check Go and DeepLX repository installation or information in the ComfyUI terminal!: {response.status_code}, {response.text}"
)
raise e
except Exception as e:
print(f"Error DeepLX: {e}")
raise e
def translate(prompt, source, target):
"""Translate function"""
# Check prompt exist
if prompt is None or prompt.strip() == "":
return ""
# Create body request
payload = {"source_lang": source, "target_lang": target, "text": prompt}
response_translate_text = createRequest(payload)
return response_translate_text
class DeepLXTranslateCLIPTextEncodeNode:
@classmethod
def INPUT_TYPES(self):
source_langs = list(LANGUAGES_CODES.get("source").keys())
target_langs = list(LANGUAGES_CODES.get("target").keys())
return {
"required": {
"source_translate": (
source_langs,
{"default": SETTINGS.get("source_lang", "Russian"), "tooltip": "Translation source languge"},
),
"target_translate": (
target_langs,
{"default": SETTINGS.get("target_lang", "English"), "tooltip": "Translation target languge"},
),
"prompt": (
"STRING",
{"multiline": True, "placeholder": "Input prompt text"},
),
"clip": ("CLIP",),
}
}
RETURN_TYPES = (
"CONDITIONING",
"STRING",
)
FUNCTION = "deeplx_translate_text"
DESCRIPTION = "This is a node that translates the prompt into another language using DeepLX."
CATEGORY = "AlekPet Nodes/conditioning"
def deeplx_translate_text(self, source_translate, target_translate, prompt, clip):
if not DEEPLX_SERVER_RUNNING:
raise ValueError(
"DeepLX server is not running! Please check Go and DeepLX repository installation or information in the ComfyUI terminal!"
)
# Get short value source and target code languages
source_translate_code = LANGUAGES_CODES["source"][source_translate]
target_translate_code = LANGUAGES_CODES["target"][target_translate]
prompt = translate(prompt, source_translate_code, target_translate_code)
tokens = clip.tokenize(prompt)
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
return ([[cond, {"pooled_output": pooled}]], prompt)
class DeepLXTranslateTextNode(DeepLXTranslateCLIPTextEncodeNode):
@classmethod
def INPUT_TYPES(self):
return_types = super().INPUT_TYPES()
del return_types["required"]["clip"]
return return_types
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "deeplx_translate_text"
CATEGORY = "AlekPet Nodes/text"
def deeplx_translate_text(self, source_translate, target_translate, prompt):
if not DEEPLX_SERVER_RUNNING:
raise ValueError(
"DeepLX server is not running! Please check Go and DeepLX repository installation or information in the ComfyUI terminal!"
)
# Get short value source and target code languages
source_translate_code = LANGUAGES_CODES["source"][source_translate]
target_translate_code = LANGUAGES_CODES["target"][target_translate]
prompt = translate(prompt, source_translate_code, target_translate_code)
return (prompt,)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 478 KiB

-219
View File
@@ -1,219 +0,0 @@
# Title: Autoinstall Golang (Go) and DeepLX for ComfyUI DeepLXTranslateNode
# Author: AlekPet (https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet)
# DeepLXTranslateNode README: https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/blob/master/DeepLXTranslateNode/README.md
import os
from sys import version_info, platform as _platform
import platform
import requests
from tqdm import tqdm
import shutil
class ColPrintInstall:
RED = "\033[1;31;40m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
MAGNETA = "\033[95m"
BLUE = "\033[94m"
CLEAR = "\033[0m"
GO_URLS = {
"win32": "https://go.dev/dl/go1.24.2.windows-386.zip",
"win64": "https://go.dev/dl/go1.24.2.windows-amd64.zip",
"darwin": "https://go.dev/dl/go1.24.2.darwin-arm64.tar.gz",
"linux_i386_i686": "https://go.dev/dl/go1.24.2.linux-386.tar.gz",
"linux_x86_64": "https://go.dev/dl/go1.24.3.linux-amd64.tar.gz",
"linux_aarch64": "https://go.dev/dl/go1.24.3.linux-arm64.tar.gz",
"linux_armv6l": "https://go.dev/dl/go1.24.3.linux-armv6l.tar.gz",
}
FILES_DOWNLOAD = {
"Golang": {
"url": None,
},
"DeepLX": {
"url":"https://github.com/OwO-Network/DeepLX/archive/refs/heads/main.zip",
},
}
WORK_DIR = os.path.dirname(os.path.abspath(__file__))
def getPlatform():
""" Get platform machine """
platform_name = None
if _platform.startswith("linux"):
# Check architecture linux
arch = platform.uname()
if arch.machine == "x86_64":
platform_name = "linux_x86_64"
elif arch.machine == "i386" or arch.machine == "i686":
platform_name = "linux_i386_i686"
elif arch.machine == "aarch64":
platform_name = "linux_aarch64"
elif arch.machine == "armv6l" or arch.machine == "armv7l":
platform_name = "linux_armv6l"
else:
platform_name = "linux_i386_i686"
elif _platform == "darwin":
platform_name = "darwin"
elif _platform == "win32":
# Check Windows id 64-bit
if platform.machine().endswith('64'):
platform_name = "win64"
else:
platform_name = "win32"
return platform_name
def download(url, save_to = WORK_DIR):
""" Download file """
file_name = url.split('/')[-1]
print(f"{ColPrintInstall.YELLOW}[DeepLXTranslateNode]{ColPrintInstall.BLUE} Downloading file: {url}{ColPrintInstall.CLEAR}")
response = requests.get(url, stream=True)
file_size = int(response.headers.get('Content-Length', 0))
save_file_path = os.path.join(save_to, file_name)
chunk_size = 1024
with open(save_file_path, "wb") as handle, tqdm(
desc=file_name,
total=file_size,
unit='B',
unit_scale=True,
unit_divisor=chunk_size,
) as progress_bar:
for data in response.iter_content(chunk_size=chunk_size):
handle.write(data)
progress_bar.update(len(data))
print(f"{ColPrintInstall.YELLOW}[DeepLXTranslateNode]{ColPrintInstall.GREEN} File '{file_name}' downloaded!{ColPrintInstall.CLEAR}")
return file_name, save_file_path
def extractArchive(file_path, extract_to = WORK_DIR, removeArchive = True):
""" Extract zip or tar.gzip archive """
try:
file_name = os.path.basename(file_path)
ext = os.path.splitext(file_path)[-1]
desc = f"Extracting '{file_name}'"
# Type archive
if ext == ".gz":
# TarFile
import tarfile
# Safe extract for python < 3.12 (secure path traversal)
def safe_extract(tar, path=".", members=None):
if members is None:
members = tar.getmembers()
safe_members = []
for member in members:
member_path = os.path.join(path, member.name)
if not os.path.abspath(member_path).startswith(os.path.abspath(path)):
continue
safe_members.append(member)
tar.extractall(path, safe_members)
# Open tar archive
with tarfile.open(file_path, 'r') as tar_archive:
members = tar_archive.getmembers()
# Python >= 3.12
if version_info >= (3, 12):
for file in tqdm(members, desc=desc, unit="file"):
tar_archive.extract(file, extract_to, filter='data')
else:
for member in tqdm(members, desc=desc):
safe_extract(tar_archive, extract_to, [member])
elif ext == ".zip":
# ZipFile
import zipfile
# Open zip archive
with zipfile.ZipFile(file_path, 'r') as zip_archive:
file_list = zip_archive.namelist()
with tqdm(file_list, desc=desc, unit="file") as pbar:
for file in pbar:
zip_archive.extract(file, extract_to)
print(f"{ColPrintInstall.YELLOW}[DeepLXTranslateNode]{ColPrintInstall.GREEN} Archive '{file_name}' extracted.{ColPrintInstall.CLEAR}")
# Remove archive file
if removeArchive:
os.remove(file_path)
print(f"{ColPrintInstall.YELLOW}[DeepLXTranslateNode]{ColPrintInstall.MAGNETA} File {file_name} removed, after extract.{ColPrintInstall.CLEAR}\n")
except Exception as e:
raise e
def otherOperations(name, save_path_dir):
if name == "DeepLX":
path_to_deeplx_master = os.path.join(save_path_dir, "DeepLX-main")
path_to_rename = os.path.join(save_path_dir, "DeepLX")
if os.path.isdir(path_to_deeplx_master) and not os.path.exists(path_to_rename):
os.rename(path_to_deeplx_master, path_to_rename)
print(f"{ColPrintInstall.YELLOW}[DeepLXTranslateNode]{ColPrintInstall.MAGNETA} Directory 'DeepLX-main' renamed to 'DeepLX'!{ColPrintInstall.CLEAR}")
else:
print(f"{ColPrintInstall.YELLOW}[DeepLXTranslateNode]{ColPrintInstall.MAGNETA} Directory 'DeepLX' already exists or directory {path_to_deeplx_master}' is not correct! Renaming operation abort!{ColPrintInstall.CLEAR}")
shutil.rmtree(path_to_deeplx_master, ignore_errors=True)
def main():
print(f"""{ColPrintInstall.MAGNETA}~~~~~~~~~ Autoinstall Golang (Go) and DeepLX for ComfyUI DeepLXTranslateNode by AlekPet ~~~~~~~~~
#{ColPrintInstall.CLEAR} Github: {ColPrintInstall.BLUE}https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet
{ColPrintInstall.MAGNETA}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{ColPrintInstall.CLEAR}""")
try:
platform_name = getPlatform()
print(f"{ColPrintInstall.YELLOW}[DeepLXTranslateNode]{ColPrintInstall.GREEN} Your platform is: {'macOS (Darwin)' if platform_name == 'darwin' else platform_name.capitalize()}{ColPrintInstall.CLEAR}")
if platform_name is None:
raise Exception("Unable to determine identifying the underlying platform!")
url_down = GO_URLS[platform_name]
# Download links (Golang and DeepLX)
FILES_DOWNLOAD["Golang"]["url"] = url_down
for keyName in FILES_DOWNLOAD:
url = FILES_DOWNLOAD[keyName].get("url")
if url is None:
continue
# Download file
file_name, path_to_file = download(url, WORK_DIR)
# Extract file
extractArchive(path_to_file)
# Other operation
otherOperations(keyName, WORK_DIR)
print(f"{ColPrintInstall.GREEN}--------------------\nAutoinstall has completed its work...{ColPrintInstall.CLEAR}")
except Exception as e:
raise e
if __name__ == "__main__":
main()
@@ -1,5 +0,0 @@
#!/bin/bash
# Install Golang and DeepLX for ComfyUI DeepLXTranslateNode by AlekPet
# Github repository: https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/DeepLXTranslateNode
python install_deeplx.py
@@ -1,6 +0,0 @@
@echo off
REM ### Install Golang and DeepLX for ComfyUI DeepLXTranslateNode by AlekPet ###
REM ### Github repository: https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/DeepLXTranslateNode
..\..\..\..\python_embeded\python.exe install_deeplx.py
pause
@@ -188,7 +188,11 @@ function get_support_langs() {
// Auth
const auth_data = responseData?.auth_data;
if (widget_auth_data) {
if (
widget_auth_data &&
auth_data instanceof Object &&
Object.keys(auth_data)?.length
) {
let placeholder = Object.keys(placeholders).find(
(ph_key) => ph_key.includes(service) || ph_key === service
);
@@ -0,0 +1,130 @@
.alekpet_extras_node_speechrecognition_box {
margin: 0px;
padding: 0px;
position: absolute;
text-align: center;
cursor: pointer;
font-size: 1rem;
display: flex;
justify-content: center;
flex-flow: column;
width: 45px;
font-family: monospace;
align-items: center;
margin-top: 3px;
}
.alekpet_extras_node_speechrecognition_row {
position: relative;
display: flex;
gap: 3px;
justify-content: center;
align-items: center;
}
.alekpet_extras_node_recognition_icon {
width: 8px;
height: 8px;
border: 1px solid var(--input-text);
background: transparent;
border-radius: 50%;
padding: 0;
margin: 0px 5px 0px 0px;
display: inline-block;
}
.alekpet_extras_node_recognition_icon_active {
border: 1px solid var(--input-text);
background: var(--error-text);
border-radius: 0;
}
.alekpet_extras_node_recognition_icon_box {
display: flex;
justify-content: center;
align-items: baseline;
gap: 3px;
transition: all 0.7s;
margin-top: 2px;
}
.alekpet_extras_node_recognition_icon:hover {
background: var(--error-text);
}
.alekpet_extras_node_recognition_aborted {
transition: all 0.3s;
font-size: 0.6rem;
}
.alekpet_extras_node_recognition_aborted:hover {
color: var(--error-text);
}
.alekpet_extras_node_info {
display: flex;
align-self: center;
}
.alekpet_extras_node_speech_recognition_checkbox {
width: 10px;
height: 10px;
accent-color: var(--input-text);
padding: 0;
margin: 0;
}
.alekpet_extras_node_speech_recognition_checkbox:not(:checked) {
opacity: 0.5;
}
.alekpet_extras_node_recognition_save {
accent-color: var(--p-red-400);
}
.alekpet_extras_node_speech_icon {
font-size: 0.6em;
opacity: 1;
line-height: 1;
margin-top: 2px;
}
.alekpet_extras_node_speech_icon_playing {
opacity: 0.7;
}
.alekpet_extras_node_speech_icon_playing::after {
content: "✖";
position: absolute;
right: 3px;
color: var(--error-text);
}
/* Settings */
.alekpet_extras_node_speech_recognition_settings_props_label {
background: #646363;
color: #cdcbcb;
border-radius: 5px;
padding: 3px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
}
.alekpet_extras_node_speech_recognition_settings_props_label:hover {
background: darkgrey;
color: white;
}
.alekpet_extras_node_speech_recognition_settings_props_input {
max-width: 10px;
padding: 0;
margin: 0;
outline: 0;
}
.alekpet_extras_node_speech_recognition_settings_button {
display: flex;
font-size: 0.6rem;
}
-131
View File
@@ -1,134 +1,3 @@
.alekpet_extras_node_speechrecognition_box {
margin: 0px;
padding: 0px;
position: absolute;
text-align: center;
cursor: pointer;
font-size: 1rem;
display: flex;
justify-content: center;
flex-flow: column;
width: 45px;
font-family: monospace;
align-items: center;
margin-top: 3px;
}
.alekpet_extras_node_speechrecognition_row {
position: relative;
display: flex;
gap: 3px;
justify-content: center;
align-items: center;
}
.alekpet_extras_node_recognition_icon {
width: 8px;
height: 8px;
border: 1px solid var(--input-text);
background: transparent;
border-radius: 50%;
padding: 0;
margin: 0px 5px 0px 0px;
display: inline-block;
}
.alekpet_extras_node_recognition_icon_active {
border: 1px solid var(--input-text);
background: var(--error-text);
border-radius: 0;
}
.alekpet_extras_node_recognition_icon_box {
display: flex;
justify-content: center;
align-items: baseline;
gap: 3px;
transition: all 0.7s;
margin-top: 2px;
}
.alekpet_extras_node_recognition_icon:hover {
background: var(--error-text);
}
.alekpet_extras_node_recognition_aborted {
transition: all 0.3s;
font-size: 0.6rem;
}
.alekpet_extras_node_recognition_aborted:hover {
color: var(--error-text);
}
.alekpet_extras_node_info {
display: flex;
align-self: center;
}
.alekpet_extras_node_speech_recognition_checkbox {
width: 10px;
height: 10px;
accent-color: var(--input-text);
padding: 0;
margin: 0;
}
.alekpet_extras_node_speech_recognition_checkbox:not(:checked) {
opacity: 0.5;
}
.alekpet_extras_node_recognition_save {
accent-color: var(--p-red-400);
}
.alekpet_extras_node_speech_icon {
font-size: 0.6em;
opacity: 1;
line-height: 1;
margin-top: 2px;
}
.alekpet_extras_node_speech_icon_playing {
opacity: 0.7;
}
.alekpet_extras_node_speech_icon_playing::after {
content: "✖";
position: absolute;
right: 3px;
color: var(--error-text);
}
/* Settings */
.alekpet_extras_node_speech_recognition_settings_props_label {
background: #646363;
color: #cdcbcb;
border-radius: 5px;
padding: 3px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: nowrap;
}
.alekpet_extras_node_speech_recognition_settings_props_label:hover {
background: darkgrey;
color: white;
}
.alekpet_extras_node_speech_recognition_settings_props_input {
max-width: 10px;
padding: 0;
margin: 0;
outline: 0;
}
.alekpet_extras_node_speech_recognition_settings_button {
display: flex;
font-size: 0.6rem;
}
/* Style custom Confirm buttons */
.alekpet_confButtons {
border-radius: 3px;
+1 -710
View File
@@ -7,564 +7,17 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { ComfyWidgets } from "../../scripts/widgets.js";
import { $el } from "../../scripts/ui.js";
import { isValidStyle, comfyuiDesktopConfirm } from "./utils.js";
import { addStylesheet } from "../../scripts/utils.js";
import {
SpeechWidget,
makeColorWidget,
createPreiviewSize,
speechRect,
SpeechSynthesis,
checkboxLSCheckedByKey,
} from "./lib/extrasnode/extras_node_widgets.js";
import { RecognationSpeechDialog } from "./lib/extrasnode/extras_node_dialogs.js";
import { makeColorWidget } from "./lib/extrasnode/extras_node_widgets.js";
const convertIdClass = (text) => text.replaceAll(".", "_");
const idExt = "alekpet.ExtrasNode";
// LocalStorage settings
const PreviewImageSizeLS = localStorage.getItem(
`Comfy.Settings.${idExt}.PreviewImage`
);
const PreviewImageColorTextLS = localStorage.getItem(
`Comfy.Settings.${idExt}.PreviewImageColorText`
);
const PreviewImageColorBgLS = localStorage.getItem(
`Comfy.Settings.${idExt}.PreviewImageColorBg`
);
// Recognation & speech
const SpeechAndRecognationSpeechLS = localStorage.getItem(
`Comfy.Settings.${idExt}.SpeechAndRecognationSpeech`
);
// end - Recognation & speech
// Preview image, video and audio select list combo
const PreviewImageVideoComboLS = localStorage.getItem(
`Comfy.Settings.${idExt}.PreviewImageVideoCombo`
);
// Settings set values from LS or default
let PreviewImageSize = PreviewImageSizeLS
? JSON.parse(PreviewImageSizeLS)
: false,
PreviewImageColorText =
PreviewImageColorTextLS && PreviewImageColorTextLS.trim() !== ""
? PreviewImageColorTextLS
: document.documentElement.style.getPropertyValue("--input-text") ||
"#dddddd",
PreviewImageColorBg = PreviewImageColorBgLS ? PreviewImageColorBgLS : "",
// Speech & Recognition widget settings
SpeechAndRecognationSpeech = SpeechAndRecognationSpeechLS
? JSON.parse(SpeechAndRecognationSpeechLS)
: true,
SpeechAndRecognationSpeechSaveAs = JSON.parse(
localStorage.getItem(`${idExt}.SpeechAndRecognationSpeechSaveAs`),
false
),
// Preview image, video and audio select list combo
PreviewImageVideoCombo = PreviewImageVideoComboLS
? JSON.parse(PreviewImageVideoComboLS)
: true;
// Preview image, video and audio content loading function
const SUPPORTS_FORMAT = {
image: ["jpg", "jpeg", "bmp", "png", "gif", "tiff", "avif"],
video: ["mp4", "webm"],
audio: ["ogg", "wav", "mp3", "webm"],
};
const loadingContent = (src) => {
return new Promise((res, rej) => {
let ext = src.slice(src.lastIndexOf(".") + 1).toLowerCase();
ext = /\w+/.exec(ext)[0];
if (SUPPORTS_FORMAT.image.includes(ext)) {
const img = new Image();
img.crossOrigin = "";
img.onload = (e) => res({ raw: img, type: "image", src });
img.onerror = (err) => rej(err);
img.src = src;
}
if (SUPPORTS_FORMAT.video.includes(ext)) {
const video = document.querySelector(".preview_vid");
video.onerror = (err) => rej(err);
video.addEventListener("canplay", (e) => {
if (video.videoWidth !== 0) {
res({ raw: video, type: "video", src });
}
});
video.src = src;
}
if (SUPPORTS_FORMAT.audio.includes(ext)) {
const audio = document.querySelector(".preview_audio");
audio.onerror = (err) => rej(err);
audio.addEventListener("canplaythrough", (e) => {
res({ raw: audio, type: "audio", src });
});
audio.src = src;
}
});
};
function drawVid(raw, canvas, ctx, type) {
ctx.drawImage(raw, 0, 0, canvas.width, canvas.height);
canvas.requanim = requestAnimationFrame(
drawVid.bind(this, raw, canvas, ctx, type)
);
}
//
// Register Extension
app.registerExtension({
name: idExt,
init() {
addStylesheet("css/extrasnode/extras_node_styles.css", import.meta.url);
// -- Preview image, audio, video --
// Close when click on item inside context menu
function closePreviewContextClick() {
if (this.root?.preview_content_combo) {
this.root.preview_content_combo.remove();
this.root.preview_content_combo = null;
}
}
// Add preview element
function addPreviewModule(params, element) {
if (!PreviewImageVideoCombo) return;
const [name, value, options] = params;
if (value instanceof Object) return;
if (!this.root?.preview_content_combo) {
const preview_content_combo = $el(
"div.preview_content_combo",
{
style: {
position: "absolute",
maxWidth: "160px",
zIndex: 9999999,
},
parent: document.body,
},
[
$el("canvas.preview_img", {
style: { width: "100%" },
}),
$el("video.preview_vid", {
style: { opacity: 0, width: 0, height: 0 },
crossOrigin: "anonymous",
autoplay: false,
muted: false,
preload: "auto",
}),
$el("audio.preview_audio", {
style: { opacity: 0, position: "absolute", left: 0 },
crossOrigin: "anonymous",
autoplay: false,
muted: false,
preload: "auto",
controls: true,
}),
]
);
this.root.preview_content_combo = preview_content_combo;
}
LiteGraph.pointerListenerAdd(element, "enter", (e) => {
if (element?.dataset?.value) {
const body_rect = document.body.getBoundingClientRect();
const root_rect = this.root.getBoundingClientRect();
const { x, y } = element.getBoundingClientRect();
//const preview_rect = this.root.preview_content_combo.getBoundingClientRect();
//const scale = app.graph.extra.ds.scale;
const canvas = this.root.preview_content_combo.children[0];
const ctx = canvas.getContext("2d");
loadingContent(
api.apiURL(
`/view?filename=${encodeURIComponent(
element.dataset.value
)}&type=input`
)
).then(({ raw, type, src }) => {
this.root.preview_content_combo.style.maxWidth =
type === "audio" ? "300px" : "160px";
const previewWidth = parseInt(
this.root.preview_content_combo.style.maxWidth
);
// if (scale >= 1) {
// this.root.preview_content_combo.style.top = `${
// (y - root_rect.top) / scale
// }px`;
// } else {
// this.root.preview_content_combo.style.top = `${
// y - root_rect.top
// }px`;
// }
this.root.preview_content_combo.style.top = `${y}px`;
if (
body_rect.width &&
root_rect.right + previewWidth > body_rect.width
) {
this.root.preview_content_combo.style.left = `${
root_rect.left - previewWidth - 10
}px`;
} else {
this.root.preview_content_combo.style.left = `${
root_rect.left + root_rect.width + 10
}px`;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
const video = document.querySelector(".preview_vid");
const audio = document.querySelector(".preview_audio");
if (type === "image") {
canvas.width = raw.naturalWidth;
canvas.height = raw.naturalHeight;
if (!video.paused) {
video.pause();
video.currentTime = 0;
}
cancelAnimationFrame(canvas.requanim);
} else if (type === "video") {
canvas.width = raw.videoWidth;
canvas.height = raw.videoHeight;
video.play();
} else if (type === "audio") {
Object.assign(audio.style, {
opacity: 1,
display: "",
});
audio.play();
}
if (type === "video" || type === "image") {
Object.assign(audio.style, {
opacity: 0,
display: "none",
});
!audio.paused && audio.pause();
ctx.drawImage(raw, 0, 0, canvas.width, canvas.height);
}
if (type === "video")
canvas.requanim = requestAnimationFrame(
drawVid.bind(this, raw, canvas, ctx, type)
);
});
}
});
LiteGraph.pointerListenerAdd(element, "leave", (e) => {
if (element?.dataset?.value) {
const canvas = this.root.preview_content_combo.children[0];
const video = document.querySelector(".preview_vid");
if (!video.paused) {
video.pause();
video.currentTime = 0;
}
cancelAnimationFrame(canvas.requanim);
}
});
}
// AddItem
const addItem = ContextMenu.prototype.addItem;
ContextMenu.prototype.addItem = function () {
const element = addItem?.apply(this, arguments);
addPreviewModule.call(this, arguments, element);
};
// CloseItem
const closeItem = ContextMenu.prototype.close;
ContextMenu.prototype.close = function () {
closeItem?.apply(this, arguments);
closePreviewContextClick.call(this);
};
// Old verison comfyui, support context menu LiteGraph path...
// AddItem
const addItemLitegraph = LiteGraph.ContextMenu.prototype.addItem;
LiteGraph.ContextMenu.prototype.addItem = function () {
const element = addItemLitegraph?.apply(this, arguments);
addPreviewModule.call(this, arguments, element);
};
// CloseItem
const closeItemLitegraph = LiteGraph.ContextMenu.prototype.close;
LiteGraph.ContextMenu.prototype.close = function () {
closeItemLitegraph?.apply(this, arguments);
closePreviewContextClick.call(this);
};
const originalCloseAllContextMenus = LiteGraph.closeAllContextMenus;
LiteGraph.closeAllContextMenus = function () {
originalCloseAllContextMenus?.apply(this, arguments);
const previewCombo = document.querySelector(".preview_content_combo");
if (previewCombo) {
previewCombo.remove();
}
};
// -- end - Preview image, audio, video --
// PreviewImage settings ui
app.ui.settings.addSetting({
id: `${idExt}.PreviewImage`,
name: "🔸 Preview Image",
defaultValue: true,
type: (name, sett, val) => {
const newUI = document.querySelector(".p-dialog-header");
return $el("tr", [
!newUI
? $el("td", [
$el("label", {
textContent: name,
for: convertIdClass(`${idExt}.PreviewImage_size_checkbox`),
}),
])
: "",
$el("td", [
$el(
"label",
{
style: { display: "block" },
textContent: "Display image size: ",
for: convertIdClass(`${idExt}.PreviewImage_size_checkbox`),
},
[
$el("input", {
id: convertIdClass(`${idExt}.PreviewImage_size_checkbox`),
type: "checkbox",
checked: val,
onchange: (e) => {
const checked = !!e.target.checked;
PreviewImageSize = checked;
localStorage.setItem(
`Comfy.Settings.${idExt}.PreviewImage`,
checked
);
sett(checked);
},
}),
]
),
$el(
"label",
{
textContent: "Color text: ",
style: { display: "block" },
},
[
$el("input", {
id: convertIdClass(`${idExt}.PreviewImage_color_input`),
type: "color",
value: PreviewImageColorText,
onchange: (e) => {
const colorVal =
e.target.value ||
document.documentElement.style.getPropertyValue(
"--input-text"
) ||
"#dddddd";
PreviewImageColorText = colorVal;
localStorage.setItem(
`Comfy.Settings.${idExt}.PreviewImageColorText`,
PreviewImageColorText
);
},
}),
]
),
$el(
"label",
{
textContent: "Color background: ",
style: { display: "block" },
},
[
$el("input", {
id: convertIdClass(
`${idExt}.PreviewImage_color_background_input`
),
type: "color",
value: PreviewImageColorBg,
onchange: (e) => {
const colorVal = e.target.value || "";
PreviewImageColorBg = colorVal;
localStorage.setItem(
`Comfy.Settings.${idExt}.PreviewImageColorBg`,
PreviewImageColorBg
);
},
}),
]
),
$el("button", {
textContent: "Reset colors",
onclick: () => {
PreviewImageColorText = isValidStyle(
"color",
document.documentElement.style.getPropertyValue(
"--input-text"
) || "#ddd"
).color_hex;
PreviewImageColorBg = "";
document.querySelector(
"#alekpet_ExtrasNode_PreviewImage_color_input"
).value = PreviewImageColorText;
document.querySelector(
"#alekpet_ExtrasNode_PreviewImage_color_background_input"
).value = PreviewImageColorBg;
localStorage.setItem(
`Comfy.Settings.${idExt}.PreviewImageColorText`,
PreviewImageColorText
);
localStorage.setItem(
`Comfy.Settings.${idExt}.PreviewImageColorBg`,
PreviewImageColorBg
);
},
style: {
display: "block",
},
}),
]),
]);
},
});
// Speech & Recognition speech settings ui
app.ui.settings.addSetting({
id: `${idExt}.SpeechAndRecognationSpeech`,
name: "🔸 Speak text & Recognition speech",
defaultValue: true,
type: (name, sett, val) => {
const newUI = document.querySelector(".p-dialog-header");
return $el("tr", [
!newUI
? $el("td", [
$el("label", {
textContent: name,
for: convertIdClass(
`${idExt}.SpeechAndRecognationSpeech_show`
),
}),
])
: "",
$el("td", [
$el(
"label",
{
style: { display: "flex", alignItems: "center" },
textContent: "Enabled: ",
for: convertIdClass(`${idExt}.SpeechAndRecognationSpeech_show`),
},
[
$el("input", {
id: convertIdClass(
`${idExt}.SpeechAndRecognationSpeech_show`
),
type: "checkbox",
checked: val,
onchange: async (e) => {
const checked = !!e.target.checked;
SpeechAndRecognationSpeech = checked;
localStorage.setItem(
`Comfy.Settings.${idExt}.SpeechAndRecognationSpeech`,
checked
);
if (await comfyuiDesktopConfirm("Reload page?")) {
location.reload();
}
sett(checked);
},
}),
$el("span", {
textContent: "(Then reload the page)",
style: { fontSize: "0.6rem", color: "yellow" },
}),
]
),
$el(
"div",
{
style: {
display: "flex",
gap: "5px",
margin: "5px 0",
},
title: "Show modal window when saving recorded audio.",
},
[
$el("span", { textContent: "Output Save as?" }),
$el("input", {
type: "checkbox",
checked: SpeechAndRecognationSpeechSaveAs,
onchange: (e) => {
localStorage.setItem(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
!!e.target.checked
);
SpeechAndRecognationSpeechSaveAs = !!e.target.checked;
checkboxLSCheckedByKey(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
".alekpet_extras_node_recognition_saveAs"
);
},
}),
]
),
$el("button", {
textContent: "Speech settings",
onclick: () => {
new RecognationSpeechDialog().show();
},
style: {
display: "block",
},
}),
]),
]);
},
});
// Preview image, video and audio settings ui
app.ui.settings.addSetting({
id: `${idExt}.PreviewImageVideoCombo`,
name: "🔸 Preview images and videos in the list",
defaultValue: true,
type: "boolean",
onChange(value) {
PreviewImageVideoCombo = value;
},
});
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// --- Preview Text Node
@@ -698,172 +151,10 @@ app.registerExtension({
};
break;
}
case "PreviewImage": {
// Node Created
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const ret = onNodeCreated
? onNodeCreated.apply(this, arguments)
: undefined;
if (PreviewImageSize)
createPreiviewSize(this, nodeData.name, {
color: PreviewImageColorText,
});
return ret;
};
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function ({ images }) {
onExecuted?.apply(this, arguments);
if (PreviewImageSize !== undefined) {
let widgetRes = this?.widgets?.find(
(w) => w.type === "show_resolution"
);
if (!PreviewImageSize) {
if (widgetRes) {
for (const w of this.widgets) {
if (w.type === "show_resolution") {
w?.onRemove();
}
}
this.widgets.length = 0;
this.onResize(this.size);
}
} else {
if (!widgetRes) {
widgetRes = createPreiviewSize(this, nodeData.name, {
color: PreviewImageColorText,
});
this.onResize(this.size);
} else {
widgetRes.element.style.color = PreviewImageColorText;
}
if (images.length) {
const image = new Image();
image.onload = () => {
widgetRes.value = `<div style="border: solid 1px var(--border-color); border-radius: 4px; padding: 5px; ${
PreviewImageColorBg !== ""
? "background:" + PreviewImageColorBg
: ""
}">Width: ${image.naturalWidth}, Height: ${
image.naturalHeight
}</div>`;
};
image.src = api.apiURL(
"/view?" +
new URLSearchParams(images[0]).toString() +
app.getPreviewFormatParam() +
app.getRandParam()
);
}
}
}
};
break;
}
default: {
break;
}
}
// -- Speech & Recognition speech widget
// If ui settings is true and SpeechSynthesis or speechRecognition is not undefined
if (SpeechAndRecognationSpeech && (speechRect || SpeechSynthesis)) {
let nodeIsMultiString = false;
let outputNode = false;
if (nodeData?.input && nodeData?.input?.required) {
for (const inp of Object.keys(nodeData.input.required)) {
if (nodeData.input.required[inp][1]?.multiline) {
const type = nodeData.input.required[inp][0];
if (["STRING"].includes(type)) {
nodeIsMultiString = true;
break;
}
}
}
}
if (nodeData?.output) {
for (const out of nodeData.output) {
if (["STRING"].includes(out)) {
nodeIsMultiString = true;
outputNode = true;
break;
}
}
}
if (nodeIsMultiString) {
// Node Created
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = async function () {
const ret = onNodeCreated
? onNodeCreated.apply(this, arguments)
: undefined;
// Find all widget type customtext
const widgetsTextMulti = this?.widgets?.filter((w) =>
!outputNode
? ["customtext", "converted-widget"].includes(w.type)
: ["customtext"].includes(w.type)
);
const isIncludesSpeech = this?.widgets?.some(
(w) => w.type === "speak_and_recognation_type"
);
await new Promise((res) =>
setTimeout(() => {
res();
}, 16 * this.widgets?.length ?? 1)
);
if (!isIncludesSpeech && widgetsTextMulti?.length) {
widgetsTextMulti.forEach(async (w) => {
this.addCustomWidget(
SpeechWidget(this, "speak_and_recognation", [false, true], w)
);
});
}
return ret;
};
// onConfigure
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = async function (w) {
onConfigure?.apply(this, arguments);
if (w?.widgets_values?.length) {
await new Promise((res) =>
setTimeout(() => {
res();
}, 16 * this.widgets?.length ?? 1)
);
const ids_speech_clear = this.widgets.reduce(function (
arr,
el,
idx
) {
if (el.type === "speak_and_recognation_type") arr.push(idx);
return arr;
},
[]);
for (const id of ids_speech_clear)
this?.widgets[id]?.callback(w.widgets_values[id]);
}
};
}
}
// -- end - Speech & Recognition speech widget
},
});
+241
View File
@@ -0,0 +1,241 @@
/*
* Title: Extras extension - Preview Image Size
* Author: AlekPet
* Github: https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode
*/
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { $el } from "../../scripts/ui.js";
import { isValidStyle } from "./utils.js";
import { createPreiviewSize } from "./lib/extrasnode/extras_node_widgets.js";
const convertIdClass = (text) => text.replaceAll(".", "_");
const idExt = "alekpet.ExtrasNode";
// LocalStorage settings
const PreviewImageSizeLS = localStorage.getItem(
`Comfy.Settings.${idExt}.PreviewImage`
);
const PreviewImageColorTextLS = localStorage.getItem(
`Comfy.Settings.${idExt}.PreviewImageColorText`
);
const PreviewImageColorBgLS = localStorage.getItem(
`Comfy.Settings.${idExt}.PreviewImageColorBg`
);
// Settings set values from LS or default
let PreviewImageSize = PreviewImageSizeLS
? JSON.parse(PreviewImageSizeLS)
: false,
PreviewImageColorText =
PreviewImageColorTextLS && PreviewImageColorTextLS.trim() !== ""
? PreviewImageColorTextLS
: document.documentElement.style.getPropertyValue("--input-text") ||
"#dddddd",
PreviewImageColorBg = PreviewImageColorBgLS ? PreviewImageColorBgLS : "";
// -- Extension: PreviewImageSize --
app.registerExtension({
name: `${idExt}.PreviewImageSize`,
init() {
// PreviewImage settings ui
app.ui.settings.addSetting({
id: `${idExt}.PreviewImage`,
name: "🔸 Preview Image Size",
defaultValue: true,
type: (name, sett, val) => {
const newUI = document.querySelector(".p-dialog-header");
return $el("tr", [
!newUI
? $el("td", [
$el("label", {
textContent: name,
for: convertIdClass(`${idExt}.PreviewImage_size_checkbox`),
}),
])
: "",
$el("td", [
$el(
"label",
{
style: { display: "block" },
textContent: "Display image size: ",
for: convertIdClass(`${idExt}.PreviewImage_size_checkbox`),
},
[
$el("input", {
id: convertIdClass(`${idExt}.PreviewImage_size_checkbox`),
type: "checkbox",
checked: val,
onchange: (e) => {
const checked = !!e.target.checked;
PreviewImageSize = checked;
localStorage.setItem(
`Comfy.Settings.${idExt}.PreviewImage`,
checked
);
sett(checked);
},
}),
]
),
$el(
"label",
{
textContent: "Color text: ",
style: { display: "block" },
},
[
$el("input", {
id: convertIdClass(`${idExt}.PreviewImage_color_input`),
type: "color",
value: PreviewImageColorText,
onchange: (e) => {
const colorVal =
e.target.value ||
document.documentElement.style.getPropertyValue(
"--input-text"
) ||
"#dddddd";
PreviewImageColorText = colorVal;
localStorage.setItem(
`Comfy.Settings.${idExt}.PreviewImageColorText`,
PreviewImageColorText
);
},
}),
]
),
$el(
"label",
{
textContent: "Color background: ",
style: { display: "block" },
},
[
$el("input", {
id: convertIdClass(
`${idExt}.PreviewImage_color_background_input`
),
type: "color",
value: PreviewImageColorBg,
onchange: (e) => {
const colorVal = e.target.value || "";
PreviewImageColorBg = colorVal;
localStorage.setItem(
`Comfy.Settings.${idExt}.PreviewImageColorBg`,
PreviewImageColorBg
);
},
}),
]
),
$el("button", {
textContent: "Reset colors",
onclick: () => {
PreviewImageColorText = isValidStyle(
"color",
document.documentElement.style.getPropertyValue(
"--input-text"
) || "#ddd"
).color_hex;
PreviewImageColorBg = "";
document.querySelector(
"#alekpet_ExtrasNode_PreviewImage_color_input"
).value = PreviewImageColorText;
document.querySelector(
"#alekpet_ExtrasNode_PreviewImage_color_background_input"
).value = PreviewImageColorBg;
localStorage.setItem(
`Comfy.Settings.${idExt}.PreviewImageColorText`,
PreviewImageColorText
);
localStorage.setItem(
`Comfy.Settings.${idExt}.PreviewImageColorBg`,
PreviewImageColorBg
);
},
style: {
display: "block",
},
}),
]),
]);
},
});
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "PreviewImage") {
// Node Created
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const ret = onNodeCreated
? onNodeCreated.apply(this, arguments)
: undefined;
if (PreviewImageSize)
createPreiviewSize(this, nodeData.name, {
color: PreviewImageColorText,
});
return ret;
};
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function ({ images }) {
onExecuted?.apply(this, arguments);
if (PreviewImageSize !== undefined) {
let widgetRes = this?.widgets?.find(
(w) => w.type === "show_resolution"
);
if (!PreviewImageSize) {
if (widgetRes) {
for (const w of this.widgets) {
if (w.type === "show_resolution") {
w?.onRemove();
}
}
this.widgets.length = 0;
this.onResize(this.size);
}
} else {
if (!widgetRes) {
widgetRes = createPreiviewSize(this, nodeData.name, {
color: PreviewImageColorText,
});
this.onResize(this.size);
} else {
widgetRes.element.style.color = PreviewImageColorText;
}
if (images.length) {
const image = new Image();
image.onload = () => {
widgetRes.value = `<div style="border: solid 1px var(--border-color); border-radius: 4px; padding: 5px; ${
PreviewImageColorBg !== ""
? "background:" + PreviewImageColorBg
: ""
}">Width: ${image.naturalWidth}, Height: ${
image.naturalHeight
}</div>`;
};
image.src = api.apiURL(
"/view?" +
new URLSearchParams(images[0]).toString() +
app.getPreviewFormatParam() +
app.getRandParam()
);
}
}
}
};
}
},
});
+294
View File
@@ -0,0 +1,294 @@
/*
* Title: Extras extension - Preview Media (image, audio, video)
* Author: AlekPet
* Github: https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode
*/
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
import { $el } from "../../scripts/ui.js";
const idExt = "alekpet.ExtrasNode";
// LocalStorage settings - Preview image, video and audio select list combo
const PreviewImageVideoComboLS = localStorage.getItem(
`Comfy.Settings.${idExt}.PreviewImageVideoCombo`
);
// Settings set values from LS or default - Preview image, video and audio select list combo
let PreviewImageVideoCombo = PreviewImageVideoComboLS
? JSON.parse(PreviewImageVideoComboLS)
: true;
// Preview image, video and audio content loading function
const SUPPORTS_FORMAT = {
image: ["jpg", "jpeg", "bmp", "png", "gif", "tiff", "avif"],
video: ["mp4", "webm"],
audio: ["ogg", "wav", "mp3", "webm"],
};
const loadingContent = (src) => {
return new Promise((res, rej) => {
let ext = src.slice(src.lastIndexOf(".") + 1).toLowerCase();
ext = /\w+/.exec(ext)[0];
if (SUPPORTS_FORMAT.image.includes(ext)) {
const img = new Image();
img.crossOrigin = "";
img.onload = (e) => res({ raw: img, type: "image", src });
img.onerror = (err) => rej(err);
img.src = src;
}
if (SUPPORTS_FORMAT.video.includes(ext)) {
const video = document.querySelector(".preview_vid");
video.onerror = (err) => rej(err);
video.addEventListener("canplay", (e) => {
if (video.videoWidth !== 0) {
res({ raw: video, type: "video", src });
}
});
video.src = src;
}
if (SUPPORTS_FORMAT.audio.includes(ext)) {
const audio = document.querySelector(".preview_audio");
audio.onerror = (err) => rej(err);
audio.addEventListener("canplaythrough", (e) => {
res({ raw: audio, type: "audio", src });
});
audio.src = src;
}
});
};
function drawVid(raw, canvas, ctx, type) {
ctx.drawImage(raw, 0, 0, canvas.width, canvas.height);
canvas.requanim = requestAnimationFrame(
drawVid.bind(this, raw, canvas, ctx, type)
);
}
//
// -- Extension: Preview image, audio, video --
app.registerExtension({
name: `${idExt}.PreviewMedia`,
init() {
// Preview image, video and audio settings ui
app.ui.settings.addSetting({
id: `${idExt}.PreviewImageVideoCombo`,
name: "🔸 Preview images and videos in the list",
defaultValue: true,
type: "boolean",
onChange(value) {
PreviewImageVideoCombo = value;
},
});
// -- Preview image, audio, video --
// Close when click on item inside context menu
function closePreviewContextClick() {
if (this.root?.preview_content_combo) {
this.root.preview_content_combo.remove();
this.root.preview_content_combo = null;
}
}
// Add preview element
function addPreviewModule(params, element) {
if (!PreviewImageVideoCombo) return;
const [name, value, options] = params;
if (value instanceof Object) return;
if (!this.root?.preview_content_combo) {
const preview_content_combo = $el(
"div.preview_content_combo",
{
style: {
position: "absolute",
maxWidth: "160px",
zIndex: 9999999,
},
parent: document.body,
},
[
$el("canvas.preview_img", {
style: { width: "100%" },
}),
$el("video.preview_vid", {
style: { opacity: 0, width: 0, height: 0 },
crossOrigin: "anonymous",
autoplay: false,
muted: false,
preload: "auto",
}),
$el("audio.preview_audio", {
style: { opacity: 0, position: "absolute", left: 0 },
crossOrigin: "anonymous",
autoplay: false,
muted: false,
preload: "auto",
controls: true,
}),
]
);
this.root.preview_content_combo = preview_content_combo;
}
LiteGraph.pointerListenerAdd(element, "enter", (e) => {
if (element?.dataset?.value) {
const body_rect = document.body.getBoundingClientRect();
const root_rect = this.root.getBoundingClientRect();
const { x, y } = element.getBoundingClientRect();
//const preview_rect = this.root.preview_content_combo.getBoundingClientRect();
//const scale = app.graph.extra.ds.scale;
const canvas = this.root.preview_content_combo.children[0];
const ctx = canvas.getContext("2d");
loadingContent(
api.apiURL(
`/view?filename=${encodeURIComponent(
element.dataset.value
)}&type=input`
)
).then(({ raw, type, src }) => {
this.root.preview_content_combo.style.maxWidth =
type === "audio" ? "300px" : "160px";
const previewWidth = parseInt(
this.root.preview_content_combo.style.maxWidth
);
// if (scale >= 1) {
// this.root.preview_content_combo.style.top = `${
// (y - root_rect.top) / scale
// }px`;
// } else {
// this.root.preview_content_combo.style.top = `${
// y - root_rect.top
// }px`;
// }
this.root.preview_content_combo.style.top = `${y}px`;
if (
body_rect.width &&
root_rect.right + previewWidth > body_rect.width
) {
this.root.preview_content_combo.style.left = `${
root_rect.left - previewWidth - 10
}px`;
} else {
this.root.preview_content_combo.style.left = `${
root_rect.left + root_rect.width + 10
}px`;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
const video = document.querySelector(".preview_vid");
const audio = document.querySelector(".preview_audio");
if (type === "image") {
canvas.width = raw.naturalWidth;
canvas.height = raw.naturalHeight;
if (!video.paused) {
video.pause();
video.currentTime = 0;
}
cancelAnimationFrame(canvas.requanim);
} else if (type === "video") {
canvas.width = raw.videoWidth;
canvas.height = raw.videoHeight;
video.play();
} else if (type === "audio") {
Object.assign(audio.style, {
opacity: 1,
display: "",
});
audio.play();
}
if (type === "video" || type === "image") {
Object.assign(audio.style, {
opacity: 0,
display: "none",
});
!audio.paused && audio.pause();
ctx.drawImage(raw, 0, 0, canvas.width, canvas.height);
}
if (type === "video")
canvas.requanim = requestAnimationFrame(
drawVid.bind(this, raw, canvas, ctx, type)
);
});
}
});
LiteGraph.pointerListenerAdd(element, "leave", (e) => {
if (element?.dataset?.value) {
const canvas = this.root.preview_content_combo.children[0];
const video = document.querySelector(".preview_vid");
if (!video.paused) {
video.pause();
video.currentTime = 0;
}
cancelAnimationFrame(canvas.requanim);
}
});
}
// AddItem
const addItem = ContextMenu.prototype.addItem;
ContextMenu.prototype.addItem = function () {
const element = addItem?.apply(this, arguments);
addPreviewModule.call(this, arguments, element);
};
// CloseItem
const closeItem = ContextMenu.prototype.close;
ContextMenu.prototype.close = function () {
closeItem?.apply(this, arguments);
closePreviewContextClick.call(this);
};
// Old verison comfyui, support context menu LiteGraph path...
// AddItem
const addItemLitegraph = LiteGraph.ContextMenu.prototype.addItem;
LiteGraph.ContextMenu.prototype.addItem = function () {
const element = addItemLitegraph?.apply(this, arguments);
addPreviewModule.call(this, arguments, element);
};
// CloseItem
const closeItemLitegraph = LiteGraph.ContextMenu.prototype.close;
LiteGraph.ContextMenu.prototype.close = function () {
closeItemLitegraph?.apply(this, arguments);
closePreviewContextClick.call(this);
};
const originalCloseAllContextMenus = LiteGraph.closeAllContextMenus;
LiteGraph.closeAllContextMenus = function () {
originalCloseAllContextMenus?.apply(this, arguments);
const previewCombo = document.querySelector(".preview_content_combo");
if (previewCombo) {
previewCombo.remove();
}
};
// -- end - Preview image, audio, video --
},
});
@@ -0,0 +1,226 @@
/*
* Title: Extras extension - Speech And Recognition
* Author: AlekPet
* Github: https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode
*/
import { app } from "../../scripts/app.js";
import { $el } from "../../scripts/ui.js";
import { comfyuiDesktopConfirm } from "./utils.js";
import { addStylesheet } from "../../scripts/utils.js";
import {
SpeechWidget,
speechRect,
SpeechSynthesis,
checkboxLSCheckedByKey,
} from "./lib/extrasnode/extras_speech_and_recognation_widget.js";
import { RecognationSpeechDialog } from "./lib/extrasnode/extras_speech_and_recognation_dialog.js";
const convertIdClass = (text) => text.replaceAll(".", "_");
const idExt = "alekpet.ExtrasNode";
// Recognation & speech localStorage settings
const SpeechAndRecognationSpeechLS = localStorage.getItem(
`Comfy.Settings.${idExt}.SpeechAndRecognationSpeech`
);
// Settings set values from LS or default for Speech & Recognition widget settings
let SpeechAndRecognationSpeech = SpeechAndRecognationSpeechLS
? JSON.parse(SpeechAndRecognationSpeechLS)
: true,
SpeechAndRecognationSpeechSaveAs = JSON.parse(
localStorage.getItem(`${idExt}.SpeechAndRecognationSpeechSaveAs`),
false
);
// -- Extension: Speak text & Recognition speech --
app.registerExtension({
name: `${idExt}.SpeechAndRecognationSpeech`,
init() {
addStylesheet(
"css/extrasnode/extras_node_speech_recognition_style.css",
import.meta.url
);
// Speech & Recognition speech settings ui
app.ui.settings.addSetting({
id: `${idExt}.SpeechAndRecognationSpeech`,
name: "🔸 Speak text & Recognition speech",
defaultValue: true,
type: (name, sett, val) => {
const newUI = document.querySelector(".p-dialog-header");
return $el("tr", [
!newUI
? $el("td", [
$el("label", {
textContent: name,
for: convertIdClass(
`${idExt}.SpeechAndRecognationSpeech_show`
),
}),
])
: "",
$el("td", [
$el(
"label",
{
style: { display: "flex", alignItems: "center" },
textContent: "Enabled: ",
for: convertIdClass(`${idExt}.SpeechAndRecognationSpeech_show`),
},
[
$el("input", {
id: convertIdClass(
`${idExt}.SpeechAndRecognationSpeech_show`
),
type: "checkbox",
checked: val,
onchange: async (e) => {
const checked = !!e.target.checked;
SpeechAndRecognationSpeech = checked;
localStorage.setItem(
`Comfy.Settings.${idExt}.SpeechAndRecognationSpeech`,
checked
);
if (await comfyuiDesktopConfirm("Reload page?")) {
location.reload();
}
sett(checked);
},
}),
$el("span", {
textContent: "(Then reload the page)",
style: { fontSize: "0.6rem", color: "yellow" },
}),
]
),
$el(
"div",
{
style: {
display: "flex",
gap: "5px",
margin: "5px 0",
},
title: "Show modal window when saving recorded audio.",
},
[
$el("span", { textContent: "Output Save as?" }),
$el("input", {
type: "checkbox",
checked: SpeechAndRecognationSpeechSaveAs,
onchange: (e) => {
localStorage.setItem(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
!!e.target.checked
);
SpeechAndRecognationSpeechSaveAs = !!e.target.checked;
checkboxLSCheckedByKey(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
".alekpet_extras_node_recognition_saveAs"
);
},
}),
]
),
$el("button", {
textContent: "Speech settings",
onclick: () => {
new RecognationSpeechDialog().show();
},
style: {
display: "block",
},
}),
]),
]);
},
});
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
// -- Speech & Recognition speech widget
// If ui settings is true and SpeechSynthesis or speechRecognition is not undefined
if (SpeechAndRecognationSpeech && (speechRect || SpeechSynthesis)) {
// Node Created
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = async function () {
const ret = onNodeCreated
? onNodeCreated.apply(this, arguments)
: undefined;
let nodeIsMultiString = false;
if (nodeData?.input && nodeData?.input?.required) {
for (const inp of Object.keys(nodeData.input.required)) {
if (
nodeData.input.required[inp][1]?.multiline &&
!nodeData.input.required[inp][1]?.forceInput
) {
const type = nodeData.input.required[inp][0];
if (["STRING"].includes(type)) {
nodeIsMultiString = true;
break;
}
}
}
}
if (nodeData?.output) {
for (const out of nodeData.output) {
const isElementTextArea = this?.widgets?.some(
(w) =>
w?.element?.tagName === "TEXTAREA" ||
w?.inputEl?.tagName === "TEXTAREA"
);
if (isElementTextArea && ["STRING"].includes(out)) {
nodeIsMultiString = true;
break;
}
}
}
if (!nodeIsMultiString) return ret;
// Find all widget type customtext
const widgetsTextMulti = this?.widgets?.filter((w) =>
["customtext", "converted-widget"].includes(w.type)
);
const isIncludesSpeech = this?.widgets?.some(
(w) => w.type === "speak_and_recognation_type"
);
if (!isIncludesSpeech && widgetsTextMulti?.length) {
widgetsTextMulti.forEach(async (w) => {
this.addCustomWidget(
SpeechWidget(this, "speak_and_recognation", [false, true], w)
);
});
}
return ret;
};
// onConfigure
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = async function (w) {
onConfigure?.apply(this, arguments);
if (w?.widgets_values?.length) {
const ids_speech_clear = this.widgets.reduce(function (arr, el, idx) {
if (el.type === "speak_and_recognation_type") arr.push(idx);
return arr;
}, []);
if (!ids_speech_clear.length) return;
for (const id of ids_speech_clear)
this?.widgets[id]?.callback(w.widgets_values[id]);
}
};
}
// -- end - Speech & Recognition speech widget
},
});
+2 -685
View File
@@ -4,687 +4,12 @@
* Github: https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode
*/
import { api } from "../../../../scripts/api.js";
import { app } from "../../../../scripts/app.js";
import { $el } from "../../../../scripts/ui.js";
import {
rgbToHex,
isValidStyle,
createWindowModal,
animateClick,
comfyuiDesktopAlert,
} from "../../utils.js";
import { RecognationSpeechDialog } from "./extras_node_dialogs.js";
import { rgbToHex, isValidStyle } from "../../utils.js";
const idExt = "alekpet.ExtrasNode";
const CONVERTED_TYPE = "converted-widget";
/* ~~~ Speech & Recognition speech Widget ~~~ */
const spRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
const SpeechSynthesis = window.speechSynthesis;
const regExpFileName = /[^\p{L}\d]+/gu;
const maxLenAudioFileName = 50;
let speechRect;
let mediaRecorder = null;
let audioChunks = [];
if (spRecognition) {
speechRect = new spRecognition();
speechRect.elements = null;
speechRect.isRecognition = false;
speechRect.isRecognitionAbort = false;
speechRect.lastText = "";
speechRect.addEventListener("result", (event) => {
const results = event?.results[0][0]?.transcript;
if (results && speechRect?.elements?.length) {
const clear_text = speechRect.elements[0].querySelector(
".alekpet_extras_node_recognition_clear"
);
if (clear_text.checked) {
speechRect.elements[1].value = results;
} else {
speechRect.elements[1].value += " " + results;
}
}
});
const resetSpeechRecognition = () => {
if (speechRect?.elements?.length) {
const info = speechRect.elements[0].querySelector(
".alekpet_extras_node_info span"
);
const icon_rec = speechRect.elements[0].querySelector(
".alekpet_extras_node_recognition_icon"
);
speechRect.elements[0].querySelector(
".alekpet_extras_node_recognition_aborted"
).style.display = "none";
icon_rec.classList.remove("alekpet_extras_node_recognition_icon_active");
setTimeout(() => (info.textContent = ""), 1000);
}
setStylesAllElements(".alekpet_extras_node_recognition_icon", null, {
display: "inline-block",
});
if (mediaRecorder) {
speechRect.lastText = speechRect?.elements[1].value;
mediaRecorder.stop();
} else {
speechRect.lastText = "";
speechRect.isRecognitionAbort = false;
}
mediaRecorder = null;
audioChunks = [];
speechRect.isRecognition = false;
speechRect.elements = null;
};
speechRect.addEventListener("audiostart", (e) => {
speechRect.isRecognition = true;
});
speechRect.addEventListener("end", () => {
resetSpeechRecognition();
speechRect.stop();
});
speechRect.addEventListener("speechend", () => {
resetSpeechRecognition();
speechRect.stop();
});
speechRect.addEventListener("error", (event) => {
if (speechRect?.elements?.length) {
const info = speechRect.elements[0].querySelector(
".alekpet_extras_node_info span"
);
info.textContent = event.error;
}
resetSpeechRecognition();
console.log(">> Error recognition: " + event.error);
});
} else {
console.warn(
"Your browser does not support: SpeechRecognition.\nIf Firefox: go to about:config > media.webspeech.recognition.enable and media.webspeech.recognition.force_enable > set true"
);
}
if (!SpeechSynthesis) {
console.warn("Your browser does not support: speechSynthesis");
}
// Get position speech SpeechRecognition widget
function getPostition(ctx, w_width, y, n_height, wInput) {
const MARGIN = 10;
const rect = ctx.canvas.getBoundingClientRect();
const transform = new DOMMatrix()
.scaleSelf(rect.width / ctx.canvas.width, rect.height / ctx.canvas.height)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN);
const scale = new DOMMatrix().scaleSelf(transform.a, transform.d);
return {
transformOrigin: "0 0",
transform: scale,
transform: transform,
left: `${transform.a * w_width - 70 * scale.a + rect.left}px`,
top: `${(wInput.last_y - 15) * scale.d + scale.f + rect.top}px`,
maxWidth: `${w_width - MARGIN * 2}px`,
maxHeight: `${n_height - MARGIN * 2}px`,
zIndex: wInput?.inputEl?.style?.zIndex
? +wInput?.inputEl.style.zIndex + 1
: 20,
};
}
function getVoiceAndSettings() {
const voices = speechSynthesis.getVoices();
const { voice, volume, pitch, rate } =
RecognationSpeechDialog.getSettingsRecSpeechLS();
const voiceSelected = voices.filter((v) => v.name === voice);
return {
voice: voiceSelected.length ? voiceSelected[0] : null,
volume,
pitch,
rate,
};
}
// Function return utterance
function speakSynthesisUtterance(text, options = {}) {
const utterance = new SpeechSynthesisUtterance(text);
Object.assign(utterance, { ...options, ...getVoiceAndSettings() });
return utterance;
}
// Check premissions
async function checkPremissions(
device = { name: "microphone" },
update = null
) {
return navigator.permissions
.query(device)
.then((result) => {
const state = result.state;
if (state == "granted") {
return { device, state, status: true };
} else if (state == "prompt") {
return { device, state, status: false };
} else if (state == "denied") {
return { device, state, status: false };
}
result.onchange = update;
})
.catch((e) => ({ device, state: "error", status: false }));
}
// Check checkbox
function checkboxLSCheckedByKey(lsKey, selector, defaultVal = false) {
const currValue = JSON.parse(localStorage.getItem(lsKey, defaultVal));
Array.from(document.querySelectorAll(selector)).forEach(
(saveAs) => (saveAs.checked = currValue)
);
}
// Set styles
const setStylesAllElements = (selector, exclude = null, styles = {}) => {
let elements = Array.from(document.querySelectorAll(selector));
if (exclude) elements = elements.filter((r) => r !== exclude);
elements = elements.map((r) => Object.assign(r.style, styles));
};
function SpeechWidget(node, inputName, inputData, widgetsText) {
const widget = {
type: "speak_and_recognation_type",
name: inputName,
value: inputData,
size: [22, 1],
options: { hideOnZoom: true },
text_element: widgetsText?.inputEl,
draw(ctx, node, widget_width, y, widget_height) {
const hidden = widgetsText?.element?.hidden || widgetsText.hidden;
widget.element.style.display =
hidden || app.canvas.ds.scale < 0.6 ? "none" : "flex";
if (hidden) {
widget.options.onHide?.(widget);
}
if (hidden) {
return;
}
Object.assign(
widget.element.style,
getPostition(ctx, widget_width, y, node.size[1], widgetsText)
);
},
computeSize(...args) {
return [22, 1];
},
async callback(v) {
if (widgetsText?.element?.hasAttribute("readonly")) return;
widget.value = v ?? inputData ?? [false, true];
const checkboxSave = widget.element.querySelector(
".alekpet_extras_node_recognition_save"
);
const checkboxClear = widget.element.querySelector(
".alekpet_extras_node_recognition_clear"
);
if (checkboxClear) checkboxClear.checked = widget.value[1] ?? false;
if (checkboxSave) checkboxSave.checked = widget.value[0] ?? false;
},
onRemove() {
widget.element?.remove();
},
};
const buttons = [];
if (speechRect && !widgetsText?.element?.hasAttribute("readonly")) {
buttons.push(
$el("div.alekpet_extras_node_recognition_icon_box", [
$el("span.alekpet_extras_node_recognition_aborted", {
title: "Speech recognition abort",
textContent: "✖",
style: { display: "none" },
onclick: async function (e) {
const info = widget.element.querySelector(
".alekpet_extras_node_info span"
);
if (speechRect.isRecognition) {
e.currentTarget.style.display = "none";
speechRect.isRecognitionAbort = true;
speechRect.abort();
info.textContent = "aborted";
this.classList.remove(
"alekpet_extras_node_recognition_icon_active"
);
setTimeout(() => (info.textContent = ""), 2000);
}
},
}),
$el("span.alekpet_extras_node_recognition_icon", {
title: "Speech recognition",
onclick: async function (e) {
const info = widget.element.querySelector(
".alekpet_extras_node_info span"
);
const abort = widget.element.querySelector(
".alekpet_extras_node_recognition_aborted"
);
const checkboxSave = widget.element.querySelector(
".alekpet_extras_node_recognition_save"
);
// Hide other recognitions buttons
setStylesAllElements(
".alekpet_extras_node_recognition_icon",
e.currentTarget,
{
display: "none",
}
);
// Recognition
if (speechRect.elements === null) {
// Record audio
if (checkboxSave.checked) {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
});
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (event) => {
audioChunks.push(event.data);
};
mediaRecorder.onstop = async () => {
if (speechRect.isRecognitionAbort) {
speechRect.lastText = "";
speechRect.isRecognitionAbort = false;
return;
}
const saveAsWindow = JSON.parse(
localStorage.getItem(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
false
)
);
// Filename
let nameFile = "recording.webm";
if (speechRect?.lastText?.length) {
nameFile = `${speechRect.lastText
.slice(0, maxLenAudioFileName)
.replaceAll(regExpFileName, "_")}.webm`;
}
// Get audio
const audioBlob = new Blob(audioChunks, {
type: "audio/webm",
});
if (!saveAsWindow) {
const body = new FormData();
body.append("image", audioBlob, nameFile);
body.append("overwrite", "true");
const resp = await api.fetchApi("/upload/image", {
method: "POST",
body,
});
if (resp.status !== 200) {
console.error(
"[ExtrasNode] Recording audio not saved!"
);
return;
}
console.log(
`[ExtrasNode] Recording audio "${nameFile}" saved successfully!`
);
} else {
const audioUrl = URL.createObjectURL(audioBlob);
const linkDown = document.createElement("a");
linkDown.href = audioUrl;
linkDown.download = nameFile;
linkDown.click();
}
speechRect.lastText = "";
};
} catch (err) {
comfyuiDesktopAlert(
`Device "Microphone" - ${err.message}!\n\nCheck device or allow access!`
);
}
}
// end - Record audio
speechRect.elements = [widget.element, widgetsText.inputEl];
info.textContent = "recognition";
this.classList.add("alekpet_extras_node_recognition_icon_active");
speechRect.start();
mediaRecorder && mediaRecorder.start();
abort.style.display = "inline-block";
} else {
speechRect.stop();
abort.style.display = "none";
info.textContent = "stoped";
this.classList.remove(
"alekpet_extras_node_recognition_icon_active"
);
setTimeout(() => (info.textContent = ""), 2000);
}
},
}),
// Settings elements
$el("div.alekpet_extras_node_speech_recognition_settings", [
$el(
"div.alekpet_extras_node_speech_recognition_settings_button.pi.pi-cog",
{
title: "Settings",
onclick: (e) => {
checkboxLSCheckedByKey(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
".alekpet_extras_node_recognition_saveAs"
);
animateClick(e.currentTarget.nextElementSibling);
},
}
),
createWindowModal({
textTitle: null,
stylesClose: {
top: "-4px",
right: "4px",
width: "auto",
height: "auto",
padding: "2px",
fontSize: "0.4rem",
lineHeight: 1,
},
stylesBox: {
background: "transparent",
border: 0,
padding: 0,
boxShadow: "none",
},
stylesBody: {
display: "flex",
flexDirection: "column",
alignItems: "stretch",
gap: "3px",
textWrap: "wrap",
background: "rgb(131, 131, 131)",
color: "white",
padding: "2px",
marginTop: "2px",
borderRadius: "6px",
fontSize: "0.4rem",
minWidth: "85px",
},
stylesWrapper: {
minWidth: "100px",
transform: "translate(0%, 0%)",
},
textBody: [
$el(
"label.alekpet_extras_node_speech_recognition_settings_props_label",
[
$el(
"span.alekpet_extras_node_speech_recognition_settings_props_name",
{
textContent: "Recoding audio",
title: "Save in audio file after recognition",
}
),
$el(
"input.alekpet_extras_node_speech_recognition_settings_props_input.alekpet_extras_node_speech_recognition_checkbox.alekpet_extras_node_recognition_save",
{
type: "checkbox",
checked: widget.value[0] ?? false,
onchange: (e) =>
widget?.callback([!!e.target.checked, widget.value[1]]),
}
),
]
),
$el(
"label.alekpet_extras_node_speech_recognition_settings_props_label",
[
$el(
"span.alekpet_extras_node_speech_recognition_settings_props_name",
{
textContent: "Clear text",
title: "Clear text after recognition",
}
),
$el(
"input.alekpet_extras_node_speech_recognition_settings_props_input.alekpet_extras_node_speech_recognition_checkbox.alekpet_extras_node_recognition_clear",
{
type: "checkbox",
checked: widget.value[1] ?? true,
onchange: (e) =>
widget?.callback([widget.value[0], !!e.target.checked]),
}
),
]
),
$el("hr", { style: { padding: 0, margin: 0 } }),
$el(
"label.alekpet_extras_node_speech_recognition_settings_props_label",
[
$el(
"span.alekpet_extras_node_speech_recognition_settings_props_name",
{
textContent: "'Save as' window?",
title: "Show modal window when saving recorded audio.",
}
),
$el(
"input.alekpet_extras_node_speech_recognition_settings_props_input.alekpet_extras_node_speech_recognition_checkbox.alekpet_extras_node_recognition_saveAs",
{
type: "checkbox",
checked: JSON.parse(
localStorage.getItem(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
false
)
),
onchange: (e) => {
const check = !!e.target.checked;
const settCheck = document.body.querySelector(
"[id$='.SpeechAndRecognationSpeech'] div input"
);
settCheck && (settCheck.checked = check);
localStorage.setItem(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
check
);
checkboxLSCheckedByKey(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
".alekpet_extras_node_recognition_saveAs"
);
},
}
),
]
),
],
}),
]),
// end -- Settings elements
])
);
}
if (SpeechSynthesis && speechSynthesis) {
function buttonsStyles(
speechesButtons,
action = "add",
className = "alekpet_extras_node_speech_icon_playing"
) {
speechesButtons?.forEach((speechButton) =>
speechButton?.classList[action](className)
);
const settingTestSpeech = document.querySelector(
".panel_settings_recognation_speech_voice_test"
);
if (settingTestSpeech) {
if (action === "add") {
settingTestSpeech.style.opacity = 0.7;
settingTestSpeech.style.color = "var(--error-text)";
settingTestSpeech.title = "Cancel test speech";
settingTestSpeech.textContent = "Cancel test";
} else {
settingTestSpeech.style.opacity = 1;
settingTestSpeech.title = "Run test speech";
settingTestSpeech.textContent = "Test speech";
settingTestSpeech.style.color = "limegreen";
}
}
}
buttons.push(
$el("span.alekpet_extras_node_speech_icon", {
onclick: function () {
try {
const info = widget.element.querySelector(
".alekpet_extras_node_info span"
);
const speechesButtons = Array.from(
document.querySelectorAll(".alekpet_extras_node_speech_icon")
);
// Already playing
if (speechSynthesis.speaking) {
SpeechSynthesis.cancel();
info.textContent = "canceled";
this.title = "Speak text";
buttonsStyles(speechesButtons, "remove");
setTimeout(() => (info.textContent = ""), 2000);
return;
}
// Start playing text
const text = widgetsText?.element?.value;
if (text.trim() !== "") {
const utterance = speakSynthesisUtterance(text, {
onend: (e) => {
this.title = "Speak text";
info.textContent = "";
buttonsStyles(speechesButtons, "remove");
},
});
if (utterance) {
this.title = "Cancel speech";
info.textContent = "saying now";
buttonsStyles(speechesButtons);
SpeechSynthesis.speak(utterance);
}
} else {
const utterance = speakSynthesisUtterance(
`${widgetsText?.name || "This"}}, field is empty!`,
{
onend: (e) => {
this.title = "Speak text";
info.textContent = "";
buttonsStyles(speechesButtons, "remove");
},
}
);
if (utterance) {
this.title = "Cancel speech";
info.textContent = "empty text!";
buttonsStyles(speechesButtons);
SpeechSynthesis.speak(utterance);
}
}
} catch (err) {
console.log(err);
}
},
textContent: "🔊",
title: "Speak text",
})
);
}
widget.element = $el(
"div.alekpet_extras_node_speechrecognition_box",
{ style: { display: "none" } },
[
$el("div.alekpet_extras_node_speechrecognition_row", [...buttons]),
$el("div.alekpet_extras_node_info", [
$el("span", {
textContent: "",
style: { fontSize: "0.4em" },
}),
]),
]
);
const collapse = node.collapse;
node.collapse = function () {
collapse.apply(this, arguments);
if (this.flags?.collapsed) {
widget.element.hidden = true;
widget.element.style.display = "none";
}
widget.element.dataset.collapsed = this.flags?.collapsed ? "true" : "false";
};
const onRemovedOrig = node.onRemoved;
node.onRemoved = function () {
node?.widgets?.forEach((w) => {
if (w.type === "speak_and_recognation_type") {
w?.onRemove();
}
});
onRemovedOrig?.apply(node, arguments);
};
document.body.appendChild(widget.element);
return widget;
}
/* ~~~ end - Speech & Recognition speech Widget ~~~ */
/* ~~~ Color Widget ~~~ */
function makeColorWidget(node, inputName, inputData, widget) {
const color_hex = $el("input", {
@@ -781,12 +106,4 @@ function createPreiviewSize(node, name, options) {
}
/* ~~~ end - Preview Image Size Widget ~~~ */
export {
SpeechWidget,
makeColorWidget,
createPreiviewSize,
speechRect,
SpeechSynthesis,
speakSynthesisUtterance,
checkboxLSCheckedByKey,
};
export { makeColorWidget, createPreiviewSize };
@@ -1,6 +1,12 @@
/*
* Title: Extras extension - Speech And Recognition Dialog
* Author: AlekPet
* Github: https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode
*/
import { ComfyDialog } from "../../../../../scripts/ui.js";
import { makeElement } from "../../utils.js";
import { speakSynthesisUtterance } from "./extras_node_widgets.js";
import { speakSynthesisUtterance } from "./extras_speech_and_recognation_widget.js";
const idExt = "alekpet.ExtrasNode";
@@ -34,7 +40,7 @@ export class RecognationSpeechDialog extends ComfyDialog {
class: ["panel_settings_recognation_speech"],
style: {
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr",
gridTemplateColumns: "0.2fr 1fr 0.2fr",
alignItems: "center",
justifyContent: "center",
gap: "5px",
@@ -198,8 +204,9 @@ export class RecognationSpeechDialog extends ComfyDialog {
const box = makeElement("div", {
class: ["box_recognation_speech"],
style: {
width: "25vw",
width: "80vw",
color: "var(--input-text)",
padding: "20px",
},
children: [await this.createPanelSettings()],
});
@@ -0,0 +1,691 @@
/*
* Title: Extras Speech And Recognation Widget
* Author: AlekPet
* Github: https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode
*/
import { api } from "../../../../scripts/api.js";
import { app } from "../../../../scripts/app.js";
import { $el } from "../../../../scripts/ui.js";
import {
createWindowModal,
animateClick,
comfyuiDesktopAlert,
} from "../../utils.js";
import { RecognationSpeechDialog } from "./extras_speech_and_recognation_dialog.js";
const idExt = "alekpet.ExtrasNode";
/* ~~~ Speech & Recognition speech Widget ~~~ */
const spRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
const SpeechSynthesis = window.speechSynthesis;
const regExpFileName = /[^\p{L}\d]+/gu;
const maxLenAudioFileName = 50;
let speechRect;
let mediaRecorder = null;
let audioChunks = [];
if (spRecognition) {
speechRect = new spRecognition();
speechRect.elements = null;
speechRect.isRecognition = false;
speechRect.isRecognitionAbort = false;
speechRect.lastText = "";
speechRect.addEventListener("result", (event) => {
const results = event?.results[0][0]?.transcript;
if (results && speechRect?.elements?.length) {
const clear_text = speechRect.elements[0].querySelector(
".alekpet_extras_node_recognition_clear"
);
if (clear_text.checked) {
speechRect.elements[1].value = results;
} else {
speechRect.elements[1].value += " " + results;
}
}
});
const resetSpeechRecognition = () => {
if (speechRect?.elements?.length) {
const info = speechRect.elements[0].querySelector(
".alekpet_extras_node_info span"
);
const icon_rec = speechRect.elements[0].querySelector(
".alekpet_extras_node_recognition_icon"
);
speechRect.elements[0].querySelector(
".alekpet_extras_node_recognition_aborted"
).style.display = "none";
icon_rec.classList.remove("alekpet_extras_node_recognition_icon_active");
setTimeout(() => (info.textContent = ""), 1000);
}
setStylesAllElements(".alekpet_extras_node_recognition_icon", null, {
display: "inline-block",
});
if (mediaRecorder) {
speechRect.lastText = speechRect?.elements[1].value;
mediaRecorder.stop();
} else {
speechRect.lastText = "";
speechRect.isRecognitionAbort = false;
}
mediaRecorder = null;
audioChunks = [];
speechRect.isRecognition = false;
speechRect.elements = null;
};
speechRect.addEventListener("audiostart", (e) => {
speechRect.isRecognition = true;
});
speechRect.addEventListener("end", () => {
resetSpeechRecognition();
speechRect.stop();
});
speechRect.addEventListener("speechend", () => {
resetSpeechRecognition();
speechRect.stop();
});
speechRect.addEventListener("error", (event) => {
if (speechRect?.elements?.length) {
const info = speechRect.elements[0].querySelector(
".alekpet_extras_node_info span"
);
info.textContent = event.error;
}
resetSpeechRecognition();
console.log(">> Error recognition: " + event.error);
});
} else {
console.warn(
"Your browser does not support: SpeechRecognition.\nIf Firefox: go to about:config > media.webspeech.recognition.enable and media.webspeech.recognition.force_enable > set true"
);
}
if (!SpeechSynthesis) {
console.warn("Your browser does not support: speechSynthesis");
}
// Get position speech SpeechRecognition widget
function getPostition(ctx, w_width, y, n_height, wInput) {
const MARGIN = 10;
const rect = ctx.canvas.getBoundingClientRect();
const transform = new DOMMatrix()
.scaleSelf(rect.width / ctx.canvas.width, rect.height / ctx.canvas.height)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN);
const scale = new DOMMatrix().scaleSelf(transform.a, transform.d);
return {
transformOrigin: "0 0",
transform: scale,
transform: transform,
left: `${transform.a * w_width - 70 * scale.a + rect.left}px`,
top: `${(wInput.last_y - 15) * scale.d + scale.f + rect.top}px`,
maxWidth: `${w_width - MARGIN * 2}px`,
maxHeight: `${n_height - MARGIN * 2}px`,
zIndex: wInput?.inputEl?.style?.zIndex
? +wInput?.inputEl.style.zIndex + 1
: 20,
};
}
function getVoiceAndSettings() {
const voices = speechSynthesis.getVoices();
const { voice, volume, pitch, rate } =
RecognationSpeechDialog.getSettingsRecSpeechLS();
const voiceSelected = voices.filter((v) => v.name === voice);
return {
voice: voiceSelected.length ? voiceSelected[0] : null,
volume,
pitch,
rate,
};
}
// Function return utterance
function speakSynthesisUtterance(text, options = {}) {
const utterance = new SpeechSynthesisUtterance(text);
Object.assign(utterance, { ...options, ...getVoiceAndSettings() });
return utterance;
}
// Check premissions
async function checkPremissions(
device = { name: "microphone" },
update = null
) {
return navigator.permissions
.query(device)
.then((result) => {
const state = result.state;
if (state == "granted") {
return { device, state, status: true };
} else if (state == "prompt") {
return { device, state, status: false };
} else if (state == "denied") {
return { device, state, status: false };
}
result.onchange = update;
})
.catch((e) => ({ device, state: "error", status: false }));
}
// Check checkbox
function checkboxLSCheckedByKey(lsKey, selector, defaultVal = false) {
const currValue = JSON.parse(localStorage.getItem(lsKey, defaultVal));
Array.from(document.querySelectorAll(selector)).forEach(
(saveAs) => (saveAs.checked = currValue)
);
}
// Set styles
const setStylesAllElements = (selector, exclude = null, styles = {}) => {
let elements = Array.from(document.querySelectorAll(selector));
if (exclude) elements = elements.filter((r) => r !== exclude);
elements = elements.map((r) => Object.assign(r.style, styles));
};
function SpeechWidget(node, inputName, inputData, widgetsText) {
const widget = {
type: "speak_and_recognation_type",
name: inputName,
value: inputData,
size: [22, 1],
options: { hideOnZoom: true },
text_element: widgetsText?.inputEl,
draw(ctx, node, widget_width, y, widget_height) {
const hidden = widgetsText?.element?.hidden || widgetsText.hidden;
widget.element.style.display =
hidden || app.canvas.ds.scale < 0.6 ? "none" : "flex";
if (hidden) {
widget.options.onHide?.(widget);
}
if (hidden) {
return;
}
Object.assign(
widget.element.style,
getPostition(ctx, widget_width, y, node.size[1], widgetsText)
);
},
computeSize(...args) {
return [22, 1];
},
async callback(v) {
if (widgetsText?.element?.hasAttribute("readonly")) return;
widget.value = v ?? inputData ?? [false, true];
const checkboxSave = widget.element.querySelector(
".alekpet_extras_node_recognition_save"
);
const checkboxClear = widget.element.querySelector(
".alekpet_extras_node_recognition_clear"
);
if (checkboxClear) checkboxClear.checked = widget.value[1] ?? false;
if (checkboxSave) checkboxSave.checked = widget.value[0] ?? false;
},
onRemove() {
widget.element?.remove();
},
};
const buttons = [];
if (speechRect && !widgetsText?.element?.hasAttribute("readonly")) {
buttons.push(
$el("div.alekpet_extras_node_recognition_icon_box", [
$el("span.alekpet_extras_node_recognition_aborted", {
title: "Speech recognition abort",
textContent: "✖",
style: { display: "none" },
onclick: async function (e) {
const info = widget.element.querySelector(
".alekpet_extras_node_info span"
);
if (speechRect.isRecognition) {
e.currentTarget.style.display = "none";
speechRect.isRecognitionAbort = true;
speechRect.abort();
info.textContent = "aborted";
this.classList.remove(
"alekpet_extras_node_recognition_icon_active"
);
setTimeout(() => (info.textContent = ""), 2000);
}
},
}),
$el("span.alekpet_extras_node_recognition_icon", {
title: "Speech recognition",
onclick: async function (e) {
const info = widget.element.querySelector(
".alekpet_extras_node_info span"
);
const abort = widget.element.querySelector(
".alekpet_extras_node_recognition_aborted"
);
const checkboxSave = widget.element.querySelector(
".alekpet_extras_node_recognition_save"
);
// Hide other recognitions buttons
setStylesAllElements(
".alekpet_extras_node_recognition_icon",
e.currentTarget,
{
display: "none",
}
);
// Recognition
if (speechRect.elements === null) {
// Record audio
if (checkboxSave.checked) {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
});
mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = (event) => {
audioChunks.push(event.data);
};
mediaRecorder.onstop = async () => {
if (speechRect.isRecognitionAbort) {
speechRect.lastText = "";
speechRect.isRecognitionAbort = false;
return;
}
const saveAsWindow = JSON.parse(
localStorage.getItem(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
false
)
);
// Filename
let nameFile = "recording.webm";
if (speechRect?.lastText?.length) {
nameFile = `${speechRect.lastText
.slice(0, maxLenAudioFileName)
.replaceAll(regExpFileName, "_")}.webm`;
}
// Get audio
const audioBlob = new Blob(audioChunks, {
type: "audio/webm",
});
if (!saveAsWindow) {
const body = new FormData();
body.append("image", audioBlob, nameFile);
body.append("overwrite", "true");
const resp = await api.fetchApi("/upload/image", {
method: "POST",
body,
});
if (resp.status !== 200) {
console.error(
"[ExtrasNode] Recording audio not saved!"
);
return;
}
console.log(
`[ExtrasNode] Recording audio "${nameFile}" saved successfully!`
);
} else {
const audioUrl = URL.createObjectURL(audioBlob);
const linkDown = document.createElement("a");
linkDown.href = audioUrl;
linkDown.download = nameFile;
linkDown.click();
}
speechRect.lastText = "";
};
} catch (err) {
comfyuiDesktopAlert(
`Device "Microphone" - ${err.message}!\n\nCheck device or allow access!`
);
}
}
// end - Record audio
speechRect.elements = [widget.element, widgetsText.inputEl];
info.textContent = "recognition";
this.classList.add("alekpet_extras_node_recognition_icon_active");
speechRect.start();
mediaRecorder && mediaRecorder.start();
abort.style.display = "inline-block";
} else {
speechRect.stop();
abort.style.display = "none";
info.textContent = "stoped";
this.classList.remove(
"alekpet_extras_node_recognition_icon_active"
);
setTimeout(() => (info.textContent = ""), 2000);
}
},
}),
// Settings elements
$el("div.alekpet_extras_node_speech_recognition_settings", [
$el(
"div.alekpet_extras_node_speech_recognition_settings_button.pi.pi-cog",
{
title: "Settings",
onclick: (e) => {
checkboxLSCheckedByKey(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
".alekpet_extras_node_recognition_saveAs"
);
animateClick(e.currentTarget.nextElementSibling);
},
}
),
createWindowModal({
textTitle: null,
stylesClose: {
top: "-4px",
right: "4px",
width: "auto",
height: "auto",
padding: "2px",
fontSize: "0.4rem",
lineHeight: 1,
},
stylesBox: {
background: "transparent",
border: 0,
padding: 0,
boxShadow: "none",
},
stylesBody: {
display: "flex",
flexDirection: "column",
alignItems: "stretch",
gap: "3px",
textWrap: "wrap",
background: "rgb(131, 131, 131)",
color: "white",
padding: "2px",
marginTop: "2px",
borderRadius: "6px",
fontSize: "0.4rem",
minWidth: "85px",
},
stylesWrapper: {
minWidth: "100px",
transform: "translate(0%, 0%)",
},
textBody: [
$el(
"label.alekpet_extras_node_speech_recognition_settings_props_label",
[
$el(
"span.alekpet_extras_node_speech_recognition_settings_props_name",
{
textContent: "Recoding audio",
title: "Save in audio file after recognition",
}
),
$el(
"input.alekpet_extras_node_speech_recognition_settings_props_input.alekpet_extras_node_speech_recognition_checkbox.alekpet_extras_node_recognition_save",
{
type: "checkbox",
checked: widget.value[0] ?? false,
onchange: (e) =>
widget?.callback([!!e.target.checked, widget.value[1]]),
}
),
]
),
$el(
"label.alekpet_extras_node_speech_recognition_settings_props_label",
[
$el(
"span.alekpet_extras_node_speech_recognition_settings_props_name",
{
textContent: "Clear text",
title: "Clear text after recognition",
}
),
$el(
"input.alekpet_extras_node_speech_recognition_settings_props_input.alekpet_extras_node_speech_recognition_checkbox.alekpet_extras_node_recognition_clear",
{
type: "checkbox",
checked: widget.value[1] ?? true,
onchange: (e) =>
widget?.callback([widget.value[0], !!e.target.checked]),
}
),
]
),
$el("hr", { style: { padding: 0, margin: 0 } }),
$el(
"label.alekpet_extras_node_speech_recognition_settings_props_label",
[
$el(
"span.alekpet_extras_node_speech_recognition_settings_props_name",
{
textContent: "'Save as' window?",
title: "Show modal window when saving recorded audio.",
}
),
$el(
"input.alekpet_extras_node_speech_recognition_settings_props_input.alekpet_extras_node_speech_recognition_checkbox.alekpet_extras_node_recognition_saveAs",
{
type: "checkbox",
checked: JSON.parse(
localStorage.getItem(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
false
)
),
onchange: (e) => {
const check = !!e.target.checked;
const settCheck = document.body.querySelector(
"[id$='.SpeechAndRecognationSpeech'] div input"
);
settCheck && (settCheck.checked = check);
localStorage.setItem(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
check
);
checkboxLSCheckedByKey(
`${idExt}.SpeechAndRecognationSpeechSaveAs`,
".alekpet_extras_node_recognition_saveAs"
);
},
}
),
]
),
],
}),
]),
// end -- Settings elements
])
);
}
if (SpeechSynthesis && speechSynthesis) {
function buttonsStyles(
speechesButtons,
action = "add",
className = "alekpet_extras_node_speech_icon_playing"
) {
speechesButtons?.forEach((speechButton) =>
speechButton?.classList[action](className)
);
const settingTestSpeech = document.querySelector(
".panel_settings_recognation_speech_voice_test"
);
if (settingTestSpeech) {
if (action === "add") {
settingTestSpeech.style.opacity = 0.7;
settingTestSpeech.style.color = "var(--error-text)";
settingTestSpeech.title = "Cancel test speech";
settingTestSpeech.textContent = "Cancel test";
} else {
settingTestSpeech.style.opacity = 1;
settingTestSpeech.title = "Run test speech";
settingTestSpeech.textContent = "Test speech";
settingTestSpeech.style.color = "limegreen";
}
}
}
buttons.push(
$el("span.alekpet_extras_node_speech_icon", {
onclick: function () {
try {
const info = widget.element.querySelector(
".alekpet_extras_node_info span"
);
const speechesButtons = Array.from(
document.querySelectorAll(".alekpet_extras_node_speech_icon")
);
// Already playing
if (speechSynthesis.speaking) {
SpeechSynthesis.cancel();
info.textContent = "canceled";
this.title = "Speak text";
buttonsStyles(speechesButtons, "remove");
setTimeout(() => (info.textContent = ""), 2000);
return;
}
// Start playing text
const text = widgetsText?.element?.value;
if (text.trim() !== "") {
const utterance = speakSynthesisUtterance(text, {
onend: (e) => {
this.title = "Speak text";
info.textContent = "";
buttonsStyles(speechesButtons, "remove");
},
});
if (utterance) {
this.title = "Cancel speech";
info.textContent = "saying now";
buttonsStyles(speechesButtons);
SpeechSynthesis.speak(utterance);
}
} else {
const utterance = speakSynthesisUtterance(
`${widgetsText?.name || "This"}}, field is empty!`,
{
onend: (e) => {
this.title = "Speak text";
info.textContent = "";
buttonsStyles(speechesButtons, "remove");
},
}
);
if (utterance) {
this.title = "Cancel speech";
info.textContent = "empty text!";
buttonsStyles(speechesButtons);
SpeechSynthesis.speak(utterance);
}
}
} catch (err) {
console.log(err);
}
},
textContent: "🔊",
title: "Speak text",
})
);
}
widget.element = $el(
"div.alekpet_extras_node_speechrecognition_box",
{ style: { display: "none" } },
[
$el("div.alekpet_extras_node_speechrecognition_row", [...buttons]),
$el("div.alekpet_extras_node_info", [
$el("span", {
textContent: "",
style: { fontSize: "0.4em" },
}),
]),
]
);
const collapse = node.collapse;
node.collapse = function () {
collapse.apply(this, arguments);
if (this.flags?.collapsed) {
widget.element.hidden = true;
widget.element.style.display = "none";
}
widget.element.dataset.collapsed = this.flags?.collapsed ? "true" : "false";
};
const onRemovedOrig = node.onRemoved;
node.onRemoved = function () {
node?.widgets?.forEach((w) => {
if (w.type === "speak_and_recognation_type") {
w?.onRemove();
}
});
onRemovedOrig?.apply(node, arguments);
};
document.body.appendChild(widget.element);
return widget;
}
/* ~~~ end - Speech & Recognition speech Widget ~~~ */
export {
SpeechWidget,
speechRect,
SpeechSynthesis,
speakSynthesisUtterance,
checkboxLSCheckedByKey,
};
+18 -20
View File
@@ -8,26 +8,24 @@ If you enjoy my work, consider **[supporting me](https://alekpet.github.io/suppo
# List Nodes:
| Name | Description | ComfyUI category |
| :------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------: | :-----------------------: |
| [**PoseNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/PoseNode) | The node set pose ControlNet | AlekPet Node/image |
| [**PainterNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/PainterNode) | The node set sketch, scrumble image ControlNet and other nodes | AlekPet Node/image |
| [**GoogleTranslateTextNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/GoogleTranslateNode) | The node translate promt uses module **googletrans** from other languages into english and return string | AlekPet Node/conditioning |
| [**GoogleTranslateCLIPTextEncodeNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/GoogleTranslateNode) | The node translate promt uses module **googletrans** from other languages into english, and return conditioning | AlekPet Node/text |
| [**DeepTranslatorTextNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/DeepTranslatorNode) | The node translate promt uses module **Deep Translator** from other languages into english and return string | AlekPet Node/text |
| [**DeepTranslatorCLIPTextEncodeNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/DeepTranslatorNode) | The node translate promt uses module **Deep Translator** from other languages into english, and return conditioning | AlekPet Node/conditioning |
| [**DeepLXTranslateTextNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/DeepLXTranslateNode) | The node translate promt uses **DeepLX** server written for Golang(Go) from other languages into english and return string | AlekPet Node/text |
| [**DeepLXTranslateCLIPTextEncodeNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/DeepLXTranslateNode) | The node translate promt uses **DeepLX** server written for Golang(Go) from other languages into english, and return conditioning | AlekPet Node/conditioning |
| [**ArgosTranslateTextNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ArgosTranslateNode) | The node translate promt uses module **Argos Translator** from other languages into english and return string | AlekPet Node/text |
| [**ArgosTranslateCLIPTextEncodeNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ArgosTranslateNode) | The node translate promt uses module **Argos Translator** from other languages into english, and return conditioning | AlekPet Node/conditioning |
| [**ChatGLM4TranslateTextNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ChatGLMNode) | This translator node uses artificial intelligence to translate prompts and return string | AlekPet Node/text |
| [**ChatGLM4TranslateCLIPTextEncodeNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ChatGLMNode) | This translator node uses artificial intelligence to translate prompts and return conditioning | AlekPet Node/conditioning |
| [**ChatGLM4InstructNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ChatGLMNode) | This node uses artificial intelligence to generate prompt | AlekPet Node/Instruct |
| [**ChatGLM4InstructMediaNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ChatGLMNode) | This node uses artificial intelligence to describe what is shown in the media | AlekPet Node/Instruct |
| [**PreviewTextNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode) | The node displays the input text | AlekPet Node/extras |
| [**ColorsCorrectNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode) | The node for correcting image colors | AlekPet Node/extras |
| [**HexToHueNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode) | The node convert HEX color to HUE (degrees and normal [-0.5, 0.5]) | AlekPet Node/extras |
| [**IDENode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/IDENode) | The node that allows you to run code written in **Python** or **Javascript** directly in the node | AlekPet Node/experiments |
| Name | Description | ComfyUI category |
| :------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------: | :-----------------------: |
| [**PoseNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/PoseNode) | The node set pose ControlNet | AlekPet Node/image |
| [**PainterNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/PainterNode) | The node set sketch, scrumble image ControlNet and other nodes | AlekPet Node/image |
| [**GoogleTranslateTextNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/GoogleTranslateNode) | The node translate promt uses module **googletrans** from other languages into english and return string | AlekPet Node/conditioning |
| [**GoogleTranslateCLIPTextEncodeNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/GoogleTranslateNode) | The node translate promt uses module **googletrans** from other languages into english, and return conditioning | AlekPet Node/text |
| [**DeepTranslatorTextNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/DeepTranslatorNode) | The node translate promt uses module **Deep Translator** from other languages into english and return string | AlekPet Node/text |
| [**DeepTranslatorCLIPTextEncodeNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/DeepTranslatorNode) | The node translate promt uses module **Deep Translator** from other languages into english, and return conditioning | AlekPet Node/conditioning |
| [**ArgosTranslateTextNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ArgosTranslateNode) | The node translate promt uses module **Argos Translator** from other languages into english and return string | AlekPet Node/text |
| [**ArgosTranslateCLIPTextEncodeNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ArgosTranslateNode) | The node translate promt uses module **Argos Translator** from other languages into english, and return conditioning | AlekPet Node/conditioning |
| [**ChatGLM4TranslateTextNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ChatGLMNode) | This translator node uses artificial intelligence to translate prompts and return string | AlekPet Node/text |
| [**ChatGLM4TranslateCLIPTextEncodeNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ChatGLMNode) | This translator node uses artificial intelligence to translate prompts and return conditioning | AlekPet Node/conditioning |
| [**ChatGLM4InstructNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ChatGLMNode) | This node uses artificial intelligence to generate prompt | AlekPet Node/Instruct |
| [**ChatGLM4InstructMediaNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ChatGLMNode) | This node uses artificial intelligence to describe what is shown in the media | AlekPet Node/Instruct |
| [**PreviewTextNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode) | The node displays the input text | AlekPet Node/extras |
| [**ColorsCorrectNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode) | The node for correcting image colors | AlekPet Node/extras |
| [**HexToHueNode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/ExtrasNode) | The node convert HEX color to HUE (degrees and normal [-0.5, 0.5]) | AlekPet Node/extras |
| [**IDENode**](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/tree/master/IDENode) | The node that allows you to run code written in **Python** or **Javascript** directly in the node | AlekPet Node/experiments |
# Installing
+140 -110
View File
@@ -1,6 +1,6 @@
# Title: ComfyUI Install Customs Nodes and javascript files
# Author: AlekPet
# Version: 2024.08.08
# Version: 2025.06.26
import os
import importlib.util
import subprocess
@@ -32,7 +32,8 @@ extension_dirs = [
DEBUG = False
NODE_CLASS_MAPPINGS = dict() # dynamic class nodes append in mappings
NODE_DISPLAY_NAME_MAPPINGS = dict() # dynamic display names nodes append mappings names
# dynamic display names nodes append mappings names
NODE_DISPLAY_NAME_MAPPINGS = dict()
humanReadableTextReg = re.compile("(?<=[a-z0-9])([A-Z])|(?<=[A-Z0-9])([A-Z][a-z]+)")
module_name_cut_version = re.compile("[>=<]")
@@ -47,9 +48,7 @@ def get_version_extension():
if os.path.isfile(toml_file):
try:
with open(toml_file, "r") as v:
version = list(
filter(lambda l: l.startswith("version"), v.readlines())
)[0]
version = list(filter(lambda l: l.startswith("version"), v.readlines()))[0]
version = version.split("=")[1].replace('"', "").strip()
return f" \033[1;34mv{version}\033[0m\033[1;35m"
except Exception as e:
@@ -69,7 +68,7 @@ def information(datas):
try:
print(info, end="\r", flush=True)
except UnicodeError:
safe_text = info.encode('utf-8', errors='replace').decode('utf-8', errors='replace')
safe_text = info.encode("utf-8", errors="replace").decode("utf-8", errors="replace")
print(safe_text, end="\r", flush=True)
@@ -80,11 +79,7 @@ def printColorInfo(text, color="\033[92m"):
def get_classes(code):
tree = ast.parse(code)
return [
n.name
for n in ast.walk(tree)
if isinstance(n, ast.ClassDef) and "Node" in n.name
]
return [n.name for n in ast.walk(tree) if isinstance(n, ast.ClassDef) and "Node" in n.name]
def addComfyUINodesToMapping(nodeElement):
@@ -102,9 +97,7 @@ def addComfyUINodesToMapping(nodeElement):
# remove extensions .py
module_without_py = f.replace(ext[1], "")
# Import module
spec = importlib.util.spec_from_file_location(
module_without_py, os.path.join(node_folder, f)
)
spec = importlib.util.spec_from_file_location(module_without_py, os.path.join(node_folder, f))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
classes_names = list(
@@ -115,22 +108,11 @@ def addComfyUINodesToMapping(nodeElement):
)
for class_module_name in classes_names:
# Check module
if (
class_module_name
and class_module_name not in NODE_CLASS_MAPPINGS.keys()
):
log(
f" [*] Class node found '{class_module_name}' add to NODE_CLASS_MAPPINGS..."
)
NODE_CLASS_MAPPINGS.update(
{class_module_name: getattr(module, class_module_name)}
)
if class_module_name and class_module_name not in NODE_CLASS_MAPPINGS.keys():
log(f" [*] Class node found '{class_module_name}' add to NODE_CLASS_MAPPINGS...")
NODE_CLASS_MAPPINGS.update({class_module_name: getattr(module, class_module_name)})
NODE_DISPLAY_NAME_MAPPINGS.update(
{
class_module_name: humanReadableTextReg.sub(
" \\1\\2", class_module_name
)
}
{class_module_name: humanReadableTextReg.sub(" \\1\\2", class_module_name)}
)
@@ -168,8 +150,8 @@ def module_install(commands, cwd="."):
stderr=subprocess.PIPE,
text=True,
bufsize=1,
encoding='utf-8',
errors='replace'
encoding="utf-8",
errors="replace",
)
out = threading.Thread(target=information, args=(result.stdout,))
err = threading.Thread(target=information, args=(result.stderr,))
@@ -197,17 +179,13 @@ def checkModules(nodeElement):
log(" -> File 'requirements.txt' found!")
with open(file_requir) as f:
required_modules = {
module_name_cut_version.split(line.strip())[0]
for line in f
if not line.startswith("#")
module_name_cut_version.split(line.strip())[0] for line in f if not line.startswith("#")
}
modules_to_install = required_modules - installed_modules
if modules_to_install:
module_install(
[sys.executable, "-m", "pip", "install", *modules_to_install]
)
module_install([sys.executable, "-m", "pip", "install", *modules_to_install])
nodes_list_dict = {}
@@ -318,7 +296,8 @@ try:
)
except Exception as e:
nodes_list_dict["ArgosTranslateNode"]["error"] = e
if "ArgosTranslateNode" in nodes_list_dict:
nodes_list_dict["ArgosTranslateNode"]["error"] = e
# DeepTranslatorNode
@@ -342,31 +321,8 @@ try:
)
except Exception as e:
nodes_list_dict["DeepTranslatorNode"]["error"] = e
# DeepLXTranslateNode
try:
from .DeepLXTranslateNode.deeplx_translate_node import (
DeepLXTranslateCLIPTextEncodeNode,
DeepLXTranslateTextNode,
)
NODE_CLASS_MAPPINGS.update(
{
"DeepLXTranslateCLIPTextEncodeNode": DeepLXTranslateCLIPTextEncodeNode,
"DeepLXTranslateTextNode": DeepLXTranslateTextNode,
}
)
NODE_DISPLAY_NAME_MAPPINGS.update(
{
"DeepLXTranslateCLIPTextEncodeNode": "DeepLX Translate CLIP Text Encode Node",
"DeepLXTranslateTextNode": "DeepLX Translate Text Node",
}
)
except Exception as e:
nodes_list_dict["DeepLXTranslateNode"]["error"] = e
if "DeepTranslatorNode" in nodes_list_dict:
nodes_list_dict["DeepTranslatorNode"]["error"] = e
# GoogleTranslateNode
@@ -390,83 +346,157 @@ try:
)
except Exception as e:
nodes_list_dict["GoogleTranslateNode"]["error"] = e
if "GoogleTranslateNode" in nodes_list_dict:
nodes_list_dict["GoogleTranslateNode"]["error"] = e
# ChatGLMNode
from .ChatGLMNode.chatglm_node import (
ChatGLM4TranslateCLIPTextEncodeNode,
ChatGLM4TranslateTextNode,
ChatGLM4InstructNode,
ChatGLM4InstructMediaNode,
)
try:
from .ChatGLMNode.chatglm_node import (
ChatGLM4TranslateCLIPTextEncodeNode,
ChatGLM4TranslateTextNode,
ChatGLM4InstructNode,
ChatGLM4InstructMediaNode,
)
NODE_CLASS_MAPPINGS.update(
{
"ChatGLM4TranslateCLIPTextEncodeNode": ChatGLM4TranslateCLIPTextEncodeNode,
"ChatGLM4TranslateTextNode": ChatGLM4TranslateTextNode,
"ChatGLM4InstructNode": ChatGLM4InstructNode,
"ChatGLM4InstructMediaNode": ChatGLM4InstructMediaNode,
}
)
NODE_DISPLAY_NAME_MAPPINGS.update(
{
"ChatGLM4TranslateCLIPTextEncodeNode": "ChatGLM-4 Translate CLIP Text Encode Node",
"ChatGLM4TranslateTextNode": "ChatGLM-4 Translate Text Node",
"ChatGLM4InstructNode": "ChatGLM-4 Instruct Node",
"ChatGLM4InstructMediaNode": "ChatGLM-4 Instruct Media Node",
}
)
except Exception as e:
if "ChatGLMNode" in nodes_list_dict:
nodes_list_dict["ChatGLMNode"]["error"] = e
# ExtrasNode
from .ExtrasNode.extras_node import PreviewTextNode, HexToHueNode, ColorsCorrectNode
try:
from .ExtrasNode.extras_node import PreviewTextNode, HexToHueNode, ColorsCorrectNode
NODE_CLASS_MAPPINGS.update(
{
"PreviewTextNode": PreviewTextNode,
"HexToHueNode": HexToHueNode,
"ColorsCorrectNode": ColorsCorrectNode,
}
)
NODE_DISPLAY_NAME_MAPPINGS.update(
{
"PreviewTextNode": "Preview Text Node",
"HexToHueNode": "HEX to HUE Node",
"ColorsCorrectNode": "Colors Correct Node",
}
)
except Exception as e:
if "ExtrasNode" in nodes_list_dict:
nodes_list_dict["ExtrasNode"]["error"] = e
# PainterNode
from .PainterNode.painter_node import PainterNode
try:
from .PainterNode.painter_node import PainterNode
NODE_CLASS_MAPPINGS.update(
{
"PainterNode": PainterNode,
}
)
NODE_DISPLAY_NAME_MAPPINGS.update(
{
"PainterNode": "Painter Node",
}
)
except Exception as e:
if "PainterNode" in nodes_list_dict:
nodes_list_dict["PainterNode"]["error"] = e
# PoseNode
from .PoseNode.pose_node import PoseNode
try:
from .PoseNode.pose_node import PoseNode
NODE_CLASS_MAPPINGS.update(
{
"PoseNode": PoseNode,
}
)
NODE_DISPLAY_NAME_MAPPINGS.update(
{
"PoseNode": "Pose Node",
}
)
except Exception as e:
if "PoseNode" in nodes_list_dict:
nodes_list_dict["PoseNode"]["error"] = e
# IDENode
from .IDENode.ide_node import IDENode
try:
from .IDENode.ide_node import IDENode
NODE_CLASS_MAPPINGS.update(
{
"IDENode": IDENode,
}
)
NODE_CLASS_MAPPINGS.update(
{
"ChatGLM4TranslateCLIPTextEncodeNode": ChatGLM4TranslateCLIPTextEncodeNode,
"ChatGLM4TranslateTextNode": ChatGLM4TranslateTextNode,
"ChatGLM4InstructNode": ChatGLM4InstructNode,
"ChatGLM4InstructMediaNode": ChatGLM4InstructMediaNode,
"PreviewTextNode": PreviewTextNode,
"HexToHueNode": HexToHueNode,
"ColorsCorrectNode": ColorsCorrectNode,
"PainterNode": PainterNode,
"PoseNode": PoseNode,
"IDENode": IDENode,
}
)
NODE_DISPLAY_NAME_MAPPINGS.update(
{
"IDENode": "IDE Node",
}
)
NODE_DISPLAY_NAME_MAPPINGS.update(
{
"ChatGLM4TranslateCLIPTextEncodeNode": "ChatGLM-4 Translate CLIP Text Encode Node",
"ChatGLM4TranslateTextNode": "ChatGLM-4 Translate Text Node",
"ChatGLM4InstructNode": "ChatGLM-4 Instruct Node",
"ChatGLM4InstructMediaNode": "ChatGLM-4 Instruct Media Node",
"PreviewTextNode": "Preview Text Node",
"HexToHueNode": "HEX to HUE Node",
"ColorsCorrectNode": "Colors Correct Node",
"PainterNode": "Painter Node",
"PoseNode": "Pose Node",
"IDENode": "IDE Node",
}
)
except Exception as e:
if "IDENode" in nodes_list_dict:
nodes_list_dict["IDENode"]["error"] = e
# Information
printColorInfo(
f"\n### [START] ComfyUI AlekPet Nodes{get_version_extension()} ###", "\033[1;35m"
)
printColorInfo(f"\n### [START] ComfyUI AlekPet Nodes{get_version_extension()} ###", "\033[1;35m")
failed_nodes_text = ""
len_nodes = len(nodes_list_dict)
for key, node in enumerate(nodes_list_dict):
currentNode = nodes_list_dict[node]
if currentNode.get("error", None) is None:
currentNodeVals = currentNode.get("error")
if currentNodeVals is None:
status = "\033[92m[Loading]"
else:
status = "\033[1;31;40m[Failed]"
color_error = "\033[1;31;40m"
status = color_error + " [Failed]"
error_message = currentNode.get("error", "Error")
failed_nodes_text += f"\033[93m{node} -> \033[1;31;40m{error_message}\033[0m\n"
if(len(currentNodeVals.args) == 2 and currentNodeVals.args[1] == "warning"):
color_error = "\033[33m"
status = color_error + "[Warning]"
error_message = currentNodeVals.args[0]
failed_nodes_text += f"\033[93m{node} -> {color_error}{error_message}\033[0m\n"
message = currentNode.get("message", "No message available")
printColorInfo(f"{message}{status}\033[0m")
if key == len_nodes - 1 and failed_nodes_text:
printColorInfo(
f"\n\033[1;31;40m* Nodes have been temporarily disabled due to the error *\033[0m\n{failed_nodes_text}"
f"\n\033[1;31;40m* Nodes have been temporarily disabled due to the error or specially *\033[0m\n{failed_nodes_text}"
)
printColorInfo(f"### [END] ComfyUI AlekPet Nodes ###", "\033[1;35m")
+2 -2
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui_custom_nodes_alekpet"
description = "Nodes: PoseNode, PainterNode, TranslateTextNode, TranslateCLIPTextEncodeNode, DeepTranslatorTextNode, DeepTranslatorCLIPTextEncodeNode, ArgosTranslateTextNode, ArgosTranslateCLIPTextEncodeNode, ChatGLM4TranslateCLIPTextEncodeNode, ChatGLM4TranslateTextNode, ChatGLM4InstructNode, ChatGLM4InstructMediaNode, DeepLXTranslateTextNode, DeepLXTranslateCLIPTextEncodeNode, PreviewTextNode, HexToHueNode, ColorsCorrectNode, IDENode."
version = "1.0.71"
description = "Nodes: PoseNode, PainterNode, TranslateTextNode, TranslateCLIPTextEncodeNode, DeepTranslatorTextNode, DeepTranslatorCLIPTextEncodeNode, ArgosTranslateTextNode, ArgosTranslateCLIPTextEncodeNode, ChatGLM4TranslateCLIPTextEncodeNode, ChatGLM4TranslateTextNode, ChatGLM4InstructNode, ChatGLM4InstructMediaNode, PreviewTextNode, HexToHueNode, ColorsCorrectNode, IDENode."
version = "1.0.74"
license = { file = "LICENSE" }
[project.urls]