Saving an opus stream from a voice channel to .mp3/.wav/.webm

So with the help of another thread here, I managed to save an opus stream to .ogg. Now, I need to save that same opus stream to an .mp3 file instead, and i’m even more stuck than last time. Can I somehow save the audio to a .mp3 or .wav file instead? Help would be much mich appreciated…
const { opus } = require("prism-media");

//other stuff

let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
const oggStream = new opus.OggLogicalBitstream({
opusHead: new opus.OpusHead({
channelCount: 2,
sampleRate: 48000
}),
pageSizeControl: {
maxPackets: 10
}
});

let buffer = [];
oggStream.on("data", (chunk) => {
buffer.push(chunk);
});

opusStream.on("end", async () => {
logReliably(user.globalName + " stopped speaking. " + getTimeElapsedSinceLastCall());
oggStream.destroy()
fs.createWriteStream('output.ogg').write(Buffer.concat(buffer))
buffer = []
});

opusStream.pipe(oggStream)
const { opus } = require("prism-media");

//other stuff

let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
const oggStream = new opus.OggLogicalBitstream({
opusHead: new opus.OpusHead({
channelCount: 2,
sampleRate: 48000
}),
pageSizeControl: {
maxPackets: 10
}
});

let buffer = [];
oggStream.on("data", (chunk) => {
buffer.push(chunk);
});

opusStream.on("end", async () => {
logReliably(user.globalName + " stopped speaking. " + getTimeElapsedSinceLastCall());
oggStream.destroy()
fs.createWriteStream('output.ogg').write(Buffer.concat(buffer))
buffer = []
});

