Logo remover

import React, { useRef, useState, useEffect } from "react"; import { createFFmpeg, fetchFile } from "@ffmpeg/ffmpeg"; /** * Logo Remover — Single-file React component * -------------------------------------------------- * Features: * - Upload a video (mp4/webm) * - Visual overlay to select a rectangular logo area (drag to draw) * - Preview selection and choose removal method: Delogo (FFmpeg) or Send to server * - Use ffmpeg.wasm (client-side) to apply FFmpeg's `delogo` filter and export processed video * - Export & download resulting video * * Notes & instructions: * - This is a frontend-only proof-of-concept. ffmpeg.wasm runs fully in-browser but is large. * - For production or large files, run FFmpeg on a backend (Node / Python) and perform AI inpainting * for cleaner results. * - NPM deps: react, @ffmpeg/ffmpeg, tailwindcss (for styling). All code uses Tailwind classes. * - To improve UX: add progress bars, server fallbacks, chunked uploads, and background processing. * * How delogo works: * FFmpeg has a built-in filter `delogo` that can remove / blur a rectangular region: * -vf "delogo=x=100:y=50:w=200:h=80:show=0" * This is fast and often acceptable for small, static logos. */ export default function LogoRemoverApp() { const videoRef = useRef(null); const overlayRef = useRef(null); const [videoFile, setVideoFile] = useState(null); const [videoURL, setVideoURL] = useState(""); const [selection, setSelection] = useState(null); // { x,y,w,h } in video pixels const [isDrawing, setIsDrawing] = useState(false); const [startPos, setStartPos] = useState(null); const [ffmpeg, setFfmpeg] = useState(null); const [ffmpegLoading, setFfmpegLoading] = useState(false); const [processing, setProcessing] = useState(false); const [outputURL, setOutputURL] = useState(""); const [method, setMethod] = useState("delogo"); // or 'server' useEffect(() => { return () => { if (videoURL) URL.revokeObjectURL(videoURL); if (outputURL) URL.revokeObjectURL(outputURL); }; }, [videoURL, outputURL]); async function loadFFmpeg() { if (ffmpeg) return ffmpeg; setFfmpegLoading(true); const _ffmpeg = createFFmpeg({ log: true }); await _ffmpeg.load(); setFfmpeg(_ffmpeg); setFfmpegLoading(false); return _ffmpeg; } function handleFile(e) { const f = e.target.files[0]; if (!f) return; setVideoFile(f); const url = URL.createObjectURL(f); setVideoURL(url); setSelection(null); setOutputURL(""); } function videoClientRect() { const v = videoRef.current; if (!v) return null; return v.getBoundingClientRect(); } function clientToVideoPixels(clientX, clientY) { const rect = videoClientRect(); const v = videoRef.current; if (!rect || !v) return { x: 0, y: 0 }; const videoWidth = v.videoWidth || v.clientWidth; const videoHeight = v.videoHeight || v.clientHeight; const relX = clientX - rect.left; const relY = clientY - rect.top; // Map from client-space to intrinsic video pixels const x = Math.max(0, Math.min(videoWidth, Math.round((relX / rect.width) * videoWidth))); const y = Math.max(0, Math.min(videoHeight, Math.round((relY / rect.height) * videoHeight))); return { x, y }; } function handleMouseDown(e) { if (!videoRef.current) return; setIsDrawing(true); const { x, y } = clientToVideoPixels(e.clientX, e.clientY); setStartPos({ x, y }); setSelection({ x, y, w: 0, h: 0 }); } function handleMouseMove(e) { if (!isDrawing || !startPos) return; const { x, y } = clientToVideoPixels(e.clientX, e.clientY); const sx = Math.min(startPos.x, x); const sy = Math.min(startPos.y, y); const w = Math.abs(x - startPos.x); const h = Math.abs(y - startPos.y); setSelection({ x: sx, y: sy, w, h }); } function handleMouseUp() { setIsDrawing(false); setStartPos(null); } function selectionStyle() { if (!selection || !videoRef.current) return { display: "none" }; const rect = videoClientRect(); if (!rect) return { display: "none" }; const v = videoRef.current; const videoWidth = v.videoWidth || v.clientWidth; const videoHeight = v.videoHeight || v.clientHeight; const left = (selection.x / videoWidth) * rect.width + rect.left; const top = (selection.y / videoHeight) * rect.height + rect.top; const width = (selection.w / videoWidth) * rect.width; const height = (selection.h / videoHeight) * rect.height; return { position: "absolute", left: left + "px", top: top + "px", width: width + "px", height: height + "px", border: "2px dashed #ffd166", backgroundColor: "rgba(255,209,102,0.12)", pointerEvents: "none", zIndex: 40, }; } async function runDelogo() { if (!videoFile || !selection) return alert("Please upload a video and draw a selection over the logo."); setProcessing(true); const ff = await loadFFmpeg(); // Read file into mem ff.FS("writeFile", "input.mp4", await fetchFile(videoFile)); // compute delogo filter using intrinsic video pixels const x = selection.x; const y = selection.y; const w = Math.max(2, selection.w); const h = Math.max(2, selection.h); // delogo expects integer positions; ensure they are within frame const delogoStr = `delogo=x=${x}:y=${y}:w=${w}:h=${h}:show=0`; try { await ff.run("-i", "input.mp4", "-vf", delogoStr, "-c:a", "copy", "output.mp4"); const data = ff.FS("readFile", "output.mp4"); const blob = new Blob([data.buffer], { type: "video/mp4" }); const url = URL.createObjectURL(blob); setOutputURL(url); } catch (err) { console.error(err); alert("Processing failed. See console for details."); } setProcessing(false); } async function sendToServer() { // For AI-based logo removal / inpainting, you would send the video + bbox to a backend // The backend would run robust frame-by-frame inpainting (e.g. using Video Inpainting models) // and return a processed file or job ID. if (!videoFile || !selection) return alert("Upload video and select region first."); setProcessing(true); try { const fd = new FormData(); fd.append("video", videoFile); fd.append("x", selection.x); fd.append("y", selection.y); fd.append("w", selection.w); fd.append("h", selection.h); // TODO: replace with your server endpoint const res = await fetch("/api/remove-logo", { method: "POST", body: fd }); if (!res.ok) throw new Error("Server error"); const blob = await res.blob(); const url = URL.createObjectURL(blob); setOutputURL(url); } catch (err) { console.error(err); alert("Server processing failed. Check server logs and endpoint."); } setProcessing(false); } return (

Logo Remover — Video Tool

Upload a video, draw over the logo, and remove it using FFmpeg's delogo filter or send to your server for advanced inpainting.

{videoURL ? ( <>

Developer notes: This demo uses ffmpeg.wasm which loads a large WASM binary. For production, prefer server-side processing and a job queue for large uploads.

); }

Post a Comment

0 Comments