Disabling FaceFusion's NSFW Filter from Source Code
FaceFusion 3.6.1 has a built-in content analyser. In the source I checked, image and video workflows call it before doing the main processing work. If the analyser says the target is NSFW, the workflow returns early.
This blog records a tiny local patch to disable that gate from source code.
I only do this for research on local model behaviour and source-code analysis, not for anything illegal, non-consensual, or prohibited for anyone under 18. In short: lab coat on, age gate on, common sense on. The lab coat may be imaginary; the age gate is not. 🧪
The examples below are based on FaceFusion 3.6.1.
Table of Contents
Find where FaceFusion checks content
In the image workflow, setup calls analyse_image() before creating temporary files:
def setup() -> ErrorCode:
if analyse_image(state_manager.get_item('target_path')):
return 3
The video workflow does the same with analyse_video():
def setup() -> ErrorCode:
trim_frame_start, trim_frame_end = restrict_trim_frame(state_manager.get_item('target_path'), state_manager.get_item('trim_frame_start'), state_manager.get_item('trim_frame_end'))
if analyse_video(state_manager.get_item('target_path'), trim_frame_start, trim_frame_end):
return 3
Both paths eventually land in facefusion/content_analyser.py. For a single frame, the call chain is short:
def analyse_frame(vision_frame : VisionFrame) -> bool:
return detect_nsfw(vision_frame)
So the practical switch is detect_nsfw().
Make detect_nsfw() always return False
Originally, detect_nsfw() combined three detector results and returned true when at least two detectors agreed:
def detect_nsfw(vision_frame : VisionFrame) -> bool:
is_nsfw_1 = detect_with_nsfw_1(vision_frame)
is_nsfw_2 = detect_with_nsfw_2(vision_frame)
is_nsfw_3 = detect_with_nsfw_3(vision_frame)
return is_nsfw_1 and is_nsfw_2 or is_nsfw_1 and is_nsfw_3 or is_nsfw_2 and is_nsfw_3
For my local research checkout, I changed it to:
def detect_nsfw(vision_frame : VisionFrame) -> bool:
return False
That is intentionally blunt. analyse_image() and analyse_video() still run, but the underlying frame result is always “not NSFW”, so the early return 3 branch is not triggered by this analyser.
Remove the content analyser hash guard
There is one more check in facefusion/core.py. During common_pre_check(), FaceFusion hashes the source of the content_analyser module:
content_analyser_content = inspect.getsource(content_analyser).encode()
content_analyser_hash = hash_helper.create_hash(content_analyser_content)
return all(module.pre_check() for module in common_modules) and content_analyser_hash == 'b14e7b92'
After editing content_analyser.py, that hash no longer matches. So the second local change is to stop enforcing the original hash:
content_analyser_content = inspect.getsource(content_analyser).encode()
content_analyser_hash = hash_helper.create_hash(content_analyser_content)
return all(module.pre_check() for module in common_modules)
Strictly speaking, the two hash-related lines become unused after this edit. I left them there in my quick patch because the goal was to change as little runtime behaviour as possible outside the guard itself. If I were maintaining a long-lived fork, I would either remove the unused lines or replace this with a proper configuration flag.
Review the final diff
The whole patch is small:
diff --git a/facefusion/content_analyser.py b/facefusion/content_analyser.py
@@
def detect_nsfw(vision_frame : VisionFrame) -> bool:
- is_nsfw_1 = detect_with_nsfw_1(vision_frame)
- is_nsfw_2 = detect_with_nsfw_2(vision_frame)
- is_nsfw_3 = detect_with_nsfw_3(vision_frame)
-
- return is_nsfw_1 and is_nsfw_2 or is_nsfw_1 and is_nsfw_3 or is_nsfw_2 and is_nsfw_3
+ return False
diff --git a/facefusion/core.py b/facefusion/core.py
@@
- return all(module.pre_check() for module in common_modules) and content_analyser_hash == 'b14e7b92'
+ return all(module.pre_check() for module in common_modules)
Verify the patch locally
First, confirm only the intended files changed:
git -C /path/to/facefusion diff --stat
git -C /path/to/facefusion diff -- facefusion/content_analyser.py facefusion/core.py
Then run FaceFusion from that modified checkout and retry the input that previously stopped at the content analyser gate.
For a quick source-level sanity check, I also like searching the call sites:
rg -n "analyse_image|analyse_video|detect_nsfw|content_analyser_hash" facefusion
If rg is not available, use plain grep instead:
grep -RInE "analyse_image|analyse_video|detect_nsfw|content_analyser_hash" facefusion
In my checkout, the important shape is:
image_to_image.pycallsanalyse_image()during setup.image_to_video.pycallsanalyse_video()during setup.analyse_frame()delegates todetect_nsfw().common_pre_check()no longer rejects the edited analyser source by hash.
Keep the patch local
This is the kind of modification I would keep as a local research patch, not something to casually publish as a default behaviour change. Filters can be annoying when they block legitimate experiments, but they also exist because image-generation and face-processing tools can be abused very easily.
So yes, this is how I disabled the gate in source code. No, this is not a permission slip for creepy things. If the target is non-consensual, illegal, or involves anyone under 18, the correct patch is not in Python. It is in the user’s judgement. Please upgrade that dependency first. 🧠
