Merge branch 'master' of https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet into tool_Crop
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
go/
|
||||
DeepLX/
|
||||
config.json
|
||||
@@ -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:**
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
@@ -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 |
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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
|
||||
},
|
||||
});
|
||||
@@ -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 };
|
||||
|
||||
+10
-3
@@ -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,
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user