diff --git a/backend/Dockerfile b/backend/Dockerfile index 49c0c65..5b753bd 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -3,6 +3,10 @@ FROM node:18-alpine AS base # Install system dependencies RUN apk add --no-cache libc6-compat libreoffice ttf-liberation ffmpeg +RUN apk add --update --no-cache python3 py3-pip \ + && ln -sf python3 /usr/bin/python \ + && pip3 install --no-cache --upgrade pip setuptools \ +RUN pip3 install --no-cache watchdog gradio rembg WORKDIR /app diff --git a/backend/server.ts b/backend/server.ts index 6051ea6..2a8bdf5 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -9,6 +9,7 @@ import { tmzConvert } from "./src/routes/tmzconvert.route"; import { generateQRCode } from "./src/routes/generateqrcode.route"; import { wordCounter } from "./src/routes/wordcounter.route"; import { videoToAudio } from "./src/routes/videotoaudio.route"; +import { removeBG } from "./src/routes/removebg.route"; const app = Fastify({ logger: true }); @@ -27,6 +28,7 @@ app.register(tmzConvert); app.register(generateQRCode); app.register(wordCounter); app.register(videoToAudio); +app.register(removeBG); const PORT = process.env.PORT || 4000; app.listen({ port: Number(PORT), host: "0.0.0.0" }, () => { diff --git a/backend/src/routes/removebg.route.ts b/backend/src/routes/removebg.route.ts new file mode 100644 index 0000000..ed55bf9 --- /dev/null +++ b/backend/src/routes/removebg.route.ts @@ -0,0 +1,78 @@ +import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; +import { promises as fs } from "fs"; +import * as path from "path"; +import { randomUUID } from "crypto"; +import { spawn } from "child_process"; + +export async function removeBG(app: FastifyInstance) { + app.post( + "/api/remove-bg", + async (request: FastifyRequest, reply: FastifyReply) => { + const tmpDir = path.join(process.cwd(), "tmp"); + const sessionId = randomUUID(); + const inputPath = path.join(tmpDir, `input-${sessionId}.png`); + const outputPath = path.join(tmpDir, `output-${sessionId}.png`); + + try { + const parts = request.parts(); + await fs.mkdir(tmpDir, { recursive: true }); + + let fileBuffer: Buffer | null = null; + + for await (const part of parts) { + if (part.type === "file") { + fileBuffer = await part.toBuffer(); + } + } + + if (!fileBuffer) { + return reply.status(400).send({ error: "No file uploaded!" }); + } + + await fs.writeFile(inputPath, fileBuffer); + console.log("Received file, buffer length:", fileBuffer.length); + + await new Promise((resolve, reject) => { + const pythonProcess = spawn("rembg", ["i", inputPath, outputPath]); + + pythonProcess.stderr.on("data", (data) => { + console.log(`ffmpeg stderr: ${data}`); + }); + + pythonProcess.on("close", (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`rembg process exited with code ${code}`)); + } + }); + + pythonProcess.on("error", (err) => { + reject(err); + }); + }); + + const outputImageBuffer = await fs.readFile(outputPath); + await Promise.all([fs.unlink(inputPath), fs.unlink(outputPath)]); + + reply + .header("Content-Type", "image/png") + .header("Content-Disposition", `attachment; filename="converted.png"`) + .status(200) + .send(outputImageBuffer); + } catch (error) { + try { + await Promise.all([ + fs.unlink(inputPath).catch(() => {}), + fs.unlink(outputPath).catch(() => {}), + ]); + } catch (cleanupError) { + console.error("Cleanup error:", cleanupError); + } + + console.error("Convert error:", error); + reply.status(500).send({ error: "Error while converting!" }); + } + }, + ); +} diff --git a/frontend/src/app/bg-remover/layout.tsx b/frontend/src/app/bg-remover/layout.tsx new file mode 100644 index 0000000..4f2608a --- /dev/null +++ b/frontend/src/app/bg-remover/layout.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import type { Metadata } from "next"; +import { toolLinks } from "@/constants"; + +export const metadata: Metadata = { + title: toolLinks[6].title, + description: "Remove backgrounds from your images!", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/frontend/src/app/bg-remover/page.tsx b/frontend/src/app/bg-remover/page.tsx new file mode 100644 index 0000000..7ddf051 --- /dev/null +++ b/frontend/src/app/bg-remover/page.tsx @@ -0,0 +1,96 @@ +"use client"; +import React, { useState } from "react"; +import Navbar from "../../components/Navbar"; +import Footer from "../../components/Footer"; +import Button from "../../components/Button"; + +export default function BGRemover() { + const [file, setFile] = useState(null); + const [loading, setLoading] = useState(false); + + const handleFileChange = (event: React.ChangeEvent) => { + event.preventDefault(); + + if (event.target.files && event.target.files.length > 0) { + const selectedFile = event.target.files[0]; + setFile(selectedFile); + } + }; + + const removeBG = async () => { + if (!file) { + alert("No file selected"); + return; + } else if (file.size > 5 * 1024 * 1024) { + alert("File size should be less than 5MB"); + return; + } + + const formData = new FormData(); + formData.append("file", file); + + setLoading(true); + + try { + const response = await fetch(process.env.backend_url + "/api/remove-bg", { + method: "POST", + body: formData, + }); + + if (!response.ok) { + console.error(`Error: ${response.statusText}`); + return; + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const filename = file.name.split(".")[0]; + + const a = document.createElement("a"); + a.href = url; + a.download = `${filename}.png`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + setTimeout(() => { + URL.revokeObjectURL(url); + }, 5000); + } catch (error) { + console.error("Error while converting:", error); + alert("Error while converting!"); + } finally { + setLoading(false); + } + }; + + return ( +
+ +
+

bg-remover

+
+ +
+
+
+
+
+ ); +}