Compare commits

...
26 Commits
Author SHA1 Message Date
gokayfem 81f9031625 add none 2025-05-31 02:06:14 +03:00
Holger Will 1c1be8ae31 fix: remove opencv dependency to avoid ComfyUI ecosystem conflicts 2025-05-30 22:38:47 +02:00
Holger Will cd5c9ef258 feat: add example workflow 2025-05-30 20:42:11 +02:00
Holger Will ec8880895d fix: add missing aspect_ratio parameter to Kontext image generation methods and fix text-to-image endpoints. 2025-05-30 20:12:34 +02:00
Holger Will f8b1efa75d fix: update endpoint paths for multi quality image generation in image_node.py 2025-05-30 17:51:40 +02:00
Holger Will 975d555e29 feat: add new Flux Pro Kontext image generation nodes and update README and requirements 2025-05-30 15:56:47 +02:00
Gökay Aydoğan 4988995bf7 Merge pull request #11 from ComfyNodePRs/pyproject
Add pyproject.toml for Custom Node Registry
2025-05-28 05:55:31 +03:00
Gökay Aydoğan ee026dd560 Merge pull request #12 from ComfyNodePRs/publish
Add Github Action for Publishing to Comfy Registry
2025-05-28 05:55:22 +03:00
Gökay Aydoğan 93d6ad2875 Update pyproject.toml 2025-05-28 05:55:07 +03:00
Gökay Aydoğan c22792a581 Merge pull request #18 from venturero/new_image_nodes_2
New image nodes 2
2025-05-28 05:40:07 +03:00
Gökay Aydoğan 779e0b1028 Merge pull request #20 from gokayfem/trainers
feat: add wan and ltx trainer
2025-05-28 05:39:01 +03:00
gokayfem 2f7f43da45 feat: add wan and ltx trainer 2025-05-28 05:38:19 +03:00
semiventurero a8bdb5bc6d image_node.py file updated with ideogramv3 2025-05-25 14:14:16 +03:00
semiventurero c3fae085d6 hidream and ideogram 2025-05-25 13:56:12 +03:00
Gökay Aydoğan 1c67dda258 Merge pull request #15 from pixelworldai/main
Updated: KlingMaster/WanPro/CombinedVideoGeneration
2025-05-08 16:36:53 +03:00
pixelworld AI 96b0cd0976 docs: Add Wan Pro 2025-05-07 21:35:55 -05:00
pixelworld AI 116bfbd4e0 feat: Add Wan Pro, update Luma and Minimax endpoints, add Luma end image support 2025-05-07 21:35:29 -05:00
pixelworld AI 2797366781 feat: enhance video generation capabilities
- Add Kling Pro v1.6 node with tail image support

- Rename original Kling Pro to v1.0 for clarity

- Add Kling Master v2.0 node

- Update Combined Video Generation node:

  - Add service toggles for each provider

  - Use Kling Pro v1.6 instead of v1.0

  - Add version numbers to output names

  - Maintain concurrent processing of enabled services

- Fix API key initialization in combined node

- Add proper error handling for disabled services
2025-05-06 21:15:06 -05:00
pixelworld AI ebecad477a docs: update video generation section with new nodes and versions
- Add Kling Pro v1.0 and v1.6 nodes

- Add Kling Master v2.0 node

- Add MiniMax nodes (standard, text-to-video, subject reference)

- Add Google Veo2 node

- Add Video Upscaler node

- Add Combined Video Generation node with service toggles

