Chaoses-Ib / ComfyScript

A Python frontend and library for ComfyUI
MIT License
383 stars 21 forks source link

`ComfyScript: Failed to load node VHS_VideoCombine` because of `AttributeError: 'list' object has no attribute 'removesuffix'` #22

Open JCBrouwer opened 7 months ago

JCBrouwer commented 7 months ago

Hi there, thanks for this super cool repo, this is exactly what I've been waiting for to really dive into comfyUI!

I'm trying to get an animateDiff workflow working, but I'm running into an issue with the node which outputs the video/gif.

It's from https://github.com/Kosinkadink/ComfyUI-VideoHelperSuite

The workflow is here: whileaf-lora-workflow.json

It gets transpiled to:

from comfy_script.runtime import *
load()
from comfy_script.runtime.nodes import *

with Workflow():
    model, clip, vae = CheckpointLoaderSimple('v1-5-pruned-emaonly.safetensors')
    model, clip = LoraLoader(model, clip, 'whileaf.safetensors', 1, 1)
    motion_model = ADELoadAnimateDiffModel('v3_sd15_mm.ckpt', None)
    m_models = ADEApplyAnimateDiffModel(motion_model, 0, 1, None, None, None, None, None)
    context_opts = ADELoopedUniformContextOptions(16, 1, 4, True, 'pyramid', False, 0, 1, None, None)
    settings = ADEAnimateDiffSamplingSettings(0, 'FreeNoise', 'comfy', 0, None, None, 0, False, None, None)
    model = ADEUseEvolvedSampling(model, 'autoselect', m_models, context_opts, settings)
    conditioning = CLIPTextEncode('whileaf whileaf creepy slime calligraphy graffiti runes', clip)
    conditioning2 = CLIPTextEncode('ugly', clip)
    latent = EmptyLatentImage(512, 512, 48)
    latent = KSampler(model, 820058635513319, 20, 8, 'dpmpp_2m_sde_gpu', 'karras', conditioning, conditioning2, latent, 1)
    image = VAEDecode(latent, vae)
    _ = VHSVideoCombine(image, 8, 0, 'AnimateDiff', 'image/gif', False, True, None, None)

but running it gives the following errors:

ComfyScript: Using ComfyUI from http://127.0.0.1:8188/
Nodes: 357
ComfyScript: Failed to load node VHS_VideoCombine
Traceback (most recent call last):
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/src/comfy_script/runtime/nodes.py", line 19, in load
    fact.add_node(node_info)
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/src/comfy_script/runtime/factory.py", line 392, in add_node
    inputs.append(f'{input_id}: {type_and_hint(type_info, name, optional, config.get("default"))[1]}')
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/src/comfy_script/runtime/factory.py", line 264, in type_and_hint
    enum_c, t = astutil.to_str_enum(id, { _remove_extension(s): s for s in type_info }, '    ')
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/src/comfy_script/runtime/factory.py", line 264, in <dictcomp>
    enum_c, t = astutil.to_str_enum(id, { _remove_extension(s): s for s in type_info }, '    ')
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/src/comfy_script/runtime/factory.py", line 19, in _remove_extension
    path = path.removesuffix(ext)
