Update negative prompt for Moonvalley nodes (#9038)

Co-authored-by: thorsten <thorsten@tripod-digital.co.nz>
This commit is contained in:
Thor-ATX 2025-07-26 09:27:03 +12:00 committed by GitHub
parent c0207b473f
commit c60467a148
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -2,7 +2,11 @@ import logging
from typing import Any, Callable, Optional, TypeVar from typing import Any, Callable, Optional, TypeVar
import random import random
import torch import torch
from comfy_api_nodes.util.validation_utils import get_image_dimensions, validate_image_dimensions, validate_video_dimensions from comfy_api_nodes.util.validation_utils import (
get_image_dimensions,
validate_image_dimensions,
validate_video_dimensions,
)
from comfy_api_nodes.apis import ( from comfy_api_nodes.apis import (
@ -10,7 +14,7 @@ from comfy_api_nodes.apis import (
MoonvalleyTextToVideoInferenceParams, MoonvalleyTextToVideoInferenceParams,
MoonvalleyVideoToVideoInferenceParams, MoonvalleyVideoToVideoInferenceParams,
MoonvalleyVideoToVideoRequest, MoonvalleyVideoToVideoRequest,
MoonvalleyPromptResponse MoonvalleyPromptResponse,
) )
from comfy_api_nodes.apis.client import ( from comfy_api_nodes.apis.client import (
ApiEndpoint, ApiEndpoint,
@ -54,20 +58,26 @@ MAX_VIDEO_SIZE = 1024 * 1024 * 1024 # 1 GB max for in-memory video processing
MOONVALLEY_MAREY_MAX_PROMPT_LENGTH = 5000 MOONVALLEY_MAREY_MAX_PROMPT_LENGTH = 5000
R = TypeVar("R") R = TypeVar("R")
class MoonvalleyApiError(Exception): class MoonvalleyApiError(Exception):
"""Base exception for Moonvalley API errors.""" """Base exception for Moonvalley API errors."""
pass pass
def is_valid_task_creation_response(response: MoonvalleyPromptResponse) -> bool: def is_valid_task_creation_response(response: MoonvalleyPromptResponse) -> bool:
"""Verifies that the initial response contains a task ID.""" """Verifies that the initial response contains a task ID."""
return bool(response.id) return bool(response.id)
def validate_task_creation_response(response) -> None: def validate_task_creation_response(response) -> None:
if not is_valid_task_creation_response(response): if not is_valid_task_creation_response(response):
error_msg = f"Moonvalley Marey API: Initial request failed. Code: {response.code}, Message: {response.message}, Data: {response}" error_msg = f"Moonvalley Marey API: Initial request failed. Code: {response.code}, Message: {response.message}, Data: {response}"
logging.error(error_msg) logging.error(error_msg)
raise MoonvalleyApiError(error_msg) raise MoonvalleyApiError(error_msg)
def get_video_from_response(response): def get_video_from_response(response):
video = response.output_url video = response.output_url
logging.info( logging.info(
@ -102,16 +112,17 @@ def poll_until_finished(
poll_interval=16.0, poll_interval=16.0,
failed_statuses=["error"], failed_statuses=["error"],
status_extractor=lambda response: ( status_extractor=lambda response: (
response.status response.status if response and response.status else None
if response and response.status
else None
), ),
auth_kwargs=auth_kwargs, auth_kwargs=auth_kwargs,
result_url_extractor=result_url_extractor, result_url_extractor=result_url_extractor,
node_id=node_id, node_id=node_id,
).execute() ).execute()
def validate_prompts(prompt:str, negative_prompt: str, max_length=MOONVALLEY_MAREY_MAX_PROMPT_LENGTH):
def validate_prompts(
prompt: str, negative_prompt: str, max_length=MOONVALLEY_MAREY_MAX_PROMPT_LENGTH
):
"""Verifies that the prompt isn't empty and that neither prompt is too long.""" """Verifies that the prompt isn't empty and that neither prompt is too long."""
if not prompt: if not prompt:
raise ValueError("Positive prompt is empty") raise ValueError("Positive prompt is empty")
@ -123,6 +134,7 @@ def validate_prompts(prompt:str, negative_prompt: str, max_length=MOONVALLEY_MAR
) )
return True return True
def validate_input_media(width, height, with_frame_conditioning, num_frames_in=None): def validate_input_media(width, height, with_frame_conditioning, num_frames_in=None):
# inference validation # inference validation
# T = num_frames # T = num_frames
@ -130,9 +142,7 @@ def validate_input_media(width, height, with_frame_conditioning, num_frames_in=N
# with image conditioning: H*W must be divisible by 8192 # with image conditioning: H*W must be divisible by 8192
# without image conditioning: T divisible by 32 # without image conditioning: T divisible by 32
if num_frames_in and not num_frames_in % 16 == 0: if num_frames_in and not num_frames_in % 16 == 0:
return False, ( return False, ("The input video total frame count must be divisible by 16!")
"The input video total frame count must be divisible by 16!"
)
if height % 8 != 0 or width % 8 != 0: if height % 8 != 0 or width % 8 != 0:
return False, ( return False, (
@ -147,12 +157,12 @@ def validate_input_media(width, height, with_frame_conditioning, num_frames_in=N
) )
else: else:
if num_frames_in and not num_frames_in % 32 == 0: if num_frames_in and not num_frames_in % 32 == 0:
return False, ( return False, ("The input video total frame count must be divisible by 32!")
"The input video total frame count must be divisible by 32!"
)
def validate_input_image(image: torch.Tensor, with_frame_conditioning: bool=False) -> None: def validate_input_image(
image: torch.Tensor, with_frame_conditioning: bool = False
) -> None:
""" """
Validates the input image adheres to the expectations of the API: Validates the input image adheres to the expectations of the API:
- The image resolution should not be less than 300*300px - The image resolution should not be less than 300*300px
@ -161,9 +171,14 @@ def validate_input_image(image: torch.Tensor, with_frame_conditioning: bool=Fals
""" """
height, width = get_image_dimensions(image) height, width = get_image_dimensions(image)
validate_input_media(width, height, with_frame_conditioning) validate_input_media(width, height, with_frame_conditioning)
validate_image_dimensions(image, min_width=300, min_height=300, max_height=MAX_HEIGHT, max_width=MAX_WIDTH) validate_image_dimensions(
image, min_width=300, min_height=300, max_height=MAX_HEIGHT, max_width=MAX_WIDTH
)
def validate_input_video(video: VideoInput, num_frames_out: int, with_frame_conditioning: bool=False):
def validate_input_video(
video: VideoInput, num_frames_out: int, with_frame_conditioning: bool = False
):
try: try:
width, height = video.get_dimensions() width, height = video.get_dimensions()
except Exception as e: except Exception as e:
@ -171,7 +186,13 @@ def validate_input_video(video: VideoInput, num_frames_out: int, with_frame_cond
raise ValueError(f"Cannot get video dimensions: {e}") from e raise ValueError(f"Cannot get video dimensions: {e}") from e
validate_input_media(width, height, with_frame_conditioning) validate_input_media(width, height, with_frame_conditioning)
validate_video_dimensions(video, min_width=MIN_VID_WIDTH, min_height=MIN_VID_HEIGHT, max_width=MAX_VID_WIDTH, max_height=MAX_VID_HEIGHT) validate_video_dimensions(
video,
min_width=MIN_VID_WIDTH,
min_height=MIN_VID_HEIGHT,
max_width=MAX_VID_WIDTH,
max_height=MAX_VID_HEIGHT,
)
trimmed_video = validate_input_video_length(video, num_frames_out) trimmed_video = validate_input_video_length(video, num_frames_out)
return trimmed_video return trimmed_video
@ -180,22 +201,29 @@ def validate_input_video(video: VideoInput, num_frames_out: int, with_frame_cond
def validate_input_video_length(video: VideoInput, num_frames: int): def validate_input_video_length(video: VideoInput, num_frames: int):
if video.get_duration() > 60: if video.get_duration() > 60:
raise MoonvalleyApiError("Input Video lenth should be less than 1min. Please trim.") raise MoonvalleyApiError(
"Input Video lenth should be less than 1min. Please trim."
)
if num_frames == 128: if num_frames == 128:
if video.get_duration() < 5: if video.get_duration() < 5:
raise MoonvalleyApiError("Input Video length is less than 5s. Please use a video longer than or equal to 5s.") raise MoonvalleyApiError(
"Input Video length is less than 5s. Please use a video longer than or equal to 5s."
)
if video.get_duration() > 5: if video.get_duration() > 5:
# trim video to 5s # trim video to 5s
video = trim_video(video, 5) video = trim_video(video, 5)
if num_frames == 256: if num_frames == 256:
if video.get_duration() < 10: if video.get_duration() < 10:
raise MoonvalleyApiError("Input Video length is less than 10s. Please use a video longer than or equal to 10s.") raise MoonvalleyApiError(
"Input Video length is less than 10s. Please use a video longer than or equal to 10s."
)
if video.get_duration() > 10: if video.get_duration() > 10:
# trim video to 10s # trim video to 10s
video = trim_video(video, 10) video = trim_video(video, 10)
return video return video
def trim_video(video: VideoInput, duration_sec: float) -> VideoInput: def trim_video(video: VideoInput, duration_sec: float) -> VideoInput:
""" """
Returns a new VideoInput object trimmed from the beginning to the specified duration, Returns a new VideoInput object trimmed from the beginning to the specified duration,
@ -219,8 +247,8 @@ def trim_video(video: VideoInput, duration_sec: float) -> VideoInput:
input_source = video.get_stream_source() input_source = video.get_stream_source()
# Open containers # Open containers
input_container = av.open(input_source, mode='r') input_container = av.open(input_source, mode="r")
output_container = av.open(output_buffer, mode='w', format='mp4') output_container = av.open(output_buffer, mode="w", format="mp4")
# Set up output streams for re-encoding # Set up output streams for re-encoding
video_stream = None video_stream = None
@ -230,22 +258,32 @@ def trim_video(video: VideoInput, duration_sec: float) -> VideoInput:
logging.info(f"Found stream: type={stream.type}, class={type(stream)}") logging.info(f"Found stream: type={stream.type}, class={type(stream)}")
if isinstance(stream, av.VideoStream): if isinstance(stream, av.VideoStream):
# Create output video stream with same parameters # Create output video stream with same parameters
video_stream = output_container.add_stream('h264', rate=stream.average_rate) video_stream = output_container.add_stream(
"h264", rate=stream.average_rate
)
video_stream.width = stream.width video_stream.width = stream.width
video_stream.height = stream.height video_stream.height = stream.height
video_stream.pix_fmt = 'yuv420p' video_stream.pix_fmt = "yuv420p"
logging.info(f"Added video stream: {stream.width}x{stream.height} @ {stream.average_rate}fps") logging.info(
f"Added video stream: {stream.width}x{stream.height} @ {stream.average_rate}fps"
)
elif isinstance(stream, av.AudioStream): elif isinstance(stream, av.AudioStream):
# Create output audio stream with same parameters # Create output audio stream with same parameters
audio_stream = output_container.add_stream('aac', rate=stream.sample_rate) audio_stream = output_container.add_stream(
"aac", rate=stream.sample_rate
)
audio_stream.sample_rate = stream.sample_rate audio_stream.sample_rate = stream.sample_rate
audio_stream.layout = stream.layout audio_stream.layout = stream.layout
logging.info(f"Added audio stream: {stream.sample_rate}Hz, {stream.channels} channels") logging.info(
f"Added audio stream: {stream.sample_rate}Hz, {stream.channels} channels"
)
# Calculate target frame count that's divisible by 32 # Calculate target frame count that's divisible by 32
fps = input_container.streams.video[0].average_rate fps = input_container.streams.video[0].average_rate
estimated_frames = int(duration_sec * fps) estimated_frames = int(duration_sec * fps)
target_frames = (estimated_frames // 32) * 32 # Round down to nearest multiple of 32 target_frames = (
estimated_frames // 32
) * 32 # Round down to nearest multiple of 32
if target_frames == 0: if target_frames == 0:
raise ValueError("Video too short: need at least 32 frames for Moonvalley") raise ValueError("Video too short: need at least 32 frames for Moonvalley")
@ -268,7 +306,9 @@ def trim_video(video: VideoInput, duration_sec: float) -> VideoInput:
for packet in video_stream.encode(): for packet in video_stream.encode():
output_container.mux(packet) output_container.mux(packet)
logging.info(f"Encoded {frame_count} video frames (target: {target_frames})") logging.info(
f"Encoded {frame_count} video frames (target: {target_frames})"
)
# Decode and re-encode audio frames # Decode and re-encode audio frames
if audio_stream: if audio_stream:
@ -292,7 +332,6 @@ def trim_video(video: VideoInput, duration_sec: float) -> VideoInput:
output_container.close() output_container.close()
input_container.close() input_container.close()
# Return as VideoFromFile using the buffer # Return as VideoFromFile using the buffer
output_buffer.seek(0) output_buffer.seek(0)
return VideoFromFile(output_buffer) return VideoFromFile(output_buffer)
@ -305,6 +344,7 @@ def trim_video(video: VideoInput, duration_sec: float) -> VideoInput:
output_container.close() output_container.close()
raise RuntimeError(f"Failed to trim video: {str(e)}") from e raise RuntimeError(f"Failed to trim video: {str(e)}") from e
# --- BaseMoonvalleyVideoNode --- # --- BaseMoonvalleyVideoNode ---
class BaseMoonvalleyVideoNode: class BaseMoonvalleyVideoNode:
def parseWidthHeightFromRes(self, resolution: str): def parseWidthHeightFromRes(self, resolution: str):
@ -328,7 +368,7 @@ class BaseMoonvalleyVideoNode:
"Motion Transfer": "motion_control", "Motion Transfer": "motion_control",
"Canny": "canny_control", "Canny": "canny_control",
"Pose Transfer": "pose_control", "Pose Transfer": "pose_control",
"Depth": "depth_control" "Depth": "depth_control",
} }
if value in control_map: if value in control_map:
return control_map[value] return control_map[value]
@ -355,31 +395,63 @@ class BaseMoonvalleyVideoNode:
return { return {
"required": { "required": {
"prompt": model_field_to_node_input( "prompt": model_field_to_node_input(
IO.STRING, MoonvalleyTextToVideoRequest, "prompt_text", IO.STRING,
multiline=True MoonvalleyTextToVideoRequest,
"prompt_text",
multiline=True,
), ),
"negative_prompt": model_field_to_node_input( "negative_prompt": model_field_to_node_input(
IO.STRING, IO.STRING,
MoonvalleyTextToVideoInferenceParams, MoonvalleyTextToVideoInferenceParams,
"negative_prompt", "negative_prompt",
multiline=True, multiline=True,
default="gopro, bright, contrast, static, overexposed, bright, vignette, artifacts, still, noise, texture, scanlines, videogame, 360 camera, VR, transition, flare, saturation, distorted, warped, wide angle, contrast, saturated, vibrant, glowing, cross dissolve, texture, videogame, saturation, cheesy, ugly hands, mutated hands, mutant, disfigured, extra fingers, blown out, horrible, blurry, worst quality, bad, transition, dissolve, cross-dissolve, melt, fade in, fade out, wobbly, weird, low quality, plastic, stock footage, video camera, boring, static", default="low-poly, flat shader, bad rigging, stiff animation, uncanny eyes, low-quality textures, looping glitch, cheap effect, overbloom, bloom spam, default lighting, game asset, stiff face, ugly specular, AI artifacts",
), ),
"resolution": (
"resolution": (IO.COMBO, { IO.COMBO,
"options": ["16:9 (1920 x 1080)", {
"options": [
"16:9 (1920 x 1080)",
"9:16 (1080 x 1920)", "9:16 (1080 x 1920)",
"1:1 (1152 x 1152)", "1:1 (1152 x 1152)",
"4:3 (1440 x 1080)", "4:3 (1440 x 1080)",
"3:4 (1080 x 1440)", "3:4 (1080 x 1440)",
"21:9 (2560 x 1080)"], "21:9 (2560 x 1080)",
],
"default": "16:9 (1920 x 1080)", "default": "16:9 (1920 x 1080)",
"tooltip": "Resolution of the output video", "tooltip": "Resolution of the output video",
}), },
),
# "length": (IO.COMBO,{"options":['5s','10s'], "default": '5s'}), # "length": (IO.COMBO,{"options":['5s','10s'], "default": '5s'}),
"prompt_adherence": model_field_to_node_input(IO.FLOAT,MoonvalleyTextToVideoInferenceParams,"guidance_scale",default=7.0, step=1, min=1, max=20), "prompt_adherence": model_field_to_node_input(
"seed": model_field_to_node_input(IO.INT,MoonvalleyTextToVideoInferenceParams, "seed", default=random.randint(0, 2**32 - 1), min=0, max=4294967295, step=1, display="number", tooltip="Random seed value", control_after_generate=True), IO.FLOAT,
"steps": model_field_to_node_input(IO.INT, MoonvalleyTextToVideoInferenceParams, "steps", default=100, min=1, max=100), MoonvalleyTextToVideoInferenceParams,
"guidance_scale",
default=7.0,
step=1,
min=1,
max=20,
),
"seed": model_field_to_node_input(
IO.INT,
MoonvalleyTextToVideoInferenceParams,
"seed",
default=random.randint(0, 2**32 - 1),
min=0,
max=4294967295,
step=1,
display="number",
tooltip="Random seed value",
control_after_generate=True,
),
"steps": model_field_to_node_input(
IO.INT,
MoonvalleyTextToVideoInferenceParams,
"steps",
default=100,
min=1,
max=100,
),
}, },
"hidden": { "hidden": {
"auth_token": "AUTH_TOKEN_COMFY_ORG", "auth_token": "AUTH_TOKEN_COMFY_ORG",
@ -393,7 +465,7 @@ class BaseMoonvalleyVideoNode:
"image_url", "image_url",
tooltip="The reference image used to generate the video", tooltip="The reference image used to generate the video",
), ),
} },
} }
RETURN_TYPES = ("STRING",) RETURN_TYPES = ("STRING",)
@ -404,6 +476,7 @@ class BaseMoonvalleyVideoNode:
def generate(self, **kwargs): def generate(self, **kwargs):
return None return None
# --- MoonvalleyImg2VideoNode --- # --- MoonvalleyImg2VideoNode ---
class MoonvalleyImg2VideoNode(BaseMoonvalleyVideoNode): class MoonvalleyImg2VideoNode(BaseMoonvalleyVideoNode):
@ -415,9 +488,11 @@ class MoonvalleyImg2VideoNode(BaseMoonvalleyVideoNode):
RETURN_NAMES = ("video",) RETURN_NAMES = ("video",)
DESCRIPTION = "Moonvalley Marey Image to Video Node" DESCRIPTION = "Moonvalley Marey Image to Video Node"
def generate(self, prompt, negative_prompt, unique_id: Optional[str] = None, **kwargs): def generate(
self, prompt, negative_prompt, unique_id: Optional[str] = None, **kwargs
):
image = kwargs.get("image", None) image = kwargs.get("image", None)
if (image is None): if image is None:
raise MoonvalleyApiError("image is required") raise MoonvalleyApiError("image is required")
total_frames = get_total_frames_from_length() total_frames = get_total_frames_from_length()
@ -433,24 +508,25 @@ class MoonvalleyImg2VideoNode(BaseMoonvalleyVideoNode):
num_frames=total_frames, num_frames=total_frames,
width=width_height.get("width"), width=width_height.get("width"),
height=width_height.get("height"), height=width_height.get("height"),
use_negative_prompts=True use_negative_prompts=True,
) )
"""Upload image to comfy backend to have a URL available for further processing""" """Upload image to comfy backend to have a URL available for further processing"""
# Get MIME type from tensor - assuming PNG format for image tensors # Get MIME type from tensor - assuming PNG format for image tensors
mime_type = "image/png" mime_type = "image/png"
image_url = upload_images_to_comfyapi(image, max_images=1, auth_kwargs=kwargs, mime_type=mime_type)[0] image_url = upload_images_to_comfyapi(
image, max_images=1, auth_kwargs=kwargs, mime_type=mime_type
)[0]
request = MoonvalleyTextToVideoRequest( request = MoonvalleyTextToVideoRequest(
image_url=image_url, image_url=image_url, prompt_text=prompt, inference_params=inference_params
prompt_text=prompt,
inference_params=inference_params
) )
initial_operation = SynchronousOperation( initial_operation = SynchronousOperation(
endpoint=ApiEndpoint(path=API_IMG2VIDEO_ENDPOINT, endpoint=ApiEndpoint(
path=API_IMG2VIDEO_ENDPOINT,
method=HttpMethod.POST, method=HttpMethod.POST,
request_model=MoonvalleyTextToVideoRequest, request_model=MoonvalleyTextToVideoRequest,
response_model=MoonvalleyPromptResponse response_model=MoonvalleyPromptResponse,
), ),
request=request, request=request,
auth_kwargs=kwargs, auth_kwargs=kwargs,
@ -465,6 +541,7 @@ class MoonvalleyImg2VideoNode(BaseMoonvalleyVideoNode):
video = download_url_to_video_output(final_response.output_url) video = download_url_to_video_output(final_response.output_url)
return (video,) return (video,)
# --- MoonvalleyVid2VidNode --- # --- MoonvalleyVid2VidNode ---
class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode): class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode):
def __init__(self): def __init__(self):
@ -479,7 +556,14 @@ class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode):
if param in input_types["optional"]: if param in input_types["optional"]:
del input_types["optional"][param] del input_types["optional"][param]
input_types["optional"] = { input_types["optional"] = {
"video": (IO.VIDEO, {"default": "", "multiline": False, "tooltip": "The reference video used to generate the output video. Input a 5s video for 128 frames and a 10s video for 256 frames. Longer videos will be trimmed automatically."}), "video": (
IO.VIDEO,
{
"default": "",
"multiline": False,
"tooltip": "The reference video used to generate the output video. Input a 5s video for 128 frames and a 10s video for 256 frames. Longer videos will be trimmed automatically.",
},
),
"control_type": ( "control_type": (
["Motion Transfer", "Pose Transfer"], ["Motion Transfer", "Pose Transfer"],
{"default": "Motion Transfer"}, {"default": "Motion Transfer"},
@ -493,7 +577,7 @@ class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode):
"max": 100, "max": 100,
"tooltip": "Only used if control_type is 'Motion Transfer'", "tooltip": "Only used if control_type is 'Motion Transfer'",
}, },
) ),
} }
return input_types return input_types
@ -501,14 +585,15 @@ class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode):
RETURN_TYPES = ("VIDEO",) RETURN_TYPES = ("VIDEO",)
RETURN_NAMES = ("video",) RETURN_NAMES = ("video",)
def generate(self, prompt, negative_prompt, unique_id: Optional[str] = None, **kwargs): def generate(
self, prompt, negative_prompt, unique_id: Optional[str] = None, **kwargs
):
video = kwargs.get("video") video = kwargs.get("video")
num_frames = get_total_frames_from_length() num_frames = get_total_frames_from_length()
if not video: if not video:
raise MoonvalleyApiError("video is required") raise MoonvalleyApiError("video is required")
"""Validate video input""" """Validate video input"""
video_url = "" video_url = ""
if video: if video:
@ -525,7 +610,7 @@ class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode):
steps=kwargs.get("steps"), steps=kwargs.get("steps"),
seed=kwargs.get("seed"), seed=kwargs.get("seed"),
guidance_scale=kwargs.get("prompt_adherence"), guidance_scale=kwargs.get("prompt_adherence"),
control_params={'motion_intensity': motion_intensity} control_params={"motion_intensity": motion_intensity},
) )
control = self.parseControlParameter(control_type) control = self.parseControlParameter(control_type)
@ -534,14 +619,15 @@ class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode):
control_type=control, control_type=control,
video_url=video_url, video_url=video_url,
prompt_text=prompt, prompt_text=prompt,
inference_params=inference_params inference_params=inference_params,
) )
initial_operation = SynchronousOperation( initial_operation = SynchronousOperation(
endpoint=ApiEndpoint(path=API_VIDEO2VIDEO_ENDPOINT, endpoint=ApiEndpoint(
path=API_VIDEO2VIDEO_ENDPOINT,
method=HttpMethod.POST, method=HttpMethod.POST,
request_model=MoonvalleyVideoToVideoRequest, request_model=MoonvalleyVideoToVideoRequest,
response_model=MoonvalleyPromptResponse response_model=MoonvalleyPromptResponse,
), ),
request=request, request=request,
auth_kwargs=kwargs, auth_kwargs=kwargs,
@ -558,6 +644,7 @@ class MoonvalleyVideo2VideoNode(BaseMoonvalleyVideoNode):
return (video,) return (video,)
# --- MoonvalleyTxt2VideoNode --- # --- MoonvalleyTxt2VideoNode ---
class MoonvalleyTxt2VideoNode(BaseMoonvalleyVideoNode): class MoonvalleyTxt2VideoNode(BaseMoonvalleyVideoNode):
def __init__(self): def __init__(self):
@ -575,7 +662,9 @@ class MoonvalleyTxt2VideoNode(BaseMoonvalleyVideoNode):
del input_types["optional"][param] del input_types["optional"][param]
return input_types return input_types
def generate(self, prompt, negative_prompt, unique_id: Optional[str] = None, **kwargs): def generate(
self, prompt, negative_prompt, unique_id: Optional[str] = None, **kwargs
):
validate_prompts(prompt, negative_prompt, MOONVALLEY_MAREY_MAX_PROMPT_LENGTH) validate_prompts(prompt, negative_prompt, MOONVALLEY_MAREY_MAX_PROMPT_LENGTH)
width_height = self.parseWidthHeightFromRes(kwargs.get("resolution")) width_height = self.parseWidthHeightFromRes(kwargs.get("resolution"))
num_frames = get_total_frames_from_length() num_frames = get_total_frames_from_length()
@ -590,15 +679,15 @@ class MoonvalleyTxt2VideoNode(BaseMoonvalleyVideoNode):
height=width_height.get("height"), height=width_height.get("height"),
) )
request = MoonvalleyTextToVideoRequest( request = MoonvalleyTextToVideoRequest(
prompt_text=prompt, prompt_text=prompt, inference_params=inference_params
inference_params=inference_params
) )
initial_operation = SynchronousOperation( initial_operation = SynchronousOperation(
endpoint=ApiEndpoint(path=API_TXT2VIDEO_ENDPOINT, endpoint=ApiEndpoint(
path=API_TXT2VIDEO_ENDPOINT,
method=HttpMethod.POST, method=HttpMethod.POST,
request_model=MoonvalleyTextToVideoRequest, request_model=MoonvalleyTextToVideoRequest,
response_model=MoonvalleyPromptResponse response_model=MoonvalleyPromptResponse,
), ),
request=request, request=request,
auth_kwargs=kwargs, auth_kwargs=kwargs,
@ -615,7 +704,6 @@ class MoonvalleyTxt2VideoNode(BaseMoonvalleyVideoNode):
return (video,) return (video,)
NODE_CLASS_MAPPINGS = { NODE_CLASS_MAPPINGS = {
"MoonvalleyImg2VideoNode": MoonvalleyImg2VideoNode, "MoonvalleyImg2VideoNode": MoonvalleyImg2VideoNode,
"MoonvalleyTxt2VideoNode": MoonvalleyTxt2VideoNode, "MoonvalleyTxt2VideoNode": MoonvalleyTxt2VideoNode,
@ -629,6 +717,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
# "MoonvalleyVideo2VideoNode": "Moonvalley Marey Video to Video", # "MoonvalleyVideo2VideoNode": "Moonvalley Marey Video to Video",
} }
def get_total_frames_from_length(length="5s"): def get_total_frames_from_length(length="5s"):
# if length == '5s': # if length == '5s':
# return 128 # return 128