From 47d3520c19eb606ff040b4e0b7bbb02b107243c5 Mon Sep 17 00:00:00 2001 From: vnyash Date: Thu, 13 Jun 2024 07:56:13 +0530 Subject: [PATCH] deepfuze --- .DS_Store | Bin 0 -> 8196 bytes .editorconfig | 8 + .gitignore | 4 + README.md | 144 +++ __init__.py | 6 + audio_playback.py | 20 + deepfuze/.DS_Store | Bin 0 -> 8196 bytes deepfuze/__init__.py | 0 deepfuze/audio.py | 137 ++ deepfuze/choices.py | 37 + deepfuze/common_helper.py | 46 + deepfuze/config.py | 91 ++ deepfuze/content_analyser.py | 112 ++ deepfuze/core.py | 438 +++++++ deepfuze/download.py | 49 + deepfuze/execution.py | 112 ++ deepfuze/face_analyser.py | 586 +++++++++ deepfuze/face_helper.py | 169 +++ deepfuze/face_masker.py | 155 +++ deepfuze/face_store.py | 48 + deepfuze/ffmpeg.py | 146 +++ deepfuze/filesystem.py | 135 ++ deepfuze/globals.py | 60 + deepfuze/installer.py | 77 ++ deepfuze/logger.py | 47 + deepfuze/memory.py | 21 + deepfuze/metadata.py | 13 + deepfuze/normalizer.py | 39 + deepfuze/process_manager.py | 53 + deepfuze/processors/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 172 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 194 bytes deepfuze/processors/frame/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 178 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 200 bytes .../frame/__pycache__/choices.cpython-310.pyc | Bin 0 -> 1736 bytes .../frame/__pycache__/choices.cpython-311.pyc | Bin 0 -> 2402 bytes .../frame/__pycache__/core.cpython-310.pyc | Bin 0 -> 3814 bytes .../frame/__pycache__/core.cpython-311.pyc | Bin 0 -> 7281 bytes .../frame/__pycache__/globals.cpython-310.pyc | Bin 0 -> 922 bytes .../frame/__pycache__/globals.cpython-311.pyc | Bin 0 -> 1377 bytes .../frame/__pycache__/typings.cpython-310.pyc | Bin 0 -> 1571 bytes .../frame/__pycache__/typings.cpython-311.pyc | Bin 0 -> 2026 bytes deepfuze/processors/frame/choices.py | 16 + deepfuze/processors/frame/core.py | 116 ++ deepfuze/processors/frame/globals.py | 14 + deepfuze/processors/frame/modules/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 186 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 208 bytes .../__pycache__/face_debugger.cpython-310.pyc | Bin 0 -> 7746 bytes .../__pycache__/face_debugger.cpython-311.pyc | Bin 0 -> 15566 bytes .../__pycache__/face_enhancer.cpython-310.pyc | Bin 0 -> 10669 bytes .../__pycache__/face_enhancer.cpython-311.pyc | Bin 0 -> 18907 bytes .../__pycache__/face_swapper.cpython-310.pyc | Bin 0 -> 12585 bytes .../__pycache__/face_swapper.cpython-311.pyc | Bin 0 -> 23595 bytes .../frame_colorizer.cpython-310.pyc | Bin 0 -> 9273 bytes .../frame_colorizer.cpython-311.pyc | Bin 0 -> 17373 bytes .../frame_enhancer.cpython-310.pyc | Bin 0 -> 9654 bytes .../frame_enhancer.cpython-311.pyc | Bin 0 -> 16847 bytes .../__pycache__/lip_syncer.cpython-310.pyc | Bin 0 -> 10008 bytes .../__pycache__/lip_syncer.cpython-311.pyc | Bin 0 -> 19083 bytes .../processors/frame/modules/face_debugger.py | 192 +++ .../processors/frame/modules/face_enhancer.py | 301 +++++ .../processors/frame/modules/face_swapper.py | 369 ++++++ .../frame/modules/frame_colorizer.py | 241 ++++ .../frame/modules/frame_enhancer.py | 263 ++++ .../processors/frame/modules/lip_syncer.py | 260 ++++ deepfuze/processors/frame/typings.py | 41 + deepfuze/statistics.py | 51 + deepfuze/thread_helper.py | 21 + deepfuze/typing.py | 122 ++ deepfuze/uis/__init__.py | 0 .../uis/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 165 bytes .../uis/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 187 bytes .../uis/__pycache__/choices.cpython-310.pyc | Bin 0 -> 563 bytes .../uis/__pycache__/choices.cpython-311.pyc | Bin 0 -> 746 bytes deepfuze/uis/__pycache__/core.cpython-310.pyc | Bin 0 -> 5548 bytes deepfuze/uis/__pycache__/core.cpython-311.pyc | Bin 0 -> 9352 bytes .../uis/__pycache__/overrides.cpython-310.pyc | Bin 0 -> 770 bytes .../uis/__pycache__/overrides.cpython-311.pyc | Bin 0 -> 1273 bytes .../uis/__pycache__/typing.cpython-310.pyc | Bin 0 -> 1573 bytes .../uis/__pycache__/typing.cpython-311.pyc | Bin 0 -> 1752 bytes deepfuze/uis/assets/fixes.css | 7 + deepfuze/uis/assets/overrides.css | 58 + deepfuze/uis/choices.py | 7 + deepfuze/uis/components/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 176 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 198 bytes .../__pycache__/about.cpython-310.pyc | Bin 0 -> 758 bytes .../__pycache__/about.cpython-311.pyc | Bin 0 -> 1209 bytes .../common_options.cpython-310.pyc | Bin 0 -> 1243 bytes .../common_options.cpython-311.pyc | Bin 0 -> 2072 bytes .../__pycache__/execution.cpython-310.pyc | Bin 0 -> 1441 bytes .../__pycache__/execution.cpython-311.pyc | Bin 0 -> 2334 bytes .../execution_queue_count.cpython-310.pyc | Bin 0 -> 1135 bytes .../execution_queue_count.cpython-311.pyc | Bin 0 -> 1799 bytes .../execution_thread_count.cpython-310.pyc | Bin 0 -> 1141 bytes .../execution_thread_count.cpython-311.pyc | Bin 0 -> 1805 bytes .../__pycache__/face_analyser.cpython-310.pyc | Bin 0 -> 4359 bytes .../__pycache__/face_analyser.cpython-311.pyc | Bin 0 -> 9297 bytes .../__pycache__/face_masker.cpython-310.pyc | Bin 0 -> 4088 bytes .../__pycache__/face_masker.cpython-311.pyc | Bin 0 -> 9031 bytes .../__pycache__/face_selector.cpython-310.pyc | Bin 0 -> 5142 bytes .../__pycache__/face_selector.cpython-311.pyc | Bin 0 -> 10029 bytes .../frame_processors.cpython-310.pyc | Bin 0 -> 1899 bytes .../frame_processors.cpython-311.pyc | Bin 0 -> 3248 bytes .../frame_processors_options.cpython-310.pyc | Bin 0 -> 7109 bytes .../frame_processors_options.cpython-311.pyc | Bin 0 -> 15734 bytes .../__pycache__/memory.cpython-310.pyc | Bin 0 -> 1557 bytes .../__pycache__/memory.cpython-311.pyc | Bin 0 -> 2603 bytes .../__pycache__/output.cpython-310.pyc | Bin 0 -> 2892 bytes .../__pycache__/output.cpython-311.pyc | Bin 0 -> 6234 bytes .../output_options.cpython-310.pyc | Bin 0 -> 5317 bytes .../output_options.cpython-311.pyc | Bin 0 -> 12269 bytes .../__pycache__/preview.cpython-310.pyc | Bin 0 -> 6221 bytes .../__pycache__/preview.cpython-311.pyc | Bin 0 -> 11951 bytes .../__pycache__/source.cpython-310.pyc | Bin 0 -> 2330 bytes .../__pycache__/source.cpython-311.pyc | Bin 0 -> 4221 bytes .../__pycache__/target.cpython-310.pyc | Bin 0 -> 2306 bytes .../__pycache__/target.cpython-311.pyc | Bin 0 -> 4531 bytes .../__pycache__/temp_frame.cpython-310.pyc | Bin 0 -> 1541 bytes .../__pycache__/temp_frame.cpython-311.pyc | Bin 0 -> 2499 bytes .../__pycache__/trim_frame.cpython-310.pyc | Bin 0 -> 2557 bytes .../__pycache__/trim_frame.cpython-311.pyc | Bin 0 -> 4824 bytes deepfuze/uis/components/about.py | 23 + deepfuze/uis/components/benchmark.py | 140 +++ deepfuze/uis/components/benchmark_options.py | 29 + deepfuze/uis/components/common_options.py | 35 + deepfuze/uis/components/execution.py | 33 + .../uis/components/execution_queue_count.py | 28 + .../uis/components/execution_thread_count.py | 29 + deepfuze/uis/components/face_analyser.py | 123 ++ deepfuze/uis/components/face_masker.py | 119 ++ deepfuze/uis/components/face_selector.py | 165 +++ deepfuze/uis/components/frame_processors.py | 40 + .../components/frame_processors_options.py | 216 ++++ deepfuze/uis/components/memory.py | 41 + deepfuze/uis/components/output.py | 88 ++ deepfuze/uis/components/output_options.py | 161 +++ deepfuze/uis/components/preview.py | 207 +++ deepfuze/uis/components/source.py | 67 + deepfuze/uis/components/target.py | 83 ++ deepfuze/uis/components/temp_frame.py | 41 + deepfuze/uis/components/trim_frame.py | 79 ++ deepfuze/uis/components/webcam.py | 180 +++ deepfuze/uis/components/webcam_options.py | 37 + deepfuze/uis/core.py | 156 +++ .../__pycache__/default.cpython-310.pyc | Bin 0 -> 3027 bytes .../__pycache__/default.cpython-311.pyc | Bin 0 -> 8799 bytes deepfuze/uis/layouts/benchmark.py | 67 + deepfuze/uis/layouts/default.py | 81 ++ deepfuze/uis/layouts/webcam.py | 50 + deepfuze/uis/overrides.py | 13 + deepfuze/uis/typing.py | 53 + deepfuze/vision.py | 231 ++++ deepfuze/voice_extractor.py | 129 ++ deepfuze/wording.py | 220 ++++ install.py | 12 + llm_node.py | 39 + mypy.ini | 7 + nodes.py | 1110 +++++++++++++++++ requirements.txt | 13 + run.py | 6 + tests/__init__.py | 0 tests/test_audio.py | 26 + tests/test_cli_face_debugger.py | 31 + tests/test_cli_face_enhancer.py | 32 + tests/test_cli_face_swapper.py | 31 + tests/test_cli_frame_colorizer.py | 32 + tests/test_cli_frame_enhancer.py | 31 + tests/test_cli_lip_syncer.py | 32 + tests/test_common_helper.py | 15 + tests/test_config.py | 96 ++ tests/test_download.py | 23 + tests/test_execution.py | 27 + tests/test_face_analyser.py | 103 ++ tests/test_ffmpeg.py | 113 ++ tests/test_filesystem.py | 90 ++ tests/test_memory.py | 8 + tests/test_normalizer.py | 30 + tests/test_process_manager.py | 22 + tests/test_vision.py | 109 ++ tests/test_wording.py | 7 + utils.py | 234 ++++ 184 files changed, 10075 insertions(+) create mode 100644 .DS_Store create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 README.md create mode 100755 __init__.py create mode 100644 audio_playback.py create mode 100644 deepfuze/.DS_Store create mode 100644 deepfuze/__init__.py create mode 100644 deepfuze/audio.py create mode 100755 deepfuze/choices.py create mode 100644 deepfuze/common_helper.py create mode 100644 deepfuze/config.py create mode 100644 deepfuze/content_analyser.py create mode 100755 deepfuze/core.py create mode 100644 deepfuze/download.py create mode 100644 deepfuze/execution.py create mode 100644 deepfuze/face_analyser.py create mode 100644 deepfuze/face_helper.py create mode 100755 deepfuze/face_masker.py create mode 100644 deepfuze/face_store.py create mode 100644 deepfuze/ffmpeg.py create mode 100644 deepfuze/filesystem.py create mode 100755 deepfuze/globals.py create mode 100644 deepfuze/installer.py create mode 100644 deepfuze/logger.py create mode 100644 deepfuze/memory.py create mode 100644 deepfuze/metadata.py create mode 100644 deepfuze/normalizer.py create mode 100644 deepfuze/process_manager.py create mode 100644 deepfuze/processors/__init__.py create mode 100644 deepfuze/processors/__pycache__/__init__.cpython-310.pyc create mode 100644 deepfuze/processors/__pycache__/__init__.cpython-311.pyc create mode 100644 deepfuze/processors/frame/__init__.py create mode 100644 deepfuze/processors/frame/__pycache__/__init__.cpython-310.pyc create mode 100644 deepfuze/processors/frame/__pycache__/__init__.cpython-311.pyc create mode 100644 deepfuze/processors/frame/__pycache__/choices.cpython-310.pyc create mode 100644 deepfuze/processors/frame/__pycache__/choices.cpython-311.pyc create mode 100644 deepfuze/processors/frame/__pycache__/core.cpython-310.pyc create mode 100644 deepfuze/processors/frame/__pycache__/core.cpython-311.pyc create mode 100644 deepfuze/processors/frame/__pycache__/globals.cpython-310.pyc create mode 100644 deepfuze/processors/frame/__pycache__/globals.cpython-311.pyc create mode 100644 deepfuze/processors/frame/__pycache__/typings.cpython-310.pyc create mode 100644 deepfuze/processors/frame/__pycache__/typings.cpython-311.pyc create mode 100755 deepfuze/processors/frame/choices.py create mode 100644 deepfuze/processors/frame/core.py create mode 100755 deepfuze/processors/frame/globals.py create mode 100644 deepfuze/processors/frame/modules/__init__.py create mode 100644 deepfuze/processors/frame/modules/__pycache__/__init__.cpython-310.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/__init__.cpython-311.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/face_debugger.cpython-310.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/face_debugger.cpython-311.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/face_enhancer.cpython-310.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/face_enhancer.cpython-311.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/face_swapper.cpython-310.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/face_swapper.cpython-311.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/frame_colorizer.cpython-310.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/frame_colorizer.cpython-311.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/frame_enhancer.cpython-310.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/frame_enhancer.cpython-311.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/lip_syncer.cpython-310.pyc create mode 100644 deepfuze/processors/frame/modules/__pycache__/lip_syncer.cpython-311.pyc create mode 100755 deepfuze/processors/frame/modules/face_debugger.py create mode 100755 deepfuze/processors/frame/modules/face_enhancer.py create mode 100755 deepfuze/processors/frame/modules/face_swapper.py create mode 100644 deepfuze/processors/frame/modules/frame_colorizer.py create mode 100644 deepfuze/processors/frame/modules/frame_enhancer.py create mode 100755 deepfuze/processors/frame/modules/lip_syncer.py create mode 100644 deepfuze/processors/frame/typings.py create mode 100644 deepfuze/statistics.py create mode 100644 deepfuze/thread_helper.py create mode 100755 deepfuze/typing.py create mode 100644 deepfuze/uis/__init__.py create mode 100644 deepfuze/uis/__pycache__/__init__.cpython-310.pyc create mode 100644 deepfuze/uis/__pycache__/__init__.cpython-311.pyc create mode 100644 deepfuze/uis/__pycache__/choices.cpython-310.pyc create mode 100644 deepfuze/uis/__pycache__/choices.cpython-311.pyc create mode 100644 deepfuze/uis/__pycache__/core.cpython-310.pyc create mode 100644 deepfuze/uis/__pycache__/core.cpython-311.pyc create mode 100644 deepfuze/uis/__pycache__/overrides.cpython-310.pyc create mode 100644 deepfuze/uis/__pycache__/overrides.cpython-311.pyc create mode 100644 deepfuze/uis/__pycache__/typing.cpython-310.pyc create mode 100644 deepfuze/uis/__pycache__/typing.cpython-311.pyc create mode 100644 deepfuze/uis/assets/fixes.css create mode 100644 deepfuze/uis/assets/overrides.css create mode 100644 deepfuze/uis/choices.py create mode 100644 deepfuze/uis/components/__init__.py create mode 100644 deepfuze/uis/components/__pycache__/__init__.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/__init__.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/about.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/about.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/common_options.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/common_options.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/execution.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/execution.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/execution_queue_count.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/execution_queue_count.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/execution_thread_count.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/execution_thread_count.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/face_analyser.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/face_analyser.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/face_masker.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/face_masker.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/face_selector.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/face_selector.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/frame_processors.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/frame_processors.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/frame_processors_options.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/frame_processors_options.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/memory.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/memory.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/output.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/output.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/output_options.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/output_options.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/preview.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/preview.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/source.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/source.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/target.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/target.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/temp_frame.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/temp_frame.cpython-311.pyc create mode 100644 deepfuze/uis/components/__pycache__/trim_frame.cpython-310.pyc create mode 100644 deepfuze/uis/components/__pycache__/trim_frame.cpython-311.pyc create mode 100644 deepfuze/uis/components/about.py create mode 100644 deepfuze/uis/components/benchmark.py create mode 100644 deepfuze/uis/components/benchmark_options.py create mode 100644 deepfuze/uis/components/common_options.py create mode 100644 deepfuze/uis/components/execution.py create mode 100644 deepfuze/uis/components/execution_queue_count.py create mode 100644 deepfuze/uis/components/execution_thread_count.py create mode 100644 deepfuze/uis/components/face_analyser.py create mode 100755 deepfuze/uis/components/face_masker.py create mode 100644 deepfuze/uis/components/face_selector.py create mode 100644 deepfuze/uis/components/frame_processors.py create mode 100755 deepfuze/uis/components/frame_processors_options.py create mode 100644 deepfuze/uis/components/memory.py create mode 100644 deepfuze/uis/components/output.py create mode 100644 deepfuze/uis/components/output_options.py create mode 100755 deepfuze/uis/components/preview.py create mode 100644 deepfuze/uis/components/source.py create mode 100644 deepfuze/uis/components/target.py create mode 100644 deepfuze/uis/components/temp_frame.py create mode 100644 deepfuze/uis/components/trim_frame.py create mode 100644 deepfuze/uis/components/webcam.py create mode 100644 deepfuze/uis/components/webcam_options.py create mode 100644 deepfuze/uis/core.py create mode 100644 deepfuze/uis/layouts/__pycache__/default.cpython-310.pyc create mode 100644 deepfuze/uis/layouts/__pycache__/default.cpython-311.pyc create mode 100644 deepfuze/uis/layouts/benchmark.py create mode 100755 deepfuze/uis/layouts/default.py create mode 100644 deepfuze/uis/layouts/webcam.py create mode 100644 deepfuze/uis/overrides.py create mode 100644 deepfuze/uis/typing.py create mode 100644 deepfuze/vision.py create mode 100644 deepfuze/voice_extractor.py create mode 100755 deepfuze/wording.py create mode 100755 install.py create mode 100644 llm_node.py create mode 100644 mypy.ini create mode 100644 nodes.py create mode 100644 requirements.txt create mode 100755 run.py create mode 100644 tests/__init__.py create mode 100644 tests/test_audio.py create mode 100644 tests/test_cli_face_debugger.py create mode 100644 tests/test_cli_face_enhancer.py create mode 100644 tests/test_cli_face_swapper.py create mode 100644 tests/test_cli_frame_colorizer.py create mode 100644 tests/test_cli_frame_enhancer.py create mode 100644 tests/test_cli_lip_syncer.py create mode 100644 tests/test_common_helper.py create mode 100644 tests/test_config.py create mode 100644 tests/test_download.py create mode 100644 tests/test_execution.py create mode 100644 tests/test_face_analyser.py create mode 100644 tests/test_ffmpeg.py create mode 100644 tests/test_filesystem.py create mode 100644 tests/test_memory.py create mode 100644 tests/test_normalizer.py create mode 100644 tests/test_process_manager.py create mode 100644 tests/test_vision.py create mode 100644 tests/test_wording.py create mode 100644 utils.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6508a31cbec522668cd6b458cbf540eae38b986d GIT binary patch literal 8196 zcmeHM&u<$=6n>MY&AO(|rcEG0Ag%BPsZmMWs0blc<0gU#p@cX}6ZMC?>z$Y->lw4V zPMSoKe1;Q$0NgooMdBae!j&81Pk<{26uy~Rr)xVE5(gBiGuF&|JM+FbGoSa(`mKqG z)wmiJ6TGT@0K%BX?QP3nwT+YZZsWxz6E8L$jk2F?Qm zcxJ2Oyx_TSMr~^uunfGE45;(LLshbx%btjE8lfW~;atmd+3q=v$$ z$sSk^WqFFh3>@R0GKZ|@vS*}$6EkpP`OGZOP?(+_I8U1ss~Txr%YbEIlmV5ymvzn_ zkr>F|4_0E?3}P9;LIu@_s5eh^fkre%K6NPsMgrfV7}p0Bpl#BwegiP<;fEm)4$vH* z4(-6xA@RW(*_;1nAMB@{e1$&-oj6Lfg@s?)*oE(?$;f@;@9A0oNriXOzC`P^3@=Myav6v=g@ZHb z052k3O0f1XaTr8eYvW3Q2{m6v&nO$htKcc(F9FpB;*8yE&Wg4d;gM^e(?PGAr=q0; zpBYvZ>vfFqs(4~o3p`;_wpa$vKLZ!c5@%KZzx?9&|L1=rc7>J!%fNqTfQ>KJmui6b zf42;KQn|K=`V>_aW!E!OL(u4S98#y_kf(nbLib>*Fx6c4j6@I0|NbK2Vtz$vYyIGBDr+nz*_*wIb7z3bL;~cSGJy-3T9)Y!MY46^999>yo=9^v3>XG3GO&tERo=7vBFyaH$4>&)_5$TWgC44f z=<7eY;cSZPi?L`67yVFlX`CGDa}H%ecPKirr1&jC&WgO9)35Nz3i@q~9~ELg<}fwr zD$&D7!KW6_EWjIyt{4>Jzvu8a<3q6zs|Q7hnS&hSMv91GBjk4=E~G00uoIQpWkx?~ zjL!2Y1L)KHlN>XGnAEkaU{Q$?>;#Z5z7?RDPaU;@(NECZh(gR!2n%t_w z_z!%O#Ho&7((sfizlaC3TZZkydL)i8LSZb4Q@NyZx~SQW;IRU(l-~@}267)cM0rz~ z1;lLzLAHE&MtMqcr)=nzv713wV1c6O!Mgy+qDX?#3}ykgLbgH}vRcRpv6;cyZ0P_N zOAyTAdyPXIUXUlm-MDCOl$%mVR2dP&n+`h3B9Dka9kmF5$OgqYfOqf8W+;)J8RRme zGypcK5fO5f#-Jy#)Jw|H%ovs27&Mb8Tg{lu?2IYOcc~4R`R8zCBYa@TTXk_uTJ}TD6;xR2aK{*c~vy?{uN^>V+S6)Tp6` zVJBj2Gr3{emffnYoSyF7+g)?Ewl~k#oYVVT8*9$a_U_r4ZQZ(a_rcTFQP2z32UHj| znf@RYi^=!%BUP}}=i}xe3{*%}o3w3l%<-Z%sJQx3Zs@Em12J8LT8P;R*dHKrZAi+K zg6!s8(Z{HuR+BEReI+rYESY(uWy!fSmvS Optional[List[AudioFrame]]: + return read_audio(audio_path, fps) + + +def read_audio(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]: + sample_rate = 48000 + channel_total = 2 + + if is_audio(audio_path): + audio_buffer = read_audio_buffer(audio_path, sample_rate, channel_total) + audio = numpy.frombuffer(audio_buffer, dtype = numpy.int16).reshape(-1, 2) + audio = prepare_audio(audio) + spectrogram = create_spectrogram(audio) + audio_frames = extract_audio_frames(spectrogram, fps) + return audio_frames + return None + + +@lru_cache(maxsize = 128) +def read_static_voice(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]: + return read_voice(audio_path, fps) + + +def read_voice(audio_path : str, fps : Fps) -> Optional[List[AudioFrame]]: + sample_rate = 48000 + channel_total = 2 + chunk_size = 1024 * 240 + step_size = 1024 * 180 + + if is_audio(audio_path): + audio_buffer = read_audio_buffer(audio_path, sample_rate, channel_total) + audio = numpy.frombuffer(audio_buffer, dtype = numpy.int16).reshape(-1, 2) + audio = batch_extract_voice(audio, chunk_size, step_size) + audio = prepare_voice(audio) + spectrogram = create_spectrogram(audio) + audio_frames = extract_audio_frames(spectrogram, fps) + return audio_frames + return None + + +def get_audio_frame(audio_path : str, fps : Fps, frame_number : int = 0) -> Optional[AudioFrame]: + if is_audio(audio_path): + audio_frames = read_static_audio(audio_path, fps) + if frame_number in range(len(audio_frames)): + return audio_frames[frame_number] + return None + + +def get_voice_frame(audio_path : str, fps : Fps, frame_number : int = 0) -> Optional[AudioFrame]: + if is_audio(audio_path): + voice_frames = read_static_voice(audio_path, fps) + if frame_number in range(len(voice_frames)): + return voice_frames[frame_number] + return None + + +def create_empty_audio_frame() -> AudioFrame: + mel_filter_total = 80 + step_size = 16 + audio_frame = numpy.zeros((mel_filter_total, step_size)).astype(numpy.int16) + return audio_frame + + +def prepare_audio(audio : numpy.ndarray[Any, Any]) -> Audio: + if audio.ndim > 1: + audio = numpy.mean(audio, axis = 1) + audio = audio / numpy.max(numpy.abs(audio), axis = 0) + audio = scipy.signal.lfilter([ 1.0, -0.97 ], [ 1.0 ], audio) + return audio + + +def prepare_voice(audio : numpy.ndarray[Any, Any]) -> Audio: + sample_rate = 48000 + resample_rate = 16000 + + audio = scipy.signal.resample(audio, int(len(audio) * resample_rate / sample_rate)) + audio = prepare_audio(audio) + return audio + + +def convert_hertz_to_mel(hertz : float) -> float: + return 2595 * numpy.log10(1 + hertz / 700) + + +def convert_mel_to_hertz(mel : numpy.ndarray[Any, Any]) -> numpy.ndarray[Any, Any]: + return 700 * (10 ** (mel / 2595) - 1) + + +def create_mel_filter_bank() -> MelFilterBank: + mel_filter_total = 80 + mel_bin_total = 800 + sample_rate = 16000 + min_frequency = 55.0 + max_frequency = 7600.0 + mel_filter_bank = numpy.zeros((mel_filter_total, mel_bin_total // 2 + 1)) + mel_frequency_range = numpy.linspace(convert_hertz_to_mel(min_frequency), convert_hertz_to_mel(max_frequency), mel_filter_total + 2) + indices = numpy.floor((mel_bin_total + 1) * convert_mel_to_hertz(mel_frequency_range) / sample_rate).astype(numpy.int16) + + for index in range(mel_filter_total): + start = indices[index] + end = indices[index + 1] + mel_filter_bank[index, start:end] = scipy.signal.windows.triang(end - start) + return mel_filter_bank + + +def create_spectrogram(audio : Audio) -> Spectrogram: + mel_bin_total = 800 + mel_bin_overlap = 600 + mel_filter_bank = create_mel_filter_bank() + 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 extract_audio_frames(spectrogram : Spectrogram, fps : Fps) -> List[AudioFrame]: + mel_filter_total = 80 + step_size = 16 + audio_frames = [] + indices = numpy.arange(0, spectrogram.shape[1], mel_filter_total / fps).astype(numpy.int16) + indices = indices[indices >= step_size] + + for index in indices: + start = max(0, index - step_size) + audio_frames.append(spectrogram[:, start:index]) + return audio_frames diff --git a/deepfuze/choices.py b/deepfuze/choices.py new file mode 100755 index 0000000..a063960 --- /dev/null +++ b/deepfuze/choices.py @@ -0,0 +1,37 @@ +from typing import List, Dict + +from deepfuze.typing import VideoMemoryStrategy, FaceSelectorMode, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, FaceDetectorModel, FaceMaskType, FaceMaskRegion, TempFrameFormat, OutputVideoEncoder, OutputVideoPreset +from deepfuze.common_helper import create_int_range, create_float_range + +video_memory_strategies : List[VideoMemoryStrategy] = [ 'strict', 'moderate', 'tolerant' ] +face_analyser_orders : List[FaceAnalyserOrder] = [ 'left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small', 'best-worst', 'worst-best' ] +face_analyser_ages : List[FaceAnalyserAge] = [ 'child', 'teen', 'adult', 'senior' ] +face_analyser_genders : List[FaceAnalyserGender] = [ 'female', 'male' ] +face_detector_set : Dict[FaceDetectorModel, List[str]] =\ +{ + 'many': [ '640x640' ], + 'retinaface': [ '160x160', '320x320', '480x480', '512x512', '640x640' ], + 'scrfd': [ '160x160', '320x320', '480x480', '512x512', '640x640' ], + 'yoloface': [ '640x640' ], + 'yunet': [ '160x160', '320x320', '480x480', '512x512', '640x640', '768x768', '960x960', '1024x1024' ] +} +face_selector_modes : List[FaceSelectorMode] = [ 'many', 'one', 'reference' ] +face_mask_types : List[FaceMaskType] = [ 'box', 'occlusion', 'region' ] +face_mask_regions : List[FaceMaskRegion] = [ 'skin', 'left-eyebrow', 'right-eyebrow', 'left-eye', 'right-eye', 'glasses', 'nose', 'mouth', 'upper-lip', 'lower-lip' ] +temp_frame_formats : List[TempFrameFormat] = [ 'bmp', 'jpg', 'png' ] +output_video_encoders : List[OutputVideoEncoder] = [ 'libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc', 'h264_amf', 'hevc_amf' ] +output_video_presets : List[OutputVideoPreset] = [ 'ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow' ] + +image_template_sizes : List[float] = [ 0.25, 0.5, 0.75, 1, 1.5, 2, 2.5, 3, 3.5, 4 ] +video_template_sizes : List[int] = [ 240, 360, 480, 540, 720, 1080, 1440, 2160, 4320 ] + +execution_thread_count_range : List[int] = create_int_range(1, 128, 1) +execution_queue_count_range : List[int] = create_int_range(1, 32, 1) +system_memory_limit_range : List[int] = create_int_range(0, 128, 1) +face_detector_score_range : List[float] = create_float_range(0.0, 1.0, 0.05) +face_landmarker_score_range : List[float] = create_float_range(0.0, 1.0, 0.05) +face_mask_blur_range : List[float] = create_float_range(0.0, 1.0, 0.05) +face_mask_padding_range : List[int] = create_int_range(0, 100, 1) +reference_face_distance_range : List[float] = create_float_range(0.0, 1.5, 0.05) +output_image_quality_range : List[int] = create_int_range(0, 100, 1) +output_video_quality_range : List[int] = create_int_range(0, 100, 1) diff --git a/deepfuze/common_helper.py b/deepfuze/common_helper.py new file mode 100644 index 0000000..d37419b --- /dev/null +++ b/deepfuze/common_helper.py @@ -0,0 +1,46 @@ +from typing import List, Any +import platform + + +def create_metavar(ranges : List[Any]) -> str: + return '[' + str(ranges[0]) + '-' + str(ranges[-1]) + ']' + + +def create_int_range(start : int, end : int, step : int) -> List[int]: + int_range = [] + current = start + + while current <= end: + int_range.append(current) + current += step + return int_range + + +def create_float_range(start : float, end : float, step : float) -> List[float]: + float_range = [] + current = start + + while current <= end: + float_range.append(round(current, 2)) + current = round(current + step, 2) + return float_range + + +def is_linux() -> bool: + return to_lower_case(platform.system()) == 'linux' + + +def is_macos() -> bool: + return to_lower_case(platform.system()) == 'darwin' + + +def is_windows() -> bool: + return to_lower_case(platform.system()) == 'windows' + + +def to_lower_case(__string__ : Any) -> str: + return str(__string__).lower() + + +def get_first(__list__ : Any) -> Any: + return next(iter(__list__), None) diff --git a/deepfuze/config.py b/deepfuze/config.py new file mode 100644 index 0000000..160f346 --- /dev/null +++ b/deepfuze/config.py @@ -0,0 +1,91 @@ +from configparser import ConfigParser +from typing import Any, Optional, List + +import deepfuze.globals + +CONFIG = None + + +def get_config() -> ConfigParser: + global CONFIG + + if CONFIG is None: + CONFIG = ConfigParser() + CONFIG.read(deepfuze.globals.config_path, encoding = 'utf-8') + return CONFIG + + +def clear_config() -> None: + global CONFIG + + CONFIG = None + + +def get_str_value(key : str, fallback : Optional[str] = None) -> Optional[str]: + value = get_value_by_notation(key) + + if value or fallback: + return str(value or fallback) + return None + + +def get_int_value(key : str, fallback : Optional[str] = None) -> Optional[int]: + value = get_value_by_notation(key) + + if value or fallback: + return int(value or fallback) + return None + + +def get_float_value(key : str, fallback : Optional[str] = None) -> Optional[float]: + value = get_value_by_notation(key) + + if value or fallback: + return float(value or fallback) + return None + + +def get_bool_value(key : str, fallback : Optional[str] = None) -> Optional[bool]: + value = get_value_by_notation(key) + + if value == 'True' or fallback == 'True': + return True + if value == 'False' or fallback == 'False': + return False + return None + + +def get_str_list(key : str, fallback : Optional[str] = None) -> Optional[List[str]]: + value = get_value_by_notation(key) + + if value or fallback: + return [ str(value) for value in (value or fallback).split(' ') ] + return None + + +def get_int_list(key : str, fallback : Optional[str] = None) -> Optional[List[int]]: + value = get_value_by_notation(key) + + if value or fallback: + return [ int(value) for value in (value or fallback).split(' ') ] + return None + + +def get_float_list(key : str, fallback : Optional[str] = None) -> Optional[List[float]]: + value = get_value_by_notation(key) + + if value or fallback: + return [ float(value) for value in (value or fallback).split(' ') ] + return None + + +def get_value_by_notation(key : str) -> Optional[Any]: + config = get_config() + + if '.' in key: + section, name = key.split('.') + if section in config and name in config[section]: + return config[section][name] + if key in config: + return config[key] + return None diff --git a/deepfuze/content_analyser.py b/deepfuze/content_analyser.py new file mode 100644 index 0000000..71bf91f --- /dev/null +++ b/deepfuze/content_analyser.py @@ -0,0 +1,112 @@ +from typing import Any +from functools import lru_cache +from time import sleep +import cv2 +import numpy +import onnxruntime +from tqdm import tqdm + +import deepfuze.globals +from deepfuze import process_manager, wording +from deepfuze.thread_helper import thread_lock, conditional_thread_semaphore +from deepfuze.typing import VisionFrame, ModelSet, Fps +from deepfuze.execution import apply_execution_provider_options +from deepfuze.vision import get_video_frame, count_video_frame_total, read_image, detect_video_fps +from deepfuze.filesystem import resolve_relative_path, is_file +from deepfuze.download import conditional_download + +CONTENT_ANALYSER = None +MODELS : ModelSet =\ +{ + 'open_nsfw': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/open_nsfw.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/open_nsfw.onnx') + } +} +PROBABILITY_LIMIT = 0.80 +RATE_LIMIT = 10 +STREAM_COUNTER = 0 + + +def get_content_analyser() -> Any: + global CONTENT_ANALYSER + + with thread_lock(): + while process_manager.is_checking(): + sleep(0.5) + if CONTENT_ANALYSER is None: + model_path = MODELS.get('open_nsfw').get('path') + CONTENT_ANALYSER = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + return CONTENT_ANALYSER + + +def clear_content_analyser() -> None: + global CONTENT_ANALYSER + + CONTENT_ANALYSER = None + + +def pre_check() -> bool: + download_directory_path = resolve_relative_path('../../../models/deepfuze') + model_url = MODELS.get('open_nsfw').get('url') + model_path = MODELS.get('open_nsfw').get('path') + + if not deepfuze.globals.skip_download: + process_manager.check() + conditional_download(download_directory_path, [ model_url ]) + process_manager.end() + return is_file(model_path) + + +def analyse_stream(vision_frame : VisionFrame, video_fps : Fps) -> bool: + global STREAM_COUNTER + + STREAM_COUNTER = STREAM_COUNTER + 1 + if STREAM_COUNTER % int(video_fps) == 0: + return analyse_frame(vision_frame) + return False + + +def analyse_frame(vision_frame : VisionFrame) -> bool: + content_analyser = get_content_analyser() + vision_frame = prepare_frame(vision_frame) + with conditional_thread_semaphore(deepfuze.globals.execution_providers): + probability = content_analyser.run(None, + { + content_analyser.get_inputs()[0].name: vision_frame + })[0][0][1] + return probability > PROBABILITY_LIMIT + + +def prepare_frame(vision_frame : VisionFrame) -> VisionFrame: + vision_frame = cv2.resize(vision_frame, (224, 224)).astype(numpy.float32) + vision_frame -= numpy.array([ 104, 117, 123 ]).astype(numpy.float32) + vision_frame = numpy.expand_dims(vision_frame, axis = 0) + return vision_frame + + +@lru_cache(maxsize = None) +def analyse_image(image_path : str) -> bool: + frame = read_image(image_path) + return analyse_frame(frame) + + +@lru_cache(maxsize = None) +def analyse_video(video_path : str, start_frame : int, end_frame : int) -> bool: + video_frame_total = count_video_frame_total(video_path) + video_fps = detect_video_fps(video_path) + frame_range = range(start_frame or 0, end_frame or video_frame_total) + rate = 0.0 + counter = 0 + + with tqdm(total = len(frame_range), desc = wording.get('analysing'), unit = 'frame', ascii = ' =', disable = deepfuze.globals.log_level in [ 'warn', 'error' ]) as progress: + for frame_number in frame_range: + if frame_number % int(video_fps) == 0: + frame = get_video_frame(video_path, frame_number) + if analyse_frame(frame): + counter += 1 + rate = counter * int(video_fps) / len(frame_range) * 100 + progress.update() + progress.set_postfix(rate = rate) + return rate > RATE_LIMIT diff --git a/deepfuze/core.py b/deepfuze/core.py new file mode 100755 index 0000000..2f77f79 --- /dev/null +++ b/deepfuze/core.py @@ -0,0 +1,438 @@ +import os + +os.environ['OMP_NUM_THREADS'] = '1' + +import signal +import sys +import warnings +import shutil +import numpy +import onnxruntime +from time import sleep, time +from argparse import ArgumentParser, HelpFormatter + +import deepfuze.choices +import deepfuze.globals +from deepfuze.face_analyser import get_one_face, get_average_face +from deepfuze.face_store import get_reference_faces, append_reference_face +from deepfuze import face_analyser, face_masker, content_analyser, config, process_manager, metadata, logger, wording, voice_extractor +from deepfuze.content_analyser import analyse_image, analyse_video +from deepfuze.processors.frame.core import get_frame_processors_modules, load_frame_processor_module +from deepfuze.common_helper import create_metavar, get_first +from deepfuze.execution import encode_execution_providers, decode_execution_providers +from deepfuze.normalizer import normalize_output_path, normalize_padding, normalize_fps +from deepfuze.memory import limit_system_memory +from deepfuze.statistics import conditional_log_statistics +from deepfuze.download import conditional_download +from deepfuze.filesystem import get_temp_frame_paths, get_temp_file_path, create_temp, move_temp, clear_temp, is_image, is_video, filter_audio_paths, resolve_relative_path, list_directory +from deepfuze.ffmpeg import extract_frames, merge_video, copy_image, finalize_image, restore_audio, replace_audio +from deepfuze.vision import read_image, read_static_images, detect_image_resolution, restrict_video_fps, create_image_resolutions, get_video_frame, detect_video_resolution, detect_video_fps, restrict_video_resolution, restrict_image_resolution, create_video_resolutions, pack_resolution, unpack_resolution + +onnxruntime.set_default_logger_severity(3) +warnings.filterwarnings('ignore', category = UserWarning, module = 'gradio') + + +def cli() -> None: + signal.signal(signal.SIGINT, lambda signal_number, frame: destroy()) + program = ArgumentParser(formatter_class = lambda prog: HelpFormatter(prog, max_help_position = 200), add_help = False) + # general + program.add_argument('-c', '--config', help = wording.get('help.config'), dest = 'config_path', default = 'deepfuze.ini') + apply_config(program) + program.add_argument('-s', '--source', help = wording.get('help.source'), action = 'append', dest = 'source_paths', default = config.get_str_list('general.source_paths')) + program.add_argument('-t', '--target', help = wording.get('help.target'), dest = 'target_path', default = config.get_str_value('general.target_path')) + program.add_argument('-o', '--output', help = wording.get('help.output'), dest = 'output_path', default = config.get_str_value('general.output_path')) + program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version') + # misc + group_misc = program.add_argument_group('misc') + group_misc.add_argument('--force-download', help = wording.get('help.force_download'), action = 'store_true', default = config.get_bool_value('misc.force_download')) + group_misc.add_argument('--skip-download', help = wording.get('help.skip_download'), action = 'store_true', default = config.get_bool_value('misc.skip_download')) + group_misc.add_argument('--headless', help = wording.get('help.headless'), action = 'store_true', default = config.get_bool_value('misc.headless')) + group_misc.add_argument('--log-level', help = wording.get('help.log_level'), default = config.get_str_value('misc.log_level', 'info'), choices = logger.get_log_levels()) + # execution + execution_providers = encode_execution_providers(onnxruntime.get_available_providers()) + group_execution = program.add_argument_group('execution') + group_execution.add_argument('--execution-device-id', help = wording.get('help.execution_device_id'), default = config.get_str_value('execution.face_detector_size', '0')) + group_execution.add_argument('--execution-providers', help = wording.get('help.execution_providers').format(choices = ', '.join(execution_providers)), default = config.get_str_list('execution.execution_providers', 'cpu'), choices = execution_providers, nargs = '+', metavar = 'EXECUTION_PROVIDERS') + group_execution.add_argument('--execution-thread-count', help = wording.get('help.execution_thread_count'), type = int, default = config.get_int_value('execution.execution_thread_count', '4'), choices = deepfuze.choices.execution_thread_count_range, metavar = create_metavar(deepfuze.choices.execution_thread_count_range)) + group_execution.add_argument('--execution-queue-count', help = wording.get('help.execution_queue_count'), type = int, default = config.get_int_value('execution.execution_queue_count', '1'), choices = deepfuze.choices.execution_queue_count_range, metavar = create_metavar(deepfuze.choices.execution_queue_count_range)) + # memory + group_memory = program.add_argument_group('memory') + group_memory.add_argument('--video-memory-strategy', help = wording.get('help.video_memory_strategy'), default = config.get_str_value('memory.video_memory_strategy', 'strict'), choices = deepfuze.choices.video_memory_strategies) + group_memory.add_argument('--system-memory-limit', help = wording.get('help.system_memory_limit'), type = int, default = config.get_int_value('memory.system_memory_limit', '0'), choices = deepfuze.choices.system_memory_limit_range, metavar = create_metavar(deepfuze.choices.system_memory_limit_range)) + # face analyser + group_face_analyser = program.add_argument_group('face analyser') + group_face_analyser.add_argument('--face-analyser-order', help = wording.get('help.face_analyser_order'), default = config.get_str_value('face_analyser.face_analyser_order', 'left-right'), choices = deepfuze.choices.face_analyser_orders) + group_face_analyser.add_argument('--face-analyser-age', help = wording.get('help.face_analyser_age'), default = config.get_str_value('face_analyser.face_analyser_age'), choices = deepfuze.choices.face_analyser_ages) + group_face_analyser.add_argument('--face-analyser-gender', help = wording.get('help.face_analyser_gender'), default = config.get_str_value('face_analyser.face_analyser_gender'), choices = deepfuze.choices.face_analyser_genders) + group_face_analyser.add_argument('--face-detector-model', help = wording.get('help.face_detector_model'), default = config.get_str_value('face_analyser.face_detector_model', 'yoloface'), choices = deepfuze.choices.face_detector_set.keys()) + group_face_analyser.add_argument('--face-detector-size', help = wording.get('help.face_detector_size'), default = config.get_str_value('face_analyser.face_detector_size', '640x640')) + group_face_analyser.add_argument('--face-detector-score', help = wording.get('help.face_detector_score'), type = float, default = config.get_float_value('face_analyser.face_detector_score', '0.5'), choices = deepfuze.choices.face_detector_score_range, metavar = create_metavar(deepfuze.choices.face_detector_score_range)) + group_face_analyser.add_argument('--face-landmarker-score', help = wording.get('help.face_landmarker_score'), type = float, default = config.get_float_value('face_analyser.face_landmarker_score', '0.5'), choices = deepfuze.choices.face_landmarker_score_range, metavar = create_metavar(deepfuze.choices.face_landmarker_score_range)) + # face selector + group_face_selector = program.add_argument_group('face selector') + group_face_selector.add_argument('--face-selector-mode', help = wording.get('help.face_selector_mode'), default = config.get_str_value('face_selector.face_selector_mode', 'reference'), choices = deepfuze.choices.face_selector_modes) + group_face_selector.add_argument('--reference-face-position', help = wording.get('help.reference_face_position'), type = int, default = config.get_int_value('face_selector.reference_face_position', '0')) + group_face_selector.add_argument('--reference-face-distance', help = wording.get('help.reference_face_distance'), type = float, default = config.get_float_value('face_selector.reference_face_distance', '0.6'), choices = deepfuze.choices.reference_face_distance_range, metavar = create_metavar(deepfuze.choices.reference_face_distance_range)) + group_face_selector.add_argument('--reference-frame-number', help = wording.get('help.reference_frame_number'), type = int, default = config.get_int_value('face_selector.reference_frame_number', '0')) + # face mask + group_face_mask = program.add_argument_group('face mask') + group_face_mask.add_argument('--face-mask-types', help = wording.get('help.face_mask_types').format(choices = ', '.join(deepfuze.choices.face_mask_types)), default = config.get_str_list('face_mask.face_mask_types', 'box'), choices = deepfuze.choices.face_mask_types, nargs = '+', metavar = 'FACE_MASK_TYPES') + group_face_mask.add_argument('--face-mask-blur', help = wording.get('help.face_mask_blur'), type = float, default = config.get_float_value('face_mask.face_mask_blur', '0.3'), choices = deepfuze.choices.face_mask_blur_range, metavar = create_metavar(deepfuze.choices.face_mask_blur_range)) + group_face_mask.add_argument('--face-mask-padding', help = wording.get('help.face_mask_padding'), type = int, default = config.get_int_list('face_mask.face_mask_padding', '0 0 0 0'), nargs = '+') + group_face_mask.add_argument('--face-mask-regions', help = wording.get('help.face_mask_regions').format(choices = ', '.join(deepfuze.choices.face_mask_regions)), default = config.get_str_list('face_mask.face_mask_regions', ' '.join(deepfuze.choices.face_mask_regions)), choices = deepfuze.choices.face_mask_regions, nargs = '+', metavar = 'FACE_MASK_REGIONS') + # frame extraction + group_frame_extraction = program.add_argument_group('frame extraction') + group_frame_extraction.add_argument('--trim-frame-start', help = wording.get('help.trim_frame_start'), type = int, default = deepfuze.config.get_int_value('frame_extraction.trim_frame_start')) + group_frame_extraction.add_argument('--trim-frame-end', help = wording.get('help.trim_frame_end'), type = int, default = deepfuze.config.get_int_value('frame_extraction.trim_frame_end')) + group_frame_extraction.add_argument('--temp-frame-format', help = wording.get('help.temp_frame_format'), default = config.get_str_value('frame_extraction.temp_frame_format', 'png'), choices = deepfuze.choices.temp_frame_formats) + group_frame_extraction.add_argument('--keep-temp', help = wording.get('help.keep_temp'), action = 'store_true', default = config.get_bool_value('frame_extraction.keep_temp')) + # output creation + group_output_creation = program.add_argument_group('output creation') + group_output_creation.add_argument('--output-image-quality', help = wording.get('help.output_image_quality'), type = int, default = config.get_int_value('output_creation.output_image_quality', '80'), choices = deepfuze.choices.output_image_quality_range, metavar = create_metavar(deepfuze.choices.output_image_quality_range)) + group_output_creation.add_argument('--output-image-resolution', help = wording.get('help.output_image_resolution'), default = config.get_str_value('output_creation.output_image_resolution')) + group_output_creation.add_argument('--output-video-encoder', help = wording.get('help.output_video_encoder'), default = config.get_str_value('output_creation.output_video_encoder', 'libx264'), choices = deepfuze.choices.output_video_encoders) + group_output_creation.add_argument('--output-video-preset', help = wording.get('help.output_video_preset'), default = config.get_str_value('output_creation.output_video_preset', 'veryfast'), choices = deepfuze.choices.output_video_presets) + group_output_creation.add_argument('--output-video-quality', help = wording.get('help.output_video_quality'), type = int, default = config.get_int_value('output_creation.output_video_quality', '80'), choices = deepfuze.choices.output_video_quality_range, metavar = create_metavar(deepfuze.choices.output_video_quality_range)) + group_output_creation.add_argument('--output-video-resolution', help = wording.get('help.output_video_resolution'), default = config.get_str_value('output_creation.output_video_resolution')) + group_output_creation.add_argument('--output-video-fps', help = wording.get('help.output_video_fps'), type = float, default = config.get_str_value('output_creation.output_video_fps')) + group_output_creation.add_argument('--skip-audio', help = wording.get('help.skip_audio'), action = 'store_true', default = config.get_bool_value('output_creation.skip_audio')) + # frame processors + available_frame_processors = list_directory('deepfuze/processors/frame/modules') + program = ArgumentParser(parents = [ program ], formatter_class = program.formatter_class, add_help = True) + group_frame_processors = program.add_argument_group('frame processors') + group_frame_processors.add_argument('--frame-processors', help = wording.get('help.frame_processors').format(choices = ', '.join(available_frame_processors)), default = config.get_str_list('frame_processors.frame_processors', 'face_swapper'), nargs = '+') + for frame_processor in available_frame_processors: + frame_processor_module = load_frame_processor_module(frame_processor) + frame_processor_module.register_args(group_frame_processors) + # uis + available_ui_layouts = list_directory('deepfuze/uis/layouts') + group_uis = program.add_argument_group('uis') + group_uis.add_argument('--open-browser', help=wording.get('help.open_browser'), action = 'store_true', default = config.get_bool_value('uis.open_browser')) + group_uis.add_argument('--ui-layouts', help = wording.get('help.ui_layouts').format(choices = ', '.join(available_ui_layouts)), default = config.get_str_list('uis.ui_layouts', 'default'), nargs = '+') + run(program) + + +def apply_config(program : ArgumentParser) -> None: + known_args = program.parse_known_args() + deepfuze.globals.config_path = get_first(known_args).config_path + + +def validate_args(program : ArgumentParser) -> None: + try: + for action in program._actions: + if action.default: + if isinstance(action.default, list): + for default in action.default: + program._check_value(action, default) + else: + program._check_value(action, action.default) + except Exception as exception: + program.error(str(exception)) + + +def apply_args(program : ArgumentParser) -> None: + args = program.parse_args() + # general + deepfuze.globals.source_paths = args.source_paths + deepfuze.globals.target_path = args.target_path + deepfuze.globals.output_path = args.output_path + # misc + deepfuze.globals.force_download = args.force_download + deepfuze.globals.skip_download = args.skip_download + deepfuze.globals.headless = args.headless + deepfuze.globals.log_level = args.log_level + # execution + deepfuze.globals.execution_device_id = args.execution_device_id + deepfuze.globals.execution_providers = decode_execution_providers(args.execution_providers) + deepfuze.globals.execution_thread_count = args.execution_thread_count + deepfuze.globals.execution_queue_count = args.execution_queue_count + # memory + deepfuze.globals.video_memory_strategy = args.video_memory_strategy + deepfuze.globals.system_memory_limit = args.system_memory_limit + # face analyser + deepfuze.globals.face_analyser_order = args.face_analyser_order + deepfuze.globals.face_analyser_age = args.face_analyser_age + deepfuze.globals.face_analyser_gender = args.face_analyser_gender + deepfuze.globals.face_detector_model = args.face_detector_model + if args.face_detector_size in deepfuze.choices.face_detector_set[args.face_detector_model]: + deepfuze.globals.face_detector_size = args.face_detector_size + else: + deepfuze.globals.face_detector_size = '640x640' + deepfuze.globals.face_detector_score = args.face_detector_score + deepfuze.globals.face_landmarker_score = args.face_landmarker_score + # face selector + deepfuze.globals.face_selector_mode = args.face_selector_mode + deepfuze.globals.reference_face_position = args.reference_face_position + deepfuze.globals.reference_face_distance = args.reference_face_distance + deepfuze.globals.reference_frame_number = args.reference_frame_number + # face mask + deepfuze.globals.face_mask_types = args.face_mask_types + deepfuze.globals.face_mask_blur = args.face_mask_blur + deepfuze.globals.face_mask_padding = normalize_padding(args.face_mask_padding) + deepfuze.globals.face_mask_regions = args.face_mask_regions + # frame extraction + deepfuze.globals.trim_frame_start = args.trim_frame_start + deepfuze.globals.trim_frame_end = args.trim_frame_end + deepfuze.globals.temp_frame_format = args.temp_frame_format + deepfuze.globals.keep_temp = args.keep_temp + # output creation + deepfuze.globals.output_image_quality = args.output_image_quality + if is_image(args.target_path): + output_image_resolution = detect_image_resolution(args.target_path) + output_image_resolutions = create_image_resolutions(output_image_resolution) + if args.output_image_resolution in output_image_resolutions: + deepfuze.globals.output_image_resolution = args.output_image_resolution + else: + deepfuze.globals.output_image_resolution = pack_resolution(output_image_resolution) + deepfuze.globals.output_video_encoder = args.output_video_encoder + deepfuze.globals.output_video_preset = args.output_video_preset + deepfuze.globals.output_video_quality = args.output_video_quality + if is_video(args.target_path): + output_video_resolution = detect_video_resolution(args.target_path) + output_video_resolutions = create_video_resolutions(output_video_resolution) + if args.output_video_resolution in output_video_resolutions: + deepfuze.globals.output_video_resolution = args.output_video_resolution + else: + deepfuze.globals.output_video_resolution = pack_resolution(output_video_resolution) + if args.output_video_fps or is_video(args.target_path): + deepfuze.globals.output_video_fps = normalize_fps(args.output_video_fps) or detect_video_fps(args.target_path) + deepfuze.globals.skip_audio = args.skip_audio + # frame processors + available_frame_processors = list_directory('deepfuze/processors/frame/modules') + deepfuze.globals.frame_processors = args.frame_processors + for frame_processor in available_frame_processors: + frame_processor_module = load_frame_processor_module(frame_processor) + frame_processor_module.apply_args(program) + # uis + deepfuze.globals.open_browser = args.open_browser + deepfuze.globals.ui_layouts = args.ui_layouts + + +def run(program : ArgumentParser) -> None: + validate_args(program) + apply_args(program) + logger.init(deepfuze.globals.log_level) + + if deepfuze.globals.system_memory_limit > 0: + limit_system_memory(deepfuze.globals.system_memory_limit) + if deepfuze.globals.force_download: + force_download() + return + if not pre_check() or not content_analyser.pre_check() or not face_analyser.pre_check() or not face_masker.pre_check() or not voice_extractor.pre_check(): + return + for frame_processor_module in get_frame_processors_modules(deepfuze.globals.frame_processors): + if not frame_processor_module.pre_check(): + return + if deepfuze.globals.headless: + conditional_process() + else: + import deepfuze.uis.core as ui + + for ui_layout in ui.get_ui_layouts_modules(deepfuze.globals.ui_layouts): + if not ui_layout.pre_check(): + return + ui.launch() + + +def destroy() -> None: + process_manager.stop() + while process_manager.is_processing(): + sleep(0.5) + if deepfuze.globals.target_path: + clear_temp(deepfuze.globals.target_path) + sys.exit(0) + + +def pre_check() -> bool: + if sys.version_info < (3, 9): + logger.error(wording.get('python_not_supported').format(version = '3.9'), __name__.upper()) + return False + if not shutil.which('ffmpeg'): + logger.error(wording.get('ffmpeg_not_installed'), __name__.upper()) + return False + return True + + +def conditional_process() -> None: + start_time = time() + for frame_processor_module in get_frame_processors_modules(deepfuze.globals.frame_processors): + while not frame_processor_module.post_check(): + logger.disable() + sleep(0.5) + logger.enable() + if not frame_processor_module.pre_process('output'): + return + conditional_append_reference_faces() + if is_image(deepfuze.globals.target_path): + process_image(start_time) + if is_video(deepfuze.globals.target_path): + process_video(start_time) + + +def conditional_append_reference_faces() -> None: + if 'reference' in deepfuze.globals.face_selector_mode and not get_reference_faces(): + source_frames = read_static_images(deepfuze.globals.source_paths) + source_face = get_average_face(source_frames) + if is_video(deepfuze.globals.target_path): + reference_frame = get_video_frame(deepfuze.globals.target_path, deepfuze.globals.reference_frame_number) + else: + reference_frame = read_image(deepfuze.globals.target_path) + reference_face = get_one_face(reference_frame, deepfuze.globals.reference_face_position) + append_reference_face('origin', reference_face) + if source_face and reference_face: + for frame_processor_module in get_frame_processors_modules(deepfuze.globals.frame_processors): + abstract_reference_frame = frame_processor_module.get_reference_frame(source_face, reference_face, reference_frame) + if numpy.any(abstract_reference_frame): + reference_frame = abstract_reference_frame + reference_face = get_one_face(reference_frame, deepfuze.globals.reference_face_position) + append_reference_face(frame_processor_module.__name__, reference_face) + + +def force_download() -> None: + download_directory_path = resolve_relative_path('../../../models/deepfuze') + available_frame_processors = list_directory('deepfuze/processors/frame/modules') + model_list =\ + [ + content_analyser.MODELS, + face_analyser.MODELS, + face_masker.MODELS, + voice_extractor.MODELS + ] + + for frame_processor_module in get_frame_processors_modules(available_frame_processors): + if hasattr(frame_processor_module, 'MODELS'): + model_list.append(frame_processor_module.MODELS) + model_urls = [ models[model].get('url') for models in model_list for model in models ] + + conditional_download(download_directory_path, model_urls) + + +def process_image(start_time : float) -> None: + normed_output_path = normalize_output_path(deepfuze.globals.target_path, deepfuze.globals.output_path) + if analyse_image(deepfuze.globals.target_path): + return + # clear temp + logger.debug(wording.get('clearing_temp'), __name__.upper()) + clear_temp(deepfuze.globals.target_path) + # create temp + logger.debug(wording.get('creating_temp'), __name__.upper()) + create_temp(deepfuze.globals.target_path) + # copy image + process_manager.start() + temp_image_resolution = pack_resolution(restrict_image_resolution(deepfuze.globals.target_path, unpack_resolution(deepfuze.globals.output_image_resolution))) + logger.info(wording.get('copying_image').format(resolution = temp_image_resolution), __name__.upper()) + if copy_image(deepfuze.globals.target_path, temp_image_resolution): + logger.debug(wording.get('copying_image_succeed'), __name__.upper()) + else: + logger.error(wording.get('copying_image_failed'), __name__.upper()) + return + # process image + temp_file_path = get_temp_file_path(deepfuze.globals.target_path) + for frame_processor_module in get_frame_processors_modules(deepfuze.globals.frame_processors): + logger.info(wording.get('processing'), frame_processor_module.NAME) + frame_processor_module.process_image(deepfuze.globals.source_paths, temp_file_path, temp_file_path) + frame_processor_module.post_process() + if is_process_stopping(): + return + # finalize image + logger.info(wording.get('finalizing_image').format(resolution = deepfuze.globals.output_image_resolution), __name__.upper()) + if finalize_image(deepfuze.globals.target_path, normed_output_path, deepfuze.globals.output_image_resolution): + logger.debug(wording.get('finalizing_image_succeed'), __name__.upper()) + else: + logger.warn(wording.get('finalizing_image_skipped'), __name__.upper()) + # clear temp + logger.debug(wording.get('clearing_temp'), __name__.upper()) + clear_temp(deepfuze.globals.target_path) + # validate image + if is_image(normed_output_path): + seconds = '{:.2f}'.format((time() - start_time) % 60) + logger.info(wording.get('processing_image_succeed').format(seconds = seconds), __name__.upper()) + conditional_log_statistics() + else: + logger.error(wording.get('processing_image_failed'), __name__.upper()) + process_manager.end() + + +def process_video(start_time : float) -> None: + normed_output_path = normalize_output_path(deepfuze.globals.target_path, deepfuze.globals.output_path) + if analyse_video(deepfuze.globals.target_path, deepfuze.globals.trim_frame_start, deepfuze.globals.trim_frame_end): + return + # clear temp + logger.debug(wording.get('clearing_temp'), __name__.upper()) + clear_temp(deepfuze.globals.target_path) + # create temp + logger.debug(wording.get('creating_temp'), __name__.upper()) + create_temp(deepfuze.globals.target_path) + # extract frames + process_manager.start() + temp_video_resolution = pack_resolution(restrict_video_resolution(deepfuze.globals.target_path, unpack_resolution(deepfuze.globals.output_video_resolution))) + temp_video_fps = restrict_video_fps(deepfuze.globals.target_path, deepfuze.globals.output_video_fps) + logger.info(wording.get('extracting_frames').format(resolution = temp_video_resolution, fps = temp_video_fps), __name__.upper()) + if extract_frames(deepfuze.globals.target_path, temp_video_resolution, temp_video_fps): + logger.debug(wording.get('extracting_frames_succeed'), __name__.upper()) + else: + if is_process_stopping(): + return + logger.error(wording.get('extracting_frames_failed'), __name__.upper()) + return + # process frames + temp_frame_paths = get_temp_frame_paths(deepfuze.globals.target_path) + if temp_frame_paths: + for frame_processor_module in get_frame_processors_modules(deepfuze.globals.frame_processors): + logger.info(wording.get('processing'), frame_processor_module.NAME) + frame_processor_module.process_video(deepfuze.globals.source_paths, temp_frame_paths) + frame_processor_module.post_process() + if is_process_stopping(): + return + else: + logger.error(wording.get('temp_frames_not_found'), __name__.upper()) + return + # merge video + logger.info(wording.get('merging_video').format(resolution = deepfuze.globals.output_video_resolution, fps = deepfuze.globals.output_video_fps), __name__.upper()) + if merge_video(deepfuze.globals.target_path, deepfuze.globals.output_video_resolution, deepfuze.globals.output_video_fps): + logger.debug(wording.get('merging_video_succeed'), __name__.upper()) + else: + if is_process_stopping(): + return + logger.error(wording.get('merging_video_failed'), __name__.upper()) + return + # handle audio + if deepfuze.globals.skip_audio: + logger.info(wording.get('skipping_audio'), __name__.upper()) + move_temp(deepfuze.globals.target_path, normed_output_path) + else: + if 'lip_syncer' in deepfuze.globals.frame_processors: + source_audio_path = get_first(filter_audio_paths(deepfuze.globals.source_paths)) + if source_audio_path and replace_audio(deepfuze.globals.target_path, source_audio_path, normed_output_path): + logger.debug(wording.get('restoring_audio_succeed'), __name__.upper()) + else: + if is_process_stopping(): + return + logger.warn(wording.get('restoring_audio_skipped'), __name__.upper()) + move_temp(deepfuze.globals.target_path, normed_output_path) + else: + if restore_audio(deepfuze.globals.target_path, normed_output_path, deepfuze.globals.output_video_fps): + logger.debug(wording.get('restoring_audio_succeed'), __name__.upper()) + else: + if is_process_stopping(): + return + logger.warn(wording.get('restoring_audio_skipped'), __name__.upper()) + move_temp(deepfuze.globals.target_path, normed_output_path) + # clear temp + logger.debug(wording.get('clearing_temp'), __name__.upper()) + clear_temp(deepfuze.globals.target_path) + # validate video + if is_video(normed_output_path): + seconds = '{:.2f}'.format((time() - start_time)) + logger.info(wording.get('processing_video_succeed').format(seconds = seconds), __name__.upper()) + conditional_log_statistics() + else: + logger.error(wording.get('processing_video_failed'), __name__.upper()) + process_manager.end() + + +def is_process_stopping() -> bool: + if process_manager.is_stopping(): + process_manager.end() + logger.info(wording.get('processing_stopped'), __name__.upper()) + return process_manager.is_pending() diff --git a/deepfuze/download.py b/deepfuze/download.py new file mode 100644 index 0000000..5e53edd --- /dev/null +++ b/deepfuze/download.py @@ -0,0 +1,49 @@ +import os +import subprocess +import ssl +import urllib.request +from typing import List +from functools import lru_cache +from tqdm import tqdm + +import deepfuze.globals +from deepfuze import wording +from deepfuze.common_helper import is_macos +from deepfuze.filesystem import get_file_size, is_file + +if is_macos(): + ssl._create_default_https_context = ssl._create_unverified_context + + +def conditional_download(download_directory_path : str, urls : List[str]) -> None: + print("here..",download_directory_path) + for url in urls: + download_file_path = os.path.join(download_directory_path, os.path.basename(url)) + initial_size = get_file_size(download_file_path) + download_size = get_download_size(url) + if initial_size < download_size: + with tqdm(total = download_size, initial = initial_size, desc = wording.get('downloading'), unit = 'B', unit_scale = True, unit_divisor = 1024, ascii = ' =', disable = deepfuze.globals.log_level in [ 'warn', 'error' ]) as progress: + subprocess.Popen([ 'curl', '--create-dirs', '--silent', '--insecure', '--location', '--continue-at', '-', '--output', download_file_path, url ]) + current_size = initial_size + while current_size < download_size: + if is_file(download_file_path): + current_size = get_file_size(download_file_path) + progress.update(current_size - progress.n) + if download_size and not is_download_done(url, download_file_path): + os.remove(download_file_path) + conditional_download(download_directory_path, [ url ]) + + +@lru_cache(maxsize = None) +def get_download_size(url : str) -> int: + try: + response = urllib.request.urlopen(url, timeout = 10) + return int(response.getheader('Content-Length')) + except (OSError, ValueError): + return 0 + + +def is_download_done(url : str, file_path : str) -> bool: + if is_file(file_path): + return get_download_size(url) == get_file_size(file_path) + return False diff --git a/deepfuze/execution.py b/deepfuze/execution.py new file mode 100644 index 0000000..00843e1 --- /dev/null +++ b/deepfuze/execution.py @@ -0,0 +1,112 @@ +from typing import List, Any +from functools import lru_cache +import subprocess +import xml.etree.ElementTree as ElementTree +import onnxruntime + +from deepfuze.typing import ExecutionDevice, ValueAndUnit + + +def encode_execution_providers(execution_providers : List[str]) -> List[str]: + return [ execution_provider.replace('ExecutionProvider', '').lower() for execution_provider in execution_providers ] + + +def decode_execution_providers(execution_providers : List[str]) -> List[str]: + available_execution_providers = onnxruntime.get_available_providers() + encoded_execution_providers = encode_execution_providers(available_execution_providers) + + return [ execution_provider for execution_provider, encoded_execution_provider in zip(available_execution_providers, encoded_execution_providers) if any(execution_provider in encoded_execution_provider for execution_provider in execution_providers) ] + + +def has_execution_provider(execution_provider : str) -> bool: + return execution_provider in onnxruntime.get_available_providers() + + +def apply_execution_provider_options(execution_device_id : str, execution_providers : List[str]) -> List[Any]: + execution_providers_with_options : List[Any] = [] + + for execution_provider in execution_providers: + if execution_provider == 'CUDAExecutionProvider': + execution_providers_with_options.append((execution_provider, + { + 'device_id': execution_device_id, + 'cudnn_conv_algo_search': 'EXHAUSTIVE' if use_exhaustive() else 'DEFAULT' + })) + elif execution_provider == 'OpenVINOExecutionProvider': + execution_providers_with_options.append((execution_provider, + { + 'device_id': execution_device_id, + 'device_type': execution_device_id + '_FP32' + })) + elif execution_provider in [ 'DmlExecutionProvider', 'ROCMExecutionProvider' ]: + execution_providers_with_options.append((execution_provider, + { + 'device_id': execution_device_id + })) + else: + execution_providers_with_options.append(execution_provider) + return execution_providers_with_options + + +def use_exhaustive() -> bool: + execution_devices = detect_static_execution_devices() + product_names = ('GeForce GTX 1630', 'GeForce GTX 1650', 'GeForce GTX 1660') + + return any(execution_device.get('product').get('name').startswith(product_names) for execution_device in execution_devices) + + +def run_nvidia_smi() -> subprocess.Popen[bytes]: + commands = [ 'nvidia-smi', '--query', '--xml-format' ] + return subprocess.Popen(commands, stdout = subprocess.PIPE) + + +@lru_cache(maxsize = None) +def detect_static_execution_devices() -> List[ExecutionDevice]: + return detect_execution_devices() + + +def detect_execution_devices() -> List[ExecutionDevice]: + execution_devices : List[ExecutionDevice] = [] + try: + output, _ = run_nvidia_smi().communicate() + root_element = ElementTree.fromstring(output) + except Exception: + root_element = ElementTree.Element('xml') + + for gpu_element in root_element.findall('gpu'): + execution_devices.append( + { + 'driver_version': root_element.find('driver_version').text, + 'framework': + { + 'name': 'CUDA', + 'version': root_element.find('cuda_version').text + }, + 'product': + { + 'vendor': 'NVIDIA', + 'name': gpu_element.find('product_name').text.replace('NVIDIA ', '') + }, + 'video_memory': + { + 'total': create_value_and_unit(gpu_element.find('fb_memory_usage/total').text), + 'free': create_value_and_unit(gpu_element.find('fb_memory_usage/free').text) + }, + 'utilization': + { + 'gpu': create_value_and_unit(gpu_element.find('utilization/gpu_util').text), + 'memory': create_value_and_unit(gpu_element.find('utilization/memory_util').text) + } + }) + return execution_devices + + +def create_value_and_unit(text : str) -> ValueAndUnit: + value, unit = text.split() + value_and_unit : ValueAndUnit =\ + { + 'value': value, + 'unit': unit + } + + return value_and_unit diff --git a/deepfuze/face_analyser.py b/deepfuze/face_analyser.py new file mode 100644 index 0000000..1cc3c85 --- /dev/null +++ b/deepfuze/face_analyser.py @@ -0,0 +1,586 @@ +from typing import Any, Optional, List, Tuple +from time import sleep +import cv2 +import numpy +import onnxruntime + +import deepfuze.globals +from deepfuze import process_manager +from deepfuze.common_helper import get_first +from deepfuze.face_helper import estimate_matrix_by_face_landmark_5, warp_face_by_face_landmark_5, warp_face_by_translation, create_static_anchors, distance_to_face_landmark_5, distance_to_bounding_box, convert_face_landmark_68_to_5, apply_nms, categorize_age, categorize_gender +from deepfuze.face_store import get_static_faces, set_static_faces +from deepfuze.execution import apply_execution_provider_options +from deepfuze.download import conditional_download +from deepfuze.filesystem import resolve_relative_path, is_file +from deepfuze.thread_helper import thread_lock, thread_semaphore, conditional_thread_semaphore +from deepfuze.typing import VisionFrame, Face, FaceSet, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, ModelSet, BoundingBox, FaceLandmarkSet, FaceLandmark5, FaceLandmark68, Score, FaceScoreSet, Embedding +from deepfuze.vision import resize_frame_resolution, unpack_resolution + +FACE_ANALYSER = None +MODELS : ModelSet =\ +{ + 'face_detector_retinaface': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/retinaface_10g.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/retinaface_10g.onnx') + }, + 'face_detector_scrfd': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/scrfd_2.5g.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/scrfd_2.5g.onnx') + }, + 'face_detector_yoloface': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/yoloface_8n.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/yoloface_8n.onnx') + }, + 'face_detector_yunet': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/yunet_2023mar.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/yunet_2023mar.onnx') + }, + 'face_recognizer_arcface_blendswap': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_w600k_r50.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/arcface_w600k_r50.onnx') + }, + 'face_recognizer_arcface_inswapper': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_w600k_r50.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/arcface_w600k_r50.onnx') + }, + 'face_recognizer_arcface_simswap': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_simswap.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/arcface_simswap.onnx') + }, + 'face_recognizer_arcface_uniface': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/arcface_w600k_r50.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/arcface_w600k_r50.onnx') + }, + 'face_landmarker_68': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/2dfan4.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/2dfan4.onnx') + }, + 'face_landmarker_68_5': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/face_landmarker_68_5.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/face_landmarker_68_5.onnx') + }, + 'gender_age': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gender_age.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/gender_age.onnx') + } +} + + +def get_face_analyser() -> Any: + global FACE_ANALYSER + + face_detectors = {} + face_landmarkers = {} + + with thread_lock(): + while process_manager.is_checking(): + sleep(0.5) + if FACE_ANALYSER is None: + if deepfuze.globals.face_detector_model in [ 'many', 'retinaface' ]: + face_detectors['retinaface'] = onnxruntime.InferenceSession(MODELS.get('face_detector_retinaface').get('path'), providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + if deepfuze.globals.face_detector_model in [ 'many', 'scrfd' ]: + face_detectors['scrfd'] = onnxruntime.InferenceSession(MODELS.get('face_detector_scrfd').get('path'), providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + if deepfuze.globals.face_detector_model in [ 'many', 'yoloface' ]: + face_detectors['yoloface'] = onnxruntime.InferenceSession(MODELS.get('face_detector_yoloface').get('path'), providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + if deepfuze.globals.face_detector_model in [ 'yunet' ]: + face_detectors['yunet'] = cv2.FaceDetectorYN.create(MODELS.get('face_detector_yunet').get('path'), '', (0, 0)) + if deepfuze.globals.face_recognizer_model == 'arcface_blendswap': + face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_blendswap').get('path'), providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + if deepfuze.globals.face_recognizer_model == 'arcface_inswapper': + face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_inswapper').get('path'), providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + if deepfuze.globals.face_recognizer_model == 'arcface_simswap': + face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_simswap').get('path'), providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + if deepfuze.globals.face_recognizer_model == 'arcface_uniface': + face_recognizer = onnxruntime.InferenceSession(MODELS.get('face_recognizer_arcface_uniface').get('path'), providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + face_landmarkers['68'] = onnxruntime.InferenceSession(MODELS.get('face_landmarker_68').get('path'), providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + face_landmarkers['68_5'] = onnxruntime.InferenceSession(MODELS.get('face_landmarker_68_5').get('path'), providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + gender_age = onnxruntime.InferenceSession(MODELS.get('gender_age').get('path'), providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + FACE_ANALYSER =\ + { + 'face_detectors': face_detectors, + 'face_recognizer': face_recognizer, + 'face_landmarkers': face_landmarkers, + 'gender_age': gender_age + } + return FACE_ANALYSER + + +def clear_face_analyser() -> Any: + global FACE_ANALYSER + + FACE_ANALYSER = None + + +def pre_check() -> bool: + download_directory_path = resolve_relative_path('../../../models/deepfuze') + model_urls =\ + [ + MODELS.get('face_landmarker_68').get('url'), + MODELS.get('face_landmarker_68_5').get('url'), + MODELS.get('gender_age').get('url') + ] + model_paths =\ + [ + MODELS.get('face_landmarker_68').get('path'), + MODELS.get('face_landmarker_68_5').get('path'), + MODELS.get('gender_age').get('path') + ] + + if deepfuze.globals.face_detector_model in [ 'many', 'retinaface' ]: + model_urls.append(MODELS.get('face_detector_retinaface').get('url')) + model_paths.append(MODELS.get('face_detector_retinaface').get('path')) + if deepfuze.globals.face_detector_model in [ 'many', 'scrfd' ]: + model_urls.append(MODELS.get('face_detector_scrfd').get('url')) + model_paths.append(MODELS.get('face_detector_scrfd').get('path')) + if deepfuze.globals.face_detector_model in [ 'many', 'yoloface' ]: + model_urls.append(MODELS.get('face_detector_yoloface').get('url')) + model_paths.append(MODELS.get('face_detector_yoloface').get('path')) + if deepfuze.globals.face_detector_model in [ 'yunet' ]: + model_urls.append(MODELS.get('face_detector_yunet').get('url')) + model_paths.append(MODELS.get('face_detector_yunet').get('path')) + if deepfuze.globals.face_recognizer_model == 'arcface_blendswap': + model_urls.append(MODELS.get('face_recognizer_arcface_blendswap').get('url')) + model_paths.append(MODELS.get('face_recognizer_arcface_blendswap').get('path')) + if deepfuze.globals.face_recognizer_model == 'arcface_inswapper': + model_urls.append(MODELS.get('face_recognizer_arcface_inswapper').get('url')) + model_paths.append(MODELS.get('face_recognizer_arcface_inswapper').get('path')) + if deepfuze.globals.face_recognizer_model == 'arcface_simswap': + model_urls.append(MODELS.get('face_recognizer_arcface_simswap').get('url')) + model_paths.append(MODELS.get('face_recognizer_arcface_simswap').get('path')) + if deepfuze.globals.face_recognizer_model == 'arcface_uniface': + model_urls.append(MODELS.get('face_recognizer_arcface_uniface').get('url')) + model_paths.append(MODELS.get('face_recognizer_arcface_uniface').get('path')) + + if not deepfuze.globals.skip_download: + process_manager.check() + conditional_download(download_directory_path, model_urls) + process_manager.end() + return all(is_file(model_path) for model_path in model_paths) + + +def detect_with_retinaface(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]: + face_detector = get_face_analyser().get('face_detectors').get('retinaface') + face_detector_width, face_detector_height = unpack_resolution(face_detector_size) + temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height)) + ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0] + ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1] + feature_strides = [ 8, 16, 32 ] + feature_map_channel = 3 + anchor_total = 2 + bounding_box_list = [] + face_landmark_5_list = [] + score_list = [] + + detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size) + with thread_semaphore(): + detections = face_detector.run(None, + { + face_detector.get_inputs()[0].name: detect_vision_frame + }) + for index, feature_stride in enumerate(feature_strides): + keep_indices = numpy.where(detections[index] >= deepfuze.globals.face_detector_score)[0] + if keep_indices.any(): + stride_height = face_detector_height // feature_stride + stride_width = face_detector_width // feature_stride + anchors = create_static_anchors(feature_stride, anchor_total, stride_height, stride_width) + bounding_box_raw = detections[index + feature_map_channel] * feature_stride + face_landmark_5_raw = detections[index + feature_map_channel * 2] * feature_stride + for bounding_box in distance_to_bounding_box(anchors, bounding_box_raw)[keep_indices]: + bounding_box_list.append(numpy.array( + [ + bounding_box[0] * ratio_width, + bounding_box[1] * ratio_height, + bounding_box[2] * ratio_width, + bounding_box[3] * ratio_height + ])) + for face_landmark_5 in distance_to_face_landmark_5(anchors, face_landmark_5_raw)[keep_indices]: + face_landmark_5_list.append(face_landmark_5 * [ ratio_width, ratio_height ]) + for score in detections[index][keep_indices]: + score_list.append(score[0]) + return bounding_box_list, face_landmark_5_list, score_list + + +def detect_with_scrfd(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]: + face_detector = get_face_analyser().get('face_detectors').get('scrfd') + face_detector_width, face_detector_height = unpack_resolution(face_detector_size) + temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height)) + ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0] + ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1] + feature_strides = [ 8, 16, 32 ] + feature_map_channel = 3 + anchor_total = 2 + bounding_box_list = [] + face_landmark_5_list = [] + score_list = [] + + detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size) + with thread_semaphore(): + detections = face_detector.run(None, + { + face_detector.get_inputs()[0].name: detect_vision_frame + }) + for index, feature_stride in enumerate(feature_strides): + keep_indices = numpy.where(detections[index] >= deepfuze.globals.face_detector_score)[0] + if keep_indices.any(): + stride_height = face_detector_height // feature_stride + stride_width = face_detector_width // feature_stride + anchors = create_static_anchors(feature_stride, anchor_total, stride_height, stride_width) + bounding_box_raw = detections[index + feature_map_channel] * feature_stride + face_landmark_5_raw = detections[index + feature_map_channel * 2] * feature_stride + for bounding_box in distance_to_bounding_box(anchors, bounding_box_raw)[keep_indices]: + bounding_box_list.append(numpy.array( + [ + bounding_box[0] * ratio_width, + bounding_box[1] * ratio_height, + bounding_box[2] * ratio_width, + bounding_box[3] * ratio_height + ])) + for face_landmark_5 in distance_to_face_landmark_5(anchors, face_landmark_5_raw)[keep_indices]: + face_landmark_5_list.append(face_landmark_5 * [ ratio_width, ratio_height ]) + for score in detections[index][keep_indices]: + score_list.append(score[0]) + return bounding_box_list, face_landmark_5_list, score_list + + +def detect_with_yoloface(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]: + face_detector = get_face_analyser().get('face_detectors').get('yoloface') + face_detector_width, face_detector_height = unpack_resolution(face_detector_size) + temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height)) + ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0] + ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1] + bounding_box_list = [] + face_landmark_5_list = [] + score_list = [] + + detect_vision_frame = prepare_detect_frame(temp_vision_frame, face_detector_size) + with thread_semaphore(): + detections = face_detector.run(None, + { + face_detector.get_inputs()[0].name: detect_vision_frame + }) + detections = numpy.squeeze(detections).T + bounding_box_raw, score_raw, face_landmark_5_raw = numpy.split(detections, [ 4, 5 ], axis = 1) + keep_indices = numpy.where(score_raw > deepfuze.globals.face_detector_score)[0] + if keep_indices.any(): + bounding_box_raw, face_landmark_5_raw, score_raw = bounding_box_raw[keep_indices], face_landmark_5_raw[keep_indices], score_raw[keep_indices] + for bounding_box in bounding_box_raw: + bounding_box_list.append(numpy.array( + [ + (bounding_box[0] - bounding_box[2] / 2) * ratio_width, + (bounding_box[1] - bounding_box[3] / 2) * ratio_height, + (bounding_box[0] + bounding_box[2] / 2) * ratio_width, + (bounding_box[1] + bounding_box[3] / 2) * ratio_height + ])) + face_landmark_5_raw[:, 0::3] = (face_landmark_5_raw[:, 0::3]) * ratio_width + face_landmark_5_raw[:, 1::3] = (face_landmark_5_raw[:, 1::3]) * ratio_height + for face_landmark_5 in face_landmark_5_raw: + face_landmark_5_list.append(numpy.array(face_landmark_5.reshape(-1, 3)[:, :2])) + score_list = score_raw.ravel().tolist() + return bounding_box_list, face_landmark_5_list, score_list + + +def detect_with_yunet(vision_frame : VisionFrame, face_detector_size : str) -> Tuple[List[BoundingBox], List[FaceLandmark5], List[Score]]: + face_detector = get_face_analyser().get('face_detectors').get('yunet') + face_detector_width, face_detector_height = unpack_resolution(face_detector_size) + temp_vision_frame = resize_frame_resolution(vision_frame, (face_detector_width, face_detector_height)) + ratio_height = vision_frame.shape[0] / temp_vision_frame.shape[0] + ratio_width = vision_frame.shape[1] / temp_vision_frame.shape[1] + bounding_box_list = [] + face_landmark_5_list = [] + score_list = [] + + face_detector.setInputSize((temp_vision_frame.shape[1], temp_vision_frame.shape[0])) + face_detector.setScoreThreshold(deepfuze.globals.face_detector_score) + with thread_semaphore(): + _, detections = face_detector.detect(temp_vision_frame) + if numpy.any(detections): + for detection in detections: + bounding_box_list.append(numpy.array( + [ + detection[0] * ratio_width, + detection[1] * ratio_height, + (detection[0] + detection[2]) * ratio_width, + (detection[1] + detection[3]) * ratio_height + ])) + face_landmark_5_list.append(detection[4:14].reshape((5, 2)) * [ ratio_width, ratio_height ]) + score_list.append(detection[14]) + return bounding_box_list, face_landmark_5_list, score_list + + +def prepare_detect_frame(temp_vision_frame : VisionFrame, face_detector_size : str) -> VisionFrame: + face_detector_width, face_detector_height = unpack_resolution(face_detector_size) + detect_vision_frame = numpy.zeros((face_detector_height, face_detector_width, 3)) + detect_vision_frame[:temp_vision_frame.shape[0], :temp_vision_frame.shape[1], :] = temp_vision_frame + detect_vision_frame = (detect_vision_frame - 127.5) / 128.0 + detect_vision_frame = numpy.expand_dims(detect_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32) + return detect_vision_frame + + +def create_faces(vision_frame : VisionFrame, bounding_box_list : List[BoundingBox], face_landmark_5_list : List[FaceLandmark5], score_list : List[Score]) -> List[Face]: + faces = [] + if deepfuze.globals.face_detector_score > 0: + sort_indices = numpy.argsort(-numpy.array(score_list)) + bounding_box_list = [ bounding_box_list[index] for index in sort_indices ] + face_landmark_5_list = [face_landmark_5_list[index] for index in sort_indices] + score_list = [ score_list[index] for index in sort_indices ] + iou_threshold = 0.1 if deepfuze.globals.face_detector_model == 'many' else 0.4 + keep_indices = apply_nms(bounding_box_list, iou_threshold) + for index in keep_indices: + bounding_box = bounding_box_list[index] + face_landmark_5_68 = face_landmark_5_list[index] + face_landmark_68_5 = expand_face_landmark_68_from_5(face_landmark_5_68) + face_landmark_68 = face_landmark_68_5 + face_landmark_68_score = 0.0 + if deepfuze.globals.face_landmarker_score > 0: + face_landmark_68, face_landmark_68_score = detect_face_landmark_68(vision_frame, bounding_box) + if face_landmark_68_score > deepfuze.globals.face_landmarker_score: + face_landmark_5_68 = convert_face_landmark_68_to_5(face_landmark_68) + landmarks : FaceLandmarkSet =\ + { + '5': face_landmark_5_list[index], + '5/68': face_landmark_5_68, + '68': face_landmark_68, + '68/5': face_landmark_68_5 + } + scores : FaceScoreSet = \ + { + 'detector': score_list[index], + 'landmarker': face_landmark_68_score + } + embedding, normed_embedding = calc_embedding(vision_frame, landmarks.get('5/68')) + gender, age = detect_gender_age(vision_frame, bounding_box) + faces.append(Face( + bounding_box = bounding_box, + landmarks = landmarks, + scores = scores, + embedding = embedding, + normed_embedding = normed_embedding, + gender = gender, + age = age + )) + return faces + + +def calc_embedding(temp_vision_frame : VisionFrame, face_landmark_5 : FaceLandmark5) -> Tuple[Embedding, Embedding]: + face_recognizer = get_face_analyser().get('face_recognizer') + crop_vision_frame, matrix = warp_face_by_face_landmark_5(temp_vision_frame, face_landmark_5, 'arcface_112_v2', (112, 112)) + crop_vision_frame = crop_vision_frame / 127.5 - 1 + crop_vision_frame = crop_vision_frame[:, :, ::-1].transpose(2, 0, 1).astype(numpy.float32) + crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0) + with conditional_thread_semaphore(deepfuze.globals.execution_providers): + embedding = face_recognizer.run(None, + { + face_recognizer.get_inputs()[0].name: crop_vision_frame + })[0] + embedding = embedding.ravel() + normed_embedding = embedding / numpy.linalg.norm(embedding) + return embedding, normed_embedding + + +def detect_face_landmark_68(temp_vision_frame : VisionFrame, bounding_box : BoundingBox) -> Tuple[FaceLandmark68, Score]: + face_landmarker = get_face_analyser().get('face_landmarkers').get('68') + scale = 195 / numpy.subtract(bounding_box[2:], bounding_box[:2]).max() + translation = (256 - numpy.add(bounding_box[2:], bounding_box[:2]) * scale) * 0.5 + crop_vision_frame, affine_matrix = warp_face_by_translation(temp_vision_frame, translation, scale, (256, 256)) + crop_vision_frame = cv2.cvtColor(crop_vision_frame, cv2.COLOR_RGB2Lab) + if numpy.mean(crop_vision_frame[:, :, 0]) < 30: + crop_vision_frame[:, :, 0] = cv2.createCLAHE(clipLimit = 2).apply(crop_vision_frame[:, :, 0]) + crop_vision_frame = cv2.cvtColor(crop_vision_frame, cv2.COLOR_Lab2RGB) + crop_vision_frame = crop_vision_frame.transpose(2, 0, 1).astype(numpy.float32) / 255.0 + with conditional_thread_semaphore(deepfuze.globals.execution_providers): + face_landmark_68, face_heatmap = face_landmarker.run(None, + { + face_landmarker.get_inputs()[0].name: [ crop_vision_frame ] + }) + face_landmark_68 = face_landmark_68[:, :, :2][0] / 64 + face_landmark_68 = face_landmark_68.reshape(1, -1, 2) * 256 + face_landmark_68 = cv2.transform(face_landmark_68, cv2.invertAffineTransform(affine_matrix)) + face_landmark_68 = face_landmark_68.reshape(-1, 2) + face_landmark_68_score = numpy.amax(face_heatmap, axis = (2, 3)) + face_landmark_68_score = numpy.mean(face_landmark_68_score) + return face_landmark_68, face_landmark_68_score + + +def expand_face_landmark_68_from_5(face_landmark_5 : FaceLandmark5) -> FaceLandmark68: + face_landmarker = get_face_analyser().get('face_landmarkers').get('68_5') + affine_matrix = estimate_matrix_by_face_landmark_5(face_landmark_5, 'ffhq_512', (1, 1)) + face_landmark_5 = cv2.transform(face_landmark_5.reshape(1, -1, 2), affine_matrix).reshape(-1, 2) + with conditional_thread_semaphore(deepfuze.globals.execution_providers): + face_landmark_68_5 = face_landmarker.run(None, + { + face_landmarker.get_inputs()[0].name: [ face_landmark_5 ] + })[0][0] + face_landmark_68_5 = cv2.transform(face_landmark_68_5.reshape(1, -1, 2), cv2.invertAffineTransform(affine_matrix)).reshape(-1, 2) + return face_landmark_68_5 + + +def detect_gender_age(temp_vision_frame : VisionFrame, bounding_box : BoundingBox) -> Tuple[int, int]: + gender_age = get_face_analyser().get('gender_age') + bounding_box = bounding_box.reshape(2, -1) + scale = 64 / numpy.subtract(*bounding_box[::-1]).max() + translation = 48 - bounding_box.sum(axis = 0) * scale * 0.5 + crop_vision_frame, affine_matrix = warp_face_by_translation(temp_vision_frame, translation, scale, (96, 96)) + crop_vision_frame = crop_vision_frame[:, :, ::-1].transpose(2, 0, 1).astype(numpy.float32) + crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0) + with conditional_thread_semaphore(deepfuze.globals.execution_providers): + prediction = gender_age.run(None, + { + gender_age.get_inputs()[0].name: crop_vision_frame + })[0][0] + gender = int(numpy.argmax(prediction[:2])) + age = int(numpy.round(prediction[2] * 100)) + return gender, age + + +def get_one_face(vision_frame : VisionFrame, position : int = 0) -> Optional[Face]: + many_faces = get_many_faces(vision_frame) + if many_faces: + try: + return many_faces[position] + except IndexError: + return many_faces[-1] + return None + + +def get_average_face(vision_frames : List[VisionFrame], position : int = 0) -> Optional[Face]: + average_face = None + faces = [] + embedding_list = [] + normed_embedding_list = [] + + for vision_frame in vision_frames: + face = get_one_face(vision_frame, position) + if face: + faces.append(face) + embedding_list.append(face.embedding) + normed_embedding_list.append(face.normed_embedding) + if faces: + first_face = get_first(faces) + average_face = Face( + bounding_box = first_face.bounding_box, + landmarks = first_face.landmarks, + scores = first_face.scores, + embedding = numpy.mean(embedding_list, axis = 0), + normed_embedding = numpy.mean(normed_embedding_list, axis = 0), + gender = first_face.gender, + age = first_face.age + ) + return average_face + + +def get_many_faces(vision_frame : VisionFrame) -> List[Face]: + faces = [] + try: + faces_cache = get_static_faces(vision_frame) + if faces_cache: + faces = faces_cache + else: + bounding_box_list = [] + face_landmark_5_list = [] + score_list = [] + + if deepfuze.globals.face_detector_model in [ 'many', 'retinaface']: + bounding_box_list_retinaface, face_landmark_5_list_retinaface, score_list_retinaface = detect_with_retinaface(vision_frame, deepfuze.globals.face_detector_size) + bounding_box_list.extend(bounding_box_list_retinaface) + face_landmark_5_list.extend(face_landmark_5_list_retinaface) + score_list.extend(score_list_retinaface) + if deepfuze.globals.face_detector_model in [ 'many', 'scrfd' ]: + bounding_box_list_scrfd, face_landmark_5_list_scrfd, score_list_scrfd = detect_with_scrfd(vision_frame, deepfuze.globals.face_detector_size) + bounding_box_list.extend(bounding_box_list_scrfd) + face_landmark_5_list.extend(face_landmark_5_list_scrfd) + score_list.extend(score_list_scrfd) + if deepfuze.globals.face_detector_model in [ 'many', 'yoloface' ]: + bounding_box_list_yoloface, face_landmark_5_list_yoloface, score_list_yoloface = detect_with_yoloface(vision_frame, deepfuze.globals.face_detector_size) + bounding_box_list.extend(bounding_box_list_yoloface) + face_landmark_5_list.extend(face_landmark_5_list_yoloface) + score_list.extend(score_list_yoloface) + if deepfuze.globals.face_detector_model in [ 'yunet' ]: + bounding_box_list_yunet, face_landmark_5_list_yunet, score_list_yunet = detect_with_yunet(vision_frame, deepfuze.globals.face_detector_size) + bounding_box_list.extend(bounding_box_list_yunet) + face_landmark_5_list.extend(face_landmark_5_list_yunet) + score_list.extend(score_list_yunet) + if bounding_box_list and face_landmark_5_list and score_list: + faces = create_faces(vision_frame, bounding_box_list, face_landmark_5_list, score_list) + if faces: + set_static_faces(vision_frame, faces) + if deepfuze.globals.face_analyser_order: + faces = sort_by_order(faces, deepfuze.globals.face_analyser_order) + if deepfuze.globals.face_analyser_age: + faces = filter_by_age(faces, deepfuze.globals.face_analyser_age) + if deepfuze.globals.face_analyser_gender: + faces = filter_by_gender(faces, deepfuze.globals.face_analyser_gender) + except (AttributeError, ValueError): + pass + return faces + + +def find_similar_faces(reference_faces : FaceSet, vision_frame : VisionFrame, face_distance : float) -> List[Face]: + similar_faces : List[Face] = [] + many_faces = get_many_faces(vision_frame) + + if reference_faces: + for reference_set in reference_faces: + if not similar_faces: + for reference_face in reference_faces[reference_set]: + for face in many_faces: + if compare_faces(face, reference_face, face_distance): + similar_faces.append(face) + return similar_faces + + +def compare_faces(face : Face, reference_face : Face, face_distance : float) -> bool: + current_face_distance = calc_face_distance(face, reference_face) + return current_face_distance < face_distance + + +def calc_face_distance(face : Face, reference_face : Face) -> float: + if hasattr(face, 'normed_embedding') and hasattr(reference_face, 'normed_embedding'): + return 1 - numpy.dot(face.normed_embedding, reference_face.normed_embedding) + return 0 + + +def sort_by_order(faces : List[Face], order : FaceAnalyserOrder) -> List[Face]: + if order == 'left-right': + return sorted(faces, key = lambda face: face.bounding_box[0]) + if order == 'right-left': + return sorted(faces, key = lambda face: face.bounding_box[0], reverse = True) + if order == 'top-bottom': + return sorted(faces, key = lambda face: face.bounding_box[1]) + if order == 'bottom-top': + return sorted(faces, key = lambda face: face.bounding_box[1], reverse = True) + if order == 'small-large': + return sorted(faces, key = lambda face: (face.bounding_box[2] - face.bounding_box[0]) * (face.bounding_box[3] - face.bounding_box[1])) + if order == 'large-small': + return sorted(faces, key = lambda face: (face.bounding_box[2] - face.bounding_box[0]) * (face.bounding_box[3] - face.bounding_box[1]), reverse = True) + if order == 'best-worst': + return sorted(faces, key = lambda face: face.scores.get('detector'), reverse = True) + if order == 'worst-best': + return sorted(faces, key = lambda face: face.scores.get('detector')) + return faces + + +def filter_by_age(faces : List[Face], age : FaceAnalyserAge) -> List[Face]: + filter_faces = [] + for face in faces: + if categorize_age(face.age) == age: + filter_faces.append(face) + return filter_faces + + +def filter_by_gender(faces : List[Face], gender : FaceAnalyserGender) -> List[Face]: + filter_faces = [] + for face in faces: + if categorize_gender(face.gender) == gender: + filter_faces.append(face) + return filter_faces diff --git a/deepfuze/face_helper.py b/deepfuze/face_helper.py new file mode 100644 index 0000000..ffc8725 --- /dev/null +++ b/deepfuze/face_helper.py @@ -0,0 +1,169 @@ +from typing import Any, Tuple, List +from cv2.typing import Size +from functools import lru_cache +import cv2 +import numpy + +from deepfuze.typing import BoundingBox, FaceLandmark5, FaceLandmark68, VisionFrame, Mask, Matrix, Translation, WarpTemplate, WarpTemplateSet, FaceAnalyserAge, FaceAnalyserGender + +WARP_TEMPLATES : WarpTemplateSet =\ +{ + 'arcface_112_v1': numpy.array( + [ + [ 0.35473214, 0.45658929 ], + [ 0.64526786, 0.45658929 ], + [ 0.50000000, 0.61154464 ], + [ 0.37913393, 0.77687500 ], + [ 0.62086607, 0.77687500 ] + ]), + 'arcface_112_v2': numpy.array( + [ + [ 0.34191607, 0.46157411 ], + [ 0.65653393, 0.45983393 ], + [ 0.50022500, 0.64050536 ], + [ 0.37097589, 0.82469196 ], + [ 0.63151696, 0.82325089 ] + ]), + 'arcface_128_v2': numpy.array( + [ + [ 0.36167656, 0.40387734 ], + [ 0.63696719, 0.40235469 ], + [ 0.50019687, 0.56044219 ], + [ 0.38710391, 0.72160547 ], + [ 0.61507734, 0.72034453 ] + ]), + 'ffhq_512': numpy.array( + [ + [ 0.37691676, 0.46864664 ], + [ 0.62285697, 0.46912813 ], + [ 0.50123859, 0.61331904 ], + [ 0.39308822, 0.72541100 ], + [ 0.61150205, 0.72490465 ] + ]) +} + + +def estimate_matrix_by_face_landmark_5(face_landmark_5 : FaceLandmark5, warp_template : WarpTemplate, crop_size : Size) -> Matrix: + normed_warp_template = WARP_TEMPLATES.get(warp_template) * crop_size + affine_matrix = cv2.estimateAffinePartial2D(face_landmark_5, normed_warp_template, method = cv2.RANSAC, ransacReprojThreshold = 100)[0] + return affine_matrix + + +def warp_face_by_face_landmark_5(temp_vision_frame : VisionFrame, face_landmark_5 : FaceLandmark5, warp_template : WarpTemplate, crop_size : Size) -> Tuple[VisionFrame, Matrix]: + affine_matrix = estimate_matrix_by_face_landmark_5(face_landmark_5, warp_template, crop_size) + crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size, borderMode = cv2.BORDER_REPLICATE, flags = cv2.INTER_AREA) + return crop_vision_frame, affine_matrix + + +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) + 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]: + interpolation_method = cv2.INTER_AREA + else: + interpolation_method = cv2.INTER_LINEAR + crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size, flags = interpolation_method) + return crop_vision_frame, affine_matrix + + +def warp_face_by_translation(temp_vision_frame : VisionFrame, translation : Translation, scale : float, crop_size : Size) -> Tuple[VisionFrame, Matrix]: + affine_matrix = numpy.array([ [ scale, 0, translation[0] ], [ 0, scale, translation[1] ] ]) + crop_vision_frame = cv2.warpAffine(temp_vision_frame, affine_matrix, crop_size) + return crop_vision_frame, affine_matrix + + +def paste_back(temp_vision_frame : VisionFrame, crop_vision_frame : VisionFrame, crop_mask : Mask, affine_matrix : Matrix) -> VisionFrame: + inverse_matrix = cv2.invertAffineTransform(affine_matrix) + temp_size = temp_vision_frame.shape[:2][::-1] + inverse_mask = cv2.warpAffine(crop_mask, inverse_matrix, temp_size).clip(0, 1) + inverse_vision_frame = cv2.warpAffine(crop_vision_frame, inverse_matrix, temp_size, borderMode = cv2.BORDER_REPLICATE) + paste_vision_frame = temp_vision_frame.copy() + paste_vision_frame[:, :, 0] = inverse_mask * inverse_vision_frame[:, :, 0] + (1 - inverse_mask) * temp_vision_frame[:, :, 0] + paste_vision_frame[:, :, 1] = inverse_mask * inverse_vision_frame[:, :, 1] + (1 - inverse_mask) * temp_vision_frame[:, :, 1] + paste_vision_frame[:, :, 2] = inverse_mask * inverse_vision_frame[:, :, 2] + (1 - inverse_mask) * temp_vision_frame[:, :, 2] + return paste_vision_frame + + +@lru_cache(maxsize = None) +def create_static_anchors(feature_stride : int, anchor_total : int, stride_height : int, stride_width : int) -> numpy.ndarray[Any, Any]: + y, x = numpy.mgrid[:stride_height, :stride_width][::-1] + anchors = numpy.stack((y, x), axis = -1) + anchors = (anchors * feature_stride).reshape((-1, 2)) + anchors = numpy.stack([ anchors ] * anchor_total, axis = 1).reshape((-1, 2)) + return anchors + + +def create_bounding_box_from_face_landmark_68(face_landmark_68 : FaceLandmark68) -> BoundingBox: + min_x, min_y = numpy.min(face_landmark_68, axis = 0) + max_x, max_y = numpy.max(face_landmark_68, axis = 0) + bounding_box = numpy.array([ min_x, min_y, max_x, max_y ]).astype(numpy.int16) + return bounding_box + + +def distance_to_bounding_box(points : numpy.ndarray[Any, Any], distance : numpy.ndarray[Any, Any]) -> BoundingBox: + x1 = points[:, 0] - distance[:, 0] + y1 = points[:, 1] - distance[:, 1] + x2 = points[:, 0] + distance[:, 2] + y2 = points[:, 1] + distance[:, 3] + bounding_box = numpy.column_stack([ x1, y1, x2, y2 ]) + return bounding_box + + +def distance_to_face_landmark_5(points : numpy.ndarray[Any, Any], distance : numpy.ndarray[Any, Any]) -> FaceLandmark5: + x = points[:, 0::2] + distance[:, 0::2] + y = points[:, 1::2] + distance[:, 1::2] + face_landmark_5 = numpy.stack((x, y), axis = -1) + return face_landmark_5 + + +def convert_face_landmark_68_to_5(face_landmark_68 : FaceLandmark68) -> FaceLandmark5: + face_landmark_5 = numpy.array( + [ + numpy.mean(face_landmark_68[36:42], axis = 0), + numpy.mean(face_landmark_68[42:48], axis = 0), + face_landmark_68[30], + face_landmark_68[48], + face_landmark_68[54] + ]) + return face_landmark_5 + + +def apply_nms(bounding_box_list : List[BoundingBox], iou_threshold : float) -> List[int]: + keep_indices = [] + dimension_list = numpy.reshape(bounding_box_list, (-1, 4)) + x1 = dimension_list[:, 0] + y1 = dimension_list[:, 1] + x2 = dimension_list[:, 2] + y2 = dimension_list[:, 3] + areas = (x2 - x1 + 1) * (y2 - y1 + 1) + indices = numpy.arange(len(bounding_box_list)) + while indices.size > 0: + index = indices[0] + remain_indices = indices[1:] + keep_indices.append(index) + xx1 = numpy.maximum(x1[index], x1[remain_indices]) + yy1 = numpy.maximum(y1[index], y1[remain_indices]) + xx2 = numpy.minimum(x2[index], x2[remain_indices]) + yy2 = numpy.minimum(y2[index], y2[remain_indices]) + width = numpy.maximum(0, xx2 - xx1 + 1) + height = numpy.maximum(0, yy2 - yy1 + 1) + iou = width * height / (areas[index] + areas[remain_indices] - width * height) + indices = indices[numpy.where(iou <= iou_threshold)[0] + 1] + return keep_indices + + +def categorize_age(age : int) -> FaceAnalyserAge: + if age < 13: + return 'child' + elif age < 19: + return 'teen' + elif age < 60: + return 'adult' + return 'senior' + + +def categorize_gender(gender : int) -> FaceAnalyserGender: + if gender == 0: + return 'female' + return 'male' diff --git a/deepfuze/face_masker.py b/deepfuze/face_masker.py new file mode 100755 index 0000000..bf3c366 --- /dev/null +++ b/deepfuze/face_masker.py @@ -0,0 +1,155 @@ +from typing import Any, Dict, List +from cv2.typing import Size +from functools import lru_cache +from time import sleep +import cv2 +import numpy +import onnxruntime + +import deepfuze.globals +from deepfuze import process_manager +from deepfuze.thread_helper import thread_lock, conditional_thread_semaphore +from deepfuze.typing import FaceLandmark68, VisionFrame, Mask, Padding, FaceMaskRegion, ModelSet +from deepfuze.execution import apply_execution_provider_options +from deepfuze.filesystem import resolve_relative_path, is_file +from deepfuze.download import conditional_download + +FACE_OCCLUDER = None +FACE_PARSER = None +MODELS : ModelSet =\ +{ + 'face_occluder': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/face_occluder.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/face_occluder.onnx') + }, + 'face_parser': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/face_parser.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/face_parser.onnx') + } +} +FACE_MASK_REGIONS : Dict[FaceMaskRegion, int] =\ +{ + 'skin': 1, + 'left-eyebrow': 2, + 'right-eyebrow': 3, + 'left-eye': 4, + 'right-eye': 5, + 'glasses': 6, + 'nose': 10, + 'mouth': 11, + 'upper-lip': 12, + 'lower-lip': 13 +} + + +def get_face_occluder() -> Any: + global FACE_OCCLUDER + + with thread_lock(): + while process_manager.is_checking(): + sleep(0.5) + if FACE_OCCLUDER is None: + model_path = MODELS.get('face_occluder').get('path') + FACE_OCCLUDER = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + return FACE_OCCLUDER + + +def get_face_parser() -> Any: + global FACE_PARSER + + with thread_lock(): + while process_manager.is_checking(): + sleep(0.5) + if FACE_PARSER is None: + model_path = MODELS.get('face_parser').get('path') + FACE_PARSER = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + return FACE_PARSER + + +def clear_face_occluder() -> None: + global FACE_OCCLUDER + + FACE_OCCLUDER = None + + +def clear_face_parser() -> None: + global FACE_PARSER + + FACE_PARSER = None + + +def pre_check() -> bool: + download_directory_path = resolve_relative_path('../../../models/deepfuze') + model_urls =\ + [ + MODELS.get('face_occluder').get('url'), + MODELS.get('face_parser').get('url') + ] + model_paths =\ + [ + MODELS.get('face_occluder').get('path'), + MODELS.get('face_parser').get('path') + ] + + if not deepfuze.globals.skip_download: + process_manager.check() + conditional_download(download_directory_path, model_urls) + process_manager.end() + return all(is_file(model_path) for model_path in model_paths) + + +@lru_cache(maxsize = None) +def create_static_box_mask(crop_size : Size, face_mask_blur : float, face_mask_padding : Padding) -> Mask: + blur_amount = int(crop_size[0] * 0.5 * face_mask_blur) + blur_area = max(blur_amount // 2, 1) + box_mask : Mask = numpy.ones(crop_size, numpy.float32) + box_mask[:max(blur_area, int(crop_size[1] * face_mask_padding[0] / 100)), :] = 0 + box_mask[-max(blur_area, int(crop_size[1] * face_mask_padding[2] / 100)):, :] = 0 + box_mask[:, :max(blur_area, int(crop_size[0] * face_mask_padding[3] / 100))] = 0 + box_mask[:, -max(blur_area, int(crop_size[0] * face_mask_padding[1] / 100)):] = 0 + if blur_amount > 0: + box_mask = cv2.GaussianBlur(box_mask, (0, 0), blur_amount * 0.25) + return box_mask + + +def create_occlusion_mask(crop_vision_frame : VisionFrame) -> Mask: + face_occluder = get_face_occluder() + prepare_vision_frame = cv2.resize(crop_vision_frame, face_occluder.get_inputs()[0].shape[1:3][::-1]) + prepare_vision_frame = numpy.expand_dims(prepare_vision_frame, axis = 0).astype(numpy.float32) / 255 + prepare_vision_frame = prepare_vision_frame.transpose(0, 1, 2, 3) + with conditional_thread_semaphore(deepfuze.globals.execution_providers): + occlusion_mask : Mask = face_occluder.run(None, + { + face_occluder.get_inputs()[0].name: prepare_vision_frame + })[0][0] + occlusion_mask = occlusion_mask.transpose(0, 1, 2).clip(0, 1).astype(numpy.float32) + occlusion_mask = cv2.resize(occlusion_mask, crop_vision_frame.shape[:2][::-1]) + occlusion_mask = (cv2.GaussianBlur(occlusion_mask.clip(0, 1), (0, 0), 5).clip(0.5, 1) - 0.5) * 2 + return occlusion_mask + + +def create_region_mask(crop_vision_frame : VisionFrame, face_mask_regions : List[FaceMaskRegion]) -> Mask: + face_parser = get_face_parser() + prepare_vision_frame = cv2.flip(cv2.resize(crop_vision_frame, (512, 512)), 1) + prepare_vision_frame = numpy.expand_dims(prepare_vision_frame, axis = 0).astype(numpy.float32)[:, :, ::-1] / 127.5 - 1 + prepare_vision_frame = prepare_vision_frame.transpose(0, 3, 1, 2) + with conditional_thread_semaphore(deepfuze.globals.execution_providers): + region_mask : Mask = face_parser.run(None, + { + face_parser.get_inputs()[0].name: prepare_vision_frame + })[0][0] + region_mask = numpy.isin(region_mask.argmax(0), [ FACE_MASK_REGIONS[region] for region in face_mask_regions ]) + region_mask = cv2.resize(region_mask.astype(numpy.float32), crop_vision_frame.shape[:2][::-1]) + region_mask = (cv2.GaussianBlur(region_mask.clip(0, 1), (0, 0), 5).clip(0.5, 1) - 0.5) * 2 + return region_mask + + +def create_mouth_mask(face_landmark_68 : FaceLandmark68) -> Mask: + convex_hull = cv2.convexHull(face_landmark_68[numpy.r_[3:14, 31:36]].astype(numpy.int32)) + mouth_mask : Mask = numpy.zeros((512, 512)).astype(numpy.float32) + mouth_mask = cv2.fillConvexPoly(mouth_mask, convex_hull, 1.0) + mouth_mask = cv2.erode(mouth_mask.clip(0, 1), numpy.ones((21, 3))) + mouth_mask = cv2.GaussianBlur(mouth_mask, (0, 0), sigmaX = 1, sigmaY = 15) + return mouth_mask diff --git a/deepfuze/face_store.py b/deepfuze/face_store.py new file mode 100644 index 0000000..b05fdbf --- /dev/null +++ b/deepfuze/face_store.py @@ -0,0 +1,48 @@ +from typing import Optional, List +import hashlib +import numpy + +from deepfuze.typing import VisionFrame, Face, FaceStore, FaceSet + +FACE_STORE: FaceStore =\ +{ + 'static_faces': {}, + 'reference_faces': {} +} + + +def get_static_faces(vision_frame : VisionFrame) -> Optional[List[Face]]: + frame_hash = create_frame_hash(vision_frame) + if frame_hash in FACE_STORE['static_faces']: + return FACE_STORE['static_faces'][frame_hash] + return None + + +def set_static_faces(vision_frame : VisionFrame, faces : List[Face]) -> None: + frame_hash = create_frame_hash(vision_frame) + if frame_hash: + FACE_STORE['static_faces'][frame_hash] = faces + + +def clear_static_faces() -> None: + FACE_STORE['static_faces'] = {} + + +def create_frame_hash(vision_frame : VisionFrame) -> Optional[str]: + return hashlib.sha1(vision_frame.tobytes()).hexdigest() if numpy.any(vision_frame) else None + + +def get_reference_faces() -> Optional[FaceSet]: + if FACE_STORE['reference_faces']: + return FACE_STORE['reference_faces'] + return None + + +def append_reference_face(name : str, face : Face) -> None: + if name not in FACE_STORE['reference_faces']: + FACE_STORE['reference_faces'][name] = [] + FACE_STORE['reference_faces'][name].append(face) + + +def clear_reference_faces() -> None: + FACE_STORE['reference_faces'] = {} diff --git a/deepfuze/ffmpeg.py b/deepfuze/ffmpeg.py new file mode 100644 index 0000000..a2aebba --- /dev/null +++ b/deepfuze/ffmpeg.py @@ -0,0 +1,146 @@ +from typing import List, Optional +import os +import subprocess +import filetype + +import deepfuze.globals +from deepfuze import logger, process_manager +from deepfuze.typing import OutputVideoPreset, Fps, AudioBuffer +from deepfuze.filesystem import get_temp_frames_pattern, get_temp_file_path +from deepfuze.vision import restrict_video_fps + + +def run_ffmpeg(args : List[str]) -> bool: + commands = [ 'ffmpeg', '-hide_banner', '-loglevel', 'error' ] + commands.extend(args) + process = subprocess.Popen(commands, stderr = subprocess.PIPE, stdout = subprocess.PIPE) + + while process_manager.is_processing(): + try: + if deepfuze.globals.log_level == 'debug': + log_debug(process) + return process.wait(timeout = 0.5) == 0 + except subprocess.TimeoutExpired: + continue + return process.returncode == 0 + + +def open_ffmpeg(args : List[str]) -> subprocess.Popen[bytes]: + commands = [ 'ffmpeg', '-hide_banner', '-loglevel', 'quiet' ] + commands.extend(args) + return subprocess.Popen(commands, stdin = subprocess.PIPE, stdout = subprocess.PIPE) + + +def log_debug(process : subprocess.Popen[bytes]) -> None: + _, stderr = process.communicate() + errors = stderr.decode().split(os.linesep) + + for error in errors: + if error.strip(): + logger.debug(error.strip(), __name__.upper()) + + +def extract_frames(target_path : str, temp_video_resolution : str, temp_video_fps : Fps) -> bool: + trim_frame_start = deepfuze.globals.trim_frame_start + trim_frame_end = deepfuze.globals.trim_frame_end + temp_frames_pattern = get_temp_frames_pattern(target_path, '%04d') + commands = [ '-i', target_path, '-s', str(temp_video_resolution), '-q:v', '0' ] + + if trim_frame_start is not None and trim_frame_end is not None: + commands.extend([ '-vf', 'trim=start_frame=' + str(trim_frame_start) + ':end_frame=' + str(trim_frame_end) + ',fps=' + str(temp_video_fps) ]) + elif trim_frame_start is not None: + commands.extend([ '-vf', 'trim=start_frame=' + str(trim_frame_start) + ',fps=' + str(temp_video_fps) ]) + elif trim_frame_end is not None: + commands.extend([ '-vf', 'trim=end_frame=' + str(trim_frame_end) + ',fps=' + str(temp_video_fps) ]) + else: + commands.extend([ '-vf', 'fps=' + str(temp_video_fps) ]) + commands.extend([ '-vsync', '0', temp_frames_pattern ]) + return run_ffmpeg(commands) + + +def merge_video(target_path : str, output_video_resolution : str, output_video_fps : Fps) -> bool: + temp_video_fps = restrict_video_fps(target_path, output_video_fps) + temp_file_path = get_temp_file_path(target_path) + temp_frames_pattern = get_temp_frames_pattern(target_path, '%04d') + commands = [ '-r', str(temp_video_fps), '-i', temp_frames_pattern, '-s', str(output_video_resolution), '-c:v', deepfuze.globals.output_video_encoder ] + + if deepfuze.globals.output_video_encoder in [ 'libx264', 'libx265' ]: + output_video_compression = round(51 - (deepfuze.globals.output_video_quality * 0.51)) + commands.extend([ '-crf', str(output_video_compression), '-preset', deepfuze.globals.output_video_preset ]) + if deepfuze.globals.output_video_encoder in [ 'libvpx-vp9' ]: + output_video_compression = round(63 - (deepfuze.globals.output_video_quality * 0.63)) + commands.extend([ '-crf', str(output_video_compression) ]) + if deepfuze.globals.output_video_encoder in [ 'h264_nvenc', 'hevc_nvenc' ]: + output_video_compression = round(51 - (deepfuze.globals.output_video_quality * 0.51)) + commands.extend([ '-cq', str(output_video_compression), '-preset', map_nvenc_preset(deepfuze.globals.output_video_preset) ]) + if deepfuze.globals.output_video_encoder in [ 'h264_amf', 'hevc_amf' ]: + output_video_compression = round(51 - (deepfuze.globals.output_video_quality * 0.51)) + commands.extend([ '-qp_i', str(output_video_compression), '-qp_p', str(output_video_compression), '-quality', map_amf_preset(deepfuze.globals.output_video_preset) ]) + commands.extend([ '-vf', 'framerate=fps=' + str(output_video_fps), '-pix_fmt', 'yuv420p', '-colorspace', 'bt709', '-y', temp_file_path ]) + return run_ffmpeg(commands) + + +def copy_image(target_path : str, temp_image_resolution : str) -> bool: + temp_file_path = get_temp_file_path(target_path) + is_webp = filetype.guess_mime(target_path) == 'image/webp' + temp_image_compression = 100 if is_webp else 0 + commands = [ '-i', target_path, '-s', str(temp_image_resolution), '-q:v', str(temp_image_compression), '-y', temp_file_path ] + return run_ffmpeg(commands) + + +def finalize_image(target_path : str, output_path : str, output_image_resolution : str) -> bool: + temp_file_path = get_temp_file_path(target_path) + output_image_compression = round(31 - (deepfuze.globals.output_image_quality * 0.31)) + commands = [ '-i', temp_file_path, '-s', str(output_image_resolution), '-q:v', str(output_image_compression), '-y', output_path ] + return run_ffmpeg(commands) + + +def read_audio_buffer(target_path : str, sample_rate : int, channel_total : int) -> Optional[AudioBuffer]: + commands = [ '-i', target_path, '-vn', '-f', 's16le', '-acodec', 'pcm_s16le', '-ar', str(sample_rate), '-ac', str(channel_total), '-'] + process = open_ffmpeg(commands) + audio_buffer, _ = process.communicate() + if process.returncode == 0: + return audio_buffer + return None + + +def restore_audio(target_path : str, output_path : str, output_video_fps : Fps) -> bool: + trim_frame_start = deepfuze.globals.trim_frame_start + trim_frame_end = deepfuze.globals.trim_frame_end + temp_file_path = get_temp_file_path(target_path) + commands = [ '-i', temp_file_path ] + + if trim_frame_start is not None: + start_time = trim_frame_start / output_video_fps + commands.extend([ '-ss', str(start_time) ]) + if trim_frame_end is not None: + end_time = trim_frame_end / output_video_fps + commands.extend([ '-to', str(end_time) ]) + commands.extend([ '-i', target_path, '-c', 'copy', '-map', '0:v:0', '-map', '1:a:0', '-shortest', '-y', output_path ]) + return run_ffmpeg(commands) + + +def replace_audio(target_path : str, audio_path : str, output_path : str) -> bool: + temp_file_path = get_temp_file_path(target_path) + commands = [ '-i', temp_file_path, '-i', audio_path, '-af', 'apad', '-shortest', '-y', output_path ] + return run_ffmpeg(commands) + + +def map_nvenc_preset(output_video_preset : OutputVideoPreset) -> Optional[str]: + if output_video_preset in [ 'ultrafast', 'superfast', 'veryfast', 'faster', 'fast' ]: + return 'fast' + if output_video_preset == 'medium': + return 'medium' + if output_video_preset in [ 'slow', 'slower', 'veryslow' ]: + return 'slow' + return None + + +def map_amf_preset(output_video_preset : OutputVideoPreset) -> Optional[str]: + if output_video_preset in [ 'ultrafast', 'superfast', 'veryfast' ]: + return 'speed' + if output_video_preset in [ 'faster', 'fast', 'medium' ]: + return 'balanced' + if output_video_preset in [ 'slow', 'slower', 'veryslow' ]: + return 'quality' + return None diff --git a/deepfuze/filesystem.py b/deepfuze/filesystem.py new file mode 100644 index 0000000..ff965d6 --- /dev/null +++ b/deepfuze/filesystem.py @@ -0,0 +1,135 @@ +from typing import List, Optional +import glob +import os +import shutil +import tempfile +import filetype +from pathlib import Path + +import deepfuze.globals +from deepfuze.common_helper import is_windows + +if is_windows(): + import ctypes + + +def get_temp_frame_paths(target_path : str) -> List[str]: + temp_frames_pattern = get_temp_frames_pattern(target_path, '*') + return sorted(glob.glob(temp_frames_pattern)) + + +def get_temp_frames_pattern(target_path : str, temp_frame_prefix : str) -> str: + temp_directory_path = get_temp_directory_path(target_path) + return os.path.join(temp_directory_path, temp_frame_prefix + '.' + deepfuze.globals.temp_frame_format) + + +def get_temp_file_path(target_path : str) -> str: + _, target_extension = os.path.splitext(os.path.basename(target_path)) + temp_directory_path = get_temp_directory_path(target_path) + return os.path.join(temp_directory_path, 'temp' + target_extension) + + +def get_temp_directory_path(target_path : str) -> str: + target_name, _ = os.path.splitext(os.path.basename(target_path)) + temp_directory_path = os.path.join(tempfile.gettempdir(), 'facefusion') + return os.path.join(temp_directory_path, target_name) + + +def create_temp(target_path : str) -> None: + temp_directory_path = get_temp_directory_path(target_path) + Path(temp_directory_path).mkdir(parents = True, exist_ok = True) + + +def move_temp(target_path : str, output_path : str) -> None: + temp_file_path = get_temp_file_path(target_path) + + if is_file(temp_file_path): + if is_file(output_path): + os.remove(output_path) + shutil.move(temp_file_path, output_path) + + +def clear_temp(target_path : str) -> None: + temp_directory_path = get_temp_directory_path(target_path) + parent_directory_path = os.path.dirname(temp_directory_path) + + if not deepfuze.globals.keep_temp and is_directory(temp_directory_path): + shutil.rmtree(temp_directory_path, ignore_errors = True) + if os.path.exists(parent_directory_path) and not os.listdir(parent_directory_path): + os.rmdir(parent_directory_path) + + +def get_file_size(file_path : str) -> int: + if is_file(file_path): + return os.path.getsize(file_path) + return 0 + + +def is_file(file_path : str) -> bool: + return bool(file_path and os.path.isfile(file_path)) + + +def is_directory(directory_path : str) -> bool: + return bool(directory_path and os.path.isdir(directory_path)) + + +def is_audio(audio_path : str) -> bool: + return is_file(audio_path) and filetype.helpers.is_audio(audio_path) + + +def has_audio(audio_paths : List[str]) -> bool: + if audio_paths: + return any(is_audio(audio_path) for audio_path in audio_paths) + return False + + +def is_image(image_path : str) -> bool: + return is_file(image_path) and filetype.helpers.is_image(image_path) + + +def has_image(image_paths: List[str]) -> bool: + if image_paths: + return any(is_image(image_path) for image_path in image_paths) + return False + + +def is_video(video_path : str) -> bool: + return is_file(video_path) and filetype.helpers.is_video(video_path) + + +def filter_audio_paths(paths : List[str]) -> List[str]: + if paths: + return [ path for path in paths if is_audio(path) ] + return [] + + +def filter_image_paths(paths : List[str]) -> List[str]: + if paths: + return [ path for path in paths if is_image(path) ] + return [] + + +def resolve_relative_path(path : str) -> str: + return os.path.abspath(os.path.join(os.path.dirname(__file__), path)) + + +def list_directory(directory_path : str) -> Optional[List[str]]: + if is_directory(directory_path): + files = os.listdir(directory_path) + files = [ Path(file).stem for file in files if not Path(file).stem.startswith(('.', '__')) ] + return sorted(files) + return None + + +def sanitize_path_for_windows(full_path : str) -> Optional[str]: + buffer_size = 0 + + while True: + unicode_buffer = ctypes.create_unicode_buffer(buffer_size) + buffer_threshold = ctypes.windll.kernel32.GetShortPathNameW(full_path, unicode_buffer, buffer_size) #type:ignore[attr-defined] + + if buffer_size > buffer_threshold: + return unicode_buffer.value + if buffer_threshold == 0: + return None + buffer_size = buffer_threshold diff --git a/deepfuze/globals.py b/deepfuze/globals.py new file mode 100755 index 0000000..9b1a941 --- /dev/null +++ b/deepfuze/globals.py @@ -0,0 +1,60 @@ +from typing import List, Optional + +from deepfuze.typing import LogLevel, VideoMemoryStrategy, FaceSelectorMode, FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, FaceMaskType, FaceMaskRegion, OutputVideoEncoder, OutputVideoPreset, FaceDetectorModel, FaceRecognizerModel, TempFrameFormat, Padding + +# general +config_path : Optional[str] = None +source_paths : Optional[List[str]] = None +target_path : Optional[str] = None +output_path : Optional[str] = None +# misc +force_download : Optional[bool] = None +skip_download : Optional[bool] = None +headless : Optional[bool] = None +log_level : Optional[LogLevel] = None +# execution +execution_device_id : Optional[str] = None +execution_providers : List[str] = [] +execution_thread_count : Optional[int] = None +execution_queue_count : Optional[int] = None +# memory +video_memory_strategy : Optional[VideoMemoryStrategy] = None +system_memory_limit : Optional[int] = None +# face analyser +face_analyser_order : Optional[FaceAnalyserOrder] = None +face_analyser_age : Optional[FaceAnalyserAge] = None +face_analyser_gender : Optional[FaceAnalyserGender] = None +face_detector_model : Optional[FaceDetectorModel] = None +face_detector_size : Optional[str] = None +face_detector_score : Optional[float] = None +face_landmarker_score : Optional[float] = None +face_recognizer_model : Optional[FaceRecognizerModel] = None +# face selector +face_selector_mode : Optional[FaceSelectorMode] = None +reference_face_position : Optional[int] = None +reference_face_distance : Optional[float] = None +reference_frame_number : Optional[int] = None +# face mask +face_mask_types : Optional[List[FaceMaskType]] = None +face_mask_blur : Optional[float] = None +face_mask_padding : Optional[Padding] = None +face_mask_regions : Optional[List[FaceMaskRegion]] = None +# frame extraction +trim_frame_start : Optional[int] = None +trim_frame_end : Optional[int] = None +temp_frame_format : Optional[TempFrameFormat] = None +keep_temp : Optional[bool] = None +# output creation +output_image_quality : Optional[int] = None +output_image_resolution : Optional[str] = None +output_video_encoder : Optional[OutputVideoEncoder] = None +output_video_preset : Optional[OutputVideoPreset] = None +output_video_quality : Optional[int] = None +output_video_resolution : Optional[str] = None +output_video_fps : Optional[float] = None +skip_audio : Optional[bool] = None +# frame processors +frame_processors : List[str] = [] +# uis +open_browser : Optional[bool] = None +ui_layouts : List[str] = [] diff --git a/deepfuze/installer.py b/deepfuze/installer.py new file mode 100644 index 0000000..bb18203 --- /dev/null +++ b/deepfuze/installer.py @@ -0,0 +1,77 @@ +from typing import Dict, Tuple +import sys +import os +import tempfile +import subprocess +import inquirer +from argparse import ArgumentParser, HelpFormatter + +from deepfuze import metadata, wording +from deepfuze.common_helper import is_linux, is_macos, is_windows + +if is_macos(): + os.environ['SYSTEM_VERSION_COMPAT'] = '0' + +ONNXRUNTIMES : Dict[str, Tuple[str, str]] = {} + +if is_macos(): + ONNXRUNTIMES['default'] = ('onnxruntime', '1.17.3') +else: + ONNXRUNTIMES['default'] = ('onnxruntime', '1.17.3') + ONNXRUNTIMES['cuda-12.2'] = ('onnxruntime-gpu', '1.17.1') + ONNXRUNTIMES['cuda-11.8'] = ('onnxruntime-gpu', '1.17.1') + ONNXRUNTIMES['openvino'] = ('onnxruntime-openvino', '1.15.0') +if is_linux(): + ONNXRUNTIMES['rocm-5.4.2'] = ('onnxruntime-rocm', '1.16.3') + ONNXRUNTIMES['rocm-5.6'] = ('onnxruntime-rocm', '1.16.3') +if is_windows(): + ONNXRUNTIMES['directml'] = ('onnxruntime-directml', '1.17.3') + + +def cli() -> None: + program = ArgumentParser(formatter_class = lambda prog: HelpFormatter(prog, max_help_position = 200)) + program.add_argument('--onnxruntime', help = wording.get('help.install_dependency').format(dependency = 'onnxruntime'), choices = ONNXRUNTIMES.keys()) + program.add_argument('--skip-conda', help = wording.get('help.skip_conda'), action = 'store_true') + program.add_argument('-v', '--version', version = metadata.get('name') + ' ' + metadata.get('version'), action = 'version') + run(program) + + +def run(program : ArgumentParser) -> None: + args = program.parse_args() + python_id = 'cp' + str(sys.version_info.major) + str(sys.version_info.minor) + + if not args.skip_conda and 'CONDA_PREFIX' not in os.environ: + sys.stdout.write(wording.get('conda_not_activated') + os.linesep) + sys.exit(1) + if args.onnxruntime: + answers =\ + { + 'onnxruntime': args.onnxruntime + } + else: + answers = inquirer.prompt( + [ + inquirer.List('onnxruntime', message = wording.get('help.install_dependency').format(dependency = 'onnxruntime'), choices = list(ONNXRUNTIMES.keys())) + ]) + if answers: + onnxruntime = answers['onnxruntime'] + onnxruntime_name, onnxruntime_version = ONNXRUNTIMES[onnxruntime] + + subprocess.call([ 'pip', 'install', '-r', 'requirements.txt', '--force-reinstall' ]) + if onnxruntime == 'rocm-5.4.2' or onnxruntime == 'rocm-5.6': + if python_id in [ 'cp39', 'cp310', 'cp311' ]: + rocm_version = onnxruntime.replace('-', '') + rocm_version = rocm_version.replace('.', '') + wheel_name = 'onnxruntime_training-' + onnxruntime_version + '+' + rocm_version + '-' + python_id + '-' + python_id + '-manylinux_2_17_x86_64.manylinux2014_x86_64.whl' + wheel_path = os.path.join(tempfile.gettempdir(), wheel_name) + wheel_url = 'https://download.onnxruntime.ai/' + wheel_name + subprocess.call([ 'curl', '--silent', '--location', '--continue-at', '-', '--output', wheel_path, wheel_url ]) + subprocess.call([ 'pip', 'uninstall', wheel_path, '-y', '-q' ]) + subprocess.call([ 'pip', 'install', wheel_path, '--force-reinstall' ]) + os.remove(wheel_path) + else: + subprocess.call([ 'pip', 'uninstall', 'onnxruntime', onnxruntime_name, '-y', '-q' ]) + if onnxruntime == 'cuda-12.2': + subprocess.call([ 'pip', 'install', onnxruntime_name + '==' + onnxruntime_version, '--extra-index-url', 'https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/onnxruntime-cuda-12/pypi/simple', '--force-reinstall' ]) + else: + subprocess.call([ 'pip', 'install', onnxruntime_name + '==' + onnxruntime_version, '--force-reinstall' ]) diff --git a/deepfuze/logger.py b/deepfuze/logger.py new file mode 100644 index 0000000..7409851 --- /dev/null +++ b/deepfuze/logger.py @@ -0,0 +1,47 @@ +from typing import Dict +from logging import basicConfig, getLogger, Logger, DEBUG, INFO, WARNING, ERROR + +from deepfuze.typing import LogLevel + + +def init(log_level : LogLevel) -> None: + basicConfig(format = None) + get_package_logger().setLevel(get_log_levels()[log_level]) + + +def get_package_logger() -> Logger: + return getLogger('facefusion') + + +def debug(message : str, scope : str) -> None: + get_package_logger().debug('[' + scope + '] ' + message) + + +def info(message : str, scope : str) -> None: + get_package_logger().info('[' + scope + '] ' + message) + + +def warn(message : str, scope : str) -> None: + get_package_logger().warning('[' + scope + '] ' + message) + + +def error(message : str, scope : str) -> None: + get_package_logger().error('[' + scope + '] ' + message) + + +def enable() -> None: + get_package_logger().disabled = False + + +def disable() -> None: + get_package_logger().disabled = True + + +def get_log_levels() -> Dict[LogLevel, int]: + return\ + { + 'error': ERROR, + 'warn': WARNING, + 'info': INFO, + 'debug': DEBUG + } diff --git a/deepfuze/memory.py b/deepfuze/memory.py new file mode 100644 index 0000000..7b746be --- /dev/null +++ b/deepfuze/memory.py @@ -0,0 +1,21 @@ +from deepfuze.common_helper import is_macos, is_windows + +if is_windows(): + import ctypes +else: + import resource + + +def limit_system_memory(system_memory_limit : int = 1) -> bool: + if is_macos(): + system_memory_limit = system_memory_limit * (1024 ** 6) + else: + system_memory_limit = system_memory_limit * (1024 ** 3) + try: + if is_windows(): + ctypes.windll.kernel32.SetProcessWorkingSetSize(-1, ctypes.c_size_t(system_memory_limit), ctypes.c_size_t(system_memory_limit)) #type:ignore[attr-defined] + else: + resource.setrlimit(resource.RLIMIT_DATA, (system_memory_limit, system_memory_limit)) + return True + except Exception: + return False diff --git a/deepfuze/metadata.py b/deepfuze/metadata.py new file mode 100644 index 0000000..402cdc6 --- /dev/null +++ b/deepfuze/metadata.py @@ -0,0 +1,13 @@ +METADATA =\ +{ + 'name': 'FaceFusion', + 'description': 'Next generation face swapper and enhancer', + 'version': '2.6.0', + 'license': 'MIT', + 'author': 'Henry Ruhs', + 'url': 'https://deepfuze.io' +} + + +def get(key : str) -> str: + return METADATA[key] diff --git a/deepfuze/normalizer.py b/deepfuze/normalizer.py new file mode 100644 index 0000000..5068dce --- /dev/null +++ b/deepfuze/normalizer.py @@ -0,0 +1,39 @@ +from typing import List, Optional +import hashlib +import os + +import deepfuze.globals +from deepfuze.filesystem import is_directory +from deepfuze.typing import Padding, Fps + + +def normalize_output_path(target_path : Optional[str], output_path : Optional[str]) -> Optional[str]: + if target_path and output_path: + target_name, target_extension = os.path.splitext(os.path.basename(target_path)) + if is_directory(output_path): + output_hash = hashlib.sha1(str(deepfuze.globals.__dict__).encode('utf-8')).hexdigest()[:8] + output_name = target_name + '-' + output_hash + return os.path.join(output_path, output_name + target_extension) + output_name, output_extension = os.path.splitext(os.path.basename(output_path)) + output_directory_path = os.path.dirname(output_path) + if is_directory(output_directory_path) and output_extension: + return os.path.join(output_directory_path, output_name + target_extension) + return None + + +def normalize_padding(padding : Optional[List[int]]) -> Optional[Padding]: + if padding and len(padding) == 1: + return tuple([ padding[0] ] * 4) #type:ignore[return-value] + if padding and len(padding) == 2: + return tuple([ padding[0], padding[1], padding[0], padding[1] ]) #type:ignore[return-value] + if padding and len(padding) == 3: + return tuple([ padding[0], padding[1], padding[2], padding[1] ]) #type:ignore[return-value] + if padding and len(padding) == 4: + return tuple(padding) #type:ignore[return-value] + return None + + +def normalize_fps(fps : Optional[float]) -> Optional[Fps]: + if fps is not None: + return max(1.0, min(fps, 60.0)) + return None diff --git a/deepfuze/process_manager.py b/deepfuze/process_manager.py new file mode 100644 index 0000000..1dfa0dd --- /dev/null +++ b/deepfuze/process_manager.py @@ -0,0 +1,53 @@ +from typing import Generator, List + +from deepfuze.typing import QueuePayload, ProcessState + +PROCESS_STATE : ProcessState = 'pending' + + +def get_process_state() -> ProcessState: + return PROCESS_STATE + + +def set_process_state(process_state : ProcessState) -> None: + global PROCESS_STATE + + PROCESS_STATE = process_state + + +def is_checking() -> bool: + return get_process_state() == 'checking' + + +def is_processing() -> bool: + return get_process_state() == 'processing' + + +def is_stopping() -> bool: + return get_process_state() == 'stopping' + + +def is_pending() -> bool: + return get_process_state() == 'pending' + + +def check() -> None: + set_process_state('checking') + + +def start() -> None: + set_process_state('processing') + + +def stop() -> None: + set_process_state('stopping') + + +def end() -> None: + set_process_state('pending') + + +def manage(queue_payloads : List[QueuePayload]) -> Generator[QueuePayload, None, None]: + for query_payload in queue_payloads: + if is_processing(): + yield query_payload diff --git a/deepfuze/processors/__init__.py b/deepfuze/processors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deepfuze/processors/__pycache__/__init__.cpython-310.pyc b/deepfuze/processors/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c08eed1fa8998a65df913f4cf5f2a8a0c0b0d4d3 GIT binary patch literal 172 zcmd1j<>g`kf)B-sX(0MBh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6vXKeRZts93); zu{cBDCAB!aB)>qvJh99uC$m7exTG{CGhaU~F*!A@v^X2KczG$)edA)F%ytrVE_OiwJWj! literal 0 HcmV?d00001 diff --git a/deepfuze/processors/__pycache__/__init__.cpython-311.pyc b/deepfuze/processors/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96438c67ce132599784ac9e912e44c1251c472d3 GIT binary patch literal 194 zcmZ3^%ge<81ggmyX(0MBh=2h`DC095kTIPhg&~+hlhJP_LlF~@{~09tD^x$UIJKx) zzcR5nL*FH}IJ+djK;Jn(H?1<%Q$M-1xFkO}J}*BdwOBtbF*!A@v^Xg`kf)B-sX(0MBh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10WKeRZts93); zu{cBDCAB!aB)>qvJh99uC$m7exTG{CGhaU~F*!A@v^XW ko2nllpP83g5+AQuPTZlX-=wL5i8IxkZX$hfy4)9Mn=XD3^1aI H87Kw-0dq8n literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/__pycache__/choices.cpython-310.pyc b/deepfuze/processors/frame/__pycache__/choices.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93efcdf5bce69a60954e554a09f8a581afb2ed42 GIT binary patch literal 1736 zcmZ9MJCEZw5P&7W-dw?R!>eq+!k zDL*Mz0oDRm(ah4U3akyx(yXRg9aslgRkMa>O<-MMJ4=?!bncrOSyEDH9&v(zpc=V!bH8y4GQz$a{-}N}f8AV}| zA&!%Pjw`bJ5qtClU2cMa@?SC<%bp@XCo7zIl>f>IjSichzu+`Ik-6`(Iyd2NR0V<^04n;0sC>W?_M$>SGyh; z_QSrWcg0w&_XDjGno%!fd?Gx?>Hg-z3q4|4!~vC+fF^`;sb56oSPv9JLBzNw>mJm> zXFR5SUl05=zzLd7ot%E3)911oq%=WGpCjktF=su@9N9Xr&E7lnoawxuFXSDk0#ZH( zLus@TTF9Zw9?@f8P^=mwpnBpaI~)DhJI4plk%J%%nzix5vufb1R>gn0&|7J9NuD2qtw?_}?!y)Jgn zNMsn&m$$x}CZwIIaXOu@hQ?BJd-7N)h@R9c%MK;BLRbXU~vt zE+<3J!{L6*udYjzUwnI5JO|FL{Wc!SO14Ykmhys%F;X%7jR;vX^;jIU1g&TUQ^t#G z`PBC-t$ znf;o6UVS}@txsZmD;C1prs+;rpWqs7BKm`BflwK?jb`DUIV=p#(_h~l6-MUQ=KubR BFsA?j literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/__pycache__/choices.cpython-311.pyc b/deepfuze/processors/frame/__pycache__/choices.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..320bd4678c30699b1f08ff360eb9ce703a344448 GIT binary patch literal 2402 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 literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/__pycache__/core.cpython-311.pyc b/deepfuze/processors/frame/__pycache__/core.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a7818ec71ca8cfedf8eeeaf1a7cbc4c4da409d7 GIT binary patch literal 7281 zcmcgQTWlN0wX+XCC5n1d5~-)9BxV&$c5KIXEG4xqDNbR_j^(rt{a`8XN~TP5nb~Dz zDOAB40g?tT;(Ku~;(L*zYEif@?4tQd0He4CT=X{0=dQ4Uh(QSiDDsg%8cuEdJGx(BEjGlH6tF@f{AK5728!LK2rm z5i92+94qq?9&$b@q{N7razq>)Z5NWxlq=$5xR`XOJP{AW9Z7GhDbkc`jx@8fGwDnD zB0h$5$(D#8`nr;>skTU4sy))q>fOnXRA;1<;hv;F)fMSV1tI|sEumPq+$?)~D;y)a zNAgZWX$eV9zvLplQnMVCd~(yGD7CyTMnaMw=z}$SXtVVPx>lg;llr7K$or-C2KtVM za;MxT`6O=9Ep-9?fE0lCcH8)_`ZA2)4RnK2k8G=#dgUR;OI_bZrxXO*VX1LW%5y!P zD20G-m&DDZaQ|0S7IR^qw4c9<<(PCiFhiZYS1KVZ%8Sq^qs{@TvpXt981Y6X=Yu|##WO`j1Co) z({V+TqcWQ-p`@c3th||!WUNAGXHr>Ok}+|uC|F9Qmq^d-xnrkiqG!*YIWaRof9BkL z^z@nO*G|pMhyA2uN!Fr^bQ(GaofBB^iYMh5-d5d2X_bry6RM<1tt)_{!O(|gt0 zbxsqjBcW+@xp$q{T(z1Um*elixcA_1CDo->?mSf7;BE$Qap}Pkq%j>~(TpPei*-Ts zmAgQ;Tpd$utE=@?aEZ6`wNW=(g$AJs}}8R;gF5TD!(~l zxuw$O|2)HBWh9V7YXz4?`#L!EI%vdo;YUJx)yh}6dO3qc6Aj#cn?xP<8UEiS`cJPn zEv}&J!s}>-%TcZ5oLk!^$#P~fyC#p9uZe=yadrpBQ%v`$<0RN{C!%RZi&FhUl@+Fw ze??C>8Qh63LDgDHPE*a+Mm$hdu$D|L5TAuct!Z}BJTvD{o|&E}uB$OMrfHaT%qiL{ zwc%zk1_jh%sYqy=RkCS`RbqOqh(o3Xm>w%4fIcG5MFpo~8gWOXX{yDd#F5QpK>ax+ zc_F(*gp>+ET~&!FUr%UZk*$pc8kbe(xG5!TR~3nkMLhELxNPY=db@`p{o~@qYbxm3 ziPe~Tbz)jpuW3qV;)Ie~Tz&18iFj7kloW_klGRG(o@v;OS=fw;%C1b*c4dOGF%efV z?A0m>(G6(SyJcbG!@$BQ{MGNl#^h0nZ{k~up;0sRoWB1DdL&`&PZ*(PGqkMpJ;km) zCBzE@4CJpBy={8??z>}#_j%L%d`zy2zNLo_8UDkj|L~UorA_}!`nj0yf64GKnEr*&rR0``HzjOHnki`ppEdcc&SzQQ zcUNz&|Lp3|)Bl+^2FA^Saf6>Q`3aq$DDsZCcfY&u$764g0kOh@y1qSWizp2Bv#GDs==?B{F=_MZJVg%fMH>p z60CPn=LXb%km-%Xljy&^Gd;o|oyWUR>=OR8>)3uM{kfY1NLm{Xi)u3*^k^`{LCwMk zNCQ|&>UfSCgE24U?^v5bZ+P|429wpR1#%~ z{vXo?fdH#k6(Oxo7|T_BnNqW47Yc|vunyL3gKo*TWVc`-z;5tq9TJ*H_$UyEoou

0qQ;Rk9d7q)be^s*+Ys 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 literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/__pycache__/globals.cpython-311.pyc b/deepfuze/processors/frame/__pycache__/globals.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d035fad518d614102583b71cedea32a1715d5d7 GIT binary patch literal 1377 zcmcJOOHUI~6vxl>U7)4(0UD!7VpJY>6oFJy+<-(9#RVZTlgMOfZ(-uRa_(o88SGp(e}jFK_4N)~xs86q(v zk;LtOPq;bx+aK4y(zR&szOBa%asA0VlSj3P-5kaR=}NYVo&6OplN-LerWA}I~j z3`L}jBr`yUBT~86Z6qS&NU{SpxsYtZbpDUHi|Lr3c%w7d&%AS7-L^DiI^`GEwFX%u zy9axFgl@RR^v8tr%G%eg2BB~5CNaXKtxuZc^qtZ=)l9N#8#dJs`(`mP2TSvBbZ4u5 z(Q4B#wXnU~j3%l&j%1NZqM@o8Obs(H%w2<6O}`kFvv4m_Vx=WEqc-}xxycSYl&@wp zJb@p%7t_KmTy?_^1w(gK791)ZH9g^{+_s}zdsMu@G=btzH3@NA2ZyBUP`g1Gvni_v zv#Vh)qe+pR5|S2@5t0=mJ`g&L;*YATW?8nYiGPf#Djg9hCs16sT$&f*sE~q?u>dfa z(xM1ULdqyV;0zVccjRqGD3jY7+n3h}`{>$^ylR`R_V$L{IAE@Asutcp>zhy3@D%HK z3b}tA`SLh&a2$Ehuy-|sRh{;Erj8ePV33#eE{?-*7}!4QX(Li?32>B90y#_c`(;| zG|Qpf`Fs*T`oaO%vmVU$mX-sP-=FVJJm-MXvIoom&aKXKD0FJao81zYV=(UpiVJ!N GZ~QN8+JE5y literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/__pycache__/typings.cpython-310.pyc b/deepfuze/processors/frame/__pycache__/typings.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c7ff5d28c403b39a972a033e2ff3e4bc08b7b186 GIT binary patch literal 1571 zcmZ8h&2Hm15SC=w@}K;h?aw3V;CStI0w0QEi%kO*NPEe)2VMkPq#VMMD3H`{tZQFm zU!d=>FVU;6d+M#{0&O~!WXFyS&&)UTaX2#+#a*{+!1K4n;h!zT_@cq-13u#|{N-m5 z#2{v5#3nLh3t6VdR@8`XWXDa^j9aJ`JIFB&YLdpc25N)0L3cnmL3cs7K=(j9p!=ZP zpj~Q_4sBA0bU#_YTWA2dcY=q2`+#j=xStxp7=dR%hSXsupfRA)5lsM%f!zZ3q-0OY z^xp6;{(=q+&nlcdrl`PC(Rsg5DY;>R^6bLC#Q`lGE$^u+x<6!u@mqmoTJ(NnlJTSj zUN`S=c$N^BY-bz(m=8l(F^h0QVk~yEt9YxK3C5~`~=^V2AkrSMwF-9%82{D{*stIHB? zr&RchUGg5h$t4mpg3Y`??0|kJCcM6ZMg}Lxm@pj8E81r zB%ey5C;PZW({o(4b52^lAgxYG4_K9%R%j?wNV$W_VTH@BvPcQ+%Lgn{je8&Qhh@Z4 z9~KmoTbRTR-DKM>6+b0urX;-GLpTpAXaYmhlaj?$VX>vk|4_cfeptTJ<=P*U2MoM= zZ86sQ?yF;}(=|j7?o82>JQH<7(bt}f@-n>B!)N&+Llv5qp%=M_cPzc%KX2w$nbD-q zjP-4+YAdt+;$0LiwNK%e2@5t94ZZ7FcNAv&GG5j}TL+t3=r5IUK%mjtU=)o}+1jC` zpW%K)6{+sF@>GQ%d#aq1vu-Ni;Mh!wh5mvHSBrrbBQ3^SOhEi$h*#Ry;-B~PUnLcC zzQ^)menaI>@pN9F=}am;g!yxir)DZR9H!*3xIDpm)xW$<_eJ}=n3F7`-Z mzOmX?+dTYi3m(({&wjF-=N5SX1#b%&PoV9C)+`-sKm8Aw2<>nH literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/__pycache__/typings.cpython-311.pyc b/deepfuze/processors/frame/__pycache__/typings.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c16fe39689a23e42ca249d7c549d7e4eab4c8b4 GIT binary patch literal 2026 zcma)7O>Y}T7@pnrSDZNhSldZylC}s`7UZ~goe-f4i8e$E4O~Kt@S&@Tch*^DznIx= zYoB`H2joT%J%bQGhCTFP%_$NmZm!CyC*Ik$Q`;25#(p)=`#$r$AG729QYhpRJdae% z{5^xv9|BCD>}2u!u7uD}$Uuf};sUOM?WY7kIS6dLAXi z+MEiRxVeJ+glGE1H3-#~c)sTu#Pp~`=rG?m{XTZ})^>YleS2oz;VXTgxcZ4n_4dy0 z4BBb6dEqkFy4CK?!1k@q9$%w`L3NX4x^IUpHd=-kPHd=$J;8pgTCiNP_6gNn?LA(Z zgSzRrcKI^1oJ)Rv3ZMu*bUo9wdKR46a_CxO2trg#^Q>X?JlmtZG+F4F1{Mpf9?u!X zvkl7}@bYx^HdwrLu>eD*TAr7BHo;V<1lv|H(4TgA&Sr*1#j4q=Sbk-AaRgiST zsF>{2Hdp6y(#|}ovq0+1l1_0DD|L90`5<`)lfwayQ-yXwvCd90^#xvjf*-eS%hzE8 zuN=T6_Q^@u?-P3H`eDG}?JmN35K3Gal9&|ZYJh2<1p4FnCDzUOm5%p*>7HWXP4@D# z&^u0!X(Ff4kKoS0*?1wH2vSXWs_|vGCx&kgrwU0(jYA9C#Usl<9=uJaO_VDWWh`!6 zQd_L@jaKEEVBo_oqY5OXDUlVQec4gstjzV2_*O4li7~w?GJh=0C&Dy@*%qb==2!F@EPC^(`G671ngh&E zoBM>F1)kr$=Q-x!!C|u(!ZmVq7b;^@XJa2)J%Co5)1I3?_24|22Qu*?Hj^Q+ZGZ44 zcgOLJ&?cYLJCF|kVIcM+*s&r>(g=MRz3X#oH^Uas#o6lA+ zYFp>uN7rs&)HX&}J{YGJNgg9Guas0B_+WrO&ZGLZ|3w3}!)U7|)XFs>m`w>mFt3z! z4FthJz2gm!% YHX0(=`umHyRFO+no)yI({R~dyZvc8~C;$Ke literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/choices.py b/deepfuze/processors/frame/choices.py new file mode 100755 index 0000000..55b9940 --- /dev/null +++ b/deepfuze/processors/frame/choices.py @@ -0,0 +1,16 @@ +from typing import List + +from deepfuze.common_helper import create_int_range +from deepfuze.processors.frame.typings import FaceDebuggerItem, FaceEnhancerModel, FaceSwapperModel, FrameColorizerModel, FrameEnhancerModel, LipSyncerModel + +face_debugger_items : List[FaceDebuggerItem] = [ 'bounding-box', 'face-landmark-5', 'face-landmark-5/68', 'face-landmark-68', 'face-landmark-68/5', 'face-mask', 'face-detector-score', 'face-landmarker-score', 'age', 'gender' ] +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_swapper_models : List[FaceSwapperModel] = [ 'blendswap_256', 'inswapper_128', 'inswapper_128_fp16', 'simswap_256', 'simswap_512_unofficial', 'uniface_256' ] +frame_colorizer_models : List[FrameColorizerModel] = [ 'ddcolor', 'ddcolor_artistic', 'deoldify', 'deoldify_artistic', 'deoldify_stable' ] +frame_colorizer_sizes : List[str] = [ '192x192', '256x256', '384x384', '512x512' ] +frame_enhancer_models : List[FrameEnhancerModel] = [ 'clear_reality_x4', 'lsdir_x4', 'nomos8k_sc_x4', 'real_esrgan_x2', 'real_esrgan_x2_fp16', 'real_esrgan_x4', 'real_esrgan_x4_fp16', 'real_hatgan_x4', 'span_kendata_x4', 'ultra_sharp_x4' ] +lip_syncer_models : List[LipSyncerModel] = [ 'wav2lip_gan' ] + +face_enhancer_blend_range : List[int] = create_int_range(0, 100, 1) +frame_colorizer_blend_range : List[int] = create_int_range(0, 100, 1) +frame_enhancer_blend_range : List[int] = create_int_range(0, 100, 1) diff --git a/deepfuze/processors/frame/core.py b/deepfuze/processors/frame/core.py new file mode 100644 index 0000000..f0bec91 --- /dev/null +++ b/deepfuze/processors/frame/core.py @@ -0,0 +1,116 @@ +import os +import sys +import importlib +from concurrent.futures import ThreadPoolExecutor, as_completed +from queue import Queue +from types import ModuleType +from typing import Any, List +from tqdm import tqdm + +import deepfuze.globals +from deepfuze.typing import ProcessFrames, QueuePayload +from deepfuze.execution import encode_execution_providers +from deepfuze import logger, wording + +FRAME_PROCESSORS_MODULES : List[ModuleType] = [] +FRAME_PROCESSORS_METHODS =\ +[ + 'get_frame_processor', + 'clear_frame_processor', + 'get_options', + 'set_options', + 'register_args', + 'apply_args', + 'pre_check', + 'post_check', + 'pre_process', + 'post_process', + 'get_reference_frame', + 'process_frame', + 'process_frames', + 'process_image', + 'process_video' +] + + +def load_frame_processor_module(frame_processor : str) -> Any: + try: + frame_processor_module = importlib.import_module('deepfuze.processors.frame.modules.' + frame_processor) + for method_name in FRAME_PROCESSORS_METHODS: + if not hasattr(frame_processor_module, method_name): + raise NotImplementedError + except ModuleNotFoundError as exception: + logger.error(wording.get('frame_processor_not_loaded').format(frame_processor = frame_processor), __name__.upper()) + logger.debug(exception.msg, __name__.upper()) + sys.exit(1) + except NotImplementedError: + logger.error(wording.get('frame_processor_not_implemented').format(frame_processor = frame_processor), __name__.upper()) + sys.exit(1) + return frame_processor_module + + +def get_frame_processors_modules(frame_processors : List[str]) -> List[ModuleType]: + global FRAME_PROCESSORS_MODULES + + if not FRAME_PROCESSORS_MODULES: + for frame_processor in frame_processors: + frame_processor_module = load_frame_processor_module(frame_processor) + FRAME_PROCESSORS_MODULES.append(frame_processor_module) + return FRAME_PROCESSORS_MODULES + + +def clear_frame_processors_modules() -> None: + global FRAME_PROCESSORS_MODULES + + for frame_processor_module in get_frame_processors_modules(deepfuze.globals.frame_processors): + frame_processor_module.clear_frame_processor() + FRAME_PROCESSORS_MODULES = [] + + +def multi_process_frames(source_paths : List[str], temp_frame_paths : List[str], process_frames : ProcessFrames) -> None: + queue_payloads = create_queue_payloads(temp_frame_paths) + with tqdm(total = len(queue_payloads), desc = wording.get('processing'), unit = 'frame', ascii = ' =', disable = deepfuze.globals.log_level in [ 'warn', 'error' ]) as progress: + progress.set_postfix( + { + 'execution_providers': encode_execution_providers(deepfuze.globals.execution_providers), + 'execution_thread_count': deepfuze.globals.execution_thread_count, + 'execution_queue_count': deepfuze.globals.execution_queue_count + }) + with ThreadPoolExecutor(max_workers = deepfuze.globals.execution_thread_count) as executor: + futures = [] + queue : Queue[QueuePayload] = create_queue(queue_payloads) + queue_per_future = max(len(queue_payloads) // deepfuze.globals.execution_thread_count * deepfuze.globals.execution_queue_count, 1) + while not queue.empty(): + future = executor.submit(process_frames, source_paths, pick_queue(queue, queue_per_future), progress.update) + futures.append(future) + for future_done in as_completed(futures): + future_done.result() + + +def create_queue(queue_payloads : List[QueuePayload]) -> Queue[QueuePayload]: + queue : Queue[QueuePayload] = Queue() + for queue_payload in queue_payloads: + queue.put(queue_payload) + return queue + + +def pick_queue(queue : Queue[QueuePayload], queue_per_future : int) -> List[QueuePayload]: + queues = [] + for _ in range(queue_per_future): + if not queue.empty(): + queues.append(queue.get()) + return queues + + +def create_queue_payloads(temp_frame_paths : List[str]) -> List[QueuePayload]: + queue_payloads = [] + temp_frame_paths = sorted(temp_frame_paths, key = os.path.basename) + + for frame_number, frame_path in enumerate(temp_frame_paths): + frame_payload : QueuePayload =\ + { + 'frame_number': frame_number, + 'frame_path': frame_path + } + queue_payloads.append(frame_payload) + return queue_payloads diff --git a/deepfuze/processors/frame/globals.py b/deepfuze/processors/frame/globals.py new file mode 100755 index 0000000..a94be83 --- /dev/null +++ b/deepfuze/processors/frame/globals.py @@ -0,0 +1,14 @@ +from typing import List, Optional + +from deepfuze.processors.frame.typings import FaceDebuggerItem, FaceEnhancerModel, FaceSwapperModel, FrameColorizerModel, FrameEnhancerModel, LipSyncerModel + +face_debugger_items : Optional[List[FaceDebuggerItem]] = None +face_enhancer_model : Optional[FaceEnhancerModel] = None +face_enhancer_blend : Optional[int] = None +face_swapper_model : Optional[FaceSwapperModel] = None +frame_colorizer_model : Optional[FrameColorizerModel] = None +frame_colorizer_blend : Optional[int] = None +frame_colorizer_size : Optional[str] = None +frame_enhancer_model : Optional[FrameEnhancerModel] = None +frame_enhancer_blend : Optional[int] = None +lip_syncer_model : Optional[LipSyncerModel] = None diff --git a/deepfuze/processors/frame/modules/__init__.py b/deepfuze/processors/frame/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deepfuze/processors/frame/modules/__pycache__/__init__.cpython-310.pyc b/deepfuze/processors/frame/modules/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a49965b969a7ab6fa3b9f87a27602c61a720aa9 GIT binary patch literal 186 zcmd1j<>g`kf)B-sX(0MBh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10YKeRZts93); zu{cBDCAB!aB)>qvJh99uC$m7exTG{CGhaU~F*!A@v^XW oo2s9ipHiBWTC5)*pP83g5+AQuP3;sY}yBjX1K K7*WIw6axSPPdB&# literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/modules/__pycache__/face_debugger.cpython-310.pyc b/deepfuze/processors/frame/modules/__pycache__/face_debugger.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48289995abb9d29d019517794aae60ec1761b290 GIT binary patch literal 7746 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 a)B2n~j&CtpM2m5L?)JF8G<9-xDgS@q-2o;5 literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/modules/__pycache__/face_debugger.cpython-311.pyc b/deepfuze/processors/frame/modules/__pycache__/face_debugger.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d726eef235b39e90f45c4cf7acfbb22d5db3edbc GIT binary patch literal 15566 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 literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/modules/__pycache__/face_enhancer.cpython-310.pyc b/deepfuze/processors/frame/modules/__pycache__/face_enhancer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0bc8d9a9e3b86d43e642e1783832f92a68d942d GIT binary patch literal 10669 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�uCpm8@gc_y7dd|UM>ZP;eX^m5E&a( zP@vYsa57BoOLqc|FJd@_!F;_;VmGsv7;b>&6e`D|0y9*SZw-NLOLrIJC~WBBl^ye{ zOB{*L-nB*>HC#*H69Dq=L>%61ggaIF_?jlFkvyJ>>WKqk#Mq!XZO1-=H4W zFY)*2=_EPK=cA@b@gyCISRwB|&6~uBZ0R8#*2=7hD#dX4+~|=1LbKC_*zeX~HPL^W_juh2>Xp}w$+9;v1`_GxjWGn4Y z7yNC*+0(T~|Nl*JG^)nCFqmfkO2)Unr>`BG$T+C)-fQv?6bWJc$%eht9bI!Lhq;HS zri7$?gbH#i<}IBUo0Bh^h_tac=;G`_2(($wQ!8|X+wA|&ySLirppdl=o1$%(|+shK9)7O8lR z=FgN$~V#LUZvu7D(FiACx1D!s;tFY z&F4%ZC$A)BOgLQ-TT^o8*;$R9IP!|vDT!^DiF`$@FiDH>W$e1Te2F=F%bbLf##gDA zadWls$0&Adu}$P?&6w2bHwN2CQ2GtSa~uF{7|c41&-FqfOijZBOTjie{CyX_Hc|M>2q{rUd~KuUXp literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/modules/__pycache__/face_enhancer.cpython-311.pyc b/deepfuze/processors/frame/modules/__pycache__/face_enhancer.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25adbdcc1c97e9def8ded1e1b52ce7151f9d6d03 GIT binary patch literal 18907 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 literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/modules/__pycache__/face_swapper.cpython-310.pyc b/deepfuze/processors/frame/modules/__pycache__/face_swapper.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..59e522c0575974dce1544345dc935dfdaf59ef9e GIT binary patch literal 12585 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 G%l{2Vv`Y&B literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/modules/__pycache__/face_swapper.cpython-311.pyc b/deepfuze/processors/frame/modules/__pycache__/face_swapper.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9262bfdc98f63c2688ea0958e4e2fa7abfaf3c56 GIT binary patch literal 23595 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 U!w1;%OYFxm9q4{Bq+<5}18VtwmjD0& literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/modules/__pycache__/frame_colorizer.cpython-310.pyc b/deepfuze/processors/frame/modules/__pycache__/frame_colorizer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..54ed46ae8f7ddfa42d214001c0f81f2f6b6df20e GIT binary patch literal 9273 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 literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/modules/__pycache__/frame_colorizer.cpython-311.pyc b/deepfuze/processors/frame/modules/__pycache__/frame_colorizer.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4de53d74736b8fecdd656135b5a3d090bd319a9a GIT binary patch literal 17373 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 A2><{9 literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/modules/__pycache__/frame_enhancer.cpython-310.pyc b/deepfuze/processors/frame/modules/__pycache__/frame_enhancer.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df8ddbc0c4e4e002031fc15b53c84381659fa719 GIT binary patch literal 9654 zcmcIqS#KQ2cJ6I@hBMq3iIilC+BLRGO0q1=mb_^7l`T;VwOqHQ+i6V|$sxOEM%6u% zNDLDvyv_y~2C^UtVgzIYBvDob$cDTm`44%jyd=m=1mq!k3vS}8e5bl+rf0+(y~sm! z^{J{;Ri{p!I$N{a*O%Au`_Isu|BEA<_HR^4|N2q+2v7LCrl#H2n8tKp3v@>h495se z#|$jT(y86>GlA{c3O4<0kaKbhw)`Ha2mO|xck=38a0=?(>-4F2e=uNZx)uzoa>yB0 z<1&6{4)#|5~uy*{$Hb zzbBY{@{RfAUNn83=TPmg2T??;D~c1C^|)j)9b$;yy3i|;6DG& zV9J>ark&|v#+eC@I!9G|zke(^?i>%^a^6z)0slnsw)1vy(m5HtL*HGWh_9*;G)c2<9`%vGn>hXLBP(PTeA42_bs(u9ZBI?ccUq}6oRQ=6w z^3Fwmm|tR3XNdBTxy6r6XsA!4KEw5SgB@kZerP%0V+-s!dkfHIc7mJi?TNVGVkg-< zX#J$sdW!38hFSAAdzZb3o=@3nZmSlHonh~zvY>Sf71F~>NYNAEpm&1uEOXLNgumNd^+R-|>M z97ZyGr5thL`7(E{7L}_N&oAn-|EyT32fPwp_e97AS~8)}d2JSB?NYTeUtW*{HBl|` zFmwa2;w^9?ZNIue@9g8MVCBjJMvatw?g@9^D{&WC{z^RAwzwG+MOY))wiA^%$|Q1Ue$_20Zc}c>+w!mP2>G>~lGWnXCRI?h5XE zQ5gUcFLUTG2VkRuXvbA-Xf(YCR()Kd`Q;#Rn-u^RUd+h6+IT#}kb=Vq)0dJ-3y%d( zIjIZFs1%rx>OBj=+x)y#NHG*<%J=S!WnFzdm#^Jnmd^Zh`-@XEE?E?UyfGX zCnu(>mCBRV!_(6<)5*~P53y)I`vM~VEJuZfLcETGB)%?uX(?V?Aw&lPUGjXsdgql9 z?uV=_S_JQTk>E~*&tKWXm1)tCPfsV`Np;+)L?vvy3ksW-I zhAq%=V6g7mvq3tmBAuFnV%7VONOR|F$4_xjCi=P%C9UArj@3p{H2H_}8!7Rcy{dL=5uEg8IAndgGT2G7A6 zfjJcvcgcL)zKTQCFw*v#7=Hw_?3P)&u}z;Y^NMukdpy;|rx^aD(#&mmXW`6>7cS0R z;Nfyqt<6049v%0~wW%qx>m{qeBGva-qi!&(XcozpkH*u-tXeZ0uSi&w~5Vmxk zCZMy53)+pK`>}#2q)0ty=1t?d@jP#-cA0tP`E_2RZ8d)cw>Hp=N<)vdhKA4#oD;KX zz(Pc6(TLZZ1KhQ!EWm|p3hWQjuNc>Bid%qX7Cc=5b6?pI^AT0>hxwty}(GdA_e z+_0F*tW6`zEZHpc6|x2j!&VqsU}S-lV>#BNaB!i4*@L(<|CP!rfYXK9)ooK#ih)y2 znVV7!T%B0tertrv~EUb?74 zlg6o|kefTiRV!Mlad8)*T2yA6u|+}ldW^YV60nH^p^(4`;Syb7Yf2`mZ;x=jRhimL1rd;t+BVmS~S{xUE^^3eC` zM|JT4K!?DPoYF(`t&~GU+t4<(HNBxP;i9?pHt{ywa%;(ajlxwmy&xeIH&DEArxdvm zZ=z9JL`yfN`8W!tuCHl*8wIJRUR}%sEa7SW=rXk~6x~p01Q;fm9$gtCLm47SdQ!7s zmW;?;vX(O8fymy-Zjxz%k!t7*`nmzw-sn+n`34M`GC8Y5FZdx!sfahQUTl8~Vkg1G zuK4snCP>+ZzHqr*YekGgS*46JNn$sUqzPdaI{-nxw>QHv7r_iKMOCp92dF)B?S+@aJDch|Wi5gjQ zrCgV+@NkqNR;?R=z`Q7jey~)%Gn@K$*!oQ{k=b)vln0(3o^Iih8X^2HJeC8Kdtx=+ z=BV5r@3_T~N;Oi^wjAV7O1>8$yheT^@Wk>Zxh=+}2;U_C(yF#Q0N>QA&aI4T@lofF!+)5sa3dp`B zBHG}d1W|Dxcx}mzr@U3{735xs_LV5avnbIPuTdWzgs4$bph79ohXf6vfIpT>H0dIw zj;waRX3qYq?9Pz;V#k<;N*3SxjFx!a;H?02|=& zNR(M8bv`S!vG^In9Ex~Q z!#R=@8+(Bra!Je3IVgSbV*upyz%~1V z+6KBjD$$}J8Cnv6MP6Q5jAUL_9+w&NpTlb6v}A+`sF2HzyJ6T_b6BQaVf+bXiIi=6 zgdC0&o9VWcz$#hoOB};k`u-8*hQuse*{WfEP#;9>VMC9S8*P~Ox4WMAXFehk*2S;V z=7y5l+l=TCVLFy99wox0xg%{!hkC@{mqxo}`j_TBVg&~8;45|9B;L$o8D?)`pBahurM5=lH4fN4#lyPs-sl6KjZ{rGjMR*>e;~2WN2fh_(v+39=dt8ZiJn zaiuFE?kbnI7m_V>kE5snG@PG zxoQkXTzT{s46crSWJc9Kuun3;2KEOJ2<_dRv187(uK=+$7X05JNbU7E2_jFUXp7&* zOJ>7|bfK@hd1gf+k{e z>r}}!AacREGGnJUu0bVXH{_I%!p3!`H_mq6*u=@Aj~o`G0WLI*aSf(3lkCl!RhxOn zXieHBHgkhuYLI5>(Q(R*P_kP59JLhS=igOVhqI!o?wTT1;dgKDmoVaA@u;)whXL9~ z+oM)QZ`Ha$c7XB+xRJ`9s!ECW+58bqM4iW@{PdQit(F1-zmFdA2UL7PMJIRTNMG&O(}x=MeJ7Lyb@iQytiAeB z)WJ8B6AOY*Gv4Z>Bz}=zb z*29c4=hiH;J#oGQ1jSpTdw}4u2QO(SpWr4@Q5z8Zs5dMAlp4qz5r2+C_H}FoN;f|H zgoU(OHMbl|+?_OUyLs5(!Nc&sFn>6T0xm!P4BdhXwUCK`>ilo?zhBWqd^WT6V+yY*r66kQi=SQq|#B7HxU_|WL$oi`~Ng zsj1p$lpI~Ww@@6!m=2D2W2($TY^@H{{P(FiheGCDmmEpgh3Tyk7avlWN_!5tI8%hj zDsm28H_nJ&rG84e#(wNAf*w#Yk3w1xs#Ra54pcNND1=hMgi`X#8HtTHnKorqVhwI0 zf{E1*U1W7TQg=z|1M+cX?hN81B&+c+H@HjxfMSQ%n`A7Y$c%A>F;u6&LHz*AalCgW zEf%sHM#hG^gTnh?BllfF*5Bu!4_@v!^S#^b9fe_gIMZ+U_YU{v^TR#EBg0v=W{0hP Knccnndj1b1taZKs literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/modules/__pycache__/frame_enhancer.cpython-311.pyc b/deepfuze/processors/frame/modules/__pycache__/frame_enhancer.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e04369bd44f63788248e6e1327a85b13716935a GIT binary patch literal 16847 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 literal 0 HcmV?d00001 diff --git a/deepfuze/processors/frame/modules/__pycache__/lip_syncer.cpython-311.pyc b/deepfuze/processors/frame/modules/__pycache__/lip_syncer.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f179b2a18123197433190e0398c4960fe0e59ec GIT binary patch literal 19083 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 None: + pass + + +def clear_frame_processor() -> None: + pass + + +def get_options(key : Literal['model']) -> None: + pass + + +def set_options(key : Literal['model'], value : Any) -> None: + pass + + +def register_args(program : ArgumentParser) -> None: + program.add_argument('--face-debugger-items', help = wording.get('help.face_debugger_items').format(choices = ', '.join(frame_processors_choices.face_debugger_items)), default = config.get_str_list('frame_processors.face_debugger_items', 'face-landmark-5/68 face-mask'), choices = frame_processors_choices.face_debugger_items, nargs = '+', metavar = 'FACE_DEBUGGER_ITEMS') + + +def apply_args(program : ArgumentParser) -> None: + args = program.parse_args() + frame_processors_globals.face_debugger_items = args.face_debugger_items + + +def pre_check() -> bool: + return True + + +def post_check() -> bool: + return True + + +def pre_process(mode : ProcessMode) -> bool: + return True + + +def post_process() -> None: + read_static_image.cache_clear() + if deepfuze.globals.video_memory_strategy == 'strict' or deepfuze.globals.video_memory_strategy == 'moderate': + clear_frame_processor() + if deepfuze.globals.video_memory_strategy == 'strict': + clear_face_analyser() + clear_content_analyser() + clear_face_occluder() + clear_face_parser() + + +def debug_face(target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame: + primary_color = (0, 0, 255) + secondary_color = (0, 255, 0) + tertiary_color = (255, 255, 0) + bounding_box = target_face.bounding_box.astype(numpy.int32) + temp_vision_frame = temp_vision_frame.copy() + has_face_landmark_5_fallback = numpy.array_equal(target_face.landmarks.get('5'), target_face.landmarks.get('5/68')) + has_face_landmark_68_fallback = numpy.array_equal(target_face.landmarks.get('68'), target_face.landmarks.get('68/5')) + + if 'bounding-box' in frame_processors_globals.face_debugger_items: + cv2.rectangle(temp_vision_frame, (bounding_box[0], bounding_box[1]), (bounding_box[2], bounding_box[3]), primary_color, 2) + if 'face-mask' in frame_processors_globals.face_debugger_items: + crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), 'arcface_128_v2', (512, 512)) + inverse_matrix = cv2.invertAffineTransform(affine_matrix) + temp_size = temp_vision_frame.shape[:2][::-1] + crop_mask_list = [] + if 'box' in deepfuze.globals.face_mask_types: + box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], 0, deepfuze.globals.face_mask_padding) + crop_mask_list.append(box_mask) + if 'occlusion' in deepfuze.globals.face_mask_types: + occlusion_mask = create_occlusion_mask(crop_vision_frame) + crop_mask_list.append(occlusion_mask) + if 'region' in deepfuze.globals.face_mask_types: + region_mask = create_region_mask(crop_vision_frame, deepfuze.globals.face_mask_regions) + crop_mask_list.append(region_mask) + crop_mask = numpy.minimum.reduce(crop_mask_list).clip(0, 1) + crop_mask = (crop_mask * 255).astype(numpy.uint8) + 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_vision_frame[inverse_vision_frame > 0] = 255 + inverse_contours = cv2.findContours(inverse_vision_frame, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)[0] + cv2.drawContours(temp_vision_frame, inverse_contours, -1, tertiary_color if has_face_landmark_5_fallback else secondary_color, 2) + if 'face-landmark-5' in frame_processors_globals.face_debugger_items and numpy.any(target_face.landmarks.get('5')): + face_landmark_5 = target_face.landmarks.get('5').astype(numpy.int32) + for index in range(face_landmark_5.shape[0]): + cv2.circle(temp_vision_frame, (face_landmark_5[index][0], face_landmark_5[index][1]), 3, primary_color, -1) + if 'face-landmark-5/68' in frame_processors_globals.face_debugger_items and numpy.any(target_face.landmarks.get('5/68')): + face_landmark_5_68 = target_face.landmarks.get('5/68').astype(numpy.int32) + for index in range(face_landmark_5_68.shape[0]): + cv2.circle(temp_vision_frame, (face_landmark_5_68[index][0], face_landmark_5_68[index][1]), 3, tertiary_color if has_face_landmark_5_fallback else secondary_color, -1) + if 'face-landmark-68' in frame_processors_globals.face_debugger_items and numpy.any(target_face.landmarks.get('68')): + face_landmark_68 = target_face.landmarks.get('68').astype(numpy.int32) + for index in range(face_landmark_68.shape[0]): + cv2.circle(temp_vision_frame, (face_landmark_68[index][0], face_landmark_68[index][1]), 3, tertiary_color if has_face_landmark_68_fallback else secondary_color, -1) + if 'face-landmark-68/5' in frame_processors_globals.face_debugger_items and numpy.any(target_face.landmarks.get('68')): + face_landmark_68 = target_face.landmarks.get('68/5').astype(numpy.int32) + for index in range(face_landmark_68.shape[0]): + cv2.circle(temp_vision_frame, (face_landmark_68[index][0], face_landmark_68[index][1]), 3, primary_color, -1) + if bounding_box[3] - bounding_box[1] > 50 and bounding_box[2] - bounding_box[0] > 50: + top = bounding_box[1] + left = bounding_box[0] - 20 + if 'face-detector-score' in frame_processors_globals.face_debugger_items: + face_score_text = str(round(target_face.scores.get('detector'), 2)) + top = top + 20 + cv2.putText(temp_vision_frame, face_score_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2) + if 'face-landmarker-score' in frame_processors_globals.face_debugger_items: + face_score_text = str(round(target_face.scores.get('landmarker'), 2)) + top = top + 20 + cv2.putText(temp_vision_frame, face_score_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, tertiary_color if has_face_landmark_5_fallback else secondary_color, 2) + if 'age' in frame_processors_globals.face_debugger_items: + face_age_text = categorize_age(target_face.age) + top = top + 20 + cv2.putText(temp_vision_frame, face_age_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2) + if 'gender' in frame_processors_globals.face_debugger_items: + face_gender_text = categorize_gender(target_face.gender) + top = top + 20 + cv2.putText(temp_vision_frame, face_gender_text, (left, top), cv2.FONT_HERSHEY_SIMPLEX, 0.5, primary_color, 2) + return temp_vision_frame + + +def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame: + pass + + +def process_frame(inputs : FaceDebuggerInputs) -> VisionFrame: + reference_faces = inputs.get('reference_faces') + target_vision_frame = inputs.get('target_vision_frame') + + if deepfuze.globals.face_selector_mode == 'many': + many_faces = get_many_faces(target_vision_frame) + if many_faces: + for target_face in many_faces: + target_vision_frame = debug_face(target_face, target_vision_frame) + if deepfuze.globals.face_selector_mode == 'one': + target_face = get_one_face(target_vision_frame) + if target_face: + target_vision_frame = debug_face(target_face, target_vision_frame) + if deepfuze.globals.face_selector_mode == 'reference': + similar_faces = find_similar_faces(reference_faces, target_vision_frame, deepfuze.globals.reference_face_distance) + if similar_faces: + for similar_face in similar_faces: + target_vision_frame = debug_face(similar_face, target_vision_frame) + return target_vision_frame + + +def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None: + reference_faces = get_reference_faces() if 'reference' in deepfuze.globals.face_selector_mode else None + + for queue_payload in process_manager.manage(queue_payloads): + target_vision_path = queue_payload['frame_path'] + target_vision_frame = read_image(target_vision_path) + output_vision_frame = process_frame( + { + 'reference_faces': reference_faces, + 'target_vision_frame': target_vision_frame + }) + write_image(target_vision_path, output_vision_frame) + update_progress(1) + + +def process_image(source_paths : List[str], target_path : str, output_path : str) -> None: + reference_faces = get_reference_faces() if 'reference' in deepfuze.globals.face_selector_mode else None + target_vision_frame = read_static_image(target_path) + output_vision_frame = process_frame( + { + 'reference_faces': reference_faces, + 'target_vision_frame': target_vision_frame + }) + write_image(output_path, output_vision_frame) + + +def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None: + frame_processors.multi_process_frames(source_paths, temp_frame_paths, process_frames) diff --git a/deepfuze/processors/frame/modules/face_enhancer.py b/deepfuze/processors/frame/modules/face_enhancer.py new file mode 100755 index 0000000..de58d77 --- /dev/null +++ b/deepfuze/processors/frame/modules/face_enhancer.py @@ -0,0 +1,301 @@ +from typing import Any, List, Literal, Optional +from argparse import ArgumentParser +from time import sleep +import cv2 +import numpy +import onnxruntime + +import deepfuze.globals +import deepfuze.processors.frame.core as frame_processors +from deepfuze import config, process_manager, logger, wording +from deepfuze.face_analyser import get_many_faces, clear_face_analyser, find_similar_faces, get_one_face +from deepfuze.face_masker import create_static_box_mask, create_occlusion_mask, clear_face_occluder +from deepfuze.face_helper import warp_face_by_face_landmark_5, paste_back +from deepfuze.execution import apply_execution_provider_options +from deepfuze.content_analyser import clear_content_analyser +from deepfuze.face_store import get_reference_faces +from deepfuze.normalizer import normalize_output_path +from deepfuze.thread_helper import thread_lock, thread_semaphore +from deepfuze.typing import Face, VisionFrame, UpdateProgress, ProcessMode, ModelSet, OptionsWithModel, QueuePayload +from deepfuze.common_helper import create_metavar +from deepfuze.filesystem import is_file, is_image, is_video, resolve_relative_path +from deepfuze.download import conditional_download, is_download_done +from deepfuze.vision import read_image, read_static_image, write_image +from deepfuze.processors.frame.typings import FaceEnhancerInputs +from deepfuze.processors.frame import globals as frame_processors_globals +from deepfuze.processors.frame import choices as frame_processors_choices + +FRAME_PROCESSOR = None +NAME = __name__.upper() +MODELS : ModelSet =\ +{ + 'codeformer': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/codeformer.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/codeformer.onnx'), + 'template': 'ffhq_512', + 'size': (512, 512) + }, + 'gfpgan_1.2': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.2.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/gfpgan_1.2.onnx'), + 'template': 'ffhq_512', + 'size': (512, 512) + }, + 'gfpgan_1.3': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.3.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/gfpgan_1.3.onnx'), + 'template': 'ffhq_512', + 'size': (512, 512) + }, + 'gfpgan_1.4': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gfpgan_1.4.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/gfpgan_1.4.onnx'), + 'template': 'ffhq_512', + 'size': (512, 512) + }, + 'gpen_bfr_256': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_256.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/gpen_bfr_256.onnx'), + 'template': 'arcface_128_v2', + 'size': (256, 256) + }, + 'gpen_bfr_512': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_512.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/gpen_bfr_512.onnx'), + 'template': 'ffhq_512', + 'size': (512, 512) + }, + 'gpen_bfr_1024': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_1024.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/gpen_bfr_1024.onnx'), + 'template': 'ffhq_512', + 'size': (1024, 1024) + }, + 'gpen_bfr_2048': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/gpen_bfr_2048.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/gpen_bfr_2048.onnx'), + 'template': 'ffhq_512', + 'size': (2048, 2048) + }, + 'restoreformer_plus_plus': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/restoreformer_plus_plus.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/restoreformer_plus_plus.onnx'), + 'template': 'ffhq_512', + 'size': (512, 512) + } +} +OPTIONS : Optional[OptionsWithModel] = None + + +def get_frame_processor() -> Any: + global FRAME_PROCESSOR + + with thread_lock(): + while process_manager.is_checking(): + sleep(0.5) + if FRAME_PROCESSOR is None: + model_path = get_options('model').get('path') + FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + return FRAME_PROCESSOR + + +def clear_frame_processor() -> None: + global FRAME_PROCESSOR + + FRAME_PROCESSOR = None + + +def get_options(key : Literal['model']) -> Any: + global OPTIONS + + if OPTIONS is None: + OPTIONS =\ + { + 'model': MODELS[frame_processors_globals.face_enhancer_model] + } + return OPTIONS.get(key) + + +def set_options(key : Literal['model'], value : Any) -> None: + global OPTIONS + + OPTIONS[key] = value + + +def register_args(program : ArgumentParser) -> None: + program.add_argument('--face-enhancer-model', help = wording.get('help.face_enhancer_model'), default = config.get_str_value('frame_processors.face_enhancer_model', 'gfpgan_1.4'), choices = frame_processors_choices.face_enhancer_models) + program.add_argument('--face-enhancer-blend', help = wording.get('help.face_enhancer_blend'), type = int, default = config.get_int_value('frame_processors.face_enhancer_blend', '80'), choices = frame_processors_choices.face_enhancer_blend_range, metavar = create_metavar(frame_processors_choices.face_enhancer_blend_range)) + + +def apply_args(program : ArgumentParser) -> None: + args = program.parse_args() + frame_processors_globals.face_enhancer_model = args.face_enhancer_model + frame_processors_globals.face_enhancer_blend = args.face_enhancer_blend + + +def pre_check() -> bool: + download_directory_path = resolve_relative_path('../../../models/deepfuze') + model_url = get_options('model').get('url') + model_path = get_options('model').get('path') + + if not deepfuze.globals.skip_download: + process_manager.check() + conditional_download(download_directory_path, [ model_url ]) + process_manager.end() + return is_file(model_path) + + +def post_check() -> bool: + model_url = get_options('model').get('url') + model_path = get_options('model').get('path') + + if not deepfuze.globals.skip_download and not is_download_done(model_url, model_path): + logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME) + return False + if not is_file(model_path): + logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME) + return False + return True + + +def pre_process(mode : ProcessMode) -> bool: + if mode in [ 'output', 'preview' ] and not is_image(deepfuze.globals.target_path) and not is_video(deepfuze.globals.target_path): + logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME) + return False + if mode == 'output' and not normalize_output_path(deepfuze.globals.target_path, deepfuze.globals.output_path): + logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME) + return False + return True + + +def post_process() -> None: + read_static_image.cache_clear() + if deepfuze.globals.video_memory_strategy == 'strict' or deepfuze.globals.video_memory_strategy == 'moderate': + clear_frame_processor() + if deepfuze.globals.video_memory_strategy == 'strict': + clear_face_analyser() + clear_content_analyser() + clear_face_occluder() + + +def enhance_face(target_face: Face, temp_vision_frame : VisionFrame) -> VisionFrame: + model_template = get_options('model').get('template') + model_size = get_options('model').get('size') + crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), model_template, model_size) + box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], deepfuze.globals.face_mask_blur, (0, 0, 0, 0)) + crop_mask_list =\ + [ + box_mask + ] + + if 'occlusion' in deepfuze.globals.face_mask_types: + occlusion_mask = create_occlusion_mask(crop_vision_frame) + crop_mask_list.append(occlusion_mask) + crop_vision_frame = prepare_crop_frame(crop_vision_frame) + crop_vision_frame = apply_enhance(crop_vision_frame) + crop_vision_frame = normalize_crop_frame(crop_vision_frame) + crop_mask = numpy.minimum.reduce(crop_mask_list).clip(0, 1) + paste_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix) + temp_vision_frame = blend_frame(temp_vision_frame, paste_vision_frame) + return temp_vision_frame + + +def apply_enhance(crop_vision_frame : VisionFrame) -> VisionFrame: + frame_processor = get_frame_processor() + frame_processor_inputs = {} + + for frame_processor_input in frame_processor.get_inputs(): + if frame_processor_input.name == 'input': + frame_processor_inputs[frame_processor_input.name] = crop_vision_frame + if frame_processor_input.name == 'weight': + weight = numpy.array([ 1 ]).astype(numpy.double) + frame_processor_inputs[frame_processor_input.name] = weight + with thread_semaphore(): + crop_vision_frame = frame_processor.run(None, frame_processor_inputs)[0][0] + return crop_vision_frame + + +def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame: + crop_vision_frame = crop_vision_frame[:, :, ::-1] / 255.0 + crop_vision_frame = (crop_vision_frame - 0.5) / 0.5 + crop_vision_frame = numpy.expand_dims(crop_vision_frame.transpose(2, 0, 1), axis = 0).astype(numpy.float32) + return crop_vision_frame + + +def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame: + crop_vision_frame = numpy.clip(crop_vision_frame, -1, 1) + crop_vision_frame = (crop_vision_frame + 1) / 2 + crop_vision_frame = crop_vision_frame.transpose(1, 2, 0) + crop_vision_frame = (crop_vision_frame * 255.0).round() + crop_vision_frame = crop_vision_frame.astype(numpy.uint8)[:, :, ::-1] + return crop_vision_frame + + +def blend_frame(temp_vision_frame : VisionFrame, paste_vision_frame : VisionFrame) -> VisionFrame: + face_enhancer_blend = 1 - (frame_processors_globals.face_enhancer_blend / 100) + temp_vision_frame = cv2.addWeighted(temp_vision_frame, face_enhancer_blend, paste_vision_frame, 1 - face_enhancer_blend, 0) + return temp_vision_frame + + +def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame: + return enhance_face(target_face, temp_vision_frame) + + +def process_frame(inputs : FaceEnhancerInputs) -> VisionFrame: + reference_faces = inputs.get('reference_faces') + target_vision_frame = inputs.get('target_vision_frame') + + if deepfuze.globals.face_selector_mode == 'many': + many_faces = get_many_faces(target_vision_frame) + if many_faces: + for target_face in many_faces: + target_vision_frame = enhance_face(target_face, target_vision_frame) + if deepfuze.globals.face_selector_mode == 'one': + target_face = get_one_face(target_vision_frame) + if target_face: + target_vision_frame = enhance_face(target_face, target_vision_frame) + if deepfuze.globals.face_selector_mode == 'reference': + similar_faces = find_similar_faces(reference_faces, target_vision_frame, deepfuze.globals.reference_face_distance) + if similar_faces: + for similar_face in similar_faces: + target_vision_frame = enhance_face(similar_face, target_vision_frame) + return target_vision_frame + + +def process_frames(source_path : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None: + reference_faces = get_reference_faces() if 'reference' in deepfuze.globals.face_selector_mode else None + + for queue_payload in process_manager.manage(queue_payloads): + target_vision_path = queue_payload['frame_path'] + target_vision_frame = read_image(target_vision_path) + output_vision_frame = process_frame( + { + 'reference_faces': reference_faces, + 'target_vision_frame': target_vision_frame + }) + write_image(target_vision_path, output_vision_frame) + update_progress(1) + + +def process_image(source_path : str, target_path : str, output_path : str) -> None: + reference_faces = get_reference_faces() if 'reference' in deepfuze.globals.face_selector_mode else None + target_vision_frame = read_static_image(target_path) + output_vision_frame = process_frame( + { + 'reference_faces': reference_faces, + 'target_vision_frame': target_vision_frame + }) + write_image(output_path, output_vision_frame) + + +def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None: + frame_processors.multi_process_frames(None, temp_frame_paths, process_frames) diff --git a/deepfuze/processors/frame/modules/face_swapper.py b/deepfuze/processors/frame/modules/face_swapper.py new file mode 100755 index 0000000..49f5068 --- /dev/null +++ b/deepfuze/processors/frame/modules/face_swapper.py @@ -0,0 +1,369 @@ +from typing import Any, List, Literal, Optional +from argparse import ArgumentParser +from time import sleep +import numpy +import onnx +import onnxruntime +from onnx import numpy_helper + +import deepfuze.globals +import deepfuze.processors.frame.core as frame_processors +from deepfuze import config, process_manager, logger, wording +from deepfuze.execution import has_execution_provider, apply_execution_provider_options +from deepfuze.face_analyser import get_one_face, get_average_face, get_many_faces, find_similar_faces, clear_face_analyser +from deepfuze.face_masker import create_static_box_mask, create_occlusion_mask, create_region_mask, clear_face_occluder, clear_face_parser +from deepfuze.face_helper import warp_face_by_face_landmark_5, paste_back +from deepfuze.face_store import get_reference_faces +from deepfuze.content_analyser import clear_content_analyser +from deepfuze.normalizer import normalize_output_path +from deepfuze.thread_helper import thread_lock, conditional_thread_semaphore +from deepfuze.typing import Face, Embedding, VisionFrame, UpdateProgress, ProcessMode, ModelSet, OptionsWithModel, QueuePayload +from deepfuze.filesystem import is_file, is_image, has_image, is_video, filter_image_paths, resolve_relative_path +from deepfuze.download import conditional_download, is_download_done +from deepfuze.vision import read_image, read_static_image, read_static_images, write_image +from deepfuze.processors.frame.typings import FaceSwapperInputs +from deepfuze.processors.frame import globals as frame_processors_globals +from deepfuze.processors.frame import choices as frame_processors_choices + +FRAME_PROCESSOR = None +MODEL_INITIALIZER = None +NAME = __name__.upper() +MODELS : ModelSet =\ +{ + 'blendswap_256': + { + 'type': 'blendswap', + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/blendswap_256.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/blendswap_256.onnx'), + 'template': 'ffhq_512', + 'size': (256, 256), + 'mean': [ 0.0, 0.0, 0.0 ], + 'standard_deviation': [ 1.0, 1.0, 1.0 ] + }, + 'inswapper_128': + { + 'type': 'inswapper', + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/inswapper_128.onnx'), + 'template': 'arcface_128_v2', + 'size': (128, 128), + 'mean': [ 0.0, 0.0, 0.0 ], + 'standard_deviation': [ 1.0, 1.0, 1.0 ] + }, + 'inswapper_128_fp16': + { + 'type': 'inswapper', + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128_fp16.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/inswapper_128_fp16.onnx'), + 'template': 'arcface_128_v2', + 'size': (128, 128), + 'mean': [ 0.0, 0.0, 0.0 ], + 'standard_deviation': [ 1.0, 1.0, 1.0 ] + }, + 'simswap_256': + { + 'type': 'simswap', + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/simswap_256.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/simswap_256.onnx'), + 'template': 'arcface_112_v1', + 'size': (256, 256), + 'mean': [ 0.485, 0.456, 0.406 ], + 'standard_deviation': [ 0.229, 0.224, 0.225 ] + }, + 'simswap_512_unofficial': + { + 'type': 'simswap', + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/simswap_512_unofficial.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/simswap_512_unofficial.onnx'), + 'template': 'arcface_112_v1', + 'size': (512, 512), + 'mean': [ 0.0, 0.0, 0.0 ], + 'standard_deviation': [ 1.0, 1.0, 1.0 ] + }, + 'uniface_256': + { + 'type': 'uniface', + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/uniface_256.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/uniface_256.onnx'), + 'template': 'ffhq_512', + 'size': (256, 256), + 'mean': [ 0.0, 0.0, 0.0 ], + 'standard_deviation': [ 1.0, 1.0, 1.0 ] + } +} +OPTIONS : Optional[OptionsWithModel] = None + + +def get_frame_processor() -> Any: + global FRAME_PROCESSOR + + with thread_lock(): + while process_manager.is_checking(): + sleep(0.5) + if FRAME_PROCESSOR is None: + model_path = get_options('model').get('path') + FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + return FRAME_PROCESSOR + + +def clear_frame_processor() -> None: + global FRAME_PROCESSOR + + FRAME_PROCESSOR = None + + +def get_model_initializer() -> Any: + global MODEL_INITIALIZER + + with thread_lock(): + while process_manager.is_checking(): + sleep(0.5) + if MODEL_INITIALIZER is None: + model_path = get_options('model').get('path') + model = onnx.load(model_path) + MODEL_INITIALIZER = numpy_helper.to_array(model.graph.initializer[-1]) + return MODEL_INITIALIZER + + +def clear_model_initializer() -> None: + global MODEL_INITIALIZER + + MODEL_INITIALIZER = None + + +def get_options(key : Literal['model']) -> Any: + global OPTIONS + + if OPTIONS is None: + OPTIONS =\ + { + 'model': MODELS[frame_processors_globals.face_swapper_model] + } + return OPTIONS.get(key) + + +def set_options(key : Literal['model'], value : Any) -> None: + global OPTIONS + + OPTIONS[key] = value + + +def register_args(program : ArgumentParser) -> None: + if has_execution_provider('CoreMLExecutionProvider') or has_execution_provider('OpenVINOExecutionProvider'): + face_swapper_model_fallback = 'inswapper_128' + else: + face_swapper_model_fallback = 'inswapper_128_fp16' + program.add_argument('--face-swapper-model', help = wording.get('help.face_swapper_model'), default = config.get_str_value('frame_processors.face_swapper_model', face_swapper_model_fallback), choices = frame_processors_choices.face_swapper_models) + + +def apply_args(program : ArgumentParser) -> None: + args = program.parse_args() + frame_processors_globals.face_swapper_model = args.face_swapper_model + if args.face_swapper_model == 'blendswap_256': + deepfuze.globals.face_recognizer_model = 'arcface_blendswap' + if args.face_swapper_model == 'inswapper_128' or args.face_swapper_model == 'inswapper_128_fp16': + deepfuze.globals.face_recognizer_model = 'arcface_inswapper' + if args.face_swapper_model == 'simswap_256' or args.face_swapper_model == 'simswap_512_unofficial': + deepfuze.globals.face_recognizer_model = 'arcface_simswap' + if args.face_swapper_model == 'uniface_256': + deepfuze.globals.face_recognizer_model = 'arcface_uniface' + + +def pre_check() -> bool: + download_directory_path = resolve_relative_path('../../../models/deepfuze') + model_url = get_options('model').get('url') + model_path = get_options('model').get('path') + + if not deepfuze.globals.skip_download: + process_manager.check() + conditional_download(download_directory_path, [ model_url ]) + process_manager.end() + return is_file(model_path) + + +def post_check() -> bool: + model_url = get_options('model').get('url') + model_path = get_options('model').get('path') + + if not deepfuze.globals.skip_download and not is_download_done(model_url, model_path): + logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME) + return False + if not is_file(model_path): + logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME) + return False + return True + + +def pre_process(mode : ProcessMode) -> bool: + if not has_image(deepfuze.globals.source_paths): + logger.error(wording.get('select_image_source') + wording.get('exclamation_mark'), NAME) + return False + source_image_paths = filter_image_paths(deepfuze.globals.source_paths) + source_frames = read_static_images(source_image_paths) + for source_frame in source_frames: + if not get_one_face(source_frame): + logger.error(wording.get('no_source_face_detected') + wording.get('exclamation_mark'), NAME) + return False + if mode in [ 'output', 'preview' ] and not is_image(deepfuze.globals.target_path) and not is_video(deepfuze.globals.target_path): + logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME) + return False + if mode == 'output' and not normalize_output_path(deepfuze.globals.target_path, deepfuze.globals.output_path): + logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME) + return False + return True + + +def post_process() -> None: + read_static_image.cache_clear() + if deepfuze.globals.video_memory_strategy == 'strict' or deepfuze.globals.video_memory_strategy == 'moderate': + clear_model_initializer() + clear_frame_processor() + if deepfuze.globals.video_memory_strategy == 'strict': + clear_face_analyser() + clear_content_analyser() + clear_face_occluder() + clear_face_parser() + + +def swap_face(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame: + model_template = get_options('model').get('template') + model_size = get_options('model').get('size') + crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), model_template, model_size) + crop_mask_list = [] + + if 'box' in deepfuze.globals.face_mask_types: + box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], deepfuze.globals.face_mask_blur, deepfuze.globals.face_mask_padding) + crop_mask_list.append(box_mask) + if 'occlusion' in deepfuze.globals.face_mask_types: + occlusion_mask = create_occlusion_mask(crop_vision_frame) + crop_mask_list.append(occlusion_mask) + crop_vision_frame = prepare_crop_frame(crop_vision_frame) + crop_vision_frame = apply_swap(source_face, crop_vision_frame) + crop_vision_frame = normalize_crop_frame(crop_vision_frame) + if 'region' in deepfuze.globals.face_mask_types: + region_mask = create_region_mask(crop_vision_frame, deepfuze.globals.face_mask_regions) + crop_mask_list.append(region_mask) + crop_mask = numpy.minimum.reduce(crop_mask_list).clip(0, 1) + temp_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix) + return temp_vision_frame + + +def apply_swap(source_face : Face, crop_vision_frame : VisionFrame) -> VisionFrame: + frame_processor = get_frame_processor() + model_type = get_options('model').get('type') + frame_processor_inputs = {} + + for frame_processor_input in frame_processor.get_inputs(): + if frame_processor_input.name == 'source': + if model_type == 'blendswap' or model_type == 'uniface': + frame_processor_inputs[frame_processor_input.name] = prepare_source_frame(source_face) + else: + frame_processor_inputs[frame_processor_input.name] = prepare_source_embedding(source_face) + if frame_processor_input.name == 'target': + frame_processor_inputs[frame_processor_input.name] = crop_vision_frame + with conditional_thread_semaphore(deepfuze.globals.execution_providers): + crop_vision_frame = frame_processor.run(None, frame_processor_inputs)[0][0] + return crop_vision_frame + + +def prepare_source_frame(source_face : Face) -> VisionFrame: + model_type = get_options('model').get('type') + source_vision_frame = read_static_image(deepfuze.globals.source_paths[0]) + if model_type == 'blendswap': + source_vision_frame, _ = warp_face_by_face_landmark_5(source_vision_frame, source_face.landmarks.get('5/68'), 'arcface_112_v2', (112, 112)) + if model_type == 'uniface': + source_vision_frame, _ = warp_face_by_face_landmark_5(source_vision_frame, source_face.landmarks.get('5/68'), 'ffhq_512', (256, 256)) + source_vision_frame = source_vision_frame[:, :, ::-1] / 255.0 + source_vision_frame = source_vision_frame.transpose(2, 0, 1) + source_vision_frame = numpy.expand_dims(source_vision_frame, axis = 0).astype(numpy.float32) + return source_vision_frame + + +def prepare_source_embedding(source_face : Face) -> Embedding: + model_type = get_options('model').get('type') + if model_type == 'inswapper': + model_initializer = get_model_initializer() + source_embedding = source_face.embedding.reshape((1, -1)) + source_embedding = numpy.dot(source_embedding, model_initializer) / numpy.linalg.norm(source_embedding) + else: + source_embedding = source_face.normed_embedding.reshape(1, -1) + return source_embedding + + +def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame: + model_mean = get_options('model').get('mean') + model_standard_deviation = get_options('model').get('standard_deviation') + crop_vision_frame = crop_vision_frame[:, :, ::-1] / 255.0 + crop_vision_frame = (crop_vision_frame - model_mean) / model_standard_deviation + crop_vision_frame = crop_vision_frame.transpose(2, 0, 1) + crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0).astype(numpy.float32) + return crop_vision_frame + + +def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame: + crop_vision_frame = crop_vision_frame.transpose(1, 2, 0) + crop_vision_frame = (crop_vision_frame * 255.0).round() + crop_vision_frame = crop_vision_frame[:, :, ::-1] + return crop_vision_frame + + +def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame: + return swap_face(source_face, target_face, temp_vision_frame) + + +def process_frame(inputs : FaceSwapperInputs) -> VisionFrame: + reference_faces = inputs.get('reference_faces') + source_face = inputs.get('source_face') + target_vision_frame = inputs.get('target_vision_frame') + + if deepfuze.globals.face_selector_mode == 'many': + many_faces = get_many_faces(target_vision_frame) + if many_faces: + for target_face in many_faces: + target_vision_frame = swap_face(source_face, target_face, target_vision_frame) + if deepfuze.globals.face_selector_mode == 'one': + target_face = get_one_face(target_vision_frame) + if target_face: + target_vision_frame = swap_face(source_face, target_face, target_vision_frame) + if deepfuze.globals.face_selector_mode == 'reference': + similar_faces = find_similar_faces(reference_faces, target_vision_frame, deepfuze.globals.reference_face_distance) + if similar_faces: + for similar_face in similar_faces: + target_vision_frame = swap_face(source_face, similar_face, target_vision_frame) + return target_vision_frame + + +def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None: + reference_faces = get_reference_faces() if 'reference' in deepfuze.globals.face_selector_mode else None + source_frames = read_static_images(source_paths) + source_face = get_average_face(source_frames) + + for queue_payload in process_manager.manage(queue_payloads): + target_vision_path = queue_payload['frame_path'] + target_vision_frame = read_image(target_vision_path) + output_vision_frame = process_frame( + { + 'reference_faces': reference_faces, + 'source_face': source_face, + 'target_vision_frame': target_vision_frame + }) + write_image(target_vision_path, output_vision_frame) + update_progress(1) + + +def process_image(source_paths : List[str], target_path : str, output_path : str) -> None: + reference_faces = get_reference_faces() if 'reference' in deepfuze.globals.face_selector_mode else None + source_frames = read_static_images(source_paths) + source_face = get_average_face(source_frames) + target_vision_frame = read_static_image(target_path) + output_vision_frame = process_frame( + { + 'reference_faces': reference_faces, + 'source_face': source_face, + 'target_vision_frame': target_vision_frame + }) + write_image(output_path, output_vision_frame) + + +def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None: + frame_processors.multi_process_frames(source_paths, temp_frame_paths, process_frames) diff --git a/deepfuze/processors/frame/modules/frame_colorizer.py b/deepfuze/processors/frame/modules/frame_colorizer.py new file mode 100644 index 0000000..1305746 --- /dev/null +++ b/deepfuze/processors/frame/modules/frame_colorizer.py @@ -0,0 +1,241 @@ +from typing import Any, List, Literal, Optional +from argparse import ArgumentParser +from time import sleep +import cv2 +import numpy +import onnxruntime + +import deepfuze.globals +import deepfuze.processors.frame.core as frame_processors +from deepfuze import config, process_manager, logger, wording +from deepfuze.face_analyser import clear_face_analyser +from deepfuze.content_analyser import clear_content_analyser +from deepfuze.execution import apply_execution_provider_options +from deepfuze.normalizer import normalize_output_path +from deepfuze.thread_helper import thread_lock, thread_semaphore +from deepfuze.typing import Face, VisionFrame, UpdateProgress, ProcessMode, ModelSet, OptionsWithModel, QueuePayload +from deepfuze.common_helper import create_metavar +from deepfuze.filesystem import is_file, resolve_relative_path, is_image, is_video +from deepfuze.download import conditional_download, is_download_done +from deepfuze.vision import read_image, read_static_image, write_image, unpack_resolution +from deepfuze.processors.frame.typings import FrameColorizerInputs +from deepfuze.processors.frame import globals as frame_processors_globals +from deepfuze.processors.frame import choices as frame_processors_choices + +FRAME_PROCESSOR = None +NAME = __name__.upper() +MODELS : ModelSet =\ +{ + 'ddcolor': + { + 'type': 'ddcolor', + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/ddcolor.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/ddcolor.onnx') + }, + 'ddcolor_artistic': + { + 'type': 'ddcolor', + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/ddcolor_artistic.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/ddcolor_artistic.onnx') + }, + 'deoldify': + { + 'type': 'deoldify', + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/deoldify.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/deoldify.onnx') + }, + 'deoldify_artistic': + { + 'type': 'deoldify', + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/deoldify_artistic.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/deoldify_artistic.onnx') + }, + 'deoldify_stable': + { + 'type': 'deoldify', + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/deoldify_stable.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/deoldify_stable.onnx') + } +} +OPTIONS : Optional[OptionsWithModel] = None + + +def get_frame_processor() -> Any: + global FRAME_PROCESSOR + + with thread_lock(): + while process_manager.is_checking(): + sleep(0.5) + if FRAME_PROCESSOR is None: + model_path = get_options('model').get('path') + FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + return FRAME_PROCESSOR + + +def clear_frame_processor() -> None: + global FRAME_PROCESSOR + + FRAME_PROCESSOR = None + + +def get_options(key : Literal['model']) -> Any: + global OPTIONS + + if OPTIONS is None: + OPTIONS =\ + { + 'model': MODELS[frame_processors_globals.frame_colorizer_model] + } + return OPTIONS.get(key) + + +def set_options(key : Literal['model'], value : Any) -> None: + global OPTIONS + + OPTIONS[key] = value + + +def register_args(program : ArgumentParser) -> None: + program.add_argument('--frame-colorizer-model', help = wording.get('help.frame_colorizer_model'), default = config.get_str_value('frame_processors.frame_colorizer_model', 'ddcolor'), choices = frame_processors_choices.frame_colorizer_models) + program.add_argument('--frame-colorizer-blend', help = wording.get('help.frame_colorizer_blend'), type = int, default = config.get_int_value('frame_processors.frame_colorizer_blend', '100'), choices = frame_processors_choices.frame_colorizer_blend_range, metavar = create_metavar(frame_processors_choices.frame_colorizer_blend_range)) + program.add_argument('--frame-colorizer-size', help = wording.get('help.frame_colorizer_size'), type = str, default = config.get_str_value('frame_processors.frame_colorizer_size', '256x256'), choices = frame_processors_choices.frame_colorizer_sizes) + + +def apply_args(program : ArgumentParser) -> None: + args = program.parse_args() + frame_processors_globals.frame_colorizer_model = args.frame_colorizer_model + frame_processors_globals.frame_colorizer_blend = args.frame_colorizer_blend + frame_processors_globals.frame_colorizer_size = args.frame_colorizer_size + + +def pre_check() -> bool: + download_directory_path = resolve_relative_path('../../../models/deepfuze') + model_url = get_options('model').get('url') + model_path = get_options('model').get('path') + + if not deepfuze.globals.skip_download: + process_manager.check() + conditional_download(download_directory_path, [ model_url ]) + process_manager.end() + return is_file(model_path) + + +def post_check() -> bool: + model_url = get_options('model').get('url') + model_path = get_options('model').get('path') + + if not deepfuze.globals.skip_download and not is_download_done(model_url, model_path): + logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME) + return False + if not is_file(model_path): + logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME) + return False + return True + + +def pre_process(mode : ProcessMode) -> bool: + if mode in [ 'output', 'preview' ] and not is_image(deepfuze.globals.target_path) and not is_video(deepfuze.globals.target_path): + logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME) + return False + if mode == 'output' and not normalize_output_path(deepfuze.globals.target_path, deepfuze.globals.output_path): + logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME) + return False + return True + + +def post_process() -> None: + read_static_image.cache_clear() + if deepfuze.globals.video_memory_strategy == 'strict' or deepfuze.globals.video_memory_strategy == 'moderate': + clear_frame_processor() + if deepfuze.globals.video_memory_strategy == 'strict': + clear_face_analyser() + clear_content_analyser() + + +def colorize_frame(temp_vision_frame : VisionFrame) -> VisionFrame: + frame_processor = get_frame_processor() + prepare_vision_frame = prepare_temp_frame(temp_vision_frame) + with thread_semaphore(): + color_vision_frame = frame_processor.run(None, + { + frame_processor.get_inputs()[0].name: prepare_vision_frame + })[0][0] + color_vision_frame = merge_color_frame(temp_vision_frame, color_vision_frame) + color_vision_frame = blend_frame(temp_vision_frame, color_vision_frame) + return color_vision_frame + + +def prepare_temp_frame(temp_vision_frame : VisionFrame) -> VisionFrame: + model_size = unpack_resolution(frame_processors_globals.frame_colorizer_size) + model_type = get_options('model').get('type') + temp_vision_frame = cv2.cvtColor(temp_vision_frame, cv2.COLOR_BGR2GRAY) + temp_vision_frame = cv2.cvtColor(temp_vision_frame, cv2.COLOR_GRAY2RGB) + if model_type == 'ddcolor': + temp_vision_frame = (temp_vision_frame / 255.0).astype(numpy.float32) + temp_vision_frame = cv2.cvtColor(temp_vision_frame, cv2.COLOR_RGB2LAB)[:, :, :1] + temp_vision_frame = numpy.concatenate((temp_vision_frame, numpy.zeros_like(temp_vision_frame), numpy.zeros_like(temp_vision_frame)), axis = -1) + temp_vision_frame = cv2.cvtColor(temp_vision_frame, cv2.COLOR_LAB2RGB) + temp_vision_frame = cv2.resize(temp_vision_frame, model_size) + 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 + + +def merge_color_frame(temp_vision_frame : VisionFrame, color_vision_frame : VisionFrame) -> VisionFrame: + model_type = get_options('model').get('type') + color_vision_frame = color_vision_frame.transpose(1, 2, 0) + color_vision_frame = cv2.resize(color_vision_frame, (temp_vision_frame.shape[1], temp_vision_frame.shape[0])) + if model_type == 'ddcolor': + temp_vision_frame = (temp_vision_frame / 255.0).astype(numpy.float32) + temp_vision_frame = cv2.cvtColor(temp_vision_frame, cv2.COLOR_BGR2LAB)[:, :, :1] + color_vision_frame = numpy.concatenate((temp_vision_frame, color_vision_frame), axis = -1) + color_vision_frame = cv2.cvtColor(color_vision_frame, cv2.COLOR_LAB2BGR) + color_vision_frame = (color_vision_frame * 255.0).round().astype(numpy.uint8) + if model_type == 'deoldify': + temp_blue_channel, _, _ = cv2.split(temp_vision_frame) + color_vision_frame = cv2.cvtColor(color_vision_frame, cv2.COLOR_BGR2RGB).astype(numpy.uint8) + color_vision_frame = cv2.cvtColor(color_vision_frame, cv2.COLOR_BGR2LAB) + _, color_green_channel, color_red_channel = cv2.split(color_vision_frame) + color_vision_frame = cv2.merge((temp_blue_channel, color_green_channel, color_red_channel)) + color_vision_frame = cv2.cvtColor(color_vision_frame, cv2.COLOR_LAB2BGR) + return color_vision_frame + + +def blend_frame(temp_vision_frame : VisionFrame, paste_vision_frame : VisionFrame) -> VisionFrame: + frame_colorizer_blend = 1 - (frame_processors_globals.frame_colorizer_blend / 100) + temp_vision_frame = cv2.addWeighted(temp_vision_frame, frame_colorizer_blend, paste_vision_frame, 1 - frame_colorizer_blend, 0) + return temp_vision_frame + + +def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame: + pass + + +def process_frame(inputs : FrameColorizerInputs) -> VisionFrame: + target_vision_frame = inputs.get('target_vision_frame') + return colorize_frame(target_vision_frame) + + +def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None: + for queue_payload in process_manager.manage(queue_payloads): + target_vision_path = queue_payload['frame_path'] + target_vision_frame = read_image(target_vision_path) + output_vision_frame = process_frame( + { + 'target_vision_frame': target_vision_frame + }) + write_image(target_vision_path, output_vision_frame) + update_progress(1) + + +def process_image(source_paths : List[str], target_path : str, output_path : str) -> None: + target_vision_frame = read_static_image(target_path) + output_vision_frame = process_frame( + { + 'target_vision_frame': target_vision_frame + }) + write_image(output_path, output_vision_frame) + + +def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None: + frame_processors.multi_process_frames(None, temp_frame_paths, process_frames) diff --git a/deepfuze/processors/frame/modules/frame_enhancer.py b/deepfuze/processors/frame/modules/frame_enhancer.py new file mode 100644 index 0000000..40d2f3e --- /dev/null +++ b/deepfuze/processors/frame/modules/frame_enhancer.py @@ -0,0 +1,263 @@ +from typing import Any, List, Literal, Optional +from argparse import ArgumentParser +from time import sleep +import cv2 +import numpy +import onnxruntime + +import deepfuze.globals +import deepfuze.processors.frame.core as frame_processors +from deepfuze import config, process_manager, logger, wording +from deepfuze.face_analyser import clear_face_analyser +from deepfuze.content_analyser import clear_content_analyser +from deepfuze.execution import apply_execution_provider_options +from deepfuze.normalizer import normalize_output_path +from deepfuze.thread_helper import thread_lock, conditional_thread_semaphore +from deepfuze.typing import Face, VisionFrame, UpdateProgress, ProcessMode, ModelSet, OptionsWithModel, QueuePayload +from deepfuze.common_helper import create_metavar +from deepfuze.filesystem import is_file, resolve_relative_path, is_image, is_video +from deepfuze.download import conditional_download, is_download_done +from deepfuze.vision import read_image, read_static_image, write_image, merge_tile_frames, create_tile_frames +from deepfuze.processors.frame.typings import FrameEnhancerInputs +from deepfuze.processors.frame import globals as frame_processors_globals +from deepfuze.processors.frame import choices as frame_processors_choices + +FRAME_PROCESSOR = None +NAME = __name__.upper() +MODELS : ModelSet =\ +{ + 'clear_reality_x4': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/clear_reality_x4.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/clear_reality_x4.onnx'), + 'size': (128, 8, 4), + 'scale': 4 + }, + 'lsdir_x4': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/lsdir_x4.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/lsdir_x4.onnx'), + 'size': (128, 8, 4), + 'scale': 4 + }, + 'nomos8k_sc_x4': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/nomos8k_sc_x4.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/nomos8k_sc_x4.onnx'), + 'size': (128, 8, 4), + 'scale': 4 + }, + 'real_esrgan_x2': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x2.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/real_esrgan_x2.onnx'), + 'size': (256, 16, 8), + 'scale': 2 + }, + 'real_esrgan_x2_fp16': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x2_fp16.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/real_esrgan_x2_fp16.onnx'), + 'size': (256, 16, 8), + 'scale': 2 + }, + 'real_esrgan_x4': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x4.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/real_esrgan_x4.onnx'), + 'size': (256, 16, 8), + 'scale': 4 + }, + 'real_esrgan_x4_fp16': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_esrgan_x4_fp16.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/real_esrgan_x4_fp16.onnx'), + 'size': (256, 16, 8), + 'scale': 4 + }, + 'real_hatgan_x4': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/real_hatgan_x4.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/real_hatgan_x4.onnx'), + 'size': (256, 16, 8), + 'scale': 4 + }, + 'span_kendata_x4': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/span_kendata_x4.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/span_kendata_x4.onnx'), + 'size': (128, 8, 4), + 'scale': 4 + }, + 'ultra_sharp_x4': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/ultra_sharp_x4.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/ultra_sharp_x4.onnx'), + 'size': (128, 8, 4), + 'scale': 4 + } +} +OPTIONS : Optional[OptionsWithModel] = None + + +def get_frame_processor() -> Any: + global FRAME_PROCESSOR + + with thread_lock(): + while process_manager.is_checking(): + sleep(0.5) + if FRAME_PROCESSOR is None: + model_path = get_options('model').get('path') + FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + return FRAME_PROCESSOR + + +def clear_frame_processor() -> None: + global FRAME_PROCESSOR + + FRAME_PROCESSOR = None + + +def get_options(key : Literal['model']) -> Any: + global OPTIONS + + if OPTIONS is None: + OPTIONS =\ + { + 'model': MODELS[frame_processors_globals.frame_enhancer_model] + } + return OPTIONS.get(key) + + +def set_options(key : Literal['model'], value : Any) -> None: + global OPTIONS + + OPTIONS[key] = value + + +def register_args(program : ArgumentParser) -> None: + program.add_argument('--frame-enhancer-model', help = wording.get('help.frame_enhancer_model'), default = config.get_str_value('frame_processors.frame_enhancer_model', 'span_kendata_x4'), choices = frame_processors_choices.frame_enhancer_models) + program.add_argument('--frame-enhancer-blend', help = wording.get('help.frame_enhancer_blend'), type = int, default = config.get_int_value('frame_processors.frame_enhancer_blend', '80'), choices = frame_processors_choices.frame_enhancer_blend_range, metavar = create_metavar(frame_processors_choices.frame_enhancer_blend_range)) + + +def apply_args(program : ArgumentParser) -> None: + args = program.parse_args() + frame_processors_globals.frame_enhancer_model = args.frame_enhancer_model + frame_processors_globals.frame_enhancer_blend = args.frame_enhancer_blend + + +def pre_check() -> bool: + download_directory_path = resolve_relative_path('../../../models/deepfuze') + model_url = get_options('model').get('url') + model_path = get_options('model').get('path') + + if not deepfuze.globals.skip_download: + process_manager.check() + conditional_download(download_directory_path, [ model_url ]) + process_manager.end() + return is_file(model_path) + + +def post_check() -> bool: + model_url = get_options('model').get('url') + model_path = get_options('model').get('path') + + if not deepfuze.globals.skip_download and not is_download_done(model_url, model_path): + logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME) + return False + if not is_file(model_path): + logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME) + return False + return True + + +def pre_process(mode : ProcessMode) -> bool: + if mode in [ 'output', 'preview' ] and not is_image(deepfuze.globals.target_path) and not is_video(deepfuze.globals.target_path): + logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME) + return False + if mode == 'output' and not normalize_output_path(deepfuze.globals.target_path, deepfuze.globals.output_path): + logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME) + return False + return True + + +def post_process() -> None: + read_static_image.cache_clear() + if deepfuze.globals.video_memory_strategy == 'strict' or deepfuze.globals.video_memory_strategy == 'moderate': + clear_frame_processor() + if deepfuze.globals.video_memory_strategy == 'strict': + clear_face_analyser() + clear_content_analyser() + + +def enhance_frame(temp_vision_frame : VisionFrame) -> VisionFrame: + frame_processor = get_frame_processor() + size = get_options('model').get('size') + scale = get_options('model').get('scale') + temp_height, temp_width = temp_vision_frame.shape[:2] + tile_vision_frames, pad_width, pad_height = create_tile_frames(temp_vision_frame, size) + + for index, tile_vision_frame in enumerate(tile_vision_frames): + with conditional_thread_semaphore(deepfuze.globals.execution_providers): + tile_vision_frame = frame_processor.run(None, + { + frame_processor.get_inputs()[0].name : prepare_tile_frame(tile_vision_frame) + })[0] + tile_vision_frames[index] = normalize_tile_frame(tile_vision_frame) + merge_vision_frame = merge_tile_frames(tile_vision_frames, temp_width * scale, temp_height * scale, pad_width * scale, pad_height * scale, (size[0] * scale, size[1] * scale, size[2] * scale)) + temp_vision_frame = blend_frame(temp_vision_frame, merge_vision_frame) + return temp_vision_frame + + +def prepare_tile_frame(vision_tile_frame : VisionFrame) -> VisionFrame: + vision_tile_frame = numpy.expand_dims(vision_tile_frame[:, :, ::-1], axis = 0) + vision_tile_frame = vision_tile_frame.transpose(0, 3, 1, 2) + vision_tile_frame = vision_tile_frame.astype(numpy.float32) / 255 + return vision_tile_frame + + +def normalize_tile_frame(vision_tile_frame : VisionFrame) -> VisionFrame: + vision_tile_frame = vision_tile_frame.transpose(0, 2, 3, 1).squeeze(0) * 255 + vision_tile_frame = vision_tile_frame.clip(0, 255).astype(numpy.uint8)[:, :, ::-1] + return vision_tile_frame + + +def blend_frame(temp_vision_frame : VisionFrame, merge_vision_frame : VisionFrame) -> VisionFrame: + frame_enhancer_blend = 1 - (frame_processors_globals.frame_enhancer_blend / 100) + temp_vision_frame = cv2.resize(temp_vision_frame, (merge_vision_frame.shape[1], merge_vision_frame.shape[0])) + temp_vision_frame = cv2.addWeighted(temp_vision_frame, frame_enhancer_blend, merge_vision_frame, 1 - frame_enhancer_blend, 0) + return temp_vision_frame + + +def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame: + pass + + +def process_frame(inputs : FrameEnhancerInputs) -> VisionFrame: + target_vision_frame = inputs.get('target_vision_frame') + return enhance_frame(target_vision_frame) + + +def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None: + for queue_payload in process_manager.manage(queue_payloads): + target_vision_path = queue_payload['frame_path'] + target_vision_frame = read_image(target_vision_path) + output_vision_frame = process_frame( + { + 'target_vision_frame': target_vision_frame + }) + write_image(target_vision_path, output_vision_frame) + update_progress(1) + + +def process_image(source_paths : List[str], target_path : str, output_path : str) -> None: + target_vision_frame = read_static_image(target_path) + output_vision_frame = process_frame( + { + 'target_vision_frame': target_vision_frame + }) + write_image(output_path, output_vision_frame) + + +def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None: + frame_processors.multi_process_frames(None, temp_frame_paths, process_frames) diff --git a/deepfuze/processors/frame/modules/lip_syncer.py b/deepfuze/processors/frame/modules/lip_syncer.py new file mode 100755 index 0000000..599eb49 --- /dev/null +++ b/deepfuze/processors/frame/modules/lip_syncer.py @@ -0,0 +1,260 @@ +from typing import Any, List, Literal, Optional +from argparse import ArgumentParser +from time import sleep +import cv2 +import numpy +import onnxruntime + +import deepfuze.globals +import deepfuze.processors.frame.core as frame_processors +from deepfuze import config, process_manager, logger, wording +from deepfuze.execution import apply_execution_provider_options +from deepfuze.face_analyser import get_one_face, get_many_faces, find_similar_faces, clear_face_analyser +from deepfuze.face_masker import create_static_box_mask, create_occlusion_mask, create_mouth_mask, clear_face_occluder, clear_face_parser +from deepfuze.face_helper import warp_face_by_face_landmark_5, warp_face_by_bounding_box, paste_back, create_bounding_box_from_face_landmark_68 +from deepfuze.face_store import get_reference_faces +from deepfuze.content_analyser import clear_content_analyser +from deepfuze.normalizer import normalize_output_path +from deepfuze.thread_helper import thread_lock, conditional_thread_semaphore +from deepfuze.typing import Face, VisionFrame, UpdateProgress, ProcessMode, ModelSet, OptionsWithModel, AudioFrame, QueuePayload +from deepfuze.filesystem import is_file, has_audio, resolve_relative_path +from deepfuze.download import conditional_download, is_download_done +from deepfuze.audio import read_static_voice, get_voice_frame, create_empty_audio_frame +from deepfuze.filesystem import is_image, is_video, filter_audio_paths +from deepfuze.common_helper import get_first +from deepfuze.vision import read_image, read_static_image, write_image, restrict_video_fps +from deepfuze.processors.frame.typings import LipSyncerInputs +from deepfuze.voice_extractor import clear_voice_extractor +from deepfuze.processors.frame import globals as frame_processors_globals +from deepfuze.processors.frame import choices as frame_processors_choices + +FRAME_PROCESSOR = None +NAME = __name__.upper() +MODELS : ModelSet =\ +{ + 'wav2lip_gan': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/wav2lip_gan.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/wav2lip_gan.onnx') + } +} +OPTIONS : Optional[OptionsWithModel] = None + + +def get_frame_processor() -> Any: + global FRAME_PROCESSOR + + with thread_lock(): + while process_manager.is_checking(): + sleep(0.5) + if FRAME_PROCESSOR is None: + model_path = get_options('model').get('path') + FRAME_PROCESSOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + return FRAME_PROCESSOR + + +def clear_frame_processor() -> None: + global FRAME_PROCESSOR + + FRAME_PROCESSOR = None + + +def get_options(key : Literal['model']) -> Any: + global OPTIONS + + if OPTIONS is None: + OPTIONS =\ + { + 'model': MODELS[frame_processors_globals.lip_syncer_model] + } + return OPTIONS.get(key) + + +def set_options(key : Literal['model'], value : Any) -> None: + global OPTIONS + + OPTIONS[key] = value + + +def register_args(program : ArgumentParser) -> None: + program.add_argument('--lip-syncer-model', help = wording.get('help.lip_syncer_model'), default = config.get_str_value('frame_processors.lip_syncer_model', 'wav2lip_gan'), choices = frame_processors_choices.lip_syncer_models) + + +def apply_args(program : ArgumentParser) -> None: + args = program.parse_args() + frame_processors_globals.lip_syncer_model = args.lip_syncer_model + + +def pre_check() -> bool: + download_directory_path = resolve_relative_path('../../../models/deepfuze') + model_url = get_options('model').get('url') + model_path = get_options('model').get('path') + + if not deepfuze.globals.skip_download: + process_manager.check() + conditional_download(download_directory_path, [ model_url ]) + process_manager.end() + return is_file(model_path) + + +def post_check() -> bool: + model_url = get_options('model').get('url') + model_path = get_options('model').get('path') + + if not deepfuze.globals.skip_download and not is_download_done(model_url, model_path): + logger.error(wording.get('model_download_not_done') + wording.get('exclamation_mark'), NAME) + return False + if not is_file(model_path): + logger.error(wording.get('model_file_not_present') + wording.get('exclamation_mark'), NAME) + return False + return True + + +def pre_process(mode : ProcessMode) -> bool: + if not has_audio(deepfuze.globals.source_paths): + logger.error(wording.get('select_audio_source') + wording.get('exclamation_mark'), NAME) + return False + if mode in [ 'output', 'preview' ] and not is_image(deepfuze.globals.target_path) and not is_video(deepfuze.globals.target_path): + logger.error(wording.get('select_image_or_video_target') + wording.get('exclamation_mark'), NAME) + return False + if mode == 'output' and not normalize_output_path(deepfuze.globals.target_path, deepfuze.globals.output_path): + logger.error(wording.get('select_file_or_directory_output') + wording.get('exclamation_mark'), NAME) + return False + return True + + +def post_process() -> None: + read_static_image.cache_clear() + read_static_voice.cache_clear() + if deepfuze.globals.video_memory_strategy == 'strict' or deepfuze.globals.video_memory_strategy == 'moderate': + clear_frame_processor() + if deepfuze.globals.video_memory_strategy == 'strict': + clear_face_analyser() + clear_content_analyser() + clear_face_occluder() + clear_face_parser() + clear_voice_extractor() + + +def sync_lip(target_face : Face, temp_audio_frame : AudioFrame, temp_vision_frame : VisionFrame) -> VisionFrame: + frame_processor = get_frame_processor() + crop_mask_list = [] + temp_audio_frame = prepare_audio_frame(temp_audio_frame) + crop_vision_frame, affine_matrix = warp_face_by_face_landmark_5(temp_vision_frame, target_face.landmarks.get('5/68'), 'ffhq_512', (512, 512)) + face_landmark_68 = cv2.transform(target_face.landmarks.get('68').reshape(1, -1, 2), affine_matrix).reshape(-1, 2) + bounding_box = create_bounding_box_from_face_landmark_68(face_landmark_68) + bounding_box[1] -= numpy.abs(bounding_box[3] - bounding_box[1]) * 0.125 + mouth_mask = create_mouth_mask(face_landmark_68) + crop_mask_list.append(mouth_mask) + box_mask = create_static_box_mask(crop_vision_frame.shape[:2][::-1], deepfuze.globals.face_mask_blur, deepfuze.globals.face_mask_padding) + crop_mask_list.append(box_mask) + + if 'occlusion' in deepfuze.globals.face_mask_types: + occlusion_mask = create_occlusion_mask(crop_vision_frame) + crop_mask_list.append(occlusion_mask) + close_vision_frame, close_matrix = warp_face_by_bounding_box(crop_vision_frame, bounding_box, (96, 96)) + close_vision_frame = prepare_crop_frame(close_vision_frame) + with conditional_thread_semaphore(deepfuze.globals.execution_providers): + close_vision_frame = frame_processor.run(None, + { + 'source': temp_audio_frame, + 'target': close_vision_frame + })[0] + crop_vision_frame = normalize_crop_frame(close_vision_frame) + crop_vision_frame = cv2.warpAffine(crop_vision_frame, cv2.invertAffineTransform(close_matrix), (512, 512), borderMode = cv2.BORDER_REPLICATE) + crop_mask = numpy.minimum.reduce(crop_mask_list) + paste_vision_frame = paste_back(temp_vision_frame, crop_vision_frame, crop_mask, affine_matrix) + return paste_vision_frame + + +def prepare_audio_frame(temp_audio_frame : AudioFrame) -> AudioFrame: + temp_audio_frame = numpy.maximum(numpy.exp(-5 * numpy.log(10)), temp_audio_frame) + temp_audio_frame = numpy.log10(temp_audio_frame) * 1.6 + 3.2 + temp_audio_frame = temp_audio_frame.clip(-4, 4).astype(numpy.float32) + temp_audio_frame = numpy.expand_dims(temp_audio_frame, axis = (0, 1)) + return temp_audio_frame + + +def prepare_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame: + crop_vision_frame = numpy.expand_dims(crop_vision_frame, axis = 0) + prepare_vision_frame = crop_vision_frame.copy() + prepare_vision_frame[:, 48:] = 0 + crop_vision_frame = numpy.concatenate((prepare_vision_frame, crop_vision_frame), axis = 3) + crop_vision_frame = crop_vision_frame.transpose(0, 3, 1, 2).astype('float32') / 255.0 + return crop_vision_frame + + +def normalize_crop_frame(crop_vision_frame : VisionFrame) -> VisionFrame: + crop_vision_frame = crop_vision_frame[0].transpose(1, 2, 0) + crop_vision_frame = crop_vision_frame.clip(0, 1) * 255 + crop_vision_frame = crop_vision_frame.astype(numpy.uint8) + return crop_vision_frame + + +def get_reference_frame(source_face : Face, target_face : Face, temp_vision_frame : VisionFrame) -> VisionFrame: + pass + + +def process_frame(inputs : LipSyncerInputs) -> VisionFrame: + reference_faces = inputs.get('reference_faces') + source_audio_frame = inputs.get('source_audio_frame') + target_vision_frame = inputs.get('target_vision_frame') + + if deepfuze.globals.face_selector_mode == 'many': + many_faces = get_many_faces(target_vision_frame) + if many_faces: + for target_face in many_faces: + target_vision_frame = sync_lip(target_face, source_audio_frame, target_vision_frame) + if deepfuze.globals.face_selector_mode == 'one': + target_face = get_one_face(target_vision_frame) + if target_face: + target_vision_frame = sync_lip(target_face, source_audio_frame, target_vision_frame) + if deepfuze.globals.face_selector_mode == 'reference': + similar_faces = find_similar_faces(reference_faces, target_vision_frame, deepfuze.globals.reference_face_distance) + if similar_faces: + for similar_face in similar_faces: + target_vision_frame = sync_lip(similar_face, source_audio_frame, target_vision_frame) + return target_vision_frame + + +def process_frames(source_paths : List[str], queue_payloads : List[QueuePayload], update_progress : UpdateProgress) -> None: + reference_faces = get_reference_faces() if 'reference' in deepfuze.globals.face_selector_mode else None + source_audio_path = get_first(filter_audio_paths(source_paths)) + temp_video_fps = restrict_video_fps(deepfuze.globals.target_path, deepfuze.globals.output_video_fps) + + for queue_payload in process_manager.manage(queue_payloads): + frame_number = queue_payload['frame_number'] + target_vision_path = queue_payload['frame_path'] + source_audio_frame = get_voice_frame(source_audio_path, temp_video_fps, frame_number) + if not numpy.any(source_audio_frame): + source_audio_frame = create_empty_audio_frame() + target_vision_frame = read_image(target_vision_path) + output_vision_frame = process_frame( + { + 'reference_faces': reference_faces, + 'source_audio_frame': source_audio_frame, + 'target_vision_frame': target_vision_frame + }) + write_image(target_vision_path, output_vision_frame) + update_progress(1) + + +def process_image(source_paths : List[str], target_path : str, output_path : str) -> None: + reference_faces = get_reference_faces() if 'reference' in deepfuze.globals.face_selector_mode else None + source_audio_frame = create_empty_audio_frame() + target_vision_frame = read_static_image(target_path) + output_vision_frame = process_frame( + { + 'reference_faces': reference_faces, + 'source_audio_frame': source_audio_frame, + 'target_vision_frame': target_vision_frame + }) + write_image(output_path, output_vision_frame) + + +def process_video(source_paths : List[str], temp_frame_paths : List[str]) -> None: + source_audio_paths = filter_audio_paths(deepfuze.globals.source_paths) + temp_video_fps = restrict_video_fps(deepfuze.globals.target_path, deepfuze.globals.output_video_fps) + for source_audio_path in source_audio_paths: + read_static_voice(source_audio_path, temp_video_fps) + frame_processors.multi_process_frames(source_paths, temp_frame_paths, process_frames) diff --git a/deepfuze/processors/frame/typings.py b/deepfuze/processors/frame/typings.py new file mode 100644 index 0000000..b13c67b --- /dev/null +++ b/deepfuze/processors/frame/typings.py @@ -0,0 +1,41 @@ +from typing import Literal, TypedDict + +from deepfuze.typing import Face, FaceSet, AudioFrame, VisionFrame + +FaceDebuggerItem = Literal['bounding-box', 'face-landmark-5', 'face-landmark-5/68', 'face-landmark-68', 'face-landmark-68/5', 'face-mask', 'face-detector-score', 'face-landmarker-score', 'age', 'gender'] +FaceEnhancerModel = Literal['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'] +FaceSwapperModel = Literal['blendswap_256', 'inswapper_128', 'inswapper_128_fp16', 'simswap_256', 'simswap_512_unofficial', 'uniface_256'] +FrameColorizerModel = Literal['ddcolor', 'ddcolor_artistic', 'deoldify', 'deoldify_artistic', 'deoldify_stable'] +FrameEnhancerModel = Literal['clear_reality_x4', 'lsdir_x4', 'nomos8k_sc_x4', 'real_esrgan_x2', 'real_esrgan_x2_fp16', 'real_esrgan_x4', 'real_esrgan_x4_fp16', 'real_hatgan_x4', 'span_kendata_x4', 'ultra_sharp_x4'] +LipSyncerModel = Literal['wav2lip_gan'] + +FaceDebuggerInputs = TypedDict('FaceDebuggerInputs', +{ + 'reference_faces' : FaceSet, + 'target_vision_frame' : VisionFrame +}) +FaceEnhancerInputs = TypedDict('FaceEnhancerInputs', +{ + 'reference_faces' : FaceSet, + 'target_vision_frame' : VisionFrame +}) +FaceSwapperInputs = TypedDict('FaceSwapperInputs', +{ + 'reference_faces' : FaceSet, + 'source_face' : Face, + 'target_vision_frame' : VisionFrame +}) +FrameColorizerInputs = TypedDict('FrameColorizerInputs', +{ + 'target_vision_frame' : VisionFrame +}) +FrameEnhancerInputs = TypedDict('FrameEnhancerInputs', +{ + 'target_vision_frame' : VisionFrame +}) +LipSyncerInputs = TypedDict('LipSyncerInputs', +{ + 'reference_faces' : FaceSet, + 'source_audio_frame' : AudioFrame, + 'target_vision_frame' : VisionFrame +}) diff --git a/deepfuze/statistics.py b/deepfuze/statistics.py new file mode 100644 index 0000000..90dbb35 --- /dev/null +++ b/deepfuze/statistics.py @@ -0,0 +1,51 @@ +from typing import Any, Dict +import numpy + +import deepfuze.globals +from deepfuze.face_store import FACE_STORE +from deepfuze.typing import FaceSet +from deepfuze import logger + + +def create_statistics(static_faces : FaceSet) -> Dict[str, Any]: + face_detector_score_list = [] + face_landmarker_score_list = [] + statistics =\ + { + 'min_face_detector_score': 0, + 'min_face_landmarker_score': 0, + 'max_face_detector_score': 0, + 'max_face_landmarker_score': 0, + 'average_face_detector_score': 0, + 'average_face_landmarker_score': 0, + 'total_face_landmark_5_fallbacks': 0, + 'total_frames_with_faces': 0, + 'total_faces': 0 + } + + for faces in static_faces.values(): + statistics['total_frames_with_faces'] = statistics.get('total_frames_with_faces') + 1 + for face in faces: + statistics['total_faces'] = statistics.get('total_faces') + 1 + face_detector_score_list.append(face.scores.get('detector')) + face_landmarker_score_list.append(face.scores.get('landmarker')) + if numpy.array_equal(face.landmarks.get('5'), face.landmarks.get('5/68')): + statistics['total_face_landmark_5_fallbacks'] = statistics.get('total_face_landmark_5_fallbacks') + 1 + + if face_detector_score_list: + statistics['min_face_detector_score'] = round(min(face_detector_score_list), 2) + statistics['max_face_detector_score'] = round(max(face_detector_score_list), 2) + statistics['average_face_detector_score'] = round(numpy.mean(face_detector_score_list), 2) + if face_landmarker_score_list: + statistics['min_face_landmarker_score'] = round(min(face_landmarker_score_list), 2) + statistics['max_face_landmarker_score'] = round(max(face_landmarker_score_list), 2) + statistics['average_face_landmarker_score'] = round(numpy.mean(face_landmarker_score_list), 2) + return statistics + + +def conditional_log_statistics() -> None: + if deepfuze.globals.log_level == 'debug': + statistics = create_statistics(FACE_STORE.get('static_faces')) + + for name, value in statistics.items(): + logger.debug(str(name) + ': ' + str(value), __name__.upper()) diff --git a/deepfuze/thread_helper.py b/deepfuze/thread_helper.py new file mode 100644 index 0000000..c08c6f1 --- /dev/null +++ b/deepfuze/thread_helper.py @@ -0,0 +1,21 @@ +from typing import List, Union, ContextManager +import threading +from contextlib import nullcontext + +THREAD_LOCK : threading.Lock = threading.Lock() +THREAD_SEMAPHORE : threading.Semaphore = threading.Semaphore() +NULL_CONTEXT : ContextManager[None] = nullcontext() + + +def thread_lock() -> threading.Lock: + return THREAD_LOCK + + +def thread_semaphore() -> threading.Semaphore: + return THREAD_SEMAPHORE + + +def conditional_thread_semaphore(execution_providers : List[str]) -> Union[threading.Semaphore, ContextManager[None]]: + if 'DmlExecutionProvider' in execution_providers: + return THREAD_SEMAPHORE + return NULL_CONTEXT diff --git a/deepfuze/typing.py b/deepfuze/typing.py new file mode 100755 index 0000000..bc05f80 --- /dev/null +++ b/deepfuze/typing.py @@ -0,0 +1,122 @@ +from typing import Any, Literal, Callable, List, Tuple, Dict, TypedDict +from collections import namedtuple +import numpy + +BoundingBox = numpy.ndarray[Any, Any] +FaceLandmark5 = numpy.ndarray[Any, Any] +FaceLandmark68 = numpy.ndarray[Any, Any] +FaceLandmarkSet = TypedDict('FaceLandmarkSet', +{ + '5' : FaceLandmark5, #type:ignore[valid-type] + '5/68' : FaceLandmark5, #type:ignore[valid-type] + '68' : FaceLandmark68, #type:ignore[valid-type] + '68/5' : FaceLandmark68 #type:ignore[valid-type] +}) +Score = float +FaceScoreSet = TypedDict('FaceScoreSet', +{ + 'detector' : Score, + 'landmarker' : Score +}) +Embedding = numpy.ndarray[Any, Any] +Face = namedtuple('Face', +[ + 'bounding_box', + 'landmarks', + 'scores', + 'embedding', + 'normed_embedding', + 'gender', + 'age' +]) +FaceSet = Dict[str, List[Face]] +FaceStore = TypedDict('FaceStore', +{ + 'static_faces' : FaceSet, + 'reference_faces': FaceSet +}) + +VisionFrame = numpy.ndarray[Any, Any] +Mask = numpy.ndarray[Any, Any] +Matrix = numpy.ndarray[Any, Any] +Translation = numpy.ndarray[Any, Any] + +AudioBuffer = bytes +Audio = numpy.ndarray[Any, Any] +AudioChunk = numpy.ndarray[Any, Any] +AudioFrame = numpy.ndarray[Any, Any] +Spectrogram = numpy.ndarray[Any, Any] +MelFilterBank = numpy.ndarray[Any, Any] + +Fps = float +Padding = Tuple[int, int, int, int] +Resolution = Tuple[int, int] + +ProcessState = Literal['checking', 'processing', 'stopping', 'pending'] +QueuePayload = TypedDict('QueuePayload', +{ + 'frame_number' : int, + 'frame_path' : str +}) +UpdateProgress = Callable[[int], None] +ProcessFrames = Callable[[List[str], List[QueuePayload], UpdateProgress], None] + +WarpTemplate = Literal['arcface_112_v1', 'arcface_112_v2', 'arcface_128_v2', 'ffhq_512'] +WarpTemplateSet = Dict[WarpTemplate, numpy.ndarray[Any, Any]] +ProcessMode = Literal['output', 'preview', 'stream'] + +LogLevel = Literal['error', 'warn', 'info', 'debug'] +VideoMemoryStrategy = Literal['strict', 'moderate', 'tolerant'] +FaceSelectorMode = Literal['many', 'one', 'reference'] +FaceAnalyserOrder = Literal['left-right', 'right-left', 'top-bottom', 'bottom-top', 'small-large', 'large-small', 'best-worst', 'worst-best'] +FaceAnalyserAge = Literal['child', 'teen', 'adult', 'senior'] +FaceAnalyserGender = Literal['female', 'male'] +FaceDetectorModel = Literal['many', 'retinaface', 'scrfd', 'yoloface', 'yunet'] +FaceDetectorTweak = Literal['low-luminance', 'high-luminance'] +FaceRecognizerModel = Literal['arcface_blendswap', 'arcface_inswapper', 'arcface_simswap', 'arcface_uniface'] +FaceMaskType = Literal['box', 'occlusion', 'region'] +FaceMaskRegion = Literal['skin', 'left-eyebrow', 'right-eyebrow', 'left-eye', 'right-eye', 'glasses', 'nose', 'mouth', 'upper-lip', 'lower-lip'] +TempFrameFormat = Literal['jpg', 'png', 'bmp'] +OutputVideoEncoder = Literal['libx264', 'libx265', 'libvpx-vp9', 'h264_nvenc', 'hevc_nvenc', 'h264_amf', 'hevc_amf'] +OutputVideoPreset = Literal['ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow', 'slower', 'veryslow'] + +ModelValue = Dict[str, Any] +ModelSet = Dict[str, ModelValue] +OptionsWithModel = TypedDict('OptionsWithModel', +{ + 'model' : ModelValue +}) + +ValueAndUnit = TypedDict('ValueAndUnit', +{ + 'value' : str, + 'unit' : str +}) +ExecutionDeviceFramework = TypedDict('ExecutionDeviceFramework', +{ + 'name' : str, + 'version' : str +}) +ExecutionDeviceProduct = TypedDict('ExecutionDeviceProduct', +{ + 'vendor' : str, + 'name' : str +}) +ExecutionDeviceVideoMemory = TypedDict('ExecutionDeviceVideoMemory', +{ + 'total' : ValueAndUnit, + 'free' : ValueAndUnit +}) +ExecutionDeviceUtilization = TypedDict('ExecutionDeviceUtilization', +{ + 'gpu' : ValueAndUnit, + 'memory' : ValueAndUnit +}) +ExecutionDevice = TypedDict('ExecutionDevice', +{ + 'driver_version' : str, + 'framework' : ExecutionDeviceFramework, + 'product' : ExecutionDeviceProduct, + 'video_memory' : ExecutionDeviceVideoMemory, + 'utilization' : ExecutionDeviceUtilization +}) diff --git a/deepfuze/uis/__init__.py b/deepfuze/uis/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deepfuze/uis/__pycache__/__init__.cpython-310.pyc b/deepfuze/uis/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7bd9d4476b9940585096a066c392677f1eae465 GIT binary patch literal 165 zcmd1j<>g`kf)B-sX(0MBh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6v5KeRZts93); zu{cBDCAB!aB)>qvJh99uC$m7exTG{CGhaU~F*!A@v^X@~ literal 0 HcmV?d00001 diff --git a/deepfuze/uis/__pycache__/__init__.cpython-311.pyc b/deepfuze/uis/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bdc550c19e413ed9cf3d9765022772a1edc68c8d GIT binary patch literal 187 zcmZ3^%ge<81ggmyX(0MBh=2h`DC095kTIPhg&~+hlhJP_LlF~@{~09t%TGVFIJKx) zzcR5nL*FH}IJ+djK;Jn(H?1<%Q$M-1xFkO}J}*BdwOBtbF*!A@v^X4BO~Jn1{hJq3={(Z@6<0p literal 0 HcmV?d00001 diff --git a/deepfuze/uis/__pycache__/choices.cpython-310.pyc b/deepfuze/uis/__pycache__/choices.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99acf2fd40ac00b98db1a69857c5cbbeb75c7b79 GIT binary patch literal 563 zcmYjPL2KJE6qYPEu|tzV>0tW_J~UQT&vhY;9R|B>yNojM#b}n&irAL1?8eE@*=>JG z*PV9Td8a*@vQ?m`Pv3j`p7bHTTrLod--hovM(C&8JRbx$?_io7!W|Mw;0o0^$Jk-7 z@@kS3jP8*j-*N6sEYDseITQZYac98Ifg=+)o47e}!N0m-i$wSZMT=jck0NaSkIMA0 zF22fdC9gkeAtQ1M_foc3y{y~Q!t7Oh#RsAEX*pq`4^5@Hu&YwnwQdUC_DVMQ>RWET@BNfK1ZRQ^~Y8<_nik1wl8!+Nvz4KiGF@0xW6a{Y@-SbJLD7wJqLkK@mw{qBpKOf>dBRdlxxp|1+ zOSA8F8z1;1tH3eSJImt#{hmW;9hVPUmC~%+qYbv;&QQs>@FkFOKV*f@y-V*Bzrue3 Dqn4T6 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/__pycache__/choices.cpython-311.pyc b/deepfuze/uis/__pycache__/choices.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..976d9e74d00701d79cabbecce1b3ac23fe4568d5 GIT binary patch literal 746 zcmZWn&ubGw6n>MfNZ>G1L9oA=H8-pnvR>h&7nc;pNRHUao87Z*cW zkh3Y0FVF)QT;fAO`h-Yc@C$*`R|xEbt9&Q@A|=t)%g=z`a3aZLAwY*R( zs8(;(R;B8yZ=q59BRv~L6kmBf!MXOHest_$kGZs=Ow|#k(MCdpXj_=bc!iwewrWYH8w0KlgJDAWBK&r&cJr)Amw=8&D7(Y3$2J+T#_3Up2T9svg|Nq ziJhBRR;)_>7KWd2h7r#9v>vB1*CsX}YR@SjB`ngOv0yOiz0jPLCoHf+d1LLe;Z=&g zHD|~?hw{y6a$eoV=Sh9~G`@%K_-TND!!TC}At!J*yMa@9oL$H1{ce`yaCvrn<{YhM z&_x_W_hh}3&hn6y|UlGgzIy`}*+c113o>Vf=fA|If$r G*Zl+cjM1t9 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/__pycache__/core.cpython-310.pyc b/deepfuze/uis/__pycache__/core.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d1f87c8e7944406d619dfde48783e03bbf4aa32c GIT binary patch literal 5548 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 P(&O==beiM``k(qA`dv+M literal 0 HcmV?d00001 diff --git a/deepfuze/uis/__pycache__/core.cpython-311.pyc b/deepfuze/uis/__pycache__/core.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da6f7634e144063f2efb819c5e1d7ff5a218c8bf GIT binary patch literal 9352 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 UJjGBqs2v|x@dov3O7{AH0|E$U0ssI2 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/__pycache__/overrides.cpython-310.pyc b/deepfuze/uis/__pycache__/overrides.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7046b58f3232e73686f2d32d2545d58b4825944d GIT binary patch literal 770 zcmY*XO>fgc5Z&4JM^d$XscNajxxx~yrK+kBAXEewF1a8$)?>s*6upUQIL?J z|HC=*m-fmjH_lvO*0yLEYiDQ1<9%=5IzKqrBWS-gPrd>nKV7nG9wyhg^apf;2$+)v z3;|qB=X6M$oef#DdqYn!$!5L;2?;$R!jnPb310-?yqX-bt%kkvn<81-?z#-*>(8B&9QwPQnW9GA`udczWSLN-*O|AMH7 z)@%*6w;>y-ec^B5m>_mASb68OV!Hiy#erb}k zr%_JFyA-^TB? zR64G>{usZJ`jg3v_%mN#%+umbn^L5CobZWEN}cA}Zdj%|&X-cDR7ibZRQ9Mv9yQ{P zOg?H!g$SeFB|e6Spzb*ielyyCy1KbtXQS&W28I}@=^C}V)wI=DK&$LGK=tTA`Pdz? z?&PdoAeE5ohD8MGKE7>6ZQWf=?P15XNawda_wkK(*Lw=;5Vxpr15*`gHdU^w96R;E tId}gy%tZT_Fl|%=-h8zK<@8kzUhl;s7v)^ObW>^vv`>2Ao=5D+{|hDNxi0_! literal 0 HcmV?d00001 diff --git a/deepfuze/uis/__pycache__/overrides.cpython-311.pyc b/deepfuze/uis/__pycache__/overrides.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f22ea6ebb656f572e82b02d31479565650bebb3 GIT binary patch literal 1273 zcmZ`2O-~a+bY{2RE)^0L1x<{FSPxB*iWFk-3sgun0i%YR^gy$8W=kvW*4bT>Hjwln zHxJ&>M3WvAYq&ICsRuw;iS!gj)O{Ws!LB@$FD|%#jWMn0DvVk-%cmf}HuQib!#dmpvW2V; zU$!k1yv{R4n>^1ZCDNv>%f~rwO9<`2@_=-BHXuK%{T76>{5}EexYDXk%Ovxm(jd_+Z7P$~(lnYa(DaRE&7_mD zk&@rG^efjRiYuqquDNO^NAoFzP*?E++@9shSkn}+jOC(5O;;h5$3!H?-vObvu2W|6 zG7JqJJ&G=xphb(Cxe~ok&F7X;h~6`DdU5eVG-aEXkxS-byBXEB6xD4rW8^zUJ7Y$T zHOg29Vnz!^cfh+pDcYH|jHH(uI|cUzV3|oEW%M)H_iE(z+8ev_m}3Jzih#B4nbmbo9{`S0QRZ|QnC?u6rY zHNJ=Ahj0aQ{i1+flca)OOp-U{os(p>I}Y7%&&WFJ?%DBdd2>eaUlZ0f2hd$*I zWKjUupSN>Rq=06M`b0462d~MyKEzG}b2VdSSjgnlj6as~|B9XEauNz3#7pf@OgX)LRzV zKz(I_2a1HB;mX8^TebdLs&>w~GZLK9A`4yAJ;DG#TBs9OqXyssB8jyurWzhK4%SEhTZzW3@E~F<83mx-qok;vnPH}i&TXWkd!h}%~(t#@Oy@+2u!d#n=%T*?nnhw z2D4zKNDP7IxT@@)Q7kT(Fxr&BZViBhmdl4Df_rP=5E`L^SWpP45|WBp;-4xQfr11* zayKb7GfX5wL^uaEwr7RiT>wsAVp8U4o%dYkWCLXiU@uH`I0XB(fF%Qi%|>7HZ>Ik8_3;hG`Vxm7s*<4WHtSkc_kH7c#2Vqqy=49r8^9&WMzo zfhPsGP;jBJh#QHI*723p$Lr{-5EgJnE7N|B^kETQ!7HivIl~Ih|99AjA~iBKfh1-e z7`Qbk5WBZn%%5&BlDewy5TDmLQ(K_YWaD1h^0s%F}7cQUSp&3yI*tWAMv*R?u}7E;_UmU zqh4k^WaiK1;4}6MJ(v?c9=r!V!3Q&#k?93q`*UrwPC3X_IZ8Duy8f9mJt$b8%en2m e!lI@eUb~o8KR~nNbli^j$n{*$YyRz=_uPMY_DxCv literal 0 HcmV?d00001 diff --git a/deepfuze/uis/__pycache__/typing.cpython-311.pyc b/deepfuze/uis/__pycache__/typing.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33ad37f578000d4e170325124c188b70f1f19776 GIT binary patch literal 1752 zcmZ`(JC7qp5T2L4x5k4wSZj5!U5OQe(H#~iw9#<_feBUl|s7HHI-DUav2XL{7# zv%WzD4hRSc$Uul3koW-v{D=)MusT8_B3tQ*E~0wI^RNvwesuL$RsD6>*Y&gO_6&Sp z^Jmk`j$!2QNUcgxzuz8{mIZS~=f*yri%0wh1mUtnd6nL%< zHXMRTNTS_WY;G1|Bq1~xz&*y1Vw3**I*J=^uv_a`h?Lu*W#Ry2Lz_0i<)&&>L|?Wt zm#a3Q&-eykN|GQZ{0tWqZS^mdP}eUP|Q5lkh~MK-B(fvbeh5 zG!=DqCwRSPT5TTBlC5*yZ(u+7DQa#ds;hID_#`ebEGN2kTwhIb5R7KUJ*{tY<=(7} z?Wh|iG)h3(S&%B{{4IKSXz@3(Rq?yJU%9G_GDo>`ThBTAM>kE?4a)_c4YkRO-+hn8DN7)c%B_bFu~DhsX9e?*@w8R%RB|A(9{*}Q!`#O62ct*WD#R!! zVm5jo#2Ma|5oVa?k4{E@CiKln&|i$SET@@3Nw?OWQQfrEzre|DUDdE{gZho!xJazMe0fW&h=GKf36D_`~r}_wetR button +{ + border: unset; + border-bottom: 0.125rem solid transparent; + font-size: 1.125em; + margin: 0.5rem 1rem; + padding: 0; +} + +:root:root:root .tab-nav > button.selected +{ + border-bottom: 0.125rem solid; +} diff --git a/deepfuze/uis/choices.py b/deepfuze/uis/choices.py new file mode 100644 index 0000000..6d49d7e --- /dev/null +++ b/deepfuze/uis/choices.py @@ -0,0 +1,7 @@ +from typing import List + +from deepfuze.uis.typing import WebcamMode + +common_options : List[str] = [ 'keep-temp', 'skip-audio', 'skip-download' ] +webcam_modes : List[WebcamMode] = [ 'inline', 'udp', 'v4l2' ] +webcam_resolutions : List[str] = [ '320x240', '640x480', '800x600', '1024x768', '1280x720', '1280x960', '1920x1080', '2560x1440', '3840x2160' ] diff --git a/deepfuze/uis/components/__init__.py b/deepfuze/uis/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/deepfuze/uis/components/__pycache__/__init__.cpython-310.pyc b/deepfuze/uis/components/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45a66f9abc48816fc469b861716bc097f7b70e84 GIT binary patch literal 176 zcmd1j<>g`kf)B-sX(0MBh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o10aKeRZts93); zu{cBDCAB!aB)>qvJh99uC$m7exTG{CGhaU~F*!A@v^XNCFp literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/about.cpython-310.pyc b/deepfuze/uis/components/__pycache__/about.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94ff5079efd0f9b9150dea0d65c4d7591b15497c GIT binary patch literal 758 zcmYjP&u`N(6t*4b$Fj9!6|6$yz%>UNi6fd2D@Eb}EfTGSdWpQ+?Xo3KWILVO^RE04 z?8v|5D<^K8ktXq+3bmH}`Fr1Up1&8n>U3fR4#BqU2 zJjd8^Qjj@;)hoP`&S~k-eb5OD1Qz5be2zrK>4j5{4iq1hBOdbTp3Y-VzM>@l4b@SC zZ8WJ(UP)Gr6Jn!M7{-}lHoU78&*jRVTn;C*X*Qfqr<1Ymj3(pD>4*PepoD3ZT)0|? zBY@yNOg#cH=ms0|!{hh?bJ9mG_a1N`aoTbp^x(V4XuH#gogpX@IF3OLI0C=NV@o9~ zg~cyzxE4x-XB*ZkFPYj{uTe$fS%1xnMnImaoJnJ8k;~idS(EDlhZc>g!D)c GcfEgsu)z2L literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/about.cpython-311.pyc b/deepfuze/uis/components/__pycache__/about.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fd813d56e93547faf906a3625a9492f9552a3b4 GIT binary patch literal 1209 zcmaJ=zi$&U6!!hd<#KH(2~Y}z!T?H-RJ|Qlky?sGB}fMnhAfeZ-8OCRF7o*b2^}&Z z!O)>YrFLLofFc9~e@Y@{h%8k?Vqz;QEDVU}v`(7KKR>^T$1McodU<*A zl#0+NY4nG7q#Ue+vW*IeAfi-}s}vPQ>S|Rjs<3KR&DD##8!1L$U&RJhsJasEySdO; z5H*QD4Hj@>|KUV>9Fs(!>P3sF_mLg@EPdFDFc)gP;(NH7vsGcblw*Q97STsOBNcB+ z4A0Ci6!N9nLO#Ec6Y;r)+)Vy<-!vHIA@j~A?CY5T&$mv<T&9bm^f^`Wg&MfF#zr7xmH-fXP-##*DvTwD37i|V zJugg`huRY5%#d995KO+=TXX=3zCUmZ0c7gbaj@*n(O{MPHRqP^F4hb4PB{#?@0L8D z(7^c>|92JgcFMk6^F8YEz`+mwkWba>0(!+Gl(CBtME;QA2|yElGg0FFR(#J&ZcJ{j zwXN}vHU9EU+q(Ap@vHR@^X+uDlg_rSYB-B=vBNy$x0Zc=MrmXnP^d|PEEEIE5f7`$GChG()X z*jWH!{kmYUYKOIwRTKK1{Jpzkq*TJ5=W{H7P*5uM>e#y=J1@P+2uyy~@8-1Yldwv& z>Ck6O+yO drnxiv;!@}I^%j~0X`{(~Bd%!s=mh8``V%q#7W4oB literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/common_options.cpython-310.pyc b/deepfuze/uis/components/__pycache__/common_options.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..67fdd6fc87012872d70fb2d977d16d05e455e989 GIT binary patch literal 1243 zcmZ8g%WfMt6eT$ijYbdKs?pX#T(rB)!WNLV5d=|!rf4k%NnEtr4Co;(OCAjeB*(6e zWS4FJAz9{^xXmiNt_=jpz4RkW2=MaEC5PwS2UV|^5*Ys*eiVj;e8i8{0Y35&rnvxf zKm-xABuhG_OEzT`zgZbf1HXpT2-cvCmhm+9dtupHCet=x##2dUu;^?N*%i?q5q;4T z$(wN61Jo8NpuXrxCfBxzhy^CK=3 zBA6qX=5H`F@|sw(pbMruugJSV(D#&Hc|yJEJ?z#K>22=`yeD3?Z1S!x!uQms3;0_^ zZv);H1VV|&m+ccN>m4hX^<`>K=JgJ5#9UqWJQnJ#Dis$Ozcq8STd3tyRk`v}o4h!d z#mS-iFP~}E)LG!7k{`-aqi`3U^0JXx&!tDakVlPyM7h?iRENAY&XeXy>SA8kvJ%dd z=bqeo^8AL}#WT%8lk1JH89kxKesIA|TGs(}Am=LG{cmOJ7>;FBcVYQJ^v|$d{yRJ{ zQk&s9H^;-r(wtaT56}4N{Sx9g785Z1Dti?I(Gc#hRV6Fy;Yvn!>vODTC8X{EjX|z3 zNN7rLW(X*xjHaLU9oYP)=I~80BwzCZ0IauV7TWx+Z*QmRIrUOy udG71uljH(pwb!wt7fY*sYLmUC5=|)|VCoD;Hy{a380sIeG)On@gkJ#p=qMfl literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/common_options.cpython-311.pyc b/deepfuze/uis/components/__pycache__/common_options.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c33a6d3cf664c263f29d0d06b0d1663355ac3af GIT binary patch literal 2072 zcma)7&2Jk;6rY)0uN}vB8bShXzU(LgS#4q=gn+6of;2z`wW~Hp$d}b-XW}?n@2+Oo zZ3Cf12m}WX+`<8&TnItwp?}I6Enuw_sS+n{M&XnbZ`N7IrfS4^_RX7lGw;25Z+_1| zr_)IUZBAcblVybd6qBgIJZ#6`!LWV}=ee!iLWFQH7EGsNU zsd!gie1B>2gLg_FtMA<@m2U?MXKsU=de<>vV(>}u9j{;!fGYY7xjpg?Snae85^;QV z8|{fRPc@47VgF(G41SibDD3DQUF3i25;q9jIl82$bfu$H2yMh8-W`rOSRIIXD)*mu zpN}N4tDEj^^eDCuQ90~JMoUoyNmPm_1DlN5^De8~CrM|sX3uL4TC+~lAy2LQreSIH zlU$?bEa+CfZkehT=G#&Ab*68wS`XAJw;Fa{3Sx$~$_%c63qkCjW;9qn9VFK@ovk$- zcp^c(YFMk9;RK;kRTvEv&9<3IgV4Ad8V^F_YGgc6s$2t~g7o64#n2!Z!40G;bAv(f z4&3QHv`oL7TtqOBc?JeYWIubN7RnCePGL)P)(bb8v*}uPVbQ9uZIzb`dc$$8x@uaK zIfXOdds(nn03)_#GSdy&p0F&~TjDXBlyN=|=0p;lAA#(kwt|wWozWvXZMk4s2!JQq`wi6e+2RHRHq+p@m%aYgB_EIYc(Rq7e>~;o-a4I6 zwPs&^{Lq_SI-Tb{EP5EFJmCq@QOpkqio0b)0q5dUT=>jKfdtQ;0b=14%N>71EM|J7 zfRV5e@E65lfk8edGz_V#W}23(iT?~oRYQz~ISnMo<((_##P)9HPZHPa7Ht^p2G4^) zWYPH@NL#`fw@}V|MsK0?PWXF*E_zXKp((Gwx6mtIe}D4!l7}Yu5(i^`{<@#N;USnW sHPKRA$zZ9C`pFE&A7Oyve`!L(@8a)o?v4Dg+(v>w0Kkgzd>Y>SU+A>IPyhe` literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/execution.cpython-310.pyc b/deepfuze/uis/components/__pycache__/execution.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cb85b5c498da0e720779d49ea6bcddfd706e0a7c GIT binary patch literal 1441 zcmZ`(&2QT_6elHFvgJ4*PWlye!_dP34VVoKI~Kz*H|UBDc9Awipo_qWv?Es%1(HgW zAvz82_rIA|`l-;VA_MG1-Io9QO>J+>u0;V-wJ5V6Cf=Y((b9*=(y(jj<#z5(7Y9|$V;o)F%}mGgja@+}yD zz`Zvl+4&2iB#CE(p9^hlch;Cf)~uW+)JE^6;zhjxcQ-EuQ`wy5B4aRpp@o9s&u@km zt3=i)nG3C@(pe>WTM7-w?u$B?Tx8;0%aZ{tApCOKQAppVN=j1g3Kx zek51uMa%B8cSX7P!DlfikPCnM>z8ezNB6{<<)G1{Ar{fnwzcaEKzh`IPltuHk zHZ6oWzB_&0g%pf)Sv9g2wbA3-+eXa=ro0C3HsKnL&mi!-G{)~=^%%OJreY6fS5z2i zIsM?HoFVDn6jTxxb<-McBiR~s65j!`VSdW$g|Pc=!=$d(ch7q? z$M-(Ur4kOQ-CJ9BE0-01hZ@D{VA`z=Zm6YU_$$y^=2YXjvA#CS0lSyc?(@|@0#d>ry4~!K6Px|8X4ffc zsmh_f_0E+Gv=tnB=%J^oe?Tj(t3|a&vV_!LdNT@KIQ7lgUfYm#cJ|G0=Dm3{^FC(2 zPp6Xz%8ap9TpvK_Z{F#YSR3sA3g97HLnbn@g>0;1TT&&=`;sNAvS?#!Otb^40Qd0se){vc6)Aq1B41GyIKqMlU-|H-Yv;z=1XvWV&&rF+x z-^A2I&?ccBF;fJS(XO1C=$&7TnL{9T*pzOf%<#WlTSj8BrIP2f#7f04xsGlvXE2L@ z?ozYlYyuoHETU7bs2fDnfxY7q3d1Mb!&JA4R-vvzJkO<`X1iw9A|9xnAdcahL?icz zQRS++*mlVz)MFDS*_U@}eto{Mc57i}SzEoa^2x&G`5QO2!qxf0wM#4O+LaqCYpYD5 z#II7vi0lC%4!;cl?(aYzA|FH3F69XO;L%Pgb7@gb`~)PPcBKY89EAK-7nhX|egfcW zr-S}y_HZPG%83pxNBa9`K9iono}Nbg(vv0vPsz()oUWF<+#Z(Hj4fi^-Ei+|o7AmV zGBO*m^bKOMxUuDyz$t8CTeqqtlVZvy)yVa2TeA99_bjxESd+d+$T>261@{_+&^J@@1UVcaS zw(^&Wch`3-`GRW~ch(m2M%D9OTXP^}ynI)EF9aLp4cD%?4sm=hA1#-w?C^+iAhhT) zScu!)TLba{y;jid2jS^+jpV`a&IDr@YRQXX@?vA?^y4qX*~QxIQaHO*8@e72U2i1c zf2@WxAB8j5YRRQAxzvouX62U%$SY0)Jl(34XO{tgRJaMb&vrir0+AOn5PB5FRL>yt zJNF0-_Dc8SXShEW`27H}AT2{`DJ7>;^*t7Mt3C%AS=gB=#+L4E5;jq-n7Y4b>d_;x zrZ@tf0K^mvlAkzq3ObzS%>j9U8hH9Ioc$ALpP!k3zDi#s^flI{0B2t!ByC8q1Z}=U zTZ+d)h0%L(eq3Roe*pBSW`81Kz1n=KR;3_z6_xfnL3DX0>&7`OT4dv=YhEcABj&j0p0eYB| zF;2MID8K9JK_D3{20U)ZN#QFx$$6X{hu`}Q2zS(I4UdI*EWl$8e6oh8LOd1Vsrq;} z7T(D{jPPpLIa&DM&13cQuUJmBhYuP(t_RhBxAsz|wY$GuoCXNR)XM@CC zEio4+<^nt?{F#v`zXO*uV@&aPD)1~&?j*`B-r6re55e_u#okO&MccM^nK*wT{vc#8*d$f=+#bjs~4tM<;^*T1RJt z{#r-Zg8o`Zw}ZXvi;ourbn4Oi55+KdDV!+;2>OdPwAfVQSZ<1EZi1oUm{}RKh`oKCIA2c literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/execution_queue_count.cpython-310.pyc b/deepfuze/uis/components/__pycache__/execution_queue_count.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3bb0a71849bb0099ab998515f4493147030fc718 GIT binary patch literal 1135 zcmZuwJ#P~+7`A<%y(Vdi)Y8xntT)g|%oRdvgH#Enf;JM$a=2$Fz4X4Yef>a;UHcCh zVB{}(W$MNZ1H5*F8blo3>pXdE>)ow=!#t%s+pB;q0o5kYrVDS><9w+&#oP%xA%kJC1;bYz5dVTVt!d z#XLqPE^D#&jW=#Ha*X2jJ5WV2*5OgDvZ|!{5Ej8@C0JHY_0HhkV1IOScr;Ajj0U4Y zvVSxho+QVw4-W=!wJ*48M46f+7DoV!eR$+60EK2)kuMIzU$Js$-pmIKW+5XzG-pl^ zZ7uNmuG>S*yY=V_LtxY&etgDV|$Q*LLu!;xzf>urhL*!P}IRR zuTE($^~O>cI!Mo|Eag&fFMXc~T28re;p}fnY#6Twe!lOIBp0%OP32ktfXj1L)%{C) z@jTD!XHqp_tpDI*kwQZHX;su!$x9{sOI3I4Yg4Ea^0NwRvIF2cA@=bmj?C|ucnn+r z717wVMeG8(FQS6pyd|bDp&-OGil3Erqh##ZsB|EB&Z*>jr>PlLJXz}KA@qB;2?E;B zGpJ5!tVjaT#bO&K;Ttd(Qd8Hz!*KJJHP{03$y!X{s(>{-1VihvD;^mz|BkVp7Nm`; zcmiN#tYiz@bxef0lMtR2k2~5|*EQS*VH8ChKzASNu{&=HEBMcgc{kW*viT7v_j)Z! zXjxW@nl6?}V$*Mf)lOC_>tVsC?Gu}#aIdIXlk*oQdTFBSpb&>7#4X$s>Rm z3rU4g4&}zZ95`@50hIiJT#&eP*`TekMzU0?r`(JJmtNX8ldK)95f#tl@4cCM`{unj z@6FG}qKSZJoVCUeIzmt7B&F=JaQF=fk5CN}M6ie2xQ?-uHBYN+itF`^;#tLYFVi;a z2Iy#Zj$*2{#!{0PQvuOD$=rq+87IcWOx*;Y13pd)Bu@(@OSDFYm=Ck{BGFb*sdym2 zEMYOW+zH*lx4k8pM$up z8>Z!zy82M}0bJex(SN0GW@vjiyA6@-45G2~1i>{JOAoF_E}x|v)QRLhSYJdmqLvdx zerR#eC6pb=#4ruu;5kq!E3%$_pL!zurR_yjWOztBBG-0(w;i=Xus5VoGKJA(HgN-C ztSCb)12%--q@geywnG~chZ9Av=>_*~kBc`?^+M#FwZL^K7n7&%Tg>*Gl*!0`MTdas zS)*Lzl=1SW&DY9zDPIqRPI)0{H#TdFWhdfc(6;=5P+lIoaZ-egl%1g634H2@ynO1h zvz<*@hdv~TU4lc$;m!Nue^F-AoF1`s2nm{ci;DkAxUs zJ{s%-oIBDDGSjLX?tuW^0GQH`gE~)FBoE~m$8=JA7jMJO@5tL%Bp(W>8%=hr;rg8@ zL?BiB~TX8y$Ovp}T=K!3OQAuxA^fy3wguWh$fA|_$gQ!$YcnKmoD-BLa zkXB1rF)oc?RsOPRxhG)~{#xmkjSHV*mW==!{UDc^mUAJ7fr@5jV>x~soxWHF-}J}6=0={rsL7^WUdmUsqZ&_ zF?Nf|l@DSB)5<=o^o=~$`)Cv{V7!j|=p`J(cwVi~vh3D)lb_`vj3q literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/execution_thread_count.cpython-310.pyc b/deepfuze/uis/components/__pycache__/execution_thread_count.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19047c456a60611cebe3ca61dd7ff593e1499460 GIT binary patch literal 1141 zcmZuwO>fgM7`C0yWoMN0f>V}eRa2@|7CGbM7K5NLaQhoZ z>}ju{r@YYKIV~ElU8#7jgLz)&^JWf&UK$cd+Mf!_@=E*1_Co?ng|su}N=Fl#@kt{= zQU}wbI;DlwTdQs8AUmt_j7$A=^?_8-a>|7ZU;m24L*v%KPnwJ*7c#k`@+{fs@`I}C z96*TA#G0h1@A*NgWysR4~W5a$_7LZTYV+t1ryx}1@+Js&4z<~L8jP0}} zZB)f$03%~1TiC8+BFxQ%_^f!`(Z0H>;Z6vnDB1wJdtZ_MqA{%CKQ9&yVH?UONSNg7 zjWnfYSt)8-S*EGYzYSJ9d8w?2C7-raY>L9&dBvK7zckTH6IBO=I3yu%;Vy}ssI~3> E0gGQ500000 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/execution_thread_count.cpython-311.pyc b/deepfuze/uis/components/__pycache__/execution_thread_count.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a7ba0947828499694022b0af1016e3cf96f68b1d GIT binary patch literal 1805 zcmb7EO>7%Q6rNezYkOl;+Ek%!YEy~`F^Krk1CUlmC5}WSl%OP*kT0vPXY8!mA8Te8 znhK#D%8h%kJ)i(exaWezoy!Jog*B3;NXkc*obOIdTYrlxqRnN~cbc*;q;dQ%4- z&B{_twQepkc{LFb&6D&Un2{->KTJ0b;91~Pq(JhtKr%#Yr-|_}(<~Bg167Jg^2-Vq zxwT$k`<~^j!X*2Z&xq}HL}~f+<)y~v%G#>A`N{h7-MYE7)>z#%H$GjdFRu%o(I8}A zYjipY(kF-*9nWJKT>zL+cO(Z%4q&S*wG|P(3mSW4tx%J5!hR{3ABDOE| z4P}U>!G=h6XdsNX)uQc?!;vD}ar}Fh!^O<`ZisAa%ePyUi|gkrn9TAzl*!BHJTw+#V{u?yj*aWFachvDj`Oo| z{)2;yasKu%-MIEyTyrDOk88cdd~Z;gi3{bpaQonHT&Rs_AIF7{hEw{@)Sn38F9{JY zpA2>d&YkE8nQYY&>mWc!04DT!RO`u#mxF8_h&e#di9 z-mX7cb0R!PIBfydLMqqnRr&1&)3iL#4=mZd+%#1ksyvF6?FA>EPGA&U=HKDTCd}STXMgSe+^Fff1^tMzw;rJBJU6PpYq2dRky?1rWu0li(;W{db!qVk}2ztO2RYmcApHFi6#-8#_vZtK%} z>+_~qk4tM08#CEvT=uZhjF|?G>y4+4`%hcDohPk&}oC$-&Aps#yTX@a?Y%0^wB`O6k|1pgg`x^g4YmS_#@3Lm0y){bYU1=}v4lB-N<6uKRjVhf zIrJQf9rk*N#XZxQg$gzN?wGk9-|IssUt%dRBrkt}U)2h_ioFyjYz-5U9QH^3;dBU$ z^CdEopq?|LLx&y0V53e&MNZ^vrbDd9rLmE3#$|KkRgS}v#K$T&eq#Mp)X?-5{e8`7 zzSgSOSQ011=(${^G06VZhwWP=d*1j9$MZ$;YGk?GVsTCY4wmAO9D_*om_z$xVeF2N zgk4ujCzhpdd?J12A&#;v_Auy2D1oWyp3Ao< z#{y0lJYSa~ihlsaUPuu0d;N!!wM^2)BsNLl^POqgBm>Bdv%jnS*DTnG0a~ywwO0WF z>{~O5wn5AOw(`Gp9`WGG5q1}X#m+QnYZkonO!I)@+5pZM4)?w@+#?NWncPq<3t9giNJgVThk088_f(pVT|c$P1yRt{Quo(o!X2Q`{-pGfpalPUMBI{O{O z7s>II%XT41&P4YBmtU+;C)D9?C!T+D&kQoN^hyOt<{%9>UMW_IirE>D5SF~Gv>46{*GqMqi{c9wzx_gl0`czEX;?E>#(A?64^%aRre z`vXAiJs@4&l`ntD>+s_0ep3Ph_YQJsu*9i0B2=z*N5_@;T5mFxR}FR(KI|3}bXO(v z6X_E6hTQ}5CwRX>?<;j+4bL8dlzj{&76WY{27cya)ByW4;-4VF7JDDa&y9dK z5CK2)5q=LCMO;R}7JC=Sl?Y*hd#_CA$uRM{Oz$nOuELC)~DVBHFN9 z*^r3ukXxic7|Nu-E3+6TB5uW(vnZ2~G-Ek|y@ANGQ*)7J??Ei{E<=LR{2GAQMq^Eo zvKG3G<~G_u4E)Sz#RI^eBmOB8Y_Z<}`MD9$1|r~RKEgx5P7s$7umx*|he5M?QzZED z1paDcI9<#@Vj{sDlnWD_*1?%`Tiqn&BF6;knkiR0xQ56(g)&niZ1kBzg8vj?3qXQX zS*hVoQtnx}VsbZ@St0TrIL$_5?%-deey1b%U%AW9j(NEwvJcQup1*kV1iREdN?OkwSNV}}RRjx!UPJDCe|Gqmr*t~~YJ4{?L@F;mwyTZacSummS&Q7@9& TlQz;H7SlzuXl|MKT{V9MRgy!f literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/face_analyser.cpython-311.pyc b/deepfuze/uis/components/__pycache__/face_analyser.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27402066e63f68866423fcc50774044594f4bb96 GIT binary patch literal 9297 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 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/face_masker.cpython-310.pyc b/deepfuze/uis/components/__pycache__/face_masker.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e67628541f8bb29ccf3e074097a9fc4b120993d GIT binary patch literal 4088 zcmbVP-EZ4Q66f+mAJnI1NtRN_`8wpP`-qXl-L-cuiYB(>7)|8BcCJ_93WC;BvJ^^G zm#d@+l%hcI)#yFmV|#D;mln9Edno)?^r>iCq%%v>qCTznYXJ%(fFPAeF zxW4u6&Nnqh`5zAA7joZ+=6^;M<)K0qN?c_?T4bQM)PdI02x6+Mw{-b5S_%0~%BSWg z2B}s`%II!-V7AObrj?On!_5wItsKy*op*?%_X~B!DbmEdh^$DYB$1>Pkna6rRb!Zo%A=jiE^b&Bd(957T zBekL~onE0=0l!MsL#6SnGmK0_6RG_X?|DPp6~@tc+s&*_RfJ=jlt)#)hT$ev%9z740Wte@dvlI zxA&U6*3tfhNL9>&hgu067x{5Jf zl9sxPG}5(#e5tQ=t|j{#TEKRxW%Wa-2|Cj=(h7?txUR-Dj?b!QNKtAK1e|B}; z^KW#ZDp&(33l@jE;9Knzr~L?|tSrO4he-}clHXdhQTU83mwYLNF)cnVQ(-V0AYWM1X}~u?i4yRux;bvlu8!XOd{EJy<^+;MPaH|W|T;DnN541$ZdtGwTqd>4D^iFb$H+{ET+N;SQ(@fhcbmk zd8$u3huOm}oL{pj!|xKI9e7W~+LUwd7dd5Rfx&Vxi{-H^U{^#XF|lCXK3hR%3OYPS zPi)`9{egQ%6mS%}2Z0v~dmH3__}k_~-(mjdQ`nX`uWD0c|FpvlGKkIugoNB5AHl+e+45J4#ot4@?iW25LoUw z9C~nTT<>a>oWe1{jedeBxk1%aENwc-q`1!YkelWv&+u$lf38fFZRO+ib21??BL|12 zobkz3S<g;uAuu1*ewz+ezFZe z2HM9wu97?{k}PCFk?5}&27&zsx;EC}=)!V_yHAGb4iFP%WXMwqzfCktrDUSOcMPGb ze5w#0^m@G#`~^3Bz%`T1Rn%-MzH#;E5!b1T!eaqPiGn ztiWv@UK3qNW0Olk&rXyHnWz)3qhrW|C9xe~%q;4U2%XYE5i`^b}bAG)I*eB5B z8q0*?nEeU51&?ChNHZ=2F`qNB93iI!vO#UKZIEjuV!=nXbHNACOLWevTOc`x=HuFn zkj)H{hRRCtXg02i1Z-aTE?^iLhL$mulu0E@=+~gM-UyE+X~Ya8{GJKUAzAdp8KGh< zVk`-rSdFQ}kA=3!9C;kb`XW#r3!~$DHV;xx>kWAj81zCcg3@cU5~SW8cy#PK?_uuyxUH%R>=88u)c`9(NiC7t Q+tkdv*VCoshQ6-<3~4z0-T(jq literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/face_masker.cpython-311.pyc b/deepfuze/uis/components/__pycache__/face_masker.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b73f558241cde02337cea8e12b1a80eca88c86b3 GIT binary patch literal 9031 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 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/face_selector.cpython-311.pyc b/deepfuze/uis/components/__pycache__/face_selector.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0956303f5cfec354677b77da7afcb0b6ec2b5f21 GIT binary patch literal 10029 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 QAHU5e&hz}1fXny)0H!dTqW}N^ literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/frame_processors.cpython-310.pyc b/deepfuze/uis/components/__pycache__/frame_processors.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..675ff8f2941c6b0576f7bac4e9daa0a19f89d08e GIT binary patch literal 1899 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 SKRx|B``cNY|9@%)rhfr%&goPD literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/frame_processors_options.cpython-310.pyc b/deepfuze/uis/components/__pycache__/frame_processors_options.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b295b5368ca3358ce9ffffdae4eeb10b63d2b13f GIT binary patch literal 7109 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 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/frame_processors_options.cpython-311.pyc b/deepfuze/uis/components/__pycache__/frame_processors_options.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ac099f7fac9d499f73013c8b208bda4ad7872a30 GIT binary patch literal 15734 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 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/output.cpython-311.pyc b/deepfuze/uis/components/__pycache__/output.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fbde0864c02173e5cd7af6a482411073379a37f9 GIT binary patch literal 6234 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 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/output_options.cpython-311.pyc b/deepfuze/uis/components/__pycache__/output_options.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c04a025562628996c59d8d3a30dd37cd94a3b3f GIT binary patch literal 12269 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 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/preview.cpython-311.pyc b/deepfuze/uis/components/__pycache__/preview.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e877c3d0697427e1a0bf374737d6a88fdee372e GIT binary patch literal 11951 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 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/source.cpython-310.pyc b/deepfuze/uis/components/__pycache__/source.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d1bc5c39e08a32a524cf12051dc71b41b8908ac0 GIT binary patch literal 2330 zcmZuy&2Jk;6yMn|+iN>coR21Hg(%R%f@&8~51^=3lb9V&DxGrvDUo(&6~IL@#gn?&f0Cyfafm}j(%zw z#vj;Od|c>!0Zsf4=E$H1C9#o^fFx#MCRSh(^qH|8*!t}RF1)SSO=>|+kJ)iOX#@?8 zo!Cp7K~rNlZYAxYoh$`QNhjze%fYht*W#6=8*~Ai;VL7{o;0=$wnpna=vmjE4ee=Y z&kgO_)E*B!tF%Siuk2t8&=Tzc+NR6QWH1YDG7IM0fVMd@w~UcZS7;X^Zc_8m@Ynu? zGaA07>d$i-WoZ~I`)MSka=t0@m>u{;)lM@`qjU_=-iu-e@5Y$P!6@QF`nGDEgklgD zG|DO%CE=Kb^1%KQ=B1j=nTyQ?^R; zg23z+;Pw>TF#A$z@ZHkFxYD3CTEDcy4)~X0g=eL${bcH2kzLx5oePr~fIPW^h#GKB z%>z|WidaT)C%$uT_mccmu&3b*uz5NLJITlAPVbZ*=jWXw5e8}LI;0s%M_=fBj zkl6~$vz!U##NjcEmGdHu3pON>so7%!u%R776Ki0`aDZ>!m!yQFY#BeA-vfVM11Hij zVXqC}RA%pfu(Gi1Ku^jy;ahF~`Ko^;7#IDsP@ME1Gx43w@;;p5lQ_!n35gWykHR4v z6#}TZ2;o5eIWeF_i&@Q>owzio3oTM-rI?@VHk^t0A^u(zQxxe zO4VLOB07$l^2AAYI;axSn(n{i^Q@-{>#y=Ue&=0nQPswAb_@%sS)?X9hpJ*Icg#aL z5l0?!B&4!HHoT2p+yJkk!BWJVV3f0q3E)c_@1ux#2M1eVd`B&#%&zTj(W7^GJ^Oes zLd`BjA4qF%?z)oU)gAF|TplEa>01UYdSF^ki`c{@u34{jiTn3)Hpyh~skj8{a6GUlAdu&p2scR|_-8=vyxlx)IQm9&1NCdv4v!>kP zNti-yaf_UWP%to6RgUHyt4>lK0Vw3RufR%$%5ZAblS2W+>56}V8HYlOy zD?oFZ6KRwvaZ`c@Q0k>AZj=^(04K4~13nwDJ;hF`%gdl9>yt)lmnO9?O~791losL< zD2N(Zf~puSG_#l)#u(o@xVBv+xdly2vkO2=(?MxYroyc9umNMf$+zI8Y&`ZG7tL?N z$mh^>g1VraDzpEm8<G02hCkDzRN#`S5Va; zO;P?n4t{{fNAn??s|&@uOPi*$Wq6zk}8BBY4gTo{9zlVs`OgvB}IItOa;Q%$lnAYihP zsVlLsF3g0h3^UHO(krHS27@q7Ga2H4vKS01s_QK3nt-X)GSigShhIpUPM+34eJ9Ci g5wnL_*#%zRh5^YlF?B7n39V~(Ntbw*cjvDCADyi;`v3p{ literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/source.cpython-311.pyc b/deepfuze/uis/components/__pycache__/source.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..22162ebbebbbfa5db2f028d993f3589bf10b678e GIT binary patch literal 4221 zcmbVPO>7&-6`olxDUzZ@{ZX=IRZ<-Ph_Wrpv6VKiqExY@La8bvvXuZ-Tx!K#QdDd;3SIEv068dt3%Ia=rhri(ssrobLkjfLdr{QDve=-+0s;c`)EfiY7hl@OMK7w8UGK)_AKDj)#@D zc$>v}u(|;we zWpY_THu<@Xs+rzvg}f}yN2%F#GsnbCHVsvNCL>Ew2GWw2NM)F+MR_x@DyRveAZBuQ zHKPb=$?QmFWKCkWAdwfeRn_5H8a59Gi!dn-GlFg*lSt;2d@d_xHM9BZmBoe2(}|hc zxoI<0YfLUq&0cX@vp=8w>9px%l2%~ZWc}o@Y=X}N-`*__jY0#{s3#3L^|a>H*HGI^ z+o1c%(Z5YJ=V}T0ANx|Rxjrf(Yjr~f^Gt(L{GMvz2C2VZ7d`G36p6|eDQkb-+*vA; z9e$gK)x&2eA)s~C>#zdLiLlMOMeiMQxNM4SgT^DL8;*OM(|Q~R(e`bL6gl^5-L2am zSHENXW4xvx?>f2GdcPuJT+xFoDiYDR)3gn@_Q<(8A?)ha=JcY_i?@2wLwdDqC+nKv ztbVP3zqJka@yM|eN?e)&?fA0KF;P0GiLHfv)A# z8{Uzd(n@|KT*#;+YOcVN68IMM;LRYG;1dP#2fMDV=Oxwj%EF2yo8B9OT#%BEvBGlj z>O0}v!(H4bX*j6s!P~S5N49@!zvecZuad99pRWKFkP^`&*K2ufM(-JV*lqNT zI=nqlAu~}ALu)mgZe-NViY%Eybv1W0VV_y3wn-bX>MaYAqMZzV9ZuTWgxQpqb1SeK zv#x0&8ADGpeQ72D>M;ZxhTd&@02dirtF-`zKFtsUSO}%wNd!rTUT=D55hWSgC~E-{ z^_p#nqpsC8a%@y}T@hZM>fD;OAq!L;Rs+s@VU9se3=IP@6VIBRhX$@e{RoVxM4kmn zr2ltoM*pbM|8BW!!swdNY5Q*14_@SjBb%RA!bhvT&%;+qojhwL%^jO(^zPH;;E)j< zs+@aQ4=(AG9|HYi*a-F+!Ke=TDaSgvO*PV2uzO@?Hhe^#`s_f zs^!Ra!@l-gMV)r+M*6=UIIq8TxjZms3`~_H(?(?aRs6Q@d!foeOb@=J2aiF1e614d ztAx9%O?*470mxG%r9Rkyd(VHb%k|v-)mOzby=c&jI=#3{yIpNyL1u$+zDf9;gr6C;N%DhdC=&~4qrpBuBAq8%?T+Gnw$VaNSfQBZfL|;3jh)~53cUZWb*}0HJfq; z4J%QvWwNF(xhiBKa{CH-QGjrac4`|a;@clB#{9=xWXw@5W71UEhi63*OO zdwmTccSbJqMQ(?42P2oJGiuRW^c>Vo!8=bR|KH5`PChu^;WHnmi8Dv}dDk`L#dN~A z*9W8)paK}6)|{O~cnV%6%(6NRT2YRnvdLRUV}~Tm5*dbsjb7Vc4~rX@K(6)zfw<9n z&dyunUH)FcXSI5Gq86zw zbY^E!cn(Pn$tVyNz1G5F8;sf8C_(ZLO8*U{L@MXs)?0sKukwKeWx4g7VQt}0Q>9)& z~;@)JNVY)OXa}{V{oF}{hrbNp3Zf?gk3o? z^4YM#bs5|nyMgd#pWYwSfBZ8&Fjo%D8G*ScslUm3QmYa&$$iK{afL2$-`mAgy8vcz z0Y#n#uJY#T&w35oY0$x4f9qyw>)OLb-G8C%zhL+;{PCl2KKaWDJ)Y3Vmdj(y#@Mp% zU)Jez{Z7ys=A(V44_-a+2ZW*RFvLQJj%%Gh{HJh&^++vN+qZS`I@I)I+iU>f^@y|ZB?oF$oKk`>i$U=1e{LN=Sz1pI@g zCK5KXT4vLR`#{^VBC&<2wmG;V_ql&u$%zG7nqX6)j6p(80Ihl`r4=%yzcN=yOUe3u zM~>?CTp|6sJ6Fgl-JL6BM0e*3`Gx+vTp?q+JAZd-xYYdl>fIanJ~dk3)X6a9G8wK6 zoh^kvzqRH2GGv5L>f|isGCBJacGY*Bj#Y`9d;t6ZgjUH5)E}e*y?b$szjvuhu>RoW YL*Gv5aq1gcA9~+7e%a=}np$@FALTx==>Px# literal 0 HcmV?d00001 diff --git a/deepfuze/uis/components/__pycache__/target.cpython-310.pyc b/deepfuze/uis/components/__pycache__/target.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..543be3522b0b438cda9e1ed3c140b3d69124084d GIT binary patch literal 2306 zcmZuy&2Jk;6yKTs@cP?vlG5}eP0N=oXe=Q(AXSJ$lgd(?3hGwXURE2=*jabKG&4?= zC_R;H`3pDz;==#KX|J5%#03r$B|fT-@dPTGxOf>J?G74&4T9-8I3=wTh^~A ztUewHpTjGE2lC8f79)w3l7OT%paf-_*nzFTPT<1VPTbTBJZ*E5O6muGS`DgNc9U9K z59*rtl1ADLnrSO&rR|`tb(N%(t_5qF_mghA9;~Mu!3N0HV3X143a8wj*0(JF7OUNZ zy`!sIv!yk4t+}Q(*R`ernoZVX?Qfjm25=p=2HZ{7<#pEN1Y&K$dwoo|tg*w^*#=m* z8GUN?uKWh~wR*I4hqEH#rT?T*ah^rVL64M`lU%Skn}E4HN_ZqfsUj7R!f`a>vfMN} z!N*+iY{ZQk);are!h3evoNyJ6fe&ST2C-uq#%VM$+;Pl!4$hl`PhzRK2xoCP%F`mx zc&1>>Hm=rQ;aEf|FE_JXq)`H)i~_uqT*Pqxow7c>|MkPYVYokdv{yFf!r<}!hZoZG z!Gpagr6;(WiEM<|UOX-^2!Jo)m45+ImLgL+wO^B0mU5OnQSQ`JmG6BgB6TFN@k?(aA{OVO0)lW*W5X3i3q{;g z_^4|me8Q)LU@v?$csv-2CYl;(atDZ9(boKP#IER|5r&D?bAvWud-b@h3Vq^WZPgq? zhUHRaK#r<%qY(T!<|hDrypjRBxPdz%`TMK>Gs%VQpGNYi|A5PvDlhsc(eXz~T-=pv z26yaVWMq|v!uFT#CHv+yyTvJXM8>#Shw$6Tsm&f}B1ZBOppfLziXxH0{o?AEw#BKa4XX=J90;WDn42HlP3Q`sS50D?jOodf1 zRBgWM#mcI>(dp(MU~?N8TAS#I$!d1o-cVjT17H3-ZTJY%8a}I_*YH*Gv z*zdIk&O3oO*KH)Wkh_Uw8_6vsJ4oI}@(vR0Bi$zA4sy%ZfXH`I@h*~!YG5J62SE1W zmDhlH!~=-b$hM{}pf`Z`foea9vj$!Vy<4RR0|Um6!0HGa$qtaRdx7lTd8?Z<>xSN3 zu_6n9R=MEjj(FR0)j^Lrvk8ubOEv_FJOu!y=DIj%%oJH)*#aYsa08qG<=TqHw5}c- zCQCYBIBLpv7)Du^UziLr=N~?vw{k#{-p46;LX8pwc1TLr?0%A@R^kvuM>?D_KIS`p`F5v@fSV^qbvy zUj2>Z@&3v=9`)C&%ceuA_&UP!d!8@6QRGcPOAi0kY_oJ(3j{ml97y6q~Sc) zxIBkhpHulf@3euu04=WyS}-4U#sX@G7RraTa6atx1vR2|<~tocs7AF|KBjf$yR>*d z?$A2aZmlQZ47j&Kk@Lu?7?68l&K8+_ zA7zsNV4Gw(TbQm@RmBdyTQy3RvZS8MV7uc|g~+AyEDR?Ksv;3lHzcD}5Q|bl(d|B$ zMwFsLlyX6F$uKW4SyGh@Z^vd8Lo5O=>ZL2Nc1agYnl$TT3nf{p0COKvW=p!E5V2Mg z3l*(eDJx|Iwv4mc4wqOYlBU>wcl5}#I5~CVh#g(& zOr3oD=xXoG)Wnf@?I2N%8YvfAcgtF)Mm&7a{=zXeGC-MH#Q;jH%zOAe<95m&;JxSZ zUqwc^HGI`?gd3F4Ut?b|DYG5(&qU_~YIWg~@n0HU?dq3~hDAsH(7t<~24dvnj2b)E=adl`de^Bd@mfhAH6+rq zN;{~7af77_R|2Ee@+`pl>g*L^^pbME>NXnCv}!IBumx2*uc&s!G3P?1RyI~}<8l>z zfv_N{HDwaaHloi}E{QI8$3jUjod>rPD1!kL_NLQh0H7URk;C-|t)64;zeAZ`!jud) z06$Z#>EMv;j#;&GUQ%_tb7f{#GUf=g7&|yiB)L=}%s7a^C_EgIuorN)B+Mr5ICBl+ z`&0QN;)$t~Q`4l2jmOxKFvYwQ>2~ll%r6o4I3x-nBiM29TRw5y2#&;dPy4V4H>`Uk z*MT{t-YQWRO3Ed0YV1g^6VsW%X;!}fAbVO@h@QPH>2ui$MZaKFs@ZocTJiGfscfO9 z8x>6~S7b%cuHH*K3eTJM9H^dkWg4wsW<}}@7l+WNYAxBw11Zw*=8Rc z;Im;XG6-xG>h#&>hG;kcPXyp!3@mMGZb3c$^;n`Q@EqSn4Dg6cHoMj)N089lL>xEN zNDW)5u?PKD>Hy`E%STOau)cLCL#Iq`OFfWmq7H6XLr8pa$V%?Cl6y_z4NG|Aq4-3Y zpu$9BAp0=>C}O?!p7qv8=9p-WiRQqJH84Z@4fS-6^1aI+nEdAYwjqZ8%;Zy)PyIc; z$4c*gFl400 z9Q`X!KEcT^CU2knR{iq{dN@yU(!}R1e2%uNvyL5LUZ>E0SY|a$+-Ls*m*@}KcYu5k zKDU6Q^p1tl^J5U)&KYd2m9mJ|3+?1+`&j$+&3p4j?}FD3+U+aI_&(=%>ApE$8o@Hw z=Q)dzxq;-5jJ}rK2>RZ^mrC9ivfJnR!{!>{`Q&=xdHq%VYhJ~^8}Jl#5&yq; zwf4Ge-K>w(dxQ29yeJEM4cTW_biycM zX%k5?kY->j1KSwb$-oc;ERH&1lCV5tH8jG?ay) zG!t*M@J5O^Hkc*%S-6klzIq_U+;Kn zO3#V(LX}QlG$$`wlNV{^BIPfx2zwXg$(iAt70x{@eyu*v{plc`k?DTL+^<;sTf%M| z`eeYuNeibMp{{Gu8`F1B)6hOMw9g9ddwAyS4>wmskggHt%?R`G&Kuf^)m&h7B}H=Int3I=uWgi}@CU9!6jwKxmiQQ)^w8ae z8J};AIbi)V_;Jlu6PBO3*;le{3${XmZNM@oyQgi$O?jMj%hfJN&sd6P$3;;pmn-a_ zWu>f(q8mvZ-E^`rM?qQY;zSTv^ZX&^`_N&nBG**qN94!A$Kr%e05*AyaUE@=>(n}m zEjhoZ=r!7^b+m>0YaJb@Yt=d$qW)S(@6h#Y9gS0et)mILR(+b-z7&5tG`!UL>D=A#S1WTTieY!OS1YIl9*@VP?z``flD%F`;Q3p##ruGeKXK7~z(&4> zp??Q)K{(+wCk35Rin^D3GtbTbEO2wn&3+yf;Vg7MLEbK+S>*IqzE{Mv*y&;3DY~;R z=pO3{D!ldnBO>~|{haV2-{bKw!E69phj&3c;1PN(Jl^B`upaWhh8@ zTo#oqMQQBg^H(RY)5+P(lUM2F^z7v2c{)BjeLX(?eriJ{Os&c}#%w+zEWU%GzXD;% zni}s{pVQx{3D&K32(rD7%$}R$JIcLx0qbxA`2Fe6PwPw{8$g*Z5N5h?I8&}<#pQLG z1h$p4OOf05d?mBF(6)8O@>H$nn{Btq7&_e0a4b!X9Sm+;( ztVY-D>YF^PzSgD&Y@;2FrV7cAK9H|RyXeQ&4f0)bq4pt;?t%#Xh=%aSG=8r>0mHpY zcwj)Rt`aXm*;K+n55My$F+TV1=v#72)D!NH$q&7^P@h|JOYc0>0P+tPP$>LPwK&0zmAib;X$x=`Ou( znH9DUurCZVM%kVcg#^Eid*Z1_xE&V4tR&y@tsXjao@paWbkRuBK8;D*O|UH-KVN`ih@oq5?cY(=#ZKkene+{DI_06klgkhp?Vg5dS}@V+|cX40!7*n8F_iaL{SuHzMO{z=cyI ztc&;*Y+u6A_%VovYoIcMX__3_(A-q;Z&0}Fu-oCTHIKLVz(u;ZvAukoi*?XAvHcwW zIqidneUart-#~?m%?&zi?BSK4OC?lGnL-fh6k3AofW_BQJ7^n}k59-Ih3rs8LH>sUnTUp-Gh{1sS1b>m4T!yIyN{ zoGPeQbD$E3ARz>54;*rUBGeu@apn&|XpOeQTFFu+PTY(Nmx=>##-E$0v$MZ>GxKI< z=e^&1`$ICBKu~51o5lYk2>r=BodSJPp8pKZ1GJ7zWMT{1IFB)xB}>XnqLuRz(MClp zTM=8yD?%q?#cVaN3OQ=U?LdV#Ys< zTe*L0k4#4P;sS(vwO{o|$lh7YFr5h-l`it+D#eup~VI`+*Sj*6f-FB#1 zDsO<8S|fI4i5fOpa;R-Eu3B>S?UG3xkkcE4>D7{6aO{dxCS?|kuU)-%U0+(ga_y?V zw6c2b%9_5ox^jJS<^APAp@dawxzN1<2r9fXyw86Fcz_s&rd_tWGK+S`5ZdC}qL}y* zXguy|@x9t*tGi-L>5Q@d`B-Okt2@euJ7WmFL|3qZYlTmKB0T~&Jnk?883QH)>&nZY zPEvPf17 zlnrW_B_~i9+p8!)D3HPSL853BNU`dI@2JK{hYlU)2C)sxxn)>xFnDN^a_a;G%%I$p z6@zV3ZjT?)b3pXs!`!+{sGHj{+|Aq~aX(>BCHJml7kAd*%N43Fb8Nlrn8eNXzN24+ z+vmF6aC5!mXDd6L4rP;2Iu0jsdb_ItyXap9P0oDlG!lb*XS}g21;SmC~uG;TCj*g zVL{BPGQJ|K28bsR zT6)e;&+VS5N779sNjGslooga2-^4B4E!O3!`wO36d^lEcy3_*^sKnN!!)^#+bp|0FZNMU!=(S8T_5|W7_U4#NCOW-d~Wje%Rgu@tz6o)j2S2%F`3kT5Gxy0dBc-_kYVEu^pMcz+N z`pGl3$XP#f7K|!o8}ZcM)D&(z`>Kc4ZVOhY}gm)f84)J#py_-e+(nby{$uuq{s zY`A5F1v4N#0BZM#OfRYHT?-28;^nN|cN4xV+>|IpPoWDQr z<6$4qc%q8$0w(hKTF#CH3frl`?~L+~64nWZdg*GmogBs4p^0`zZe`oSz7_55_%9|H z?HezaEaL6}$L&^H9qyas|AB%-iHM$7m~j^cDP1?pWd~vjQn;(@A{&YjX}L>WIPAw4 z>P6czs}{LT=U@wN9XAcwlrhG2blf{s>nOP^o@eNo*Qxc603&rY;e~4*z3GK(9i8^V zwT|YzaD8_El8440ez0Hs&hby4^AL0|)zGD;qGGv;!Y~%aJR}ccTE^Kgx4*vEL?9dl V4dV+xO#(^rg5Vg$THc#=l1&P`nm2FW^P7G1K7Ql8*=!Ja{w|_^b#&i1L;5nPT%*2D9hJ|-|OjBjA*oJ#VNGWv=# zgPF`?HgkTnPl%S*E=j30q&c*P_LPdRrE|)kl+xPh6J|~b=4xWVj2UXcJa?PuU%I0& zKu%|RI@43FufJ@(BEPOuBJ0CB){4o5{7!$Nk$1MHdfogUD$Rm-YEqK9H%Sp4QA+Ur z0fuZbPkK{Bb4}SqjuFV|{Lb^MumO!QO99L7cbB;n83E^W&*HrrI$hfy--VhMKK7agE#<$u1fIu*tF z4u62-!a+k5YDCnLYhZQt#|4z3stuC-Nt6_74$gvAG>YVb!0f7p%7*%+s#TG|CzLY~ z5sPzW?&e3z+0$_lfB-}^k<0<{&0wQqRgG8(A1s3PiuT-Xty7Ed)xB85jgkEGxcj)^ zqUat+#X)zQ7f)qA>K;Xhw~~1DSs};ZSoi#(RTjeCJ@1t6xdU6HV+94DK|1H*6UCQ6 zT*sj0YrQevxsB?|b3M91U3g2{fVoXK-Zsn@6(57~g|;z3fSJ&3vkQzQLu?0t!xva? zCaf%J&Q>-y)4-V0ZSrILgfI){<_VR~giPqvkTnSw1B2OU`)u{uk}gsQQN@X~(HJ1W zo24{PqX9o#>6rSaasZC<2$I-4h%(UI8i2sOmX9Uc`>xKVstFFzRB*L29_E>)q}Fok+q@A zC;$)S;Dw1Ri27{R2Y}alumZ4~>4BfEQ19D28VdG(Q=r4Tfp!1h_v^X|#Z?qwkz51fQ*j;nv%1$25jR102S$P2=21@<;U?XrTBA4W;ptBwlN(D;vc`o>Z zew_L8DJ)7~B=o`%u7)W+fyhtjQzGVPet{fw197E0zEirS19mBh7&_l5BlurY>w^9v zi~@_ofvACxCAU;G&X66)8UGvmiN57ge=IrrRq z&pG#XUe4b`p+*Ab{p9tO5G3S(*r^o0jJ)ckg#4Av5s`>gCJK#Hg~ge|#W{tK^AyLK z?1_8q)*JUh%gH{)ANSjHyxgEP#v5(iBL|dVJZR%yIixhjn{3=CH!Cgi7Ns@b3VkND zNxbA)Y_Du(cLgERF42D)dO#C?2Tg-b(fZ|eI{}uFM34p zJLf!|-_2QFIWr&WGh=C5izA&a(UBIiT+9#=$=V}ugjO&6$4iA`y;LLSh+ zP$BpZ3Tjh?z)IooA-wBj_?$vXCUAg!NHl(lea+32B}&OWtOqOZt`+keg{OeNC3W*}X}ulo889EzeCs&P!?sNSsw80IkRl>sw*hL`}j! zVB;#Pg~fqAs`yk^BU*AQuYt*1z6CjZO^`J!0B1pq%?bK-wE@XmTmtQrT1~DISiUMFtZ!7@|YF3wPwzh3&u62u#(a^@D(df^Y z2j?_N)drUZ?fT%Dq}|Z7xxvw_l3Jdd98BglJ*y-#Sy9pkYln5CaOHzF&oo%u#6WJ@ z0tb_UCskvxSTlUuKjCq2lQ%&UY+pTPM1~82V`ku3u`y5#H5EfG#ZX%*;ALEiAm9k= zd9$lbdbAK2G6O?9lXkypA)e5m<7Q}|8Tw6u?>G7W67jLYjgG_LFPa0tH3u#hI;PBy zDTB3d^bMPRr_H`8qi@FeV#Z)SYfA-osMvo7(d7b*75k4=(2d@sX75R}chcy+YD`@< zSohlR3+(-3-zh}rk*{x{f^K+&cNYI7-;qm%u?gzHjqd&Tri|`Mto`T92;05U6}@-Z z=o-h`H&#Yiq~xh_I{YtfS+89Z-O{(>Z{NP%I_QkN@e&p>B1hJbP8R3|lU^|Bg$>$a zLd!Esr^?k$ZpUfJxr$FaSI%m+ijqnH0Sxb?% zlsp{cUUGZ~|C#_m$B+S|LU{YDQX^?SQX-VYA%wS6MJ{@GDI1(p;1rWn0D)TKY&P&V6?v~G$@5j}>Z%+$WunlbrGUDy& z?QDbpWwN)YMcFaWw4Dpg#yAyI5Icg~0em9XU`JsU^AxoQ!9E0d(P|XIdkFR;I0zs{ z)kDx|_=t|ms>g8-!N)l9UjU%5d~kCN;aQ4p7etD^{dfCJw#$TA@IhZ0J+RTxyt;So zk4FEQA9ooI(}jjb2jVYYsU|E0D zYG_S-B>qb>{9^_GnCTz;PwL-_@zoy;IR`Yi=Q~mk`l;YFV8@A(CAqmdV9Tl#sM7En_&f~>+6;;mlQc>u_gJ9!+ zkRj-+2+rZe5di2w+6y#n(y&3p#Z#aDC_Fp$nF00C2-dmzyIm%2H|ascuFg^M@P9U$*Ms$M-peXOe4cI`Jwy_MerYg(Z)ep7 zyqgpn4-;dKHrpDE?^gD@>M*W_;39n5D1iDK+X(l6A2$zwVjljq5WZ-JFB-J1I5KXW zpMN$oZ;Z^}zk2_wkw}&=alb*a+Ls7RqR*I)bz5G2IR`&vRlFG$52T{!QPH{B_GPuz za>Idg9_&0eW(QW-HH#?x4$i8QePFBAHHT?@Hsj>Xbv$WV(v~5(C{Ehl?&86WKZ(*X ze%Xn#lQ$p4g_{!zA(Mfh)cB)ZOC+3Z8k12wO4+7wap{b1>zs3!dvMma7mR8@`%f!b zF)vFWs}s;g>(N#LLiC`tNDdpnpcYB!w*B{<>^3U3NP3L=S|r0peJzq>Mtv=kKBK-C z$$4YD`h56=K_Y9t_m4j~V|IOL5a^yLkQ2qD#|;u*^WX1yu-EJyGzfH$7s&CFH%iYz z6x{+RXamIvxdnE5!OMJ?U;Fajl}8g#ri{HGnURwanQh;*1-3ExC( None: + global ABOUT_BUTTON + global DONATE_BUTTON + + ABOUT_BUTTON = gradio.Button( + value = metadata.get('name') + ' ' + metadata.get('version'), + variant = 'primary', + link = metadata.get('url') + ) + DONATE_BUTTON = gradio.Button( + value = wording.get('uis.donate_button'), + link = 'https://donate.deepfuze.io', + size = 'sm' + ) diff --git a/deepfuze/uis/components/benchmark.py b/deepfuze/uis/components/benchmark.py new file mode 100644 index 0000000..3a27731 --- /dev/null +++ b/deepfuze/uis/components/benchmark.py @@ -0,0 +1,140 @@ +from typing import Any, Optional, List, Dict, Generator +from time import sleep, perf_counter +import tempfile +import statistics +import gradio + +import deepfuze.globals +from deepfuze import process_manager, wording +from deepfuze.face_store import clear_static_faces +from deepfuze.processors.frame.core import get_frame_processors_modules +from deepfuze.vision import count_video_frame_total, detect_video_resolution, detect_video_fps, pack_resolution +from deepfuze.core import conditional_process +from deepfuze.memory import limit_system_memory +from deepfuze.filesystem import clear_temp +from deepfuze.uis.core import get_ui_component + +BENCHMARK_RESULTS_DATAFRAME : Optional[gradio.Dataframe] = None +BENCHMARK_START_BUTTON : Optional[gradio.Button] = None +BENCHMARK_CLEAR_BUTTON : Optional[gradio.Button] = None +BENCHMARKS : Dict[str, str] =\ +{ + '240p': '../../models/facefusion/examples/target-240p.mp4', + '360p': '../../models/facefusion/examples/target-360p.mp4', + '540p': '../../models/facefusion/examples/target-540p.mp4', + '720p': '../../models/facefusion/examples/target-720p.mp4', + '1080p': '../../models/facefusion/examples/target-1080p.mp4', + '1440p': '../../models/facefusion/examples/target-1440p.mp4', + '2160p': '../../models/facefusion/examples/target-2160p.mp4' +} + + +def render() -> None: + global BENCHMARK_RESULTS_DATAFRAME + global BENCHMARK_START_BUTTON + global BENCHMARK_CLEAR_BUTTON + + BENCHMARK_RESULTS_DATAFRAME = gradio.Dataframe( + label = wording.get('uis.benchmark_results_dataframe'), + headers = + [ + 'target_path', + 'benchmark_cycles', + 'average_run', + 'fastest_run', + 'slowest_run', + 'relative_fps' + ], + datatype = + [ + 'str', + 'number', + 'number', + 'number', + 'number', + 'number' + ] + ) + BENCHMARK_START_BUTTON = gradio.Button( + value = wording.get('uis.start_button'), + variant = 'primary', + size = 'sm' + ) + BENCHMARK_CLEAR_BUTTON = gradio.Button( + value = wording.get('uis.clear_button'), + size = 'sm' + ) + + +def listen() -> None: + benchmark_runs_checkbox_group = get_ui_component('benchmark_runs_checkbox_group') + benchmark_cycles_slider = get_ui_component('benchmark_cycles_slider') + + if benchmark_runs_checkbox_group and benchmark_cycles_slider: + BENCHMARK_START_BUTTON.click(start, inputs = [ benchmark_runs_checkbox_group, benchmark_cycles_slider ], outputs = BENCHMARK_RESULTS_DATAFRAME) + BENCHMARK_CLEAR_BUTTON.click(clear, outputs = BENCHMARK_RESULTS_DATAFRAME) + + +def start(benchmark_runs : List[str], benchmark_cycles : int) -> Generator[List[Any], None, None]: + deepfuze.globals.source_paths = [ '../../models/facefusion/examples/source.jpg', '../../models/facefusion/examples/source.mp3' ] + deepfuze.globals.output_path = tempfile.gettempdir() + deepfuze.globals.face_landmarker_score = 0 + deepfuze.globals.temp_frame_format = 'bmp' + deepfuze.globals.output_video_preset = 'ultrafast' + benchmark_results = [] + target_paths = [ BENCHMARKS[benchmark_run] for benchmark_run in benchmark_runs if benchmark_run in BENCHMARKS ] + + if target_paths: + pre_process() + for target_path in target_paths: + deepfuze.globals.target_path = target_path + benchmark_results.append(benchmark(benchmark_cycles)) + yield benchmark_results + post_process() + + +def pre_process() -> None: + if deepfuze.globals.system_memory_limit > 0: + limit_system_memory(deepfuze.globals.system_memory_limit) + for frame_processor_module in get_frame_processors_modules(deepfuze.globals.frame_processors): + frame_processor_module.get_frame_processor() + + +def post_process() -> None: + clear_static_faces() + + +def benchmark(benchmark_cycles : int) -> List[Any]: + process_times = [] + video_frame_total = count_video_frame_total(deepfuze.globals.target_path) + output_video_resolution = detect_video_resolution(deepfuze.globals.target_path) + deepfuze.globals.output_video_resolution = pack_resolution(output_video_resolution) + deepfuze.globals.output_video_fps = detect_video_fps(deepfuze.globals.target_path) + + for index in range(benchmark_cycles): + start_time = perf_counter() + conditional_process() + end_time = perf_counter() + process_times.append(end_time - start_time) + average_run = round(statistics.mean(process_times), 2) + fastest_run = round(min(process_times), 2) + slowest_run = round(max(process_times), 2) + relative_fps = round(video_frame_total * benchmark_cycles / sum(process_times), 2) + + return\ + [ + deepfuze.globals.target_path, + benchmark_cycles, + average_run, + fastest_run, + slowest_run, + relative_fps + ] + + +def clear() -> gradio.Dataframe: + while process_manager.is_processing(): + sleep(0.5) + if deepfuze.globals.target_path: + clear_temp(deepfuze.globals.target_path) + return gradio.Dataframe(value = None) diff --git a/deepfuze/uis/components/benchmark_options.py b/deepfuze/uis/components/benchmark_options.py new file mode 100644 index 0000000..46302a0 --- /dev/null +++ b/deepfuze/uis/components/benchmark_options.py @@ -0,0 +1,29 @@ +from typing import Optional +import gradio + +from deepfuze import wording +from deepfuze.uis.core import register_ui_component +from deepfuze.uis.components.benchmark import BENCHMARKS + +BENCHMARK_RUNS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None +BENCHMARK_CYCLES_SLIDER : Optional[gradio.Button] = None + + +def render() -> None: + global BENCHMARK_RUNS_CHECKBOX_GROUP + global BENCHMARK_CYCLES_SLIDER + + BENCHMARK_RUNS_CHECKBOX_GROUP = gradio.CheckboxGroup( + label = wording.get('uis.benchmark_runs_checkbox_group'), + value = list(BENCHMARKS.keys()), + choices = list(BENCHMARKS.keys()) + ) + BENCHMARK_CYCLES_SLIDER = gradio.Slider( + label = wording.get('uis.benchmark_cycles_slider'), + value = 5, + step = 1, + minimum = 1, + maximum = 10 + ) + register_ui_component('benchmark_runs_checkbox_group', BENCHMARK_RUNS_CHECKBOX_GROUP) + register_ui_component('benchmark_cycles_slider', BENCHMARK_CYCLES_SLIDER) diff --git a/deepfuze/uis/components/common_options.py b/deepfuze/uis/components/common_options.py new file mode 100644 index 0000000..8227d5a --- /dev/null +++ b/deepfuze/uis/components/common_options.py @@ -0,0 +1,35 @@ +from typing import Optional, List +import gradio + +import deepfuze.globals +from deepfuze import wording +from deepfuze.uis import choices as uis_choices + +COMMON_OPTIONS_CHECKBOX_GROUP : Optional[gradio.Checkboxgroup] = None + + +def render() -> None: + global COMMON_OPTIONS_CHECKBOX_GROUP + + value = [] + if deepfuze.globals.keep_temp: + value.append('keep-temp') + if deepfuze.globals.skip_audio: + value.append('skip-audio') + if deepfuze.globals.skip_download: + value.append('skip-download') + COMMON_OPTIONS_CHECKBOX_GROUP = gradio.Checkboxgroup( + label = wording.get('uis.common_options_checkbox_group'), + choices = uis_choices.common_options, + value = value + ) + + +def listen() -> None: + COMMON_OPTIONS_CHECKBOX_GROUP.change(update, inputs = COMMON_OPTIONS_CHECKBOX_GROUP) + + +def update(common_options : List[str]) -> None: + deepfuze.globals.keep_temp = 'keep-temp' in common_options + deepfuze.globals.skip_audio = 'skip-audio' in common_options + deepfuze.globals.skip_download = 'skip-download' in common_options diff --git a/deepfuze/uis/components/execution.py b/deepfuze/uis/components/execution.py new file mode 100644 index 0000000..583a1da --- /dev/null +++ b/deepfuze/uis/components/execution.py @@ -0,0 +1,33 @@ +from typing import List, Optional +import gradio +import onnxruntime + +import deepfuze.globals +from deepfuze import wording +from deepfuze.face_analyser import clear_face_analyser +from deepfuze.processors.frame.core import clear_frame_processors_modules +from deepfuze.execution import encode_execution_providers, decode_execution_providers + +EXECUTION_PROVIDERS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None + + +def render() -> None: + global EXECUTION_PROVIDERS_CHECKBOX_GROUP + + EXECUTION_PROVIDERS_CHECKBOX_GROUP = gradio.CheckboxGroup( + label = wording.get('uis.execution_providers_checkbox_group'), + choices = encode_execution_providers(onnxruntime.get_available_providers()), + value = encode_execution_providers(deepfuze.globals.execution_providers) + ) + + +def listen() -> None: + EXECUTION_PROVIDERS_CHECKBOX_GROUP.change(update_execution_providers, inputs = EXECUTION_PROVIDERS_CHECKBOX_GROUP, outputs = EXECUTION_PROVIDERS_CHECKBOX_GROUP) + + +def update_execution_providers(execution_providers : List[str]) -> gradio.CheckboxGroup: + clear_face_analyser() + clear_frame_processors_modules() + execution_providers = execution_providers or encode_execution_providers(onnxruntime.get_available_providers()) + deepfuze.globals.execution_providers = decode_execution_providers(execution_providers) + return gradio.CheckboxGroup(value = execution_providers) diff --git a/deepfuze/uis/components/execution_queue_count.py b/deepfuze/uis/components/execution_queue_count.py new file mode 100644 index 0000000..2cfe3a4 --- /dev/null +++ b/deepfuze/uis/components/execution_queue_count.py @@ -0,0 +1,28 @@ +from typing import Optional +import gradio + +import deepfuze.globals +import deepfuze.choices +from deepfuze import wording + +EXECUTION_QUEUE_COUNT_SLIDER : Optional[gradio.Slider] = None + + +def render() -> None: + global EXECUTION_QUEUE_COUNT_SLIDER + + EXECUTION_QUEUE_COUNT_SLIDER = gradio.Slider( + label = wording.get('uis.execution_queue_count_slider'), + value = deepfuze.globals.execution_queue_count, + step = deepfuze.choices.execution_queue_count_range[1] - deepfuze.choices.execution_queue_count_range[0], + minimum = deepfuze.choices.execution_queue_count_range[0], + maximum = deepfuze.choices.execution_queue_count_range[-1] + ) + + +def listen() -> None: + EXECUTION_QUEUE_COUNT_SLIDER.release(update_execution_queue_count, inputs = EXECUTION_QUEUE_COUNT_SLIDER) + + +def update_execution_queue_count(execution_queue_count : int = 1) -> None: + deepfuze.globals.execution_queue_count = execution_queue_count diff --git a/deepfuze/uis/components/execution_thread_count.py b/deepfuze/uis/components/execution_thread_count.py new file mode 100644 index 0000000..be83a96 --- /dev/null +++ b/deepfuze/uis/components/execution_thread_count.py @@ -0,0 +1,29 @@ +from typing import Optional +import gradio + +import deepfuze.globals +import deepfuze.choices +from deepfuze import wording + +EXECUTION_THREAD_COUNT_SLIDER : Optional[gradio.Slider] = None + + +def render() -> None: + global EXECUTION_THREAD_COUNT_SLIDER + + EXECUTION_THREAD_COUNT_SLIDER = gradio.Slider( + label = wording.get('uis.execution_thread_count_slider'), + value = deepfuze.globals.execution_thread_count, + step = deepfuze.choices.execution_thread_count_range[1] - deepfuze.choices.execution_thread_count_range[0], + minimum = deepfuze.choices.execution_thread_count_range[0], + maximum = deepfuze.choices.execution_thread_count_range[-1] + ) + + +def listen() -> None: + EXECUTION_THREAD_COUNT_SLIDER.release(update_execution_thread_count, inputs = EXECUTION_THREAD_COUNT_SLIDER) + + +def update_execution_thread_count(execution_thread_count : int = 1) -> None: + deepfuze.globals.execution_thread_count = execution_thread_count + diff --git a/deepfuze/uis/components/face_analyser.py b/deepfuze/uis/components/face_analyser.py new file mode 100644 index 0000000..f4bfd2f --- /dev/null +++ b/deepfuze/uis/components/face_analyser.py @@ -0,0 +1,123 @@ +from typing import Optional, Dict, Any, Tuple + +import gradio + +import deepfuze.globals +import deepfuze.choices +from deepfuze import face_analyser, wording +from deepfuze.typing import FaceAnalyserOrder, FaceAnalyserAge, FaceAnalyserGender, FaceDetectorModel +from deepfuze.uis.core import register_ui_component + +FACE_ANALYSER_ORDER_DROPDOWN : Optional[gradio.Dropdown] = None +FACE_ANALYSER_AGE_DROPDOWN : Optional[gradio.Dropdown] = None +FACE_ANALYSER_GENDER_DROPDOWN : Optional[gradio.Dropdown] = None +FACE_DETECTOR_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None +FACE_DETECTOR_SIZE_DROPDOWN : Optional[gradio.Dropdown] = None +FACE_DETECTOR_SCORE_SLIDER : Optional[gradio.Slider] = None +FACE_LANDMARKER_SCORE_SLIDER : Optional[gradio.Slider] = None + + +def render() -> None: + global FACE_ANALYSER_ORDER_DROPDOWN + global FACE_ANALYSER_AGE_DROPDOWN + global FACE_ANALYSER_GENDER_DROPDOWN + global FACE_DETECTOR_MODEL_DROPDOWN + global FACE_DETECTOR_SIZE_DROPDOWN + global FACE_DETECTOR_SCORE_SLIDER + global FACE_LANDMARKER_SCORE_SLIDER + + face_detector_size_dropdown_args : Dict[str, Any] =\ + { + 'label': wording.get('uis.face_detector_size_dropdown'), + 'value': deepfuze.globals.face_detector_size + } + if deepfuze.globals.face_detector_size in deepfuze.choices.face_detector_set[deepfuze.globals.face_detector_model]: + face_detector_size_dropdown_args['choices'] = deepfuze.choices.face_detector_set[deepfuze.globals.face_detector_model] + with gradio.Row(): + FACE_ANALYSER_ORDER_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.face_analyser_order_dropdown'), + choices = deepfuze.choices.face_analyser_orders, + value = deepfuze.globals.face_analyser_order + ) + FACE_ANALYSER_AGE_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.face_analyser_age_dropdown'), + choices = [ 'none' ] + deepfuze.choices.face_analyser_ages, + value = deepfuze.globals.face_analyser_age or 'none' + ) + FACE_ANALYSER_GENDER_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.face_analyser_gender_dropdown'), + choices = [ 'none' ] + deepfuze.choices.face_analyser_genders, + value = deepfuze.globals.face_analyser_gender or 'none' + ) + FACE_DETECTOR_MODEL_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.face_detector_model_dropdown'), + choices = deepfuze.choices.face_detector_set.keys(), + value = deepfuze.globals.face_detector_model + ) + FACE_DETECTOR_SIZE_DROPDOWN = gradio.Dropdown(**face_detector_size_dropdown_args) + with gradio.Row(): + FACE_DETECTOR_SCORE_SLIDER = gradio.Slider( + label = wording.get('uis.face_detector_score_slider'), + value = deepfuze.globals.face_detector_score, + step = deepfuze.choices.face_detector_score_range[1] - deepfuze.choices.face_detector_score_range[0], + minimum = deepfuze.choices.face_detector_score_range[0], + maximum = deepfuze.choices.face_detector_score_range[-1] + ) + FACE_LANDMARKER_SCORE_SLIDER = gradio.Slider( + label = wording.get('uis.face_landmarker_score_slider'), + value = deepfuze.globals.face_landmarker_score, + step = deepfuze.choices.face_landmarker_score_range[1] - deepfuze.choices.face_landmarker_score_range[0], + minimum = deepfuze.choices.face_landmarker_score_range[0], + maximum = deepfuze.choices.face_landmarker_score_range[-1] + ) + register_ui_component('face_analyser_order_dropdown', FACE_ANALYSER_ORDER_DROPDOWN) + register_ui_component('face_analyser_age_dropdown', FACE_ANALYSER_AGE_DROPDOWN) + register_ui_component('face_analyser_gender_dropdown', FACE_ANALYSER_GENDER_DROPDOWN) + register_ui_component('face_detector_model_dropdown', FACE_DETECTOR_MODEL_DROPDOWN) + register_ui_component('face_detector_size_dropdown', FACE_DETECTOR_SIZE_DROPDOWN) + register_ui_component('face_detector_score_slider', FACE_DETECTOR_SCORE_SLIDER) + register_ui_component('face_landmarker_score_slider', FACE_LANDMARKER_SCORE_SLIDER) + + +def listen() -> None: + FACE_ANALYSER_ORDER_DROPDOWN.change(update_face_analyser_order, inputs = FACE_ANALYSER_ORDER_DROPDOWN) + FACE_ANALYSER_AGE_DROPDOWN.change(update_face_analyser_age, inputs = FACE_ANALYSER_AGE_DROPDOWN) + FACE_ANALYSER_GENDER_DROPDOWN.change(update_face_analyser_gender, inputs = FACE_ANALYSER_GENDER_DROPDOWN) + FACE_DETECTOR_MODEL_DROPDOWN.change(update_face_detector_model, inputs = FACE_DETECTOR_MODEL_DROPDOWN, outputs = [ FACE_DETECTOR_MODEL_DROPDOWN, FACE_DETECTOR_SIZE_DROPDOWN ]) + FACE_DETECTOR_SIZE_DROPDOWN.change(update_face_detector_size, inputs = FACE_DETECTOR_SIZE_DROPDOWN) + FACE_DETECTOR_SCORE_SLIDER.release(update_face_detector_score, inputs = FACE_DETECTOR_SCORE_SLIDER) + FACE_LANDMARKER_SCORE_SLIDER.release(update_face_landmarker_score, inputs = FACE_LANDMARKER_SCORE_SLIDER) + + +def update_face_analyser_order(face_analyser_order : FaceAnalyserOrder) -> None: + deepfuze.globals.face_analyser_order = face_analyser_order if face_analyser_order != 'none' else None + + +def update_face_analyser_age(face_analyser_age : FaceAnalyserAge) -> None: + deepfuze.globals.face_analyser_age = face_analyser_age if face_analyser_age != 'none' else None + + +def update_face_analyser_gender(face_analyser_gender : FaceAnalyserGender) -> None: + deepfuze.globals.face_analyser_gender = face_analyser_gender if face_analyser_gender != 'none' else None + + +def update_face_detector_model(face_detector_model : FaceDetectorModel) -> Tuple[gradio.Dropdown, gradio.Dropdown]: + deepfuze.globals.face_detector_model = face_detector_model + update_face_detector_size('640x640') + if face_analyser.pre_check(): + if deepfuze.globals.face_detector_size in deepfuze.choices.face_detector_set[face_detector_model]: + return gradio.Dropdown(value = deepfuze.globals.face_detector_model), gradio.Dropdown(value = deepfuze.globals.face_detector_size, choices = deepfuze.choices.face_detector_set[face_detector_model]) + return gradio.Dropdown(value = deepfuze.globals.face_detector_model), gradio.Dropdown(value = deepfuze.globals.face_detector_size, choices = [ deepfuze.globals.face_detector_size ]) + return gradio.Dropdown(), gradio.Dropdown() + + +def update_face_detector_size(face_detector_size : str) -> None: + deepfuze.globals.face_detector_size = face_detector_size + + +def update_face_detector_score(face_detector_score : float) -> None: + deepfuze.globals.face_detector_score = face_detector_score + + +def update_face_landmarker_score(face_landmarker_score : float) -> None: + deepfuze.globals.face_landmarker_score = face_landmarker_score diff --git a/deepfuze/uis/components/face_masker.py b/deepfuze/uis/components/face_masker.py new file mode 100755 index 0000000..7805f99 --- /dev/null +++ b/deepfuze/uis/components/face_masker.py @@ -0,0 +1,119 @@ +from typing import Optional, Tuple, List +import gradio + +import deepfuze.globals +import deepfuze.choices +from deepfuze import wording +from deepfuze.typing import FaceMaskType, FaceMaskRegion +from deepfuze.uis.core import register_ui_component + +FACE_MASK_TYPES_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None +FACE_MASK_BLUR_SLIDER : Optional[gradio.Slider] = None +FACE_MASK_BOX_GROUP : Optional[gradio.Group] = None +FACE_MASK_REGION_GROUP : Optional[gradio.Group] = None +FACE_MASK_PADDING_TOP_SLIDER : Optional[gradio.Slider] = None +FACE_MASK_PADDING_RIGHT_SLIDER : Optional[gradio.Slider] = None +FACE_MASK_PADDING_BOTTOM_SLIDER : Optional[gradio.Slider] = None +FACE_MASK_PADDING_LEFT_SLIDER : Optional[gradio.Slider] = None +FACE_MASK_REGION_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None + + +def render() -> None: + global FACE_MASK_TYPES_CHECKBOX_GROUP + global FACE_MASK_BLUR_SLIDER + global FACE_MASK_BOX_GROUP + global FACE_MASK_REGION_GROUP + global FACE_MASK_PADDING_TOP_SLIDER + global FACE_MASK_PADDING_RIGHT_SLIDER + global FACE_MASK_PADDING_BOTTOM_SLIDER + global FACE_MASK_PADDING_LEFT_SLIDER + global FACE_MASK_REGION_CHECKBOX_GROUP + + has_box_mask = 'box' in deepfuze.globals.face_mask_types + has_region_mask = 'region' in deepfuze.globals.face_mask_types + FACE_MASK_TYPES_CHECKBOX_GROUP = gradio.CheckboxGroup( + label = wording.get('uis.face_mask_types_checkbox_group'), + choices = deepfuze.choices.face_mask_types, + value = deepfuze.globals.face_mask_types + ) + with gradio.Group(visible = has_box_mask) as FACE_MASK_BOX_GROUP: + FACE_MASK_BLUR_SLIDER = gradio.Slider( + label = wording.get('uis.face_mask_blur_slider'), + step = deepfuze.choices.face_mask_blur_range[1] - deepfuze.choices.face_mask_blur_range[0], + minimum = deepfuze.choices.face_mask_blur_range[0], + maximum = deepfuze.choices.face_mask_blur_range[-1], + value = deepfuze.globals.face_mask_blur + ) + with gradio.Row(): + FACE_MASK_PADDING_TOP_SLIDER = gradio.Slider( + label = wording.get('uis.face_mask_padding_top_slider'), + step = deepfuze.choices.face_mask_padding_range[1] - deepfuze.choices.face_mask_padding_range[0], + minimum = deepfuze.choices.face_mask_padding_range[0], + maximum = deepfuze.choices.face_mask_padding_range[-1], + value = deepfuze.globals.face_mask_padding[0] + ) + FACE_MASK_PADDING_RIGHT_SLIDER = gradio.Slider( + label = wording.get('uis.face_mask_padding_right_slider'), + step = deepfuze.choices.face_mask_padding_range[1] - deepfuze.choices.face_mask_padding_range[0], + minimum = deepfuze.choices.face_mask_padding_range[0], + maximum = deepfuze.choices.face_mask_padding_range[-1], + value = deepfuze.globals.face_mask_padding[1] + ) + with gradio.Row(): + FACE_MASK_PADDING_BOTTOM_SLIDER = gradio.Slider( + label = wording.get('uis.face_mask_padding_bottom_slider'), + step = deepfuze.choices.face_mask_padding_range[1] - deepfuze.choices.face_mask_padding_range[0], + minimum = deepfuze.choices.face_mask_padding_range[0], + maximum = deepfuze.choices.face_mask_padding_range[-1], + value = deepfuze.globals.face_mask_padding[2] + ) + FACE_MASK_PADDING_LEFT_SLIDER = gradio.Slider( + label = wording.get('uis.face_mask_padding_left_slider'), + step = deepfuze.choices.face_mask_padding_range[1] - deepfuze.choices.face_mask_padding_range[0], + minimum = deepfuze.choices.face_mask_padding_range[0], + maximum = deepfuze.choices.face_mask_padding_range[-1], + value = deepfuze.globals.face_mask_padding[3] + ) + with gradio.Row(): + FACE_MASK_REGION_CHECKBOX_GROUP = gradio.CheckboxGroup( + label = wording.get('uis.face_mask_region_checkbox_group'), + choices = deepfuze.choices.face_mask_regions, + value = deepfuze.globals.face_mask_regions, + visible = has_region_mask + ) + register_ui_component('face_mask_types_checkbox_group', FACE_MASK_TYPES_CHECKBOX_GROUP) + register_ui_component('face_mask_blur_slider', FACE_MASK_BLUR_SLIDER) + register_ui_component('face_mask_padding_top_slider', FACE_MASK_PADDING_TOP_SLIDER) + register_ui_component('face_mask_padding_right_slider', FACE_MASK_PADDING_RIGHT_SLIDER) + register_ui_component('face_mask_padding_bottom_slider', FACE_MASK_PADDING_BOTTOM_SLIDER) + register_ui_component('face_mask_padding_left_slider', FACE_MASK_PADDING_LEFT_SLIDER) + register_ui_component('face_mask_region_checkbox_group', FACE_MASK_REGION_CHECKBOX_GROUP) + + +def listen() -> None: + FACE_MASK_TYPES_CHECKBOX_GROUP.change(update_face_mask_type, inputs = FACE_MASK_TYPES_CHECKBOX_GROUP, outputs = [ FACE_MASK_TYPES_CHECKBOX_GROUP, FACE_MASK_BOX_GROUP, FACE_MASK_REGION_CHECKBOX_GROUP ]) + FACE_MASK_BLUR_SLIDER.release(update_face_mask_blur, inputs = FACE_MASK_BLUR_SLIDER) + FACE_MASK_REGION_CHECKBOX_GROUP.change(update_face_mask_regions, inputs = FACE_MASK_REGION_CHECKBOX_GROUP, outputs = FACE_MASK_REGION_CHECKBOX_GROUP) + face_mask_padding_sliders = [ FACE_MASK_PADDING_TOP_SLIDER, FACE_MASK_PADDING_RIGHT_SLIDER, FACE_MASK_PADDING_BOTTOM_SLIDER, FACE_MASK_PADDING_LEFT_SLIDER ] + for face_mask_padding_slider in face_mask_padding_sliders: + face_mask_padding_slider.release(update_face_mask_padding, inputs = face_mask_padding_sliders) + + +def update_face_mask_type(face_mask_types : List[FaceMaskType]) -> Tuple[gradio.CheckboxGroup, gradio.Group, gradio.CheckboxGroup]: + deepfuze.globals.face_mask_types = face_mask_types or deepfuze.choices.face_mask_types + has_box_mask = 'box' in face_mask_types + has_region_mask = 'region' in face_mask_types + return gradio.CheckboxGroup(value = deepfuze.globals.face_mask_types), gradio.Group(visible = has_box_mask), gradio.CheckboxGroup(visible = has_region_mask) + + +def update_face_mask_blur(face_mask_blur : float) -> None: + deepfuze.globals.face_mask_blur = face_mask_blur + + +def update_face_mask_padding(face_mask_padding_top : int, face_mask_padding_right : int, face_mask_padding_bottom : int, face_mask_padding_left : int) -> None: + deepfuze.globals.face_mask_padding = (face_mask_padding_top, face_mask_padding_right, face_mask_padding_bottom, face_mask_padding_left) + + +def update_face_mask_regions(face_mask_regions : List[FaceMaskRegion]) -> gradio.CheckboxGroup: + deepfuze.globals.face_mask_regions = face_mask_regions or deepfuze.choices.face_mask_regions + return gradio.CheckboxGroup(value = deepfuze.globals.face_mask_regions) diff --git a/deepfuze/uis/components/face_selector.py b/deepfuze/uis/components/face_selector.py new file mode 100644 index 0000000..eea2920 --- /dev/null +++ b/deepfuze/uis/components/face_selector.py @@ -0,0 +1,165 @@ +from typing import List, Optional, Tuple, Any, Dict + +import gradio + +import deepfuze.globals +import deepfuze.choices +from deepfuze import wording +from deepfuze.face_store import clear_static_faces, clear_reference_faces +from deepfuze.vision import get_video_frame, read_static_image, normalize_frame_color +from deepfuze.filesystem import is_image, is_video +from deepfuze.face_analyser import get_many_faces +from deepfuze.typing import VisionFrame, FaceSelectorMode +from deepfuze.uis.core import get_ui_component, get_ui_components, register_ui_component + +FACE_SELECTOR_MODE_DROPDOWN : Optional[gradio.Dropdown] = None +REFERENCE_FACE_POSITION_GALLERY : Optional[gradio.Gallery] = None +REFERENCE_FACE_DISTANCE_SLIDER : Optional[gradio.Slider] = None + + +def render() -> None: + global FACE_SELECTOR_MODE_DROPDOWN + global REFERENCE_FACE_POSITION_GALLERY + global REFERENCE_FACE_DISTANCE_SLIDER + + reference_face_gallery_args : Dict[str, Any] =\ + { + 'label': wording.get('uis.reference_face_gallery'), + 'object_fit': 'cover', + 'columns': 8, + 'allow_preview': False, + 'visible': 'reference' in deepfuze.globals.face_selector_mode + } + if is_image(deepfuze.globals.target_path): + reference_frame = read_static_image(deepfuze.globals.target_path) + reference_face_gallery_args['value'] = extract_gallery_frames(reference_frame) + if is_video(deepfuze.globals.target_path): + reference_frame = get_video_frame(deepfuze.globals.target_path, deepfuze.globals.reference_frame_number) + reference_face_gallery_args['value'] = extract_gallery_frames(reference_frame) + FACE_SELECTOR_MODE_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.face_selector_mode_dropdown'), + choices = deepfuze.choices.face_selector_modes, + value = deepfuze.globals.face_selector_mode + ) + REFERENCE_FACE_POSITION_GALLERY = gradio.Gallery(**reference_face_gallery_args) + REFERENCE_FACE_DISTANCE_SLIDER = gradio.Slider( + label = wording.get('uis.reference_face_distance_slider'), + value = deepfuze.globals.reference_face_distance, + step = deepfuze.choices.reference_face_distance_range[1] - deepfuze.choices.reference_face_distance_range[0], + minimum = deepfuze.choices.reference_face_distance_range[0], + maximum = deepfuze.choices.reference_face_distance_range[-1], + visible = 'reference' in deepfuze.globals.face_selector_mode + ) + register_ui_component('face_selector_mode_dropdown', FACE_SELECTOR_MODE_DROPDOWN) + register_ui_component('reference_face_position_gallery', REFERENCE_FACE_POSITION_GALLERY) + register_ui_component('reference_face_distance_slider', REFERENCE_FACE_DISTANCE_SLIDER) + + +def listen() -> None: + FACE_SELECTOR_MODE_DROPDOWN.change(update_face_selector_mode, inputs = FACE_SELECTOR_MODE_DROPDOWN, outputs = [ REFERENCE_FACE_POSITION_GALLERY, REFERENCE_FACE_DISTANCE_SLIDER ]) + REFERENCE_FACE_POSITION_GALLERY.select(clear_and_update_reference_face_position) + REFERENCE_FACE_DISTANCE_SLIDER.release(update_reference_face_distance, inputs = REFERENCE_FACE_DISTANCE_SLIDER) + + for ui_component in get_ui_components( + [ + 'target_image', + 'target_video' + ]): + for method in [ 'upload', 'change', 'clear' ]: + getattr(ui_component, method)(update_reference_face_position) + getattr(ui_component, method)(update_reference_position_gallery, outputs = REFERENCE_FACE_POSITION_GALLERY) + + for ui_component in get_ui_components( + [ + 'face_analyser_order_dropdown', + 'face_analyser_age_dropdown', + 'face_analyser_gender_dropdown' + ]): + ui_component.change(update_reference_position_gallery, outputs = REFERENCE_FACE_POSITION_GALLERY) + + for ui_component in get_ui_components( + [ + 'face_detector_model_dropdown', + 'face_detector_size_dropdown' + ]): + ui_component.change(clear_and_update_reference_position_gallery, outputs = REFERENCE_FACE_POSITION_GALLERY) + + for ui_component in get_ui_components( + [ + 'face_detector_score_slider', + 'face_landmarker_score_slider' + ]): + ui_component.release(clear_and_update_reference_position_gallery, outputs=REFERENCE_FACE_POSITION_GALLERY) + + preview_frame_slider = get_ui_component('preview_frame_slider') + if preview_frame_slider: + preview_frame_slider.change(update_reference_frame_number, inputs = preview_frame_slider) + preview_frame_slider.release(update_reference_position_gallery, outputs = REFERENCE_FACE_POSITION_GALLERY) + + +def update_face_selector_mode(face_selector_mode : FaceSelectorMode) -> Tuple[gradio.Gallery, gradio.Slider]: + if face_selector_mode == 'many': + deepfuze.globals.face_selector_mode = face_selector_mode + return gradio.Gallery(visible = False), gradio.Slider(visible = False) + if face_selector_mode == 'one': + deepfuze.globals.face_selector_mode = face_selector_mode + return gradio.Gallery(visible = False), gradio.Slider(visible = False) + if face_selector_mode == 'reference': + deepfuze.globals.face_selector_mode = face_selector_mode + return gradio.Gallery(visible = True), gradio.Slider(visible = True) + + +def clear_and_update_reference_face_position(event : gradio.SelectData) -> gradio.Gallery: + clear_reference_faces() + clear_static_faces() + update_reference_face_position(event.index) + return update_reference_position_gallery() + + +def update_reference_face_position(reference_face_position : int = 0) -> None: + deepfuze.globals.reference_face_position = reference_face_position + + +def update_reference_face_distance(reference_face_distance : float) -> None: + deepfuze.globals.reference_face_distance = reference_face_distance + + +def update_reference_frame_number(reference_frame_number : int) -> None: + deepfuze.globals.reference_frame_number = reference_frame_number + + +def clear_and_update_reference_position_gallery() -> gradio.Gallery: + clear_reference_faces() + clear_static_faces() + return update_reference_position_gallery() + + +def update_reference_position_gallery() -> gradio.Gallery: + gallery_vision_frames = [] + if is_image(deepfuze.globals.target_path): + temp_vision_frame = read_static_image(deepfuze.globals.target_path) + gallery_vision_frames = extract_gallery_frames(temp_vision_frame) + if is_video(deepfuze.globals.target_path): + temp_vision_frame = get_video_frame(deepfuze.globals.target_path, deepfuze.globals.reference_frame_number) + gallery_vision_frames = extract_gallery_frames(temp_vision_frame) + if gallery_vision_frames: + return gradio.Gallery(value = gallery_vision_frames) + return gradio.Gallery(value = None) + + +def extract_gallery_frames(temp_vision_frame : VisionFrame) -> List[VisionFrame]: + gallery_vision_frames = [] + faces = get_many_faces(temp_vision_frame) + + for face in faces: + start_x, start_y, end_x, end_y = map(int, face.bounding_box) + padding_x = int((end_x - start_x) * 0.25) + padding_y = int((end_y - start_y) * 0.25) + start_x = max(0, start_x - padding_x) + start_y = max(0, start_y - padding_y) + end_x = max(0, end_x + padding_x) + end_y = max(0, end_y + padding_y) + crop_vision_frame = temp_vision_frame[start_y:end_y, start_x:end_x] + crop_vision_frame = normalize_frame_color(crop_vision_frame) + gallery_vision_frames.append(crop_vision_frame) + return gallery_vision_frames diff --git a/deepfuze/uis/components/frame_processors.py b/deepfuze/uis/components/frame_processors.py new file mode 100644 index 0000000..185fe11 --- /dev/null +++ b/deepfuze/uis/components/frame_processors.py @@ -0,0 +1,40 @@ +from typing import List, Optional +import gradio + +import deepfuze.globals +from deepfuze import wording +from deepfuze.processors.frame.core import load_frame_processor_module, clear_frame_processors_modules +from deepfuze.filesystem import list_directory +from deepfuze.uis.core import register_ui_component + +FRAME_PROCESSORS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None + + +def render() -> None: + global FRAME_PROCESSORS_CHECKBOX_GROUP + + FRAME_PROCESSORS_CHECKBOX_GROUP = gradio.CheckboxGroup( + label = wording.get('uis.frame_processors_checkbox_group'), + choices = sort_frame_processors(deepfuze.globals.frame_processors), + value = deepfuze.globals.frame_processors + ) + register_ui_component('frame_processors_checkbox_group', FRAME_PROCESSORS_CHECKBOX_GROUP) + + +def listen() -> None: + FRAME_PROCESSORS_CHECKBOX_GROUP.change(update_frame_processors, inputs = FRAME_PROCESSORS_CHECKBOX_GROUP, outputs = FRAME_PROCESSORS_CHECKBOX_GROUP) + + +def update_frame_processors(frame_processors : List[str]) -> gradio.CheckboxGroup: + deepfuze.globals.frame_processors = frame_processors + clear_frame_processors_modules() + for frame_processor in frame_processors: + frame_processor_module = load_frame_processor_module(frame_processor) + if not frame_processor_module.pre_check(): + return gradio.CheckboxGroup() + return gradio.CheckboxGroup(value = deepfuze.globals.frame_processors, choices = sort_frame_processors(deepfuze.globals.frame_processors)) + + +def sort_frame_processors(frame_processors : List[str]) -> list[str]: + available_frame_processors = list_directory('facefusion/processors/frame/modules') + return sorted(available_frame_processors, key = lambda frame_processor : frame_processors.index(frame_processor) if frame_processor in frame_processors else len(frame_processors)) diff --git a/deepfuze/uis/components/frame_processors_options.py b/deepfuze/uis/components/frame_processors_options.py new file mode 100755 index 0000000..759eb76 --- /dev/null +++ b/deepfuze/uis/components/frame_processors_options.py @@ -0,0 +1,216 @@ +from typing import List, Optional, Tuple +import gradio + +import deepfuze.globals +from deepfuze import face_analyser, wording +from deepfuze.processors.frame.core import load_frame_processor_module +from deepfuze.processors.frame import globals as frame_processors_globals, choices as frame_processors_choices +from deepfuze.processors.frame.typings import FaceDebuggerItem, FaceEnhancerModel, FaceSwapperModel, FrameColorizerModel, FrameEnhancerModel, LipSyncerModel +from deepfuze.uis.core import get_ui_component, register_ui_component + +FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP : Optional[gradio.CheckboxGroup] = None +FACE_ENHANCER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None +FACE_ENHANCER_BLEND_SLIDER : Optional[gradio.Slider] = None +FACE_SWAPPER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None +FRAME_COLORIZER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None +FRAME_COLORIZER_BLEND_SLIDER : Optional[gradio.Slider] = None +FRAME_COLORIZER_SIZE_DROPDOWN : Optional[gradio.Dropdown] = None +FRAME_ENHANCER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None +FRAME_ENHANCER_BLEND_SLIDER : Optional[gradio.Slider] = None +LIP_SYNCER_MODEL_DROPDOWN : Optional[gradio.Dropdown] = None + + +def render() -> None: + global FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP + global FACE_ENHANCER_MODEL_DROPDOWN + global FACE_ENHANCER_BLEND_SLIDER + global FACE_SWAPPER_MODEL_DROPDOWN + global FRAME_COLORIZER_MODEL_DROPDOWN + global FRAME_COLORIZER_BLEND_SLIDER + global FRAME_COLORIZER_SIZE_DROPDOWN + global FRAME_ENHANCER_MODEL_DROPDOWN + global FRAME_ENHANCER_BLEND_SLIDER + global LIP_SYNCER_MODEL_DROPDOWN + + FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP = gradio.CheckboxGroup( + label = wording.get('uis.face_debugger_items_checkbox_group'), + choices = frame_processors_choices.face_debugger_items, + value = frame_processors_globals.face_debugger_items, + visible = 'face_debugger' in deepfuze.globals.frame_processors + ) + FACE_ENHANCER_MODEL_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.face_enhancer_model_dropdown'), + choices = frame_processors_choices.face_enhancer_models, + value = frame_processors_globals.face_enhancer_model, + visible = 'face_enhancer' in deepfuze.globals.frame_processors + ) + FACE_ENHANCER_BLEND_SLIDER = gradio.Slider( + label = wording.get('uis.face_enhancer_blend_slider'), + value = frame_processors_globals.face_enhancer_blend, + step = frame_processors_choices.face_enhancer_blend_range[1] - frame_processors_choices.face_enhancer_blend_range[0], + minimum = frame_processors_choices.face_enhancer_blend_range[0], + maximum = frame_processors_choices.face_enhancer_blend_range[-1], + visible = 'face_enhancer' in deepfuze.globals.frame_processors + ) + FACE_SWAPPER_MODEL_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.face_swapper_model_dropdown'), + choices = frame_processors_choices.face_swapper_models, + value = frame_processors_globals.face_swapper_model, + visible = 'face_swapper' in deepfuze.globals.frame_processors + ) + FRAME_COLORIZER_MODEL_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.frame_colorizer_model_dropdown'), + choices = frame_processors_choices.frame_colorizer_models, + value = frame_processors_globals.frame_colorizer_model, + visible = 'frame_colorizer' in deepfuze.globals.frame_processors + ) + FRAME_COLORIZER_BLEND_SLIDER = gradio.Slider( + label = wording.get('uis.frame_colorizer_blend_slider'), + value = frame_processors_globals.frame_colorizer_blend, + step = frame_processors_choices.frame_colorizer_blend_range[1] - frame_processors_choices.frame_colorizer_blend_range[0], + minimum = frame_processors_choices.frame_colorizer_blend_range[0], + maximum = frame_processors_choices.frame_colorizer_blend_range[-1], + visible = 'frame_colorizer' in deepfuze.globals.frame_processors + ) + FRAME_COLORIZER_SIZE_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.frame_colorizer_size_dropdown'), + choices = frame_processors_choices.frame_colorizer_sizes, + value = frame_processors_globals.frame_colorizer_size, + visible = 'frame_colorizer' in deepfuze.globals.frame_processors + ) + FRAME_ENHANCER_MODEL_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.frame_enhancer_model_dropdown'), + choices = frame_processors_choices.frame_enhancer_models, + value = frame_processors_globals.frame_enhancer_model, + visible = 'frame_enhancer' in deepfuze.globals.frame_processors + ) + FRAME_ENHANCER_BLEND_SLIDER = gradio.Slider( + label = wording.get('uis.frame_enhancer_blend_slider'), + value = frame_processors_globals.frame_enhancer_blend, + step = frame_processors_choices.frame_enhancer_blend_range[1] - frame_processors_choices.frame_enhancer_blend_range[0], + minimum = frame_processors_choices.frame_enhancer_blend_range[0], + maximum = frame_processors_choices.frame_enhancer_blend_range[-1], + visible = 'frame_enhancer' in deepfuze.globals.frame_processors + ) + LIP_SYNCER_MODEL_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.lip_syncer_model_dropdown'), + choices = frame_processors_choices.lip_syncer_models, + value = frame_processors_globals.lip_syncer_model, + visible = 'lip_syncer' in deepfuze.globals.frame_processors + ) + register_ui_component('face_debugger_items_checkbox_group', FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP) + register_ui_component('face_enhancer_model_dropdown', FACE_ENHANCER_MODEL_DROPDOWN) + register_ui_component('face_enhancer_blend_slider', FACE_ENHANCER_BLEND_SLIDER) + register_ui_component('face_swapper_model_dropdown', FACE_SWAPPER_MODEL_DROPDOWN) + register_ui_component('frame_colorizer_model_dropdown', FRAME_COLORIZER_MODEL_DROPDOWN) + register_ui_component('frame_colorizer_blend_slider', FRAME_COLORIZER_BLEND_SLIDER) + register_ui_component('frame_colorizer_size_dropdown', FRAME_COLORIZER_SIZE_DROPDOWN) + register_ui_component('frame_enhancer_model_dropdown', FRAME_ENHANCER_MODEL_DROPDOWN) + register_ui_component('frame_enhancer_blend_slider', FRAME_ENHANCER_BLEND_SLIDER) + register_ui_component('lip_syncer_model_dropdown', LIP_SYNCER_MODEL_DROPDOWN) + + +def listen() -> None: + FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP.change(update_face_debugger_items, inputs = FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP) + FACE_ENHANCER_MODEL_DROPDOWN.change(update_face_enhancer_model, inputs = FACE_ENHANCER_MODEL_DROPDOWN, outputs = FACE_ENHANCER_MODEL_DROPDOWN) + FACE_ENHANCER_BLEND_SLIDER.release(update_face_enhancer_blend, inputs = FACE_ENHANCER_BLEND_SLIDER) + FACE_SWAPPER_MODEL_DROPDOWN.change(update_face_swapper_model, inputs = FACE_SWAPPER_MODEL_DROPDOWN, outputs = FACE_SWAPPER_MODEL_DROPDOWN) + FRAME_COLORIZER_MODEL_DROPDOWN.change(update_frame_colorizer_model, inputs = FRAME_COLORIZER_MODEL_DROPDOWN, outputs = FRAME_COLORIZER_MODEL_DROPDOWN) + FRAME_COLORIZER_BLEND_SLIDER.release(update_frame_colorizer_blend, inputs = FRAME_COLORIZER_BLEND_SLIDER) + FRAME_COLORIZER_SIZE_DROPDOWN.change(update_frame_colorizer_size, inputs = FRAME_COLORIZER_SIZE_DROPDOWN, outputs = FRAME_COLORIZER_SIZE_DROPDOWN) + FRAME_ENHANCER_MODEL_DROPDOWN.change(update_frame_enhancer_model, inputs = FRAME_ENHANCER_MODEL_DROPDOWN, outputs = FRAME_ENHANCER_MODEL_DROPDOWN) + FRAME_ENHANCER_BLEND_SLIDER.release(update_frame_enhancer_blend, inputs = FRAME_ENHANCER_BLEND_SLIDER) + LIP_SYNCER_MODEL_DROPDOWN.change(update_lip_syncer_model, inputs = LIP_SYNCER_MODEL_DROPDOWN, outputs = LIP_SYNCER_MODEL_DROPDOWN) + frame_processors_checkbox_group = get_ui_component('frame_processors_checkbox_group') + if frame_processors_checkbox_group: + frame_processors_checkbox_group.change(update_frame_processors, inputs = frame_processors_checkbox_group, outputs = [ FACE_DEBUGGER_ITEMS_CHECKBOX_GROUP, FACE_ENHANCER_MODEL_DROPDOWN, FACE_ENHANCER_BLEND_SLIDER, FACE_SWAPPER_MODEL_DROPDOWN, FRAME_COLORIZER_MODEL_DROPDOWN, FRAME_COLORIZER_BLEND_SLIDER, FRAME_COLORIZER_SIZE_DROPDOWN, FRAME_ENHANCER_MODEL_DROPDOWN, FRAME_ENHANCER_BLEND_SLIDER, LIP_SYNCER_MODEL_DROPDOWN ]) + + +def update_frame_processors(frame_processors : List[str]) -> Tuple[gradio.CheckboxGroup, gradio.Dropdown, gradio.Slider, gradio.Dropdown, gradio.Dropdown, gradio.Slider, gradio.Dropdown, gradio.Dropdown, gradio.Slider, gradio.Dropdown]: + has_face_debugger = 'face_debugger' in frame_processors + has_face_enhancer = 'face_enhancer' in frame_processors + has_face_swapper = 'face_swapper' in frame_processors + has_frame_colorizer = 'frame_colorizer' in frame_processors + has_frame_enhancer = 'frame_enhancer' in frame_processors + has_lip_syncer = 'lip_syncer' in frame_processors + return gradio.CheckboxGroup(visible = has_face_debugger), gradio.Dropdown(visible = has_face_enhancer), gradio.Slider(visible = has_face_enhancer), gradio.Dropdown(visible = has_face_swapper), gradio.Dropdown(visible = has_frame_colorizer), gradio.Slider(visible = has_frame_colorizer), gradio.Dropdown(visible = has_frame_colorizer), gradio.Dropdown(visible = has_frame_enhancer), gradio.Slider(visible = has_frame_enhancer), gradio.Dropdown(visible = has_lip_syncer) + + +def update_face_debugger_items(face_debugger_items : List[FaceDebuggerItem]) -> None: + frame_processors_globals.face_debugger_items = face_debugger_items + + +def update_face_enhancer_model(face_enhancer_model : FaceEnhancerModel) -> gradio.Dropdown: + frame_processors_globals.face_enhancer_model = face_enhancer_model + face_enhancer_module = load_frame_processor_module('face_enhancer') + face_enhancer_module.clear_frame_processor() + face_enhancer_module.set_options('model', face_enhancer_module.MODELS[face_enhancer_model]) + if face_enhancer_module.pre_check(): + return gradio.Dropdown(value = frame_processors_globals.face_enhancer_model) + return gradio.Dropdown() + + +def update_face_enhancer_blend(face_enhancer_blend : int) -> None: + frame_processors_globals.face_enhancer_blend = face_enhancer_blend + + +def update_face_swapper_model(face_swapper_model : FaceSwapperModel) -> gradio.Dropdown: + frame_processors_globals.face_swapper_model = face_swapper_model + if face_swapper_model == 'blendswap_256': + deepfuze.globals.face_recognizer_model = 'arcface_blendswap' + if face_swapper_model == 'inswapper_128' or face_swapper_model == 'inswapper_128_fp16': + deepfuze.globals.face_recognizer_model = 'arcface_inswapper' + if face_swapper_model == 'simswap_256' or face_swapper_model == 'simswap_512_unofficial': + deepfuze.globals.face_recognizer_model = 'arcface_simswap' + if face_swapper_model == 'uniface_256': + deepfuze.globals.face_recognizer_model = 'arcface_uniface' + face_swapper_module = load_frame_processor_module('face_swapper') + face_swapper_module.clear_model_initializer() + face_swapper_module.clear_frame_processor() + face_swapper_module.set_options('model', face_swapper_module.MODELS[face_swapper_model]) + if face_analyser.pre_check() and face_swapper_module.pre_check(): + return gradio.Dropdown(value = frame_processors_globals.face_swapper_model) + return gradio.Dropdown() + + +def update_frame_colorizer_model(frame_colorizer_model : FrameColorizerModel) -> gradio.Dropdown: + frame_processors_globals.frame_colorizer_model = frame_colorizer_model + frame_colorizer_module = load_frame_processor_module('frame_colorizer') + frame_colorizer_module.clear_frame_processor() + frame_colorizer_module.set_options('model', frame_colorizer_module.MODELS[frame_colorizer_model]) + if frame_colorizer_module.pre_check(): + return gradio.Dropdown(value = frame_processors_globals.frame_colorizer_model) + return gradio.Dropdown() + + +def update_frame_colorizer_blend(frame_colorizer_blend : int) -> None: + frame_processors_globals.frame_colorizer_blend = frame_colorizer_blend + + +def update_frame_colorizer_size(frame_colorizer_size : str) -> gradio.Dropdown: + frame_processors_globals.frame_colorizer_size = frame_colorizer_size + return gradio.Dropdown(value = frame_processors_globals.frame_colorizer_size) + + +def update_frame_enhancer_model(frame_enhancer_model : FrameEnhancerModel) -> gradio.Dropdown: + frame_processors_globals.frame_enhancer_model = frame_enhancer_model + frame_enhancer_module = load_frame_processor_module('frame_enhancer') + frame_enhancer_module.clear_frame_processor() + frame_enhancer_module.set_options('model', frame_enhancer_module.MODELS[frame_enhancer_model]) + if frame_enhancer_module.pre_check(): + return gradio.Dropdown(value = frame_processors_globals.frame_enhancer_model) + return gradio.Dropdown() + + +def update_frame_enhancer_blend(frame_enhancer_blend : int) -> None: + frame_processors_globals.frame_enhancer_blend = frame_enhancer_blend + + +def update_lip_syncer_model(lip_syncer_model : LipSyncerModel) -> gradio.Dropdown: + frame_processors_globals.lip_syncer_model = lip_syncer_model + lip_syncer_module = load_frame_processor_module('lip_syncer') + lip_syncer_module.clear_frame_processor() + lip_syncer_module.set_options('model', lip_syncer_module.MODELS[lip_syncer_model]) + if lip_syncer_module.pre_check(): + return gradio.Dropdown(value = frame_processors_globals.lip_syncer_model) + return gradio.Dropdown() diff --git a/deepfuze/uis/components/memory.py b/deepfuze/uis/components/memory.py new file mode 100644 index 0000000..4446ae0 --- /dev/null +++ b/deepfuze/uis/components/memory.py @@ -0,0 +1,41 @@ +from typing import Optional +import gradio + +import deepfuze.globals +import deepfuze.choices +from deepfuze.typing import VideoMemoryStrategy +from deepfuze import wording + +VIDEO_MEMORY_STRATEGY_DROPDOWN : Optional[gradio.Dropdown] = None +SYSTEM_MEMORY_LIMIT_SLIDER : Optional[gradio.Slider] = None + + +def render() -> None: + global VIDEO_MEMORY_STRATEGY_DROPDOWN + global SYSTEM_MEMORY_LIMIT_SLIDER + + VIDEO_MEMORY_STRATEGY_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.video_memory_strategy_dropdown'), + choices = deepfuze.choices.video_memory_strategies, + value = deepfuze.globals.video_memory_strategy + ) + SYSTEM_MEMORY_LIMIT_SLIDER = gradio.Slider( + label = wording.get('uis.system_memory_limit_slider'), + step =deepfuze.choices.system_memory_limit_range[1] - deepfuze.choices.system_memory_limit_range[0], + minimum = deepfuze.choices.system_memory_limit_range[0], + maximum = deepfuze.choices.system_memory_limit_range[-1], + value = deepfuze.globals.system_memory_limit + ) + + +def listen() -> None: + VIDEO_MEMORY_STRATEGY_DROPDOWN.change(update_video_memory_strategy, inputs = VIDEO_MEMORY_STRATEGY_DROPDOWN) + SYSTEM_MEMORY_LIMIT_SLIDER.release(update_system_memory_limit, inputs = SYSTEM_MEMORY_LIMIT_SLIDER) + + +def update_video_memory_strategy(video_memory_strategy : VideoMemoryStrategy) -> None: + deepfuze.globals.video_memory_strategy = video_memory_strategy + + +def update_system_memory_limit(system_memory_limit : int) -> None: + deepfuze.globals.system_memory_limit = system_memory_limit diff --git a/deepfuze/uis/components/output.py b/deepfuze/uis/components/output.py new file mode 100644 index 0000000..b2005c8 --- /dev/null +++ b/deepfuze/uis/components/output.py @@ -0,0 +1,88 @@ +from typing import Tuple, Optional +from time import sleep +import gradio + +import deepfuze.globals +from deepfuze import process_manager, wording +from deepfuze.core import conditional_process +from deepfuze.memory import limit_system_memory +from deepfuze.normalizer import normalize_output_path +from deepfuze.uis.core import get_ui_component +from deepfuze.filesystem import clear_temp, is_image, is_video + +OUTPUT_IMAGE : Optional[gradio.Image] = None +OUTPUT_VIDEO : Optional[gradio.Video] = None +OUTPUT_START_BUTTON : Optional[gradio.Button] = None +OUTPUT_CLEAR_BUTTON : Optional[gradio.Button] = None +OUTPUT_STOP_BUTTON : Optional[gradio.Button] = None + + +def render() -> None: + global OUTPUT_IMAGE + global OUTPUT_VIDEO + global OUTPUT_START_BUTTON + global OUTPUT_STOP_BUTTON + global OUTPUT_CLEAR_BUTTON + + OUTPUT_IMAGE = gradio.Image( + label = wording.get('uis.output_image_or_video'), + visible = False + ) + OUTPUT_VIDEO = gradio.Video( + label = wording.get('uis.output_image_or_video') + ) + OUTPUT_START_BUTTON = gradio.Button( + value = wording.get('uis.start_button'), + variant = 'primary', + size = 'sm' + ) + OUTPUT_STOP_BUTTON = gradio.Button( + value = wording.get('uis.stop_button'), + variant = 'primary', + size = 'sm', + visible = False + ) + OUTPUT_CLEAR_BUTTON = gradio.Button( + value = wording.get('uis.clear_button'), + size = 'sm' + ) + + +def listen() -> None: + output_path_textbox = get_ui_component('output_path_textbox') + if output_path_textbox: + OUTPUT_START_BUTTON.click(start, outputs = [ OUTPUT_START_BUTTON, OUTPUT_STOP_BUTTON ]) + OUTPUT_START_BUTTON.click(process, outputs = [ OUTPUT_IMAGE, OUTPUT_VIDEO, OUTPUT_START_BUTTON, OUTPUT_STOP_BUTTON ]) + OUTPUT_STOP_BUTTON.click(stop, outputs = [ OUTPUT_START_BUTTON, OUTPUT_STOP_BUTTON ]) + OUTPUT_CLEAR_BUTTON.click(clear, outputs = [ OUTPUT_IMAGE, OUTPUT_VIDEO ]) + + +def start() -> Tuple[gradio.Button, gradio.Button]: + while not process_manager.is_processing(): + sleep(0.5) + return gradio.Button(visible = False), gradio.Button(visible = True) + + +def process() -> Tuple[gradio.Image, gradio.Video, gradio.Button, gradio.Button]: + normed_output_path = normalize_output_path(deepfuze.globals.target_path, deepfuze.globals.output_path) + if deepfuze.globals.system_memory_limit > 0: + limit_system_memory(deepfuze.globals.system_memory_limit) + conditional_process() + if is_image(normed_output_path): + return gradio.Image(value = normed_output_path, visible = True), gradio.Video(value = None, visible = False), gradio.Button(visible = True), gradio.Button(visible = False) + if is_video(normed_output_path): + return gradio.Image(value = None, visible = False), gradio.Video(value = normed_output_path, visible = True), gradio.Button(visible = True), gradio.Button(visible = False) + return gradio.Image(value = None), gradio.Video(value = None), gradio.Button(visible = True), gradio.Button(visible = False) + + +def stop() -> Tuple[gradio.Button, gradio.Button]: + process_manager.stop() + return gradio.Button(visible = True), gradio.Button(visible = False) + + +def clear() -> Tuple[gradio.Image, gradio.Video]: + while process_manager.is_processing(): + sleep(0.5) + if deepfuze.globals.target_path: + clear_temp(deepfuze.globals.target_path) + return gradio.Image(value = None), gradio.Video(value = None) diff --git a/deepfuze/uis/components/output_options.py b/deepfuze/uis/components/output_options.py new file mode 100644 index 0000000..2a1e97f --- /dev/null +++ b/deepfuze/uis/components/output_options.py @@ -0,0 +1,161 @@ +from typing import Optional, Tuple +import gradio + +import deepfuze.globals +import deepfuze.choices +from deepfuze import wording +from deepfuze.typing import OutputVideoEncoder, OutputVideoPreset, Fps +from deepfuze.filesystem import is_image, is_video +from deepfuze.uis.core import get_ui_components, register_ui_component +from deepfuze.vision import detect_image_resolution, create_image_resolutions, detect_video_fps, detect_video_resolution, create_video_resolutions, pack_resolution + +OUTPUT_PATH_TEXTBOX : Optional[gradio.Textbox] = None +OUTPUT_IMAGE_QUALITY_SLIDER : Optional[gradio.Slider] = None +OUTPUT_IMAGE_RESOLUTION_DROPDOWN : Optional[gradio.Dropdown] = None +OUTPUT_VIDEO_ENCODER_DROPDOWN : Optional[gradio.Dropdown] = None +OUTPUT_VIDEO_PRESET_DROPDOWN : Optional[gradio.Dropdown] = None +OUTPUT_VIDEO_RESOLUTION_DROPDOWN : Optional[gradio.Dropdown] = None +OUTPUT_VIDEO_QUALITY_SLIDER : Optional[gradio.Slider] = None +OUTPUT_VIDEO_FPS_SLIDER : Optional[gradio.Slider] = None + + +def render() -> None: + global OUTPUT_PATH_TEXTBOX + global OUTPUT_IMAGE_QUALITY_SLIDER + global OUTPUT_IMAGE_RESOLUTION_DROPDOWN + global OUTPUT_VIDEO_ENCODER_DROPDOWN + global OUTPUT_VIDEO_PRESET_DROPDOWN + global OUTPUT_VIDEO_RESOLUTION_DROPDOWN + global OUTPUT_VIDEO_QUALITY_SLIDER + global OUTPUT_VIDEO_FPS_SLIDER + + output_image_resolutions = [] + output_video_resolutions = [] + if is_image(deepfuze.globals.target_path): + output_image_resolution = detect_image_resolution(deepfuze.globals.target_path) + output_image_resolutions = create_image_resolutions(output_image_resolution) + if is_video(deepfuze.globals.target_path): + output_video_resolution = detect_video_resolution(deepfuze.globals.target_path) + output_video_resolutions = create_video_resolutions(output_video_resolution) + deepfuze.globals.output_path = deepfuze.globals.output_path or '.' + OUTPUT_PATH_TEXTBOX = gradio.Textbox( + label = wording.get('uis.output_path_textbox'), + value = deepfuze.globals.output_path, + max_lines = 1 + ) + OUTPUT_IMAGE_QUALITY_SLIDER = gradio.Slider( + label = wording.get('uis.output_image_quality_slider'), + value = deepfuze.globals.output_image_quality, + step = deepfuze.choices.output_image_quality_range[1] - deepfuze.choices.output_image_quality_range[0], + minimum = deepfuze.choices.output_image_quality_range[0], + maximum = deepfuze.choices.output_image_quality_range[-1], + visible = is_image(deepfuze.globals.target_path) + ) + OUTPUT_IMAGE_RESOLUTION_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.output_image_resolution_dropdown'), + choices = output_image_resolutions, + value = deepfuze.globals.output_image_resolution, + visible = is_image(deepfuze.globals.target_path) + ) + OUTPUT_VIDEO_ENCODER_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.output_video_encoder_dropdown'), + choices = deepfuze.choices.output_video_encoders, + value = deepfuze.globals.output_video_encoder, + visible = is_video(deepfuze.globals.target_path) + ) + OUTPUT_VIDEO_PRESET_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.output_video_preset_dropdown'), + choices = deepfuze.choices.output_video_presets, + value = deepfuze.globals.output_video_preset, + visible = is_video(deepfuze.globals.target_path) + ) + OUTPUT_VIDEO_QUALITY_SLIDER = gradio.Slider( + label = wording.get('uis.output_video_quality_slider'), + value = deepfuze.globals.output_video_quality, + step = deepfuze.choices.output_video_quality_range[1] - deepfuze.choices.output_video_quality_range[0], + minimum = deepfuze.choices.output_video_quality_range[0], + maximum = deepfuze.choices.output_video_quality_range[-1], + visible = is_video(deepfuze.globals.target_path) + ) + OUTPUT_VIDEO_RESOLUTION_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.output_video_resolution_dropdown'), + choices = output_video_resolutions, + value = deepfuze.globals.output_video_resolution, + visible = is_video(deepfuze.globals.target_path) + ) + OUTPUT_VIDEO_FPS_SLIDER = gradio.Slider( + label = wording.get('uis.output_video_fps_slider'), + value = deepfuze.globals.output_video_fps, + step = 0.01, + minimum = 1, + maximum = 60, + visible = is_video(deepfuze.globals.target_path) + ) + register_ui_component('output_path_textbox', OUTPUT_PATH_TEXTBOX) + register_ui_component('output_video_fps_slider', OUTPUT_VIDEO_FPS_SLIDER) + + +def listen() -> None: + OUTPUT_PATH_TEXTBOX.change(update_output_path, inputs = OUTPUT_PATH_TEXTBOX) + OUTPUT_IMAGE_QUALITY_SLIDER.release(update_output_image_quality, inputs = OUTPUT_IMAGE_QUALITY_SLIDER) + OUTPUT_IMAGE_RESOLUTION_DROPDOWN.change(update_output_image_resolution, inputs = OUTPUT_IMAGE_RESOLUTION_DROPDOWN) + OUTPUT_VIDEO_ENCODER_DROPDOWN.change(update_output_video_encoder, inputs = OUTPUT_VIDEO_ENCODER_DROPDOWN) + OUTPUT_VIDEO_PRESET_DROPDOWN.change(update_output_video_preset, inputs = OUTPUT_VIDEO_PRESET_DROPDOWN) + OUTPUT_VIDEO_QUALITY_SLIDER.release(update_output_video_quality, inputs = OUTPUT_VIDEO_QUALITY_SLIDER) + OUTPUT_VIDEO_RESOLUTION_DROPDOWN.change(update_output_video_resolution, inputs = OUTPUT_VIDEO_RESOLUTION_DROPDOWN) + OUTPUT_VIDEO_FPS_SLIDER.release(update_output_video_fps, inputs = OUTPUT_VIDEO_FPS_SLIDER) + + for ui_component in get_ui_components( + [ + 'target_image', + 'target_video' + ]): + for method in [ 'upload', 'change', 'clear' ]: + getattr(ui_component, method)(remote_update, outputs = [ OUTPUT_IMAGE_QUALITY_SLIDER, OUTPUT_IMAGE_RESOLUTION_DROPDOWN, OUTPUT_VIDEO_ENCODER_DROPDOWN, OUTPUT_VIDEO_PRESET_DROPDOWN, OUTPUT_VIDEO_QUALITY_SLIDER, OUTPUT_VIDEO_RESOLUTION_DROPDOWN, OUTPUT_VIDEO_FPS_SLIDER ]) + + +def remote_update() -> Tuple[gradio.Slider, gradio.Dropdown, gradio.Dropdown, gradio.Dropdown, gradio.Slider, gradio.Dropdown, gradio.Slider]: + if is_image(deepfuze.globals.target_path): + output_image_resolution = detect_image_resolution(deepfuze.globals.target_path) + output_image_resolutions = create_image_resolutions(output_image_resolution) + deepfuze.globals.output_image_resolution = pack_resolution(output_image_resolution) + return gradio.Slider(visible = True), gradio.Dropdown(visible = True, value = deepfuze.globals.output_image_resolution, choices = output_image_resolutions), gradio.Dropdown(visible = False), gradio.Dropdown(visible = False), gradio.Slider(visible = False), gradio.Dropdown(visible = False, value = None, choices = None), gradio.Slider(visible = False, value = None) + if is_video(deepfuze.globals.target_path): + output_video_resolution = detect_video_resolution(deepfuze.globals.target_path) + output_video_resolutions = create_video_resolutions(output_video_resolution) + deepfuze.globals.output_video_resolution = pack_resolution(output_video_resolution) + deepfuze.globals.output_video_fps = detect_video_fps(deepfuze.globals.target_path) + return gradio.Slider(visible = False), gradio.Dropdown(visible = False), gradio.Dropdown(visible = True), gradio.Dropdown(visible = True), gradio.Slider(visible = True), gradio.Dropdown(visible = True, value = deepfuze.globals.output_video_resolution, choices = output_video_resolutions), gradio.Slider(visible = True, value = deepfuze.globals.output_video_fps) + return gradio.Slider(visible = False), gradio.Dropdown(visible = False, value = None, choices = None), gradio.Dropdown(visible = False), gradio.Dropdown(visible = False), gradio.Slider(visible = False), gradio.Dropdown(visible = False, value = None, choices = None), gradio.Slider(visible = False, value = None) + + +def update_output_path(output_path : str) -> None: + deepfuze.globals.output_path = output_path + + +def update_output_image_quality(output_image_quality : int) -> None: + deepfuze.globals.output_image_quality = output_image_quality + + +def update_output_image_resolution(output_image_resolution : str) -> None: + deepfuze.globals.output_image_resolution = output_image_resolution + + +def update_output_video_encoder(output_video_encoder: OutputVideoEncoder) -> None: + deepfuze.globals.output_video_encoder = output_video_encoder + + +def update_output_video_preset(output_video_preset : OutputVideoPreset) -> None: + deepfuze.globals.output_video_preset = output_video_preset + + +def update_output_video_quality(output_video_quality : int) -> None: + deepfuze.globals.output_video_quality = output_video_quality + + +def update_output_video_resolution(output_video_resolution : str) -> None: + deepfuze.globals.output_video_resolution = output_video_resolution + + +def update_output_video_fps(output_video_fps : Fps) -> None: + deepfuze.globals.output_video_fps = output_video_fps diff --git a/deepfuze/uis/components/preview.py b/deepfuze/uis/components/preview.py new file mode 100755 index 0000000..a52f88f --- /dev/null +++ b/deepfuze/uis/components/preview.py @@ -0,0 +1,207 @@ +from typing import Any, Dict, Optional +from time import sleep +import cv2 +import gradio +import numpy + +import deepfuze.globals +from deepfuze import logger, wording +from deepfuze.audio import get_audio_frame, create_empty_audio_frame +from deepfuze.common_helper import get_first +from deepfuze.core import conditional_append_reference_faces +from deepfuze.face_analyser import get_average_face, clear_face_analyser +from deepfuze.face_store import clear_static_faces, get_reference_faces, clear_reference_faces +from deepfuze.typing import Face, FaceSet, AudioFrame, VisionFrame +from deepfuze.vision import get_video_frame, count_video_frame_total, normalize_frame_color, resize_frame_resolution, read_static_image, read_static_images +from deepfuze.filesystem import is_image, is_video, filter_audio_paths +from deepfuze.content_analyser import analyse_frame +from deepfuze.processors.frame.core import load_frame_processor_module +from deepfuze.uis.core import get_ui_component, get_ui_components, register_ui_component + +PREVIEW_IMAGE : Optional[gradio.Image] = None +PREVIEW_FRAME_SLIDER : Optional[gradio.Slider] = None + + +def render() -> None: + global PREVIEW_IMAGE + global PREVIEW_FRAME_SLIDER + + preview_image_args : Dict[str, Any] =\ + { + 'label': wording.get('uis.preview_image'), + 'interactive': False + } + preview_frame_slider_args : Dict[str, Any] =\ + { + 'label': wording.get('uis.preview_frame_slider'), + 'step': 1, + 'minimum': 0, + 'maximum': 100, + 'visible': False + } + conditional_append_reference_faces() + reference_faces = get_reference_faces() if 'reference' in deepfuze.globals.face_selector_mode else None + source_frames = read_static_images(deepfuze.globals.source_paths) + source_face = get_average_face(source_frames) + source_audio_path = get_first(filter_audio_paths(deepfuze.globals.source_paths)) + source_audio_frame = create_empty_audio_frame() + if source_audio_path and deepfuze.globals.output_video_fps and deepfuze.globals.reference_frame_number: + temp_audio_frame = get_audio_frame(source_audio_path, deepfuze.globals.output_video_fps, deepfuze.globals.reference_frame_number) + if numpy.any(temp_audio_frame): + source_audio_frame = temp_audio_frame + if is_image(deepfuze.globals.target_path): + target_vision_frame = read_static_image(deepfuze.globals.target_path) + preview_vision_frame = process_preview_frame(reference_faces, source_face, source_audio_frame, target_vision_frame) + preview_image_args['value'] = normalize_frame_color(preview_vision_frame) + if is_video(deepfuze.globals.target_path): + temp_vision_frame = get_video_frame(deepfuze.globals.target_path, deepfuze.globals.reference_frame_number) + preview_vision_frame = process_preview_frame(reference_faces, source_face, source_audio_frame, temp_vision_frame) + preview_image_args['value'] = normalize_frame_color(preview_vision_frame) + preview_image_args['visible'] = True + preview_frame_slider_args['value'] = deepfuze.globals.reference_frame_number + preview_frame_slider_args['maximum'] = count_video_frame_total(deepfuze.globals.target_path) + preview_frame_slider_args['visible'] = True + PREVIEW_IMAGE = gradio.Image(**preview_image_args) + PREVIEW_FRAME_SLIDER = gradio.Slider(**preview_frame_slider_args) + register_ui_component('preview_frame_slider', PREVIEW_FRAME_SLIDER) + + +def listen() -> None: + PREVIEW_FRAME_SLIDER.release(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) + reference_face_position_gallery = get_ui_component('reference_face_position_gallery') + if reference_face_position_gallery: + reference_face_position_gallery.select(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) + + for ui_component in get_ui_components( + [ + 'source_audio', + 'source_image', + 'target_image', + 'target_video' + ]): + for method in [ 'upload', 'change', 'clear' ]: + getattr(ui_component, method)(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) + + for ui_component in get_ui_components( + [ + 'target_image', + 'target_video' + ]): + for method in [ 'upload', 'change', 'clear' ]: + getattr(ui_component, method)(update_preview_frame_slider, outputs = PREVIEW_FRAME_SLIDER) + + for ui_component in get_ui_components( + [ + 'face_debugger_items_checkbox_group', + 'frame_colorizer_size_dropdown', + 'face_selector_mode_dropdown', + 'face_mask_types_checkbox_group', + 'face_mask_region_checkbox_group', + 'face_analyser_order_dropdown', + 'face_analyser_age_dropdown', + 'face_analyser_gender_dropdown' + ]): + ui_component.change(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) + + for ui_component in get_ui_components( + [ + 'face_enhancer_blend_slider', + 'frame_colorizer_blend_slider', + 'frame_enhancer_blend_slider', + 'trim_frame_start_slider', + 'trim_frame_end_slider', + 'reference_face_distance_slider', + 'face_mask_blur_slider', + 'face_mask_padding_top_slider', + 'face_mask_padding_bottom_slider', + 'face_mask_padding_left_slider', + 'face_mask_padding_right_slider', + 'output_video_fps_slider' + ]): + ui_component.release(update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) + + for ui_component in get_ui_components( + [ + 'frame_processors_checkbox_group', + 'face_enhancer_model_dropdown', + 'face_swapper_model_dropdown', + 'frame_colorizer_model_dropdown', + 'frame_enhancer_model_dropdown', + 'lip_syncer_model_dropdown', + 'face_detector_model_dropdown', + 'face_detector_size_dropdown' + ]): + ui_component.change(clear_and_update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) + + for ui_component in get_ui_components( + [ + 'face_detector_score_slider', + 'face_landmarker_score_slider' + ]): + ui_component.release(clear_and_update_preview_image, inputs = PREVIEW_FRAME_SLIDER, outputs = PREVIEW_IMAGE) + + +def clear_and_update_preview_image(frame_number : int = 0) -> gradio.Image: + clear_face_analyser() + clear_reference_faces() + clear_static_faces() + return update_preview_image(frame_number) + + +def update_preview_image(frame_number : int = 0) -> gradio.Image: + for frame_processor in deepfuze.globals.frame_processors: + frame_processor_module = load_frame_processor_module(frame_processor) + while not frame_processor_module.post_check(): + logger.disable() + sleep(0.5) + logger.enable() + conditional_append_reference_faces() + reference_faces = get_reference_faces() if 'reference' in deepfuze.globals.face_selector_mode else None + source_frames = read_static_images(deepfuze.globals.source_paths) + source_face = get_average_face(source_frames) + source_audio_path = get_first(filter_audio_paths(deepfuze.globals.source_paths)) + source_audio_frame = create_empty_audio_frame() + if source_audio_path and deepfuze.globals.output_video_fps and deepfuze.globals.reference_frame_number: + reference_audio_frame_number = deepfuze.globals.reference_frame_number + if deepfuze.globals.trim_frame_start: + reference_audio_frame_number -= deepfuze.globals.trim_frame_start + temp_audio_frame = get_audio_frame(source_audio_path, deepfuze.globals.output_video_fps, reference_audio_frame_number) + if numpy.any(temp_audio_frame): + source_audio_frame = temp_audio_frame + if is_image(deepfuze.globals.target_path): + target_vision_frame = read_static_image(deepfuze.globals.target_path) + preview_vision_frame = process_preview_frame(reference_faces, source_face, source_audio_frame, target_vision_frame) + preview_vision_frame = normalize_frame_color(preview_vision_frame) + return gradio.Image(value = preview_vision_frame) + if is_video(deepfuze.globals.target_path): + temp_vision_frame = get_video_frame(deepfuze.globals.target_path, frame_number) + preview_vision_frame = process_preview_frame(reference_faces, source_face, source_audio_frame, temp_vision_frame) + preview_vision_frame = normalize_frame_color(preview_vision_frame) + return gradio.Image(value = preview_vision_frame) + return gradio.Image(value = None) + + +def update_preview_frame_slider() -> gradio.Slider: + if is_video(deepfuze.globals.target_path): + video_frame_total = count_video_frame_total(deepfuze.globals.target_path) + return gradio.Slider(maximum = video_frame_total, visible = True) + return gradio.Slider(value = None, maximum = None, visible = False) + + +def process_preview_frame(reference_faces : FaceSet, source_face : Face, source_audio_frame : AudioFrame, target_vision_frame : VisionFrame) -> VisionFrame: + target_vision_frame = resize_frame_resolution(target_vision_frame, (640, 640)) + if analyse_frame(target_vision_frame): + return cv2.GaussianBlur(target_vision_frame, (99, 99), 0) + for frame_processor in deepfuze.globals.frame_processors: + frame_processor_module = load_frame_processor_module(frame_processor) + logger.disable() + if frame_processor_module.pre_process('preview'): + logger.enable() + target_vision_frame = frame_processor_module.process_frame( + { + 'reference_faces': reference_faces, + 'source_face': source_face, + 'source_audio_frame': source_audio_frame, + 'target_vision_frame': target_vision_frame + }) + return target_vision_frame diff --git a/deepfuze/uis/components/source.py b/deepfuze/uis/components/source.py new file mode 100644 index 0000000..6e1868e --- /dev/null +++ b/deepfuze/uis/components/source.py @@ -0,0 +1,67 @@ +from typing import Optional, List, Tuple +import gradio + +import deepfuze.globals +from deepfuze import wording +from deepfuze.uis.typing import File +from deepfuze.common_helper import get_first +from deepfuze.filesystem import has_audio, has_image, filter_audio_paths, filter_image_paths +from deepfuze.uis.core import register_ui_component + +SOURCE_FILE : Optional[gradio.File] = None +SOURCE_AUDIO : Optional[gradio.Audio] = None +SOURCE_IMAGE : Optional[gradio.Image] = None + + +def render() -> None: + global SOURCE_FILE + global SOURCE_AUDIO + global SOURCE_IMAGE + + has_source_audio = has_audio(deepfuze.globals.source_paths) + has_source_image = has_image(deepfuze.globals.source_paths) + SOURCE_FILE = gradio.File( + file_count = 'multiple', + file_types = + [ + '.mp3', + '.wav', + '.png', + '.jpg', + '.webp' + ], + label = wording.get('uis.source_file'), + value = deepfuze.globals.source_paths if has_source_audio or has_source_image else None + ) + source_file_names = [ source_file_value['name'] for source_file_value in SOURCE_FILE.value ] if SOURCE_FILE.value else None + source_audio_path = get_first(filter_audio_paths(source_file_names)) + source_image_path = get_first(filter_image_paths(source_file_names)) + SOURCE_AUDIO = gradio.Audio( + value = source_audio_path if has_source_audio else None, + visible = has_source_audio, + show_label = False + ) + SOURCE_IMAGE = gradio.Image( + value = source_image_path if has_source_image else None, + visible = has_source_image, + show_label = False + ) + register_ui_component('source_audio', SOURCE_AUDIO) + register_ui_component('source_image', SOURCE_IMAGE) + + +def listen() -> None: + SOURCE_FILE.change(update, inputs = SOURCE_FILE, outputs = [ SOURCE_AUDIO, SOURCE_IMAGE ]) + + +def update(files : List[File]) -> Tuple[gradio.Audio, gradio.Image]: + file_names = [ file.name for file in files ] if files else None + has_source_audio = has_audio(file_names) + has_source_image = has_image(file_names) + if has_source_audio or has_source_image: + source_audio_path = get_first(filter_audio_paths(file_names)) + source_image_path = get_first(filter_image_paths(file_names)) + deepfuze.globals.source_paths = file_names + return gradio.Audio(value = source_audio_path, visible = has_source_audio), gradio.Image(value = source_image_path, visible = has_source_image) + deepfuze.globals.source_paths = None + return gradio.Audio(value = None, visible = False), gradio.Image(value = None, visible = False) diff --git a/deepfuze/uis/components/target.py b/deepfuze/uis/components/target.py new file mode 100644 index 0000000..f9d9d0a --- /dev/null +++ b/deepfuze/uis/components/target.py @@ -0,0 +1,83 @@ +from typing import Tuple, Optional +import gradio + +import deepfuze.globals +from deepfuze import wording +from deepfuze.face_store import clear_static_faces, clear_reference_faces +from deepfuze.uis.typing import File +from deepfuze.filesystem import get_file_size, is_image, is_video +from deepfuze.uis.core import register_ui_component +from deepfuze.vision import get_video_frame, normalize_frame_color + +FILE_SIZE_LIMIT = 512 * 1024 * 1024 + +TARGET_FILE : Optional[gradio.File] = None +TARGET_IMAGE : Optional[gradio.Image] = None +TARGET_VIDEO : Optional[gradio.Video] = None + + +def render() -> None: + global TARGET_FILE + global TARGET_IMAGE + global TARGET_VIDEO + + is_target_image = is_image(deepfuze.globals.target_path) + is_target_video = is_video(deepfuze.globals.target_path) + TARGET_FILE = gradio.File( + label = wording.get('uis.target_file'), + file_count = 'single', + file_types = + [ + '.png', + '.jpg', + '.webp', + '.webm', + '.mp4' + ], + value = deepfuze.globals.target_path if is_target_image or is_target_video else None + ) + target_image_args =\ + { + 'show_label': False, + 'visible': False + } + target_video_args =\ + { + 'show_label': False, + 'visible': False + } + if is_target_image: + target_image_args['value'] = TARGET_FILE.value['name'] + target_image_args['visible'] = True + if is_target_video: + if get_file_size(deepfuze.globals.target_path) > FILE_SIZE_LIMIT: + preview_vision_frame = normalize_frame_color(get_video_frame(deepfuze.globals.target_path)) + target_image_args['value'] = preview_vision_frame + target_image_args['visible'] = True + else: + target_video_args['value'] = TARGET_FILE.value['name'] + target_video_args['visible'] = True + TARGET_IMAGE = gradio.Image(**target_image_args) + TARGET_VIDEO = gradio.Video(**target_video_args) + register_ui_component('target_image', TARGET_IMAGE) + register_ui_component('target_video', TARGET_VIDEO) + + +def listen() -> None: + TARGET_FILE.change(update, inputs = TARGET_FILE, outputs = [ TARGET_IMAGE, TARGET_VIDEO ]) + + +def update(file : File) -> Tuple[gradio.Image, gradio.Video]: + clear_reference_faces() + clear_static_faces() + if file and is_image(file.name): + deepfuze.globals.target_path = file.name + return gradio.Image(value = file.name, visible = True), gradio.Video(value = None, visible = False) + if file and is_video(file.name): + deepfuze.globals.target_path = file.name + if get_file_size(file.name) > FILE_SIZE_LIMIT: + preview_vision_frame = normalize_frame_color(get_video_frame(file.name)) + return gradio.Image(value = preview_vision_frame, visible = True), gradio.Video(value = None, visible = False) + return gradio.Image(value = None, visible = False), gradio.Video(value = file.name, visible = True) + deepfuze.globals.target_path = None + return gradio.Image(value = None, visible = False), gradio.Video(value = None, visible = False) diff --git a/deepfuze/uis/components/temp_frame.py b/deepfuze/uis/components/temp_frame.py new file mode 100644 index 0000000..2610bbf --- /dev/null +++ b/deepfuze/uis/components/temp_frame.py @@ -0,0 +1,41 @@ +from typing import Optional +import gradio + +import deepfuze.globals +import deepfuze.choices +from deepfuze import wording +from deepfuze.typing import TempFrameFormat +from deepfuze.filesystem import is_video +from deepfuze.uis.core import get_ui_component + +TEMP_FRAME_FORMAT_DROPDOWN : Optional[gradio.Dropdown] = None + + +def render() -> None: + global TEMP_FRAME_FORMAT_DROPDOWN + + TEMP_FRAME_FORMAT_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.temp_frame_format_dropdown'), + choices = deepfuze.choices.temp_frame_formats, + value = deepfuze.globals.temp_frame_format, + visible = is_video(deepfuze.globals.target_path) + ) + + +def listen() -> None: + TEMP_FRAME_FORMAT_DROPDOWN.change(update_temp_frame_format, inputs = TEMP_FRAME_FORMAT_DROPDOWN) + target_video = get_ui_component('target_video') + if target_video: + for method in [ 'upload', 'change', 'clear' ]: + getattr(target_video, method)(remote_update, outputs = TEMP_FRAME_FORMAT_DROPDOWN) + + +def remote_update() -> gradio.Dropdown: + if is_video(deepfuze.globals.target_path): + return gradio.Dropdown(visible = True) + return gradio.Dropdown(visible = False) + + +def update_temp_frame_format(temp_frame_format : TempFrameFormat) -> None: + deepfuze.globals.temp_frame_format = temp_frame_format + diff --git a/deepfuze/uis/components/trim_frame.py b/deepfuze/uis/components/trim_frame.py new file mode 100644 index 0000000..3584eaa --- /dev/null +++ b/deepfuze/uis/components/trim_frame.py @@ -0,0 +1,79 @@ +from typing import Any, Dict, Tuple, Optional +import gradio + +import deepfuze.globals +from deepfuze import wording +from deepfuze.face_store import clear_static_faces +from deepfuze.vision import count_video_frame_total +from deepfuze.filesystem import is_video +from deepfuze.uis.core import get_ui_components, register_ui_component + +TRIM_FRAME_START_SLIDER : Optional[gradio.Slider] = None +TRIM_FRAME_END_SLIDER : Optional[gradio.Slider] = None + + +def render() -> None: + global TRIM_FRAME_START_SLIDER + global TRIM_FRAME_END_SLIDER + + trim_frame_start_slider_args : Dict[str, Any] =\ + { + 'label': wording.get('uis.trim_frame_start_slider'), + 'step': 1, + 'minimum': 0, + 'maximum': 100, + 'visible': False + } + trim_frame_end_slider_args : Dict[str, Any] =\ + { + 'label': wording.get('uis.trim_frame_end_slider'), + 'step': 1, + 'minimum': 0, + 'maximum': 100, + 'visible': False + } + if is_video(deepfuze.globals.target_path): + video_frame_total = count_video_frame_total(deepfuze.globals.target_path) + trim_frame_start_slider_args['value'] = deepfuze.globals.trim_frame_start or 0 + trim_frame_start_slider_args['maximum'] = video_frame_total + trim_frame_start_slider_args['visible'] = True + trim_frame_end_slider_args['value'] = deepfuze.globals.trim_frame_end or video_frame_total + trim_frame_end_slider_args['maximum'] = video_frame_total + trim_frame_end_slider_args['visible'] = True + with gradio.Row(): + TRIM_FRAME_START_SLIDER = gradio.Slider(**trim_frame_start_slider_args) + TRIM_FRAME_END_SLIDER = gradio.Slider(**trim_frame_end_slider_args) + register_ui_component('trim_frame_start_slider', TRIM_FRAME_START_SLIDER) + register_ui_component('trim_frame_end_slider', TRIM_FRAME_END_SLIDER) + + +def listen() -> None: + TRIM_FRAME_START_SLIDER.release(update_trim_frame_start, inputs = TRIM_FRAME_START_SLIDER) + TRIM_FRAME_END_SLIDER.release(update_trim_frame_end, inputs = TRIM_FRAME_END_SLIDER) + for ui_component in get_ui_components( + [ + 'target_image', + 'target_video' + ]): + for method in [ 'upload', 'change', 'clear' ]: + getattr(ui_component, method)(remote_update, outputs = [ TRIM_FRAME_START_SLIDER, TRIM_FRAME_END_SLIDER ]) + + +def remote_update() -> Tuple[gradio.Slider, gradio.Slider]: + if is_video(deepfuze.globals.target_path): + video_frame_total = count_video_frame_total(deepfuze.globals.target_path) + deepfuze.globals.trim_frame_start = None + deepfuze.globals.trim_frame_end = None + return gradio.Slider(value = 0, maximum = video_frame_total, visible = True), gradio.Slider(value = video_frame_total, maximum = video_frame_total, visible = True) + return gradio.Slider(value = None, maximum = None, visible = False), gradio.Slider(value = None, maximum = None, visible = False) + + +def update_trim_frame_start(trim_frame_start : int) -> None: + clear_static_faces() + deepfuze.globals.trim_frame_start = trim_frame_start if trim_frame_start > 0 else None + + +def update_trim_frame_end(trim_frame_end : int) -> None: + clear_static_faces() + video_frame_total = count_video_frame_total(deepfuze.globals.target_path) + deepfuze.globals.trim_frame_end = trim_frame_end if trim_frame_end < video_frame_total else None diff --git a/deepfuze/uis/components/webcam.py b/deepfuze/uis/components/webcam.py new file mode 100644 index 0000000..da993eb --- /dev/null +++ b/deepfuze/uis/components/webcam.py @@ -0,0 +1,180 @@ +from typing import Optional, Generator, Deque +import os +import subprocess +import cv2 +import gradio +from time import sleep +from concurrent.futures import ThreadPoolExecutor +from collections import deque +from tqdm import tqdm + +import deepfuze.globals +from deepfuze import logger, wording +from deepfuze.audio import create_empty_audio_frame +from deepfuze.common_helper import is_windows +from deepfuze.content_analyser import analyse_stream +from deepfuze.filesystem import filter_image_paths +from deepfuze.typing import VisionFrame, Face, Fps +from deepfuze.face_analyser import get_average_face +from deepfuze.processors.frame.core import get_frame_processors_modules, load_frame_processor_module +from deepfuze.ffmpeg import open_ffmpeg +from deepfuze.vision import normalize_frame_color, read_static_images, unpack_resolution +from deepfuze.uis.typing import StreamMode, WebcamMode +from deepfuze.uis.core import get_ui_component, get_ui_components + +WEBCAM_CAPTURE : Optional[cv2.VideoCapture] = None +WEBCAM_IMAGE : Optional[gradio.Image] = None +WEBCAM_START_BUTTON : Optional[gradio.Button] = None +WEBCAM_STOP_BUTTON : Optional[gradio.Button] = None + + +def get_webcam_capture() -> Optional[cv2.VideoCapture]: + global WEBCAM_CAPTURE + + if WEBCAM_CAPTURE is None: + if is_windows(): + webcam_capture = cv2.VideoCapture(0, cv2.CAP_DSHOW) + else: + webcam_capture = cv2.VideoCapture(0) + if webcam_capture and webcam_capture.isOpened(): + WEBCAM_CAPTURE = webcam_capture + return WEBCAM_CAPTURE + + +def clear_webcam_capture() -> None: + global WEBCAM_CAPTURE + + if WEBCAM_CAPTURE: + WEBCAM_CAPTURE.release() + WEBCAM_CAPTURE = None + + +def render() -> None: + global WEBCAM_IMAGE + global WEBCAM_START_BUTTON + global WEBCAM_STOP_BUTTON + + WEBCAM_IMAGE = gradio.Image( + label = wording.get('uis.webcam_image') + ) + WEBCAM_START_BUTTON = gradio.Button( + value = wording.get('uis.start_button'), + variant = 'primary', + size = 'sm' + ) + WEBCAM_STOP_BUTTON = gradio.Button( + value = wording.get('uis.stop_button'), + size = 'sm' + ) + + +def listen() -> None: + start_event = None + webcam_mode_radio = get_ui_component('webcam_mode_radio') + webcam_resolution_dropdown = get_ui_component('webcam_resolution_dropdown') + webcam_fps_slider = get_ui_component('webcam_fps_slider') + if webcam_mode_radio and webcam_resolution_dropdown and webcam_fps_slider: + start_event = WEBCAM_START_BUTTON.click(start, inputs = [ webcam_mode_radio, webcam_resolution_dropdown, webcam_fps_slider ], outputs = WEBCAM_IMAGE) + WEBCAM_STOP_BUTTON.click(stop, cancels = start_event) + + for ui_component in get_ui_components( + [ + 'frame_processors_checkbox_group', + 'face_swapper_model_dropdown', + 'face_enhancer_model_dropdown', + 'frame_enhancer_model_dropdown', + 'lip_syncer_model_dropdown', + 'source_image' + ]): + ui_component.change(update, cancels = start_event) + + +def start(webcam_mode : WebcamMode, webcam_resolution : str, webcam_fps : Fps) -> Generator[VisionFrame, None, None]: + deepfuze.globals.face_selector_mode = 'one' + deepfuze.globals.face_analyser_order = 'large-small' + source_image_paths = filter_image_paths(deepfuze.globals.source_paths) + source_frames = read_static_images(source_image_paths) + source_face = get_average_face(source_frames) + stream = None + + if webcam_mode in [ 'udp', 'v4l2' ]: + stream = open_stream(webcam_mode, webcam_resolution, webcam_fps) #type:ignore[arg-type] + webcam_width, webcam_height = unpack_resolution(webcam_resolution) + webcam_capture = get_webcam_capture() + if webcam_capture and webcam_capture.isOpened(): + webcam_capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG')) #type:ignore[attr-defined] + webcam_capture.set(cv2.CAP_PROP_FRAME_WIDTH, webcam_width) + webcam_capture.set(cv2.CAP_PROP_FRAME_HEIGHT, webcam_height) + webcam_capture.set(cv2.CAP_PROP_FPS, webcam_fps) + for capture_frame in multi_process_capture(source_face, webcam_capture, webcam_fps): + if webcam_mode == 'inline': + yield normalize_frame_color(capture_frame) + else: + try: + stream.stdin.write(capture_frame.tobytes()) + except Exception: + clear_webcam_capture() + yield None + + +def multi_process_capture(source_face : Face, webcam_capture : cv2.VideoCapture, webcam_fps : Fps) -> Generator[VisionFrame, None, None]: + with tqdm(desc = wording.get('processing'), unit = 'frame', ascii = ' =', disable = deepfuze.globals.log_level in [ 'warn', 'error' ]) as progress: + with ThreadPoolExecutor(max_workers = deepfuze.globals.execution_thread_count) as executor: + futures = [] + deque_capture_frames : Deque[VisionFrame] = deque() + while webcam_capture and webcam_capture.isOpened(): + _, capture_frame = webcam_capture.read() + if analyse_stream(capture_frame, webcam_fps): + return + future = executor.submit(process_stream_frame, source_face, capture_frame) + futures.append(future) + for future_done in [ future for future in futures if future.done() ]: + capture_frame = future_done.result() + deque_capture_frames.append(capture_frame) + futures.remove(future_done) + while deque_capture_frames: + progress.update() + yield deque_capture_frames.popleft() + + +def update() -> None: + for frame_processor in deepfuze.globals.frame_processors: + frame_processor_module = load_frame_processor_module(frame_processor) + while not frame_processor_module.post_check(): + logger.disable() + sleep(0.5) + logger.enable() + + +def stop() -> gradio.Image: + clear_webcam_capture() + return gradio.Image(value = None) + + +def process_stream_frame(source_face : Face, target_vision_frame : VisionFrame) -> VisionFrame: + source_audio_frame = create_empty_audio_frame() + for frame_processor_module in get_frame_processors_modules(deepfuze.globals.frame_processors): + logger.disable() + if frame_processor_module.pre_process('stream'): + logger.enable() + target_vision_frame = frame_processor_module.process_frame( + { + 'source_face': source_face, + 'source_audio_frame': source_audio_frame, + 'target_vision_frame': target_vision_frame + }) + return target_vision_frame + + +def open_stream(stream_mode : StreamMode, stream_resolution : str, stream_fps : Fps) -> subprocess.Popen[bytes]: + commands = [ '-f', 'rawvideo', '-pix_fmt', 'bgr24', '-s', stream_resolution, '-r', str(stream_fps), '-i', '-'] + if stream_mode == 'udp': + commands.extend([ '-b:v', '2000k', '-f', 'mpegts', 'udp://localhost:27000?pkt_size=1316' ]) + if stream_mode == 'v4l2': + try: + device_name = os.listdir('/sys/devices/virtual/video4linux')[0] + if device_name: + commands.extend([ '-f', 'v4l2', '/dev/' + device_name ]) + except FileNotFoundError: + logger.error(wording.get('stream_not_loaded').format(stream_mode = stream_mode), __name__.upper()) + return open_ffmpeg(commands) diff --git a/deepfuze/uis/components/webcam_options.py b/deepfuze/uis/components/webcam_options.py new file mode 100644 index 0000000..b662ad8 --- /dev/null +++ b/deepfuze/uis/components/webcam_options.py @@ -0,0 +1,37 @@ +from typing import Optional +import gradio + +from deepfuze import wording +from deepfuze.uis import choices as uis_choices +from deepfuze.uis.core import register_ui_component + +WEBCAM_MODE_RADIO : Optional[gradio.Radio] = None +WEBCAM_RESOLUTION_DROPDOWN : Optional[gradio.Dropdown] = None +WEBCAM_FPS_SLIDER : Optional[gradio.Slider] = None + + +def render() -> None: + global WEBCAM_MODE_RADIO + global WEBCAM_RESOLUTION_DROPDOWN + global WEBCAM_FPS_SLIDER + + WEBCAM_MODE_RADIO = gradio.Radio( + label = wording.get('uis.webcam_mode_radio'), + choices = uis_choices.webcam_modes, + value = 'inline' + ) + WEBCAM_RESOLUTION_DROPDOWN = gradio.Dropdown( + label = wording.get('uis.webcam_resolution_dropdown'), + choices = uis_choices.webcam_resolutions, + value = uis_choices.webcam_resolutions[0] + ) + WEBCAM_FPS_SLIDER = gradio.Slider( + label = wording.get('uis.webcam_fps_slider'), + value = 25, + step = 1, + minimum = 1, + maximum = 60 + ) + register_ui_component('webcam_mode_radio', WEBCAM_MODE_RADIO) + register_ui_component('webcam_resolution_dropdown', WEBCAM_RESOLUTION_DROPDOWN) + register_ui_component('webcam_fps_slider', WEBCAM_FPS_SLIDER) diff --git a/deepfuze/uis/core.py b/deepfuze/uis/core.py new file mode 100644 index 0000000..881c6c3 --- /dev/null +++ b/deepfuze/uis/core.py @@ -0,0 +1,156 @@ +from typing import Dict, Optional, Any, List +from types import ModuleType +import os +import importlib +import sys +import gradio + +import deepfuze.globals +from deepfuze.uis import overrides +from deepfuze import metadata, logger, wording +from deepfuze.uis.typing import Component, ComponentName +from deepfuze.filesystem import resolve_relative_path + +os.environ['GRADIO_ANALYTICS_ENABLED'] = '0' + +gradio.processing_utils.encode_array_to_base64 = overrides.encode_array_to_base64 +gradio.processing_utils.encode_pil_to_base64 = overrides.encode_pil_to_base64 + +UI_COMPONENTS: Dict[ComponentName, Component] = {} +UI_LAYOUT_MODULES : List[ModuleType] = [] +UI_LAYOUT_METHODS =\ +[ + 'pre_check', + 'pre_render', + 'render', + 'listen', + 'run' +] + + +def load_ui_layout_module(ui_layout : str) -> Any: + try: + ui_layout_module = importlib.import_module('deepfuze.uis.layouts.' + ui_layout) + for method_name in UI_LAYOUT_METHODS: + if not hasattr(ui_layout_module, method_name): + raise NotImplementedError + except ModuleNotFoundError as exception: + logger.error(wording.get('ui_layout_not_loaded').format(ui_layout = ui_layout), __name__.upper()) + logger.debug(exception.msg, __name__.upper()) + sys.exit(1) + except NotImplementedError: + logger.error(wording.get('ui_layout_not_implemented').format(ui_layout = ui_layout), __name__.upper()) + sys.exit(1) + return ui_layout_module + + +def get_ui_layouts_modules(ui_layouts : List[str]) -> List[ModuleType]: + global UI_LAYOUT_MODULES + + if not UI_LAYOUT_MODULES: + for ui_layout in ui_layouts: + ui_layout_module = load_ui_layout_module(ui_layout) + UI_LAYOUT_MODULES.append(ui_layout_module) + return UI_LAYOUT_MODULES + + +def get_ui_component(component_name : ComponentName) -> Optional[Component]: + if component_name in UI_COMPONENTS: + return UI_COMPONENTS[component_name] + return None + + +def get_ui_components(component_names : List[ComponentName]) -> Optional[List[Component]]: + ui_components = [] + + for component_name in component_names: + component = get_ui_component(component_name) + if component: + ui_components.append(component) + return ui_components + + +def register_ui_component(component_name : ComponentName, component: Component) -> None: + UI_COMPONENTS[component_name] = component + + +def launch() -> None: + ui_layouts_total = len(deepfuze.globals.ui_layouts) + with gradio.Blocks(theme = get_theme(), css = get_css(), title = metadata.get('name') + ' ' + metadata.get('version')) as ui: + for ui_layout in deepfuze.globals.ui_layouts: + ui_layout_module = load_ui_layout_module(ui_layout) + if ui_layout_module.pre_render(): + if ui_layouts_total > 1: + with gradio.Tab(ui_layout): + ui_layout_module.render() + ui_layout_module.listen() + else: + ui_layout_module.render() + ui_layout_module.listen() + + for ui_layout in deepfuze.globals.ui_layouts: + ui_layout_module = load_ui_layout_module(ui_layout) + ui_layout_module.run(ui) + + +def get_theme() -> gradio.Theme: + return gradio.themes.Base( + primary_hue = gradio.themes.colors.red, + secondary_hue = gradio.themes.colors.neutral, + font = gradio.themes.GoogleFont('Open Sans') + ).set( + background_fill_primary = '*neutral_100', + block_background_fill = 'white', + block_border_width = '0', + block_label_background_fill = '*primary_100', + block_label_background_fill_dark = '*primary_600', + block_label_border_width = 'none', + block_label_margin = '0.5rem', + block_label_radius = '*radius_md', + block_label_text_color = '*primary_500', + block_label_text_color_dark = 'white', + block_label_text_weight = '600', + block_title_background_fill = '*primary_100', + block_title_background_fill_dark = '*primary_600', + block_title_padding = '*block_label_padding', + block_title_radius = '*block_label_radius', + block_title_text_color = '*primary_500', + block_title_text_size = '*text_sm', + block_title_text_weight = '600', + block_padding = '0.5rem', + border_color_primary = 'transparent', + border_color_primary_dark = 'transparent', + button_large_padding = '2rem 0.5rem', + button_large_text_weight = 'normal', + button_primary_background_fill = '*primary_500', + button_primary_text_color = 'white', + button_secondary_background_fill = 'white', + button_secondary_border_color = 'transparent', + button_secondary_border_color_dark = 'transparent', + button_secondary_border_color_hover = 'transparent', + button_secondary_border_color_hover_dark = 'transparent', + button_secondary_text_color = '*neutral_800', + button_small_padding = '0.75rem', + checkbox_background_color = '*neutral_200', + checkbox_background_color_selected = '*primary_600', + checkbox_background_color_selected_dark = '*primary_700', + checkbox_border_color_focus = '*primary_500', + checkbox_border_color_focus_dark = '*primary_600', + checkbox_border_color_selected = '*primary_600', + checkbox_border_color_selected_dark = '*primary_700', + checkbox_label_background_fill = '*neutral_50', + checkbox_label_background_fill_hover = '*neutral_50', + checkbox_label_background_fill_selected = '*primary_500', + checkbox_label_background_fill_selected_dark = '*primary_600', + checkbox_label_text_color_selected = 'white', + input_background_fill = '*neutral_50', + shadow_drop = 'none', + slider_color = '*primary_500', + slider_color_dark = '*primary_600' + ) + + +def get_css() -> str: + fixes_css_path = resolve_relative_path('uis/assets/fixes.css') + overrides_css_path = resolve_relative_path('uis/assets/overrides.css') + return open(fixes_css_path, 'r').read() + open(overrides_css_path, 'r').read() diff --git a/deepfuze/uis/layouts/__pycache__/default.cpython-310.pyc b/deepfuze/uis/layouts/__pycache__/default.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f621e56a368ea49cc4f5d3005a7f9c1ee6a4fcb GIT binary patch literal 3027 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 literal 0 HcmV?d00001 diff --git a/deepfuze/uis/layouts/benchmark.py b/deepfuze/uis/layouts/benchmark.py new file mode 100644 index 0000000..92f6885 --- /dev/null +++ b/deepfuze/uis/layouts/benchmark.py @@ -0,0 +1,67 @@ +import multiprocessing +import gradio + +import deepfuze.globals +from deepfuze.download import conditional_download +from deepfuze.uis.components import about, frame_processors, frame_processors_options, execution, execution_thread_count, execution_queue_count, memory, benchmark_options, benchmark + + +def pre_check() -> bool: + if not deepfuze.globals.skip_download: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.mp3', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-360p.mp4', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-540p.mp4', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-720p.mp4', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-1080p.mp4', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-1440p.mp4', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-2160p.mp4' + ]) + return True + return False + + +def pre_render() -> bool: + return True + + +def render() -> gradio.Blocks: + with gradio.Blocks() as layout: + with gradio.Row(): + with gradio.Column(scale = 2): + with gradio.Blocks(): + about.render() + with gradio.Blocks(): + frame_processors.render() + with gradio.Blocks(): + frame_processors_options.render() + with gradio.Blocks(): + execution.render() + execution_thread_count.render() + execution_queue_count.render() + with gradio.Blocks(): + memory.render() + with gradio.Blocks(): + benchmark_options.render() + with gradio.Column(scale = 5): + with gradio.Blocks(): + benchmark.render() + return layout + + +def listen() -> None: + frame_processors.listen() + frame_processors_options.listen() + execution.listen() + execution_thread_count.listen() + execution_queue_count.listen() + memory.listen() + benchmark.listen() + + +def run(ui : gradio.Blocks) -> None: + concurrency_count = min(2, multiprocessing.cpu_count()) + ui.queue(concurrency_count = concurrency_count).launch(show_api = False, quiet = True, inbrowser = deepfuze.globals.open_browser) diff --git a/deepfuze/uis/layouts/default.py b/deepfuze/uis/layouts/default.py new file mode 100755 index 0000000..c80dc57 --- /dev/null +++ b/deepfuze/uis/layouts/default.py @@ -0,0 +1,81 @@ +import multiprocessing +import gradio + +import deepfuze.globals +from deepfuze.uis.components import about, frame_processors, frame_processors_options, execution, execution_thread_count, execution_queue_count, memory, temp_frame, output_options, common_options, source, target, output, preview, trim_frame, face_analyser, face_selector, face_masker + + +def pre_check() -> bool: + return True + + +def pre_render() -> bool: + return True + + +def render() -> gradio.Blocks: + with gradio.Blocks() as layout: + with gradio.Row(): + with gradio.Column(scale = 2): + with gradio.Blocks(): + about.render() + with gradio.Blocks(): + frame_processors.render() + with gradio.Blocks(): + frame_processors_options.render() + with gradio.Blocks(): + execution.render() + execution_thread_count.render() + execution_queue_count.render() + with gradio.Blocks(): + memory.render() + with gradio.Blocks(): + temp_frame.render() + with gradio.Blocks(): + output_options.render() + with gradio.Column(scale = 2): + with gradio.Blocks(): + source.render() + with gradio.Blocks(): + target.render() + with gradio.Blocks(): + output.render() + with gradio.Column(scale = 3): + with gradio.Blocks(): + preview.render() + with gradio.Blocks(): + trim_frame.render() + with gradio.Blocks(): + face_selector.render() + with gradio.Blocks(): + face_masker.render() + with gradio.Blocks(): + face_analyser.render() + with gradio.Blocks(): + common_options.render() + return layout + + +def listen() -> None: + frame_processors.listen() + frame_processors_options.listen() + execution.listen() + execution_thread_count.listen() + execution_queue_count.listen() + memory.listen() + temp_frame.listen() + output_options.listen() + source.listen() + target.listen() + output.listen() + preview.listen() + trim_frame.listen() + face_selector.listen() + face_masker.listen() + face_analyser.listen() + common_options.listen() + + +def run(ui : gradio.Blocks) -> None: + concurrency_count = min(8, multiprocessing.cpu_count()) + ui.queue(concurrency_count = concurrency_count).launch(show_api = False, quiet = True, inbrowser = deepfuze.globals.open_browser) diff --git a/deepfuze/uis/layouts/webcam.py b/deepfuze/uis/layouts/webcam.py new file mode 100644 index 0000000..0809699 --- /dev/null +++ b/deepfuze/uis/layouts/webcam.py @@ -0,0 +1,50 @@ +import multiprocessing +import gradio + +import deepfuze.globals +from deepfuze.uis.components import about, frame_processors, frame_processors_options, execution, execution_thread_count, webcam_options, source, webcam + + +def pre_check() -> bool: + return True + + +def pre_render() -> bool: + return True + + +def render() -> gradio.Blocks: + with gradio.Blocks() as layout: + with gradio.Row(): + with gradio.Column(scale = 2): + with gradio.Blocks(): + about.render() + with gradio.Blocks(): + frame_processors.render() + with gradio.Blocks(): + frame_processors_options.render() + with gradio.Blocks(): + execution.render() + execution_thread_count.render() + with gradio.Blocks(): + webcam_options.render() + with gradio.Blocks(): + source.render() + with gradio.Column(scale = 5): + with gradio.Blocks(): + webcam.render() + return layout + + +def listen() -> None: + frame_processors.listen() + frame_processors_options.listen() + execution.listen() + execution_thread_count.listen() + source.listen() + webcam.listen() + + +def run(ui : gradio.Blocks) -> None: + concurrency_count = min(2, multiprocessing.cpu_count()) + ui.queue(concurrency_count = concurrency_count).launch(show_api = False, quiet = True, inbrowser = deepfuze.globals.open_browser) diff --git a/deepfuze/uis/overrides.py b/deepfuze/uis/overrides.py new file mode 100644 index 0000000..7f3c470 --- /dev/null +++ b/deepfuze/uis/overrides.py @@ -0,0 +1,13 @@ +from typing import Any +import cv2 +import numpy +import base64 + + +def encode_array_to_base64(array : numpy.ndarray[Any, Any]) -> str: + buffer = cv2.imencode('.jpg', array[:, :, ::-1])[1] + return 'data:image/jpeg;base64,' + base64.b64encode(buffer.tobytes()).decode('utf-8') + + +def encode_pil_to_base64(image : Any) -> str: + return encode_array_to_base64(numpy.asarray(image)[:, :, ::-1]) diff --git a/deepfuze/uis/typing.py b/deepfuze/uis/typing.py new file mode 100644 index 0000000..59d06f5 --- /dev/null +++ b/deepfuze/uis/typing.py @@ -0,0 +1,53 @@ +from typing import Literal, Any, IO +import gradio + +File = IO[Any] +Component = gradio.File or gradio.Image or gradio.Video or gradio.Slider +ComponentName = Literal\ +[ + 'source_audio', + 'source_image', + 'target_image', + 'target_video', + 'preview_frame_slider', + 'trim_frame_start_slider', + 'trim_frame_end_slider', + 'face_selector_mode_dropdown', + 'reference_face_position_gallery', + 'reference_face_distance_slider', + 'face_analyser_order_dropdown', + 'face_analyser_age_dropdown', + 'face_analyser_gender_dropdown', + 'face_detector_model_dropdown', + 'face_detector_size_dropdown', + 'face_detector_score_slider', + 'face_landmarker_score_slider', + 'face_mask_types_checkbox_group', + 'face_mask_blur_slider', + 'face_mask_padding_top_slider', + 'face_mask_padding_bottom_slider', + 'face_mask_padding_left_slider', + 'face_mask_padding_right_slider', + 'face_mask_region_checkbox_group', + 'frame_processors_checkbox_group', + 'face_debugger_items_checkbox_group', + 'face_enhancer_model_dropdown', + 'face_enhancer_blend_slider', + 'face_swapper_model_dropdown', + 'frame_colorizer_model_dropdown', + 'frame_colorizer_blend_slider', + 'frame_colorizer_size_dropdown', + 'frame_enhancer_model_dropdown', + 'frame_enhancer_blend_slider', + 'lip_syncer_model_dropdown', + 'output_path_textbox', + 'output_video_fps_slider', + 'benchmark_runs_checkbox_group', + 'benchmark_cycles_slider', + 'webcam_mode_radio', + 'webcam_resolution_dropdown', + 'webcam_fps_slider' +] + +WebcamMode = Literal['inline', 'udp', 'v4l2'] +StreamMode = Literal['udp', 'v4l2'] diff --git a/deepfuze/vision.py b/deepfuze/vision.py new file mode 100644 index 0000000..117a2ef --- /dev/null +++ b/deepfuze/vision.py @@ -0,0 +1,231 @@ +from typing import Optional, List, Tuple +from functools import lru_cache +import cv2 +import numpy +from cv2.typing import Size + +from deepfuze.common_helper import is_windows +from deepfuze.typing import VisionFrame, Resolution, Fps +from deepfuze.choices import image_template_sizes, video_template_sizes +from deepfuze.filesystem import is_image, is_video, sanitize_path_for_windows + + +@lru_cache(maxsize = 128) +def read_static_image(image_path : str) -> Optional[VisionFrame]: + return read_image(image_path) + + +def read_static_images(image_paths : List[str]) -> Optional[List[VisionFrame]]: + frames = [] + if image_paths: + for image_path in image_paths: + frames.append(read_static_image(image_path)) + return frames + + +def read_image(image_path : str) -> Optional[VisionFrame]: + if is_image(image_path): + if is_windows(): + image_path = sanitize_path_for_windows(image_path) + return cv2.imread(image_path) + return None + + +def write_image(image_path : str, vision_frame : VisionFrame) -> bool: + if image_path: + if is_windows(): + image_path = sanitize_path_for_windows(image_path) + return cv2.imwrite(image_path, vision_frame) + return False + + +def detect_image_resolution(image_path : str) -> Optional[Resolution]: + if is_image(image_path): + image = read_image(image_path) + height, width = image.shape[:2] + return width, height + return None + + +def restrict_image_resolution(image_path : str, resolution : Resolution) -> Resolution: + if is_image(image_path): + image_resolution = detect_image_resolution(image_path) + if image_resolution < resolution: + return image_resolution + return resolution + + +def create_image_resolutions(resolution : Resolution) -> List[str]: + resolutions = [] + temp_resolutions = [] + + if resolution: + width, height = resolution + temp_resolutions.append(normalize_resolution(resolution)) + for template_size in image_template_sizes: + temp_resolutions.append(normalize_resolution((width * template_size, height * template_size))) + temp_resolutions = sorted(set(temp_resolutions)) + for temp_resolution in temp_resolutions: + resolutions.append(pack_resolution(temp_resolution)) + return resolutions + + +def get_video_frame(video_path : str, frame_number : int = 0) -> Optional[VisionFrame]: + if is_video(video_path): + if is_windows(): + video_path = sanitize_path_for_windows(video_path) + video_capture = cv2.VideoCapture(video_path) + if video_capture.isOpened(): + frame_total = video_capture.get(cv2.CAP_PROP_FRAME_COUNT) + video_capture.set(cv2.CAP_PROP_POS_FRAMES, min(frame_total, frame_number - 1)) + has_vision_frame, vision_frame = video_capture.read() + video_capture.release() + if has_vision_frame: + return vision_frame + return None + + +def count_video_frame_total(video_path : str) -> int: + if is_video(video_path): + if is_windows(): + video_path = sanitize_path_for_windows(video_path) + video_capture = cv2.VideoCapture(video_path) + if video_capture.isOpened(): + video_frame_total = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT)) + video_capture.release() + return video_frame_total + return 0 + + +def detect_video_fps(video_path : str) -> Optional[float]: + if is_video(video_path): + if is_windows(): + video_path = sanitize_path_for_windows(video_path) + video_capture = cv2.VideoCapture(video_path) + if video_capture.isOpened(): + video_fps = video_capture.get(cv2.CAP_PROP_FPS) + video_capture.release() + return video_fps + return None + + +def restrict_video_fps(video_path : str, fps : Fps) -> Fps: + if is_video(video_path): + video_fps = detect_video_fps(video_path) + if video_fps < fps: + return video_fps + return fps + + +def detect_video_resolution(video_path : str) -> Optional[Resolution]: + if is_video(video_path): + if is_windows(): + video_path = sanitize_path_for_windows(video_path) + video_capture = cv2.VideoCapture(video_path) + if video_capture.isOpened(): + width = video_capture.get(cv2.CAP_PROP_FRAME_WIDTH) + height = video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT) + video_capture.release() + return int(width), int(height) + return None + + +def restrict_video_resolution(video_path : str, resolution : Resolution) -> Resolution: + if is_video(video_path): + video_resolution = detect_video_resolution(video_path) + if video_resolution < resolution: + return video_resolution + return resolution + + +def create_video_resolutions(resolution : Resolution) -> List[str]: + resolutions = [] + temp_resolutions = [] + + if resolution: + width, height = resolution + temp_resolutions.append(normalize_resolution(resolution)) + for template_size in video_template_sizes: + if width > height: + temp_resolutions.append(normalize_resolution((template_size * width / height, template_size))) + else: + temp_resolutions.append(normalize_resolution((template_size, template_size * height / width))) + temp_resolutions = sorted(set(temp_resolutions)) + for temp_resolution in temp_resolutions: + resolutions.append(pack_resolution(temp_resolution)) + return resolutions + + +def normalize_resolution(resolution : Tuple[float, float]) -> Resolution: + width, height = resolution + + if width and height: + normalize_width = round(width / 2) * 2 + normalize_height = round(height / 2) * 2 + return normalize_width, normalize_height + return 0, 0 + + +def pack_resolution(resolution : Resolution) -> str: + width, height = normalize_resolution(resolution) + return str(width) + 'x' + str(height) + + +def unpack_resolution(resolution : str) -> Resolution: + width, height = map(int, resolution.split('x')) + return width, height + + +def resize_frame_resolution(vision_frame : VisionFrame, max_resolution : Resolution) -> VisionFrame: + height, width = vision_frame.shape[:2] + max_width, max_height = max_resolution + + if height > max_height or width > max_width: + scale = min(max_height / height, max_width / width) + new_width = int(width * scale) + new_height = int(height * scale) + return cv2.resize(vision_frame, (new_width, new_height)) + return vision_frame + + +def normalize_frame_color(vision_frame : VisionFrame) -> VisionFrame: + return cv2.cvtColor(vision_frame, cv2.COLOR_BGR2RGB) + + +def create_tile_frames(vision_frame : VisionFrame, size : Size) -> Tuple[List[VisionFrame], int, int]: + vision_frame = numpy.pad(vision_frame, ((size[1], size[1]), (size[1], size[1]), (0, 0))) + tile_width = size[0] - 2 * size[2] + pad_size_bottom = size[2] + tile_width - vision_frame.shape[0] % tile_width + pad_size_right = size[2] + tile_width - vision_frame.shape[1] % tile_width + pad_vision_frame = numpy.pad(vision_frame, ((size[2], pad_size_bottom), (size[2], pad_size_right), (0, 0))) + pad_height, pad_width = pad_vision_frame.shape[:2] + row_range = range(size[2], pad_height - size[2], tile_width) + col_range = range(size[2], pad_width - size[2], tile_width) + tile_vision_frames = [] + + for row_vision_frame in row_range: + top = row_vision_frame - size[2] + bottom = row_vision_frame + size[2] + tile_width + for column_vision_frame in col_range: + left = column_vision_frame - size[2] + right = column_vision_frame + size[2] + tile_width + tile_vision_frames.append(pad_vision_frame[top:bottom, left:right, :]) + return tile_vision_frames, pad_width, pad_height + + +def merge_tile_frames(tile_vision_frames : List[VisionFrame], temp_width : int, temp_height : int, pad_width : int, pad_height : int, size : Size) -> VisionFrame: + merge_vision_frame = numpy.zeros((pad_height, pad_width, 3)).astype(numpy.uint8) + tile_width = tile_vision_frames[0].shape[1] - 2 * size[2] + tiles_per_row = min(pad_width // tile_width, len(tile_vision_frames)) + + for index, tile_vision_frame in enumerate(tile_vision_frames): + tile_vision_frame = tile_vision_frame[size[2]:-size[2], size[2]:-size[2]] + row_index = index // tiles_per_row + col_index = index % tiles_per_row + top = row_index * tile_vision_frame.shape[0] + bottom = top + tile_vision_frame.shape[0] + left = col_index * tile_vision_frame.shape[1] + right = left + tile_vision_frame.shape[1] + merge_vision_frame[top:bottom, left:right, :] = tile_vision_frame + merge_vision_frame = merge_vision_frame[size[1] : size[1] + temp_height, size[1]: size[1] + temp_width, :] + return merge_vision_frame diff --git a/deepfuze/voice_extractor.py b/deepfuze/voice_extractor.py new file mode 100644 index 0000000..222d193 --- /dev/null +++ b/deepfuze/voice_extractor.py @@ -0,0 +1,129 @@ +from typing import Any, Tuple +from time import sleep +import scipy +import numpy +import onnxruntime + +import deepfuze.globals +from deepfuze import process_manager +from deepfuze.thread_helper import thread_lock, thread_semaphore +from deepfuze.typing import ModelSet, AudioChunk, Audio +from deepfuze.execution import apply_execution_provider_options +from deepfuze.filesystem import resolve_relative_path, is_file +from deepfuze.download import conditional_download + +VOICE_EXTRACTOR = None +MODELS : ModelSet =\ +{ + 'voice_extractor': + { + 'url': 'https://github.com/facefusion/facefusion-assets/releases/download/models/voice_extractor.onnx', + 'path': resolve_relative_path('../../../models/deepfuze/voice_extractor.onnx') + } +} + + +def get_voice_extractor() -> Any: + global VOICE_EXTRACTOR + + with thread_lock(): + while process_manager.is_checking(): + sleep(0.5) + if VOICE_EXTRACTOR is None: + model_path = MODELS.get('voice_extractor').get('path') + VOICE_EXTRACTOR = onnxruntime.InferenceSession(model_path, providers = apply_execution_provider_options(deepfuze.globals.execution_device_id, deepfuze.globals.execution_providers)) + return VOICE_EXTRACTOR + + +def clear_voice_extractor() -> None: + global VOICE_EXTRACTOR + + VOICE_EXTRACTOR = None + + +def pre_check() -> bool: + download_directory_path = resolve_relative_path('../../../models/deepfuze') + model_url = MODELS.get('voice_extractor').get('url') + model_path = MODELS.get('voice_extractor').get('path') + + if not deepfuze.globals.skip_download: + process_manager.check() + conditional_download(download_directory_path, [ model_url ]) + process_manager.end() + return is_file(model_path) + + +def batch_extract_voice(audio : Audio, chunk_size : int, step_size : int) -> Audio: + temp_audio = numpy.zeros((audio.shape[0], 2)).astype(numpy.float32) + temp_chunk = numpy.zeros((audio.shape[0], 2)).astype(numpy.float32) + + for start in range(0, audio.shape[0], step_size): + end = min(start + chunk_size, audio.shape[0]) + temp_audio[start:end, ...] += extract_voice(audio[start:end, ...]) + temp_chunk[start:end, ...] += 1 + audio = temp_audio / temp_chunk + return audio + + +def extract_voice(temp_audio_chunk : AudioChunk) -> AudioChunk: + voice_extractor = get_voice_extractor() + chunk_size = 1024 * (voice_extractor.get_inputs()[0].shape[3] - 1) + trim_size = 3840 + temp_audio_chunk, pad_size = prepare_audio_chunk(temp_audio_chunk.T, chunk_size, trim_size) + temp_audio_chunk = decompose_audio_chunk(temp_audio_chunk, trim_size) + with thread_semaphore(): + temp_audio_chunk = voice_extractor.run(None, + { + voice_extractor.get_inputs()[0].name: temp_audio_chunk + })[0] + temp_audio_chunk = compose_audio_chunk(temp_audio_chunk, trim_size) + temp_audio_chunk = normalize_audio_chunk(temp_audio_chunk, chunk_size, trim_size, pad_size) + return temp_audio_chunk + + +def prepare_audio_chunk(temp_audio_chunk : AudioChunk, chunk_size : int, trim_size : int) -> Tuple[AudioChunk, int]: + step_size = chunk_size - 2 * trim_size + pad_size = step_size - temp_audio_chunk.shape[1] % step_size + audio_chunk_size = temp_audio_chunk.shape[1] + pad_size + temp_audio_chunk = temp_audio_chunk.astype(numpy.float32) / numpy.iinfo(numpy.int16).max + temp_audio_chunk = numpy.pad(temp_audio_chunk, ((0, 0), (trim_size, trim_size + pad_size))) + temp_audio_chunks = [] + + for index in range(0, audio_chunk_size, step_size): + temp_audio_chunks.append(temp_audio_chunk[:, index:index + chunk_size]) + temp_audio_chunk = numpy.concatenate(temp_audio_chunks, axis = 0) + temp_audio_chunk = temp_audio_chunk.reshape((-1, chunk_size)) + return temp_audio_chunk, pad_size + + +def decompose_audio_chunk(temp_audio_chunk : AudioChunk, trim_size : int) -> AudioChunk: + frame_size = 7680 + frame_overlap = 6656 + voice_extractor_shape = get_voice_extractor().get_inputs()[0].shape + window = scipy.signal.windows.hann(frame_size) + temp_audio_chunk = scipy.signal.stft(temp_audio_chunk, nperseg = frame_size, noverlap = frame_overlap, window = window)[2] + temp_audio_chunk = numpy.stack((numpy.real(temp_audio_chunk), numpy.imag(temp_audio_chunk)), axis = -1).transpose((0, 3, 1, 2)) + temp_audio_chunk = temp_audio_chunk.reshape(-1, 2, 2, trim_size + 1, voice_extractor_shape[3]).reshape(-1, voice_extractor_shape[1], trim_size + 1, voice_extractor_shape[3]) + temp_audio_chunk = temp_audio_chunk[:, :, :voice_extractor_shape[2]] + temp_audio_chunk /= numpy.sqrt(1.0 / window.sum() ** 2) + return temp_audio_chunk + + +def compose_audio_chunk(temp_audio_chunk : AudioChunk, trim_size : int) -> AudioChunk: + frame_size = 7680 + frame_overlap = 6656 + voice_extractor_shape = get_voice_extractor().get_inputs()[0].shape + window = scipy.signal.windows.hann(frame_size) + temp_audio_chunk = numpy.pad(temp_audio_chunk, ((0, 0), (0, 0), (0, trim_size + 1 - voice_extractor_shape[2]), (0, 0))) + temp_audio_chunk = temp_audio_chunk.reshape(-1, 2, trim_size + 1, voice_extractor_shape[3]).transpose((0, 2, 3, 1)) + temp_audio_chunk = temp_audio_chunk[:, :, :, 0] + 1j * temp_audio_chunk[:, :, :, 1] + temp_audio_chunk = scipy.signal.istft(temp_audio_chunk, nperseg = frame_size, noverlap = frame_overlap, window = window)[1] + temp_audio_chunk *= numpy.sqrt(1.0 / window.sum() ** 2) + return temp_audio_chunk + + +def normalize_audio_chunk(temp_audio_chunk : AudioChunk, chunk_size : int, trim_size : int, pad_size : int) -> AudioChunk: + temp_audio_chunk = temp_audio_chunk.reshape((-1, 2, chunk_size)) + temp_audio_chunk = temp_audio_chunk[:, :, trim_size:-trim_size].transpose(1, 0, 2) + temp_audio_chunk = temp_audio_chunk.reshape(2, -1)[:, :-pad_size].T + return temp_audio_chunk diff --git a/deepfuze/wording.py b/deepfuze/wording.py new file mode 100755 index 0000000..1401e44 --- /dev/null +++ b/deepfuze/wording.py @@ -0,0 +1,220 @@ +from typing import Any, Dict, Optional + +WORDING : Dict[str, Any] =\ +{ + 'conda_not_activated': 'Conda is not activated', + 'python_not_supported': 'Python version is not supported, upgrade to {version} or higher', + 'ffmpeg_not_installed': 'FFMpeg is not installed', + 'creating_temp': 'Creating temporary resources', + 'extracting_frames': 'Extracting frames with a resolution of {resolution} and {fps} frames per second', + 'extracting_frames_succeed': 'Extracting frames succeed', + 'extracting_frames_failed': 'Extracting frames failed', + 'analysing': 'Analysing', + 'processing': 'Processing', + 'downloading': 'Downloading', + 'temp_frames_not_found': 'Temporary frames not found', + 'copying_image': 'Copying image with a resolution of {resolution}', + 'copying_image_succeed': 'Copying image succeed', + 'copying_image_failed': 'Copying image failed', + 'finalizing_image': 'Finalizing image with a resolution of {resolution}', + 'finalizing_image_succeed': 'Finalizing image succeed', + 'finalizing_image_skipped': 'Finalizing image skipped', + 'merging_video': 'Merging video with a resolution of {resolution} and {fps} frames per second', + 'merging_video_succeed': 'Merging video succeed', + 'merging_video_failed': 'Merging video failed', + 'skipping_audio': 'Skipping audio', + 'restoring_audio_succeed': 'Restoring audio succeed', + 'restoring_audio_skipped': 'Restoring audio skipped', + 'clearing_temp': 'Clearing temporary resources', + 'processing_stopped': 'Processing stopped', + 'processing_image_succeed': 'Processing to image succeed in {seconds} seconds', + 'processing_image_failed': 'Processing to image failed', + 'processing_video_succeed': 'Processing to video succeed in {seconds} seconds', + 'processing_video_failed': 'Processing to video failed', + 'model_download_not_done': 'Download of the model is not done', + 'model_file_not_present': 'File of the model is not present', + 'select_image_source': 'Select a image for source path', + 'select_audio_source': 'Select a audio for source path', + 'select_video_target': 'Select a video for target path', + 'select_image_or_video_target': 'Select a image or video for target path', + 'select_file_or_directory_output': 'Select a file or directory for output path', + 'no_source_face_detected': 'No source face detected', + 'frame_processor_not_loaded': 'Frame processor {frame_processor} could not be loaded', + 'frame_processor_not_implemented': 'Frame processor {frame_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', + 'point': '.', + 'comma': ',', + 'colon': ':', + 'question_mark': '?', + 'exclamation_mark': '!', + 'help': + { + # installer + 'install_dependency': 'select the variant of {dependency} to install', + 'skip_conda': 'skip the conda environment check', + # general + 'config': 'choose the config file to override defaults', + 'source': 'choose single or multiple source images or audios', + 'target': 'choose single target image or video', + 'output': 'specify the output file or directory', + # misc + 'force_download': 'force automate downloads and exit', + 'skip_download': 'omit automate downloads and remote lookups', + 'headless': 'run the program without a user interface', + 'log_level': 'adjust the message severity displayed in the terminal', + # execution + 'execution_device_id': 'specify the device used for processing', + 'execution_providers': 'accelerate the model inference using different providers (choices: {choices}, ...)', + 'execution_thread_count': 'specify the amount of parallel threads while processing', + 'execution_queue_count': 'specify the amount of frames each thread is processing', + # memory + 'video_memory_strategy': 'balance fast frame processing and low VRAM usage', + 'system_memory_limit': 'limit the available RAM that can be used while processing', + # face analyser + 'face_analyser_order': 'specify the order in which the face analyser detects faces', + 'face_analyser_age': 'filter the detected faces based on their age', + 'face_analyser_gender': 'filter the detected faces based on their gender', + 'face_detector_model': 'choose the model responsible for detecting the face', + 'face_detector_size': 'specify the size of the frame provided to the face detector', + 'face_detector_score': 'filter the detected faces base on the confidence score', + 'face_landmarker_score': 'filter the detected landmarks base on the confidence score', + # face selector + 'face_selector_mode': 'use reference based tracking or simple matching', + 'reference_face_position': 'specify the position used to create the reference face', + 'reference_face_distance': 'specify the desired similarity between the reference face and target face', + 'reference_frame_number': 'specify the frame used to create the reference face', + # face mask + 'face_mask_types': 'mix and match different face mask types (choices: {choices})', + 'face_mask_blur': 'specify the degree of blur applied the box mask', + 'face_mask_padding': 'apply top, right, bottom and left padding to the box mask', + 'face_mask_regions': 'choose the facial features used for the region mask (choices: {choices})', + # frame extraction + 'trim_frame_start': 'specify the the start frame of the target video', + 'trim_frame_end': 'specify the the end frame of the target video', + 'temp_frame_format': 'specify the temporary resources format', + 'keep_temp': 'keep the temporary resources after processing', + # output creation + 'output_image_quality': 'specify the image quality which translates to the compression factor', + 'output_image_resolution': 'specify the image output resolution based on the target image', + 'output_video_encoder': 'specify the encoder use for the video compression', + 'output_video_preset': 'balance fast video processing and video file size', + 'output_video_quality': 'specify the video quality which translates to the compression factor', + 'output_video_resolution': 'specify the video output resolution based on the target video', + 'output_video_fps': 'specify the video output fps based on the target video', + 'skip_audio': 'omit the audio from the target video', + # frame processors + 'frame_processors': 'load a single or multiple frame processors. (choices: {choices}, ...)', + 'face_debugger_items': 'load a single or multiple frame processors (choices: {choices})', + 'face_enhancer_model': 'choose the model responsible for enhancing the face', + 'face_enhancer_blend': 'blend the enhanced into the previous face', + 'face_swapper_model': 'choose the model responsible for swapping the face', + 'frame_colorizer_model': 'choose the model responsible for colorizing the frame', + 'frame_colorizer_blend': 'blend the colorized into the previous frame', + 'frame_colorizer_size': 'specify the size of the frame provided to the frame colorizer', + 'frame_enhancer_model': 'choose the model responsible for enhancing the frame', + 'frame_enhancer_blend': 'blend the enhanced into the previous frame', + 'lip_syncer_model': 'choose the model responsible for syncing the lips', + # uis + 'open_browser': 'open the browser once the program is ready', + 'ui_layouts': 'launch a single or multiple UI layouts (choices: {choices}, ...)' + }, + 'uis': + { + # general + 'start_button': 'START', + 'stop_button': 'STOP', + 'clear_button': 'CLEAR', + # about + 'donate_button': 'DONATE', + # benchmark + 'benchmark_results_dataframe': 'BENCHMARK RESULTS', + # benchmark options + 'benchmark_runs_checkbox_group': 'BENCHMARK RUNS', + 'benchmark_cycles_slider': 'BENCHMARK CYCLES', + # common options + 'common_options_checkbox_group': 'OPTIONS', + # execution + 'execution_providers_checkbox_group': 'EXECUTION PROVIDERS', + # execution queue count + 'execution_queue_count_slider': 'EXECUTION QUEUE COUNT', + # execution thread count + 'execution_thread_count_slider': 'EXECUTION THREAD COUNT', + # face analyser + 'face_analyser_order_dropdown': 'FACE ANALYSER ORDER', + 'face_analyser_age_dropdown': 'FACE ANALYSER AGE', + 'face_analyser_gender_dropdown': 'FACE ANALYSER GENDER', + 'face_detector_model_dropdown': 'FACE DETECTOR MODEL', + 'face_detector_size_dropdown': 'FACE DETECTOR SIZE', + 'face_detector_score_slider': 'FACE DETECTOR SCORE', + 'face_landmarker_score_slider': 'FACE LANDMARKER SCORE', + # face masker + 'face_mask_types_checkbox_group': 'FACE MASK TYPES', + 'face_mask_blur_slider': 'FACE MASK BLUR', + 'face_mask_padding_top_slider': 'FACE MASK PADDING TOP', + 'face_mask_padding_right_slider': 'FACE MASK PADDING RIGHT', + 'face_mask_padding_bottom_slider': 'FACE MASK PADDING BOTTOM', + 'face_mask_padding_left_slider': 'FACE MASK PADDING LEFT', + 'face_mask_region_checkbox_group': 'FACE MASK REGIONS', + # face selector + 'face_selector_mode_dropdown': 'FACE SELECTOR MODE', + 'reference_face_gallery': 'REFERENCE FACE', + 'reference_face_distance_slider': 'REFERENCE FACE DISTANCE', + # frame processors + 'frame_processors_checkbox_group': 'FRAME PROCESSORS', + # frame processors options + 'face_debugger_items_checkbox_group': 'FACE DEBUGGER ITEMS', + 'face_enhancer_model_dropdown': 'FACE ENHANCER MODEL', + 'face_enhancer_blend_slider': 'FACE ENHANCER BLEND', + 'face_swapper_model_dropdown': 'FACE SWAPPER MODEL', + 'frame_colorizer_model_dropdown': 'FRAME COLORIZER MODEL', + 'frame_colorizer_blend_slider': 'FRAME COLORIZER BLEND', + 'frame_colorizer_size_dropdown': 'FRAME COLORIZER SIZE', + 'frame_enhancer_model_dropdown': 'FRAME ENHANCER MODEL', + 'frame_enhancer_blend_slider': 'FRAME ENHANCER BLEND', + 'lip_syncer_model_dropdown': 'LIP SYNCER MODEL', + # memory + 'video_memory_strategy_dropdown': 'VIDEO MEMORY STRATEGY', + 'system_memory_limit_slider': 'SYSTEM MEMORY LIMIT', + # output + 'output_image_or_video': 'OUTPUT', + # output options + 'output_path_textbox': 'OUTPUT PATH', + 'output_image_quality_slider': 'OUTPUT IMAGE QUALITY', + 'output_image_resolution_dropdown': 'OUTPUT IMAGE RESOLUTION', + 'output_video_encoder_dropdown': 'OUTPUT VIDEO ENCODER', + 'output_video_preset_dropdown': 'OUTPUT VIDEO PRESET', + 'output_video_quality_slider': 'OUTPUT VIDEO QUALITY', + 'output_video_resolution_dropdown': 'OUTPUT VIDEO RESOLUTION', + 'output_video_fps_slider': 'OUTPUT VIDEO FPS', + # preview + 'preview_image': 'PREVIEW', + 'preview_frame_slider': 'PREVIEW FRAME', + # source + 'source_file': 'SOURCE', + # target + 'target_file': 'TARGET', + # temp frame + 'temp_frame_format_dropdown': 'TEMP FRAME FORMAT', + # trim frame + 'trim_frame_start_slider': 'TRIM FRAME START', + 'trim_frame_end_slider': 'TRIM FRAME END', + # webcam + 'webcam_image': 'WEBCAM', + # webcam options + 'webcam_mode_radio': 'WEBCAM MODE', + 'webcam_resolution_dropdown': 'WEBCAM RESOLUTION', + 'webcam_fps_slider': 'WEBCAM FPS' + } +} + + +def get(key : str) -> Optional[str]: + if '.' in key: + section, name = key.split('.') + if section in WORDING and name in WORDING[section]: + return WORDING[section][name] + if key in WORDING: + return WORDING[key] + return None diff --git a/install.py b/install.py new file mode 100755 index 0000000..9569a1f --- /dev/null +++ b/install.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 + +import os +import subprocess + +os.environ['PIP_BREAK_SYSTEM_PACKAGES'] = '1' +subprocess.call([ 'pip', 'install', 'inquirer', '-q' ]) + +from deepfuze import installer + +if __name__ == '__main__': + installer.cli() diff --git a/llm_node.py b/llm_node.py new file mode 100644 index 0000000..9082295 --- /dev/null +++ b/llm_node.py @@ -0,0 +1,39 @@ +from openai import OpenAI + +class LLM_node: + + @classmethod + def INPUT_TYPES(self): + return { + "required":{ + "system_prompt" : ("STRING",{"default":"","multiline": True,},), + "user_query": ("STRING", {"default":"","multiline": True,},), + "model_name": (["gpt-3.5-turbo","gpt-4o","gpt-4-turbo","gpt-4",""],), + "api_key": ("STRING",{"default":""},) + }, + "optional":{ + "max_tokens" : ("INT", {"default":250,"min":10,"max":2000,"step":10},), + "temperature" : ("FLOAT", {"default":0,"min":0,"max":1,"step":0.1}), + "timeout": ("INT", {"default":10,"min":1,"max":200,"step":1},), + } + } + + CATEGORY = "LLM" + RETURN_TYPES = ("STRING",) + RETURN_NAMES = ("text",) + FUNCTION = "run_llm" + + def run_llm(self, system_prompt, user_query, model_name,temperature,api_key,max_tokens,timeout): + client = OpenAI(api_key=api_key) + response = client.chat.completions.create( + model=model_name, + messages=[ + {"role": "system", "content": system_prompt}, + {"role": "user", "content": user_query} + ], + # stop = stop.split(","), + temperature=temperature, + max_tokens=max_tokens, + timeout=timeout + ) + return (text,) diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..64218bc --- /dev/null +++ b/mypy.ini @@ -0,0 +1,7 @@ +[mypy] +check_untyped_defs = True +disallow_any_generics = True +disallow_untyped_calls = True +disallow_untyped_defs = True +ignore_missing_imports = True +strict_optional = False diff --git a/nodes.py b/nodes.py new file mode 100644 index 0000000..b7764a3 --- /dev/null +++ b/nodes.py @@ -0,0 +1,1110 @@ + +import os +import sys +import json +import subprocess +import numpy as np +import re +import cv2 +import time +import itertools +import numpy as np +import datetime +from typing import List +import torch +import psutil + +from PIL import Image, ExifTags +from PIL.PngImagePlugin import PngInfo +from pathlib import Path +from string import Template +from pydub import AudioSegment +from .utils import BIGMAX, DIMMAX, calculate_file_hash, get_sorted_dir_files_from_directory, get_audio, lazy_eval, hash_path, validate_path, strip_path +from PIL import Image, ImageOps +from comfy.utils import common_upscale, ProgressBar + +from scipy.io.wavfile import write +import folder_paths +from .utils import ffmpeg_path, get_audio, hash_path, validate_path, requeue_workflow, gifski_path, calculate_file_hash, strip_path +from comfy.utils import ProgressBar +from .utils import BIGMAX, DIMMAX, calculate_file_hash, get_sorted_dir_files_from_directory, get_audio, lazy_eval, hash_path, validate_path, strip_path +from .llm_node import LLM_node +from .audio_playback import PlayBackAudio + + +# folder_paths.folder_names_and_paths["VHS_video_formats"] = ( +# [ +# os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "video_formats"), +# ], +# [".json"] +# ) + +result_dir = os.path.join(folder_paths.get_output_directory(),"deepfuze") +audio_dir = os.path.join(folder_paths.get_input_directory(),"audio") + +try: + os.makedirs(result_dir) +except: pass +try: + os.makedirs(audio_dir) +except: pass +audio_extensions = ['mp3', 'mp4', 'wav', 'ogg'] + + + +video_extensions = ['webm', 'mp4', 'mkv', 'gif'] + + +def is_gif(filename) -> bool: + file_parts = filename.split('.') + return len(file_parts) > 1 and file_parts[-1] == "gif" + + +def target_size(width, height, force_size, custom_width, custom_height) -> tuple[int, int]: + if force_size == "Custom": + return (custom_width, custom_height) + elif force_size == "Custom Height": + force_size = "?x"+str(custom_height) + elif force_size == "Custom Width": + force_size = str(custom_width)+"x?" + + if force_size != "Disabled": + force_size = force_size.split("x") + if force_size[0] == "?": + width = (width*int(force_size[1]))//height + #Limit to a multple of 8 for latent conversion + width = int(width)+4 & ~7 + height = int(force_size[1]) + elif force_size[1] == "?": + height = (height*int(force_size[0]))//width + height = int(height)+4 & ~7 + width = int(force_size[0]) + else: + width = int(force_size[0]) + height = int(force_size[1]) + return (width, height) + +def cv_frame_generator(video, force_rate, frame_load_cap, skip_first_frames, + select_every_nth, meta_batch=None, unique_id=None): + video_cap = cv2.VideoCapture(strip_path(video)) + if not video_cap.isOpened(): + raise ValueError(f"{video} could not be loaded with cv.") + pbar = ProgressBar(frame_load_cap) if frame_load_cap > 0 else None + + # extract video metadata + fps = video_cap.get(cv2.CAP_PROP_FPS) + width = int(video_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + total_frames = int(video_cap.get(cv2.CAP_PROP_FRAME_COUNT)) + duration = total_frames / fps + + # set video_cap to look at start_index frame + total_frame_count = 0 + total_frames_evaluated = -1 + frames_added = 0 + base_frame_time = 1 / fps + prev_frame = None + + if force_rate == 0: + target_frame_time = base_frame_time + else: + target_frame_time = 1/force_rate + + yield (width, height, fps, duration, total_frames, target_frame_time) + + time_offset=target_frame_time - base_frame_time + while video_cap.isOpened(): + if time_offset < target_frame_time: + is_returned = video_cap.grab() + # if didn't return frame, video has ended + if not is_returned: + break + time_offset += base_frame_time + if time_offset < target_frame_time: + continue + time_offset -= target_frame_time + # if not at start_index, skip doing anything with frame + total_frame_count += 1 + if total_frame_count <= skip_first_frames: + continue + else: + total_frames_evaluated += 1 + + # if should not be selected, skip doing anything with frame + if total_frames_evaluated%select_every_nth != 0: + continue + + # opencv loads images in BGR format (yuck), so need to convert to RGB for ComfyUI use + # follow up: can videos ever have an alpha channel? + # To my testing: No. opencv has no support for alpha + unused, frame = video_cap.retrieve() + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + # convert frame to comfyui's expected format + # TODO: frame contains no exif information. Check if opencv2 has already applied + frame = np.array(frame, dtype=np.float32) + torch.from_numpy(frame).div_(255) + if prev_frame is not None: + inp = yield prev_frame + if inp is not None: + #ensure the finally block is called + return + prev_frame = frame + frames_added += 1 + if pbar is not None: + pbar.update_absolute(frames_added, frame_load_cap) + # if cap exists and we've reached it, stop processing frames + if frame_load_cap > 0 and frames_added >= frame_load_cap: + break + if meta_batch is not None: + meta_batch.inputs.pop(unique_id) + meta_batch.has_closed_inputs = True + if prev_frame is not None: + yield prev_frame + +def load_video_cv(video: str, force_rate: int, force_size: str, + custom_width: int,custom_height: int, frame_load_cap: int, + skip_first_frames: int, select_every_nth: int, + meta_batch=None, unique_id=None, memory_limit_mb=None): + print(meta_batch) + if meta_batch is None or unique_id not in meta_batch.inputs: + gen = cv_frame_generator(video, force_rate, frame_load_cap, skip_first_frames, + select_every_nth, meta_batch, unique_id) + (width, height, fps, duration, total_frames, target_frame_time) = next(gen) + + if meta_batch is not None: + meta_batch.inputs[unique_id] = (gen, width, height, fps, duration, total_frames, target_frame_time) + + else: + (gen, width, height, fps, duration, total_frames, target_frame_time) = meta_batch.inputs[unique_id] + + if memory_limit_mb is not None: + memory_limit *= 2 ** 20 + else: + #TODO: verify if garbage collection should be performed here. + #leaves ~128 MB unreserved for safety + memory_limit = (psutil.virtual_memory().available + psutil.swap_memory().free) - 2 ** 27 + #space required to load as f32, exist as latent with wiggle room, decode to f32 + max_loadable_frames = int(memory_limit//(width*height*3*(4+4+1/10))) + if meta_batch is not None: + if meta_batch.frames_per_batch > max_loadable_frames: + raise RuntimeError(f"Meta Batch set to {meta_batch.frames_per_batch} frames but only {max_loadable_frames} can fit in memory") + gen = itertools.islice(gen, meta_batch.frames_per_batch) + else: + original_gen = gen + gen = itertools.islice(gen, max_loadable_frames) + + #Some minor wizardry to eliminate a copy and reduce max memory by a factor of ~2 + images = torch.from_numpy(np.fromiter(gen, np.dtype((np.float32, (height, width, 3))))) + if meta_batch is None: + try: + next(original_gen) + raise RuntimeError(f"Memory limit hit after loading {len(images)} frames. Stopping execution.") + except StopIteration: + pass + if len(images) == 0: + raise RuntimeError("No frames generated") + if force_size != "Disabled": + new_size = target_size(width, height, force_size, custom_width, custom_height) + if new_size[0] != width or new_size[1] != height: + s = images.movedim(-1,1) + s = common_upscale(s, new_size[0], new_size[1], "lanczos", "center") + images = s.movedim(1,-1) + + #Setup lambda for lazy audio capture + audio = lambda : get_audio(video, skip_first_frames * target_frame_time, + frame_load_cap*target_frame_time*select_every_nth) + #Adjust target_frame_time for select_every_nth + target_frame_time *= select_every_nth + video_info = { + "source_fps": fps, + "source_frame_count": total_frames, + "source_duration": duration, + "source_width": width, + "source_height": height, + "loaded_fps": 1/target_frame_time, + "loaded_frame_count": len(images), + "loaded_duration": len(images) * target_frame_time, + "loaded_width": images.shape[2], + "loaded_height": images.shape[1], + } + print("images", type(images)) + return (images, len(images), lazy_eval(audio), video_info) + + + +class AudioData: + def __init__(self, audio_file) -> None: + + # Extract the sample rate + sample_rate = audio_file.frame_rate + + # Get the number of audio channels + num_channels = audio_file.channels + + # Extract the audio data as a NumPy array + audio_data = np.array(audio_file.get_array_of_samples()) + self.audio_data = audio_data + self.sample_rate = sample_rate + self.num_channels = num_channels + + def get_channel_audio_data(self, channel: int): + if channel < 0 or channel >= self.num_channels: + raise IndexError(f"Channel '{channel}' out of range. total channels is '{self.num_channels}'.") + return self.audio_data[channel::self.num_channels] + + def get_channel_fft(self, channel: int): + audio_data = self.get_channel_audio_data(channel) + return fft(audio_data) + + +def gen_format_widgets(video_format): + for k in video_format: + if k.endswith("_pass"): + for i in range(len(video_format[k])): + if isinstance(video_format[k][i], list): + item = [video_format[k][i]] + yield item + video_format[k][i] = item[0] + else: + if isinstance(video_format[k], list): + item = [video_format[k]] + yield item + video_format[k] = item[0] + +def get_video_formats(): + formats = [] + for format_name in folder_paths.get_filename_list("VHS_video_formats"): + format_name = format_name[:-5] + video_format_path = folder_paths.get_full_path("VHS_video_formats", format_name + ".json") + with open(video_format_path, 'r') as stream: + video_format = json.load(stream) + if "gifski_pass" in video_format and gifski_path is None: + #Skip format + continue + widgets = [w[0] for w in gen_format_widgets(video_format)] + if (len(widgets) > 0): + formats.append(["video/" + format_name, widgets]) + else: + formats.append("video/" + format_name) + return formats + +def get_format_widget_defaults(format_name): + video_format_path = folder_paths.get_full_path("VHS_video_formats", format_name + ".json") + with open(video_format_path, 'r') as stream: + video_format = json.load(stream) + results = {} + for w in gen_format_widgets(video_format): + if len(w[0]) > 2 and 'default' in w[0][2]: + default = w[0][2]['default'] + else: + if type(w[0][1]) is list: + default = w[0][1][0] + else: + #NOTE: This doesn't respect max/min, but should be good enough as a fallback to a fallback to a fallback + default = {"BOOLEAN": False, "INT": 0, "FLOAT": 0, "STRING": ""}[w[0][1]] + results[w[0][0]] = default + return results + + +def apply_format_widgets(format_name, kwargs): + video_format_path = folder_paths.get_full_path("VHS_video_formats", format_name + ".json") + print(video_format_path) + with open(video_format_path, 'r') as stream: + video_format = json.load(stream) + for w in gen_format_widgets(video_format): + print(w[0][0]) + assert(w[0][0] in kwargs) + if len(w[0]) > 3: + w[0] = Template(w[0][3]).substitute(val=kwargs[w[0][0]]) + else: + w[0] = str(kwargs[w[0][0]]) + return video_format + +def tensor_to_int(tensor, bits): + #TODO: investigate benefit of rounding by adding 0.5 before clip/cast + tensor = tensor.cpu().numpy() * (2**bits-1) + return np.clip(tensor, 0, (2**bits-1)) +def tensor_to_shorts(tensor): + return tensor_to_int(tensor, 16).astype(np.uint16) +def tensor_to_bytes(tensor): + return tensor_to_int(tensor, 8).astype(np.uint8) + +def ffmpeg_process(args, video_format, video_metadata, file_path, env): + + res = None + frame_data = yield + total_frames_output = 0 + if video_format.get('save_metadata', 'False') != 'False': + os.makedirs(folder_paths.get_temp_directory(), exist_ok=True) + metadata = json.dumps(video_metadata) + metadata_path = os.path.join(folder_paths.get_temp_directory(), "metadata.txt") + #metadata from file should escape = ; # \ and newline + metadata = metadata.replace("\\","\\\\") + metadata = metadata.replace(";","\\;") + metadata = metadata.replace("#","\\#") + metadata = metadata.replace("=","\\=") + metadata = metadata.replace("\n","\\\n") + metadata = "comment=" + metadata + with open(metadata_path, "w") as f: + f.write(";FFMETADATA1\n") + f.write(metadata) + m_args = args[:1] + ["-i", metadata_path] + args[1:] + ["-metadata", "creation_time=now"] + with subprocess.Popen(m_args + [file_path], stderr=subprocess.PIPE, + stdin=subprocess.PIPE, env=env) as proc: + try: + while frame_data is not None: + proc.stdin.write(frame_data) + #TODO: skip flush for increased speed + frame_data = yield + total_frames_output+=1 + proc.stdin.flush() + proc.stdin.close() + res = proc.stderr.read() + except BrokenPipeError as e: + err = proc.stderr.read() + #Check if output file exists. If it does, the re-execution + #will also fail. This obscures the cause of the error + #and seems to never occur concurrent to the metadata issue + if os.path.exists(file_path): + raise Exception("An error occurred in the ffmpeg subprocess:\n" \ + + err.decode("utf-8")) + #Res was not set + print(err.decode("utf-8"), end="", file=sys.stderr) + print("An error occurred when saving with metadata") + if res != b'': + with subprocess.Popen(args + [file_path], stderr=subprocess.PIPE, + stdin=subprocess.PIPE, env=env) as proc: + try: + while frame_data is not None: + proc.stdin.write(frame_data) + frame_data = yield + total_frames_output+=1 + proc.stdin.flush() + proc.stdin.close() + res = proc.stderr.read() + except BrokenPipeError as e: + res = proc.stderr.read() + raise Exception("An error occurred in the ffmpeg subprocess:\n" \ + + res.decode("utf-8")) + yield total_frames_output + if len(res) > 0: + print(res.decode("utf-8"), end="", file=sys.stderr) + +def gifski_process(args, video_format, file_path, env): + frame_data = yield + with subprocess.Popen(args + video_format['main_pass'] + ['-f', 'yuv4mpegpipe', '-'], + stderr=subprocess.PIPE, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, env=env) as procff: + with subprocess.Popen([gifski_path] + video_format['gifski_pass'] + + ['-q', '-o', file_path, '-'], stderr=subprocess.PIPE, + stdin=procff.stdout, stdout=subprocess.PIPE, + env=env) as procgs: + try: + while frame_data is not None: + procff.stdin.write(frame_data) + frame_data = yield + procff.stdin.flush() + procff.stdin.close() + resff = procff.stderr.read() + resgs = procgs.stderr.read() + outgs = procgs.stdout.read() + except BrokenPipeError as e: + procff.stdin.close() + resff = procff.stderr.read() + resgs = procgs.stderr.read() + raise Exception("An error occurred while creating gifski output\n" \ + + "Make sure you are using gifski --version >=1.32.0\nffmpeg: " \ + + resff.decode("utf-8") + '\ngifski: ' + resgs.decode("utf-8")) + if len(resff) > 0: + print(resff.decode("utf-8"), end="", file=sys.stderr) + if len(resgs) > 0: + print(resgs.decode("utf-8"), end="", file=sys.stderr) + #should always be empty as the quiet flag is passed + if len(outgs) > 0: + print(outgs.decode("utf-8")) + +def to_pingpong(inp): + if not hasattr(inp, "__getitem__"): + inp = list(inp) + yield from inp + for i in range(len(inp)-2,0,-1): + yield inp[i] + + +video_extensions = ['webm', 'mp4', 'mkv', 'gif'] + + +def is_gif(filename) -> bool: + file_parts = filename.split('.') + return len(file_parts) > 1 and file_parts[-1] == "gif" + + +def target_size(width, height, force_size, custom_width, custom_height) -> tuple[int, int]: + if force_size == "Custom": + return (custom_width, custom_height) + elif force_size == "Custom Height": + force_size = "?x"+str(custom_height) + elif force_size == "Custom Width": + force_size = str(custom_width)+"x?" + + if force_size != "Disabled": + force_size = force_size.split("x") + if force_size[0] == "?": + width = (width*int(force_size[1]))//height + #Limit to a multple of 8 for latent conversion + width = int(width)+4 & ~7 + height = int(force_size[1]) + elif force_size[1] == "?": + height = (height*int(force_size[0]))//width + height = int(height)+4 & ~7 + width = int(force_size[0]) + else: + width = int(force_size[0]) + height = int(force_size[1]) + return (width, height) + +def cv_frame_generator(video, force_rate, frame_load_cap, skip_first_frames, + select_every_nth, meta_batch=None, unique_id=None): + video_cap = cv2.VideoCapture(strip_path(video)) + if not video_cap.isOpened(): + raise ValueError(f"{video} could not be loaded with cv.") + pbar = ProgressBar(frame_load_cap) if frame_load_cap > 0 else None + + # extract video metadata + fps = video_cap.get(cv2.CAP_PROP_FPS) + width = int(video_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + total_frames = int(video_cap.get(cv2.CAP_PROP_FRAME_COUNT)) + duration = total_frames / fps + + # set video_cap to look at start_index frame + total_frame_count = 0 + total_frames_evaluated = -1 + frames_added = 0 + base_frame_time = 1 / fps + prev_frame = None + + if force_rate == 0: + target_frame_time = base_frame_time + else: + target_frame_time = 1/force_rate + + yield (width, height, fps, duration, total_frames, target_frame_time) + + time_offset=target_frame_time - base_frame_time + while video_cap.isOpened(): + if time_offset < target_frame_time: + is_returned = video_cap.grab() + # if didn't return frame, video has ended + if not is_returned: + break + time_offset += base_frame_time + if time_offset < target_frame_time: + continue + time_offset -= target_frame_time + # if not at start_index, skip doing anything with frame + total_frame_count += 1 + if total_frame_count <= skip_first_frames: + continue + else: + total_frames_evaluated += 1 + + # if should not be selected, skip doing anything with frame + if total_frames_evaluated%select_every_nth != 0: + continue + + # opencv loads images in BGR format (yuck), so need to convert to RGB for ComfyUI use + # follow up: can videos ever have an alpha channel? + # To my testing: No. opencv has no support for alpha + unused, frame = video_cap.retrieve() + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) + # convert frame to comfyui's expected format + # TODO: frame contains no exif information. Check if opencv2 has already applied + frame = np.array(frame, dtype=np.float32) + torch.from_numpy(frame).div_(255) + if prev_frame is not None: + inp = yield prev_frame + if inp is not None: + #ensure the finally block is called + return + prev_frame = frame + frames_added += 1 + if pbar is not None: + pbar.update_absolute(frames_added, frame_load_cap) + # if cap exists and we've reached it, stop processing frames + if frame_load_cap > 0 and frames_added >= frame_load_cap: + break + if meta_batch is not None: + meta_batch.inputs.pop(unique_id) + meta_batch.has_closed_inputs = True + if prev_frame is not None: + yield prev_frame + +def load_video_cv(video: str, force_rate: int, force_size: str, + custom_width: int,custom_height: int, frame_load_cap: int, + skip_first_frames: int, select_every_nth: int, + meta_batch=None, unique_id=None, memory_limit_mb=None): + print(meta_batch) + if meta_batch is None or unique_id not in meta_batch.inputs: + gen = cv_frame_generator(video, force_rate, frame_load_cap, skip_first_frames, + select_every_nth, meta_batch, unique_id) + (width, height, fps, duration, total_frames, target_frame_time) = next(gen) + + if meta_batch is not None: + meta_batch.inputs[unique_id] = (gen, width, height, fps, duration, total_frames, target_frame_time) + + else: + (gen, width, height, fps, duration, total_frames, target_frame_time) = meta_batch.inputs[unique_id] + + if memory_limit_mb is not None: + memory_limit *= 2 ** 20 + else: + #TODO: verify if garbage collection should be performed here. + #leaves ~128 MB unreserved for safety + memory_limit = (psutil.virtual_memory().available + psutil.swap_memory().free) - 2 ** 27 + #space required to load as f32, exist as latent with wiggle room, decode to f32 + max_loadable_frames = int(memory_limit//(width*height*3*(4+4+1/10))) + if meta_batch is not None: + if meta_batch.frames_per_batch > max_loadable_frames: + raise RuntimeError(f"Meta Batch set to {meta_batch.frames_per_batch} frames but only {max_loadable_frames} can fit in memory") + gen = itertools.islice(gen, meta_batch.frames_per_batch) + else: + original_gen = gen + gen = itertools.islice(gen, max_loadable_frames) + + #Some minor wizardry to eliminate a copy and reduce max memory by a factor of ~2 + images = torch.from_numpy(np.fromiter(gen, np.dtype((np.float32, (height, width, 3))))) + if meta_batch is None: + try: + next(original_gen) + raise RuntimeError(f"Memory limit hit after loading {len(images)} frames. Stopping execution.") + except StopIteration: + pass + if len(images) == 0: + raise RuntimeError("No frames generated") + if force_size != "Disabled": + new_size = target_size(width, height, force_size, custom_width, custom_height) + if new_size[0] != width or new_size[1] != height: + s = images.movedim(-1,1) + s = common_upscale(s, new_size[0], new_size[1], "lanczos", "center") + images = s.movedim(1,-1) + + #Setup lambda for lazy audio capture + audio = lambda : get_audio(video, skip_first_frames * target_frame_time, + frame_load_cap*target_frame_time*select_every_nth) + #Adjust target_frame_time for select_every_nth + target_frame_time *= select_every_nth + video_info = { + "source_fps": fps, + "source_frame_count": total_frames, + "source_duration": duration, + "source_width": width, + "source_height": height, + "loaded_fps": 1/target_frame_time, + "loaded_frame_count": len(images), + "loaded_duration": len(images) * target_frame_time, + "loaded_width": images.shape[2], + "loaded_height": images.shape[1], + } + print("images", type(images)) + return (images, len(images), lazy_eval(audio), video_info) + + + + + + +class DeepFuzeAdavance: + @classmethod + def INPUT_TYPES(s): + ffmpeg_formats = get_video_formats() + return { + "required": { + "images": ("IMAGE",), + "audio": ("AUDIO",), + "enhancer": ("None,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".split(","),{"default":'None'}), + "frame_enhancer": ("None,clear_reality_x4,lsdir_x4,nomos8k_sc_x4,real_esrgan_x2,real_esrgan_x2_fp16,real_esrgan_x4,real_esrgan_x4_fp16,real_hatgan_x4,span_kendata_x4,ultra_sharp_x4".split(","),{"default":'None'}), + "face_mask_padding_left": ("FLOAT",{"default":0,"min":0,"max":3,"step":0.1}), + "face_mask_padding_right": ("FLOAT",{"default":0,"min":0,"max":3,"step":0.1}), + "face_mask_padding_bottom": ("FLOAT",{"default":0,"min":0,"max":3,"step":0.1}), + "face_mask_padding_top": ("FLOAT",{"default":0,"min":0,"max":3,"step":0.1}), + "trim_frame_start": ("INT",{"default":0,"max":2000},), + "trim_frame_end": ("INT",{"default":0,"max":2000},), + "device" : (["cpu","gpu"],{"default":"cpu"}), + "frame_rate": ( + "FLOAT", + {"default": 25, "min": 1, "step": 1}, + ), + + }, + "optional": { + "meta_batch": ("VHS_BatchManager",), + "loop_count": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}), + "filename_prefix": ("STRING", {"default": "deepfuze"}), + "pingpong": ("BOOLEAN", {"default": False}), + "save_output": ("BOOLEAN", {"default": True}), + }, + "hidden": { + "prompt": "PROMPT", + "format": (["image/gif", "image/webp"] + ffmpeg_formats,{"default":"video/h265-mp4"}), + "extra_pnginfo": "EXTRA_PNGINFO", + "unique_id": "UNIQUE_ID" + }, + } + + + RETURN_TYPES = ("IMAGE", "INT", "VHS_AUDIO", "VHS_VIDEOINFO",) + RETURN_NAMES = ("IMAGE", "frame_count", "audio", "video_info",) + + # RETURN_TYPES = ("VHS_FILENAMES",) + # RETURN_NAMES = ("Filenames",) + # OUTPUT_NODE = True + CATEGORY = "DeepFuze" + FUNCTION = "lipsyncgenerate" + + def lipsyncgenerate( + self, + images, + audio, + enhancer, + frame_enhancer, + face_mask_padding_left, + face_mask_padding_right, + face_mask_padding_bottom, + face_mask_padding_top, + trim_frame_start, + trim_frame_end, + device, + frame_rate: int, + loop_count: int, + filename_prefix="deepfuze", + format="video/h265-mp4", + pingpong=False, + save_output=True, + prompt=None, + extra_pnginfo=None, + unique_id=None, + manual_format_widgets=None, + meta_batch=None + ): + print(enhancer,frame_rate,format) + if isinstance(images, torch.Tensor) and images.size(0) == 0: + return ("",) + pbar = ProgressBar(len(images)) + trim_frame_end = len(images)-trim_frame_end + + first_image = images[0] + # get output information + output_dir = ( + folder_paths.get_output_directory() + if save_output + else folder_paths.get_temp_directory() + ) + ( + full_output_folder, + filename, + _, + subfolder, + _, + ) = folder_paths.get_save_image_path(filename_prefix, output_dir) + output_files = [] + + metadata = PngInfo() + video_metadata = {} + if prompt is not None: + metadata.add_text("prompt", json.dumps(prompt)) + video_metadata["prompt"] = prompt + if extra_pnginfo is not None: + for x in extra_pnginfo: + metadata.add_text(x, json.dumps(extra_pnginfo[x])) + video_metadata[x] = extra_pnginfo[x] + metadata.add_text("CreationTime", datetime.datetime.now().isoformat(" ")[:19]) + + if meta_batch is not None and unique_id in meta_batch.outputs: + (counter, output_process) = meta_batch.outputs[unique_id] + else: + # comfy counter workaround + max_counter = 0 + + # Loop through the existing files + matcher = re.compile(f"{re.escape(filename)}_(\\d+)\\D*\\..+", re.IGNORECASE) + for existing_file in os.listdir(full_output_folder): + # Check if the file matches the expected format + match = matcher.fullmatch(existing_file) + if match: + # Extract the numeric portion of the filename + file_counter = int(match.group(1)) + # Update the maximum counter value if necessary + if file_counter > max_counter: + max_counter = file_counter + + # Increment the counter by 1 to get the next available value + counter = max_counter + 1 + output_process = None + + # save first frame as png to keep metadata + file = f"{filename}_{counter:05}.png" + file_path = os.path.join(full_output_folder, file) + Image.fromarray(tensor_to_bytes(first_image)).save( + file_path, + pnginfo=metadata, + compress_level=4, + ) + output_files.append(file_path) + + format_type, format_ext = format.split("/") + print(format_type, format_ext) + if format_type == "image": + if meta_batch is not None: + raise Exception("Pillow('image/') formats are not compatible with batched output") + image_kwargs = {} + if format_ext == "gif": + image_kwargs['disposal'] = 2 + if format_ext == "webp": + #Save timestamp information + exif = Image.Exif() + exif[ExifTags.IFD.Exif] = {36867: datetime.datetime.now().isoformat(" ")[:19]} + image_kwargs['exif'] = exif + file = f"{filename}_{counter:05}.{format_ext}" + file_path = os.path.join(full_output_folder, file) + if pingpong: + images = to_pingpong(images) + frames = map(lambda x : Image.fromarray(tensor_to_bytes(x)), images) + # Use pillow directly to save an animated image + next(frames).save( + file_path, + format=format_ext.upper(), + save_all=True, + append_images=frames, + duration=round(1000 / frame_rate), + loop=loop_count, + compress_level=4, + **image_kwargs + ) + output_files.append(file_path) + else: + # Use ffmpeg to save a video + if ffmpeg_path is None: + raise ProcessLookupError(f"ffmpeg is required for video outputs and could not be found.\nIn order to use video outputs, you must either:\n- Install imageio-ffmpeg with pip,\n- Place a ffmpeg executable in {os.path.abspath('')}, or\n- Install ffmpeg and add it to the system path.") + + #Acquire additional format_widget values + kwargs = None + if manual_format_widgets is None: + if prompt is not None: + kwargs = prompt[unique_id]['inputs'] + else: + manual_format_widgets = {} + if kwargs is None: + kwargs = get_format_widget_defaults(format_ext) + missing = {} + for k in kwargs.keys(): + if k in manual_format_widgets: + kwargs[k] = manual_format_widgets[k] + else: + missing[k] = kwargs[k] + if len(missing) > 0: + print("Extra format values were not provided, the following defaults will be used: " + str(kwargs) + "\nThis is likely due to usage of ComfyUI-to-python. These values can be manually set by supplying a manual_format_widgets argument") + kwargs["format"] = format + kwargs['pix_fmt'] = 'yuv420p10le' + kwargs['crf'] = 22 + kwargs["save_metadata"] = ["save_metadata", "BOOLEAN", {"default": True}] + print(kwargs) + video_format = apply_format_widgets(format_ext, kwargs) + has_alpha = first_image.shape[-1] == 4 + dim_alignment = video_format.get("dim_alignment", 8) + if (first_image.shape[1] % dim_alignment) or (first_image.shape[0] % dim_alignment): + #output frames must be padded + to_pad = (-first_image.shape[1] % dim_alignment, + -first_image.shape[0] % dim_alignment) + padding = (to_pad[0]//2, to_pad[0] - to_pad[0]//2, + to_pad[1]//2, to_pad[1] - to_pad[1]//2) + padfunc = torch.nn.ReplicationPad2d(padding) + def pad(image): + image = image.permute((2,0,1))#HWC to CHW + padded = padfunc(image.to(dtype=torch.float32)) + return padded.permute((1,2,0)) + images = map(pad, images) + new_dims = (-first_image.shape[1] % dim_alignment + first_image.shape[1], + -first_image.shape[0] % dim_alignment + first_image.shape[0]) + dimensions = f"{new_dims[0]}x{new_dims[1]}" + print("Output images were not of valid resolution and have had padding applied") + else: + dimensions = f"{first_image.shape[1]}x{first_image.shape[0]}" + if loop_count > 0: + loop_args = ["-vf", "loop=loop=" + str(loop_count)+":size=" + str(len(images))] + else: + loop_args = [] + if pingpong: + if meta_batch is not None: + print("pingpong is incompatible with batched output") + images = to_pingpong(images) + if video_format.get('input_color_depth', '8bit') == '16bit': + images = map(tensor_to_shorts, images) + if has_alpha: + i_pix_fmt = 'rgba64' + else: + i_pix_fmt = 'rgb48' + else: + images = map(tensor_to_bytes, images) + if has_alpha: + i_pix_fmt = 'rgba' + else: + i_pix_fmt = 'rgb24' + file = f"{filename}_{counter:05}.{video_format['extension']}" + file_path = os.path.join(full_output_folder, file) + if loop_count > 0: + loop_args = ["-vf", "loop=loop=" + str(loop_count)+":size=" + str(len(images))] + else: + loop_args = [] + bitrate_arg = [] + bitrate = video_format.get('bitrate') + if bitrate is not None: + bitrate_arg = ["-b:v", str(bitrate) + "M" if video_format.get('megabit') == 'True' else str(bitrate) + "K"] + args = [ffmpeg_path, "-v", "error", "-f", "rawvideo", "-pix_fmt", i_pix_fmt, + "-s", dimensions, "-r", str(frame_rate), "-i", "-"] \ + + loop_args + + images = map(lambda x: x.tobytes(), images) + env=os.environ.copy() + if "environment" in video_format: + env.update(video_format["environment"]) + + if "pre_pass" in video_format: + if meta_batch is not None: + #Performing a prepass requires keeping access to all frames. + #Potential solutions include keeping just output frames in + #memory or using 3 passes with intermediate file, but + #very long gifs probably shouldn't be encouraged + raise Exception("Formats which require a pre_pass are incompatible with Batch Manager.") + images = [b''.join(images)] + os.makedirs(folder_paths.get_temp_directory(), exist_ok=True) + pre_pass_args = args[:13] + video_format['pre_pass'] + try: + subprocess.run(pre_pass_args, input=images[0], env=env, + capture_output=True, check=True) + except subprocess.CalledProcessError as e: + raise Exception("An error occurred in the ffmpeg prepass:\n" \ + + e.stderr.decode("utf-8")) + if "inputs_main_pass" in video_format: + args = args[:13] + video_format['inputs_main_pass'] + args[13:] + + if output_process is None: + if 'gifski_pass' in video_format: + output_process = gifski_process(args, video_format, file_path, env) + else: + args += video_format['main_pass'] + bitrate_arg + output_process = ffmpeg_process(args, video_format, video_metadata, file_path, env) + #Proceed to first yield + output_process.send(None) + if meta_batch is not None: + meta_batch.outputs[unique_id] = (counter, output_process) + + for image in images: + pbar.update(1) + output_process.send(image) + if meta_batch is not None: + requeue_workflow((meta_batch.unique_id, not meta_batch.has_closed_inputs)) + if meta_batch is None or meta_batch.has_closed_inputs: + #Close pipe and wait for termination. + try: + total_frames_output = output_process.send(None) + output_process.send(None) + except StopIteration: + pass + if meta_batch is not None: + meta_batch.outputs.pop(unique_id) + if len(meta_batch.outputs) == 0: + meta_batch.reset() + else: + #batch is unfinished + #TODO: Check if empty output breaks other custom nodes + return {"ui": {"unfinished_batch": [True]}, "result": ((save_output, []),)} + + output_files.append(file_path) + + audio_file = os.path.join(audio_dir,str(time.time()).replace(".","")+".wav") + write(audio_file,audio.sample_rate,audio.audio_data) + print(audio_file) + filename = os.path.join(result_dir,f"{str(time.time()).replace('.','')}.mp4") + enhanced_filename = os.path.join(result_dir,f"enhanced_{str(time.time()).replace('.','')}.mp4") + command = [ + 'python', + './run.py', # Script to run + '--frame-processors', + "lip_syncer", + "-s", + audio_file, + '-t', # Argument: segmentation path + output_files[-1], + '-o', + filename, + '--trim-frame-start', + str(trim_frame_start), + '--trim-frame-end', + str(trim_frame_end), + # '--face-mask-padding', + # [str(face_mask_padding_top),str(face_mask_padding_bottom),str(face_mask_padding_left),str(face_mask_padding_right)], + '--headless' + ] + if device=="gpu": + command.extend(['--execution-providers',"coreml"]) + print(command) + result = subprocess.run(command,cwd="custom_nodes/ComfyUI-DeepFuze",stdout=subprocess.PIPE) + # print(result.stdout.splitlines()[-1]) + if enhancer!="None": + command = [ + 'python', + './run.py', # Script to run + '--frame-processors', + "face_enhancer", + "-t", + filename, + '-o', + enhanced_filename, + '--headless' + ] + print(command) + result = subprocess.run(command,cwd="custom_nodes/ComfyUI-DeepFuze",stdout=subprocess.PIPE) + filename = enhanced_filename + + if frame_enhancer!="None": + command = [ + 'python', + './run.py', # Script to run + '--frame-processors', + "frame_enhancer", + "-t", + filename, + '-o', + enhanced_filename, + '--headless' + ] + print(command) + result = subprocess.run(command,cwd="custom_nodes/ComfyUI-DeepFuze",stdout=subprocess.PIPE) + filename = enhanced_filename + + print(result.stderr) + try: + os.system(f"rm {audio_file}") + except: pass + return load_video_cv(filename,0,'Disabled',512,512,0,0,1) + previews = [ + { + "filename": file, + "subfolder": subfolder, + "type": "output" if save_output else "temp", + "format": format, + } + ] + return {"ui": {"gifs": previews}, "result": ((save_output, output_files),)} + + +import folder_paths +import torch +import time +import os +from TTS.api import TTS +from pydub import AudioSegment + +from scipy.io.wavfile import write + +if torch.backends.mps.is_available(): + device = "mps" +elif torch.cuda.is_available(): + device = "cuda" +else: + device = "cpu" + + + +import numpy as np +from scipy.fft import fft + +class AudioData: + def __init__(self, audio_file) -> None: + + # Extract the sample rate + sample_rate = audio_file.frame_rate + + # Get the number of audio channels + num_channels = audio_file.channels + + # Extract the audio data as a NumPy array + audio_data = np.array(audio_file.get_array_of_samples()) + self.audio_data = audio_data + self.sample_rate = sample_rate + self.num_channels = num_channels + + def get_channel_audio_data(self, channel: int): + if channel < 0 or channel >= self.num_channels: + raise IndexError(f"Channel '{channel}' out of range. total channels is '{self.num_channels}'.") + return self.audio_data[channel::self.num_channels] + + def get_channel_fft(self, channel: int): + audio_data = self.get_channel_audio_data(channel) + return fft(audio_data) + + + + +checkpoint_path_voice = os.path.join(folder_paths.models_dir,"deepfuze") +print(checkpoint_path_voice) + +audio_path = os.path.join(folder_paths.get_input_directory(),"audio") + +tts = TTS() +tts.load_tts_model_by_path(model_path=checkpoint_path_voice,config_path=os.path.join(checkpoint_path_voice,"config.json")) +os.makedirs(audio_path,exist_ok=True) + +class TTS_generation: + + @classmethod + def INPUT_TYPES(self): + return { + "required": { + "audio": ("AUDIO",), + "text": ("STRING",{ + "multiline": True, + "default": "Uploaded Audio and text should be in same language" + }), + "device": (["cpu","cuda","mps"],), + "supported_language": ("English (en), Spanish (es), French (fr), German (de), Italian (it), Portuguese (pt), Polish (pl), Turkish (tr), Russian (ru), Dutch (nl), Czech (cs), Arabic (ar), Chinese (zh-cn), Japanese (ja), Hungarian (hu), Korean (ko), Hindi (hi)".split(","),), + } + } + + RETURN_TYPES = ("AUDIO",) # Output type(s) of the node + FUNCTION = "generate_audio" # Entry-point method name + + CATEGORY = "DeepFuze" # Category for the node in the UI + + def generate_audio(self, audio, text,device): + print(text) + try: + tts.to(device) + except:pass + try: + file_path = os.path.join(audio_path,str(time.time()).replace(".","")+".wav") + write(file_path,audio.sample_rate,audio.audio_data) + tts.tts_to_file(text=text, speaker_wav=file_path, language="en", file_path=file_path) + audio_file = AudioSegment.from_file(file_path, format="wav") + audio_data = AudioData(audio_file) + except:pass + try: + tts.to("cpu") + except: pass + return (audio_data,) + + +NODE_CLASS_MAPPINGS = { + "DeepFuzeAdavance": DeepFuzeAdavance, + "TTS_generation":TTS_generation, + "LLM_node": LLM_node, + "PlayBackAudio": PlayBackAudio + +} +NODE_DISPLAY_NAME_MAPPINGS = { + "DeepFuzeAdavance": "DeepFuze Lipsync", + "TTS_generation":"DeepFuze TTS", + "LLM_node": "Openai LLM", + "PlayBackAudio": "Play Audio" +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..001a7cc --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +filetype==1.2.0 +gradio==3.50.2 +numpy==1.26.4 +onnx==1.16.0 +onnxruntime==1.17.3 +opencv-python==4.9.0.80 +psutil==5.9.8 +tqdm==4.66.4 +scipy==1.13.0 +openai +sounddevice +pydub +TTS \ No newline at end of file diff --git a/run.py b/run.py new file mode 100755 index 0000000..b26c475 --- /dev/null +++ b/run.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +from deepfuze import core + +if __name__ == '__main__': + core.cli() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_audio.py b/tests/test_audio.py new file mode 100644 index 0000000..148f01b --- /dev/null +++ b/tests/test_audio.py @@ -0,0 +1,26 @@ +import subprocess +import pytest + +from deepfuze.audio import get_audio_frame, read_static_audio +from deepfuze.download import conditional_download + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.mp3' + ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/source.mp3', '../../models/facefusion/examples/source.wav' ]) + + +def test_get_audio_frame() -> None: + assert get_audio_frame('../../models/facefusion/examples/source.mp3', 25) is not None + assert get_audio_frame('../../models/facefusion/examples/source.wav', 25) is not None + assert get_audio_frame('invalid', 25) is None + + +def test_read_static_audio() -> None: + assert len(read_static_audio('../../models/facefusion/examples/source.mp3', 25)) == 280 + assert len(read_static_audio('../../models/facefusion/examples/source.wav', 25)) == 280 + assert read_static_audio('invalid', 25) is None diff --git a/tests/test_cli_face_debugger.py b/tests/test_cli_face_debugger.py new file mode 100644 index 0000000..694fb9e --- /dev/null +++ b/tests/test_cli_face_debugger.py @@ -0,0 +1,31 @@ +import subprocess +import sys +import pytest + +from deepfuze.download import conditional_download + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4' + ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vframes', '1', '../../models/facefusion/examples/target-240p.jpg' ]) + + +def test_debug_face_to_image() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'face_debugger', '-t', '../../models/facefusion/examples/target-240p.jpg', '-o', '../../models/facefusion/examples/test_debug_face_to_image.jpg', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'image succeed' in run.stdout.decode() + + +def test_debug_face_to_video() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'face_debugger', '-t', '../../models/facefusion/examples/target-240p.mp4', '-o', '../../models/facefusion/examples/test_debug_face_to_video.mp4', '--trim-frame-end', '10', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'video succeed' in run.stdout.decode() diff --git a/tests/test_cli_face_enhancer.py b/tests/test_cli_face_enhancer.py new file mode 100644 index 0000000..6def546 --- /dev/null +++ b/tests/test_cli_face_enhancer.py @@ -0,0 +1,32 @@ +import subprocess +import sys +import pytest + +from deepfuze.download import conditional_download + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4' + ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vframes', '1', '../../models/facefusion/examples/target-240p.jpg' ]) + + +def test_enhance_face_to_image() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'face_enhancer', '-t', '../../models/facefusion/examples/target-240p.jpg', '-o', '../../models/facefusion/examples/test_enhance_face_to_image.jpg', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'image succeed' in run.stdout.decode() + + +def test_enhance_face_to_video() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'face_enhancer', '-t', '../../models/facefusion/examples/target-240p.mp4', '-o', '../../models/facefusion/examples/test_enhance_face_to_video.mp4', '--trim-frame-end', '10', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'video succeed' in run.stdout.decode() + diff --git a/tests/test_cli_face_swapper.py b/tests/test_cli_face_swapper.py new file mode 100644 index 0000000..d269422 --- /dev/null +++ b/tests/test_cli_face_swapper.py @@ -0,0 +1,31 @@ +import subprocess +import sys +import pytest + +from deepfuze.download import conditional_download + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4' + ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vframes', '1', '../../models/facefusion/examples/target-240p.jpg' ]) + + +def test_swap_face_to_image() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'face_swapper', '-s', '../../models/facefusion/examples/source.jpg', '-t', '../../models/facefusion/examples/target-240p.jpg', '-o', '../../models/facefusion/examples/test_swap_face_to_image.jpg', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'image succeed' in run.stdout.decode() + + +def test_swap_face_to_video() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'face_swapper', '-s', '../../models/facefusion/examples/source.jpg', '-t', '../../models/facefusion/examples/target-240p.mp4', '-o', '../../models/facefusion/examples/test_swap_face_to_video.mp4', '--trim-frame-end', '10', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'video succeed' in run.stdout.decode() diff --git a/tests/test_cli_frame_colorizer.py b/tests/test_cli_frame_colorizer.py new file mode 100644 index 0000000..0f254fe --- /dev/null +++ b/tests/test_cli_frame_colorizer.py @@ -0,0 +1,32 @@ +import subprocess +import sys +import pytest + +from deepfuze.download import conditional_download + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4' + ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vframes', '1', '-vf', 'hue=s=0', '../../models/facefusion/examples/target-240p-0sat.jpg' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vf', 'hue=s=0', '../../models/facefusion/examples/target-240p-0sat.mp4' ]) + + +def test_colorize_frame_to_image() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'frame_colorizer', '-t', '../../models/facefusion/examples/target-240p-0sat.jpg', '-o', '../../models/facefusion/examples/test_colorize_frame_to_image.jpg', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'image succeed' in run.stdout.decode() + + +def test_colorize_frame_to_video() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'frame_colorizer', '-t', '../../models/facefusion/examples/target-240p-0sat.mp4', '-o', '../../models/facefusion/examples/test_colorize_frame_to_video.mp4', '--trim-frame-end', '10', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'video succeed' in run.stdout.decode() diff --git a/tests/test_cli_frame_enhancer.py b/tests/test_cli_frame_enhancer.py new file mode 100644 index 0000000..b2b34bc --- /dev/null +++ b/tests/test_cli_frame_enhancer.py @@ -0,0 +1,31 @@ +import subprocess +import sys +import pytest + +from deepfuze.download import conditional_download + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4' + ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vframes', '1', '../../models/facefusion/examples/target-240p.jpg' ]) + + +def test_enhance_frame_to_image() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'frame_enhancer', '-t', '../../models/facefusion/examples/target-240p.jpg', '-o', '../../models/facefusion/examples/test_enhance_frame_to_image.jpg', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'image succeed' in run.stdout.decode() + + +def test_enhance_frame_to_video() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'frame_enhancer', '-t', '../../models/facefusion/examples/target-240p.mp4', '-o', '../../models/facefusion/examples/test_enhance_frame_to_video.mp4', '--trim-frame-end', '10', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'video succeed' in run.stdout.decode() diff --git a/tests/test_cli_lip_syncer.py b/tests/test_cli_lip_syncer.py new file mode 100644 index 0000000..29991d2 --- /dev/null +++ b/tests/test_cli_lip_syncer.py @@ -0,0 +1,32 @@ +import subprocess +import sys +import pytest + +from deepfuze.download import conditional_download + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.mp3', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4' + ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vframes', '1', '../../models/facefusion/examples/target-240p.jpg' ]) + + +def test_sync_lip_to_image() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'lip_syncer', '-s', '../../models/facefusion/examples/source.mp3', '-t', '../../models/facefusion/examples/target-240p.jpg', '-o', '../../models/facefusion/examples/test_sync_lip_to_image.jpg', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'image succeed' in run.stdout.decode() + + +def test_sync_lip_to_video() -> None: + commands = [ sys.executable, 'run.py', '--frame-processors', 'lip_syncer', '-s', '../../models/facefusion/examples/source.mp3', '-t', '../../models/facefusion/examples/target-240p.mp4', '-o', '../../models/facefusion/examples/test_sync_lip_to_video.mp4', '--trim-frame-end', '10', '--headless' ] + run = subprocess.run(commands, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) + + assert run.returncode == 0 + assert 'video succeed' in run.stdout.decode() diff --git a/tests/test_common_helper.py b/tests/test_common_helper.py new file mode 100644 index 0000000..a83f540 --- /dev/null +++ b/tests/test_common_helper.py @@ -0,0 +1,15 @@ +from deepfuze.common_helper import create_metavar, create_int_range, create_float_range + + +def test_create_metavar() -> None: + assert create_metavar([ 1, 2, 3, 4, 5 ]) == '[1-5]' + + +def test_create_int_range() -> None: + assert create_int_range(0, 2, 1) == [ 0, 1, 2 ] + assert create_float_range(0, 1, 1) == [ 0, 1 ] + + +def test_create_float_range() -> None: + assert create_float_range(0.0, 1.0, 0.5) == [ 0.0, 0.5, 1.0 ] + assert create_float_range(0.0, 1.0, 0.05) == [ 0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.35, 0.40, 0.45, 0.50, 0.55, 0.60, 0.65, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95, 1.0 ] diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..f16bcaa --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,96 @@ +from configparser import ConfigParser +import pytest + +from deepfuze import config + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + config.CONFIG = ConfigParser() + config.CONFIG.read_dict( + { + 'str': + { + 'valid': 'a', + 'unset': '' + }, + 'int': + { + 'valid': '1', + 'unset': '' + }, + 'float': + { + 'valid': '1.0', + 'unset': '' + }, + 'bool': + { + 'valid': 'True', + 'unset': '' + }, + 'str_list': + { + 'valid': 'a b c', + 'unset': '' + }, + 'int_list': + { + 'valid': '1 2 3', + 'unset': '' + }, + 'float_list': + { + 'valid': '1.0 2.0 3.0', + 'unset': '' + } + }) + + +def test_get_str_value() -> None: + assert config.get_str_value('str.valid') == 'a' + assert config.get_str_value('str.unset', 'b') == 'b' + assert config.get_str_value('str.unset') is None + assert config.get_str_value('str.invalid') is None + + +def test_get_int_value() -> None: + assert config.get_int_value('int.valid') == 1 + assert config.get_int_value('int.unset', '1') == 1 + assert config.get_int_value('int.unset') is None + assert config.get_int_value('int.invalid') is None + + +def test_get_float_value() -> None: + assert config.get_float_value('float.valid') == 1.0 + assert config.get_float_value('float.unset', '1.0') == 1.0 + assert config.get_float_value('float.unset') is None + assert config.get_float_value('float.invalid') is None + + +def test_get_bool_value() -> None: + assert config.get_bool_value('bool.valid') is True + assert config.get_bool_value('bool.unset', 'False') is False + assert config.get_bool_value('bool.unset') is None + assert config.get_bool_value('bool.invalid') is None + + +def test_get_str_list() -> None: + assert config.get_str_list('str_list.valid') == [ 'a', 'b', 'c' ] + assert config.get_str_list('str_list.unset', 'c b a') == [ 'c', 'b', 'a' ] + assert config.get_str_list('str_list.unset') is None + assert config.get_str_list('str_list.invalid') is None + + +def test_get_int_list() -> None: + assert config.get_int_list('int_list.valid') == [ 1, 2, 3 ] + assert config.get_int_list('int_list.unset', '3 2 1') == [ 3, 2, 1 ] + assert config.get_int_list('int_list.unset') is None + assert config.get_int_list('int_list.invalid') is None + + +def test_get_float_list() -> None: + assert config.get_float_list('float_list.valid') == [ 1.0, 2.0, 3.0 ] + assert config.get_float_list('float_list.unset', '3.0 2.0 1.0') == [ 3.0, 2.0, 1.0 ] + assert config.get_float_list('float_list.unset') is None + assert config.get_float_list('float_list.invalid') is None diff --git a/tests/test_download.py b/tests/test_download.py new file mode 100644 index 0000000..69b8abb --- /dev/null +++ b/tests/test_download.py @@ -0,0 +1,23 @@ +import pytest + +from deepfuze.download import conditional_download, get_download_size, is_download_done + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4' + ]) + + +def test_get_download_size() -> None: + assert get_download_size('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4') == 191675 + assert get_download_size('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-360p.mp4') == 370732 + assert get_download_size('invalid') == 0 + + +def test_is_download_done() -> None: + assert is_download_done('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4', '../../models/facefusion/examples/target-240p.mp4') is True + assert is_download_done('https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4', 'invalid') is False + assert is_download_done('invalid', 'invalid') is False diff --git a/tests/test_execution.py b/tests/test_execution.py new file mode 100644 index 0000000..7ad7fcc --- /dev/null +++ b/tests/test_execution.py @@ -0,0 +1,27 @@ +from deepfuze.execution import encode_execution_providers, decode_execution_providers, has_execution_provider, apply_execution_provider_options + + +def test_encode_execution_providers() -> None: + assert encode_execution_providers([ 'CPUExecutionProvider' ]) == [ 'cpu' ] + + +def test_decode_execution_providers() -> None: + assert decode_execution_providers([ 'cpu' ]) == [ 'CPUExecutionProvider' ] + + +def test_has_execution_provider() -> None: + assert has_execution_provider('CPUExecutionProvider') is True + assert has_execution_provider('InvalidExecutionProvider') is False + + +def test_multiple_execution_providers() -> None: + execution_provider_with_options =\ + [ + 'CPUExecutionProvider', + ('CUDAExecutionProvider', + { + 'device_id': '1', + 'cudnn_conv_algo_search': 'DEFAULT' + }) + ] + assert apply_execution_provider_options('1', [ 'CPUExecutionProvider', 'CUDAExecutionProvider' ]) == execution_provider_with_options diff --git a/tests/test_face_analyser.py b/tests/test_face_analyser.py new file mode 100644 index 0000000..62126eb --- /dev/null +++ b/tests/test_face_analyser.py @@ -0,0 +1,103 @@ +import subprocess +import pytest + +import deepfuze.globals +from deepfuze.download import conditional_download +from deepfuze.face_analyser import pre_check, clear_face_analyser, get_one_face +from deepfuze.typing import Face +from deepfuze.vision import read_static_image + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg' + ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/source.jpg', '-vf', 'crop=iw*0.8:ih*0.8', '../../models/facefusion/examples/source-80crop.jpg' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/source.jpg', '-vf', 'crop=iw*0.7:ih*0.7', '../../models/facefusion/examples/source-70crop.jpg' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/source.jpg', '-vf', 'crop=iw*0.6:ih*0.6', '../../models/facefusion/examples/source-60crop.jpg' ]) + + +@pytest.fixture(autouse = True) +def before_each() -> None: + deepfuze.globals.face_detector_score = 0.5 + deepfuze.globals.face_landmarker_score = 0.5 + deepfuze.globals.face_recognizer_model = 'arcface_inswapper' + clear_face_analyser() + + +def test_get_one_face_with_retinaface() -> None: + deepfuze.globals.face_detector_model = 'retinaface' + deepfuze.globals.face_detector_size = '320x320' + + pre_check() + source_paths =\ + [ + '../../models/facefusion/examples/source.jpg', + '../../models/facefusion/examples/source-80crop.jpg', + '../../models/facefusion/examples/source-70crop.jpg', + '../../models/facefusion/examples/source-60crop.jpg' + ] + for source_path in source_paths: + source_frame = read_static_image(source_path) + face = get_one_face(source_frame) + + assert isinstance(face, Face) + + +def test_get_one_face_with_scrfd() -> None: + deepfuze.globals.face_detector_model = 'scrfd' + deepfuze.globals.face_detector_size = '640x640' + + pre_check() + source_paths =\ + [ + '../../models/facefusion/examples/source.jpg', + '../../models/facefusion/examples/source-80crop.jpg', + '../../models/facefusion/examples/source-70crop.jpg', + '../../models/facefusion/examples/source-60crop.jpg' + ] + for source_path in source_paths: + source_frame = read_static_image(source_path) + face = get_one_face(source_frame) + + assert isinstance(face, Face) + + +def test_get_one_face_with_yoloface() -> None: + deepfuze.globals.face_detector_model = 'yoloface' + deepfuze.globals.face_detector_size = '640x640' + + pre_check() + source_paths =\ + [ + '../../models/facefusion/examples/source.jpg', + '../../models/facefusion/examples/source-80crop.jpg', + '../../models/facefusion/examples/source-70crop.jpg', + '../../models/facefusion/examples/source-60crop.jpg' + ] + for source_path in source_paths: + source_frame = read_static_image(source_path) + face = get_one_face(source_frame) + + assert isinstance(face, Face) + + +def test_get_one_face_with_yunet() -> None: + deepfuze.globals.face_detector_model = 'yunet' + deepfuze.globals.face_detector_size = '640x640' + + pre_check() + source_paths =\ + [ + '../../models/facefusion/examples/source.jpg', + '../../models/facefusion/examples/source-80crop.jpg', + '../../models/facefusion/examples/source-70crop.jpg', + '../../models/facefusion/examples/source-60crop.jpg' + ] + for source_path in source_paths: + source_frame = read_static_image(source_path) + face = get_one_face(source_frame) + + assert isinstance(face, Face) diff --git a/tests/test_ffmpeg.py b/tests/test_ffmpeg.py new file mode 100644 index 0000000..2a428d8 --- /dev/null +++ b/tests/test_ffmpeg.py @@ -0,0 +1,113 @@ +import glob +import subprocess +import pytest + +import deepfuze.globals +from deepfuze import process_manager +from deepfuze.filesystem import get_temp_directory_path, create_temp, clear_temp +from deepfuze.download import conditional_download +from deepfuze.ffmpeg import extract_frames, read_audio_buffer + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + process_manager.start() + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.mp3', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4' + ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/source.mp3', '../../models/facefusion/examples/source.wav' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vf', 'fps=25', '../../models/facefusion/examples/target-240p-25fps.mp4' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vf', 'fps=30', '../../models/facefusion/examples/target-240p-30fps.mp4' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vf', 'fps=60', '../../models/facefusion/examples/target-240p-60fps.mp4' ]) + + +@pytest.fixture(scope = 'function', autouse = True) +def before_each() -> None: + deepfuze.globals.trim_frame_start = None + deepfuze.globals.trim_frame_end = None + deepfuze.globals.temp_frame_format = 'jpg' + + +def test_extract_frames() -> None: + target_paths =\ + [ + '../../models/facefusion/examples/target-240p-25fps.mp4', + '../../models/facefusion/examples/target-240p-30fps.mp4', + '../../models/facefusion/examples/target-240p-60fps.mp4' + ] + + for target_path in target_paths: + temp_directory_path = get_temp_directory_path(target_path) + create_temp(target_path) + + assert extract_frames(target_path, '452x240', 30.0) is True + assert len(glob.glob1(temp_directory_path, '*.jpg')) == 324 + + clear_temp(target_path) + + +def test_extract_frames_with_trim_start() -> None: + deepfuze.globals.trim_frame_start = 224 + data_provider =\ + [ + ('../../models/facefusion/examples/target-240p-25fps.mp4', 55), + ('../../models/facefusion/examples/target-240p-30fps.mp4', 100), + ('../../models/facefusion/examples/target-240p-60fps.mp4', 212) + ] + + for target_path, frame_total in data_provider: + temp_directory_path = get_temp_directory_path(target_path) + create_temp(target_path) + + assert extract_frames(target_path, '452x240', 30.0) is True + assert len(glob.glob1(temp_directory_path, '*.jpg')) == frame_total + + clear_temp(target_path) + + +def test_extract_frames_with_trim_start_and_trim_end() -> None: + deepfuze.globals.trim_frame_start = 124 + deepfuze.globals.trim_frame_end = 224 + data_provider =\ + [ + ('../../models/facefusion/examples/target-240p-25fps.mp4', 120), + ('../../models/facefusion/examples/target-240p-30fps.mp4', 100), + ('../../models/facefusion/examples/target-240p-60fps.mp4', 50) + ] + + for target_path, frame_total in data_provider: + temp_directory_path = get_temp_directory_path(target_path) + create_temp(target_path) + + assert extract_frames(target_path, '452x240', 30.0) is True + assert len(glob.glob1(temp_directory_path, '*.jpg')) == frame_total + + clear_temp(target_path) + + +def test_extract_frames_with_trim_end() -> None: + deepfuze.globals.trim_frame_end = 100 + data_provider =\ + [ + ('../../models/facefusion/examples/target-240p-25fps.mp4', 120), + ('../../models/facefusion/examples/target-240p-30fps.mp4', 100), + ('../../models/facefusion/examples/target-240p-60fps.mp4', 50) + ] + + for target_path, frame_total in data_provider: + temp_directory_path = get_temp_directory_path(target_path) + create_temp(target_path) + + assert extract_frames(target_path, '426x240', 30.0) is True + assert len(glob.glob1(temp_directory_path, '*.jpg')) == frame_total + + clear_temp(target_path) + + +def test_read_audio_buffer() -> None: + assert isinstance(read_audio_buffer('../../models/facefusion/examples/source.mp3', 1, 1), bytes) + assert isinstance(read_audio_buffer('../../models/facefusion/examples/source.wav', 1, 1), bytes) + assert read_audio_buffer('../../models/facefusion/examples/invalid.mp3', 1, 1) is None diff --git a/tests/test_filesystem.py b/tests/test_filesystem.py new file mode 100644 index 0000000..dde07aa --- /dev/null +++ b/tests/test_filesystem.py @@ -0,0 +1,90 @@ +import shutil +import pytest + +from deepfuze.common_helper import is_windows +from deepfuze.download import conditional_download +from deepfuze.filesystem import get_file_size, is_file, is_directory, is_audio, has_audio, is_image, has_image, is_video, filter_audio_paths, filter_image_paths, list_directory, sanitize_path_for_windows + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.mp3', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4' + ]) + shutil.copyfile('../../models/facefusion/examples/source.jpg', '../../models/facefusion/examples/söurce.jpg') + + +def test_get_file_size() -> None: + assert get_file_size('../../models/facefusion/examples/source.jpg') > 0 + assert get_file_size('invalid') == 0 + + +def test_is_file() -> None: + assert is_file('../../models/facefusion/examples/source.jpg') is True + assert is_file('../../models/facefusion/examples') is False + assert is_file('invalid') is False + + +def test_is_directory() -> None: + assert is_directory('../../models/facefusion/examples') is True + assert is_directory('../../models/facefusion/examples/source.jpg') is False + assert is_directory('invalid') is False + + +def test_is_audio() -> None: + assert is_audio('../../models/facefusion/examples/source.mp3') is True + assert is_audio('../../models/facefusion/examples/source.jpg') is False + assert is_audio('invalid') is False + + +def test_has_audio() -> None: + assert has_audio([ '../../models/facefusion/examples/source.mp3' ]) is True + assert has_audio([ '../../models/facefusion/examples/source.mp3', '../../models/facefusion/examples/source.jpg' ]) is True + assert has_audio([ '../../models/facefusion/examples/source.jpg', '../../models/facefusion/examples/source.jpg' ]) is False + assert has_audio([ 'invalid' ]) is False + + +def test_is_image() -> None: + assert is_image('../../models/facefusion/examples/source.jpg') is True + assert is_image('../../models/facefusion/examples/target-240p.mp4') is False + assert is_image('invalid') is False + + +def test_has_image() -> None: + assert has_image([ '../../models/facefusion/examples/source.jpg' ]) is True + assert has_image([ '../../models/facefusion/examples/source.jpg', '../../models/facefusion/examples/source.mp3' ]) is True + assert has_image([ '../../models/facefusion/examples/source.mp3', '../../models/facefusion/examples/source.mp3' ]) is False + assert has_image([ 'invalid' ]) is False + + +def test_is_video() -> None: + assert is_video('../../models/facefusion/examples/target-240p.mp4') is True + assert is_video('../../models/facefusion/examples/source.jpg') is False + assert is_video('invalid') is False + + +def test_filter_audio_paths() -> None: + assert filter_audio_paths([ '../../models/facefusion/examples/source.jpg', '../../models/facefusion/examples/source.mp3' ]) == [ '../../models/facefusion/examples/source.mp3' ] + assert filter_audio_paths([ '../../models/facefusion/examples/source.jpg', '../../models/facefusion/examples/source.jpg' ]) == [] + assert filter_audio_paths([ 'invalid' ]) == [] + + +def test_filter_image_paths() -> None: + assert filter_image_paths([ '../../models/facefusion/examples/source.jpg', '../../models/facefusion/examples/source.mp3' ]) == [ '../../models/facefusion/examples/source.jpg' ] + assert filter_image_paths([ '../../models/facefusion/examples/source.mp3', '../../models/facefusion/examples/source.mp3' ]) == [] + assert filter_audio_paths([ 'invalid' ]) == [] + + +def test_list_directory() -> None: + assert list_directory('../../models/facefusion/examples') + assert list_directory('../../models/facefusion/examples/source.jpg') is None + assert list_directory('invalid') is None + + +def test_sanitize_path_for_windows() -> None: + if is_windows(): + assert sanitize_path_for_windows('../../models/facefusion/examples/söurce.jpg') == 'ASSETS~1/examples/SURCE~1.JPG' + assert sanitize_path_for_windows('invalid') is None diff --git a/tests/test_memory.py b/tests/test_memory.py new file mode 100644 index 0000000..36a3619 --- /dev/null +++ b/tests/test_memory.py @@ -0,0 +1,8 @@ +from deepfuze.common_helper import is_linux, is_macos +from deepfuze.memory import limit_system_memory + + +def test_limit_system_memory() -> None: + assert limit_system_memory(4) is True + if is_linux() or is_macos(): + assert limit_system_memory(1024) is False diff --git a/tests/test_normalizer.py b/tests/test_normalizer.py new file mode 100644 index 0000000..8c7aa5c --- /dev/null +++ b/tests/test_normalizer.py @@ -0,0 +1,30 @@ +from deepfuze.common_helper import is_linux, is_macos +from deepfuze.normalizer import normalize_output_path, normalize_padding, normalize_fps + + +def test_normalize_output_path() -> None: + if is_linux() or is_macos(): + assert normalize_output_path('../../models/facefusion/examples/target-240p.mp4', '../../models/facefusion/examples/target-240p.mp4') == '../../models/facefusion/examples/target-240p.mp4' + assert normalize_output_path('../../models/facefusion/examples/target-240p.mp4', '../../models/facefusion/examples').startswith('../../models/facefusion/examples/target-240p') + assert normalize_output_path('../../models/facefusion/examples/target-240p.mp4', '../../models/facefusion/examples').endswith('.mp4') + assert normalize_output_path('../../models/facefusion/examples/target-240p.mp4', '../../models/facefusion/examples/output.mp4') == '../../models/facefusion/examples/output.mp4' + assert normalize_output_path('../../models/facefusion/examples/target-240p.mp4', '../../models/facefusion/examples/invalid') is None + assert normalize_output_path('../../models/facefusion/examples/target-240p.mp4', '../../models/facefusion/invalid/output.mp4') is None + assert normalize_output_path('../../models/facefusion/examples/target-240p.mp4', 'invalid') is None + assert normalize_output_path('../../models/facefusion/examples/target-240p.mp4', None) is None + assert normalize_output_path(None, '../../models/facefusion/examples/output.mp4') is None + + +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_fps() -> None: + assert normalize_fps(0.0) == 1.0 + assert normalize_fps(25.0) == 25.0 + assert normalize_fps(61.0) == 60.0 + assert normalize_fps(None) is None diff --git a/tests/test_process_manager.py b/tests/test_process_manager.py new file mode 100644 index 0000000..b5f783d --- /dev/null +++ b/tests/test_process_manager.py @@ -0,0 +1,22 @@ +from deepfuze.process_manager import set_process_state, is_processing, is_stopping, is_pending, start, stop, end + + +def test_start() -> None: + set_process_state('pending') + start() + + assert is_processing() + + +def test_stop() -> None: + set_process_state('processing') + stop() + + assert is_stopping() + + +def test_end() -> None: + set_process_state('processing') + end() + + assert is_pending() diff --git a/tests/test_vision.py b/tests/test_vision.py new file mode 100644 index 0000000..1faf6b5 --- /dev/null +++ b/tests/test_vision.py @@ -0,0 +1,109 @@ +import subprocess +import pytest + +from deepfuze.download import conditional_download +from deepfuze.vision import detect_image_resolution, restrict_image_resolution, create_image_resolutions, get_video_frame, count_video_frame_total, detect_video_fps, restrict_video_fps, detect_video_resolution, restrict_video_resolution, create_video_resolutions, normalize_resolution, pack_resolution, unpack_resolution + + +@pytest.fixture(scope = 'module', autouse = True) +def before_all() -> None: + conditional_download('../../models/facefusion/examples', + [ + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/source.jpg', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-240p.mp4', + 'https://github.com/facefusion/facefusion-assets/releases/download/examples/target-1080p.mp4' + ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vframes', '1', '../../models/facefusion/examples/target-240p.jpg' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-1080p.mp4', '-vframes', '1', '../../models/facefusion/examples/target-1080p.jpg' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vframes', '1', '-vf', 'transpose=0', '../../models/facefusion/examples/target-240p-90deg.jpg' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-1080p.mp4', '-vframes', '1', '-vf', 'transpose=0', '../../models/facefusion/examples/target-1080p-90deg.jpg' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vf', 'fps=25', '../../models/facefusion/examples/target-240p-25fps.mp4' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vf', 'fps=30', '../../models/facefusion/examples/target-240p-30fps.mp4' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vf', 'fps=60', '../../models/facefusion/examples/target-240p-60fps.mp4' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-240p.mp4', '-vf', 'transpose=0', '../../models/facefusion/examples/target-240p-90deg.mp4' ]) + subprocess.run([ 'ffmpeg', '-i', '../../models/facefusion/examples/target-1080p.mp4', '-vf', 'transpose=0', '../../models/facefusion/examples/target-1080p-90deg.mp4' ]) + + +def test_detect_image_resolution() -> None: + assert detect_image_resolution('../../models/facefusion/examples/target-240p.jpg') == (426, 226) + assert detect_image_resolution('../../models/facefusion/examples/target-240p-90deg.jpg') == (226, 426) + assert detect_image_resolution('../../models/facefusion/examples/target-1080p.jpg') == (2048, 1080) + assert detect_image_resolution('../../models/facefusion/examples/target-1080p-90deg.jpg') == (1080, 2048) + assert detect_image_resolution('invalid') is None + + +def test_restrict_image_resolution() -> None: + assert restrict_image_resolution('../../models/facefusion/examples/target-1080p.jpg', (426, 226)) == (426, 226) + assert restrict_image_resolution('../../models/facefusion/examples/target-1080p.jpg', (2048, 1080)) == (2048, 1080) + assert restrict_image_resolution('../../models/facefusion/examples/target-1080p.jpg', (4096, 2160)) == (2048, 1080) + + +def test_create_image_resolutions() -> None: + assert create_image_resolutions((426, 226)) == [ '106x56', '212x112', '320x170', '426x226', '640x340', '852x452', '1064x564', '1278x678', '1492x792', '1704x904' ] + assert create_image_resolutions((226, 426)) == [ '56x106', '112x212', '170x320', '226x426', '340x640', '452x852', '564x1064', '678x1278', '792x1492', '904x1704' ] + assert create_image_resolutions((2048, 1080)) == [ '512x270', '1024x540', '1536x810', '2048x1080', '3072x1620', '4096x2160', '5120x2700', '6144x3240', '7168x3780', '8192x4320' ] + assert create_image_resolutions((1080, 2048)) == [ '270x512', '540x1024', '810x1536', '1080x2048', '1620x3072', '2160x4096', '2700x5120', '3240x6144', '3780x7168', '4320x8192' ] + assert create_image_resolutions(None) == [] + + +def test_get_video_frame() -> None: + assert get_video_frame('../../models/facefusion/examples/target-240p-25fps.mp4') is not None + assert get_video_frame('invalid') is None + + +def test_count_video_frame_total() -> None: + assert count_video_frame_total('../../models/facefusion/examples/target-240p-25fps.mp4') == 270 + assert count_video_frame_total('../../models/facefusion/examples/target-240p-30fps.mp4') == 324 + assert count_video_frame_total('../../models/facefusion/examples/target-240p-60fps.mp4') == 648 + assert count_video_frame_total('invalid') == 0 + + +def test_detect_video_fps() -> None: + assert detect_video_fps('../../models/facefusion/examples/target-240p-25fps.mp4') == 25.0 + assert detect_video_fps('../../models/facefusion/examples/target-240p-30fps.mp4') == 30.0 + assert detect_video_fps('../../models/facefusion/examples/target-240p-60fps.mp4') == 60.0 + assert detect_video_fps('invalid') is None + + +def test_restrict_video_fps() -> None: + assert restrict_video_fps('../../models/facefusion/examples/target-1080p.mp4', 20.0) == 20.0 + assert restrict_video_fps('../../models/facefusion/examples/target-1080p.mp4', 25.0) == 25.0 + assert restrict_video_fps('../../models/facefusion/examples/target-1080p.mp4', 60.0) == 25.0 + + +def test_detect_video_resolution() -> None: + assert detect_video_resolution('../../models/facefusion/examples/target-240p.mp4') == (426, 226) + assert detect_video_resolution('../../models/facefusion/examples/target-240p-90deg.mp4') == (226, 426) + assert detect_video_resolution('../../models/facefusion/examples/target-1080p.mp4') == (2048, 1080) + assert detect_video_resolution('../../models/facefusion/examples/target-1080p-90deg.mp4') == (1080, 2048) + assert detect_video_resolution('invalid') is None + + +def test_restrict_video_resolution() -> None: + assert restrict_video_resolution('../../models/facefusion/examples/target-1080p.mp4', (426, 226)) == (426, 226) + assert restrict_video_resolution('../../models/facefusion/examples/target-1080p.mp4', (2048, 1080)) == (2048, 1080) + assert restrict_video_resolution('../../models/facefusion/examples/target-1080p.mp4', (4096, 2160)) == (2048, 1080) + + +def test_create_video_resolutions() -> None: + assert create_video_resolutions((426, 226)) == [ '426x226', '452x240', '678x360', '904x480', '1018x540', '1358x720', '2036x1080', '2714x1440', '4072x2160', '8144x4320' ] + assert create_video_resolutions((226, 426)) == [ '226x426', '240x452', '360x678', '480x904', '540x1018', '720x1358', '1080x2036', '1440x2714', '2160x4072', '4320x8144' ] + assert create_video_resolutions((2048, 1080)) == [ '456x240', '682x360', '910x480', '1024x540', '1366x720', '2048x1080', '2730x1440', '4096x2160', '8192x4320' ] + assert create_video_resolutions((1080, 2048)) == [ '240x456', '360x682', '480x910', '540x1024', '720x1366', '1080x2048', '1440x2730', '2160x4096', '4320x8192' ] + assert create_video_resolutions(None) == [] + + +def test_normalize_resolution() -> None: + assert normalize_resolution((2.5, 2.5)) == (2, 2) + assert normalize_resolution((3.0, 3.0)) == (4, 4) + assert normalize_resolution((6.5, 6.5)) == (6, 6) + + +def test_pack_resolution() -> None: + assert pack_resolution((1, 1)) == '0x0' + assert pack_resolution((2, 2)) == '2x2' + + +def test_unpack_resolution() -> None: + assert unpack_resolution('0x0') == (0, 0) + assert unpack_resolution('2x2') == (2, 2) diff --git a/tests/test_wording.py b/tests/test_wording.py new file mode 100644 index 0000000..7813345 --- /dev/null +++ b/tests/test_wording.py @@ -0,0 +1,7 @@ +from deepfuze import wording + + +def test_get() -> None: + assert wording.get('python_not_supported') + assert wording.get('help.source') + assert wording.get('invalid') is None diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..5d7c284 --- /dev/null +++ b/utils.py @@ -0,0 +1,234 @@ +import hashlib +import os +from typing import Iterable +import shutil +import subprocess +import re + +import server + +BIGMIN = -(2**53-1) +BIGMAX = (2**53-1) + +DIMMAX = 8192 + +def ffmpeg_suitability(path): + try: + version = subprocess.run([path, "-version"], check=True, + capture_output=True).stdout.decode("utf-8") + except: + return 0 + score = 0 + #rough layout of the importance of various features + simple_criterion = [("libvpx", 20),("264",10), ("265",3), + ("svtav1",5),("libopus", 1)] + for criterion in simple_criterion: + if version.find(criterion[0]) >= 0: + score += criterion[1] + #obtain rough compile year from copyright information + copyright_index = version.find('2000-2') + if copyright_index >= 0: + copyright_year = version[copyright_index+6:copyright_index+9] + if copyright_year.isnumeric(): + score += int(copyright_year) + return score + + +if "VHS_FORCE_FFMPEG_PATH" in os.environ: + ffmpeg_path = os.environ.get("VHS_FORCE_FFMPEG_PATH") +else: + ffmpeg_paths = [] + try: + from imageio_ffmpeg import get_ffmpeg_exe + imageio_ffmpeg_path = get_ffmpeg_exe() + ffmpeg_paths.append(imageio_ffmpeg_path) + except: + if "VHS_USE_IMAGEIO_FFMPEG" in os.environ: + raise + logger.warn("Failed to import imageio_ffmpeg") + if "VHS_USE_IMAGEIO_FFMPEG" in os.environ: + ffmpeg_path = imageio_ffmpeg_path + else: + system_ffmpeg = shutil.which("ffmpeg") + if system_ffmpeg is not None: + ffmpeg_paths.append(system_ffmpeg) + if os.path.isfile("ffmpeg"): + ffmpeg_paths.append(os.path.abspath("ffmpeg")) + if os.path.isfile("ffmpeg.exe"): + ffmpeg_paths.append(os.path.abspath("ffmpeg.exe")) + if len(ffmpeg_paths) == 0: + logger.error("No valid ffmpeg found.") + ffmpeg_path = None + elif len(ffmpeg_paths) == 1: + #Evaluation of suitability isn't required, can take sole option + #to reduce startup time + ffmpeg_path = ffmpeg_paths[0] + else: + ffmpeg_path = max(ffmpeg_paths, key=ffmpeg_suitability) +gifski_path = os.environ.get("VHS_GIFSKI", None) +if gifski_path is None: + gifski_path = os.environ.get("JOV_GIFSKI", None) + if gifski_path is None: + gifski_path = shutil.which("gifski") + +def is_safe_path(path): + if "VHS_STRICT_PATHS" not in os.environ: + return True + basedir = os.path.abspath('.') + try: + common_path = os.path.commonpath([basedir, path]) + except: + #Different drive on windows + return False + return common_path == basedir + +def get_sorted_dir_files_from_directory(directory: str, skip_first_images: int=0, select_every_nth: int=1, extensions: Iterable=None): + directory = strip_path(directory) + dir_files = os.listdir(directory) + dir_files = sorted(dir_files) + dir_files = [os.path.join(directory, x) for x in dir_files] + dir_files = list(filter(lambda filepath: os.path.isfile(filepath), dir_files)) + # filter by extension, if needed + if extensions is not None: + extensions = list(extensions) + new_dir_files = [] + for filepath in dir_files: + ext = "." + filepath.split(".")[-1] + if ext.lower() in extensions: + new_dir_files.append(filepath) + dir_files = new_dir_files + # start at skip_first_images + dir_files = dir_files[skip_first_images:] + dir_files = dir_files[0::select_every_nth] + return dir_files + + +# modified from https://stackoverflow.com/questions/22058048/hashing-a-file-in-python +def calculate_file_hash(filename: str, hash_every_n: int = 1): + #Larger video files were taking >.5 seconds to hash even when cached, + #so instead the modified time from the filesystem is used as a hash + h = hashlib.sha256() + h.update(filename.encode()) + h.update(str(os.path.getmtime(filename)).encode()) + return h.hexdigest() + +prompt_queue = server.PromptServer.instance.prompt_queue +def requeue_workflow_unchecked(): + """Requeues the current workflow without checking for multiple requeues""" + currently_running = prompt_queue.currently_running + (_, _, prompt, extra_data, outputs_to_execute) = next(iter(currently_running.values())) + + #Ensure batch_managers are marked stale + prompt = prompt.copy() + for uid in prompt: + if prompt[uid]['class_type'] == 'VHS_BatchManager': + prompt[uid]['inputs']['requeue'] = prompt[uid]['inputs'].get('requeue',0)+1 + + #execution.py has guards for concurrency, but server doesn't. + #TODO: Check that this won't be an issue + number = -server.PromptServer.instance.number + server.PromptServer.instance.number += 1 + prompt_id = str(server.uuid.uuid4()) + prompt_queue.put((number, prompt_id, prompt, extra_data, outputs_to_execute)) + +requeue_guard = [None, 0, 0, {}] +def requeue_workflow(requeue_required=(-1,True)): + assert(len(prompt_queue.currently_running) == 1) + global requeue_guard + (run_number, _, prompt, _, _) = next(iter(prompt_queue.currently_running.values())) + if requeue_guard[0] != run_number: + #Calculate a count of how many outputs are managed by a batch manager + managed_outputs=0 + for bm_uid in prompt: + if prompt[bm_uid]['class_type'] == 'VHS_BatchManager': + for output_uid in prompt: + if prompt[output_uid]['class_type'] in ["VHS_VideoCombine"]: + for inp in prompt[output_uid]['inputs'].values(): + if inp == [bm_uid, 0]: + managed_outputs+=1 + requeue_guard = [run_number, 0, managed_outputs, {}] + requeue_guard[1] = requeue_guard[1]+1 + requeue_guard[3][requeue_required[0]] = requeue_required[1] + if requeue_guard[1] == requeue_guard[2] and max(requeue_guard[3].values()): + requeue_workflow_unchecked() + +def get_audio(file, start_time=0, duration=0): + args = [ffmpeg_path, "-v", "error", "-i", file] + if start_time > 0: + args += ["-ss", str(start_time)] + if duration > 0: + args += ["-t", str(duration)] + try: + res = subprocess.run(args + ["-f", "wav", "-"], + stdout=subprocess.PIPE, check=True).stdout + except subprocess.CalledProcessError as e: + logger.warning(f"Failed to extract audio from: {file}") + return False + return res + + +def lazy_eval(func): + class Cache: + def __init__(self, func): + self.res = None + self.func = func + def get(self): + if self.res is None: + self.res = self.func() + return self.res + cache = Cache(func) + return lambda : cache.get() + + +def is_url(url): + return url.split("://")[0] in ["http", "https"] + +def validate_sequence(path): + #Check if path is a valid ffmpeg sequence that points to at least one file + (path, file) = os.path.split(path) + if not os.path.isdir(path): + return False + match = re.search('%0?\\d+d', file) + if not match: + return False + seq = match.group() + if seq == '%d': + seq = '\\\\d+' + else: + seq = '\\\\d{%s}' % seq[1:-1] + file_matcher = re.compile(re.sub('%0?\\d+d', seq, file)) + for file in os.listdir(path): + if file_matcher.fullmatch(file): + return True + return False + +def strip_path(path): + #This leaves whitespace inside quotes and only a single " + #thus ' ""test"' -> '"test' + #consider path.strip(string.whitespace+"\"") + #or weightier re.fullmatch("[\\s\"]*(.+?)[\\s\"]*", path).group(1) + path = path.strip() + if path.startswith("\""): + path = path[1:] + if path.endswith("\""): + path = path[:-1] + return path +def hash_path(path): + if path is None: + return "input" + if is_url(path): + return "url" + return calculate_file_hash(strip_path(path)) + + +def validate_path(path, allow_none=False, allow_url=True): + if path is None: + return allow_none + if is_url(path): + #Probably not feasible to check if url resolves here + if not allow_url: + return "URLs are unsupported for this path" + return is_safe_path(path) + if not os.path.isfile(strip_path(path)): + return "Invalid file path: {}".format(path) + return is_safe_path(path)