opusStream.pipe(oggStream)
37 Replies
d.js toolkit
d.js toolkit5mo ago
- What's your exact discord.js npm list discord.js and node node -v version? - Not a discord.js issue? Check out #other-js-ts. - Consider reading #how-to-get-help to improve your question! - Explain what exactly your issue is. - Post the full error stack trace, not just the top part! - Show your code! - Issue solved? Press the button! - Marked as resolved by OP
ThePedroo
ThePedroo5mo ago
You must use ffmpeg for that You can use prism-media's ffmpeg class, which is basically a cp.spawn Then, pipe to ffmpeg, and write the output to a file Also, if you're not going to pipe with createWriteStream, use fs.writeFile
KaiFireborn
KaiFireborn5mo ago
async function onStartedSpeaking(userId, voiceChannel) {
let user = await getUserById(userId);

let listener = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
let audioOutput = new prism.FFmpeg({
args: ["-f", "opus", "-ar", "48000", "-ac", "2", "-codec:a", "libmp3lame", "-i", "pipe:0"]
});
listener.pipe(audioOutput);

buffer = []
audioOutput.on("data", (data) => {
buffer.push(data);
});

listener.on("end", async () => {
audioOutput.end();

const audioOutputFilename = "output.mp3";
saveOutput(audioOutputFilename, buffer);

await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});
}
async function onStartedSpeaking(userId, voiceChannel) {
let user = await getUserById(userId);

let listener = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
let audioOutput = new prism.FFmpeg({
args: ["-f", "opus", "-ar", "48000", "-ac", "2", "-codec:a", "libmp3lame", "-i", "pipe:0"]
});
listener.pipe(audioOutput);

buffer = []
audioOutput.on("data", (data) => {
buffer.push(data);
});

listener.on("end", async () => {
audioOutput.end();

const audioOutputFilename = "output.mp3";
saveOutput(audioOutputFilename, buffer);

await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});
}
To be honest, I don't feel to sure in this field at all. Is that the way I'm supposed to do it? I keep getting EOF.
ThePedroo
ThePedroo5mo ago
You shouldn't need to manually end audioOutput Please also provide backtrace of the error
KaiFireborn
KaiFireborn5mo ago
Oh, sorry, completely fogot. So basically, this works perfectly: (without converting the file to mp3, of course)
async function onStartedSpeaking(userId, voiceChannel) {
getTimeElapsedSinceLastCall();
let user = await getUserById(userId);
logReliably("🟢 " + user.globalName + " started speaking. " + getTimeElapsedSinceLastCall());
setMuteSelectively(true, false, true, voiceChannel, user.id);

// voiceReceiver.speaking.removeAllListeners();
let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
let audioOutput = new prism.FFmpeg({
args: ["-f", "opus", "-ar", "48000", "-ac", "2", "-codec:a", "libmp3lame", "-i", "pipe:0"]
});
// opusStream.pipe(audioOutput);

buffer = []
opusStream.on("data", (chunk) => {
buffer.push(chunk);
});

opusStream.on("end", async () => {
logReliably("🟠 " + user.globalName + " stopped speaking, saving. " + getTimeElapsedSinceLastCall());
setMuteSelectively(false, true, true, voiceChannel, user.id);

const audioOutputFilename = "output.mp3";
saveOutput(audioOutputFilename, buffer);

// await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});
}
async function onStartedSpeaking(userId, voiceChannel) {
getTimeElapsedSinceLastCall();
let user = await getUserById(userId);
logReliably("🟢 " + user.globalName + " started speaking. " + getTimeElapsedSinceLastCall());
setMuteSelectively(true, false, true, voiceChannel, user.id);

// voiceReceiver.speaking.removeAllListeners();
let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
let audioOutput = new prism.FFmpeg({
args: ["-f", "opus", "-ar", "48000", "-ac", "2", "-codec:a", "libmp3lame", "-i", "pipe:0"]
});
// opusStream.pipe(audioOutput);

buffer = []
opusStream.on("data", (chunk) => {
buffer.push(chunk);
});

opusStream.on("end", async () => {
logReliably("🟠 " + user.globalName + " stopped speaking, saving. " + getTimeElapsedSinceLastCall());
setMuteSelectively(false, true, true, voiceChannel, user.id);

const audioOutputFilename = "output.mp3";
saveOutput(audioOutputFilename, buffer);

// await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});
}
However, the moment I uncomment opusStream.pipe(audioOutput);, "end" never gets emitted on opusStream So I tried rewiring both events to audioOutput:
audioOutput.on("data", (chunk) => {
buffer.push(chunk);
});

audioOutput.on("end", async () => {
logReliably("🟠 " + user.globalName + " stopped speaking, saving. " + getTimeElapsedSinceLastCall());
setMuteSelectively(false, true, true, voiceChannel, user.id);

const audioOutputFilename = "output.mp3";
saveOutput(audioOutputFilename, buffer);

// await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});
audioOutput.on("data", (chunk) => {
buffer.push(chunk);
});

audioOutput.on("end", async () => {
logReliably("🟠 " + user.globalName + " stopped speaking, saving. " + getTimeElapsedSinceLastCall());
setMuteSelectively(false, true, true, voiceChannel, user.id);

const audioOutputFilename = "output.mp3";
saveOutput(audioOutputFilename, buffer);

// await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});
But now the stream's "end" gets emitted almost immediately (40ms, independent of when opusStream (the user talking) actualkly ends)... I suspect the args for FFmpeg are wrong to some extent, but I lack the experience with the module to actually tell where
ThePedroo
ThePedroo5mo ago
Try removing -i pipe:0, it should be added by prism-media by default
KaiFireborn
KaiFireborn5mo ago
That actually works! The last problem is that the written file is unreadable, yet again.
ThePedroo
ThePedroo5mo ago
If you want to save to a file Use -i filename, don't use fs That way you have faster execution and allows you to not need to save all data to an array If you simply want to send to Discord, you don't need to save to a file though
KaiFireborn
KaiFireborn5mo ago
No, I have to send the file to a different API that only accepts files Well I tried, but the moment I add that "-i" flag, it goes back to the 40ms problem
ThePedroo
ThePedroo5mo ago
Unless it's a library, it will always accept buffers -i file.mp3?
KaiFireborn
KaiFireborn5mo ago
Yeah args: ["-f", "opus", "-ar", "48000", "-ac", "2", "-codec:a", "libmp3lame", "-i", "output.mp3"]
ThePedroo
ThePedroo5mo ago
oops 🤦‍♂️ Messed up with the arguments remove the -i
KaiFireborn
KaiFireborn5mo ago
Oh I wondered, it means input, right?
ThePedroo
ThePedroo5mo ago
Yep, I confused it with output
KaiFireborn
KaiFireborn5mo ago
Okay, the file is still not playable, and now it seems to trigger on("end") over and over again Because instead of a single "started speaking/stopped speaking" log it's just spam
ThePedroo
ThePedroo5mo ago
Save the opus to a file, and try using ffmpeg via command-line to see what happens
KaiFireborn
KaiFireborn5mo ago
// voiceReceiver.speaking.removeAllListeners();
let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
buffer = []
opusStream.on("data", (chunk) => {
buffer.push(chunk);
});
// let audioOutput = new prism.FFmpeg({
// args: ["-f", "opus", "-ar", "48000", "-ac", "2", "-codec:a", "libmp3lame", "output.mp3"]
// });
// opusStream.pipe(audioOutput);

opusStream.on("end", async () => {
logReliably("🟠 " + user.globalName + " stopped speaking, saving. " + getTimeElapsedSinceLastCall());
setMuteSelectively(false, true, true, voiceChannel, user.id);

const audioOutputFilename = "output.mp3";
saveOutput("test.opus", buffer);

// await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});
// voiceReceiver.speaking.removeAllListeners();
let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
buffer = []
opusStream.on("data", (chunk) => {
buffer.push(chunk);
});
// let audioOutput = new prism.FFmpeg({
// args: ["-f", "opus", "-ar", "48000", "-ac", "2", "-codec:a", "libmp3lame", "output.mp3"]
// });
// opusStream.pipe(audioOutput);

opusStream.on("end", async () => {
logReliably("🟠 " + user.globalName + " stopped speaking, saving. " + getTimeElapsedSinceLastCall());
setMuteSelectively(false, true, true, voiceChannel, user.id);

const audioOutputFilename = "output.mp3";
saveOutput("test.opus", buffer);

// await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});
Well "end" triggers correctly - but only if that .on("data) event is there Not sure if the file has anything usable inside, but one gets written and it takes 8KB; The ones written by ffmpeg are all 0KB I don't remember ever being this confused before -_- Actually no, ffmpeg doesn't write a file at all I tried saving it into a buffer and putting that into a fs write stream, and the written file is still 0KB. I think the ffmpeg command ight still be wrong
ThePedroo
ThePedroo5mo ago
OOps
KaiFireborn
KaiFireborn5mo ago
Okay nevermind, it totally works
ThePedroo
ThePedroo5mo ago
Discord buggy With cmd or with the code?
KaiFireborn
KaiFireborn5mo ago
I downloaded a different file and it converted it with those arguments. With cmd Oh not anymore Hang on I've got an idea
ThePedroo
ThePedroo5mo ago
Also, you forgot to .end the opusStream wait nvm
KaiFireborn
KaiFireborn5mo ago
Doesn't it end automatically through discordjs? Yeah
ThePedroo
ThePedroo5mo ago
Yep, I was confusing with fs write stream Ensure you're properly saving
KaiFireborn
KaiFireborn5mo ago
Okay well, cmd doesn't work if you specify the format to opus However, the part in js still doesn't save the file at all
let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});

let audioOutput = new prism.FFmpeg({
args: ["-ar", "48000", "-ac", "2", "-codec:a", "libmp3lame", "output.mp3"]
});
opusStream.pipe(audioOutput);


audioOutput.on("end", async () => {
logReliably("🟠 " + user.globalName + " stopped speaking, saving. " + getTimeElapsedSinceLastCall());
setMuteSelectively(false, true, true, voiceChannel, user.id);

// await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});
let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});

let audioOutput = new prism.FFmpeg({
args: ["-ar", "48000", "-ac", "2", "-codec:a", "libmp3lame", "output.mp3"]
});
opusStream.pipe(audioOutput);


audioOutput.on("end", async () => {
logReliably("🟠 " + user.globalName + " stopped speaking, saving. " + getTimeElapsedSinceLastCall());
setMuteSelectively(false, true, true, voiceChannel, user.id);

// await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});
Right now it looks like this... Thanks for your time, by the way
ThePedroo
ThePedroo5mo ago
What outputs if you ffprobe inputfile? Np also
KaiFireborn
KaiFireborn5mo ago
I don't have an actual input file since it all comes from the opus stream, but trying it with the placeholder one I get
ThePedroo
ThePedroo5mo ago
Save it to a file, and execute ffprobe
KaiFireborn
KaiFireborn5mo ago
let buffer = [];
opusStream.on("data", (data) => {
buffer.push(data);
});

opusStream.on("end", async () => {
fs.writeFileSync("output.opus", Buffer.concat(buffer));
})
let buffer = [];
opusStream.on("data", (data) => {
buffer.push(data);
});

opusStream.on("end", async () => {
fs.writeFileSync("output.opus", Buffer.concat(buffer));
})
Like this, right?
ffprobe version 6.1.1 Copyright (c) 2007-2023 the FFmpeg developers
built with Apple clang version 15.0.0 (clang-1500.1.0.2.5)
configuration: --prefix=/usr/local/Cellar/ffmpeg/6.1.1_2 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags='-Wl,-ld_classic' --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libaribb24 --enable-libbluray --enable-libdav1d --enable-libharfbuzz --enable-libjxl --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopenvino --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-audiotoolbox
libavutil 58. 29.100 / 58. 29.100
libavcodec 60. 31.102 / 60. 31.102
libavformat 60. 16.100 / 60. 16.100
libavdevice 60. 3.100 / 60. 3.100
libavfilter 9. 12.100 / 9. 12.100
libswscale 7. 5.100 / 7. 5.100
libswresample 4. 12.100 / 4. 12.100
libpostproc 57. 3.100 / 57. 3.100
output.opus: Invalid data found when processing input
ffprobe version 6.1.1 Copyright (c) 2007-2023 the FFmpeg developers
built with Apple clang version 15.0.0 (clang-1500.1.0.2.5)
configuration: --prefix=/usr/local/Cellar/ffmpeg/6.1.1_2 --enable-shared --enable-pthreads --enable-version3 --cc=clang --host-cflags= --host-ldflags='-Wl,-ld_classic' --enable-ffplay --enable-gnutls --enable-gpl --enable-libaom --enable-libaribb24 --enable-libbluray --enable-libdav1d --enable-libharfbuzz --enable-libjxl --enable-libmp3lame --enable-libopus --enable-librav1e --enable-librist --enable-librubberband --enable-libsnappy --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtesseract --enable-libtheora --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libxvid --enable-lzma --enable-libfontconfig --enable-libfreetype --enable-frei0r --enable-libass --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopenvino --enable-libspeex --enable-libsoxr --enable-libzmq --enable-libzimg --disable-libjack --disable-indev=jack --enable-videotoolbox --enable-audiotoolbox
libavutil 58. 29.100 / 58. 29.100
libavcodec 60. 31.102 / 60. 31.102
libavformat 60. 16.100 / 60. 16.100
libavdevice 60. 3.100 / 60. 3.100
libavfilter 9. 12.100 / 9. 12.100
libswscale 7. 5.100 / 7. 5.100
libswresample 4. 12.100 / 4. 12.100
libpostproc 57. 3.100 / 57. 3.100
output.opus: Invalid data found when processing input
Invalid data... I'm not sure if I'm saving it correctly though; When we saved it as .ogg, it used to work
ThePedroo
ThePedroo5mo ago
I'm curious If you add ogg head to it, and try to ffprobe it, what happens? FFmpeg probably doesn't support opus like that
KaiFireborn
KaiFireborn5mo ago
Well I still have an ogg file saved from yesterday Nothing in that respect should've changed, so Input #0, ogg, from 'output.ogg': Duration: 00:00:02.56, start: 0.000000, bitrate: 59 kb/s Stream #0:0: Audio: opus, 48000 Hz, stereo, fltp Yeah it works properly Maybe we have to use some other library after all. I didn't come far with it previously, but maybe discordjs/opus? It has some kind of opus encoder/decoder, but I couldn't find any documentation
ThePedroo
ThePedroo5mo ago
Just add ogg heads and pass to ffmpeg Done
KaiFireborn
KaiFireborn5mo ago
But for that I would need the prism-media alpha version again Wouldn't it break the FFmpeg class? Yeah I have no idea anymore.. Guess I'll try to continue tomorrow, but I'm pretty sure I'm doing that part wrong
ThePedroo
ThePedroo5mo ago
Not really, you'll just need to pipe 2x
KaiFireborn
KaiFireborn5mo ago
let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
const oggStream = new opus.OggLogicalBitstream({
opusHead: new opus.OpusHead({
channelCount: 2,
sampleRate: 48000
}),
pageSizeControl: {
maxPackets: 10
}
});

let buffer = [];
oggStream.on("data", (chunk) => {
buffer.push(chunk);
});



opusStream.on("end", async () => {
logReliably("🟠 " + user.globalName + " stopped speaking, saving. " + getTimeElapsedSinceLastCall());
setMuteSelectively(false, true, true, voiceChannel, user.id);

oggStream.destroy()
let ws = fs.createWriteStream('output.ogg')
ws.write(Buffer.concat(buffer))
logReliably("Finished writing output.ogg")
ws.end()
buffer = []

let audioOutput = new prism.FFmpeg({
args: ["-ar", "48000", "-ac", "2", "-codec:a", "libmp3lame", "-i", "ogg_output.ogg", "mp3_output.mp3"]
});

// await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});

opusStream.pipe(oggStream)
let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
const oggStream = new opus.OggLogicalBitstream({
opusHead: new opus.OpusHead({
channelCount: 2,
sampleRate: 48000
}),
pageSizeControl: {
maxPackets: 10
}
});

let buffer = [];
oggStream.on("data", (chunk) => {
buffer.push(chunk);
});



opusStream.on("end", async () => {
logReliably("🟠 " + user.globalName + " stopped speaking, saving. " + getTimeElapsedSinceLastCall());
setMuteSelectively(false, true, true, voiceChannel, user.id);

oggStream.destroy()
let ws = fs.createWriteStream('output.ogg')
ws.write(Buffer.concat(buffer))
logReliably("Finished writing output.ogg")
ws.end()
buffer = []

let audioOutput = new prism.FFmpeg({
args: ["-ar", "48000", "-ac", "2", "-codec:a", "libmp3lame", "-i", "ogg_output.ogg", "mp3_output.mp3"]
});

// await transcribe_and_reply(audioOutputFilename);
setMuteSelectively(true, false, false, voiceChannel, user.id);
});

opusStream.pipe(oggStream)
Okay I tried real quick, and the problem with end triggering over and over is there again. I'll see what I can do in way of debugging tomorrow, but Okay so being me I couldn't just let the problem hang and focus on my other work So in the end, I just decided to run the ffmeg command through the terminal instead of using that prism-media class to convert the written .ogg file to .mp3.
async function onStartedSpeaking(userId, voiceChannel) {
let user = await getUserById(userId);

let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
const oggStream = new opus.OggLogicalBitstream({
opusHead: new opus.OpusHead({
channelCount: 2,
sampleRate: 48000
}),
pageSizeControl: {
maxPackets: 10
}
});

let buffer = [];
oggStream.on("data", (chunk) => {
buffer.push(chunk);
});

opusStream.on("end", async () => {
oggStream.destroy()
let ws = fs.createWriteStream('output.ogg')
ws.write(Buffer.concat(buffer))
logReliably("Finished writing output.ogg")
ws.end()
buffer = []

exec("ffmpeg -y -i output.ogg -ar 48000 -ac 2 -codec:a libmp3lame output.mp3")
});

opusStream.pipe(oggStream)
}
async function onStartedSpeaking(userId, voiceChannel) {
let user = await getUserById(userId);

let opusStream = voiceReceiver.subscribe(userId, {
end: {
behavior: EndBehaviorType.AfterSilence,
duration: allowedPauseDurationForInputVoice
}
});
const oggStream = new opus.OggLogicalBitstream({
opusHead: new opus.OpusHead({
channelCount: 2,
sampleRate: 48000
}),
pageSizeControl: {
maxPackets: 10
}
});

let buffer = [];
oggStream.on("data", (chunk) => {
buffer.push(chunk);
});

opusStream.on("end", async () => {
oggStream.destroy()
let ws = fs.createWriteStream('output.ogg')
ws.write(Buffer.concat(buffer))
logReliably("Finished writing output.ogg")
ws.end()
buffer = []

exec("ffmpeg -y -i output.ogg -ar 48000 -ac 2 -codec:a libmp3lame output.mp3")
});

opusStream.pipe(oggStream)
}
Once again, thanks a ton for your help - it's probably what you meant earlier, too. Definitely couldn't have done it alone..
ThePedroo
ThePedroo5mo ago
(Also, prism-media also executes through shell, soo..)
KaiFireborn
KaiFireborn5mo ago
(Funny, couldn't get it to work that way)