Spaces:
Running
Running
from dataclasses import dataclass | |
from typing import Optional | |
from pathlib import Path | |
import tempfile | |
import gradio as gr | |
from models.face_detector import YOLOFaceDetector | |
from masking.text import TextCensor | |
from masking.emoji import EmojiCensor | |
from masking.blur import BlurCensor | |
from processors.processor import MediaProcessor | |
class CensorSettings: | |
type: Optional[str] = None | |
# Blur settings | |
blur_factor: Optional[int] = None | |
# Emoji settings | |
emoji: Optional[str] = None | |
emoji_scale: Optional[float] = None | |
# Text settings | |
text: Optional[str] = None | |
text_scale: Optional[float] = None | |
draw_background: Optional[bool] = None | |
background_color: Optional[str] = None | |
text_color: Optional[str] = None | |
font: Optional[str] = None | |
class FaceCensorUI: | |
CENSOR_TYPES = ["Blur", "Text", "Emoji"] | |
FONTS = ["arial.ttf", "times.ttf"] | |
DEFAULT_SETTINGS = { | |
"blur_factor": 99, | |
"emoji": "😁", | |
"emoji_scale": 1.0, | |
"text": "Hello", | |
"text_scale": 0.2, | |
"draw_background": True, | |
"background_color": "#000000", | |
"text_color": "#00FF00", | |
"font": "arial.ttf" | |
} | |
def __init__(self): | |
self.detector = YOLOFaceDetector() | |
def create_censor(self, settings): | |
# Map censor name to censor object | |
censor_map = { | |
"Blur": lambda: BlurCensor(blur_factor=settings.blur_factor), | |
"Emoji": lambda: EmojiCensor( | |
emoji=settings.emoji, | |
scale_factor=settings.emoji_scale, | |
font="seguiemj.ttf" | |
), | |
"Text": lambda: TextCensor( | |
text=settings.text, | |
scale_factor=settings.text_scale, | |
draw_background=settings.draw_background, | |
background_color=settings.background_color, | |
text_color=settings.text_color, | |
font=settings.font or "arial.ttf" | |
) | |
} | |
if settings.type not in censor_map: | |
raise ValueError(f"Unknown censor type: {settings.type}") | |
return censor_map[settings.type]() | |
def process_file(self, input_media, settings_dict): | |
try: | |
settings = CensorSettings(**settings_dict) | |
censor = self.create_censor(settings) | |
processor = MediaProcessor(self.detector, censor) | |
temp_dir = Path(tempfile.mkdtemp()) | |
input_path = Path(input_media.name) | |
if input_path.suffix.lower() in ('.mp4', '.avi', '.mov'): | |
output_path = temp_dir / "output.mp4" | |
processor.process_video(str(input_path), str(output_path)) | |
else: | |
output_path = temp_dir / "output.jpg" | |
processor.process_image(str(input_path), str(output_path)) | |
return str(output_path) | |
except Exception as e: | |
return f"Error processing media: {str(e)}" | |
def create_ui(self): | |
with gr.Blocks(title="Face Censor") as ui: | |
gr.Markdown("# Censor Faces Automatically!") | |
with gr.Tab("File Processing"): | |
self._create_media_section() | |
self._create_settings_section() | |
with gr.Row(): | |
gr.Markdown( | |
""" | |
## [View source code on GitHub](https://github.com/Spring-0/face-censor) | |
*⭐ the repo to show support* | |
""" | |
) | |
return ui | |
def _create_media_section(self): | |
with gr.Row(): | |
self.input_media = gr.File(label="Upload Image/Video") | |
self.output_media = gr.File(label="Processed Output") | |
def _create_settings_section(self): | |
self.censor_type = gr.Dropdown( | |
choices=self.CENSOR_TYPES, | |
value=self.CENSOR_TYPES[0], | |
label="Censoring Method" | |
) | |
self.blur_settings = self._create_blur_settings() | |
self.emoji_settings = self._create_emoji_settings() | |
self.text_settings = self._create_text_settings() | |
self.process_btn = gr.Button("Process") | |
self.censor_type.change( | |
self._update_settings_visibility, | |
inputs=[self.censor_type], | |
outputs=[self.blur_settings, self.emoji_settings, self.text_settings] | |
) | |
self.process_btn.click( | |
self._handle_processing, | |
inputs=self._get_all_inputs(), | |
outputs=[self.output_media] | |
) | |
def _create_blur_settings(self): | |
with gr.Row(visible=True) as section: | |
self.blur_factor = gr.Slider( | |
minimum=1, | |
maximum=199, | |
value=self.DEFAULT_SETTINGS["blur_factor"], | |
step=2, | |
label="Blur Factor" | |
) | |
return section | |
def _create_emoji_settings(self): | |
with gr.Row(visible=False) as section: | |
self.emoji = gr.Textbox( | |
value=self.DEFAULT_SETTINGS["emoji"], | |
label="Emoji" | |
) | |
self.emoji_scale = gr.Slider( | |
minimum=0.1, | |
maximum=2.0, | |
value=self.DEFAULT_SETTINGS["emoji_scale"], | |
step=0.1, | |
label="Emoji Scale Factor" | |
) | |
return section | |
def _create_text_settings(self): | |
with gr.Column(visible=False) as section: | |
with gr.Row(equal_height=True): | |
self.text = gr.Textbox( | |
value=self.DEFAULT_SETTINGS["text"], | |
label="Text" | |
) | |
self.text_scale = gr.Slider( | |
minimum=0.1, | |
maximum=2.0, | |
value=self.DEFAULT_SETTINGS["text_scale"], | |
step=0.1, | |
label="Text Scale Factor" | |
) | |
with gr.Row(equal_height=True): | |
self.draw_background = gr.Checkbox( | |
value=self.DEFAULT_SETTINGS["draw_background"], | |
label="Draw Background", | |
) | |
self.background_color = gr.ColorPicker( | |
value=self.DEFAULT_SETTINGS["background_color"], | |
label="Background Color" | |
) | |
with gr.Row(equal_height=True): | |
self.text_color = gr.ColorPicker( | |
value=self.DEFAULT_SETTINGS["text_color"], | |
label="Text Color" | |
) | |
self.font = gr.Dropdown( | |
choices=self.FONTS, | |
value=self.DEFAULT_SETTINGS["font"], | |
label="Font" | |
) | |
return section | |
def _update_settings_visibility(self, censor_choice): | |
return [ | |
gr.Row(visible=censor_choice=="Blur"), | |
gr.Row(visible=censor_choice=="Emoji"), | |
gr.Row(visible=censor_choice=="Text") | |
] | |
def _get_all_inputs(self): | |
return [ | |
self.input_media, | |
self.censor_type, | |
self.blur_factor, | |
self.emoji, | |
self.emoji_scale, | |
self.text, | |
self.text_scale, | |
self.draw_background, | |
self.background_color, | |
self.text_color, | |
self.font | |
] | |
def _handle_processing(self, *args): | |
input_media = args[0] | |
settings_dict = { | |
"type": args[1], | |
"blur_factor": args[2], | |
"emoji": args[3], | |
"emoji_scale": args[4], | |
"text": args[5], | |
"text_scale": args[6], | |
"draw_background": args[7], | |
"background_color": args[8], | |
"text_color": args[9], | |
"font": args[10] | |
} | |
return self.process_file(input_media, settings_dict) | |