|
|
|
|
@ -1,14 +1,13 @@
@@ -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
@@ -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")); |
|
|
|
|
|