mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-05-25 00:17:23 +08:00
[Partner Nodes] add widget for automatic upscaling for the ByteDance2Reference node (#14032)
Signed-off-by: bigcat88 <bigcat88@icloud.com>
This commit is contained in:
parent
2ca1480f91
commit
b293f8cefd
@ -43,15 +43,16 @@ from comfy_api_nodes.util import (
|
|||||||
ApiEndpoint,
|
ApiEndpoint,
|
||||||
download_url_to_image_tensor,
|
download_url_to_image_tensor,
|
||||||
download_url_to_video_output,
|
download_url_to_video_output,
|
||||||
|
downscale_video_to_max_pixels,
|
||||||
get_number_of_images,
|
get_number_of_images,
|
||||||
image_tensor_pair_to_batch,
|
image_tensor_pair_to_batch,
|
||||||
poll_op,
|
poll_op,
|
||||||
resize_video_to_pixel_budget,
|
|
||||||
sync_op,
|
sync_op,
|
||||||
upload_audio_to_comfyapi,
|
upload_audio_to_comfyapi,
|
||||||
upload_image_to_comfyapi,
|
upload_image_to_comfyapi,
|
||||||
upload_images_to_comfyapi,
|
upload_images_to_comfyapi,
|
||||||
upload_video_to_comfyapi,
|
upload_video_to_comfyapi,
|
||||||
|
upscale_video_to_min_pixels,
|
||||||
validate_image_aspect_ratio,
|
validate_image_aspect_ratio,
|
||||||
validate_image_dimensions,
|
validate_image_dimensions,
|
||||||
validate_string,
|
validate_string,
|
||||||
@ -110,12 +111,13 @@ def _validate_ref_video_pixels(video: Input.Video, model_id: str, resolution: st
|
|||||||
max_px = limits.get("max")
|
max_px = limits.get("max")
|
||||||
if min_px and pixels < min_px:
|
if min_px and pixels < min_px:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Reference video {index} is too small: {w}x{h} = {pixels:,}px. " f"Minimum is {min_px:,}px for this model."
|
f"Reference video {index} is too small: {w}x{h} = {pixels:,} total pixels. "
|
||||||
|
f"Minimum for this model is {min_px:,} total pixels."
|
||||||
)
|
)
|
||||||
if max_px and pixels > max_px:
|
if max_px and pixels > max_px:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Reference video {index} is too large: {w}x{h} = {pixels:,}px. "
|
f"Reference video {index} is too large: {w}x{h} = {pixels:,} total pixels. "
|
||||||
f"Maximum is {max_px:,}px for this model. Try downscaling the video."
|
f"Maximum for this model is {max_px:,} total pixels. Try downscaling the video."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -1676,14 +1678,14 @@ class ByteDance2FirstLastFrameNode(IO.ComfyNode):
|
|||||||
"first_frame_asset_id",
|
"first_frame_asset_id",
|
||||||
default="",
|
default="",
|
||||||
tooltip="Seedance asset_id to use as the first frame. "
|
tooltip="Seedance asset_id to use as the first frame. "
|
||||||
"Mutually exclusive with the first_frame image input.",
|
"Mutually exclusive with the first_frame image input.",
|
||||||
optional=True,
|
optional=True,
|
||||||
),
|
),
|
||||||
IO.String.Input(
|
IO.String.Input(
|
||||||
"last_frame_asset_id",
|
"last_frame_asset_id",
|
||||||
default="",
|
default="",
|
||||||
tooltip="Seedance asset_id to use as the last frame. "
|
tooltip="Seedance asset_id to use as the last frame. "
|
||||||
"Mutually exclusive with the last_frame image input.",
|
"Mutually exclusive with the last_frame image input.",
|
||||||
optional=True,
|
optional=True,
|
||||||
),
|
),
|
||||||
IO.Int.Input(
|
IO.Int.Input(
|
||||||
@ -1865,11 +1867,20 @@ def _seedance2_reference_inputs(resolutions: list[str], default_ratio: str = "16
|
|||||||
IO.Boolean.Input(
|
IO.Boolean.Input(
|
||||||
"auto_downscale",
|
"auto_downscale",
|
||||||
default=False,
|
default=False,
|
||||||
advanced=True,
|
|
||||||
optional=True,
|
optional=True,
|
||||||
tooltip="Automatically downscale reference videos that exceed the model's pixel budget "
|
tooltip="Automatically downscale reference videos that exceed the model's pixel budget "
|
||||||
"for the selected resolution. Aspect ratio is preserved; videos already within limits are untouched.",
|
"for the selected resolution. Aspect ratio is preserved; videos already within limits are untouched.",
|
||||||
),
|
),
|
||||||
|
IO.Boolean.Input(
|
||||||
|
"auto_upscale",
|
||||||
|
default=False,
|
||||||
|
advanced=True,
|
||||||
|
optional=True,
|
||||||
|
tooltip="Automatically upscale reference videos that are below the model's minimum pixel count "
|
||||||
|
"for the selected resolution. Aspect ratio is preserved; videos already meeting the minimum are "
|
||||||
|
"untouched. Note: upscaling a low-resolution source does not add real detail and may produce "
|
||||||
|
"lower-quality generations.",
|
||||||
|
),
|
||||||
IO.Autogrow.Input(
|
IO.Autogrow.Input(
|
||||||
"reference_assets",
|
"reference_assets",
|
||||||
template=IO.Autogrow.TemplateNames(
|
template=IO.Autogrow.TemplateNames(
|
||||||
@ -2030,7 +2041,13 @@ class ByteDance2ReferenceNode(IO.ComfyNode):
|
|||||||
max_px = SEEDANCE2_REF_VIDEO_PIXEL_LIMITS.get(model_id, {}).get(model["resolution"], {}).get("max")
|
max_px = SEEDANCE2_REF_VIDEO_PIXEL_LIMITS.get(model_id, {}).get(model["resolution"], {}).get("max")
|
||||||
if max_px:
|
if max_px:
|
||||||
for key in reference_videos:
|
for key in reference_videos:
|
||||||
reference_videos[key] = resize_video_to_pixel_budget(reference_videos[key], max_px)
|
reference_videos[key] = downscale_video_to_max_pixels(reference_videos[key], max_px)
|
||||||
|
|
||||||
|
if model.get("auto_upscale") and reference_videos:
|
||||||
|
min_px = SEEDANCE2_REF_VIDEO_PIXEL_LIMITS.get(model_id, {}).get(model["resolution"], {}).get("min")
|
||||||
|
if min_px:
|
||||||
|
for key in reference_videos:
|
||||||
|
reference_videos[key] = upscale_video_to_min_pixels(reference_videos[key], min_px)
|
||||||
|
|
||||||
total_video_duration = 0.0
|
total_video_duration = 0.0
|
||||||
for i, key in enumerate(reference_videos, 1):
|
for i, key in enumerate(reference_videos, 1):
|
||||||
|
|||||||
@ -16,16 +16,17 @@ from .conversions import (
|
|||||||
convert_mask_to_image,
|
convert_mask_to_image,
|
||||||
downscale_image_tensor,
|
downscale_image_tensor,
|
||||||
downscale_image_tensor_by_max_side,
|
downscale_image_tensor_by_max_side,
|
||||||
|
downscale_video_to_max_pixels,
|
||||||
image_tensor_pair_to_batch,
|
image_tensor_pair_to_batch,
|
||||||
pil_to_bytesio,
|
pil_to_bytesio,
|
||||||
resize_mask_to_image,
|
resize_mask_to_image,
|
||||||
resize_video_to_pixel_budget,
|
|
||||||
tensor_to_base64_string,
|
tensor_to_base64_string,
|
||||||
tensor_to_bytesio,
|
tensor_to_bytesio,
|
||||||
tensor_to_pil,
|
tensor_to_pil,
|
||||||
text_filepath_to_base64_string,
|
text_filepath_to_base64_string,
|
||||||
text_filepath_to_data_uri,
|
text_filepath_to_data_uri,
|
||||||
trim_video,
|
trim_video,
|
||||||
|
upscale_video_to_min_pixels,
|
||||||
video_to_base64_string,
|
video_to_base64_string,
|
||||||
)
|
)
|
||||||
from .download_helpers import (
|
from .download_helpers import (
|
||||||
@ -88,16 +89,17 @@ __all__ = [
|
|||||||
"convert_mask_to_image",
|
"convert_mask_to_image",
|
||||||
"downscale_image_tensor",
|
"downscale_image_tensor",
|
||||||
"downscale_image_tensor_by_max_side",
|
"downscale_image_tensor_by_max_side",
|
||||||
|
"downscale_video_to_max_pixels",
|
||||||
"image_tensor_pair_to_batch",
|
"image_tensor_pair_to_batch",
|
||||||
"pil_to_bytesio",
|
"pil_to_bytesio",
|
||||||
"resize_mask_to_image",
|
"resize_mask_to_image",
|
||||||
"resize_video_to_pixel_budget",
|
|
||||||
"tensor_to_base64_string",
|
"tensor_to_base64_string",
|
||||||
"tensor_to_bytesio",
|
"tensor_to_bytesio",
|
||||||
"tensor_to_pil",
|
"tensor_to_pil",
|
||||||
"text_filepath_to_base64_string",
|
"text_filepath_to_base64_string",
|
||||||
"text_filepath_to_data_uri",
|
"text_filepath_to_data_uri",
|
||||||
"trim_video",
|
"trim_video",
|
||||||
|
"upscale_video_to_min_pixels",
|
||||||
"video_to_base64_string",
|
"video_to_base64_string",
|
||||||
# Validation utilities
|
# Validation utilities
|
||||||
"get_image_dimensions",
|
"get_image_dimensions",
|
||||||
|
|||||||
@ -415,14 +415,48 @@ def trim_video(video: Input.Video, duration_sec: float) -> Input.Video:
|
|||||||
raise RuntimeError(f"Failed to trim video: {str(e)}") from e
|
raise RuntimeError(f"Failed to trim video: {str(e)}") from e
|
||||||
|
|
||||||
|
|
||||||
def resize_video_to_pixel_budget(video: Input.Video, total_pixels: int) -> Input.Video:
|
def downscale_video_to_max_pixels(video: Input.Video, max_pixels: int) -> Input.Video:
|
||||||
"""Downscale a video to fit within ``total_pixels`` (w * h), preserving aspect ratio.
|
"""Downscale a video to fit within ``max_pixels`` (w * h), preserving aspect ratio.
|
||||||
|
|
||||||
Returns the original video object untouched when it already fits. Preserves frame rate, duration, and audio.
|
Returns the original video object untouched when it already fits. Preserves frame rate, duration, and audio.
|
||||||
Aspect ratio is preserved up to a fraction of a percent (even-dim rounding).
|
Aspect ratio is preserved up to a fraction of a percent (even-dim rounding).
|
||||||
"""
|
"""
|
||||||
src_w, src_h = video.get_dimensions()
|
src_w, src_h = video.get_dimensions()
|
||||||
scale_dims = _compute_downscale_dims(src_w, src_h, total_pixels)
|
scale_dims = _compute_downscale_dims(src_w, src_h, max_pixels)
|
||||||
|
if scale_dims is None:
|
||||||
|
return video
|
||||||
|
return _apply_video_scale(video, scale_dims)
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_upscale_dims(src_w: int, src_h: int, total_pixels: int) -> tuple[int, int] | None:
|
||||||
|
"""Return upscaled (w, h) with even dims meeting at least ``total_pixels``, or None if already large enough.
|
||||||
|
|
||||||
|
Source aspect ratio is preserved; output may drift by a fraction of a percent because both dimensions
|
||||||
|
are rounded up to even values (many codecs require divisible-by-2). The result is guaranteed to be at
|
||||||
|
least ``total_pixels``.
|
||||||
|
"""
|
||||||
|
pixels = src_w * src_h
|
||||||
|
if pixels >= total_pixels:
|
||||||
|
return None
|
||||||
|
scale = math.sqrt(total_pixels / pixels)
|
||||||
|
new_w = math.ceil(src_w * scale)
|
||||||
|
new_h = math.ceil(src_h * scale)
|
||||||
|
if new_w % 2:
|
||||||
|
new_w += 1
|
||||||
|
if new_h % 2:
|
||||||
|
new_h += 1
|
||||||
|
return new_w, new_h
|
||||||
|
|
||||||
|
|
||||||
|
def upscale_video_to_min_pixels(video: Input.Video, min_pixels: int) -> Input.Video:
|
||||||
|
"""Upscale a video to meet at least ``min_pixels`` (w * h), preserving aspect ratio.
|
||||||
|
|
||||||
|
Returns the original video object untouched when it already meets the minimum. Preserves frame rate,
|
||||||
|
duration, and audio. Aspect ratio is preserved up to a fraction of a percent (even-dim rounding).
|
||||||
|
Note: upscaling a low-resolution source does not add real detail; downstream model quality may suffer.
|
||||||
|
"""
|
||||||
|
src_w, src_h = video.get_dimensions()
|
||||||
|
scale_dims = _compute_upscale_dims(src_w, src_h, min_pixels)
|
||||||
if scale_dims is None:
|
if scale_dims is None:
|
||||||
return video
|
return video
|
||||||
return _apply_video_scale(video, scale_dims)
|
return _apply_video_scale(video, scale_dims)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user