|
|
|
|
|
import os |
|
import gradio as gr |
|
from refacer import Refacer |
|
import imageio |
|
from PIL import Image |
|
import tempfile |
|
import base64 |
|
import shutil |
|
import time |
|
|
|
|
|
NUM_FACES = 8 |
|
FORCE_CPU = True |
|
|
|
|
|
refacer = Refacer(force_cpu=FORCE_CPU) |
|
|
|
|
|
def run_image(*vars): |
|
image_path = vars[0] |
|
origins = vars[1:(NUM_FACES + 1)] |
|
destinations = vars[(NUM_FACES + 1):(NUM_FACES * 2) + 1] |
|
thresholds = vars[(NUM_FACES * 2) + 1:-2] |
|
face_mode = vars[-2] |
|
partial_reface_ratio = vars[-1] |
|
disable_similarity = (face_mode in ["Single Face", "Multiple Faces"]) |
|
multiple_faces_mode = (face_mode == "Multiple Faces") |
|
faces = [{'origin': origins[k] if not multiple_faces_mode else None, 'destination': destinations[k], 'threshold': thresholds[k] if not multiple_faces_mode else 0.0} for k in range(NUM_FACES) if destinations[k] is not None] |
|
return refacer.reface_image(image_path, faces, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode, partial_reface_ratio=partial_reface_ratio) |
|
|
|
def run_video(*vars): |
|
video_path = vars[0] |
|
origins = vars[1:(NUM_FACES + 1)] |
|
destinations = vars[(NUM_FACES + 1):(NUM_FACES * 2) + 1] |
|
thresholds = vars[(NUM_FACES * 2) + 1:-3] |
|
preview = vars[-3] |
|
face_mode = vars[-2] |
|
partial_reface_ratio = vars[-1] |
|
disable_similarity = (face_mode in ["Single Face", "Multiple Faces"]) |
|
multiple_faces_mode = (face_mode == "Multiple Faces") |
|
faces = [{'origin': origins[k] if not multiple_faces_mode else None, 'destination': destinations[k], 'threshold': thresholds[k] if not multiple_faces_mode else 0.0} for k in range(NUM_FACES) if destinations[k] is not None] |
|
mp4_path, gif_path = refacer.reface(video_path, faces, preview=preview, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode, partial_reface_ratio=partial_reface_ratio) |
|
return mp4_path, gif_path |
|
|
|
def load_first_frame(filepath): |
|
if filepath is None: return None |
|
with imageio.get_reader(filepath) as reader: |
|
return reader.get_data(0) |
|
|
|
def extract_faces_auto(filepath, max_faces=5, isvideo=False): |
|
if filepath is None: return [None] * max_faces |
|
if isvideo and os.path.getsize(filepath) > 10 * 1024 * 1024: |
|
print("Video too large for auto-extract.") |
|
return [None] * max_faces |
|
try: |
|
frame = load_first_frame(filepath) |
|
faces = refacer.extract_faces_from_image(frame, max_faces=max_faces) |
|
return faces + [None] * (max_faces - len(faces)) |
|
except Exception as e: |
|
print(f"Could not extract faces: {e}") |
|
return [None] * max_faces |
|
|
|
def toggle_tabs_and_faces(mode): |
|
if mode == "Single Face": |
|
return [gr.update(visible=(i == 0)) for i in range(NUM_FACES)] + [gr.update(visible=False)] * NUM_FACES |
|
elif mode == "Multiple Faces": |
|
return [gr.update(visible=True)] * NUM_FACES + [gr.update(visible=False)] * NUM_FACES |
|
else: |
|
return [gr.update(visible=True)] * NUM_FACES + [gr.update(visible=True)] * NUM_FACES |
|
|
|
def handle_tif_preview(filepath): |
|
if filepath is None: return None |
|
with Image.open(filepath) as img, tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as temp_file: |
|
img.convert('RGB').save(temp_file.name) |
|
return temp_file.name |
|
|
|
|
|
theme = gr.themes.Base(primary_hue="blue", secondary_hue="cyan") |
|
|
|
with gr.Blocks(theme=theme, title="NeoRefacer - AI Refacer") as demo: |
|
with open("icon.png", "rb") as f: |
|
icon_data = base64.b64encode(f.read()).decode() |
|
gr.Markdown(f'<div style="display: flex; align-items: center;"><img src="data:image/png;base64,{icon_data}" style="width:40px;height:40px;margin-right:10px;"><span style="font-size: 2em; font-weight: bold; color:#2563eb;">NeoRefacer</span></div>') |
|
|
|
for mode in ["Image", "GIF", "TIFF", "Video"]: |
|
with gr.Tab(f"{mode} Mode"): |
|
is_video_or_gif = mode in ["Video", "GIF"] |
|
|
|
with gr.Row(): |
|
if is_video_or_gif: |
|
input_component = gr.Video(label=f"Original {mode}") |
|
if mode == "GIF": |
|
output_main = gr.Video(label="Refaced GIF (MP4)", interactive=False, format="mp4") |
|
output_secondary = gr.Image(label="Refaced GIF (GIF)", type="filepath") |
|
else: |
|
output_main = gr.Video(label="Refaced Video", interactive=False, format="mp4") |
|
output_secondary = gr.File(visible=False) |
|
elif mode == "TIFF": |
|
input_component = gr.File(label="Original TIF", file_types=[".tif", ".tiff"]) |
|
output_main = gr.Image(label="Refaced TIF Preview", type="filepath") |
|
output_secondary = gr.File(label="Refaced TIF (Download)", interactive=False) |
|
else: |
|
input_component = gr.Image(label="Original image", type="filepath") |
|
output_main = gr.Image(label="Refaced image", interactive=False, type="filepath") |
|
|
|
with gr.Row(): |
|
face_mode_radio = gr.Radio(["Single Face", "Multiple Faces", "Faces By Match"], value="Single Face", label="Replacement Mode") |
|
partial_reface_slider = gr.Slider(label="Reface Ratio (0=Full, 0.5=Half)", minimum=0.0, maximum=0.5, value=0.0, step=0.1) |
|
reface_btn = gr.Button(f"Reface {mode}", variant="primary") |
|
if is_video_or_gif: |
|
preview_checkbox = gr.Checkbox(label="Preview (fast)", value=False) |
|
|
|
origins, destinations, thresholds, face_tabs = [], [], [], [] |
|
for i in range(NUM_FACES): |
|
with gr.Tab(f"Face #{i+1}", visible=(i==0)) as tab: |
|
with gr.Row(): |
|
origin_img = gr.Image(label="Face to replace (Match)", visible=False) |
|
dest_img = gr.Image(label="Destination face (Target)") |
|
thresh_slider = gr.Slider(label="Threshold (for Match mode)", minimum=0.0, maximum=1.0, value=0.2) |
|
origins.append(origin_img); destinations.append(dest_img); thresholds.append(thresh_slider); face_tabs.append(tab) |
|
|
|
|
|
face_mode_radio.change(toggle_tabs_and_faces, inputs=face_mode_radio, outputs=face_tabs + origins) |
|
|
|
if mode == "TIFF": |
|
tif_preview = gr.Image(label="TIF Preview", type="filepath") |
|
input_component.change(handle_tif_preview, inputs=input_component, outputs=tif_preview) |
|
input_component.change(lambda fp: extract_faces_auto(fp, max_faces=NUM_FACES), inputs=input_component, outputs=destinations) |
|
reface_btn.click(lambda fp, *args: (handle_tif_preview(run_image(fp, *args)), run_image(fp, *args)), inputs=[input_component] + origins + destinations + thresholds + [face_mode_radio, partial_reface_slider], outputs=[output_main, output_secondary]) |
|
elif is_video_or_gif: |
|
input_component.change(lambda fp: extract_faces_auto(fp, max_faces=NUM_FACES, isvideo=True), inputs=input_component, outputs=destinations) |
|
reface_btn.click(run_video, inputs=[input_component] + origins + destinations + thresholds + [preview_checkbox, face_mode_radio, partial_reface_slider], outputs=[output_main, output_secondary]) |
|
else: |
|
input_component.change(lambda fp: extract_faces_auto(fp, max_faces=NUM_FACES), inputs=input_component, outputs=destinations) |
|
reface_btn.click(run_image, inputs=[input_component] + origins + destinations + thresholds + [face_mode_radio, partial_reface_slider], outputs=[output_main]) |
|
|
|
|
|
demo.load(lambda: toggle_tabs_and_faces("Single Face"), outputs=face_tabs + origins) |
|
|
|
|
|
demo.queue().launch() |
|
|
|
|