Init support tile control lora and regional prompt
This commit is contained in:
@@ -5,6 +5,7 @@ import comfy.sample
|
||||
import nodes
|
||||
import node_helpers
|
||||
import latent_preview
|
||||
from comfy.comfy_types import IO
|
||||
|
||||
def emptyimage(width, height, batch_size=1, color=(0,0,0)):
|
||||
r = torch.full([batch_size, height, width, 1], color[0] / 255, dtype=torch.float32, device="cpu")
|
||||
@@ -17,7 +18,7 @@ def imagecrop(image, width, height, x, y):
|
||||
y = min(y, image.shape[1] - 1)
|
||||
to_x = width + x
|
||||
to_y = height + y
|
||||
img = image[:,y:to_y, x:to_x, :]
|
||||
img = image[:,y:to_y, x:to_x, :].clone()
|
||||
return img
|
||||
|
||||
def feather(mask, left=0, top=0, right=0, bottom=0):
|
||||
@@ -132,13 +133,25 @@ def temporalistgen(num_total_frame, length, num_crossfade, num_loopback_crossfad
|
||||
raise ValueError("temporalistgen: loopback_crossfade数值过大,尝试减小\nloopback_crossfade too large")
|
||||
return slice_list
|
||||
|
||||
def corssfadevideos(video1, video2, num_corssfade_frame):
|
||||
def crop_resize_img_list(croparea_list, image):
|
||||
image_list = []
|
||||
init_width = croparea_list[0]['width_crop']
|
||||
init_height = croparea_list[0]['height_crop']
|
||||
for item in croparea_list:
|
||||
cropped_image = imagecrop(image, item['width_crop'], item['height_crop'], item['offset_x'], item['offset_y'])
|
||||
if cropped_image.shape[1] != init_height or cropped_image.shape[2] != init_width:
|
||||
cropped_image = comfy.utils.common_upscale(cropped_image.movedim(-1, 1), init_width, init_height, "bilinear", "center").movedim(1, -1)
|
||||
image_list.append(cropped_image)
|
||||
result = torch.cat(image_list, dim=0)
|
||||
return result
|
||||
|
||||
def crossfadevideos(video1, video2, num_corssfade_frame):
|
||||
if video1.ndim != video2.ndim:
|
||||
raise ValueError("corssfadevideos: 拼接图片类型不一致\nImageType Mismatch")
|
||||
raise ValueError("crossfadevideos: 拼接图片类型不一致\nImageType Mismatch")
|
||||
if video1[[0],].shape != video2[[0],].shape:
|
||||
raise ValueError("corssfadevideos: 拼接图片尺寸不一致\nImageSize Mismatch")
|
||||
raise ValueError("crossfadevideos: 拼接图片尺寸不一致\nImageSize Mismatch")
|
||||
if num_corssfade_frame > video1.shape[0] or num_corssfade_frame > video2.shape[0]:
|
||||
raise ValueError("corssfadevideos: 拼接图片数目应大于过渡数目\nVideoLength should be longer than CrossLength")
|
||||
raise ValueError("crossfadevideos: 拼接图片数目应大于过渡数目\nVideoLength should be longer than CrossLength")
|
||||
video_slice1 = video1[:-num_corssfade_frame]
|
||||
video_slice2 = video1[-num_corssfade_frame:]
|
||||
video_slice3 = video2[:num_corssfade_frame]
|
||||
@@ -160,7 +173,7 @@ def corssfadevideos(video1, video2, num_corssfade_frame):
|
||||
return result
|
||||
|
||||
def vace_sample(model, positive, negative, vae, width, height, length, strength, seed, cfg, sampler_name, scheduler, steps, denoise, video,
|
||||
control_video=None, control_masks=None, reference_image=None):
|
||||
control_video=None, control_masks=None, reference_image=None, tile_control_video=None):
|
||||
# from comfyui
|
||||
latent_length = ((length - 1) // 4) + 1
|
||||
if control_video is not None:
|
||||
@@ -213,16 +226,23 @@ def vace_sample(model, positive, negative, vae, width, height, length, strength,
|
||||
|
||||
mask = mask.unsqueeze(0)
|
||||
|
||||
positive = node_helpers.conditioning_set_values(positive, {"vace_frames": [control_video_latent], "vace_mask": [mask], "vace_strength": [strength]}, append=True)
|
||||
negative = node_helpers.conditioning_set_values(negative, {"vace_frames": [control_video_latent], "vace_mask": [mask], "vace_strength": [strength]}, append=True)
|
||||
|
||||
# sample
|
||||
|
||||
latent = vae.encode(video[:,:,:,:3])
|
||||
if reference_image is not None:
|
||||
latent = torch.cat((reference_image_vaed, latent), dim=2)
|
||||
noise = comfy.sample.prepare_noise(latent, seed)
|
||||
# add "concat_latent_image" to support tile control lora
|
||||
if tile_control_video is not None:
|
||||
tile_control_latent = vae.encode(tile_control_video[:,:,:,:3])
|
||||
if reference_image is not None:
|
||||
tile_control_latent = torch.cat((reference_image_vaed, tile_control_latent), dim=2)
|
||||
positive = node_helpers.conditioning_set_values(positive, {"vace_frames": [control_video_latent], "vace_mask": [mask], "vace_strength": [strength], "concat_latent_image": tile_control_latent}, append=True)
|
||||
negative = node_helpers.conditioning_set_values(negative, {"vace_frames": [control_video_latent], "vace_mask": [mask], "vace_strength": [strength], "concat_latent_image": tile_control_latent}, append=True)
|
||||
else:
|
||||
positive = node_helpers.conditioning_set_values(positive, {"vace_frames": [control_video_latent], "vace_mask": [mask], "vace_strength": [strength]}, append=True)
|
||||
negative = node_helpers.conditioning_set_values(negative, {"vace_frames": [control_video_latent], "vace_mask": [mask], "vace_strength": [strength]}, append=True)
|
||||
|
||||
noise = comfy.sample.prepare_noise(latent, seed)
|
||||
callback = latent_preview.prepare_callback(model, steps)
|
||||
disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED
|
||||
samples = comfy.sample.sample(model, noise, steps, cfg, sampler_name, scheduler, positive, negative, latent,
|
||||
@@ -266,6 +286,7 @@ class UltimateVideoUpscaler:
|
||||
"croparea_list": ("LIST", ),
|
||||
"reference_image": ("IMAGE", ),
|
||||
"control_video": ("IMAGE", ),
|
||||
"tile_control_video": ("IMAGE", ),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,18 +307,23 @@ QQ群:948626609
|
||||
|
||||
def upscale_video(self, model, width_upscale, height_upscale, width, height, length, pad_mask_limit, crossfade_frame, loopback_crossfade,
|
||||
crop_ref, ref_as_init_frame, noise_aug, input_video, seed, steps, cfg, sampler_name, scheduler, positive, negative, denoise, vae,
|
||||
croparea_list=None, reference_image=None, control_video=None):
|
||||
croparea_list=None, reference_image=None, control_video=None, tile_control_video=None):
|
||||
if control_video is not None and control_video.shape[0] != input_video.shape[0]:
|
||||
raise ValueError("控制视频帧数与输入视频帧数应当一致\nFrame count of ControlVideo and InputVideo should be the same")
|
||||
if tile_control_video is not None and tile_control_video.shape[0] != input_video.shape[0]:
|
||||
raise ValueError("tile控制视频帧数与输入视频帧数应当一致\nFrame count of TileControlVideo and InputVideo should be the same")
|
||||
if loopback_crossfade > 0:
|
||||
cross_slice = input_video[:loopback_crossfade].clone()
|
||||
input_video = torch.cat((input_video, cross_slice), dim=0)
|
||||
if control_video is not None:
|
||||
control_cross_slice = control_video[:loopback_crossfade].clone()
|
||||
control_video = torch.cat((control_video, control_cross_slice), dim=0)
|
||||
if tile_control_video is not None:
|
||||
tile_control_cross_slice = tile_control_video[:loopback_crossfade].clone()
|
||||
tile_control_video = torch.cat((tile_control_video, tile_control_cross_slice), dim=0)
|
||||
total_frame = input_video.shape[0]
|
||||
if total_frame > length and crossfade_frame == 0:
|
||||
raise ValueError("视频帧数大于length,需要设置crossfade_frame以启用时间分割\nFrame count of input video is larger than length, need set a proper value for crossfade_frame to enable temporal tiling")
|
||||
raise ValueError("视频帧数大于length,需要设置crossfade_frame以启用时间分割\nFrame count of input video is larger than length, need set a proper value for crossfade_frame to enable temporal tiling")
|
||||
strength = 1 # VACE Strength
|
||||
temporalist = temporalistgen(total_frame, length, crossfade_frame, loopback_crossfade)
|
||||
upscaled_videos_list = []
|
||||
@@ -309,8 +335,11 @@ QQ群:948626609
|
||||
upscaled_video = comfy.utils.common_upscale(input_video[start_index:(start_index + length_n)].movedim(-1, 1), width_upscale, height_upscale, "bilinear", "center").movedim(1, -1)
|
||||
if control_video is not None:
|
||||
up_scaled_control = comfy.utils.common_upscale(control_video[start_index:(start_index + length_n)].movedim(-1, 1), width_upscale, height_upscale, "bilinear", "center").movedim(1, -1)
|
||||
if tile_control_video is not None:
|
||||
up_scaled_tile_control = comfy.utils.common_upscale(tile_control_video[start_index:(start_index + length_n)].movedim(-1, 1), width_upscale, height_upscale, "bilinear", "center").movedim(1, -1)
|
||||
upscaled_video = add_noise(upscaled_video, noise_aug, seed)
|
||||
if turn_index > 0 and cross_fade > 0:
|
||||
# replace crossfade frames
|
||||
upscaled_video[:cross_fade] = upscaled_videos_list[turn_index-1][-cross_fade:].clone()
|
||||
if croparea_list is None:
|
||||
croparea_list = spatialistgen(width_upscale, height_upscale, width, height)
|
||||
@@ -336,6 +365,10 @@ QQ群:948626609
|
||||
controls = imgcomposite(crop_ctl, crop_gen, 0, 0, 1-mask_ctl) if index != 0 else crop_ctl
|
||||
else:
|
||||
controls = crop_gen if index != 0 else torch.full((length_n, height_crop_n, width_crop_n, 3), 0.5, device='cpu')
|
||||
if tile_control_video is not None:
|
||||
crop_tile_ctl = imagecrop(up_scaled_tile_control, width_crop_n, height_crop_n, offset_x_n, offset_y_n)
|
||||
else:
|
||||
crop_tile_ctl = None
|
||||
if reference_image is not None:
|
||||
reference_image = comfy.utils.common_upscale(reference_image.movedim(-1, 1), width_upscale, height_upscale, "bilinear", "center").movedim(1, -1)
|
||||
if crop_ref is True:
|
||||
@@ -352,12 +385,17 @@ QQ群:948626609
|
||||
init_ctl = imagecrop(upscaled_videos_list[turn_index-1][-cross_fade:].clone(), width_crop_n, height_crop_n, offset_x_n, offset_y_n)
|
||||
controls[:cross_fade] = init_ctl
|
||||
mask_ctl[:cross_fade] = torch.full((1, height_crop_n, width_crop_n), 0.0, device='cpu')
|
||||
# if crop_tile_ctl is not None:
|
||||
# init_tile_ctl = imagecrop(upscaled_videos_list[turn_index-1][-cross_fade:].clone(), width_crop_n, height_crop_n, offset_x_n, offset_y_n)
|
||||
# controls[:cross_fade] = init_ctl
|
||||
if turn['flag_final_slice'] is True and loopback_crossfade > 0:
|
||||
end_ctl = imagecrop(upscaled_videos_list[0][:loopback_crossfade].clone(), width_crop_n, height_crop_n, offset_x_n, offset_y_n)
|
||||
controls[-loopback_crossfade:] = end_ctl
|
||||
mask_ctl[-loopback_crossfade:] = torch.full((1, height_crop_n, width_crop_n), 0.0, device='cpu')
|
||||
if 'cond_p' in item:
|
||||
positive = item['cond_p']
|
||||
sampled_video = vace_sample(model, positive, negative, vae, width_crop_n, height_crop_n, length_n, strength, seed, cfg, sampler_name, scheduler, steps, denoise, video,
|
||||
controls, mask_ctl, refimg)
|
||||
controls, mask_ctl, refimg, crop_tile_ctl)
|
||||
mask_feather = feather(torch.full((1, height_crop_n, width_crop_n), 1.0, device='cpu'), item['feather_left'], item['feather_top'], item['feather_right'], item['feather_bottom'])
|
||||
mask_feather = repeat_tensor(mask_feather, length_n)
|
||||
result_video = imgcomposite(result_video, sampled_video, offset_x_n, offset_y_n, mask_feather)
|
||||
@@ -370,10 +408,10 @@ QQ群:948626609
|
||||
index = 0
|
||||
while index < len(upscaled_videos_list):
|
||||
cross_fade = temporalist[index + 1]['num_crossfade']
|
||||
result_video = corssfadevideos(result_video, upscaled_videos_list[index], cross_fade)
|
||||
result_video = crossfadevideos(result_video, upscaled_videos_list[index], cross_fade)
|
||||
index += 1
|
||||
if loopback_crossfade > 0:
|
||||
crossed_start = corssfadevideos(result_video[-loopback_crossfade:], result_video[:loopback_crossfade], loopback_crossfade)
|
||||
crossed_start = crossfadevideos(result_video[-loopback_crossfade:], result_video[:loopback_crossfade], loopback_crossfade)
|
||||
result_video[:loopback_crossfade] = crossed_start
|
||||
result_video = result_video[:(total_frame - loopback_crossfade)]
|
||||
return (result_video, )
|
||||
@@ -385,19 +423,33 @@ class CustomCropArea:
|
||||
"required": {
|
||||
"width_upscale": ("INT", {"default": 1280, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
"height_upscale": ("INT", {"default": 720, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 8}),
|
||||
"presets": (["'H' for wide screen", "'三' for long narrow screen"], {
|
||||
"default": "'H' for wide screen",
|
||||
"width": ("INT", {"default": 832, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
|
||||
"height": ("INT", {"default": 480, "min": 16, "max": nodes.MAX_RESOLUTION, "step": 16}),
|
||||
"presets": (["default", "'H' for wide screen", "'三' for long narrow screen"], {
|
||||
"default": "default",
|
||||
"tooltip": "预设分割方案\nPresets of cropping plan"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"reference_image": ("IMAGE", ),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST",)
|
||||
RETURN_TYPES = ("LIST", "IMAGE")
|
||||
RETURN_NAMES = ("croparea_list", "IMAGE")
|
||||
FUNCTION = "custom_croplist_gen"
|
||||
CATEGORY = "SuperUltimateVaceTools"
|
||||
DESCRIPTION = "Use preset of cropping plan"
|
||||
def custom_croplist_gen(self, width_upscale, height_upscale, presets):
|
||||
if presets == "'H' for wide screen":
|
||||
def custom_croplist_gen(self, width_upscale, height_upscale, width, height, presets, reference_image=None):
|
||||
if reference_image is not None:
|
||||
reference_image = comfy.utils.common_upscale(reference_image.movedim(-1, 1), width_upscale, height_upscale, "bilinear", "center").movedim(1, -1)
|
||||
|
||||
if presets == "default":
|
||||
# same as SuperUltimateVACEUpscale
|
||||
result = spatialistgen(width_upscale, height_upscale, width, height)
|
||||
|
||||
elif presets == "'H' for wide screen":
|
||||
# left&right cropped image will be stretched
|
||||
if height_upscale%16 != 0:
|
||||
raise ValueError("‘H’方案下放大高度必须为16的倍数\n'H' plan requires height_upscale to be multiplier of 16")
|
||||
result = [
|
||||
@@ -523,15 +575,48 @@ class CustomCropArea:
|
||||
'feather_bottom': 0,
|
||||
},
|
||||
]
|
||||
return (result,)
|
||||
if reference_image is not None:
|
||||
img_batch = crop_resize_img_list(result, reference_image)
|
||||
else:
|
||||
img_batch = None
|
||||
return (result, img_batch)
|
||||
|
||||
class BatchPrompt:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"clip": (IO.CLIP, {"tooltip": "The CLIP model used for encoding the text."}),
|
||||
"prompt_list": ("STRING", ),
|
||||
"croparea_list": ("LIST", ),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LIST", )
|
||||
RETURN_NAMES = ("croparea_list", )
|
||||
FUNCTION = "func"
|
||||
CATEGORY = "SuperUltimateVaceTools"
|
||||
DESCRIPTION = "batch conditioning prompt list"
|
||||
def func(self, clip, prompt_list, croparea_list):
|
||||
if len(prompt_list) != len(croparea_list):
|
||||
raise ValueError("提示词队列长度与切割队列长度不一致,检查节点连接是否正确\nLength of prompt_list is not same as croparea_list, check nodes connection")
|
||||
index = 0
|
||||
for prompt in prompt_list:
|
||||
tokens = clip.tokenize(prompt)
|
||||
croparea_list[index]['cond_p'] = clip.encode_from_tokens_scheduled(tokens)
|
||||
index += 1
|
||||
|
||||
return (croparea_list, )
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SuperUltimateVACEUpscale": UltimateVideoUpscaler,
|
||||
"CustomCropArea": CustomCropArea
|
||||
"CustomCropArea": CustomCropArea,
|
||||
"BatchPromptCropArea": BatchPrompt
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SuperUltimateVACEUpscale": "SuperUltimate VACE Upscale",
|
||||
"CustomCropArea": "Custom Crop Area"
|
||||
"CustomCropArea": "Custom Crop Area",
|
||||
"BatchPromptCropArea": "Batch Prompt Crop Area"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user