Files

390 lines
17 KiB
Python
Raw Permalink Normal View History

2025-04-10 22:08:59 +01:00
import os
os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE"
2023-06-05 08:27:14 +02:00
import gradio as gr
from refacer import Refacer
import argparse
2023-06-08 12:31:53 +08:00
import ngrok
2025-04-10 22:08:59 +01:00
import imageio
import numpy as np
from PIL import Image
import tempfile
import base64
import pyfiglet
import shutil
import time
print("\033[94m" + pyfiglet.Figlet(font='slant').renderText("NeoRefacer") + "\033[0m")
2023-06-03 08:04:06 +02:00
2025-04-10 22:08:59 +01:00
def cleanup_temp(folder_path):
try:
shutil.rmtree(folder_path)
print("Gradio cache cleared successfully.")
except Exception as e:
print(f"Error: {e}")
# Prepare temp folder
os.environ["GRADIO_TEMP_DIR"] = "./tmp"
if os.path.exists("./tmp"):
cleanup_temp(os.environ['GRADIO_TEMP_DIR'])
if not os.path.exists("./tmp"):
os.makedirs("./tmp")
# Parse arguments
parser = argparse.ArgumentParser(description='Refacer')
2025-04-10 22:08:59 +01:00
parser.add_argument("--max_num_faces", type=int, default=8)
parser.add_argument("--force_cpu", default=False, action="store_true")
parser.add_argument("--share_gradio", default=False, action="store_true")
parser.add_argument("--server_name", type=str, default="127.0.0.1")
parser.add_argument("--server_port", type=int, default=7860)
parser.add_argument("--colab_performance", default=False, action="store_true")
parser.add_argument("--ngrok", type=str, default=None)
parser.add_argument("--ngrok_region", type=str, default="us")
args = parser.parse_args()
2023-06-05 08:27:14 +02:00
2025-04-10 22:08:59 +01:00
# Initialize
refacer = Refacer(force_cpu=args.force_cpu, colab_performance=args.colab_performance)
num_faces = args.max_num_faces
2023-06-05 08:27:14 +02:00
2025-04-10 22:08:59 +01:00
def create_dummy_image():
dummy = Image.new('RGB', (1, 1), color=(255, 255, 255))
temp_file = tempfile.NamedTemporaryFile(delete=False, dir="./tmp", suffix=".png")
2025-04-10 22:08:59 +01:00
dummy.save(temp_file.name)
return temp_file.name
2023-06-08 12:28:14 +08:00
2025-04-10 22:08:59 +01:00
def run_image(*vars):
image_path = vars[0]
origins = vars[1:(num_faces+1)]
destinations = vars[(num_faces+1):(num_faces*2)+1]
2025-04-27 01:06:35 +01:00
thresholds = vars[(num_faces*2)+1:-2]
face_mode = vars[-2]
partial_reface_ratio = vars[-1]
2023-06-08 12:28:14 +08:00
2025-04-10 22:08:59 +01:00
disable_similarity = (face_mode in ["Single Face", "Multiple Faces"])
multiple_faces_mode = (face_mode == "Multiple Faces")
2023-06-08 12:28:14 +08:00
2025-04-10 22:08:59 +01:00
faces = []
for k in range(num_faces):
if destinations[k] is not None:
faces.append({
'origin': origins[k] if not multiple_faces_mode else None,
'destination': destinations[k],
'threshold': thresholds[k] if not multiple_faces_mode else 0.0
})
2023-06-08 12:28:14 +08:00
2025-04-27 01:06:35 +01:00
return refacer.reface_image(image_path, faces, disable_similarity=disable_similarity, multiple_faces_mode=multiple_faces_mode, partial_reface_ratio=partial_reface_ratio)
2023-06-08 12:28:14 +08:00
2023-06-05 08:27:14 +02:00
def run(*vars):
2025-04-10 22:08:59 +01:00
video_path = vars[0]
origins = vars[1:(num_faces+1)]
destinations = vars[(num_faces+1):(num_faces*2)+1]
2025-04-27 01:06:35 +01:00
thresholds = vars[(num_faces*2)+1:-3]
preview = vars[-3]
face_mode = vars[-2]
partial_reface_ratio = vars[-1]
2025-04-10 22:08:59 +01:00
disable_similarity = (face_mode in ["Single Face", "Multiple Faces"])
multiple_faces_mode = (face_mode == "Multiple Faces")
2023-06-05 08:27:14 +02:00
faces = []
2025-04-10 22:08:59 +01:00
for k in range(num_faces):
if destinations[k] is not None:
2023-06-05 08:27:14 +02:00
faces.append({
2025-04-10 22:08:59 +01:00
'origin': origins[k] if not multiple_faces_mode else None,
'destination': destinations[k],
'threshold': thresholds[k] if not multiple_faces_mode else 0.0
2023-06-05 08:27:14 +02:00
})
2025-04-27 01:06:35 +01:00
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)
2025-04-10 22:08:59 +01:00
return mp4_path, gif_path if gif_path else None
2023-06-05 08:27:14 +02:00
2025-04-10 22:08:59 +01:00
def load_first_frame(filepath):
if filepath is None:
return None
frames = imageio.get_reader(filepath)
return frames.get_data(0)
def extract_faces_auto(filepath, refacer_instance, max_faces=5, isvideo=False):
if filepath is None:
return [None] * max_faces
2025-04-27 01:06:35 +01:00
if isvideo and os.path.getsize(filepath) > 5 * 1024 * 1024:
print("Video too large for auto-extract, skipping face extraction.")
return [None] * max_faces
2025-04-10 22:08:59 +01:00
frame = load_first_frame(filepath)
if frame is None:
return [None] * max_faces
while len(frame.shape) > 3:
2025-04-27 01:06:35 +01:00
frame = frame[0]
if frame.shape[-1] != 3:
raise ValueError(f"Expected last dimension to be 3 (RGB), but got {frame.shape[-1]}")
2025-04-10 22:08:59 +01:00
temp_image_path = os.path.join("./tmp", f"temp_face_extract_{int(time.time() * 1000)}.png")
Image.fromarray(frame).save(temp_image_path)
try:
faces = refacer_instance.extract_faces_from_image(temp_image_path, max_faces=max_faces)
2025-04-27 01:06:35 +01:00
return faces + [None] * (max_faces - len(faces))
2025-04-10 22:08:59 +01:00
finally:
if os.path.exists(temp_image_path):
try:
os.remove(temp_image_path)
except Exception as e:
print(f"Warning: Could not delete temp file {temp_image_path}: {e}")
def toggle_tabs_and_faces(mode, face_tabs, origin_faces):
if mode == "Single Face":
tab_updates = [gr.update(visible=(i == 0)) for i in range(len(face_tabs))]
origin_updates = [gr.update(visible=False) for _ in range(len(origin_faces))]
elif mode == "Multiple Faces":
tab_updates = [gr.update(visible=True) for _ in range(len(face_tabs))]
origin_updates = [gr.update(visible=False) for _ in range(len(origin_faces))]
else:
tab_updates = [gr.update(visible=True) for _ in range(len(face_tabs))]
origin_updates = [gr.update(visible=True) for _ in range(len(origin_faces))]
return tab_updates + origin_updates
2025-04-27 01:06:35 +01:00
def handle_tif_preview(filepath):
if filepath is None:
return None
preview_path = os.path.join("./tmp", f"tif_preview_{int(time.time() * 1000)}.jpg")
Image.open(filepath).convert('RGB').save(preview_path)
return preview_path
2025-04-10 22:08:59 +01:00
# --- UI ---
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()
icon_html = f'<img src="data:image/png;base64,{icon_data}" style="width:40px;height:40px;margin-right:10px;">'
2023-06-05 08:27:14 +02:00
with gr.Row():
2025-04-10 22:08:59 +01:00
gr.Markdown(f"""
<div style="display: flex; align-items: center;">
{icon_html}
<span style="font-size: 2em; font-weight: bold; color:#2563eb;">NeoRefacer</span>
</div>
""")
# --- IMAGE MODE ---
with gr.Tab("Image Mode"):
with gr.Row():
image_input = gr.Image(label="Original image", type="filepath")
image_output = gr.Image(label="Refaced image", interactive=False, type="filepath")
with gr.Row():
2025-04-27 01:06:35 +01:00
face_mode_image = gr.Radio(["Single Face", "Multiple Faces", "Faces By Match"], value="Single Face", label="Replacement Mode")
partial_reface_ratio_image = gr.Slider(label="Reface Ratio (0 = Full Face, 0.5 = Half Face)", minimum=0.0, maximum=0.5, value=0.0, step=0.1)
2025-04-10 22:08:59 +01:00
image_btn = gr.Button("Reface Image", variant="primary")
origin_image, destination_image, thresholds_image, face_tabs_image = [], [], [], []
for i in range(num_faces):
with gr.Tab(f"Face #{i+1}") as tab:
with gr.Row():
origin = gr.Image(label="Face to replace")
destination = gr.Image(label="Destination face")
threshold = gr.Slider(label="Threshold", minimum=0.0, maximum=1.0, value=0.2)
origin_image.append(origin)
destination_image.append(destination)
thresholds_image.append(threshold)
face_tabs_image.append(tab)
2025-04-27 01:06:35 +01:00
face_mode_image.change(fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_image, origin_image), inputs=[face_mode_image], outputs=face_tabs_image + origin_image)
demo.load(fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_image, origin_image), inputs=None, outputs=face_tabs_image + origin_image)
2025-04-10 22:08:59 +01:00
2025-04-27 01:06:35 +01:00
image_btn.click(fn=run_image, inputs=[image_input] + origin_image + destination_image + thresholds_image + [face_mode_image, partial_reface_ratio_image], outputs=[image_output])
image_input.change(fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces), inputs=image_input, outputs=origin_image)
image_input.change(fn=lambda _: 0.0, inputs=image_input, outputs=partial_reface_ratio_image)
2025-04-10 22:08:59 +01:00
# --- GIF MODE ---
with gr.Tab("GIF Mode"):
with gr.Row():
gif_input = gr.File(label="Original GIF", file_types=[".gif"])
gif_preview = gr.Video(label="GIF Preview", interactive=False)
gif_output = gr.Video(label="Refaced GIF (MP4)", interactive=False, format="mp4")
gif_file_output = gr.Image(label="Refaced GIF (GIF)", type="filepath")
with gr.Row():
2025-04-27 01:06:35 +01:00
face_mode_gif = gr.Radio(["Single Face", "Multiple Faces", "Faces By Match"], value="Single Face", label="Replacement Mode")
partial_reface_ratio_gif = gr.Slider(label="Reface Ratio (0 = Full Face, 0.5 = Half Face)", minimum=0.0, maximum=0.5, value=0.0, step=0.1)
2025-04-10 22:08:59 +01:00
gif_btn = gr.Button("Reface GIF", variant="primary")
2025-04-27 01:06:35 +01:00
preview_checkbox_gif = gr.Checkbox(label="Preview Generation (skip 90% of frames)", value=False)
2025-04-10 22:08:59 +01:00
origin_gif, destination_gif, thresholds_gif, face_tabs_gif = [], [], [], []
for i in range(num_faces):
with gr.Tab(f"Face #{i+1}") as tab:
with gr.Row():
origin = gr.Image(label="Face to replace")
destination = gr.Image(label="Destination face")
threshold = gr.Slider(label="Threshold", minimum=0.0, maximum=1.0, value=0.2)
origin_gif.append(origin)
destination_gif.append(destination)
thresholds_gif.append(threshold)
face_tabs_gif.append(tab)
2025-04-27 01:06:35 +01:00
face_mode_gif.change(fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_gif, origin_gif), inputs=[face_mode_gif], outputs=face_tabs_gif + origin_gif)
demo.load(fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_gif, origin_gif), inputs=None, outputs=face_tabs_gif + origin_gif)
2025-04-10 22:08:59 +01:00
2025-04-27 01:06:35 +01:00
gif_btn.click(fn=run, inputs=[gif_input] + origin_gif + destination_gif + thresholds_gif + [preview_checkbox_gif, face_mode_gif, partial_reface_ratio_gif], outputs=[gif_output, gif_file_output])
2025-04-10 22:08:59 +01:00
2025-04-27 01:06:35 +01:00
gif_input.change(fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces), inputs=gif_input, outputs=origin_gif)
gif_input.change(fn=lambda file: file, inputs=gif_input, outputs=[gif_preview])
gif_input.change(fn=lambda _: 0.0, inputs=gif_input, outputs=partial_reface_ratio_gif)
2025-04-10 22:08:59 +01:00
# --- TIF MODE ---
with gr.Tab("TIFF Mode"):
with gr.Row():
tif_input = gr.File(label="Original TIF", file_types=[".tif", ".tiff"])
tif_preview = gr.Image(label="TIF Preview (Cover Page)", type="filepath")
tif_output_preview = gr.Image(label="Refaced TIF Preview (Cover Page)", type="filepath")
tif_output_file = gr.File(label="Refaced TIF (Download)", interactive=False)
with gr.Row():
face_mode_tif = gr.Radio(
choices=["Single Face", "Multiple Faces", "Faces By Match"],
value="Single Face",
label="Replacement Mode"
)
2025-04-27 01:06:35 +01:00
partial_reface_ratio_tif = gr.Slider(label="Reface Ratio (0 = Full Face, 0.5 = Half Face)", minimum=0.0, maximum=0.5, value=0.0, step=0.1)
tif_btn = gr.Button("Reface TIF", variant="primary")
origin_tif, destination_tif, thresholds_tif, face_tabs_tif = [], [], [], []
for i in range(num_faces):
with gr.Tab(f"Face #{i+1}") as tab:
with gr.Row():
origin = gr.Image(label="Face to replace")
destination = gr.Image(label="Destination face")
threshold = gr.Slider(label="Threshold", minimum=0.0, maximum=1.0, value=0.2)
origin_tif.append(origin)
destination_tif.append(destination)
thresholds_tif.append(threshold)
face_tabs_tif.append(tab)
face_mode_tif.change(
fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_tif, origin_tif),
inputs=[face_mode_tif],
outputs=face_tabs_tif + origin_tif
)
demo.load(
fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_tif, origin_tif),
inputs=None,
outputs=face_tabs_tif + origin_tif
)
def process_tif(tif_path, *vars):
original_img = Image.open(tif_path)
if hasattr(original_img, "n_frames") and original_img.n_frames > 1:
original_img.seek(0)
temp_preview_path = os.path.join("./tmp", f"tif_preview_{int(time.time() * 1000)}.jpg")
original_img.convert('RGB').save(temp_preview_path)
refaced_path = run_image(tif_path, *vars)
refaced_img = Image.open(refaced_path)
if hasattr(refaced_img, "n_frames") and refaced_img.n_frames > 1:
refaced_img.seek(0)
temp_refaced_preview_path = os.path.join("./tmp", f"refaced_tif_preview_{int(time.time() * 1000)}.jpg")
refaced_img.convert('RGB').save(temp_refaced_preview_path)
return temp_preview_path, temp_refaced_preview_path, refaced_path
tif_btn.click(
fn=lambda tif_path, *args: process_tif(tif_path, *args),
2025-04-27 01:06:35 +01:00
inputs=[tif_input] + origin_tif + destination_tif + thresholds_tif + [face_mode_tif, partial_reface_ratio_tif],
outputs=[tif_preview, tif_output_preview, tif_output_file]
)
tif_input.change(
fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces),
inputs=tif_input,
outputs=origin_tif
)
tif_input.change(
2025-04-27 01:06:35 +01:00
fn=handle_tif_preview,
inputs=tif_input,
outputs=tif_preview
)
2025-04-27 01:06:35 +01:00
tif_input.change(fn=lambda _: 0.0, inputs=tif_input, outputs=partial_reface_ratio_tif)
2025-04-10 22:08:59 +01:00
# --- VIDEO MODE ---
with gr.Tab("Video Mode"):
with gr.Row():
video_input = gr.Video(label="Original video", format="mp4")
video_output = gr.Video(label="Refaced Video", interactive=False, format="mp4")
with gr.Row():
face_mode_video = gr.Radio(
choices=["Single Face", "Multiple Faces", "Faces By Match"],
value="Single Face",
label="Replacement Mode"
)
2025-04-27 01:06:35 +01:00
partial_reface_ratio_video = gr.Slider(label="Reface Ratio (0 = Full Face, 0.5 = Half Face)", minimum=0.0, maximum=0.5, value=0.0, step=0.1)
2025-04-10 22:08:59 +01:00
video_btn = gr.Button("Reface Video", variant="primary")
preview_checkbox_video = gr.Checkbox(label="Preview Generation (skip 90% of frames)", value=False)
origin_video, destination_video, thresholds_video, face_tabs_video = [], [], [], []
for i in range(num_faces):
with gr.Tab(f"Face #{i+1}") as tab:
with gr.Row():
origin = gr.Image(label="Face to replace")
destination = gr.Image(label="Destination face")
threshold = gr.Slider(label="Threshold", minimum=0.0, maximum=1.0, value=0.2)
origin_video.append(origin)
destination_video.append(destination)
thresholds_video.append(threshold)
face_tabs_video.append(tab)
face_mode_video.change(
fn=lambda mode: toggle_tabs_and_faces(mode, face_tabs_video, origin_video),
inputs=[face_mode_video],
outputs=face_tabs_video + origin_video
)
demo.load(
fn=lambda: toggle_tabs_and_faces("Single Face", face_tabs_video, origin_video),
inputs=None,
outputs=face_tabs_video + origin_video
)
video_input.change(
fn=lambda filepath: extract_faces_auto(filepath, refacer, max_faces=num_faces, isvideo=True),
inputs=video_input,
outputs=origin_video
)
2025-04-27 01:06:35 +01:00
video_input.change(fn=lambda _: 0.0, inputs=video_input, outputs=partial_reface_ratio_video)
2025-04-10 22:08:59 +01:00
video_btn.click(
fn=lambda *args: run(*args),
2025-04-27 01:06:35 +01:00
inputs=[video_input] + origin_video + destination_video + thresholds_video + [preview_checkbox_video, face_mode_video, partial_reface_ratio_video],
2025-04-10 22:08:59 +01:00
outputs=[video_output, gr.File(visible=False)]
)
# --- ngrok connect (optional) ---
if args.ngrok:
def connect(token, port, options):
try:
public_url = ngrok.connect(f"127.0.0.1:{port}", **options).url()
print(f'ngrok URL: {public_url}')
except Exception as e:
print(f'ngrok connection aborted: {e}')
2023-06-05 08:27:14 +02:00
connect(args.ngrok, args.server_port, {'region': args.ngrok_region, 'authtoken_from_env': False})
2025-04-10 22:08:59 +01:00
# --- Launch app ---
demo.queue().launch(favicon_path="icon.png", show_error=True, share=args.share_gradio, server_name=args.server_name, server_port=args.server_port)