From 753fd52701830c234f6ee7762fcbdc349cb932de Mon Sep 17 00:00:00 2001 From: Atanner Date: Mon, 14 Apr 2025 22:55:18 -0600 Subject: [PATCH] Enhance audio conversion endpoint with duration analysis and progress logging --- index.js | 68 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/index.js b/index.js index 93cab02..8694e4c 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,13 @@ const express = require("express"); const multer = require("multer"); -const { spawn } = require("child_process"); +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" }]), (req, res) => { - if (!req.files || !req.files.audio || !req.files.image) { - console.error("❌ Missing file(s) in request"); +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."); } @@ -16,56 +15,67 @@ app.post("/convert", upload.fields([{ name: "audio" }, { name: "image" }]), (req 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", "fast", + "-preset", "ultrafast", "-tune", "stillimage", - "-crf", "18", + "-crf", "23", "-c:a", "aac", "-shortest", "-pix_fmt", "yuv420p", + "-movflags", "+faststart", output ]; - console.log(`🚀 Starting FFmpeg conversion: ${output}`); + console.log(`🚀 Starting conversion to ${output}`); const ffmpeg = spawn("ffmpeg", args); - let lastLog = Date.now(); - ffmpeg.stderr.on("data", (data) => { - const now = Date.now(); - const text = data.toString(); + const line = data.toString().trim(); + const timeMatch = line.match(/time=(\d+):(\d+):([\d.]+)/); - // Log a progress update every 3 seconds max - if (text.includes("frame=") && now - lastLog > 3000) { - console.log(`[FFmpeg Progress] ${text.trim()}`); - lastLog = now; + 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); } - - // If you want verbose output uncomment this: - // console.log(`[FFmpeg Raw] ${text.trim()}`); }); ffmpeg.on("exit", (code) => { if (code !== 0) { - console.error(`❌ FFmpeg failed with code ${code}`); - res.status(500).send("Conversion failed"); - } else { - console.log("✅ FFmpeg finished successfully. Sending file..."); - res.download(output, () => { - fs.unlinkSync(audioPath); - fs.unlinkSync(imagePath); - fs.unlinkSync(output); - }); + console.error(`❌ FFmpeg exited with code ${code}`); + return res.status(500).send("Conversion failed"); } + + console.log("✅ Conversion complete. Sending file..."); + res.download(output, () => { + fs.unlinkSync(audioPath); + fs.unlinkSync(imagePath); + fs.unlinkSync(output); + }); }); }); app.get("/", (req, res) => { - res.send("🎧 FileConvert API is up."); + res.send("🎧 FileConvert API is running."); }); -app.listen(3000, () => console.log("✅ FileConvert API running on http://0.0.0.0:3000")); +app.listen(3000, () => console.log("✅ Listening on http://0.0.0.0:3000"));