diff --git a/__init__.py b/__init__.py index 34d72b1..e16a9c1 100644 --- a/__init__.py +++ b/__init__.py @@ -19,6 +19,7 @@ try: from .videoinput import VideoInputPreprocessor from .videocombine import VideoBackgroundRestorer from .choose_face_only_mode import FaceOnlyModeSwitch + from .cropper import VideoFramePreprocessor # Map the class names to the classes themselves NODE_CLASS_MAPPINGS["FaceProcessorLoader"] = FaceProcessorLoader @@ -26,6 +27,7 @@ try: NODE_CLASS_MAPPINGS["VideoInputPreprocessor"] = VideoInputPreprocessor NODE_CLASS_MAPPINGS["VideoBackgroundRestorer"] = VideoBackgroundRestorer NODE_CLASS_MAPPINGS["FaceOnlyModeSwitch"] = FaceOnlyModeSwitch + NODE_CLASS_MAPPINGS["VideoFramePreprocessor"] = VideoFramePreprocessor NODE_DISPLAY_NAME_MAPPINGS["FaceProcessorLoader"] = "Stand-In Processor Loader" @@ -33,6 +35,7 @@ try: NODE_DISPLAY_NAME_MAPPINGS["VideoInputPreprocessor"] = "Stand-In VideoInputPreprocessor" NODE_DISPLAY_NAME_MAPPINGS["VideoBackgroundRestorer"] = "Stand-In Background Restorer" NODE_DISPLAY_NAME_MAPPINGS["FaceOnlyModeSwitch"] = "Face Only Mode Switch (Stand-In)" + NODE_DISPLAY_NAME_MAPPINGS["VideoFramePreprocessor"] = "Stand-In Trimmer & Cropper" logger.info("Successfully loaded all Stand-In nodes.") diff --git a/cropper.py b/cropper.py new file mode 100644 index 0000000..afb3201 --- /dev/null +++ b/cropper.py @@ -0,0 +1,67 @@ +import torch + +class VideoFramePreprocessor: + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "images": ("IMAGE",), # Input is a batch of video frames + } + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("processed_images",) + FUNCTION = "process_frames" + CATEGORY = "Stand-In" # Placed in a sub-category for organization + + def process_frames(self, images: torch.Tensor): + # Input tensor shape: (batch_size/frames, height, width, channels) + if images.dim() != 4: + raise ValueError("Input must be a batch of images (video frames).") + + total_frames, original_h, original_w, _ = images.shape + print(f"Original video specs: {total_frames} frames, {original_w}x{original_h}") + + # 1. Trim frame count to be 4n+1 + # We find the largest number <= total_frames that satisfies the condition. + new_total_frames = total_frames - ((total_frames - 1) % 4) + + if new_total_frames != total_frames: + print(f"Trimming frames to be 4n+1: {total_frames} -> {new_total_frames}") + images = images[:new_total_frames, :, :, :] + else: + print("Frame count already meets 4n+1 requirement. No trimming needed.") + + # 2. Crop dimensions to the nearest multiple of 8 (rounding down) + new_h = (original_h // 8) * 8 + new_w = (original_w // 8) * 8 + + if new_h != original_h or new_w != original_w: + print(f"Cropping dimensions to a multiple of 8: {original_w}x{original_h} -> {new_w}x{new_h}") + + # Calculate the amount to remove from each side for a center crop + h_to_remove = original_h - new_h + w_to_remove = original_w - new_w + + h_start = h_to_remove // 2 + w_start = w_to_remove // 2 + + # Perform the centered crop using tensor slicing + processed_images = images[:, h_start : h_start + new_h, w_start : w_start + new_w, :] + else: + print("Dimensions are already multiples of 8. No cropping needed.") + processed_images = images + + print(f"Final video specs: {processed_images.shape[0]} frames, {processed_images.shape[2]}x{processed_images.shape[1]}") + + return (processed_images,) + + +NODE_CLASS_MAPPINGS = { + "VideoFramePreprocessor": VideoFramePreprocessor, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "VideoFramePreprocessor": "Stand-In Trimmer & Cropper", +} diff --git a/example/face_swap.json b/example/face_swap.json index b502367..b4a9122 100644 --- a/example/face_swap.json +++ b/example/face_swap.json @@ -1,89 +1,9 @@ { "id": "206247b6-9fec-4ed2-8927-e4f388c674d4", "revision": 0, - "last_node_id": 150, - "last_link_id": 264, + "last_node_id": 151, + "last_link_id": 267, "nodes": [ - { - "id": 50, - "type": "CLIPTextEncode", - "pos": [ - 493.1703796386719, - 1356.8651123046875 - ], - "size": [ - 400, - 200 - ], - "flags": {}, - "order": 19, - "mode": 2, - "inputs": [ - { - "name": "clip", - "type": "CLIP", - "link": 53 - } - ], - "outputs": [ - { - "name": "CONDITIONING", - "type": "CONDITIONING", - "slot_index": 0, - "links": [ - 55 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.26", - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "色调艳丽,过曝,静态,细节模糊不清,字幕,风格,作品,画作,画面,静止,整体发灰,最差质量,低质量,JPEG压缩残留,丑陋的,残缺的,多余的手指,画得不好的手部,画得不好的脸部,畸形的,毁容的,形态畸形的肢体,手指融合,静止不动的画面,杂乱的背景,三条腿,背景人很多,倒着走" - ] - }, - { - "id": 49, - "type": "CLIPTextEncode", - "pos": [ - 493.1703796386719, - 1106.8651123046875 - ], - "size": [ - 400, - 200 - ], - "flags": {}, - "order": 18, - "mode": 2, - "inputs": [ - { - "name": "clip", - "type": "CLIP", - "link": 52 - } - ], - "outputs": [ - { - "name": "CONDITIONING", - "type": "CONDITIONING", - "slot_index": 0, - "links": [ - 54 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.26", - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "high quality nature video featuring a red panda balancing on a bamboo stem while a bird lands on it's head, on the background there is a waterfall" - ] - }, { "id": 36, "type": "Note", @@ -130,66 +50,6 @@ "color": "#432", "bgcolor": "#653" }, - { - "id": 51, - "type": "Note", - "pos": [ - 163.1707763671875, - 936.8648071289062 - ], - "size": [ - 253.16725158691406, - 88 - ], - "flags": {}, - "order": 2, - "mode": 2, - "inputs": [], - "outputs": [], - "properties": {}, - "widgets_values": [ - "You can also use native ComfyUI text encoding with these nodes instead of the original, the models are node specific and can't otherwise be mixed." - ], - "color": "#432", - "bgcolor": "#653" - }, - { - "id": 48, - "type": "CLIPLoader", - "pos": [ - 133.1708221435547, - 1106.8651123046875 - ], - "size": [ - 315, - 106 - ], - "flags": {}, - "order": 3, - "mode": 2, - "inputs": [], - "outputs": [ - { - "name": "CLIP", - "type": "CLIP", - "slot_index": 0, - "links": [ - 52, - 53 - ] - } - ], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.26", - "Node name for S&R": "CLIPLoader" - }, - "widgets_values": [ - "t5\\t5xxl_fp8_e4m3fn_scaled.safetensors", - "wan", - "default" - ] - }, { "id": 70, "type": "WanVideoSetBlockSwap", @@ -202,7 +62,7 @@ 46 ], "flags": {}, - "order": 25, + "order": 21, "mode": 0, "inputs": [ { @@ -247,7 +107,7 @@ 202 ], "flags": {}, - "order": 4, + "order": 2, "mode": 0, "inputs": [], "outputs": [ @@ -289,7 +149,7 @@ 202 ], "flags": {}, - "order": 5, + "order": 3, "mode": 0, "inputs": [], "outputs": [ @@ -331,7 +191,7 @@ 196.76773071289062 ], "flags": {}, - "order": 6, + "order": 4, "mode": 0, "inputs": [], "outputs": [], @@ -354,7 +214,7 @@ 46 ], "flags": {}, - "order": 30, + "order": 26, "mode": 0, "inputs": [ { @@ -399,7 +259,7 @@ 176 ], "flags": {}, - "order": 7, + "order": 5, "mode": 0, "inputs": [ { @@ -450,7 +310,7 @@ 274 ], "flags": {}, - "order": 20, + "order": 16, "mode": 0, "inputs": [ { @@ -522,78 +382,6 @@ "color": "#223", "bgcolor": "#335" }, - { - "id": 46, - "type": "WanVideoTextEmbedBridge", - "pos": [ - 943.1697387695312, - 1096.8651123046875 - ], - "size": [ - 315, - 46 - ], - "flags": { - "collapsed": true - }, - "order": 23, - "mode": 2, - "inputs": [ - { - "name": "positive", - "type": "CONDITIONING", - "link": 54 - }, - { - "name": "negative", - "shape": 7, - "type": "CONDITIONING", - "link": 55 - } - ], - "outputs": [ - { - "name": "text_embeds", - "type": "WANVIDEOTEXTEMBEDS", - "links": null - } - ], - "properties": { - "cnr_id": "ComfyUI-WanVideoWrapper", - "ver": "d9b1f4d1a5aea91d101ae97a54714a5861af3f50", - "Node name for S&R": "WanVideoTextEmbedBridge" - }, - "widgets_values": [] - }, - { - "id": 121, - "type": "PreviewImage", - "pos": [ - 37.458839416503906, - -72.97832489013672 - ], - "size": [ - 140, - 246 - ], - "flags": {}, - "order": 27, - "mode": 0, - "inputs": [ - { - "name": "images", - "type": "IMAGE", - "link": 227 - } - ], - "outputs": [], - "properties": { - "cnr_id": "comfy-core", - "ver": "0.3.49", - "Node name for S&R": "PreviewImage" - }, - "widgets_values": [] - }, { "id": 38, "type": "WanVideoVAELoader", @@ -606,7 +394,7 @@ 82 ], "flags": {}, - "order": 8, + "order": 6, "mode": 0, "inputs": [ { @@ -652,7 +440,7 @@ 57.857322692871094 ], "flags": {}, - "order": 31, + "order": 28, "mode": 0, "inputs": [ { @@ -694,7 +482,7 @@ 873.39453125 ], "flags": {}, - "order": 33, + "order": 29, "mode": 0, "inputs": [ { @@ -856,7 +644,7 @@ 355.67169189453125 ], "flags": {}, - "order": 24, + "order": 20, "mode": 0, "inputs": [ { @@ -909,7 +697,7 @@ 242 ], "flags": {}, - "order": 28, + "order": 23, "mode": 0, "inputs": [ { @@ -965,7 +753,7 @@ 200 ], "flags": {}, - "order": 9, + "order": 7, "mode": 0, "inputs": [ { @@ -1014,7 +802,7 @@ 126 ], "flags": {}, - "order": 26, + "order": 25, "mode": 0, "inputs": [ { @@ -1086,7 +874,7 @@ 130 ], "flags": {}, - "order": 10, + "order": 8, "mode": 0, "inputs": [], "outputs": [ @@ -1125,7 +913,7 @@ 206 ], "flags": {}, - "order": 21, + "order": 19, "mode": 0, "inputs": [ { @@ -1209,7 +997,7 @@ 143.0945281982422 ], "flags": {}, - "order": 11, + "order": 9, "mode": 0, "inputs": [], "outputs": [], @@ -1232,7 +1020,7 @@ 198 ], "flags": {}, - "order": 34, + "order": 30, "mode": 0, "inputs": [ { @@ -1284,7 +1072,7 @@ 150 ], "flags": {}, - "order": 36, + "order": 32, "mode": 0, "inputs": [ { @@ -1330,7 +1118,7 @@ 242 ], "flags": {}, - "order": 32, + "order": 27, "mode": 0, "inputs": [ { @@ -1386,7 +1174,7 @@ 210 ], "flags": {}, - "order": 12, + "order": 10, "mode": 0, "inputs": [], "outputs": [], @@ -1409,7 +1197,7 @@ 498.3180847167969 ], "flags": {}, - "order": 13, + "order": 11, "mode": 0, "inputs": [], "outputs": [ @@ -1438,95 +1226,6 @@ "color": "#2a363b", "bgcolor": "#3f5159" }, - { - "id": 131, - "type": "VHS_LoadVideo", - "pos": [ - 2823.573486328125, - -359.01287841796875 - ], - "size": [ - 660, - 693.2307739257812 - ], - "flags": {}, - "order": 14, - "mode": 0, - "inputs": [ - { - "name": "meta_batch", - "shape": 7, - "type": "VHS_BatchManager", - "link": null - }, - { - "name": "vae", - "shape": 7, - "type": "VAE", - "link": null - } - ], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 244, - 257 - ] - }, - { - "name": "frame_count", - "type": "INT", - "links": [ - 246 - ] - }, - { - "name": "audio", - "type": "AUDIO", - "links": null - }, - { - "name": "video_info", - "type": "VHS_VIDEOINFO", - "links": [ - 249 - ] - } - ], - "properties": { - "cnr_id": "comfyui-videohelpersuite", - "ver": "1.7.4", - "Node name for S&R": "VHS_LoadVideo" - }, - "widgets_values": { - "video": "13.mp4", - "force_rate": 0, - "custom_width": 0, - "custom_height": 0, - "frame_load_cap": 0, - "skip_first_frames": 0, - "select_every_nth": 1, - "format": "AnimateDiff", - "choose video to upload": "image", - "videopreview": { - "hidden": false, - "paused": false, - "params": { - "filename": "13.mp4", - "type": "input", - "format": "video/mp4", - "force_rate": 0, - "custom_width": 0, - "custom_height": 0, - "frame_load_cap": 0, - "skip_first_frames": 0, - "select_every_nth": 1 - } - } - } - }, { "id": 143, "type": "MarkdownNote", @@ -1539,7 +1238,7 @@ 240 ], "flags": {}, - "order": 15, + "order": 12, "mode": 0, "inputs": [], "outputs": [], @@ -1562,7 +1261,7 @@ 334 ], "flags": {}, - "order": 37, + "order": 33, "mode": 0, "inputs": [ { @@ -1639,7 +1338,7 @@ 198 ], "flags": {}, - "order": 22, + "order": 17, "mode": 0, "inputs": [ { @@ -1705,7 +1404,7 @@ 266 ], "flags": {}, - "order": 35, + "order": 31, "mode": 0, "inputs": [ { @@ -1721,7 +1420,7 @@ { "name": "orig_images", "type": "IMAGE", - "link": 257 + "link": 267 }, { "name": "face_only_mode", @@ -1769,7 +1468,7 @@ 266 ], "flags": {}, - "order": 29, + "order": 24, "mode": 0, "inputs": [ { @@ -1780,7 +1479,7 @@ { "name": "images", "type": "IMAGE", - "link": 244 + "link": 266 }, { "name": "face_rgba", @@ -1840,7 +1539,7 @@ 58 ], "flags": {}, - "order": 16, + "order": 13, "mode": 0, "inputs": [], "outputs": [ @@ -1875,7 +1574,7 @@ 58 ], "flags": {}, - "order": 17, + "order": 14, "mode": 0, "inputs": [], "outputs": [ @@ -1895,8 +1594,163 @@ "Node name for S&R": "FaceOnlyModeSwitch" }, "widgets_values": [ - true + false ] + }, + { + "id": 121, + "type": "PreviewImage", + "pos": [ + 57.08710479736328, + -116.29588317871094 + ], + "size": [ + 140, + 246 + ], + "flags": {}, + "order": 22, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 227 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.3.49", + "Node name for S&R": "PreviewImage" + }, + "widgets_values": [] + }, + { + "id": 131, + "type": "VHS_LoadVideo", + "pos": [ + -1432.3470458984375, + -227.6167449951172 + ], + "size": [ + 660, + 693.2307739257812 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "meta_batch", + "shape": 7, + "type": "VHS_BatchManager", + "link": null + }, + { + "name": "vae", + "shape": 7, + "type": "VAE", + "link": null + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 265 + ] + }, + { + "name": "frame_count", + "type": "INT", + "links": [ + 246 + ] + }, + { + "name": "audio", + "type": "AUDIO", + "links": null + }, + { + "name": "video_info", + "type": "VHS_VIDEOINFO", + "links": [ + 249 + ] + } + ], + "properties": { + "cnr_id": "comfyui-videohelpersuite", + "ver": "1.7.4", + "Node name for S&R": "VHS_LoadVideo" + }, + "widgets_values": { + "video": "13.mp4", + "force_rate": 0, + "custom_width": 0, + "custom_height": 0, + "frame_load_cap": 0, + "skip_first_frames": 0, + "select_every_nth": 1, + "format": "AnimateDiff", + "choose video to upload": "image", + "videopreview": { + "hidden": false, + "paused": false, + "params": { + "filename": "13.mp4", + "type": "input", + "format": "video/mp4", + "force_rate": 0, + "custom_width": 0, + "custom_height": 0, + "frame_load_cap": 0, + "skip_first_frames": 0, + "select_every_nth": 1 + } + } + } + }, + { + "id": 151, + "type": "VideoFramePreprocessor", + "pos": [ + -995.4586791992188, + -315.4657287597656 + ], + "size": [ + 215.98690795898438, + 26 + ], + "flags": {}, + "order": 18, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 265 + } + ], + "outputs": [ + { + "name": "processed_images", + "type": "IMAGE", + "links": [ + 266, + 267 + ] + } + ], + "properties": { + "aux_id": "WeChatCV/Stand-In_Preprocessor_ComfyUI", + "ver": "172ca8a19b39b110d76b3f8d9bbe8e16417c9f2d", + "Node name for S&R": "VideoFramePreprocessor" + }, + "widgets_values": [] } ], "links": [ @@ -1932,38 +1786,6 @@ 0, "VAE" ], - [ - 52, - 48, - 0, - 49, - 0, - "CLIP" - ], - [ - 53, - 48, - 0, - 50, - 0, - "CLIP" - ], - [ - 54, - 49, - 0, - 46, - 0, - "CONDITIONING" - ], - [ - 55, - 50, - 0, - 46, - 1, - "CONDITIONING" - ], [ 79, 22, @@ -2140,14 +1962,6 @@ 2, "IMAGE" ], - [ - 244, - 131, - 0, - 138, - 1, - "IMAGE" - ], [ 245, 138, @@ -2212,14 +2026,6 @@ 1, "IMAGE" ], - [ - 257, - 131, - 0, - 146, - 2, - "IMAGE" - ], [ 258, 146, @@ -2251,30 +2057,40 @@ 146, 3, "BOOLEAN" + ], + [ + 265, + 131, + 0, + 151, + 0, + "IMAGE" + ], + [ + 266, + 151, + 0, + 138, + 1, + "IMAGE" + ], + [ + 267, + 151, + 0, + 146, + 2, + "IMAGE" ] ], - "groups": [ - { - "id": 1, - "title": "ComfyUI text encoding alternative", - "bounding": [ - 70.3548812866211, - 792.9315185546875, - 1210.621337890625, - 805.9080810546875 - ], - "color": "#3f789e", - "font_size": 24, - "flags": {} - } - ], + "groups": [], "config": {}, "extra": { "ds": { - "scale": 0.3452271214393293, + "scale": 0.6727499949325989, "offset": [ - 2196.1039676207993, - 1308.1874027167953 + 798.9888409617952, + 485.8244985963852 ] }, "frontendVersion": "1.24.4", diff --git a/videoinput.py b/videoinput.py index 8c52920..cb48fe7 100644 --- a/videoinput.py +++ b/videoinput.py @@ -38,7 +38,7 @@ class VideoInputPreprocessor: "with_neck": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}), "face_only_mode": ("BOOLEAN", {"default": True, "label_on": "enabled", "label_off": "disabled"}), "feather_amount": ("INT", {"default": 21, "min": 0, "max": 151, "step": 2, "display": "slider"}), - "random_horizontal_flip_chance": ("FLOAT", {"default": 0.15, "min": 0.0, "max": 1.0, "step": 0.05}), + "random_horizontal_flip_chance": ("FLOAT", {"default": 0.05, "min": 0.0, "max": 1.0, "step": 0.05}), } }