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 ? (
<>
{/* Overlay for drawing selection: capture events on top of the video */}
{/* Visual selection shown as absolute positioned box */}
>
) : (
No video loaded — upload to begin.
)}
Draw a rectangle over the logo (click & drag) on the video preview. For moving logos, use server-side
inpainting that processes frames.
0 Comments