Initial commit

This commit is contained in:
henryruhs
2025-06-30 18:12:45 +02:00
commit 83d3ccdc7e
14 changed files with 245 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_size = 4
indent_style = tab
trim_trailing_whitespace = true
+5
View File
@@ -0,0 +1,5 @@
[flake8]
select = E22, E23, E24, E27, E3, E4, E7, F, I1, I2
plugins = flake8-import-order
application_import_names = facefusion
import-order-style = pycharm
+1
View File
@@ -0,0 +1 @@
custom: [ buymeacoffee.com/facefusion, ko-fi.com/facefusion ]
+19
View File
@@ -0,0 +1,19 @@
name: ci
on: [ push, pull_request ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install flake8
- run: pip install flake8-import-order
- run: pip install mypy
- run: flake8 facefusion_api
- run: mypy facefusion_api
+3
View File
@@ -0,0 +1,3 @@
__pycache__
.idea
.vscode
+3
View File
@@ -0,0 +1,3 @@
OpenRAIL-S license
Copyright (c) 2025 Henry Ruhs
+7
View File
@@ -0,0 +1,7 @@
FaceFusion ComfyUI
==================
> Industry leading face manipulation platform.
[![Build Status](https://img.shields.io/github/actions/workflow/status/facefusion/facefusion-comfyui/ci.yml.svg?branch=master)](https://github.com/facefusion/facefusion-comfyui/actions?query=workflow:ci)
![License](https://img.shields.io/badge/license-OpenRAIL--S-green)
+10
View File
@@ -0,0 +1,10 @@
from .facefusion_api import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
from install import install
install()
__all__ =\
[
'NODE_CLASS_MAPPINGS',
'NODE_DISPLAY_NAME_MAPPINGS'
]
+14
View File
@@ -0,0 +1,14 @@
from .core import SwapFaceImage, SwapFaceVideo
from .types import NodeClassMapping, NodeDisplayNameMapping
NODE_CLASS_MAPPINGS : NodeClassMapping =\
{
'SwapFaceImage': SwapFaceImage,
'SwapFaceVideo': SwapFaceVideo
}
NODE_DISPLAY_NAME_MAPPINGS : NodeDisplayNameMapping =\
{
'SwapFaceImage': 'Image Swap Face',
'SwapFaceVideo': 'Video Swap Face'
}
+151
View File
@@ -0,0 +1,151 @@
from concurrent.futures import ThreadPoolExecutor
from functools import partial
from io import BytesIO
from typing import Tuple
import torch
from comfy.comfy_types import IO
from comfy_api.input_impl.video_types import VideoFromComponents
from comfy_api.util import VideoComponents
from comfy_api_nodes.apinode_utils import bytesio_to_image_tensor, tensor_to_bytesio
from httpx import Client as HttpClient, Headers
from torch import Tensor
from .types import FaceSwapperModel, NodeInputTypes
class SwapFaceImage:
@classmethod
def INPUT_TYPES(cls) -> NodeInputTypes:
return\
{
'required':
{
'source_image': (IO.IMAGE,),
'target_image': (IO.IMAGE,),
'api_token':
(
'STRING',
{
'default': '0'
}
),
'face_swapper_model':
(
[
'hyperswap_1a_256',
'hyperswap_1b_256',
'hyperswap_1c_256'
],
{
'default': 'hyperswap_1c_256'
}
)
}
}
RETURN_TYPES = (IO.IMAGE,)
FUNCTION = 'process'
CATEGORY = 'FaceFusion API'
@staticmethod
def process(source_image : Tensor, target_image : Tensor, api_token : str, face_swapper_model : FaceSwapperModel) -> Tuple[Tensor]:
output_tensor: Tensor = SwapFaceImage.swap_face(source_image, target_image, api_token, face_swapper_model)
return (output_tensor,)
@staticmethod
def swap_face(source_tensor : Tensor, target_tensor, api_token : str, face_swapper_model : FaceSwapperModel) -> Tensor:
source_buffer : BytesIO = tensor_to_bytesio(source_tensor, mime_type = 'image/webp')
target_buffer : BytesIO = tensor_to_bytesio(target_tensor, mime_type = 'image/webp')
url = 'https://api.facefusion.io/inferences/swap-face'
files =\
{
'source': ('source.webp', source_buffer, 'image/webp'),
'target': ('target.webp', target_buffer, 'image/webp'),
}
data =\
{
'face_swapper_model': face_swapper_model,
}
headers = Headers()
if api_token:
headers['X-Token'] = api_token
with HttpClient(timeout = 10) as http_client:
response = http_client.post(url, headers = headers, files = files, data = data)
output_buffer = BytesIO(response.content)
output_tensor = bytesio_to_image_tensor(output_buffer)
return output_tensor
class SwapFaceVideo:
@classmethod
def INPUT_TYPES(cls) -> NodeInputTypes:
return\
{
'required':
{
'source_image': (IO.IMAGE,),
'target_video': (IO.VIDEO,),
'api_token':
(
'STRING',
{
'default': '0'
}
),
'face_swapper_model':
(
[
'hyperswap_1a_256',
'hyperswap_1b_256',
'hyperswap_1c_256'
],
{
'default': 'hyperswap_1a_256'
}
),
'max_workers':
(
'INT',
{
'default': 16,
'min': 1,
'max': 32
}
)
}
}
RETURN_TYPES = (IO.VIDEO,)
FUNCTION = 'process'
CATEGORY = 'FaceFusion API'
@staticmethod
def process(source_image : Tensor, target_video : VideoFromComponents, api_token : str, face_swapper_model : FaceSwapperModel, max_workers : int) -> Tuple[VideoFromComponents]:
video_components = target_video.get_components()
output_tensors = []
swap_face = partial(
SwapFaceImage.swap_face,
source_image,
api_token = api_token,
face_swapper_model = face_swapper_model
)
with ThreadPoolExecutor(max_workers = max_workers) as executor:
for temp_tensor in executor.map(swap_face, video_components.images):
temp_tensor = temp_tensor.squeeze(0)[..., :3]
output_tensors.append(temp_tensor)
output_video_components = VideoComponents(
images = torch.stack(output_tensors),
audio = video_components.audio,
frame_rate = video_components.frame_rate
)
output_video = VideoFromComponents(output_video_components)
return (output_video,)
+9
View File
@@ -0,0 +1,9 @@
from typing import Any, Dict, Literal, Type, TypeAlias
NodeInputTypes : TypeAlias = Dict[str, Any]
NodeClassMapping : TypeAlias = Dict[str, Type]
NodeDisplayNameMapping : TypeAlias = Dict[str, str]
Image : TypeAlias = Any
FaceSwapperModel = Literal['hyperswap_1a_256', 'hyperswap_1b_256', 'hyperswap_1c_256']
+6
View File
@@ -0,0 +1,6 @@
import subprocess
from shutil import which
def install() -> None:
subprocess.run([ which('pip'), 'install', '-r', 'requirements.txt' ])
+7
View File
@@ -0,0 +1,7 @@
[mypy]
check_untyped_defs = True
disallow_any_generics = True
disallow_untyped_calls = True
disallow_untyped_defs = True
ignore_missing_imports = True
strict_optional = False
+2
View File
@@ -0,0 +1,2 @@
httpx==0.28.1