| import gradio as gr |
| import cv2 |
| import numpy as np |
| import os, shutil, subprocess |
|
|
| WORK = "work" |
| os.makedirs(WORK, exist_ok=True) |
|
|
| def extract_frames(video): |
| subprocess.run(["ffmpeg", "-y", "-i", video, f"{WORK}/f_%04d.png"], |
| stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
|
|
| def build_video(out): |
| subprocess.run([ |
| "ffmpeg", "-y", "-framerate", "30", |
| "-i", f"{WORK}/c_%04d.png", |
| "-c:v", "libx264", "-pix_fmt", "yuv420p", out |
| ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) |
|
|
| def detect_initial_box(frames): |
| |
| prev = cv2.cvtColor(frames[0], cv2.COLOR_BGR2GRAY) |
| acc = None |
| for img in frames[1:4]: |
| gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) |
| flow = cv2.calcOpticalFlowFarneback(prev, gray, None, 0.5, 3, 15, 3, 5, 1.2, 0) |
| mag, _ = cv2.cartToPolar(flow[...,0], flow[...,1]) |
| _, m = cv2.threshold(mag, 2.0, 255, cv2.THRESH_BINARY) |
| acc = m if acc is None else cv2.add(acc, m) |
| prev = gray |
|
|
| acc = acc.astype("uint8") |
| kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (15,15)) |
| acc = cv2.morphologyEx(acc, cv2.MORPH_CLOSE, kernel) |
|
|
| cnts, _ = cv2.findContours(acc, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) |
| if not cnts: |
| return None |
| c = max(cnts, key=cv2.contourArea) |
| return cv2.boundingRect(c) |
|
|
| def process(video): |
| if video is None: |
| return None |
|
|
| shutil.rmtree(WORK, ignore_errors=True) |
| os.makedirs(WORK, exist_ok=True) |
|
|
| inp = os.path.join(WORK, "input.mp4") |
| shutil.copy(video, inp) |
|
|
| extract_frames(inp) |
|
|
| frame_files = sorted([f for f in os.listdir(WORK) if f.startswith("f_")]) |
| frames = [cv2.imread(os.path.join(WORK, f)) for f in frame_files[:5]] |
|
|
| box = detect_initial_box(frames) |
| if box is None: |
| return None |
|
|
| tracker = cv2.TrackerCSRT_create() |
| tracker.init(frames[0], box) |
|
|
| prev_clean = None |
| for i, f in enumerate(frame_files): |
| img = cv2.imread(os.path.join(WORK, f)) |
| ok, b = tracker.update(img) |
| if ok: |
| x,y,w,h = [int(v) for v in b] |
| mask = np.zeros(img.shape[:2], dtype="uint8") |
| cv2.rectangle(mask, (x,y), (x+w,y+h), 255, -1) |
| clean = cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA) |
| if prev_clean is not None: |
| clean = cv2.addWeighted(clean, 0.7, prev_clean, 0.3, 0) |
| else: |
| clean = img |
|
|
| cv2.imwrite(os.path.join(WORK, f"c_{i:04d}.png"), clean) |
| prev_clean = clean |
|
|
| out = os.path.join(WORK, "output.mp4") |
| build_video(out) |
| return out |
|
|
| ui = gr.Interface( |
| fn=process, |
| inputs=gr.Video(label="Upload video (≤8–10 sec)"), |
| outputs=gr.Video(label="Watermark removed (experimental)"), |
| title="Auto + Moving Watermark Remover (AI-assisted)", |
| description=( |
| "Experimental free tool. Best for small or semi-transparent moving watermarks. " |
| "Upload only videos you own or have permission to edit." |
| ) |
| ) |
|
|
| ui.launch() |
|
|