const express = require("express"); const multer = require("multer"); const { spawn, execSync } = require("child_process"); const fs = require("fs"); const app = express(); const upload = multer({ dest: "uploads/" }); app.post("/convert", upload.fields([{ name: "audio" }, { name: "image" }]), async (req, res) => { if (!req.files?.audio || !req.files?.image) { return res.status(400).send("Missing audio or image file."); } const audioPath = req.files.audio[0].path; const imagePath = req.files.image[0].path; const output = `output-${Date.now()}.mp4`; let durationSec = 0; try { const durationStr = execSync( `ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 ${audioPath}` ).toString().trim(); durationSec = parseFloat(durationStr); console.log(`๐ŸŽง Audio duration: ${durationSec.toFixed(2)} sec`); } catch (err) { console.error("โŒ Could not determine audio duration"); return res.status(500).send("Failed to analyze audio."); } const args = [ "-loop", "1", "-i", imagePath, "-i", audioPath, "-c:v", "libx264", "-preset", "ultrafast", "-tune", "stillimage", "-crf", "23", "-c:a", "aac", "-shortest", "-pix_fmt", "yuv420p", "-movflags", "+faststart", output ]; console.log(`๐Ÿš€ Starting conversion to ${output}`); const ffmpeg = spawn("ffmpeg", args); ffmpeg.stderr.on("data", (data) => { const line = data.toString().trim(); const timeMatch = line.match(/time=(\d+):(\d+):([\d.]+)/); if (timeMatch) { const [, h, m, s] = timeMatch; const timeSec = parseInt(h) * 3600 + parseInt(m) * 60 + parseFloat(s); const percent = Math.min(100, ((timeSec / durationSec) * 100).toFixed(1)); console.log(`${line} | ๐Ÿ“Š ${percent}%`); } else { console.log(line); } }); ffmpeg.on("exit", (code) => { if (code !== 0) { console.error(`โŒ FFmpeg exited with code ${code}`); return res.status(500).send("Conversion failed"); } console.log("โœ… Conversion complete. Sending file..."); res.setHeader('Content-Type', 'video/mp4'); res.setHeader('Content-Disposition', `attachment; filename="${output}"`); const stream = fs.createReadStream(output); stream.pipe(res); stream.on('close', () => { fs.unlinkSync(audioPath); fs.unlinkSync(imagePath); fs.unlinkSync(output); console.log("๐Ÿงน Cleaned up temp files"); }); stream.on('error', (err) => { console.error("โŒ Stream error:", err); res.status(500).send("Error streaming file."); }); }); }); app.get("/", (req, res) => { res.send("๐ŸŽง FileConvert API is running."); }); app.listen(3000, () => console.log("โœ… Listening on http://0.0.0.0:3000"));