import spaces import gradio as gr import pyte import math import subprocess import sys import tempfile import time import os import pty import select import fcntl import struct import termios from pathlib import Path from huggingface_hub import hf_hub_download from PIL import Image from ultralytics import YOLO from preservation import PreservedWatermarkRemover from ignored_preview import save_ignored_preview from clean import load_clean, clean_image from illustrious import load_illustrious, prepare_illustrious, infer_illustrious, save_illustrious from watermark_remover import WatermarkRemover INPUT_IMAGE_HEIGHT = 600 TEXT_WORKER = [sys.executable, "-m", "text_detection"] TIME_GPU = 13 MODEL_DEVICE = "cuda" PART_MODELS = { "eyes": ("killjoyelite/anime-eye-yolov8", "eye_yolov8n.pt", 0.25, 0), "hands": ("deepghs/anime_hand_detection", "hand_detect_v1.0_s/model.pt", 0.3, 0.12), "feet": ("Claquasse/foot_anime_yolo", "foot_anime_yolo11m_v3.pt", 0.25, 0.02), } def load_models(): subprocess.run(TEXT_WORKER, check=True) removers = {} for model_id in (WatermarkRemover.DEFAULT_MODEL_ID, WatermarkRemover.CTRLREGEN_MODEL_ID): remover = PreservedWatermarkRemover((), model_id=model_id, device=MODEL_DEVICE) remover.preload() removers[model_id] = remover parts = {} for part, (repo, filename, _, _) in PART_MODELS.items(): detector = YOLO(hf_hub_download(repo_id=repo, filename=filename)) detector.fuse() parts[part] = detector.to(MODEL_DEVICE) return { "watermark": removers, "parts": parts, "clean": load_clean(MODEL_DEVICE), "illustrious": load_illustrious(MODEL_DEVICE), } def set_terminal_size(fd, rows=24, cols=80): size = struct.pack('HHHH', rows, cols, 0, 0) fcntl.ioctl(fd, termios.TIOCSWINSZ, size) def run_in_pty(cmd, env=None, timeout=600): screen = pyte.Screen(80, 30) stream = pyte.Stream(screen) master_fd, slave_fd = pty.openpty() set_terminal_size(master_fd, rows=30, cols=80) proc = subprocess.Popen( cmd, stdout=slave_fd, stderr=slave_fd, stdin=slave_fd, close_fds=True, env=env, ) os.close(slave_fd) start_time = time.time() try: while True: if time.time() - start_time > timeout: proc.kill() yield get_screen_text(screen), True return ready, _, _ = select.select([master_fd], [], [], 0.3) if ready: try: data = os.read(master_fd, 4096) if not data: break stream.feed(data.decode("utf-8", errors="replace")) except OSError: break if proc.poll() is not None: while True: r, _, _ = select.select([master_fd], [], [], 0.1) if r: try: d = os.read(master_fd, 4096) if not d: break stream.feed(d.decode("utf-8", errors="replace")) except OSError: break else: break yield get_screen_text(screen), True return yield get_screen_text(screen), False # while loop exited via break (empty read or OSError) # yield final screen with is_done=True yield get_screen_text(screen), True finally: try: os.close(master_fd) except OSError: pass if proc.poll() is None: proc.kill() proc.wait() def get_screen_text(screen): lines = [] for row in range(screen.lines): line = "" for col in range(screen.columns): line += screen.buffer[row][col].data lines.append(line.rstrip()) while lines and not lines[-1].strip(): lines.pop() return '\n'.join(lines) def escape_html(text): return text.replace('&', '&').replace('<', '<').replace('>', '>') def make_terminal_html(content, cursor=True): cursor_html = "โ" if cursor else "" return f"
{content}{cursor_html}Remove invisible AI watermarks (SynthID, StableSignature, TreeRing) and strip AI metadata from images
""") with gr.Tabs(): with gr.TabItem("๐ง Watermark Removal"): upload_wm = gr.Image(label="Upload Image (PNG or JPEG)", sources=["upload"], type="filepath", image_mode=None, format="png", height=INPUT_IMAGE_HEIGHT) model = gr.Radio( ["CtrlRegen Illustrious (SDXL)", "CtrlRegen", "Default"], value="CtrlRegen", label="Model", ) with gr.Row(): ignore_eyes = gr.Checkbox(value=True, label="Ignore eyes") ignore_hands = gr.Checkbox(value=True, label="Ignore hands") ignore_feet = gr.Checkbox(value=True, label="Ignore feet") ignore_text = gr.Checkbox(value=True, label="Ignore text") clean_after = gr.Checkbox(value=True, label="Clean after") with gr.Row(): strength = gr.Slider( minimum=0.01, maximum=1.0, value=0.02, step=0.01, label="Strength", info="Lower = less change. Increase if watermark persists.", ) steps = gr.Slider( minimum=10, maximum=100, value=50, step=5, label="Denoising Steps", info="More steps = better quality but slower.", ) with gr.Row(): btn_super_minimal = gr.Button("Super Minimal (0.02/50)", size="sm") btn_minimal = gr.Button("Minimal (0.04/50)", size="sm") btn_balanced = gr.Button("Balanced (0.15/50)", size="sm") btn_aggressive = gr.Button("Aggressive (0.35/60)", size="sm") btn_max = gr.Button("Maximum (0.70/60)", size="sm") btn_super_minimal.click(lambda: (0.02, 50), outputs=[strength, steps]) btn_minimal.click(lambda: (0.04, 50), outputs=[strength, steps]) btn_balanced.click(lambda: (0.15, 50), outputs=[strength, steps]) btn_aggressive.click(lambda: (0.35, 60), outputs=[strength, steps]) btn_max.click(lambda: (0.70, 60), outputs=[strength, steps]) run_btn = gr.Button("๐ Remove Watermark", variant="primary", size="lg") wm_terminal = gr.HTML(value=make_terminal_html("Waiting for input...")) with gr.Row(): output_image = gr.Image( type="filepath", image_mode=None, format="png", interactive=False, show_label=False, height=INPUT_IMAGE_HEIGHT, buttons=["download", "fullscreen"], ) ignored_image = gr.Image( type="filepath", image_mode=None, format="png", interactive=False, show_label=False, height=INPUT_IMAGE_HEIGHT, buttons=["download", "fullscreen"], ) run_btn.click( fn=remove_watermark, inputs=[upload_wm, strength, steps, model, ignore_eyes, ignore_hands, ignore_feet, clean_after, ignore_text], outputs=[wm_terminal, output_image, ignored_image], concurrency_limit=1, ) with gr.TabItem("๐ Check AI Metadata"): upload_meta = gr.Image(label="Upload Image to Check (PNG or JPEG)", sources=["upload"], type="filepath", image_mode=None, height=INPUT_IMAGE_HEIGHT) check_btn = gr.Button("๐ Check Metadata", variant="secondary", size="lg") meta_terminal = gr.HTML(value=make_terminal_html("Waiting for input...")) check_btn.click( fn=check_ai_metadata, inputs=[upload_meta], outputs=[meta_terminal], ) gr.Markdown(""" --- """) if __name__ == "__main__": demo.launch(ssr_mode=False, css=terminal_css, allowed_paths=["/tmp"])