diff --git a/.github/workflow_preview_video_frames.jpg b/.github/workflow_preview_video_frames.jpg new file mode 100644 index 0000000..51d3c96 Binary files /dev/null and b/.github/workflow_preview_video_frames.jpg differ diff --git a/.github/workflow_swap_image.jpg b/.github/workflow_swap_image.jpg index bb6823a..3ec508a 100644 Binary files a/.github/workflow_swap_image.jpg and b/.github/workflow_swap_image.jpg differ diff --git a/.github/workflow_swap_video.jpg b/.github/workflow_swap_video.jpg index efc21e9..d902f09 100644 Binary files a/.github/workflow_swap_video.jpg and b/.github/workflow_swap_video.jpg differ diff --git a/README.md b/README.md index 3cfa8c5..57c4140 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,34 @@ This project is under development. There may be compatibility issues in future u ### Install custom node +Install custom node in directory `ComfyUI/custom_nodes`. + ```bash git clone https://github.com/jeffy5/comfyui-faceless-node pip install -r requirements.txt ``` +### Setup conda + +```bash +# Install conda +curl -LO https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh +bash Miniconda3-latest-Linux-x86_64.sh + +# Setup conda env +conda init --all +conda create --name facefusion python=3.10 +conda activate facefusion + +# Setup cuda +conda install conda-forge::cuda-runtime=12.4.1 cudnn=8.9.2.26 conda-forge::gputil=1.4.0 +``` + ### Download model All models is same as facefusion which can be found in [facefusion assets](https://github.com/facefusion/facefusion-assets). -Now it will use the following model by default. In the future, dynamic model selection will be supported. Please download the model based on the following directory structure. +Now it will use the following models by default. In the future, dynamic model selection will be supported. Please download the model based on the directory structure. Put all into directory `CmofyUI/models/faceless`. @@ -40,7 +58,7 @@ Put all into directory `CmofyUI/models/faceless`. You can download models by running `download_models.py` script under directory `CmofyUI/custom_nodes/comfyui-faceless-node`. ```bash -# Install required models +# Install default required models python download_models.py # Install all models @@ -49,17 +67,25 @@ python download_models.py --all ## Example workflows -### Image face swap +You can find same example workflows in directory `examples`. -![Image Face Swap](https://raw.githubusercontent.com/jeffy5/comfyui-faceless-node/main/.github/workflow_swap_image.jpg) +### Image face swap and face restore -### Video face swap +![Image Face Swap And Face Restore](https://raw.githubusercontent.com/jeffy5/comfyui-faceless-node/main/.github/workflow_swap_image.jpg) -![Video Face Swap](https://raw.githubusercontent.com/jeffy5/comfyui-faceless-node/main/.github/workflow_swap_video.jpg) +[image_face_swap_and_restore.json](https://raw.githubusercontent.com/jeffy5/comfyui-faceless-node/main/.github/examples/image_face_swap_and_restore.json) -### Load frames to preview image +### Video face swap and face restore -![Load Frames To Preview](https://raw.githubusercontent.com/jeffy5/comfyui-faceless-node/main/.github/workflow_load_frames.jpg) +![Video Face Swap And Face Restore](https://raw.githubusercontent.com/jeffy5/comfyui-faceless-node/main/.github/workflow_swap_video.jpg) + +[video_face_swap_and_restore.json](https://raw.githubusercontent.com/jeffy5/comfyui-faceless-node/main/.github/examples/video_face_swap_and_restore.json) + +### Preview video frames + +![Preview Video Frames](https://raw.githubusercontent.com/jeffy5/comfyui-faceless-node/main/.github/workflow_preview_video_frames.jpg) + +[preview_video_frames.json](https://raw.githubusercontent.com/jeffy5/comfyui-faceless-node/main/.github/examples/preview_video_frames.json) It doesn't support preview video for now. All video will be save in directory `output/faceless`. diff --git a/examples/image_face_swap_and_restore.json b/examples/image_face_swap_and_restore.json new file mode 100644 index 0000000..d4c6ff4 --- /dev/null +++ b/examples/image_face_swap_and_restore.json @@ -0,0 +1,232 @@ +{ + "last_node_id": 5, + "last_link_id": 4, + "nodes": [ + { + "id": 1, + "type": "LoadImage", + "pos": [ + 333, + 193 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 1 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "majic-girl.jpeg", + "image" + ] + }, + { + "id": 2, + "type": "LoadImage", + "pos": [ + 348, + 613 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 2 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "sd3-girl.jpeg", + "image" + ] + }, + { + "id": 4, + "type": "FacelessFaceRestore", + "pos": [ + 1263, + 432 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 3 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 4 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FacelessFaceRestore" + }, + "widgets_values": [ + "gfpgan_1.4.onnx" + ] + }, + { + "id": 5, + "type": "PreviewImage", + "pos": [ + 1680, + 430 + ], + "size": { + "0": 210, + "1": 250 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 4 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + }, + { + "id": 3, + "type": "FacelessFaceSwap", + "pos": [ + 825, + 434 + ], + "size": { + "0": 315, + "1": 126 + }, + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 1 + }, + { + "name": "face_image", + "type": "IMAGE", + "link": 2 + } + ], + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 3 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FacelessFaceSwap" + }, + "widgets_values": [ + "inswapper_128.onnx", + "yoloface_8n.onnx", + "arcface_w600k_r50.onnx" + ] + } + ], + "links": [ + [ + 1, + 1, + 0, + 3, + 0, + "IMAGE" + ], + [ + 2, + 2, + 0, + 3, + 1, + "IMAGE" + ], + [ + 3, + 3, + 0, + 4, + 0, + "IMAGE" + ], + [ + 4, + 4, + 0, + 5, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file diff --git a/examples/images/majic-girl.jpeg b/examples/images/majic-girl.jpeg new file mode 100644 index 0000000..4578ed8 Binary files /dev/null and b/examples/images/majic-girl.jpeg differ diff --git a/examples/images/sd3-girl.jpeg b/examples/images/sd3-girl.jpeg new file mode 100644 index 0000000..93cbae6 Binary files /dev/null and b/examples/images/sd3-girl.jpeg differ diff --git a/examples/preview_video_frames.json b/examples/preview_video_frames.json new file mode 100644 index 0000000..c443c80 --- /dev/null +++ b/examples/preview_video_frames.json @@ -0,0 +1,273 @@ +{ + "last_node_id": 15, + "last_link_id": 17, + "nodes": [ + { + "id": 1, + "type": "LoadImage", + "pos": [ + 327, + 133 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 9 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "majic-girl.jpeg", + "image" + ] + }, + { + "id": 6, + "type": "FacelessVideoFaceSwap", + "pos": [ + 830, + 443 + ], + "size": { + "0": 315, + "1": 126 + }, + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "source_image", + "type": "IMAGE", + "link": 9 + }, + { + "name": "target_video", + "type": "FACELESS_VIDEO", + "link": 10 + } + ], + "outputs": [ + { + "name": "video", + "type": "FACELESS_VIDEO", + "links": [ + 8 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FacelessVideoFaceSwap" + }, + "widgets_values": [ + "inswapper_128.onnx", + "yoloface_8n.onnx", + "arcface_w600k_r50.onnx" + ] + }, + { + "id": 9, + "type": "FacelessLoadVideo", + "pos": [ + 390, + 680 + ], + "size": { + "0": 210, + "1": 154 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "video", + "type": "FACELESS_VIDEO", + "links": [ + 10 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FacelessLoadVideo" + }, + "widgets_values": [ + "pexels-girl.mp4", + true, + -1, + 2, + "image" + ] + }, + { + "id": 7, + "type": "FacelessVideoFaceRestore", + "pos": [ + 1275, + 446 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "video", + "type": "FACELESS_VIDEO", + "link": 8 + } + ], + "outputs": [ + { + "name": "video", + "type": "FACELESS_VIDEO", + "links": [ + 16 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FacelessVideoFaceRestore" + }, + "widgets_values": [ + "gfpgan_1.4.onnx" + ] + }, + { + "id": 15, + "type": "FacelessLoadVideoImages", + "pos": [ + 1687, + 443 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "video", + "type": "FACELESS_VIDEO", + "link": 16 + } + ], + "outputs": [ + { + "name": "images", + "type": "IMAGE", + "links": [ + 17 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FacelessLoadVideoImages" + } + }, + { + "id": 11, + "type": "PreviewImage", + "pos": [ + 1985, + 442 + ], + "size": [ + 210, + 246 + ], + "flags": {}, + "order": 5, + "mode": 0, + "inputs": [ + { + "name": "images", + "type": "IMAGE", + "link": 17 + } + ], + "properties": { + "Node name for S&R": "PreviewImage" + } + } + ], + "links": [ + [ + 8, + 6, + 0, + 7, + 0, + "FACELESS_VIDEO" + ], + [ + 9, + 1, + 0, + 6, + 0, + "IMAGE" + ], + [ + 10, + 9, + 0, + 6, + 1, + "FACELESS_VIDEO" + ], + [ + 16, + 7, + 0, + 15, + 0, + "FACELESS_VIDEO" + ], + [ + 17, + 15, + 0, + 11, + 0, + "IMAGE" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file diff --git a/examples/video_face_swap_and_restore.json b/examples/video_face_swap_and_restore.json new file mode 100644 index 0000000..8eab3f0 --- /dev/null +++ b/examples/video_face_swap_and_restore.json @@ -0,0 +1,229 @@ +{ + "last_node_id": 9, + "last_link_id": 10, + "nodes": [ + { + "id": 8, + "type": "FacelessSaveVideo", + "pos": [ + 1752, + 449 + ], + "size": { + "0": 210, + "1": 26 + }, + "flags": {}, + "order": 4, + "mode": 0, + "inputs": [ + { + "name": "video", + "type": "FACELESS_VIDEO", + "link": 7 + } + ], + "properties": { + "Node name for S&R": "FacelessSaveVideo" + } + }, + { + "id": 1, + "type": "LoadImage", + "pos": [ + 327, + 133 + ], + "size": { + "0": 315, + "1": 314 + }, + "flags": {}, + "order": 0, + "mode": 0, + "outputs": [ + { + "name": "IMAGE", + "type": "IMAGE", + "links": [ + 9 + ], + "shape": 3, + "slot_index": 0 + }, + { + "name": "MASK", + "type": "MASK", + "links": null, + "shape": 3 + } + ], + "properties": { + "Node name for S&R": "LoadImage" + }, + "widgets_values": [ + "majic-girl.jpeg", + "image" + ] + }, + { + "id": 9, + "type": "FacelessLoadVideo", + "pos": [ + 386, + 682 + ], + "size": { + "0": 210, + "1": 154 + }, + "flags": {}, + "order": 1, + "mode": 0, + "outputs": [ + { + "name": "video", + "type": "FACELESS_VIDEO", + "links": [ + 10 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FacelessLoadVideo" + }, + "widgets_values": [ + "pexels-girl.mp4", + true, + -1, + -1, + "image" + ] + }, + { + "id": 7, + "type": "FacelessVideoFaceRestore", + "pos": [ + 1275, + 446 + ], + "size": { + "0": 315, + "1": 58 + }, + "flags": {}, + "order": 3, + "mode": 0, + "inputs": [ + { + "name": "video", + "type": "FACELESS_VIDEO", + "link": 8 + } + ], + "outputs": [ + { + "name": "video", + "type": "FACELESS_VIDEO", + "links": [ + 7 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FacelessVideoFaceRestore" + }, + "widgets_values": [ + "gfpgan_1.4.onnx" + ] + }, + { + "id": 6, + "type": "FacelessVideoFaceSwap", + "pos": [ + 830, + 443 + ], + "size": { + "0": 315, + "1": 126 + }, + "flags": {}, + "order": 2, + "mode": 0, + "inputs": [ + { + "name": "source_image", + "type": "IMAGE", + "link": 9 + }, + { + "name": "target_video", + "type": "FACELESS_VIDEO", + "link": 10 + } + ], + "outputs": [ + { + "name": "video", + "type": "FACELESS_VIDEO", + "links": [ + 8 + ], + "shape": 3, + "slot_index": 0 + } + ], + "properties": { + "Node name for S&R": "FacelessVideoFaceSwap" + }, + "widgets_values": [ + "inswapper_128.onnx", + "yoloface_8n.onnx", + "arcface_w600k_r50.onnx" + ] + } + ], + "links": [ + [ + 7, + 7, + 0, + 8, + 0, + "FACELESS_VIDEO" + ], + [ + 8, + 6, + 0, + 7, + 0, + "FACELESS_VIDEO" + ], + [ + 9, + 1, + 0, + 6, + 0, + "IMAGE" + ], + [ + 10, + 9, + 0, + 6, + 1, + "FACELESS_VIDEO" + ] + ], + "groups": [], + "config": {}, + "extra": {}, + "version": 0.4 +} \ No newline at end of file diff --git a/faceless/nodes/globals.py b/faceless/nodes/globals.py index d1bded0..404f2ae 100644 --- a/faceless/nodes/globals.py +++ b/faceless/nodes/globals.py @@ -1,7 +1,7 @@ from .nodes_load_video import NodesLoadVideo from .nodes_load_video_url import NodesLoadVideoUrl from .nodes_load_image_url import NodesLoadImageUrl -from .nodes_load_frames import NodesLoadFrames +from .nodes_load_video_images import NodesLoadVideoImages from .nodes_save_video import NodesSaveVideo from .nodes_merge_videos import NodesMergeVideos from .nodes_upload_video import NodesUploadVideo @@ -15,7 +15,7 @@ from .nodes_video_remove_background import NodesVideoRemoveBackground NODE_CLASS_MAPPINGS = { "FacelessLoadVideo": NodesLoadVideo, - "FacelessLoadFrames": NodesLoadFrames, + "FacelessLoadVideoImages": NodesLoadVideoImages, "FacelessLoadImageUrl": NodesLoadImageUrl, "FacelessLoadVideoUrl": NodesLoadVideoUrl, "FacelessMergeVideos": NodesMergeVideos, @@ -33,7 +33,7 @@ NODE_CLASS_MAPPINGS = { NODE_DISPLAY_NAME_MAPPINGS = { "FacelessLoadVideo": "Load Video", - "FacelessLoadFrames": "Load Frames", + "FacelessLoadVideoImages": "Load Images (Video)", "FacelessLoadImageUrl": "Load Image (Url)", "FacelessLoadVideoUrl": "Load Video (Url)", "FacelessMergeVideos": "Merge Videos", diff --git a/faceless/nodes/nodes_load_frames.py b/faceless/nodes/nodes_load_video_images.py similarity index 95% rename from faceless/nodes/nodes_load_frames.py rename to faceless/nodes/nodes_load_video_images.py index baec9de..e90d0a2 100644 --- a/faceless/nodes/nodes_load_frames.py +++ b/faceless/nodes/nodes_load_video_images.py @@ -7,7 +7,7 @@ import numpy as np from ..filesystem import is_image from ..typing import FacelessVideo -class NodesLoadFrames: +class NodesLoadVideoImages: @classmethod def INPUT_TYPES(cls): return { @@ -18,6 +18,7 @@ class NodesLoadFrames: CATEGORY = "faceless" RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("images",) FUNCTION = "load_frames" def load_frames(self, video: FacelessVideo):