- Update node descriptions and version information
2025-05-06 21:14:59 -05:00
Jacob Garner a46b9465e6 Veo2 and multivid update 2025-04-28 10:12:57 -05:00
Jacob Garner 1d2ecc823e SyncClient 2025-04-28 10:12:52 -05:00
async () = process.env.GIT_USERNAME || (await ghUser()).email && (await ghUser()).name || DIE("Missing env.GIT_USERNAME") a8c202b045 chore(publish): Add Github Action for Publishing to Comfy Registry 2025-04-26 06:00:37 +00:00
async () = process.env.GIT_USERNAME || (await ghUser()).email && (await ghUser()).name || DIE("Missing env.GIT_USERNAME") e2a41cc5ff chore(pyproject): Add pyproject.toml for Custom Node Registry 2025-04-26 06:00:37 +00:00
Gökay Aydoğan 68328f8526 Merge pull request #10 from gokayfem/sync-client
Sync client
2025-04-24 14:54:44 +03:00
pixelworldai 6a4b736773 fixed duration/aspect ratio definitions 2025-04-11 19:20:26 -05:00
pixelworldai cfd626541b Update video_node.py
Added Combined Video Generation (fal), for simultaneous Kling Pro, Luma Dream, and Minimax image2video generation.
2025-04-11 17:53:43 -05:00
8 changed files with 1280 additions and 15 deletions
+28
View File
@@ -0,0 +1,28 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- main
- master
paths:
- "pyproject.toml"
permissions:
issues: write
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'gokayfem' }}
steps:
- name: Check out code
uses: actions/checkout@v4
with:
submodules: true
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@v1
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+8
View File
@@ -160,3 +160,11 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Cursor and SpecStory
.specstory/
.cursor/
.cursorignore
.cursorindexingignore
memory-bank/
.DS_Store
+23 -2
View File
@@ -56,14 +56,35 @@ After installation and configuration, restart ComfyUI. The new nodes will be ava
- **Flux Dev (fal)**: Use the development version of Flux for image generation
- **Flux Schnell (fal)**: Fast image generation with Flux Schnell
- **Flux Pro 1.1 (fal)**: Latest version of Flux Pro for image generation
- **Flux Ultra (fal)**: Ultra-high quality image generation with advanced controls
- **Flux General (fal)**: ControlNets, Ipadapters, Loras for Flux Dev
- **Flux LoRA (fal)**: Flux with dual LoRA support for custom styles
- **Flux Pro Kontext (fal)**: Context-aware single image-to-image generation with max_quality toggle
- **Flux Pro Kontext Multi (fal)**: Multi-image composition (2-4 images) with context awareness and max_quality toggle
- **Flux Pro Kontext Text-to-Image (fal)**: Text-to-image with aspect ratio controls and max_quality toggle
- **Recraft V3 (fal)**: Professional design generation with multiple style options
- **Sana (fal)**: High-quality image synthesis with ultra-high resolution support
- **HiDream Full (fal)**: Advanced image generation with comprehensive parameter control
- **Ideogram v3 (fal)**: Advanced text-to-image generation with typography support
### Video Generation
- **Kling Video Generation (fal)**: Generate videos using the Kling model
- **Kling Pro Video Generation (fal)**: Advanced video generation with Kling Pro
- **Kling Pro v1.0 Video Generation (fal)**: Original version of Kling Pro for video generation
- **Kling Pro v1.6 Video Generation (fal)**: Latest version of Kling Pro with improved quality
- **Kling Master v2.0 Video Generation (fal)**: Advanced video generation with Kling Master
- **Runway Gen3 Image-to-Video (fal)**: Convert images to videos using Runway Gen3
- **Luma Dream Machine (fal)**: Create videos with Luma Dream Machine
- **MiniMax Video Generation (fal)**: Generate videos using MiniMax model
- **MiniMax Text-to-Video (fal)**: Create videos from text prompts using MiniMax
- **MiniMax Subject Reference (fal)**: Generate videos with subject reference using MiniMax
- **Google Veo2 Image-to-Video (fal)**: Convert images to videos using Google's Veo2 model
- **Wan Pro Image-to-Video (fal)**: High-quality video generation with Wan Pro model
- **Video Upscaler (fal)**: Upscale video quality using AI
- **Combined Video Generation (fal)**: Generate videos using multiple services simultaneously
- Supports Kling Pro v1.6, Kling Master v2.0, MiniMax, Luma, Veo2, and Wan Pro
- Each service can be individually enabled/disabled
- Wan Pro runs with safety checker enabled and automatic seed selection
- **Load Video from URL**: Load and process videos from a given URL
### Language Models (LLMs)
@@ -101,7 +122,7 @@ If you encounter any errors during installation or usage, try the following:
1. Ensure you have the latest version of ComfyUI installed
2. Update this custom node package:
```
cd custom_nodes/ComfyUI-FLUX-fal-API
cd custom_nodes/ComfyUI-fal-API
git pull
pip install -r requirements.txt
```
@@ -0,0 +1,390 @@
{
"id": "e3b1097d-0ba0-4fb1-b1f9-510d5a9ee4b1",
"revision": 0,
"last_node_id": 65,
"last_link_id": 71,
"nodes": [
{
"id": 23,
"type": "LoadImage",
"pos": [
400,
830
],
"size": [
300,
370
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
60,
64
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.32",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png",
"image"
]
},
{
"id": 63,
"type": "SaveImage",
"pos": [
1300,
1880
],
"size": [
300,
370
],
"flags": {
"collapsed": false
},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 66
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.32"
},
"widgets_values": [
"kontext-sample-t2i"
]
},
{
"id": 62,
"type": "SaveImage",
"pos": [
1310,
1330
],
"size": [
300,
370
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 65
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.32"
},
"widgets_values": [
"kontext-sample-i2i-multi"
]
},
{
"id": 24,
"type": "SaveImage",
"pos": [
1300,
830
],
"size": [
300,
370
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 22
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.32"
},
"widgets_values": [
"kontext-sample-i2i"
]
},
{
"id": 59,
"type": "FluxProKontextMulti_fal",
"pos": [
800,
1330
],
"size": [
400,
430
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "image_1",
"type": "IMAGE",
"link": 64
},
{
"name": "image_2",
"type": "IMAGE",
"link": 70
},
{
"name": "image_3",
"shape": 7,
"type": "IMAGE",
"link": null
},
{
"name": "image_4",
"shape": 7,
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
65
]
}
],
"properties": {
"aux_id": "a-und-b/ComfyUI-fal-API",
"ver": "ec8880895d86bb5c720e167585b559e03fcf9024",
"Node name for S&R": "FluxProKontextMulti_fal"
},
"widgets_values": [
"",
"1:1",
false,
3.5,
1,
"2",
"jpeg",
false,
22,
"fixed"
]
},
{
"id": 61,
"type": "LoadImage",
"pos": [
400,
1350
],
"size": [
300,
370
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
70
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.32",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png",
"image"
]
},
{
"id": 22,
"type": "FluxProKontext_fal",
"pos": [
800,
830
],
"size": [
400,
370
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 60
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
22
]
}
],
"properties": {
"aux_id": "a-und-b/ComfyUI-fal-API",
"ver": "975d555e29c2bec2e8ffa1aa33f5bdc686345024",
"Node name for S&R": "FluxProKontext_fal"
},
"widgets_values": [
"make it a 3d render",
"1:1",
false,
1,
1,
"6",
"png",
false,
41,
"fixed"
]
},
{
"id": 60,
"type": "FluxProKontextTextToImage_fal",
"pos": [
800,
1880
],
"size": [
400,
380
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
66
]
}
],
"properties": {
"aux_id": "a-und-b/ComfyUI-fal-API",
"ver": "ec8880895d86bb5c720e167585b559e03fcf9024",
"Node name for S&R": "FluxProKontextTextToImage_fal"
},
"widgets_values": [
"a childs drawing of grumpy cat",
"1:1",
false,
3.5,
1,
"2",
"jpeg",
false,
33,
"fixed"
]
}
],
"links": [
[
22,
22,
0,
24,
0,
"IMAGE"
],
[
60,
23,
0,
22,
0,
"IMAGE"
],
[
64,
23,
0,
59,
0,
"IMAGE"
],
[
65,
59,
0,
62,
0,
"IMAGE"
],
[
66,
60,
0,
63,
0,
"IMAGE"
],
[
70,
61,
0,
59,
1,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"frontendVersion": "1.18.9"
},
"version": 0.4
}
+284 -3
View File
@@ -169,6 +169,98 @@ class Recraft:
print(f"Error generating image with Recraft: {str(e)}")
return self.create_blank_image()
class HidreamFull:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"image_size": (["square_hd", "square", "portrait_4_3", "portrait_16_9", "landscape_4_3", "landscape_16_9", "custom"], {"default": "landscape_4_3"}),
"width": ("INT", {"default": 1024, "min": 512, "max": 1440, "step": 32}),
"height": ("INT", {"default": 768, "min": 512, "max": 1440, "step": 32}),
"num_inference_steps": ("INT", {"default": 28, "min": 1, "max": 100}),
"guidance_scale": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 20.0}),
"num_images": ("INT", {"default": 1, "min": 1, "max": 10}),
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2"}),
},
"optional": {
"seed": ("INT", {"default": -1}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "generate_image"
CATEGORY = "FAL/Image"
def generate_image(self, prompt, image_size, width, height, num_inference_steps, guidance_scale, num_images, safety_tolerance, seed=-1):
arguments = {
"prompt": prompt,
"num_inference_steps": num_inference_steps,
"guidance_scale": guidance_scale,
"num_images": num_images,
"safety_tolerance": safety_tolerance
}
if image_size == "custom":
arguments["image_size"] = {"width": width, "height": height}
else:
arguments["image_size"] = image_size
if seed != -1:
arguments["seed"] = seed
try:
handler = fal_client.submit("fal-ai/hidream-i1-full", arguments=arguments)
result = handler.get()
return self.process_result(result)
except Exception as e:
print(f"Error generating image with Hidream Full: {str(e)}")
return self.create_blank_image()
class Ideogramv3:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"image_size": (["square_hd", "square", "portrait_4_3", "portrait_16_9", "landscape_4_3", "landscape_16_9", "custom"], {"default": "landscape_4_3"}),
"width": ("INT", {"default": 1024, "min": 512, "max": 1440, "step": 32}),
"height": ("INT", {"default": 768, "min": 512, "max": 1440, "step": 32}),
"num_inference_steps": ("INT", {"default": 28, "min": 1, "max": 100}),
"guidance_scale": ("FLOAT", {"default": 3.5, "min": 0.0, "max": 20.0}),
"num_images": ("INT", {"default": 1, "min": 1, "max": 10}),
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2"}),
},
"optional": {
"seed": ("INT", {"default": -1}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "generate_image"
CATEGORY = "FAL/Image"
def generate_image(self, prompt, image_size, width, height, num_inference_steps, guidance_scale, num_images, safety_tolerance, seed=-1):
arguments = {
"prompt": prompt,
"num_inference_steps": num_inference_steps,
"guidance_scale": guidance_scale,
"num_images": num_images,
"safety_tolerance": safety_tolerance
}
if image_size == "custom":
arguments["image_size"] = {"width": width, "height": height}
else:
arguments["image_size"] = image_size
if seed != -1:
arguments["seed"] = seed
try:
handler = fal_client.submit("fal-ai/ideogram/v3", arguments=arguments)
result = handler.get()
return self.process_result(result)
except Exception as e:
print(f"Error generating image with Ideogramv3: {str(e)}")
return self.create_blank_image()
class FluxPro:
@classmethod
def INPUT_TYPES(cls):
@@ -449,7 +541,7 @@ class FluxLora:
return self.process_result(result)
except Exception as e:
print(f"Error generating image with FluxLora: {str(e)}")
return self.create_blank_image()
return self.create_blank_image()
class FluxGeneral:
@classmethod
@@ -631,6 +723,185 @@ class FluxGeneral:
result = mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])).movedim(1, -1).expand(-1, -1, -1, 3)
return result
class FluxProKontext:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"image": ("IMAGE",),
},
"optional": {
"aspect_ratio": ([None, "21:9", "16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "9:21"], {"default": None}),
"max_quality": ("BOOLEAN", {"default": False}),
"guidance_scale": ("FLOAT", {"default": 3.5, "min": 1.0, "max": 20.0, "step": 0.1}),
"num_images": ("INT", {"default": 1, "min": 1, "max": 4}),
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2"}),
"output_format": (["jpeg", "png"], {"default": "jpeg"}),
"sync_mode": ("BOOLEAN", {"default": False}),
"seed": ("INT", {"default": 0, "min": 0, "max": 2**32-1}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "generate_image"
CATEGORY = "FAL/Image"
def generate_image(self, prompt, image, aspect_ratio="1:1", max_quality=False, guidance_scale=3.5, num_images=1, safety_tolerance="2", output_format="jpeg", sync_mode=False, seed=0):
# Upload the input image to get URL
image_url = upload_image(image)
if not image_url:
model_name = "Flux Pro Kontext Max" if max_quality else "Flux Pro Kontext"
print(f"Error: Failed to upload image for {model_name}")
return self.create_blank_image()
# Dynamic endpoint selection based on max_quality toggle
endpoint = "fal-ai/flux-pro/kontext/max" if max_quality else "fal-ai/flux-pro/kontext"
arguments = {
"prompt": prompt,
"image_url": image_url,
"aspect_ratio": aspect_ratio,
"guidance_scale": guidance_scale,
"num_images": num_images,
"safety_tolerance": safety_tolerance,
"output_format": output_format,
"sync_mode": sync_mode
}
if seed > 0:
arguments["seed"] = seed
try:
handler = fal_client.submit(endpoint, arguments=arguments)
result = handler.get()
return self.process_result(result)
except Exception as e:
model_name = "Flux Pro Kontext Max" if max_quality else "Flux Pro Kontext"
print(f"Error generating image with {model_name}: {str(e)}")
return self.create_blank_image()
class FluxProKontextMulti:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"image_1": ("IMAGE",),
"image_2": ("IMAGE",),
},
"optional": {
"image_3": ("IMAGE",),
"image_4": ("IMAGE",),
"aspect_ratio": ([None, "21:9", "16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "9:21"], {"default": None}),
"max_quality": ("BOOLEAN", {"default": False}),
"guidance_scale": ("FLOAT", {"default": 3.5, "min": 1.0, "max": 20.0, "step": 0.1}),
"num_images": ("INT", {"default": 1, "min": 1, "max": 4}),
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2"}),
"output_format": (["jpeg", "png"], {"default": "jpeg"}),
"sync_mode": ("BOOLEAN", {"default": False}),
"seed": ("INT", {"default": 0, "min": 0, "max": 2**32-1}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "generate_image"
CATEGORY = "FAL/Image"
def generate_image(self, prompt, image_1, image_2, image_3=None, image_4=None, aspect_ratio="1:1", max_quality=False, guidance_scale=3.5, num_images=1, safety_tolerance="2", output_format="jpeg", sync_mode=False, seed=0):
# Upload all provided images
image_urls = []
for i, img in enumerate([image_1, image_2, image_3, image_4], 1):
if img is not None:
url = upload_image(img)
if url:
image_urls.append(url)
else:
model_name = "Flux Pro Kontext Max Multi" if max_quality else "Flux Pro Kontext Multi"
print(f"Error: Failed to upload image {i} for {model_name}")
return self.create_blank_image()
if len(image_urls) < 2:
model_name = "Flux Pro Kontext Max Multi" if max_quality else "Flux Pro Kontext Multi"
print(f"Error: At least 2 images required for {model_name}")
return self.create_blank_image()
# Dynamic endpoint selection based on max_quality toggle
endpoint = "fal-ai/flux-pro/kontext/max/multi" if max_quality else "fal-ai/flux-pro/kontext/multi"
arguments = {
"prompt": prompt,
"image_urls": image_urls,
"aspect_ratio": aspect_ratio,
"guidance_scale": guidance_scale,
"num_images": num_images,
"safety_tolerance": safety_tolerance,
"output_format": output_format,
"sync_mode": sync_mode
}
if seed > 0:
arguments["seed"] = seed
try:
handler = fal_client.submit(endpoint, arguments=arguments)
result = handler.get()
return self.process_result(result)
except Exception as e:
model_name = "Flux Pro Kontext Max Multi" if max_quality else "Flux Pro Kontext Multi"
print(f"Error generating image with {model_name}: {str(e)}")
return self.create_blank_image()
class FluxProKontextTextToImage:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
},
"optional": {
"aspect_ratio": (["21:9", "16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16", "9:21"], {"default": "1:1"}),
"max_quality": ("BOOLEAN", {"default": False}),
"guidance_scale": ("FLOAT", {"default": 3.5, "min": 1.0, "max": 20.0, "step": 0.1}),
"num_images": ("INT", {"default": 1, "min": 1, "max": 4}),
"safety_tolerance": (["1", "2", "3", "4", "5", "6"], {"default": "2"}),
"output_format": (["jpeg", "png"], {"default": "jpeg"}),
"sync_mode": ("BOOLEAN", {"default": False}),
"seed": ("INT", {"default": 0, "min": 0, "max": 2**32-1}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "generate_image"
CATEGORY = "FAL/Image"
def generate_image(self, prompt, aspect_ratio="1:1", max_quality=False, guidance_scale=3.5, num_images=1, safety_tolerance="2", output_format="jpeg", sync_mode=False, seed=0):
# Dynamic endpoint selection based on max_quality toggle
endpoint = "fal-ai/flux-pro/kontext/max/text-to-image" if max_quality else "fal-ai/flux-pro/kontext/text-to-image"
arguments = {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"guidance_scale": guidance_scale,
"num_images": num_images,
"safety_tolerance": safety_tolerance,
"output_format": output_format,
"sync_mode": sync_mode
}
if seed > 0:
arguments["seed"] = seed
try:
handler = fal_client.submit(endpoint, arguments=arguments)
result = handler.get()
return self.process_result(result)
except Exception as e:
model_name = "Flux Pro Kontext Max Text-to-Image" if max_quality else "Flux Pro Kontext Text-to-Image"
print(f"Error generating image with {model_name}: {str(e)}")
return self.create_blank_image()
# Common methods for all classes
def process_result(self, result):
images = []
@@ -656,12 +927,14 @@ def create_blank_image(self):
return (img_tensor,)
# Add common methods to all classes
for cls in [FluxPro, FluxDev, FluxSchnell, FluxPro11, FluxUltra, FluxGeneral, FluxLora, Recraft, Sana]:
for cls in [Ideogramv3,HidreamFull, FluxPro, FluxDev, FluxSchnell, FluxPro11, FluxUltra, FluxGeneral, FluxLora, Recraft, Sana, FluxProKontext, FluxProKontextMulti, FluxProKontextTextToImage]:
cls.process_result = process_result
cls.create_blank_image = create_blank_image
# Node class mappings
NODE_CLASS_MAPPINGS = {
"Ideogramv3_fal":Ideogramv3,
"Hidreamfull_fal": HidreamFull,
"FluxPro_fal": FluxPro,
"FluxDev_fal": FluxDev,
"FluxSchnell_fal": FluxSchnell,
@@ -671,10 +944,15 @@ NODE_CLASS_MAPPINGS = {
"FluxLora_fal": FluxLora,
"Recraft_fal": Recraft,
"Sana_fal": Sana,
"FluxProKontext_fal": FluxProKontext,
"FluxProKontextMulti_fal": FluxProKontextMulti,
"FluxProKontextTextToImage_fal": FluxProKontextTextToImage,
}
# Node display name mappings
NODE_DISPLAY_NAME_MAPPINGS = {
"Ideogramv3_fal": "Ideogramv3 (fal)",
"Hidreamfull_fal": "HidreamFull (fal)",
"FluxPro_fal": "Flux Pro (fal)",
"FluxDev_fal": "Flux Dev (fal)",
"FluxSchnell_fal": "Flux Schnell (fal)",
@@ -683,5 +961,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"FluxGeneral_fal": "Flux General (fal)",
"FluxLora_fal": "Flux LoRA (fal)",
"Recraft_fal": "Recraft V3 (fal)",
"Sana_fal": "Sana (fal)"
"Sana_fal": "Sana (fal)",
"FluxProKontext_fal": "Flux Pro Kontext (fal)",
"FluxProKontextMulti_fal": "Flux Pro Kontext Multi (fal)",
"FluxProKontextTextToImage_fal": "Flux Pro Kontext Text-to-Image (fal)",
}
+129
View File
@@ -163,14 +163,143 @@ class HunyuanVideoLoraTrainerNode:
print(f"Error during LoRA training: {str(e)}")
return ("Error: Training failed.", "")
class WanLoraTrainerNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"training_data_url": ("STRING", {"default": ""}),
"number_of_steps": ("INT", {"default": 400, "min": 5, "max": 10000, "step": 1}),
"learning_rate": ("FLOAT", {"default": 0.0002, "min": 0.00001, "max": 0.01}),
},
"optional": {
"trigger_phrase": ("STRING", {"default": ""}),
"auto_scale_input": ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("lora_file_url",)
FUNCTION = "train_lora"
CATEGORY = "FAL/Training"
def train_lora(self, training_data_url, number_of_steps, learning_rate, trigger_phrase="", auto_scale_input=True):
try:
if not training_data_url:
return ("Error: No training data URL provided.",)
# Prepare arguments for the API
arguments = {
"training_data_url": training_data_url,
"number_of_steps": number_of_steps,
"learning_rate": learning_rate,
"auto_scale_input": auto_scale_input
}
if trigger_phrase:
arguments["trigger_phrase"] = trigger_phrase
# Submit training job
handler = fal_client.submit("fal-ai/wan-trainer", arguments=arguments)
result = handler.get()
lora_url = result["lora_file"]["url"]
return (lora_url,)
except Exception as e:
print(f"Error during LoRA training: {str(e)}")
return ("Error: Training failed.",)
class LtxVideoTrainerNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"training_data_url": ("STRING", {"default": ""}),
"rank": (["8", "16", "32", "64", "128"], {"default": "128"}),
"number_of_steps": ("INT", {"default": 1000, "min": 100, "max": 10000, "step": 1}),
"number_of_frames": ("INT", {"default": 81, "min": 1, "max": 1000}),
"frame_rate": ("INT", {"default": 25, "min": 1, "max": 60}),
"resolution": (["low", "medium", "high"], {"default": "medium"}),
"aspect_ratio": (["16:9", "1:1", "9:16"], {"default": "1:1"}),
"learning_rate": ("FLOAT", {"default": 0.0002, "min": 0.00001, "max": 0.01}),
},
"optional": {
"trigger_phrase": ("STRING", {"default": ""}),
"auto_scale_input": ("BOOLEAN", {"default": False}),
"split_input_into_scenes": ("BOOLEAN", {"default": True}),
"split_input_duration_threshold": ("FLOAT", {"default": 30.0, "min": 1.0, "max": 300.0}),
"validation_negative_prompt": ("STRING", {"default": "blurry, low quality, bad quality, out of focus"}),
"validation_number_of_frames": ("INT", {"default": 81, "min": 1, "max": 1000}),
"validation_resolution": (["low", "medium", "high"], {"default": "high"}),
"validation_aspect_ratio": (["16:9", "1:1", "9:16"], {"default": "1:1"}),
"validation_reverse": ("BOOLEAN", {"default": False}),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("lora_file_url",)
FUNCTION = "train_lora"
CATEGORY = "FAL/Training"
def train_lora(self, training_data_url, rank, number_of_steps, number_of_frames, frame_rate,
resolution, aspect_ratio, learning_rate, trigger_phrase="", auto_scale_input=False,
split_input_into_scenes=True, split_input_duration_threshold=30.0,
validation_negative_prompt="blurry, low quality, bad quality, out of focus",
validation_number_of_frames=81, validation_resolution="high",
validation_aspect_ratio="1:1", validation_reverse=False):
try:
if not training_data_url:
return ("Error: No training data URL provided.",)
# Prepare arguments for the API
arguments = {
"training_data_url": training_data_url,
"rank": int(rank),
"number_of_steps": number_of_steps,
"number_of_frames": number_of_frames,
"frame_rate": frame_rate,
"resolution": resolution,
"aspect_ratio": aspect_ratio,
"learning_rate": learning_rate,
"auto_scale_input": auto_scale_input,
"split_input_into_scenes": split_input_into_scenes,
"split_input_duration_threshold": split_input_duration_threshold,
"validation_negative_prompt": validation_negative_prompt,
"validation_number_of_frames": validation_number_of_frames,
"validation_resolution": validation_resolution,
"validation_aspect_ratio": validation_aspect_ratio,
"validation_reverse": validation_reverse
}
if trigger_phrase:
arguments["trigger_phrase"] = trigger_phrase
# Submit training job
handler = fal_client.submit("fal-ai/ltx-video-trainer", arguments=arguments)
result = handler.get()
lora_url = result["lora_file"]["url"]
return (lora_url,)
except Exception as e:
print(f"Error during LoRA training: {str(e)}")
return ("Error: Training failed.",)
# Node class mappings
NODE_CLASS_MAPPINGS = {
"FluxLoraTrainer_fal": FluxLoraTrainerNode,
"HunyuanVideoLoraTrainer_fal": HunyuanVideoLoraTrainerNode,
"WanLoraTrainer_fal": WanLoraTrainerNode,
"LtxVideoTrainer_fal": LtxVideoTrainerNode,
}
# Node display name mappings
NODE_DISPLAY_NAME_MAPPINGS = {
"FluxLoraTrainer_fal": "Flux LoRA Trainer (fal)",
"HunyuanVideoLoraTrainer_fal": "Hunyuan Video LoRA Trainer (fal)",
"WanLoraTrainer_fal": "WAN LoRA Trainer (fal)",
"LtxVideoTrainer_fal": "LTX Video LoRA Trainer (fal)",
}
+403 -10
View File
@@ -1,5 +1,6 @@
import os
import configparser
from fal_client import submit, upload_file, AsyncClient
import torch
from PIL import Image
import tempfile
@@ -7,6 +8,8 @@ import numpy as np
import requests
from urllib.parse import urlparse
import cv2
import asyncio
import aiohttp
from fal_client.client import SyncClient
current_dir = os.path.dirname(os.path.abspath(__file__))
@@ -91,7 +94,7 @@ class MiniMaxNode:
"image_url": image_url,
}
handler = fal_client.submit("fal-ai/minimax-video/image-to-video", arguments=arguments)
handler = fal_client.submit("fal-ai/minimax/video-01-live/image-to-video", arguments=arguments)
result = handler.get()
video_url = result["video"]["url"]
return (video_url,)
@@ -169,7 +172,113 @@ class KlingNode:
print(f"Error generating video: {str(e)}")
return ("Error: Unable to generate video.",)
class KlingProNode:
class KlingPro10Node:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"duration": (["5", "10"], {"default": "5"}),
"aspect_ratio": (["16:9", "9:16", "1:1"], {"default": "16:9"}),
},
"optional": {
"image": ("IMAGE",),
"tail_image": ("IMAGE",),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_video"
CATEGORY = "FAL/VideoGeneration"
def generate_video(self, prompt, duration, aspect_ratio, image=None, tail_image=None):
arguments = {
"prompt": prompt,
"duration": duration,
"aspect_ratio": aspect_ratio,
}
try:
if image is not None:
image_url = upload_image(image)
if image_url:
arguments["image_url"] = image_url
# Handle tail image if provided
if tail_image is not None:
tail_image_url = upload_image(tail_image)
if tail_image_url:
arguments["tail_image_url"] = tail_image_url
else:
return ("Error: Unable to upload tail image.",)
handler = fal_client.submit("fal-ai/kling-video/v1/pro/image-to-video", arguments=arguments)
else:
return ("Error: Unable to upload image.",)
else:
handler = fal_client.submit("fal-ai/kling-video/v1/pro/text-to-video", arguments=arguments)
result = handler.get()
video_url = result["video"]["url"]
return (video_url,)
except Exception as e:
print(f"Error generating video: {str(e)}")
return ("Error: Unable to generate video.",)
class KlingPro16Node:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"duration": (["5", "10"], {"default": "5"}),
"aspect_ratio": (["16:9", "9:16", "1:1"], {"default": "16:9"}),
},
"optional": {
"image": ("IMAGE",),
"tail_image": ("IMAGE",),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_video"
CATEGORY = "FAL/VideoGeneration"
def generate_video(self, prompt, duration, aspect_ratio, image=None, tail_image=None):
arguments = {
"prompt": prompt,
"duration": duration,
"aspect_ratio": aspect_ratio,
}
try:
if image is not None:
image_url = upload_image(image)
if image_url:
arguments["image_url"] = image_url
# Handle tail image if provided
if tail_image is not None:
tail_image_url = upload_image(tail_image)
if tail_image_url:
arguments["tail_image_url"] = tail_image_url
else:
return ("Error: Unable to upload tail image.",)
handler = fal_client.submit("fal-ai/kling-video/v1.6/pro/image-to-video", arguments=arguments)
else:
return ("Error: Unable to upload image.",)
else:
handler = fal_client.submit("fal-ai/kling-video/v1.6/pro/text-to-video", arguments=arguments)
result = handler.get()
video_url = result["video"]["url"]
return (video_url,)
except Exception as e:
print(f"Error generating video: {str(e)}")
return ("Error: Unable to generate video.",)
class KlingMasterNode:
@classmethod
def INPUT_TYPES(cls):
return {
@@ -199,11 +308,11 @@ class KlingProNode:
image_url = upload_image(image)
if image_url:
arguments["image_url"] = image_url
handler = fal_client.submit("fal-ai/kling-video/v1/pro/image-to-video", arguments=arguments)
handler = fal_client.submit("fal-ai/kling-video/v2/master/image-to-video", arguments=arguments)
else:
return ("Error: Unable to upload image.",)
else:
handler = fal_client.submit("fal-ai/kling-video/v1/pro/text-to-video", arguments=arguments)
handler = fal_client.submit("fal-ai/kling-video/v2/master/text-to-video", arguments=arguments)
result = handler.get()
video_url = result["video"]["url"]
@@ -258,6 +367,7 @@ class LumaDreamMachineNode:
},
"optional": {
"image": ("IMAGE",),
"end_image": ("IMAGE",),
"loop": ("BOOLEAN", {"default": False}),
},
}
@@ -266,7 +376,7 @@ class LumaDreamMachineNode:
FUNCTION = "generate_video"
CATEGORY = "FAL/VideoGeneration"
def generate_video(self, prompt, mode, aspect_ratio, image=None, loop=False):
def generate_video(self, prompt, mode, aspect_ratio, image=None, end_image=None, loop=False):
arguments = {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
@@ -281,9 +391,17 @@ class LumaDreamMachineNode:
if not image_url:
return ("Error: Unable to upload image.",)
arguments["image_url"] = image_url
endpoint = "fal-ai/luma-dream-machine/image-to-video"
if end_image is not None:
end_image_url = upload_image(end_image)
if end_image_url:
arguments["end_image_url"] = end_image_url
else:
return ("Error: Unable to upload end image.",)
endpoint = "fal-ai/luma-dream-machine/ray-2/image-to-video"
else:
endpoint = "fal-ai/luma-dream-machine"
endpoint = "fal-ai/luma-dream-machine/ray-2"
handler = fal_client.submit(endpoint, arguments=arguments)
result = handler.get()
@@ -500,10 +618,279 @@ class Veo2ImageToVideoNode:
print(f"Error generating video: {str(e)}")
return ("Error: Unable to generate video.",)
class CombinedVideoGenerationNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"image": ("IMAGE",),
"kling_duration": (["5", "10"], {"default": "5"}),
"kling_luma_aspect_ratio": (["16:9", "9:16", "1:1"], {"default": "16:9"}),
"luma_loop": ("BOOLEAN", {"default": False}),
"veo2_aspect_ratio": (["auto", "auto_prefer_portrait", "16:9", "9:16"], {"default": "auto"}),
"veo2_duration": (["5s", "6s", "7s", "8s"], {"default": "5s"}),
"enable_klingpro": ("BOOLEAN", {"default": True}),
"enable_klingmaster": ("BOOLEAN", {"default": True}),
"enable_minimax": ("BOOLEAN", {"default": True}),
"enable_luma": ("BOOLEAN", {"default": True}),
"enable_veo2": ("BOOLEAN", {"default": True}),
"enable_wanpro": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
RETURN_NAMES = ("klingpro_v1.6_video", "klingmaster_v2.0_video", "minimax_video", "luma_video", "veo2_video", "wanpro_video")
FUNCTION = "generate_videos"
CATEGORY = "FAL/VideoGeneration"
async def generate_klingpro_video(self, client, prompt, image_url, kling_duration, kling_luma_aspect_ratio):
try:
arguments = {
"prompt": prompt,
"image_url": image_url,
"duration": kling_duration,
"aspect_ratio": kling_luma_aspect_ratio,
}
handler = await client.submit("fal-ai/kling-video/v1.6/pro/image-to-video", arguments=arguments)
while True:
result = await handler.get()
if "video" in result and "url" in result["video"]:
return result["video"]["url"]
elif result.get("status") == "FAILED":
raise Exception("Video generation failed")
await asyncio.sleep(1)
except Exception as e:
print(f"Error generating KlingPro video: {str(e)}")
return "Error: Unable to generate KlingPro video."
async def generate_klingmaster_video(self, client, prompt, image_url, kling_duration, kling_luma_aspect_ratio):
try:
arguments = {
"prompt": prompt,
"image_url": image_url,
"duration": kling_duration,
"aspect_ratio": kling_luma_aspect_ratio,
}
handler = await client.submit("fal-ai/kling-video/v2/master/image-to-video", arguments=arguments)
while True:
result = await handler.get()
if "video" in result and "url" in result["video"]:
return result["video"]["url"]
elif result.get("status") == "FAILED":
raise Exception("Video generation failed")
await asyncio.sleep(1)
except Exception as e:
print(f"Error generating KlingMaster video: {str(e)}")
return "Error: Unable to generate KlingMaster video."
async def generate_minimax_video(self, client, prompt, image_url):
try:
arguments = {
"prompt": prompt,
"image_url": image_url,
}
handler = await client.submit("fal-ai/minimax/video-01-live/image-to-video", arguments=arguments)
while True:
result = await handler.get()
if "video" in result and "url" in result["video"]:
return result["video"]["url"]
elif result.get("status") == "FAILED":
raise Exception("Video generation failed")
await asyncio.sleep(1)
except Exception as e:
print(f"Error generating MiniMax video: {str(e)}")
return "Error: Unable to generate MiniMax video."
async def generate_luma_video(self, client, prompt, image_url, kling_luma_aspect_ratio, luma_loop):
try:
arguments = {
"prompt": prompt,
"image_url": image_url,
"aspect_ratio": kling_luma_aspect_ratio,
"loop": luma_loop,
}
handler = await client.submit("fal-ai/luma-dream-machine/ray-2/image-to-video", arguments=arguments)
while True:
result = await handler.get()
if "video" in result and "url" in result["video"]:
return result["video"]["url"]
elif result.get("status") == "FAILED":
raise Exception("Video generation failed")
await asyncio.sleep(1)
except Exception as e:
print(f"Error generating Luma video: {str(e)}")
return "Error: Unable to generate Luma video."
async def generate_veo2_video(self, client, prompt, image_url, aspect_ratio, duration):
try:
arguments = {
"prompt": prompt,
"image_url": image_url,
"aspect_ratio": aspect_ratio,
"duration": duration,
}
handler = await client.submit("fal-ai/veo2/image-to-video", arguments=arguments)
while True:
result = await handler.get()
if "video" in result and "url" in result["video"]:
return result["video"]["url"]
elif result.get("status") == "FAILED":
raise Exception("Video generation failed")
await asyncio.sleep(1)
except Exception as e:
print(f"Error generating Veo2 video: {str(e)}")
return "Error: Unable to generate Veo2 video."
async def generate_wanpro_video(self, client, prompt, image_url):
try:
arguments = {
"prompt": prompt,
"image_url": image_url,
"enable_safety_checker": True,
"seed": None # Let the API choose a random seed
}
handler = await client.submit("fal-ai/wan-pro/image-to-video", arguments=arguments)
while True:
result = await handler.get()
if "video" in result and "url" in result["video"]:
return result["video"]["url"]
elif result.get("status") == "FAILED":
raise Exception("Video generation failed")
await asyncio.sleep(1)
except Exception as e:
print(f"Error generating Wan Pro video: {str(e)}")
return "Error: Unable to generate Wan Pro video."
async def generate_all_videos(self, prompt, image_url, kling_duration, kling_luma_aspect_ratio, luma_loop, veo2_aspect_ratio, veo2_duration, enable_klingpro, enable_klingmaster, enable_minimax, enable_luma, enable_veo2, enable_wanpro):
try:
tasks = []
results = [None] * 6 # Initialize results list with None values
# Create async client with the same key as the sync client
client = AsyncClient(key=fal_key)
# Add tasks based on enabled services
if enable_klingpro:
tasks.append(self.generate_klingpro_video(client, prompt, image_url, kling_duration, kling_luma_aspect_ratio))
else:
tasks.append(None)
if enable_klingmaster:
tasks.append(self.generate_klingmaster_video(client, prompt, image_url, kling_duration, kling_luma_aspect_ratio))
else:
tasks.append(None)
if enable_minimax:
tasks.append(self.generate_minimax_video(client, prompt, image_url))
else:
tasks.append(None)
if enable_luma:
tasks.append(self.generate_luma_video(client, prompt, image_url, kling_luma_aspect_ratio, luma_loop))
else:
tasks.append(None)
if enable_veo2:
tasks.append(self.generate_veo2_video(client, prompt, image_url, veo2_aspect_ratio, veo2_duration))
else:
tasks.append(None)
if enable_wanpro:
tasks.append(self.generate_wanpro_video(client, prompt, image_url))
else:
tasks.append(None)
# Filter out None tasks and execute them
valid_tasks = [task for task in tasks if task is not None]
if valid_tasks:
completed_results = await asyncio.gather(*valid_tasks)
# Place results in their correct positions
result_index = 0
for i, task in enumerate(tasks):
if task is not None:
results[i] = completed_results[result_index]
result_index += 1
else:
results[i] = "Service disabled"
return results
except Exception as e:
print(f"Error in generate_all_videos: {str(e)}")
return ["Error: Unable to generate videos."] * 6
def generate_videos(self, prompt, image, kling_duration, kling_luma_aspect_ratio, luma_loop, veo2_aspect_ratio, veo2_duration, enable_klingpro, enable_klingmaster, enable_minimax, enable_luma, enable_veo2, enable_wanpro):
try:
# Upload image once to be used by all services
image_url = upload_image(image)
if not image_url:
return ("Error: Unable to upload image.",) * 6
# Create event loop for async operations
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
# Run all video generations concurrently
results = loop.run_until_complete(
self.generate_all_videos(prompt, image_url, kling_duration, kling_luma_aspect_ratio, luma_loop, veo2_aspect_ratio, veo2_duration, enable_klingpro, enable_klingmaster, enable_minimax, enable_luma, enable_veo2, enable_wanpro)
)
loop.close()
return tuple(results)
except Exception as e:
print(f"Error in combined video generation: {str(e)}")
return ("Error: Unable to generate videos.",) * 6
class WanProNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"prompt": ("STRING", {"default": "", "multiline": True}),
"image": ("IMAGE",),
},
"optional": {
"seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}),
"enable_safety_checker": ("BOOLEAN", {"default": True}),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "generate_video"
CATEGORY = "FAL/VideoGeneration"
def generate_video(self, prompt, image, seed=0, enable_safety_checker=True):
try:
image_url = upload_image(image)
if not image_url:
return ("Error: Unable to upload image.",)
arguments = {
"prompt": prompt,
"image_url": image_url,
"enable_safety_checker": enable_safety_checker,
}
# Only add seed if it's not 0 (default)
if seed != 0:
arguments["seed"] = seed
handler = fal_client.submit("fal-ai/wan-pro/image-to-video", arguments=arguments)
result = handler.get()
video_url = result["video"]["url"]
return (video_url,)
except Exception as e:
print(f"Error generating video: {str(e)}")
return ("Error: Unable to generate video.",)
# Update Node class mappings
NODE_CLASS_MAPPINGS = {
"Kling_fal": KlingNode,
"KlingPro_fal": KlingProNode,
"KlingPro10_fal": KlingPro10Node,
"KlingPro16_fal": KlingPro16Node,
"KlingMaster_fal": KlingMasterNode,
"RunwayGen3_fal": RunwayGen3Node,
"LumaDreamMachine_fal": LumaDreamMachineNode,
"LoadVideoURL": LoadVideoURL,
@@ -511,13 +898,17 @@ NODE_CLASS_MAPPINGS = {
"MiniMaxTextToVideo_fal": MiniMaxTextToVideoNode,
"MiniMaxSubjectReference_fal": MiniMaxSubjectReferenceNode,
"VideoUpscaler_fal": VideoUpscalerNode,
"CombinedVideoGeneration_fal": CombinedVideoGenerationNode,
"Veo2ImageToVideo_fal": Veo2ImageToVideoNode,
"WanPro_fal": WanProNode,
}
# Update Node display name mappings
NODE_DISPLAY_NAME_MAPPINGS = {
"Kling_fal": "Kling Video Generation (fal)",
"KlingPro_fal": "Kling Pro Video Generation (fal)",
"KlingPro10_fal": "Kling Pro v1.0 Video Generation (fal)",
"KlingPro16_fal": "Kling Pro v1.6 Video Generation (fal)",
"KlingMaster_fal": "Kling Master v2.0 Video Generation (fal)",
"RunwayGen3_fal": "Runway Gen3 Image-to-Video (fal)",
"LumaDreamMachine_fal": "Luma Dream Machine (fal)",
"LoadVideoURL": "Load Video from URL",
@@ -525,5 +916,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"MiniMaxTextToVideo_fal": "MiniMax Text-to-Video (fal)",
"MiniMaxSubjectReference_fal": "MiniMax Subject Reference (fal)",
"VideoUpscaler_fal": "Video Upscaler (fal)",
"CombinedVideoGeneration_fal": "Combined Video Generation (fal)",
"Veo2ImageToVideo_fal": "Google Veo2 Image-to-Video (fal)",
}
"WanPro_fal": "Wan Pro Image-to-Video (fal)",
}
+15
View File
@@ -0,0 +1,15 @@
[project]
name = "fal-api"
description = "Custom nodes for using fal API. Video generation with Kling, Runway, Luma. Image generation with Flux. LLMs and VLMs OpenAI, Claude, Llama and Gemini."
version = "1.0.0"
license = {file = "LICENSE"}
dependencies = ["fal-client", "torch"]
[project.urls]
Repository = "https://github.com/gokayfem/ComfyUI-fal-API"
# Used by Comfy Registry https://comfyregistry.org
[tool.comfy]
PublisherId = "gokayfem"
DisplayName = "ComfyUI-fal-API"
Icon = ""