I'm working on a project to generate a click track using React.js and FFmpeg. The project includes an Express.js backend where I construct an FFmpeg command to combine multiple audio files (count-ins, vocal cues, and click sounds) into a single track. The final output should be an MP3 file.
However, I'm encountering an error when running the FFmpeg command:
Cannot find a matching stream for unlabeled input pad 0 on filter Parsed_amix_54
How can I correctly construct the FFmpeg command to avoid the "Cannot find a matching stream for unlabeled input pad" error and ensure all inputs are properly processed and mixed?
Here is the relevant part of my server.js code:
const express = require('express');
const bodyParser = require('body-parser');
const { exec } = require('child_process');
const path = require('path');
const fs = require('fs');
const ffmpegPath = require('ffmpeg-static');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(bodyParser.json());
app.post('/generate-click-track', (req, res) => {
const { tempo, timeSignature, clickSound, subdivisions, sections } = req.body;
const clickSoundPath = path.join(__dirname, 'public', 'click-sounds');
const vocalCuesPath = path.join(__dirname, 'public', 'vocal-cues');
const beatsPerBar = parseInt(timeSignature.split('/')[0]);
const beatsPerMinute = tempo;
let totalBeats = 0;
const sectionStarts = [];
sections.forEach((section, index) => {
if (index > 0) {
sectionStarts.push(totalBeats);
}
totalBeats += section.length * beatsPerBar;
});
const outputFilePath = path.join(__dirname, 'output', 'click-track.mp3');
const tempDir = path.join(__dirname, 'temp');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir);
}
const countInFiles = Array.from({ length: beatsPerBar }, (_, i) => path.join(vocalCuesPath, 'count-ins', `${i + 1}.wav`));
let ffmpegCommand = '';
// Add count-in files
countInFiles.forEach(file => {
ffmpegCommand += `-i "${file}" `;
});
// Add section vocal cues and click sounds
sections.forEach((section, index) => {
const vocalCueFile = path.join(vocalCuesPath, `${section.name}.wav`);
ffmpegCommand += `-i "${vocalCueFile}" `;
for (let i = 0; i < section.length * beatsPerBar; i++) {
const clickFile = i % beatsPerBar === 0 ? `${clickSound}-accents.mp3` : `${clickSound}.mp3`;
ffmpegCommand += `-i "${path.join(clickSoundPath, clickFile)}" `;
}
});
ffmpegCommand += `-filter_complex "`;
let inputCount = 0;
countInFiles.forEach((_, index) => {
ffmpegCommand += `[${inputCount}:0]adelay=${index * (60 / beatsPerMinute) * 1000}|${index * (60 / beatsPerMinute) * 1000}[a${inputCount}]; `;
inputCount++;
});
sections.forEach((section, index) => {
const delay = (sectionStarts[index] ? sectionStarts[index] : 0) * (60 / beatsPerMinute) * 1000;
ffmpegCommand += `[${inputCount}:0]adelay=${delay}|${delay}[a${inputCount}]; `;
inputCount++;
for (let i = 0; i < section.length * beatsPerBar; i++) {
const delay = (sectionStarts[index] ? sectionStarts[index] : 0) * (60 / beatsPerMinute) * 1000 + (i * 60 / beatsPerMinute) * 1000;
ffmpegCommand += `[${inputCount}:0]adelay=${delay}|${delay}[a${inputCount}]; `;
inputCount++;
}
});
ffmpegCommand += `amix=inputs=${inputCount}:duration=longest" -codec:a libmp3lame -b:a 192k -y "${outputFilePath}"`;
console.log(`Executing ffmpeg command: ${ffmpegCommand}`);
exec(`${ffmpegPath} ${ffmpegCommand}`, (error, stdout, stderr) => {
if (error) {
console.error(`Error generating click track: ${error.message}`);
res.status(500).send('Error generating click track');
return;
}
res.download(outputFilePath, 'click-track.mp3', (err) => {
if (err) {
console.error(`Error sending file: ${err.message}`);
}
// Clean up temp directory
fs.readdir(tempDir, (err, files) => {
if (err) throw err;
for (const file of files) {
fs.unlink(path.join(tempDir, file), err => {
if (err) throw err;
});
}
});
});
});
});
app.listen(3001, () => {
console.log('Server running on http://localhost:3001');
});
I can't include the full error messge as it is too long, but if anyone needs it, I'm happy to link to a text file that includes it.
The inputs to the amix filter should be specified e.g. for 4 inputs,
[a0][a1][a2][a3]amix=inputs=4:duration=longest