feat: initial Papo bot scaffold
This commit is contained in:
143
node_modules/prism-media/src/opus/OggDemuxer.js
generated
vendored
Normal file
143
node_modules/prism-media/src/opus/OggDemuxer.js
generated
vendored
Normal file
@@ -0,0 +1,143 @@
|
||||
const { Transform } = require('stream');
|
||||
|
||||
const OGG_PAGE_HEADER_SIZE = 26;
|
||||
const STREAM_STRUCTURE_VERSION = 0;
|
||||
|
||||
const charCode = x => x.charCodeAt(0);
|
||||
const OGGS_HEADER = Buffer.from([...'OggS'].map(charCode));
|
||||
const OPUS_HEAD = Buffer.from([...'OpusHead'].map(charCode));
|
||||
const OPUS_TAGS = Buffer.from([...'OpusTags'].map(charCode));
|
||||
|
||||
/**
|
||||
* Demuxes an Ogg stream (containing Opus audio) to output an Opus stream.
|
||||
* @extends {TransformStream}
|
||||
* @memberof opus
|
||||
*/
|
||||
class OggDemuxer extends Transform {
|
||||
/**
|
||||
* Creates a new OggOpus demuxer.
|
||||
* @param {Object} [options] options that you would pass to a regular Transform stream.
|
||||
* @memberof opus
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
super(Object.assign({ readableObjectMode: true }, options));
|
||||
this._remainder = null;
|
||||
this._head = null;
|
||||
this._bitstream = null;
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, done) {
|
||||
if (this._remainder) {
|
||||
chunk = Buffer.concat([this._remainder, chunk]);
|
||||
this._remainder = null;
|
||||
}
|
||||
|
||||
try {
|
||||
while (chunk) {
|
||||
const result = this._readPage(chunk);
|
||||
if (result) chunk = result;
|
||||
else break;
|
||||
}
|
||||
} catch (error) {
|
||||
done(error);
|
||||
return;
|
||||
}
|
||||
this._remainder = chunk;
|
||||
done();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a page from a buffer
|
||||
* @private
|
||||
* @param {Buffer} chunk the chunk containing the page
|
||||
* @returns {boolean|Buffer} if a buffer, it will be a slice of the excess data of the original, otherwise it will be
|
||||
* false and would indicate that there is not enough data to go ahead with reading this page.
|
||||
*/
|
||||
_readPage(chunk) {
|
||||
if (chunk.length < OGG_PAGE_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
if (!chunk.slice(0, 4).equals(OGGS_HEADER)) {
|
||||
throw Error(`capture_pattern is not ${OGGS_HEADER}`);
|
||||
}
|
||||
if (chunk.readUInt8(4) !== STREAM_STRUCTURE_VERSION) {
|
||||
throw Error(`stream_structure_version is not ${STREAM_STRUCTURE_VERSION}`);
|
||||
}
|
||||
|
||||
if (chunk.length < 27) return false;
|
||||
const pageSegments = chunk.readUInt8(26);
|
||||
if (chunk.length < 27 + pageSegments) return false;
|
||||
const table = chunk.slice(27, 27 + pageSegments);
|
||||
const bitstream = chunk.readUInt32BE(14);
|
||||
|
||||
let sizes = [], totalSize = 0;
|
||||
|
||||
for (let i = 0; i < pageSegments;) {
|
||||
let size = 0, x = 255;
|
||||
while (x === 255) {
|
||||
if (i >= table.length) return false;
|
||||
x = table.readUInt8(i);
|
||||
i++;
|
||||
size += x;
|
||||
}
|
||||
sizes.push(size);
|
||||
totalSize += size;
|
||||
}
|
||||
|
||||
if (chunk.length < 27 + pageSegments + totalSize) return false;
|
||||
|
||||
let start = 27 + pageSegments;
|
||||
for (const size of sizes) {
|
||||
const segment = chunk.slice(start, start + size);
|
||||
const header = segment.slice(0, 8);
|
||||
if (this._head) {
|
||||
if (header.equals(OPUS_TAGS)) this.emit('tags', segment);
|
||||
else if (this._bitstream === bitstream) this.push(segment);
|
||||
} else if (header.equals(OPUS_HEAD)) {
|
||||
this.emit('head', segment);
|
||||
this._head = segment;
|
||||
this._bitstream = bitstream;
|
||||
} else {
|
||||
this.emit('unknownSegment', segment);
|
||||
}
|
||||
start += size;
|
||||
}
|
||||
return chunk.slice(start);
|
||||
}
|
||||
|
||||
_destroy(err, cb) {
|
||||
this._cleanup();
|
||||
return cb ? cb(err) : undefined;
|
||||
}
|
||||
|
||||
_final(cb) {
|
||||
this._cleanup();
|
||||
cb();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the demuxer when it is no longer required.
|
||||
* @private
|
||||
*/
|
||||
_cleanup() {
|
||||
this._remainder = null;
|
||||
this._head = null;
|
||||
this._bitstream = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted when the demuxer encounters the opus head.
|
||||
* @event OggDemuxer#head
|
||||
* @memberof opus
|
||||
* @param {Buffer} segment a buffer containing the opus head data.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Emitted when the demuxer encounters opus tags.
|
||||
* @event OggDemuxer#tags
|
||||
* @memberof opus
|
||||
* @param {Buffer} segment a buffer containing the opus tags.
|
||||
*/
|
||||
|
||||
module.exports = OggDemuxer;
|
||||
212
node_modules/prism-media/src/opus/Opus.js
generated
vendored
Normal file
212
node_modules/prism-media/src/opus/Opus.js
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
// Partly based on https://github.com/Rantanen/node-opus/blob/master/lib/Encoder.js
|
||||
|
||||
const { Transform } = require('stream');
|
||||
const loader = require('../util/loader');
|
||||
|
||||
const CTL = {
|
||||
BITRATE: 4002,
|
||||
FEC: 4012,
|
||||
PLP: 4014,
|
||||
};
|
||||
|
||||
let Opus = {};
|
||||
|
||||
function loadOpus(refresh = false) {
|
||||
if (Opus.Encoder && !refresh) return Opus;
|
||||
|
||||
Opus = loader.require([
|
||||
['@discordjs/opus', opus => ({ Encoder: opus.OpusEncoder })],
|
||||
['node-opus', opus => ({ Encoder: opus.OpusEncoder })],
|
||||
['opusscript', opus => ({ Encoder: opus })],
|
||||
]);
|
||||
return Opus;
|
||||
}
|
||||
|
||||
const charCode = x => x.charCodeAt(0);
|
||||
const OPUS_HEAD = Buffer.from([...'OpusHead'].map(charCode));
|
||||
const OPUS_TAGS = Buffer.from([...'OpusTags'].map(charCode));
|
||||
|
||||
// frame size = (channels * rate * frame_duration) / 1000
|
||||
|
||||
/**
|
||||
* Takes a stream of Opus data and outputs a stream of PCM data, or the inverse.
|
||||
* **You shouldn't directly instantiate this class, see opus.Encoder and opus.Decoder instead!**
|
||||
* @memberof opus
|
||||
* @extends TransformStream
|
||||
* @protected
|
||||
*/
|
||||
class OpusStream extends Transform {
|
||||
/**
|
||||
* Creates a new Opus transformer.
|
||||
* @private
|
||||
* @memberof opus
|
||||
* @param {Object} [options] options that you would pass to a regular Transform stream
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
if (!loadOpus().Encoder) {
|
||||
throw Error('Could not find an Opus module! Please install @discordjs/opus, node-opus, or opusscript.');
|
||||
}
|
||||
super(Object.assign({ readableObjectMode: true }, options));
|
||||
if (Opus.name === 'opusscript') {
|
||||
options.application = Opus.Encoder.Application[options.application];
|
||||
}
|
||||
this.encoder = new Opus.Encoder(options.rate, options.channels, options.application);
|
||||
|
||||
this._options = options;
|
||||
this._required = this._options.frameSize * this._options.channels * 2;
|
||||
}
|
||||
|
||||
_encode(buffer) {
|
||||
return this.encoder.encode(buffer, this._options.frameSize);
|
||||
}
|
||||
|
||||
_decode(buffer) {
|
||||
return this.encoder.decode(buffer, Opus.name === 'opusscript' ? null : this._options.frameSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Opus module being used - `opusscript`, `node-opus`, or `@discordjs/opus`.
|
||||
* @type {string}
|
||||
* @readonly
|
||||
* @example
|
||||
* console.log(`Using Opus module ${prism.opus.Encoder.type}`);
|
||||
*/
|
||||
static get type() {
|
||||
return Opus.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bitrate of the stream.
|
||||
* @param {number} bitrate the bitrate to use use, e.g. 48000
|
||||
* @public
|
||||
*/
|
||||
setBitrate(bitrate) {
|
||||
(this.encoder.applyEncoderCTL || this.encoder.encoderCTL)
|
||||
.apply(this.encoder, [CTL.BITRATE, Math.min(128e3, Math.max(16e3, bitrate))]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables forward error correction.
|
||||
* @param {boolean} enabled whether or not to enable FEC.
|
||||
* @public
|
||||
*/
|
||||
setFEC(enabled) {
|
||||
(this.encoder.applyEncoderCTL || this.encoder.encoderCTL)
|
||||
.apply(this.encoder, [CTL.FEC, enabled ? 1 : 0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expected packet loss over network transmission.
|
||||
* @param {number} [percentage] a percentage (represented between 0 and 1)
|
||||
*/
|
||||
setPLP(percentage) {
|
||||
(this.encoder.applyEncoderCTL || this.encoder.encoderCTL)
|
||||
.apply(this.encoder, [CTL.PLP, Math.min(100, Math.max(0, percentage * 100))]);
|
||||
}
|
||||
|
||||
_final(cb) {
|
||||
this._cleanup();
|
||||
cb();
|
||||
}
|
||||
|
||||
_destroy(err, cb) {
|
||||
this._cleanup();
|
||||
return cb ? cb(err) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up the Opus stream when it is no longer needed
|
||||
* @private
|
||||
*/
|
||||
_cleanup() {
|
||||
if (Opus.name === 'opusscript' && this.encoder) this.encoder.delete();
|
||||
this.encoder = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An Opus encoder stream.
|
||||
*
|
||||
* Outputs opus packets in [object mode.](https://nodejs.org/api/stream.html#stream_object_mode)
|
||||
* @extends opus.OpusStream
|
||||
* @memberof opus
|
||||
* @example
|
||||
* const encoder = new prism.opus.Encoder({ frameSize: 960, channels: 2, rate: 48000 });
|
||||
* pcmAudio.pipe(encoder);
|
||||
* // encoder will now output Opus-encoded audio packets
|
||||
*/
|
||||
class Encoder extends OpusStream {
|
||||
/**
|
||||
* Creates a new Opus encoder stream.
|
||||
* @memberof opus
|
||||
* @param {Object} options options that you would pass to a regular OpusStream, plus a few more:
|
||||
* @param {number} options.frameSize the frame size in bytes to use (e.g. 960 for stereo audio at 48KHz with a frame
|
||||
* duration of 20ms)
|
||||
* @param {number} options.channels the number of channels to use
|
||||
* @param {number} options.rate the sampling rate in Hz
|
||||
*/
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this._buffer = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, done) {
|
||||
this._buffer = Buffer.concat([this._buffer, chunk]);
|
||||
let n = 0;
|
||||
while (this._buffer.length >= this._required * (n + 1)) {
|
||||
const buf = this._encode(this._buffer.slice(n * this._required, (n + 1) * this._required));
|
||||
this.push(buf);
|
||||
n++;
|
||||
}
|
||||
if (n > 0) this._buffer = this._buffer.slice(n * this._required);
|
||||
return done();
|
||||
}
|
||||
|
||||
_destroy(err, cb) {
|
||||
super._destroy(err, cb);
|
||||
this._buffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An Opus decoder stream.
|
||||
*
|
||||
* Note that any stream you pipe into this must be in
|
||||
* [object mode](https://nodejs.org/api/stream.html#stream_object_mode) and should output Opus packets.
|
||||
* @extends opus.OpusStream
|
||||
* @memberof opus
|
||||
* @example
|
||||
* const decoder = new prism.opus.Decoder({ frameSize: 960, channels: 2, rate: 48000 });
|
||||
* input.pipe(decoder);
|
||||
* // decoder will now output PCM audio
|
||||
*/
|
||||
class Decoder extends OpusStream {
|
||||
_transform(chunk, encoding, done) {
|
||||
const signature = chunk.slice(0, 8);
|
||||
if (chunk.length >= 8 && signature.equals(OPUS_HEAD)) {
|
||||
this.emit('format', {
|
||||
channels: this._options.channels,
|
||||
sampleRate: this._options.rate,
|
||||
bitDepth: 16,
|
||||
float: false,
|
||||
signed: true,
|
||||
version: chunk.readUInt8(8),
|
||||
preSkip: chunk.readUInt16LE(10),
|
||||
gain: chunk.readUInt16LE(16),
|
||||
});
|
||||
return done();
|
||||
}
|
||||
if (chunk.length >= 8 && signature.equals(OPUS_TAGS)) {
|
||||
this.emit('tags', chunk);
|
||||
return done();
|
||||
}
|
||||
try {
|
||||
this.push(this._decode(chunk));
|
||||
} catch (e) {
|
||||
return done(e);
|
||||
}
|
||||
return done();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Decoder, Encoder };
|
||||
24
node_modules/prism-media/src/opus/WebmDemuxer.js
generated
vendored
Normal file
24
node_modules/prism-media/src/opus/WebmDemuxer.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
const WebmBaseDemuxer = require('../core/WebmBase');
|
||||
|
||||
const OPUS_HEAD = Buffer.from([...'OpusHead'].map(x => x.charCodeAt(0)));
|
||||
|
||||
/**
|
||||
* Demuxes a Webm stream (containing Opus audio) to output an Opus stream.
|
||||
* @extends core.WebmBaseDemuxer
|
||||
* @memberof opus
|
||||
* @example
|
||||
* const fs = require('fs');
|
||||
* const file = fs.createReadStream('./audio.webm');
|
||||
* const demuxer = new prism.opus.WebmDemuxer();
|
||||
* const opus = file.pipe(demuxer);
|
||||
* // opus is now a ReadableStream in object mode outputting Opus packets
|
||||
*/
|
||||
class WebmDemuxer extends WebmBaseDemuxer {
|
||||
_checkHead(data) {
|
||||
if (!data.slice(0, 8).equals(OPUS_HEAD)) {
|
||||
throw Error('Audio codec is not Opus!');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebmDemuxer;
|
||||
10
node_modules/prism-media/src/opus/index.js
generated
vendored
Normal file
10
node_modules/prism-media/src/opus/index.js
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Opus features
|
||||
* @namespace opus
|
||||
*/
|
||||
module.exports = {
|
||||
// Encoder and Decoder
|
||||
...require('./Opus'),
|
||||
OggDemuxer: require('./OggDemuxer'),
|
||||
WebmDemuxer: require('./WebmDemuxer'),
|
||||
};
|
||||
Reference in New Issue
Block a user