Compare commits

...
6 Commits
6 changed files with 437 additions and 34 deletions
+133 -32
View File
@@ -1,19 +1,16 @@
# SF ComfyUI Nodes
A collection of custom nodes for ComfyUI developed by Stillfront.
![Stillfront](assets/logo.png)
## Features
A collection of custom ComfyUI nodes developed by [Stillfront](https://www.stillfront.com). All nodes are prefixed with **SF** for easy searching in ComfyUI.
- **LLM Integration** — Chat with Claude, Gemini, and OpenAI GPT models, with optional image input
- **WaveSpeed AI** — Image and video generation (VEO, Sora, Qwen, ByteDance, Kling, VIDU, WAN, and more)
- **VertexAI** — Google Imagen3 and VEO video generation via Vertex AI
- **Utility Nodes** — Resolution presets, dynamic prompt lists, text analysis
---
## Installation
### Via ComfyUI Manager (recommended)
Search for **"SF ComfyUI Nodes"** in ComfyUI Manager and click Install.
Search for **"SF ComfyUI Nodes"** in the ComfyUI Nodes Manager and click **Install**.
### Manual
@@ -24,47 +21,151 @@ cd comfyui-sf-nodes
pip install -r requirements.txt
```
---
## Configuration
1. Copy `config.ini.tmp` to `config.ini`
2. Add your API keys:
- `WAVESPEED_API_KEY`
- `ANTHROPIC_API_KEY` (for Claude)
- `GEMINI_API_KEY` (for Gemini)
- Google Cloud credentials (for VertexAI nodes)
Copy `config.ini.tmp` to `config.ini` and fill in your API keys:
```ini
[API]
wavespeed_api_key = YOUR_WAVESPEED_API_KEY
```
Alternatively, set keys via environment variables:
| Variable | Used by |
|---|---|
| `WAVESPEED_API_KEY` | All WaveSpeed nodes |
| `ANTHROPIC_API_KEY` | SF LLM Chat (Claude) |
| `GEMINI_API_KEY` | SF LLM Chat (Gemini) |
| `OPENAI_API_KEY` | SF LLM Chat (GPT) |
| Google Application Default Credentials | All VertexAI nodes |
For VertexAI nodes, authenticate via the [Google Cloud CLI](https://cloud.google.com/sdk/docs/install):
```bash
gcloud auth application-default login
```
---
## Node Categories
All nodes are prefixed with **SF** for easy searching in ComfyUI.
### Stillfront/LLM
- **SF LLM Chat** — Multi-model chat (Claude, Gemini, GPT) with image support and token usage reporting
- **SF Text Analyzer** — Text analysis utilities
### Stillfront/WaveSpeed
- **SF WaveSpeed Client** — API client configuration
- **SF VEO 3.1 Text to Video** — Google VEO video generation
- **SF Sora 2 Text to Video** — OpenAI Sora video generation
- **SF Qwen Image** — Qwen image generation and editing
- **SF ByteDance Seedream** — ByteDance image generation
- **SF Kling** — Kling video generation
- **SF VIDU** — VIDU video generation
- And many more...
Chat with large language models directly inside your ComfyUI workflows, with optional image input.
| Node | Description |
|---|---|
| **SF LLM Chat** | Multi-model chat supporting Claude (Opus, Sonnet, Haiku), Gemini 3 (Pro, Flash), and OpenAI GPT-5 series. Supports image input and reports token usage. |
| **SF Text Analyzer** | Analyze and process text strings within workflows. |
---
### Stillfront/VertexAI
- **SF Imagen3** — Google Imagen3 image generation
- **SF VEO Video** — Video generation via Vertex AI
Generate images and videos using Google Cloud Vertex AI. Requires a Google Cloud project and Application Default Credentials.
| Node | Description |
|---|---|
| **SF VertexAI Imagen 3 Text to Image** | Generate images from text using Google Imagen 3.0. Supports multiple aspect ratios, safety settings, and up to 4 images per run. |
| **SF VertexAI Imagen 4 Text to Image** | Generate images using the latest Imagen 4.0 model. |
| **SF VertexAI Imagen 4 Upscale** | Upscale images using Imagen 4.0. |
| **SF VertexAI Nano Banana Pro** | Image generation via the Nano Banana Pro model on Vertex AI. |
| **SF VertexAI Nano Banana Pro Edit** | Edit existing images using Nano Banana Pro on Vertex AI. |
| **SF VertexAI Veo 3.1 Text to Video** | Generate videos from text prompts using Google VEO 3.1 via Vertex AI. |
| **SF VertexAI Veo 3.1 Image to Video** | Animate a still image into a video using Google VEO 3.1 via Vertex AI. |
---
### Stillfront/WaveSpeed
Image and video generation nodes powered by the [WaveSpeed AI](https://wavespeed.ai) API. All nodes require the **SF WaveSpeed Client** node to authenticate.
#### Setup
Connect an **SF WaveSpeed Client** node (enter your WaveSpeed API key) to any WaveSpeed generation node.
#### Video Generation
| Node | Description |
|---|---|
| **SF WaveSpeed VEO 3.1 Text to Video** | Google VEO 3.1 text-to-video generation. |
| **SF WaveSpeed VEO 3.1 Image to Video** | Google VEO 3.1 image-to-video animation. |
| **SF WaveSpeed VEO 3.1 Reference to Video** | VEO 3.1 video generation using a reference image. |
| **SF WaveSpeed VEO 3.1 Fast Text to Video** | Faster VEO 3.1 text-to-video variant. |
| **SF WaveSpeed VEO 3.1 Fast Image to Video** | Faster VEO 3.1 image-to-video variant. |
| **SF WaveSpeed Sora 2 Text to Video** | OpenAI Sora 2 text-to-video generation. |
| **SF WaveSpeed Sora 2 Text to Video Pro** | Sora 2 Pro — higher quality text-to-video. |
| **SF WaveSpeed Sora 2 Image to Video** | OpenAI Sora 2 image-to-video animation. |
| **SF WaveSpeed Sora 2 Image to Video Pro** | Sora 2 Pro — higher quality image-to-video. |
| **SF WaveSpeed Wan 2.5 Text to Video** | WAN 2.5 text-to-video generation. |
| **SF WaveSpeed Wan 2.5 Text to Video Fast** | WAN 2.5 faster text-to-video variant. |
| **SF WaveSpeed Wan 2.5 Image to Video** | WAN 2.5 image-to-video animation. |
| **SF WaveSpeed Wan 2.5 Image to Video Fast** | WAN 2.5 faster image-to-video variant. |
| **SF WaveSpeed Wan 2.2 Animate** | WAN 2.2 image animation. |
| **SF WaveSpeed InfiniteTalk** | Animate a portrait image with audio to create a talking-head video. |
| **SF WaveSpeed InfiniteTalk Multi** | InfiniteTalk with multiple reference inputs. |
#### Image Generation
| Node | Description |
|---|---|
| **SF WaveSpeed Nano Banana Text to Image** | Nano Banana text-to-image generation. |
| **SF WaveSpeed Nano Banana Edit** | Edit images using the Nano Banana model. |
| **SF WaveSpeed Nano Banana Pro Text to Image** | Nano Banana Pro text-to-image. |
| **SF WaveSpeed Nano Banana Pro Text to Image Multi** | Nano Banana Pro with multiple inputs. |
| **SF WaveSpeed Nano Banana Pro Text to Image Ultra** | Nano Banana Pro Ultra quality variant. |
| **SF WaveSpeed Nano Banana Pro Edit** | Edit images with Nano Banana Pro. |
| **SF WaveSpeed Nano Banana Pro Edit Multi** | Nano Banana Pro edit with multiple inputs. |
| **SF WaveSpeed Nano Banana Pro Edit Ultra** | Nano Banana Pro edit Ultra quality variant. |
| **SF WaveSpeed Qwen Text to Image** | Qwen text-to-image generation. |
| **SF WaveSpeed Qwen Edit** | Edit images using Qwen. |
| **SF WaveSpeed Qwen Edit Plus** | Qwen Edit with extended capabilities. |
| **SF WaveSpeed Qwen Edit LoRA** | Qwen edit with LoRA support. |
| **SF WaveSpeed Qwen Edit Plus LoRA** | Qwen Edit Plus with LoRA support. |
| **SF WaveSpeed Seedream V4** | ByteDance Seedream V4 image generation. |
| **SF WaveSpeed Seedream V4 Sequential** | Seedream V4 sequential generation. |
| **SF WaveSpeed Seedream V4 Edit** | Edit images with Seedream V4. |
| **SF WaveSpeed Seedream V4 Edit Sequential** | Seedream V4 sequential editing. |
| **SF WaveSpeed Wan 2.5 Text to Image** | WAN 2.5 text-to-image generation. |
| **SF WaveSpeed Wan 2.5 Image Edit** | Edit images using WAN 2.5. |
| **SF WaveSpeed Flux Kontext Dev** | Flux Kontext Dev image generation. |
| **SF WaveSpeed Flux Kontext Pro** | Flux Kontext Pro image generation. |
| **SF WaveSpeed Flux Kontext Max** | Flux Kontext Max — highest quality variant. |
| **SF WaveSpeed Flux ControlNet Union Pro 2** | Flux image generation with ControlNet guidance. |
#### Upscalers
| Node | Description |
|---|---|
| **SF WaveSpeed Image Upscaler** | Upscale images via WaveSpeed AI. |
| **SF WaveSpeed Runway Upscale** | Upscale images using RunwayML via WaveSpeed. |
---
### Stillfront/Utils
- **SF Qwen Resolution** — Resolution presets with visual preview
- **SF Prompt List** — Dynamic multi-input text combiner (2–50 inputs)
Utility nodes for building and enhancing workflows.
| Node | Description |
|---|---|
| **SF Prompt List** | Combine 2–50 text inputs into a single comma-separated string. Add or remove inputs using the `inputcount` control. |
| **SF Qwen Resolution** | Visual resolution preset selector with aspect ratio preview. |
| **SF Text Analyzer** | Analyze text content within a workflow. |
| **SF Google Sheet Cell** | Read a single cell from a publicly shared Google Sheet. Specify the sheet tab by GID, row by number (1-indexed), and column by letter (A, B, C, ...). |
| **SF Google Sheet Range** | Read a range of cells from a publicly shared Google Sheet and join them into a single string with a configurable delimiter. Supports multi-row, multi-column ranges. |
**Google Sheet nodes require the sheet to be shared as "Anyone with the link can view."**
The sheet tab GID is found in the browser URL after `#gid=` when you have that tab open.
---
## Requirements
- Python 3.10+
- ComfyUI
- See `requirements.txt` for Python dependencies
- See `requirements.txt` for full Python dependency list
## License
MIT License
MIT License — see [LICENSE](LICENSE) for details.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

+124
View File
@@ -0,0 +1,124 @@
import csv
import io
import re
import requests
def col_letter_to_index(col_str: str) -> int:
"""Convert a column letter (A, B, C, ... Z, AA, AB, ...) to a 0-based index."""
col_str = col_str.strip().upper()
if not col_str or not col_str.isalpha():
raise ValueError(f"Invalid column label '{col_str}'. Use letters like A, B, C, AA, AB, etc.")
result = 0
for char in col_str:
result = result * 26 + (ord(char) - ord("A") + 1)
return result - 1
class SFGoogleSheetCell:
"""
Reads a single cell from a publicly shared Google Sheet.
Row is 1-indexed. Column uses letter notation (A, B, C, ...) matching Google Sheets.
The sheet must be shared as "Anyone with the link can view."
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"url": (
"STRING",
{
"multiline": False,
"default": "https://docs.google.com/spreadsheets/d/.../edit",
"tooltip": (
"Full Google Sheets URL. "
"The sheet must be shared: Share → Anyone with the link → Viewer."
),
},
),
"sheet_gid": (
"STRING",
{
"default": "0",
"tooltip": (
"Sheet tab GID — found in the URL after '#gid='. "
"Use 0 for the first/default sheet. "
"Example: for ...#gid=1234567890 enter 1234567890."
),
},
),
"row": (
"INT",
{
"default": 1,
"min": 1,
"step": 1,
"tooltip": "Row number starting from 1 (matches the row numbers shown in Google Sheets).",
},
),
"col": (
"STRING",
{
"default": "A",
"tooltip": "Column letter as shown in Google Sheets (A, B, C, ... Z, AA, AB, ...).",
},
),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("cell_value",)
FUNCTION = "get_cell"
CATEGORY = "Stillfront/Utils"
def get_cell(self, url: str, sheet_gid: str, row: int, col: str) -> tuple:
spreadsheet_id = self._extract_id(url)
gid = sheet_gid.strip() or "0"
col_idx = col_letter_to_index(col)
csv_url = (
f"https://docs.google.com/spreadsheets/d/{spreadsheet_id}"
f"/gviz/tq?tqx=out:csv&gid={gid}"
)
try:
response = requests.get(csv_url, timeout=15)
response.raise_for_status()
except requests.exceptions.HTTPError as e:
raise RuntimeError(
f"Could not access Google Sheet (HTTP {response.status_code}). "
f"Make sure the sheet is shared as 'Anyone with the link can view'. Error: {e}"
)
except requests.exceptions.RequestException as e:
raise RuntimeError(f"Failed to fetch Google Sheet: {e}")
content = response.content.decode("utf-8")
table = list(csv.reader(io.StringIO(content)))
row_idx = row - 1
if row_idx >= len(table):
raise ValueError(f"Row {row} is out of range — the sheet has {len(table)} rows.")
row_data = table[row_idx]
if col_idx >= len(row_data):
raise ValueError(
f"Column '{col.upper()}' is out of range — row {row} has {len(row_data)} columns."
)
return (str(row_data[col_idx]),)
def _extract_id(self, url: str) -> str:
match = re.search(r"/d/([a-zA-Z0-9-_]+)", url)
if match:
return match.group(1)
if re.match(r"^[a-zA-Z0-9-_]+$", url.strip()):
return url.strip()
raise ValueError(
"Could not extract spreadsheet ID. Please provide a full Google Sheets URL."
)
NODE_CLASS_MAPPINGS = {"SFGoogleSheetCell": SFGoogleSheetCell}
NODE_DISPLAY_NAME_MAPPINGS = {"SFGoogleSheetCell": "SF Google Sheet Cell"}
+162
View File
@@ -0,0 +1,162 @@
import csv
import io
import re
import requests
from .sf_google_sheet_cell import col_letter_to_index
class SFGoogleSheetRange:
"""
Reads a range of cells from a publicly shared Google Sheet and joins them
into a single string using a configurable delimiter.
Cells are read left to right, top to bottom.
Row numbers are 1-indexed. Columns use letter notation (A, B, C, ...) matching Google Sheets.
The sheet must be shared as "Anyone with the link can view."
"""
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"url": (
"STRING",
{
"multiline": False,
"default": "https://docs.google.com/spreadsheets/d/.../edit",
"tooltip": (
"Full Google Sheets URL. "
"The sheet must be shared: Share → Anyone with the link → Viewer."
),
},
),
"sheet_gid": (
"STRING",
{
"default": "0",
"tooltip": (
"Sheet tab GID — found in the URL after '#gid='. "
"Use 0 for the first/default sheet."
),
},
),
"start_row": (
"INT",
{
"default": 1,
"min": 1,
"step": 1,
"tooltip": "First row of the range (1 = first row in the sheet).",
},
),
"start_col": (
"STRING",
{
"default": "A",
"tooltip": "First column of the range (e.g. A, B, C, AA, ...).",
},
),
"end_row": (
"INT",
{
"default": 1,
"min": 1,
"step": 1,
"tooltip": "Last row of the range (inclusive).",
},
),
"end_col": (
"STRING",
{
"default": "A",
"tooltip": "Last column of the range (inclusive, e.g. A, B, C, AA, ...).",
},
),
"delimiter": (
"STRING",
{
"default": ", ",
"multiline": False,
"tooltip": (
"String placed between each cell value in the output. "
"Can be typed directly or connected from another string node."
),
},
),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("text",)
FUNCTION = "get_range"
CATEGORY = "Stillfront/Utils"
def get_range(
self,
url: str,
sheet_gid: str,
start_row: int,
start_col: str,
end_row: int,
end_col: str,
delimiter: str,
) -> tuple:
start_col_idx = col_letter_to_index(start_col)
end_col_idx = col_letter_to_index(end_col)
if start_row > end_row:
raise ValueError(f"start_row ({start_row}) must be ≤ end_row ({end_row}).")
if start_col_idx > end_col_idx:
raise ValueError(
f"start_col '{start_col.upper()}' must be ≤ end_col '{end_col.upper()}'."
)
table = self._fetch_sheet(url, sheet_gid)
cells = []
for r in range(start_row - 1, end_row):
if r >= len(table):
break
row_data = table[r]
for c in range(start_col_idx, end_col_idx + 1):
value = row_data[c] if c < len(row_data) else ""
cells.append(str(value))
return (delimiter.join(cells),)
def _fetch_sheet(self, url: str, sheet_gid: str) -> list:
spreadsheet_id = self._extract_id(url)
gid = sheet_gid.strip() or "0"
csv_url = (
f"https://docs.google.com/spreadsheets/d/{spreadsheet_id}"
f"/gviz/tq?tqx=out:csv&gid={gid}"
)
try:
response = requests.get(csv_url, timeout=15)
response.raise_for_status()
except requests.exceptions.HTTPError as e:
raise RuntimeError(
f"Could not access Google Sheet (HTTP {response.status_code}). "
f"Make sure the sheet is shared as 'Anyone with the link can view'. Error: {e}"
)
except requests.exceptions.RequestException as e:
raise RuntimeError(f"Failed to fetch Google Sheet: {e}")
content = response.content.decode("utf-8")
return list(csv.reader(io.StringIO(content)))
def _extract_id(self, url: str) -> str:
match = re.search(r"/d/([a-zA-Z0-9-_]+)", url)
if match:
return match.group(1)
if re.match(r"^[a-zA-Z0-9-_]+$", url.strip()):
return url.strip()
raise ValueError(
"Could not extract spreadsheet ID. Please provide a full Google Sheets URL."
)
NODE_CLASS_MAPPINGS = {"SFGoogleSheetRange": SFGoogleSheetRange}
NODE_DISPLAY_NAME_MAPPINGS = {"SFGoogleSheetRange": "SF Google Sheet Range"}
+16
View File
@@ -0,0 +1,16 @@
class SFVersionCheck:
@classmethod
def INPUT_TYPES(cls):
return {"required": {}}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("version",)
FUNCTION = "get_version"
CATEGORY = "Stillfront/Utils"
def get_version(self):
return ("SF ComfyUI Nodes v1.0.2",)
NODE_CLASS_MAPPINGS = {"SFVersionCheck": SFVersionCheck}
NODE_DISPLAY_NAME_MAPPINGS = {"SFVersionCheck": "SF Version Check"}
+2 -2
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-sf-nodes"
description = "Custom nodes for ComfyUI including LLM Chat with support for Claude, Gemini, and OpenAI models, WaveSpeed AI image/video generation, VertexAI integration, and workflow utilities"
version = "1.0.1"
version = "1.0.7"
license = { file = "LICENSE" }
dependencies = [
"requests>=2.31.0",
@@ -19,4 +19,4 @@ Repository = "https://github.com/Stillfront/comfyui-sf-nodes"
[tool.comfy]
PublisherId = "sf"
DisplayName = "SF ComfyUI Nodes"
Icon = ""
Icon = "https://raw.githubusercontent.com/Stillfront/comfyui-sf-nodes/main/assets/logo.png"