You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
80 lines
2.3 KiB
80 lines
2.3 KiB
// convert.js |
|
|
|
const fs = require("fs"); |
|
const path = require("path"); |
|
const { spawn, execSync } = require("child_process"); |
|
|
|
// Ensure output folder exists |
|
if (!fs.existsSync("output")) fs.mkdirSync("output"); |
|
|
|
// Step 1: Search for files |
|
const inputDir = "inputs"; |
|
const files = fs.readdirSync(inputDir); |
|
|
|
const audioFile = files.find(f => f.toLowerCase().endsWith(".wav")); |
|
const imageFile = files.find(f => f.toLowerCase().match(/\.(jpg|jpeg|svg|png)$/)); |
|
|
|
if (!audioFile || !imageFile) { |
|
console.error("❌ Missing .wav and/or .jpg/.svg file in inputs/"); |
|
process.exit(1); |
|
} |
|
|
|
const audioPath = path.join(inputDir, audioFile); |
|
const imagePath = path.join(inputDir, imageFile); |
|
const outputName = `output-${Date.now() + Math.random() * 1000}.mp4`; |
|
const outputPath = path.join("output", outputName); |
|
|
|
// Step 2: Get audio duration |
|
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"); |
|
process.exit(1); |
|
} |
|
|
|
// Step 3: Convert using ffmpeg |
|
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", |
|
outputPath |
|
]; |
|
|
|
console.log(`🚀 Starting conversion: ${path.basename(imageFile)} + ${path.basename(audioFile)} → ${outputName}`); |
|
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; |
|
} |
|
|
|
console.log(`✅ Conversion complete! Saved to: ${outputPath}`); |
|
});
|
|
|