Compare commits

..
13 Commits
Author SHA1 Message Date
Henry RuhsandGitHub 3f81a8a784 patch-3.7.1 (#1178)
* patch 3.7.1

* fix bug for 2 processors on image to image pipeline (#1177)

* reduce default value of target_frame_amount

* restore old performance

* restore old performance

* restore old performance

* restore old performance
2026-07-05 16:43:36 +02:00
henryruhs b8f80460cf last minute change to frame distribution 2026-06-30 16:23:09 +02:00
henryruhs 5305968de7 fix wording 2026-06-30 16:09:22 +02:00
henryruhs b6d740e64a fix wording 2026-06-30 16:03:42 +02:00
henryruhs 75bd742858 update preview 2026-06-30 15:12:26 +02:00
a2cbfd73b1 3.7.0 (#1175)
* mark as next, introduce dynamic scale for face debugger

* use latest onnxruntime

* update within Gradio 5

* Remove system memory limit (#986)

* remove system memory limit from ui

* remove system memory limit from args.py

* flatten the face store

* prevent countless importlib.import_module calls

* remove --onnxruntime from install.py

* remove --onnxruntime from install.py

* resolve static inference providers to fix macos (#1127)

* resolve static inference providers to fix macos

* fix lint

* restore old behaviour

* restore old behaviour

* handle ghost and uniface as well

* adjust condition for ghost and uniface

* fix Gradio gallery styles

* remove face store (#1132)

* fix dataflow in streamer

* Face selector auto mode (#1137)

* introduce face selector auto mode

* introduce face selector auto mode

* introduce face selector auto mode

* correct way is to pass source_vision_frames

* make the world a better place

* fix dataflow in faceswapper, no read of files withing inner methods (#1148)

* fix dataflow in faceswapper, no read of files withing inner methods

* fix lint

* adjust code more

* adjust code more

* bring back the face store but for source and reference only (#1149)

* bring back the face store but for source and reference only

* fix ci

* minor improvement

* guard for tobytes()

* drop condition in select_faces()

* Replace CONFIG_PARSER global with @lru_cache (#1147)

* remove global config_parser

* fix import order

* remove lambda

* remove unused block

* optimize app context detection

* decouple common modules from core (#1152)

* decouple common modules from core

* remove that nonsense

* remove that nonsense

* minor adjustment to workflows

* Tag HEVC output as hvc1 and move moov atom to the front (#1153)

* Tag HEVC output as hvc1 and move moov atom to the front

ffmpeg defaults HEVC in MP4 to the 'hev1' sample entry and leaves the moov
atom at the tail. Apple players (QuickTime, Finder QuickLook) refuse to decode
'hev1' and stall reading a tail-placed moov on large files, so hevc_nvenc /
libx265 renders cannot be previewed on macOS.

- add ffmpeg_builder.set_video_tag(): emit `-tag:v hvc1` for every HEVC
  encoder (libx265, hevc_nvenc, hevc_amf, hevc_qsv, hevc_videotoolbox).
  Applied in merge_video where the encoder is known; `-c:v copy` in the audio
  mux / concat steps preserves the tag.
- add ffmpeg_builder.set_faststart(): emit `-movflags +faststart`, applied in
  restore_audio / replace_audio / concat_video which write the final output.

H.264 and other codecs are left untouched. Verified on a real hevc_nvenc
render: hev1 hung QuickLook (no thumbnail); after the patch the file is hvc1
with a front-placed moov and QuickLook generates a thumbnail.

* Restrict hvc1 tag and faststart to quicktime containers

Gate set_video_tag / set_faststart on the output container format
(m4v, mov, mp4) via get_file_format(), so non-quicktime muxers no longer
receive -tag:v hvc1 / -movflags +faststart. Trim test_set_video_tag to a
single positive and negative assertion.

Addresses review on #1153.

* Move hvc1 tag and faststart gates into ffmpeg_builder

Rename set_video_tag / set_faststart to conditional_* and push the
container-format gate (m4v, mov, mp4) inside the builders, keeping
ffmpeg.py free of inline conditionals. Matches the set_image_quality
pattern. Addresses review on #1153.

* post cleanup after merge

* Pack target frames (#1158)

* pack target frames

* add todos

* add todos, resolve todos

* resolve todos

* change names

* revert to single target frame for select faces

* fix lint

* return empty frame

* get() have no default

* Fix trim (#1162)

* fix trim

* fix trim

* rename ffmpeg builder method

* rename to temp_frame_set and temp_frame_pattern

---------

Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com>
Co-authored-by: Harisreedhar <46858047+harisreedhar@users.noreply.github.com>

* Implement face tracker (#1163)

* add face tracker

* change get_nearest_track_face -> get_nearest_track_index

* create face_creator.py and move methods around

* add type FaceTrack

* naming

* remove iou test, don't belong there

* fix spaces

* rename to interpolate_points

* rename to find_best_face_track

* just track_faces

* cleanp

* previous next naming

* remove >= and >=

* rename

* remove helper from test and use face from source.jpg

* make get_anchor_indices more readable

* track_faces() call before and is forwarded to select_faces

* change to interpolate_faces

* rename methods

* rename methods

* rename variables

* remove dtype

* move face_anlyser -> face_creator

* claenup face_creator.py

* move tests to dedicated test face detector

* move tracking inside select_faces

* simplify face_tracker (#1165)

* minor renaming

* improve face_tracker test (#1166)

* improve face_tracker test

* cleanup

* Add target frame amount (#1167)

* introduce --target-frame-amount

* add ui

* make track_faces conditional

* update choices.py

* fix []

* rename component file to frame_process.py

* fix track preview (#1168)

* introduce face origin (#1169)

* add guard to prevent failure

* show and hide voice extractor according to lip syncer

* rename average_face_coordinates to average_face_geometry

* use static faces for select_faces()

* face store with lock (#1171)

* face store with lock

* face store with lock

* remove refill color from bbox

* adjust tests and handle frame_position proper way

* enforce similar naming

* introduce face tracker score

* introduce face tracker score

* fix/audio-trim-alignment (#1173)

* fix audio offset

* fix audio offset

* remove reference_frame_number check

---------

Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com>

* reduce face tracker score from 0 to 0.5

* mark as 3.7.0

* make face tracker stateless

---------

Co-authored-by: Harisreedhar <46858047+harisreedhar@users.noreply.github.com>
Co-authored-by: kazuki nakai <kazuki.nakai@agiletec.net>
Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com>
2026-06-30 15:00:02 +02:00
Henry RuhsandGitHub 5b7d145aa7 Patch 3.6.1 (#1078)
* Avast intercepts ssl cert and breaks curl

* modernize dependencies

* fix sanitizer for int range

* comment out tests

* disable testing for CI

* disable testing for CI
2026-04-19 21:17:39 +02:00
Henry RuhsandGitHub 519360bcd6 Update LICENSE.md 2026-04-01 09:10:08 +02:00
57fcb86b82 3.6.0 (#1062)
* mark as next

* add fran model

* add support for corridor key (#1060)

* introduce despill color

* simplify the apply dispill color

* finalize naming for both fill and despill

* follow vision_frame convension

* patch fran model

* adjust fran urls

* Feat/dynamic env setup (#1061)

* dynamic environment setup

* dynamic environment setup

* fix fran model

* prevent directml using incompatible corridor_key model

* fix environment setup for windows

* switch to corridor_key_1024 and corridor_key_2048

* switch to corridor_key_1024 and corridor_key_2048

* mark it as 3.6.0

* rename environment to conda

* rename environment to conda

* fix testing for face analyser

* some background remove cosmetics

* some background remove cosmetics

* some background remove cosmetics

* update preview

---------

Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com>
2026-03-16 15:08:43 +01:00
henryruhs 2cc05d4fba update licenses 2026-03-09 21:24:53 +01:00
Henry RuhsandGitHub a498f3d618 Patch 3.5.4 (#1055)
* remove insecure flag from curl

* eleminate repating definitons

* limit processors and ui layouts by choices

* follow couple of v4 standards

* use more secure mkstemp

* dynamic cache path for execution providers

* fix benchmarker, prevent path traveling via job-id

* fix order in execution provider choices

* resort by prioroty

* introduce support for QNN

* close file description for Windows to stop crying

* prevent ConnectionResetError under windows

* needed for nested .caches directory as onnxruntime does not create it

* different approach to silent asyncio

* update dependencies

* simplify the name to just inference providers

* switch to trt_builder_optimization_level 4
2026-03-08 11:00:45 +01:00
c7976ec9d4 Fix list literal spacing to use [ x, y ] style (#1044)
Enforce consistent space inside square brackets for all list literals
across source and test files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-18 09:18:03 +01:00
Henry RuhsandGitHub 8801668562 3.5.3
* honor webcam resolution to avoid stripe mismatch, update dependencies

* avoid version conflicts

* enforce prores video extraction to 8 bit

* make the installer more robust on execution switch

* make the installer more robust on execution switch

* improve the installer env handling

* different approach to handle env
2026-02-11 09:35:08 +01:00
233 changed files with 7001 additions and 6442 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+2 -4
View File
@@ -33,9 +33,8 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python install.py --onnxruntime default --skip-conda
- run: python install.py default --skip-conda
- run: pip install pytest
- run: pip install httpx
- run: pytest
report:
needs: test
@@ -49,11 +48,10 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python install.py --onnxruntime default --skip-conda
- run: python install.py default --skip-conda
- run: pip install coveralls
- run: pip install pytest
- run: pip install pytest-cov
- run: pip install httpx
- run: pytest tests --cov facefusion
- run: coveralls --service github
env:
+1 -1
View File
@@ -1,3 +1,3 @@
OpenRAIL-AS license
Copyright (c) 2025 Henry Ruhs
Copyright (c) 2026 Henry Ruhs
+7 -1
View File
@@ -8,6 +8,12 @@ FaceFusion
![License](https://img.shields.io/badge/license-OpenRAIL--AS-green)
Preview
-------
![Preview](https://raw.githubusercontent.com/facefusion/facefusion/master/.github/preview.png?sanitize=true)
Installation
------------
@@ -28,10 +34,10 @@ options:
commands:
run run the program
headless-run run the program in headless mode
batch-run run the program in batch mode
force-download force automate downloads and exit
benchmark benchmark the program
api start the API server
job-list list jobs by status
job-create create a drafted job
job-submit submit a drafted job to become a queued job
+14 -8
View File
@@ -1,6 +1,3 @@
[workflow]
workflow =
[paths]
temp_path =
jobs_path =
@@ -35,6 +32,9 @@ reference_face_position =
reference_face_distance =
reference_frame_number =
[face_tracker]
face_tracker_score =
[face_masker]
face_occluder_model =
face_parser_model =
@@ -51,6 +51,10 @@ voice_extractor_model =
trim_frame_start =
trim_frame_end =
temp_frame_format =
keep_temp =
[frame_distribution]
target_frame_amount =
[output_creation]
output_image_quality =
@@ -69,7 +73,8 @@ processors =
age_modifier_model =
age_modifier_direction =
background_remover_model =
background_remover_color =
background_remover_fill_color =
background_remover_despill_color =
deep_swapper_model =
deep_swapper_morph =
expression_restorer_model =
@@ -105,6 +110,11 @@ frame_enhancer_blend =
lip_syncer_model =
lip_syncer_weight =
[uis]
open_browser =
ui_layouts =
ui_workflow =
[download]
download_providers =
download_scope =
@@ -114,10 +124,6 @@ benchmark_mode =
benchmark_resolutions =
benchmark_cycle_count =
[api]
api_host =
api_port =
[execution]
execution_device_ids =
execution_providers =
+2 -1
View File
@@ -4,7 +4,8 @@ import os
os.environ['OMP_NUM_THREADS'] = '1'
from facefusion import core
from facefusion import conda, core
if __name__ == '__main__':
conda.setup()
core.cli()
-15
View File
@@ -1,15 +0,0 @@
from typing import Optional
from starlette.datastructures import Headers
from starlette.types import Scope
def get_sec_websocket_protocol(scope : Scope) -> Optional[str]:
protocol_header = Headers(scope = scope).get('Sec-WebSocket-Protocol')
if protocol_header:
protocol, _, _ = protocol_header.partition(',')
return protocol.strip()
return None
-50
View File
@@ -1,50 +0,0 @@
from typing import Optional
from facefusion.audio import detect_audio_duration
from facefusion.filesystem import is_audio, is_image, is_video
from facefusion.types import AudioMetadata, ImageMetadata, MediaType, VideoMetadata
from facefusion.vision import count_video_frame_total, detect_image_resolution, detect_video_duration, detect_video_fps, detect_video_resolution
def extract_audio_metadata(file_path : str) -> AudioMetadata:
metadata : AudioMetadata =\
{
'duration': detect_audio_duration(file_path),
'sample_rate': 0,
'frame_total': 0,
'channels': 0,
'format': ''
}
return metadata
def extract_image_metadata(file_path : str) -> ImageMetadata:
resolution = detect_image_resolution(file_path)
metadata : ImageMetadata =\
{
'resolution': resolution if resolution else (0, 0)
}
return metadata
def extract_video_metadata(file_path : str) -> VideoMetadata:
resolution = detect_video_resolution(file_path)
fps = detect_video_fps(file_path)
metadata : VideoMetadata =\
{
'duration': detect_video_duration(file_path),
'frame_total': count_video_frame_total(file_path),
'fps': fps if fps else 0.0,
'resolution': resolution if resolution else (0, 0)
}
return metadata
def detect_media_type(file_path : str) -> Optional[MediaType]:
if is_audio(file_path):
return 'audio'
if is_image(file_path):
return 'image'
if is_video(file_path):
return 'video'
return None
-92
View File
@@ -1,92 +0,0 @@
import os
import uuid
from datetime import datetime, timedelta
from typing import List, Optional, cast
from facefusion.apis.asset_helper import detect_media_type, extract_audio_metadata, extract_image_metadata, extract_video_metadata
from facefusion.filesystem import get_file_format, get_file_name
from facefusion.types import AssetId, AssetSet, AssetStore, AssetType, AudioAsset, AudioFormat, ImageAsset, ImageFormat, SessionId, VideoAsset, VideoFormat
ASSET_STORE : AssetStore = {}
def create_asset(session_id : SessionId, asset_type : AssetType, asset_path : str) -> Optional[AudioAsset | ImageAsset | VideoAsset]:
asset_id = str(uuid.uuid4())
asset_name = get_file_name(asset_path)
asset_format = get_file_format(asset_path)
asset_size = os.path.getsize(asset_path)
media_type = detect_media_type(asset_path)
created_at = datetime.now()
expires_at = created_at + timedelta(hours = 2)
if session_id not in ASSET_STORE:
ASSET_STORE[session_id] = {}
if media_type == 'audio':
ASSET_STORE[session_id][asset_id] = cast(AudioAsset,
{
'id': asset_id,
'created_at': created_at,
'expires_at': expires_at,
'type': asset_type,
'media': media_type,
'name': asset_name,
'format': cast(AudioFormat, asset_format),
'size': asset_size,
'path': asset_path,
'metadata': extract_audio_metadata(asset_path)
})
if media_type == 'image':
ASSET_STORE[session_id][asset_id] = cast(ImageAsset,
{
'id': asset_id,
'created_at': created_at,
'expires_at': expires_at,
'type': asset_type,
'media': media_type,
'name': asset_name,
'format': cast(ImageFormat, asset_format),
'size': asset_size,
'path': asset_path,
'metadata': extract_image_metadata(asset_path)
})
if media_type == 'video':
ASSET_STORE[session_id][asset_id] = cast(VideoAsset,
{
'id': asset_id,
'created_at': created_at,
'expires_at': expires_at,
'type': asset_type,
'media': media_type,
'name': asset_name,
'format': cast(VideoFormat, asset_format),
'size': asset_size,
'path': asset_path,
'metadata': extract_video_metadata(asset_path)
})
return ASSET_STORE[session_id].get(asset_id)
def get_assets(session_id : SessionId) -> Optional[AssetSet]:
return ASSET_STORE.get(session_id)
def get_asset(session_id : SessionId, asset_id : AssetId) -> Optional[AudioAsset | ImageAsset | VideoAsset]:
if session_id in ASSET_STORE:
return ASSET_STORE.get(session_id).get(asset_id)
return None
def delete_assets(session_id : SessionId, asset_ids : List[AssetId]) -> None:
if session_id in ASSET_STORE:
for asset_id in asset_ids:
if asset_id in ASSET_STORE.get(session_id):
del ASSET_STORE[session_id][asset_id]
return None
def clear() -> None:
ASSET_STORE.clear()
-162
View File
@@ -1,162 +0,0 @@
from typing import Any, Dict
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.status import HTTP_200_OK
import facefusion.choices
from facefusion.execution import get_available_execution_providers
from facefusion.ffmpeg import get_available_encoder_set
from facefusion.processors.modules.face_debugger import choices as face_debugger_choices
from facefusion.processors.modules.face_enhancer import choices as face_enhancer_choices
from facefusion.processors.modules.face_swapper import choices as face_swapper_choices
from facefusion.processors.modules.frame_enhancer import choices as frame_enhancer_choices
async def get_choices(request : Request) -> JSONResponse:
available_execution_providers = get_available_execution_providers()
available_encoder_set = get_available_encoder_set()
choices_data : Dict[str, Any] =\
{
'face_detector_models': facefusion.choices.face_detector_models,
'face_detector_set': facefusion.choices.face_detector_set,
'face_landmarker_models': facefusion.choices.face_landmarker_models,
'face_selector_modes': facefusion.choices.face_selector_modes,
'face_selector_orders': facefusion.choices.face_selector_orders,
'face_selector_genders': facefusion.choices.face_selector_genders,
'face_selector_races': facefusion.choices.face_selector_races,
'face_occluder_models': facefusion.choices.face_occluder_models,
'face_parser_models': facefusion.choices.face_parser_models,
'face_mask_types': facefusion.choices.face_mask_types,
'face_mask_areas': facefusion.choices.face_mask_areas,
'face_mask_regions': facefusion.choices.face_mask_regions,
'voice_extractor_models': facefusion.choices.voice_extractor_models,
'workflows': facefusion.choices.workflows,
'audio_formats': facefusion.choices.audio_formats,
'image_formats': facefusion.choices.image_formats,
'video_formats': facefusion.choices.video_formats,
'temp_frame_formats': facefusion.choices.temp_frame_formats,
'output_audio_encoders': available_encoder_set.get('audio'),
'output_video_encoders': available_encoder_set.get('video'),
'output_video_presets': facefusion.choices.output_video_presets,
'execution_providers': available_execution_providers,
'video_memory_strategies': facefusion.choices.video_memory_strategies,
'log_levels': facefusion.choices.log_levels,
'face_swapper_models': face_swapper_choices.face_swapper_models,
'face_swapper_set': face_swapper_choices.face_swapper_set,
'face_enhancer_models': face_enhancer_choices.face_enhancer_models,
'frame_enhancer_models': frame_enhancer_choices.frame_enhancer_models,
'face_debugger_items': face_debugger_choices.face_debugger_items,
'face_detector_angles': list(facefusion.choices.face_detector_angles),
'face_detector_score_range':
{
'min': min(facefusion.choices.face_detector_score_range),
'max': max(facefusion.choices.face_detector_score_range),
'step': facefusion.choices.face_detector_score_range[1] - facefusion.choices.face_detector_score_range[0]
},
'face_landmarker_score_range':
{
'min': min(facefusion.choices.face_landmarker_score_range),
'max': max(facefusion.choices.face_landmarker_score_range),
'step': facefusion.choices.face_landmarker_score_range[1] - facefusion.choices.face_landmarker_score_range[0]
},
'face_mask_blur_range':
{
'min': min(facefusion.choices.face_mask_blur_range),
'max': max(facefusion.choices.face_mask_blur_range),
'step': facefusion.choices.face_mask_blur_range[1] - facefusion.choices.face_mask_blur_range[0]
},
'face_mask_padding_range':
{
'min': min(facefusion.choices.face_mask_padding_range),
'max': max(facefusion.choices.face_mask_padding_range),
'step': 1
},
'face_selector_age_range':
{
'min': min(facefusion.choices.face_selector_age_range),
'max': max(facefusion.choices.face_selector_age_range),
'step': 1
},
'reference_face_distance_range':
{
'min': min(facefusion.choices.reference_face_distance_range),
'max': max(facefusion.choices.reference_face_distance_range),
'step': facefusion.choices.reference_face_distance_range[1] - facefusion.choices.reference_face_distance_range[0]
},
'output_image_quality_range':
{
'min': min(facefusion.choices.output_image_quality_range),
'max': max(facefusion.choices.output_image_quality_range),
'step': 1
},
'output_image_scale_range':
{
'min': min(facefusion.choices.output_image_scale_range),
'max': max(facefusion.choices.output_image_scale_range),
'step': facefusion.choices.output_image_scale_range[1] - facefusion.choices.output_image_scale_range[0]
},
'output_audio_quality_range':
{
'min': min(facefusion.choices.output_audio_quality_range),
'max': max(facefusion.choices.output_audio_quality_range),
'step': 1
},
'output_audio_volume_range':
{
'min': min(facefusion.choices.output_audio_volume_range),
'max': max(facefusion.choices.output_audio_volume_range),
'step': 1
},
'output_video_quality_range':
{
'min': min(facefusion.choices.output_video_quality_range),
'max': max(facefusion.choices.output_video_quality_range),
'step': 1
},
'output_video_scale_range':
{
'min': min(facefusion.choices.output_video_scale_range),
'max': max(facefusion.choices.output_video_scale_range),
'step': facefusion.choices.output_video_scale_range[1] - facefusion.choices.output_video_scale_range[0]
},
'execution_thread_count_range':
{
'min': min(facefusion.choices.execution_thread_count_range),
'max': max(facefusion.choices.execution_thread_count_range),
'step': 1
},
'face_detector_margin_range':
{
'min': min(facefusion.choices.face_detector_margin_range),
'max': max(facefusion.choices.face_detector_margin_range),
'step': 1
},
'face_swapper_weight_range':
{
'min': min(face_swapper_choices.face_swapper_weight_range),
'max': max(face_swapper_choices.face_swapper_weight_range),
'step': face_swapper_choices.face_swapper_weight_range[1] - face_swapper_choices.face_swapper_weight_range[0]
},
'face_enhancer_blend_range':
{
'min': min(face_enhancer_choices.face_enhancer_blend_range),
'max': max(face_enhancer_choices.face_enhancer_blend_range),
'step': 1
},
'face_enhancer_weight_range':
{
'min': min(face_enhancer_choices.face_enhancer_weight_range),
'max': max(face_enhancer_choices.face_enhancer_weight_range),
'step': face_enhancer_choices.face_enhancer_weight_range[1] - face_enhancer_choices.face_enhancer_weight_range[0]
},
'frame_enhancer_blend_range':
{
'min': min(frame_enhancer_choices.frame_enhancer_blend_range),
'max': max(frame_enhancer_choices.frame_enhancer_blend_range),
'step': 1
}
}
return JSONResponse(choices_data, status_code = HTTP_200_OK)
-47
View File
@@ -1,47 +0,0 @@
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.routing import Route, WebSocketRoute
from facefusion.apis.choices import get_choices
from facefusion.apis.endpoints.assets import delete_asset, delete_assets, get_asset, get_assets, upload_asset
from facefusion.apis.endpoints.ping import websocket_ping
from facefusion.apis.endpoints.session import create_session, create_session_guard, destroy_session, get_session, refresh_session
from facefusion.apis.endpoints.state import get_state, set_state
from facefusion.apis.endpoints.stream import delete_stream, post_stream, websocket_stream
from facefusion.apis.metrics import websocket_metrics
from facefusion.apis.remote import remote
from facefusion.apis.timeline import get_timeline
from facefusion.apis.version import create_version_guard
def create_api() -> Starlette:
version_guard = Middleware(create_version_guard)
session_guard = Middleware(create_session_guard)
routes =\
[
Route('/session', create_session, methods = [ 'POST' ], middleware = [ version_guard ]),
Route('/session', get_session, methods = [ 'GET' ], middleware = [ version_guard, session_guard ]),
Route('/session', refresh_session, methods = [ 'PUT' ], middleware = [ version_guard ]),
Route('/session', destroy_session, methods = [ 'DELETE' ], middleware = [ version_guard, session_guard ]),
Route('/state', get_state, methods = [ 'GET' ], middleware = [ version_guard, session_guard ]),
Route('/state', set_state, methods = [ 'PUT' ], middleware = [ version_guard, session_guard ]),
Route('/assets', get_assets, methods = [ 'GET' ], middleware = [ version_guard, session_guard ]),
Route('/assets', upload_asset, methods = [ 'POST' ], middleware = [ version_guard, session_guard ]),
Route('/assets/{asset_id}', get_asset, methods = [ 'GET' ], middleware = [ version_guard, session_guard ]),
Route('/assets/{asset_id}', delete_asset, methods = [ 'DELETE' ], middleware = [ version_guard, session_guard ]),
Route('/assets', delete_assets, methods = [ 'DELETE' ], middleware = [ version_guard, session_guard ]),
Route('/choices', get_choices, methods = [ 'GET' ], middleware = [ version_guard, session_guard ]),
Route('/remote', remote, methods = [ 'POST' ], middleware = [ version_guard, session_guard ]),
Route('/timeline/{count:int}', get_timeline, methods = [ 'GET' ], middleware = [ version_guard, session_guard ]),
Route('/stream', post_stream, methods = [ 'POST' ], middleware = [ version_guard, session_guard ]),
Route('/stream', delete_stream, methods = [ 'DELETE' ], middleware = [ version_guard, session_guard ]),
WebSocketRoute('/stream', websocket_stream, middleware = [ version_guard, session_guard ]),
WebSocketRoute('/metrics', websocket_metrics, middleware = [ version_guard, session_guard ]),
WebSocketRoute('/ping', websocket_ping, middleware = [ version_guard, session_guard ])
]
api = Starlette(routes = routes)
api.add_middleware(CORSMiddleware, allow_origins = [ '*' ], allow_methods = [ '*' ], allow_headers = [ '*' ])
return api
-163
View File
@@ -1,163 +0,0 @@
import tempfile
from typing import Any, Dict, List, Optional
from starlette.datastructures import UploadFile
from starlette.requests import Request
from starlette.responses import FileResponse, JSONResponse, Response
from starlette.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND
from facefusion import session_manager
from facefusion.apis import asset_store
from facefusion.apis.asset_helper import detect_media_type
from facefusion.apis.endpoints.session import extract_access_token
from facefusion.filesystem import get_file_extension, remove_file
from facefusion.types import AudioAsset, ImageAsset, VideoAsset
def translate_asset(asset : AudioAsset | ImageAsset | VideoAsset) -> Optional[Dict[str, Any]]:
return\
{
'id': asset.get('id'),
'created_at': asset.get('created_at').isoformat(),
'expires_at': asset.get('expires_at').isoformat(),
'type': asset.get('type'),
'media_type': asset.get('media'),
'filename': asset.get('name'),
'format': asset.get('format'),
'size': asset.get('size'),
'metadata': asset.get('metadata')
}
async def upload_asset(request : Request) -> Response:
access_token = extract_access_token(request.scope)
session_id = session_manager.find_session_id(access_token)
asset_type = request.query_params.get('type')
if session_id and asset_type in [ 'source', 'target' ]:
form = await request.form()
upload_files = form.getlist('file')
asset_paths = await save_asset_files(upload_files) # type: ignore[arg-type]
if asset_paths:
asset_ids : List[str] = []
for asset_path in asset_paths:
asset = asset_store.create_asset(session_id, asset_type, asset_path) # type: ignore[arg-type]
asset_id = asset.get('id')
if asset_id:
asset_ids.append(asset_id)
if asset_ids:
if asset_type == 'target':
return JSONResponse(
{
'asset_id': asset_ids[0]
}, status_code = HTTP_201_CREATED)
return JSONResponse(
{
'asset_ids': asset_ids
}, status_code = HTTP_201_CREATED)
return Response(status_code = HTTP_400_BAD_REQUEST)
async def save_asset_files(upload_files : List[UploadFile]) -> List[str]:
asset_paths : List[str] = []
for upload_file in upload_files:
upload_file_extension = get_file_extension(upload_file.filename)
with tempfile.NamedTemporaryFile(suffix = upload_file_extension, delete = False) as temp_file:
while upload_chunk := await upload_file.read(1024):
temp_file.write(upload_chunk)
temp_file.flush()
media_type = detect_media_type(temp_file.name)
if media_type:
asset_paths.append(temp_file.name)
else:
remove_file(temp_file.name)
return asset_paths
async def get_assets(request : Request) -> Response:
access_token = extract_access_token(request.scope)
session_id = session_manager.find_session_id(access_token)
asset_type = request.query_params.get('type')
if session_id:
asset_set = asset_store.get_assets(session_id)
assets = []
if asset_set:
for asset in asset_set.values():
if not asset_type or asset.get('type') == asset_type:
assets.append(translate_asset(asset))
return JSONResponse(
{
'assets': assets,
'count': len(assets)
}, status_code = HTTP_200_OK)
return Response(status_code = HTTP_400_BAD_REQUEST)
async def get_asset(request : Request) -> Response:
access_token = extract_access_token(request.scope)
session_id = session_manager.find_session_id(access_token)
asset_id = request.path_params.get('asset_id')
action = request.query_params.get('action')
if session_id and asset_id:
asset = asset_store.get_asset(session_id, asset_id)
if asset:
if action == 'download':
return FileResponse(asset.get('path'), filename = asset.get('name'))
return JSONResponse(translate_asset(asset), status_code = HTTP_200_OK)
return Response(status_code = HTTP_404_NOT_FOUND)
async def delete_asset(request : Request) -> Response:
access_token = extract_access_token(request.scope)
session_id = session_manager.find_session_id(access_token)
asset_id = request.path_params.get('asset_id')
if session_id and asset_id:
asset_set = asset_store.get_assets(session_id)
if asset_set and asset_id in asset_set:
remove_file(asset_set.get(asset_id).get('path'))
asset_store.delete_assets(session_id, [ asset_id ])
return Response(status_code = HTTP_200_OK)
return Response(status_code = HTTP_404_NOT_FOUND)
async def delete_assets(request : Request) -> Response:
access_token = extract_access_token(request.scope)
session_id = session_manager.find_session_id(access_token)
body = await request.json()
asset_ids = body.get('asset_ids')
if session_id and asset_ids:
asset_set = asset_store.get_assets(session_id)
if asset_set:
for asset_id in asset_ids:
if asset_id in asset_set:
remove_file(asset_set.get(asset_id).get('path'))
asset_store.delete_assets(session_id, asset_ids)
return Response(status_code = HTTP_200_OK)
return Response(status_code = HTTP_404_NOT_FOUND)
-16
View File
@@ -1,16 +0,0 @@
from starlette.websockets import WebSocket
from facefusion.apis.api_helper import get_sec_websocket_protocol
async def websocket_ping(websocket : WebSocket) -> None:
subprotocol = get_sec_websocket_protocol(websocket.scope)
await websocket.accept(subprotocol = subprotocol)
try:
while True:
await websocket.receive()
except Exception:
pass
-148
View File
@@ -1,148 +0,0 @@
import os
import secrets
from typing import Optional
from starlette.datastructures import Headers
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_401_UNAUTHORIZED, HTTP_426_UPGRADE_REQUIRED
from starlette.types import ASGIApp, Receive, Scope, Send
from facefusion import session_context, session_manager, translator
from facefusion.apis.api_helper import get_sec_websocket_protocol
from facefusion.types import Token
async def create_session(request : Request) -> JSONResponse:
body = await request.json()
if not body.get('api_key') or body.get('api_key') == os.getenv('FACEFUSION_API_KEY'):
session_id = secrets.token_urlsafe(16)
session = session_manager.create_session()
session_context.set_session_id(session_id)
session_manager.set_session(session_id, session)
return JSONResponse(
{
'access_token': session.get('access_token'),
'refresh_token': session.get('refresh_token')
}, status_code = HTTP_201_CREATED)
return JSONResponse(
{
'message': translator.get('something_went_wrong', 'facefusion.apis')
}, status_code = HTTP_401_UNAUTHORIZED)
async def get_session(request : Request) -> JSONResponse:
access_token = extract_access_token(request.scope)
if access_token:
session_id = session_manager.find_session_id(access_token)
if session_id:
session = session_manager.get_session(session_id)
return JSONResponse(
{
'access_token': session.get('access_token'),
'refresh_token': session.get('refresh_token'),
'created_at': session.get('created_at').isoformat(),
'expires_at': session.get('expires_at').isoformat()
}, status_code = HTTP_200_OK)
return JSONResponse(
{
'message': translator.get('something_went_wrong', 'facefusion.apis')
}, status_code = HTTP_401_UNAUTHORIZED)
async def refresh_session(request : Request) -> JSONResponse:
body = await request.json()
for session_id, session in session_manager.SESSIONS.items():
if session.get('refresh_token') == body.get('refresh_token'):
__session__ = session_manager.create_session()
session_manager.set_session(session_id, __session__)
return JSONResponse(
{
'access_token': __session__.get('access_token'),
'refresh_token': __session__.get('refresh_token')
}, status_code = HTTP_200_OK)
return JSONResponse(
{
'message': translator.get('something_went_wrong', 'facefusion.apis')
}, status_code = HTTP_401_UNAUTHORIZED)
async def destroy_session(request : Request) -> JSONResponse:
access_token = extract_access_token(request.scope)
if access_token:
session_id = session_manager.find_session_id(access_token)
if session_id:
session_manager.clear_session(session_id)
return JSONResponse(
{
'message': translator.get('ok', 'facefusion.apis')
}, status_code = HTTP_200_OK)
return JSONResponse(
{
'message': translator.get('something_went_wrong', 'facefusion.apis')
}, status_code = HTTP_401_UNAUTHORIZED)
def create_session_guard(app : ASGIApp) -> ASGIApp:
async def middleware(scope : Scope, receive : Receive, send : Send) -> None:
access_token = extract_access_token(scope)
if access_token:
session_id = session_manager.find_session_id(access_token)
if session_id:
if session_manager.validate_session(session_id):
session_context.set_session_id(session_id)
return await app(scope, receive, send)
response = JSONResponse(
{
'message': translator.get('invalid_access_token', 'facefusion.apis')
}, status_code = HTTP_426_UPGRADE_REQUIRED)
return await response(scope, receive, send)
response = JSONResponse(
{
'message': translator.get('invalid_access_token', 'facefusion.apis')
}, status_code = HTTP_401_UNAUTHORIZED)
return await response(scope, receive, send)
return middleware
def extract_access_token(scope : Scope) -> Optional[Token]:
if scope.get('type') == 'http':
auth_header = Headers(scope = scope).get('Authorization')
if auth_header:
auth_prefix, _, access_token = auth_header.partition(' ')
if auth_prefix.lower() == 'bearer' and access_token:
return access_token
if scope.get('type') == 'websocket':
subprotocol = get_sec_websocket_protocol(scope)
if subprotocol:
protocol_prefix, _, access_token = subprotocol.partition('.')
if protocol_prefix == 'access_token' and access_token:
return access_token
return None
-80
View File
@@ -1,80 +0,0 @@
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.status import HTTP_200_OK, HTTP_404_NOT_FOUND
from facefusion import args_store, session_manager, state_manager, translator
from facefusion.apis import asset_store
from facefusion.apis.endpoints.session import extract_access_token
async def get_state(request : Request) -> JSONResponse:
api_args = args_store.filter_api_args(state_manager.get_state())
return JSONResponse(state_manager.collect_state(api_args), status_code = HTTP_200_OK)
async def set_state(request : Request) -> JSONResponse:
action = request.query_params.get('action')
asset_type = request.query_params.get('asset_type')
if action == 'select' and asset_type == 'source':
return await select_source(request)
if action == 'select' and asset_type == 'target':
return await select_target(request)
body = await request.json()
api_args = args_store.get_api_args()
for key, value in body.items():
if key in api_args:
state_manager.set_item(key, value)
__api_args__ = args_store.filter_api_args(state_manager.get_state())
return JSONResponse(state_manager.collect_state(__api_args__), status_code = HTTP_200_OK)
async def select_source(request : Request) -> JSONResponse:
body = await request.json()
asset_ids = body.get('asset_ids')
access_token = extract_access_token(request.scope)
session_id = session_manager.find_session_id(access_token)
if isinstance(asset_ids, list) and session_id:
source_paths = []
for asset_id in asset_ids:
asset = asset_store.get_asset(session_id, asset_id)
if asset:
source_paths.append(asset.get('path'))
state_manager.set_item('source_paths', source_paths)
__api_args__ = args_store.filter_api_args(state_manager.get_state())
return JSONResponse(state_manager.collect_state(__api_args__), status_code = HTTP_200_OK)
return JSONResponse(
{
'message': translator.get('source_asset_not_found', 'facefusion.apis')
}, status_code = HTTP_404_NOT_FOUND)
async def select_target(request : Request) -> JSONResponse:
body = await request.json()
asset_id = body.get('asset_id')
access_token = extract_access_token(request.scope)
session_id = session_manager.find_session_id(access_token)
if isinstance(asset_id, str) and session_id:
asset = asset_store.get_asset(session_id, asset_id)
if asset:
state_manager.set_item('target_path', asset.get('path'))
__api_args__ = args_store.filter_api_args(state_manager.get_state())
return JSONResponse(state_manager.collect_state(__api_args__), status_code = HTTP_200_OK)
return JSONResponse(
{
'message': translator.get('target_asset_not_found', 'facefusion.apis')
}, status_code = HTTP_404_NOT_FOUND)
-52
View File
@@ -1,52 +0,0 @@
from starlette.requests import Request
from starlette.responses import Response
from starlette.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_404_NOT_FOUND
from starlette.websockets import WebSocket, WebSocketState
from facefusion import session_context, session_manager
from facefusion.apis.api_helper import get_sec_websocket_protocol
from facefusion.apis.endpoints.session import extract_access_token
from facefusion.apis.stream_manager import destroy_stream, process_image, process_video
async def websocket_stream(websocket : WebSocket) -> None:
subprotocol = get_sec_websocket_protocol(websocket.scope)
access_token = extract_access_token(websocket.scope)
session_id = session_manager.find_session_id(access_token)
session_context.set_session_id(session_id)
await websocket.accept(subprotocol = subprotocol)
await process_image(websocket)
if websocket.client_state == WebSocketState.CONNECTED:
await websocket.close()
async def post_stream(request : Request) -> Response:
headers =\
{
'Location': request.url_for('delete_stream').path
}
content_type = request.headers.get('content-type')
access_token = extract_access_token(request.scope)
session_id = session_manager.find_session_id(access_token)
session_context.set_session_id(session_id)
if session_id and content_type == 'application/sdp':
sdp_offer = await request.body()
sdp_answer = process_video(session_id, sdp_offer.decode())
if sdp_answer:
return Response(sdp_answer, status_code = HTTP_201_CREATED, media_type = 'application/sdp', headers = headers)
return Response(status_code = HTTP_404_NOT_FOUND)
async def delete_stream(request : Request) -> Response:
access_token = extract_access_token(request.scope)
session_id = session_manager.find_session_id(access_token)
if session_id and destroy_stream(session_id):
return Response(status_code = HTTP_200_OK)
return Response(status_code = HTTP_404_NOT_FOUND)
-14
View File
@@ -1,14 +0,0 @@
from facefusion.types import Locales
LOCALES : Locales =\
{
'en':
{
'ok': 'ok',
'something_went_wrong': 'something went wrong',
'invalid_access_token': 'invalid access token',
'invalid_refresh_token': 'invalid refresh token',
'source_asset_not_found': 'source asset not found',
'target_asset_not_found': 'target asset not found'
}
}
-12
View File
@@ -1,12 +0,0 @@
from facefusion.types import Locals
LOCALS : Locals =\
{
'en':
{
'ok': 'ok',
'something_went_wrong': 'something went wrong',
'invalid_access_token': 'invalid access token',
'invalid_refresh_token': 'invalid refresh token'
}
}
-76
View File
@@ -1,76 +0,0 @@
import asyncio
from functools import lru_cache
from typing import Any, Dict, Optional, cast
from starlette.datastructures import Headers
from starlette.websockets import WebSocket, WebSocketDisconnect
from facefusion import state_manager
from facefusion.execution import detect_execution_devices
from facefusion.system import get_cpu_info, get_disk_info, get_load_average, get_network_info
from facefusion.system import get_operating_system_info, get_python_info, get_ram_info, get_temperature_info
from facefusion.types import SystemInfo
@lru_cache(maxsize = 1)
def get_cached_static_system_info() -> Dict[str, Any]:
return\
{
'operating_system': get_operating_system_info(),
'python': get_python_info()
}
@lru_cache(maxsize = 1)
def get_cached_semi_static_system_info(temp_path : Optional[str]) -> Dict[str, Any]:
return\
{
'disk': get_disk_info(temp_path),
'network': get_network_info()
}
def get_optimized_system_info(temp_path : Optional[str] = None) -> SystemInfo:
static_data = get_cached_static_system_info()
semi_static_data = get_cached_semi_static_system_info(temp_path)
dynamic_data : Dict[str, Any] =\
{
'cpu': get_cpu_info(),
'ram': get_ram_info(),
'temperatures': get_temperature_info(),
'load_average': get_load_average()
}
return cast(SystemInfo, {**static_data, **semi_static_data, **dynamic_data})
async def websocket_metrics(websocket : WebSocket) -> None:
subprotocol = get_requested_subprotocol(websocket)
await websocket.accept(subprotocol = subprotocol)
try:
while True:
temp_path = state_manager.get_temp_path()
execution_devices = detect_execution_devices()
system_info = get_optimized_system_info(temp_path)
metrics =\
{
'devices': execution_devices,
'system': system_info
}
await websocket.send_json(metrics)
await asyncio.sleep(2)
except (WebSocketDisconnect, Exception):
pass
def get_requested_subprotocol(websocket : WebSocket) -> Optional[str]:
headers = Headers(scope = websocket.scope)
protocol_header = headers.get('Sec-WebSocket-Protocol')
if protocol_header:
protocol, _, _ = protocol_header.partition(',')
return protocol.strip()
return None
-384
View File
@@ -1,384 +0,0 @@
import os
import tempfile
from typing import Any, Dict, List
import httpx
import yt_dlp # type: ignore
from gallery_dl import config as gallery_config, extractor as gallery_extractor, job as gallery_job
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.status import HTTP_200_OK, HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_500_INTERNAL_SERVER_ERROR
from facefusion import logger
from facefusion.apis import asset_store
from facefusion.choices import audio_formats
from facefusion.session_context import get_session_id
def resolve_image_urls(url : str) -> List[str]:
gallery_config.load()
image_urls : List[str] = []
try:
for extractor_instance in gallery_extractor.extractors():
if extractor_instance.pattern and extractor_instance.pattern.match(url):
logger.info(f'Detected gallery URL using extractor: {extractor_instance.__name__}', __name__)
extractor_obj = extractor_instance.from_url(url)
if extractor_obj:
for msg in extractor_obj:
if isinstance(msg, tuple) and len(msg) >= 2:
msg_type = msg[0]
if msg_type == 5:
image_data = msg[1]
image_url = image_data.get('url')
if image_url:
image_urls.append(image_url)
break
if not image_urls:
logger.info('Not a gallery URL, treating as direct image URL', __name__)
image_urls = [url]
except Exception as e:
logger.error(f'Failed to extract image URLs: {e}', __name__)
logger.info('Falling back to treating as direct image URL', __name__)
image_urls = [url]
return image_urls
def download_images_from_url(url : str, asset_type : str) -> List[str]:
gallery_config.load()
temp_dir = tempfile.gettempdir()
asset_ids : List[str] = []
is_gallery = False
for extractor_instance in gallery_extractor.extractors():
if extractor_instance.pattern and extractor_instance.pattern.match(url):
logger.info(f'Detected gallery URL using extractor: {extractor_instance.__name__}', __name__)
is_gallery = True
output_dir = os.path.join(temp_dir, f'facefusion_gallery_{os.urandom(8).hex()}')
os.makedirs(output_dir, exist_ok = True)
gallery_config.set((), 'base-directory', output_dir)
gallery_config.set((), 'skip', False)
gdl_job = gallery_job.DownloadJob(url)
gdl_job.run()
session_id = get_session_id()
for root, dirs, files in os.walk(output_dir):
for filename in files:
file_path = os.path.join(root, filename)
asset = asset_store.create_asset(session_id, asset_type, file_path)
if asset:
asset_ids.append(asset.get('id'))
logger.info(f'Registered image as asset {asset.get("id")}', __name__)
break
if not is_gallery:
logger.info('Not a gallery URL, treating as direct image URL', __name__)
with httpx.stream('GET', url, timeout = 30, follow_redirects = True) as response:
response.raise_for_status()
content_type = response.headers.get('content-type', '')
if not content_type.startswith('image/'):
raise ValueError(f'URL does not point to an image. Content-Type: {content_type}')
file_extension = None
if 'image/jpeg' in content_type or 'image/jpg' in content_type:
file_extension = '.jpg'
if 'image/png' in content_type:
file_extension = '.png'
if 'image/gif' in content_type:
file_extension = '.gif'
if 'image/webp' in content_type:
file_extension = '.webp'
if not file_extension:
url_path = url.split('?')[0]
if '.' in url_path:
file_extension = '.' + url_path.split('.')[-1].lower()
else:
file_extension = '.jpg'
filename = f'facefusion_image_{os.urandom(8).hex()}{file_extension}'
file_path = os.path.join(temp_dir, filename)
with open(file_path, 'wb') as f:
for chunk in response.iter_bytes(chunk_size = 8192):
f.write(chunk)
session_id = get_session_id()
asset = asset_store.create_asset(session_id, asset_type, file_path)
if asset:
asset_ids.append(asset.get('id'))
logger.info(f'Downloaded and registered image as asset {asset.get("id")}', __name__)
return asset_ids
def download_audio_from_url(url : str, asset_type : str) -> List[str]:
temp_dir = tempfile.gettempdir()
asset_ids : List[str] = []
# Extract file extension from URL
url_path = url.split('?')[0]
url_extension = os.path.splitext(url_path)[1].lstrip('.')
# Validate extension against supported audio formats
if url_extension not in audio_formats:
raise ValueError(f'Unsupported audio format: {url_extension}. Supported formats: {", ".join(audio_formats)}')
logger.info(f'Downloading audio from URL with extension: {url_extension}', __name__)
with httpx.stream('GET', url, timeout = 30, follow_redirects = True) as response:
response.raise_for_status()
filename = f'facefusion_audio_{os.urandom(8).hex()}.{url_extension}'
file_path = os.path.join(temp_dir, filename)
with open(file_path, 'wb') as f:
for chunk in response.iter_bytes(chunk_size = 8192):
f.write(chunk)
session_id = get_session_id()
asset = asset_store.create_asset(session_id, asset_type, file_path)
if asset:
asset_ids.append(asset.get('id'))
logger.info(f'Downloaded and registered audio as asset {asset.get("id")}', __name__)
return asset_ids
async def remote(request : Request) -> JSONResponse:
body = await request.json()
url = body.get('url')
action = request.query_params.get('action')
media_type = request.query_params.get('media_type', 'video')
asset_type = request.query_params.get('asset_type', 'target')
if not action:
return JSONResponse({'message': 'No action provided. Must be "resolve" or "download"'}, status_code = HTTP_400_BAD_REQUEST)
if action not in ['resolve', 'download']:
return JSONResponse({'message': 'Invalid action. Must be "resolve" or "download"'}, status_code = HTTP_400_BAD_REQUEST)
if media_type not in ['image', 'video', 'audio']:
return JSONResponse({'message': 'Invalid media_type. Must be "image", "video", or "audio"'}, status_code = HTTP_400_BAD_REQUEST)
if asset_type not in ['source', 'target']:
return JSONResponse({'message': 'Invalid asset_type. Must be "source" or "target"'}, status_code = HTTP_400_BAD_REQUEST)
if not url:
return JSONResponse({'message': 'No URL provided'}, status_code = HTTP_400_BAD_REQUEST)
if not isinstance(url, str):
return JSONResponse({'message': 'URL must be a string'}, status_code = HTTP_400_BAD_REQUEST)
url = url.strip()
if not url.startswith('http://') and not url.startswith('https://'):
return JSONResponse({'message': 'URL must start with http:// or https://'}, status_code = HTTP_400_BAD_REQUEST)
quality = body.get('quality', '720p')
if quality not in ['360p', '480p', '720p', '1080p']:
return JSONResponse({'message': 'Quality must be 360p, 480p, 720p, or 1080p'}, status_code = HTTP_400_BAD_REQUEST)
if action == 'resolve':
if media_type == 'image':
image_urls = resolve_image_urls(url)
logger.info(f'Resolved {len(image_urls)} image URL(s)', __name__)
response_data =\
{
'message': 'Image URL(s) resolved successfully',
'image_urls': image_urls,
'count': len(image_urls)
}
return JSONResponse(response_data, status_code = HTTP_200_OK)
quality_map =\
{
'360p': 'bestvideo[height<=360][ext=mp4]+bestaudio[ext=m4a]/best[height<=360]',
'480p': 'bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]/best[height<=480]',
'720p': 'bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720]',
'1080p': 'bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[height<=1080]'
}
ydl_opts : Dict[str, Any] =\
{
'format': quality_map[quality],
'quiet': True,
'no_warnings': True
}
logger.info(f'Extracting stream URL from {url} at {quality}', __name__)
try:
ydl = yt_dlp.YoutubeDL(ydl_opts)
info = ydl.extract_info(url, download = False)
except Exception as e:
logger.error(f'Failed to extract video information: {e}', __name__)
return JSONResponse({'message': f'Failed to extract video information: {str(e)}'}, status_code = HTTP_500_INTERNAL_SERVER_ERROR)
if not info:
logger.error('Failed to extract video information', __name__)
return JSONResponse({'message': 'Failed to extract video information'}, status_code = HTTP_500_INTERNAL_SERVER_ERROR)
stream_url = info.get('url')
if not stream_url:
if 'requested_formats' in info and len(info['requested_formats']) > 0:
stream_url = info['requested_formats'][0].get('url')
logger.info('Using URL from requested_formats (video track)', __name__)
elif 'formats' in info and len(info['formats']) > 0:
for fmt in reversed(info['formats']):
if fmt.get('url') and fmt.get('vcodec') != 'none':
stream_url = fmt['url']
logger.info(f'Using URL from format: {fmt.get("format_id")}', __name__)
break
if not stream_url:
logger.error('No stream URL found in any format', __name__)
logger.debug(f'Available keys in info: {list(info.keys())}', __name__)
return JSONResponse({'message': 'No stream URL found'}, status_code = HTTP_500_INTERNAL_SERVER_ERROR)
audio_url = None
if 'requested_formats' in info and len(info['requested_formats']) > 1:
audio_url = info['requested_formats'][1].get('url')
if audio_url:
logger.info('Found separate audio track URL', __name__)
duration = info.get('duration')
fps = info.get('fps')
width = info.get('width')
height = info.get('height')
total_frames = None
if duration and fps:
total_frames = int(duration * fps)
logger.info(f'Calculated total frames: {total_frames} ({duration}s * {fps} fps)', __name__)
logger.info('Stream URL extracted successfully', __name__)
response_data =\
{
'message': 'Stream URL resolved successfully',
'stream_url': stream_url,
'audio_url': audio_url,
'duration': duration,
'fps': fps,
'total_frames': total_frames,
'width': width,
'height': height
}
return JSONResponse(response_data, status_code = HTTP_200_OK)
if action == 'download':
if media_type == 'image':
try:
asset_ids = download_images_from_url(url, asset_type)
except ValueError as e:
return JSONResponse({'message': str(e)}, status_code = HTTP_400_BAD_REQUEST)
except Exception as e:
logger.error(f'Failed to download image(s): {e}', __name__)
return JSONResponse({'message': f'Failed to download image(s): {str(e)}'}, status_code = HTTP_500_INTERNAL_SERVER_ERROR)
response_data =\
{
'message': f'Downloaded and registered {len(asset_ids)} image(s)',
'asset_ids': asset_ids,
'count': len(asset_ids)
}
return JSONResponse(response_data, status_code = HTTP_201_CREATED)
if media_type == 'audio':
try:
asset_ids = download_audio_from_url(url, asset_type)
except ValueError as e:
return JSONResponse({'message': str(e)}, status_code = HTTP_400_BAD_REQUEST)
except Exception as e:
logger.error(f'Failed to download audio: {e}', __name__)
return JSONResponse({'message': f'Failed to download audio: {str(e)}'}, status_code = HTTP_500_INTERNAL_SERVER_ERROR)
response_data =\
{
'message': f'Downloaded and registered {len(asset_ids)} audio file(s)',
'asset_ids': asset_ids,
'count': len(asset_ids)
}
return JSONResponse(response_data, status_code = HTTP_201_CREATED)
quality_map =\
{
'360p': 'bestvideo[height<=360][ext=mp4]+bestaudio[ext=m4a]/best[height<=360][ext=mp4]/best[height<=360]',
'480p': 'bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]/best[height<=480][ext=mp4]/best[height<=480]',
'720p': 'bestvideo[height<=720][ext=mp4]+bestaudio[ext=m4a]/best[height<=720][ext=mp4]/best[height<=720]',
'1080p': 'bestvideo[height<=1080][ext=mp4]+bestaudio[ext=m4a]/best[height<=1080][ext=mp4]/best[height<=1080]'
}
temp_dir = tempfile.gettempdir()
output_path = os.path.join(temp_dir, 'facefusion_remote_%(id)s.%(ext)s')
download_opts : Dict[str, Any] =\
{
'format': quality_map[quality],
'outtmpl': output_path,
'quiet': False,
'no_warnings': False
}
logger.info(f'Downloading video from {url} at {quality}', __name__)
ydl = yt_dlp.YoutubeDL(download_opts)
info = ydl.extract_info(url, download = True)
if not info:
logger.error('Failed to download video', __name__)
return JSONResponse({'message': 'Failed to download video'}, status_code = HTTP_500_INTERNAL_SERVER_ERROR)
downloaded_file = ydl.prepare_filename(info)
if not os.path.exists(downloaded_file):
logger.error(f'Downloaded file not found: {downloaded_file}', __name__)
return JSONResponse({'message': 'Downloaded file not found'}, status_code = HTTP_500_INTERNAL_SERVER_ERROR)
duration = info.get('duration')
fps = info.get('fps')
width = info.get('width')
height = info.get('height')
total_frames = None
if duration and fps:
total_frames = int(duration * fps)
logger.info(f'Calculated total frames: {total_frames} ({duration}s * {fps} fps)', __name__)
session_id = get_session_id()
asset = asset_store.create_asset(session_id, asset_type, downloaded_file)
asset_id = asset.get('id') if asset else None
logger.info(f'Video downloaded and registered as asset {asset_id}', __name__)
response_data =\
{
'message': 'Video downloaded and registered as asset',
'asset_id': asset_id,
'metadata':
{
'duration': duration,
'fps': fps,
'total_frames': total_frames,
'width': width,
'height': height
}
}
return JSONResponse(response_data, status_code = HTTP_201_CREATED)
return JSONResponse({'message': 'Invalid request'}, status_code = HTTP_400_BAD_REQUEST)
-71
View File
@@ -1,71 +0,0 @@
import ctypes
import time
from functools import partial
from queue import Queue
from typing import Optional, Tuple
import numpy
from facefusion import rtc
from facefusion.apis.stream_event import create_receive_event
from facefusion.codecs import opus_decoder, opus_encoder
from facefusion.types import AudioCodec, AudioFrame, OpusDecoder, RtcPeer, RtcPeerAudio
def run_audio_encode_loop(rtc_peer : RtcPeer, audio_queue : Queue[Tuple[float, AudioFrame]]) -> None:
temp_audio_time, temp_audio_frame = audio_queue.get()
audio_encoder = opus_encoder.create(48000, 2)
audio_timestamp = 0
while numpy.any(temp_audio_frame):
audio_frame_size = len(temp_audio_frame) // 2
audio_buffer = opus_encoder.encode(audio_encoder, temp_audio_frame.tobytes(), audio_frame_size)
if audio_buffer:
rtc.send_audio(rtc_peer, audio_buffer, audio_timestamp)
audio_timestamp += audio_frame_size
temp_audio_time, temp_audio_frame = audio_queue.get()
opus_encoder.destroy(audio_encoder)
def receive_audio_frames(rtc_peer_audio : RtcPeerAudio, audio_queue : Queue[Tuple[float, AudioFrame]]) -> None:
audio_track = rtc_peer_audio.get('receiver_track')
audio_codec = rtc_peer_audio.get('codec')
audio_decoder = create_audio_decoder(audio_codec)
audio_frame_handler = partial(handle_audio_frame, audio_codec, audio_decoder, audio_queue)
receive_event = create_receive_event(audio_track, audio_frame_handler)
receive_event.wait()
empty_audio_frame = numpy.empty(0)
audio_queue.put((0.0, empty_audio_frame))
destroy_audio_decoder(audio_codec, audio_decoder)
def decode_audio_frame(audio_codec : AudioCodec, audio_decoder : OpusDecoder, input_buffer : bytes) -> Optional[bytes]:
if audio_codec == 'opus':
return opus_decoder.decode(audio_decoder, input_buffer, 960, 2)
return None
def create_audio_decoder(audio_codec : AudioCodec) -> Optional[OpusDecoder]:
if audio_codec == 'opus':
return opus_decoder.create(48000, 2)
return None
def destroy_audio_decoder(audio_codec : AudioCodec, audio_decoder : OpusDecoder) -> None:
if audio_codec == 'opus':
opus_decoder.destroy(audio_decoder)
#todo: Alias Time for float
def handle_audio_frame(audio_codec : AudioCodec, audio_decoder : OpusDecoder, audio_queue : Queue[Tuple[float, AudioFrame]], track : int, data : ctypes.c_void_p, size : int, info : ctypes.c_void_p, pointer : ctypes.c_void_p) -> None:
audio_buffer = ctypes.string_at(data, size)
audio_frame = decode_audio_frame(audio_codec, audio_decoder, audio_buffer)
if audio_frame:
temp_audio_frame = numpy.frombuffer(audio_frame, dtype = numpy.float32)
audio_queue.put((time.monotonic(), temp_audio_frame))
-24
View File
@@ -1,24 +0,0 @@
import ctypes
import threading
from functools import partial
from facefusion.libraries import datachannel as datachannel_module
from facefusion.types import FrameHandler
def create_receive_event(track : int, frame_handler : FrameHandler) -> threading.Event:
datachannel_library = datachannel_module.create_static_library()
receive_event = threading.Event()
frame_callback = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p)(frame_handler)
close_callback = ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p)(partial(dispatch_event, receive_event))
datachannel_library.rtcSetFrameCallback(track, frame_callback)
datachannel_library.rtcSetClosedCallback(track, close_callback)
receive_event.frame_callback = frame_callback # type: ignore[attr-defined]
receive_event.close_callback = close_callback # type: ignore[attr-defined]
return receive_event
def dispatch_event(event : threading.Event, track : int, pointer : ctypes.c_void_p) -> None:
event.set()
-137
View File
@@ -1,137 +0,0 @@
import ctypes
import threading
from collections.abc import AsyncIterator
from concurrent.futures import Future, ThreadPoolExecutor
from queue import Queue
from typing import Optional, Tuple
import cv2
import numpy
from starlette.websockets import WebSocket
from facefusion import rtc, rtc_store, state_manager, streamer
from facefusion.apis.stream_audio import receive_audio_frames, run_audio_encode_loop
from facefusion.apis.stream_video import receive_video_frames, run_video_encode_loop
from facefusion.libraries import datachannel as datachannel_module
from facefusion.types import AudioCodec, AudioFrame, PeerConnection, Resolution, RtcPeer, RtcPeerAudio, SdpAnswer, SdpOffer, SessionId, VideoCodec, VisionFrame
async def process_image(websocket : WebSocket) -> None:
capture_vision_frame = await anext(receive_vision_frames(websocket), None)
if numpy.any(capture_vision_frame):
output_vision_frame = streamer.process_stream_frame(capture_vision_frame)
is_success, output_frame_buffer = cv2.imencode('.jpg', output_vision_frame)
if is_success:
await websocket.send_bytes(output_frame_buffer.tobytes())
async def receive_vision_frames(websocket : WebSocket) -> AsyncIterator[VisionFrame]:
websocket_event = await websocket.receive()
while websocket_event.get('type') == 'websocket.receive':
frame_buffer = websocket_event.get('bytes') or bytes()
vision_frame = cv2.imdecode(numpy.frombuffer(frame_buffer, numpy.uint8), cv2.IMREAD_COLOR)
if numpy.any(vision_frame):
yield vision_frame
websocket_event = await websocket.receive()
def process_video(session_id : SessionId, sdp_offer : SdpOffer) -> Optional[SdpAnswer]:
video_codec : VideoCodec = 'vp8'
if rtc.get_payload_type(sdp_offer, 'vp9'):
video_codec = 'vp9'
if rtc.get_payload_type(sdp_offer, 'av1'):
video_codec = 'av1'
video_payload_type = rtc.get_payload_type(sdp_offer, video_codec)
if video_payload_type:
peer_connection : PeerConnection = rtc.create_peer_connection()
video_receiver_track = rtc.add_video_track(peer_connection, 'recvonly', video_codec, video_payload_type)
video_sender_track = rtc.add_video_track(peer_connection, 'sendonly', video_codec, video_payload_type)
sender_bitrate = ctypes.c_uint(0)
receiver_bitrate = ctypes.c_uint(8000)
rtc.wire_sender_bitrate(video_sender_track, sender_bitrate)
audio_codec : AudioCodec = 'opus'
audio_payload_type = rtc.get_payload_type(sdp_offer, audio_codec)
if audio_payload_type:
audio_receiver_track = rtc.add_audio_track(peer_connection, 'recvonly', audio_codec, audio_payload_type)
audio_sender_track = rtc.add_audio_track(peer_connection, 'sendonly', audio_codec, audio_payload_type)
rtc.set_remote_description(peer_connection, sdp_offer)
sdp_answer = rtc.create_sdp_answer(peer_connection)
if sdp_answer:
rtc_peer : RtcPeer =\
{
'peer_connection': peer_connection,
'video':
{
'sender_track': video_sender_track,
'receiver_track': video_receiver_track,
'codec': video_codec
},
'sender_bitrate': sender_bitrate,
'receiver_bitrate': receiver_bitrate
}
if audio_payload_type:
rtc_peer['audio'] = RtcPeerAudio(
sender_track = audio_sender_track,
receiver_track = audio_receiver_track,
codec = audio_codec
)
rtc_store.init_peers(session_id)
rtc_store.get_peers(session_id).append(rtc_peer)
threading.Thread(target = run_peer_loop, args = (session_id, rtc_peer), daemon = True).start()
return sdp_answer
datachannel_module.create_static_library().rtcDeletePeerConnection(peer_connection)
return None
def run_peer_loop(session_id : SessionId, rtc_peer : RtcPeer) -> None:
execution_thread_count = state_manager.get_item('execution_thread_count')
#todo: is bytes, Resolution not a XXXPointer type
video_queue : Queue[Tuple[float, Future[Tuple[bytes, Resolution]]]] = Queue(maxsize = execution_thread_count)
audio_queue : Queue[Tuple[float, AudioFrame]] = Queue(maxsize = execution_thread_count * 10)
video_executor = ThreadPoolExecutor(max_workers = execution_thread_count)
video_receiver_thread = threading.Thread(target = receive_video_frames, args = (rtc_peer.get('video'), video_queue, video_executor), daemon = True)
video_encoder_thread = threading.Thread(target = run_video_encode_loop, args = (rtc_peer, video_queue), daemon = True)
video_receiver_thread.start()
video_encoder_thread.start()
if rtc_peer.get('audio'):
audio_receiver_thread = threading.Thread(target = receive_audio_frames, args = (rtc_peer.get('audio'), audio_queue), daemon = True)
audio_encoder_thread = threading.Thread(target = run_audio_encode_loop, args = (rtc_peer, audio_queue), daemon = True)
audio_receiver_thread.start()
audio_encoder_thread.start()
audio_receiver_thread.join()
audio_encoder_thread.join()
video_receiver_thread.join()
video_encoder_thread.join()
video_executor.shutdown(wait = True)
rtc_store.delete_peers(session_id)
def destroy_stream(session_id : SessionId) -> bool:
if rtc_store.has_peers(session_id):
rtc_store.delete_peers(session_id)
return not rtc_store.has_peers(session_id)
return False
-204
View File
@@ -1,204 +0,0 @@
import ctypes
import time
from concurrent.futures import Future, ThreadPoolExecutor
from functools import partial
from queue import Queue
from typing import Optional, Tuple
import cv2
import numpy
from facefusion import rtc, streamer
from facefusion.apis.stream_event import create_receive_event
from facefusion.codecs import aom_decoder, aom_encoder, vpx_decoder, vpx_encoder
from facefusion.types import AomDecoder, AomEncoder, AomPointer, BitRate, Resolution, RtcPeer, RtcPeerVideo, VideoCodec, VisionFrame, VpxDecoder, VpxEncoder, VpxPointer
def run_video_encode_loop(rtc_peer : RtcPeer, video_queue : Queue[Tuple[float, Future[Tuple[bytes, Resolution]]]]) -> None:
video_codec = rtc_peer.get('video').get('codec')
video_time, video_future = video_queue.get()
video_buffer, video_resolution = video_future.result()
if video_buffer:
temp_resolution : Resolution = video_resolution
temp_bitrate : BitRate = 8000
video_encoder = create_video_encoder(video_codec, temp_resolution, temp_bitrate)
temp_video_time = video_time
frame_index = 0
while video_buffer:
encode_start = time.monotonic()
sender_bitrate = calculate_sender_bitrate(rtc_peer, temp_bitrate)
if video_resolution[0] - temp_resolution[0] or video_resolution[1] - temp_resolution[1]:
temp_resolution = video_resolution
update_video_encoder_resolution(video_codec, video_encoder, temp_resolution)
if sender_bitrate - temp_bitrate:
temp_bitrate = sender_bitrate
update_video_encoder_bitrate(video_codec, video_encoder, temp_bitrate)
__video_buffer__ = encode_video_frame(video_codec, video_encoder, video_buffer, temp_resolution, frame_index)
if __video_buffer__:
video_timestamp = int(video_time * 90000)
rtc.send_video(rtc_peer, __video_buffer__, video_timestamp)
encode_time = time.monotonic() - encode_start
frame_interval = video_time - temp_video_time
temp_video_time = video_time
receiver_bitrate = calculate_receiver_bitrate(rtc_peer, encode_time, frame_interval)
rtc.adapt_receiver_bitrate(rtc_peer, receiver_bitrate)
frame_index += 1
video_time, video_future = video_queue.get()
video_buffer, video_resolution = video_future.result()
destroy_video_encoder(video_codec, video_encoder)
rtc.clear_bitrate(rtc_peer)
def receive_video_frames(rtc_peer_video : RtcPeerVideo, video_queue : Queue[Tuple[float, Future[Tuple[bytes, Resolution]]]], video_executor : ThreadPoolExecutor) -> None:
video_track = rtc_peer_video.get('receiver_track')
video_codec = rtc_peer_video.get('codec')
video_decoder = create_video_decoder(video_codec)
video_frame_handler = partial(handle_video_frame, video_codec, video_decoder, video_queue, video_executor)
receive_event = create_receive_event(video_track, video_frame_handler)
receive_event.wait()
empty_future : Future[Tuple[bytes, Resolution]] = Future()
empty_future.set_result((bytes(), (0, 0)))
video_queue.put((0.0, empty_future))
destroy_video_decoder(video_codec, video_decoder)
def process_video_frame(input_vision_frame : VisionFrame) -> Tuple[bytes, Resolution]:
output_vision_frame = streamer.process_stream_frame(input_vision_frame)
output_resolution : Resolution = (output_vision_frame.shape[1], output_vision_frame.shape[0])
output_buffer = cv2.cvtColor(output_vision_frame, cv2.COLOR_BGR2YUV_I420).tobytes()
return output_buffer, output_resolution
def calculate_receiver_bitrate(rtc_peer : RtcPeer, encode_time : float, frame_interval : float) -> BitRate:
min_bitrate : BitRate = 500
max_bitrate : BitRate = 8000
bitrate : BitRate = rtc_peer.get('receiver_bitrate').value
if frame_interval > 0:
scale = frame_interval / encode_time
bitrate = int(bitrate * scale)
bitrate = max(min_bitrate, min(max_bitrate, bitrate))
return bitrate
#todo: does not feel final as this is an clamp and not calculate
def calculate_sender_bitrate(rtc_peer : RtcPeer, bitrate : BitRate) -> BitRate:
min_bitrate : BitRate = 500
max_bitrate : BitRate = 8000
peer_bitrate : BitRate = rtc_peer.get('sender_bitrate').value
if peer_bitrate > 0:
bitrate = max(min_bitrate, min(max_bitrate, peer_bitrate))
return bitrate
def decode_video_frame(video_codec : VideoCodec, video_decoder : VpxDecoder | AomDecoder, input_buffer : bytes) -> Optional[VisionFrame]:
if video_codec == 'av1':
aom_pointer = aom_decoder.decode(video_decoder, input_buffer)
if aom_pointer:
return normalize_vision_frame(aom_pointer)
if video_codec in [ 'vp8', 'vp9' ]:
vpx_pointer = vpx_decoder.decode(video_decoder, input_buffer)
if vpx_pointer:
return normalize_vision_frame(vpx_pointer)
return None
def encode_video_frame(video_codec : VideoCodec, video_encoder : VpxEncoder | AomEncoder, input_buffer : bytes, frame_resolution : Resolution, frame_index : int) -> bytes:
if video_codec == 'av1':
return aom_encoder.encode(video_encoder, input_buffer, frame_resolution, frame_index)
if video_codec in [ 'vp8', 'vp9' ]:
return vpx_encoder.encode(video_encoder, input_buffer, frame_resolution, frame_index)
return bytes()
def normalize_vision_frame(frame_pointer : AomPointer | VpxPointer) -> VisionFrame:
frame_width, frame_height = frame_pointer.get('resolution')
vision_frame = numpy.frombuffer(frame_pointer.get('buffer'), dtype = numpy.uint8).reshape((frame_height * 3 // 2, frame_width))
return cv2.cvtColor(vision_frame, cv2.COLOR_YUV2BGR_I420)
def create_video_decoder(video_codec : VideoCodec) -> Optional[VpxDecoder | AomDecoder]:
if video_codec == 'av1':
return aom_decoder.create(8)
if video_codec in [ 'vp8', 'vp9' ]:
return vpx_decoder.create(video_codec, 8)
return None
def create_video_encoder(video_codec : VideoCodec, frame_resolution : Resolution, bitrate : BitRate) -> Optional[VpxEncoder | AomEncoder]:
if video_codec == 'av1':
return aom_encoder.create(frame_resolution, bitrate, 8, 10)
if video_codec in [ 'vp8', 'vp9' ]:
return vpx_encoder.create(video_codec, frame_resolution, bitrate, 8, 10)
return None
def destroy_video_decoder(video_codec : VideoCodec, video_decoder : VpxDecoder | AomDecoder) -> None:
if video_codec == 'av1':
aom_decoder.destroy(video_decoder)
if video_codec in [ 'vp8', 'vp9' ]:
vpx_decoder.destroy(video_decoder)
def destroy_video_encoder(video_codec : VideoCodec, video_encoder : VpxEncoder | AomEncoder) -> None:
if video_codec == 'av1':
aom_encoder.destroy(video_encoder)
if video_codec in [ 'vp8', 'vp9' ]:
vpx_encoder.destroy(video_encoder)
def update_video_encoder_resolution(video_codec : VideoCodec, video_encoder : VpxEncoder | AomEncoder, frame_resolution : Resolution) -> bool:
if video_codec == 'av1':
return aom_encoder.update_resolution(video_encoder, frame_resolution)
if video_codec in [ 'vp8', 'vp9' ]:
return vpx_encoder.update_resolution(video_encoder, frame_resolution)
return False
def update_video_encoder_bitrate(video_codec : VideoCodec, video_encoder : VpxEncoder | AomEncoder, bitrate : BitRate) -> bool:
if video_codec == 'av1':
return aom_encoder.update_bitrate(video_encoder, bitrate)
if video_codec in [ 'vp8', 'vp9' ]:
return vpx_encoder.update_bitrate(video_encoder, bitrate)
return False
#todo: we can remove the dead args or pass audio buffer
def handle_video_frame(video_codec : VideoCodec, video_decoder : VpxDecoder | AomDecoder, video_queue : Queue[Tuple[float, Future[Tuple[bytes, Resolution]]]], video_executor : ThreadPoolExecutor, track : int, data : ctypes.c_void_p, size : int, info : ctypes.c_void_p, pointer : ctypes.c_void_p) -> None:
video_buffer = ctypes.string_at(data, size)
vision_frame = decode_video_frame(video_codec, video_decoder, video_buffer)
if numpy.any(vision_frame) and video_queue.qsize() < video_queue.maxsize:
video_future = video_executor.submit(process_video_frame, vision_frame)
video_queue.put((time.monotonic(), video_future))
-207
View File
@@ -1,207 +0,0 @@
import base64
import subprocess
from typing import List, Optional
import cv2
import numpy
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
from facefusion import logger
from facefusion.apis import asset_store
from facefusion.filesystem import is_video
from facefusion.video_manager import get_video_capture
from facefusion.vision import fit_contain_frame
def extract_frame_at_timestamp(stream_url : str, timestamp : float, width : int, height : int) -> Optional[numpy.ndarray]:
ffmpeg_command =\
[
'ffmpeg',
'-user_agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'-ss', str(timestamp),
'-i', stream_url,
'-vf', f'scale={width}:{height}',
'-frames:v', '1',
'-f', 'rawvideo',
'-pix_fmt', 'bgr24',
'-'
]
try:
result = subprocess.run(ffmpeg_command, capture_output = True, timeout = 10)
if result.returncode == 0 and result.stdout:
frame_size = width * height * 3
if len(result.stdout) >= frame_size:
frame = numpy.frombuffer(result.stdout[:frame_size], dtype = numpy.uint8).reshape((height, width, 3))
return frame
except Exception as e:
logger.debug(f'Failed to extract frame at {timestamp}s: {e}', __name__)
return None
async def get_timeline(request: Request) -> JSONResponse:
"""
Return N preview frames (as base64 JPEGs) from the target video,
resized to specified resolution for timeline preview.
Route: /timeline/{count:int}?target_path=...&is_remote_stream=true&duration=120&fps=30&target_width=1920&target_height=1080&width=160&height=120
"""
# Extract and validate requested count
try:
count = int(request.path_params.get('count', 0))
except (TypeError, ValueError):
return JSONResponse({'message': 'Invalid count parameter'}, status_code=HTTP_400_BAD_REQUEST)
if count <= 0:
return JSONResponse({'message': 'Count must be a positive integer'}, status_code=HTTP_400_BAD_REQUEST)
# Extract and validate preview resolution parameters
try:
preview_width = int(request.query_params.get('width', 160))
preview_height = int(request.query_params.get('height', 120))
except (TypeError, ValueError):
return JSONResponse({'message': 'Invalid width or height parameter'}, status_code=HTTP_400_BAD_REQUEST)
if preview_width <= 0 or preview_height <= 0 or preview_width > 1920 or preview_height > 1080:
return JSONResponse({'message': 'Width and height must be between 1 and 1920x1080'}, status_code=HTTP_400_BAD_REQUEST)
# Extract target_path or asset_id (one is required)
target_path = request.query_params.get('target_path')
asset_id = request.query_params.get('asset_id')
# Extract is_remote_stream flag
is_remote_stream_param = request.query_params.get('is_remote_stream', 'false').lower()
is_remote_stream = is_remote_stream_param in ['true', '1', 'yes']
# Resolve asset_id to path if provided (for local files)
if asset_id and not target_path:
from facefusion.session_context import get_session_id
session_id = get_session_id()
asset = asset_store.get_asset(session_id, asset_id)
if not asset:
return JSONResponse({'message': f'Asset not found: {asset_id}'}, status_code=HTTP_400_BAD_REQUEST)
target_path = asset.get('path')
if not target_path:
return JSONResponse({'message': 'Asset has no path'}, status_code=HTTP_400_BAD_REQUEST)
is_remote_stream = False # Assets are always local files
logger.debug(f'Resolved asset_id {asset_id} to path for timeline preview', __name__)
# Now check if we have a target_path
if not target_path:
return JSONResponse({'message': 'Missing required parameter: either target_path or asset_id'}, status_code=HTTP_400_BAD_REQUEST)
# Extract video metadata (optional for local files, required for remote streams)
duration = None
fps = None
width = 1280
height = 720
if request.query_params.get('duration'):
try:
duration = float(request.query_params.get('duration'))
except (TypeError, ValueError):
return JSONResponse({'message': 'Invalid duration parameter'}, status_code=HTTP_400_BAD_REQUEST)
if request.query_params.get('fps'):
try:
fps = float(request.query_params.get('fps'))
except (TypeError, ValueError):
return JSONResponse({'message': 'Invalid fps parameter'}, status_code=HTTP_400_BAD_REQUEST)
if request.query_params.get('target_width'):
try:
width = int(request.query_params.get('target_width'))
except (TypeError, ValueError):
return JSONResponse({'message': 'Invalid target_width parameter'}, status_code=HTTP_400_BAD_REQUEST)
if request.query_params.get('target_height'):
try:
height = int(request.query_params.get('target_height'))
except (TypeError, ValueError):
return JSONResponse({'message': 'Invalid target_height parameter'}, status_code=HTTP_400_BAD_REQUEST)
previews: List[str] = []
if is_remote_stream:
if not duration or duration <= 0:
return JSONResponse({'message': 'Duration not available for remote stream'}, status_code=HTTP_400_BAD_REQUEST)
frame_total = 0
if duration and fps:
try:
frame_total = int(float(duration) * float(fps))
except Exception:
frame_total = 0
sample_count = min(count, frame_total) if frame_total > 0 else count
timestamps = list(numpy.linspace(0, float(duration), num=sample_count, endpoint=False))
logger.info(f'Extracting {sample_count} frames from remote stream using ffmpeg', __name__)
for timestamp in timestamps:
frame = extract_frame_at_timestamp(target_path, timestamp, width, height)
if frame is None:
logger.warn(f'Failed to extract frame at {timestamp}s', __name__)
continue
thumb_bgr = fit_contain_frame(frame, (preview_width, preview_height))
if thumb_bgr.shape[1] != preview_width or thumb_bgr.shape[0] != preview_height:
thumb_bgr = cv2.resize(thumb_bgr, (preview_width, preview_height))
ok_enc, buf = cv2.imencode('.jpg', thumb_bgr, [cv2.IMWRITE_JPEG_QUALITY, 50])
if not ok_enc:
logger.warn(f'JPEG encode failed for timestamp {timestamp}s', __name__)
continue
b64 = base64.b64encode(buf.tobytes()).decode('ascii')
previews.append(b64)
else:
video_capture = get_video_capture(target_path)
if not video_capture or not video_capture.isOpened():
logger.error(f'Unable to open video capture for target: {target_path}', __name__)
return JSONResponse({'message': 'Unable to open target video'}, status_code=HTTP_400_BAD_REQUEST)
frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT) or 0)
if frame_total <= 0 and is_video(target_path):
return JSONResponse({'message': 'Could not determine frame count for target video'}, status_code=HTTP_400_BAD_REQUEST)
sample_count = min(count, frame_total)
indices: List[int] = list(numpy.linspace(1, frame_total, num=sample_count, endpoint=True, dtype=int))
for frame_number in indices:
video_capture.set(cv2.CAP_PROP_POS_FRAMES, max(0, frame_number - 1))
ok_read, frame = video_capture.read()
if not ok_read or frame is None:
logger.warn(f'Failed reading frame {frame_number}', __name__)
continue
thumb_bgr = fit_contain_frame(frame, (preview_width, preview_height))
if thumb_bgr.shape[1] != preview_width or thumb_bgr.shape[0] != preview_height:
thumb_bgr = cv2.resize(thumb_bgr, (preview_width, preview_height))
ok_enc, buf = cv2.imencode('.jpg', thumb_bgr, [cv2.IMWRITE_JPEG_QUALITY, 50])
if not ok_enc:
logger.warn(f'JPEG encode failed for frame {frame_number}', __name__)
continue
b64 = base64.b64encode(buf.tobytes()).decode('ascii')
previews.append(b64)
logger.info(f'Returned {len(previews)}/{sample_count} timeline frames at {preview_width}x{preview_height}', __name__)
return JSONResponse({
'message': 'ok',
'count': len(previews),
'requested': count,
'width': preview_width,
'height': preview_height,
'format': 'jpeg',
'frames': previews
}, status_code=HTTP_200_OK)
-98
View File
@@ -1,98 +0,0 @@
import subprocess
from functools import lru_cache
from typing import Optional
from starlette.datastructures import Headers
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.types import ASGIApp, Receive, Scope, Send
from starlette.websockets import WebSocket
@lru_cache(maxsize = 1)
def get_api_version() -> str:
try:
result = subprocess.run(['git', 'rev-parse', 'HEAD'], capture_output = True, text = True, check = True)
return result.stdout.strip()
except Exception:
return 'unknown'
def check_version_match(request : Request) -> Optional[JSONResponse]:
client_version = request.headers.get('X-API-Version')
server_version = get_api_version()
if not client_version:
return JSONResponse({'error': 'Missing X-API-Version header', 'server_version': server_version}, status_code = 400)
if client_version != server_version:
return JSONResponse({'error': 'Version mismatch', 'client_version': client_version, 'server_version': server_version}, status_code = 409)
return None
def check_version_match_websocket(websocket : WebSocket) -> Optional[str]:
client_version = websocket.headers.get('X-API-Version')
server_version = get_api_version()
if not client_version:
return f'Missing X-API-Version header, server version: {server_version}'
if client_version != server_version:
return f'Version mismatch: client={client_version}, server={server_version}'
return None
async def version_guard_middleware(scope : Scope, receive : Receive, send : Send, app : ASGIApp) -> None:
if scope['type'] == 'http':
# Skip version check for OPTIONS requests (CORS preflight)
if scope.get('method') == 'OPTIONS':
await app(scope, receive, send)
return
headers = Headers(scope = scope)
client_version = headers.get('X-API-Version')
server_version = get_api_version()
if not client_version:
response = JSONResponse({'error': 'Missing X-API-Version header', 'server_version': server_version}, status_code = 400)
await response(scope, receive, send)
return
if client_version != server_version:
response = JSONResponse({'error': 'Version mismatch', 'client_version': client_version, 'server_version': server_version}, status_code = 409)
await response(scope, receive, send)
return
if scope['type'] == 'websocket':
headers = Headers(scope = scope)
client_version = headers.get('X-API-Version')
# For WebSocket connections, also check subprotocols since browsers can't set custom headers
if not client_version:
protocol_header = headers.get('Sec-WebSocket-Protocol')
if protocol_header:
# Parse subprotocols to find api_version
protocols = [p.strip() for p in protocol_header.split(',')]
for protocol in protocols:
if protocol.startswith('api_version.'):
client_version = protocol.split('.', 1)[1]
break
server_version = get_api_version()
if not client_version or client_version != server_version:
websocket = WebSocket(scope, receive = receive, send = send)
reason = f'Missing X-API-Version header, server version: {server_version}' if not client_version else f'Version mismatch: client={client_version}, server={server_version}'
await websocket.close(code = 1008, reason = reason)
return
await app(scope, receive, send)
def create_version_guard(app : ASGIApp) -> ASGIApp:
async def version_guard_app(scope : Scope, receive : Receive, send : Send) -> None:
await version_guard_middleware(scope, receive, send, app)
return version_guard_app
+5 -3
View File
@@ -5,12 +5,14 @@ from facefusion.types import AppContext
def detect_app_context() -> AppContext:
jobs_path = os.path.join('facefusion', 'jobs')
uis_path = os.path.join('facefusion', 'uis')
frame = sys._getframe(1)
while frame:
if os.path.join('facefusion', 'jobs') in frame.f_code.co_filename:
if jobs_path in frame.f_code.co_filename:
return 'cli'
if os.path.join('facefusion', 'apis') in frame.f_code.co_filename:
return 'api'
if uis_path in frame.f_code.co_filename:
return 'ui'
frame = frame.f_back
return 'cli'
@@ -1,4 +1,6 @@
from facefusion import state_manager
from facefusion.filesystem import get_file_name, is_video, resolve_file_paths
from facefusion.jobs import job_store
from facefusion.normalizer import normalize_fps, normalize_space
from facefusion.processors.core import get_processors_modules
from facefusion.types import ApplyStateItem, Args
@@ -6,30 +8,22 @@ from facefusion.vision import detect_video_fps
def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
# general
apply_state_item('command', args.get('command'))
# workflow
apply_state_item('workflow', args.get('workflow'))
# paths
apply_state_item('temp_path', args.get('temp_path'))
apply_state_item('jobs_path', args.get('jobs_path'))
apply_state_item('source_paths', args.get('source_paths'))
apply_state_item('target_path', args.get('target_path'))
apply_state_item('output_path', args.get('output_path'))
# patterns
apply_state_item('source_pattern', args.get('source_pattern'))
apply_state_item('target_pattern', args.get('target_pattern'))
apply_state_item('output_pattern', args.get('output_pattern'))
# face detector
apply_state_item('face_detector_model', args.get('face_detector_model'))
apply_state_item('face_detector_size', args.get('face_detector_size'))
apply_state_item('face_detector_margin', normalize_space(args.get('face_detector_margin')))
apply_state_item('face_detector_angles', args.get('face_detector_angles'))
apply_state_item('face_detector_score', args.get('face_detector_score'))
# face landmarker
apply_state_item('face_landmarker_model', args.get('face_landmarker_model'))
apply_state_item('face_landmarker_score', args.get('face_landmarker_score'))
# face selector
apply_state_item('face_selector_mode', args.get('face_selector_mode'))
apply_state_item('face_selector_order', args.get('face_selector_order'))
apply_state_item('face_selector_age_start', args.get('face_selector_age_start'))
@@ -39,7 +33,7 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('reference_face_position', args.get('reference_face_position'))
apply_state_item('reference_face_distance', args.get('reference_face_distance'))
apply_state_item('reference_frame_number', args.get('reference_frame_number'))
# face masker
apply_state_item('face_tracker_score', args.get('face_tracker_score'))
apply_state_item('face_occluder_model', args.get('face_occluder_model'))
apply_state_item('face_parser_model', args.get('face_parser_model'))
apply_state_item('face_mask_types', args.get('face_mask_types'))
@@ -47,13 +41,12 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('face_mask_regions', args.get('face_mask_regions'))
apply_state_item('face_mask_blur', args.get('face_mask_blur'))
apply_state_item('face_mask_padding', normalize_space(args.get('face_mask_padding')))
# voice extractor
apply_state_item('voice_extractor_model', args.get('voice_extractor_model'))
# frame extraction
apply_state_item('trim_frame_start', args.get('trim_frame_start'))
apply_state_item('trim_frame_end', args.get('trim_frame_end'))
apply_state_item('temp_frame_format', args.get('temp_frame_format'))
# output creation
apply_state_item('keep_temp', args.get('keep_temp'))
apply_state_item('target_frame_amount', args.get('target_frame_amount'))
apply_state_item('output_image_quality', args.get('output_image_quality'))
apply_state_item('output_image_scale', args.get('output_image_scale'))
apply_state_item('output_audio_encoder', args.get('output_audio_encoder'))
@@ -63,34 +56,63 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('output_video_preset', args.get('output_video_preset'))
apply_state_item('output_video_quality', args.get('output_video_quality'))
apply_state_item('output_video_scale', args.get('output_video_scale'))
if args.get('output_video_fps') or is_video(args.get('target_path')):
output_video_fps = normalize_fps(args.get('output_video_fps')) or detect_video_fps(args.get('target_path'))
apply_state_item('output_video_fps', output_video_fps)
# processors
available_processors = [ get_file_name(file_path) for file_path in resolve_file_paths('facefusion/processors/modules') ]
apply_state_item('processors', args.get('processors'))
for processor_module in get_processors_modules(available_processors):
processor_module.apply_args(args, apply_state_item)
# execution
apply_state_item('open_browser', args.get('open_browser'))
apply_state_item('ui_layouts', args.get('ui_layouts'))
apply_state_item('ui_workflow', args.get('ui_workflow'))
apply_state_item('execution_device_ids', args.get('execution_device_ids'))
apply_state_item('execution_providers', args.get('execution_providers'))
apply_state_item('execution_thread_count', args.get('execution_thread_count'))
# download
apply_state_item('download_providers', args.get('download_providers'))
apply_state_item('download_scope', args.get('download_scope'))
# benchmark
apply_state_item('benchmark_mode', args.get('benchmark_mode'))
apply_state_item('benchmark_resolutions', args.get('benchmark_resolutions'))
apply_state_item('benchmark_cycle_count', args.get('benchmark_cycle_count'))
# api
apply_state_item('api_host', args.get('api_host'))
apply_state_item('api_port', args.get('api_port'))
# memory
apply_state_item('video_memory_strategy', args.get('video_memory_strategy'))
# misc
apply_state_item('log_level', args.get('log_level'))
apply_state_item('halt_on_error', args.get('halt_on_error'))
# jobs
apply_state_item('job_id', args.get('job_id'))
apply_state_item('job_status', args.get('job_status'))
apply_state_item('step_index', args.get('step_index'))
def reduce_step_args(args : Args) -> Args:
step_args =\
{
key: args[key] for key in args if key in job_store.get_step_keys()
}
return step_args
def reduce_job_args(args : Args) -> Args:
job_args =\
{
key: args[key] for key in args if key in job_store.get_job_keys()
}
return job_args
def collect_step_args() -> Args:
step_args =\
{
key: state_manager.get_item(key) for key in job_store.get_step_keys() #type:ignore[arg-type]
}
return step_args
def collect_job_args() -> Args:
job_args =\
{
key: state_manager.get_item(key) for key in job_store.get_job_keys() #type:ignore[arg-type]
}
return job_args
-66
View File
@@ -1,66 +0,0 @@
from typing import List
from facefusion.types import Args, ArgsStore, Scope
ARGS_STORE : ArgsStore =\
{
'api': [],
'cli': [],
'sys': []
}
def get_api_args() -> List[str]:
return ARGS_STORE.get('api')
def get_sys_args() -> List[str]:
return ARGS_STORE.get('sys')
def get_cli_args() -> List[str]:
return ARGS_STORE.get('cli')
def register_args(keys : List[str], scopes : List[Scope]) -> None:
for key in keys:
for scope in scopes:
if scope == 'api':
ARGS_STORE['api'].append(key)
if scope == 'cli':
ARGS_STORE['cli'].append(key)
if scope == 'sys':
ARGS_STORE['sys'].append(key)
def filter_api_args(args : Args) -> Args:
api_args =\
{
key: args.get(key) for key in args if key in get_api_args() #type:ignore[literal-required]
}
return api_args
def filter_sys_args(args : Args) -> Args:
sys_args =\
{
key: args.get(key) for key in args if key in get_sys_args() #type:ignore[literal-required]
}
return sys_args
def filter_cli_args(args : Args) -> Args:
cli_args =\
{
key: args.get(key) for key in args if key in get_cli_args() #type:ignore[literal-required]
}
return cli_args
def filter_step_args(args : Args) -> Args:
step_args =\
{
key: args.get(key) for key in args if key in get_cli_args() and key not in get_sys_args() #type:ignore[literal-required]
}
return step_args
-111
View File
@@ -1,111 +0,0 @@
import os
import uuid
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, TypeAlias
from facefusion import filesystem, state_manager
from facefusion.session_context import get_session_id
AssetRegistry : TypeAlias = Dict[str, Dict[str, Any]]
def get_asset_registry() -> AssetRegistry:
registry = state_manager.get_item('asset_registry')
if not registry:
registry = {}
state_manager.set_item('asset_registry', registry)
return registry
def register(asset_type : str, file_path : str, filename : str = None, metadata : Optional[Dict[str, Any]] = None) -> str:
if asset_type not in ['source', 'target', 'output']:
raise ValueError(f"Invalid asset_type: {asset_type}. Must be 'source', 'target', or 'output'")
asset_id = str(uuid.uuid4())
session_id = get_session_id()
if not session_id:
raise ValueError("No active session - cannot register asset without session_id")
if not filename:
filename = os.path.basename(file_path)
file_size = os.path.getsize(file_path)
file_format = filesystem.get_file_format(file_path)
media_type = None
if filesystem.is_image(file_path):
media_type = 'image'
if filesystem.is_video(file_path):
media_type = 'video'
if filesystem.is_audio(file_path):
media_type = 'audio'
asset_data =\
{
'id': asset_id,
'session_id': session_id,
'type': asset_type,
'media_type': media_type,
'format': file_format,
'path': file_path,
'filename': filename,
'size': file_size,
'created_at': datetime.now(timezone.utc).isoformat()
}
if metadata:
asset_data['metadata'] = metadata
registry = get_asset_registry()
registry[asset_id] = asset_data
state_manager.set_item('asset_registry', registry)
return asset_id
def get_asset(asset_id : str) -> Optional[Dict[str, Any]]:
registry = get_asset_registry()
return registry.get(asset_id)
def list_assets(asset_type : Optional[str] = None) -> List[Dict[str, Any]]:
registry = get_asset_registry()
session_id = get_session_id()
assets = list(registry.values())
if session_id:
assets = [a for a in assets if a.get('session_id') == session_id]
if asset_type:
if asset_type not in ['source', 'target', 'output']:
raise ValueError(f"Invalid asset_type: {asset_type}")
assets = [a for a in assets if a.get('type') == asset_type]
return assets
def delete_asset(asset_id : str) -> bool:
registry = get_asset_registry()
asset = registry.get(asset_id)
if not asset:
return False
file_path = asset.get('path')
if file_path and os.path.exists(file_path):
os.remove(file_path)
del registry[asset_id]
state_manager.set_item('asset_registry', registry)
return True
def cleanup_session_assets(session_id : str) -> None:
registry = get_asset_registry()
assets_to_delete = [aid for aid, asset in registry.items() if asset.get('session_id') == session_id]
for asset_id in assets_to_delete:
delete_asset(asset_id)
+2 -30
View File
@@ -1,6 +1,5 @@
import math
from functools import lru_cache
from typing import Any, List, Optional, Tuple
from typing import Any, List, Optional
import numpy
import scipy
@@ -8,8 +7,7 @@ from numpy.typing import NDArray
from facefusion.ffmpeg import read_audio_buffer
from facefusion.filesystem import is_audio
from facefusion.media_helper import restrict_trim_frame
from facefusion.types import Audio, AudioFrame, Duration, Fps, Mel, MelFilterBank, Spectrogram
from facefusion.types import Audio, AudioFrame, Fps, Mel, MelFilterBank, Spectrogram
from facefusion.voice_extractor import batch_extract_voice
@@ -143,29 +141,3 @@ def create_spectrogram(audio : Audio) -> Spectrogram:
spectrogram = scipy.signal.stft(audio, nperseg = mel_bin_total, nfft = mel_bin_total, noverlap = mel_bin_overlap)[2]
spectrogram = numpy.dot(mel_filter_bank, numpy.abs(spectrogram))
return spectrogram
def count_audio_frame_total(audio_path : str, fps : Fps) -> int:
audio_duration = detect_audio_duration(audio_path)
if audio_duration > 0:
return math.ceil(audio_duration * fps)
return 0
def detect_audio_duration(audio_path : str) -> Duration:
audio_sample_rate = 48000
audio_sample_size = 16
audio_channel_total = 2
if is_audio(audio_path):
audio_buffer = read_audio_buffer(audio_path, audio_sample_rate, audio_sample_size, audio_channel_total)
if audio_buffer:
audio = numpy.frombuffer(audio_buffer, dtype = numpy.int16).reshape(-1, audio_channel_total)
audio_duration = len(audio) / audio_sample_rate
return audio_duration
return 0
def restrict_trim_audio_frame(audio_path : str, fps : Fps, trim_frame_start : Optional[int], trim_frame_end : Optional[int]) -> Tuple[int, int]:
audio_frame_total = count_audio_frame_total(audio_path, fps)
return restrict_trim_frame(audio_frame_total, trim_frame_start, trim_frame_end)
+3 -3
View File
@@ -9,7 +9,7 @@ import facefusion.choices
from facefusion import content_analyser, core, state_manager
from facefusion.cli_helper import render_table
from facefusion.download import conditional_download, resolve_download_url
from facefusion.face_store import clear_static_faces
from facefusion.face_store import clear_faces
from facefusion.filesystem import get_file_extension
from facefusion.types import BenchmarkCycleSet
from facefusion.vision import count_video_frame_total, detect_video_fps
@@ -64,7 +64,7 @@ def cycle(cycle_count : int) -> BenchmarkCycleSet:
if state_manager.get_item('benchmark_mode') == 'cold':
content_analyser.analyse_image.cache_clear()
content_analyser.analyse_video.cache_clear()
clear_static_faces()
clear_faces()
start_time = perf_counter()
core.conditional_process()
@@ -89,7 +89,7 @@ def cycle(cycle_count : int) -> BenchmarkCycleSet:
def suggest_output_path(target_path : str) -> str:
target_file_extension = get_file_extension(target_path)
return os.path.join(tempfile.gettempdir(), hashlib.sha1().hexdigest()[:8] + target_file_extension)
return os.path.join(tempfile.gettempdir(), hashlib.sha1(target_path.encode()).hexdigest() + target_file_extension)
def render() -> None:
+2 -2
View File
@@ -43,9 +43,9 @@ def detect_local_camera_ids(id_start : int, id_end : int) -> List[int]:
local_camera_ids = []
for camera_id in range(id_start, id_end):
cv2.setLogLevel(0)
cv2.utils.logging.setLogLevel(0)
camera_capture = get_local_camera_capture(camera_id)
cv2.setLogLevel(3)
cv2.utils.logging.setLogLevel(3)
if camera_capture and camera_capture.isOpened():
local_camera_ids.append(camera_id)
+39 -35
View File
@@ -1,8 +1,8 @@
import logging
from typing import List, Sequence
from typing import List, Sequence, get_args
from facefusion.common_helper import create_float_range, create_int_range
from facefusion.types import Angle, AudioEncoder, AudioFormat, AudioTypeSet, BenchmarkMode, BenchmarkResolution, BenchmarkSet, DownloadProvider, DownloadProviderSet, DownloadScope, EncoderSet, ExecutionProvider, ExecutionProviderSet, FaceDetectorModel, FaceDetectorSet, FaceLandmarkerModel, FaceMaskArea, FaceMaskAreaSet, FaceMaskRegion, FaceMaskRegionSet, FaceMaskType, FaceOccluderModel, FaceParserModel, FaceSelectorMode, FaceSelectorOrder, Gender, ImageFormat, ImageTypeSet, JobStatus, LogLevel, LogLevelSet, Race, Score, TempFrameFormat, VideoEncoder, VideoFormat, VideoMemoryStrategy, VideoPreset, VideoTypeSet, VoiceExtractorModel, WorkFlow
from facefusion.types import Angle, AudioEncoder, AudioFormat, AudioTypeSet, BenchmarkMode, BenchmarkResolution, BenchmarkSet, DownloadProvider, DownloadProviderSet, DownloadScope, EncoderSet, ExecutionProvider, ExecutionProviderSet, FaceDetectorModel, FaceDetectorSet, FaceLandmarkerModel, FaceMaskArea, FaceMaskAreaSet, FaceMaskRegion, FaceMaskRegionSet, FaceMaskType, FaceOccluderModel, FaceParserModel, FaceSelectorGender, FaceSelectorMode, FaceSelectorOrder, FaceSelectorRace, Gender, ImageFormat, ImageTypeSet, JobStatus, LogLevel, LogLevelSet, Race, Score, TempFrameFormat, UiWorkflow, VideoEncoder, VideoFormat, VideoMemoryStrategy, VideoPreset, VideoTypeSet, VoiceExtractorModel
face_detector_set : FaceDetectorSet =\
{
@@ -12,15 +12,17 @@ face_detector_set : FaceDetectorSet =\
'yolo_face': [ '640x640' ],
'yunet': [ '640x640' ]
}
face_detector_models : List[FaceDetectorModel] = list(face_detector_set.keys())
face_landmarker_models : List[FaceLandmarkerModel] = [ 'many', '2dfan4', 'peppa_wutz' ]
face_selector_modes : List[FaceSelectorMode] = [ 'many', 'one', 'reference' ]
face_selector_orders : List[FaceSelectorOrder] = [ 'left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small', 'best-worst', 'worst-best' ]
face_selector_genders : List[Gender] = [ 'female', 'male' ]
face_selector_races : List[Race] = [ 'white', 'black', 'latino', 'asian', 'indian', 'arabic' ]
face_occluder_models : List[FaceOccluderModel] = [ 'many', 'xseg_1', 'xseg_2', 'xseg_3' ]
face_parser_models : List[FaceParserModel] = [ 'bisenet_resnet_18', 'bisenet_resnet_34' ]
face_mask_types : List[FaceMaskType] = [ 'box', 'occlusion', 'area', 'region' ]
face_detector_models : List[FaceDetectorModel] = list(get_args(FaceDetectorModel))
face_landmarker_models : List[FaceLandmarkerModel] = list(get_args(FaceLandmarkerModel))
face_selector_modes : List[FaceSelectorMode] = list(get_args(FaceSelectorMode))
face_selector_orders : List[FaceSelectorOrder] = list(get_args(FaceSelectorOrder))
genders : List[Gender] = list(get_args(Gender))
races : List[Race] = list(get_args(Race))
face_selector_genders : List[FaceSelectorGender] = list(get_args(FaceSelectorGender))
face_selector_races : List[FaceSelectorRace] = list(get_args(FaceSelectorRace))
face_occluder_models : List[FaceOccluderModel] = list(get_args(FaceOccluderModel))
face_parser_models : List[FaceParserModel] = list(get_args(FaceParserModel))
face_mask_types : List[FaceMaskType] = list(get_args(FaceMaskType))
face_mask_area_set : FaceMaskAreaSet =\
{
'upper-face': [ 0, 1, 2, 31, 32, 33, 34, 35, 14, 15, 16, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17 ],
@@ -40,12 +42,10 @@ face_mask_region_set : FaceMaskRegionSet =\
'upper-lip': 12,
'lower-lip': 13
}
face_mask_areas : List[FaceMaskArea] = list(face_mask_area_set.keys())
face_mask_regions : List[FaceMaskRegion] = list(face_mask_region_set.keys())
face_mask_areas : List[FaceMaskArea] = list(get_args(FaceMaskArea))
face_mask_regions : List[FaceMaskRegion] = list(get_args(FaceMaskRegion))
voice_extractor_models : List[VoiceExtractorModel] = [ 'kim_vocal_1', 'kim_vocal_2', 'uvr_mdxnet' ]
workflows : List[WorkFlow] = [ 'auto', 'audio-to-image', 'image-to-image', 'image-to-video' ]
voice_extractor_models : List[VoiceExtractorModel] = list(get_args(VoiceExtractorModel))
audio_type_set : AudioTypeSet =\
{
@@ -76,21 +76,21 @@ video_type_set : VideoTypeSet =\
'webm': 'video/webm',
'wmv': 'video/x-ms-wmv'
}
audio_formats : List[AudioFormat] = list(audio_type_set.keys())
image_formats : List[ImageFormat] = list(image_type_set.keys())
video_formats : List[VideoFormat] = list(video_type_set.keys())
temp_frame_formats : List[TempFrameFormat] = [ 'bmp', 'jpeg', 'png', 'tiff' ]
audio_formats : List[AudioFormat] = list(get_args(AudioFormat))
image_formats : List[ImageFormat] = list(get_args(ImageFormat))
video_formats : List[VideoFormat] = list(get_args(VideoFormat))
temp_frame_formats : List[TempFrameFormat] = list(get_args(TempFrameFormat))
output_audio_encoders : List[AudioEncoder] = list(get_args(AudioEncoder))
output_video_encoders : List[VideoEncoder] = list(get_args(VideoEncoder))
output_encoder_set : EncoderSet =\
{
'audio': [ 'flac', 'aac', 'libmp3lame', 'libopus', 'libvorbis', 'pcm_s16le', 'pcm_s32le' ],
'video': [ 'libx264', 'libx264rgb', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc', 'h264_amf', 'hevc_amf', 'h264_qsv', 'hevc_qsv', 'h264_videotoolbox', 'hevc_videotoolbox', 'rawvideo' ]
'audio': output_audio_encoders,
'video': output_video_encoders
}
output_audio_encoders : List[AudioEncoder] = output_encoder_set.get('audio')
output_video_encoders : List[VideoEncoder] = output_encoder_set.get('video')
output_video_presets : List[VideoPreset] = [ 'ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow' ]
output_video_presets : List[VideoPreset] = list(get_args(VideoPreset))
benchmark_modes : List[BenchmarkMode] = [ 'warm', 'cold' ]
benchmark_modes : List[BenchmarkMode] = list(get_args(BenchmarkMode))
benchmark_set : BenchmarkSet =\
{
'240p': '.assets/examples/target-240p.mp4',
@@ -101,20 +101,21 @@ benchmark_set : BenchmarkSet =\
'1440p': '.assets/examples/target-1440p.mp4',
'2160p': '.assets/examples/target-2160p.mp4'
}
benchmark_resolutions : List[BenchmarkResolution] = list(benchmark_set.keys())
benchmark_resolutions : List[BenchmarkResolution] = list(get_args(BenchmarkResolution))
execution_provider_set : ExecutionProviderSet =\
{
'cuda': 'CUDAExecutionProvider',
'tensorrt': 'TensorrtExecutionProvider',
'directml': 'DmlExecutionProvider',
'rocm': 'ROCMExecutionProvider',
'migraphx': 'MIGraphXExecutionProvider',
'openvino': 'OpenVINOExecutionProvider',
'coreml': 'CoreMLExecutionProvider',
'openvino': 'OpenVINOExecutionProvider',
'qnn': 'QNNExecutionProvider',
'directml': 'DmlExecutionProvider',
'cpu': 'CPUExecutionProvider'
}
execution_providers : List[ExecutionProvider] = list(execution_provider_set.keys())
execution_providers : List[ExecutionProvider] = list(get_args(ExecutionProvider))
download_provider_set : DownloadProviderSet =\
{
'github':
@@ -135,10 +136,10 @@ download_provider_set : DownloadProviderSet =\
'path': '/facefusion/{base_name}/resolve/main/{file_name}'
}
}
download_providers : List[DownloadProvider] = list(download_provider_set.keys())
download_scopes : List[DownloadScope] = [ 'lite', 'full' ]
download_providers : List[DownloadProvider] = list(get_args(DownloadProvider))
download_scopes : List[DownloadScope] = list(get_args(DownloadScope))
video_memory_strategies : List[VideoMemoryStrategy] = [ 'strict', 'moderate', 'tolerant' ]
video_memory_strategies : List[VideoMemoryStrategy] = list(get_args(VideoMemoryStrategy))
log_level_set : LogLevelSet =\
{
@@ -147,9 +148,10 @@ log_level_set : LogLevelSet =\
'info': logging.INFO,
'debug': logging.DEBUG
}
log_levels : List[LogLevel] = list(log_level_set.keys())
log_levels : List[LogLevel] = list(get_args(LogLevel))
job_statuses : List[JobStatus] = [ 'drafted', 'queued', 'completed', 'failed' ]
ui_workflows : List[UiWorkflow] = list(get_args(UiWorkflow))
job_statuses : List[JobStatus] = list(get_args(JobStatus))
benchmark_cycle_count_range : Sequence[int] = create_int_range(1, 10, 1)
execution_thread_count_range : Sequence[int] = create_int_range(1, 32, 1)
@@ -161,6 +163,8 @@ face_mask_blur_range : Sequence[float] = create_float_range(0.0, 1.0, 0.05)
face_mask_padding_range : Sequence[int] = create_int_range(0, 100, 1)
face_selector_age_range : Sequence[int] = create_int_range(0, 100, 1)
reference_face_distance_range : Sequence[float] = create_float_range(0.0, 1.0, 0.05)
face_tracker_score_range : Sequence[Score] = create_float_range(0.0, 0.5, 0.05)
target_frame_amount_range : Sequence[int] = create_int_range(0, 10, 1)
output_image_quality_range : Sequence[int] = create_int_range(0, 100, 1)
output_image_scale_range : Sequence[float] = create_float_range(0.25, 8.0, 0.25)
output_audio_quality_range : Sequence[int] = create_int_range(0, 100, 1)
View File
-70
View File
@@ -1,70 +0,0 @@
import ctypes
import struct
from typing import Optional
from facefusion.libraries import aom as aom_module
from facefusion.types import AomDecoder, AomPointer
def create(thread_count : int) -> Optional[AomDecoder]:
aom_library = aom_module.create_static_library()
if aom_library:
aom_decoder = ctypes.create_string_buffer(128)
aom_codec = ctypes.c_void_p.in_dll(aom_library, 'aom_codec_av1_dx_algo')
config_buffer = ctypes.create_string_buffer(128)
struct.pack_into('I', config_buffer, 0, thread_count)
struct.pack_into('I', config_buffer, 12, 1)
if aom_library.aom_codec_dec_init_ver(aom_decoder, ctypes.byref(aom_codec), config_buffer, 0, 22) == 0:
return aom_decoder
return None
def decode(aom_decoder : AomDecoder, input_buffer : bytes) -> Optional[AomPointer]:
aom_library = aom_module.create_static_library()
if aom_library and input_buffer:
input_total = len(input_buffer)
temp_buffer = ctypes.create_string_buffer(input_buffer)
if aom_library.aom_codec_decode(aom_decoder, temp_buffer, input_total, None) == 0:
address = aom_library.aom_codec_get_frame(aom_decoder, ctypes.byref(ctypes.c_void_p(0)))
if address:
frame_width = ctypes.c_uint.from_address(address + 28).value & ~1
frame_height = ctypes.c_uint.from_address(address + 32).value & ~1
return AomPointer(
buffer = collect(address, frame_width, frame_height),
resolution = (frame_width, frame_height)
)
return None
def collect(address : int, frame_width : int, frame_height : int) -> bytes:
output_parts = []
for index in range(3):
plane_pointer = ctypes.c_void_p.from_address(address + 64 + index * 8).value
stride = ctypes.c_int.from_address(address + 88 + index * 4).value
plane_width = frame_width >> (index > 0)
plane_height = frame_height >> (index > 0)
if stride == plane_width:
output_parts.append(ctypes.string_at(plane_pointer, plane_width * plane_height))
else:
for row in range(plane_height):
output_parts.append(ctypes.string_at(plane_pointer + row * stride, plane_width))
return bytes().join(output_parts)
def destroy(aom_decoder : AomDecoder) -> None:
aom_library = aom_module.create_static_library()
if aom_library:
aom_library.aom_codec_destroy(aom_decoder)
-94
View File
@@ -1,94 +0,0 @@
import ctypes
import struct
from typing import Optional
from facefusion.libraries import aom as aom_module
from facefusion.types import AomEncoder, BitRate, Resolution
def create(frame_resolution : Resolution, bitrate : BitRate, thread_count : int, cpu_count : int) -> Optional[AomEncoder]:
aom_library = aom_module.create_static_library()
if aom_library:
aom_encoder = ctypes.create_string_buffer(1152)
aom_codec = ctypes.c_void_p.in_dll(aom_library, 'aom_codec_av1_cx_algo')
config_buffer = ctypes.create_string_buffer(1024)
if aom_library.aom_codec_enc_config_default(ctypes.byref(aom_codec), config_buffer, 1) == 0:
struct.pack_into('I', config_buffer, 4, thread_count)
struct.pack_into('I', config_buffer, 12, frame_resolution[0])
struct.pack_into('I', config_buffer, 16, frame_resolution[1])
struct.pack_into('I', config_buffer, 136, bitrate)
struct.pack_into('I', config_buffer, 192, 30)
if aom_library.aom_codec_enc_init_ver(aom_encoder, ctypes.byref(aom_codec), config_buffer, 0, 25) == 0:
aom_library.aom_codec_control(aom_encoder, 13, ctypes.c_int(cpu_count))
aom_library.aom_codec_control(aom_encoder, 75, ctypes.c_int(1))
aom_library.aom_codec_control(aom_encoder, 106, ctypes.c_int(0))
aom_library.aom_codec_control(aom_encoder, 122, ctypes.c_int(0))
aom_library.aom_codec_control(aom_encoder, 123, ctypes.c_int(0))
ctypes.memmove(ctypes.addressof(aom_encoder) + 128, config_buffer, 1024)
return aom_encoder
return None
def encode(aom_encoder : AomEncoder, input_buffer : bytes, frame_resolution : Resolution, frame_index : int) -> bytes:
aom_library = aom_module.create_static_library()
output_buffer = bytes()
if aom_library:
temp_buffer = ctypes.create_string_buffer(256)
encode_buffer = ctypes.create_string_buffer(input_buffer)
if aom_library.aom_img_wrap(temp_buffer, 0x102, frame_resolution[0], frame_resolution[1], 1, encode_buffer) and aom_library.aom_codec_encode(aom_encoder, temp_buffer, frame_index, 1, 0, 1) == 0:
output_buffer = collect(aom_encoder)
return output_buffer
def collect(aom_encoder : AomEncoder) -> bytes:
aom_library = aom_module.create_static_library()
output_parts = []
packet_cursor = ctypes.c_void_p(0)
packet = aom_library.aom_codec_get_cx_data(aom_encoder, ctypes.byref(packet_cursor))
while packet:
if ctypes.c_int.from_address(packet).value == 0:
buffer_pointer = ctypes.c_void_p.from_address(packet + 8).value
buffer_size = ctypes.c_size_t.from_address(packet + 16).value
output_parts.append(ctypes.string_at(buffer_pointer, buffer_size))
packet = aom_library.aom_codec_get_cx_data(aom_encoder, ctypes.byref(packet_cursor))
return bytes().join(output_parts)
def update_resolution(aom_encoder : AomEncoder, frame_resolution : Resolution) -> bool:
aom_library = aom_module.create_static_library()
if aom_library:
struct.pack_into('I', aom_encoder, 128 + 12, frame_resolution[0])
struct.pack_into('I', aom_encoder, 128 + 16, frame_resolution[1])
return aom_library.aom_codec_enc_config_set(aom_encoder, ctypes.cast(ctypes.addressof(aom_encoder) + 128, ctypes.c_void_p)) == 0
return False
def update_bitrate(aom_encoder : AomEncoder, bitrate : BitRate) -> bool:
aom_library = aom_module.create_static_library()
if aom_library:
struct.pack_into('I', aom_encoder, 128 + 136, bitrate)
return aom_library.aom_codec_enc_config_set(aom_encoder, ctypes.cast(ctypes.addressof(aom_encoder) + 128, ctypes.c_void_p)) == 0
return False
def destroy(aom_encoder : AomEncoder) -> None:
aom_library = aom_module.create_static_library()
if aom_library:
aom_library.aom_codec_destroy(aom_encoder)
-36
View File
@@ -1,36 +0,0 @@
import ctypes
from typing import Optional
from facefusion.libraries import opus as opus_module
from facefusion.types import OpusDecoder
def create(sample_rate : int, channel_total : int) -> Optional[OpusDecoder]:
opus_library = opus_module.create_static_library()
if opus_library:
return opus_library.opus_decoder_create(sample_rate, channel_total, ctypes.byref(ctypes.c_int(0)))
return None
def decode(opus_decoder : OpusDecoder, input_buffer : bytes, frame_size : int, channel_total : int) -> bytes:
opus_library = opus_module.create_static_library()
output_buffer = bytes()
if opus_library:
input_total = len(input_buffer)
decode_buffer = (ctypes.c_float * (frame_size * channel_total))()
decode_length = opus_library.opus_decode_float(opus_decoder, input_buffer, input_total, decode_buffer, frame_size, 0)
if decode_length:
output_buffer = ctypes.string_at(ctypes.addressof(decode_buffer), decode_length * channel_total * ctypes.sizeof(ctypes.c_float))
return output_buffer
def destroy(opus_decoder : OpusDecoder) -> None:
opus_library = opus_module.create_static_library()
if opus_library:
opus_library.opus_decoder_destroy(opus_decoder)
-36
View File
@@ -1,36 +0,0 @@
import ctypes
from typing import Optional
from facefusion.libraries import opus as opus_module
from facefusion.types import OpusEncoder
def create(sample_rate : int, channel_total : int) -> Optional[OpusEncoder]:
opus_library = opus_module.create_static_library()
if opus_library:
return opus_library.opus_encoder_create(sample_rate, channel_total, 2049, ctypes.byref(ctypes.c_int(0)))
return None
def encode(opus_encoder : OpusEncoder, input_buffer : bytes, frame_size : int) -> bytes:
opus_library = opus_module.create_static_library()
output_buffer = bytes()
if opus_library:
temp_buffer = ctypes.create_string_buffer(2048)
encode_buffer = ctypes.cast(ctypes.create_string_buffer(input_buffer), ctypes.POINTER(ctypes.c_float))
encode_length = opus_library.opus_encode_float(opus_encoder, encode_buffer, frame_size, temp_buffer, 2048)
if encode_length:
output_buffer = temp_buffer.raw[:encode_length]
return output_buffer
def destroy(opus_encoder : OpusEncoder) -> None:
opus_library = opus_module.create_static_library()
if opus_library:
opus_library.opus_encoder_destroy(opus_encoder)
-74
View File
@@ -1,74 +0,0 @@
import ctypes
import struct
from typing import Optional
from facefusion.libraries import vpx as vpx_module
from facefusion.types import VpxDecoder, VpxPointer, VxpVideoCodec
def create(video_codec : VxpVideoCodec, thread_count : int) -> Optional[VpxDecoder]:
vpx_library = vpx_module.create_static_library()
if vpx_library:
vpx_decoder = ctypes.create_string_buffer(64)
vpx_algo = 'vpx_codec_vp8_dx_algo'
if video_codec == 'vp9':
vpx_algo = 'vpx_codec_vp9_dx_algo'
vpx_codec = ctypes.c_void_p.in_dll(vpx_library, vpx_algo)
config_buffer = ctypes.create_string_buffer(128)
struct.pack_into('I', config_buffer, 0, thread_count)
if vpx_library.vpx_codec_dec_init_ver(vpx_decoder, ctypes.byref(vpx_codec), config_buffer, 0, 12) == 0:
return vpx_decoder
return None
def decode(vpx_decoder : VpxDecoder, input_buffer : bytes) -> Optional[VpxPointer]:
vpx_library = vpx_module.create_static_library()
if vpx_library and input_buffer:
input_total = len(input_buffer)
temp_buffer = ctypes.create_string_buffer(input_buffer)
if vpx_library.vpx_codec_decode(vpx_decoder, temp_buffer, input_total, None, 0) == 0:
address = vpx_library.vpx_codec_get_frame(vpx_decoder, ctypes.byref(ctypes.c_void_p(0)))
if address:
frame_width = ctypes.c_uint.from_address(address + 24).value & ~1
frame_height = ctypes.c_uint.from_address(address + 28).value & ~1
return VpxPointer(
buffer = collect(address, frame_width, frame_height),
resolution = (frame_width, frame_height)
)
return None
def collect(address : int, frame_width : int, frame_height : int) -> bytes:
output_parts = []
for index in range(3):
plane_pointer = ctypes.c_void_p.from_address(address + 48 + index * 8).value
stride = ctypes.c_int.from_address(address + 80 + index * 4).value
plane_width = frame_width >> (index > 0)
plane_height = frame_height >> (index > 0)
if stride == plane_width:
output_parts.append(ctypes.string_at(plane_pointer, plane_width * plane_height))
else:
for row in range(plane_height):
output_parts.append(ctypes.string_at(plane_pointer + row * stride, plane_width))
return bytes().join(output_parts)
def destroy(vpx_decoder : VpxDecoder) -> None:
vpx_library = vpx_module.create_static_library()
if vpx_library:
vpx_library.vpx_codec_destroy(vpx_decoder)
-104
View File
@@ -1,104 +0,0 @@
import ctypes
import struct
from typing import Optional
from facefusion.libraries import vpx as vpx_module
from facefusion.types import BitRate, Resolution, VpxEncoder, VxpVideoCodec
def create(video_codec : VxpVideoCodec, frame_resolution : Resolution, bitrate : BitRate, thread_count : int, cpu_count : int) -> Optional[VpxEncoder]:
vpx_library = vpx_module.create_static_library()
if vpx_library:
vpx_encoder = ctypes.create_string_buffer(640)
vpx_algo = 'vpx_codec_vp8_cx_algo'
if video_codec == 'vp9':
vpx_algo = 'vpx_codec_vp9_cx_algo'
vpx_codec = ctypes.c_void_p.in_dll(vpx_library, vpx_algo)
config_buffer = ctypes.create_string_buffer(512)
if vpx_library.vpx_codec_enc_config_default(ctypes.byref(vpx_codec), config_buffer, 0) == 0:
struct.pack_into('I', config_buffer, 4, thread_count)
struct.pack_into('I', config_buffer, 12, frame_resolution[0])
struct.pack_into('I', config_buffer, 16, frame_resolution[1])
struct.pack_into('I', config_buffer, 28, 1)
struct.pack_into('I', config_buffer, 36, 0)
struct.pack_into('I', config_buffer, 44, 0)
struct.pack_into('I', config_buffer, 72, 0)
struct.pack_into('I', config_buffer, 112, bitrate)
struct.pack_into('I', config_buffer, 116, 2)
struct.pack_into('I', config_buffer, 120, 50)
struct.pack_into('I', config_buffer, 124, 50)
struct.pack_into('I', config_buffer, 128, 50)
if vpx_library.vpx_codec_enc_init_ver(vpx_encoder, ctypes.byref(vpx_codec), config_buffer, 0, 39) == 0:
vpx_library.vpx_codec_control_(vpx_encoder, 13, ctypes.c_int(cpu_count))
vpx_library.vpx_codec_control_(vpx_encoder, 12, ctypes.c_int(0))
vpx_library.vpx_codec_control_(vpx_encoder, 27, ctypes.c_int(10))
ctypes.memmove(ctypes.addressof(vpx_encoder) + 64, config_buffer, 512)
return vpx_encoder
return None
def encode(vpx_encoder : VpxEncoder, input_buffer : bytes, frame_resolution : Resolution, frame_index : int) -> bytes:
vpx_library = vpx_module.create_static_library()
output_buffer = bytes()
if vpx_library:
temp_buffer = ctypes.create_string_buffer(256)
encode_buffer = ctypes.create_string_buffer(input_buffer)
if vpx_library.vpx_img_wrap(temp_buffer, 0x102, frame_resolution[0], frame_resolution[1], 1, encode_buffer) and vpx_library.vpx_codec_encode(vpx_encoder, temp_buffer, frame_index, 1, 0, 1) == 0:
output_buffer = collect(vpx_encoder)
return output_buffer
def collect(vpx_encoder : VpxEncoder) -> bytes:
vpx_library = vpx_module.create_static_library()
output_parts = []
packet_cursor = ctypes.c_void_p(0)
packet = vpx_library.vpx_codec_get_cx_data(vpx_encoder, ctypes.byref(packet_cursor))
while packet:
if ctypes.c_int.from_address(packet).value == 0:
buffer_pointer = ctypes.c_void_p.from_address(packet + 8).value
buffer_size = ctypes.c_size_t.from_address(packet + 16).value
output_parts.append(ctypes.string_at(buffer_pointer, buffer_size))
packet = vpx_library.vpx_codec_get_cx_data(vpx_encoder, ctypes.byref(packet_cursor))
return bytes().join(output_parts)
def update_resolution(vpx_encoder : VpxEncoder, frame_resolution : Resolution) -> bool:
vpx_library = vpx_module.create_static_library()
if vpx_library:
struct.pack_into('I', vpx_encoder, 64 + 12, frame_resolution[0])
struct.pack_into('I', vpx_encoder, 64 + 16, frame_resolution[1])
return vpx_library.vpx_codec_enc_config_set(vpx_encoder, ctypes.cast(ctypes.addressof(vpx_encoder) + 64, ctypes.c_void_p)) == 0
return False
def update_bitrate(vpx_encoder : VpxEncoder, bitrate : BitRate) -> bool:
vpx_library = vpx_module.create_static_library()
if vpx_library:
struct.pack_into('I', vpx_encoder, 64 + 112, bitrate)
return vpx_library.vpx_codec_enc_config_set(vpx_encoder, ctypes.cast(ctypes.addressof(vpx_encoder) + 64, ctypes.c_void_p)) == 0
return False
def destroy(vpx_encoder : VpxEncoder) -> None:
vpx_library = vpx_module.create_static_library()
if vpx_library:
vpx_library.vpx_codec_destroy(vpx_encoder)
+6
View File
@@ -78,6 +78,12 @@ def get_first(__list__ : Any) -> Any:
return None
def get_middle(__list__ : Any) -> Any:
if isinstance(__list__, Sequence) and __list__:
return __list__[len(__list__) // 2]
return None
def get_last(__list__ : Any) -> Any:
if isinstance(__list__, Reversible):
return next(reversed(__list__), None)
+41
View File
@@ -0,0 +1,41 @@
import os
import sys
from typing import List
from facefusion.common_helper import is_linux, is_windows
def setup() -> None:
conda_prefix = os.getenv('CONDA_PREFIX')
conda_ready = os.getenv('CONDA_READY')
if conda_prefix and not conda_ready:
if is_linux():
python_id = 'python' + str(sys.version_info.major) + '.' + str(sys.version_info.minor)
library_paths : List[str] =\
[
os.path.join(conda_prefix, 'lib'),
os.path.join(conda_prefix, 'lib', python_id, 'site-packages', 'tensorrt_libs')
]
library_paths = list(filter(os.path.exists, library_paths))
if library_paths:
if os.getenv('LD_LIBRARY_PATH'):
library_paths.append(os.getenv('LD_LIBRARY_PATH'))
os.environ['LD_LIBRARY_PATH'] = os.pathsep.join(library_paths)
os.environ['CONDA_READY'] = '1'
os.execv(sys.executable, [ sys.executable ] + sys.argv)
if is_windows():
library_paths =\
[
os.path.join(conda_prefix, 'Lib'),
os.path.join(conda_prefix, 'Lib', 'site-packages', 'tensorrt_libs')
]
library_paths = list(filter(os.path.exists, library_paths))
if library_paths:
if os.getenv('PATH'):
library_paths.append(os.getenv('PATH'))
os.environ['PATH'] = os.pathsep.join(library_paths)
os.environ['CONDA_READY'] = '1'
+12 -21
View File
@@ -1,29 +1,20 @@
from configparser import ConfigParser
from functools import lru_cache
from typing import List, Optional
from facefusion import state_manager
from facefusion.common_helper import cast_bool, cast_float, cast_int
CONFIG_PARSER = None
def get_config_parser() -> ConfigParser:
global CONFIG_PARSER
if CONFIG_PARSER is None:
CONFIG_PARSER = ConfigParser()
CONFIG_PARSER.read(state_manager.get_item('config_path'), encoding = 'utf-8')
return CONFIG_PARSER
def clear_config_parser() -> None:
global CONFIG_PARSER
CONFIG_PARSER = None
@lru_cache
def get_static_config_parser() -> ConfigParser:
config_parser = ConfigParser()
config_parser.read(state_manager.get_item('config_path'), encoding = 'utf-8')
return config_parser
def get_str_value(section : str, option : str, fallback : Optional[str] = None) -> Optional[str]:
config_parser = get_config_parser()
config_parser = get_static_config_parser()
if config_parser.has_option(section, option) and config_parser.get(section, option).strip():
return config_parser.get(section, option)
@@ -31,7 +22,7 @@ def get_str_value(section : str, option : str, fallback : Optional[str] = None)
def get_int_value(section : str, option : str, fallback : Optional[str] = None) -> Optional[int]:
config_parser = get_config_parser()
config_parser = get_static_config_parser()
if config_parser.has_option(section, option) and config_parser.get(section, option).strip():
return config_parser.getint(section, option)
@@ -39,7 +30,7 @@ def get_int_value(section : str, option : str, fallback : Optional[str] = None)
def get_float_value(section : str, option : str, fallback : Optional[str] = None) -> Optional[float]:
config_parser = get_config_parser()
config_parser = get_static_config_parser()
if config_parser.has_option(section, option) and config_parser.get(section, option).strip():
return config_parser.getfloat(section, option)
@@ -47,7 +38,7 @@ def get_float_value(section : str, option : str, fallback : Optional[str] = None
def get_bool_value(section : str, option : str, fallback : Optional[str] = None) -> Optional[bool]:
config_parser = get_config_parser()
config_parser = get_static_config_parser()
if config_parser.has_option(section, option) and config_parser.get(section, option).strip():
return config_parser.getboolean(section, option)
@@ -55,7 +46,7 @@ def get_bool_value(section : str, option : str, fallback : Optional[str] = None)
def get_str_list(section : str, option : str, fallback : Optional[str] = None) -> Optional[List[str]]:
config_parser = get_config_parser()
config_parser = get_static_config_parser()
if config_parser.has_option(section, option) and config_parser.get(section, option).strip():
return config_parser.get(section, option).split()
@@ -65,7 +56,7 @@ def get_str_list(section : str, option : str, fallback : Optional[str] = None) -
def get_int_list(section : str, option : str, fallback : Optional[str] = None) -> Optional[List[int]]:
config_parser = get_config_parser()
config_parser = get_static_config_parser()
if config_parser.has_option(section, option) and config_parser.get(section, option).strip():
return list(map(int, config_parser.get(section, option).split()))
+7 -13
View File
@@ -1,16 +1,14 @@
from functools import lru_cache
from typing import List, Tuple
from typing import Tuple
import numpy
from tqdm import tqdm
from facefusion import inference_manager, state_manager, translator
from facefusion.common_helper import is_macos
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.execution import has_execution_provider
from facefusion.filesystem import resolve_relative_path
from facefusion.thread_helper import conditional_thread_semaphore
from facefusion.types import Detection, DownloadScope, DownloadSet, ExecutionProvider, Fps, InferencePool, ModelSet, VisionFrame
from facefusion.types import Detection, DownloadScope, DownloadSet, Fps, InferencePool, ModelSet, VisionFrame
from facefusion.vision import detect_video_fps, fit_contain_frame, read_image, read_video_frame
STREAM_COUNTER = 0
@@ -119,12 +117,6 @@ def clear_inference_pool() -> None:
inference_manager.clear_inference_pool(__name__, model_names)
def resolve_execution_providers() -> List[ExecutionProvider]:
if is_macos() and has_execution_provider('coreml'):
return [ 'cpu' ]
return state_manager.get_item('execution_providers')
def collect_model_downloads() -> Tuple[DownloadSet, DownloadSet]:
model_set = create_static_model_set('full')
model_hash_set = {}
@@ -175,10 +167,12 @@ def analyse_video(video_path : str, trim_frame_start : int, trim_frame_end : int
for frame_number in frame_range:
if frame_number % int(video_fps) == 0:
vision_frame = read_video_frame(video_path, frame_number)
total += 1
if analyse_frame(vision_frame):
counter += 1
if numpy.any(vision_frame):
total += 1
if analyse_frame(vision_frame):
counter += 1
if counter > 0 and total > 0:
rate = counter / total * 100
+43 -75
View File
@@ -5,23 +5,18 @@ import signal
import sys
from time import time
import uvicorn
from facefusion import args_store, benchmarker, cli_helper, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, hash_helper, logger, state_manager, translator, voice_extractor
from facefusion.apis.core import create_api
from facefusion.args_helper import apply_args
from facefusion import benchmarker, cli_helper, content_analyser, hash_helper, logger, state_manager, translator
from facefusion.args import apply_args, collect_job_args, reduce_job_args, reduce_step_args
from facefusion.download import conditional_download_hashes, conditional_download_sources
from facefusion.exit_helper import hard_exit, signal_exit
from facefusion.filesystem import get_file_extension, get_file_name, resolve_file_paths, resolve_file_pattern
from facefusion.filesystem import has_audio, has_image, has_video
from facefusion.filesystem import get_file_extension, get_file_name, is_image, is_video, resolve_file_paths, resolve_file_pattern
from facefusion.jobs import job_helper, job_manager, job_runner
from facefusion.jobs.job_list import compose_job_list
from facefusion.libraries import aom as aom_module, datachannel as datachannel_module, opus as opus_module, vpx as vpx_module
from facefusion.processors.core import get_processors_modules
from facefusion.program import create_program
from facefusion.program_helper import validate_args
from facefusion.types import Args, ErrorCode, WorkFlow
from facefusion.workflows import audio_to_image, image_to_image, image_to_video
from facefusion.types import Args, ErrorCode
from facefusion.workflows import image_to_image, image_to_video
def cli() -> None:
@@ -54,31 +49,37 @@ def route(args : Args) -> None:
hard_exit(2)
benchmarker.render()
if state_manager.get_item('command') == 'api':
logger.info(translator.get('api_started').format(host = state_manager.get_item('api_host'), port = state_manager.get_item('api_port')), __name__)
uvicorn.run(create_api(), host = state_manager.get_item('api_host'), port = state_manager.get_item('api_port'))
hard_exit(1)
if state_manager.get_item('command') in [ 'job-list', 'job-create', 'job-submit', 'job-submit-all', 'job-delete', 'job-delete-all', 'job-add-step', 'job-remix-step', 'job-insert-step', 'job-remove-step' ]:
if not job_manager.init_jobs(state_manager.get_jobs_path()):
if not job_manager.init_jobs(state_manager.get_item('jobs_path')):
hard_exit(1)
error_code = route_job_manager(args)
hard_exit(error_code)
if state_manager.get_item('command') == 'run':
if not job_manager.init_jobs(state_manager.get_jobs_path()):
import facefusion.uis.core as ui
if not common_pre_check() or not processors_pre_check():
hard_exit(2)
for ui_layout in ui.get_ui_layouts_modules(state_manager.get_item('ui_layouts')):
if not ui_layout.pre_check():
hard_exit(2)
ui.init()
ui.launch()
if state_manager.get_item('command') == 'headless-run':
if not job_manager.init_jobs(state_manager.get_item('jobs_path')):
hard_exit(1)
error_code = process_headless(args)
hard_exit(error_code)
if state_manager.get_item('command') == 'batch-run':
if not job_manager.init_jobs(state_manager.get_jobs_path()):
if not job_manager.init_jobs(state_manager.get_item('jobs_path')):
hard_exit(1)
error_code = process_batch(args)
hard_exit(error_code)
if state_manager.get_item('command') in [ 'job-run', 'job-run-all', 'job-retry', 'job-retry-all' ]:
if not job_manager.init_jobs(state_manager.get_jobs_path()):
if not job_manager.init_jobs(state_manager.get_item('jobs_path')):
hard_exit(1)
error_code = route_job_runner()
hard_exit(error_code)
@@ -100,25 +101,9 @@ def pre_check() -> bool:
def common_pre_check() -> bool:
common_modules =\
[
aom_module,
datachannel_module,
content_analyser,
face_classifier,
face_detector,
face_landmarker,
face_masker,
face_recognizer,
opus_module,
voice_extractor,
vpx_module
]
content_analyser_content = inspect.getsource(content_analyser).encode()
content_analyser_hash = hash_helper.create_hash(content_analyser_content)
return all(module.pre_check() for module in common_modules) and content_analyser_hash == 'b14e7b92'
return hash_helper.create_hash(content_analyser_content) == '975d67d6'
def processors_pre_check() -> bool:
@@ -129,22 +114,19 @@ def processors_pre_check() -> bool:
def force_download() -> ErrorCode:
common_modules =\
[
content_analyser,
face_classifier,
face_detector,
face_landmarker,
face_masker,
face_recognizer,
voice_extractor
]
download_scope = state_manager.get_item('download_scope')
available_processors = [ get_file_name(file_path) for file_path in resolve_file_paths('facefusion/processors/modules') ]
processor_modules = get_processors_modules(available_processors)
common_modules = []
for processor_module in processor_modules:
for common_module in processor_module.get_common_modules():
if common_module not in common_modules:
common_modules.append(common_module)
for module in common_modules + processor_modules:
if hasattr(module, 'create_static_model_set'):
for model in module.create_static_model_set(state_manager.get_item('download_scope')).values():
for model in module.create_static_model_set(download_scope).values():
model_hash_set = model.get('hashes')
model_source_set = model.get('sources')
@@ -200,7 +182,7 @@ def route_job_manager(args : Args) -> ErrorCode:
return 1
if state_manager.get_item('command') == 'job-add-step':
step_args = args_store.filter_step_args(args)
step_args = reduce_step_args(args)
if job_manager.add_step(state_manager.get_item('job_id'), step_args):
logger.info(translator.get('job_step_added').format(job_id = state_manager.get_item('job_id')), __name__)
@@ -209,7 +191,7 @@ def route_job_manager(args : Args) -> ErrorCode:
return 1
if state_manager.get_item('command') == 'job-remix-step':
step_args = args_store.filter_step_args(args)
step_args = reduce_step_args(args)
if job_manager.remix_step(state_manager.get_item('job_id'), state_manager.get_item('step_index'), step_args):
logger.info(translator.get('job_remix_step_added').format(job_id = state_manager.get_item('job_id'), step_index = state_manager.get_item('step_index')), __name__)
@@ -218,7 +200,7 @@ def route_job_manager(args : Args) -> ErrorCode:
return 1
if state_manager.get_item('command') == 'job-insert-step':
step_args = args_store.filter_step_args(args)
step_args = reduce_step_args(args)
if job_manager.insert_step(state_manager.get_item('job_id'), state_manager.get_item('step_index'), step_args):
logger.info(translator.get('job_step_inserted').format(job_id = state_manager.get_item('job_id'), step_index = state_manager.get_item('step_index')), __name__)
@@ -272,7 +254,7 @@ def route_job_runner() -> ErrorCode:
def process_headless(args : Args) -> ErrorCode:
job_id = job_helper.suggest_job_id('headless')
step_args = args_store.filter_step_args(args)
step_args = reduce_step_args(args)
if job_manager.create_job(job_id) and job_manager.add_step(job_id, step_args) and job_manager.submit_job(job_id) and job_runner.run_job(job_id, process_step):
return 0
@@ -281,9 +263,10 @@ def process_headless(args : Args) -> ErrorCode:
def process_batch(args : Args) -> ErrorCode:
job_id = job_helper.suggest_job_id('batch')
step_args = args_store.filter_step_args(args)
source_paths = resolve_file_pattern(step_args.get('source_pattern'))
target_paths = resolve_file_pattern(step_args.get('target_pattern'))
step_args = reduce_step_args(args)
job_args = reduce_job_args(args)
source_paths = resolve_file_pattern(job_args.get('source_pattern'))
target_paths = resolve_file_pattern(job_args.get('target_pattern'))
if job_manager.create_job(job_id):
if source_paths and target_paths:
@@ -292,7 +275,7 @@ def process_batch(args : Args) -> ErrorCode:
step_args['target_path'] = target_path
try:
step_args['output_path'] = step_args.get('output_pattern').format(index = index, source_name = get_file_name(source_path), target_name = get_file_name(target_path), target_extension = get_file_extension(target_path))
step_args['output_path'] = job_args.get('output_pattern').format(index = index, source_name = get_file_name(source_path), target_name = get_file_name(target_path), target_extension = get_file_extension(target_path))
except KeyError:
return 1
@@ -306,7 +289,7 @@ def process_batch(args : Args) -> ErrorCode:
step_args['target_path'] = target_path
try:
step_args['output_path'] = step_args.get('output_pattern').format(index = index, target_name = get_file_name(target_path), target_extension = get_file_extension(target_path))
step_args['output_path'] = job_args.get('output_pattern').format(index = index, target_name = get_file_name(target_path), target_extension = get_file_extension(target_path))
except KeyError:
return 1
@@ -319,10 +302,8 @@ def process_batch(args : Args) -> ErrorCode:
def process_step(job_id : str, step_index : int, step_args : Args) -> bool:
step_total = job_manager.count_step_total(job_id)
cli_args = args_store.filter_cli_args(state_manager.get_state()) #type:ignore[arg-type]
args = cli_args.copy()
args.update(step_args)
apply_args(args, state_manager.set_item)
step_args.update(collect_job_args())
apply_args(step_args, state_manager.set_item)
logger.info(translator.get('processing_step').format(step_current = step_index + 1, step_total = step_total), __name__)
if common_pre_check() and processors_pre_check():
@@ -334,28 +315,15 @@ def process_step(job_id : str, step_index : int, step_args : Args) -> bool:
def conditional_process() -> ErrorCode:
start_time = time()
if state_manager.get_item('workflow') == 'auto':
state_manager.set_item('workflow', detect_workflow())
for processor_module in get_processors_modules(state_manager.get_item('processors')):
if not processor_module.pre_process('output'):
return 2
if state_manager.get_item('workflow') == 'audio-to-image':
return audio_to_image.process(start_time)
if state_manager.get_item('workflow') == 'image-to-image':
if is_image(state_manager.get_item('target_path')):
return image_to_image.process(start_time)
if state_manager.get_item('workflow') == 'image-to-video':
if is_video(state_manager.get_item('target_path')):
return image_to_video.process(start_time)
return 0
def detect_workflow() -> WorkFlow:
if has_video([ state_manager.get_item('target_path') ]):
return 'image-to-video'
if has_audio(state_manager.get_item('source_paths')) and has_image([ state_manager.get_item('target_path') ]):
return 'audio-to-image'
return 'image-to-image'
+6 -2
View File
@@ -9,14 +9,14 @@ from facefusion.types import Command
def run(commands : List[Command]) -> List[Command]:
user_agent = metadata.get('name') + '/' + metadata.get('version')
return [ shutil.which('curl'), '--user-agent', user_agent, '--insecure', '--location', '--silent' ] + commands
return [ shutil.which('curl'), '--user-agent', user_agent, '--location', '--silent', '--ssl-no-revoke' ] + commands
def chain(*commands : List[Command]) -> List[Command]:
return list(itertools.chain(*commands))
def head(url : str) -> List[Command]:
def ping(url : str) -> List[Command]:
return [ '-I', url ]
@@ -26,3 +26,7 @@ def download(url : str, download_file_path : str) -> List[Command]:
def set_timeout(timeout : int) -> List[Command]:
return [ '--connect-timeout', str(timeout) ]
def set_retry(retry : int) -> List[Command]:
return [ '--retry', str(retry) ]
+4 -3
View File
@@ -29,7 +29,8 @@ def conditional_download(download_directory_path : str, urls : List[str]) -> Non
with tqdm(total = download_size, initial = initial_size, desc = translator.get('downloading'), unit = 'B', unit_scale = True, unit_divisor = 1024, ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
commands = curl_builder.chain(
curl_builder.download(url, download_file_path),
curl_builder.set_timeout(5)
curl_builder.set_timeout(5),
curl_builder.set_retry(5)
)
open_curl(commands)
current_size = initial_size
@@ -44,7 +45,7 @@ def conditional_download(download_directory_path : str, urls : List[str]) -> Non
@lru_cache(maxsize = 64)
def get_static_download_size(url : str) -> int:
commands = curl_builder.chain(
curl_builder.head(url),
curl_builder.ping(url),
curl_builder.set_timeout(5)
)
process = open_curl(commands)
@@ -62,7 +63,7 @@ def get_static_download_size(url : str) -> int:
@lru_cache(maxsize = 64)
def ping_static_url(url : str) -> bool:
commands = curl_builder.chain(
curl_builder.head(url),
curl_builder.ping(url),
curl_builder.set_timeout(5)
)
process = open_curl(commands)
+61 -27
View File
@@ -1,15 +1,17 @@
import os
import shutil
import subprocess
import xml.etree.ElementTree as ElementTree
from functools import lru_cache
from typing import List, Optional
from onnxruntime import get_available_providers, set_default_logger_severity
import onnxruntime
import facefusion.choices
from facefusion.types import ExecutionDevice, ExecutionProvider, InferenceSessionProvider, ValueAndUnit
from facefusion.filesystem import create_directory, is_directory
from facefusion.types import ExecutionDevice, ExecutionProvider, InferenceOptionSet, InferenceProvider, ValueAndUnit
set_default_logger_severity(3)
onnxruntime.set_default_logger_severity(3)
def has_execution_provider(execution_provider : ExecutionProvider) -> bool:
@@ -17,7 +19,7 @@ def has_execution_provider(execution_provider : ExecutionProvider) -> bool:
def get_available_execution_providers() -> List[ExecutionProvider]:
inference_session_providers = get_available_providers()
inference_session_providers = onnxruntime.get_available_providers()
available_execution_providers : List[ExecutionProvider] = []
for execution_provider, execution_provider_value in facefusion.choices.execution_provider_set.items():
@@ -28,54 +30,86 @@ def get_available_execution_providers() -> List[ExecutionProvider]:
return available_execution_providers
def create_inference_session_providers(execution_device_id : int, execution_providers : List[ExecutionProvider]) -> List[InferenceSessionProvider]:
inference_session_providers : List[InferenceSessionProvider] = []
def create_inference_providers(execution_device_id : int, execution_providers : List[ExecutionProvider]) -> List[InferenceProvider]:
inference_providers : List[InferenceProvider] = []
cache_path = resolve_cache_path()
for execution_provider in execution_providers:
if execution_provider == 'cuda':
inference_session_providers.append((facefusion.choices.execution_provider_set.get(execution_provider),
inference_providers.append((facefusion.choices.execution_provider_set.get(execution_provider),
{
'device_id': execution_device_id,
'cudnn_conv_algo_search': resolve_cudnn_conv_algo_search()
}))
if execution_provider == 'tensorrt':
inference_session_providers.append((facefusion.choices.execution_provider_set.get(execution_provider),
inference_option_set : InferenceOptionSet =\
{
'device_id': execution_device_id,
'trt_engine_cache_enable': True,
'trt_engine_cache_path': '.caches',
'trt_timing_cache_enable': True,
'trt_timing_cache_path': '.caches',
'trt_builder_optimization_level': 5
}))
'device_id': execution_device_id
}
if is_directory(cache_path) or create_directory(cache_path):
inference_option_set.update(
{
'trt_engine_cache_enable': True,
'trt_engine_cache_path': cache_path,
'trt_timing_cache_enable': True,
'trt_timing_cache_path': cache_path,
'trt_builder_optimization_level': 4
})
inference_providers.append((facefusion.choices.execution_provider_set.get(execution_provider), inference_option_set))
if execution_provider in [ 'directml', 'rocm' ]:
inference_session_providers.append((facefusion.choices.execution_provider_set.get(execution_provider),
inference_providers.append((facefusion.choices.execution_provider_set.get(execution_provider),
{
'device_id': execution_device_id
}))
if execution_provider == 'migraphx':
inference_session_providers.append((facefusion.choices.execution_provider_set.get(execution_provider),
inference_option_set =\
{
'device_id': execution_device_id,
'migraphx_model_cache_dir': '.caches'
}))
'device_id': execution_device_id
}
if is_directory(cache_path) or create_directory(cache_path):
inference_option_set.update(
{
'migraphx_model_cache_dir': cache_path
})
inference_providers.append((facefusion.choices.execution_provider_set.get(execution_provider), inference_option_set))
if execution_provider == 'coreml':
inference_option_set =\
{
'SpecializationStrategy': 'FastPrediction'
}
if is_directory(cache_path) or create_directory(cache_path):
inference_option_set.update(
{
'ModelCacheDirectory': cache_path
})
inference_providers.append((facefusion.choices.execution_provider_set.get(execution_provider), inference_option_set))
if execution_provider == 'openvino':
inference_session_providers.append((facefusion.choices.execution_provider_set.get(execution_provider),
inference_providers.append((facefusion.choices.execution_provider_set.get(execution_provider),
{
'device_type': resolve_openvino_device_type(execution_device_id),
'precision': 'FP32'
}))
if execution_provider == 'coreml':
inference_session_providers.append((facefusion.choices.execution_provider_set.get(execution_provider),
if execution_provider == 'qnn':
inference_providers.append((facefusion.choices.execution_provider_set.get(execution_provider),
{
'SpecializationStrategy': 'FastPrediction',
'ModelCacheDirectory': '.caches'
'device_id': execution_device_id,
'backend_type': 'htp'
}))
if 'cpu' in execution_providers:
inference_session_providers.append(facefusion.choices.execution_provider_set.get('cpu'))
inference_providers.append(facefusion.choices.execution_provider_set.get('cpu'))
return inference_session_providers
return inference_providers
def resolve_cache_path() -> str:
return os.path.join('.caches', onnxruntime.get_version_string())
def resolve_cudnn_conv_algo_search() -> str:
+2 -2
View File
@@ -28,7 +28,7 @@ def graceful_exit(error_code : ErrorCode) -> None:
while process_manager.is_processing():
sleep(0.5)
if state_manager.get_item('output_path'):
clear_temp_directory(state_manager.get_temp_path(), state_manager.get_item('output_path'))
if state_manager.get_item('target_path'):
clear_temp_directory(state_manager.get_item('target_path'))
hard_exit(error_code)
+1 -1
View File
@@ -20,7 +20,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'__metadata__':
{
'vendor': 'dchen236',
'license': 'Non-Commercial',
'license': 'CC-BY-4.0',
'year': 2021
},
'hashes':
@@ -2,14 +2,13 @@ from typing import List, Optional
import numpy
from facefusion import state_manager
from facefusion.common_helper import get_first
from facefusion import face_store, state_manager
from facefusion.common_helper import get_first, get_middle
from facefusion.face_classifier import classify_face
from facefusion.face_detector import detect_faces, detect_faces_by_angle
from facefusion.face_helper import apply_nms, convert_to_face_landmark_5, estimate_face_angle, get_nms_threshold
from facefusion.face_helper import apply_nms, average_points, convert_to_face_landmark_5, estimate_face_angle, get_nms_threshold
from facefusion.face_landmarker import detect_face_landmark, estimate_face_landmark_68_5
from facefusion.face_recognizer import calculate_face_embedding
from facefusion.face_store import get_static_faces, set_static_faces
from facefusion.types import BoundingBox, Face, FaceLandmark5, FaceLandmarkSet, FaceScoreSet, Score, VisionFrame
@@ -47,7 +46,9 @@ def create_faces(vision_frame : VisionFrame, bounding_boxes : List[BoundingBox],
}
face_embedding, face_embedding_norm = calculate_face_embedding(vision_frame, face_landmark_set.get('5/68'))
gender, age, race = classify_face(vision_frame, face_landmark_set.get('5/68'))
faces.append(Face(
origin = 'detect',
bounding_box = bounding_box,
score_set = face_score_set,
landmark_set = face_landmark_set,
@@ -68,7 +69,102 @@ def get_one_face(faces : List[Face], position : int = 0) -> Optional[Face]:
return None
def get_average_face(faces : List[Face]) -> Optional[Face]:
def get_many_faces(vision_frames : List[VisionFrame]) -> List[Face]:
many_faces : List[Face] = []
for vision_frame in vision_frames:
if numpy.any(vision_frame):
all_bounding_boxes = []
all_face_scores = []
all_face_landmarks_5 = []
for face_detector_angle in state_manager.get_item('face_detector_angles'):
if face_detector_angle == 0:
bounding_boxes, face_scores, face_landmarks_5 = detect_faces(vision_frame)
else:
bounding_boxes, face_scores, face_landmarks_5 = detect_faces_by_angle(vision_frame, face_detector_angle)
all_bounding_boxes.extend(bounding_boxes)
all_face_scores.extend(face_scores)
all_face_landmarks_5.extend(face_landmarks_5)
if all_bounding_boxes and all_face_scores and all_face_landmarks_5 and state_manager.get_item('face_detector_score') > 0:
faces = create_faces(vision_frame, all_bounding_boxes, all_face_scores, all_face_landmarks_5)
if faces:
many_faces.extend(faces)
return many_faces
def get_static_faces(vision_frames : List[VisionFrame]) -> List[Face]:
many_faces : List[Face] = []
for vision_frame in vision_frames:
faces = face_store.get_faces(vision_frame)
if not faces:
with face_store.resolve_lock(vision_frame):
faces = face_store.get_faces(vision_frame)
if not faces:
faces = get_many_faces([ vision_frame ])
if faces:
face_store.set_faces(vision_frame, faces)
many_faces.extend(faces)
return many_faces
def refill_faces(faces : List[Optional[Face]]) -> List[Face]:
fill_faces = []
anchor_index_previous = -1
for index, face in enumerate(faces):
if face:
for gap_index in range(anchor_index_previous + 1, index):
average_factor = (gap_index - anchor_index_previous) / (index - anchor_index_previous)
average_face = average_face_geometry([faces[anchor_index_previous], face], average_factor)
fill_faces.append(average_face)
fill_faces.append(face)
anchor_index_previous = index
return fill_faces
def average_face_geometry(faces : List[Face], average_factor : float) -> Face:
face_first = get_first(faces)
face_middle = get_middle(faces)
face_anchor = face_middle
if average_factor < 0.5:
face_anchor = face_first
landmark_set : FaceLandmarkSet =\
{
'5': average_points(face_first.landmark_set.get('5'), face_middle.landmark_set.get('5'), average_factor),
'5/68': average_points(face_first.landmark_set.get('5/68'), face_middle.landmark_set.get('5/68'), average_factor),
'68': average_points(face_first.landmark_set.get('68'), face_middle.landmark_set.get('68'), average_factor),
'68/5': average_points(face_first.landmark_set.get('68/5'), face_middle.landmark_set.get('68/5'), average_factor)
}
return Face(
origin = 'refill',
bounding_box = average_points(face_first.bounding_box, face_middle.bounding_box, average_factor),
score_set = face_anchor.score_set,
landmark_set = landmark_set,
angle = estimate_face_angle(landmark_set.get('68/5')),
embedding = face_anchor.embedding,
embedding_norm = face_anchor.embedding_norm,
gender = face_anchor.gender,
age = face_anchor.age,
race = face_anchor.race
)
def average_face_identity(faces : List[Face]) -> Optional[Face]:
face_embeddings = []
face_embeddings_norm = []
@@ -80,6 +176,7 @@ def get_average_face(faces : List[Face]) -> Optional[Face]:
face_embeddings_norm.append(face.embedding_norm)
return Face(
origin = first_face.origin,
bounding_box = first_face.bounding_box,
score_set = first_face.score_set,
landmark_set = first_face.landmark_set,
@@ -93,37 +190,6 @@ def get_average_face(faces : List[Face]) -> Optional[Face]:
return None
def get_many_faces(vision_frames : List[VisionFrame]) -> List[Face]:
many_faces : List[Face] = []
for vision_frame in vision_frames:
if numpy.any(vision_frame):
static_faces = get_static_faces(vision_frame)
if static_faces:
many_faces.extend(static_faces)
else:
all_bounding_boxes = []
all_face_scores = []
all_face_landmarks_5 = []
for face_detector_angle in state_manager.get_item('face_detector_angles'):
if face_detector_angle == 0:
bounding_boxes, face_scores, face_landmarks_5 = detect_faces(vision_frame)
else:
bounding_boxes, face_scores, face_landmarks_5 = detect_faces_by_angle(vision_frame, face_detector_angle)
all_bounding_boxes.extend(bounding_boxes)
all_face_scores.extend(face_scores)
all_face_landmarks_5.extend(face_landmarks_5)
if all_bounding_boxes and all_face_scores and all_face_landmarks_5 and state_manager.get_item('face_detector_score') > 0:
faces = create_faces(vision_frame, all_bounding_boxes, all_face_scores, all_face_landmarks_5)
if faces:
many_faces.extend(faces)
set_static_faces(vision_frame, faces)
return many_faces
def scale_face(target_face : Face, target_vision_frame : VisionFrame, temp_vision_frame : VisionFrame) -> Face:
scale_x = temp_vision_frame.shape[1] / target_vision_frame.shape[1]
scale_y = temp_vision_frame.shape[0] / target_vision_frame.shape[0]
+5 -5
View File
@@ -381,11 +381,11 @@ def detect_with_yunet(vision_frame : VisionFrame, face_detector_size : str) -> T
face_scores.extend(face_scores_raw[keep_indices])
face_landmarks_5_raw = numpy.concatenate(
[
face_landmarks_5_raw[:, [0, 1]] * feature_stride + anchors,
face_landmarks_5_raw[:, [2, 3]] * feature_stride + anchors,
face_landmarks_5_raw[:, [4, 5]] * feature_stride + anchors,
face_landmarks_5_raw[:, [6, 7]] * feature_stride + anchors,
face_landmarks_5_raw[:, [8, 9]] * feature_stride + anchors
face_landmarks_5_raw[:, [ 0, 1 ]] * feature_stride + anchors,
face_landmarks_5_raw[:, [ 2, 3 ]] * feature_stride + anchors,
face_landmarks_5_raw[:, [ 4, 5 ]] * feature_stride + anchors,
face_landmarks_5_raw[:, [ 6, 7 ]] * feature_stride + anchors,
face_landmarks_5_raw[:, [ 8, 9 ]] * feature_stride + anchors
], axis = -1).reshape(-1, 5, 2)
for face_landmark_raw_5 in face_landmarks_5_raw[keep_indices]:
+22 -2
View File
@@ -81,7 +81,7 @@ def warp_face_by_face_landmark_5(temp_vision_frame : VisionFrame, face_landmark_
def warp_face_by_bounding_box(temp_vision_frame : VisionFrame, bounding_box : BoundingBox, crop_size : Size) -> Tuple[VisionFrame, Matrix]:
source_points = numpy.array([ [ bounding_box[0], bounding_box[1] ], [bounding_box[2], bounding_box[1] ], [ bounding_box[0], bounding_box[3] ] ]).astype(numpy.float32)
source_points = numpy.array([ [ bounding_box[0], bounding_box[1] ], [ bounding_box[2], bounding_box[1] ], [ bounding_box[0], bounding_box[3] ] ]).astype(numpy.float32)
target_points = numpy.array([ [ 0, 0 ], [ crop_size[0], 0 ], [ 0, crop_size[1] ] ]).astype(numpy.float32)
affine_matrix = cv2.getAffineTransform(source_points, target_points)
if bounding_box[2] - bounding_box[0] > crop_size[0] or bounding_box[3] - bounding_box[1] > crop_size[1]:
@@ -247,10 +247,30 @@ def get_nms_threshold(face_detector_model : FaceDetectorModel, face_detector_ang
def merge_matrix(temp_matrices : List[Matrix]) -> Matrix:
matrix = numpy.vstack([temp_matrices[0], [0, 0, 1]])
matrix = numpy.vstack([ temp_matrices[0], [ 0, 0, 1 ] ])
for temp_matrix in temp_matrices[1:]:
temp_matrix = numpy.vstack([ temp_matrix, [ 0, 0, 1 ] ])
matrix = numpy.dot(temp_matrix, matrix)
return matrix[:2, :]
def calculate_bounding_box_overlap(bounding_box_a : BoundingBox, bounding_box_b : BoundingBox) -> float:
intersection_x1 = max(bounding_box_a[0], bounding_box_b[0])
intersection_y1 = max(bounding_box_a[1], bounding_box_b[1])
intersection_x2 = min(bounding_box_a[2], bounding_box_b[2])
intersection_y2 = min(bounding_box_a[3], bounding_box_b[3])
intersection = max(0, intersection_x2 - intersection_x1) * max(0, intersection_y2 - intersection_y1)
bounding_box_area = (bounding_box_a[2] - bounding_box_a[0]) * (bounding_box_a[3] - bounding_box_a[1])
reference_bounding_box_area = (bounding_box_b[2] - bounding_box_b[0]) * (bounding_box_b[3] - bounding_box_b[1])
union = bounding_box_area + reference_bounding_box_area - intersection
if union > 0:
return intersection / union
return 0.0
def average_points(points_previous : Points, points_next : Points, average_factor : float) -> Points:
return points_previous * (1 - average_factor) + points_next * average_factor
+1 -1
View File
@@ -233,7 +233,7 @@ def create_area_mask(crop_vision_frame : VisionFrame, face_landmark_68 : FaceLan
convex_hull = cv2.convexHull(face_landmark_68[landmark_points].astype(numpy.int32))
area_mask = numpy.zeros(crop_size).astype(numpy.float32)
cv2.fillConvexPoly(area_mask, convex_hull, 1.0) # type: ignore[call-overload]
cv2.fillConvexPoly(area_mask, convex_hull, 1.0) #type:ignore[call-overload]
area_mask = (cv2.GaussianBlur(area_mask.clip(0, 1), (0, 0), 5).clip(0.5, 1) - 0.5) * 2
return area_mask
+41 -16
View File
@@ -2,26 +2,35 @@ from typing import List
import numpy
import facefusion.choices
from facefusion import state_manager
from facefusion.face_analyser import get_many_faces, get_one_face
from facefusion.common_helper import get_first, get_middle
from facefusion.face_creator import get_one_face, get_static_faces
from facefusion.face_tracker import track_faces
from facefusion.types import Face, FaceSelectorOrder, Gender, Race, Score, VisionFrame
def select_faces(reference_vision_frame : VisionFrame, target_vision_frame : VisionFrame) -> List[Face]:
target_faces = get_many_faces([ target_vision_frame ])
def select_faces(reference_vision_frame : VisionFrame, source_vision_frames : List[VisionFrame], target_vision_frames : List[VisionFrame]) -> List[Face]:
source_faces = get_static_faces(source_vision_frames)
if state_manager.get_item('face_tracker_score') > 0:
target_faces = track_faces(target_vision_frames, state_manager.get_item('face_tracker_score'))
else:
target_faces = get_static_faces([ get_middle(target_vision_frames) ])
if state_manager.get_item('face_selector_mode') == 'many':
return sort_and_filter_faces(target_faces)
return sort_and_filter_faces(source_faces, target_faces)
if state_manager.get_item('face_selector_mode') == 'one':
target_face = get_one_face(sort_and_filter_faces(target_faces))
target_face = get_one_face(sort_and_filter_faces(source_faces, target_faces))
if target_face:
return [ target_face ]
if state_manager.get_item('face_selector_mode') == 'reference':
reference_faces = get_many_faces([ reference_vision_frame ])
reference_faces = sort_and_filter_faces(reference_faces)
reference_faces = get_static_faces([ reference_vision_frame ])
reference_faces = sort_and_filter_faces(source_faces, reference_faces)
reference_face = get_one_face(reference_faces, state_manager.get_item('reference_face_position'))
if reference_face:
match_faces = find_match_faces([ reference_face ], target_faces, state_manager.get_item('reference_face_distance'))
return match_faces
@@ -53,17 +62,33 @@ def calculate_face_distance(face : Face, reference_face : Face) -> float:
return 0
def sort_and_filter_faces(faces : List[Face]) -> List[Face]:
if faces:
def sort_and_filter_faces(source_faces : List[Face], target_faces : List[Face]) -> List[Face]:
if target_faces:
if state_manager.get_item('face_selector_order'):
faces = sort_faces_by_order(faces, state_manager.get_item('face_selector_order'))
if state_manager.get_item('face_selector_gender'):
faces = filter_faces_by_gender(faces, state_manager.get_item('face_selector_gender'))
if state_manager.get_item('face_selector_race'):
faces = filter_faces_by_race(faces, state_manager.get_item('face_selector_race'))
target_faces = sort_faces_by_order(target_faces, state_manager.get_item('face_selector_order'))
face_selector_gender = state_manager.get_item('face_selector_gender')
face_selector_race = state_manager.get_item('face_selector_race')
if source_faces and face_selector_gender == 'auto' or face_selector_race == 'auto':
source_face = get_first(sort_faces_by_order(source_faces, 'large-small'))
if source_face:
if face_selector_gender == 'auto':
face_selector_gender = source_face.gender
if face_selector_race == 'auto':
face_selector_race = source_face.race
if face_selector_gender in facefusion.choices.genders:
target_faces = filter_faces_by_gender(target_faces, face_selector_gender)
if face_selector_race in facefusion.choices.races:
target_faces = filter_faces_by_race(target_faces, face_selector_race)
if state_manager.get_item('face_selector_age_start') or state_manager.get_item('face_selector_age_end'):
faces = filter_faces_by_age(faces, state_manager.get_item('face_selector_age_start'), state_manager.get_item('face_selector_age_end'))
return faces
target_faces = filter_faces_by_age(target_faces, state_manager.get_item('face_selector_age_start'), state_manager.get_item('face_selector_age_end'))
return target_faces
def sort_faces_by_order(faces : List[Face], order : FaceSelectorOrder) -> List[Face]:
+29 -15
View File
@@ -1,28 +1,42 @@
import threading
from typing import List, Optional
import numpy
from facefusion.hash_helper import create_hash
from facefusion.types import Face, FaceStore, VisionFrame
FACE_STORE : FaceStore =\
{
'static_faces': {}
}
FACE_STORE : FaceStore = {}
def get_face_store() -> FaceStore:
return FACE_STORE
def get_faces(vision_frame : VisionFrame) -> Optional[List[Face]]:
if numpy.any(vision_frame):
vision_hash = create_hash(vision_frame.tobytes())
if FACE_STORE.get(vision_hash):
return FACE_STORE.get(vision_hash).get('faces')
return None
def get_static_faces(vision_frame : VisionFrame) -> Optional[List[Face]]:
vision_hash = create_hash(vision_frame.tobytes())
return FACE_STORE.get('static_faces').get(vision_hash)
def set_faces(vision_frame : VisionFrame, faces : List[Face]) -> None:
if numpy.any(vision_frame):
vision_hash = create_hash(vision_frame.tobytes())
FACE_STORE.setdefault(vision_hash,
{
'lock': threading.Lock()
})['faces'] = faces
def set_static_faces(vision_frame : VisionFrame, faces : List[Face]) -> None:
vision_hash = create_hash(vision_frame.tobytes())
if vision_hash:
FACE_STORE['static_faces'][vision_hash] = faces
def resolve_lock(vision_frame : VisionFrame) -> threading.Lock:
if numpy.any(vision_frame):
vision_hash = create_hash(vision_frame.tobytes())
return FACE_STORE.setdefault(vision_hash,
{
'lock': threading.Lock()
}).get('lock')
return threading.Lock()
def clear_static_faces() -> None:
FACE_STORE['static_faces'].clear()
def clear_faces() -> None:
FACE_STORE.clear()
+61
View File
@@ -0,0 +1,61 @@
from typing import List
from facefusion.common_helper import get_first, get_last
from facefusion.face_creator import get_static_faces, refill_faces
from facefusion.face_helper import calculate_bounding_box_overlap
from facefusion.types import Face, FaceTrack, Score, VisionFrame
def track_faces(vision_frames : List[VisionFrame], score : Score) -> List[Face]:
target_index = len(vision_frames) // 2
face_tracks = create_face_tracks(vision_frames, score)
temp_faces = []
for face_track in face_tracks:
track_indices = sorted(face_track)
track_index_first = get_first(track_indices)
track_index_last = get_last(track_indices)
track_range = range(track_index_first, track_index_last + 1)
if target_index in track_range:
fill_faces = []
for index in track_range:
fill_faces.append(face_track.get(index))
temp_faces.append(refill_faces(fill_faces)[target_index - track_index_first])
return temp_faces
def create_face_tracks(vision_frames : List[VisionFrame], score : Score) -> List[FaceTrack]:
face_tracks : List[FaceTrack] = []
for frame_index, vision_frame in enumerate(vision_frames):
for face in get_static_faces([ vision_frame ]):
face_track = select_face_track(face_tracks, face, score)
if face_track:
face_track[frame_index] = face
else:
face_tracks.append(
{
frame_index : face
})
return face_tracks
def select_face_track(face_tracks : List[FaceTrack], face : Face, score : Score) -> FaceTrack:
select_track : FaceTrack = {}
select_score = score
for face_track in face_tracks:
track_face = face_track.get(get_last(face_track))
track_score = calculate_bounding_box_overlap(face.bounding_box, track_face.bounding_box)
if track_score > select_score:
select_score = track_score
select_track = face_track
return select_track
+31 -39
View File
@@ -9,7 +9,7 @@ from tqdm import tqdm
import facefusion.choices
from facefusion import ffmpeg_builder, logger, process_manager, state_manager, translator
from facefusion.filesystem import get_file_format, remove_file
from facefusion.temp_helper import get_temp_file_path, get_temp_frames_pattern
from facefusion.temp_helper import get_temp_file_path, get_temp_frame_pattern
from facefusion.types import AudioBuffer, AudioEncoder, Command, EncoderSet, Fps, Resolution, UpdateProgress, VideoEncoder, VideoFormat
from facefusion.vision import detect_video_duration, detect_video_fps, pack_resolution, predict_video_frame_total
@@ -107,16 +107,18 @@ def get_available_encoder_set() -> EncoderSet:
return available_encoder_set
def extract_frames(target_path : str, output_path : str, temp_video_resolution : Resolution, temp_video_fps : Fps, trim_frame_start : int, trim_frame_end : int) -> bool:
def extract_frames(target_path : str, temp_video_resolution : Resolution, temp_video_fps : Fps, trim_frame_start : int, trim_frame_end : int) -> bool:
extract_frame_total = predict_video_frame_total(target_path, temp_video_fps, trim_frame_start, trim_frame_end)
temp_frames_pattern = get_temp_frames_pattern(state_manager.get_temp_path(), output_path, state_manager.get_item('temp_frame_format'), '%08d')
temp_frame_pattern = get_temp_frame_pattern(target_path, '%08d')
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_input(target_path),
ffmpeg_builder.set_media_resolution(pack_resolution(temp_video_resolution)),
ffmpeg_builder.set_frame_quality(0),
ffmpeg_builder.enforce_pixel_format('rgb24'),
ffmpeg_builder.select_frame_range(trim_frame_start, trim_frame_end, temp_video_fps),
ffmpeg_builder.prevent_frame_drop(),
ffmpeg_builder.set_output(temp_frames_pattern)
ffmpeg_builder.set_start_number(trim_frame_start),
ffmpeg_builder.set_output(temp_frame_pattern)
)
with tqdm(total = extract_frame_total, desc = translator.get('extracting'), unit = 'frame', ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
@@ -124,26 +126,8 @@ def extract_frames(target_path : str, output_path : str, temp_video_resolution :
return process.returncode == 0
def spawn_frames(target_path : str, output_path : str, temp_video_resolution : Resolution, temp_video_fps : Fps, trim_frame_start : int, trim_frame_end : int) -> bool:
spawn_frame_total = trim_frame_end - trim_frame_start
duration = spawn_frame_total / temp_video_fps
temp_frames_pattern = get_temp_frames_pattern(state_manager.get_temp_path(), output_path, state_manager.get_item('temp_frame_format'), '%08d')
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_loop(),
ffmpeg_builder.set_input(target_path),
ffmpeg_builder.set_video_duration(duration),
ffmpeg_builder.set_video_fps(temp_video_fps),
ffmpeg_builder.set_media_resolution(pack_resolution(temp_video_resolution)),
ffmpeg_builder.set_output(temp_frames_pattern)
)
with tqdm(total = spawn_frame_total, desc = translator.get('spawning'), unit = 'frame', ascii = ' =', disable = state_manager.get_item('log_level') in [ 'warn', 'error' ]) as progress:
process = run_ffmpeg_with_progress(commands, partial(update_progress, progress))
return process.returncode == 0
def copy_image(target_path : str, output_path : str, temp_image_resolution : Resolution) -> bool:
temp_image_path = get_temp_file_path(state_manager.get_temp_path(), output_path)
def copy_image(target_path : str, temp_image_resolution : Resolution) -> bool:
temp_image_path = get_temp_file_path(target_path)
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_input(target_path),
ffmpeg_builder.set_media_resolution(pack_resolution(temp_image_resolution)),
@@ -153,13 +137,13 @@ def copy_image(target_path : str, output_path : str, temp_image_resolution : Res
return run_ffmpeg(commands).returncode == 0
def finalize_image(output_path : str, output_image_resolution : Resolution) -> bool:
def finalize_image(target_path : str, output_path : str, output_image_resolution : Resolution) -> bool:
output_image_quality = state_manager.get_item('output_image_quality')
temp_image_path = get_temp_file_path(state_manager.get_temp_path(), output_path)
temp_image_path = get_temp_file_path(target_path)
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_input(temp_image_path),
ffmpeg_builder.set_media_resolution(pack_resolution(output_image_resolution)),
ffmpeg_builder.set_image_quality(output_path, output_image_quality),
ffmpeg_builder.set_image_quality(target_path, output_image_quality),
ffmpeg_builder.force_output(output_path)
)
return run_ffmpeg(commands).returncode == 0
@@ -187,9 +171,10 @@ def restore_audio(target_path : str, output_path : str, trim_frame_start : int,
output_audio_quality = state_manager.get_item('output_audio_quality')
output_audio_volume = state_manager.get_item('output_audio_volume')
target_video_fps = detect_video_fps(target_path)
temp_video_path = get_temp_file_path(state_manager.get_temp_path(), output_path)
temp_video_format = cast(VideoFormat, get_file_format(output_path))
temp_video_path = get_temp_file_path(target_path)
temp_video_format = cast(VideoFormat, get_file_format(temp_video_path))
temp_video_duration = detect_video_duration(temp_video_path)
output_video_format = cast(VideoFormat, get_file_format(output_path))
output_audio_encoder = fix_audio_encoder(temp_video_format, output_audio_encoder)
commands = ffmpeg_builder.chain(
@@ -203,18 +188,20 @@ def restore_audio(target_path : str, output_path : str, trim_frame_start : int,
ffmpeg_builder.select_media_stream('0:v:0'),
ffmpeg_builder.select_media_stream('1:a:0'),
ffmpeg_builder.set_video_duration(temp_video_duration),
ffmpeg_builder.set_faststart(output_video_format),
ffmpeg_builder.force_output(output_path)
)
return run_ffmpeg(commands).returncode == 0
def replace_audio(audio_path : str, output_path : str) -> bool:
def replace_audio(target_path : str, audio_path : str, output_path : str) -> bool:
output_audio_encoder = state_manager.get_item('output_audio_encoder')
output_audio_quality = state_manager.get_item('output_audio_quality')
output_audio_volume = state_manager.get_item('output_audio_volume')
temp_video_path = get_temp_file_path(state_manager.get_temp_path(), output_path)
temp_video_format = cast(VideoFormat, get_file_format(output_path))
temp_video_path = get_temp_file_path(target_path)
temp_video_format = cast(VideoFormat, get_file_format(temp_video_path))
temp_video_duration = detect_video_duration(temp_video_path)
output_video_format = cast(VideoFormat, get_file_format(output_path))
output_audio_encoder = fix_audio_encoder(temp_video_format, output_audio_encoder)
commands = ffmpeg_builder.chain(
@@ -225,27 +212,29 @@ def replace_audio(audio_path : str, output_path : str) -> bool:
ffmpeg_builder.set_audio_quality(output_audio_encoder, output_audio_quality),
ffmpeg_builder.set_audio_volume(output_audio_volume),
ffmpeg_builder.set_video_duration(temp_video_duration),
ffmpeg_builder.set_faststart(output_video_format),
ffmpeg_builder.force_output(output_path)
)
return run_ffmpeg(commands).returncode == 0
def merge_video(target_path : str, output_path : str, temp_video_fps : Fps, output_video_resolution : Resolution, trim_frame_start : int, trim_frame_end : int) -> bool:
output_video_fps = state_manager.get_item('output_video_fps')
def merge_video(target_path : str, temp_video_fps : Fps, output_video_resolution : Resolution, output_video_fps : Fps, trim_frame_start : int, trim_frame_end : int) -> bool:
output_video_encoder = state_manager.get_item('output_video_encoder')
output_video_quality = state_manager.get_item('output_video_quality')
output_video_preset = state_manager.get_item('output_video_preset')
merge_frame_total = predict_video_frame_total(target_path, output_video_fps, trim_frame_start, trim_frame_end)
temp_video_path = get_temp_file_path(state_manager.get_temp_path(), output_path)
temp_video_format = cast(VideoFormat, get_file_format(output_path))
temp_frames_pattern = get_temp_frames_pattern(state_manager.get_temp_path(), output_path, state_manager.get_item('temp_frame_format'), '%08d')
temp_video_path = get_temp_file_path(target_path)
temp_video_format = cast(VideoFormat, get_file_format(temp_video_path))
temp_frame_pattern = get_temp_frame_pattern(target_path, '%08d')
output_video_encoder = fix_video_encoder(temp_video_format, output_video_encoder)
commands = ffmpeg_builder.chain(
ffmpeg_builder.set_input_fps(temp_video_fps),
ffmpeg_builder.set_input(temp_frames_pattern),
ffmpeg_builder.set_start_number(trim_frame_start),
ffmpeg_builder.set_input(temp_frame_pattern),
ffmpeg_builder.set_media_resolution(pack_resolution(output_video_resolution)),
ffmpeg_builder.set_video_encoder(output_video_encoder),
ffmpeg_builder.set_video_tag(output_video_encoder, temp_video_format),
ffmpeg_builder.set_video_quality(output_video_encoder, output_video_quality),
ffmpeg_builder.set_video_preset(output_video_encoder, output_video_preset),
ffmpeg_builder.concat(
@@ -262,7 +251,8 @@ def merge_video(target_path : str, output_path : str, temp_video_fps : Fps, outp
def concat_video(output_path : str, temp_output_paths : List[str]) -> bool:
concat_video_path = tempfile.mktemp()
file_descriptor, concat_video_path = tempfile.mkstemp()
os.close(file_descriptor)
with open(concat_video_path, 'w') as concat_video_file:
for temp_output_path in temp_output_paths:
@@ -271,11 +261,13 @@ def concat_video(output_path : str, temp_output_paths : List[str]) -> bool:
concat_video_file.close()
output_path = os.path.abspath(output_path)
output_video_format = cast(VideoFormat, get_file_format(output_path))
commands = ffmpeg_builder.chain(
ffmpeg_builder.unsafe_concat(),
ffmpeg_builder.set_input(concat_video_file.name),
ffmpeg_builder.copy_video_encoder(),
ffmpeg_builder.copy_audio_encoder(),
ffmpeg_builder.set_faststart(output_video_format),
ffmpeg_builder.force_output(output_path)
)
process = run_ffmpeg(commands)
+22 -6
View File
@@ -5,7 +5,7 @@ from typing import List, Optional
import numpy
from facefusion.filesystem import get_file_format
from facefusion.types import AudioEncoder, Command, CommandSet, Duration, Fps, StreamMode, VideoEncoder, VideoPreset
from facefusion.types import AudioEncoder, Command, CommandSet, Duration, Fps, StreamMode, VideoEncoder, VideoFormat, VideoPreset
def run(commands : List[Command]) -> List[Command]:
@@ -48,7 +48,11 @@ def set_input(input_path : str) -> List[Command]:
def set_input_fps(input_fps : Fps) -> List[Command]:
return [ '-r', str(input_fps)]
return [ '-r', str(input_fps) ]
def set_start_number(frame_number : int) -> List[Command]:
return [ '-start_number', str(frame_number) ]
def set_output(output_path : str) -> List[Command]:
@@ -59,10 +63,6 @@ def force_output(output_path : str) -> List[Command]:
return [ '-y', output_path ]
def set_loop() -> List[Command]:
return [ '-loop', '1' ]
def cast_stream() -> List[Command]:
return [ '-' ]
@@ -83,6 +83,10 @@ def unsafe_concat() -> List[Command]:
return [ '-f', 'concat', '-safe', '0' ]
def enforce_pixel_format(pixel_format : str) -> List[Command]:
return [ '-pix_fmt', pixel_format ]
def set_pixel_format(video_encoder : VideoEncoder) -> List[Command]:
if video_encoder == 'rawvideo':
return [ '-pix_fmt', 'rgb24' ]
@@ -187,6 +191,18 @@ def copy_video_encoder() -> List[Command]:
return set_video_encoder('copy')
def set_faststart(video_format : VideoFormat) -> List[Command]:
if video_format in [ 'm4v', 'mov', 'mp4' ]:
return [ '-movflags', '+faststart' ]
return []
def set_video_tag(video_encoder : VideoEncoder, video_format : VideoFormat) -> List[Command]:
if video_format in [ 'm4v', 'mov', 'mp4' ] and video_encoder in [ 'libx265', 'hevc_nvenc', 'hevc_amf', 'hevc_qsv', 'hevc_videotoolbox' ]:
return [ '-tag:v', 'hvc1' ]
return []
def set_video_quality(video_encoder : VideoEncoder, video_quality : int) -> List[Command]:
if video_encoder in [ 'libx264', 'libx264rgb', 'libx265' ]:
video_compression = numpy.round(numpy.interp(video_quality, [ 0, 100 ], [ 51, 0 ])).astype(int).item()
+9
View File
@@ -42,6 +42,15 @@ def get_file_format(file_path : str) -> Optional[str]:
return None
def same_file_extension(first_file_path : str, second_file_path : str) -> bool:
first_file_extension = get_file_extension(first_file_path)
second_file_extension = get_file_extension(second_file_path)
if first_file_extension and second_file_extension:
return get_file_extension(first_file_path) == get_file_extension(second_file_path)
return False
def is_file(file_path : str) -> bool:
if file_path:
return os.path.isfile(file_path)
+26 -19
View File
@@ -1,5 +1,6 @@
import importlib
import random
from functools import lru_cache
from time import sleep, time
from typing import List
@@ -8,16 +9,16 @@ from onnxruntime import InferenceSession
from facefusion import logger, process_manager, state_manager, translator
from facefusion.app_context import detect_app_context
from facefusion.common_helper import is_windows
from facefusion.execution import create_inference_session_providers, has_execution_provider
from facefusion.execution import create_inference_providers, has_execution_provider
from facefusion.exit_helper import fatal_exit
from facefusion.filesystem import get_file_name, is_file
from facefusion.time_helper import calculate_end_time
from facefusion.types import DownloadSet, ExecutionProvider, InferencePool, InferencePoolSet
from facefusion.types import DownloadSet, ExecutionProvider, InferencePool, InferencePoolSet, InferenceProvider
INFERENCE_POOL_SET : InferencePoolSet =\
{
'cli': {},
'api': {}
'ui': {}
}
@@ -25,37 +26,38 @@ def get_inference_pool(module_name : str, model_names : List[str], model_source_
while process_manager.is_checking():
sleep(0.5)
execution_device_ids = state_manager.get_item('execution_device_ids')
execution_providers = resolve_execution_providers(module_name)
execution_providers = state_manager.get_item('execution_providers')
app_context = detect_app_context()
for execution_device_id in execution_device_ids:
inference_context = get_inference_context(module_name, model_names, execution_device_id, execution_providers)
if app_context == 'cli' and INFERENCE_POOL_SET.get('api').get(inference_context):
INFERENCE_POOL_SET['cli'][inference_context] = INFERENCE_POOL_SET.get('api').get(inference_context)
if app_context == 'api' and INFERENCE_POOL_SET.get('cli').get(inference_context):
INFERENCE_POOL_SET['api'][inference_context] = INFERENCE_POOL_SET.get('cli').get(inference_context)
if app_context == 'cli' and INFERENCE_POOL_SET.get('ui').get(inference_context):
INFERENCE_POOL_SET['cli'][inference_context] = INFERENCE_POOL_SET.get('ui').get(inference_context)
if app_context == 'ui' and INFERENCE_POOL_SET.get('cli').get(inference_context):
INFERENCE_POOL_SET['ui'][inference_context] = INFERENCE_POOL_SET.get('cli').get(inference_context)
if not INFERENCE_POOL_SET.get(app_context).get(inference_context):
INFERENCE_POOL_SET[app_context][inference_context] = create_inference_pool(model_source_set, execution_device_id, execution_providers)
inference_providers = resolve_static_inference_providers(module_name, execution_device_id)
INFERENCE_POOL_SET[app_context][inference_context] = create_inference_pool(model_source_set, inference_providers)
current_inference_context = get_inference_context(module_name, model_names, random.choice(execution_device_ids), execution_providers)
return INFERENCE_POOL_SET.get(app_context).get(current_inference_context)
def create_inference_pool(model_source_set : DownloadSet, execution_device_id : int, execution_providers : List[ExecutionProvider]) -> InferencePool:
def create_inference_pool(model_source_set : DownloadSet, inference_providers : List[InferenceProvider]) -> InferencePool:
inference_pool : InferencePool = {}
for model_name in model_source_set.keys():
model_path = model_source_set.get(model_name).get('path')
if is_file(model_path):
inference_pool[model_name] = create_inference_session(model_path, execution_device_id, execution_providers)
inference_pool[model_name] = create_inference_session(model_path, inference_providers)
return inference_pool
def clear_inference_pool(module_name : str, model_names : List[str]) -> None:
execution_device_ids = state_manager.get_item('execution_device_ids')
execution_providers = resolve_execution_providers(module_name)
execution_providers = state_manager.get_item('execution_providers')
app_context = detect_app_context()
if is_windows() and has_execution_provider('directml'):
@@ -67,13 +69,12 @@ def clear_inference_pool(module_name : str, model_names : List[str]) -> None:
del INFERENCE_POOL_SET[app_context][inference_context]
def create_inference_session(model_path : str, execution_device_id : int, execution_providers : List[ExecutionProvider]) -> InferenceSession:
def create_inference_session(model_path : str, inference_providers : List[InferenceProvider]) -> InferenceSession:
model_file_name = get_file_name(model_path)
start_time = time()
try:
inference_session_providers = create_inference_session_providers(execution_device_id, execution_providers)
inference_session = InferenceSession(model_path, providers = inference_session_providers)
inference_session = InferenceSession(model_path, providers = inference_providers)
logger.debug(translator.get('loading_model_succeeded').format(model_name = model_file_name, seconds = calculate_end_time(start_time)), __name__)
return inference_session
@@ -87,9 +88,15 @@ def get_inference_context(module_name : str, model_names : List[str], execution_
return inference_context
def resolve_execution_providers(module_name : str) -> List[ExecutionProvider]:
@lru_cache()
def resolve_static_inference_providers(module_name : str, execution_device_id : int) -> List[InferenceProvider]:
module = importlib.import_module(module_name)
execution_providers = state_manager.get_item('execution_providers')
if hasattr(module, 'resolve_execution_providers'):
return getattr(module, 'resolve_execution_providers')()
return state_manager.get_item('execution_providers')
if hasattr(module, 'resolve_inference_providers'):
inference_providers = getattr(module, 'resolve_inference_providers')()
if inference_providers:
return inference_providers
return create_inference_providers(execution_device_id, execution_providers)
+23 -49
View File
@@ -10,30 +10,34 @@ from types import FrameType
from facefusion import metadata
from facefusion.common_helper import is_linux, is_windows
LOCALS =\
LOCALES =\
{
'install_dependency': 'install the {dependency} package',
'force_reinstall': 'force reinstall of packages',
'skip_conda': 'skip the conda environment check',
'conda_not_activated': 'conda is not activated'
}
ONNXRUNTIME_SET =\
{
'default': ('onnxruntime', '1.23.2')
'default': ('onnxruntime', '1.26.0')
}
if is_windows() or is_linux():
ONNXRUNTIME_SET['cuda'] = ('onnxruntime-gpu', '1.23.2')
ONNXRUNTIME_SET['openvino'] = ('onnxruntime-openvino', '1.23.0')
ONNXRUNTIME_SET['cuda'] = ('onnxruntime-gpu', '1.26.0')
ONNXRUNTIME_SET['openvino'] = ('onnxruntime-openvino', '1.24.1')
if is_windows():
ONNXRUNTIME_SET['directml'] = ('onnxruntime-directml', '1.23.0')
ONNXRUNTIME_SET['directml'] = ('onnxruntime-directml', '1.24.4')
ONNXRUNTIME_SET['qnn'] = ('onnxruntime-qnn', '1.24.4')
if is_linux():
ONNXRUNTIME_SET['rocm'] = ('onnxruntime-rocm', '1.21.0')
ONNXRUNTIME_SET['migraphx'] = ('onnxruntime-migraphx', '1.25.0')
ONNXRUNTIME_SET['rocm'] = ('onnxruntime-rocm', '1.22.2.post1')
def cli() -> None:
signal.signal(signal.SIGINT, signal_exit)
program = ArgumentParser(formatter_class = partial(HelpFormatter, max_help_position = 50))
program.add_argument('--onnxruntime', help = LOCALS.get('install_dependency').format(dependency = 'onnxruntime'), choices = ONNXRUNTIME_SET.keys(), required = True)
program.add_argument('--skip-conda', help = LOCALS.get('skip_conda'), action = 'store_true')
program.add_argument('onnxruntime', help = LOCALES.get('install_dependency').format(dependency = 'onnxruntime'), choices = ONNXRUNTIME_SET.keys())
program.add_argument('--force-reinstall', help = LOCALES.get('force_reinstall'), action = 'store_true')
program.add_argument('--skip-conda', help = LOCALES.get('skip_conda'), action = 'store_true')
program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version')
run(program)
@@ -45,56 +49,26 @@ def signal_exit(signum : int, frame : FrameType) -> None:
def run(program : ArgumentParser) -> None:
args = program.parse_args()
has_conda = 'CONDA_PREFIX' in os.environ
onnxruntime_name, onnxruntime_version = ONNXRUNTIME_SET.get(args.onnxruntime)
if not args.skip_conda and not has_conda:
sys.stdout.write(LOCALS.get('conda_not_activated') + os.linesep)
sys.stdout.write(LOCALES.get('conda_not_activated') + os.linesep)
sys.exit(1)
commands = [ shutil.which('pip'), 'install' ]
if args.force_reinstall:
commands.append('--force-reinstall')
with open('requirements.txt') as file:
for line in file.readlines():
__line__ = line.strip()
if not __line__.startswith('onnxruntime'):
subprocess.call([ shutil.which('pip'), 'install', line, '--force-reinstall' ])
commands.append(__line__)
if args.onnxruntime == 'rocm':
python_id = 'cp' + str(sys.version_info.major) + str(sys.version_info.minor)
onnxruntime_name, onnxruntime_version = ONNXRUNTIME_SET.get(args.onnxruntime)
commands.append(onnxruntime_name + '==' + onnxruntime_version)
if python_id in [ 'cp310', 'cp312' ]:
wheel_name = 'onnxruntime_rocm-' + onnxruntime_version + '-' + python_id + '-' + python_id + '-linux_x86_64.whl'
wheel_url = 'https://repo.radeon.com/rocm/manylinux/rocm-rel-6.4/' + wheel_name
subprocess.call([ shutil.which('pip'), 'install', wheel_url, '--force-reinstall' ])
else:
subprocess.call([ shutil.which('pip'), 'install', onnxruntime_name + '==' + onnxruntime_version, '--force-reinstall' ])
if args.onnxruntime == 'cuda' and has_conda:
library_paths = []
if is_linux():
if os.getenv('LD_LIBRARY_PATH'):
library_paths = os.getenv('LD_LIBRARY_PATH').split(os.pathsep)
python_id = 'python' + str(sys.version_info.major) + '.' + str(sys.version_info.minor)
library_paths.extend(
[
os.path.join(os.getenv('CONDA_PREFIX'), 'lib'),
os.path.join(os.getenv('CONDA_PREFIX'), 'lib', python_id, 'site-packages', 'tensorrt_libs')
])
library_paths = list(dict.fromkeys([ library_path for library_path in library_paths if os.path.exists(library_path) ]))
subprocess.call([ shutil.which('conda'), 'env', 'config', 'vars', 'set', 'LD_LIBRARY_PATH=' + os.pathsep.join(library_paths) ])
if is_windows():
if os.getenv('PATH'):
library_paths = os.getenv('PATH').split(os.pathsep)
library_paths.extend(
[
os.path.join(os.getenv('CONDA_PREFIX'), 'Lib'),
os.path.join(os.getenv('CONDA_PREFIX'), 'Lib', 'site-packages', 'tensorrt_libs')
])
library_paths = list(dict.fromkeys([ library_path for library_path in library_paths if os.path.exists(library_path) ]))
subprocess.call([ shutil.which('conda'), 'env', 'config', 'vars', 'set', 'PATH=' + os.pathsep.join(library_paths) ])
subprocess.call([ shutil.which('pip'), 'uninstall', 'onnxruntime', onnxruntime_name, '-y', '-q' ])
subprocess.call(commands)
+2
View File
@@ -6,6 +6,7 @@ import facefusion.choices
from facefusion.filesystem import create_directory, get_file_name, is_directory, is_file, move_file, remove_directory, remove_file, resolve_file_pattern
from facefusion.jobs.job_helper import get_step_output_path
from facefusion.json import read_json, write_json
from facefusion.sanitizer import sanitize_job_id
from facefusion.time_helper import get_current_date_time
from facefusion.types import Args, Job, JobSet, JobStatus, JobStep, JobStepStatus
@@ -261,5 +262,6 @@ def find_job_path(job_id : str) -> Optional[str]:
def get_job_file_name(job_id : str) -> Optional[str]:
if job_id:
job_id = sanitize_job_id(job_id)
return job_id + '.json'
return None
+27
View File
@@ -0,0 +1,27 @@
from typing import List
from facefusion.types import JobStore
JOB_STORE : JobStore =\
{
'job_keys': [],
'step_keys': []
}
def get_job_keys() -> List[str]:
return JOB_STORE.get('job_keys')
def get_step_keys() -> List[str]:
return JOB_STORE.get('step_keys')
def register_job_keys(job_keys : List[str]) -> None:
for job_key in job_keys:
JOB_STORE['job_keys'].append(job_key)
def register_step_keys(step_keys : List[str]) -> None:
for step_key in step_keys:
JOB_STORE['step_keys'].append(step_key)
-134
View File
@@ -1,134 +0,0 @@
import ctypes
from functools import lru_cache
from typing import Optional
from facefusion.common_helper import is_linux, is_macos, is_windows
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url_by_provider
from facefusion.filesystem import resolve_relative_path
from facefusion.types import LibrarySet
@lru_cache
def create_static_library_set() -> Optional[LibrarySet]:
if is_linux():
return\
{
'hashes':
{
'aom':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libaom.hash'),
'path': resolve_relative_path('../.libraries/libaom.hash')
}
},
'sources':
{
'aom':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libaom.so'),
'path': resolve_relative_path('../.libraries/libaom.so')
}
}
}
if is_macos():
return\
{
'hashes':
{
'aom':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libaom.hash'),
'path': resolve_relative_path('../.libraries/libaom.hash')
}
},
'sources':
{
'aom':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libaom.dylib'),
'path': resolve_relative_path('../.libraries/libaom.dylib')
}
}
}
if is_windows():
return\
{
'hashes':
{
'aom':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/aom.hash'),
'path': resolve_relative_path('../.libraries/aom.hash')
}
},
'sources':
{
'aom':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/aom.dll'),
'path': resolve_relative_path('../.libraries/aom.dll')
}
}
}
return None
def pre_check() -> bool:
library_hash_set = create_static_library_set().get('hashes')
library_source_set = create_static_library_set().get('sources')
return conditional_download_hashes(library_hash_set) and conditional_download_sources(library_source_set)
@lru_cache
def create_static_library() -> Optional[ctypes.CDLL]:
library_path = create_static_library_set().get('sources').get('aom').get('path')
if library_path:
if is_windows():
library = ctypes.CDLL(library_path, winmode = 0)
else:
library = ctypes.CDLL(library_path)
if library:
return init_ctypes(library)
return None
def init_ctypes(library : ctypes.CDLL) -> ctypes.CDLL:
library.aom_codec_enc_config_default.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint ]
library.aom_codec_enc_config_default.restype = ctypes.c_int
library.aom_codec_enc_init_ver.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_int ]
library.aom_codec_enc_init_ver.restype = ctypes.c_int
library.aom_codec_encode.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int64, ctypes.c_ulong, ctypes.c_long, ctypes.c_ulong ]
library.aom_codec_encode.restype = ctypes.c_int
library.aom_codec_get_cx_data.argtypes = [ ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p) ]
library.aom_codec_get_cx_data.restype = ctypes.c_void_p
library.aom_codec_enc_config_set.argtypes = [ ctypes.c_void_p, ctypes.c_void_p ]
library.aom_codec_enc_config_set.restype = ctypes.c_int
library.aom_codec_destroy.argtypes = [ ctypes.c_void_p ]
library.aom_codec_destroy.restype = ctypes.c_int
library.aom_img_wrap.argtypes = [ ctypes.c_void_p, ctypes.c_int, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p ]
library.aom_img_wrap.restype = ctypes.c_void_p
library.aom_codec_control.argtypes = [ ctypes.c_void_p, ctypes.c_int, ctypes.c_int ]
library.aom_codec_control.restype = ctypes.c_int
library.aom_codec_dec_init_ver.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_int ]
library.aom_codec_dec_init_ver.restype = ctypes.c_int
library.aom_codec_decode.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, ctypes.c_void_p ]
library.aom_codec_decode.restype = ctypes.c_int
library.aom_codec_get_frame.argtypes = [ ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p) ]
library.aom_codec_get_frame.restype = ctypes.c_void_p
return library
-294
View File
@@ -1,294 +0,0 @@
import ctypes
from functools import lru_cache
from typing import Optional
from facefusion.common_helper import is_linux, is_macos, is_windows
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url_by_provider
from facefusion.filesystem import resolve_relative_path
from facefusion.types import LibrarySet
@lru_cache
def create_static_library_set() -> Optional[LibrarySet]:
if is_linux():
return\
{
'hashes':
{
'crypto':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libcrypto.hash'),
'path': resolve_relative_path('../.libraries/libcrypto.hash')
},
'datachannel':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libdatachannel_next.hash'),
'path': resolve_relative_path('../.libraries/libdatachannel_next.hash')
},
'ssl':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libssl.hash'),
'path': resolve_relative_path('../.libraries/libssl.hash')
}
},
'sources':
{
'crypto':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libcrypto.so'),
'path': resolve_relative_path('../.libraries/libcrypto.so')
},
'datachannel':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libdatachannel_next.so'),
'path': resolve_relative_path('../.libraries/libdatachannel_next.so')
},
'ssl':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libssl.so'),
'path': resolve_relative_path('../.libraries/libssl.so')
}
}
}
if is_macos():
return\
{
'hashes':
{
'crypto':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libcrypto.hash'),
'path': resolve_relative_path('../.libraries/libcrypto.hash')
},
'datachannel':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libdatachannel_next.hash'),
'path': resolve_relative_path('../.libraries/libdatachannel_next.hash')
},
'ssl':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libssl.hash'),
'path': resolve_relative_path('../.libraries/libssl.hash')
}
},
'sources':
{
'crypto':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libcrypto.dylib'),
'path': resolve_relative_path('../.libraries/libcrypto.dylib')
},
'datachannel':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libdatachannel_next.dylib'),
'path': resolve_relative_path('../.libraries/libdatachannel_next.dylib')
},
'ssl':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libssl.dylib'),
'path': resolve_relative_path('../.libraries/libssl.dylib')
}
}
}
if is_windows():
return\
{
'hashes':
{
'crypto':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/crypto.hash'),
'path': resolve_relative_path('../.libraries/crypto.hash')
},
'datachannel':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/datachannel_next.hash'),
'path': resolve_relative_path('../.libraries/datachannel_next.hash')
},
'ssl':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/ssl.hash'),
'path': resolve_relative_path('../.libraries/ssl.hash')
}
},
'sources':
{
'crypto':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/crypto.dll'),
'path': resolve_relative_path('../.libraries/crypto.dll')
},
'datachannel':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/datachannel_next.dll'),
'path': resolve_relative_path('../.libraries/datachannel_next.dll')
},
'ssl':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/ssl.dll'),
'path': resolve_relative_path('../.libraries/ssl.dll')
}
}
}
return None
def pre_check() -> bool:
library_hash_set = create_static_library_set().get('hashes')
library_source_set = create_static_library_set().get('sources')
return conditional_download_hashes(library_hash_set) and conditional_download_sources(library_source_set)
@lru_cache
def create_static_library() -> Optional[ctypes.CDLL]:
crypto_source_path = create_static_library_set().get('sources').get('crypto').get('path')
datachannel_source_path = create_static_library_set().get('sources').get('datachannel').get('path')
ssl_source_path = create_static_library_set().get('sources').get('ssl').get('path')
if crypto_source_path and datachannel_source_path and ssl_source_path:
if is_windows():
ctypes.CDLL(crypto_source_path, winmode = 0)
ctypes.CDLL(ssl_source_path, winmode = 0)
library = ctypes.CDLL(datachannel_source_path, winmode = 0)
else:
ctypes.CDLL(crypto_source_path)
ctypes.CDLL(ssl_source_path)
library = ctypes.CDLL(datachannel_source_path)
if library:
return init_ctypes(library)
return None
def init_ctypes(library : ctypes.CDLL) -> ctypes.CDLL:
library.rtcInitLogger.argtypes = [ ctypes.c_int, ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p) ]
library.rtcInitLogger.restype = None
library.rtcInitLogger(2, ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_char_p)(0))
library.rtcCreatePeerConnection.restype = ctypes.c_int
library.rtcDeletePeerConnection.argtypes = [ ctypes.c_int ]
library.rtcDeletePeerConnection.restype = ctypes.c_int
library.rtcSetLocalDescription.argtypes = [ ctypes.c_int, ctypes.c_char_p ]
library.rtcSetLocalDescription.restype = ctypes.c_int
library.rtcSetRemoteDescription.argtypes = [ ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p ]
library.rtcSetRemoteDescription.restype = ctypes.c_int
library.rtcAddTrackEx.restype = ctypes.c_int
library.rtcSendMessage.argtypes = [ ctypes.c_int, ctypes.c_void_p, ctypes.c_int ]
library.rtcSendMessage.restype = ctypes.c_int
library.rtcSetAV1Packetizer.restype = ctypes.c_int
library.rtcSetVP8Packetizer.restype = ctypes.c_int
library.rtcSetVP9Packetizer.restype = ctypes.c_int
library.rtcChainRtcpSrReporter.argtypes = [ ctypes.c_int ]
library.rtcChainRtcpSrReporter.restype = ctypes.c_int
library.rtcSetTrackRtpTimestamp.argtypes = [ ctypes.c_int, ctypes.c_uint32 ]
library.rtcSetTrackRtpTimestamp.restype = ctypes.c_int
library.rtcIsOpen.argtypes = [ ctypes.c_int ]
library.rtcIsOpen.restype = ctypes.c_bool
library.rtcChainRtcpNackResponder.argtypes = [ ctypes.c_int, ctypes.c_uint ]
library.rtcChainRtcpNackResponder.restype = ctypes.c_int
library.rtcGetLocalDescription.argtypes = [ ctypes.c_int, ctypes.c_char_p, ctypes.c_int ]
library.rtcGetLocalDescription.restype = ctypes.c_int
library.rtcSetOpusPacketizer.restype = ctypes.c_int
library.rtcGetPayloadTypesForCodec.argtypes = [ ctypes.c_char_p, ctypes.c_char_p, ctypes.POINTER(ctypes.c_int), ctypes.c_int ]
library.rtcGetPayloadTypesForCodec.restype = ctypes.c_int
library.rtcSetAV1Depacketizer.argtypes = [ ctypes.c_int, ctypes.c_int ]
library.rtcSetAV1Depacketizer.restype = ctypes.c_int
library.rtcSetVP8Depacketizer.restype = ctypes.c_int
library.rtcSetVP9Depacketizer.restype = ctypes.c_int
library.rtcSetOpusDepacketizer.restype = ctypes.c_int
library.rtcChainRtcpReceivingSession.argtypes = [ ctypes.c_int ]
library.rtcChainRtcpReceivingSession.restype = ctypes.c_int
library.rtcSetFrameCallback.argtypes = [ ctypes.c_int, ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p) ]
library.rtcSetFrameCallback.restype = ctypes.c_int
library.rtcSetUserPointer.argtypes = [ ctypes.c_int, ctypes.c_void_p ]
library.rtcSetUserPointer.restype = None
library.rtcChainRembHandler.argtypes = [ ctypes.c_int, ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_uint, ctypes.c_void_p) ]
library.rtcChainRembHandler.restype = ctypes.c_int
library.rtcRequestBitrate.argtypes = [ ctypes.c_int, ctypes.c_uint ]
library.rtcRequestBitrate.restype = ctypes.c_int
library.rtcSetClosedCallback.argtypes = [ ctypes.c_int, ctypes.CFUNCTYPE(None, ctypes.c_int, ctypes.c_void_p) ]
library.rtcSetClosedCallback.restype = ctypes.c_int
return library
def define_rtc_configuration() -> ctypes.Structure:
return type('RTC_CONFIGURATION', (ctypes.Structure,),
{
'_fields_':
[
('iceServers', ctypes.POINTER(ctypes.c_char_p)),
('iceServersCount', ctypes.c_int),
('proxyServer', ctypes.c_char_p),
('bindAddress', ctypes.c_char_p),
('certificateType', ctypes.c_int),
('iceTransportPolicy', ctypes.c_int),
('enableIceTcp', ctypes.c_bool),
('enableIceUdpMux', ctypes.c_bool),
('disableAutoNegotiation', ctypes.c_bool),
('forceMediaTransport', ctypes.c_bool),
('portRangeBegin', ctypes.c_ushort),
('portRangeEnd', ctypes.c_ushort),
('mtu', ctypes.c_int),
('maxMessageSize', ctypes.c_int)
]
})()
def define_rtc_track_init() -> ctypes.Structure:
return type('RTC_TRACK_INIT', (ctypes.Structure,),
{
'_fields_':
[
('direction', ctypes.c_int),
('codec', ctypes.c_int),
('payloadType', ctypes.c_int),
('ssrc', ctypes.c_uint32),
('mid', ctypes.c_char_p),
('name', ctypes.c_char_p),
('msid', ctypes.c_char_p),
('trackId', ctypes.c_char_p),
('profile', ctypes.c_char_p)
]
})()
def define_rtc_packetizer_init() -> ctypes.Structure:
return type('RTC_PACKETIZER_INIT', (ctypes.Structure,),
{
'_fields_':
[
('ssrc', ctypes.c_uint32),
('cname', ctypes.c_char_p),
('payloadType', ctypes.c_uint8),
('clockRate', ctypes.c_uint32),
('sequenceNumber', ctypes.c_uint16),
('timestamp', ctypes.c_uint32),
('maxFragmentSize', ctypes.c_uint16),
('nalSeparator', ctypes.c_int),
('obuPacketization', ctypes.c_int)
]
})()
-121
View File
@@ -1,121 +0,0 @@
import ctypes
from functools import lru_cache
from typing import Optional
from facefusion.common_helper import is_linux, is_macos, is_windows
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url_by_provider
from facefusion.filesystem import resolve_relative_path
from facefusion.types import LibrarySet
@lru_cache
def create_static_library_set() -> Optional[LibrarySet]:
if is_linux():
return\
{
'hashes':
{
'opus':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libopus.hash'),
'path': resolve_relative_path('../.libraries/libopus.hash')
}
},
'sources':
{
'opus':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libopus.so'),
'path': resolve_relative_path('../.libraries/libopus.so')
}
}
}
if is_macos():
return\
{
'hashes':
{
'opus':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libopus.hash'),
'path': resolve_relative_path('../.libraries/libopus.hash')
}
},
'sources':
{
'opus':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libopus.dylib'),
'path': resolve_relative_path('../.libraries/libopus.dylib')
}
}
}
if is_windows():
return\
{
'hashes':
{
'opus':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/opus.hash'),
'path': resolve_relative_path('../.libraries/opus.hash')
}
},
'sources':
{
'opus':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/opus.dll'),
'path': resolve_relative_path('../.libraries/opus.dll')
}
}
}
return None
def pre_check() -> bool:
library_hash_set = create_static_library_set().get('hashes')
library_source_set = create_static_library_set().get('sources')
return conditional_download_hashes(library_hash_set) and conditional_download_sources(library_source_set)
@lru_cache
def create_static_library() -> Optional[ctypes.CDLL]:
library_path = create_static_library_set().get('sources').get('opus').get('path')
if library_path:
if is_windows():
library = ctypes.CDLL(library_path, winmode = 0)
else:
library = ctypes.CDLL(library_path)
if library:
return init_ctypes(library)
return None
def init_ctypes(library : ctypes.CDLL) -> ctypes.CDLL:
library.opus_encoder_create.argtypes = [ ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int) ]
library.opus_encoder_create.restype = ctypes.c_void_p
library.opus_encode_float.argtypes = [ ctypes.c_void_p, ctypes.POINTER(ctypes.c_float), ctypes.c_int, ctypes.c_char_p, ctypes.c_int ]
library.opus_encode_float.restype = ctypes.c_int
library.opus_encoder_destroy.argtypes = [ ctypes.c_void_p ]
library.opus_encoder_destroy.restype = None
library.opus_decoder_create.argtypes = [ ctypes.c_int, ctypes.c_int, ctypes.POINTER(ctypes.c_int) ]
library.opus_decoder_create.restype = ctypes.c_void_p
library.opus_decode_float.argtypes = [ ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.POINTER(ctypes.c_float), ctypes.c_int, ctypes.c_int ]
library.opus_decode_float.restype = ctypes.c_int
library.opus_decoder_destroy.argtypes = [ ctypes.c_void_p ]
library.opus_decoder_destroy.restype = None
return library
-134
View File
@@ -1,134 +0,0 @@
import ctypes
from functools import lru_cache
from typing import Optional
from facefusion.common_helper import is_linux, is_macos, is_windows
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url_by_provider
from facefusion.filesystem import resolve_relative_path
from facefusion.types import LibrarySet
@lru_cache
def create_static_library_set() -> Optional[LibrarySet]:
if is_linux():
return\
{
'hashes':
{
'vpx':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libvpx.hash'),
'path': resolve_relative_path('../.libraries/libvpx.hash')
}
},
'sources':
{
'vpx':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'linux/libvpx.so'),
'path': resolve_relative_path('../.libraries/libvpx.so')
}
}
}
if is_macos():
return\
{
'hashes':
{
'vpx':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libvpx.hash'),
'path': resolve_relative_path('../.libraries/libvpx.hash')
}
},
'sources':
{
'vpx':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'macos/libvpx.dylib'),
'path': resolve_relative_path('../.libraries/libvpx.dylib')
}
}
}
if is_windows():
return\
{
'hashes':
{
'vpx':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/vpx.hash'),
'path': resolve_relative_path('../.libraries/vpx.hash')
}
},
'sources':
{
'vpx':
{
'url': resolve_download_url_by_provider('huggingface', 'libraries-4.0.0', 'windows/vpx.dll'),
'path': resolve_relative_path('../.libraries/vpx.dll')
}
}
}
return None
def pre_check() -> bool:
library_hash_set = create_static_library_set().get('hashes')
library_source_set = create_static_library_set().get('sources')
return conditional_download_hashes(library_hash_set) and conditional_download_sources(library_source_set)
@lru_cache
def create_static_library() -> Optional[ctypes.CDLL]:
library_path = create_static_library_set().get('sources').get('vpx').get('path')
if library_path:
if is_windows():
library = ctypes.CDLL(library_path, winmode = 0)
else:
library = ctypes.CDLL(library_path)
if library:
return init_ctypes(library)
return None
def init_ctypes(library : ctypes.CDLL) -> ctypes.CDLL:
library.vpx_codec_enc_config_default.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint ]
library.vpx_codec_enc_config_default.restype = ctypes.c_int
library.vpx_codec_enc_init_ver.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_int ]
library.vpx_codec_enc_init_ver.restype = ctypes.c_int
library.vpx_codec_encode.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int64, ctypes.c_ulong, ctypes.c_long, ctypes.c_ulong ]
library.vpx_codec_encode.restype = ctypes.c_int
library.vpx_codec_get_cx_data.argtypes = [ ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p) ]
library.vpx_codec_get_cx_data.restype = ctypes.c_void_p
library.vpx_codec_enc_config_set.argtypes = [ ctypes.c_void_p, ctypes.c_void_p ]
library.vpx_codec_enc_config_set.restype = ctypes.c_int
library.vpx_codec_destroy.argtypes = [ ctypes.c_void_p ]
library.vpx_codec_destroy.restype = ctypes.c_int
library.vpx_img_wrap.argtypes = [ ctypes.c_void_p, ctypes.c_int, ctypes.c_uint, ctypes.c_uint, ctypes.c_uint, ctypes.c_void_p ]
library.vpx_img_wrap.restype = ctypes.c_void_p
library.vpx_codec_control_.argtypes = [ ctypes.c_void_p, ctypes.c_int, ctypes.c_int ]
library.vpx_codec_control_.restype = ctypes.c_int
library.vpx_codec_dec_init_ver.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_long, ctypes.c_int ]
library.vpx_codec_dec_init_ver.restype = ctypes.c_int
library.vpx_codec_decode.argtypes = [ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint, ctypes.c_void_p, ctypes.c_long ]
library.vpx_codec_decode.restype = ctypes.c_int
library.vpx_codec_get_frame.argtypes = [ ctypes.c_void_p, ctypes.POINTER(ctypes.c_void_p) ]
library.vpx_codec_get_frame.restype = ctypes.c_void_p
return library
+14 -11
View File
@@ -1,6 +1,6 @@
from facefusion.types import Locals
from facefusion.types import Locales
LOCALS : Locals =\
LOCALES : Locales =\
{
'en':
{
@@ -12,11 +12,8 @@ LOCALS : Locals =\
'extracting_frames': 'extracting frames with a resolution of {resolution} and {fps} frames per second',
'extracting_frames_succeeded': 'extracting frames succeeded',
'extracting_frames_failed': 'extracting frames failed',
'spawning_frames_succeeded': 'spawning frames succeeded',
'spawning_frames_failed': 'spawning frames failed',
'analysing': 'analysing',
'extracting': 'extracting',
'spawning': 'spawning',
'streaming': 'streaming',
'processing': 'processing',
'merging': 'merging',
@@ -51,9 +48,10 @@ LOCALS : Locals =\
'no_source_face_detected': 'no source face detected',
'processor_not_loaded': 'processor {processor} could not be loaded',
'processor_not_implemented': 'processor {processor} not implemented correctly',
'ui_layout_not_loaded': 'ui layout {ui_layout} could not be loaded',
'ui_layout_not_implemented': 'ui layout {ui_layout} not implemented correctly',
'stream_not_loaded': 'stream {stream_mode} could not be loaded',
'stream_not_supported': 'stream not supported',
'api_started': 'started API on {host}:{port}',
'job_created': 'job {job_id} created',
'job_not_created': 'job {job_id} not created',
'job_submitted': 'job {job_id} submitted',
@@ -101,7 +99,6 @@ LOCALS : Locals =\
{
'install_dependency': 'choose the variant of {dependency} to install',
'skip_conda': 'skip the conda environment check',
'workflow': 'choose the workflow',
'config_path': 'choose the config file to override defaults',
'temp_path': 'specify the directory for the temporary resources',
'jobs_path': 'specify the directory to store jobs',
@@ -127,6 +124,7 @@ LOCALS : Locals =\
'reference_face_position': 'specify the position used to create the reference face',
'reference_face_distance': 'specify the similarity between the reference face and target face',
'reference_frame_number': 'specify the frame used to create the reference face',
'face_tracker_score': 'specify the overlap score used to match the tracked faces',
'face_occluder_model': 'choose the model responsible for the occlusion mask',
'face_parser_model': 'choose the model responsible for the region mask',
'face_mask_types': 'mix and match different face mask types (choices: {choices})',
@@ -138,6 +136,8 @@ LOCALS : Locals =\
'trim_frame_start': 'specify the starting frame of the target video',
'trim_frame_end': 'specify the ending frame of the target video',
'temp_frame_format': 'specify the temporary resources format',
'keep_temp': 'keep the temporary resources after processing',
'target_frame_amount': 'specify the amount of target frames forwarded to the processor',
'output_image_quality': 'specify the image quality which translates to the image compression',
'output_image_scale': 'specify the image scale based on the target image',
'output_audio_encoder': 'specify the encoder used for the audio',
@@ -151,13 +151,14 @@ LOCALS : Locals =\
'processors': 'load a single or multiple processors (choices: {choices}, ...)',
'background-remover-model': 'choose the model responsible for removing the background',
'background-remover-color': 'apply red, green blue and alpha values of the background',
'open_browser': 'open the browser once the program is ready',
'ui_layouts': 'launch a single or multiple UI layouts (choices: {choices}, ...)',
'ui_workflow': 'choose the ui workflow',
'download_providers': 'download using different providers (choices: {choices}, ...)',
'download_scope': 'specify the download scope',
'benchmark_mode': 'choose the benchmark mode',
'benchmark_resolutions': 'choose the resolutions for the benchmarks (choices: {choices}, ...)',
'benchmark_cycle_count': 'specify the amount of cycles per benchmark',
'api_host': 'specify the API host',
'api_port': 'specify the API port',
'execution_device_ids': 'specify the devices used for processing',
'execution_providers': 'inference using different providers (choices: {choices}, ...)',
'execution_thread_count': 'specify the amount of parallel threads while processing',
@@ -165,10 +166,10 @@ LOCALS : Locals =\
'log_level': 'adjust the message severity displayed in the terminal',
'halt_on_error': 'halt the program once an error occurred',
'run': 'run the program',
'headless_run': 'run the program in headless mode',
'batch_run': 'run the program in batch mode',
'force_download': 'force automate downloads and exit',
'benchmark': 'benchmark the program',
'api': 'start the API server',
'job_id': 'specify the job id',
'job_status': 'specify the job status',
'step_index': 'specify the step index',
@@ -189,7 +190,7 @@ LOCALS : Locals =\
},
'about':
{
'fund': 'fund training server',
'fund': 'fund ai workstation',
'subscribe': 'become a member',
'join': 'join our community'
},
@@ -224,6 +225,7 @@ LOCALS : Locals =\
'face_selector_mode_dropdown': 'FACE SELECTOR MODE',
'face_selector_order_dropdown': 'FACE SELECTOR ORDER',
'face_selector_race_dropdown': 'FACE SELECTOR RACE',
'face_tracker_score_slider': 'FACE TRACKER SCORE',
'face_occluder_model_dropdown': 'FACE OCCLUDER MODEL',
'face_parser_model_dropdown': 'FACE PARSER MODEL',
'voice_extractor_model_dropdown': 'VOICE EXTRACTOR MODEL',
@@ -261,6 +263,7 @@ LOCALS : Locals =\
'temp_frame_format_dropdown': 'TEMP FRAME FORMAT',
'terminal_textbox': 'TERMINAL',
'trim_frame_slider': 'TRIM FRAME',
'ui_workflow': 'UI WORKFLOW',
'video_memory_strategy_dropdown': 'VIDEO MEMORY STRATEGY',
'webcam_fps_slider': 'WEBCAM FPS',
'webcam_image': 'WEBCAM',
-17
View File
@@ -1,17 +0,0 @@
from typing import Optional, Tuple
def restrict_trim_frame(frame_total : int, trim_frame_start : Optional[int], trim_frame_end : Optional[int]) -> Tuple[int, int]:
if isinstance(trim_frame_start, int):
trim_frame_start = max(0, min(trim_frame_start, frame_total))
if isinstance(trim_frame_end, int):
trim_frame_end = max(0, min(trim_frame_end, frame_total))
if isinstance(trim_frame_start, int) and isinstance(trim_frame_end, int):
return trim_frame_start, trim_frame_end
if isinstance(trim_frame_start, int):
return trim_frame_start, frame_total
if isinstance(trim_frame_end, int):
return 0, trim_frame_end
return 0, frame_total
+1 -1
View File
@@ -4,7 +4,7 @@ METADATA =\
{
'name': 'FaceFusion',
'description': 'Industry leading face manipulation platform',
'version': 'v4',
'version': '3.7.1',
'license': 'OpenRAIL-AS',
'author': 'Henry Ruhs',
'url': 'https://facefusion.io'
+2 -2
View File
@@ -19,9 +19,9 @@ def normalize_space(spaces : Optional[List[int]]) -> Optional[Padding]:
if spaces and len(spaces) == 1:
return tuple([spaces[0]] * 4) #type:ignore[return-value]
if spaces and len(spaces) == 2:
return tuple([spaces[0], spaces[1], spaces[0], spaces[1]]) #type:ignore[return-value]
return tuple([ spaces[0], spaces[1], spaces[0], spaces[1] ]) #type:ignore[return-value]
if spaces and len(spaces) == 3:
return tuple([spaces[0], spaces[1], spaces[2], spaces[1]]) #type:ignore[return-value]
return tuple([ spaces[0], spaces[1], spaces[2], spaces[1] ]) #type:ignore[return-value]
if spaces and len(spaces) == 4:
return tuple(spaces) #type:ignore[return-value]
return None
+1
View File
@@ -12,6 +12,7 @@ PROCESSORS_METHODS =\
'clear_inference_pool',
'register_args',
'apply_args',
'get_common_modules',
'pre_check',
'pre_process',
'post_process',
@@ -1,8 +1,8 @@
from typing import List, Sequence
from typing import List, Sequence, get_args
from facefusion.common_helper import create_int_range
from facefusion.processors.modules.age_modifier.types import AgeModifierModel
age_modifier_models : List[AgeModifierModel] = [ 'styleganex_age' ]
age_modifier_models : List[AgeModifierModel] = list(get_args(AgeModifierModel))
age_modifier_direction_range : Sequence[int] = create_int_range(-100, 100, 1)
@@ -1,34 +1,70 @@
from argparse import ArgumentParser
from functools import lru_cache
from types import ModuleType
from typing import List
import cv2
import numpy
import facefusion.args_store
import facefusion.choices
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, state_manager, translator, video_manager
from facefusion.common_helper import create_int_metavar, is_macos
from facefusion.common_helper import create_int_metavar, get_middle
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.execution import has_execution_provider
from facefusion.face_analyser import scale_face
from facefusion.face_creator import scale_face
from facefusion.face_helper import merge_matrix, paste_back, scale_face_landmark_5, warp_face_by_face_landmark_5
from facefusion.face_masker import create_box_mask, create_occlusion_mask
from facefusion.face_selector import select_faces
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
from facefusion.processors.modules.age_modifier import choices as age_modifier_choices
from facefusion.processors.modules.age_modifier.types import AgeModifierDirection, AgeModifierInputs
from facefusion.processors.types import ProcessorOutputs
from facefusion.program_helper import find_argument_group
from facefusion.thread_helper import thread_semaphore
from facefusion.types import ApplyStateItem, Args, DownloadScope, Face, InferencePool, ModelOptions, ModelSet, ProcessMode, VisionFrame
from facefusion.vision import match_frame_color, read_static_image, read_static_video_frame
from facefusion.vision import match_frame_color, read_static_image, read_static_video_chunk, read_static_video_frame
@lru_cache()
def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
return\
{
'fran':
{
'__metadata__':
{
'vendor': 'ry-lu',
'license': 'mit',
'year': 2024
},
'hashes':
{
'age_modifier':
{
'url': resolve_download_url('models-3.6.0', 'fran.hash'),
'path': resolve_relative_path('../.assets/models/fran.hash')
}
},
'sources':
{
'age_modifier':
{
'url': resolve_download_url('models-3.6.0', 'fran.onnx'),
'path': resolve_relative_path('../.assets/models/fran.onnx')
}
},
'templates':
{
'target': 'ffhq_512',
},
'sizes':
{
'target': (1024, 1024),
},
'mean': [ 0.0, 0.0, 0.0 ],
'standard_deviation': [ 1.0, 1.0, 1.0 ]
},
'styleganex_age':
{
'__metadata__':
@@ -62,7 +98,9 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
{
'target': (256, 256),
'target_with_background': (384, 384)
}
},
'mean': [ 0.5, 0.5, 0.5 ],
'standard_deviation': [ 0.5, 0.5, 0.5 ]
}
}
@@ -87,9 +125,9 @@ def get_model_options() -> ModelOptions:
def register_args(program : ArgumentParser) -> None:
group_processors = find_argument_group(program, 'processors')
if group_processors:
group_processors.add_argument('--age-modifier-model', help = translator.get('help.model', __package__), default = config.get_str_value('processors', 'age_modifier_model', 'styleganex_age'), choices = age_modifier_choices.age_modifier_models)
group_processors.add_argument('--age-modifier-model', help = translator.get('help.model', __package__), default = config.get_str_value('processors', 'age_modifier_model', 'fran'), choices = age_modifier_choices.age_modifier_models)
group_processors.add_argument('--age-modifier-direction', help = translator.get('help.direction', __package__), type = int, default = config.get_int_value('processors', 'age_modifier_direction', '0'), choices = age_modifier_choices.age_modifier_direction_range, metavar = create_int_metavar(age_modifier_choices.age_modifier_direction_range))
facefusion.args_store.register_args([ 'age_modifier_model', 'age_modifier_direction' ], scopes = [ 'api', 'cli' ])
facefusion.jobs.job_store.register_step_keys([ 'age_modifier_model', 'age_modifier_direction' ])
def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
@@ -97,10 +135,18 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('age_modifier_direction', args.get('age_modifier_direction'))
def get_common_modules() -> List[ModuleType]:
return [ content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer ]
def pre_check() -> bool:
model_hash_set = get_model_options().get('hashes')
model_source_set = get_model_options().get('sources')
for common_module in get_common_modules():
if not common_module.pre_check():
return False
return conditional_download_hashes(model_hash_set) and conditional_download_sources(model_source_set)
@@ -111,22 +157,24 @@ def pre_process(mode : ProcessMode) -> bool:
if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
logger.error(translator.get('specify_image_or_video_output') + translator.get('exclamation_mark'), __name__)
return False
if mode == 'output' and not same_file_extension(state_manager.get_item('target_path'), state_manager.get_item('output_path')):
logger.error(translator.get('match_target_and_output_extension') + translator.get('exclamation_mark'), __name__)
return False
return True
def post_process() -> None:
read_static_image.cache_clear()
read_static_video_frame.cache_clear()
read_static_video_chunk.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
content_analyser.clear_inference_pool()
face_classifier.clear_inference_pool()
face_detector.clear_inference_pool()
face_landmarker.clear_inference_pool()
face_masker.clear_inference_pool()
face_recognizer.clear_inference_pool()
for common_module in get_common_modules():
common_module.clear_inference_pool()
def modify_age(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
@@ -134,41 +182,63 @@ def modify_age(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFra
model_sizes = get_model_options().get('sizes')
face_landmark_5 = target_face.landmark_set.get('5/68').copy()
crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, face_landmark_5, model_templates.get('target'), model_sizes.get('target'))
extend_face_landmark_5 = scale_face_landmark_5(face_landmark_5, 0.875)
extend_vision_frame, extend_affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, extend_face_landmark_5, model_templates.get('target_with_background'), model_sizes.get('target_with_background'))
extend_vision_frame_raw = extend_vision_frame.copy()
box_mask = create_box_mask(extend_vision_frame, state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
crop_masks =\
[
box_mask
]
if 'occlusion' in state_manager.get_item('face_mask_types'):
occlusion_mask = create_occlusion_mask(crop_vision_frame)
temp_matrix = merge_matrix([ extend_affine_matrix, cv2.invertAffineTransform(affine_matrix) ])
occlusion_mask = cv2.warpAffine(occlusion_mask, temp_matrix, model_sizes.get('target_with_background'))
crop_masks.append(occlusion_mask)
if state_manager.get_item('age_modifier_model') == 'fran':
box_mask = create_box_mask(crop_vision_frame, state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
crop_masks =\
[
box_mask
]
crop_vision_frame = prepare_vision_frame(crop_vision_frame)
extend_vision_frame = prepare_vision_frame(extend_vision_frame)
age_modifier_direction = numpy.array(numpy.interp(state_manager.get_item('age_modifier_direction'), [ -100, 100 ], [ 2.5, -2.5 ])).astype(numpy.float32)
extend_vision_frame = forward(crop_vision_frame, extend_vision_frame, age_modifier_direction)
extend_vision_frame = normalize_extend_frame(extend_vision_frame)
extend_vision_frame = match_frame_color(extend_vision_frame_raw, extend_vision_frame)
extend_affine_matrix *= (model_sizes.get('target')[0] * 4) / model_sizes.get('target_with_background')[0]
crop_mask = numpy.minimum.reduce(crop_masks).clip(0, 1)
crop_mask = cv2.resize(crop_mask, (model_sizes.get('target')[0] * 4, model_sizes.get('target')[1] * 4))
paste_vision_frame = paste_back(temp_vision_frame, extend_vision_frame, crop_mask, extend_affine_matrix)
return paste_vision_frame
if 'occlusion' in state_manager.get_item('face_mask_types'):
occlusion_mask = create_occlusion_mask(crop_vision_frame)
crop_masks.append(occlusion_mask)
crop_vision_frame = prepare_vision_frame(crop_vision_frame)
target_age = numpy.mean(target_face.age)
age_modifier_direction = numpy.array([ target_age, target_age + state_manager.get_item('age_modifier_direction') ], dtype = numpy.float32) / 100
age_modifier_direction = age_modifier_direction.clip(0, 1)
crop_vision_frame = forward(crop_vision_frame, crop_vision_frame, age_modifier_direction)
crop_vision_frame = normalize_vision_frame(crop_vision_frame)
crop_mask = numpy.minimum.reduce(crop_masks).clip(0, 1)
paste_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix)
return paste_vision_frame
if state_manager.get_item('age_modifier_model') == 'styleganex_age':
extend_face_landmark_5 = scale_face_landmark_5(face_landmark_5, 0.875)
extend_vision_frame, extend_affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, extend_face_landmark_5, model_templates.get('target_with_background'), model_sizes.get('target_with_background'))
extend_vision_frame_raw = extend_vision_frame.copy()
box_mask = create_box_mask(extend_vision_frame, state_manager.get_item('face_mask_blur'), (0, 0, 0, 0))
crop_masks =\
[
box_mask
]
if 'occlusion' in state_manager.get_item('face_mask_types'):
occlusion_mask = create_occlusion_mask(crop_vision_frame)
temp_matrix = merge_matrix([ extend_affine_matrix, cv2.invertAffineTransform(affine_matrix) ])
occlusion_mask = cv2.warpAffine(occlusion_mask, temp_matrix, model_sizes.get('target_with_background'))
crop_masks.append(occlusion_mask)
crop_vision_frame = prepare_vision_frame(crop_vision_frame)
extend_vision_frame = prepare_vision_frame(extend_vision_frame)
age_modifier_direction = numpy.array(numpy.interp(state_manager.get_item('age_modifier_direction'), [ -100, 100 ], [ 2.5, -2.5 ])).astype(numpy.float32)
extend_vision_frame = forward(crop_vision_frame, extend_vision_frame, age_modifier_direction)
extend_vision_frame = normalize_extend_frame(extend_vision_frame)
extend_vision_frame = match_frame_color(extend_vision_frame_raw, extend_vision_frame)
extend_affine_matrix *= (model_sizes.get('target')[0] * 4) / model_sizes.get('target_with_background')[0]
crop_mask = numpy.minimum.reduce(crop_masks).clip(0, 1)
crop_mask = cv2.resize(crop_mask, (model_sizes.get('target')[0] * 4, model_sizes.get('target')[1] * 4))
paste_vision_frame = paste_back(temp_vision_frame, extend_vision_frame, crop_mask, extend_affine_matrix)
return paste_vision_frame
return temp_vision_frame
def forward(crop_vision_frame : VisionFrame, extend_vision_frame : VisionFrame, age_modifier_direction : AgeModifierDirection) -> VisionFrame:
age_modifier = get_inference_pool().get('age_modifier')
age_modifier_inputs = {}
if is_macos() and has_execution_provider('coreml'):
age_modifier.set_providers([ facefusion.choices.execution_provider_set.get('cpu') ])
for age_modifier_input in age_modifier.get_inputs():
if age_modifier_input.name == 'target':
age_modifier_inputs[age_modifier_input.name] = crop_vision_frame
@@ -184,12 +254,24 @@ def forward(crop_vision_frame : VisionFrame, extend_vision_frame : VisionFrame,
def prepare_vision_frame(vision_frame : VisionFrame) -> VisionFrame:
model_mean = get_model_options().get('mean')
model_standard_deviation = get_model_options().get('standard_deviation')
vision_frame = vision_frame[:, :, ::-1] / 255.0
vision_frame = (vision_frame - 0.5) / 0.5
vision_frame = (vision_frame - model_mean) / model_standard_deviation
vision_frame = numpy.expand_dims(vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32)
return vision_frame
def normalize_vision_frame(vision_frame : VisionFrame) -> VisionFrame:
model_mean = get_model_options().get('mean')
model_standard_deviation = get_model_options().get('standard_deviation')
vision_frame = vision_frame.transpose(1, 2, 0)
vision_frame = vision_frame * model_standard_deviation + model_mean
vision_frame = vision_frame.clip(0, 1)
vision_frame = vision_frame[:, :, ::-1] * 255
return vision_frame
def normalize_extend_frame(extend_vision_frame : VisionFrame) -> VisionFrame:
model_sizes = get_model_options().get('sizes')
extend_vision_frame = numpy.clip(extend_vision_frame, -1, 1)
@@ -203,10 +285,13 @@ def normalize_extend_frame(extend_vision_frame : VisionFrame) -> VisionFrame:
def process_frame(inputs : AgeModifierInputs) -> ProcessorOutputs:
reference_vision_frame = inputs.get('reference_vision_frame')
target_vision_frame = inputs.get('target_vision_frame')
source_vision_frames = inputs.get('source_vision_frames')
target_vision_frames = inputs.get('target_vision_frames')
temp_vision_frame = inputs.get('temp_vision_frame')
temp_vision_mask = inputs.get('temp_vision_mask')
target_faces = select_faces(reference_vision_frame, target_vision_frame)
target_vision_frame = get_middle(target_vision_frames)
target_faces = select_faces(reference_vision_frame, source_vision_frames, target_vision_frames)
if target_faces:
for target_face in target_faces:
@@ -1,6 +1,6 @@
from facefusion.types import Locals
from facefusion.types import Locales
LOCALS : Locals =\
LOCALES : Locales =\
{
'en':
{
@@ -1,4 +1,4 @@
from typing import Any, Literal, TypeAlias, TypedDict
from typing import Any, List, Literal, TypeAlias, TypedDict
from numpy.typing import NDArray
@@ -7,11 +7,12 @@ from facefusion.types import Mask, VisionFrame
AgeModifierInputs = TypedDict('AgeModifierInputs',
{
'reference_vision_frame' : VisionFrame,
'target_vision_frame' : VisionFrame,
'source_vision_frames' : List[VisionFrame],
'target_vision_frames' : List[VisionFrame],
'temp_vision_frame' : VisionFrame,
'temp_vision_mask' : Mask
})
AgeModifierModel = Literal['styleganex_age']
AgeModifierModel = Literal['fran', 'styleganex_age']
AgeModifierDirection : TypeAlias = NDArray[Any]
@@ -1,8 +1,8 @@
from typing import List, Sequence
from typing import List, Sequence, get_args
from facefusion.common_helper import create_int_range
from facefusion.processors.modules.background_remover.types import BackgroundRemoverModel
background_remover_models : List[BackgroundRemoverModel] = [ 'ben_2', 'birefnet_general', 'birefnet_portrait', 'isnet_general', 'modnet', 'ormbg', 'rmbg_1.4', 'rmbg_2.0', 'silueta', 'u2net_cloth', 'u2net_general', 'u2net_human', 'u2netp' ]
background_remover_models : List[BackgroundRemoverModel] = list(get_args(BackgroundRemoverModel))
background_remover_color_range : Sequence[int] = create_int_range(0, 255, 1)
@@ -1,17 +1,19 @@
from argparse import ArgumentParser
from functools import lru_cache, partial
from types import ModuleType
from typing import List, Tuple
import cv2
import numpy
import facefusion.args_store
import facefusion.choices
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
from facefusion import config, content_analyser, inference_manager, logger, state_manager, translator, video_manager
from facefusion.common_helper import is_macos
from facefusion.common_helper import is_macos, is_windows
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.execution import has_execution_provider
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
from facefusion.normalizer import normalize_color
from facefusion.processors.modules.background_remover import choices as background_remover_choices
from facefusion.processors.modules.background_remover.types import BackgroundRemoverInputs
@@ -19,8 +21,8 @@ from facefusion.processors.types import ProcessorOutputs
from facefusion.program_helper import find_argument_group
from facefusion.sanitizer import sanitize_int_range
from facefusion.thread_helper import thread_semaphore
from facefusion.types import ApplyStateItem, Args, DownloadScope, ExecutionProvider, InferencePool, Mask, ModelOptions, ModelSet, ProcessMode, VisionFrame
from facefusion.vision import read_static_image, read_static_video_frame
from facefusion.types import ApplyStateItem, Args, DownloadScope, InferencePool, InferenceProvider, Mask, ModelOptions, ModelSet, ProcessMode, VisionFrame
from facefusion.vision import read_static_image, read_static_video_chunk, read_static_video_frame
@lru_cache()
@@ -51,6 +53,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/ben_2.onnx')
}
},
'type': 'ben',
'size': (1024, 1024),
'mean': [ 0.0, 0.0, 0.0 ],
'standard_deviation': [ 1.0, 1.0, 1.0 ]
@@ -79,6 +82,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/birefnet_general.onnx')
}
},
'type': 'birefnet',
'size': (1024, 1024),
'mean': [ 0.0, 0.0, 0.0 ],
'standard_deviation': [ 1.0, 1.0, 1.0 ]
@@ -107,10 +111,69 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/birefnet_portrait.onnx')
}
},
'type': 'birefnet',
'size': (1024, 1024),
'mean': [ 0.0, 0.0, 0.0 ],
'standard_deviation': [ 1.0, 1.0, 1.0 ]
},
'corridor_key_1024':
{
'__metadata__':
{
'vendor': 'nikopueringer',
'license': 'Non-Commercial',
'year': 2025
},
'hashes':
{
'background_remover':
{
'url': resolve_download_url('models-3.6.0', 'corridor_key_1024.hash'),
'path': resolve_relative_path('../.assets/models/corridor_key_1024.hash')
}
},
'sources':
{
'background_remover':
{
'url': resolve_download_url('models-3.6.0', 'corridor_key_1024.onnx'),
'path': resolve_relative_path('../.assets/models/corridor_key_1024.onnx')
}
},
'type': 'corridor_key',
'size': (1024, 1024),
'mean': [ 0.485, 0.456, 0.406 ],
'standard_deviation': [ 0.229, 0.224, 0.225 ]
},
'corridor_key_2048':
{
'__metadata__':
{
'vendor': 'nikopueringer',
'license': 'Non-Commercial',
'year': 2025
},
'hashes':
{
'background_remover':
{
'url': resolve_download_url('models-3.6.0', 'corridor_key_2048.hash'),
'path': resolve_relative_path('../.assets/models/corridor_key_2048.hash')
}
},
'sources':
{
'background_remover':
{
'url': resolve_download_url('models-3.6.0', 'corridor_key_2048.onnx'),
'path': resolve_relative_path('../.assets/models/corridor_key_2048.onnx')
}
},
'type': 'corridor_key',
'size': (2048, 2048),
'mean': [ 0.485, 0.456, 0.406 ],
'standard_deviation': [ 0.229, 0.224, 0.225 ]
},
'isnet_general':
{
'__metadata__':
@@ -135,6 +198,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/isnet_general.onnx')
}
},
'type': 'isnet',
'size': (1024, 1024),
'mean': [ 0.5, 0.5, 0.5 ],
'standard_deviation': [ 1.0, 1.0, 1.0 ]
@@ -163,6 +227,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/modnet.onnx')
}
},
'type': 'modnet',
'size': (512, 512),
'mean': [ 0.5, 0.5, 0.5 ],
'standard_deviation': [ 0.5, 0.5, 0.5 ]
@@ -191,6 +256,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/ormbg.onnx')
}
},
'type': 'ormbg',
'size': (1024, 1024),
'mean': [ 0.0, 0.0, 0.0 ],
'standard_deviation': [ 1.0, 1.0, 1.0 ]
@@ -219,6 +285,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/rmbg_1.4.onnx')
}
},
'type': 'rmbg',
'size': (1024, 1024),
'mean': [ 0.5, 0.5, 0.5 ],
'standard_deviation': [ 1.0, 1.0, 1.0 ]
@@ -247,6 +314,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/rmbg_2.0.onnx')
}
},
'type': 'rmbg',
'size': (1024, 1024),
'mean': [ 0.485, 0.456, 0.406 ],
'standard_deviation': [ 0.229, 0.224, 0.225 ]
@@ -275,6 +343,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/silueta.onnx')
}
},
'type': 'silueta',
'size': (320, 320),
'mean': [ 0.485, 0.456, 0.406 ],
'standard_deviation': [ 0.229, 0.224, 0.225 ]
@@ -303,6 +372,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/u2net_cloth.onnx')
}
},
'type': 'u2net_cloth',
'size': (768, 768),
'mean': [ 0.485, 0.456, 0.406 ],
'standard_deviation': [ 0.229, 0.224, 0.225 ]
@@ -331,6 +401,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/u2net_general.onnx')
}
},
'type': 'u2net',
'size': (320, 320),
'mean': [ 0.485, 0.456, 0.406 ],
'standard_deviation': [ 0.229, 0.224, 0.225 ]
@@ -359,6 +430,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/u2net_human.onnx')
}
},
'type': 'u2net',
'size': (320, 320),
'mean': [ 0.485, 0.456, 0.406 ],
'standard_deviation': [ 0.229, 0.224, 0.225 ]
@@ -387,6 +459,7 @@ def create_static_model_set(download_scope : DownloadScope) -> ModelSet:
'path': resolve_relative_path('../.assets/models/u2netp.onnx')
}
},
'type': 'u2netp',
'size': (320, 320),
'mean': [ 0.485, 0.456, 0.406 ],
'standard_deviation': [ 0.229, 0.224, 0.225 ]
@@ -406,10 +479,13 @@ def clear_inference_pool() -> None:
inference_manager.clear_inference_pool(__name__, model_names)
def resolve_execution_providers() -> List[ExecutionProvider]:
if is_macos() and has_execution_provider('coreml'):
return [ 'cpu' ]
return state_manager.get_item('execution_providers')
def resolve_inference_providers() -> List[InferenceProvider]:
model_type = get_model_options().get('type')
if is_macos() and has_execution_provider('coreml') or is_windows() and has_execution_provider('directml') and model_type == 'corridor_key':
return [ facefusion.choices.execution_provider_set.get('cpu') ]
return []
def get_model_options() -> ModelOptions:
@@ -420,20 +496,30 @@ def get_model_options() -> ModelOptions:
def register_args(program : ArgumentParser) -> None:
group_processors = find_argument_group(program, 'processors')
if group_processors:
group_processors.add_argument('--background-remover-model', help = translator.get('help.model', __package__), default = config.get_str_value('processors', 'background_remover_model', 'rmbg_2.0'), choices = background_remover_choices.background_remover_models)
group_processors.add_argument('--background-remover-color', help = translator.get('help.color', __package__), type = partial(sanitize_int_range, int_range = background_remover_choices.background_remover_color_range), default = config.get_int_list('processors', 'background_remover_color', '0 0 0 0'), nargs ='+')
facefusion.args_store.register_args([ 'background_remover_model', 'background_remover_color' ], scopes = [ 'api', 'cli' ])
group_processors.add_argument('--background-remover-model', help = translator.get('help.model', __package__), default = config.get_str_value('processors', 'background_remover_model', 'modnet'), choices = background_remover_choices.background_remover_models)
group_processors.add_argument('--background-remover-fill-color', help = translator.get('help.fill_color', __package__), type = partial(sanitize_int_range, int_range = background_remover_choices.background_remover_color_range), default = config.get_int_list('processors', 'background_remover_fill_color', '0 0 0 0'), nargs = '+')
group_processors.add_argument('--background-remover-despill-color', help = translator.get('help.despill_color', __package__), type = partial(sanitize_int_range, int_range = background_remover_choices.background_remover_color_range), default = config.get_int_list('processors', 'background_remover_despill_color', '0 0 0 0'), nargs = '+')
facefusion.jobs.job_store.register_step_keys([ 'background_remover_model', 'background_remover_fill_color', 'background_remover_despill_color' ])
def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('background_remover_model', args.get('background_remover_model'))
apply_state_item('background_remover_color', normalize_color(args.get('background_remover_color')))
apply_state_item('background_remover_fill_color', normalize_color(args.get('background_remover_fill_color')))
apply_state_item('background_remover_despill_color', normalize_color(args.get('background_remover_despill_color')))
def get_common_modules() -> List[ModuleType]:
return [ content_analyser ]
def pre_check() -> bool:
model_hash_set = get_model_options().get('hashes')
model_source_set = get_model_options().get('sources')
for common_module in get_common_modules():
if not common_module.pre_check():
return False
return conditional_download_hashes(model_hash_set) and conditional_download_sources(model_source_set)
@@ -444,30 +530,47 @@ def pre_process(mode : ProcessMode) -> bool:
if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
logger.error(translator.get('specify_image_or_video_output') + translator.get('exclamation_mark'), __name__)
return False
if mode == 'output' and not same_file_extension(state_manager.get_item('target_path'), state_manager.get_item('output_path')):
logger.error(translator.get('match_target_and_output_extension') + translator.get('exclamation_mark'), __name__)
return False
return True
def post_process() -> None:
read_static_image.cache_clear()
read_static_video_frame.cache_clear()
read_static_video_chunk.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
content_analyser.clear_inference_pool()
for common_module in get_common_modules():
common_module.clear_inference_pool()
def remove_background(temp_vision_frame : VisionFrame) -> Tuple[VisionFrame, Mask]:
temp_vision_mask = forward(prepare_temp_frame(temp_vision_frame))
temp_vision_mask = normalize_vision_mask(temp_vision_mask)
temp_vision_mask = cv2.resize(temp_vision_mask, temp_vision_frame.shape[:2][::-1])
temp_vision_frame = apply_background_color(temp_vision_frame, temp_vision_mask)
return temp_vision_frame, temp_vision_mask
model_type = get_model_options().get('type')
if model_type == 'corridor_key':
remove_vision_mask, remove_vision_frame = forward_corridor_key(prepare_temp_frame(temp_vision_frame))
remove_vision_frame = numpy.squeeze(remove_vision_frame).transpose(1, 2, 0)
remove_vision_frame = numpy.clip(remove_vision_frame * 255, 0, 255).astype(numpy.uint8)
temp_vision_frame = cv2.resize(remove_vision_frame[:, :, ::-1], temp_vision_frame.shape[:2][::-1])
else:
remove_vision_mask = forward(prepare_temp_frame(temp_vision_frame))
remove_vision_mask = normalize_vision_mask(remove_vision_mask)
remove_vision_mask = cv2.resize(remove_vision_mask, temp_vision_frame.shape[:2][::-1])
temp_vision_frame = apply_despill_color(temp_vision_frame)
temp_vision_frame = apply_fill_color(temp_vision_frame, remove_vision_mask)
return temp_vision_frame, remove_vision_mask
def forward(temp_vision_frame : VisionFrame) -> VisionFrame:
background_remover = get_inference_pool().get('background_remover')
model_name = state_manager.get_item('background_remover_model')
model_type = get_model_options().get('type')
with thread_semaphore():
remove_vision_frame = background_remover.run(None,
@@ -475,20 +578,42 @@ def forward(temp_vision_frame : VisionFrame) -> VisionFrame:
'input': temp_vision_frame
})[0]
if model_name == 'u2net_cloth':
if model_type == 'u2net_cloth':
remove_vision_frame = numpy.argmax(remove_vision_frame, axis = 1)
return remove_vision_frame
def forward_corridor_key(temp_vision_frame : VisionFrame) -> Tuple[Mask, VisionFrame]:
background_remover = get_inference_pool().get('background_remover')
with thread_semaphore():
remove_vision_mask, remove_vision_frame = background_remover.run(None,
{
'input': temp_vision_frame
})
return remove_vision_mask, remove_vision_frame
def prepare_temp_frame(temp_vision_frame : VisionFrame) -> VisionFrame:
model_type = get_model_options().get('type')
model_size = get_model_options().get('size')
model_mean = get_model_options().get('mean')
model_standard_deviation = get_model_options().get('standard_deviation')
if model_type == 'corridor_key':
coarse_color = temp_vision_frame[:, :, ::-1].astype(numpy.float32) / 255.0
coarse_bias = coarse_color[:, :, 1] - numpy.maximum(coarse_color[:, :, 0], coarse_color[:, :, 2])
coarse_vision_mask = cv2.resize(1.0 - numpy.clip(coarse_bias * 2.0, 0, 1), model_size)[:, :, numpy.newaxis]
temp_vision_frame = cv2.resize(temp_vision_frame, model_size)
temp_vision_frame = temp_vision_frame[:, :, ::-1] / 255.0
temp_vision_frame = (temp_vision_frame - model_mean) / model_standard_deviation
if model_type == 'corridor_key':
temp_vision_frame = numpy.concatenate([ temp_vision_frame, coarse_vision_mask ], axis = 2)
temp_vision_frame = temp_vision_frame.transpose(2, 0, 1)
temp_vision_frame = numpy.expand_dims(temp_vision_frame, axis = 0).astype(numpy.float32)
return temp_vision_frame
@@ -500,16 +625,32 @@ def normalize_vision_mask(temp_vision_mask : Mask) -> Mask:
return temp_vision_mask
def apply_background_color(temp_vision_frame : VisionFrame, temp_vision_mask : Mask) -> VisionFrame:
background_remover_color = state_manager.get_item('background_remover_color')
def apply_fill_color(temp_vision_frame : VisionFrame, temp_vision_mask : Mask) -> VisionFrame:
background_remover_fill_color = state_manager.get_item('background_remover_fill_color')
temp_vision_mask = temp_vision_mask.astype(numpy.float32) / 255
temp_vision_mask = numpy.expand_dims(temp_vision_mask, axis = 2)
temp_vision_mask = (1 - temp_vision_mask) * background_remover_color[-1] / 255
color_frame = numpy.zeros_like(temp_vision_frame)
color_frame[:, :, 0] = background_remover_color[2]
color_frame[:, :, 1] = background_remover_color[1]
color_frame[:, :, 2] = background_remover_color[0]
temp_vision_frame = temp_vision_frame * (1 - temp_vision_mask) + color_frame * temp_vision_mask
temp_vision_mask = (1 - temp_vision_mask) * background_remover_fill_color[-1] / 255
fill_vision_frame = numpy.zeros_like(temp_vision_frame)
fill_vision_frame[:, :, 0] = background_remover_fill_color[2]
fill_vision_frame[:, :, 1] = background_remover_fill_color[1]
fill_vision_frame[:, :, 2] = background_remover_fill_color[0]
temp_vision_frame = temp_vision_frame * (1 - temp_vision_mask) + fill_vision_frame * temp_vision_mask
temp_vision_frame = temp_vision_frame.astype(numpy.uint8)
return temp_vision_frame
def apply_despill_color(temp_vision_frame : VisionFrame) -> VisionFrame:
background_remover_despill_color = state_manager.get_item('background_remover_despill_color')
temp_vision_frame = temp_vision_frame.astype(numpy.float32)
color_alpha = background_remover_despill_color[3] / 255.0
despill_vision_frame = numpy.zeros_like(temp_vision_frame)
despill_vision_frame[:, :, 0] = background_remover_despill_color[2]
despill_vision_frame[:, :, 1] = background_remover_despill_color[1]
despill_vision_frame[:, :, 2] = background_remover_despill_color[0]
color_weight = despill_vision_frame / numpy.maximum(numpy.max(background_remover_despill_color[:3]), 1)
color_limit = numpy.roll(temp_vision_frame, 1, 2) + numpy.roll(temp_vision_frame, -1, 2)
limit_vision_frame = numpy.minimum(temp_vision_frame, color_limit * 0.5)
temp_vision_frame = temp_vision_frame + (limit_vision_frame - temp_vision_frame) * color_alpha * color_weight
temp_vision_frame = temp_vision_frame.astype(numpy.uint8)
return temp_vision_frame
@@ -0,0 +1,26 @@
from facefusion.types import Locales
LOCALES : Locales =\
{
'en':
{
'help':
{
'model': 'choose the model responsible for removing the background',
'fill_color': 'apply red, green, blue and alpha values to the background',
'despill_color': 'remove red, green, blue and alpha values from the foreground'
},
'uis':
{
'model_dropdown': 'BACKGROUND REMOVER MODEL',
'fill_color_red_number': 'FILL COLOR RED',
'fill_color_green_number': 'FILL COLOR GREEN',
'fill_color_blue_number': 'FILL COLOR BLUE',
'fill_color_alpha_number': 'FILL COLOR ALPHA',
'despill_color_red_number': 'DESPILL COLOR RED',
'despill_color_green_number': 'DESPILL COLOR GREEN',
'despill_color_blue_number': 'DESPILL COLOR BLUE',
'despill_color_alpha_number': 'DESPILL COLOR ALPHA'
}
}
}
@@ -1,21 +0,0 @@
from facefusion.types import Locals
LOCALS : Locals =\
{
'en':
{
'help':
{
'model': 'choose the model responsible for removing the background',
'color': 'apply red, green blue and alpha values to the background'
},
'uis':
{
'model_dropdown': 'BACKGROUND REMOVER MODEL',
'color_red_number': 'BACKGROUND COLOR RED',
'color_green_number': 'BACKGROUND COLOR GREEN',
'color_blue_number': 'BACKGROUND COLOR BLUE',
'color_alpha_number': 'BACKGROUND COLOR ALPHA'
}
}
}
@@ -1,12 +1,12 @@
from typing import Literal, TypedDict
from typing import List, Literal, TypedDict
from facefusion.types import Mask, VisionFrame
BackgroundRemoverInputs = TypedDict('BackgroundRemoverInputs',
{
'target_vision_frame' : VisionFrame,
'target_vision_frames' : List[VisionFrame],
'temp_vision_frame' : VisionFrame,
'temp_vision_mask' : Mask
})
BackgroundRemoverModel = Literal['ben_2', 'birefnet_general', 'birefnet_portrait', 'isnet_general', 'modnet', 'ormbg', 'rmbg_1.4', 'rmbg_2.0', 'silueta', 'u2net_cloth', 'u2net_general', 'u2net_human', 'u2netp']
BackgroundRemoverModel = Literal['ben_2', 'birefnet_general', 'birefnet_portrait', 'corridor_key_1024', 'corridor_key_2048', 'isnet_general', 'modnet', 'ormbg', 'rmbg_1.4', 'rmbg_2.0', 'silueta', 'u2net_cloth', 'u2net_general', 'u2net_human', 'u2netp']
@@ -1,28 +1,29 @@
from argparse import ArgumentParser
from functools import lru_cache
from typing import Tuple
from types import ModuleType
from typing import List, Tuple
import cv2
import numpy
from cv2.typing import Size
import facefusion.args_store
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, state_manager, translator, video_manager
from facefusion.common_helper import create_int_metavar
from facefusion.common_helper import create_int_metavar, get_middle
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url_by_provider
from facefusion.face_analyser import scale_face
from facefusion.face_creator import scale_face
from facefusion.face_helper import paste_back, warp_face_by_face_landmark_5
from facefusion.face_masker import create_area_mask, create_box_mask, create_occlusion_mask, create_region_mask
from facefusion.face_selector import select_faces
from facefusion.filesystem import get_file_name, in_directory, is_image, is_video, resolve_file_paths, resolve_relative_path
from facefusion.filesystem import get_file_name, in_directory, is_image, is_video, resolve_file_paths, resolve_relative_path, same_file_extension
from facefusion.processors.modules.deep_swapper import choices as deep_swapper_choices
from facefusion.processors.modules.deep_swapper.types import DeepSwapperInputs, DeepSwapperMorph
from facefusion.processors.types import ProcessorOutputs
from facefusion.program_helper import find_argument_group
from facefusion.thread_helper import thread_semaphore
from facefusion.types import ApplyStateItem, Args, DownloadScope, Face, InferencePool, Mask, ModelOptions, ModelSet, ProcessMode, VisionFrame
from facefusion.vision import conditional_match_frame_color, read_static_image, read_static_video_frame
from facefusion.vision import conditional_match_frame_color, read_static_image, read_static_video_chunk, read_static_video_frame
@lru_cache()
@@ -278,7 +279,7 @@ def register_args(program : ArgumentParser) -> None:
if group_processors:
group_processors.add_argument('--deep-swapper-model', help = translator.get('help.model', __package__), default = config.get_str_value('processors', 'deep_swapper_model', 'iperov/elon_musk_224'), choices = deep_swapper_choices.deep_swapper_models)
group_processors.add_argument('--deep-swapper-morph', help = translator.get('help.morph', __package__), type = int, default = config.get_int_value('processors', 'deep_swapper_morph', '100'), choices = deep_swapper_choices.deep_swapper_morph_range, metavar = create_int_metavar(deep_swapper_choices.deep_swapper_morph_range))
facefusion.args_store.register_args([ 'deep_swapper_model', 'deep_swapper_morph' ], scopes = [ 'api', 'cli' ])
facefusion.jobs.job_store.register_step_keys([ 'deep_swapper_model', 'deep_swapper_morph' ])
def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
@@ -286,10 +287,18 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('deep_swapper_morph', args.get('deep_swapper_morph'))
def get_common_modules() -> List[ModuleType]:
return [ content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer ]
def pre_check() -> bool:
model_hash_set = get_model_options().get('hashes')
model_source_set = get_model_options().get('sources')
for common_module in get_common_modules():
if not common_module.pre_check():
return False
if model_hash_set and model_source_set:
return conditional_download_hashes(model_hash_set) and conditional_download_sources(model_source_set)
return True
@@ -302,22 +311,24 @@ def pre_process(mode : ProcessMode) -> bool:
if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
logger.error(translator.get('specify_image_or_video_output') + translator.get('exclamation_mark'), __name__)
return False
if mode == 'output' and not same_file_extension(state_manager.get_item('target_path'), state_manager.get_item('output_path')):
logger.error(translator.get('match_target_and_output_extension') + translator.get('exclamation_mark'), __name__)
return False
return True
def post_process() -> None:
read_static_image.cache_clear()
read_static_video_frame.cache_clear()
read_static_video_chunk.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
content_analyser.clear_inference_pool()
face_classifier.clear_inference_pool()
face_detector.clear_inference_pool()
face_landmarker.clear_inference_pool()
face_masker.clear_inference_pool()
face_recognizer.clear_inference_pool()
for common_module in get_common_modules():
common_module.clear_inference_pool()
def swap_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
@@ -408,10 +419,13 @@ def prepare_crop_mask(crop_source_mask : Mask, crop_target_mask : Mask) -> Mask:
def process_frame(inputs : DeepSwapperInputs) -> ProcessorOutputs:
reference_vision_frame = inputs.get('reference_vision_frame')
target_vision_frame = inputs.get('target_vision_frame')
source_vision_frames = inputs.get('source_vision_frames')
target_vision_frames = inputs.get('target_vision_frames')
temp_vision_frame = inputs.get('temp_vision_frame')
temp_vision_mask = inputs.get('temp_vision_mask')
target_faces = select_faces(reference_vision_frame, target_vision_frame)
target_vision_frame = get_middle(target_vision_frames)
target_faces = select_faces(reference_vision_frame, source_vision_frames, target_vision_frames)
if target_faces:
for target_face in target_faces:
@@ -1,6 +1,6 @@
from facefusion.types import Locals
from facefusion.types import Locales
LOCALS : Locals =\
LOCALES : Locales =\
{
'en':
{
@@ -1,4 +1,4 @@
from typing import Any, TypeAlias, TypedDict
from typing import Any, List, TypeAlias, TypedDict
from numpy.typing import NDArray
@@ -7,7 +7,8 @@ from facefusion.types import Mask, VisionFrame
DeepSwapperInputs = TypedDict('DeepSwapperInputs',
{
'reference_vision_frame' : VisionFrame,
'target_vision_frame' : VisionFrame,
'source_vision_frames' : List[VisionFrame],
'target_vision_frames' : List[VisionFrame],
'temp_vision_frame' : VisionFrame,
'temp_vision_mask' : Mask
})
@@ -1,10 +1,10 @@
from typing import List, Sequence
from typing import List, Sequence, get_args
from facefusion.common_helper import create_int_range
from facefusion.processors.modules.expression_restorer.types import ExpressionRestorerArea, ExpressionRestorerModel
expression_restorer_models : List[ExpressionRestorerModel] = [ 'live_portrait' ]
expression_restorer_models : List[ExpressionRestorerModel] = list(get_args(ExpressionRestorerModel))
expression_restorer_areas : List[ExpressionRestorerArea] = [ 'upper-face', 'lower-face' ]
expression_restorer_areas : List[ExpressionRestorerArea] = list(get_args(ExpressionRestorerArea))
expression_restorer_factor_range : Sequence[int] = create_int_range(0, 100, 1)
@@ -1,20 +1,21 @@
from argparse import ArgumentParser
from functools import lru_cache
from typing import Tuple
from types import ModuleType
from typing import List, Tuple
import cv2
import numpy
import facefusion.args_store
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, state_manager, translator, video_manager
from facefusion.common_helper import create_int_metavar
from facefusion.common_helper import create_int_metavar, get_middle
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.face_analyser import scale_face
from facefusion.face_creator import scale_face
from facefusion.face_helper import paste_back, warp_face_by_face_landmark_5
from facefusion.face_masker import create_box_mask, create_occlusion_mask
from facefusion.face_selector import select_faces
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
from facefusion.processors.live_portrait import create_rotation, limit_expression
from facefusion.processors.modules.expression_restorer import choices as expression_restorer_choices
from facefusion.processors.modules.expression_restorer.types import ExpressionRestorerInputs
@@ -22,7 +23,7 @@ from facefusion.processors.types import LivePortraitExpression, LivePortraitFeat
from facefusion.program_helper import find_argument_group
from facefusion.thread_helper import conditional_thread_semaphore, thread_semaphore
from facefusion.types import ApplyStateItem, Args, DownloadScope, Face, InferencePool, ModelOptions, ModelSet, ProcessMode, VisionFrame
from facefusion.vision import read_static_image, read_static_video_frame
from facefusion.vision import read_static_image, read_static_video_chunk, read_static_video_frame
@lru_cache()
@@ -101,8 +102,8 @@ def register_args(program : ArgumentParser) -> None:
if group_processors:
group_processors.add_argument('--expression-restorer-model', help = translator.get('help.model', __package__), default = config.get_str_value('processors', 'expression_restorer_model', 'live_portrait'), choices = expression_restorer_choices.expression_restorer_models)
group_processors.add_argument('--expression-restorer-factor', help = translator.get('help.factor', __package__), type = int, default = config.get_int_value('processors', 'expression_restorer_factor', '80'), choices = expression_restorer_choices.expression_restorer_factor_range, metavar = create_int_metavar(expression_restorer_choices.expression_restorer_factor_range))
group_processors.add_argument('--expression-restorer-areas', help = translator.get('help.areas', __package__).format(choices = ', '.join(expression_restorer_choices.expression_restorer_areas)), default = config.get_str_list('processors', 'expression_restorer_areas', ' '.join(expression_restorer_choices.expression_restorer_areas)), choices = expression_restorer_choices.expression_restorer_areas, nargs ='+', metavar ='EXPRESSION_RESTORER_AREAS')
facefusion.args_store.register_args([ 'expression_restorer_model', 'expression_restorer_factor', 'expression_restorer_areas' ], scopes = [ 'api', 'cli' ])
group_processors.add_argument('--expression-restorer-areas', help = translator.get('help.areas', __package__).format(choices = ', '.join(expression_restorer_choices.expression_restorer_areas)), default = config.get_str_list('processors', 'expression_restorer_areas', ' '.join(expression_restorer_choices.expression_restorer_areas)), choices = expression_restorer_choices.expression_restorer_areas, nargs = '+', metavar = 'EXPRESSION_RESTORER_AREAS')
facefusion.jobs.job_store.register_step_keys([ 'expression_restorer_model', 'expression_restorer_factor', 'expression_restorer_areas' ])
def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
@@ -111,10 +112,18 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('expression_restorer_areas', args.get('expression_restorer_areas'))
def get_common_modules() -> List[ModuleType]:
return [ content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer ]
def pre_check() -> bool:
model_hash_set = get_model_options().get('hashes')
model_source_set = get_model_options().get('sources')
for common_module in get_common_modules():
if not common_module.pre_check():
return False
return conditional_download_hashes(model_hash_set) and conditional_download_sources(model_source_set)
@@ -128,22 +137,24 @@ def pre_process(mode : ProcessMode) -> bool:
if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
logger.error(translator.get('specify_image_or_video_output') + translator.get('exclamation_mark'), __name__)
return False
if mode == 'output' and not same_file_extension(state_manager.get_item('target_path'), state_manager.get_item('output_path')):
logger.error(translator.get('match_target_and_output_extension') + translator.get('exclamation_mark'), __name__)
return False
return True
def post_process() -> None:
read_static_image.cache_clear()
read_static_video_frame.cache_clear()
read_static_video_chunk.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
content_analyser.clear_inference_pool()
face_classifier.clear_inference_pool()
face_detector.clear_inference_pool()
face_landmarker.clear_inference_pool()
face_masker.clear_inference_pool()
face_recognizer.clear_inference_pool()
for common_module in get_common_modules():
common_module.clear_inference_pool()
def restore_expression(target_face : Face, target_vision_frame : VisionFrame, temp_vision_frame : VisionFrame) -> VisionFrame:
@@ -189,12 +200,12 @@ def restrict_expression_areas(temp_expression : LivePortraitExpression, target_e
expression_restorer_areas = state_manager.get_item('expression_restorer_areas')
if 'upper-face' not in expression_restorer_areas:
target_expression[:, [1, 2, 6, 10, 11, 12, 13, 15, 16]] = temp_expression[:, [1, 2, 6, 10, 11, 12, 13, 15, 16]]
target_expression[:, [ 1, 2, 6, 10, 11, 12, 13, 15, 16 ]] = temp_expression[:, [ 1, 2, 6, 10, 11, 12, 13, 15, 16 ]]
if 'lower-face' not in expression_restorer_areas:
target_expression[:, [3, 7, 14, 17, 18, 19, 20]] = temp_expression[:, [3, 7, 14, 17, 18, 19, 20]]
target_expression[:, [ 3, 7, 14, 17, 18, 19, 20 ]] = temp_expression[:, [ 3, 7, 14, 17, 18, 19, 20 ]]
target_expression[:, [0, 4, 5, 8, 9]] = temp_expression[:, [0, 4, 5, 8, 9]]
target_expression[:, [ 0, 4, 5, 8, 9 ]] = temp_expression[:, [ 0, 4, 5, 8, 9 ]]
return target_expression
@@ -254,10 +265,13 @@ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
def process_frame(inputs : ExpressionRestorerInputs) -> ProcessorOutputs:
reference_vision_frame = inputs.get('reference_vision_frame')
target_vision_frame = inputs.get('target_vision_frame')
source_vision_frames = inputs.get('source_vision_frames')
target_vision_frames = inputs.get('target_vision_frames')
temp_vision_frame = inputs.get('temp_vision_frame')
temp_vision_mask = inputs.get('temp_vision_mask')
target_faces = select_faces(reference_vision_frame, target_vision_frame)
target_vision_frame = get_middle(target_vision_frames)
target_faces = select_faces(reference_vision_frame, source_vision_frames, target_vision_frames)
if target_faces:
for target_face in target_faces:
@@ -1,6 +1,6 @@
from facefusion.types import Locals
from facefusion.types import Locales
LOCALS : Locals =\
LOCALES : Locales =\
{
'en':
{
@@ -6,7 +6,7 @@ ExpressionRestorerInputs = TypedDict('ExpressionRestorerInputs',
{
'reference_vision_frame' : VisionFrame,
'source_vision_frames' : List[VisionFrame],
'target_vision_frame' : VisionFrame,
'target_vision_frames' : List[VisionFrame],
'temp_vision_frame' : VisionFrame,
'temp_vision_mask' : Mask
})
@@ -1,5 +1,5 @@
from typing import List
from typing import List, get_args
from facefusion.processors.modules.face_debugger.types import FaceDebuggerItem
face_debugger_items : List[FaceDebuggerItem] = [ 'bounding-box', 'face-landmark-5', 'face-landmark-5/68', 'face-landmark-68', 'face-landmark-68/5', 'face-mask' ]
face_debugger_items : List[FaceDebuggerItem] = list(get_args(FaceDebuggerItem))
@@ -1,22 +1,25 @@
from argparse import ArgumentParser
from types import ModuleType
from typing import List
import cv2
import numpy
import facefusion.args_store
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, logger, state_manager, translator, video_manager
from facefusion.face_analyser import scale_face
from facefusion.common_helper import get_middle
from facefusion.face_creator import scale_face
from facefusion.face_helper import warp_face_by_face_landmark_5
from facefusion.face_masker import create_area_mask, create_box_mask, create_occlusion_mask, create_region_mask
from facefusion.face_selector import select_faces
from facefusion.filesystem import in_directory, is_image, is_video
from facefusion.filesystem import in_directory, is_image, is_video, same_file_extension
from facefusion.processors.modules.face_debugger import choices as face_debugger_choices
from facefusion.processors.modules.face_debugger.types import FaceDebuggerInputs
from facefusion.processors.types import ProcessorOutputs
from facefusion.program_helper import find_argument_group
from facefusion.types import ApplyStateItem, Args, Face, InferencePool, ProcessMode, VisionFrame
from facefusion.vision import read_static_image, read_static_video_frame
from facefusion.vision import read_static_image, read_static_video_chunk, read_static_video_frame
def get_inference_pool() -> InferencePool:
@@ -31,14 +34,21 @@ def register_args(program : ArgumentParser) -> None:
group_processors = find_argument_group(program, 'processors')
if group_processors:
group_processors.add_argument('--face-debugger-items', help = translator.get('help.items', __package__).format(choices = ', '.join(face_debugger_choices.face_debugger_items)), default = config.get_str_list('processors', 'face_debugger_items', 'face-landmark-5/68 face-mask'), choices = face_debugger_choices.face_debugger_items, nargs = '+', metavar = 'FACE_DEBUGGER_ITEMS')
facefusion.args_store.register_args([ 'face_debugger_items' ], scopes = [ 'api', 'cli' ])
facefusion.jobs.job_store.register_step_keys([ 'face_debugger_items' ])
def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('face_debugger_items', args.get('face_debugger_items'))
def get_common_modules() -> List[ModuleType]:
return [ content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer ]
def pre_check() -> bool:
for common_module in get_common_modules():
if not common_module.pre_check():
return False
return True
@@ -49,20 +59,21 @@ def pre_process(mode : ProcessMode) -> bool:
if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
logger.error(translator.get('specify_image_or_video_output') + translator.get('exclamation_mark'), __name__)
return False
if mode == 'output' and not same_file_extension(state_manager.get_item('target_path'), state_manager.get_item('output_path')):
logger.error(translator.get('match_target_and_output_extension') + translator.get('exclamation_mark'), __name__)
return False
return True
def post_process() -> None:
read_static_image.cache_clear()
read_static_video_frame.cache_clear()
read_static_video_chunk.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
content_analyser.clear_inference_pool()
face_classifier.clear_inference_pool()
face_detector.clear_inference_pool()
face_landmarker.clear_inference_pool()
face_masker.clear_inference_pool()
face_recognizer.clear_inference_pool()
for common_module in get_common_modules():
common_module.clear_inference_pool()
def debug_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
@@ -91,21 +102,22 @@ def debug_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFra
def draw_bounding_box(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
temp_vision_frame = numpy.ascontiguousarray(temp_vision_frame)
box_color = 0, 0, 255
border_color = 100, 100, 255
bounding_box = target_face.bounding_box.astype(numpy.int32)
x1, y1, x2, y2 = bounding_box
box_color = 0, 0, 255
border_scale = calculate_scale(temp_vision_frame)
border_color = 100, 100, 255
cv2.rectangle(temp_vision_frame, (x1, y1), (x2, y2), box_color, 2)
cv2.rectangle(temp_vision_frame, (x1, y1), (x2, y2), box_color, border_scale)
if target_face.angle == 0:
cv2.line(temp_vision_frame, (x1, y1), (x2, y1), border_color, 3)
cv2.line(temp_vision_frame, (x1, y1), (x2, y1), border_color, border_scale + 1)
if target_face.angle == 180:
cv2.line(temp_vision_frame, (x1, y2), (x2, y2), border_color, 3)
cv2.line(temp_vision_frame, (x1, y2), (x2, y2), border_color, border_scale + 1)
if target_face.angle == 90:
cv2.line(temp_vision_frame, (x2, y1), (x2, y2), border_color, 3)
cv2.line(temp_vision_frame, (x2, y1), (x2, y2), border_color, border_scale + 1)
if target_face.angle == 270:
cv2.line(temp_vision_frame, (x1, y1), (x1, y2), border_color, 3)
cv2.line(temp_vision_frame, (x1, y1), (x1, y2), border_color, border_scale + 1)
return temp_vision_frame
@@ -119,11 +131,15 @@ def draw_face_mask(target_face : Face, temp_vision_frame : VisionFrame) -> Visio
crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, face_landmark_5_68, 'arcface_128', (512, 512))
inverse_matrix = cv2.invertAffineTransform(affine_matrix)
temp_size = temp_vision_frame.shape[:2][::-1]
mask_scale = calculate_scale(temp_vision_frame)
mask_color = 0, 255, 0
if numpy.array_equal(face_landmark_5, face_landmark_5_68):
mask_color = 255, 255, 0
if target_face.origin == 'refill':
mask_color = 0, 165, 255
if 'box' in state_manager.get_item('face_mask_types'):
box_mask = create_box_mask(crop_vision_frame, 0, state_manager.get_item('face_mask_padding'))
crop_masks.append(box_mask)
@@ -146,7 +162,7 @@ def draw_face_mask(target_face : Face, temp_vision_frame : VisionFrame) -> Visio
inverse_vision_frame = cv2.warpAffine(crop_mask, inverse_matrix, temp_size)
inverse_vision_frame = cv2.threshold(inverse_vision_frame, 100, 255, cv2.THRESH_BINARY)[1]
inverse_contours, _ = cv2.findContours(inverse_vision_frame, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
cv2.drawContours(temp_vision_frame, inverse_contours, -1, mask_color, 2)
cv2.drawContours(temp_vision_frame, inverse_contours, -1, mask_color, mask_scale)
return temp_vision_frame
@@ -154,13 +170,17 @@ def draw_face_mask(target_face : Face, temp_vision_frame : VisionFrame) -> Visio
def draw_face_landmark_5(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
temp_vision_frame = numpy.ascontiguousarray(temp_vision_frame)
face_landmark_5 = target_face.landmark_set.get('5')
point_scale = calculate_scale(temp_vision_frame)
point_color = 0, 0, 255
if target_face.origin == 'refill':
point_color = 0, 165, 255
if numpy.any(face_landmark_5):
face_landmark_5 = face_landmark_5.astype(numpy.int32)
for point in face_landmark_5:
cv2.circle(temp_vision_frame, tuple(point), 3, point_color, -1)
cv2.circle(temp_vision_frame, tuple(point), point_scale, point_color, -1)
return temp_vision_frame
@@ -169,16 +189,20 @@ def draw_face_landmark_5_68(target_face : Face, temp_vision_frame : VisionFrame)
temp_vision_frame = numpy.ascontiguousarray(temp_vision_frame)
face_landmark_5 = target_face.landmark_set.get('5')
face_landmark_5_68 = target_face.landmark_set.get('5/68')
point_scale = calculate_scale(temp_vision_frame)
point_color = 0, 255, 0
if numpy.array_equal(face_landmark_5, face_landmark_5_68):
point_color = 255, 255, 0
if target_face.origin == 'refill':
point_color = 0, 165, 255
if numpy.any(face_landmark_5_68):
face_landmark_5_68 = face_landmark_5_68.astype(numpy.int32)
for point in face_landmark_5_68:
cv2.circle(temp_vision_frame, tuple(point), 3, point_color, -1)
cv2.circle(temp_vision_frame, tuple(point), point_scale, point_color, -1)
return temp_vision_frame
@@ -187,16 +211,20 @@ def draw_face_landmark_68(target_face : Face, temp_vision_frame : VisionFrame) -
temp_vision_frame = numpy.ascontiguousarray(temp_vision_frame)
face_landmark_68 = target_face.landmark_set.get('68')
face_landmark_68_5 = target_face.landmark_set.get('68/5')
point_scale = calculate_scale(temp_vision_frame)
point_color = 0, 255, 0
if numpy.array_equal(face_landmark_68, face_landmark_68_5):
point_color = 255, 255, 0
if target_face.origin == 'refill':
point_color = 0, 165, 255
if numpy.any(face_landmark_68):
face_landmark_68 = face_landmark_68.astype(numpy.int32)
for point in face_landmark_68:
cv2.circle(temp_vision_frame, tuple(point), 3, point_color, -1)
cv2.circle(temp_vision_frame, tuple(point), point_scale, point_color, -1)
return temp_vision_frame
@@ -204,23 +232,36 @@ def draw_face_landmark_68(target_face : Face, temp_vision_frame : VisionFrame) -
def draw_face_landmark_68_5(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
temp_vision_frame = numpy.ascontiguousarray(temp_vision_frame)
face_landmark_68_5 = target_face.landmark_set.get('68/5')
point_scale = calculate_scale(temp_vision_frame)
point_color = 255, 255, 0
if target_face.origin == 'refill':
point_color = 0, 165, 255
if numpy.any(face_landmark_68_5):
face_landmark_68_5 = face_landmark_68_5.astype(numpy.int32)
for point in face_landmark_68_5:
cv2.circle(temp_vision_frame, tuple(point), 3, point_color, -1)
cv2.circle(temp_vision_frame, tuple(point), point_scale, point_color, -1)
return temp_vision_frame
def calculate_scale(temp_vision_frame : VisionFrame) -> int:
frame_height, _ = temp_vision_frame.shape[:2]
frame_scale = round(frame_height / 270)
return max(1, min(10, frame_scale))
def process_frame(inputs : FaceDebuggerInputs) -> ProcessorOutputs:
reference_vision_frame = inputs.get('reference_vision_frame')
target_vision_frame = inputs.get('target_vision_frame')
source_vision_frames = inputs.get('source_vision_frames')
target_vision_frames = inputs.get('target_vision_frames')
temp_vision_frame = inputs.get('temp_vision_frame')
temp_vision_mask = inputs.get('temp_vision_mask')
target_faces = select_faces(reference_vision_frame, target_vision_frame)
target_vision_frame = get_middle(target_vision_frames)
target_faces = select_faces(reference_vision_frame, source_vision_frames, target_vision_frames)
if target_faces:
for target_face in target_faces:
@@ -228,5 +269,3 @@ def process_frame(inputs : FaceDebuggerInputs) -> ProcessorOutputs:
temp_vision_frame = debug_face(target_face, temp_vision_frame)
return temp_vision_frame, temp_vision_mask
@@ -1,6 +1,6 @@
from facefusion.types import Locals
from facefusion.types import Locales
LOCALS : Locals =\
LOCALES : Locales =\
{
'en':
{
@@ -1,11 +1,12 @@
from typing import Literal, TypedDict
from typing import List, Literal, TypedDict
from facefusion.types import Mask, VisionFrame
FaceDebuggerInputs = TypedDict('FaceDebuggerInputs',
{
'reference_vision_frame' : VisionFrame,
'target_vision_frame' : VisionFrame,
'source_vision_frames' : List[VisionFrame],
'target_vision_frames' : List[VisionFrame],
'temp_vision_frame' : VisionFrame,
'temp_vision_mask' : Mask
})
@@ -1,9 +1,9 @@
from typing import List, Sequence
from typing import List, Sequence, get_args
from facefusion.common_helper import create_float_range
from facefusion.processors.modules.face_editor.types import FaceEditorModel
face_editor_models : List[FaceEditorModel] = [ 'live_portrait' ]
face_editor_models : List[FaceEditorModel] = list(get_args(FaceEditorModel))
face_editor_eyebrow_direction_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
face_editor_eye_gaze_horizontal_range : Sequence[float] = create_float_range(-1.0, 1.0, 0.05)
@@ -1,20 +1,21 @@
from argparse import ArgumentParser
from functools import lru_cache
from typing import Tuple
from types import ModuleType
from typing import List, Tuple
import cv2
import numpy
import facefusion.args_store
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, state_manager, translator, video_manager
from facefusion.common_helper import create_float_metavar
from facefusion.common_helper import create_float_metavar, get_middle
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.face_analyser import scale_face
from facefusion.face_creator import scale_face
from facefusion.face_helper import paste_back, scale_face_landmark_5, warp_face_by_face_landmark_5
from facefusion.face_masker import create_box_mask
from facefusion.face_selector import select_faces
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
from facefusion.processors.live_portrait import create_rotation, limit_angle, limit_expression
from facefusion.processors.modules.face_editor import choices as face_editor_choices
from facefusion.processors.modules.face_editor.types import FaceEditorInputs
@@ -22,7 +23,7 @@ from facefusion.processors.types import LivePortraitExpression, LivePortraitFeat
from facefusion.program_helper import find_argument_group
from facefusion.thread_helper import conditional_thread_semaphore, thread_semaphore
from facefusion.types import ApplyStateItem, Args, DownloadScope, Face, FaceLandmark68, InferencePool, ModelOptions, ModelSet, ProcessMode, VisionFrame
from facefusion.vision import read_static_image, read_static_video_frame
from facefusion.vision import read_static_image, read_static_video_chunk, read_static_video_frame
@lru_cache()
@@ -144,7 +145,7 @@ def register_args(program : ArgumentParser) -> None:
group_processors.add_argument('--face-editor-head-pitch', help = translator.get('help.head_pitch', __package__), type = float, default = config.get_float_value('processors', 'face_editor_head_pitch', '0'), choices = face_editor_choices.face_editor_head_pitch_range, metavar = create_float_metavar(face_editor_choices.face_editor_head_pitch_range))
group_processors.add_argument('--face-editor-head-yaw', help = translator.get('help.head_yaw', __package__), type = float, default = config.get_float_value('processors', 'face_editor_head_yaw', '0'), choices = face_editor_choices.face_editor_head_yaw_range, metavar = create_float_metavar(face_editor_choices.face_editor_head_yaw_range))
group_processors.add_argument('--face-editor-head-roll', help = translator.get('help.head_roll', __package__), type = float, default = config.get_float_value('processors', 'face_editor_head_roll', '0'), choices = face_editor_choices.face_editor_head_roll_range, metavar = create_float_metavar(face_editor_choices.face_editor_head_roll_range))
facefusion.args_store.register_args([ 'face_editor_model', 'face_editor_eyebrow_direction', 'face_editor_eye_gaze_horizontal', 'face_editor_eye_gaze_vertical', 'face_editor_eye_open_ratio', 'face_editor_lip_open_ratio', 'face_editor_mouth_grim', 'face_editor_mouth_pout', 'face_editor_mouth_purse', 'face_editor_mouth_smile', 'face_editor_mouth_position_horizontal', 'face_editor_mouth_position_vertical', 'face_editor_head_pitch', 'face_editor_head_yaw', 'face_editor_head_roll' ], scopes = [ 'api', 'cli' ])
facefusion.jobs.job_store.register_step_keys([ 'face_editor_model', 'face_editor_eyebrow_direction', 'face_editor_eye_gaze_horizontal', 'face_editor_eye_gaze_vertical', 'face_editor_eye_open_ratio', 'face_editor_lip_open_ratio', 'face_editor_mouth_grim', 'face_editor_mouth_pout', 'face_editor_mouth_purse', 'face_editor_mouth_smile', 'face_editor_mouth_position_horizontal', 'face_editor_mouth_position_vertical', 'face_editor_head_pitch', 'face_editor_head_yaw', 'face_editor_head_roll' ])
def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
@@ -165,10 +166,18 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('face_editor_head_roll', args.get('face_editor_head_roll'))
def get_common_modules() -> List[ModuleType]:
return [ content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer ]
def pre_check() -> bool:
model_hash_set = get_model_options().get('hashes')
model_source_set = get_model_options().get('sources')
for common_module in get_common_modules():
if not common_module.pre_check():
return False
return conditional_download_hashes(model_hash_set) and conditional_download_sources(model_source_set)
@@ -179,22 +188,24 @@ def pre_process(mode : ProcessMode) -> bool:
if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
logger.error(translator.get('specify_image_or_video_output') + translator.get('exclamation_mark'), __name__)
return False
if mode == 'output' and not same_file_extension(state_manager.get_item('target_path'), state_manager.get_item('output_path')):
logger.error(translator.get('match_target_and_output_extension') + translator.get('exclamation_mark'), __name__)
return False
return True
def post_process() -> None:
read_static_image.cache_clear()
read_static_video_frame.cache_clear()
read_static_video_chunk.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
content_analyser.clear_inference_pool()
face_classifier.clear_inference_pool()
face_detector.clear_inference_pool()
face_landmarker.clear_inference_pool()
face_masker.clear_inference_pool()
face_recognizer.clear_inference_pool()
for common_module in get_common_modules():
common_module.clear_inference_pool()
def edit_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
@@ -483,10 +494,13 @@ def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame:
def process_frame(inputs : FaceEditorInputs) -> ProcessorOutputs:
reference_vision_frame = inputs.get('reference_vision_frame')
target_vision_frame = inputs.get('target_vision_frame')
source_vision_frames = inputs.get('source_vision_frames')
target_vision_frames = inputs.get('target_vision_frames')
temp_vision_frame = inputs.get('temp_vision_frame')
temp_vision_mask = inputs.get('temp_vision_mask')
target_faces = select_faces(reference_vision_frame, target_vision_frame)
target_vision_frame = get_middle(target_vision_frames)
target_faces = select_faces(reference_vision_frame, source_vision_frames, target_vision_frames)
if target_faces:
for target_face in target_faces:
@@ -1,6 +1,6 @@
from facefusion.types import Locals
from facefusion.types import Locales
LOCALS : Locals =\
LOCALES : Locales =\
{
'en':
{
@@ -1,11 +1,12 @@
from typing import Literal, TypedDict
from typing import List, Literal, TypedDict
from facefusion.types import Mask, VisionFrame
FaceEditorInputs = TypedDict('FaceEditorInputs',
{
'reference_vision_frame' : VisionFrame,
'target_vision_frame' : VisionFrame,
'source_vision_frames' : List[VisionFrame],
'target_vision_frames' : List[VisionFrame],
'temp_vision_frame' : VisionFrame,
'temp_vision_mask' : Mask
})
@@ -1,9 +1,9 @@
from typing import List, Sequence
from typing import List, Sequence, get_args
from facefusion.common_helper import create_float_range, create_int_range
from facefusion.processors.modules.face_enhancer.types import FaceEnhancerModel
face_enhancer_models : List[FaceEnhancerModel] = [ 'codeformer', 'gfpgan_1.2', 'gfpgan_1.3', 'gfpgan_1.4', 'gpen_bfr_256', 'gpen_bfr_512', 'gpen_bfr_1024', 'gpen_bfr_2048', 'restoreformer_plus_plus' ]
face_enhancer_models : List[FaceEnhancerModel] = list(get_args(FaceEnhancerModel))
face_enhancer_blend_range : Sequence[int] = create_int_range(0, 100, 1)
@@ -1,25 +1,27 @@
from argparse import ArgumentParser
from functools import lru_cache
from types import ModuleType
from typing import List
import numpy
import facefusion.args_store
import facefusion.jobs.job_manager
import facefusion.jobs.job_store
from facefusion import config, content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer, inference_manager, logger, state_manager, translator, video_manager
from facefusion.common_helper import create_float_metavar, create_int_metavar
from facefusion.common_helper import create_float_metavar, create_int_metavar, get_middle
from facefusion.download import conditional_download_hashes, conditional_download_sources, resolve_download_url
from facefusion.face_analyser import scale_face
from facefusion.face_creator import scale_face
from facefusion.face_helper import paste_back, warp_face_by_face_landmark_5
from facefusion.face_masker import create_box_mask, create_occlusion_mask
from facefusion.face_selector import select_faces
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path
from facefusion.filesystem import in_directory, is_image, is_video, resolve_relative_path, same_file_extension
from facefusion.processors.modules.face_enhancer import choices as face_enhancer_choices
from facefusion.processors.modules.face_enhancer.types import FaceEnhancerInputs, FaceEnhancerWeight
from facefusion.processors.types import ProcessorOutputs
from facefusion.program_helper import find_argument_group
from facefusion.thread_helper import thread_semaphore
from facefusion.types import ApplyStateItem, Args, DownloadScope, Face, InferencePool, ModelOptions, ModelSet, ProcessMode, VisionFrame
from facefusion.vision import blend_frame, read_static_image, read_static_video_frame
from facefusion.vision import blend_frame, read_static_image, read_static_video_chunk, read_static_video_frame
@lru_cache()
@@ -295,7 +297,7 @@ def register_args(program : ArgumentParser) -> None:
group_processors.add_argument('--face-enhancer-model', help = translator.get('help.model', __package__), default = config.get_str_value('processors', 'face_enhancer_model', 'gfpgan_1.4'), choices = face_enhancer_choices.face_enhancer_models)
group_processors.add_argument('--face-enhancer-blend', help = translator.get('help.blend', __package__), type = int, default = config.get_int_value('processors', 'face_enhancer_blend', '80'), choices = face_enhancer_choices.face_enhancer_blend_range, metavar = create_int_metavar(face_enhancer_choices.face_enhancer_blend_range))
group_processors.add_argument('--face-enhancer-weight', help = translator.get('help.weight', __package__), type = float, default = config.get_float_value('processors', 'face_enhancer_weight', '0.5'), choices = face_enhancer_choices.face_enhancer_weight_range, metavar = create_float_metavar(face_enhancer_choices.face_enhancer_weight_range))
facefusion.args_store.register_args([ 'face_enhancer_model', 'face_enhancer_blend', 'face_enhancer_weight' ], scopes = [ 'api', 'cli' ])
facefusion.jobs.job_store.register_step_keys([ 'face_enhancer_model', 'face_enhancer_blend', 'face_enhancer_weight' ])
def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
@@ -304,10 +306,18 @@ def apply_args(args : Args, apply_state_item : ApplyStateItem) -> None:
apply_state_item('face_enhancer_weight', args.get('face_enhancer_weight'))
def get_common_modules() -> List[ModuleType]:
return [ content_analyser, face_classifier, face_detector, face_landmarker, face_masker, face_recognizer ]
def pre_check() -> bool:
model_hash_set = get_model_options().get('hashes')
model_source_set = get_model_options().get('sources')
for common_module in get_common_modules():
if not common_module.pre_check():
return False
return conditional_download_hashes(model_hash_set) and conditional_download_sources(model_source_set)
@@ -318,22 +328,24 @@ def pre_process(mode : ProcessMode) -> bool:
if mode == 'output' and not in_directory(state_manager.get_item('output_path')):
logger.error(translator.get('specify_image_or_video_output') + translator.get('exclamation_mark'), __name__)
return False
if mode == 'output' and not same_file_extension(state_manager.get_item('target_path'), state_manager.get_item('output_path')):
logger.error(translator.get('match_target_and_output_extension') + translator.get('exclamation_mark'), __name__)
return False
return True
def post_process() -> None:
read_static_image.cache_clear()
read_static_video_frame.cache_clear()
read_static_video_chunk.cache_clear()
video_manager.clear_video_pool()
if state_manager.get_item('video_memory_strategy') in [ 'strict', 'moderate' ]:
clear_inference_pool()
if state_manager.get_item('video_memory_strategy') == 'strict':
content_analyser.clear_inference_pool()
face_classifier.clear_inference_pool()
face_detector.clear_inference_pool()
face_landmarker.clear_inference_pool()
face_masker.clear_inference_pool()
face_recognizer.clear_inference_pool()
for common_module in get_common_modules():
common_module.clear_inference_pool()
def enhance_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame:
@@ -410,10 +422,13 @@ def blend_paste_frame(temp_vision_frame : VisionFrame, paste_vision_frame : Visi
def process_frame(inputs : FaceEnhancerInputs) -> ProcessorOutputs:
reference_vision_frame = inputs.get('reference_vision_frame')
target_vision_frame = inputs.get('target_vision_frame')
source_vision_frames = inputs.get('source_vision_frames')
target_vision_frames = inputs.get('target_vision_frames')
temp_vision_frame = inputs.get('temp_vision_frame')
temp_vision_mask = inputs.get('temp_vision_mask')
target_faces = select_faces(reference_vision_frame, target_vision_frame)
target_vision_frame = get_middle(target_vision_frames)
target_faces = select_faces(reference_vision_frame, source_vision_frames, target_vision_frames)
if target_faces:
for target_face in target_faces:

Some files were not shown because too many files have changed in this diff Show More