
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
monarch-stems
Advanced tools
Node and browser client for Stem-Separator-API — separate audio into stems (vocals, drums, bass, etc.) using Spleeter.
npm install monarch-stems
Node — separate by file path, then download the first stem:
const { StemSeparatorClient } = require('monarch-stems');
const fs = require('fs');
const client = new StemSeparatorClient();
const result = await client.separate('/path/to/song.mp3', { stems: '2stems' });
const buffer = await client.downloadStem(result.job_id, result.output_files[0]);
fs.writeFileSync('vocals.wav', Buffer.from(buffer));
Browser — separate a file from an <input type="file">:
import { StemSeparatorClient } from 'monarch-stems';
const client = new StemSeparatorClient();
const file = document.querySelector('input[type=file]').files[0];
const result = await client.separate(file, { stems: '2stems', format: 'mp3' });
const url = client.getStemDownloadUrl(result.job_id, result.output_files[0]);
window.open(url, '_blank');
| Environment | Requirement |
|---|---|
| Node | 18+ for native fetch and FormData. On Node 16 or older, install form-data and axios as dependencies. |
| Browser | Any modern browser with fetch and FormData. |
| API | Default client uses the public Railway API. For self-hosted, set baseUrl (and optionally apiKey) in the constructor. |
Supported audio formats (by the API): MP3, WAV, FLAC, M4A, AAC, OGG.
File size: The public API may enforce a max upload size (e.g. 100MB). See the API repo for MAX_UPLOAD_SIZE and limits.
import { StemSeparatorClient } from 'monarch-stems';
import fs from 'fs';
const client = new StemSeparatorClient();
const result = await client.separate('./song.mp3', { stems: '2stems', format: 'wav' });
for (const name of result.output_files) {
const buf = await client.downloadStem(result.job_id, name);
fs.writeFileSync(name, Buffer.from(buf));
}
const { StemSeparatorClient } = require('monarch-stems');
const fs = require('fs');
const client = new StemSeparatorClient();
const result = await client.separate('/path/to/track.mp3', {
stems: '4stems',
format: 'mp3',
bitrate: '320k',
});
console.log('Stems:', result.output_files); // e.g. ['vocals.mp3', 'drums.mp3', 'bass.mp3', 'other.mp3']
for (const name of result.output_files) {
const buffer = await client.downloadStem(result.job_id, name);
fs.writeFileSync(name, Buffer.from(buffer));
}
const result = await client.separate('/path/to/track.wav', {
stems: '5stems',
format: 'wav',
});
const client = new StemSeparatorClient({
baseUrl: 'https://your-stem-api.example.com',
apiKey: process.env.STEM_API_KEY,
timeout: 600000, // 10 min
});
const result = await client.separate('/path/to/audio.flac', { stems: '2stems' });
const client = new StemSeparatorClient();
const health = await client.checkHealth();
if (health.status !== 'healthy') {
console.error('API is not healthy:', health);
process.exit(1);
}
const result = await client.separate(file, { stems: '2stems' });
const { StemSeparatorClient, StemSeparatorError, ErrorCode } = require('monarch-stems');
const client = new StemSeparatorClient({ timeout: 300000 });
let lastErr;
for (let attempt = 1; attempt <= 2; attempt++) {
try {
const result = await client.separate(file);
console.log('Job ID:', result.job_id);
break;
} catch (err) {
lastErr = err;
if (err instanceof StemSeparatorError && err.code === ErrorCode.TIMEOUT) {
console.log(`Attempt ${attempt} timed out, retrying...`);
continue;
}
throw err;
}
}
if (lastErr) throw lastErr;
const result = await client.separate(file, { stems: '2stems' });
const vocalsUrl = client.getStemDownloadUrl(result.job_id, result.output_files[0]);
// Use in <a href="...">, window.open(), or pass to another service
console.log('Download:', vocalsUrl);
| Use case | Description |
|---|---|
| CLI / script | Separate files from disk and save stems (Node with require or ESM). |
| Web app | User uploads a file; separate and offer stems as download links or play in-browser. |
| Batch processing | Loop over many files, call separate() for each, then downloadStem() for each output file. |
| Self-hosted API | Point baseUrl and optional apiKey at your own Stem-Separator-API instance. |
| Serverless / edge | Use in serverless functions (e.g. Vercel, Netlify) with timeout and size limits in mind. |
| Karaoke / remix | Get vocals and accompaniment (2stems) or full stems (4/5) for remixing or karaoke. |
#!/usr/bin/env node
const { StemSeparatorClient } = require('monarch-stems');
const fs = require('fs');
const path = process.argv[2];
if (!path) {
console.error('Usage: node separate.js <audio-file>');
process.exit(1);
}
const client = new StemSeparatorClient();
const result = await client.separate(path, { stems: '2stems', format: 'mp3' });
for (const name of result.output_files) {
const buf = await client.downloadStem(result.job_id, name);
fs.writeFileSync(name, Buffer.from(buf));
console.log('Wrote', name);
}
const { StemSeparatorClient } = require('monarch-stems');
const fs = require('fs');
const path = require('path');
const client = new StemSeparatorClient();
const files = ['track1.mp3', 'track2.mp3'];
for (const file of files) {
const result = await client.separate(file, { stems: '2stems' });
const outDir = `stems_${result.job_id}`;
fs.mkdirSync(outDir, { recursive: true });
for (const name of result.output_files) {
const buf = await client.downloadStem(result.job_id, name);
fs.writeFileSync(path.join(outDir, name), Buffer.from(buf));
}
console.log('Done', file, '->', outDir);
}
const { StemSeparatorClient } = require('monarch-stems');
const client = new StemSeparatorClient();
// Optional: { baseUrl: 'https://your-api.example', apiKey: '...', timeout: 300000 }
(async () => {
const result = await client.separate('/path/to/song.mp3', {
stems: '2stems', // '2stems' | '4stems' | '5stems'
format: 'wav', // wav, mp3, flac, m4a, aac, ogg
bitrate: '320k',
});
console.log('Job ID:', result.job_id);
console.log('Output files:', result.output_files);
const url = client.getStemDownloadUrl(result.job_id, result.output_files[0]);
const buffer = await client.downloadStem(result.job_id, result.output_files[0]);
require('fs').writeFileSync('vocals.wav', Buffer.from(buffer));
})();
import { StemSeparatorClient } from 'monarch-stems';
const client = new StemSeparatorClient();
const fileInput = document.querySelector('input[type=file]');
const file = fileInput.files[0];
const result = await client.separate(file, { stems: '2stems', format: 'mp3' });
const url = client.getStemDownloadUrl(result.job_id, result.output_files[0]);
window.open(url, '_blank');
// Or get bytes
const arrayBuffer = await client.downloadStem(result.job_id, result.output_files[0]);
const blob = new Blob([arrayBuffer], { type: 'audio/mpeg' });
| Method / constructor | Description |
|---|---|
new StemSeparatorClient(options?) | Create a client. Options: baseUrl, apiKey, timeout (ms). |
client.separate(file, options?) | Upload audio and run separation. Returns Promise<SeparateResponse>. |
client.getStemDownloadUrl(jobId, filename) | Return URL to download one stem file. |
client.downloadStem(jobId, filename) | Fetch stem file as Promise<ArrayBuffer>. |
client.checkHealth() | Promise<HealthResponse>. |
separate(file, options?)
File or Blob. Node: file path string, Buffer, or Readable stream.stems ('2stems' | '4stems' | '5stems'), format ('wav' | 'mp3' | …), bitrate (e.g. '320k'), filename (form field name).Response (SeparateResponse): success, message, job_id, stems, output_files (array of filenames), processing_time.
All errors thrown by the client are instances of StemSeparatorError with:
code — Stable string for handling (see below).message — Human-readable description.status — Set for API_ERROR (HTTP status code).cause — Original error when available.Error codes
| Code | Meaning |
|---|---|
INVALID_ARGUMENT | Bad input: missing/empty file, baseUrl, jobId, or filename; invalid timeout; or path characters in jobId/filename. |
API_ERROR | API returned an error or non-2xx status. Check err.status and err.message. |
NETWORK_ERROR | Request failed (e.g. connection refused, DNS, CORS in browser). |
TIMEOUT | Request took longer than timeout ms. |
INVALID_RESPONSE | Response was not valid JSON or missing required fields (e.g. job_id, output_files). |
Example
const { StemSeparatorClient, StemSeparatorError, ErrorCode } = require('monarch-stems');
const client = new StemSeparatorClient();
try {
const result = await client.separate(file);
} catch (err) {
if (err instanceof StemSeparatorError) {
if (err.code === ErrorCode.TIMEOUT) {
console.log('Timed out — try a smaller file or increase timeout');
} else if (err.code === ErrorCode.API_ERROR) {
console.log('API error:', err.status, err.message);
} else if (err.code === ErrorCode.INVALID_ARGUMENT) {
console.log('Bad input:', err.message);
}
}
throw err;
}
Use this section to fix most issues without opening a GitHub issue.
separate() was called with null, undefined, or an empty string path.File/Blob (browser) or a non-empty path string / Buffer / Readable (Node). Check that the user actually selected a file before calling separate.new StemSeparatorClient({ timeout: 600000 }) (10 min).MAX_UPLOAD_SIZE in the server config (see Stem-Separator-API).stems is '2stems', '4stems', or '5stems' and format is one of the allowed values. Try another file to rule out corruption.ValueError (or other exception) objects in the JSON response, which cannot be serialized and causes a 500. (2) The client may have sent the file part without a filename, so the server didn’t recognize it as an upload.monarch-stems.app/main.py, in validation_exception_handler, sanitize the error details before returning. Do not put exception objects (e.g. ctx['error']) in the response; use only JSON-serializable values (e.g. str(e) for messages). See docs/API-VALIDATION-FIX.md in this repo for a concrete patch.baseUrl, API down, or CORS.await client.checkHealth() first. If it fails, the API is unreachable or the URL is wrong.baseUrl). If you use a custom baseUrl, the server must allow your origin (CORS).curl the baseUrl/health)./api/v1/separate. If you self-host, pull the latest API code and redeploy.fetch and FormData are available in Node 18+.form-data and axios in your project. The client will use them when fetch is not available.jobId and filename must not contain .. or path separators.job_id and output_files[n] returned by separate(). Do not construct them from user input without sanitizing.new StemSeparatorClient({ baseUrl: 'https://your-api.example', apiKey: 'your-key' }). How to obtain the key depends on your deployment (env var, dashboard, etc.).fs.existsSync(path) before calling separate(path), or catch the error (the API or Node may throw when reading the file).await client.checkHealth(). If it fails, the problem is connectivity or base URL.err.code and err.status (and err.message) when catching StemSeparatorError.noUncheckedIndexedAccess.npm run format:check in CI.Scripts: npm run typecheck, npm run lint, npm run format:check, npm run check (all three), npm test.
Tests: 21 unit tests cover validation, error codes, constructor behavior, and path-traversal safety. No network calls in tests.
npm test
Runs npm run build then Node’s built-in test runner (node --test test/index.test.js). CI runs npm test before publish (see .github/workflows/publish.yml).
MIT
FAQs
Client wrapper for Stem-Separator-API — easy stem separation
We found that monarch-stems demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.