New node - Load Audio UI
Added new node, Load Audio UI Updated workflow
This commit is contained in:
@@ -2,13 +2,15 @@ from .ltx_keyframer import LTXKeyframer
|
||||
from .multi_image_loader import MultiImageLoader
|
||||
from .ltx_sequencer import LTXSequencer
|
||||
from .speech_length_calculator import SpeechLengthCalculator
|
||||
from .load_audio_ui import LoadAudioUI
|
||||
|
||||
# Register the node classes
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LTXKeyframer": LTXKeyframer,
|
||||
"MultiImageLoader": MultiImageLoader,
|
||||
"LTXSequencer": LTXSequencer,
|
||||
"SpeechLengthCalculator": SpeechLengthCalculator
|
||||
"SpeechLengthCalculator": SpeechLengthCalculator,
|
||||
"LoadAudioUI": LoadAudioUI
|
||||
}
|
||||
|
||||
# Provide clean display names for the ComfyUI interface
|
||||
@@ -16,7 +18,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"LTXKeyframer": "LTX Keyframer",
|
||||
"MultiImageLoader": "Multi Image Loader",
|
||||
"LTXSequencer": "LTX Sequencer",
|
||||
"SpeechLengthCalculator": "Speech Length Calculator"
|
||||
"SpeechLengthCalculator": "Speech Length Calculator",
|
||||
"LoadAudioUI": "Load Audio UI"
|
||||
}
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "16f348ab-5e09-4b05-83e4-b5c8d1913f8b",
|
||||
"id": "c80f3abf-7abe-4c24-8663-009ac9c86e3d",
|
||||
"revision": 0,
|
||||
"last_node_id": 498,
|
||||
"last_link_id": 1199,
|
||||
"last_node_id": 499,
|
||||
"last_link_id": 1201,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
@@ -97,51 +97,6 @@
|
||||
"color": "#222",
|
||||
"bgcolor": "#000"
|
||||
},
|
||||
{
|
||||
"id": 381,
|
||||
"type": "MultiImageLoader",
|
||||
"pos": [
|
||||
3540,
|
||||
5950
|
||||
],
|
||||
"size": [
|
||||
340,
|
||||
300
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "multi_output",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1046,
|
||||
1047,
|
||||
1048,
|
||||
1157
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "WhatDreamsCost-ComfyUI",
|
||||
"ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24",
|
||||
"Node name for S&R": "MultiImageLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
"lanczos",
|
||||
"keep proportion",
|
||||
32,
|
||||
18,
|
||||
""
|
||||
],
|
||||
"color": "#222",
|
||||
"bgcolor": "#000"
|
||||
},
|
||||
{
|
||||
"id": 376,
|
||||
"type": "LTXSequencer",
|
||||
@@ -632,30 +587,6 @@
|
||||
"color": "#222",
|
||||
"bgcolor": "#000"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
3900,
|
||||
5740
|
||||
],
|
||||
"size": [
|
||||
230,
|
||||
140
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "LTX Sequencer Note",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Use one image at full strength for regular image to video.\n\nIf frame_insert is -1, it will be inserted at the last frame.\n\nThe insert_mode allows to switch between inserting images by setting the exact frame or by setting the exact second."
|
||||
],
|
||||
"color": "#223",
|
||||
"bgcolor": "#335"
|
||||
},
|
||||
{
|
||||
"id": 481,
|
||||
"type": "e8ae487a-31b9-46cf-be65-76e634e0bee1",
|
||||
@@ -674,7 +605,7 @@
|
||||
{
|
||||
"name": "audio",
|
||||
"type": "AUDIO",
|
||||
"link": 1189
|
||||
"link": 1200
|
||||
},
|
||||
{
|
||||
"name": "audio_vae",
|
||||
@@ -769,7 +700,7 @@
|
||||
"label": "custom audio",
|
||||
"name": "on_true",
|
||||
"type": "*",
|
||||
"link": 1190
|
||||
"link": 1201
|
||||
},
|
||||
{
|
||||
"label": "use custom audio",
|
||||
@@ -821,78 +752,6 @@
|
||||
"color": "#222",
|
||||
"bgcolor": "#000"
|
||||
},
|
||||
{
|
||||
"id": 495,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
2820,
|
||||
6540
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
90
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "Model Loader Note",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Load Custom Audio Here →\n\n(Make sure to set use custom audio to true to enable it)"
|
||||
],
|
||||
"color": "#223",
|
||||
"bgcolor": "#335"
|
||||
},
|
||||
{
|
||||
"id": 418,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
3130,
|
||||
5800
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
90
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "Model Loader Note",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"↓ Select your own models in this subgraph ↓"
|
||||
],
|
||||
"color": "#223",
|
||||
"bgcolor": "#335"
|
||||
},
|
||||
{
|
||||
"id": 439,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
2750,
|
||||
6050
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
380
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "FAQ",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: <https://github.com/wildminder/awesome-ltx2?tab=readme-ov-file>\n\nAlso the latest v1.1 spatial upscaler can be found here: <https://huggingface.co/Lightricks/LTX-2.3/tree/main>\n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n<https://huggingface.co/Kijai/LTX2.3_comfy/blob/main/vae/taeltx2_3.safetensors>\n\n**I want to use my own models!**\n\nYou can change the models in the Model Loader Subgraph →\n\n\n\n\n"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "360d64f7-96cd-42a6-9b13-6eb0387e17d9",
|
||||
@@ -988,19 +847,143 @@
|
||||
"color": "#222",
|
||||
"bgcolor": "#000"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4",
|
||||
"pos": [
|
||||
4160,
|
||||
5950
|
||||
],
|
||||
"size": [
|
||||
220,
|
||||
110
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 1048
|
||||
},
|
||||
{
|
||||
"label": "length_seconds",
|
||||
"name": "length",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "length"
|
||||
},
|
||||
"link": 21
|
||||
},
|
||||
{
|
||||
"name": "audio_vae",
|
||||
"type": "VAE",
|
||||
"link": 22
|
||||
},
|
||||
{
|
||||
"name": "frame_rate",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "frame_rate"
|
||||
},
|
||||
"link": 1109
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "video_latent",
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
803
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "audio_latent",
|
||||
"name": "Latent",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
1166
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"proxyWidgets": [
|
||||
[
|
||||
"352",
|
||||
"length"
|
||||
],
|
||||
[
|
||||
"353",
|
||||
"frame_rate"
|
||||
]
|
||||
],
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.17.2"
|
||||
},
|
||||
"widgets_values": [],
|
||||
"color": "#222",
|
||||
"bgcolor": "#000"
|
||||
},
|
||||
{
|
||||
"id": 381,
|
||||
"type": "MultiImageLoader",
|
||||
"pos": [
|
||||
3540,
|
||||
5950
|
||||
],
|
||||
"size": [
|
||||
340,
|
||||
300
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "multi_output",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
1046,
|
||||
1047,
|
||||
1048,
|
||||
1157
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "WhatDreamsCost-ComfyUI",
|
||||
"ver": "ecd00e6b404f14b07c7b54b0b0033e8bccb32d24",
|
||||
"Node name for S&R": "MultiImageLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
0,
|
||||
0,
|
||||
"lanczos",
|
||||
"keep proportion",
|
||||
32,
|
||||
18,
|
||||
""
|
||||
],
|
||||
"color": "#222",
|
||||
"bgcolor": "#000"
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "958b2c7a-1f75-4a30-84a8-892bddb9e426",
|
||||
"pos": [
|
||||
3130,
|
||||
5950
|
||||
2720,
|
||||
5960
|
||||
],
|
||||
"size": [
|
||||
390,
|
||||
480
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -1153,97 +1136,42 @@
|
||||
"bgcolor": "#000"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "2979f3eb-1c4c-45ff-8387-f1f4addb72f4",
|
||||
"id": 418,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
4160,
|
||||
5950
|
||||
2730,
|
||||
5820
|
||||
],
|
||||
"size": [
|
||||
220,
|
||||
110
|
||||
270,
|
||||
90
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 1048
|
||||
},
|
||||
{
|
||||
"label": "length_seconds",
|
||||
"name": "length",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "length"
|
||||
},
|
||||
"link": 21
|
||||
},
|
||||
{
|
||||
"name": "audio_vae",
|
||||
"type": "VAE",
|
||||
"link": 22
|
||||
},
|
||||
{
|
||||
"name": "frame_rate",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "frame_rate"
|
||||
},
|
||||
"link": 1109
|
||||
}
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "Model Loader Note",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"↓ Select your own models in this subgraph ↓"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "video_latent",
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
803
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "audio_latent",
|
||||
"name": "Latent",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
1166
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"proxyWidgets": [
|
||||
[
|
||||
"352",
|
||||
"length"
|
||||
],
|
||||
[
|
||||
"353",
|
||||
"frame_rate"
|
||||
]
|
||||
],
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.17.2"
|
||||
},
|
||||
"widgets_values": [],
|
||||
"color": "#222",
|
||||
"bgcolor": "#000"
|
||||
"color": "#223",
|
||||
"bgcolor": "#335"
|
||||
},
|
||||
{
|
||||
"id": 489,
|
||||
"type": "LoadAudio",
|
||||
"id": 499,
|
||||
"type": "LoadAudioUI",
|
||||
"pos": [
|
||||
3130,
|
||||
6480
|
||||
5960
|
||||
],
|
||||
"size": [
|
||||
300,
|
||||
140
|
||||
390,
|
||||
334
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
@@ -1251,24 +1179,98 @@
|
||||
"name": "AUDIO",
|
||||
"type": "AUDIO",
|
||||
"links": [
|
||||
1189,
|
||||
1190
|
||||
1200,
|
||||
1201
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "Load Custom Audio",
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.19.3",
|
||||
"Node name for S&R": "LoadAudio"
|
||||
"cnr_id": "WhatDreamsCost-ComfyUI",
|
||||
"ver": "919e3c15e53502a7f9165c4355a635b4fa2a07ae",
|
||||
"Node name for S&R": "LoadAudioUI"
|
||||
},
|
||||
"widgets_values": [
|
||||
"LTX2_00376_.mp3",
|
||||
"audio.wav",
|
||||
0,
|
||||
25,
|
||||
null,
|
||||
null
|
||||
null,
|
||||
""
|
||||
],
|
||||
"color": "#222",
|
||||
"bgcolor": "#000"
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
3900,
|
||||
5740
|
||||
],
|
||||
"size": [
|
||||
230,
|
||||
150
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "LTX Sequencer Note",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Use one image at full strength for regular image to video.\n\nIf frame_insert is -1, it will be inserted at the last frame.\n\nThe insert_mode allows to switch between inserting images by setting the exact frame or by setting the exact second."
|
||||
],
|
||||
"color": "#223",
|
||||
"bgcolor": "#335"
|
||||
},
|
||||
{
|
||||
"id": 495,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
3130,
|
||||
6350
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
90
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "Custom Audio Note",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"↑ Load Custom Audio Here ↑\n\n(Make sure to set use custom audio to true to enable it)"
|
||||
],
|
||||
"color": "#223",
|
||||
"bgcolor": "#335"
|
||||
},
|
||||
{
|
||||
"id": 439,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
2340,
|
||||
6060
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
380
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "FAQ",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: <https://github.com/wildminder/awesome-ltx2?tab=readme-ov-file>\n\nAlso the latest v1.1 spatial upscaler can be found here: <https://huggingface.co/Lightricks/LTX-2.3/tree/main>\n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n<https://huggingface.co/Kijai/LTX2.3_comfy/blob/main/vae/taeltx2_3.safetensors>\n\n**I want to use my own models!**\n\nYou can change the models in the Model Loader Subgraph →\n\n\n\n\n"
|
||||
],
|
||||
"color": "#323",
|
||||
"bgcolor": "#535"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
@@ -1512,22 +1514,6 @@
|
||||
5,
|
||||
"BOOLEAN"
|
||||
],
|
||||
[
|
||||
1189,
|
||||
489,
|
||||
0,
|
||||
481,
|
||||
0,
|
||||
"AUDIO"
|
||||
],
|
||||
[
|
||||
1190,
|
||||
489,
|
||||
0,
|
||||
1,
|
||||
4,
|
||||
"AUDIO"
|
||||
],
|
||||
[
|
||||
1199,
|
||||
10,
|
||||
@@ -1535,6 +1521,22 @@
|
||||
11,
|
||||
8,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
1200,
|
||||
499,
|
||||
0,
|
||||
481,
|
||||
0,
|
||||
"AUDIO"
|
||||
],
|
||||
[
|
||||
1201,
|
||||
499,
|
||||
0,
|
||||
1,
|
||||
4,
|
||||
"AUDIO"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
@@ -1545,8 +1547,8 @@
|
||||
"version": 1,
|
||||
"state": {
|
||||
"lastGroupId": 33,
|
||||
"lastNodeId": 498,
|
||||
"lastLinkId": 1199,
|
||||
"lastNodeId": 499,
|
||||
"lastLinkId": 1201,
|
||||
"lastRerouteId": 0
|
||||
},
|
||||
"revision": 0,
|
||||
@@ -2331,8 +2333,8 @@
|
||||
"version": 1,
|
||||
"state": {
|
||||
"lastGroupId": 33,
|
||||
"lastNodeId": 498,
|
||||
"lastLinkId": 1199,
|
||||
"lastNodeId": 499,
|
||||
"lastLinkId": 1201,
|
||||
"lastRerouteId": 0
|
||||
},
|
||||
"revision": 0,
|
||||
@@ -2840,8 +2842,8 @@
|
||||
"version": 1,
|
||||
"state": {
|
||||
"lastGroupId": 33,
|
||||
"lastNodeId": 498,
|
||||
"lastLinkId": 1199,
|
||||
"lastNodeId": 499,
|
||||
"lastLinkId": 1201,
|
||||
"lastRerouteId": 0
|
||||
},
|
||||
"revision": 0,
|
||||
@@ -4262,8 +4264,8 @@
|
||||
"version": 1,
|
||||
"state": {
|
||||
"lastGroupId": 33,
|
||||
"lastNodeId": 498,
|
||||
"lastLinkId": 1199,
|
||||
"lastNodeId": 499,
|
||||
"lastLinkId": 1201,
|
||||
"lastRerouteId": 0
|
||||
},
|
||||
"revision": 0,
|
||||
@@ -4728,8 +4730,8 @@
|
||||
"version": 1,
|
||||
"state": {
|
||||
"lastGroupId": 33,
|
||||
"lastNodeId": 498,
|
||||
"lastLinkId": 1199,
|
||||
"lastNodeId": 499,
|
||||
"lastLinkId": 1201,
|
||||
"lastRerouteId": 0
|
||||
},
|
||||
"revision": 0,
|
||||
@@ -5768,8 +5770,8 @@
|
||||
"id": 496,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
2170,
|
||||
5500
|
||||
2180,
|
||||
5510
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
@@ -5785,8 +5787,8 @@
|
||||
"widgets_values": [
|
||||
"# FAQ\n\n**How do I set the length of the video?**\n\nYou can set the length of the video in the Model Loader Subgraph →\n\n**I keep getting errors when running this workflow!**\n\nDouble check that all of the correct models loaded. This is using the newest version of 2x upscaler (v1.1). You can change it to the older one under the Model Loader.\n\n**Where do I download these models??**\n\nThere is a good list of models and download links here: <https://github.com/wildminder/awesome-ltx2?tab=readme-ov-file>\n\nAlso the latest v1.1 spatial upscaler can be found here: <https://huggingface.co/Lightricks/LTX-2.3/tree/main>\n\nThe tiny vae (taeltx2_3.safetensors) is found here:\n<https://huggingface.co/Kijai/LTX2.3_comfy/blob/main/vae/taeltx2_3.safetensors>\n\n**I want to use my own models!**\n\nYou can change the models in the Model Loader Subgraph →\n\n\n\n\n"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
"color": "#323",
|
||||
"bgcolor": "#535"
|
||||
}
|
||||
],
|
||||
"groups": [],
|
||||
@@ -5993,8 +5995,8 @@
|
||||
"version": 1,
|
||||
"state": {
|
||||
"lastGroupId": 33,
|
||||
"lastNodeId": 498,
|
||||
"lastLinkId": 1199,
|
||||
"lastNodeId": 499,
|
||||
"lastLinkId": 1201,
|
||||
"lastRerouteId": 0
|
||||
},
|
||||
"revision": 0,
|
||||
@@ -6269,8 +6271,8 @@
|
||||
"version": 1,
|
||||
"state": {
|
||||
"lastGroupId": 33,
|
||||
"lastNodeId": 498,
|
||||
"lastLinkId": 1199,
|
||||
"lastNodeId": 499,
|
||||
"lastLinkId": 1201,
|
||||
"lastRerouteId": 0
|
||||
},
|
||||
"revision": 0,
|
||||
@@ -6928,8 +6930,8 @@
|
||||
"version": 1,
|
||||
"state": {
|
||||
"lastGroupId": 33,
|
||||
"lastNodeId": 498,
|
||||
"lastLinkId": 1199,
|
||||
"lastNodeId": 499,
|
||||
"lastLinkId": 1201,
|
||||
"lastRerouteId": 0
|
||||
},
|
||||
"revision": 0,
|
||||
@@ -7495,10 +7497,10 @@
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true,
|
||||
"ds": {
|
||||
"scale": 1.015858193817759,
|
||||
"scale": 0.5159609120521172,
|
||||
"offset": [
|
||||
-2587.3940265259275,
|
||||
-5856.2206443447785
|
||||
-1943.5366161616175,
|
||||
-5193.440972222219
|
||||
]
|
||||
}
|
||||
},
|
||||
453
js/load_audio_ui.js
Normal file
453
js/load_audio_ui.js
Normal file
@@ -0,0 +1,453 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.LoadAudioUI",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeData.name === "LoadAudioUI") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
const onDrawBackground = nodeType.prototype.onDrawBackground;
|
||||
|
||||
// --- V1 LiteGraph Image Preview Hider ---
|
||||
nodeType.prototype.onDrawBackground = function (ctx) {
|
||||
if (onDrawBackground) {
|
||||
onDrawBackground.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
// Ensure standard creation logic runs first
|
||||
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
|
||||
const node = this; // Capture the node instance
|
||||
|
||||
// Track if we are in the initial loading phase to prevent resetting saved trim values
|
||||
node._initializing = true;
|
||||
node._should_reset_trim = false;
|
||||
|
||||
// ====================================================================
|
||||
// FIX: HIDE DEFAULT COMFYUI AUDIO PLAYER SAFELY
|
||||
// ====================================================================
|
||||
setTimeout(() => {
|
||||
if (node.widgets) {
|
||||
const nativeWidgetIndex = node.widgets.findIndex(w => w.name === "audioUI");
|
||||
if (nativeWidgetIndex !== -1) {
|
||||
const w = node.widgets[nativeWidgetIndex];
|
||||
if (w.element) {
|
||||
w.element.style.display = "none";
|
||||
w.element.style.height = "0px";
|
||||
w.element.style.position = "absolute";
|
||||
w.element.style.pointerEvents = "none";
|
||||
}
|
||||
w.type = "hidden";
|
||||
w.hidden = true;
|
||||
w.computeSize = () => [0, 0];
|
||||
|
||||
// Only update height to account for the hidden widget,
|
||||
// preserving the width (whether it's the default 475 or a user-saved value).
|
||||
const currentWidth = node.size[0];
|
||||
const recommendedHeight = node.computeSize()[1];
|
||||
node.setSize([currentWidth, recommendedHeight]);
|
||||
|
||||
if (app.graph) {
|
||||
app.graph.setDirtyCanvas(true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 10);
|
||||
// ====================================================================
|
||||
|
||||
|
||||
// --- THE CORE FIX FOR COMFYUI V2 ---
|
||||
Object.defineProperty(node, 'imgs', {
|
||||
get: function() { return undefined; },
|
||||
set: function(val) { /* Ignore attempts by ComfyUI to set an image preview */ },
|
||||
configurable: true
|
||||
});
|
||||
|
||||
// Shared upload handler
|
||||
const handleFileUpload = async (file) => {
|
||||
if (!file.type.startsWith('audio/') && !file.type.startsWith('video/')) return false;
|
||||
try {
|
||||
const body = new FormData();
|
||||
body.append("image", file);
|
||||
body.append("type", "input");
|
||||
body.append("subfolder", "");
|
||||
|
||||
const resp = await api.fetchApi("/upload/image", {
|
||||
method: "POST",
|
||||
body,
|
||||
});
|
||||
|
||||
if (resp.status === 200) {
|
||||
const data = await resp.json();
|
||||
const audioWidget = node.widgets && node.widgets.find(w => w.name === "audio");
|
||||
if (audioWidget) {
|
||||
// Manual upload should always reset the trim range
|
||||
node._should_reset_trim = true;
|
||||
audioWidget.value = data.name;
|
||||
if (audioWidget.options && audioWidget.options.values && !audioWidget.options.values.includes(data.name)) {
|
||||
audioWidget.options.values.push(data.name);
|
||||
}
|
||||
if (audioWidget.callback) {
|
||||
audioWidget.callback(data.name);
|
||||
}
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Error uploading dragged audio file:", err);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
this.onDragDrop = function(e) {
|
||||
if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
const file = e.dataTransfer.files[0];
|
||||
if (file.type.startsWith('audio/') || file.type.startsWith('video/')) {
|
||||
handleFileUpload(file);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 1. Build the Main Custom HTML Container
|
||||
const container = document.createElement("div");
|
||||
const defaultBg = "rgba(30, 30, 30, 0.9)";
|
||||
Object.assign(container.style, {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "10px",
|
||||
width: "100%",
|
||||
padding: "10px",
|
||||
boxSizing: "border-box",
|
||||
background: defaultBg,
|
||||
borderRadius: "6px",
|
||||
color: "white",
|
||||
fontFamily: "sans-serif",
|
||||
marginTop: "8px",
|
||||
flexShrink: "0",
|
||||
transition: "background 0.2s"
|
||||
});
|
||||
|
||||
const playerTop = document.createElement("div");
|
||||
Object.assign(playerTop.style, {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
padding: "0 2px",
|
||||
marginBottom: "-4px"
|
||||
});
|
||||
|
||||
const playerTitle = document.createElement("span");
|
||||
playerTitle.textContent = "No audio selected";
|
||||
Object.assign(playerTitle.style, {
|
||||
fontSize: "11px",
|
||||
color: "#aaa",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
maxWidth: "140px"
|
||||
});
|
||||
|
||||
const trimLength = document.createElement("span");
|
||||
Object.assign(trimLength.style, {
|
||||
fontSize: "11px",
|
||||
color: "#38bdf8",
|
||||
fontWeight: "bold",
|
||||
background: "rgba(56, 189, 248, 0.1)",
|
||||
padding: "3px 6px",
|
||||
borderRadius: "4px",
|
||||
whiteSpace: "nowrap"
|
||||
});
|
||||
trimLength.textContent = "Trimmed: 0.0s";
|
||||
|
||||
playerTop.appendChild(playerTitle);
|
||||
playerTop.appendChild(trimLength);
|
||||
container.appendChild(playerTop);
|
||||
|
||||
const audioEl = document.createElement("audio");
|
||||
audioEl.controls = true;
|
||||
audioEl.style.width = "100%";
|
||||
audioEl.style.height = "40px";
|
||||
audioEl.style.outline = "none";
|
||||
container.appendChild(audioEl);
|
||||
|
||||
const trimArea = document.createElement("div");
|
||||
Object.assign(trimArea.style, {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "6px",
|
||||
background: "rgba(0, 0, 0, 0.35)",
|
||||
padding: "12px",
|
||||
borderRadius: "6px",
|
||||
border: "1px solid rgba(255, 255, 255, 0.05)"
|
||||
});
|
||||
|
||||
const timeRuler = document.createElement("div");
|
||||
Object.assign(timeRuler.style, {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "22px",
|
||||
fontSize: "10px",
|
||||
color: "#aaa",
|
||||
pointerEvents: "none",
|
||||
userSelect: "none"
|
||||
});
|
||||
trimArea.appendChild(timeRuler);
|
||||
|
||||
const sliderBox = document.createElement("div");
|
||||
Object.assign(sliderBox.style, {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "24px",
|
||||
background: "#111",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
boxShadow: "inset 0 1px 3px rgba(0,0,0,0.5)"
|
||||
});
|
||||
|
||||
const fill = document.createElement("div");
|
||||
Object.assign(fill.style, {
|
||||
position: "absolute",
|
||||
height: "100%",
|
||||
background: "rgba(14, 165, 233, 0.35)",
|
||||
pointerEvents: "none"
|
||||
});
|
||||
sliderBox.appendChild(fill);
|
||||
|
||||
const createHandle = (color) => {
|
||||
const h = document.createElement("div");
|
||||
Object.assign(h.style, {
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
width: "8px",
|
||||
height: "100%",
|
||||
background: color,
|
||||
transform: "translateX(-50%)",
|
||||
pointerEvents: "none",
|
||||
boxShadow: "0 0 4px rgba(0,0,0,0.8)",
|
||||
borderRadius: "2px"
|
||||
});
|
||||
return h;
|
||||
};
|
||||
|
||||
const startHandle = createHandle("#38bdf8");
|
||||
const endHandle = createHandle("#38bdf8");
|
||||
sliderBox.appendChild(startHandle);
|
||||
sliderBox.appendChild(endHandle);
|
||||
trimArea.appendChild(sliderBox);
|
||||
|
||||
container.appendChild(trimArea);
|
||||
|
||||
// 4. Attach container to the node UI
|
||||
const widget = this.addDOMWidget("audio_ui", "audio_ui", container);
|
||||
|
||||
// --- DEFAULT SIZE FOR NEW NODES ---
|
||||
// We set the width to 475 initially.
|
||||
// If a workflow is loading, LiteGraph will overwrite this size
|
||||
// automatically after this function finishes.
|
||||
this.size = [475, this.computeSize()[1]];
|
||||
|
||||
widget.computeSize = function(width) {
|
||||
return [width, 200];
|
||||
};
|
||||
|
||||
// 5. Bind Node Data to UI dynamically
|
||||
setTimeout(() => {
|
||||
const audioWidget = node.widgets && node.widgets.find(w => w.name === "audio");
|
||||
const startWidget = node.widgets && node.widgets.find(w => w.name === "start_time");
|
||||
const endWidget = node.widgets && node.widgets.find(w => w.name === "end_time");
|
||||
|
||||
if (audioWidget) {
|
||||
const updateAudio = () => {
|
||||
if (!audioWidget.value || audioWidget.value === "none") {
|
||||
playerTitle.textContent = "No audio selected";
|
||||
return;
|
||||
}
|
||||
let filename = audioWidget.value;
|
||||
let subfolder = "";
|
||||
if (filename.includes("/") || filename.includes("\\")) {
|
||||
const sep = filename.includes("/") ? "/" : "\\";
|
||||
const parts = filename.split(sep);
|
||||
filename = parts.pop();
|
||||
subfolder = parts.join("/");
|
||||
}
|
||||
playerTitle.textContent = filename;
|
||||
audioEl.src = api.apiURL(`/view?filename=${encodeURIComponent(filename)}&type=input&subfolder=${encodeURIComponent(subfolder)}`);
|
||||
};
|
||||
audioWidget.callback = function() {
|
||||
// If user manually changes the dropdown, flag for trim reset
|
||||
if (!node._initializing) {
|
||||
node._should_reset_trim = true;
|
||||
}
|
||||
updateAudio();
|
||||
};
|
||||
updateAudio();
|
||||
}
|
||||
|
||||
container.ondragover = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
container.style.background = "rgba(14, 165, 233, 0.2)";
|
||||
};
|
||||
container.ondragleave = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
container.style.background = defaultBg;
|
||||
};
|
||||
container.ondrop = async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
container.style.background = defaultBg;
|
||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
||||
handleFileUpload(e.dataTransfer.files[0]);
|
||||
}
|
||||
};
|
||||
|
||||
let duration = 0;
|
||||
let dragging = null;
|
||||
|
||||
const formatTime = (secs) => {
|
||||
if (secs < 60) return secs.toFixed(1) + "s";
|
||||
const m = Math.floor(secs / 60);
|
||||
const s = (secs % 60).toFixed(1);
|
||||
return `${m}:${s.padStart(4, '0')}`;
|
||||
};
|
||||
|
||||
const updateRuler = () => {
|
||||
timeRuler.innerHTML = '';
|
||||
if (!duration) return;
|
||||
const numMajorTicks = 5;
|
||||
const subTicks = 4;
|
||||
const totalTicks = (numMajorTicks - 1) * subTicks;
|
||||
for (let i = 0; i <= totalTicks; i++) {
|
||||
const pct = i / totalTicks;
|
||||
const t = duration * pct;
|
||||
const isMajor = i % subTicks === 0;
|
||||
const tickWrapper = document.createElement("div");
|
||||
Object.assign(tickWrapper.style, {
|
||||
position: "absolute", left: `${pct * 100}%`, top: "0",
|
||||
display: "flex", flexDirection: "column", alignItems: "center", transform: "translateX(-50%)"
|
||||
});
|
||||
if (i === 0) { tickWrapper.style.transform = "none"; tickWrapper.style.alignItems = "flex-start"; }
|
||||
if (i === totalTicks) { tickWrapper.style.transform = "translateX(-100%)"; tickWrapper.style.alignItems = "flex-end"; }
|
||||
const line = document.createElement("div");
|
||||
Object.assign(line.style, {
|
||||
width: isMajor ? "2px" : "1px", height: isMajor ? "6px" : "4px",
|
||||
background: isMajor ? "#aaa" : "#555", marginBottom: "2px", borderRadius: "1px"
|
||||
});
|
||||
tickWrapper.appendChild(line);
|
||||
if (isMajor) {
|
||||
const label = document.createElement("div");
|
||||
label.textContent = formatTime(t);
|
||||
tickWrapper.appendChild(label);
|
||||
}
|
||||
timeRuler.appendChild(tickWrapper);
|
||||
}
|
||||
};
|
||||
|
||||
const updateUI = (syncPlayer = false) => {
|
||||
if (!duration) return;
|
||||
let s = startWidget ? parseFloat(startWidget.value) || 0 : 0;
|
||||
let e = endWidget ? parseFloat(endWidget.value) || 0 : 0;
|
||||
if (e === 0 || e > duration) e = duration;
|
||||
if (s > e) s = e;
|
||||
const sPct = (s / duration) * 100;
|
||||
const ePct = (e / duration) * 100;
|
||||
startHandle.style.left = `${sPct}%`;
|
||||
endHandle.style.left = `${ePct}%`;
|
||||
fill.style.left = `${sPct}%`;
|
||||
fill.style.width = `${ePct - sPct}%`;
|
||||
trimLength.textContent = `Trimmed: ${formatTime(e - s)}`;
|
||||
if (syncPlayer && audioEl.readyState >= 1) { audioEl.currentTime = s; }
|
||||
};
|
||||
|
||||
audioEl.onloadedmetadata = () => {
|
||||
duration = audioEl.duration;
|
||||
|
||||
// Handle trim reset for new audio selection
|
||||
if (node._should_reset_trim) {
|
||||
if (startWidget) startWidget.value = 0;
|
||||
if (endWidget) endWidget.value = parseFloat(duration.toFixed(3));
|
||||
node._should_reset_trim = false;
|
||||
} else {
|
||||
// Default clamping logic for initial load or out-of-bounds saved values
|
||||
let e = endWidget ? parseFloat(endWidget.value) || 0 : 0;
|
||||
if (endWidget && (e === 0 || e > duration)) {
|
||||
endWidget.value = parseFloat(duration.toFixed(3));
|
||||
}
|
||||
}
|
||||
|
||||
updateRuler();
|
||||
updateUI();
|
||||
app.graph.setDirtyCanvas(true, false);
|
||||
};
|
||||
|
||||
audioEl.ontimeupdate = () => {
|
||||
if (dragging || !duration) return;
|
||||
let s = startWidget ? parseFloat(startWidget.value) || 0 : 0;
|
||||
let e = endWidget ? parseFloat(endWidget.value) || duration : duration;
|
||||
if (e === 0) e = duration;
|
||||
if (audioEl.currentTime >= e) { audioEl.pause(); audioEl.currentTime = s; }
|
||||
};
|
||||
|
||||
audioEl.onplay = () => {
|
||||
let s = startWidget ? parseFloat(startWidget.value) || 0 : 0;
|
||||
let e = endWidget ? parseFloat(endWidget.value) || duration : duration;
|
||||
if (e === 0) e = duration;
|
||||
if (audioEl.currentTime < s || audioEl.currentTime >= e) { audioEl.currentTime = s; }
|
||||
};
|
||||
|
||||
[startWidget, endWidget].forEach(w => {
|
||||
if (w) {
|
||||
const orig = w.callback;
|
||||
w.callback = function() { updateUI(true); if(orig) orig.apply(this, arguments); };
|
||||
}
|
||||
});
|
||||
|
||||
sliderBox.onpointerdown = (e) => {
|
||||
if (!duration) return;
|
||||
const rect = sliderBox.getBoundingClientRect();
|
||||
const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
|
||||
const val = (x / rect.width) * duration;
|
||||
let s = startWidget ? parseFloat(startWidget.value) || 0 : 0;
|
||||
let e_val = endWidget ? parseFloat(endWidget.value) || duration : duration;
|
||||
if (Math.abs(val - s) < Math.abs(val - e_val)) {
|
||||
dragging = 'start';
|
||||
if(startWidget) startWidget.value = parseFloat(Math.min(val, e_val).toFixed(3));
|
||||
} else {
|
||||
dragging = 'end';
|
||||
if(endWidget) endWidget.value = parseFloat(Math.max(val, s).toFixed(3));
|
||||
}
|
||||
updateUI(true); app.graph.setDirtyCanvas(true, false);
|
||||
sliderBox.setPointerCapture(e.pointerId);
|
||||
};
|
||||
|
||||
sliderBox.onpointermove = (e) => {
|
||||
if (!dragging || !duration) return;
|
||||
const rect = sliderBox.getBoundingClientRect();
|
||||
const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width));
|
||||
const val = (x / rect.width) * duration;
|
||||
if (dragging === 'start') {
|
||||
let e_val = endWidget ? parseFloat(endWidget.value) || duration : duration;
|
||||
if(startWidget) startWidget.value = parseFloat(Math.min(val, e_val).toFixed(3));
|
||||
} else if (dragging === 'end') {
|
||||
const s = startWidget ? parseFloat(startWidget.value) || 0 : 0;
|
||||
if(endWidget) endWidget.value = parseFloat(Math.max(val, s).toFixed(3));
|
||||
}
|
||||
updateUI(true); app.graph.setDirtyCanvas(true, false);
|
||||
};
|
||||
|
||||
sliderBox.onpointerup = (e) => { dragging = null; sliderBox.releasePointerCapture(e.pointerId); };
|
||||
|
||||
// Exit initialization phase
|
||||
setTimeout(() => { node._initializing = false; }, 500);
|
||||
|
||||
}, 100);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
136
load_audio_ui.py
Normal file
136
load_audio_ui.py
Normal file
@@ -0,0 +1,136 @@
|
||||
import folder_paths
|
||||
import os
|
||||
import torch
|
||||
import av
|
||||
|
||||
def f32_pcm(wav: torch.Tensor) -> torch.Tensor:
|
||||
"""Convert audio to float 32 bits PCM format."""
|
||||
if wav.dtype.is_floating_point:
|
||||
return wav
|
||||
elif wav.dtype == torch.int16:
|
||||
return wav.float() / (2 ** 15)
|
||||
elif wav.dtype == torch.int32:
|
||||
return wav.float() / (2 ** 31)
|
||||
raise ValueError(f"Unsupported wav dtype: {wav.dtype}")
|
||||
|
||||
def load_audio_file(filepath: str) -> tuple[torch.Tensor, int]:
|
||||
"""Uses the latest ComfyUI av-based decoding for maximum compatibility."""
|
||||
with av.open(filepath) as af:
|
||||
if not af.streams.audio:
|
||||
raise ValueError("No audio stream found in the file.")
|
||||
|
||||
stream = af.streams.audio[0]
|
||||
sr = stream.codec_context.sample_rate
|
||||
n_channels = stream.channels
|
||||
|
||||
frames = []
|
||||
for frame in af.decode(streams=stream.index):
|
||||
buf = torch.from_numpy(frame.to_ndarray())
|
||||
if buf.shape[0] != n_channels:
|
||||
buf = buf.view(-1, n_channels).t()
|
||||
|
||||
frames.append(buf)
|
||||
|
||||
if not frames:
|
||||
raise ValueError("No audio frames decoded.")
|
||||
|
||||
wav = torch.cat(frames, dim=1)
|
||||
wav = f32_pcm(wav)
|
||||
return wav, sr
|
||||
|
||||
|
||||
class LoadAudioUI:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
try:
|
||||
files = folder_paths.get_filename_list("audio")
|
||||
except:
|
||||
files = []
|
||||
|
||||
if not files:
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
if os.path.exists(input_dir):
|
||||
all_files = [f for f in os.listdir(input_dir) if os.path.isfile(os.path.join(input_dir, f))]
|
||||
try:
|
||||
files = sorted(folder_paths.filter_files_content_types(all_files, ["audio", "video"]))
|
||||
except:
|
||||
files = sorted(all_files)
|
||||
|
||||
if not files or len(files) == 0:
|
||||
files = ["none"]
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"audio": (files, {"audio_upload": True}),
|
||||
"start_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}),
|
||||
"end_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 100000.0, "step": 0.01}),
|
||||
},
|
||||
"optional": {
|
||||
"audioUI": ("AUDIO_UI",)
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = "audio"
|
||||
RETURN_TYPES = ("AUDIO", )
|
||||
FUNCTION = "load_audio"
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(cls, audio, **kwargs):
|
||||
# CRITICAL FIX: This bypasses the "Value not in list" error.
|
||||
# By returning True, we tell ComfyUI to allow the 'audio' value even if it isn't in
|
||||
# the current dropdown list. This allows the execution to reach load_audio(),
|
||||
# where our fallback silence logic can handle the missing file gracefully.
|
||||
return True
|
||||
|
||||
def load_audio(self, audio, start_time, end_time, **kwargs):
|
||||
# Determine the annotated file path if a file is actually selected
|
||||
# We wrap this in a try/except because get_annotated_filepath can fail if
|
||||
# the input string is malformed or doesn't follow expected paths.
|
||||
try:
|
||||
audio_path = folder_paths.get_annotated_filepath(audio) if audio != "none" else ""
|
||||
except:
|
||||
audio_path = ""
|
||||
|
||||
# --- FALLBACK LOGIC ---
|
||||
# If the file is 'none' or doesn't exist on disk, provide 1 second of silence
|
||||
if audio == "none" or not audio_path or not os.path.exists(audio_path):
|
||||
missing_info = audio if audio != "none" else "None selected"
|
||||
print(f"!!! [LoadAudioUI] Warning: Audio file '{missing_info}' not found. Outputting 1 second of silence.")
|
||||
|
||||
sample_rate = 44100
|
||||
# 1 second of silence (stereo) -> shape [channels, time]
|
||||
waveform = torch.zeros((2, 44100))
|
||||
else:
|
||||
try:
|
||||
waveform, sample_rate = load_audio_file(audio_path)
|
||||
except Exception as e:
|
||||
# If decoding fails for any reason, fallback to silence rather than crashing the workflow
|
||||
print(f"!!! [LoadAudioUI] Error decoding {audio}: {e}. Falling back to silence.")
|
||||
sample_rate = 44100
|
||||
waveform = torch.zeros((2, 44100))
|
||||
|
||||
# Convert seconds to frames
|
||||
start_frame = int(start_time * sample_rate)
|
||||
if end_time > 0:
|
||||
end_frame = int(end_time * sample_rate)
|
||||
# Ensure the end_frame does not exceed the actual audio length
|
||||
end_frame = min(end_frame, waveform.shape[1])
|
||||
else:
|
||||
# 0 defaults to the end of the file
|
||||
end_frame = waveform.shape[1]
|
||||
|
||||
# Ensure start frame stays within bounds and doesn't pass the end frame
|
||||
start_frame = min(start_frame, end_frame)
|
||||
|
||||
# Trim the waveform tensor -> shape: [channels, time]
|
||||
trimmed_waveform = waveform[:, start_frame:end_frame]
|
||||
|
||||
# Final safety check: if trimming resulted in zero length, give it a tiny bit of padding
|
||||
# to prevent downstream nodes from crashing on empty tensors
|
||||
if trimmed_waveform.shape[1] == 0:
|
||||
trimmed_waveform = torch.zeros((waveform.shape[0], 1))
|
||||
|
||||
# Format for ComfyUI's standard AUDIO type: [batch, channels, time]
|
||||
audio_output = {"waveform": trimmed_waveform.unsqueeze(0), "sample_rate": sample_rate}
|
||||
|
||||
return (audio_output, )
|
||||
@@ -17,7 +17,7 @@ class MultiImageLoader:
|
||||
"height": ("INT", {"default": 0, "min": 0, "max": 8192, "step": 1}),
|
||||
"interpolation": (["lanczos", "nearest", "bilinear", "bicubic", "area", "nearest-exact"],),
|
||||
"resize_method": (["keep proportion", "stretch", "pad", "crop"],),
|
||||
"multiple_of": ("INT", {"default": 0, "min": 0, "max": 512, "step": 1}),
|
||||
"multiple_of": ("INT", {"default": 32, "min": 0, "max": 512, "step": 1}),
|
||||
"img_compression": ("INT", {"default": 18, "min": 0, "max": 100, "step": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "WhatDreamsCost-ComfyUI"
|
||||
description = "A variety of custom ComfyUI nodes and workflows for creatives."
|
||||
version = "1.2.3"
|
||||
version = "1.2.4"
|
||||
license = {file = "LICENSE"}
|
||||
# classifiers = [
|
||||
# # For OS-independent nodes (works on all operating systems)
|
||||
|
||||
Reference in New Issue
Block a user