import gradio as gr import os import cv2 import numpy as np import shutil import subprocess import time import argparse import zipfile # --- LOAD ENGINE --- try: from SinglePhoto import FaceSwapper swapper = FaceSwapper() print("FaceSwapper loaded successfully.") except ImportError: print("WARNING: 'SinglePhoto' module not found. UI will load, but swapping will fail.") class FaceSwapper: def swap_faces(self, *args, **kwargs): raise ImportError("SinglePhoto.py not found.") swapper = FaceSwapper() # --- CSS & THEME CONFIGURATION --- custom_css = """ body { background-color: #0b0f19; } .gradio-container { background-color: #0b0f19 !important; } h1.title-header { color: #00ff41; font-family: 'Courier New', Courier, monospace; text-align: center; font-weight: bold; text-shadow: 0px 0px 10px rgba(0, 255, 65, 0.5); margin-bottom: 20px; padding: 20px; border: 1px solid #00ff41; border-radius: 8px; background: #000; } .gr-button-primary { background: linear-gradient(90deg, #004d1a 0%, #00b33c 100%) !important; border: 1px solid #00ff41 !important; color: white !important; } """ shinyy_theme = gr.themes.Soft( primary_hue="green", neutral_hue="slate", font=[gr.themes.GoogleFont("Source Code Pro"), "ui-sans-serif", "system-ui", "sans-serif"], ).set( body_background_fill="#0b0f19", block_background_fill="#111827", block_border_width="1px", block_title_text_color="#00ff41", block_label_text_color="#a3a3a3", input_background_fill="#1f2937", button_primary_background_fill="#00ff41", button_primary_text_color="#000000", ) # --- UTILITY: ZIP PROJECT --- def zip_project_directory(): """Zips the current working directory for download.""" zip_filename = "project_backup.zip" # Files/Folders to ignore to keep zip clean/small ignore_folders = {'.git', '__pycache__', 'venv', 'env', '.ipynb_checkpoints'} with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf: for root, dirs, files in os.walk('.'): # Remove ignored directories from traversal dirs[:] = [d for d in dirs if d not in ignore_folders] for file in files: if file == zip_filename: continue # Don't zip the zip itself file_path = os.path.join(root, file) # Archive name is the path relative to current directory arcname = os.path.relpath(file_path, '.') zipf.write(file_path, arcname) return zip_filename # --- LOGIC FUNCTIONS --- def swap_single_photo(src_img, src_idx, dst_img, dst_idx, include_hair, progress=gr.Progress(track_tqdm=True)): log = "" start_time = time.time() try: progress(0, desc="Preparing files") if src_img is None or dst_img is None: return None, "Error: Images missing." src_path = "SinglePhoto/data_src.jpg" dst_path = "SinglePhoto/data_dst.jpg" output_path = "SinglePhoto/output_swapped.jpg" os.makedirs(os.path.dirname(src_path), exist_ok=True) os.makedirs(os.path.dirname(dst_path), exist_ok=True) os.makedirs(os.path.dirname(output_path), exist_ok=True) cv2.imwrite(src_path, cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)) cv2.imwrite(dst_path, cv2.cvtColor(dst_img, cv2.COLOR_RGB2BGR)) progress(0.5, desc="Swapping faces" + (" & hair" if include_hair else "")) # --- ENGINE CALL --- result = swapper.swap_faces(src_path, int(src_idx), dst_path, int(dst_idx), swap_hair=include_hair) cv2.imwrite(output_path, result) log += f"Swapped{' (with Hair)' if include_hair else ''} and saved.\n" progress(1, desc="Done") result_rgb = cv2.cvtColor(result, cv2.COLOR_BGR2RGB) return result_rgb, log except Exception as e: log += f"Error: {e}\n" return None, log def swap_single_src_multi_dst(src_img, dst_imgs, dst_indices, progress=gr.Progress(track_tqdm=True)): log = "" results = [] src_dir = "SingleSrcMultiDst/src" dst_dir = "SingleSrcMultiDst/dst" output_dir = "SingleSrcMultiDst/output" os.makedirs(src_dir, exist_ok=True) os.makedirs(dst_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True) if src_img is None: return [], "Error: Source image missing" if isinstance(src_img, tuple): src_img = src_img[0] src_path = os.path.join(src_dir, "data_src.jpg") cv2.imwrite(src_path, cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)) if isinstance(dst_indices, str): dst_indices_list = [int(idx.strip()) for idx in dst_indices.split(",") if idx.strip().isdigit()] else: dst_indices_list = [int(idx) for idx in dst_indices] if dst_indices else [] if not dst_imgs: return [], "Error: No destination images." for j, dst_img in enumerate(dst_imgs): if isinstance(dst_img, tuple): dst_img = dst_img[0] dst_path = os.path.join(dst_dir, f"data_dst_{j}.jpg") output_path = os.path.join(output_dir, f"output_swapped_{j}.jpg") cv2.imwrite(dst_path, cv2.cvtColor(dst_img, cv2.COLOR_RGB2BGR)) try: dst_idx = dst_indices_list[j] if j < len(dst_indices_list) else 1 result = swapper.swap_faces(src_path, 1, dst_path, int(dst_idx)) cv2.imwrite(output_path, result) results.append(output_path) except Exception as e: results.append(None) log += f"Error dst {j}: {e}\n" progress((j+1)/len(dst_imgs), desc=f"Processing {j+1}/{len(dst_imgs)}") return results, log def swap_multi_src_single_dst(src_imgs, dst_img, dst_idx, progress=gr.Progress(track_tqdm=True)): log = "" results = [] src_dir = "MultiSrcSingleDst/src" dst_dir = "MultiSrcSingleDst/dst" output_dir = "MultiSrcSingleDst/output" os.makedirs(src_dir, exist_ok=True) os.makedirs(dst_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True) if dst_img is None: return [], "Error: Dest image missing" if isinstance(dst_img, tuple): dst_img = dst_img[0] dst_path = os.path.join(dst_dir, "data_dst.jpg") cv2.imwrite(dst_path, cv2.cvtColor(dst_img, cv2.COLOR_RGB2BGR)) if not src_imgs: return [], "Error: No source images." for i, src_img in enumerate(src_imgs): if isinstance(src_img, tuple): src_img = src_img[0] src_path = os.path.join(src_dir, f"data_src_{i}.jpg") output_path = os.path.join(output_dir, f"output_swapped_{i}.jpg") cv2.imwrite(src_path, cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)) try: result = swapper.swap_faces(src_path, 1, dst_path, int(dst_idx)) cv2.imwrite(output_path, result) results.append(output_path) except Exception as e: results.append(None) log += f"Error src {i}: {e}\n" progress((i+1)/len(src_imgs)) return results, log def swap_multi_src_multi_dst(src_imgs, dst_imgs, dst_indices, progress=gr.Progress(track_tqdm=True)): log = "" results = [] src_dir = "MultiSrcMultiDst/src" dst_dir = "MultiSrcMultiDst/dst" output_dir = "MultiSrcMultiDst/output" os.makedirs(src_dir, exist_ok=True) os.makedirs(dst_dir, exist_ok=True) os.makedirs(output_dir, exist_ok=True) if isinstance(dst_indices, str): dst_indices_list = [int(idx.strip()) for idx in dst_indices.split(",") if idx.strip().isdigit()] else: dst_indices_list = [] total = max(1, len(src_imgs) * len(dst_imgs)) count = 0 for i, src_img in enumerate(src_imgs): if isinstance(src_img, tuple): src_img = src_img[0] if src_img is None: continue src_path = os.path.join(src_dir, f"data_src_{i}.jpg") if isinstance(src_img, np.ndarray): cv2.imwrite(src_path, cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)) elif isinstance(src_img, str) and os.path.exists(src_img): shutil.copy(src_img, src_path) for j, dst_img in enumerate(dst_imgs): if isinstance(dst_img, tuple): dst_img = dst_img[0] if dst_img is None: continue dst_path = os.path.join(dst_dir, f"data_dst_{j}.jpg") output_path = os.path.join(output_dir, f"output_swapped_{i}_{j}.jpg") if isinstance(dst_img, np.ndarray): cv2.imwrite(dst_path, cv2.cvtColor(dst_img, cv2.COLOR_RGB2BGR)) elif isinstance(dst_img, str) and os.path.exists(dst_img): shutil.copy(dst_img, dst_path) try: dst_idx = dst_indices_list[j] if j < len(dst_indices_list) else 1 result = swapper.swap_faces(src_path, 1, dst_path, int(dst_idx)) cv2.imwrite(output_path, result) results.append(output_path) except Exception as e: log += f"Error {i}-{j}: {e}\n" count += 1 progress(count/total) return results, log def swap_faces_custom(src_imgs, dst_img, mapping_str, progress=gr.Progress(track_tqdm=True)): log = "" dst_path = "CustomSwap/data_dst.jpg" output_path = "CustomSwap/output_swapped.jpg" src_dir = "CustomSwap/src" temp_dir = "CustomSwap/temp" os.makedirs(src_dir, exist_ok=True) os.makedirs(temp_dir, exist_ok=True) os.makedirs(os.path.dirname(dst_path), exist_ok=True) if dst_img is None: return None, "Error: Dst missing" cv2.imwrite(dst_path, cv2.cvtColor(dst_img, cv2.COLOR_RGB2BGR)) src_paths = [] if not src_imgs: return None, "Error: Src missing" for i, src_img in enumerate(src_imgs): src_path = os.path.join(src_dir, f"data_src_{i+1}.jpg") if isinstance(src_img, tuple): src_img = src_img[0] if isinstance(src_img, np.ndarray): cv2.imwrite(src_path, cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)) elif isinstance(src_img, str): shutil.copy(src_img, src_path) src_paths.append(src_path) try: mapping = [int(x.strip()) for x in mapping_str.split(",") if x.strip().isdigit()] except: return None, "Error: Invalid map" temp_dst_path = os.path.join(temp_dir, "temp_dst.jpg") shutil.copy(dst_path, temp_dst_path) for face_idx, src_idx in enumerate(mapping, start=1): if src_idx < 1 or src_idx > len(src_paths): continue try: swapped_img = swapper.swap_faces(src_paths[src_idx-1], 1, temp_dst_path, face_idx) cv2.imwrite(temp_dst_path, swapped_img) log += f"Face {face_idx} <- Src {src_idx}\n" except Exception as e: log += f"Fail face {face_idx}: {e}\n" shutil.copy(temp_dst_path, output_path) return output_path, log # --- VIDEO FUNCTIONS --- def add_audio_to_video(original_video_path, video_no_audio_path, output_path): cmd = ["ffmpeg", "-y", "-i", video_no_audio_path, "-i", original_video_path, "-c:v", "copy", "-c:a", "aac", "-map", "0:v:0", "-map", "1:a:0?", "-shortest", output_path] try: subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) return True, "" except subprocess.CalledProcessError as e: return False, e.stderr.decode() def swap_video(src_img, src_idx, video, dst_idx, delete_frames, add_audio, copy_drive, progress=gr.Progress()): log = "" src_path = "VideoSwapping/data_src.jpg" dst_video_path = "VideoSwapping/data_dst.mp4" frames_dir = "VideoSwapping/video_frames" swapped_dir = "VideoSwapping/swapped_frames" output_video_path = "VideoSwapping/output_tmp_output_video.mp4" final_output_path = "VideoSwapping/output_with_audio.mp4" os.makedirs(os.path.dirname(src_path), exist_ok=True) os.makedirs(os.path.dirname(dst_video_path), exist_ok=True) os.makedirs(frames_dir, exist_ok=True) os.makedirs(swapped_dir, exist_ok=True) if src_img is None: return None, "Error: No source image" cv2.imwrite(src_path, cv2.cvtColor(src_img, cv2.COLOR_RGB2BGR)) if isinstance(video, str) and os.path.exists(video): shutil.copy(video, dst_video_path) else: dst_video_path = video def extract_frames(video_path, output_folder): cap = cv2.VideoCapture(video_path) count = 0 paths = [] while cap.isOpened(): ret, frame = cap.read() if not ret: break fname = os.path.join(output_folder, f"frame_{count:05d}.jpg") cv2.imwrite(fname, frame) paths.append(fname) count += 1 cap.release() return paths def frames_to_video(input_folder, output_video, fps): images = sorted([img for img in os.listdir(input_folder) if img.endswith(".jpg")]) if not images: return frame0 = cv2.imread(os.path.join(input_folder, images[0])) h, w, l = frame0.shape out = cv2.VideoWriter(output_video, cv2.VideoWriter_fourcc(*'mp4v'), fps, (w, h)) for image in images: out.write(cv2.imread(os.path.join(input_folder, image))) out.release() progress(0.1, desc="Extracting frames...") frame_paths = extract_frames(dst_video_path, frames_dir) cap = cv2.VideoCapture(dst_video_path) fps = cap.get(cv2.CAP_PROP_FPS) cap.release() if fps == 0: fps = 30 for idx, frame_path in enumerate(frame_paths): swapped_name = f"swapped_{idx:05d}.jpg" out_path = os.path.join(swapped_dir, swapped_name) try: swapped = swapper.swap_faces(src_path, int(src_idx), frame_path, int(dst_idx)) cv2.imwrite(out_path, swapped) except: shutil.copy(frame_path, out_path) progress((idx+1)/len(frame_paths), desc=f"Frame {idx+1}/{len(frame_paths)}") progress(0.9, desc="Encoding video...") frames_to_video(swapped_dir, output_video_path, fps) if add_audio: add_audio_to_video(dst_video_path, output_video_path, final_output_path) else: final_output_path = output_video_path if delete_frames: shutil.rmtree(frames_dir, ignore_errors=True) shutil.rmtree(swapped_dir, ignore_errors=True) return final_output_path, "Done." def swap_video_all_faces(src_img, video, num_faces, delete_frames, add_audio, copy_drive, progress=gr.Progress()): return swap_video(src_img, 1, video, 1, delete_frames, add_audio, copy_drive, progress) # --- UI LAYOUT --- with gr.Blocks(theme=shinyy_theme, css=custom_css, title="Shinyy's Face Swapper") as demo: gr.Markdown("""