* Mark as NEXT

* Reduce caching to avoid RAM explosion

* Reduce caching to avoid RAM explosion

* Update dependencies

* add face-detector-pad-factor

* update facefusion.ini

* fix test

* change pad to margin

* fix order

* add prepare margin

* use 50% max margin

* Minor fixes part2

* Minor fixes part3

* Minor fixes part4

* Minor fixes part1

* Downgrade onnxruntime as of BiRefNet broken on CPU

add test

update

update facefusion.ini

add birefnet

* rename models

add more models

* Fix versions

* Add .claude to gitignore

* add normalize color

add 4 channel

add colors

* worflows

* cleanup

* cleanup

* cleanup

* cleanup

* add more models (#961)

* Fix naming

* changes

* Fix style and mock Gradio

* Fix style and mock Gradio

* Fix style and mock Gradio

* apply clamp

* remove clamp

* Add normalizer test

* Introduce sanitizer for the rescue (#963)

* Introduce sanitizer for the rescue

* Introduce sanitizer for the rescue

* Introduce sanitizer for the rescue

* prepare ffmpeg for alpha support

* Some cleanup

* Some cleanup

* Fix CI

* List as TypeAlias is not allowed (#967)

* List as TypeAlias is not allowed

* List as TypeAlias is not allowed

* List as TypeAlias is not allowed

* List as TypeAlias is not allowed

* Add mpeg and mxf support (#968)

* Add mpeg support

* Add mxf support

* Adjust fix_xxx_encoder for the new formats

* Extend output pattern for batch-run (#969)

* Extend output pattern for batch-run

* Add {target_extension} to allowed mixed files

* Catch invalid output pattern keys

* alpha support

* cleanup

* cleanup

* add ProcessorOutputs type

* fix preview and streamer, support alpha for background_remover

* Refactor/open close processors (#972)

* Introduce open/close processors

* Add locales for translator

* Introduce __autoload__ for translator

* More cleanup

* Fix import issues

* Resolve the scope situation for locals

* Fix installer by not using translator

* Fixes after merge

* Fixes after merge

* Fix translator keys in ui

* Use LOCALS in installer

* Update and partial fix DirectML

* Use latest onnxruntime

* Fix performance

* Fix lint issues

* fix mask

* fix lint

* fix lint

* Remove default from translator.get()

* remove 'framerate='

* fix test

* Rename and reorder models

* Align naming

* add alpha preview

* fix frame-by-frame

* Add alpha effect via css

* preview support alpha channel

* fix preview modes

* Use official assets repositories

* Add support for u2net_cloth

* fix naming

* Add more models

* Add vendor, license and year direct to the models

* Add vendor, license and year direct to the models

* Update dependencies, Minor CSS adjustment

* Ready for 3.5.0

* Fix naming

* Update about messages

* Fix return

* Use groups to show/hide

* Update preview

* Conditional merge mask

* Conditional merge mask

* Fix import order

---------

Co-authored-by: harisreedhar <h4harisreedhar.s.s@gmail.com>
Co-authored-by: Harisreedhar <46858047+harisreedhar@users.noreply.github.com>
This commit is contained in:
Henry Ruhs
2025-11-03 14:05:15 +01:00
committed by GitHub
parent 189d750621
commit 8bf9170577
155 changed files with 3519 additions and 1753 deletions

View File

@@ -0,0 +1,39 @@
import subprocess
import sys
import pytest
from facefusion.download import conditional_download
from facefusion.jobs.job_manager import clear_jobs, init_jobs
from .helper import get_test_example_file, get_test_examples_directory, get_test_jobs_directory, get_test_output_file, is_test_output_file, prepare_test_output_directory
@pytest.fixture(scope = 'module', autouse = True)
def before_all() -> None:
conditional_download(get_test_examples_directory(),
[
'https://github.com/facefusion/facefusion-assets/releases/download/examples-3.0.0/source.jpg',
'https://github.com/facefusion/facefusion-assets/releases/download/examples-3.0.0/target-240p.mp4'
])
subprocess.run(['ffmpeg', '-i', get_test_example_file('target-240p.mp4'), '-vframes', '1', get_test_example_file('target-240p.jpg')])
@pytest.fixture(scope = 'function', autouse = True)
def before_each() -> None:
clear_jobs(get_test_jobs_directory())
init_jobs(get_test_jobs_directory())
prepare_test_output_directory()
def test_remove_background_to_image() -> None:
commands = [ sys.executable, 'facefusion.py', 'headless-run', '--jobs-path', get_test_jobs_directory(), '--processors', 'background_remover', '-t', get_test_example_file('target-240p.jpg'), '-o', get_test_output_file('test-remove-background-to-image.jpg') ]
assert subprocess.run(commands).returncode == 0
assert is_test_output_file('test-remove-background-to-image.jpg') is True
def test_remove_background_to_video() -> None:
commands = [ sys.executable, 'facefusion.py', 'headless-run', '--jobs-path', get_test_jobs_directory(), '--processors', 'background_remover', '-t', get_test_example_file('target-240p.mp4'), '-o', get_test_output_file('test-remove-background-to-video.mp4'), '--trim-frame-end', '1' ]
assert subprocess.run(commands).returncode == 0
assert is_test_output_file('test-remove-background-to-video.mp4') is True

View File

@@ -42,6 +42,7 @@ def before_each() -> None:
def test_get_one_face_with_retinaface() -> None:
state_manager.init_item('face_detector_model', 'retinaface')
state_manager.init_item('face_detector_size', '320x320')
state_manager.init_item('face_detector_margin', (0, 0, 0, 0))
face_detector.pre_check()
source_paths =\
@@ -62,6 +63,7 @@ def test_get_one_face_with_retinaface() -> None:
def test_get_one_face_with_scrfd() -> None:
state_manager.init_item('face_detector_model', 'scrfd')
state_manager.init_item('face_detector_size', '640x640')
state_manager.init_item('face_detector_margin', (0, 0, 0, 0))
face_detector.pre_check()
source_paths =\
@@ -82,6 +84,7 @@ def test_get_one_face_with_scrfd() -> None:
def test_get_one_face_with_yoloface() -> None:
state_manager.init_item('face_detector_model', 'yoloface')
state_manager.init_item('face_detector_size', '640x640')
state_manager.init_item('face_detector_margin', (0, 0, 0, 0))
face_detector.pre_check()
source_paths =\
@@ -102,6 +105,7 @@ def test_get_one_face_with_yoloface() -> None:
def test_get_one_face_with_yunet() -> None:
state_manager.init_item('face_detector_model', 'yunet')
state_manager.init_item('face_detector_size', '640x640')
state_manager.init_item('face_detector_margin', (0, 0, 0, 0))
face_detector.pre_check()
source_paths =\

View File

@@ -1,7 +1,7 @@
from shutil import which
from facefusion import ffmpeg_builder
from facefusion.ffmpeg_builder import chain, run, select_frame_range, set_audio_quality, set_audio_sample_size, set_stream_mode, set_video_quality
from facefusion.ffmpeg_builder import chain, concat, keep_video_alpha, run, select_frame_range, set_audio_quality, set_audio_sample_size, set_stream_mode, set_video_encoder, set_video_fps, set_video_quality
def test_run() -> None:
@@ -9,7 +9,31 @@ def test_run() -> None:
def test_chain() -> None:
assert chain(ffmpeg_builder.set_progress()) == [ '-progress' ]
assert chain(
ffmpeg_builder.set_input('input.mp4'),
ffmpeg_builder.set_output('output.mp4')
) == [ '-i', 'input.mp4', 'output.mp4' ]
assert chain(
ffmpeg_builder.set_video_encoder('libx264'),
ffmpeg_builder.set_video_fps(30),
ffmpeg_builder.set_audio_encoder('aac')
) == [ '-c:v', 'libx264', '-vf', 'fps=30', '-c:a', 'aac' ]
def test_concat() -> None:
assert concat(
set_video_encoder('libvpx-vp9'),
set_video_fps(30)
) == [ '-c:v', 'libvpx-vp9', '-vf', 'fps=30' ]
assert concat(
set_video_encoder('libvpx-vp9'),
set_video_fps(30),
keep_video_alpha('libvpx-vp9')
) == [ '-c:v', 'libvpx-vp9', '-vf', 'fps=30,format=yuva420p' ]
assert concat(
select_frame_range(0, 100, 30),
keep_video_alpha('libvpx-vp9')
) == [ '-vf', 'trim=start_frame=0:end_frame=100,fps=30,format=yuva420p' ]
def test_set_stream_mode() -> None:

View File

@@ -1,12 +1,20 @@
from facefusion.normalizer import normalize_fps, normalize_padding
from facefusion.normalizer import normalize_color, normalize_fps, normalize_space
def test_normalize_padding() -> None:
assert normalize_padding([ 0, 0, 0, 0 ]) == (0, 0, 0, 0)
assert normalize_padding([ 1 ]) == (1, 1, 1, 1)
assert normalize_padding([ 1, 2 ]) == (1, 2, 1, 2)
assert normalize_padding([ 1, 2, 3 ]) == (1, 2, 3, 2)
assert normalize_padding(None) is None
def test_normalize_color() -> None:
assert normalize_color([ 0 ]) == (0, 0, 0, 255)
assert normalize_color([ 0, 128 ]) == (0, 128, 0, 255)
assert normalize_color([ 0, 128, 255 ]) == (0, 128, 255, 255)
assert normalize_color([ 0, 128, 255, 0 ]) == (0, 128, 255, 0)
assert normalize_color(None) is None
def test_normalize_space() -> None:
assert normalize_space([ 0, 0, 0, 0 ]) == (0, 0, 0, 0)
assert normalize_space([ 1 ]) == (1, 1, 1, 1)
assert normalize_space([ 1, 2 ]) == (1, 2, 1, 2)
assert normalize_space([ 1, 2, 3 ]) == (1, 2, 3, 2)
assert normalize_space(None) is None
def test_normalize_fps() -> None:

8
tests/test_sanitizer.py Normal file
View File

@@ -0,0 +1,8 @@
from facefusion.sanitizer import sanitize_int_range
def test_sanitize_int_range() -> None:
assert sanitize_int_range(0, [ 0, 1, 2 ]) == 0
assert sanitize_int_range(2, [0, 1, 2]) == 2
assert sanitize_int_range(-1, [ 0, 1 ]) == 0
assert sanitize_int_range(3, [ 0, 1 ]) == 0

14
tests/test_translator.py Normal file
View File

@@ -0,0 +1,14 @@
from facefusion import translator
from facefusion.locals import LOCALS
def test_load() -> None:
translator.load(LOCALS, __name__)
assert __name__ in translator.LOCAL_POOL_SET
def test_get() -> None:
assert translator.get('conda_not_activated') == 'conda is not activated'
assert translator.get('help.skip_conda') == 'skip the conda environment check'
assert translator.get('invalid') is None

View File

@@ -1,7 +0,0 @@
from facefusion import wording
def test_get() -> None:
assert wording.get('python_not_supported')
assert wording.get('help.source_paths')
assert wording.get('invalid') is None