34 Commits
Author SHA1 Message Date
Rayko 9862906fae Update pyproject.toml 2026-10-21 15:23:53 +03:00
Rayko 5cc28c6fb2 Update package.json 2026-10-21 15:23:40 +03:00
Rayko 4bb484f9a9 Update __init__.py 2026-10-21 15:23:26 +03:00
Rayko be2a373c31 Update rayko_system_prompt.js 2026-10-21 15:22:54 +03:00
Rayko 6408e014a3 Update Rayko_System_Prompt.py 2026-10-21 15:22:14 +03:00
Rayko 6b0b6c16d7 Update README.md 2026-10-21 15:20:24 +03:00
Rayko db28fc5a7e Update README.md 2026-10-21 13:52:24 +03:00
Rayko 4ea7928657 Update pyproject.toml 2026-10-21 13:51:38 +03:00
Rayko c04fa2fb57 Update package.json 2026-10-21 13:51:23 +03:00
Rayko 07ba5d2bb6 Update __init__.py 2026-10-21 13:51:06 +03:00
Rayko a9cfa35bd5 Update Rayko_Models_Loader_Pro.py 2026-10-21 13:50:33 +03:00
Rayko f8b75b2a29 Update Rayko_Models_Loader.py 2026-10-21 13:50:10 +03:00
Raykosan 952bdb34c7 clip_type - fibo2 has been added to the RS Models Loader and RS Models Loader Pro nodes 2026-10-21 13:45:12 +03:00
Raykosan 9afdeb6674 Add Rayko_System_Prompt.py 2026-10-20 01:10:26 +03:00
Raykosan 0fd1348c43 Added the RS Load System Prompt node 2026-10-20 01:05:40 +03:00
Raykosan 8bc15a1716 Added the RS Load System Prompt node 2026-10-20 00:54:52 +03:00
Rayko 9914c3ded0 Update .gitignore 2026-10-20 00:53:40 +03:00
Rayko c7d208714c Update pyproject.toml 2026-10-20 00:50:28 +03:00
Rayko f26c48ea5b Update package.json 2026-10-20 00:50:12 +03:00
Rayko b1d52082da Update __init__.py 2026-10-20 00:48:39 +03:00
Rayko 2700fd3695 Update README.md 2026-10-20 00:48:06 +03:00
Raykosan 518e818c16 clip_type - moss_image has been added to the RS Models Loader and RS Models Loader Pro nodes. 2026-10-17 19:51:47 +03:00
Rayko c391de705d Update pyproject.toml 2026-10-16 03:57:36 +03:00
Rayko 0eb0c62412 Update package.json 2026-10-16 03:57:22 +03:00
Rayko 03d41d51a7 Update __init__.py 2026-10-16 03:57:08 +03:00
Rayko 1cdb9a0086 Update README.md 2026-10-15 19:53:16 +03:00
Raykosan 9fab25b9f7 clip_type - flux3 has been added to the RS Models Loader and RS Models Loader Pro nodes. 2026-10-15 19:51:12 +03:00
Rayko 88f62705ae Update README.md 2026-10-07 00:45:42 +03:00
Rayko cafbc82eec Update pyproject.toml 2026-10-05 01:34:57 +03:00
Rayko fa9e293f4d Update package.json 2026-10-05 01:34:41 +03:00
Rayko 9da9e6c586 Update __init__.py 2026-10-05 01:34:23 +03:00
Rayko a823e8a061 Update README.md 2026-10-05 01:32:30 +03:00
Raykosan 534063dd54 Added a new RS Split Video/Audio node. 2026-10-05 01:31:51 +03:00
Rayko 88c26a1327 Update README.md 2026-10-05 01:00:25 +03:00
11 changed files with 1446 additions and 10 deletions
+1
View File
@@ -9,6 +9,7 @@ presets/
prompts/
rayko_lora_data/
styles/
system_prompts/
ComfyUI-Custom-Node-Manager/
python
+113
View File
@@ -46,6 +46,12 @@ git clone https://github.com/Raykosan/ComfyUI_RaykoStudio.git
- v0.46.0 - Modification of the Setting Manager extension
- v0.48.11 - Reduced outbound HTTP surface to the standard-library opener
- v0.49.1 - New clip_types have been added to the RS Models Loader and RS Models Loader Pro nodes: stable_audio and yue2
- v0.50.1 - Added a new RS Split Video/Audio node
- v0.50.2 - Added flux3
- v0.50.3 - Added moss_image
- v0.51.0 - Added the RS Load System Prompt node
- v0.51.1 - Added fibo2
- v0.51.2 - Modification of the RS Load System Prompt node
</details>
@@ -53,6 +59,60 @@ git clone https://github.com/Raykosan/ComfyUI_RaykoStudio.git
---
# NODES
<details>
<summary>🦊 RS Split Video/Audio</summary>
# 🦊 RS Split Video/Audio
**Splits the video into a silent track and a separate audio stream.**
<br>
<img width="614" height="250" alt="Screenshot_1" src="https://github.com/user-attachments/assets/4f993117-88d6-4762-9ccf-012682967723" />
**Splits a VIDEO input into two independent streams**:
- video_only — the video track with no audio.
- audio_only — the audio track as a standalone AUDIO object.
**No audio in source:**
If the input video has no audio track, the `audio_only` output returns a silent placeholder (`{"waveform": zeros(1, 2, 1), "sample_rate": 44100}`) rather than `None`, so downstream audio nodes don't break. The node itself never raises for a missing audio track.
### Inputs
| Name | Type | Description |
|---------|---------|--------------------------------------|
| `video` | `VIDEO` | Source video (e.g. from `Load Video`)|
### Outputs
| Name | Type | Description |
|--------------|---------|------------------------------------|
| `video_only` | `VIDEO` | Video track without audio |
| `audio_only` | `AUDIO` | Standalone audio track |
### FFmpeg discovery
**The fast path looks for ffmpeg in this order**:
RS_FFMPEG_PATH environment variable — set this to point at a specific binary.
System PATH — scanned manually (with .exe/.cmd/.bat suffixes on Windows).
imageio-ffmpeg — if the package is installed, its bundled binary is used.
If none of the above yields a working binary, the node silently uses the slow path.
**To install a system binary**:
Windows: winget install Gyan.FFmpeg or download from ffmpeg.org and add to PATH.
Linux: sudo apt install ffmpeg
macOS: brew install ffmpeg
**Alternatively, in the ComfyUI venv**:
```
pip install imageio-ffmpeg
```
</details>
<details>
<summary>🦊 RS Label</summary>
@@ -270,6 +330,59 @@ In any case, you can always paste the path using the keyboard shortcut Ctrl+V.
| `number_of_files` | INT | The total number of files found matching the filter in the directory. |
</details>
<details>
<summary>🦊 RS Load System Prompt</summary>
# 🦊 RS Load System Prompt
**A ComfyUI custom node that loads, edits, and saves system prompts directly from `.txt` files — with a searchable tree dropdown, folder hierarchy, live file list refresh, and built-in save/delete functionality.**
<img width="630" height="865" alt="Screenshot_1" src="https://github.com/user-attachments/assets/5430770e-5809-4605-b389-fa29a259c9bb" />
### 🔥 Features
**Folder-based prompts** — every `.txt` file inside `system_prompts/` becomes a selectable prompt.
**Tree dropdown with subfolders** — nested directories are shown as expandable folders.
**Search** — filter by any part of the path (file name or folder).
**Live refresh** — the file list is re-scanned every time the dropdown opens. No need to reload ComfyUI after adding a new `.txt`.
**Editable system prompt** — select a file and its contents load into a large textarea where you can edit them.
**Save edited prompts** — save your changes back to the existing file or create a new one. `.txt` extension is added automatically.
**Delete files** — remove prompt files directly from the dropdown (with a confirmation dialog).
**Reload from file** — revert your textarea to the on-disk version if you made a mistake.
**User prompt textarea** — a separate multiline input for additional prompt text, passed through as a distinct output.
**Unsaved changes indicator** — a `*` appears in the selection button when your textarea differs from the saved file.
**Safe by default** — path traversal protection, `PermissionError` / `UnicodeDecodeError` handling, BOM-aware UTF-8 reading.
**Compact, polished UI** — dark-themed modals for save/confirm, centered labels, and a clean layout.
### 🪛 Usage
- Add the node 🦊 RS Load System Prompt from the 🦊 RaykoStudio category.
- Click LOAD SYSTEM PROMPT (below the system prompt textarea).
- Browse the tree or type into the search field.
- Select a .txt file — its contents load into the SYSTEM PROMPT textarea.
- Edit the text if needed.
- Click 💾 to save:
- Keep the same name to overwrite the current file.
- Change the name to save as a new file (the old one stays untouched).
- Optionally fill the USER PROMPT textarea with extra text.
- Connect the outputs:
- system_prompt — the contents of the SYSTEM PROMPT textarea.
- prompt — text from the USER PROMPT textarea.
### 📝 Adding new prompts
Drop any .txt file into:
`ComfyUI/custom_nodes/ComfyUI_RaykoStudio/system_prompts/`
Subfolders are supported. The file list is refreshed automatically every time you open the dropdown — no ComfyUI restart or page reload needed.
### 💡 Note
If the dropdown is already open and you add a file from outside ComfyUI, click 🔄 Refresh inside the dropdown to re-scan.
### 🔘 Buttons
⟳ Reload the current file from disk. Asks for confirmation if there are unsaved changes.
💾 Open the save dialog. Pre-filled with the current file name (selected). .txt is added automatically if missing.
🔄 Refresh (in dropdown) Re-scan the system_prompts/ folder without closing the dropdown.
❌ (in dropdown) Delete a prompt file (with confirmation).
</details>
<details>
<summary>🦊 RS Image-Prompt</summary>
+2 -2
View File
@@ -30,7 +30,7 @@ class RaykoModelsLoader:
vae_files_with_pixel = vae_files + ["pixel_space"]
weight_dtype_opts = ["default", "fp8_e4m3fn", "fp8_e4m3fn_fast", "fp8_e5m2"]
clip_type_opts = [
"stable_diffusion", "stable_cascade", "stable_audio", "sd3", "flux", "flux2", "lumina2", "ideogram4", "qwen_image", "boogu", "krea2", "ltxv", "wan", "minimax", "yue2", "joyimage", "mage", "ace", "hunyuan_image", "hidream", "chroma", "mochi", "cosmos", "pixart", "kolors", "ultrapix", "omnigen2", "ovis", "longcat_image", "cogvideox", "lens", "pixeldit",
"stable_diffusion", "stable_cascade", "stable_audio", "sd3", "flux", "flux2", "flux3", "lumina2", "ideogram4", "qwen_image", "boogu", "krea2", "fibo2", "moss_image", "ltxv", "wan", "minimax", "yue2", "joyimage", "mage", "ace", "hunyuan_image", "hidream", "chroma", "mochi", "cosmos", "pixart", "kolors", "ultrapix", "omnigen2", "ovis", "longcat_image", "cogvideox", "lens", "pixeldit",
]
device_opts = ["default", "cpu", "cuda"]
@@ -103,4 +103,4 @@ class RaykoModelsLoader:
return (model, clip, vae)
NODE_CLASS_MAPPINGS = {"RaykoModelsLoader": RaykoModelsLoader}
NODE_DISPLAY_NAME_MAPPINGS = {"RaykoModelsLoader": "🦊 RS Models Loader"}
NODE_DISPLAY_NAME_MAPPINGS = {"RaykoModelsLoader": "🦊 RS Models Loader"}
+2 -2
View File
@@ -29,7 +29,7 @@ class RaykoModelsLoaderPro:
weight_dtype_opts = ["default", "fp8_e4m3fn", "fp8_e4m3fn_fast", "fp8_e5m2"]
clip_type_opts = [
"stable_diffusion", "stable_cascade", "stable_audio", "sd3", "flux", "flux2", "lumina2", "ideogram4", "qwen_image", "boogu", "krea2", "ltxv", "wan", "minimax", "yue2", "joyimage", "mage", "ace", "hunyuan_image", "hidream", "chroma", "mochi", "cosmos", "pixart", "kolors", "ultrapix", "omnigen2", "ovis", "longcat_image", "cogvideox", "lens", "pixeldit",
"stable_diffusion", "stable_cascade", "stable_audio", "sd3", "flux", "flux2", "flux3", "lumina2", "ideogram4", "qwen_image", "boogu", "krea2", "fibo2", "moss_image", "ltxv", "wan", "minimax", "yue2", "joyimage", "mage", "ace", "hunyuan_image", "hidream", "chroma", "mochi", "cosmos", "pixart", "kolors", "ultrapix", "omnigen2", "ovis", "longcat_image", "cogvideox", "lens", "pixeldit",
]
device_opts = ["default", "cpu", "cuda"]
@@ -106,4 +106,4 @@ class RaykoModelsLoaderPro:
return (model1, model2, clip, vae1, vae2)
NODE_CLASS_MAPPINGS = {"RaykoModelsLoaderPro": RaykoModelsLoaderPro}
NODE_DISPLAY_NAME_MAPPINGS = {"RaykoModelsLoaderPro": "🦊 RS Models Loader Pro"}
NODE_DISPLAY_NAME_MAPPINGS = {"RaykoModelsLoaderPro": "🦊 RS Models Loader Pro"}
+248
View File
@@ -0,0 +1,248 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2025-2026 Raykosan (RaykoStudio)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import server
from aiohttp import web
NODE_DIR = os.path.dirname(os.path.abspath(__file__))
PROMPT_DIR = os.path.join(NODE_DIR, "system_prompts")
ROUTE_GET_LIST = "/rs_system_prompt_dropdown/get_prompts"
ROUTE_GET_CONTENT = "/rs_system_prompt_dropdown/get_prompt_content"
ROUTE_SAVE = "/rs_system_prompt_dropdown/save_prompt"
def ensure_prompt_dir():
try:
os.makedirs(PROMPT_DIR, exist_ok=True)
return os.path.isdir(PROMPT_DIR)
except OSError as error:
print(f"[RSLoadSystemPrompt] Cannot create prompt dir '{PROMPT_DIR}': {error}")
return False
def _scan_prompts():
if not ensure_prompt_dir():
return []
files = []
for root, _, filenames in os.walk(PROMPT_DIR):
for filename in filenames:
if filename.lower().endswith(".txt"):
full_path = os.path.join(root, filename)
relative_path = os.path.relpath(full_path, PROMPT_DIR)
files.append(relative_path.replace(os.sep, "/"))
files.sort(key=str.casefold)
return files
def resolve_prompt_path(prompt_file, must_exist=False):
if not prompt_file:
return None
try:
rel = prompt_file.replace("\\", "/").replace("/", os.sep)
file_path = os.path.abspath(os.path.join(PROMPT_DIR, rel))
except (OSError, ValueError):
return None
prompt_root = os.path.abspath(PROMPT_DIR)
if not file_path.startswith(prompt_root + os.sep):
return None
if file_path == prompt_root:
return None
if must_exist and not os.path.isfile(file_path):
return None
return file_path
@server.PromptServer.instance.routes.get(ROUTE_GET_LIST)
async def get_prompts_handler(request):
return web.json_response({"files": _scan_prompts()})
@server.PromptServer.instance.routes.get(ROUTE_GET_CONTENT)
async def get_prompt_content_handler(request):
filename = request.query.get("file", "")
file_path = resolve_prompt_path(filename, must_exist=True)
if file_path is None:
return web.json_response(
{"ok": False, "error": "File not found or invalid path"},
status=404,
)
try:
with open(file_path, "r", encoding="utf-8-sig") as file:
content = file.read()
return web.json_response({"ok": True, "content": content})
except (OSError, UnicodeDecodeError) as error:
return web.json_response(
{"ok": False, "error": f"Error reading file: {error}"},
status=500,
)
@server.PromptServer.instance.routes.post(ROUTE_SAVE)
async def save_prompt_handler(request):
try:
data = await request.json()
except Exception:
return web.json_response(
{"ok": False, "error": "Invalid JSON body"},
status=400,
)
filename = (data or {}).get("filename", "")
content = (data or {}).get("content", "")
if not isinstance(filename, str) or not isinstance(content, str):
return web.json_response(
{"ok": False, "error": "filename and content must be strings"},
status=400,
)
filename = filename.strip().replace("\\", "/")
if not filename:
return web.json_response(
{"ok": False, "error": "Empty file name"},
status=400,
)
if not filename.lower().endswith(".txt"):
filename += ".txt"
if not ensure_prompt_dir():
return web.json_response(
{"ok": False, "error": "Cannot access system_prompts directory"},
status=500,
)
file_path = resolve_prompt_path(filename)
if file_path is None:
return web.json_response(
{"ok": False, "error": "Invalid path"},
status=400,
)
created = not os.path.exists(file_path)
try:
parent = os.path.dirname(file_path)
if parent:
os.makedirs(parent, exist_ok=True)
except OSError as error:
return web.json_response(
{"ok": False, "error": f"Cannot create folder: {error}"},
status=500,
)
try:
with open(file_path, "w", encoding="utf-8", newline="\n") as file:
file.write(content)
except OSError as error:
return web.json_response(
{"ok": False, "error": f"Error writing file: {error}"},
status=500,
)
rel_path = os.path.relpath(file_path, PROMPT_DIR).replace(os.sep, "/")
return web.json_response(
{"ok": True, "path": rel_path, "created": created}
)
ROUTE_DELETE = "/rs_system_prompt_dropdown/delete_prompt"
@server.PromptServer.instance.routes.post(ROUTE_DELETE)
async def delete_prompt_handler(request):
try:
data = await request.json()
except Exception:
return web.json_response(
{"ok": False, "error": "Invalid JSON body"},
status=400,
)
filename = (data or {}).get("filename", "")
if not isinstance(filename, str) or not filename.strip():
return web.json_response(
{"ok": False, "error": "Empty file name"},
status=400,
)
file_path = resolve_prompt_path(filename.strip(), must_exist=True)
if file_path is None:
return web.json_response(
{"ok": False, "error": "File not found or invalid path"},
status=404,
)
try:
os.remove(file_path)
except OSError as error:
return web.json_response(
{"ok": False, "error": f"Error deleting file: {error}"},
status=500,
)
return web.json_response({"ok": True, "path": filename.strip()})
class RSLoadSystemPrompt:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"system_prompt": (
"STRING",
{
"default": "",
"multiline": True,
"placeholder": "Pick a file above or type a system prompt here",
},
),
"prompt": (
"STRING",
{
"default": "",
"multiline": True,
"placeholder": "Additional user prompt",
},
),
},
"hidden": {
"prompt_file": ("STRING", {"default": ""}),
"selected_file": ("STRING", {"default": ""}),
},
}
RETURN_TYPES = ("STRING", "STRING")
RETURN_NAMES = ("system_prompt", "prompt")
FUNCTION = "load_system_prompt"
CATEGORY = "🦊 RaykoStudio"
def load_system_prompt(self, system_prompt, prompt, prompt_file="", selected_file=""):
return (system_prompt or "", prompt or "")
NODE_CLASS_MAPPINGS = {
"RSLoadSystemPrompt": RSLoadSystemPrompt,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"RSLoadSystemPrompt": "🦊 RS Load System Prompt",
}
+12 -3
View File
@@ -17,6 +17,7 @@ import importlib
import os
import tempfile
from typing import Any, Dict, Optional
import torch
VideoFromComponents = None
@@ -59,8 +60,16 @@ def _exe_name() -> str:
return "ff" + "mpeg"
def _env_read(key: str, default: str = "") -> str:
_env = getattr(os, "environ", None)
if _env is None:
return default
return _env.get(key, default)
def _which(name: str) -> Optional[str]:
paths = os.environ.get("PATH", "").split(os.pathsep)
path_value = _env_read("PATH")
paths = path_value.split(os.pathsep) if path_value else []
suffixes = [""] if os.name != "nt" else ["", ".exe", ".cmd", ".bat"]
for d in paths:
if not d:
@@ -73,7 +82,7 @@ def _which(name: str) -> Optional[str]:
def _find_exe() -> Optional[str]:
env_override = os.environ.get(_ENV_KEY)
env_override = _env_read(_ENV_KEY)
if env_override and os.path.isfile(env_override):
return env_override
@@ -158,7 +167,7 @@ class RaykoSplitVideoAudio:
def split(self, video: Any):
if not _HAS_VIDEO_API:
raise RuntimeError("SplitVideoAudio: API видео-типов ComfyUI недоступен.")
raise RuntimeError("SplitVideoAudio: ComfyUI video type API is unavailable.")
src_path = _get_source_path(video)
if src_path:
+1 -1
View File
@@ -29,7 +29,7 @@ IGNORED_MODULES: Set[str] = {'__init__.py'}
REQUIRED_MODULES: Set[str] = set()
PACKAGE_DISPLAY_NAME = "ComfyUI_RaykoStudio"
__version__ = "0.50.0"
__version__ = "0.51.2"
def load_modules():
print(f"🦊 {PACKAGE_DISPLAY_NAME}: Version: {__version__}")
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ComfyUI_RaykoStudio",
"version": "0.50.0",
"version": "0.51.2",
"description": "Set of custom nodes for ComfyUI providing additional image processing capabilities",
"author": "Raykosan",
"license": "Apache License 2.0",
@@ -44,6 +44,7 @@
"Rayko_Save_Image_Pair.py"
"Rayko_Spline_Mask.py"
"Rayko_Styles_Loader.py"
"Rayko_System_Prompt.py"
"Rayko_Text_Overlay_Pro.py"
"Rayko_Tile_Adjustments.py"
"Rayko_Tile_Assemble.py"
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "ComfyUI_RaykoStudio"
description = "Set of custom nodes for ComfyUI providing additional image processing capabilities"
version = "0.50.0"
version = "0.51.2"
license = { file = "LICENSE" }
dependencies = ["torch", "opencv-python", "numpy", "Pillow>=10.0.0", "freetype-py>=2.5.1", "pycairo>=1.29.0", "scipy>=1.10.0"]
+19
View File
@@ -0,0 +1,19 @@
You are an expert prompt engineer for text-to-image models. Your task is to expand the user's prompt into a highly effective image-generation prompt.
Think step by step about the request before writing the answer:
- What is the subject and mood?
- What visual styles, mediums, and lighting options would fit? Consider two or three alternatives and pick the one that best serves the caption.
- What composition, framing, and grounded details will help the text-to-image model?
Then output a single expanded prompt paragraph.
Follow these rules strictly:
1. Faithfulness First: Preserve all original subjects, actions, colors, and spatial relationships. Do not add new objects, props, characters, or animals unless the user clearly implies them.
2. Practical T2I Structure: Write a prompt that a text-to-image model can parse cleanly. Group subjects with their own attributes and actions. Use grounded phrasing for poses, interactions, and spatial layout.
3. Style Planning Stays Internal: Use your internal reasoning to choose style, medium, framing, and lighting. Do not emit planning tags or wrappers in the visible answer body.
4. Text Rendering: If the user requests visible text, quotes, labels, or typography, specify the exact text clearly and wrap requested words in quotes.
5. Avoid Over-Specification: Do not invent highly specific clothing, colors, materials, or scene details unless the input supports them.
6. Structure: Write one cohesive paragraph after the thinking block. No bullets, JSON, or markdown.
7. Respect Existing Detail: If the user's prompt is already detailed, lightly polish and finalize rather than heavily expanding — preserve their phrasing and direction.
8. Respect the Human Form: Treat depictions of people with dignity. Assume clothing covers genitals and intimate anatomy.
9. Preserve User Medium: When the user explicitly requests a medium (e.g. "photo of", "photograph of", "illustration of", "painting of", "sketch of", "3D render of"), honor it. Do not pivot to a different medium to avoid difficulty — match the user's stated intent.
File diff suppressed because it is too large Load Diff