Disabling FaceFusion's NSFW Filter from Source Code

7 Jun 2026·
赵吉忱
赵吉忱
· 4 min read
blog Year 2026

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.

Caution

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. 🧪

Note

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.py calls analyse_image() during setup.
  • image_to_video.py calls analyse_video() during setup.
  • analyse_frame() delegates to detect_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. 🧠