Compare commits

..
Author SHA1 Message Date
KarrixLee a4e3bc2571 bump version to v2.3.9 2025-10-10 21:27:38 +08:00
KarrixLee bf8e2a17b9 Enhance node handling in send_json_override: normalize dotted display IDs and improve logging for unknown nodes 2025-10-10 21:14:20 +08:00
KarrixLee 60c93b65f4 bump version to v2.3.8 2025-09-22 18:32:27 +08:00
KarrixLee 5d2ade4fd4 Karrix/add remove metadata boolean in save image (#109)
* fix: remove metadata in output node

* set to true

* refactor
2025-09-22 18:18:40 +08:00
BennyKok 1c0a4d5950 Update README with additional resources and recruitment note 2025-09-17 07:05:14 -07:00
bennykok 1a8e089c7d version bump 2025-09-12 10:37:45 -07:00
bennykok 557799cdb1 fix 2025-09-11 23:56:33 -07:00
bennykok d5f52131db fix 2025-09-11 23:53:26 -07:00
bennykok 755399a255 fix: symlink 2025-09-11 23:44:38 -07:00
bennykok ee3717b4b3 any file + psd psb support 2025-09-11 23:31:28 -07:00
KarrixLee c13828af56 fix: comfy manager error, bump to v2.3.6 2025-09-11 04:13:36 +08:00
KarrixLee ffda951cfa Bump version to 2.3.5 in Manager 2025-09-10 15:14:56 +08:00
KarrixLee ac0d8ba725 Bump version to 2.3.5 in Manager (#107)
* init

* tweak

* add: model display

* test

* add: real logic to upload model

* fix native mode

* tweak again

* add: workflow version control

* fix: versioning display

* refactor: remove unnecessary code
2025-09-10 15:13:04 +08:00
bennykok 31c38ddb9b version bump 2025-08-29 15:48:42 -07:00
bennykok 2f2c63fa11 proxy interrupt request 2025-08-29 15:47:54 -07:00
BennyKok 7675c5ba90 Update README with demo link and recruitment note
Added a link to the latest local demo and a call for team members.
2025-08-29 13:03:39 -07:00
bennykok 7d65d23f84 fix: add null check in findIndex to prevent TypeError in context menu
- Fixed TypeError when accessing 'content' property of null/undefined array elements
- Removed empty options.push() that was adding undefined to the array
- Bump version to 2.3.3
2025-08-26 18:34:20 -07:00
KarrixLee a91edf6371 Fix serverless machine (#106)
* refresh the machine list automatically

* update ui

* sync non local workflow

* Bump CD ver to 2.3.2 in Manager
2025-08-26 17:38:34 +08:00
KarrixLee 967709dd07 Bump CD ver to 2.3.1 in Manager 2025-08-21 19:07:55 +08:00
KarrixLee 07a827d56b Local: Sync serverless machine (#105)
* initt: add machine management functionality

- Implemented a new endpoint in custom_routes.py to retrieve machine details by ID.
- Introduced machine-manager.js to handle machine data fetching, display, and local storage management.
- Updated index.js to initialize the machine manager and display machine information in the UI.
- Adjusted workflow-list.js to modify the layout for better integration with the new machine management features.

These changes enhance the application's capability to manage and display serverless machine information effectively.

* refactor: enhance machine management UI and functionality

- Removed console error logs for improved user experience during initialization.
- Updated the display styles for "No machine found" and error messages to enhance visual clarity.
- Added buttons for adding and updating machines directly from the error and no machine states.
- Implemented a dialog for adding and updating machine IDs, improving user interaction.
- Adjusted the machine addition process to show loading states and ensure proper DOM updates.

These changes significantly improve the usability and functionality of the machine management feature.

* add: machine id when create

* remove unused code

* fetch docker steps in backend

* compare list

* can sync machine

* add: create machine from local

* add error

* add: blacklist node

* refactor: improve snapshot fetching and UI adjustments

- Introduced centralized snapshot fetching utilities in snapshot-utils.js, including a fallback for ComfyUI version retrieval.
- Updated machine-manager.js to utilize the new snapshot fetching methods, enhancing error handling and code clarity.
- Adjusted index.js to streamline snapshot fetching during workflow deployment.
- Made minor UI adjustments in workflow-list.js for better layout consistency.

These changes enhance the application's robustness in handling snapshots and improve overall user experience.

* small tweak

* tweak machine display

* chore: version to 2.3.0
2025-08-21 18:39:39 +08:00
KarrixLee 7f6eea361e tweak: add tutorial complete to true by default (#104) 2025-08-18 00:19:57 +08:00
KarrixLee 14829a2b12 Comfyui 0.3.48 compatibility (#102)
* refactor: support 3 attribute for the validation prompt

* add: comments for clarification
2025-08-04 17:23:12 +08:00
KarrixLee 03c4e2d85d Add audio file upload support 2025-07-26 18:37:09 +08:00
KarrixLee bad8b1104f Fix comfyui 0.3.45 (#100)
* Refactor post_prompt function to include prompt_id handling

- Updated the post_prompt function in custom_routes.py to retrieve and validate a prompt_id from the incoming JSON data, defaulting to a new UUID if not provided.
- Adjusted the validation call to use prompt_id alongside the prompt, ensuring proper identification and processing of prompts.

These changes enhance the functionality of prompt handling within the application.

* Refactor post_prompt and send_prompt functions for async handling

- Updated the post_prompt function to be asynchronous, allowing for non-blocking execution when validating prompts.
- Adjusted calls to post_prompt in send_prompt and comfy_deploy_run to await the asynchronous execution, ensuring proper handling of prompt submissions.
- Commented out unused prompt construction code to streamline the function.

These changes enhance the performance and responsiveness of prompt handling in the application.

* Refactor handle_execute to support asynchronous execution

- Updated the swizzle_execute function to be asynchronous, allowing for non-blocking execution of the origin_execute function.
- Added missing parameters for pending_async_nodes in the swizzle_execute function call.
- Ensured that the result from origin_execute is awaited, improving the handling of asynchronous operations.

These changes enhance the performance and responsiveness of the execution handling in the application.

* Enhance error handling in post_prompt and origin_execute functions

- Added try-except blocks in post_prompt to handle TypeErrors during prompt validation, allowing for fallback to an older signature.
- Implemented similar error handling in the origin_execute function to manage potential TypeErrors, ensuring robust execution flow.
- Improved logging to capture issues with function signatures, aiding in debugging.

These changes improve the resilience of the application when dealing with prompt and execution validation.

* Refactor post_prompt and origin_execute for synchronous handling

- Modified the post_prompt function to call validate_prompt synchronously when a TypeError occurs, improving error handling.
- Updated the origin_execute function to execute synchronously, ensuring consistent behavior during execution.

These changes enhance the robustness of prompt validation and execution processes in the application.

* tweak: optional

* Refactor swizzle_execute for async and sync handling

- Enhanced the swizzle_execute function to differentiate between asynchronous and synchronous execution paths based on the origin_execute function's nature.
- Improved the structure of the swizzle_execute function to ensure proper handling of parameters and execution flow for both async and sync scenarios.
- Maintained existing error handling while ensuring consistent behavior across execution types.

These changes improve the flexibility and robustness of the execution handling in the application.
2025-07-23 16:12:35 -07:00
KarrixLee ac8779dc54 fix 2025-07-19 10:24:04 -07:00
KarrixLee 2a5223f2f0 fix 2025-06-29 15:43:12 +08:00
KarrixLee f02aef4cb3 tweak versoin 2025-06-29 13:37:24 +08:00
KarrixLee 22458a1cd6 Merge branch 'local-flow-2' 2025-06-27 14:10:10 +08:00
Tristan-mc-qandtristan22mc 0a58eba554 feat: Add deployable EXR saver node (#98)
Co-authored-by: tristan22mc <tristan22mc@gmail.com>
2025-06-26 14:07:48 -07:00
KarrixLee def54df9c2 tweak 2025-06-17 18:37:02 +08:00
KarrixLee 32a950afe8 tweak 2025-06-17 18:07:43 +08:00
KarrixLee 8130779d94 Enhance configuration saving and workflow list management
- Updated the save method in ConfigDialog to be asynchronous, allowing for smoother handling of configuration saves.
- Added a new function to refresh the workflow list if the sidebar is open, ensuring the UI reflects the latest data after configuration changes.
- Made workflowsState globally accessible for improved state management across components.
- Adjusted the height of the workflows list for better UI layout.

These changes improve the user experience by ensuring that the workflow list is up-to-date and enhancing the overall responsiveness of the configuration dialog.
2025-06-16 21:25:33 +08:00
KarrixLee 4cbd2a8225 Add workflow retrieval functionality and enhance UI interaction
- Introduced a new endpoint in custom_routes.py for fetching workflows by ID, including authorization checks and error handling.
- Updated workflow-list.js to support fetching and displaying workflow data upon user interaction, including loading indicators and error handling.
- Enhanced the createWorkflowItem function to accept additional parameters for improved data handling and user feedback.

These changes improve the user experience by enabling seamless workflow retrieval and interaction within the application.
2025-06-16 21:01:43 +08:00
KarrixLee d6fb2daeff Add workflow list management and search functionality
- Introduced a new workflow-list.js file to manage workflows, including fetching, displaying, and searching workflows.
- Enhanced the custom_routes.py file with a new endpoint for retrieving workflows, ensuring proper authorization and query parameter handling.
- Updated index.js to initialize the workflows list and integrate search functionality within the UI.

These changes improve the user experience by allowing efficient management and retrieval of workflows in the application.
2025-06-16 18:47:18 +08:00
KarrixLee a91effe3c8 Add workflow management endpoints and enhance deployment logic
- Introduced new endpoints for creating workflows and versions in the custom_routes.py file.
- Updated the deployWorkflow function in index.js to include apiUrl in the request body and handle workflow versioning.
- Improved error handling for API requests and ensured required fields are validated before processing.
- Enhanced user feedback during deployment with updated success messages.

These changes streamline workflow management and improve the overall deployment process within the application.
2025-06-16 16:32:23 +08:00
KarrixLee c015b710fe Enhance authentication flow and improve API integration
- Added a new endpoint for handling authentication responses in the UploadQueue class.
- Updated the deployWorkflow function to include apiUrl in the configuration checks.
- Refactored API calls to use the new auth-response endpoint, ensuring proper request handling.
- Improved logging for better debugging during workflow deployment.

These changes streamline the authentication process and enhance the overall API interaction within the application.
2025-06-15 13:55:59 +08:00
KarrixLee 2738d1913a tweak 2025-06-14 22:04:53 +08:00
KarrixLee 3f4c11e3f1 Refactor event dispatching and improve code readability in index.js
- Standardized formatting for CustomEvent dispatches to enhance consistency.
- Simplified async function prompts and improved filtering logic for existing input IDs.
- Enhanced readability by restructuring multiline statements and ensuring consistent indentation.
- Added error handling for deployment processes and improved dialog display methods.

These changes aim to improve maintainability and clarity of the codebase.
2025-06-14 21:49:53 +08:00
KarrixLee 089bad5560 Revert "Implement enhanced media preview functionality in ComfyUI"
This reverts commit 46010e1dd5.
2025-06-09 16:09:38 +08:00
KarrixLee 46010e1dd5 Implement enhanced media preview functionality in ComfyUI
- Added support for video previews alongside existing image previews.
- Introduced helper functions to detect video URLs and display media accordingly.
- Updated URL widget handling to show the appropriate media type based on the input URL.
- Improved error handling for media loading failures.

This change enhances the user experience by providing a seamless way to preview both images and videos.
2025-06-08 19:31:47 +08:00
KarrixLee 52d876fa67 Enhance ComfyUIDeployExternalVideo to support default video URL input
- Added 'default_value_url' parameter to allow fetching videos from a specified URL if the input_id is not a URL.
- Updated the logic to handle video fetching, ensuring it uses the correct URL based on the input.
- Improved the return structure for optional parameters, including the new 'default_value_url' with image preview support.

This change enhances flexibility in video input handling for the ComfyUI Deploy.
2025-06-08 19:24:13 +08:00
BennyKokandDevin AI f7e7eb19d0 Add ComfyUIDeployExternalNumberSliderInt node (#95)
- Implements integer slider node for ComfyUI Deploy
- Returns INT type instead of FLOAT for proper integer input compatibility
- Uses int(round(float(input_id))) for robust conversion
- Default range 0-10 with step=1 for integer appropriateness
- Includes proper range validation and error handling
- Frontend and API support already exists

Fixes COM-1041

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-06-06 12:38:18 +08:00
ImpactFrames cb03b6718e racing condition delete check if [prompt_id] exist (#94) 2025-06-05 16:42:37 +08:00
BennyKok 7b734c415a fix: import 2025-05-27 15:14:29 +08:00
BennyKok 64d3ec6b45 Revert "fix: import issues"
This reverts commit c47865ec26.
2025-05-27 15:12:33 +08:00
bennykok c47865ec26 fix: import issues 2025-05-21 19:10:20 +08:00
BennyKok b889f79baf Merge branch 'benny/support-comfy-api-key' 2025-05-12 17:06:08 +08:00
KarrixLee 1d8fed3534 feat: only register sidebar tab for Comfy Deploy on localhost 2025-05-12 12:38:23 +08:00
BennyKok a557788e70 fix 2025-05-11 10:57:07 +08:00
BennyKok 05cccaffa2 support for API_KEY_COMFY_ORG 2025-05-11 10:41:10 +08:00
KarrixLee 85af9dd68f Test (#91)
* feat: enhance apply_random_seed_to_workflow function to support KSampler node type and handle fixed seed settings

* refactor: add flag to skip randomization in apply_random_seed_to_workflow for KSampler nodes

* tweak
2025-05-10 13:13:02 +08:00
KarrixLee 233615ea25 Karrix/external seed (#90)
* feat: add ComfyUIDeployExternalSeed node for generating random seeds with configurable limits

* refactor: update ComfyUIDeployExternalSeed to use control options for seed generation

* feat: add default_value input for ComfyUIDeployExternalSeed node to enhance seed configuration

* test

* refactor: rename lower_limit and upper_limit to min_value and max_value in ComfyUIDeployExternalSeed for clarity

* refactor: update default_value handling and control options in ComfyUIDeployExternalSeed for improved seed generation logic

* refactor: enhance seed generation logic in ComfyUIDeployExternalSeed by refining control handling and default_value checks

* refactor: update description for default_value in ComfyUIDeployExternalSeed to clarify usage and behavior
2025-05-10 03:39:27 +08:00
KarrixLee 8b6aabbfaa feat: add support for 'result' file type in upload process 2025-05-07 21:42:13 +08:00
Nick Kao c72539078c Merge pull request #89 from BennyKok/nick/private-lora-download
feat: add bearer token param in external lora
2025-05-03 10:28:30 -07:00
KarrixLee 95fc642782 Karrix/fix preview image upload queue (#88)
* refactor: update upload completion logic to rely on UploadQueue worker for final SUCCESS status

* refactor: clean up commented-out code in upload completion logic
2025-04-28 15:56:58 +08:00
KarrixLee d88ca5b748 refactor: rename 'text' to 'text_file' for consistency in file handling 2025-04-25 20:03:02 +08:00
KarrixLee e86a484160 refactor: improve logging format and add validation for file_info in upload process 2025-04-25 13:40:38 +08:00
KarrixLee 5d85bfd38f fix: ensure temp file check handles non-dictionary items 2025-04-25 13:29:34 +08:00
BennyKok 77eb9f9805 Merge branch 'benny/fix-preview-image-stuck' 2025-04-24 15:29:16 +08:00
KarrixLee 266e9d1024 refactor: enhance audio loading with error handling and import checks 2025-04-23 13:36:01 +08:00
KarrixLee dc7234640c tweak 2025-04-22 18:30:34 +08:00
KarrixLee 4e8417d501 add: output text node 2025-04-22 18:15:44 +08:00
BennyKok 62609f9c6a Benny/fix preview image stuck (#87)
* Fix preview image loading and add support for 3D model uploads

* skip: sending the upload status, make sure, we are also sending the success status after all files is uploaded
2025-04-22 12:59:20 +08:00
BennyKok 438401b8c7 fix: external enum node value replace 2025-04-22 10:27:55 +08:00
BennyKok 9beac36d0f skip: sending the upload status, make sure, we are also sending the success status after all files is uploaded 2025-04-21 14:18:18 +08:00
BennyKok 313ce956fd Fix preview image loading and add support for 3D model uploads 2025-04-21 14:08:56 +08:00
BennyKok e18d980b77 Benny/fix 3d upload (#85)
* fix 3d upload with subfolder support and enhanced logging

* Use parent folder name as subfolder for model file uploads
2025-04-20 22:54:51 +08:00
bennykok fe2d9b82b5 fix: extra options form enum list for some node 2025-04-16 17:09:51 +08:00
bennykok 22858abb31 feat: add external enum node 2025-04-16 16:18:56 +08:00
bennykok 99a5f71b2c feat: add external enum node 2025-04-16 11:52:20 +08:00
bennykok 1810d4ecdb feat: improvement to convert external inputs system 2025-04-16 01:12:28 +08:00
bennykok 3593f0b37e fix: convert external inputs layout issues 2025-04-16 00:10:39 +08:00
bennykok 858a3bcda4 fix rendering problems 2025-04-14 18:08:42 +08:00
bennykok 484147f5b4 fix convert shortcut for string type 2025-04-14 17:59:57 +08:00
Vivek 61a8f1123e fixed a minor issue related to external inputs (#84) 2025-04-14 17:56:27 +08:00
EmmanuelMr18 46b056290d fix(workflows): correct typo in masks example workflow name 2025-04-07 01:31:17 -06:00
EmmanuelMr18 2cf0497823 chore(workflows): convert example workflow previews to JPG format 2025-04-07 01:27:01 -06:00
EmmanuelMr18 4b5eec4e4c feat(workflows): add example workflows for ComfyUI templates 2025-04-07 01:20:46 -06:00
Emmanuel Morales 17c48c7d4f chore: remove model_list custom node
This node was created just for an experiment in the previous year, but never was a core feature.

I'm removing it because looks that is braking the import in local machines because i'm seeing this error locally:

```
  File "F:\ComfyUI\custom_nodes\comfyui-deploy\comfy-nodes\model_list.py", line 35, in <module>
    allModels = fetch_files("./models")
                ^^^^^^^^^^^^^^^^^^^^^^^
  File "F:\ComfyUI\custom_nodes\comfyui-deploy\comfy-nodes\model_list.py", line 23, in fetch_files
    fs.extend(fetch_files(f"{dirpath}/{dirname}"))
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "F:\ComfyUI\custom_nodes\comfyui-deploy\comfy-nodes\model_list.py", line 23, in fetch_files
    fs.extend(fetch_files(f"{dirpath}/{dirname}"))
TypeError: 'NoneType' object is not iterable

Cannot import F:\ComfyUI\custom_nodes\comfyui-deploy module for custom nodes: 'NoneType' object is not iterable
```


I'm not sure why, previously was working but was a long time ago and this node was just an experiment, so i'm deleting it
2025-04-05 21:28:37 -06:00
BennyKok cd3a2ff547 Update custom_routes.py
random seed for XlabsSampler
2025-04-03 16:58:49 +02:00
bennykok 62f8e388bb update version and toml file 2025-04-01 12:56:55 +02:00
Robin Huangandsnomiao f86c08baed chore(publish): update GitHub Actions workflow for node publishing (#83)
- Add permissions to allow issue writing
- Update action version from `main` to `v1`
- Add condition to run job only for 'BennyKok' repository owner

Co-authored-by: snomiao <snomiao+comfy-pr@gmail.com>
2025-04-01 12:55:27 +02:00
bennykok 7f64bcc3ae fix: ensure validate workflow 2025-03-31 11:46:58 +02:00
bennykok 8359d1c783 reenable ws event 2025-03-30 22:28:44 +02:00
bennykok dca27fe2ba fix: random seed for sonic node 2025-03-29 09:22:11 +01:00
BennyKok 0ae70835c4 remove sending ws event, since its not used. 2025-03-27 00:04:34 +08:00
BennyKok 99cda529a9 chore: clearer log when request time out 2025-03-26 23:55:47 +08:00
bennykok 7e640a0da4 bump version 2025-03-24 09:44:00 +08:00
BennyKok f19b80a25d Update README.md
Removed the videos since its outdated
2025-03-16 10:31:00 +08:00
karrix 1ba7640d7f refactor: image output id optional 2025-03-13 21:15:14 +08:00
Nick Kao 6ecf9c782b Merge pull request #81 from Jeremy8776/main
Fix: Organised Node List
2025-03-07 10:26:46 -08:00
Jeremy 1904b4bdbf Fix: Organised Node List
All CD nodes under one group in the node list.

Not sure what subdir are wanted.
2025-03-07 18:12:56 +00:00
karrix 4112e0ec8c feat: example workflows 2025-03-06 20:11:52 +08:00
karrix 171a227856 add: 3d upload support 2025-03-02 03:55:18 +08:00
karrix 051db3c394 add: model_file 2025-03-02 02:40:31 +08:00
bennykok 757868deaf prevent duplicated output / run 2025-03-02 00:30:23 +08:00
bennykok 7b1e7afb62 change to 50ms stagger delay 2025-03-01 22:19:40 +08:00
bennykok 9f65ce72a5 remove log 2025-03-01 22:19:04 +08:00
bennykok a424e391d1 also log Node Execution Timeline as graph 2025-03-01 19:18:11 +08:00
bennykok 6add68599b feat: add upload queue, stagger upload 2025-03-01 19:02:12 +08:00
nick b3df94d1af update: don't randomize noise_seed if it's an input from another node 2025-02-24 09:44:59 -08:00
karrix 26b149553b tweak 2025-02-19 05:13:56 +08:00
karrix 17683d353c Revert "add: return output id"
This reverts commit 7552353e30.
2025-02-19 04:53:07 +08:00
karrix 7552353e30 add: return output id 2025-02-19 04:48:50 +08:00
karrix d619ad7f3b tweak 2025-02-19 04:34:26 +08:00
karrix f0ed0ad8f7 feat: output image id 2025-02-19 03:52:21 +08:00
BennyKok 7fce0b4976 Update README.md 2025-02-15 16:52:50 +08:00
ImpactFrames 0e218752ca Add external_exr.py (#79)
* Create external_exr.py

adds a node for loading exr images from url

* Update custom_routes.py

Adds EXR to the custom routes
2025-02-14 11:13:35 +08:00
bennykok 4073a43d3d use torch audio 2025-02-07 23:14:16 +08:00
bennykok 3d6a554f7f feat: add external audio node based on VHS node 2025-02-07 21:42:44 +08:00
KarrixLee ce939fbe1b add: gpu in info (#78) 2025-02-06 15:41:56 +08:00
bennykok 48f5ce15d7 fix: fallback to default api runs 2025-02-05 17:58:57 +08:00
karrix 9512437573 feat: send back event if the graph is loading properly 2025-02-05 14:41:35 +08:00
bennykok 649e431227 feat: configure_menu_buttons 2025-01-23 13:44:31 +08:00
EmmanuelMr18 411db66d81 Revert "chore: refresh models when getting object_info"
This reverts commit 67f25b2353.
2025-01-20 01:52:52 -05:00
Emmanuel Morales 67f25b2353 chore: refresh models when getting object_info
This is a WIP that will be used to refresh the models when execution comfyUI without having to stop the server and start a new one
2025-01-19 17:26:41 -06:00
bennykok ce3b0dbe84 chore: log prompt_id on start 2025-01-19 12:39:24 +08:00
bennykok fc36a8ad0f feat: add output image node 2025-01-19 12:39:06 +08:00
Robin Huangandsnomiao 638e625d72 chore(licence-update): Update PyProject Toml - License (#77)
Co-authored-by: snomiao <snomiao+comfy-pr@gmail.com>
2025-01-10 15:44:27 +08:00
EmmanuelMr18 230cee40d2 fix: add container to the buttons injected into the right menu 2025-01-10 01:08:42 -06:00
EmmanuelMr18 73853a60ff feat: inject buttons in the right position of the comfyui menu 2025-01-07 23:49:03 -06:00
bennykok 413115571b chore: add event for updating widget 2025-01-07 21:36:12 +08:00
bennykok bf00580562 feat: update external image node to have default value 2025-01-07 21:03:52 +08:00
bennykok 6ed468d7d4 feat: drag drop proxy + inject button to toolbar 2025-01-06 13:01:39 +08:00
bennykok 5423b4ee6f fix: simply js import 2025-01-05 14:00:42 +08:00
Emmanuel Morales 2c1656756d fix(updates): make updates async to avoid blocking execution (#75)
I tracked the time and takes ~200ms everytime that we send the "Executing <NODE NAME> n%".
So this means that if you have 10 custom nodes we are adding 2 extra seconds to the execution.
200 * 10 = 2,000.
Some workflows are more complext and have more custom nodes, so this only keeps increasing.
2025-01-03 16:25:04 +08:00
bennykok ac843527d9 fix: turn perf meta into array 2024-12-09 18:42:15 +08:00
bennykok f39d216326 fix: ordered dict 2024-12-09 18:13:36 +08:00
bennykok 40ec37e58f fix 2024-12-09 16:48:11 +08:00
bennykok 1d63b21643 fix: move update run 2024-12-09 16:31:36 +08:00
bennykok 0e3baf22df fix: also send timing pref 2024-12-09 16:18:04 +08:00
bennykok 1837065ed2 fix: log printing 2024-12-09 09:34:39 +08:00
bennykok 9a8f4795d1 fix log 2024-12-09 00:35:12 +08:00
bennykok c0c617c5d2 Merge branch 'combine-text' into public-main 2024-12-09 00:24:36 +08:00
bennykok 1e33435ae5 feat: add perf counter 2024-12-09 00:11:51 +08:00
karrix 04161071f2 test 2024-12-06 18:54:56 +08:00
bennykok 32d574475c fix: backward comp with old ui 2024-11-13 18:14:36 +09:00
bennykok 1a017ee6a3 make sure link reconnect works 2024-11-13 17:59:54 +09:00
bennykok 603223741a feat: tweak ui styles 2024-11-13 17:24:11 +09:00
bennykok 2bd8b23c60 feat: convert external input 2024-11-13 14:20:24 +08:00
bennykok a82e315d6c fix: when file endpoint is null, skip uploading 2024-10-25 19:56:36 +08:00
nick 7fdfba6b6e external lora 2024-10-24 22:46:31 +08:00
BennyKok 0779136134 Update pyproject.toml 2024-10-22 11:28:59 +08:00
nick fe116a4655 clean logs 2024-10-12 23:59:58 -07:00
nick 7dd8a7e67e gpu eveent 2024-10-12 16:57:37 -07:00
nick 778e6fefe6 Merge branch 'main' into nickkao/gpu_event 2024-10-12 12:56:11 -07:00
nick fd310e8478 globals 2024-10-11 21:46:48 -07:00
karrix 3a3b93d564 tweak: modify the local storage of the dock 2024-10-11 17:15:28 +08:00
nick 82c564228d None gpu event 2024-10-10 17:58:11 -07:00
nick ad0a23434b merge 2024-10-10 17:23:19 -07:00
karrix 292f77f06b fix: default queue button position to dock 2024-10-11 02:21:32 +08:00
bennykok a139424b91 fix: output node status 2024-10-10 11:20:12 -07:00
bennykok 44a91d2093 fix: default new ui for comfyui 2024-10-09 17:20:08 -07:00
bennykok 7cff930861 fix: token will be fetched everytime to make sure it is the latest 2024-10-09 16:54:34 -07:00
nick ce464c6ce4 Merge branch 'main' into nickkao/gpu_event 2024-10-07 15:50:47 -07:00
bennykok 1c7998c554 feat: attach gpu event 2024-10-07 15:48:14 -07:00
nick 66d1e42409 lopgs 2024-10-07 14:16:55 -07:00
nick 8882f4983c fix: pydantic type simpleprompt 2024-10-04 19:14:37 -07:00
nick 492b81c340 print 2024-10-04 19:02:25 -07:00
nick 8b05ed26c9 merge 2024-10-04 18:49:30 -07:00
nick ce67604926 stuff 2024-10-04 17:34:50 -07:00
bennykok c115c22a91 fix: send ws after cd logic 2024-10-04 16:16:19 -07:00
bennykok 2f33bcf497 chore: return item on upload 2024-10-04 15:27:31 -07:00
nick bcf466c472 merge 2024-10-04 12:10:01 -07:00
bennykok f812d9d698 Merge branch 'workspace-v3' into public-main 2024-10-02 16:38:55 -07:00
nick 101b6cca57 merge 2024-09-29 12:02:46 -07:00
EdwinWong ae68aae011 fix: add workflow data to extra data 2024-09-27 18:48:51 -07:00
EmmanuelMr18 07926158f0 feat: model_list node to display all the models available 2024-09-27 18:19:56 -07:00
EmmanuelMr18 ce92dd0570 refactor: remove ExternalTextList node, was for lora traning 2024-09-27 15:13:27 -07:00
bennykok e2fcf67aec fix: graph load 2024-09-25 12:59:00 -07:00
nick 79650f48d0 merge 2024-09-24 23:16:49 -07:00
bennykok 69f63f4869 Merge branch 'jeff/fix-workflow-in-extra-data' into workspace-v3 2024-09-24 19:58:16 -07:00
bennykok 50860cd500 test 2024-09-24 19:45:53 -07:00
bennykok 2eb02fc92e fi 2024-09-24 19:36:57 -07:00
EdwinWong 5c6defbe62 fix: add workflow data to extra data 2024-09-24 15:35:48 -07:00
bennykok d1c54b2b6d fix: state 2024-09-23 19:01:47 -07:00
bennykok 3a6c3b1ae9 feat: add native run proxy 2024-09-23 15:31:13 -07:00
bennykok aea456cba9 fix face loader extenal load 2024-09-21 10:51:51 -07:00
bennykok 8c5e5c4277 feat: add ComfyUIDeployExternalTextAny 2024-09-21 10:39:34 -07:00
bennykok 02430ee62d remove some logs 2024-09-20 18:10:04 -07:00
Fawaz Kadem 764a8fee82 Add new external deploy node for face models (#66) 2024-09-18 17:00:51 -07:00
bennykok 61acffd355 fix 2024-09-18 08:20:35 -07:00
bennykok aa47f3523f fix 2024-09-17 23:36:24 -07:00
bennykok 7ed4284a6f fix 2024-09-17 23:25:19 -07:00
bennykok a403daa314 fix 2024-09-17 23:09:42 -07:00
bennykok ba9b187dcc fix 2024-09-17 22:59:27 -07:00
bennykok 1243fa4e58 fix 2024-09-17 22:55:08 -07:00
bennykok 0d1537963c fix 2024-09-17 21:48:42 -07:00
bennykok 0083b38dcc chore: log image size 2024-09-17 20:44:44 -07:00
bennykok b8dded1535 Revert "fix: roll back to unique session per request"
This reverts commit 5a78ca97bd.
2024-09-17 20:26:39 -07:00
bennykok 4927d81e73 chore: accept cd_token 2024-09-17 18:57:15 -07:00
nick 0e70db4013 merge 2024-09-17 16:37:39 -07:00
nick 06805e310d merge 2024-09-17 14:32:52 -07:00
bennykok fb6bb2357a Reapply "fix: back to sequential file upload"
This reverts commit 1f5a88b888.
2024-09-17 14:28:56 -07:00
bennykok 086d642360 Merge branch 'benny/log-sync' into public-main 2024-09-17 14:27:59 -07:00
bennykok 212daa838c Revert "feat: experiment with await + asyncio.gather for multi file in same node"
This reverts commit c08b68c41f.
2024-09-17 14:25:13 -07:00
bennykok c08b68c41f feat: experiment with await + asyncio.gather for multi file in same node 2024-09-17 12:56:42 -07:00
bennykok 5a78ca97bd fix: roll back to unique session per request 2024-09-16 23:57:40 -07:00
bennykok 1f5a88b888 Revert "fix: back to sequential file upload"
This reverts commit 3d099f88ea.
2024-09-16 23:55:16 -07:00
bennykok 946571e32e fix: await 2024-09-16 18:54:05 -07:00
bennykok e692beb009 feat: realtime log sync 2024-09-16 15:34:20 -07:00
bennykok 3d099f88ea fix: back to sequential file upload 2024-09-16 13:55:02 -07:00
karrix 65f7576748 fix: non type error when upload output 2024-09-16 12:45:53 -07:00
bennykok 2d72cd8175 fix: batch zip image input 2024-09-14 21:49:17 -07:00
bennykok 5554c95f44 Merge branch 'benny/auth_token' into public-main 2024-09-12 14:14:16 -07:00
bennykok c1003f7e31 Merge branch 'benny/zip-batch-image' into public-main 2024-09-12 14:14:08 -07:00
EdwinWong 71d60a5dd1 fix: comfydeploy node backward compatible in every comfyui 2024-09-10 01:03:50 -07:00
bennykok e011711600 feat: zip batch image support 2024-09-09 17:49:39 -07:00
nick 4cd7d7a8f9 gpu event 2024-09-08 09:55:47 -07:00
bennykok 4df9d38e56 feat: embed file public status into image output 2024-09-03 23:07:48 -07:00
bennykok 9cd626e1f6 feat: send token for cd update api 2024-09-03 21:58:39 -07:00
bennykok 503dca8fb6 chore: add log 2024-08-30 12:16:41 -07:00
bennykok 73c149b4cb fix node meta 2024-08-30 12:16:41 -07:00
bennykok 65b5b0b8c7 fix: remove content length 2024-08-30 12:16:41 -07:00
bennykok 9d6ee85402 fix: upload file acl 2024-08-30 12:16:41 -07:00
bennykok cdaed8a571 fix: include upload time 2024-08-30 12:16:41 -07:00
bennykok 3129e89cce fix: log file error log 2024-08-30 12:16:41 -07:00
bennykok 7a693eabc8 fix: size 2024-08-30 12:16:41 -07:00
bennykok 8f677e520d chore: log more test for upload file debug 2024-08-30 12:16:41 -07:00
bennykok 4c8d32c5b0 fix 2024-08-30 12:16:41 -07:00
nick a99d2568e0 video and lora node fix 2024-08-28 13:08:15 -07:00
nick 649b61c580 default vid 2024-08-26 13:46:01 -07:00
nick edff5685f9 fix: random seed 2024-08-22 17:39:03 -07:00
bennykok 9fc0c2b4a2 chore: upload node data 2024-08-21 16:34:25 -07:00
bennykok d34e2e99b1 fix: external lora for new comfyui 2024-08-21 09:46:13 -07:00
bennykok f85043db07 fix: remove default value 2024-08-20 19:14:43 -07:00
bennykok 894d8e1503 Merge branch 'benny/async-upload-file' into public-main 2024-08-20 18:02:57 -07:00
bennykok 08d631d1eb feat: async file upload for the same node 2024-08-20 17:07:50 -07:00
karrix a1031487e1 add: all node support name and description 2024-08-20 20:15:29 +08:00
bennykok ca41207192 feat: max min int for all number inputs to enable negative number input 2024-08-19 13:27:46 -07:00
bennykok 507d5ef631 feat: add a init timeout of 10 seconds for retry logic 2024-08-18 17:31:48 -07:00
bennykok dd1d9df23f fix: resolve false possible error 2024-08-18 15:38:16 -07:00
bennykok 3a14e49ca5 fix: refresh workflows list 2024-08-17 16:04:14 -07:00
nick 8147c4bfb7 video node' 2024-08-15 12:50:29 -07:00
bennykok 10268825d9 feat: support new frontend! 2024-08-14 11:09:58 -07:00
bennykok f6ea252652 fix: log when random seed is applied 2024-08-10 10:35:48 -07:00
bennykok 98cd5ef79c fix: randomize noise RandomNoise, KSamplerAdvanced, SamplerCustom 2024-08-10 10:02:01 -07:00
Emmanuel Morales 4bce5cadfb fix(text): return correctly the text in external_text_list node 2024-08-10 09:44:37 -06:00
Nick Kao f362671041 Merge pull request #61 from BennyKok/node-error-no-throw
block on bad prompt
2024-08-08 10:01:33 -07:00
nick 0582d1d869 merge 2024-08-07 20:43:38 -07:00
nick ce073a86c7 block on bad prompt 2024-08-07 20:42:12 -07:00
Emmanuel Morales 3a85a1edf2 feat(text): create node for external text list (#60)
* feat(text): create node for external text list 

This is to send a list of texts to other nodes

* refactor: remove prints and rename variable

* style: update comment

* refactor: remove unused optional inputs
2024-08-06 21:35:46 -06:00
karrix 369c1456a9 add: node focusing function 2024-08-05 00:59:52 +08:00
bennykok 01e323b7e2 fix: excessive log 2024-08-03 22:22:06 -07:00
bennykok db684d044a fix: not yield 2024-08-03 21:56:16 -07:00
BennyKok 8e12803ea1 Retry logic when calling api (#57)
* fix: retry logic, bypass logfire, clean up log

* fix: max_retries and retry_delay_multiplier, do not throw when pass the retry failed
2024-08-01 20:43:21 -07:00
Nick Kao 7585d5049a Merge pull request #58 from GwonHyeok/main
fix: ExternalLoRA node Make downloaded files reusable
2024-08-01 19:50:59 -07:00
GwonHyeok 772bb09240 fix: ExternalLoRA node Make downloaded files reusable 2024-08-02 10:29:24 +09:00
bennykok 9a7e18e651 fix: fe communication 2024-08-01 10:50:08 -07:00
Hmily a02c8d237f fix: Fix request deploy service interface error (#56) 2024-08-01 10:47:45 -07:00
nick 2ba5a0ff3d external lora 2024-08-01 10:43:24 -07:00
bennykok e0eae1068b fix: make external lora and checkpoint wildcard 2024-07-26 17:39:40 -07:00
bennykok 4f1a80fb64 fix: log issues with websocket 2024-07-22 13:36:39 -07:00
Hmily b4273b1907 fix: update next version and routing parameter errors (#55) 2024-07-22 09:40:23 -07:00
nick 10ba00e3dd update: external video node 2024-07-20 00:16:39 -07:00
nick eb40fddb76 Merge branch 'main' of https://github.com/bennykok/comfyui-deploy 2024-07-20 00:16:27 -07:00
nick 3c9d1865ca video node 2024-07-20 00:15:41 -07:00
bennykok 6fa38e9bb8 fix 2024-07-13 19:17:30 -07:00
bennykok 6e4532078f feat: update plugin js 2024-07-12 12:24:10 -07:00
nick 48d21f8d52 feat: audio output from external video node 2024-07-12 11:20:18 -07:00
BennyKokandnick a2ac1adf01 Streaming support (#52)
* feat: add streaming endpoint

* fix: run issues

* feat(plugin): add dispatchAPIEventData

* fix(plugin): event

* fix: streaming event format

* fix: prompt error

* fix: node_error proxy

* chore(plugin): add log

* custom route

---------

Co-authored-by: nick <kobenkao@gmail.com>
2024-07-11 20:03:41 -07:00
Emmanuel Morales 716790e344 fix(media upload): skip when using the CD_BYPASS_UPLOAD env var (#51)
* fix(image upload): skip when using the CD_BYPASS_UPLOAD env var

* Revert "fix(image upload): skip when using the CD_BYPASS_UPLOAD env var"

This reverts commit 384eda63e6.

* fix(upload outputs): skip images/gifs/files/mesh when env var is true

The env var is `CD_BYPASS_UPLOAD`.
When that variables is `True`, we don't upload the media to our comfy
deploy s3 bucket.

There are 2 steps.
1. save the file into our s3 bucket
2. save the saving into our database.

When `CD_BYPASS_UPLOAD` is True:
1. Skip the save file into our s3 bucket
2. Skip the save into our database

Previously we were skipping the step 1, but not the step 2. So that is
the reason of why we keep seeing the comfy deploy URL when fetching the
run details:

```
outputs: [
  {
    data:{
      gifs: [
        {
          url: "https://comfy-deploy-output.s3.amazonaws.com/video.mp4"
        }
      ],
      text: [
        "A text that you displayed with show text node"
      ]
    }
  }
]
```

With the new changes we don't save that into our database, and fetching
the details of a run will look like this:
```
outputs: [
  {
    data:{
      text: [
        "A text that you displayed with show text node"
      ]
    }
  }
]
```
2024-07-07 22:04:00 -07:00
nick c6fe88bf66 new route 2024-06-15 17:29:51 -07:00
bennykok 9b24b12006 fix: file upload issues with cloudflare 2024-06-11 17:42:52 -07:00
bennykok ff70bbdcec fix: correctly set the file content type for images, webp, jepg, png 2024-05-29 08:59:53 -07:00
haohaocreates 840bea79e8 chore(publish): Add Github Action for Publishing to Comfy Registry (#48) 2024-05-26 23:25:15 +08:00
BennyKok 0f423ce1c3 Update pyproject.toml 2024-05-26 23:21:13 +08:00
haohaocreates 2aa1a446e5 chore(pyproject): Add pyproject.toml for Custom Node Registry (#47) 2024-05-26 23:20:50 +08:00
karrix 07a7feb6ac add: slider number support 2024-05-11 14:50:46 +08:00
bennykok c5ac1b5f94 perf: turn back on async file upload 2024-05-10 13:08:37 +09:00
bennykok 00d827e232 feat: CD_BYPASS_UPLOAD 2024-05-10 11:36:00 +09:00
karrix 697fd52349 add: bool custom node 2024-05-09 14:26:43 +08:00
karrix 6b9c431df8 add: boolean input and 3d mesh support 2024-05-09 14:25:22 +08:00
bennykok 3c508c7eec feat: redirect queue prompt to iframe event in workspace mode 2024-05-07 00:42:36 +08:00
Nick Kao 409ca6f1dd Merge pull request #45 from NicholasKao1029/main
video node
2024-05-04 10:19:07 -07:00
nick df391e867e video node 2024-05-04 10:14:33 -07:00
Nick Kao c37b8be00a Merge pull request #44 from NicholasKao1029/main
Video node
2024-04-30 12:56:30 -07:00
nick a5a73e4209 clean up 2024-04-30 12:55:04 -07:00
nick c7841deea2 vid node 2024-04-30 12:19:41 -07:00
nick b0b1d64b6b external video 2024-04-27 13:32:50 -07:00
bennykok c8dc189f99 fix: external number input 2024-04-25 18:36:24 +08:00
bennykok cd5e4a5d01 fix: duplicated file upload 2024-04-25 16:14:14 +08:00
bennykok 95c15f095d chore: add file upload time log 2024-04-25 15:55:34 +08:00
nick b4c27bbbea fix: external lora 2024-04-24 23:27:01 -07:00
bennykok 810aec5135 fix: empty inputs causing run issues 2024-04-25 13:15:55 +08:00
nick c843926d6e fix: external lora takes in value outside of default 2024-04-24 17:35:09 -07:00
bennykok 797180b5c7 feat(plugin): add external image batch 2024-04-24 21:48:56 +08:00
bennykok d00ca375a2 chore: bump comfyui json version 2024-04-23 18:44:29 +08:00
bennykok be5d5d2b54 feat: update deploy method 2024-04-23 14:11:11 +08:00
bennykok d592a6ba12 feat: refactor deployment code 2024-04-22 00:07:26 +08:00
bennykok 35fed9aa4d fix: failed case marked as success 2024-04-20 01:38:31 +08:00
bennykok 3b6a753472 feat: workspace_mode and window event 2024-04-19 16:01:47 +08:00
bennykok 7d2c521645 chore: clean up custom node log 2024-04-14 15:59:33 +08:00
bennykok f363b7e871 fix: make sure to skip the temp file. 2024-04-14 00:24:21 +08:00
bennykok 1b25cfdd6c feat: add file hash cache, workflow deployment will be faster
# Conflicts:
#	.gitignore
2024-04-12 19:53:03 +08:00
bennykok 5da56b5507 chore: tweak log 2024-04-12 18:43:24 +08:00
bennykok 03d12e4099 fix!: skipping preview image as save node 2024-04-12 13:34:27 +08:00
bennykok e66712425d fix: bump comfydeploy deps 2024-04-12 12:28:41 +08:00
bennykok 81f315e14d fix: clashes with ComfyUI manager restart 2024-03-27 13:14:57 -07:00
64 changed files with 17485 additions and 1119 deletions
+25
View File
@@ -0,0 +1,25 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- main
paths:
- "pyproject.toml"
permissions:
issues: write
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'BennyKok' }}
steps:
- name: Check out code
uses: actions/checkout@v4
- 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 }}
+2 -1
View File
@@ -1,2 +1,3 @@
__pycache__
.DS_Store
.DS_Store
file-hash-cache.json
+7 -4
View File
@@ -2,6 +2,13 @@
Open source comfyui deployment platform, a `vercel` for generative workflow infra. (serverless hosted gpu with vertical intergation with comfyui)
Check out our latest lcoal demo -> https://github.com/comfy-deploy/comfyui-api-comfydeploy
Full backend and frontend is here -> https://github.com/comfy-deploy/comfydeploy
> [!NOTE]
> Im looking for creative hacker to join ComfyDeploy's core team! DM me on [twitter](https://x.com/BennyKokMusic)
Join [Discord](https://discord.gg/EEYcQmdYZw) to chat more or visit [Comfy Deploy](https://comfydeploy.com/) to get started!
Check out our latest [nextjs starter kit](https://github.com/BennyKok/comfyui-deploy-next-example) with Comfy Deploy
@@ -93,10 +100,6 @@ Major areas
# Self Hosting with Vercel
[![Video](https://img.mytsi.org/i/nFOG479.png)](https://www.youtube.com/watch?v=hWvsEY1cS2M)
Tutorial Created by [Ross](https://github.com/rossman22590) and [Syn](https://github.com/mortlsyn)
Build command
```
+37 -1
View File
@@ -2,8 +2,9 @@
@author: BennyKok
@title: comfyui-deploy
@nickname: Comfy Deploy
@description:
@description:
"""
import os
import sys
@@ -17,19 +18,23 @@ import requests
import folder_paths
from folder_paths import add_model_folder_path, get_filename_list, get_folder_paths
from tqdm import tqdm
import re
from . import custom_routes
# import routes
ag_path = os.path.join(os.path.dirname(__file__))
def get_python_files(path):
return [f[:-3] for f in os.listdir(path) if f.endswith(".py")]
def append_to_sys_path(path):
if path not in sys.path:
sys.path.append(path)
paths = ["comfy-nodes"]
files = []
@@ -41,14 +46,45 @@ for path in paths:
NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
def split_camel_case(name):
# Split on underscores first, then split each part on camelCase
parts = []
for part in name.split("_"):
# Find all camelCase boundaries
words = re.findall("[A-Z][^A-Z]*", part)
if not words: # If no camelCase found, use the whole part
words = [part]
parts.extend(words)
return parts
# Import all the modules and append their mappings
for file in files:
module = importlib.import_module(file)
# Check if the module has explicit mappings
if hasattr(module, "NODE_CLASS_MAPPINGS"):
NODE_CLASS_MAPPINGS.update(module.NODE_CLASS_MAPPINGS)
if hasattr(module, "NODE_DISPLAY_NAME_MAPPINGS"):
NODE_DISPLAY_NAME_MAPPINGS.update(module.NODE_DISPLAY_NAME_MAPPINGS)
# Auto-discover classes with ComfyUI node attributes
for name, obj in inspect.getmembers(module):
# Check if it's a class and has the required ComfyUI node attributes
if (
inspect.isclass(obj)
and hasattr(obj, "INPUT_TYPES")
and hasattr(obj, "RETURN_TYPES")
):
# Use the class name as the key if not already in mappings
if name not in NODE_CLASS_MAPPINGS:
NODE_CLASS_MAPPINGS[name] = obj
# Create a display name by converting camelCase to Title Case with spaces
words = split_camel_case(name.replace("ComfyUIDeploy", ""))
display_name = " ".join(word.capitalize() for word in words)
# print(display_name, name)
NODE_DISPLAY_NAME_MAPPINGS[name] = display_name
WEB_DIRECTORY = "web-plugin"
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
+82
View File
@@ -0,0 +1,82 @@
import io
from folder_paths import get_annotated_filepath
class ComfyUIDeployExternalAudio:
RETURN_TYPES = ("AUDIO",)
RETURN_NAMES = ("audio",)
FUNCTION = "load_audio"
CATEGORY = "🔗ComfyDeploy"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_audio"},
),
"audio_file": ("STRING", {"default": ""}),
},
"optional": {
"default_value": ("AUDIO",),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": False, "default": ""},
),
},
}
@classmethod
def VALIDATE_INPUTS(s, audio_file, **kwargs):
return True
def load_audio(
self,
input_id,
audio_file,
default_value=None,
display_name=None,
description=None,
):
try:
import torchaudio
if audio_file and audio_file != "":
if audio_file.startswith(("http://", "https://")):
# Handle URL input
try:
import requests
response = requests.get(audio_file)
audio_data = io.BytesIO(response.content)
waveform, sample_rate = torchaudio.load(audio_data)
except Exception as e:
print(f"Error loading audio from URL: {e}")
return (default_value,)
else:
# Handle local file
try:
audio_path = get_annotated_filepath(audio_file)
waveform, sample_rate = torchaudio.load(audio_path)
except Exception as e:
print(f"Error loading local audio file: {e}")
return (default_value,)
audio = {"waveform": waveform.unsqueeze(0), "sample_rate": sample_rate}
return (audio,)
else:
return (default_value,)
except ImportError as e:
print(f"Error: torchaudio not installed or cannot be imported: {e}")
return (default_value,)
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalAudio": ComfyUIDeployExternalAudio}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalAudio": "External Audio (ComfyUI Deploy)"
}
+36
View File
@@ -0,0 +1,36 @@
class ComfyUIDeployExternalBoolean:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_bool"},
),
"default_value": ("BOOLEAN", {"default": False})
},
"optional": {
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = ("BOOLEAN",)
RETURN_NAMES = ("bool_value",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, display_name=None, description=None):
print(f"Node '{input_id}' processing with switch set to {default_value}")
return [default_value]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalBoolean": ComfyUIDeployExternalBoolean}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyUIDeployExternalBoolean": "External Boolean (ComfyUI Deploy)"}
+18 -3
View File
@@ -5,6 +5,12 @@ import torch
import folder_paths
from tqdm import tqdm
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
WILDCARD = AnyType("*")
class ComfyUIDeployExternalCheckpoint:
@classmethod
def INPUT_TYPES(s):
@@ -17,17 +23,26 @@ class ComfyUIDeployExternalCheckpoint:
},
"optional": {
"default_value": (folder_paths.get_filename_list("checkpoints"), ),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = (folder_paths.get_filename_list("checkpoints"),)
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("path",)
FUNCTION = "run"
CATEGORY = "deploy"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None):
def run(self, input_id, default_value=None, display_name=None, description=None):
import requests
import os
import uuid
+46
View File
@@ -0,0 +1,46 @@
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
WILDCARD = AnyType("*")
class ComfyUIDeployExternalEnum:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_enum"},
),
},
"optional": {
"default_value": (
"STRING",
{"multiline": False, "default": "", "dynamic_enum": True},
),
"options": (
"STRING",
{"multiline": True, "default": ""},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("text",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, options=None, default_value=None, display_name=None, description=None):
return [default_value]
+110
View File
@@ -0,0 +1,110 @@
import os
import io
import cv2 as cv
import numpy as np
import torch
import requests
from folder_paths import get_annotated_filepath
class ComfyUIDeployExternalEXR:
RETURN_TYPES = ("IMAGE", "MASK")
RETURN_NAMES = ("image", "mask")
FUNCTION = "load_exr"
CATEGORY = "🔗ComfyDeploy"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_exr"},
),
"exr_file": ("STRING", {"default": ""}),
"tonemap": (["linear", "sRGB", "Reinhard"], {"default": "sRGB"}),
},
"optional": {
"default_image": ("IMAGE",),
"default_mask": ("MASK",),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": False, "default": ""},
),
}
}
@classmethod
def VALIDATE_INPUTS(s, exr_file, **kwargs):
return True
def sRGBtoLinear(self, npArray):
less = npArray <= 0.0404482362771082
npArray[less] = npArray[less] / 12.92
npArray[~less] = np.power((npArray[~less] + 0.055) / 1.055, 2.4)
def linearToSRGB(self, npArray):
less = npArray <= 0.0031308
npArray[less] = npArray[less] * 12.92
npArray[~less] = np.power(npArray[~less], 1/2.4) * 1.055 - 0.055
def load_exr(self, input_id, exr_file, tonemap="sRGB",
default_image=None, default_mask=None,
display_name=None, description=None):
try:
if exr_file and exr_file != "":
if exr_file.startswith(('http://', 'https://')):
# Handle URL input
response = requests.get(exr_file)
# Write to temp buffer
buffer = io.BytesIO(response.content)
nparr = np.frombuffer(buffer.getvalue(), np.uint8)
image = cv.imdecode(nparr, cv.IMREAD_UNCHANGED).astype(np.float32)
else:
# Handle local file
exr_path = get_annotated_filepath(exr_file)
image = cv.imread(exr_path, cv.IMREAD_UNCHANGED).astype(np.float32)
if len(image.shape) == 2:
image = np.repeat(image[..., np.newaxis], 3, axis=2)
# Extract RGB and flip channels
rgb = np.flip(image[:,:,:3], 2).copy()
# Apply tonemapping
if tonemap == "sRGB":
self.linearToSRGB(rgb)
rgb = np.clip(rgb, 0, 1)
elif tonemap == "Reinhard":
rgb = np.clip(rgb, 0, None)
rgb = rgb / (rgb + 1)
self.linearToSRGB(rgb)
rgb = np.clip(rgb, 0, 1)
rgb = torch.unsqueeze(torch.from_numpy(rgb), 0)
# Handle alpha/mask
mask = torch.zeros((1, image.shape[0], image.shape[1]), dtype=torch.float32)
if image.shape[2] > 3:
mask[0] = torch.from_numpy(np.clip(image[:,:,3], 0, 1))
return (rgb, mask)
else:
# Return defaults if no file provided
return (default_image, default_mask)
except Exception as e:
print(f"Error loading EXR: {str(e)}")
# Return defaults on error
return (default_image, default_mask)
NODE_CLASS_MAPPINGS = {
"ComfyUIDeployExternalEXR": ComfyUIDeployExternalEXR
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalEXR": "External EXR (ComfyUI Deploy)"
}
+106
View File
@@ -0,0 +1,106 @@
from PIL import Image, ImageOps
import numpy as np
import torch
import folder_paths
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
WILDCARD = AnyType("*")
class ComfyUIDeployExternalFaceModel:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_reactor_face_model"},
),
},
"optional": {
"default_face_model_name": (
"STRING",
{"multiline": False, "default": ""},
),
"face_model_save_name": ( # if `default_face_model_name` is a link to download a file, we will attempt to save it with this name
"STRING",
{"multiline": False, "default": ""},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
"face_model_url": (
"STRING",
{"multiline": False, "default": ""},
),
},
}
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("path",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
def run(
self,
input_id,
default_face_model_name=None,
face_model_save_name=None,
display_name=None,
description=None,
face_model_url=None,
):
import requests
import os
import uuid
if face_model_url and face_model_url.startswith("http"):
if face_model_save_name:
existing_face_models = folder_paths.get_filename_list("reactor/faces")
# Check if face_model_save_name exists in the list
if face_model_save_name in existing_face_models:
print(f"using face model: {face_model_save_name}")
return (face_model_save_name,)
else:
face_model_save_name = str(uuid.uuid4()) + ".safetensors"
print(face_model_save_name)
print(folder_paths.folder_names_and_paths["reactor/faces"][0][0])
destination_path = os.path.join(
folder_paths.folder_names_and_paths["reactor/faces"][0][0],
face_model_save_name,
)
print(destination_path)
print(
"Downloading external face model - "
+ face_model_url
+ " to "
+ destination_path
)
response = requests.get(
face_model_url,
headers={"User-Agent": "Mozilla/5.0"},
allow_redirects=True,
)
with open(destination_path, "wb") as out_file:
out_file.write(response.content)
return (face_model_save_name,)
else:
print(f"using face model: {default_face_model_name}")
return (default_face_model_name,)
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalFaceModel": ComfyUIDeployExternalFaceModel}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalFaceModel": "External Face Model (ComfyUI Deploy)"
}
+137
View File
@@ -0,0 +1,137 @@
import folder_paths
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
WILDCARD = AnyType("*")
class ComfyUIDeployExternalFile:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_file"},
),
},
"optional": {
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
"file_url": (
"STRING",
{"multiline": False, "default": ""},
),
},
}
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("path",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
def run(
self,
input_id,
display_name=None,
description=None,
file_url=None,
):
import requests
import os
import uuid
from urllib.parse import urlparse
if file_url:
if file_url.startswith("http"):
# Use cache directory for saving files
cache_dir = folder_paths.get_temp_directory()
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
# Always generate random filename to avoid conflicts
parsed_url = urlparse(file_url)
original_filename = os.path.basename(parsed_url.path)
# Extract file extension from original filename if available
file_extension = ""
if original_filename and "." in original_filename:
file_extension = os.path.splitext(original_filename)[1]
else:
# Try to determine extension from content-type if no extension found
file_extension = ".bin"
# Generate random filename with preserved extension
filename = str(uuid.uuid4()) + file_extension
destination_path = os.path.join(cache_dir, filename)
print(f"Cache directory: {cache_dir}")
print(f"Destination path: {destination_path}")
print(
"Downloading external file - "
+ file_url
+ " to "
+ destination_path
)
headers = {"User-Agent": "Mozilla/5.0"}
try:
response = requests.get(
file_url,
headers=headers,
allow_redirects=True,
timeout=30, # Add timeout to prevent hanging
)
response.raise_for_status()
with open(destination_path, "wb") as out_file:
out_file.write(response.content)
print(f"External file downloaded: {file_url} to {destination_path}")
return (destination_path,)
except requests.exceptions.HTTPError as e:
error_msg = f"HTTP Error {e.response.status_code}: {e.response.reason} for URL: {file_url}"
print(f"⚠️ Download failed - {error_msg}")
if e.response.status_code == 404:
print(
"💡 This URL might have expired or the file may have been deleted"
)
# Return empty string instead of crashing
return ("",)
except requests.exceptions.RequestException as e:
error_msg = (
f"Network error downloading file from {file_url}: {str(e)}"
)
print(f"⚠️ Download failed - {error_msg}")
return ("",)
except Exception as e:
error_msg = (
f"Unexpected error downloading file from {file_url}: {str(e)}"
)
print(f"⚠️ Download failed - {error_msg}")
return ("",)
else:
print(f"External file loading: {file_url}")
return (file_url,)
else:
print(f"No file URL provided")
return ("",)
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalFile": ComfyUIDeployExternalFile}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalFile": "External File (ComfyUI Deploy)"
}
+47 -28
View File
@@ -15,42 +15,61 @@ class ComfyUIDeployExternalImage:
},
"optional": {
"default_value": ("IMAGE",),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": False, "default": ""},
),
"default_value_url": ("STRING", {"image_preview": True, "default": ""}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
CATEGORY = "image"
def run(self, input_id, default_value=None):
def run(self, input_id, default_value=None, display_name=None, description=None, default_value_url=None):
image = default_value
try:
if input_id.startswith('http'):
import requests
from io import BytesIO
print("Fetching image from url: ", input_id)
response = requests.get(input_id)
image = Image.open(BytesIO(response.content))
elif input_id.startswith('data:image/png;base64,') or input_id.startswith('data:image/jpeg;base64,') or input_id.startswith('data:image/jpg;base64,'):
import base64
from io import BytesIO
print("Decoding base64 image")
base64_image = input_id[input_id.find(",")+1:]
decoded_image = base64.b64decode(base64_image)
image = Image.open(BytesIO(decoded_image))
else:
raise ValueError("Invalid image url provided.")
image = ImageOps.exif_transpose(image)
image = image.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
return [image]
except:
return [image]
# Try both input_id and default_value_url
urls_to_try = [url for url in [input_id, default_value_url] if url]
print(default_value_url)
for url in urls_to_try:
try:
if url.startswith('http'):
import requests
from io import BytesIO
print(f"Fetching image from url: {url}")
response = requests.get(url)
image = Image.open(BytesIO(response.content))
break
elif url.startswith(('data:image/png;base64,', 'data:image/jpeg;base64,', 'data:image/jpg;base64,')):
import base64
from io import BytesIO
print("Decoding base64 image")
base64_image = url[url.find(",")+1:]
decoded_image = base64.b64decode(base64_image)
image = Image.open(BytesIO(decoded_image))
break
except:
continue
if image is not None:
try:
image = ImageOps.exif_transpose(image)
image = image.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
except:
pass
return [image]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalImage": ComfyUIDeployExternalImage}
+10 -4
View File
@@ -15,17 +15,23 @@ class ComfyUIDeployExternalImageAlpha:
},
"optional": {
"default_value": ("IMAGE",),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
CATEGORY = "image"
def run(self, input_id, default_value=None):
def run(self, input_id, default_value=None, display_name=None, description=None):
image = default_value
try:
if input_id.startswith('http'):
+111
View File
@@ -0,0 +1,111 @@
import folder_paths
from PIL import Image, ImageOps
import numpy as np
import torch
import json
import comfy
class ComfyUIDeployExternalImageBatch:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_images"},
),
"images": (
"STRING",
{"multiline": False, "default": "[]"},
),
},
"optional": {
"default_value": ("IMAGE",),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
def process_image(self, image):
image = ImageOps.exif_transpose(image)
image = image.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image_tensor = torch.from_numpy(image)[None,]
return image_tensor
def run(self, input_id, images=None, default_value=None, display_name=None, description=None):
import requests
import zipfile
import io
processed_images = []
try:
images_list = json.loads(images) # Assuming images is a JSON array string
print(images_list)
for img_input in images_list:
if img_input.startswith('http') and img_input.endswith('.zip'):
print("Fetching zip file from url: ", img_input)
response = requests.get(img_input)
zip_file = zipfile.ZipFile(io.BytesIO(response.content))
for file_name in zip_file.namelist():
if file_name.lower().endswith(('.png', '.jpg', '.jpeg')):
with zip_file.open(file_name) as file:
image = Image.open(file)
image = self.process_image(image)
processed_images.append(image)
elif img_input.startswith('http'):
from io import BytesIO
print("Fetching image from url: ", img_input)
response = requests.get(img_input)
image = Image.open(BytesIO(response.content))
elif img_input.startswith('data:image/png;base64,') or img_input.startswith('data:image/jpeg;base64,') or img_input.startswith('data:image/jpg;base64,'):
import base64
from io import BytesIO
print("Decoding base64 image")
base64_image = img_input[img_input.find(",")+1:]
decoded_image = base64.b64decode(base64_image)
image = Image.open(BytesIO(decoded_image))
else:
raise ValueError("Invalid image url or base64 data provided.")
image = ImageOps.exif_transpose(image)
image = image.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image_tensor = torch.from_numpy(image)[None,]
processed_images.append(image_tensor)
except Exception as e:
print(f"Error processing images: {e}")
pass
if default_value is not None and len(images_list) == 0:
processed_images.append(default_value) # Assuming default_value is a pre-processed image tensor
# Resize images if necessary and concatenate from MakeImageBatch in ImpactPack
if processed_images:
base_shape = processed_images[0].shape[1:] # Get the shape of the first image for comparison
batch_tensor = processed_images[0]
for i in range(1, len(processed_images)):
if processed_images[i].shape[1:] != base_shape:
# Resize to match the first image's dimensions
processed_images[i] = comfy.utils.common_upscale(processed_images[i].movedim(-1, 1), base_shape[1], base_shape[0], "lanczos", "center").movedim(1, -1)
batch_tensor = torch.cat((batch_tensor, processed_images[i]), dim=0)
# Concatenate using torch.cat
else:
batch_tensor = None # or handle the empty case as needed
return (batch_tensor, )
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalImageBatch": ComfyUIDeployExternalImageBatch}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyUIDeployExternalImageBatch": "External Image Batch (ComfyUI Deploy)"}
+85 -24
View File
@@ -1,8 +1,12 @@
import folder_paths
from PIL import Image, ImageOps
import numpy as np
import torch
import folder_paths
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
WILDCARD = AnyType("*")
class ComfyUIDeployExternalLora:
@@ -16,36 +20,93 @@ class ComfyUIDeployExternalLora:
),
},
"optional": {
"default_lora_name": (folder_paths.get_filename_list("loras"), ),
}
"default_lora_name": (folder_paths.get_filename_list("loras"),),
"lora_save_name": ( # if `default_lora_name` is a link to download a file, we will attempt to save it with this name
"STRING",
{"multiline": False, "default": ""},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
"lora_url": (
"STRING",
{"multiline": False, "default": ""},
),
"bearer_token": (
"STRING",
{"multiline": False, "default": ""},
),
},
}
RETURN_TYPES = (folder_paths.get_filename_list("loras"),)
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("path",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
CATEGORY = "deploy"
def run(self, input_id, default_lora_name=None):
def run(
self,
input_id,
default_lora_name=None,
lora_save_name=None,
display_name=None,
description=None,
lora_url=None,
bearer_token=None,
):
import requests
import os
import uuid
if input_id and input_id.startswith('http'):
unique_filename = str(uuid.uuid4()) + ".safetensors"
print(unique_filename)
print(folder_paths.folder_names_and_paths["loras"][0][0])
destination_path = os.path.join(folder_paths.folder_names_and_paths["loras"][0][0], unique_filename)
print(destination_path)
print("Downloading external lora - " + input_id + " to " + destination_path)
response = requests.get(input_id, headers={'User-Agent': 'Mozilla/5.0'}, allow_redirects=True)
with open(destination_path, 'wb') as out_file:
out_file.write(response.content)
return (unique_filename,)
if lora_url:
if lora_url.startswith("http"):
if lora_save_name:
existing_loras = folder_paths.get_filename_list("loras")
# Check if lora_save_name exists in the list
if lora_save_name in existing_loras:
print(f"using lora: {lora_save_name}")
return (lora_save_name,)
else:
lora_save_name = str(uuid.uuid4()) + ".safetensors"
print(lora_save_name)
print(folder_paths.folder_names_and_paths["loras"][0][0])
destination_path = os.path.join(
folder_paths.folder_names_and_paths["loras"][0][0], lora_save_name
)
print(destination_path)
print(
"Downloading external lora - "
+ lora_url
+ " to "
+ destination_path
)
headers = {"User-Agent": "Mozilla/5.0"}
if bearer_token:
headers["Authorization"] = f"Bearer {bearer_token}"
print("using bearer token")
response = requests.get(
lora_url,
headers=headers,
allow_redirects=True,
)
with open(destination_path, "wb") as out_file:
out_file.write(response.content)
print(f"Ext Lora loading: {lora_url} to {lora_save_name}")
return (lora_save_name,)
else:
print(f"Ext Lora loading: {lora_url}")
return (lora_url,)
else:
print(f"Ext Lora loading: {default_lora_name}")
return (default_lora_name,)
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalLora": ComfyUIDeployExternalLora}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyUIDeployExternalLora": "External Lora (ComfyUI Deploy)"}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalLora": "External Lora (ComfyUI Deploy)"
}
+11 -5
View File
@@ -16,19 +16,25 @@ class ComfyUIDeployExternalNumber:
"optional": {
"default_value": (
"FLOAT",
{"multiline": True, "display": "number", "default": 0, "step": 0.01},
{"multiline": True, "display": "number", "default": 0, "min": -2147483647, "max": 2147483647, "step": 0.01},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
CATEGORY = "number"
def run(self, input_id, default_value=None):
def run(self, input_id, default_value=None, display_name=None, description=None):
try:
float_value = float(input_id)
print("my number", float_value)
+12 -6
View File
@@ -16,20 +16,26 @@ class ComfyUIDeployExternalNumberInt:
"optional": {
"default_value": (
"INT",
{"multiline": True, "display": "number", "default": 0},
{"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 0},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("value",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
CATEGORY = "number"
def run(self, input_id, default_value=None):
if not input_id or not input_id.strip().isdigit():
def run(self, input_id, default_value=None, display_name=None, description=None):
if not input_id or (isinstance(input_id, str) and not input_id.strip().isdigit()):
return [default_value]
return [int(input_id)]
+54
View File
@@ -0,0 +1,54 @@
class ComfyUIDeployExternalNumberSlider:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_number_slider"},
),
},
"optional": {
"default_value": (
"FLOAT",
{"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 0.5, "step": 0.01},
),
"min_value": (
"FLOAT",
{"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 0, "step": 0.01},
),
"max_value": (
"FLOAT",
{"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 1, "step": 0.01},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = ("FLOAT",)
RETURN_NAMES = ("value",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, min_value=0, max_value=1, display_name=None, description=None):
try:
float_value = float(input_id)
if min_value <= float_value <= max_value:
print("my number", float_value)
return [float_value]
else:
print("Number out of range. Returning default value:", default_value)
return [default_value]
except ValueError:
print("Invalid input. Returning default value:", default_value)
return [default_value]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalNumberSlider": ComfyUIDeployExternalNumberSlider}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyUIDeployExternalNumberSlider": "External Number Slider (ComfyUI Deploy)"}
+54
View File
@@ -0,0 +1,54 @@
class ComfyUIDeployExternalNumberSliderInt:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_number_slider_int"},
),
},
"optional": {
"default_value": (
"INT",
{"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 1, "step": 1},
),
"min_value": (
"INT",
{"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 0, "step": 1},
),
"max_value": (
"INT",
{"multiline": True, "display": "number", "min": -2147483647, "max": 2147483647, "default": 10, "step": 1},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("value",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, min_value=0, max_value=10, display_name=None, description=None):
try:
int_value = int(round(float(input_id)))
if min_value <= int_value <= max_value:
print("my integer", int_value)
return [int_value]
else:
print("Integer out of range. Returning default value:", default_value)
return [default_value]
except (ValueError, TypeError):
print("Invalid input. Returning default value:", default_value)
return [default_value]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalNumberSliderInt": ComfyUIDeployExternalNumberSliderInt}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyUIDeployExternalNumberSliderInt": "External Number Slider Int (ComfyUI Deploy)"}
+116
View File
@@ -0,0 +1,116 @@
import random
class ComfyUIDeployExternalSeed:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_seed"},
),
"default_value": (
"INT",
{"default": -1},
),
"min_value": (
"INT",
{"default": 1, "min": 1, "max": 999999999999999},
),
"max_value": (
"INT",
{"default": 4294967295, "min": 1, "max": 999999999999999},
),
},
"optional": {
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{
"multiline": True,
"default": 'For default value:\n"-1" (i.e. not in range): Randomize within the min and max value range. \nin range: Fixed, always the same value\n',
},
),
},
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("seed",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
# Limits
_MAX_LIMIT = 999_999_999_999_999 # 15 digits
# Store cached seed when fixed flag is enabled
_cached_seed = None
@classmethod
def IS_CHANGED(
cls,
input_id,
min_value,
max_value,
default_value=None,
**kwargs,
):
"""Inform ComfyUI whether the node output should be considered changed.
If default_value is within range (Fixed mode), we return the inputs tuple
so the cached result is reused until the user changes something.
For Randomize mode, we force re-execution each queue.
"""
# Clamp values to allowed range for check
min_value = max(1, min_value)
max_value = min(cls._MAX_LIMIT, max_value)
# Fixed mode when default_value is within range
if (
default_value is not None
and default_value >= min_value
and default_value <= max_value
):
return (input_id, default_value)
# For Randomize (default_value is -1 or out of range) we force re-execution
import random as _rnd
return _rnd.random()
def run(
self,
input_id,
min_value: int,
max_value: int,
display_name=None,
description=None,
default_value: int = -1,
):
# Clamp values to allowed range
min_value = max(1, min_value)
max_value = min(self._MAX_LIMIT, max_value)
# Ensure limits are in correct order after clamping
if min_value > max_value:
min_value, max_value = max_value, min_value
# Fixed mode: default_value is within range
if default_value >= min_value and default_value <= max_value:
seed = int(default_value)
self._cached_seed = seed
return [seed]
# Randomize mode: default_value is -1 or out of range
seed = random.randint(min_value, max_value)
self._cached_seed = seed
return [seed]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalSeed": ComfyUIDeployExternalSeed}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalSeed": "External Seed (ComfyUI Deploy)"
}
+53
View File
@@ -0,0 +1,53 @@
import re
class StringFunction:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"action": (["append", "replace"], {}),
"tidy_tags": (["yes", "no"], {}),
},
"optional": {
"text_a": ("STRING", {"multiline": True, "dynamicPrompts": False}),
"text_b": ("STRING", {"multiline": True, "dynamicPrompts": False}),
"text_c": ("STRING", {"multiline": True, "dynamicPrompts": False}),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "exec"
CATEGORY = "🔗ComfyDeploy"
OUTPUT_NODE = True
def exec(self, action, tidy_tags, text_a="", text_b="", text_c=""):
tidy_tags = tidy_tags == "yes"
out = ""
if action == "append":
out = (", " if tidy_tags else "").join(
filter(None, [text_a, text_b, text_c])
)
else:
if text_c is None:
text_c = ""
if text_b.startswith("/") and text_b.endswith("/"):
regex = text_b[1:-1]
out = re.sub(regex, text_c, text_a)
else:
out = text_a.replace(text_b, text_c)
if tidy_tags:
out = re.sub(r"\s{2,}", " ", out)
out = out.replace(" ,", ",")
out = re.sub(r",{2,}", ",", out)
out = out.strip()
return {"ui": {"text": (out,)}, "result": (out,)}
NODE_CLASS_MAPPINGS = {
"ComfyUIDeployStringCombine": StringFunction,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployStringCombine": "String Combine (ComfyUI Deploy)",
}
+10 -2
View File
@@ -18,6 +18,14 @@ class ComfyUIDeployExternalText:
"STRING",
{"multiline": True, "default": ""},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
@@ -26,9 +34,9 @@ class ComfyUIDeployExternalText:
FUNCTION = "run"
CATEGORY = "text"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None):
def run(self, input_id, default_value=None, display_name=None, description=None):
return [default_value]
+46
View File
@@ -0,0 +1,46 @@
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
WILDCARD = AnyType("*")
class ComfyUIDeployExternalTextAny:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_text"},
),
},
"optional": {
"default_value": (
"STRING",
{"multiline": True, "default": ""},
),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
}
}
RETURN_TYPES = (WILDCARD,)
RETURN_NAMES = ("text",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
def run(self, input_id, default_value=None, display_name=None, description=None):
return [default_value]
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalTextAny": ComfyUIDeployExternalTextAny}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyUIDeployExternalTextAny": "External Text Any (ComfyUI Deploy)"}
+79
View File
@@ -0,0 +1,79 @@
import os
import folder_paths
import uuid
from tqdm import tqdm
video_extensions = ["webm", "mp4", "mkv", "gif"]
class ComfyUIDeployExternalVideo:
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.get_input_directory()
files = []
for f in os.listdir(input_dir):
if os.path.isfile(os.path.join(input_dir, f)):
file_parts = f.split(".")
if len(file_parts) > 1 and (file_parts[-1] in video_extensions):
files.append(f)
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_video"},
),
},
"optional": {
"meta_batch": ("VHS_BatchManager",),
"default_value": (sorted(files),),
},
}
CATEGORY = "Video Helper Suite 🎥🅥🅗🅢"
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("video")
FUNCTION = "load_video"
CATEGORY = "🔗ComfyDeploy"
def load_video(self, input_id, default_value):
input_dir = folder_paths.get_input_directory()
if input_id.startswith("http"):
import requests
print("Fetching video from URL: ", input_id)
response = requests.get(input_id, stream=True)
file_size = int(response.headers.get("Content-Length", 0))
file_extension = input_id.split(".")[-1].split("?")[
0
] # Extract extension and handle URLs with parameters
if file_extension not in video_extensions:
file_extension = ".mp4"
unique_filename = str(uuid.uuid4()) + "." + file_extension
video_path = os.path.join(input_dir, unique_filename)
chunk_size = 1024 # 1 Kibibyte
num_bars = int(file_size / chunk_size)
with open(video_path, "wb") as out_file:
for chunk in tqdm(
response.iter_content(chunk_size=chunk_size),
total=num_bars,
unit="KB",
desc="Downloading",
leave=True,
):
out_file.write(chunk)
else:
video_path = os.path.abspath(os.path.join(input_dir, default_value))
return (video_path,)
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalVid": ComfyUIDeployExternalVideo}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalVid": "External Video (ComfyUI Deploy) path"
}
+898
View File
@@ -0,0 +1,898 @@
# credit goes to https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite
# Intended to work with https://github.com/NicholasKao1029/ComfyUI-VideoHelperSuite/tree/main
import os
import itertools
import numpy as np
import torch
from typing import Union
from torch import Tensor
import cv2
import psutil
from collections.abc import Mapping
import folder_paths
from comfy.utils import common_upscale
### Utils
import hashlib
from typing import Iterable
import shutil
import subprocess
import re
import uuid
import server
from tqdm import tqdm
BIGMIN = -(2**53 - 1)
BIGMAX = 2**53 - 1
DIMMAX = 8192
def ffmpeg_suitability(path):
try:
version = subprocess.run(
[path, "-version"], check=True, capture_output=True
).stdout.decode("utf-8")
except:
return 0
score = 0
# rough layout of the importance of various features
simple_criterion = [
("libvpx", 20),
("264", 10),
("265", 3),
("svtav1", 5),
("libopus", 1),
]
for criterion in simple_criterion:
if version.find(criterion[0]) >= 0:
score += criterion[1]
# obtain rough compile year from copyright information
copyright_index = version.find("2000-2")
if copyright_index >= 0:
copyright_year = version[copyright_index + 6 : copyright_index + 9]
if copyright_year.isnumeric():
score += int(copyright_year)
return score
if "VHS_FORCE_FFMPEG_PATH" in os.environ:
ffmpeg_path = os.environ.get("VHS_FORCE_FFMPEG_PATH")
else:
ffmpeg_paths = []
try:
from imageio_ffmpeg import get_ffmpeg_exe
imageio_ffmpeg_path = get_ffmpeg_exe()
ffmpeg_paths.append(imageio_ffmpeg_path)
except:
if "VHS_USE_IMAGEIO_FFMPEG" in os.environ:
raise
if "VHS_USE_IMAGEIO_FFMPEG" in os.environ:
ffmpeg_path = imageio_ffmpeg_path
else:
system_ffmpeg = shutil.which("ffmpeg")
if system_ffmpeg is not None:
ffmpeg_paths.append(system_ffmpeg)
if os.path.isfile("ffmpeg"):
ffmpeg_paths.append(os.path.abspath("ffmpeg"))
if os.path.isfile("ffmpeg.exe"):
ffmpeg_paths.append(os.path.abspath("ffmpeg.exe"))
if len(ffmpeg_paths) == 0:
ffmpeg_path = None
elif len(ffmpeg_paths) == 1:
# Evaluation of suitability isn't required, can take sole option
# to reduce startup time
ffmpeg_path = ffmpeg_paths[0]
else:
ffmpeg_path = max(ffmpeg_paths, key=ffmpeg_suitability)
gifski_path = os.environ.get("VHS_GIFSKI", None)
if gifski_path is None:
gifski_path = os.environ.get("JOV_GIFSKI", None)
if gifski_path is None:
gifski_path = shutil.which("gifski")
def is_safe_path(path):
if "VHS_STRICT_PATHS" not in os.environ:
return True
basedir = os.path.abspath(".")
try:
common_path = os.path.commonpath([basedir, path])
except:
# Different drive on windows
return False
return common_path == basedir
def get_sorted_dir_files_from_directory(
directory: str,
skip_first_images: int = 0,
select_every_nth: int = 1,
extensions: Iterable = None,
):
directory = strip_path(directory)
dir_files = os.listdir(directory)
dir_files = sorted(dir_files)
dir_files = [os.path.join(directory, x) for x in dir_files]
dir_files = list(filter(lambda filepath: os.path.isfile(filepath), dir_files))
# filter by extension, if needed
if extensions is not None:
extensions = list(extensions)
new_dir_files = []
for filepath in dir_files:
ext = "." + filepath.split(".")[-1]
if ext.lower() in extensions:
new_dir_files.append(filepath)
dir_files = new_dir_files
# start at skip_first_images
dir_files = dir_files[skip_first_images:]
dir_files = dir_files[0::select_every_nth]
return dir_files
# modified from https://stackoverflow.com/questions/22058048/hashing-a-file-in-python
def calculate_file_hash(filename: str, hash_every_n: int = 1):
# Larger video files were taking >.5 seconds to hash even when cached,
# so instead the modified time from the filesystem is used as a hash
h = hashlib.sha256()
h.update(filename.encode())
h.update(str(os.path.getmtime(filename)).encode())
return h.hexdigest()
prompt_queue = server.PromptServer.instance.prompt_queue
def requeue_workflow_unchecked():
"""Requeues the current workflow without checking for multiple requeues"""
currently_running = prompt_queue.currently_running
(_, _, prompt, extra_data, outputs_to_execute) = next(
iter(currently_running.values())
)
# Ensure batch_managers are marked stale
prompt = prompt.copy()
for uid in prompt:
if prompt[uid]["class_type"] == "VHS_BatchManager":
prompt[uid]["inputs"]["requeue"] = (
prompt[uid]["inputs"].get("requeue", 0) + 1
)
# execution.py has guards for concurrency, but server doesn't.
# TODO: Check that this won't be an issue
number = -server.PromptServer.instance.number
server.PromptServer.instance.number += 1
prompt_id = str(server.uuid.uuid4())
prompt_queue.put((number, prompt_id, prompt, extra_data, outputs_to_execute))
requeue_guard = [None, 0, 0, {}]
def requeue_workflow(requeue_required=(-1, True)):
assert len(prompt_queue.currently_running) == 1
global requeue_guard
(run_number, _, prompt, _, _) = next(iter(prompt_queue.currently_running.values()))
if requeue_guard[0] != run_number:
# Calculate a count of how many outputs are managed by a batch manager
managed_outputs = 0
for bm_uid in prompt:
if prompt[bm_uid]["class_type"] == "VHS_BatchManager":
for output_uid in prompt:
if prompt[output_uid]["class_type"] in ["VHS_VideoCombine"]:
for inp in prompt[output_uid]["inputs"].values():
if inp == [bm_uid, 0]:
managed_outputs += 1
requeue_guard = [run_number, 0, managed_outputs, {}]
requeue_guard[1] = requeue_guard[1] + 1
requeue_guard[3][requeue_required[0]] = requeue_required[1]
if requeue_guard[1] == requeue_guard[2] and max(requeue_guard[3].values()):
requeue_workflow_unchecked()
def get_audio(file, start_time=0, duration=0):
args = [ffmpeg_path, "-i", file]
if start_time > 0:
args += ["-ss", str(start_time)]
if duration > 0:
args += ["-t", str(duration)]
try:
# TODO: scan for sample rate and maintain
res = subprocess.run(
args + ["-f", "f32le", "-"], capture_output=True, check=True
)
audio = torch.frombuffer(bytearray(res.stdout), dtype=torch.float32)
match = re.search(", (\\d+) Hz, (\\w+), ", res.stderr.decode("utf-8"))
except subprocess.CalledProcessError as e:
raise Exception(
f"VHS failed to extract audio from {file}:\n" + e.stderr.decode("utf-8")
)
if match:
ar = int(match.group(1))
# NOTE: Just throwing an error for other channel types right now
# Will deal with issues if they come
ac = {"mono": 1, "stereo": 2}[match.group(2)]
else:
ar = 44100
ac = 2
audio = audio.reshape((-1, ac)).transpose(0, 1).unsqueeze(0)
return {"waveform": audio, "sample_rate": ar}
class LazyAudioMap(Mapping):
def __init__(self, file, start_time, duration):
self.file = file
self.start_time = start_time
self.duration = duration
self._dict = None
def __getitem__(self, key):
if self._dict is None:
self._dict = get_audio(self.file, self.start_time, self.duration)
return self._dict[key]
def __iter__(self):
if self._dict is None:
self._dict = get_audio(self.file, self.start_time, self.duration)
return iter(self._dict)
def __len__(self):
if self._dict is None:
self._dict = get_audio(self.file, self.start_time, self.duration)
return len(self._dict)
def lazy_get_audio(file, start_time=0, duration=0):
return LazyAudioMap(file, start_time, duration)
def lazy_eval(func):
class Cache:
def __init__(self, func):
self.res = None
self.func = func
def get(self):
if self.res is None:
self.res = self.func()
return self.res
cache = Cache(func)
return lambda: cache.get()
def is_url(url):
return url.split("://")[0] in ["http", "https"]
def validate_sequence(path):
# Check if path is a valid ffmpeg sequence that points to at least one file
(path, file) = os.path.split(path)
if not os.path.isdir(path):
return False
match = re.search("%0?\d+d", file)
if not match:
return False
seq = match.group()
if seq == "%d":
seq = "\\\\d+"
else:
seq = "\\\\d{%s}" % seq[1:-1]
file_matcher = re.compile(re.sub("%0?\d+d", seq, file))
for file in os.listdir(path):
if file_matcher.fullmatch(file):
return True
return False
def strip_path(path):
# This leaves whitespace inside quotes and only a single "
# thus ' ""test"' -> '"test'
# consider path.strip(string.whitespace+"\"")
# or weightier re.fullmatch("[\\s\"]*(.+?)[\\s\"]*", path).group(1)
path = path.strip()
if path.startswith('"'):
path = path[1:]
if path.endswith('"'):
path = path[:-1]
return path
def hash_path(path):
if path is None:
return "input"
if is_url(path):
return "url"
return calculate_file_hash(path.strip('"'))
def validate_path(path, allow_none=False, allow_url=True):
if path is None:
return allow_none
if is_url(path):
# Probably not feasible to check if url resolves here
return True if allow_url else "URLs are unsupported for this path"
if not os.path.isfile(path.strip('"')):
return "Invalid file path: {}".format(path)
return True
### Utils
video_extensions = ["webm", "mp4", "mkv", "gif"]
def is_gif(filename) -> bool:
file_parts = filename.split(".")
return len(file_parts) > 1 and file_parts[-1] == "gif"
def target_size(
width, height, force_size, custom_width, custom_height
) -> tuple[int, int]:
if force_size == "Custom":
return (custom_width, custom_height)
elif force_size == "Custom Height":
force_size = "?x" + str(custom_height)
elif force_size == "Custom Width":
force_size = str(custom_width) + "x?"
if force_size != "Disabled":
force_size = force_size.split("x")
if force_size[0] == "?":
width = (width * int(force_size[1])) // height
# Limit to a multple of 8 for latent conversion
width = int(width) + 4 & ~7
height = int(force_size[1])
elif force_size[1] == "?":
height = (height * int(force_size[0])) // width
height = int(height) + 4 & ~7
width = int(force_size[0])
else:
width = int(force_size[0])
height = int(force_size[1])
return (width, height)
def validate_index(
index: int,
length: int = 0,
is_range: bool = False,
allow_negative=False,
allow_missing=False,
) -> int:
# if part of range, do nothing
if is_range:
return index
# otherwise, validate index
# validate not out of range - only when latent_count is passed in
if length > 0 and index > length - 1 and not allow_missing:
raise IndexError(f"Index '{index}' out of range for {length} item(s).")
# if negative, validate not out of range
if index < 0:
if not allow_negative:
raise IndexError(f"Negative indeces not allowed, but was '{index}'.")
conv_index = length + index
if conv_index < 0 and not allow_missing:
raise IndexError(
f"Index '{index}', converted to '{conv_index}' out of range for {length} item(s)."
)
index = conv_index
return index
def convert_to_index_int(
raw_index: str,
length: int = 0,
is_range: bool = False,
allow_negative=False,
allow_missing=False,
) -> int:
try:
return validate_index(
int(raw_index),
length=length,
is_range=is_range,
allow_negative=allow_negative,
allow_missing=allow_missing,
)
except ValueError as e:
raise ValueError(f"Index '{raw_index}' must be an integer.", e)
def convert_str_to_indexes(
indexes_str: str, length: int = 0, allow_missing=False
) -> list[int]:
if not indexes_str:
return []
int_indexes = list(range(0, length))
allow_negative = length > 0
chosen_indexes = []
# parse string - allow positive ints, negative ints, and ranges separated by ':'
groups = indexes_str.split(",")
groups = [g.strip() for g in groups]
for g in groups:
# parse range of indeces (e.g. 2:16)
if ":" in g:
index_range = g.split(":", 2)
index_range = [r.strip() for r in index_range]
start_index = index_range[0]
if len(start_index) > 0:
start_index = convert_to_index_int(
start_index,
length=length,
is_range=True,
allow_negative=allow_negative,
allow_missing=allow_missing,
)
else:
start_index = 0
end_index = index_range[1]
if len(end_index) > 0:
end_index = convert_to_index_int(
end_index,
length=length,
is_range=True,
allow_negative=allow_negative,
allow_missing=allow_missing,
)
else:
end_index = length
# support step as well, to allow things like reversing, every-other, etc.
step = 1
if len(index_range) > 2:
step = index_range[2]
if len(step) > 0:
step = convert_to_index_int(
step,
length=length,
is_range=True,
allow_negative=True,
allow_missing=True,
)
else:
step = 1
# if latents were passed in, base indeces on known latent count
if len(int_indexes) > 0:
chosen_indexes.extend(int_indexes[start_index:end_index][::step])
# otherwise, assume indeces are valid
else:
chosen_indexes.extend(list(range(start_index, end_index, step)))
# parse individual indeces
else:
chosen_indexes.append(
convert_to_index_int(
g,
length=length,
allow_negative=allow_negative,
allow_missing=allow_missing,
)
)
return chosen_indexes
def select_indexes(input_obj: Union[Tensor, list], idxs: list):
if type(input_obj) == Tensor:
return input_obj[idxs]
else:
return [input_obj[i] for i in idxs]
def select_indexes_from_str(
input_obj: Union[Tensor, list], indexes: str, err_if_missing=True, err_if_empty=True
):
real_idxs = convert_str_to_indexes(
indexes, len(input_obj), allow_missing=not err_if_missing
)
if err_if_empty and len(real_idxs) == 0:
raise Exception(f"Nothing was selected based on indexes found in '{indexes}'.")
return select_indexes(input_obj, real_idxs)
###
def cv_frame_generator(
video,
force_rate,
frame_load_cap,
skip_first_frames,
select_every_nth,
meta_batch=None,
unique_id=None,
):
video_cap = cv2.VideoCapture(strip_path(video))
if not video_cap.isOpened():
raise ValueError(f"{video} could not be loaded with cv.")
pbar = None
# extract video metadata
fps = video_cap.get(cv2.CAP_PROP_FPS)
width = int(video_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(video_cap.get(cv2.CAP_PROP_FRAME_COUNT))
duration = total_frames / fps
# set video_cap to look at start_index frame
total_frame_count = 0
total_frames_evaluated = -1
frames_added = 0
base_frame_time = 1 / fps
prev_frame = None
if force_rate == 0:
target_frame_time = base_frame_time
else:
target_frame_time = 1 / force_rate
yield (width, height, fps, duration, total_frames, target_frame_time)
if meta_batch is not None:
yield min(frame_load_cap, total_frames)
time_offset = target_frame_time - base_frame_time
while video_cap.isOpened():
if time_offset < target_frame_time:
is_returned = video_cap.grab()
# if didn't return frame, video has ended
if not is_returned:
break
time_offset += base_frame_time
if time_offset < target_frame_time:
continue
time_offset -= target_frame_time
# if not at start_index, skip doing anything with frame
total_frame_count += 1
if total_frame_count <= skip_first_frames:
continue
else:
total_frames_evaluated += 1
# if should not be selected, skip doing anything with frame
if total_frames_evaluated % select_every_nth != 0:
continue
# opencv loads images in BGR format (yuck), so need to convert to RGB for ComfyUI use
# follow up: can videos ever have an alpha channel?
# To my testing: No. opencv has no support for alpha
unused, frame = video_cap.retrieve()
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# convert frame to comfyui's expected format
# TODO: frame contains no exif information. Check if opencv2 has already applied
frame = np.array(frame, dtype=np.float32)
torch.from_numpy(frame).div_(255)
if prev_frame is not None:
inp = yield prev_frame
if inp is not None:
# ensure the finally block is called
return
prev_frame = frame
frames_added += 1
if pbar is not None:
pbar.update_absolute(frames_added, frame_load_cap)
# if cap exists and we've reached it, stop processing frames
if frame_load_cap > 0 and frames_added >= frame_load_cap:
break
if meta_batch is not None:
meta_batch.inputs.pop(unique_id)
meta_batch.has_closed_inputs = True
if prev_frame is not None:
yield prev_frame
def batched(it, n):
while batch := tuple(itertools.islice(it, n)):
yield batch
def batched_vae_encode(images, vae, frames_per_batch):
for batch in batched(images, frames_per_batch):
image_batch = torch.from_numpy(np.array(batch))
yield from vae.encode(image_batch).numpy()
def load_video_cv(
video: str,
force_rate: int,
force_size: str,
custom_width: int,
custom_height: int,
frame_load_cap: int,
skip_first_frames: int,
select_every_nth: int,
meta_batch=None,
unique_id=None,
memory_limit_mb=None,
vae=None,
):
if meta_batch is None or unique_id not in meta_batch.inputs:
gen = cv_frame_generator(
video,
force_rate,
frame_load_cap,
skip_first_frames,
select_every_nth,
meta_batch,
unique_id,
)
(width, height, fps, duration, total_frames, target_frame_time) = next(gen)
if meta_batch is not None:
meta_batch.inputs[unique_id] = (
gen,
width,
height,
fps,
duration,
total_frames,
target_frame_time,
)
meta_batch.total_frames = min(meta_batch.total_frames, next(gen))
else:
(gen, width, height, fps, duration, total_frames, target_frame_time) = (
meta_batch.inputs[unique_id]
)
memory_limit = None
if memory_limit_mb is not None:
memory_limit *= 2**20
else:
# TODO: verify if garbage collection should be performed here.
# leaves ~128 MB unreserved for safety
try:
memory_limit = (
psutil.virtual_memory().available + psutil.swap_memory().free
) - 2**27
except:
print(
"Failed to calculate available memory. Memory load limit has been disabled"
)
if memory_limit is not None:
if vae is not None:
# space required to load as f32, exist as latent with wiggle room, decode to f32
max_loadable_frames = int(
memory_limit // (width * height * 3 * (4 + 4 + 1 / 10))
)
else:
# TODO: use better estimate for when vae is not None
# Consider completely ignoring for load_latent case?
max_loadable_frames = int(memory_limit // (width * height * 3 * (0.1)))
if meta_batch is not None:
if meta_batch.frames_per_batch > max_loadable_frames:
raise RuntimeError(
f"Meta Batch set to {meta_batch.frames_per_batch} frames but only {max_loadable_frames} can fit in memory"
)
gen = itertools.islice(gen, meta_batch.frames_per_batch)
else:
original_gen = gen
gen = itertools.islice(gen, max_loadable_frames)
downscale_ratio = getattr(vae, "downscale_ratio", 8)
frames_per_batch = (1920 * 1080 * 16) // (width * height) or 1
if force_size != "Disabled" or vae is not None:
new_size = target_size(
width, height, force_size, custom_width, custom_height, downscale_ratio
)
if new_size[0] != width or new_size[1] != height:
def rescale(frame):
s = torch.from_numpy(
np.fromiter(frame, np.dtype((np.float32, (height, width, 3))))
)
s = s.movedim(-1, 1)
s = common_upscale(s, new_size[0], new_size[1], "lanczos", "center")
return s.movedim(1, -1).numpy()
gen = itertools.chain.from_iterable(
map(rescale, batched(gen, frames_per_batch))
)
else:
new_size = width, height
if vae is not None:
gen = batched_vae_encode(gen, vae, frames_per_batch)
vw, vh = new_size[0] // downscale_ratio, new_size[1] // downscale_ratio
images = torch.from_numpy(np.fromiter(gen, np.dtype((np.float32, (4, vh, vw)))))
else:
# Some minor wizardry to eliminate a copy and reduce max memory by a factor of ~2
images = torch.from_numpy(
np.fromiter(gen, np.dtype((np.float32, (new_size[1], new_size[0], 3))))
)
if meta_batch is None and memory_limit is not None:
try:
next(original_gen)
raise RuntimeError(
f"Memory limit hit after loading {len(images)} frames. Stopping execution."
)
except StopIteration:
pass
if len(images) == 0:
raise RuntimeError("No frames generated")
# Setup lambda for lazy audio capture
audio = lazy_get_audio(
video,
skip_first_frames * target_frame_time,
frame_load_cap * target_frame_time * select_every_nth,
)
# Adjust target_frame_time for select_every_nth
target_frame_time *= select_every_nth
video_info = {
"source_fps": fps,
"source_frame_count": total_frames,
"source_duration": duration,
"source_width": width,
"source_height": height,
"loaded_fps": 1 / target_frame_time,
"loaded_frame_count": len(images),
"loaded_duration": len(images) * target_frame_time,
"loaded_width": new_size[0],
"loaded_height": new_size[1],
}
if vae is None:
return (images, len(images), audio, video_info, None)
else:
return (None, len(images), audio, video_info, {"samples": images})
# modeled after Video upload node
class ComfyUIDeployExternalVideo:
@classmethod
def INPUT_TYPES(s):
input_dir = folder_paths.get_input_directory()
files = []
for f in os.listdir(input_dir):
if os.path.isfile(os.path.join(input_dir, f)):
file_parts = f.split(".")
if len(file_parts) > 1 and (file_parts[-1] in video_extensions):
files.append(f)
return {
"required": {
"input_id": (
"STRING",
{"multiline": False, "default": "input_video"},
),
"force_rate": ("INT", {"default": 0, "min": 0, "max": 60, "step": 1}),
"force_size": (
[
"Disabled",
"Custom Height",
"Custom Width",
"Custom",
"256x?",
"?x256",
"256x256",
"512x?",
"?x512",
"512x512",
],
),
"custom_width": (
"INT",
{"default": 512, "min": 0, "max": DIMMAX, "step": 8},
),
"custom_height": (
"INT",
{"default": 512, "min": 0, "max": DIMMAX, "step": 8},
),
"frame_load_cap": (
"INT",
{"default": 0, "min": 0, "max": BIGMAX, "step": 1},
),
"skip_first_frames": (
"INT",
{"default": 0, "min": 0, "max": BIGMAX, "step": 1},
),
"select_every_nth": (
"INT",
{"default": 1, "min": 1, "max": BIGMAX, "step": 1},
),
},
"optional": {
"meta_batch": ("VHS_BatchManager",),
"vae": ("VAE",),
"default_video": (sorted(files),),
"display_name": (
"STRING",
{"multiline": False, "default": ""},
),
"description": (
"STRING",
{"multiline": True, "default": ""},
),
"default_value_url": ("STRING", {"image_preview": True, "default": ""}),
},
"hidden": {"unique_id": "UNIQUE_ID"},
}
CATEGORY = "Video Helper Suite 🎥🅥🅗🅢"
RETURN_TYPES = ("IMAGE", "INT", "AUDIO", "VHS_VIDEOINFO", "LATENT")
RETURN_NAMES = (
"IMAGE",
"frame_count",
"audio",
"video_info",
"LATENT",
)
FUNCTION = "load_video"
CATEGORY = "🔗ComfyDeploy"
def load_video(self, **kwargs):
input_id = kwargs.get("input_id")
force_rate = kwargs.get("force_rate")
force_size = kwargs.get("force_size", "Disabled")
custom_width = kwargs.get("custom_width")
custom_height = kwargs.get("custom_height")
frame_load_cap = kwargs.get("frame_load_cap")
skip_first_frames = kwargs.get("skip_first_frames")
select_every_nth = kwargs.get("select_every_nth")
meta_batch = kwargs.get("meta_batch")
unique_id = kwargs.get("unique_id")
default_value_url = kwargs.get("default_value_url")
input_dir = folder_paths.get_input_directory()
if input_id.startswith("http") or (
default_value_url and default_value_url.startswith("http")
):
import requests
# Use input_id if it's a URL, otherwise use default_value_url
url = input_id if input_id.startswith("http") else default_value_url
print("Fetching video from URL: ", url)
response = requests.get(url, stream=True)
file_size = int(response.headers.get("Content-Length", 0))
file_extension = url.split(".")[-1].split("?")[
0
] # Extract extension and handle URLs with parameters
if file_extension not in video_extensions:
file_extension = ".mp4"
unique_filename = str(uuid.uuid4()) + "." + file_extension
video_path = os.path.join(input_dir, unique_filename)
chunk_size = 1024 # 1 Kibibyte
num_bars = int(file_size / chunk_size)
with open(video_path, "wb") as out_file:
for chunk in tqdm(
response.iter_content(chunk_size=chunk_size),
total=num_bars,
unit="KB",
desc="Downloading",
leave=True,
):
out_file.write(chunk)
else:
video = kwargs.get("default_video", None)
if video is None:
raise "No default video given and no external video provided"
video_path = folder_paths.get_annotated_filepath(video.strip('"'))
return load_video_cv(
video=video_path,
force_rate=force_rate,
force_size=force_size,
custom_width=custom_width,
custom_height=custom_height,
frame_load_cap=frame_load_cap,
skip_first_frames=skip_first_frames,
select_every_nth=select_every_nth,
meta_batch=meta_batch,
unique_id=unique_id,
)
@classmethod
def IS_CHANGED(s, video, **kwargs):
image_path = folder_paths.get_annotated_filepath(video)
return calculate_file_hash(image_path)
NODE_CLASS_MAPPINGS = {"ComfyUIDeployExternalVideo": ComfyUIDeployExternalVideo}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyUIDeployExternalVideo": "External Video (ComfyUI Deploy x VHS)"
}
+1
View File
@@ -33,6 +33,7 @@ class ComfyDeployWebscoketImageInput:
RETURN_NAMES = ("images",)
FUNCTION = "run"
CATEGORY = "🔗ComfyDeploy"
@classmethod
def VALIDATE_INPUTS(s, input_id):
+78
View File
@@ -0,0 +1,78 @@
# In file: comfyui-deploy/comfy-nodes/output_exr.py
import os
import numpy as np
import folder_paths
# Try to set up OpenCV for EXR writing.
try:
os.environ["OPENCV_IO_ENABLE_OPENEXR"] = "1"
import cv2
OPENCV_AVAILABLE = True
except ImportError:
print("Warning: OpenCV not found for ComfyDeployOutputEXR. Please add opencv-python-headless to requirements.txt")
OPENCV_AVAILABLE = False
# ALIGNED: Renamed class to match project conventions
class ComfyDeployOutputEXR:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE", ),
"filename_prefix": ("STRING", {"default": "ComfyDeploy_EXR"})
},
# ADDED: Optional output_id for consistency with other ComfyDeploy nodes
"optional": {
"output_id": ("STRING", {"multiline": False, "default": "output_exr"}),
},
}
RETURN_TYPES = ()
# ALIGNED: Changed function name to 'run'
FUNCTION = "run"
OUTPUT_NODE = True
# ALIGNED: Matched the category name
CATEGORY = "🔗ComfyDeploy"
DESCRIPTION = "Saves the input images as EXR (HDR) files."
def run(self, images, filename_prefix="ComfyDeploy_EXR", output_id="output_exr"):
if not OPENCV_AVAILABLE:
raise ImportError("OpenCV is required to save EXR files. Please ensure opencv-python-headless is in requirements.txt.")
full_output_folder, filename, counter, subfolder, filename_prefix = (
folder_paths.get_save_image_path(
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]
)
)
results = list()
for image in images:
image_np = image.cpu().numpy()
if image_np.dtype != np.float32:
image_np = image_np.astype(np.float32)
file = f"{filename}_{counter:05}.exr"
file_path = os.path.join(full_output_folder, file)
image_np_bgr = cv2.cvtColor(image_np, cv2.COLOR_RGB2BGR)
cv2.imwrite(file_path, image_np_bgr)
results.append({
"filename": file,
"subfolder": subfolder,
"type": self.type,
"output_id": output_id, # ADDED
})
counter += 1
return {"ui": {"images": results}}
# ALIGNED: Mappings are defined at the bottom of the node file in this project
NODE_CLASS_MAPPINGS = {"ComfyDeployOutputEXR": ComfyDeployOutputEXR}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyDeployOutputEXR": "EXR Output (ComfyDeploy)"}
+168
View File
@@ -0,0 +1,168 @@
import folder_paths
import os
import shutil
import uuid
class AnyType(str):
def __ne__(self, __value: object) -> bool:
return False
WILDCARD = AnyType("*")
class ComfyDeployOutputFile:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"file_path": (
"STRING",
{
"forceInput": True,
"tooltip": "Path to the file to output and upload.",
},
),
},
"optional": {
"output_id": (
"STRING",
{"multiline": False, "default": "output_file"},
),
},
}
RETURN_TYPES = ()
FUNCTION = "run"
OUTPUT_NODE = True
CATEGORY = "🔗ComfyDeploy"
DESCRIPTION = "Outputs any file by path for upload to ComfyDeploy."
def run(self, file_path, output_id="output_file"):
if not file_path or not os.path.exists(file_path):
print(f"⚠️ File not found: {file_path}")
return {"ui": {"files": []}}
# Security checks - ensure file is within safe ComfyUI paths
try:
# Get absolute paths for comparison
file_abs_path = os.path.abspath(file_path)
base_path = folder_paths.base_path
temp_dir = folder_paths.get_temp_directory()
# Check if file is within ComfyUI base path or temp directory
if not (
file_abs_path.startswith(os.path.abspath(base_path))
or file_abs_path.startswith(os.path.abspath(temp_dir))
):
print(f"⚠️ Security: File outside allowed ComfyUI paths: {file_path}")
return {"ui": {"files": []}}
# Check for path traversal attempts (but allow absolute paths within ComfyUI)
if ".." in file_path:
print(f"⚠️ Security: Path traversal attempt detected: {file_path}")
return {"ui": {"files": []}}
except Exception as e:
print(f"⚠️ Security check failed: {str(e)}")
return {"ui": {"files": []}}
# Get the original filename and extension
original_filename = os.path.basename(file_path)
file_extension = os.path.splitext(original_filename)[1]
# Additional filename security check
if ".." in original_filename:
print(f"⚠️ Security: Insecure filename: {original_filename}")
return {"ui": {"files": []}}
results = []
# Check if file is in output folder, if not, symlink it there
try:
if file_path.startswith(self.output_dir):
# File is already in output directory - use as is
relative_path = os.path.relpath(file_path, self.output_dir)
path_parts = relative_path.split(os.sep)
if len(path_parts) > 1:
subfolder = os.sep.join(path_parts[:-1])
else:
subfolder = ""
filename = path_parts[-1]
file_type = self.type
else:
# File is not in output folder - symlink it to output/temp
print(
f"File is not in output folder, symlinking to output/temp: {file_path}"
)
output_temp_dir = os.path.join(self.output_dir, "temp")
if not os.path.exists(output_temp_dir):
os.makedirs(output_temp_dir)
# Use the existing filename but with UUID prefix to avoid conflicts
file_ext = os.path.splitext(original_filename)[1]
temp_filename = f"{uuid.uuid4()}{file_ext}"
temp_path = os.path.join(output_temp_dir, temp_filename)
# Create symlink to file in output/temp directory where upload system expects it
try:
# Remove existing symlink if it exists
if os.path.exists(temp_path):
os.remove(temp_path)
os.symlink(file_path, temp_path)
print(f"File symlinked to output/temp: {temp_path} -> {file_path}")
except OSError as e:
# Fall back to copying if symlink fails
print(f"Symlink failed ({e}), falling back to copy")
shutil.copy2(file_path, temp_path)
print(f"File copied to output/temp: {temp_path}")
# Use output/temp directory structure for upload
subfolder = "temp"
filename = temp_filename
file_type = self.type
results.append(
{
"filename": filename,
"subfolder": subfolder,
"type": file_type,
"output_id": output_id,
}
)
except Exception as e:
print(f"⚠️ Error processing file path: {str(e)}")
return {"ui": {"files": []}}
# Determine the appropriate UI key based on file type
file_ext = file_extension.lower()
if file_ext in [".png", ".jpg", ".jpeg", ".webp", ".gif", ".bmp", ".tiff"]:
ui_key = "images"
elif file_ext in [".mp3", ".wav", ".flac", ".aac", ".ogg"]:
ui_key = "audio"
elif file_ext in [".txt", ".json", ".md", ".csv"]:
ui_key = "text_file"
elif file_ext in [".exr", ".hdr"]:
ui_key = "images" # EXR files are still images
elif file_ext in [".zip", ".psb", ".psd"]:
ui_key = "files" # Archives and Photoshop project files
else:
ui_key = "files" # Generic files
return {"ui": {ui_key: results}}
NODE_CLASS_MAPPINGS = {
"ComfyDeployOutputFile": ComfyDeployOutputFile,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"ComfyDeployOutputFile": "File Output (ComfyDeploy)",
}
+104
View File
@@ -0,0 +1,104 @@
import os
import json
import numpy as np
from PIL import Image
from PIL.PngImagePlugin import PngInfo
import folder_paths
from comfy.cli_args import args
class ComfyDeployOutputImage:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = ""
self.compress_level = 4
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"images": ("IMAGE", {"tooltip": "The images to save."}),
"filename_prefix": (
"STRING",
{
"default": "ComfyUI",
"tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes.",
},
),
"file_type": (["png", "jpg", "webp"], {"default": "webp"}),
"quality": ("INT", {"default": 80, "min": 1, "max": 100, "step": 1}),
},
"optional": {
"output_id": (
"STRING",
{"multiline": False, "default": "output_images"},
),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ()
FUNCTION = "run"
OUTPUT_NODE = True
CATEGORY = "🔗ComfyDeploy"
DESCRIPTION = "Saves the input images to your ComfyUI output directory."
def run(
self,
images,
filename_prefix="ComfyUI",
file_type="png",
quality=80,
output_id="output_images",
prompt=None,
extra_pnginfo=None,
):
filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = (
folder_paths.get_save_image_path(
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]
)
)
results = list()
for batch_number, image in enumerate(images):
i = 255.0 * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
metadata = None
if not args.disable_metadata:
metadata = PngInfo()
if prompt is not None:
metadata.add_text("prompt", json.dumps(prompt))
if extra_pnginfo is not None:
for x in extra_pnginfo:
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
filename_with_batch_num = filename.replace("%batch_num%", str(batch_number))
file = f"{filename_with_batch_num}_{counter:05}_.{file_type}"
file_path = os.path.join(full_output_folder, file)
if file_type == "png":
img.save(
file_path, pnginfo=metadata, compress_level=self.compress_level
)
elif file_type == "jpg":
img.save(file_path, quality=quality, optimize=True)
elif file_type == "webp":
img.save(file_path, quality=quality)
results.append(
{
"filename": file,
"subfolder": subfolder,
"type": self.type,
"output_id": output_id,
}
)
counter += 1
return {"ui": {"images": results}}
NODE_CLASS_MAPPINGS = {"ComfyDeployOutputImage": ComfyDeployOutputImage}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyDeployOutputImage": "Image Output (ComfyDeploy)"}
+99
View File
@@ -0,0 +1,99 @@
import os
import json
import folder_paths
class ComfyDeployOutputText:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = ""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": (
"STRING",
{
"multiline": True,
"forceInput": True,
"tooltip": "The text to save.",
},
),
"filename_prefix": (
"STRING",
{
"default": "ComfyUI",
"tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% to include values from nodes.",
},
),
"file_type": (["txt", "json", "md"], {"default": "txt"}),
},
"optional": {
"output_id": (
"STRING",
{"multiline": False, "default": "output_text"},
),
},
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
}
RETURN_TYPES = ()
FUNCTION = "run"
OUTPUT_NODE = True
CATEGORY = "🔗ComfyDeploy"
DESCRIPTION = "Saves the input text to your ComfyUI output directory."
def run(
self,
text,
filename_prefix="ComfyUI",
file_type="txt",
output_id="output_text",
prompt=None,
extra_pnginfo=None,
):
filename_prefix += self.prefix_append
# For text, we don't need dimensions, so pass 0, 0
full_output_folder, filename, counter, subfolder, filename_prefix = (
folder_paths.get_save_image_path(filename_prefix, self.output_dir, 0, 0)
)
results = list()
# Create file path
file = f"{filename}_{counter:05}_.{file_type}"
file_path = os.path.join(full_output_folder, file)
# Save the text based on file type
if file_type == "json":
try:
# Try to save as JSON if the text is valid JSON
json_data = json.loads(text) if isinstance(text, str) else text
with open(file_path, "w", encoding="utf-8") as f:
json.dump(json_data, f, indent=2)
except json.JSONDecodeError:
# Fall back to saving as plain text if not valid JSON
with open(file_path, "w", encoding="utf-8") as f:
f.write(text)
else:
# Save as plain text for txt and md
with open(file_path, "w", encoding="utf-8") as f:
f.write(text)
results.append(
{
"filename": file,
"subfolder": subfolder,
"type": self.type,
"output_id": output_id,
}
)
return {"ui": {"text_file": results}}
NODE_CLASS_MAPPINGS = {"ComfyDeployOutputText": ComfyDeployOutputText}
NODE_DISPLAY_NAME_MAPPINGS = {"ComfyDeployOutputText": "Text Output (ComfyDeploy)"}
+1 -3
View File
@@ -33,10 +33,8 @@ class ComfyDeployWebscoketImageOutput:
RETURN_TYPES = ()
RETURN_NAMES = ("text",)
FUNCTION = "run"
CATEGORY = "output"
CATEGORY = "🔗ComfyDeploy"
@classmethod
def VALIDATE_INPUTS(s, output_id):
+3128 -458
View File
File diff suppressed because it is too large Load Diff
-55
View File
@@ -1,55 +0,0 @@
version: '3.9'
services:
comfy-deploy:
build:
context: .
dockerfile: ./local/Dockerfile
restart: unless-stopped
volumes:
- ./local/scripts/entrypoint.sh:/comfyui-deploy/web/deploy_entrypoint.sh
entrypoint: /comfyui-deploy/web/deploy_entrypoint.sh
ports:
- 3000:3000
depends_on:
- postgres
- pg_proxy
- localstack
environment:
VSCODE_DEV_CONTAINER: true
### comfy-deploy services
postgres:
image: "postgres:15.2-alpine"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: verceldb
POSTGRES_PORT: 5480
expose:
- 5480
pg_proxy:
image: ghcr.io/neondatabase/wsproxy:latest
environment:
APPEND_PORT: "postgres:5480"
ALLOW_ADDR_REGEX: ".*"
LOG_TRAFFIC: "true"
expose:
- 80
depends_on:
- postgres
localstack:
image: localstack/localstack:latest
environment:
SERVICES: s3
ports:
- 4566:4566
volumes:
- ../localstack/aws:/etc/localstack/init/ready.d
- ../localstack/aws:/app/web/aws
Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

+152
View File
@@ -0,0 +1,152 @@
{
"id": "ed93ac94-4f26-4ed3-a57b-73cd8f4d3494",
"revision": 0,
"last_node_id": 5,
"last_link_id": 1,
"nodes": [
{
"id": 2,
"type": "LoraLoader",
"pos": [
736.646728515625,
628.3823852539062
],
"size": [
315,
126
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "clip",
"type": "CLIP",
"link": null
},
{
"name": "lora_name",
"type": "COMBO",
"widget": {
"name": "lora_name"
},
"link": 1
}
],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": null
},
{
"name": "CLIP",
"type": "CLIP",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "LoraLoader"
},
"widgets_values": [
"1-292.safetensors",
1,
1
]
},
{
"id": 1,
"type": "ComfyUIDeployExternalLora",
"pos": [
299.6898498535156,
624.7929077148438
],
"size": [
400,
208
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "path",
"type": "*",
"links": [
1
]
}
],
"properties": {
"cnr_id": "comfyui-deploy",
"ver": "cd3a2ff5471828f9c840e746551592e882c05aa4",
"Node name for S&R": "ComfyUIDeployExternalLora"
},
"widgets_values": [
"input_lora",
"HyperSD\\FLUX.1\\Hyper-FLUX.1-dev-16steps-lora.safetensors",
"",
"",
"",
""
]
},
{
"id": 5,
"type": "Note",
"pos": [
302.09033203125,
401.2951965332031
],
"size": [
479.4894104003906,
161.61924743652344
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"\"External Lora\" node will let you to use different loras from the Comfy Deploy UI or even via API.\n\n- lora_url:\n url that will be used to download your LoRA model in execution time\n\n- lora_save_name:\n when we download your model, this will be saved in your private storage, \n give it a good name :D"
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
[
1,
1,
0,
2,
2,
"COMBO"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.167184107045006,
"offset": [
298.431389807788,
-207.58877445762934
]
},
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

+873
View File
@@ -0,0 +1,873 @@
{
"id": "351f402b-62f2-4f62-8a5e-0b9d3510e8f9",
"revision": 0,
"last_node_id": 29,
"last_link_id": 28,
"nodes": [
{
"id": 11,
"type": "JoinImageWithAlpha",
"pos": [
814.478271484375,
419.3052062988281
],
"size": [
264.5999755859375,
46
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 10
},
{
"name": "alpha",
"type": "MASK",
"link": 12
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
11
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "JoinImageWithAlpha"
},
"widgets_values": []
},
{
"id": 15,
"type": "PreviewImage",
"pos": [
1950,
640
],
"size": [
210,
246
],
"flags": {},
"order": 18,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 16
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 10,
"type": "LoadImage",
"pos": [
467.8168640136719,
422.453857421875
],
"size": [
315,
314
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
10
]
},
{
"name": "MASK",
"type": "MASK",
"links": [
12
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"Bob-Minion-Background-PNG-Image.png",
"image",
""
]
},
{
"id": 18,
"type": "Note",
"pos": [
460.8001708984375,
263.64251708984375
],
"size": [
379.4292297363281,
88
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"Option 1: CREATE THE MASK FROM THE ALPHA CHANNEL (Useful for example to generate the background of an image)\n\nMake sure that you are using \"External Image Alpha\". \n"
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 21,
"type": "LoadImage",
"pos": [
469.57025146484375,
1506.3018798828125
],
"size": [
315,
314
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
18
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"ComfyUI_temp_otmos_00005_.png",
"image",
""
]
},
{
"id": 25,
"type": "MaskToImage",
"pos": [
1283.1668701171875,
1579.603271484375
],
"size": [
176.39999389648438,
26
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "mask",
"type": "MASK",
"link": 28
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
21
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "MaskToImage"
},
"widgets_values": []
},
{
"id": 13,
"type": "SplitImageWithAlpha",
"pos": [
1602.5518798828125,
417.59869384765625
],
"size": [
277.20001220703125,
46
],
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 13
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
14,
25
]
},
{
"name": "MASK",
"type": "MASK",
"links": [
15,
26
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "SplitImageWithAlpha"
},
"widgets_values": []
},
{
"id": 29,
"type": "VAEEncodeForInpaint",
"pos": [
2220.89453125,
401.0885009765625
],
"size": [
340.20001220703125,
98
],
"flags": {},
"order": 16,
"mode": 0,
"inputs": [
{
"name": "pixels",
"type": "IMAGE",
"link": 25
},
{
"name": "vae",
"type": "VAE",
"link": null
},
{
"name": "mask",
"type": "MASK",
"link": 26
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "VAEEncodeForInpaint"
},
"widgets_values": [
6
]
},
{
"id": 14,
"type": "PreviewImage",
"pos": [
1950,
350
],
"size": [
210,
246
],
"flags": {},
"order": 14,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 14
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 17,
"type": "MaskToImage",
"pos": [
1752.33203125,
572.061767578125
],
"size": [
176.39999389648438,
26
],
"flags": {},
"order": 15,
"mode": 0,
"inputs": [
{
"name": "mask",
"type": "MASK",
"link": 15
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
16
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "MaskToImage"
},
"widgets_values": []
},
{
"id": 27,
"type": "PreviewImage",
"pos": [
1329.2960205078125,
1132.759765625
],
"size": [
210,
246
],
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 22
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 20,
"type": "LoadImage",
"pos": [
468.1963806152344,
1128.9498291015625
],
"size": [
315,
314
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
17
]
},
{
"name": "MASK",
"type": "MASK",
"links": []
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"clipspace/clipspace-mask-1126817.300000012.png [input]",
"image",
""
]
},
{
"id": 24,
"type": "ImageToMask",
"pos": [
1255.560302734375,
1468.347900390625
],
"size": [
315,
58
],
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 19
}
],
"outputs": [
{
"name": "MASK",
"type": "MASK",
"links": [
24,
28
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "ImageToMask"
},
"widgets_values": [
"red"
]
},
{
"id": 26,
"type": "PreviewImage",
"pos": [
1488.1925048828125,
1579.146728515625
],
"size": [
210,
246
],
"flags": {},
"order": 17,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 21
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 28,
"type": "VAEEncodeForInpaint",
"pos": [
1637.603271484375,
1182.81298828125
],
"size": [
340.20001220703125,
98
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "pixels",
"type": "IMAGE",
"link": 23
},
{
"name": "vae",
"type": "VAE",
"link": null
},
{
"name": "mask",
"type": "MASK",
"link": 24
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.27",
"Node name for S&R": "VAEEncodeForInpaint"
},
"widgets_values": [
6
]
},
{
"id": 19,
"type": "Note",
"pos": [
467.4615173339844,
985.1439819335938
],
"size": [
379.4292297363281,
88
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"Option 2: UPLOAD THE MASK AS OTHER IMAGE (useful if you require the image that is below the mask to do inpainting, in this case to generate new glasses)"
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 22,
"type": "ComfyUIDeployExternalImage",
"pos": [
910.8199462890625,
1132.306396484375
],
"size": [
390.5999755859375,
154
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "default_value",
"shape": 7,
"type": "IMAGE",
"link": 17
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
22,
23
]
}
],
"properties": {
"cnr_id": "comfyui-deploy",
"ver": "cd3a2ff5471828f9c840e746551592e882c05aa4",
"Node name for S&R": "ComfyUIDeployExternalImage"
},
"widgets_values": [
"input_image",
"",
"",
"",
""
]
},
{
"id": 23,
"type": "ComfyUIDeployExternalImage",
"pos": [
843.6348876953125,
1467.8876953125
],
"size": [
390.5999755859375,
154
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "default_value",
"shape": 7,
"type": "IMAGE",
"link": 18
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
19
]
}
],
"properties": {
"cnr_id": "comfyui-deploy",
"ver": "cd3a2ff5471828f9c840e746551592e882c05aa4",
"Node name for S&R": "ComfyUIDeployExternalImage"
},
"widgets_values": [
"input_image_mask",
"",
"",
"",
""
]
},
{
"id": 12,
"type": "ComfyUIDeployExternalImageAlpha",
"pos": [
1107.891357421875,
418.2679138183594
],
"size": [
466.1999816894531,
200
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "default_value",
"shape": 7,
"type": "IMAGE",
"link": 11
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
13
]
}
],
"properties": {
"cnr_id": "comfyui-deploy",
"ver": "cd3a2ff5471828f9c840e746551592e882c05aa4",
"Node name for S&R": "ComfyUIDeployExternalImageAlpha"
},
"widgets_values": [
"input_image_alpha",
"",
""
]
}
],
"links": [
[
10,
10,
0,
11,
0,
"IMAGE"
],
[
11,
11,
0,
12,
0,
"IMAGE"
],
[
12,
10,
1,
11,
1,
"MASK"
],
[
13,
12,
0,
13,
0,
"IMAGE"
],
[
14,
13,
0,
14,
0,
"IMAGE"
],
[
15,
13,
1,
17,
0,
"MASK"
],
[
16,
17,
0,
15,
0,
"IMAGE"
],
[
17,
20,
0,
22,
0,
"IMAGE"
],
[
18,
21,
0,
23,
0,
"IMAGE"
],
[
19,
23,
0,
24,
0,
"IMAGE"
],
[
21,
25,
0,
26,
0,
"IMAGE"
],
[
22,
22,
0,
27,
0,
"IMAGE"
],
[
23,
22,
0,
28,
0,
"IMAGE"
],
[
24,
24,
0,
28,
2,
"MASK"
],
[
25,
13,
0,
29,
0,
"IMAGE"
],
[
26,
13,
1,
29,
2,
"MASK"
],
[
28,
24,
0,
25,
0,
"MASK"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 0.9646149645000013,
"offset": [
48.66905973637718,
-817.5683540167485
]
},
"VHS_latentpreview": false,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
+359
View File
@@ -0,0 +1,359 @@
{
"extra": {
"ds": {
"scale": 0.6010518407212623,
"offset": [815.5938895649746, 84.94304700477853]
},
"node_versions": {
"comfy-core": "0.3.19",
"comfyui-deploy": "b3df94d1affcf7ce05ee7eeda99989194bcd9159"
}
},
"links": [
[9, 8, 0, 9, 0, "IMAGE"],
[45, 30, 1, 6, 0, "CLIP"],
[46, 30, 2, 8, 1, "VAE"],
[47, 30, 0, 31, 0, "MODEL"],
[51, 27, 0, 31, 3, "LATENT"],
[52, 31, 0, 8, 0, "LATENT"],
[54, 30, 1, 33, 0, "CLIP"],
[55, 33, 0, 31, 2, "CONDITIONING"],
[56, 6, 0, 35, 0, "CONDITIONING"],
[57, 35, 0, 31, 1, "CONDITIONING"],
[58, 38, 0, 6, 1, "STRING"],
[59, 40, 0, 27, 0, "INT"],
[60, 39, 0, 27, 1, "INT"]
],
"nodes": [
{
"id": 6,
"pos": [384, 192],
"mode": 0,
"size": [422.8500061035156, 164.30999755859375],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 7,
"title": "CLIP Text Encode (Positive Prompt)",
"inputs": [
{ "link": 45, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 58,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [56],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"cute anime girl with massive fluffy fennec ears and a big fluffy tail blonde messy long hair blue eyes wearing a maid outfit with a long black gold leaf pattern dress and a white apron mouth open placing a fancy black forest cake with candles on top of a dinner table of an old dark Victorian mansion lit by candlelight with a bright window to the foggy forest and very expensive stuff everywhere there are paintings on the walls"
]
},
{
"id": 8,
"pos": [1151, 195],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 11,
"inputs": [
{ "link": 52, "name": "samples", "type": "LATENT" },
{ "link": 46, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [9], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 9,
"pos": [1375, 194],
"mode": 0,
"size": [985.2999877929688, 1060.3800048828125],
"type": "SaveImage",
"flags": {},
"order": 12,
"inputs": [{ "link": 9, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI"]
},
{
"id": 31,
"pos": [816, 192],
"mode": 0,
"size": [315, 262],
"type": "KSampler",
"flags": {},
"order": 10,
"inputs": [
{ "link": 47, "name": "model", "type": "MODEL" },
{ "link": 57, "name": "positive", "type": "CONDITIONING" },
{ "link": 55, "name": "negative", "type": "CONDITIONING" },
{ "link": 51, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [52],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
1024035737089801,
"randomize",
20,
1,
"euler",
"simple",
1
]
},
{
"id": 35,
"pos": [576, 96],
"mode": 0,
"size": [211.60000610351562, 58],
"type": "FluxGuidance",
"flags": {},
"order": 9,
"inputs": [
{ "link": 56, "name": "conditioning", "type": "CONDITIONING" }
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [57],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "FluxGuidance" },
"widgets_values": [3.5]
},
{
"id": 37,
"pos": [60, 345],
"mode": 0,
"size": [225, 88],
"type": "MarkdownNote",
"color": "#432",
"flags": {},
"order": 0,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": {},
"widgets_values": [
"🛈 [Learn more about this workflow](https://comfyanonymous.github.io/ComfyUI_examples/flux/#flux-dev-1)"
]
},
{
"id": 34,
"pos": [825, 510],
"mode": 0,
"size": [282.8599853515625, 164.0800018310547],
"type": "Note",
"color": "#432",
"flags": {},
"order": 1,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "text": "" },
"widgets_values": [
"Note that Flux dev and schnell do not have any negative prompt so CFG should be set to 1.0. Setting CFG to 1.0 means the negative prompt is ignored."
]
},
{
"id": 30,
"pos": [48, 192],
"mode": 0,
"size": [315, 98],
"type": "CheckpointLoaderSimple",
"flags": {},
"order": 2,
"inputs": [],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [47],
"shape": 3,
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [45, 54],
"shape": 3,
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [46],
"shape": 3,
"slot_index": 2
}
],
"properties": { "Node name for S&R": "CheckpointLoaderSimple" },
"widgets_values": ["FLUX1/flux1-dev-fp8.safetensors"]
},
{
"id": 33,
"pos": [390, 400],
"mode": 0,
"size": [422.8500061035156, 164.30999755859375],
"type": "CLIPTextEncode",
"color": "#322",
"flags": { "collapsed": true },
"order": 6,
"title": "CLIP Text Encode (Negative Prompt)",
"inputs": [
{ "link": 54, "name": "clip", "type": "CLIP", "slot_index": 0 }
],
"bgcolor": "#533",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [55],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [""]
},
{
"id": 27,
"pos": [461.8629455566406, 460.2491149902344],
"mode": 0,
"size": [315, 126],
"type": "EmptySD3LatentImage",
"color": "#323",
"flags": {},
"order": 8,
"inputs": [
{
"pos": [10, 60],
"link": 59,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
},
{
"pos": [10, 36],
"link": 60,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
}
],
"bgcolor": "#535",
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [51],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "EmptySD3LatentImage" },
"widgets_values": [1024, 1024, 1]
},
{
"id": 38,
"pos": [-497.3238525390625, 306.5517578125],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 3,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [58] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"prompt",
"cute anime girl with massive fluffy fennec ears and a big fluffy tail blonde messy long hair blue eyes wearing a maid outfit with a long black gold leaf pattern dress and a white apron mouth open placing a fancy black forest cake with candles on top of a dinner table of an old dark Victorian mansion lit by candlelight with a bright window to the foggy forest and very expensive stuff everywhere there are paintings on the walls",
"Prompt",
"The prompt to generate an image from."
]
},
{
"id": 39,
"pos": [-507.4543762207031, 612.9552001953125],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [60], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 1024, "Width", "The width of the image."]
},
{
"id": 40,
"pos": [-497.8695068359375, 864.68994140625],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [59], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 1024, "Height", "The height of the image."]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-508.010986328125, 186.76593017578125, 453.23040771484375,
334.3218994140625
],
"font_size": 24
},
{
"id": 2,
"color": "#A88",
"flags": {},
"title": "Additional",
"bounding": [
-517.1227416992188, 537.53759765625, 644.4954223632812,
599.2609252929688
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 60,
"last_node_id": 40
}
+423
View File
@@ -0,0 +1,423 @@
{
"extra": {
"ds": {
"scale": 0.6830134553650709,
"offset": [750.2474149300585, 120.48924647647146]
},
"node_versions": {
"comfy-core": "0.3.19",
"comfyui-deploy": "171a227856bd5f31e97828d89f83f3741004d05e"
}
},
"links": [
[9, 8, 0, 9, 0, "IMAGE"],
[10, 11, 0, 6, 0, "CLIP"],
[12, 10, 0, 8, 1, "VAE"],
[19, 16, 0, 13, 2, "SAMPLER"],
[20, 17, 0, 13, 3, "SIGMAS"],
[23, 5, 0, 13, 4, "LATENT"],
[24, 13, 0, 8, 0, "LATENT"],
[30, 22, 0, 13, 1, "GUIDER"],
[37, 25, 0, 13, 0, "NOISE"],
[38, 12, 0, 17, 0, "MODEL"],
[39, 12, 0, 22, 0, "MODEL"],
[40, 6, 0, 22, 1, "CONDITIONING"],
[41, 28, 0, 6, 1, "STRING"],
[44, 29, 0, 5, 0, "INT"],
[45, 30, 0, 5, 1, "INT"]
],
"nodes": [
{
"id": 13,
"pos": [842, 215],
"mode": 0,
"size": [355.20001220703125, 106],
"type": "SamplerCustomAdvanced",
"flags": {},
"order": 14,
"inputs": [
{ "link": 37, "name": "noise", "type": "NOISE", "slot_index": 0 },
{ "link": 30, "name": "guider", "type": "GUIDER", "slot_index": 1 },
{ "link": 19, "name": "sampler", "type": "SAMPLER", "slot_index": 2 },
{ "link": 20, "name": "sigmas", "type": "SIGMAS", "slot_index": 3 },
{
"link": 23,
"name": "latent_image",
"type": "LATENT",
"slot_index": 4
}
],
"outputs": [
{
"name": "output",
"type": "LATENT",
"links": [24],
"shape": 3,
"slot_index": 0
},
{
"name": "denoised_output",
"type": "LATENT",
"links": null,
"shape": 3
}
],
"properties": { "Node name for S&R": "SamplerCustomAdvanced" },
"widgets_values": []
},
{
"id": 8,
"pos": [1248, 192],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 15,
"inputs": [
{ "link": 24, "name": "samples", "type": "LATENT" },
{ "link": 12, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [9], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 22,
"pos": [559, 125],
"mode": 0,
"size": [241.79998779296875, 46],
"type": "BasicGuider",
"flags": {},
"order": 13,
"inputs": [
{ "link": 39, "name": "model", "type": "MODEL", "slot_index": 0 },
{
"link": 40,
"name": "conditioning",
"type": "CONDITIONING",
"slot_index": 1
}
],
"outputs": [
{
"name": "GUIDER",
"type": "GUIDER",
"links": [30],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "BasicGuider" },
"widgets_values": []
},
{
"id": 16,
"pos": [480, 720],
"mode": 0,
"size": [315, 58],
"type": "KSamplerSelect",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "SAMPLER", "type": "SAMPLER", "links": [19], "shape": 3 }
],
"properties": { "Node name for S&R": "KSamplerSelect" },
"widgets_values": ["euler"]
},
{
"id": 17,
"pos": [480, 816],
"mode": 0,
"size": [315, 106],
"type": "BasicScheduler",
"flags": {},
"order": 10,
"inputs": [
{ "link": 38, "name": "model", "type": "MODEL", "slot_index": 0 }
],
"outputs": [
{ "name": "SIGMAS", "type": "SIGMAS", "links": [20], "shape": 3 }
],
"properties": { "Node name for S&R": "BasicScheduler" },
"widgets_values": ["simple", 4, 1]
},
{
"id": 27,
"pos": [480, 960],
"mode": 0,
"size": [311.3529052734375, 131.16229248046875],
"type": "Note",
"color": "#432",
"flags": {},
"order": 1,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "text": "" },
"widgets_values": [
"The schnell model is a distilled model that can generate a good image with only 4 steps."
]
},
{
"id": 9,
"pos": [1488, 192],
"mode": 0,
"size": [985.3012084960938, 1060.3828125],
"type": "SaveImage",
"flags": {},
"order": 16,
"inputs": [{ "link": 9, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI"]
},
{
"id": 25,
"pos": [480, 587.7890625],
"mode": 0,
"size": [315, 82],
"type": "RandomNoise",
"color": "#2a363b",
"flags": {},
"order": 2,
"inputs": [],
"bgcolor": "#3f5159",
"outputs": [
{ "name": "NOISE", "type": "NOISE", "links": [37], "shape": 3 }
],
"properties": { "Node name for S&R": "RandomNoise" },
"widgets_values": [393845863554716, "randomize"]
},
{
"id": 5,
"pos": [471.7746276855469, 423.91571044921875],
"mode": 0,
"size": [315, 126],
"type": "EmptyLatentImage",
"color": "#323",
"flags": {},
"order": 12,
"inputs": [
{
"pos": [10, 36],
"link": 44,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"pos": [10, 60],
"link": 45,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"bgcolor": "#535",
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [23], "slot_index": 0 }
],
"properties": { "Node name for S&R": "EmptyLatentImage" },
"widgets_values": [1024, 1024, 1]
},
{
"id": 26,
"pos": [41.60395812988281, 517.059326171875],
"mode": 0,
"size": [336, 288],
"type": "Note",
"color": "#432",
"flags": {},
"order": 3,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "text": "" },
"widgets_values": [
"If you get an error in any of the nodes above make sure the files are in the correct directories.\n\nSee the top of the examples page for the links : https://comfyanonymous.github.io/ComfyUI_examples/flux/\n\nflux1-schnell.safetensors goes in: ComfyUI/models/unet/\n\nt5xxl_fp16.safetensors and clip_l.safetensors go in: ComfyUI/models/clip/\n\nae.safetensors goes in: ComfyUI/models/vae/\n\n\nTip: You can set the weight_dtype above to one of the fp8 types if you have memory issues."
]
},
{
"id": 11,
"pos": [45.584651947021484, 229.13525390625],
"mode": 0,
"size": [315, 122],
"type": "DualCLIPLoader",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"links": [10],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "DualCLIPLoader" },
"widgets_values": [
"t5xxl_fp16.safetensors",
"clip_l.safetensors",
"flux",
"default"
]
},
{
"id": 12,
"pos": [45.92215347290039, 95.6536865234375],
"mode": 0,
"size": [315, 82],
"type": "UNETLoader",
"color": "#223",
"flags": { "pinned": true },
"order": 5,
"inputs": [],
"bgcolor": "#335",
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [38, 39],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "UNETLoader" },
"widgets_values": ["flux1-schnell.safetensors", "default"]
},
{
"id": 6,
"pos": [385.87054443359375, 216.75625610351562],
"mode": 0,
"size": [422.84503173828125, 164.31304931640625],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 11,
"inputs": [
{ "link": 10, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 41,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [40],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"a bottle with a beautiful rainbow galaxy inside it on top of a wooden table in the middle of a modern kitchen beside a plate of vegetables and mushrooms and a wine glasse that contains a planet earth with a plate with a half eaten apple pie on it"
]
},
{
"id": 10,
"pos": [42.45906066894531, 405.2187805175781],
"mode": 0,
"size": [315, 58],
"type": "VAELoader",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{
"name": "VAE",
"type": "VAE",
"links": [12],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "VAELoader" },
"widgets_values": ["ae.sft"]
},
{
"id": 28,
"pos": [-620.3922119140625, 199.8013916015625],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 7,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [41] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"prompt",
"a bottle with a beautiful rainbow galaxy inside it on top of a wooden table in the middle of a modern kitchen beside a plate of vegetables and mushrooms and a wine glasse that contains a planet earth with a plate with a half eaten apple pie on it",
"Prompt",
"The prompt to generate an image from."
]
},
{
"id": 29,
"pos": [-677.4290161132812, 597.3389282226562],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 8,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [44], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 1024, "Width", "The width of the image."]
},
{
"id": 30,
"pos": [-670.0703735351562, 848.3707885742188],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 9,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [45], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 1024, "Height", "The height of the image."]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-652.184326171875, 99.11231994628906, 565.7923583984375,
362.8800354003906
],
"font_size": 24
},
{
"id": 2,
"color": "#A88",
"flags": {},
"title": "Additional",
"bounding": [
-720.3274536132812, 508.1206970214844, 703.0422973632812,
576.5584716796875
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 45,
"last_node_id": 30
}
+482
View File
@@ -0,0 +1,482 @@
{
"extra": {
"ds": {
"scale": 0.6830134553650709,
"offset": [1030.9627343190857, 159.73178253107423]
},
"groupNodes": {},
"node_versions": {
"comfy-core": "0.3.12",
"comfyui-deploy": "b3df94d1affcf7ce05ee7eeda99989194bcd9159"
}
},
"links": [
[19, 16, 0, 13, 2, "SAMPLER"],
[20, 17, 0, 13, 3, "SIGMAS"],
[30, 22, 0, 13, 1, "GUIDER"],
[37, 25, 0, 13, 0, "NOISE"],
[129, 26, 0, 22, 1, "CONDITIONING"],
[175, 44, 0, 26, 0, "CONDITIONING"],
[180, 45, 0, 13, 4, "LATENT"],
[181, 13, 0, 8, 0, "LATENT"],
[190, 12, 0, 17, 0, "MODEL"],
[195, 67, 0, 22, 0, "MODEL"],
[205, 11, 0, 44, 0, "CLIP"],
[206, 10, 0, 8, 1, "VAE"],
[209, 12, 0, 67, 0, "MODEL"],
[210, 13, 0, 73, 0, "LATENT"],
[211, 10, 0, 73, 1, "VAE"],
[215, 73, 0, 75, 0, "IMAGE"],
[216, 78, 0, 44, 1, "STRING"],
[217, 79, 0, 45, 0, "INT"],
[218, 83, 0, 45, 1, "INT"]
],
"nodes": [
{
"id": 16,
"pos": [484, 751],
"mode": 0,
"size": [315, 58],
"type": "KSamplerSelect",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "SAMPLER", "type": "SAMPLER", "links": [19], "shape": 3 }
],
"properties": { "Node name for S&R": "KSamplerSelect" },
"widgets_values": ["euler"]
},
{
"id": 17,
"pos": [478, 860],
"mode": 0,
"size": [315, 106],
"type": "BasicScheduler",
"flags": {},
"order": 10,
"inputs": [
{ "link": 190, "name": "model", "type": "MODEL", "slot_index": 0 }
],
"outputs": [
{ "name": "SIGMAS", "type": "SIGMAS", "links": [20], "shape": 3 }
],
"properties": { "Node name for S&R": "BasicScheduler" },
"widgets_values": ["simple", 20, 1]
},
{
"id": 26,
"pos": [520, 100],
"mode": 0,
"size": [317.4000244140625, 58],
"type": "FluxGuidance",
"color": "#233",
"flags": {},
"order": 14,
"inputs": [
{ "link": 175, "name": "conditioning", "type": "CONDITIONING" }
],
"bgcolor": "#355",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [129],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "FluxGuidance" },
"widgets_values": [6]
},
{
"id": 45,
"pos": [475.540771484375, 432.673583984375],
"mode": 0,
"size": [315, 130],
"type": "EmptyHunyuanLatentVideo",
"flags": {},
"order": 13,
"inputs": [
{
"link": 217,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"link": 218,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [180], "slot_index": 0 }
],
"properties": { "Node name for S&R": "EmptyHunyuanLatentVideo" },
"widgets_values": [848, 480, 73, 1]
},
{
"id": 22,
"pos": [600, 0],
"mode": 0,
"size": [222.3482666015625, 46],
"type": "BasicGuider",
"flags": {},
"order": 15,
"inputs": [
{ "link": 195, "name": "model", "type": "MODEL", "slot_index": 0 },
{
"link": 129,
"name": "conditioning",
"type": "CONDITIONING",
"slot_index": 1
}
],
"outputs": [
{
"name": "GUIDER",
"type": "GUIDER",
"links": [30],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "BasicGuider" },
"widgets_values": []
},
{
"id": 67,
"pos": [360, 0],
"mode": 0,
"size": [210, 58],
"type": "ModelSamplingSD3",
"flags": {},
"order": 11,
"inputs": [{ "link": 209, "name": "model", "type": "MODEL" }],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [195], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ModelSamplingSD3" },
"widgets_values": [7]
},
{
"id": 73,
"pos": [1150, 200],
"mode": 0,
"size": [210, 150],
"type": "VAEDecodeTiled",
"flags": {},
"order": 18,
"inputs": [
{ "link": 210, "name": "samples", "type": "LATENT" },
{ "link": 211, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [215], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecodeTiled" },
"widgets_values": [256, 64, 64, 8]
},
{
"id": 8,
"pos": [1150, 90],
"mode": 2,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 17,
"inputs": [
{ "link": 181, "name": "samples", "type": "LATENT" },
{ "link": 206, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 77,
"pos": [0, 0],
"mode": 0,
"size": [350, 110],
"type": "Note",
"color": "#432",
"flags": {},
"order": 1,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": {},
"widgets_values": [
"Select a fp8 weight_dtype if you are running out of memory."
]
},
{
"id": 13,
"pos": [860, 200],
"mode": 0,
"size": [272.3617858886719, 124.53733825683594],
"type": "SamplerCustomAdvanced",
"flags": {},
"order": 16,
"inputs": [
{ "link": 37, "name": "noise", "type": "NOISE", "slot_index": 0 },
{ "link": 30, "name": "guider", "type": "GUIDER", "slot_index": 1 },
{ "link": 19, "name": "sampler", "type": "SAMPLER", "slot_index": 2 },
{ "link": 20, "name": "sigmas", "type": "SIGMAS", "slot_index": 3 },
{
"link": 180,
"name": "latent_image",
"type": "LATENT",
"slot_index": 4
}
],
"outputs": [
{
"name": "output",
"type": "LATENT",
"links": [181, 210],
"shape": 3,
"slot_index": 0
},
{
"name": "denoised_output",
"type": "LATENT",
"links": null,
"shape": 3
}
],
"properties": { "Node name for S&R": "SamplerCustomAdvanced" },
"widgets_values": []
},
{
"id": 75,
"pos": [1410, 200],
"mode": 0,
"size": [315, 366],
"type": "SaveAnimatedWEBP",
"flags": {},
"order": 19,
"inputs": [{ "link": 215, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI", 24, false, 80, "default"]
},
{
"id": 25,
"pos": [479, 618],
"mode": 0,
"size": [315, 82],
"type": "RandomNoise",
"color": "#2a363b",
"flags": {},
"order": 2,
"inputs": [],
"bgcolor": "#3f5159",
"outputs": [
{ "name": "NOISE", "type": "NOISE", "links": [37], "shape": 3 }
],
"properties": { "Node name for S&R": "RandomNoise" },
"widgets_values": [1, "randomize"]
},
{
"id": 12,
"pos": [0, 150],
"mode": 0,
"size": [350, 82],
"type": "UNETLoader",
"color": "#223",
"flags": {},
"order": 3,
"inputs": [],
"bgcolor": "#335",
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [190, 209],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "UNETLoader" },
"widgets_values": ["hunyuan_video_t2v_720p_bf16.safetensors", "default"]
},
{
"id": 10,
"pos": [0, 420],
"mode": 0,
"size": [350, 60],
"type": "VAELoader",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{
"name": "VAE",
"type": "VAE",
"links": [206, 211],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "VAELoader" },
"widgets_values": ["hunyuan_video_vae_bf16.safetensors"]
},
{
"id": 11,
"pos": [0, 270],
"mode": 0,
"size": [350, 106],
"type": "DualCLIPLoader",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"links": [205],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "DualCLIPLoader" },
"widgets_values": [
"clip_l.safetensors",
"llava_llama3_fp8_scaled.safetensors",
"hunyuan_video",
"default"
]
},
{
"id": 44,
"pos": [459.0518798828125, 226.60147094726562],
"mode": 0,
"size": [285.6000061035156, 54],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 12,
"title": "CLIP Text Encode (Positive Prompt)",
"inputs": [
{ "link": 205, "name": "clip", "type": "CLIP" },
{
"link": 216,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [175],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"anime style anime girl with massive fennec ears and one big fluffy tail, she has blonde hair long hair blue eyes wearing a pink sweater and a long blue skirt walking in a beautiful outdoor scenery with snow mountains in the background"
]
},
{
"id": 83,
"pos": [-591.1870727539062, 751.6737670898438],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [218], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 480, "Height", "The height of the video."]
},
{
"id": 74,
"pos": [1151.89599609375, 402.439697265625],
"mode": 0,
"size": [210, 170],
"type": "Note",
"color": "#432",
"flags": {},
"order": 7,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": {},
"widgets_values": [
"Use the tiled decode node by default because most people will need it.\n\nLower the tile_size and overlap if you run out of memory."
]
},
{
"id": 78,
"pos": [-560.058837890625, 155.3986358642578],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 8,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [216] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"prompt",
"anime style anime girl with massive fennec ears and one big fluffy tail, she has blonde hair long hair blue eyes wearing a pink sweater and a long blue skirt walking in a beautiful outdoor scenery with snow mountains in the background",
"Prompt",
"The prompt to generate the video from."
]
},
{
"id": 79,
"pos": [-588.2138061523438, 493.3861389160156],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 9,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [217], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 848, "Width", "The width of the video."]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-662.9779052734375, 14.93773078918457, 575.2218627929688,
384.41424560546875
],
"font_size": 24
},
{
"id": 2,
"color": "#b06634",
"flags": {},
"title": "Additional",
"bounding": [
-659.7621459960938, 414.3213195800781, 570.7182006835938,
594.72216796875
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 218,
"last_node_id": 83
}
+240
View File
@@ -0,0 +1,240 @@
{
"extra": {
"ds": { "scale": 1, "offset": { "0": 0, "1": 0 } },
"node_versions": {
"comfy-core": "0.3.12",
"comfyui-deploy": "171a227856bd5f31e97828d89f83f3741004d05e"
}
},
"links": [
[1, 4, 0, 3, 0, "MODEL"],
[2, 5, 0, 3, 3, "LATENT"],
[3, 4, 1, 6, 0, "CLIP"],
[4, 6, 0, 3, 1, "CONDITIONING"],
[5, 4, 1, 7, 0, "CLIP"],
[6, 7, 0, 3, 2, "CONDITIONING"],
[7, 3, 0, 8, 0, "LATENT"],
[8, 4, 2, 8, 1, "VAE"],
[9, 8, 0, 9, 0, "IMAGE"],
[10, 12, 0, 6, 1, "STRING"],
[11, 13, 0, 7, 1, "STRING"]
],
"nodes": [
{
"id": 5,
"pos": [473, 609],
"mode": 0,
"size": [315, 106],
"type": "EmptyLatentImage",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [2], "slot_index": 0 }
],
"properties": { "Node name for S&R": "EmptyLatentImage" },
"widgets_values": [512, 512, 1]
},
{
"id": 3,
"pos": [863, 186],
"mode": 0,
"size": [315, 262],
"type": "KSampler",
"flags": {},
"order": 7,
"inputs": [
{ "link": 1, "name": "model", "type": "MODEL" },
{ "link": 4, "name": "positive", "type": "CONDITIONING" },
{ "link": 6, "name": "negative", "type": "CONDITIONING" },
{ "link": 2, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [7], "slot_index": 0 }
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
156680208700286,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 8,
"pos": [1209, 188],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 8,
"inputs": [
{ "link": 7, "name": "samples", "type": "LATENT" },
{ "link": 8, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [9], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 9,
"pos": [1451, 189],
"mode": 0,
"size": [210, 58],
"type": "SaveImage",
"flags": {},
"order": 9,
"inputs": [{ "link": 9, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI"]
},
{
"id": 4,
"pos": [26, 474],
"mode": 0,
"size": [315, 98],
"type": "CheckpointLoaderSimple",
"flags": {},
"order": 1,
"inputs": [],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [1], "slot_index": 0 },
{ "name": "CLIP", "type": "CLIP", "links": [3, 5], "slot_index": 1 },
{ "name": "VAE", "type": "VAE", "links": [8], "slot_index": 2 }
],
"properties": { "Node name for S&R": "CheckpointLoaderSimple" },
"widgets_values": ["v1-5-pruned-emaonly.ckpt"]
},
{
"id": 6,
"pos": [415, 186],
"mode": 0,
"size": [422.84503173828125, 164.31304931640625],
"type": "CLIPTextEncode",
"flags": {},
"order": 6,
"inputs": [
{ "link": 3, "name": "clip", "type": "CLIP" },
{
"link": 10,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [4],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 7,
"pos": [413, 389],
"mode": 0,
"size": [425.27801513671875, 180.6060791015625],
"type": "CLIPTextEncode",
"flags": {},
"order": 5,
"inputs": [
{ "link": 5, "name": "clip", "type": "CLIP" },
{
"link": 11,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [6],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": ["text, watermark"]
},
{
"id": 13,
"pos": [-70, -60],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 2,
"inputs": [],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": [11],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": ["negative_prompt", "text, watermark", "", ""]
},
{
"id": 12,
"pos": [-72, 193],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 3,
"inputs": [],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": [10],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"positive_prompt",
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
"",
""
]
},
{
"id": 14,
"pos": [419, 23],
"mode": 0,
"size": [210, 96],
"type": "ComfyDeploy",
"color": "#432",
"flags": {},
"order": 4,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "version": "", "workflow_id": "", "workflow_name": "" },
"widgets_values": ["", "", "1"]
}
],
"config": {},
"groups": [],
"version": 0.4,
"last_link_id": 11,
"last_node_id": 14
}
+409
View File
@@ -0,0 +1,409 @@
{
"extra": {
"ds": {
"scale": 0.620921323059155,
"offset": [1535.1422819333457, 285.0502099350181]
},
"node_versions": {
"comfy-core": "0.3.12",
"comfyui-deploy": "171a227856bd5f31e97828d89f83f3741004d05e"
}
},
"links": [
[7, 3, 0, 8, 0, "LATENT"],
[21, 16, 0, 3, 1, "CONDITIONING"],
[51, 8, 0, 9, 0, "IMAGE"],
[53, 4, 2, 8, 1, "VAE"],
[80, 40, 0, 3, 2, "CONDITIONING"],
[99, 4, 0, 3, 0, "MODEL"],
[100, 53, 0, 3, 3, "LATENT"],
[103, 43, 0, 16, 0, "CLIP"],
[104, 43, 0, 40, 0, "CLIP"],
[105, 54, 0, 16, 1, "STRING"],
[106, 55, 0, 40, 1, "STRING"],
[107, 56, 0, 53, 0, "INT"],
[108, 57, 0, 53, 1, "INT"]
],
"nodes": [
{
"id": 8,
"pos": [1200, 96],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 14,
"inputs": [
{ "link": 7, "name": "samples", "type": "LATENT" },
{ "link": 53, "name": "vae", "type": "VAE", "slot_index": 1 }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [51], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 9,
"pos": [1440, 96],
"mode": 0,
"size": [952.5112915039062, 1007.9328002929688],
"type": "SaveImage",
"flags": {},
"order": 15,
"inputs": [
{ "link": 51, "name": "images", "type": "IMAGE", "slot_index": 0 }
],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI"]
},
{
"id": 53,
"pos": [480, 576],
"mode": 0,
"size": [315, 106],
"type": "EmptySD3LatentImage",
"flags": {},
"order": 10,
"inputs": [
{
"link": 107,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"link": 108,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [100],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "EmptySD3LatentImage" },
"widgets_values": [1024, 1024, 1]
},
{
"id": 50,
"pos": [-384, 144],
"mode": 0,
"size": [223.34756469726562, 254.37765502929688],
"type": "Note",
"color": "#432",
"flags": {},
"order": 0,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "text": "" },
"widgets_values": [
"SD3 supports different text encoder configurations, you can see how to load them here.\n\n\nMake sure to put these files:\nclip_g.safetensors\nclip_l.safetensors\nt5xxl_fp16.safetensors\n\n\nIn the ComfyUI/models/clip directory"
]
},
{
"id": 41,
"pos": [-96, 0],
"mode": 0,
"size": [315, 82],
"type": "CLIPLoader",
"flags": {},
"order": 1,
"inputs": [],
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"links": [],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPLoader" },
"widgets_values": ["t5xxl_fp16.safetensors", "sd3", "default"]
},
{
"id": 4,
"pos": [-96, 480],
"mode": 0,
"size": [384.75592041015625, 98],
"type": "CheckpointLoaderSimple",
"flags": {},
"order": 2,
"inputs": [],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [99], "slot_index": 0 },
{ "name": "CLIP", "type": "CLIP", "links": [], "slot_index": 1 },
{ "name": "VAE", "type": "VAE", "links": [53], "slot_index": 2 }
],
"properties": { "Node name for S&R": "CheckpointLoaderSimple" },
"widgets_values": ["sd3.5_large.safetensors"]
},
{
"id": 3,
"pos": [864, 96],
"mode": 0,
"size": [315, 474],
"type": "KSampler",
"flags": {},
"order": 13,
"inputs": [
{ "link": 99, "name": "model", "type": "MODEL", "slot_index": 0 },
{ "link": 21, "name": "positive", "type": "CONDITIONING" },
{ "link": 80, "name": "negative", "type": "CONDITIONING" },
{ "link": 100, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [7], "slot_index": 0 }
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
797639488625463,
"randomize",
20,
5.45,
"euler",
"sgm_uniform",
1
]
},
{
"id": 51,
"pos": [-96, 624],
"mode": 0,
"size": [384, 192],
"type": "Note",
"color": "#432",
"flags": {},
"order": 3,
"inputs": [],
"bgcolor": "#653",
"outputs": [],
"properties": { "text": "" },
"widgets_values": [
"sd3.5_large.safetensors and sd3.5_medium.safetensors are files that do not contain any CLIP/text encoder weights so you need to load them separately.\n\nThey go in the ComfyUI/models/checkpoints directory."
]
},
{
"id": 43,
"pos": [-96, 288],
"mode": 0,
"size": [315, 106],
"type": "TripleCLIPLoader",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"links": [103, 104],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "TripleCLIPLoader" },
"widgets_values": [
"clip_l.safetensors",
"clip_g.safetensors",
"t5xxl_fp16.safetensors"
]
},
{
"id": 42,
"pos": [-96, 144],
"mode": 0,
"size": [315, 106],
"type": "DualCLIPLoader",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{
"name": "CLIP",
"type": "CLIP",
"links": [],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "DualCLIPLoader" },
"widgets_values": [
"clip_l.safetensors",
"clip_g.safetensors",
"sd3",
"default"
]
},
{
"id": 40,
"pos": [399.0046081542969, 337.009765625],
"mode": 0,
"size": [397.45977783203125, 151.2898406982422],
"type": "CLIPTextEncode",
"color": "#322",
"flags": {},
"order": 11,
"title": "Negative Prompt",
"inputs": [
{ "link": 104, "name": "clip", "type": "CLIP" },
{
"link": 106,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#533",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [80],
"shape": 3,
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [""]
},
{
"id": 16,
"pos": [398.5880432128906, 108.27188110351562],
"mode": 0,
"size": [397.21484375, 158.671875],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 12,
"title": "Positive Prompt",
"inputs": [
{ "link": 103, "name": "clip", "type": "CLIP" },
{
"link": 105,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [21],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"a bottle with a rainbow galaxy inside it on top of a wooden table on a snowy mountain top with the ocean and clouds in the background with a shot glass beside containing darkness beside a snow sculpture in the shape of a fox"
]
},
{
"id": 57,
"pos": [-1015.1683349609375, 980.396240234375],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [108], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 1024, "Height", "The height of the image. "]
},
{
"id": 56,
"pos": [-1014.532470703125, 732.8568725585938],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 7,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [107], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 1024, "Width", "The width of the image. "]
},
{
"id": 55,
"pos": [-1010.8035278320312, 487.404052734375],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 8,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [106] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": ["negative_prompt", "", "Negative Prompt", ""]
},
{
"id": 54,
"pos": [-1059.1072998046875, 127.12823486328125],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 9,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [105] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"prompt",
"a bottle with a rainbow galaxy inside it on top of a wooden table on a snowy mountain top with the ocean and clouds in the background with a shot glass beside containing darkness beside a snow sculpture in the shape of a fox",
"Prompt",
"The prompt to generate an image from."
]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Different Text Encoder Configurations",
"bounding": [-144, -96, 480, 528],
"font_size": 24
},
{
"id": 2,
"color": "#b06634",
"flags": {},
"title": "Additional",
"bounding": [
-1194.5233154296875, 388.3871154785156, 722.5393676757812,
824.6746215820312
],
"font_size": 24
},
{
"id": 3,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-1148.487060546875, 30.449382781982422, 552.3358764648438,
329.3829040527344
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 108,
"last_node_id": 57
}
+452
View File
@@ -0,0 +1,452 @@
{
"extra": {
"ds": {
"scale": 0.6934334949441662,
"offset": [276.7941771713029, -7.923387380923855]
},
"node_versions": {
"comfy-core": "0.3.18",
"comfyui-deploy": "b3df94d1affcf7ce05ee7eeda99989194bcd9159"
}
},
"links": [
[35, 3, 0, 8, 0, "LATENT"],
[56, 8, 0, 28, 0, "IMAGE"],
[74, 38, 0, 6, 0, "CLIP"],
[75, 38, 0, 7, 0, "CLIP"],
[76, 39, 0, 8, 1, "VAE"],
[93, 8, 0, 47, 0, "IMAGE"],
[94, 49, 0, 51, 0, "CLIP_VISION"],
[97, 6, 0, 50, 0, "CONDITIONING"],
[98, 7, 0, 50, 1, "CONDITIONING"],
[99, 39, 0, 50, 2, "VAE"],
[101, 50, 0, 3, 1, "CONDITIONING"],
[102, 50, 1, 3, 2, "CONDITIONING"],
[103, 50, 2, 3, 3, "LATENT"],
[107, 51, 0, 50, 3, "CLIP_VISION_OUTPUT"],
[110, 37, 0, 54, 0, "MODEL"],
[111, 54, 0, 3, 0, "MODEL"],
[112, 55, 0, 6, 1, "STRING"],
[113, 56, 0, 7, 1, "STRING"],
[115, 57, 0, 50, 4, "IMAGE"],
[116, 57, 0, 51, 1, "IMAGE"],
[117, 58, 0, 50, 5, "INT"],
[118, 59, 0, 50, 6, "INT"]
],
"nodes": [
{
"id": 8,
"pos": [1210, 190],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 15,
"inputs": [
{ "link": 35, "name": "samples", "type": "LATENT" },
{ "link": 76, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [56, 93], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 39,
"pos": [866.3932495117188, 499.18597412109375],
"mode": 0,
"size": [306.36004638671875, 58],
"type": "VAELoader",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "VAE", "type": "VAE", "links": [76, 99], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAELoader" },
"widgets_values": ["wan_2.1_vae.safetensors"]
},
{
"id": 28,
"pos": [1460, 190],
"mode": 0,
"size": [870.8511352539062, 643.7430419921875],
"type": "SaveAnimatedWEBP",
"flags": {},
"order": 16,
"inputs": [{ "link": 56, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI", 16, false, 90, "default", ""]
},
{
"id": 47,
"pos": [2367.213134765625, 193.6114959716797],
"mode": 4,
"size": [315, 130],
"type": "SaveWEBM",
"flags": {},
"order": 17,
"inputs": [{ "link": 93, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": { "Node name for S&R": "SaveWEBM" },
"widgets_values": ["ComfyUI", "vp9", 24, 32]
},
{
"id": 50,
"pos": [673.0507202148438, 627.272705078125],
"mode": 0,
"size": [342.5999755859375, 250],
"type": "WanImageToVideo",
"flags": {},
"order": 13,
"inputs": [
{ "link": 97, "name": "positive", "type": "CONDITIONING" },
{ "link": 98, "name": "negative", "type": "CONDITIONING" },
{ "link": 99, "name": "vae", "type": "VAE" },
{
"link": 107,
"name": "clip_vision_output",
"type": "CLIP_VISION_OUTPUT",
"shape": 7
},
{ "link": 115, "name": "start_image", "type": "IMAGE", "shape": 7 },
{
"pos": [10, 116],
"link": 117,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"pos": [10, 140],
"link": 118,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"outputs": [
{
"name": "positive",
"type": "CONDITIONING",
"links": [101],
"slot_index": 0
},
{
"name": "negative",
"type": "CONDITIONING",
"links": [102],
"slot_index": 1
},
{ "name": "latent", "type": "LATENT", "links": [103], "slot_index": 2 }
],
"properties": { "Node name for S&R": "WanImageToVideo" },
"widgets_values": [512, 512, 33, 1]
},
{
"id": 3,
"pos": [863, 187],
"mode": 0,
"size": [315, 262],
"type": "KSampler",
"flags": {},
"order": 14,
"inputs": [
{ "link": 111, "name": "model", "type": "MODEL" },
{ "link": 101, "name": "positive", "type": "CONDITIONING" },
{ "link": 102, "name": "negative", "type": "CONDITIONING" },
{ "link": 103, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [35], "slot_index": 0 }
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
55104841546759,
"randomize",
20,
6,
"uni_pc",
"simple",
1
]
},
{
"id": 51,
"pos": [360, 640],
"mode": 0,
"size": [253.60000610351562, 78],
"type": "CLIPVisionEncode",
"flags": {},
"order": 12,
"inputs": [
{ "link": 94, "name": "clip_vision", "type": "CLIP_VISION" },
{ "link": 116, "name": "image", "type": "IMAGE" }
],
"outputs": [
{
"name": "CLIP_VISION_OUTPUT",
"type": "CLIP_VISION_OUTPUT",
"links": [107],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPVisionEncode" },
"widgets_values": ["none"]
},
{
"id": 54,
"pos": [510, 70],
"mode": 0,
"size": [315, 58],
"type": "ModelSamplingSD3",
"flags": {},
"order": 9,
"inputs": [{ "link": 110, "name": "model", "type": "MODEL" }],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [111], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ModelSamplingSD3" },
"widgets_values": [8]
},
{
"id": 6,
"pos": [415, 186],
"mode": 0,
"size": [422.84503173828125, 164.31304931640625],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 10,
"title": "CLIP Text Encode (Positive Prompt)",
"inputs": [
{ "link": 74, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 112,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [97],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"a cute anime girl with massive fennec ears and a big fluffy tail wearing a maid outfit turning around"
]
},
{
"id": 55,
"pos": [-672.6411743164062, 140.167724609375],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 1,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [112] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"positive_prompt",
"a cute anime girl with massive fennec ears and a big fluffy tail wearing a maid outfit turning around",
"Prompt",
"The text prompt to guide video generation."
]
},
{
"id": 57,
"pos": [-664.3828125, 405.5113525390625],
"mode": 0,
"size": [390.5999755859375, 366],
"type": "ComfyUIDeployExternalImage",
"flags": {},
"order": 2,
"inputs": [
{ "link": null, "name": "default_value", "type": "IMAGE", "shape": 7 }
],
"outputs": [{ "name": "image", "type": "IMAGE", "links": [115, 116] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalImage" },
"widgets_values": [
"image_url",
"Image Url",
"URL of the input image.",
"https://comfy-deploy-output.s3.us-east-2.amazonaws.com/assets/img_GZMJYXDnLbYjWybu.png",
""
]
},
{
"id": 56,
"pos": [-674.42333984375, 953.0498046875],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 3,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [113] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"negative_prompt",
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
"Negative Prompt",
"The negative prompt to use. Use it to address details that you don't want in the video. This could be colors, objects, scenery and even the small details (e.g. moustache, blurry, low resolution). "
]
},
{
"id": 58,
"pos": [-668.314453125, 1208.204833984375],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [117], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 512, "Width", "The width of the video. "]
},
{
"id": 59,
"pos": [-665.8831787109375, 1461.255126953125],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [118], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 512, "Height", "The Height of the video."]
},
{
"id": 37,
"pos": [-3.3700287342071533, 81.69654846191406],
"mode": 0,
"size": [346.7470703125, 82],
"type": "UNETLoader",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [110], "slot_index": 0 }
],
"properties": { "Node name for S&R": "UNETLoader" },
"widgets_values": ["wan2.1_i2v_480p_14B_bf16.safetensors", "default"]
},
{
"id": 38,
"pos": [-3.7316977977752686, 230.76495361328125],
"mode": 0,
"size": [390, 98],
"type": "CLIPLoader",
"flags": {},
"order": 7,
"inputs": [],
"outputs": [
{ "name": "CLIP", "type": "CLIP", "links": [74, 75], "slot_index": 0 }
],
"properties": { "Node name for S&R": "CLIPLoader" },
"widgets_values": [
"umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"wan",
"default"
]
},
{
"id": 49,
"pos": [16.57183265686035, 640.22314453125],
"mode": 0,
"size": [315, 58],
"type": "CLIPVisionLoader",
"flags": {},
"order": 8,
"inputs": [],
"outputs": [
{
"name": "CLIP_VISION",
"type": "CLIP_VISION",
"links": [94],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPVisionLoader" },
"widgets_values": ["clip_vision_h.safetensors"]
},
{
"id": 7,
"pos": [414.1811828613281, 393.8824768066406],
"mode": 0,
"size": [425.27801513671875, 180.6060791015625],
"type": "CLIPTextEncode",
"color": "#322",
"flags": {},
"order": 11,
"title": "CLIP Text Encode (Negative Prompt)",
"inputs": [
{ "link": 75, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 113,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#533",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [98],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-708.2940063476562, 47.549102783203125, 617.7969360351562,
761.303955078125
],
"font_size": 24
},
{
"id": 2,
"color": "#b06634",
"flags": {},
"title": "Additional",
"bounding": [
-703.6196899414062, 861.310791015625, 625.998779296875,
845.0675048828125
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 118,
"last_node_id": 59
}
+452
View File
@@ -0,0 +1,452 @@
{
"extra": {
"ds": {
"scale": 0.6303940863128795,
"offset": [-209.31226203277674, -32.62073943929215]
},
"node_versions": {
"comfy-core": "0.3.18",
"comfyui-deploy": "b3df94d1affcf7ce05ee7eeda99989194bcd9159"
}
},
"links": [
[1, 6, 0, 1, 0, "LATENT"],
[2, 2, 0, 1, 1, "VAE"],
[3, 1, 0, 3, 0, "IMAGE"],
[4, 1, 0, 4, 0, "IMAGE"],
[5, 9, 0, 5, 0, "CONDITIONING"],
[6, 18, 0, 5, 1, "CONDITIONING"],
[7, 2, 0, 5, 2, "VAE"],
[8, 7, 0, 5, 3, "CLIP_VISION_OUTPUT"],
[9, 11, 0, 5, 4, "IMAGE"],
[10, 13, 0, 5, 5, "INT"],
[11, 14, 0, 5, 6, "INT"],
[12, 8, 0, 6, 0, "MODEL"],
[13, 5, 0, 6, 1, "CONDITIONING"],
[14, 5, 1, 6, 2, "CONDITIONING"],
[15, 5, 2, 6, 3, "LATENT"],
[16, 17, 0, 7, 0, "CLIP_VISION"],
[17, 11, 0, 7, 1, "IMAGE"],
[18, 15, 0, 8, 0, "MODEL"],
[19, 16, 0, 9, 0, "CLIP"],
[20, 10, 0, 9, 1, "STRING"],
[21, 16, 0, 18, 0, "CLIP"],
[22, 12, 0, 18, 1, "STRING"]
],
"nodes": [
{
"id": 1,
"pos": [1823.98876953125, 702.6244506835938],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 15,
"inputs": [
{ "link": 1, "name": "samples", "type": "LATENT" },
{ "link": 2, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [3, 4], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 2,
"pos": [1480.382080078125, 1011.8104248046875],
"mode": 0,
"size": [306.36004638671875, 58],
"type": "VAELoader",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "VAE", "type": "VAE", "links": [2, 7], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAELoader" },
"widgets_values": ["wan_2.1_vae.safetensors"]
},
{
"id": 3,
"pos": [2073.98876953125, 702.6244506835938],
"mode": 0,
"size": [870.8511352539062, 643.7430419921875],
"type": "SaveAnimatedWEBP",
"flags": {},
"order": 16,
"inputs": [{ "link": 3, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI", 16, false, 90, "default", ""]
},
{
"id": 4,
"pos": [2981.201904296875, 706.2359008789062],
"mode": 4,
"size": [315, 130],
"type": "SaveWEBM",
"flags": {},
"order": 17,
"inputs": [{ "link": 4, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": { "Node name for S&R": "SaveWEBM" },
"widgets_values": ["ComfyUI", "vp9", 24, 32]
},
{
"id": 5,
"pos": [1287.03955078125, 1139.8970947265625],
"mode": 0,
"size": [342.5999755859375, 250],
"type": "WanImageToVideo",
"flags": {},
"order": 13,
"inputs": [
{ "link": 5, "name": "positive", "type": "CONDITIONING" },
{ "link": 6, "name": "negative", "type": "CONDITIONING" },
{ "link": 7, "name": "vae", "type": "VAE" },
{
"link": 8,
"name": "clip_vision_output",
"type": "CLIP_VISION_OUTPUT",
"shape": 7
},
{ "link": 9, "name": "start_image", "type": "IMAGE", "shape": 7 },
{
"pos": [10, 116],
"link": 10,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"pos": [10, 140],
"link": 11,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"outputs": [
{
"name": "positive",
"type": "CONDITIONING",
"links": [13],
"slot_index": 0
},
{
"name": "negative",
"type": "CONDITIONING",
"links": [14],
"slot_index": 1
},
{ "name": "latent", "type": "LATENT", "links": [15], "slot_index": 2 }
],
"properties": { "Node name for S&R": "WanImageToVideo" },
"widgets_values": [512, 512, 33, 1]
},
{
"id": 6,
"pos": [1476.98876953125, 699.6244506835938],
"mode": 0,
"size": [315, 262],
"type": "KSampler",
"flags": {},
"order": 14,
"inputs": [
{ "link": 12, "name": "model", "type": "MODEL" },
{ "link": 13, "name": "positive", "type": "CONDITIONING" },
{ "link": 14, "name": "negative", "type": "CONDITIONING" },
{ "link": 15, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [1], "slot_index": 0 }
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
625624384968868,
"randomize",
20,
6,
"uni_pc",
"simple",
1
]
},
{
"id": 7,
"pos": [973.98876953125, 1152.6243896484375],
"mode": 0,
"size": [253.60000610351562, 78],
"type": "CLIPVisionEncode",
"flags": {},
"order": 10,
"inputs": [
{ "link": 16, "name": "clip_vision", "type": "CLIP_VISION" },
{ "link": 17, "name": "image", "type": "IMAGE" }
],
"outputs": [
{
"name": "CLIP_VISION_OUTPUT",
"type": "CLIP_VISION_OUTPUT",
"links": [8],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPVisionEncode" },
"widgets_values": ["none"]
},
{
"id": 8,
"pos": [1123.98876953125, 582.6244506835938],
"mode": 0,
"size": [315, 58],
"type": "ModelSamplingSD3",
"flags": {},
"order": 11,
"inputs": [{ "link": 18, "name": "model", "type": "MODEL" }],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [12], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ModelSamplingSD3" },
"widgets_values": [8]
},
{
"id": 9,
"pos": [1028.98876953125, 698.6244506835938],
"mode": 0,
"size": [422.84503173828125, 164.31304931640625],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 12,
"title": "CLIP Text Encode (Positive Prompt)",
"inputs": [
{ "link": 19, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 20,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [5],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"a cute anime girl with massive fennec ears and a big fluffy tail wearing a maid outfit turning around"
]
},
{
"id": 11,
"pos": [-50.3940315246582, 918.1358032226562],
"mode": 0,
"size": [390.5999755859375, 366],
"type": "ComfyUIDeployExternalImage",
"flags": {},
"order": 1,
"inputs": [
{ "link": null, "name": "default_value", "type": "IMAGE", "shape": 7 }
],
"outputs": [{ "name": "image", "type": "IMAGE", "links": [9, 17] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalImage" },
"widgets_values": [
"image_url",
"Image Url",
"URL of the input image.",
"https://comfy-deploy-output.s3.us-east-2.amazonaws.com/assets/img_GZMJYXDnLbYjWybu.png",
""
]
},
{
"id": 12,
"pos": [-60.4345588684082, 1465.6741943359375],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 2,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [22] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"negative_prompt",
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
"Negative Prompt",
"The negative prompt to use. Use it to address details that you don't want in the video. This could be colors, objects, scenery and even the small details (e.g. moustache, blurry, low resolution). "
]
},
{
"id": 13,
"pos": [-54.3256721496582, 1720.8292236328125],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 3,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [10], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 512, "Width", "The width of the video. "]
},
{
"id": 14,
"pos": [-51.8943977355957, 1973.8795166015625],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [11], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 512, "Height", "The Height of the video."]
},
{
"id": 16,
"pos": [610.257080078125, 743.389404296875],
"mode": 0,
"size": [390, 98],
"type": "CLIPLoader",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{ "name": "CLIP", "type": "CLIP", "links": [19, 21], "slot_index": 0 }
],
"properties": { "Node name for S&R": "CLIPLoader" },
"widgets_values": [
"umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"wan",
"default"
]
},
{
"id": 17,
"pos": [630.5606079101562, 1152.8475341796875],
"mode": 0,
"size": [315, 58],
"type": "CLIPVisionLoader",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{
"name": "CLIP_VISION",
"type": "CLIP_VISION",
"links": [16],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPVisionLoader" },
"widgets_values": ["clip_vision_h.safetensors"]
},
{
"id": 18,
"pos": [1028.169921875, 906.5068969726562],
"mode": 0,
"size": [425.27801513671875, 180.6060791015625],
"type": "CLIPTextEncode",
"color": "#322",
"flags": {},
"order": 9,
"title": "CLIP Text Encode (Negative Prompt)",
"inputs": [
{ "link": 21, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 22,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#533",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [6],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
]
},
{
"id": 15,
"pos": [610.6187744140625, 594.3209838867188],
"mode": 0,
"size": [346.7470703125, 82],
"type": "UNETLoader",
"flags": {},
"order": 7,
"inputs": [],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [18], "slot_index": 0 }
],
"properties": { "Node name for S&R": "UNETLoader" },
"widgets_values": ["wan2.1_i2v_720p_14B_bf16.safetensors", "default"]
},
{
"id": 10,
"pos": [-60.696964263916016, 649.5515747070312],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 8,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [20] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"positive_prompt",
"a cute anime girl with massive fennec ears and a big fluffy tail wearing a maid outfit running towards front happily",
"Prompt",
"The text prompt to guide video generation."
]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Input",
"bounding": [
-94.30522155761719, 560.1735229492188, 617.7969360351562,
761.303955078125
],
"font_size": 24
},
{
"id": 2,
"color": "#b06634",
"flags": {},
"title": "Additional",
"bounding": [
-89.63090515136719, 1373.9351806640625, 625.998779296875,
845.0675048828125
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 22,
"last_node_id": 18
}
+359
View File
@@ -0,0 +1,359 @@
{
"extra": {
"ds": {
"scale": 0.8390545288824369,
"offset": [814.3725295729478, -347.90757575249455]
},
"node_versions": {
"comfy-core": "0.3.18",
"comfyui-deploy": "b3df94d1affcf7ce05ee7eeda99989194bcd9159"
}
},
"links": [
[35, 3, 0, 8, 0, "LATENT"],
[46, 6, 0, 3, 1, "CONDITIONING"],
[52, 7, 0, 3, 2, "CONDITIONING"],
[56, 8, 0, 28, 0, "IMAGE"],
[74, 38, 0, 6, 0, "CLIP"],
[75, 38, 0, 7, 0, "CLIP"],
[76, 39, 0, 8, 1, "VAE"],
[91, 40, 0, 3, 3, "LATENT"],
[93, 8, 0, 47, 0, "IMAGE"],
[94, 37, 0, 48, 0, "MODEL"],
[95, 48, 0, 3, 0, "MODEL"],
[96, 49, 0, 6, 1, "STRING"],
[97, 50, 0, 7, 1, "STRING"],
[99, 52, 0, 40, 1, "INT"],
[100, 51, 0, 40, 0, "INT"]
],
"nodes": [
{
"id": 8,
"pos": [1210, 190],
"mode": 0,
"size": [210, 46],
"type": "VAEDecode",
"flags": {},
"order": 12,
"inputs": [
{ "link": 35, "name": "samples", "type": "LATENT" },
{ "link": 76, "name": "vae", "type": "VAE" }
],
"outputs": [
{ "name": "IMAGE", "type": "IMAGE", "links": [56, 93], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAEDecode" },
"widgets_values": []
},
{
"id": 39,
"pos": [866.3932495117188, 499.18597412109375],
"mode": 0,
"size": [306.36004638671875, 58],
"type": "VAELoader",
"flags": {},
"order": 0,
"inputs": [],
"outputs": [
{ "name": "VAE", "type": "VAE", "links": [76], "slot_index": 0 }
],
"properties": { "Node name for S&R": "VAELoader" },
"widgets_values": ["wan_2.1_vae.safetensors"]
},
{
"id": 47,
"pos": [2367.213134765625, 193.6114959716797],
"mode": 4,
"size": [315, 130],
"type": "SaveWEBM",
"flags": {},
"order": 14,
"inputs": [{ "link": 93, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": { "Node name for S&R": "SaveWEBM" },
"widgets_values": ["ComfyUI", "vp9", 24, 32]
},
{
"id": 3,
"pos": [863, 187],
"mode": 0,
"size": [315, 262],
"type": "KSampler",
"flags": {},
"order": 11,
"inputs": [
{ "link": 95, "name": "model", "type": "MODEL" },
{ "link": 46, "name": "positive", "type": "CONDITIONING" },
{ "link": 52, "name": "negative", "type": "CONDITIONING" },
{ "link": 91, "name": "latent_image", "type": "LATENT" }
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [35], "slot_index": 0 }
],
"properties": { "Node name for S&R": "KSampler" },
"widgets_values": [
577746309562741,
"randomize",
30,
6,
"uni_pc",
"simple",
1
]
},
{
"id": 48,
"pos": [440, 50],
"mode": 0,
"size": [210, 58],
"type": "ModelSamplingSD3",
"flags": {},
"order": 7,
"inputs": [{ "link": 94, "name": "model", "type": "MODEL" }],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [95], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ModelSamplingSD3" },
"widgets_values": [8]
},
{
"id": 37,
"pos": [20, 40],
"mode": 0,
"size": [346.7470703125, 82],
"type": "UNETLoader",
"flags": {},
"order": 1,
"inputs": [],
"outputs": [
{ "name": "MODEL", "type": "MODEL", "links": [94], "slot_index": 0 }
],
"properties": { "Node name for S&R": "UNETLoader" },
"widgets_values": ["wan2.1_t2v_1.3B_fp16.safetensors", "default"]
},
{
"id": 6,
"pos": [415, 186],
"mode": 0,
"size": [422.84503173828125, 164.31304931640625],
"type": "CLIPTextEncode",
"color": "#232",
"flags": {},
"order": 8,
"title": "CLIP Text Encode (Positive Prompt)",
"inputs": [
{ "link": 74, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 96,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#353",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [46],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"a fox moving quickly in a beautiful winter scenery nature trees mountains daytime tracking camera"
]
},
{
"id": 7,
"pos": [413, 389],
"mode": 0,
"size": [425.27801513671875, 180.6060791015625],
"type": "CLIPTextEncode",
"color": "#322",
"flags": {},
"order": 9,
"title": "CLIP Text Encode (Negative Prompt)",
"inputs": [
{ "link": 75, "name": "clip", "type": "CLIP" },
{
"pos": [10, 36],
"link": 97,
"name": "text",
"type": "STRING",
"widget": { "name": "text" }
}
],
"bgcolor": "#533",
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [52],
"slot_index": 0
}
],
"properties": { "Node name for S&R": "CLIPTextEncode" },
"widgets_values": [
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走"
]
},
{
"id": 38,
"pos": [-10.047812461853027, 187.37384033203125],
"mode": 0,
"size": [390, 98],
"type": "CLIPLoader",
"flags": {},
"order": 2,
"inputs": [],
"outputs": [
{ "name": "CLIP", "type": "CLIP", "links": [74, 75], "slot_index": 0 }
],
"properties": { "Node name for S&R": "CLIPLoader" },
"widgets_values": [
"umt5_xxl_fp8_e4m3fn_scaled.safetensors",
"wan",
"default"
]
},
{
"id": 49,
"pos": [-535.2967529296875, 342.3277587890625],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 3,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [96] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"positive_prompt",
"a fox moving quickly in a beautiful winter scenery nature trees mountains daytime tracking camera",
"Prompt",
"The text prompt to guide video generation. "
]
},
{
"id": 50,
"pos": [-526.2716064453125, 703.8343505859375],
"mode": 0,
"size": [400, 200],
"type": "ComfyUIDeployExternalText",
"flags": {},
"order": 4,
"inputs": [],
"outputs": [{ "name": "text", "type": "STRING", "links": [97] }],
"properties": { "Node name for S&R": "ComfyUIDeployExternalText" },
"widgets_values": [
"negative_prompt",
"色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走",
"Negative Prompt",
"The negative prompt to use. Use it to address details that you don't want in the image. This could be colors, objects, scenery and even the small details (e.g. moustache, blurry, low resolution). "
]
},
{
"id": 40,
"pos": [516.926513671875, 619.59716796875],
"mode": 0,
"size": [315, 150],
"type": "EmptyHunyuanLatentVideo",
"flags": {},
"order": 10,
"inputs": [
{
"pos": [10, 36],
"link": 100,
"name": "width",
"type": "INT",
"widget": { "name": "width" }
},
{
"pos": [10, 60],
"link": 99,
"name": "height",
"type": "INT",
"widget": { "name": "height" }
}
],
"outputs": [
{ "name": "LATENT", "type": "LATENT", "links": [91], "slot_index": 0 }
],
"properties": { "Node name for S&R": "EmptyHunyuanLatentVideo" },
"widgets_values": [832, 480, 33, 1]
},
{
"id": 28,
"pos": [1460, 190],
"mode": 0,
"size": [870.8511352539062, 643.7430419921875],
"type": "SaveAnimatedWEBP",
"flags": {},
"order": 13,
"inputs": [{ "link": 56, "name": "images", "type": "IMAGE" }],
"outputs": [],
"properties": {},
"widgets_values": ["ComfyUI", 16, false, 90, "default"]
},
{
"id": 51,
"pos": [-522.7415161132812, 959.3386840820312],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 5,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [100], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["width", 832, "Width", "The Width of the Video. "]
},
{
"id": 52,
"pos": [-518.9917602539062, 1207.9444580078125],
"mode": 0,
"size": [453.5999755859375, 200],
"type": "ComfyUIDeployExternalNumberInt",
"flags": {},
"order": 6,
"inputs": [],
"outputs": [
{ "name": "value", "type": "INT", "links": [99], "slot_index": 0 }
],
"properties": { "Node name for S&R": "ComfyUIDeployExternalNumberInt" },
"widgets_values": ["height", 480, "Height", "The Height of the Video. "]
}
],
"config": {},
"groups": [
{
"id": 1,
"color": "#3f789e",
"flags": {},
"title": "Inputs",
"bounding": [
-560.9110717773438, 255.1485595703125, 500.94989013671875,
333.4786682128906
],
"font_size": 24
},
{
"id": 2,
"color": "#b06634",
"flags": {},
"title": "Additional",
"bounding": [
-556.5305786132812, 619.87548828125, 761.2673950195312,
811.6837768554688
],
"font_size": 24
}
],
"version": 0.4,
"last_link_id": 100,
"last_node_id": 52
}
+56 -16
View File
@@ -1,46 +1,79 @@
import struct
from enum import Enum
import aiohttp
from typing import List, Union, Any, Optional
from PIL import Image, ImageOps
from io import BytesIO
from pydantic import BaseModel as PydanticBaseModel
class BaseModel(PydanticBaseModel):
class Config:
arbitrary_types_allowed = True
class Status(Enum):
NOT_STARTED = "not-started"
RUNNING = "running"
SUCCESS = "success"
FAILED = "failed"
UPLOADING = "uploading"
CANCELLED = "cancelled"
class StreamingPrompt(BaseModel):
workflow_api: Any
auth_token: str
inputs: dict[str, Union[str, bytes, Image.Image]]
running_prompt_ids: set[str] = set()
status_endpoint: str
file_upload_endpoint: str
status_endpoint: Optional[str]
file_upload_endpoint: Optional[str]
workflow: Any
gpu_event_id: Optional[str] = None
class SimplePrompt(BaseModel):
status_endpoint: Optional[str]
file_upload_endpoint: Optional[str]
token: Optional[str]
workflow_api: dict
status: Status = Status.NOT_STARTED
progress: set = set()
last_updated_node: Optional[str] = None
uploading_nodes: set = set()
done: bool = False
is_realtime: bool = False
start_time: Optional[float] = None
gpu_event_id: Optional[str] = None
sockets = dict()
prompt_metadata: dict[str, SimplePrompt] = {}
streaming_prompt_metadata: dict[str, StreamingPrompt] = {}
class BinaryEventTypes:
PREVIEW_IMAGE = 1
UNENCODED_PREVIEW_IMAGE = 2
max_output_id_length = 24
async def send_image(image_data, sid=None, output_id:str = None):
async def send_image(image_data, sid=None, output_id: str = None):
max_length = max_output_id_length
output_id = output_id[:max_length]
padded_output_id = output_id.ljust(max_length, '\x00')
encoded_output_id = padded_output_id.encode('ascii', 'replace')
padded_output_id = output_id.ljust(max_length, "\x00")
encoded_output_id = padded_output_id.encode("ascii", "replace")
image_type = image_data[0]
image = image_data[1]
max_size = image_data[2]
quality = image_data[3]
if max_size is not None:
if hasattr(Image, 'Resampling'):
if hasattr(Image, "Resampling"):
resampling = Image.Resampling.BILINEAR
else:
resampling = Image.ANTIALIAS
@@ -64,17 +97,23 @@ async def send_image(image_data, sid=None, output_id:str = None):
position_after = bytesIO.tell()
bytes_written = position_after - position_before
print(f"Bytes written: {bytes_written}")
image.save(bytesIO, format=image_type, quality=quality, compress_level=1)
preview_bytes = bytesIO.getvalue()
await send_bytes(BinaryEventTypes.PREVIEW_IMAGE, preview_bytes, sid=sid)
async def send_socket_catch_exception(function, message):
try:
await function(message)
except (aiohttp.ClientError, aiohttp.ClientPayloadError, ConnectionResetError) as err:
except (
aiohttp.ClientError,
aiohttp.ClientPayloadError,
ConnectionResetError,
) as err:
print("send error:", err)
def encode_bytes(event, data):
if not isinstance(event, int):
raise RuntimeError(f"Binary event types must be integers, got {event}")
@@ -84,9 +123,10 @@ def encode_bytes(event, data):
message.extend(data)
return message
async def send_bytes(event, data, sid=None):
message = encode_bytes(event, data)
print("sending image to ", event, sid)
if sid is None:
@@ -94,4 +134,4 @@ async def send_bytes(event, data, sid=None):
for ws in _sockets:
await send_socket_catch_exception(ws.send_bytes, message)
elif sid in sockets:
await send_socket_catch_exception(sockets[sid].send_bytes, message)
await send_socket_catch_exception(sockets[sid].send_bytes, message)
-18
View File
@@ -1,18 +0,0 @@
FROM node:21-bullseye AS comfy_deploy
RUN apt-get update && apt-get install -y python3 make g++
RUN npm install -g bun
COPY ./web /web
WORKDIR /web
RUN cp .env.example .env.local
RUN bunx node-gyp
RUN bun i
ENTRYPOINT [ "bun", "dev" ]
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
echo "comfy deploy container starting.."
echo "Running migrations.."
bun migrate-local
echo "Starting comfy deploy.."
bun dev
+7 -1
View File
@@ -58,6 +58,9 @@ if cd_enable_log:
print("** Comfy Deploy logging enabled")
setup()
# Store the original working directory
original_cwd = os.getcwd()
try:
# Get the absolute path of the script's directory
script_dir = os.path.dirname(os.path.abspath(__file__))
@@ -66,4 +69,7 @@ try:
current_git_commit = subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode('utf-8').strip()
print(f"** Comfy Deploy Revision: {current_git_commit}")
except Exception as e:
print(f"** Comfy Deploy failed to get current git commit: {str(e)}")
print(f"** Comfy Deploy failed to get current git commit: {str(e)}")
finally:
# Change back to the original directory
os.chdir(original_cwd)
+15
View File
@@ -0,0 +1,15 @@
[project]
name = "comfyui-deploy"
description = "Open source comfyui deployment platform, a vercel for generative workflow infra."
version = "2.3.9"
license = { file = "LICENSE" }
dependencies = ["aiofiles", "pydantic", "opencv-python", "imageio-ffmpeg", "tabulate", "brotli"]
[project.urls]
Repository = "https://github.com/BennyKok/comfyui-deploy"
# Used by Comfy Registry https://comfyregistry.org
[tool.comfy]
PublisherId = "comfydeploy"
DisplayName = "comfyui-deploy"
Icon = ""
+6 -1
View File
@@ -1,2 +1,7 @@
aiofiles
pydantic
pydantic
opencv-python
imageio-ffmpeg
brotli
tabulate
# logfire
-4
View File
@@ -1,4 +0,0 @@
/** @typedef {import('../../../web/scripts/api.js').api} API*/
import { api as _api } from '../../scripts/api.js';
/** @type {API} */
export const api = _api;
-4
View File
@@ -1,4 +0,0 @@
/** @typedef {import('../../../web/scripts/app.js').ComfyApp} ComfyApp*/
import { app as _app } from '../../scripts/app.js';
/** @type {ComfyApp} */
export const app = _app;
+2624 -449
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
// Snapshot Utilities
// Centralized snapshot fetching with ComfyUI version fallback
/**
* Fetches the current snapshot with ComfyUI version fallback
* If the snapshot response has null comfyui field, it will fetch the latest ComfyUI version
* and update the snapshot with the comfyui_hash
*
* @param {Function} getDataFn - Function that returns { apiKey, apiUrl } for ComfyUI version API calls
* @returns {Promise<Object>} - The snapshot data with comfyui field populated
*/
export async function fetchSnapshot(getDataFn = null) {
try {
// Fetch the current snapshot
const response = await fetch("/snapshot/get_current");
if (!response.ok) {
throw new Error(`Snapshot fetch failed: ${response.status}`);
}
const snapshot = await response.json();
// Check if comfyui field is null and we have getDataFn for fallback
if (snapshot.comfyui === null && getDataFn) {
console.log(
"ComfyUI version is null in snapshot, fetching latest version..."
);
try {
const data = getDataFn();
if (data && data.apiKey) {
const comfyuiVersionResponse = await fetch(
`/comfyui-deploy/comfyui-version?api_url=${encodeURIComponent(
data.apiUrl || "https://api.comfydeploy.com"
)}`,
{
headers: {
Authorization: `Bearer ${data.apiKey}`,
},
}
);
if (comfyuiVersionResponse.ok) {
const versionData = await comfyuiVersionResponse.json();
if (versionData.comfyui_hash) {
console.log(
`Using ComfyUI hash from API: ${versionData.comfyui_hash}`
);
snapshot.comfyui = versionData.comfyui_hash;
}
} else {
console.warn(
"Failed to fetch ComfyUI version from API:",
comfyuiVersionResponse.status
);
}
}
} catch (error) {
console.warn("Error fetching ComfyUI version fallback:", error);
// Continue with original snapshot even if fallback fails
}
}
return snapshot;
} catch (error) {
console.error("Error fetching snapshot:", error);
throw error;
}
}
-18
View File
@@ -1,18 +0,0 @@
// /** @typedef {import('../../../web/scripts/api.js').api} API*/
// import { api as _api } from "../../scripts/api.js";
// /** @type {API} */
// export const api = _api;
/** @typedef {typeof import('../../../web/scripts/widgets.js').ComfyWidgets} Widgets*/
import { ComfyWidgets as _ComfyWidgets } from "../../scripts/widgets.js";
/**
* @type {Widgets}
*/
export const ComfyWidgets = _ComfyWidgets;
// import { LGraphNode as _LGraphNode } from "../../types/litegraph.js";
/** @typedef {typeof import('../../../web/types/litegraph.js').LGraphNode} LGraphNode*/
/** @type {LGraphNode}*/
export const LGraphNode = LiteGraph.LGraphNode;
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -74,7 +74,7 @@
"mitata": "^0.1.6",
"ms": "^2.1.3",
"nanoid": "^5.0.4",
"next": "14.1",
"next": "14.2",
"next-plausible": "^3.12.0",
"next-themes": "^0.2.1",
"next-usequerystate": "^1.13.2",
+1
View File
@@ -6,4 +6,5 @@ export const customInputNodes: Record<string, string> = {
ComfyUIDeployExternalNumberInt: "integer",
ComfyUIDeployExternalLora: "string - (public lora download url)",
ComfyUIDeployExternalCheckpoint: "string - (public checkpoints download url)",
ComfyUIDeployExternalFaceModel: "string - (public face model download url)",
};
+2 -2
View File
@@ -9,10 +9,10 @@ if (process.env.VERCEL_ENV !== "production") {
// Set the WebSocket proxy to work with the local instance
if (isDevContainer) {
// Running inside a VS Code devcontainer
neonConfig.wsProxy = (host) => "pg_proxy:80/v1";
neonConfig.wsProxy = (host) => "host.docker.internal:5481/v1";
} else {
// Not running inside a VS Code devcontainer
neonConfig.wsProxy = (host) => "pg_proxy:80/v1";
neonConfig.wsProxy = (host) => `${host}:5481/v1`;
}
// Disable all authentication and encryption
neonConfig.useSecureWebSocket = false;
+3 -1
View File
@@ -51,7 +51,9 @@ const createRunRoute = createRoute({
export const registerCreateRunRoute = (app: App) => {
app.openapi(createRunRoute, async (c) => {
const data = c.req.valid("json");
const origin = new URL(c.req.url).origin;
const proto = c.req.headers.get('x-forwarded-proto') || "http";
const host = c.req.headers.get('x-forwarded-host') || c.req.headers.get('host');
const origin = `${proto}://${host}` || new URL(c.req.url).origin;
const apiKeyTokenData = c.get("apiKeyTokenData")!;
const { deployment_id, inputs } = data;
+1 -1
View File
@@ -102,7 +102,7 @@ export const createRun = withServerPromise(
let prompt_id: string | undefined = undefined;
const shareData = {
workflow_api: workflow_api,
workflow_api_raw: workflow_api,
status_endpoint: `${origin}/api/update-run`,
file_upload_endpoint: `${origin}/api/file-upload`,
};