From fdfa2de24ded91f0c2acd62dae6cb329989c252d Mon Sep 17 00:00:00 2001 From: Bruno Fargnoli Date: Sun, 5 Apr 2026 12:20:36 +0200 Subject: [PATCH] Added node "Extract Frames from Video" --- README.md | 1 + .../ReconViaGen_MeshOnly_FromVideo.json | 1053 +++++++++++++++++ nodes.py | 119 +- pyproject.toml | 2 +- 4 files changed, 1156 insertions(+), 19 deletions(-) create mode 100644 example_workflows/ReconViaGen_MeshOnly_FromVideo.json diff --git a/README.md b/README.md index c355fbc..e290043 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ | Date | Description | | --- | --- | +| **2026-04-05** | Added node "Extract Images from Video"
Can be used with "Sparse Generator with ReconViaGen" | | **2026-04-04** | Added node "Sparse Generator with ReconViaGen" | | **2026-04-01** | Added node "Voxel to Mesh"
It replaces Remeshing to make watertight mesh | | **2026-03-21** | Added node "Projection HighPoly to LowPoly"
Added node "Render MultiView" | diff --git a/example_workflows/ReconViaGen_MeshOnly_FromVideo.json b/example_workflows/ReconViaGen_MeshOnly_FromVideo.json new file mode 100644 index 0000000..28bc191 --- /dev/null +++ b/example_workflows/ReconViaGen_MeshOnly_FromVideo.json @@ -0,0 +1,1053 @@ +{ + "id": "6eb9772a-caf3-4cd2-a1f3-8e21b2fcb356", + "revision": 0, + "last_node_id": 32, + "last_link_id": 66, + "nodes": [ + { + "id": 8, + "type": "Trellis2ShapeCascadeGenerator", + "pos": [ + 2144.5184531017253, + 89.08064130521795 + ], + "size": [ + 335.9791015625, + 338 + ], + "flags": {}, + "order": 7, + "mode": 0, + "inputs": [ + { + "name": "pipeline", + "type": "TRELLIS2PIPELINE", + "link": 16 + }, + { + "name": "image_cond", + "type": "IMAGE_COND", + "link": 58 + }, + { + "name": "shape_slat", + "type": "SHAPE_SLAT", + "link": 18 + }, + { + "name": "from_resolution", + "type": "INT", + "widget": { + "name": "from_resolution" + }, + "link": 19 + }, + { + "name": "sparse_structure_resolution", + "type": "INT", + "widget": { + "name": "sparse_structure_resolution" + }, + "link": 34 + } + ], + "outputs": [ + { + "name": "shape_slat", + "type": "SHAPE_SLAT", + "links": [ + 11 + ] + }, + { + "name": "resolution", + "type": "INT", + "links": [ + 12 + ] + }, + { + "name": "pipeline", + "type": "TRELLIS2PIPELINE", + "links": [ + 10 + ] + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "ef2286b47ce0ef0e681dffe0209aadd96de50f72", + "Node name for S&R": "Trellis2ShapeCascadeGenerator" + }, + "widgets_values": [ + 0, + 1024, + 0, + 999999, + 12, + 7.5, + 0.01, + 3, + "heun", + 0.1, + 1 + ] + }, + { + "id": 9, + "type": "Trellis2DecodeLatents", + "pos": [ + 2550.5271755896983, + 85.2758108930415 + ], + "size": [ + 270, + 122 + ], + "flags": {}, + "order": 8, + "mode": 0, + "inputs": [ + { + "name": "pipeline", + "type": "TRELLIS2PIPELINE", + "link": 10 + }, + { + "name": "shape_slat", + "type": "SHAPE_SLAT", + "link": 11 + }, + { + "name": "texture_slat", + "shape": 7, + "type": "TEXTURE_SLAT", + "link": null + }, + { + "name": "resolution", + "type": "INT", + "widget": { + "name": "resolution" + }, + "link": 12 + } + ], + "outputs": [ + { + "name": "mesh", + "type": "MESHWITHVOXEL", + "links": [ + 13 + ] + }, + { + "name": "bvh", + "type": "BVH", + "links": [] + }, + { + "name": "pipeline", + "type": "TRELLIS2PIPELINE", + "links": [] + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "ef2286b47ce0ef0e681dffe0209aadd96de50f72", + "Node name for S&R": "Trellis2DecodeLatents" + }, + "widgets_values": [ + 0, + true + ] + }, + { + "id": 10, + "type": "Trellis2FillHolesWithCuMesh", + "pos": [ + 2861.8616589898866, + 88.51255217697037 + ], + "size": [ + 312.4361328125, + 58 + ], + "flags": {}, + "order": 9, + "mode": 0, + "inputs": [ + { + "name": "mesh", + "type": "MESHWITHVOXEL", + "link": 13 + } + ], + "outputs": [ + { + "name": "mesh", + "type": "MESHWITHVOXEL", + "links": [ + 14 + ] + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "dc66f263e832b103a81a021bf6cd53eec8ff28af", + "Node name for S&R": "Trellis2FillHolesWithCuMesh" + }, + "widgets_values": [ + 1 + ] + }, + { + "id": 11, + "type": "Trellis2ReconstructMeshWithQuad", + "pos": [ + 2847.559963718577, + 257.899269352995 + ], + "size": [ + 331.5878996659427, + 142.11360851901668 + ], + "flags": {}, + "order": 10, + "mode": 0, + "inputs": [ + { + "name": "mesh", + "type": "MESHWITHVOXEL", + "link": 14 + } + ], + "outputs": [ + { + "name": "mesh", + "type": "MESHWITHVOXEL", + "links": [ + 15 + ] + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "d4d66cfc9f1ce5b1c3b9a2e504ee4b24ffad48a5", + "Node name for S&R": "Trellis2ReconstructMeshWithQuad", + "widget_ue_connectable": {} + }, + "widgets_values": [ + 1, + 1024, + true, + true + ] + }, + { + "id": 12, + "type": "Trellis2SimplifyMesh", + "pos": [ + 2886.0029502911757, + 486.99086892176337 + ], + "size": [ + 270, + 82 + ], + "flags": {}, + "order": 11, + "mode": 0, + "inputs": [ + { + "name": "mesh", + "type": "MESHWITHVOXEL", + "link": 15 + } + ], + "outputs": [ + { + "name": "mesh", + "type": "MESHWITHVOXEL", + "links": [ + 35 + ] + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "ef2286b47ce0ef0e681dffe0209aadd96de50f72", + "Node name for S&R": "Trellis2SimplifyMesh" + }, + "widgets_values": [ + 500000, + "Cumesh" + ] + }, + { + "id": 13, + "type": "Trellis2MeshWithVoxelToTrimesh", + "pos": [ + 2840.8121187391353, + 764.4960805898208 + ], + "size": [ + 349.41171875, + 58 + ], + "flags": {}, + "order": 13, + "mode": 0, + "inputs": [ + { + "name": "mesh", + "type": "MESHWITHVOXEL", + "link": 36 + } + ], + "outputs": [ + { + "name": "trimesh", + "type": "TRIMESH", + "links": [ + 21 + ] + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "ef2286b47ce0ef0e681dffe0209aadd96de50f72", + "Node name for S&R": "Trellis2MeshWithVoxelToTrimesh" + }, + "widgets_values": [ + "90 degrees" + ] + }, + { + "id": 19, + "type": "Trellis2FillHolesWithMeshlib", + "pos": [ + 2889.6686029432403, + 637.1515820571416 + ], + "size": [ + 249.284765625, + 46 + ], + "flags": {}, + "order": 12, + "mode": 4, + "inputs": [ + { + "name": "mesh", + "type": "MESHWITHVOXEL", + "link": 35 + } + ], + "outputs": [ + { + "name": "mesh", + "type": "MESHWITHVOXEL", + "links": [ + 36 + ] + }, + { + "name": "holes_filled", + "type": "INT", + "links": null + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "a5242473f68fe91d5ef0b188f8658ae0da540237", + "Node name for S&R": "Trellis2FillHolesWithMeshlib" + }, + "widgets_values": [] + }, + { + "id": 15, + "type": "Preview3D", + "pos": [ + 1271.3057615922355, + 522.7384991788329 + ], + "size": [ + 1051.7218022911484, + 1141.158091689083 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [ + { + "name": "camera_info", + "shape": 7, + "type": "LOAD3D_CAMERA", + "link": null + }, + { + "name": "bg_image", + "shape": 7, + "type": "IMAGE", + "link": null + }, + { + "name": "model_file", + "type": "STRING,FILE_3D_GLB,FILE_3D_GLTF,FILE_3D_FBX,FILE_3D_OBJ,FILE_3D_STL,FILE_3D_USDZ,FILE_3D", + "widget": { + "name": "model_file" + }, + "link": 22 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.18.1", + "Node name for S&R": "Preview3D", + "Camera Config": { + "cameraType": "perspective", + "fov": 75, + "state": { + "position": { + "x": 3.1224925587544337, + "y": 3.9751518637490415, + "z": 2.9199690658797848 + }, + "target": { + "x": 0, + "y": 2.5, + "z": 0 + }, + "zoom": 1, + "cameraType": "perspective" + } + }, + "Last Time Model File": "C:/Git/ComfyUI/output/VGGT_MV_00007_.glb", + "Resource Folder": "Git/ComfyUI/output", + "Scene Config": { + "showGrid": true, + "backgroundColor": "#282828", + "backgroundImage": "", + "backgroundRenderMode": "tiled" + }, + "Light Config": { + "intensity": 3 + }, + "Model Config": { + "upDirection": "original", + "materialMode": "normal", + "showSkeleton": false + } + }, + "widgets_values": [ + "C:/Git/ComfyUI/output/VGGT_MV_00007_.glb", + "" + ] + }, + { + "id": 14, + "type": "Trellis2ExportMesh", + "pos": [ + 2878.801482335511, + 893.0809816705423 + ], + "size": [ + 270, + 102 + ], + "flags": {}, + "order": 14, + "mode": 0, + "inputs": [ + { + "name": "trimesh", + "type": "TRIMESH", + "link": 21 + } + ], + "outputs": [ + { + "name": "glb_path", + "type": "STRING", + "links": [ + 22 + ] + }, + { + "name": "relative_path", + "type": "STRING", + "links": null + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "ef2286b47ce0ef0e681dffe0209aadd96de50f72", + "Node name for S&R": "Trellis2ExportMesh" + }, + "widgets_values": [ + "VGGT_MV", + "glb" + ] + }, + { + "id": 4, + "type": "Trellis2SparseGeneratorWithReconViaGen", + "pos": [ + 1257.1961548750526, + 122.64558954014163 + ], + "size": [ + 402.6431640625, + 334 + ], + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "pipeline", + "type": "TRELLIS2PIPELINE", + "link": 2 + }, + { + "name": "images", + "type": "IMAGE", + "link": 63 + } + ], + "outputs": [ + { + "name": "coords", + "type": "COORDS", + "links": [ + 32 + ] + }, + { + "name": "sparse_structure_resolution", + "type": "INT", + "links": [ + 34 + ] + }, + { + "name": "pipeline", + "type": "TRELLIS2PIPELINE", + "links": [ + 33 + ] + }, + { + "name": "image_cond", + "type": "IMAGE_COND", + "links": [] + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "a5242473f68fe91d5ef0b188f8658ae0da540237", + "Node name for S&R": "Trellis2SparseGeneratorWithReconViaGen" + }, + "widgets_values": [ + 12345, + "fixed", + 25, + 7.5, + 0.01, + 5, + "euler", + 32, + 0.1, + 1 + ] + }, + { + "id": 5, + "type": "Trellis2ShapeGenerator", + "pos": [ + 1728.3644132553936, + 149.205532716106 + ], + "size": [ + 335.24609375, + 266 + ], + "flags": {}, + "order": 6, + "mode": 0, + "inputs": [ + { + "name": "pipeline", + "type": "TRELLIS2PIPELINE", + "link": 33 + }, + { + "name": "image_cond", + "type": "IMAGE_COND", + "link": 57 + }, + { + "name": "coords", + "type": "COORDS", + "link": 32 + } + ], + "outputs": [ + { + "name": "shape_slat", + "type": "SHAPE_SLAT", + "links": [ + 18 + ] + }, + { + "name": "resolution", + "type": "INT", + "links": [ + 19 + ] + }, + { + "name": "pipeline", + "type": "TRELLIS2PIPELINE", + "links": [ + 16 + ] + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "a5242473f68fe91d5ef0b188f8658ae0da540237", + "Node name for S&R": "Trellis2ShapeGenerator" + }, + "widgets_values": [ + 512, + 25, + 7.5, + 0.01, + 3, + "heun", + 0.1, + 1 + ] + }, + { + "id": 1, + "type": "Trellis2LoadModel", + "pos": [ + 614.8749036576971, + -262.37703626247117 + ], + "size": [ + 301.71875, + 226 + ], + "flags": {}, + "order": 0, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "pipeline", + "type": "TRELLIS2PIPELINE", + "links": [ + 2, + 55 + ] + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "c5d66aa46bf1bbf903fd4fe9ff086ac774bb7e03", + "Node name for S&R": "Trellis2LoadModel", + "widget_ue_connectable": {} + }, + "widgets_values": [ + "microsoft/TRELLIS.2-4B", + "flash_attn", + "cuda", + true, + false, + "flex_gemm", + "flash_attn", + true + ] + }, + { + "id": 23, + "type": "PreviewImage", + "pos": [ + -23.66147835882571, + 524.1622190318901 + ], + "size": [ + 1207.3601737800832, + 1034.082524221429 + ], + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 41 + } + ], + "outputs": [], + "properties": { + "cnr_id": "comfy-core", + "ver": "0.18.1", + "Node name for S&R": "PreviewImage" + }, + "widgets_values": [] + }, + { + "id": 20, + "type": "Trellis2ExtractImagesFromVideo", + "pos": [ + 22.654179598652767, + 336.740539302737 + ], + "size": [ + 341.20859375, + 82 + ], + "flags": {}, + "order": 1, + "mode": 0, + "inputs": [], + "outputs": [ + { + "name": "images", + "type": "IMAGE", + "links": [ + 41, + 66 + ] + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "f791215b2a608f2fae67722702b06f871af9e5c4", + "Node name for S&R": "Trellis2ExtractImagesFromVideo" + }, + "widgets_values": [ + "C:\\Users\\visua\\Downloads\\model-recording-1775339461054.mp4", + 2 + ] + }, + { + "id": 31, + "type": "Trellis2ImageCondGenerator", + "pos": [ + 1272.906859863932, + -90.65371968536492 + ], + "size": [ + 308.0748046875, + 98 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "pipeline", + "type": "TRELLIS2PIPELINE", + "link": 55 + }, + { + "name": "image", + "type": "IMAGE", + "link": 64 + } + ], + "outputs": [ + { + "name": "cond_512", + "type": "IMAGE_COND", + "links": [ + 57 + ] + }, + { + "name": "cond_1024", + "type": "IMAGE_COND", + "links": [ + 58 + ] + }, + { + "name": "pipeline", + "type": "TRELLIS2PIPELINE", + "links": null + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "f791215b2a608f2fae67722702b06f871af9e5c4", + "Node name for S&R": "Trellis2ImageCondGenerator" + }, + "widgets_values": [ + 16 + ] + }, + { + "id": 2, + "type": "Trellis2PreProcessImage", + "pos": [ + 635.3919465809572, + 139.54969969071414 + ], + "size": [ + 281.8229166666667, + 106 + ], + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "image", + "type": "IMAGE", + "link": 66 + } + ], + "outputs": [ + { + "name": "image", + "type": "IMAGE", + "links": [ + 63, + 64 + ] + } + ], + "properties": { + "aux_id": "visualbruno/ComfyUI-Trellis2", + "ver": "15854282a73cf231b81d52ada22e652f414a078b", + "Node name for S&R": "Trellis2PreProcessImage", + "widget_ue_connectable": {} + }, + "widgets_values": [ + 5, + true, + 1024 + ] + } + ], + "links": [ + [ + 2, + 1, + 0, + 4, + 0, + "TRELLIS2PIPELINE" + ], + [ + 10, + 8, + 2, + 9, + 0, + "TRELLIS2PIPELINE" + ], + [ + 11, + 8, + 0, + 9, + 1, + "SHAPE_SLAT" + ], + [ + 12, + 8, + 1, + 9, + 3, + "INT" + ], + [ + 13, + 9, + 0, + 10, + 0, + "MESHWITHVOXEL" + ], + [ + 14, + 10, + 0, + 11, + 0, + "MESHWITHVOXEL" + ], + [ + 15, + 11, + 0, + 12, + 0, + "MESHWITHVOXEL" + ], + [ + 16, + 5, + 2, + 8, + 0, + "TRELLIS2PIPELINE" + ], + [ + 18, + 5, + 0, + 8, + 2, + "SHAPE_SLAT" + ], + [ + 19, + 5, + 1, + 8, + 3, + "INT" + ], + [ + 21, + 13, + 0, + 14, + 0, + "TRIMESH" + ], + [ + 22, + 14, + 0, + 15, + 2, + "STRING" + ], + [ + 32, + 4, + 0, + 5, + 2, + "COORDS" + ], + [ + 33, + 4, + 2, + 5, + 0, + "TRELLIS2PIPELINE" + ], + [ + 34, + 4, + 1, + 8, + 4, + "INT" + ], + [ + 35, + 12, + 0, + 19, + 0, + "MESHWITHVOXEL" + ], + [ + 36, + 19, + 0, + 13, + 0, + "MESHWITHVOXEL" + ], + [ + 41, + 20, + 0, + 23, + 0, + "IMAGE" + ], + [ + 55, + 1, + 0, + 31, + 0, + "TRELLIS2PIPELINE" + ], + [ + 57, + 31, + 0, + 5, + 1, + "IMAGE_COND" + ], + [ + 58, + 31, + 1, + 8, + 1, + "IMAGE_COND" + ], + [ + 63, + 2, + 0, + 4, + 1, + "IMAGE" + ], + [ + 64, + 2, + 0, + 31, + 1, + "IMAGE" + ], + [ + 66, + 20, + 0, + 2, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": { + "ds": { + "scale": 0.4240976183724851, + "offset": [ + 525.3533201242849, + 457.9701069886444 + ] + }, + "frontendVersion": "1.42.8", + "VHS_latentpreview": false, + "VHS_latentpreviewrate": 0, + "VHS_MetadataImage": true, + "VHS_KeepIntermediate": true + }, + "version": 0.4 +} \ No newline at end of file diff --git a/nodes.py b/nodes.py index 3813f39..00b7cba 100644 --- a/nodes.py +++ b/nodes.py @@ -2383,20 +2383,58 @@ class Trellis2PreProcessImage: CATEGORY = "Trellis2Wrapper" def process(self, image, padding, remove_background, max_size): - image = tensor2pil(image) - - if remove_background: - from rembg import remove - image = remove(image) - - image = self.preprocess_image(image, max_size) - - if padding>0: - border = (int(padding), int(padding), int(padding), int(padding)) - fill_color = self.parse_fill_for_image("0,0,0,255", image) - image = ImageOps.expand(image,border=border,fill=fill_color) - - image = pil2tensor(image) + if image.ndim == 3: + image = tensor2pil(image) + + if remove_background: + from rembg import remove + image = remove(image) + + image = self.preprocess_image(image, max_size) + + if padding>0: + border = (int(padding), int(padding), int(padding), int(padding)) + fill_color = self.parse_fill_for_image("0,0,0,255", image) + image = ImageOps.expand(image,border=border,fill=fill_color) + + image = pil2tensor(image) + elif image.ndim == 4: + images = convert_tensor_images_to_pil(image) + tensor_list = [] + for img in images: + if remove_background: + from rembg import remove + img = remove(img) + + img = self.preprocess_image(img, max_size) + + if padding>0: + border = (int(padding), int(padding), int(padding), int(padding)) + fill_color = self.parse_fill_for_image("0,0,0,255", img) + img = ImageOps.expand(img,border=border,fill=fill_color) + + tensor_list.append(pil2tensor(img)) + + max_h = max(t.shape[-3] for t in tensor_list) + max_w = max(t.shape[-2] for t in tensor_list) + + resized_tensors = [] + + for t in tensor_list: + # Ensure tensor is [C, H, W] for PyTorch's interpolate function + # Current shape is likely [H, W, C] or [1, H, W, C] + temp_t = t.squeeze() # Get to [H, W, C] + temp_t = temp_t.permute(2, 0, 1).unsqueeze(0) # Becomes [1, C, H, W] + + # 2. Resize to the max dimensions + # Using 'bicubic' or 'bilinear' for better quality than 'nearest' + temp_t = F.interpolate(temp_t, size=(max_h, max_w), mode='bicubic', align_corners=False) + + # 3. Convert back to ComfyUI format [H, W, C] + temp_t = temp_t.squeeze(0).permute(1, 2, 0) + resized_tensors.append(temp_t) + + image = torch.stack(resized_tensors) return (image,) @@ -3527,7 +3565,7 @@ class Trellis2ImageCondGenerator: "required": { "pipeline": ("TRELLIS2PIPELINE",), "image": ("IMAGE",), - "max_views": ("INT", {"default": 4, "min": 1, "max": 16}), + "max_views": ("INT", {"default": 1, "min": 1, "max": 999}), }, } @@ -3779,6 +3817,9 @@ class Trellis2ShapeCascadeGenerator: print(f"Num Tokens: {num_tokens}") hr_resolution = 512 break + + if pipeline.low_vram: + cond = pipeline._cond_to(cond, pipeline.device) coords_dev = coords.to(pipeline.device) # Sample structured latent @@ -4804,7 +4845,7 @@ class Trellis2SparseGeneratorWithReconViaGen: pipeline.unload_sparse_structure_vggt_cond() self.unload_vggt_model(pipeline) - return (coords, sparse_structure_resolution, pipeline,) + return (coords, sparse_structure_resolution, pipeline) def load_vggt_model(self, pipeline): if pipeline.VGGT_model is None: @@ -4900,7 +4941,7 @@ class Trellis2SparseGeneratorWithReconViaGen: ss_cond = pipeline._cond_cpu(ss_cond) torch.cuda.empty_cache() - return coords + return coords @torch.no_grad() def _run_ss_stage( @@ -4986,6 +5027,7 @@ class Trellis2SparseGeneratorWithReconViaGen: Returns: dict: The conditioning information """ + pipeline.models['sparse_structure_vggt_cond'].to(pipeline.device) cond = pipeline.models['sparse_structure_vggt_cond'](aggregated_tokens_list, image_cond) neg_cond = torch.zeros_like(cond) return { @@ -5059,7 +5101,46 @@ class Trellis2SparseGeneratorWithReconViaGen: transform = transforms.Compose([ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) - pipeline.image_cond_model_transform = transform + pipeline.image_cond_model_transform = transform + +class Trellis2ExtractImagesFromVideo: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "video_file": ("STRING",), + "frames_per_second": ("INT",{"default":1,"min":1,"max":50,"step":1}), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("images",) + FUNCTION = "process" + CATEGORY = "Trellis2Wrapper" + OUTPUT_NODE = True + + def process(self, video_file, frames_per_second): + import imageio + + vid = imageio.get_reader(video_file, 'ffmpeg') + fps = vid.get_meta_data()['fps'] + frames = [] + for i, frame in enumerate(vid): + if i % max(int(fps/frames_per_second), 1) == 0: + img = Image.fromarray(frame) + W, H = img.size + img = img.resize((int(W / H * 1024), 1024)) + frames.append(img) + vid.close() + + tensor_list = [torch.from_numpy(np.array(img).astype(np.float32) / 255.0) for img in frames] + + print(f"{len(frames)} frames extracted") + + tensor_frames = torch.stack(tensor_list) + #tensor_frames = tensor_frames.permute(0, 2, 3, 1) + + return (tensor_frames,) NODE_CLASS_MAPPINGS = { "Trellis2LoadModel": Trellis2LoadModel, @@ -5118,6 +5199,7 @@ NODE_CLASS_MAPPINGS = { "Trellis2VoxelToMesh": Trellis2VoxelToMesh, "Trellis2UnloadAllModels": Trellis2UnloadAllModels, "Trellis2SparseGeneratorWithReconViaGen": Trellis2SparseGeneratorWithReconViaGen, + "Trellis2ExtractImagesFromVideo": Trellis2ExtractImagesFromVideo, } @@ -5178,4 +5260,5 @@ NODE_DISPLAY_NAME_MAPPINGS = { "Trellis2VoxelToMesh": "Trellis2 - Voxel to Mesh", "Trellis2UnloadAllModels": "Trellis2 - Unload All ComfyUI Models", "Trellis2SparseGeneratorWithReconViaGen": "Trellis2 - Sparse Generator with ReconViaGen", + "Trellis2ExtractImagesFromVideo": "Trellis 2 - Extract Images from Video", } diff --git a/pyproject.toml b/pyproject.toml index 570f346..acd35fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "trellis2" description = "ComfyUI Wrapper for Microsoft Trellis.2 - Native and Compact Structured Latents for 3D Generation" -version = "1.0.20" +version = "1.0.21" license = {file = "LICENSE"} # classifiers = [ # # For OS-independent nodes (works on all operating systems)