Add files via upload
This commit is contained in:
40
node_modules/eris/lib/voice/streams/BaseTransformer.js
generated
vendored
Normal file
40
node_modules/eris/lib/voice/streams/BaseTransformer.js
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
"use strict";
|
||||
|
||||
const util = require("util");
|
||||
const Base = require("../../structures/Base");
|
||||
const TransformStream = require("stream").Transform;
|
||||
|
||||
class BaseTransformer extends TransformStream {
|
||||
constructor(options = {}) {
|
||||
if(options.allowHalfOpen === undefined) {
|
||||
options.allowHalfOpen = true;
|
||||
}
|
||||
if(options.highWaterMark === undefined) {
|
||||
options.highWaterMark = 0;
|
||||
}
|
||||
super(options);
|
||||
this.manualCB = false;
|
||||
}
|
||||
|
||||
setTransformCB(cb) {
|
||||
if(this.manualCB) {
|
||||
this.transformCB();
|
||||
this._transformCB = cb;
|
||||
} else {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
transformCB() {
|
||||
if(this._transformCB) {
|
||||
this._transformCB();
|
||||
this._transformCB = null;
|
||||
}
|
||||
}
|
||||
|
||||
[util.inspect.custom]() {
|
||||
return Base.prototype[util.inspect.custom].call(this);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BaseTransformer;
|
78
node_modules/eris/lib/voice/streams/DCAOpusTransformer.js
generated
vendored
Normal file
78
node_modules/eris/lib/voice/streams/DCAOpusTransformer.js
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
"use strict";
|
||||
|
||||
const BaseTransformer = require("./BaseTransformer");
|
||||
|
||||
class DCAOpusTransformer extends BaseTransformer {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
|
||||
this._remainder = null;
|
||||
}
|
||||
|
||||
process(buffer) {
|
||||
if(buffer.length - buffer._index < 2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const opusLen = buffer.readInt16LE(buffer._index);
|
||||
buffer._index += 2;
|
||||
|
||||
if(buffer.length - buffer._index < opusLen) {
|
||||
return true;
|
||||
}
|
||||
|
||||
buffer._index += opusLen;
|
||||
this.push(buffer.subarray(buffer._index - opusLen, buffer._index));
|
||||
}
|
||||
|
||||
_transform(chunk, enc, cb) {
|
||||
if(this._remainder) {
|
||||
chunk = Buffer.concat([this._remainder, chunk]);
|
||||
this._remainder = null;
|
||||
}
|
||||
|
||||
if(!this.head) {
|
||||
if(chunk.length < 4) {
|
||||
this._remainder = chunk;
|
||||
return cb();
|
||||
} else {
|
||||
const dcaVersion = chunk.subarray(0, 4);
|
||||
if(dcaVersion[0] !== 68 || dcaVersion[1] !== 67 || dcaVersion[2] !== 65) { // DCA0 or invalid
|
||||
this.head = true; // Attempt to play as if it were a DCA0 file
|
||||
} else if(dcaVersion[3] === 49) { // DCA1
|
||||
if(chunk.length < 8) {
|
||||
this._remainder = chunk;
|
||||
return cb();
|
||||
}
|
||||
const jsonLength = chunk.subarray(4, 8).readInt32LE(0);
|
||||
if(chunk.length < 8 + jsonLength) {
|
||||
this._remainder = chunk;
|
||||
return cb();
|
||||
}
|
||||
const jsonMetadata = chunk.subarray(8, 8 + jsonLength);
|
||||
this.emit("debug", jsonMetadata);
|
||||
chunk = chunk.subarray(8 + jsonLength);
|
||||
this.head = true;
|
||||
} else {
|
||||
this.emit("error", new Error("Unsupported DCA version: " + dcaVersion.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chunk._index = 0;
|
||||
|
||||
while(chunk._index < chunk.length) {
|
||||
const offset = chunk._index;
|
||||
const ret = this.process(chunk);
|
||||
if(ret) {
|
||||
this._remainder = chunk.subarray(offset);
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.setTransformCB(cb);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DCAOpusTransformer;
|
199
node_modules/eris/lib/voice/streams/FFmpegDuplex.js
generated
vendored
Normal file
199
node_modules/eris/lib/voice/streams/FFmpegDuplex.js
generated
vendored
Normal file
@ -0,0 +1,199 @@
|
||||
"use strict";
|
||||
const util = require("util");
|
||||
const Base = require("../../structures/Base");
|
||||
const ChildProcess = require("child_process");
|
||||
const DuplexStream = require("stream").Duplex;
|
||||
const PassThroughStream = require("stream").PassThrough;
|
||||
|
||||
const delegateEvents = {
|
||||
readable: "_reader",
|
||||
data: "_reader",
|
||||
end: "_reader",
|
||||
drain: "_writer",
|
||||
finish: "_writer"
|
||||
};
|
||||
|
||||
class FFmpegDuplex extends DuplexStream {
|
||||
constructor(command, options = {}) {
|
||||
if(options.highWaterMark === undefined) {
|
||||
options.highWaterMark = 0;
|
||||
}
|
||||
super(options);
|
||||
|
||||
this.command = command;
|
||||
this._reader = new PassThroughStream(options);
|
||||
this._writer = new PassThroughStream(options);
|
||||
|
||||
this._onError = this.emit.bind(this, "error");
|
||||
|
||||
this._reader.on("error", this._onError);
|
||||
this._writer.on("error", this._onError);
|
||||
|
||||
this._readableState = this._reader._readableState;
|
||||
this._writableState = this._writer._writableState;
|
||||
|
||||
["on", "once", "removeListener", "removeListeners", "listeners"].forEach((method) => {
|
||||
const og = DuplexStream.prototype[method];
|
||||
|
||||
this[method] = function(ev, fn) {
|
||||
const substream = delegateEvents[ev];
|
||||
if(substream) {
|
||||
return this[substream][method](ev, fn);
|
||||
} else {
|
||||
return og.call(this, ev, fn);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
}
|
||||
|
||||
end(chunk, enc, cb) {
|
||||
return this._writer.end(chunk, enc, cb);
|
||||
}
|
||||
|
||||
kill() {
|
||||
}
|
||||
|
||||
noop() {
|
||||
}
|
||||
|
||||
pipe(dest, opts) {
|
||||
return this._reader.pipe(dest, opts);
|
||||
}
|
||||
|
||||
read(size) {
|
||||
return this._reader.read(size);
|
||||
}
|
||||
|
||||
setEncoding(enc) {
|
||||
return this._reader.setEncoding(enc);
|
||||
}
|
||||
|
||||
spawn(args, options = {}) {
|
||||
let ex, exited, killed, ended;
|
||||
let stderr = [];
|
||||
|
||||
const onStdoutEnd = () => {
|
||||
if(exited && !ended) {
|
||||
ended = true;
|
||||
this._reader.end();
|
||||
setImmediate(this.emit.bind(this, "close"));
|
||||
}
|
||||
};
|
||||
|
||||
const onStderrData = (chunk) => {
|
||||
stderr.push(chunk);
|
||||
};
|
||||
|
||||
const cleanup = () => {
|
||||
this._process =
|
||||
this._stderr =
|
||||
this._stdout =
|
||||
this._stdin =
|
||||
stderr =
|
||||
ex =
|
||||
killed = null;
|
||||
|
||||
this.kill =
|
||||
this.destroy = this.noop;
|
||||
};
|
||||
|
||||
const onExit = (code, signal) => {
|
||||
if(exited) {
|
||||
return;
|
||||
}
|
||||
exited = true;
|
||||
|
||||
if(killed) {
|
||||
if(ex) {
|
||||
this.emit("error", ex);
|
||||
}
|
||||
this.emit("close");
|
||||
} else if(code === 0 && signal == null) {
|
||||
// All is well
|
||||
onStdoutEnd();
|
||||
} else {
|
||||
// Everything else
|
||||
ex = new Error("Command failed: " + Buffer.concat(stderr).toString("utf8"));
|
||||
ex.killed = this._process.killed || killed;
|
||||
ex.code = code;
|
||||
ex.signal = signal;
|
||||
this.emit("error", ex);
|
||||
this.emit("close");
|
||||
}
|
||||
|
||||
cleanup();
|
||||
};
|
||||
|
||||
const onError = (err) => {
|
||||
ex = err;
|
||||
this._stdout.destroy();
|
||||
this._stderr.destroy();
|
||||
onExit();
|
||||
};
|
||||
|
||||
const kill = () => {
|
||||
if(killed) {
|
||||
return;
|
||||
}
|
||||
this._stdout.destroy();
|
||||
this._stderr.destroy();
|
||||
|
||||
killed = true;
|
||||
|
||||
try {
|
||||
this._process.kill(options.killSignal || "SIGTERM");
|
||||
setTimeout(() => this._process && this._process.kill("SIGKILL"), 2000);
|
||||
} catch(e) {
|
||||
ex = e;
|
||||
onExit();
|
||||
}
|
||||
};
|
||||
|
||||
this._process = ChildProcess.spawn(this.command, args, options);
|
||||
this._stdin = this._process.stdin;
|
||||
this._stdout = this._process.stdout;
|
||||
this._stderr = this._process.stderr;
|
||||
this._writer.pipe(this._stdin);
|
||||
this._stdout.pipe(this._reader, {
|
||||
end: false
|
||||
});
|
||||
this.kill = this.destroy = kill;
|
||||
|
||||
this._stderr.on("data", onStderrData);
|
||||
|
||||
// In some cases ECONNRESET can be emitted by stdin because the process is not interested in any
|
||||
// more data but the _writer is still piping. Forget about errors emitted on stdin and stdout
|
||||
this._stdin.on("error", this.noop);
|
||||
this._stdout.on("error", this.noop);
|
||||
|
||||
this._stdout.on("end", onStdoutEnd);
|
||||
|
||||
this._process.once("close", onExit);
|
||||
this._process.once("error", onError);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
unpipe(dest) {
|
||||
return this._reader.unpipe(dest) || this.kill();
|
||||
}
|
||||
|
||||
write(chunk, enc, cb) {
|
||||
return this._writer.write(chunk, enc, cb);
|
||||
}
|
||||
|
||||
[util.inspect.custom]() {
|
||||
return Base.prototype[util.inspect.custom].call(this);
|
||||
}
|
||||
}
|
||||
|
||||
FFmpegDuplex.prototype.addListener = FFmpegDuplex.prototype.on;
|
||||
|
||||
FFmpegDuplex.spawn = function(connection, args, options) {
|
||||
return new FFmpegDuplex(connection, options).spawn(args, options);
|
||||
};
|
||||
|
||||
module.exports = FFmpegDuplex;
|
35
node_modules/eris/lib/voice/streams/FFmpegOggTransformer.js
generated
vendored
Normal file
35
node_modules/eris/lib/voice/streams/FFmpegOggTransformer.js
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
"use strict";
|
||||
|
||||
const FFmpegDuplex = require("./FFmpegDuplex");
|
||||
|
||||
module.exports = function(options = {}) {
|
||||
if(!options.command) {
|
||||
throw new Error("Invalid converter command");
|
||||
}
|
||||
if(options.frameDuration === undefined) {
|
||||
options.frameDuration = 60;
|
||||
}
|
||||
let inputArgs = [
|
||||
"-analyzeduration", "0",
|
||||
"-loglevel", "24"
|
||||
].concat(options.inputArgs || []);
|
||||
if(options.format === "pcm") {
|
||||
inputArgs = inputArgs.concat(
|
||||
"-f", "s16le",
|
||||
"-ar", "48000",
|
||||
"-ac", "2"
|
||||
);
|
||||
}
|
||||
inputArgs = inputArgs.concat(
|
||||
"-i", options.input || "-",
|
||||
"-vn"
|
||||
);
|
||||
const outputArgs = [
|
||||
"-c:a", "libopus",
|
||||
"-vbr", "on",
|
||||
"-frame_duration", "" + options.frameDuration,
|
||||
"-f", "ogg",
|
||||
"-"
|
||||
];
|
||||
return FFmpegDuplex.spawn(options.command, inputArgs.concat(options.encoderArgs || [], outputArgs));
|
||||
};
|
26
node_modules/eris/lib/voice/streams/FFmpegPCMTransformer.js
generated
vendored
Normal file
26
node_modules/eris/lib/voice/streams/FFmpegPCMTransformer.js
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
"use strict";
|
||||
|
||||
const FFmpegDuplex = require("./FFmpegDuplex");
|
||||
|
||||
module.exports = function(options = {}) {
|
||||
if(!options.command) {
|
||||
throw new Error("Invalid converter command");
|
||||
}
|
||||
if(options.samplingRate === undefined) {
|
||||
options.samplingRate = 48000;
|
||||
}
|
||||
const inputArgs = [
|
||||
"-analyzeduration", "0",
|
||||
"-loglevel", "24"
|
||||
].concat(options.inputArgs || [],
|
||||
"-i", options.input || "-",
|
||||
"-vn"
|
||||
);
|
||||
const outputArgs = [
|
||||
"-f", "s16le",
|
||||
"-ar", "" + options.samplingRate,
|
||||
"-ac", "2",
|
||||
"-"
|
||||
];
|
||||
return FFmpegDuplex.spawn(options.command, inputArgs.concat(options.encoderArgs || [], outputArgs));
|
||||
};
|
107
node_modules/eris/lib/voice/streams/OggOpusTransformer.js
generated
vendored
Normal file
107
node_modules/eris/lib/voice/streams/OggOpusTransformer.js
generated
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
"use strict";
|
||||
|
||||
const BaseTransformer = require("./BaseTransformer");
|
||||
|
||||
class OggOpusTransformer extends BaseTransformer {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
|
||||
this._remainder = null;
|
||||
this._bitstream = null;
|
||||
}
|
||||
|
||||
process(buffer) {
|
||||
if(buffer.length - buffer._index <= 26) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(buffer.toString("utf8", buffer._index, buffer._index + 4) !== "OggS") {
|
||||
return new Error("Invalid OGG magic string: " + buffer.toString("utf8", buffer._index, buffer._index + 4));
|
||||
}
|
||||
|
||||
const typeFlag = buffer.readUInt8(buffer._index + 5);
|
||||
if(typeFlag === 1) {
|
||||
return new Error("OGG continued page not supported");
|
||||
}
|
||||
|
||||
const bitstream = buffer.readUInt32BE(buffer._index + 14);
|
||||
|
||||
buffer._index += 26;
|
||||
|
||||
const segmentCount = buffer.readUInt8(buffer._index);
|
||||
if(buffer.length - buffer._index - 1 < segmentCount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const segments = [];
|
||||
let size = 0;
|
||||
let byte = 0;
|
||||
let total = 0;
|
||||
let i = 0;
|
||||
for(; i < segmentCount; i++) {
|
||||
byte = buffer.readUInt8(++buffer._index);
|
||||
if(byte < 255) {
|
||||
segments.push(size + byte);
|
||||
size = 0;
|
||||
} else {
|
||||
size += byte;
|
||||
}
|
||||
total += byte;
|
||||
}
|
||||
|
||||
++buffer._index;
|
||||
|
||||
if(buffer.length - buffer._index < total) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for(let segment of segments) {
|
||||
buffer._index += segment;
|
||||
byte = (segment = buffer.subarray(buffer._index - segment, buffer._index)).toString("utf8", 0, 8);
|
||||
if(this.head) {
|
||||
if(byte === "OpusTags") {
|
||||
this.emit("debug", segment.toString());
|
||||
} else if(bitstream === this._bitstream) {
|
||||
this.push(segment);
|
||||
}
|
||||
} else if(byte === "OpusHead") {
|
||||
this._bitstream = bitstream;
|
||||
this.emit("debug", (this.head = segment.toString()));
|
||||
} else {
|
||||
this.emit("debug", "Invalid codec: " + byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_final() {
|
||||
if(!this._bitstream) {
|
||||
this.emit("error", new Error("No Opus stream was found"));
|
||||
}
|
||||
}
|
||||
|
||||
_transform(chunk, enc, cb) {
|
||||
if(this._remainder) {
|
||||
chunk = Buffer.concat([this._remainder, chunk]);
|
||||
this._remainder = null;
|
||||
}
|
||||
|
||||
chunk._index = 0;
|
||||
|
||||
while(chunk._index < chunk.length) {
|
||||
const offset = chunk._index;
|
||||
const ret = this.process(chunk);
|
||||
if(ret) {
|
||||
this._remainder = chunk.subarray(offset);
|
||||
if(ret instanceof Error) {
|
||||
this.emit("error", ret);
|
||||
}
|
||||
cb();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.setTransformCB(cb);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OggOpusTransformer;
|
61
node_modules/eris/lib/voice/streams/PCMOpusTransformer.js
generated
vendored
Normal file
61
node_modules/eris/lib/voice/streams/PCMOpusTransformer.js
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
"use strict";
|
||||
|
||||
const BaseTransformer = require("./BaseTransformer");
|
||||
|
||||
class PCMOpusTransformer extends BaseTransformer {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
|
||||
this.opus = options.opusFactory();
|
||||
this.frameSize = options.frameSize || 2880;
|
||||
this.pcmSize = options.pcmSize || 11520;
|
||||
|
||||
this._remainder = null;
|
||||
}
|
||||
|
||||
_destroy(...args) {
|
||||
if(this.opus.delete) {
|
||||
this.opus.delete();
|
||||
}
|
||||
|
||||
return super._destroy(...args);
|
||||
}
|
||||
|
||||
_flush(cb) {
|
||||
if(this._remainder) {
|
||||
const buf = Buffer.allocUnsafe(this.pcmSize);
|
||||
this._remainder.copy(buf);
|
||||
buf.fill(0, this._remainder.length);
|
||||
this.push(this.opus.encode(buf, this.frameSize));
|
||||
this._remainder = null;
|
||||
}
|
||||
cb();
|
||||
}
|
||||
|
||||
_transform(chunk, enc, cb) {
|
||||
if(this._remainder) {
|
||||
chunk = Buffer.concat([this._remainder, chunk]);
|
||||
this._remainder = null;
|
||||
}
|
||||
|
||||
if(chunk.length < this.pcmSize) {
|
||||
this._remainder = chunk;
|
||||
return cb();
|
||||
}
|
||||
|
||||
chunk._index = 0;
|
||||
|
||||
while(chunk._index + this.pcmSize < chunk.length) {
|
||||
chunk._index += this.pcmSize;
|
||||
this.push(this.opus.encode(chunk.subarray(chunk._index - this.pcmSize, chunk._index), this.frameSize));
|
||||
}
|
||||
|
||||
if(chunk._index < chunk.length) {
|
||||
this._remainder = chunk.subarray(chunk._index);
|
||||
}
|
||||
|
||||
this.setTransformCB(cb);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PCMOpusTransformer;
|
50
node_modules/eris/lib/voice/streams/VolumeTransformer.js
generated
vendored
Normal file
50
node_modules/eris/lib/voice/streams/VolumeTransformer.js
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
"use strict";
|
||||
|
||||
const BaseTransformer = require("./BaseTransformer");
|
||||
|
||||
class VolumeTransformer extends BaseTransformer {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
|
||||
this._remainder = null;
|
||||
this.setVolume(1.0);
|
||||
}
|
||||
|
||||
setVolume(volume) {
|
||||
if(isNaN(volume) || (volume = +volume) < 0) {
|
||||
throw new Error("Invalid volume level: " + volume);
|
||||
}
|
||||
this.volume = volume;
|
||||
this.db = 10 * Math.log(1 + this.volume) / 6.931471805599453;
|
||||
}
|
||||
|
||||
_transform(chunk, enc, cb) {
|
||||
if(this._remainder) {
|
||||
chunk = Buffer.concat([this._remainder, chunk]);
|
||||
this._remainder = null;
|
||||
}
|
||||
|
||||
if(chunk.length < 2) {
|
||||
return cb();
|
||||
}
|
||||
|
||||
let buf;
|
||||
if(chunk.length & 1) {
|
||||
this._remainder = chunk.subarray(chunk.length - 1);
|
||||
buf = Buffer.allocUnsafe(chunk.length - 1);
|
||||
} else {
|
||||
buf = Buffer.allocUnsafe(chunk.length);
|
||||
}
|
||||
|
||||
for(let i = 0, num; i < buf.length - 1; i += 2) {
|
||||
// Bind transformed chunk to to 16 bit
|
||||
num = ~~(this.db * chunk.readInt16LE(i));
|
||||
buf.writeInt16LE(num >= 32767 ? 32767 : num <= -32767 ? -32767 : num, i);
|
||||
}
|
||||
|
||||
this.push(buf);
|
||||
this.setTransformCB(cb);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VolumeTransformer;
|
258
node_modules/eris/lib/voice/streams/WebmOpusTransformer.js
generated
vendored
Normal file
258
node_modules/eris/lib/voice/streams/WebmOpusTransformer.js
generated
vendored
Normal file
@ -0,0 +1,258 @@
|
||||
"use strict";
|
||||
|
||||
const BaseTransformer = require("./BaseTransformer");
|
||||
|
||||
// EBML VInt max value is (2 ^ 56 - 2), but JS only supports 2^53
|
||||
// 45 = 53 - 8 - check before last 8 bytes
|
||||
const MAX_SHIFTED_VINT = Math.pow(2, 45);
|
||||
|
||||
const STATE_CONTENT = 0;
|
||||
const STATE_TAG = 1;
|
||||
|
||||
const TAG_TYPE_END = 0;
|
||||
const TAG_TYPE_START = 1;
|
||||
const TAG_TYPE_TAG = 2;
|
||||
|
||||
const TRACKTYPE_AUDIO = 2; // EBML spec: https://www.matroska.org/technical/specs/index.html#TrackType
|
||||
|
||||
class WebmOpusTransformer extends BaseTransformer {
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
|
||||
this._tag_stack = [];
|
||||
this._state = STATE_TAG;
|
||||
this._total = 0;
|
||||
}
|
||||
|
||||
getVIntLength(buffer, index) {
|
||||
let length = 1;
|
||||
for(; length <= 8; ++length) {
|
||||
if(buffer[index] & (1 << (8 - length))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(length > 8) {
|
||||
this.emit("debug", new Error(`VInt length ${length} | ${buffer.toString("hex", index, index + length)}`));
|
||||
return null;
|
||||
}
|
||||
if(index + length > buffer.length) {
|
||||
return null;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
process(type, info) {
|
||||
if(type === TAG_TYPE_TAG) {
|
||||
if(info.name === "SimpleBlock" && (info.data.readUInt8(0) & 0xF) === this.firstAudioTrack.TrackNumber) {
|
||||
this.push(info.data.subarray(4));
|
||||
return;
|
||||
}
|
||||
if(info.name === "CodecPrivate") {
|
||||
const head = info.data.toString("utf8", 0, 8);
|
||||
if(head !== "OpusHead") {
|
||||
this.emit("error", new Error("Invalid codec: " + head));
|
||||
return;
|
||||
}
|
||||
|
||||
this.codecData = {
|
||||
version: info.data.readUInt8(8),
|
||||
channelCount: info.data.readUInt8(9),
|
||||
preSkip: info.data.readUInt16LE(10),
|
||||
inputSampleRate: info.data.readUInt32LE(12),
|
||||
outputGain: info.data.readUInt16LE(16),
|
||||
mappingFamily: info.data.readUInt8(18)
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!this.firstAudioTrack) {
|
||||
if(info.name === "TrackEntry") {
|
||||
if(type === TAG_TYPE_START) {
|
||||
this.parsingTrack = {};
|
||||
} else if(type === TAG_TYPE_END) {
|
||||
if(this.parsingTrack.TrackNumber && this.parsingTrack.TrackType === TRACKTYPE_AUDIO) {
|
||||
this.firstAudioTrack = this.parsingTrack;
|
||||
}
|
||||
delete this.parsingTrack;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(this.parsingTrack) {
|
||||
if(info.name === "TrackNumber") {
|
||||
this.parsingTrack.TrackNumber = info.data[0];
|
||||
return;
|
||||
}
|
||||
if(info.name === "TrackType") {
|
||||
this.parsingTrack.TrackType = info.data[0];
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(type === TAG_TYPE_END && info.name === "Tracks") {
|
||||
this.emit("error", new Error("No audio track"));
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
readContent(buffer) {
|
||||
const tagObj = this._tag_stack[this._tag_stack.length - 1];
|
||||
|
||||
if(tagObj.type === "m") {
|
||||
this.process(TAG_TYPE_START, tagObj);
|
||||
this._state = STATE_TAG;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(buffer.length < buffer._index + tagObj.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tagObj.data = buffer.subarray(buffer._index, buffer._index + tagObj.size);
|
||||
buffer._index += tagObj.size;
|
||||
this._total += tagObj.size;
|
||||
this._state = STATE_TAG;
|
||||
|
||||
this._tag_stack.pop();
|
||||
|
||||
this.process(TAG_TYPE_TAG, tagObj);
|
||||
|
||||
while(this._tag_stack.length > 0) {
|
||||
if(this._total < this._tag_stack[this._tag_stack.length - 1].end) {
|
||||
break;
|
||||
}
|
||||
this.process(TAG_TYPE_END, this._tag_stack.pop());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
readTag(buffer) {
|
||||
const tagSize = this.getVIntLength(buffer, buffer._index);
|
||||
if(tagSize === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size = this.getVIntLength(buffer, buffer._index + tagSize);
|
||||
if(size === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tagStr = buffer.toString("hex", buffer._index, buffer._index + tagSize);
|
||||
|
||||
const tagObj = {
|
||||
type: "unknown",
|
||||
name: "unknown",
|
||||
end: this._total + tagSize
|
||||
};
|
||||
if(schema[tagStr]) {
|
||||
tagObj.type = schema[tagStr].type;
|
||||
tagObj.name = schema[tagStr].name;
|
||||
}
|
||||
|
||||
buffer._index += tagSize;
|
||||
|
||||
let value = buffer[buffer._index] & (1 << (8 - size)) - 1;
|
||||
for(let i = 1; i < size; ++i) {
|
||||
if(i === 7 && value >= MAX_SHIFTED_VINT && buffer[buffer._index + 7] > 0) {
|
||||
tagObj.end = -1; // Special livestreaming int 0x1FFFFFFFFFFFFFF
|
||||
break;
|
||||
}
|
||||
value = (value << 8) + buffer[buffer._index + i];
|
||||
}
|
||||
if(tagObj.end !== -1) {
|
||||
tagObj.end += value + size;
|
||||
}
|
||||
tagObj.size = value;
|
||||
|
||||
buffer._index += size;
|
||||
this._total += tagSize + size;
|
||||
this._state = STATE_CONTENT;
|
||||
|
||||
this._tag_stack.push(tagObj);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_transform(chunk, enc, cb) {
|
||||
if(this._remainder) {
|
||||
chunk = Buffer.concat([this._remainder, chunk]);
|
||||
this._remainder = null;
|
||||
}
|
||||
|
||||
chunk._index = 0;
|
||||
|
||||
while(chunk._index < chunk.length) {
|
||||
if(this._state === STATE_TAG && !this.readTag(chunk)) {
|
||||
break;
|
||||
}
|
||||
if(this._state === STATE_CONTENT && !this.readContent(chunk)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(chunk._index < chunk.length) {
|
||||
this._remainder = chunk.subarray(chunk._index);
|
||||
}
|
||||
|
||||
this.setTransformCB(cb);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebmOpusTransformer;
|
||||
|
||||
const schema = {
|
||||
ae: {
|
||||
name: "TrackEntry",
|
||||
type: "m"
|
||||
},
|
||||
d7: {
|
||||
name: "TrackNumber",
|
||||
type: "u"
|
||||
},
|
||||
"86": {
|
||||
name: "CodecID",
|
||||
type: "s"
|
||||
},
|
||||
"83": {
|
||||
name: "TrackType",
|
||||
type: "u"
|
||||
},
|
||||
"1654ae6b": {
|
||||
name: "Tracks",
|
||||
type: "m"
|
||||
},
|
||||
"63a2": {
|
||||
name: "CodecPrivate",
|
||||
type: "b"
|
||||
},
|
||||
a3: {
|
||||
name: "SimpleBlock",
|
||||
type: "b"
|
||||
},
|
||||
"1a45dfa3": {
|
||||
name: "EBML",
|
||||
type: "m"
|
||||
},
|
||||
"18538067": {
|
||||
name: "Segment",
|
||||
type: "m"
|
||||
},
|
||||
"114d9b74": {
|
||||
name: "SeekHead",
|
||||
type: "m"
|
||||
},
|
||||
"1549a966": {
|
||||
name: "Info",
|
||||
type: "m"
|
||||
},
|
||||
e1: {
|
||||
name: "Audio",
|
||||
type: "m"
|
||||
},
|
||||
"1f43b675": {
|
||||
name: "Cluster",
|
||||
type: "m"
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user