AttributeError: 'list' object has no attribute 'removesuffix'
ComfyScript: Node already exists: {'input': {'required': {'frames_per_batch': ['INT', {'default': 16, 'min': 1, 'max': 128, 'step': 1}]}, 'hidden': {'prompt': 'PROMPT', 'unique_id': 'UNIQUE_ID'}}, 'output': ['VHS_BatchManager'], 'output_is_list': [False], 'output_name': ['VHS_BatchManager'], 'name': 'VHS_BatchManager', 'display_name': 'Batch Manager 🎥🅥🅗🅢', 'description': '', 'category': 'Video Helper Suite 🎥🅥🅗🅢', 'output_node': False}
ComfyScript: Failed to load node List of any [Crystools]
Traceback (most recent call last):
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/src/comfy_script/runtime/nodes.py", line 19, in load
    fact.add_node(node_info)
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/src/comfy_script/runtime/factory.py", line 420, in add_node
    output_types = [type_and_hint(type, name, output=True)[0] for type, name in output_with_name]
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/src/comfy_script/runtime/factory.py", line 420, in <listcomp>
    output_types = [type_and_hint(type, name, output=True)[0] for type, name in output_with_name]
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/src/comfy_script/runtime/factory.py", line 264, in type_and_hint
    enum_c, t = astutil.to_str_enum(id, { _remove_extension(s): s for s in type_info }, '    ')
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/src/comfy_script/astutil.py", line 149, in to_str_enum
    return to_enum(id, dic, indent, StrEnum)
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/src/comfy_script/astutil.py", line 142, in to_enum
    return c, enum_class(id, members)
  File "/home/hans/.conda/envs/hans/lib/python3.10/enum.py", line 387, in __call__
    return cls._create_(
  File "/home/hans/.conda/envs/hans/lib/python3.10/enum.py", line 518, in _create_
    enum_class = metacls.__new__(metacls, class_name, bases, classdict)
  File "/home/hans/.conda/envs/hans/lib/python3.10/enum.py", line 208, in __new__
    raise ValueError('Invalid enum member name: {0}'.format(
ValueError: Invalid enum member name: 
ComfyScript: Failed to queue prompt: <ClientResponse(http://127.0.0.1:8188/prompt) [400 Bad Request]>
<CIMultiDictProxy('Content-Type': 'application/json; charset=utf-8', 'Content-Length': '128', 'Date': 'Sat, 10 Feb 2024 13:57:54 GMT', 'Server': 'Python/3.10 aiohttp/3.9.3')>
<ClientResponse(http://127.0.0.1:8188/prompt) [400 Bad Request]>
<CIMultiDictProxy('Content-Type': 'application/json; charset=utf-8', 'Content-Length': '128', 'Date': 'Sat, 10 Feb 2024 13:57:54 GMT', 'Server': 'Python/3.10 aiohttp/3.9.3')>
{
  "error": {
    "type": "prompt_no_outputs",
    "message": "Prompt has no outputs",
    "details": "",
    "extra_info": {}
  },
  "node_errors": []
}
Traceback (most recent call last):
  File "/home/hans/.conda/envs/hans/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/home/hans/.conda/envs/hans/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/HUGE/Code/ComfyUI/custom_nodes/ComfyScript/whileaf-lora-workflow.py", line 18, in <module>
    VHSVideoCombine(image, 8, 0, 'AnimateDiff', 'image/gif', False, True, None, None)
TypeError: VHSVideoCombine() takes no arguments

Any idea what might be going on?

Chaoses-Ib commented 7 months ago

VideoHelperSuite does some hacks to ComfyUI. VHS_VideoCombine has dynamic inputs depending on what format is chosen. This is not supported by official ComfyUI and ComfyScript doesn't support it either.

ComfyScript will now ignore any invalid format values:

ComfyScript: VHS_VideoCombine: Invalid enum value: ['video/av1-webm', [['pix_fmt', ['yuv420p10le', 'yuv420p']], ['crf', 'INT', {'default': 23, 'min': 0, 'max': 100, 'step': 1}], ['input_color_depth', ['8bit', '16bit']], ['save_metadata', 'BOOLEAN', {'default': True}]]]
ComfyScript: VHS_VideoCombine: Invalid enum value: ['video/h264-mp4', [['pix_fmt', ['yuv420p', 'yuv420p10le']], ['crf', 'INT', {'default': 19, 'min': 0, 'max': 100, 'step': 1}], ['save_metadata', 'BOOLEAN', {'default': True}]]]
ComfyScript: VHS_VideoCombine: Invalid enum value: ['video/h265-mp4', [['pix_fmt', ['yuv420p10le', 'yuv420p']], ['crf', 'INT', {'default': 22, 'min': 0, 'max': 100, 'step': 1}], ['save_metadata', 'BOOLEAN', {'default': True}]]]
ComfyScript: VHS_VideoCombine: Invalid enum value: ['video/nvenc_h264-mp4', [['pix_fmt', ['yuv420p', 'yuv420p10le']], ['bitrate', 'INT', {'default': 10, 'min': 1, 'max': 999, 'step': 1}], ['megabit', 'BOOLEAN', {'default': True}], ['save_metadata', 'BOOLEAN', {'default': True}]]]
ComfyScript: VHS_VideoCombine: Invalid enum value: ['video/nvenc_hevc-mp4', [['pix_fmt', ['yuv420p', 'yuv420p10le']], ['bitrate', 'INT', {'default': 10, 'min': 1, 'max': 999, 'step': 1}], ['megabit', 'BOOLEAN', {'default': True}], ['save_metadata', 'BOOLEAN', {'default': True}]]]
ComfyScript: VHS_VideoCombine: Invalid enum value: ['video/webm', [['crf', 'INT', {'default': 20, 'min': 0, 'max': 100, 'step': 1}], ['save_metadata', 'BOOLEAN', {'default': True}]]]

If you want to use these ignore values and dynamic inputs, you can directly use string literals and keyword arguments, for example:

VHSVideoCombine(image, 8, 0, 'AnimateDiff', 'video/av1-webm', False, True, None, None, pix_fmt='yuv420p10le', crf=23, input_color_depth='8bit', save_metadata=True)
JCBrouwer commented 7 months ago

Great, thanks, I'll give it a try and report back here!

Chaoses-Ib commented 7 months ago

This issue could be kept open to help other people using VideoHelperSuite.

Chaoses-Ib commented 7 months ago

By the way, ComfyScript: Failed to load node List of any [Crystools] is now fixed.

ambocclusion commented 6 months ago

I cannot for the life of me figure out how to get output from the VHSVideoCombine node function loaded into my Python code, even as a PIL image. Is there a standard way of doing this?

Chaoses-Ib commented 6 months ago

VHSVideoCombine is an output node, and every output node will return a dict. In this dict, ui marks the values to be passed to the web UI, and result marks the real result. For VHSVideoCombine, these values are:

previews = [
    {
        "filename": file,
        "subfolder": subfolder,
        "type": "output" if save_output else "temp",
        "format": format,
    }
]
return {"ui": {"gifs": previews}, "result": ((save_output, output_files),)}

If you are using virtual mode, you can get the dict via VHSVideoCombine().wait()._output. If you are using real mode, the returned value is just the dict. After having the dict, there are two ways to retrieve the non-text results:

I'll add some interop with VHSVideoCombine, but may need several days to find some spare time.

ambocclusion commented 6 months ago

I've attempted to get this code to work:

video = VHSVideoCombine(images, 8, 0, 'final_output', 'image/gif', False, True, None, None)

results = video.wait()

return [await results._output()]

but it throws the error AttributeError: 'NoneType' object has no attribute '_output'

Chaoses-Ib commented 6 months ago

Sorry, I missed one line of code. It's now fixed and you can run git pull to update. Here is an example to read the GIF as a PIL image:

with Workflow():
    model, clip, vae = CheckpointLoaderSimple(Checkpoints.v1_5_pruned_emaonly)
    conditioning = CLIPTextEncode('whileaf whileaf creepy slime calligraphy graffiti runes', clip)
    conditioning2 = CLIPTextEncode('ugly', clip)
    latent = EmptyLatentImage(512, 512, 2)
    latent = KSampler(model, 0, 5, 8, 'dpmpp_2m_sde_gpu', 'karras', conditioning, conditioning2, latent, 1)
    image = VAEDecode(latent, vae)
    video = VHSVideoCombine(image, 8, 0, 'AnimateDiff', 'image/gif', False, True, None, None)

import PIL.Image
# {'gifs': [{'filename': 'AnimateDiff_00002.gif', 'subfolder': '', 'type': 'output', 'format': 'image/gif'}]}
filename = video.wait()._output['gifs'][0]['filename']
gif = PIL.Image.open(rf'D:/ComfyUI/output/{filename}')
display(gif)

# Second frame
gif.seek(1)
display(gif)

image

Chaoses-Ib commented 6 months ago

To display the animation instead of a frame in Jupyter Notebook:

from IPython.display import Image
Image(filename=rf'D:/ComfyUI/output/{filename}')

image

Another option is to use https://github.com/google/mediapy, which can also display videos but requires FFmpeg to be present in PATH.

ambocclusion commented 6 months ago

It works now, thank you so much!