diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 78e5c0b..4d40786 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -184,6 +184,18 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, + "node_modules/m3u8stream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.4.tgz", + "integrity": "sha512-sco80Db+30RvcaIOndenX6E6oQNgTiBKeJbFPc+yDXwPQIkryfboEbCvXPlBRq3mQTCVPQO93TDVlfRwqpD35w==", + "dependencies": { + "miniget": "^4.0.0", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mime-db": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", @@ -203,6 +215,14 @@ "node": ">= 0.6" } }, + "node_modules/miniget": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.1.tgz", + "integrity": "sha512-O/DduzDR6f+oDtVype9S/Qu5hhnx73EDYGyZKwU/qN82lehFZdfhoa4DT51SpsO+8epYrB3gcRmws56ROfTIoQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/node-fetch": { "version": "2.6.6", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", @@ -233,6 +253,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prism-media": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.2.tgz", + "integrity": "sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g==", + "peerDependencies": { + "@discordjs/opus": "^0.5.0", + "ffmpeg-static": "^4.2.7 || ^3.0.0 || ^2.4.0", + "node-opus": "^0.3.3", + "opusscript": "^0.0.8" + }, + "peerDependenciesMeta": { + "@discordjs/opus": { + "optional": true + }, + "ffmpeg-static": { + "optional": true + }, + "node-opus": { + "optional": true + }, + "opusscript": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -300,6 +350,34 @@ "optional": true } } + }, + "node_modules/ytdl-core": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.9.1.tgz", + "integrity": "sha512-6Jbp5RDhUEozlaJQAR+l8oV8AHsx3WUXxSyPxzE6wOIAaLql7Hjiy0ZM58wZoyj1YEenlEPjEqcJIjKYKxvHtQ==", + "dependencies": { + "m3u8stream": "^0.8.3", + "miniget": "^4.0.0", + "sax": "^1.1.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ytdl-core-discord": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ytdl-core-discord/-/ytdl-core-discord-1.3.1.tgz", + "integrity": "sha512-KW8zYY35jRSkxZTEQtT9EiR2exFwYKhCE8QZbRg5Ge9a0YWDDhBOixSdWb8Cn41B1uHhz8FR15E4E/k0kHNX3w==", + "dependencies": { + "@types/node": "^15.12.2", + "prism-media": "^1.3.1", + "ytdl-core": "^4.8.2" + } + }, + "node_modules/ytdl-core-discord/node_modules/@types/node": { + "version": "15.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", + "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" } } } diff --git a/node_modules/m3u8stream/LICENSE b/node_modules/m3u8stream/LICENSE new file mode 100644 index 0000000..00e8ea8 --- /dev/null +++ b/node_modules/m3u8stream/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2017 by fent + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/m3u8stream/README.md b/node_modules/m3u8stream/README.md new file mode 100644 index 0000000..5114c30 --- /dev/null +++ b/node_modules/m3u8stream/README.md @@ -0,0 +1,81 @@ +# node-m3u8stream + +Reads segments from a [m3u8 playlist][1] or [DASH MPD file][2] into a consumable stream. + +[1]: https://tools.ietf.org/html/draft-pantos-http-live-streaming-20 +[2]: https://dashif.org/docs/DASH-IF-IOP-v4.2-clean.pdf + +![Depfu](https://img.shields.io/depfu/fent/node-m3u8stream) +[![codecov](https://codecov.io/gh/fent/node-m3u8stream/branch/master/graph/badge.svg)](https://codecov.io/gh/fent/node-m3u8stream) + + +# Usage + +```js +const fs = require('fs'); +const m3u8stream = require('m3u8stream') + +m3u8stream('http://somesite.com/link/to/the/playlist.m3u8') + .pipe(fs.createWriteStream('videofile.mp4')); +``` + + +# API + +### m3u8stream(url, [options]) + +Creates a readable stream of binary media data. `options` can have the following + +* `begin` - Where to begin playing the video. Accepts an absolute unix timestamp or date and a relative time in the formats `1:23:45.123` and `1m2s`. +* `liveBuffer` - How much buffer in milliseconds to have for live streams. Default is `20000`. +* `chunkReadahead` - How many chunks to preload ahead. Default is `3`. +* `highWaterMark` - How much of the download to buffer into the stream. See [node's docs](https://nodejs.org/api/stream.html#stream_constructor_new_stream_writable_options) for more. Note that the actual amount buffered can be higher since each chunk request maintains its own buffer. +* `requestOptions` - Any options you want to pass to [miniget](https://github.com/fent/node-miniget), such as `headers`. +* `parser` - Either "m3u8" or "dash-mpd". Defaults to guessing based on the playlist url ending in `.m3u8` or `.mpd`. +* `id` - For playlist containing multiple media options. If not given, the first representation will be picked. + +### Stream#end() + +If called, stops requesting segments, and refreshing the playlist. + +#### Event: progress +* `Object` - Current segment with the following fields, + - `number` - num + - `number` - size + - `number` - duration + - `string` - url +* `number` - Total number of segments. +* `number` - Bytes downloaded up to this point. + +For static non-live playlists, emitted each time a segment has finished downloading. Since total download size is unknown until all segment endpoints are hit, progress is calculated based on how many segments are available. + +#### miniget events + +All [miniget events](https://github.com/fent/node-miniget#event-redirect) are forwarded and can be listened to from the returned stream. + +### m3u8stream.parseTimestamp(time) + +Converts human friendly time to milliseconds. Supports the format +00:00:00.000 for hours, minutes, seconds, and milliseconds respectively. +And 0ms, 0s, 0m, 0h, and together 1m1s. + +* `time` - A string (or number) giving the user-readable input data + +### Limitations + +Currently, it does not support [encrypted media segments](https://tools.ietf.org/html/draft-pantos-http-live-streaming-20#section-4.3.2.4). This is because the sites where this was tested on and intended for, YouTube and Twitch, don't use it. + +This does not parse master playlists, only media playlists. If you want to parse a master playlist to get links to media playlists, you can try the [m3u8 module](https://github.com/tedconf/node-m3u8). + + +# Install + + npm install m3u8stream + + +# Tests +Tests are written with [mocha](https://mochajs.org) + +```bash +npm test +``` diff --git a/node_modules/m3u8stream/dist/dash-mpd-parser.d.ts b/node_modules/m3u8stream/dist/dash-mpd-parser.d.ts new file mode 100644 index 0000000..b3a2bc7 --- /dev/null +++ b/node_modules/m3u8stream/dist/dash-mpd-parser.d.ts @@ -0,0 +1,11 @@ +/// +import { Writable } from 'stream'; +import { Parser } from './parser'; +/** + * A wrapper around sax that emits segments. + */ +export default class DashMPDParser extends Writable implements Parser { + private _parser; + constructor(targetID?: string); + _write(chunk: Buffer, encoding: string, callback: () => void): void; +} diff --git a/node_modules/m3u8stream/dist/dash-mpd-parser.js b/node_modules/m3u8stream/dist/dash-mpd-parser.js new file mode 100644 index 0000000..0fc81f6 --- /dev/null +++ b/node_modules/m3u8stream/dist/dash-mpd-parser.js @@ -0,0 +1,183 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = require("stream"); +const sax_1 = __importDefault(require("sax")); +const parse_time_1 = require("./parse-time"); +/** + * A wrapper around sax that emits segments. + */ +class DashMPDParser extends stream_1.Writable { + constructor(targetID) { + super(); + this._parser = sax_1.default.createStream(false, { lowercase: true }); + this._parser.on('error', this.destroy.bind(this)); + let lastTag; + let currtime = 0; + let seq = 0; + let segmentTemplate; + let timescale, offset, duration, baseURL; + let timeline = []; + let getSegments = false; + let gotSegments = false; + let isStatic; + let treeLevel; + let periodStart; + const tmpl = (str) => { + const context = { + RepresentationID: targetID, + Number: seq, + Time: currtime, + }; + return str.replace(/\$(\w+)\$/g, (m, p1) => `${context[p1]}`); + }; + this._parser.on('opentag', node => { + switch (node.name) { + case 'mpd': + currtime = + node.attributes.availabilitystarttime ? + new Date(node.attributes.availabilitystarttime).getTime() : 0; + isStatic = node.attributes.type !== 'dynamic'; + break; + case 'period': + // Reset everything on tag. + seq = 0; + timescale = 1000; + duration = 0; + offset = 0; + baseURL = []; + treeLevel = 0; + periodStart = parse_time_1.durationStr(node.attributes.start) || 0; + break; + case 'segmentlist': + seq = parseInt(node.attributes.startnumber) || seq; + timescale = parseInt(node.attributes.timescale) || timescale; + duration = parseInt(node.attributes.duration) || duration; + offset = parseInt(node.attributes.presentationtimeoffset) || offset; + break; + case 'segmenttemplate': + segmentTemplate = node.attributes; + seq = parseInt(node.attributes.startnumber) || seq; + timescale = parseInt(node.attributes.timescale) || timescale; + break; + case 'segmenttimeline': + case 'baseurl': + lastTag = node.name; + break; + case 's': + timeline.push({ + duration: parseInt(node.attributes.d), + repeat: parseInt(node.attributes.r), + time: parseInt(node.attributes.t), + }); + break; + case 'adaptationset': + case 'representation': + treeLevel++; + if (!targetID) { + targetID = node.attributes.id; + } + getSegments = node.attributes.id === `${targetID}`; + if (getSegments) { + if (periodStart) { + currtime += periodStart; + } + if (offset) { + currtime -= offset / timescale * 1000; + } + this.emit('starttime', currtime); + } + break; + case 'initialization': + if (getSegments) { + this.emit('item', { + url: baseURL.filter(s => !!s).join('') + node.attributes.sourceurl, + seq: seq, + init: true, + duration: 0, + }); + } + break; + case 'segmenturl': + if (getSegments) { + gotSegments = true; + let tl = timeline.shift(); + let segmentDuration = ((tl === null || tl === void 0 ? void 0 : tl.duration) || duration) / timescale * 1000; + this.emit('item', { + url: baseURL.filter(s => !!s).join('') + node.attributes.media, + seq: seq++, + duration: segmentDuration, + }); + currtime += segmentDuration; + } + break; + } + }); + const onEnd = () => { + if (isStatic) { + this.emit('endlist'); + } + if (!getSegments) { + this.destroy(Error(`Representation '${targetID}' not found`)); + } + else { + this.emit('end'); + } + }; + this._parser.on('closetag', tagName => { + switch (tagName) { + case 'adaptationset': + case 'representation': + treeLevel--; + if (segmentTemplate && timeline.length) { + gotSegments = true; + if (segmentTemplate.initialization) { + this.emit('item', { + url: baseURL.filter(s => !!s).join('') + + tmpl(segmentTemplate.initialization), + seq: seq, + init: true, + duration: 0, + }); + } + for (let { duration: itemDuration, repeat, time } of timeline) { + itemDuration = itemDuration / timescale * 1000; + repeat = repeat || 1; + currtime = time || currtime; + for (let i = 0; i < repeat; i++) { + this.emit('item', { + url: baseURL.filter(s => !!s).join('') + + tmpl(segmentTemplate.media), + seq: seq++, + duration: itemDuration, + }); + currtime += itemDuration; + } + } + } + if (gotSegments) { + this.emit('endearly'); + onEnd(); + this._parser.removeAllListeners(); + this.removeAllListeners('finish'); + } + break; + } + }); + this._parser.on('text', text => { + if (lastTag === 'baseurl') { + baseURL[treeLevel] = text; + lastTag = null; + } + }); + this.on('finish', onEnd); + } + _write(chunk, encoding, callback) { + this._parser.write(chunk, encoding); + callback(); + } +} +exports.default = DashMPDParser; +//# sourceMappingURL=dash-mpd-parser.js.map \ No newline at end of file diff --git a/node_modules/m3u8stream/dist/dash-mpd-parser.js.map b/node_modules/m3u8stream/dist/dash-mpd-parser.js.map new file mode 100644 index 0000000..24ba878 --- /dev/null +++ b/node_modules/m3u8stream/dist/dash-mpd-parser.js.map @@ -0,0 +1 @@ +{"version":3,"file":"dash-mpd-parser.js","sourceRoot":"","sources":["../src/dash-mpd-parser.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/node_modules/m3u8stream/dist/index.d.ts b/node_modules/m3u8stream/dist/index.d.ts new file mode 100644 index 0000000..7d3ecea --- /dev/null +++ b/node_modules/m3u8stream/dist/index.d.ts @@ -0,0 +1,31 @@ +/// +import { PassThrough } from 'stream'; +import miniget from 'miniget'; +declare namespace m3u8stream { + interface Options { + begin?: number | string; + liveBuffer?: number; + chunkReadahead?: number; + highWaterMark?: number; + requestOptions?: miniget.Options; + parser?: 'm3u8' | 'dash-mpd'; + id?: string; + } + interface Progress { + num: number; + size: number; + duration: number; + url: string; + } + interface Stream extends PassThrough { + end: () => void; + on(event: 'progress', progress: Progress, totalSegments: number, downloadedBytes: number): this; + on(event: string | symbol, listener: (...args: any) => void): this; + } + interface m3u8streamFunc { + (playlistURL: string, options?: m3u8stream.Options): Stream; + parseTimestamp(time: number | string): number; + } +} +declare let m3u8stream: m3u8stream.m3u8streamFunc; +export = m3u8stream; diff --git a/node_modules/m3u8stream/dist/index.js b/node_modules/m3u8stream/dist/index.js new file mode 100644 index 0000000..f0c1aa8 --- /dev/null +++ b/node_modules/m3u8stream/dist/index.js @@ -0,0 +1,182 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +const stream_1 = require("stream"); +const miniget_1 = __importDefault(require("miniget")); +const m3u8_parser_1 = __importDefault(require("./m3u8-parser")); +const dash_mpd_parser_1 = __importDefault(require("./dash-mpd-parser")); +const queue_1 = require("./queue"); +const parse_time_1 = require("./parse-time"); +const supportedParsers = { + m3u8: m3u8_parser_1.default, + 'dash-mpd': dash_mpd_parser_1.default, +}; +let m3u8stream = ((playlistURL, options = {}) => { + const stream = new stream_1.PassThrough(); + const chunkReadahead = options.chunkReadahead || 3; + // 20 seconds. + const liveBuffer = options.liveBuffer || 20000; + const requestOptions = options.requestOptions; + const Parser = supportedParsers[options.parser || (/\.mpd$/.test(playlistURL) ? 'dash-mpd' : 'm3u8')]; + if (!Parser) { + throw TypeError(`parser '${options.parser}' not supported`); + } + let begin = 0; + if (typeof options.begin !== 'undefined') { + begin = typeof options.begin === 'string' ? + parse_time_1.humanStr(options.begin) : + Math.max(options.begin - liveBuffer, 0); + } + const forwardEvents = (req) => { + for (let event of ['abort', 'request', 'response', 'redirect', 'retry', 'reconnect']) { + req.on(event, stream.emit.bind(stream, event)); + } + }; + let currSegment; + const streamQueue = new queue_1.Queue((req, callback) => { + currSegment = req; + // Count the size manually, since the `content-length` header is not + // always there. + let size = 0; + req.on('data', (chunk) => size += chunk.length); + req.pipe(stream, { end: false }); + req.on('end', () => callback(null, size)); + }, { concurrency: 1 }); + let segmentNumber = 0; + let downloaded = 0; + const requestQueue = new queue_1.Queue((segment, callback) => { + let reqOptions = Object.assign({}, requestOptions); + if (segment.range) { + reqOptions.headers = Object.assign({}, reqOptions.headers, { + Range: `bytes=${segment.range.start}-${segment.range.end}`, + }); + } + let req = miniget_1.default(new URL(segment.url, playlistURL).toString(), reqOptions); + req.on('error', callback); + forwardEvents(req); + streamQueue.push(req, (_, size) => { + downloaded += +size; + stream.emit('progress', { + num: ++segmentNumber, + size: size, + duration: segment.duration, + url: segment.url, + }, requestQueue.total, downloaded); + callback(null); + }); + }, { concurrency: chunkReadahead }); + const onError = (err) => { + if (ended) { + return; + } + stream.emit('error', err); + // Stop on any error. + stream.end(); + }; + // When to look for items again. + let refreshThreshold; + let minRefreshTime; + let refreshTimeout; + let fetchingPlaylist = true; + let ended = false; + let isStatic = false; + let lastRefresh; + const onQueuedEnd = (err) => { + currSegment = null; + if (err) { + onError(err); + } + else if (!fetchingPlaylist && !ended && !isStatic && + requestQueue.tasks.length + requestQueue.active <= refreshThreshold) { + let ms = Math.max(0, minRefreshTime - (Date.now() - lastRefresh)); + fetchingPlaylist = true; + refreshTimeout = setTimeout(refreshPlaylist, ms); + } + else if ((ended || isStatic) && + !requestQueue.tasks.length && !requestQueue.active) { + stream.end(); + } + }; + let currPlaylist; + let lastSeq; + let starttime = 0; + const refreshPlaylist = () => { + lastRefresh = Date.now(); + currPlaylist = miniget_1.default(playlistURL, requestOptions); + currPlaylist.on('error', onError); + forwardEvents(currPlaylist); + const parser = currPlaylist.pipe(new Parser(options.id)); + parser.on('starttime', (a) => { + if (starttime) { + return; + } + starttime = a; + if (typeof options.begin === 'string' && begin >= 0) { + begin += starttime; + } + }); + parser.on('endlist', () => { isStatic = true; }); + parser.on('endearly', currPlaylist.unpipe.bind(currPlaylist, parser)); + let addedItems = []; + const addItem = (item) => { + if (!item.init) { + if (item.seq <= lastSeq) { + return; + } + lastSeq = item.seq; + } + begin = item.time; + requestQueue.push(item, onQueuedEnd); + addedItems.push(item); + }; + let tailedItems = [], tailedItemsDuration = 0; + parser.on('item', (item) => { + let timedItem = Object.assign({ time: starttime }, item); + if (begin <= timedItem.time) { + addItem(timedItem); + } + else { + tailedItems.push(timedItem); + tailedItemsDuration += timedItem.duration; + // Only keep the last `liveBuffer` of items. + while (tailedItems.length > 1 && + tailedItemsDuration - tailedItems[0].duration > liveBuffer) { + const lastItem = tailedItems.shift(); + tailedItemsDuration -= lastItem.duration; + } + } + starttime += timedItem.duration; + }); + parser.on('end', () => { + currPlaylist = null; + // If we are too ahead of the stream, make sure to get the + // latest available items with a small buffer. + if (!addedItems.length && tailedItems.length) { + tailedItems.forEach(item => { addItem(item); }); + } + // Refresh the playlist when remaining segments get low. + refreshThreshold = Math.max(1, Math.ceil(addedItems.length * 0.01)); + // Throttle refreshing the playlist by looking at the duration + // of live items added on this refresh. + minRefreshTime = + addedItems.reduce((total, item) => item.duration + total, 0); + fetchingPlaylist = false; + onQueuedEnd(null); + }); + }; + refreshPlaylist(); + stream.end = () => { + ended = true; + streamQueue.die(); + requestQueue.die(); + clearTimeout(refreshTimeout); + currPlaylist === null || currPlaylist === void 0 ? void 0 : currPlaylist.destroy(); + currSegment === null || currSegment === void 0 ? void 0 : currSegment.destroy(); + stream_1.PassThrough.prototype.end.call(stream, null); + }; + return stream; +}); +m3u8stream.parseTimestamp = parse_time_1.humanStr; +module.exports = m3u8stream; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/node_modules/m3u8stream/dist/index.js.map b/node_modules/m3u8stream/dist/index.js.map new file mode 100644 index 0000000..71385bb --- /dev/null +++ b/node_modules/m3u8stream/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/node_modules/m3u8stream/dist/m3u8-parser.d.ts b/node_modules/m3u8stream/dist/m3u8-parser.d.ts new file mode 100644 index 0000000..d27a18e --- /dev/null +++ b/node_modules/m3u8stream/dist/m3u8-parser.d.ts @@ -0,0 +1,18 @@ +/// +import { Writable } from 'stream'; +import { Parser } from './parser'; +/** + * A very simple m3u8 playlist file parser that detects tags and segments. + */ +export default class m3u8Parser extends Writable implements Parser { + private _lastLine; + private _seq; + private _nextItemDuration; + private _nextItemRange; + private _lastItemRangeEnd; + constructor(); + private _parseAttrList; + private _parseRange; + _parseLine(line: string): void; + _write(chunk: Buffer, encoding: string, callback: () => void): void; +} diff --git a/node_modules/m3u8stream/dist/m3u8-parser.js b/node_modules/m3u8stream/dist/m3u8-parser.js new file mode 100644 index 0000000..458b266 --- /dev/null +++ b/node_modules/m3u8stream/dist/m3u8-parser.js @@ -0,0 +1,111 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = require("stream"); +/** + * A very simple m3u8 playlist file parser that detects tags and segments. + */ +class m3u8Parser extends stream_1.Writable { + constructor() { + super(); + this._lastLine = ''; + this._seq = 0; + this._nextItemDuration = null; + this._nextItemRange = null; + this._lastItemRangeEnd = 0; + this.on('finish', () => { + this._parseLine(this._lastLine); + this.emit('end'); + }); + } + _parseAttrList(value) { + let attrs = {}; + let regex = /([A-Z0-9-]+)=(?:"([^"]*?)"|([^,]*?))/g; + let match; + while ((match = regex.exec(value)) !== null) { + attrs[match[1]] = match[2] || match[3]; + } + return attrs; + } + _parseRange(value) { + if (!value) + return null; + let svalue = value.split('@'); + let start = svalue[1] ? parseInt(svalue[1]) : this._lastItemRangeEnd + 1; + let end = start + parseInt(svalue[0]) - 1; + let range = { start, end }; + this._lastItemRangeEnd = range.end; + return range; + } + _parseLine(line) { + let match = line.match(/^#(EXT[A-Z0-9-]+)(?::(.*))?/); + if (match) { + // This is a tag. + const tag = match[1]; + const value = match[2] || ''; + switch (tag) { + case 'EXT-X-PROGRAM-DATE-TIME': + this.emit('starttime', new Date(value).getTime()); + break; + case 'EXT-X-MEDIA-SEQUENCE': + this._seq = parseInt(value); + break; + case 'EXT-X-MAP': { + let attrs = this._parseAttrList(value); + if (!attrs.URI) { + this.destroy(new Error('`EXT-X-MAP` found without required attribute `URI`')); + return; + } + this.emit('item', { + url: attrs.URI, + seq: this._seq, + init: true, + duration: 0, + range: this._parseRange(attrs.BYTERANGE), + }); + break; + } + case 'EXT-X-BYTERANGE': { + this._nextItemRange = this._parseRange(value); + break; + } + case 'EXTINF': + this._nextItemDuration = + Math.round(parseFloat(value.split(',')[0]) * 1000); + break; + case 'EXT-X-ENDLIST': + this.emit('endlist'); + break; + } + } + else if (!/^#/.test(line) && line.trim()) { + // This is a segment + this.emit('item', { + url: line.trim(), + seq: this._seq++, + duration: this._nextItemDuration, + range: this._nextItemRange, + }); + this._nextItemRange = null; + } + } + _write(chunk, encoding, callback) { + let lines = chunk.toString('utf8').split('\n'); + if (this._lastLine) { + lines[0] = this._lastLine + lines[0]; + } + lines.forEach((line, i) => { + if (this.destroyed) + return; + if (i < lines.length - 1) { + this._parseLine(line); + } + else { + // Save the last line in case it has been broken up. + this._lastLine = line; + } + }); + callback(); + } +} +exports.default = m3u8Parser; +//# sourceMappingURL=m3u8-parser.js.map \ No newline at end of file diff --git a/node_modules/m3u8stream/dist/m3u8-parser.js.map b/node_modules/m3u8stream/dist/m3u8-parser.js.map new file mode 100644 index 0000000..0bccae1 --- /dev/null +++ b/node_modules/m3u8stream/dist/m3u8-parser.js.map @@ -0,0 +1 @@ +{"version":3,"file":"m3u8-parser.js","sourceRoot":"","sources":["../src/m3u8-parser.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/node_modules/m3u8stream/dist/parse-time.d.ts b/node_modules/m3u8stream/dist/parse-time.d.ts new file mode 100644 index 0000000..d67f296 --- /dev/null +++ b/node_modules/m3u8stream/dist/parse-time.d.ts @@ -0,0 +1,16 @@ +/** + * Converts human friendly time to milliseconds. Supports the format + * 00:00:00.000 for hours, minutes, seconds, and milliseconds respectively. + * And 0ms, 0s, 0m, 0h, and together 1m1s. + * + * @param {number|string} time + * @returns {number} + */ +export declare const humanStr: (time: number | string) => number; +/** + * Parses a duration string in the form of "123.456S", returns milliseconds. + * + * @param {string} time + * @returns {number} + */ +export declare const durationStr: (time: string) => number; diff --git a/node_modules/m3u8stream/dist/parse-time.js b/node_modules/m3u8stream/dist/parse-time.js new file mode 100644 index 0000000..011e226 --- /dev/null +++ b/node_modules/m3u8stream/dist/parse-time.js @@ -0,0 +1,59 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.durationStr = exports.humanStr = void 0; +const numberFormat = /^\d+$/; +const timeFormat = /^(?:(?:(\d+):)?(\d{1,2}):)?(\d{1,2})(?:\.(\d{3}))?$/; +const timeUnits = { + ms: 1, + s: 1000, + m: 60000, + h: 3600000, +}; +/** + * Converts human friendly time to milliseconds. Supports the format + * 00:00:00.000 for hours, minutes, seconds, and milliseconds respectively. + * And 0ms, 0s, 0m, 0h, and together 1m1s. + * + * @param {number|string} time + * @returns {number} + */ +exports.humanStr = (time) => { + if (typeof time === 'number') { + return time; + } + if (numberFormat.test(time)) { + return +time; + } + const firstFormat = timeFormat.exec(time); + if (firstFormat) { + return (+(firstFormat[1] || 0) * timeUnits.h) + + (+(firstFormat[2] || 0) * timeUnits.m) + + (+firstFormat[3] * timeUnits.s) + + +(firstFormat[4] || 0); + } + else { + let total = 0; + const r = /(-?\d+)(ms|s|m|h)/g; + let rs; + while ((rs = r.exec(time)) !== null) { + total += +rs[1] * timeUnits[rs[2]]; + } + return total; + } +}; +/** + * Parses a duration string in the form of "123.456S", returns milliseconds. + * + * @param {string} time + * @returns {number} + */ +exports.durationStr = (time) => { + let total = 0; + const r = /(\d+(?:\.\d+)?)(S|M|H)/g; + let rs; + while ((rs = r.exec(time)) !== null) { + total += +rs[1] * timeUnits[rs[2].toLowerCase()]; + } + return total; +}; +//# sourceMappingURL=parse-time.js.map \ No newline at end of file diff --git a/node_modules/m3u8stream/dist/parse-time.js.map b/node_modules/m3u8stream/dist/parse-time.js.map new file mode 100644 index 0000000..7e97669 --- /dev/null +++ b/node_modules/m3u8stream/dist/parse-time.js.map @@ -0,0 +1 @@ +{"version":3,"file":"parse-time.js","sourceRoot":"","sources":["../src/parse-time.ts"],"names":[],"mappings":";;;AAAA,MAAM,YAAY,GAAG,OAAO,CAAC;AAC7B,MAAM,UAAU,GAAG,qDAAqD,CAAC;AACzE,MAAM,SAAS,GAA8B;IAC3C,EAAE,EAAE,CAAC;IACL,CAAC,EAAE,IAAI;IACP,CAAC,EAAE,KAAK;IACR,CAAC,EAAE,OAAO;CACX,CAAC;AAEF;;;;;;;GAOG;AACU,QAAA,QAAQ,GAAG,CAAC,IAAqB,EAAU,EAAE;IACxD,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;QAAE,OAAO,IAAI,CAAC;KAAE;IAC9C,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,IAAI,CAAC;KAAE;IAC9C,MAAM,WAAW,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,WAAW,EAAE;QACf,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;YAC3C,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;YACtC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;YAC/B,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;KAC1B;SAAM;QACL,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,CAAC,GAAG,oBAAoB,CAAC;QAC/B,IAAI,EAAE,CAAC;QACP,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE;YACnC,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;SACpC;QACD,OAAO,KAAK,CAAC;KACd;AACH,CAAC,CAAC;AAEF;;;;;GAKG;AACU,QAAA,WAAW,GAAG,CAAC,IAAY,EAAU,EAAE;IAClD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,CAAC,GAAG,yBAAyB,CAAC;IACpC,IAAI,EAA0B,CAAC;IAC/B,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE;QACnC,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;KAClD;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"} \ No newline at end of file diff --git a/node_modules/m3u8stream/dist/parser.d.ts b/node_modules/m3u8stream/dist/parser.d.ts new file mode 100644 index 0000000..ff2ca47 --- /dev/null +++ b/node_modules/m3u8stream/dist/parser.d.ts @@ -0,0 +1,19 @@ +/// +import { Writable } from 'stream'; +export interface Item { + url: string; + seq: number; + duration: number; + time?: number; + range?: { + start: number; + end: number; + }; + init?: boolean; +} +export interface Parser extends Writable { + on(event: 'item', listener: (item: Item) => boolean): this; + on(event: string | symbol, listener: (...args: any[]) => any): this; + emit(event: 'item', item: Item): boolean; + emit(event: string, ...args: any[]): boolean; +} diff --git a/node_modules/m3u8stream/dist/parser.js b/node_modules/m3u8stream/dist/parser.js new file mode 100644 index 0000000..a4ed0a7 --- /dev/null +++ b/node_modules/m3u8stream/dist/parser.js @@ -0,0 +1,3 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +//# sourceMappingURL=parser.js.map \ No newline at end of file diff --git a/node_modules/m3u8stream/dist/parser.js.map b/node_modules/m3u8stream/dist/parser.js.map new file mode 100644 index 0000000..2c52bc6 --- /dev/null +++ b/node_modules/m3u8stream/dist/parser.js.map @@ -0,0 +1 @@ +{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/node_modules/m3u8stream/dist/queue.d.ts b/node_modules/m3u8stream/dist/queue.d.ts new file mode 100644 index 0000000..b84e3b5 --- /dev/null +++ b/node_modules/m3u8stream/dist/queue.d.ts @@ -0,0 +1,39 @@ +export declare type Callback = (err: Error | null, result?: any) => void; +interface Task { + item: T; + callback?: Callback; +} +declare type Worker = (item: T, cb: Callback) => void; +export declare class Queue { + private _worker; + private _concurrency; + tasks: Task[]; + total: number; + active: number; + /** + * A really simple queue with concurrency. + * + * @param {Function} worker + * @param {Object} options + * @param {!number} options.concurrency + */ + constructor(worker: Worker, options?: { + concurrency?: number; + }); + /** + * Push a task to the queue. + * + * @param {T} item + * @param {!Function} callback + */ + push(item: T, callback?: Callback): void; + /** + * Process next job in queue. + */ + _next(): void; + /** + * Stops processing queued jobs. + */ + die(): void; +} +export {}; diff --git a/node_modules/m3u8stream/dist/queue.js b/node_modules/m3u8stream/dist/queue.js new file mode 100644 index 0000000..25659a6 --- /dev/null +++ b/node_modules/m3u8stream/dist/queue.js @@ -0,0 +1,58 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Queue = void 0; +class Queue { + /** + * A really simple queue with concurrency. + * + * @param {Function} worker + * @param {Object} options + * @param {!number} options.concurrency + */ + constructor(worker, options = {}) { + this._worker = worker; + this._concurrency = options.concurrency || 1; + this.tasks = []; + this.total = 0; + this.active = 0; + } + /** + * Push a task to the queue. + * + * @param {T} item + * @param {!Function} callback + */ + push(item, callback) { + this.tasks.push({ item, callback }); + this.total++; + this._next(); + } + /** + * Process next job in queue. + */ + _next() { + if (this.active >= this._concurrency || !this.tasks.length) { + return; + } + const { item, callback } = this.tasks.shift(); + let callbackCalled = false; + this.active++; + this._worker(item, (err, result) => { + if (callbackCalled) { + return; + } + this.active--; + callbackCalled = true; + callback === null || callback === void 0 ? void 0 : callback(err, result); + this._next(); + }); + } + /** + * Stops processing queued jobs. + */ + die() { + this.tasks = []; + } +} +exports.Queue = Queue; +//# sourceMappingURL=queue.js.map \ No newline at end of file diff --git a/node_modules/m3u8stream/dist/queue.js.map b/node_modules/m3u8stream/dist/queue.js.map new file mode 100644 index 0000000..1d29d48 --- /dev/null +++ b/node_modules/m3u8stream/dist/queue.js.map @@ -0,0 +1 @@ +{"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":";;;AAOA,MAAa,KAAK;IAOhB;;;;;;OAMG;IACH,YAAY,MAAiB,EAAE,UAAoC,EAAE;QACnE,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC;QAC7C,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC;QACf,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IAClB,CAAC;IAGD;;;;;OAKG;IACH,IAAI,CAAC,IAAO,EAAE,QAAmB;QAC/B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,IAAI,CAAC,KAAK,EAAE,CAAC;IACf,CAAC;IAGD;;OAEG;IACH,KAAK;QACH,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;YAAE,OAAO;SAAE;QACvE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAa,CAAC;QACzD,IAAI,cAAc,GAAG,KAAK,CAAC;QAC3B,IAAI,CAAC,MAAM,EAAE,CAAC;QACd,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACjC,IAAI,cAAc,EAAE;gBAAE,OAAO;aAAE;YAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,cAAc,GAAG,IAAI,CAAC;YACtB,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAG,GAAG,EAAE,MAAM,EAAE;YACxB,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC;IAGD;;OAEG;IACH,GAAG;QACD,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;IAClB,CAAC;CACF;AA5DD,sBA4DC"} \ No newline at end of file diff --git a/node_modules/m3u8stream/package.json b/node_modules/m3u8stream/package.json new file mode 100644 index 0000000..0e5bb29 --- /dev/null +++ b/node_modules/m3u8stream/package.json @@ -0,0 +1,54 @@ +{ + "name": "m3u8stream", + "description": "Reads segments from a m3u8 or dash playlist into a consumable stream.", + "keywords": [ + "m3u8", + "hls", + "dash", + "live", + "playlist", + "segments", + "stream" + ], + "version": "0.8.4", + "repository": { + "type": "git", + "url": "git://github.com/fent/node-m3u8stream.git" + }, + "author": "fent (https://github.com/fent)", + "main": "./dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "prepare": "tsc -p tsconfig.build.json", + "build": "tsc -p tsconfig.build.json", + "test": "nyc --extension .ts --reporter=lcov --reporter=text-summary npm run test:unit", + "test:unit": "mocha -- --require ts-node/register test/*-test.ts", + "lint": "eslint ./src ./test", + "lint:fix": "eslint --fix ./src ./test" + }, + "dependencies": { + "miniget": "^4.0.0", + "sax": "^1.2.4" + }, + "devDependencies": { + "@types/mocha": "^7.0.0", + "@types/node": "^13.1.0", + "@types/sax": "^1.0.1", + "@types/sinon": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^4.8.2", + "@typescript-eslint/parser": "^4.8.2", + "eslint": "^7.14.0", + "mocha": "^7.0.1", + "nock": "^13.0.5", + "nyc": "^15.0.0", + "sinon": "^9.2.0", + "ts-node": "^9.0.0", + "typescript": "^4.0.5" + }, + "engines": { + "node": ">=10" + }, + "license": "MIT" +} diff --git a/node_modules/miniget/LICENSE b/node_modules/miniget/LICENSE new file mode 100644 index 0000000..00e8ea8 --- /dev/null +++ b/node_modules/miniget/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (C) 2017 by fent + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/miniget/README.md b/node_modules/miniget/README.md new file mode 100644 index 0000000..25ed8af --- /dev/null +++ b/node_modules/miniget/README.md @@ -0,0 +1,111 @@ +# node-miniget + +A small http(s) GET library with redirects, retries, reconnects, concatenating or streaming, and no dependencies. This keeps filesize small for potential browser use. + +![Depfu](https://img.shields.io/depfu/fent/node-miniget) +[![codecov](https://codecov.io/gh/fent/node-miniget/branch/master/graph/badge.svg)](https://codecov.io/gh/fent/node-miniget) + + +# Usage + +Concatenates a response + +```js +const miniget = require('miniget'); + +miniget('http://mywebsite.com', (err, res, body) => { + console.log('webpage contents: ', body); +}); + +// with await +let body = await miniget('http://yourwebsite.com').text(); +``` + +Request can be streamed right away + +```js +miniget('http://api.mywebsite.com/v1/messages.json') + .pipe(someWritableStream()); +``` + + +# API + +### miniget(url, [options]) + +Makes a GET request. `url` can be a string or a `URL` object. `options` can have any properties from the [`http.request()` function](https://nodejs.org/api/http.html#http_http_request_options_callback), in addition to + +* `maxRedirects` - Default is `10`. +* `maxRetries` - Number of times to retry the request if there is a 500 or connection error. Default is `2`. +* `maxReconnects` - During a big download, if there is a disconnect, miniget can try to reconnect and continue the download where it left off. Default is `0`. +* `backoff` - An object with `inc` and `max` used to calculate how long to wait to retry a request. Default is `{ inc: 100, max: 10000 }`. +* `highWaterMark` - Amount of data to buffer when in stream mode. +* `transform` - Use this to add additional features. Called with the object that `http.get()` or `https.get()` would be called with. Must return a transformed object. +* `acceptEncoding` - An object with encoding name as the key, and the value as a function that returns a decoding stream. + ```js + acceptEncoding: { gzip: () => require('zlip').createGunzip(stream) } + ``` + Given encodings will be added to the `Accept-Encoding` header, and the response will be decoded if the server responds with encoded content. + +Defaults are held in `miniget.defaultOptions` and can be adjusted globally. + +Miniget returns a readable stream, errors will then be emitted on the stream. Returned stream has additional methods added, and can emit the following events. + +### Stream#destroy([error]) + +Destroys the request. + +### Stream#destroyed + +Set to `true` after `Stream#destroy()` has been called. + +### Stream#text() + +Returns a promise that resolves to the concatenated contents of the response. + +```js +let body = await miniget('http://yourwebsite.com').text(); +``` + +#### Event: redirect +* `string` - URL redirected to. + +Emitted when the request was redirected with a redirection status code. + +#### Event: retry +* `number` - Number of retry. +* `Error` - Request or status code error. + +Emitted when the request fails, or the response has a status code >= 500. + +#### Event: reconnect +* `number` - Number of reconnect. +* `Error` - Request or response error. + +Emitted when the request or response fails after download has started. + +#### Event: request +* [`http.ClientRequest`](https://nodejs.org/api/http.html#http_class_http_clientrequest) - Request. + +Emitted when a video request is made, including after any redirects, retries, and reconnects. + +#### Event: response +* [`http.ServerResponse`](https://nodejs.org/api/http.html#http_class_http_serverresponse) - Response. + +Emitted when a video response has been found and has started downloading, including after any successful reconnects. + +#### Forwarded events + +Any events emitted from the [request](https://nodejs.org/api/http.html#http_class_http_clientrequest) or [response](https://nodejs.org/api/http.html#http_class_http_serverresponse) objects will be forwarded to the miniget stream. + +# Install + + npm install miniget + + +# Tests +Tests are written with [mocha](https://mochajs.org) + +```bash +npm test +``` diff --git a/node_modules/miniget/dist/index.d.ts b/node_modules/miniget/dist/index.d.ts new file mode 100644 index 0000000..0847c0d --- /dev/null +++ b/node_modules/miniget/dist/index.d.ts @@ -0,0 +1,65 @@ +/// +import { RequestOptions } from 'http'; +import { PassThrough, Transform } from 'stream'; +declare namespace Miniget { + interface Options extends RequestOptions { + maxRedirects?: number; + maxRetries?: number; + maxReconnects?: number; + backoff?: { + inc: number; + max: number; + }; + highWaterMark?: number; + transform?: (parsedUrl: RequestOptions) => RequestOptions; + acceptEncoding?: { + [key: string]: () => Transform; + }; + } + interface DefaultOptions extends Options { + maxRedirects: number; + maxRetries: number; + maxReconnects: number; + backoff: { + inc: number; + max: number; + }; + } + type defaultOptions = Miniget.Options; + type MinigetError = Error; + interface Stream extends PassThrough { + abort: (err?: Error) => void; + aborted: boolean; + destroy: (err?: Error) => void; + destroyed: boolean; + text: () => Promise; + on(event: 'reconnect', listener: (attempt: number, err?: Miniget.MinigetError) => void): this; + on(event: 'retry', listener: (attempt: number, err?: Miniget.MinigetError) => void): this; + on(event: 'redirect', listener: (url: string) => void): this; + on(event: string | symbol, listener: (...args: any) => void): this; + } +} +declare function Miniget(url: string | URL, options?: Miniget.Options): Miniget.Stream; +declare namespace Miniget { + var defaultOptions: { + maxRedirects: number; + maxRetries: number; + maxReconnects: number; + backoff: { + inc: number; + max: number; + }; + }; + var MinigetError: { + new (message: string, statusCode?: number | undefined): { + statusCode?: number | undefined; + name: string; + message: string; + stack?: string | undefined; + }; + captureStackTrace(targetObject: object, constructorOpt?: Function | undefined): void; + prepareStackTrace?: ((err: Error, stackTraces: NodeJS.CallSite[]) => any) | undefined; + stackTraceLimit: number; + }; +} +export = Miniget; diff --git a/node_modules/miniget/dist/index.js b/node_modules/miniget/dist/index.js new file mode 100644 index 0000000..b1da727 --- /dev/null +++ b/node_modules/miniget/dist/index.js @@ -0,0 +1,285 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +const http_1 = __importDefault(require("http")); +const https_1 = __importDefault(require("https")); +const stream_1 = require("stream"); +const httpLibs = { 'http:': http_1.default, 'https:': https_1.default }; +const redirectStatusCodes = new Set([301, 302, 303, 307, 308]); +const retryStatusCodes = new Set([429, 503]); +// `request`, `response`, `abort`, left out, miniget will emit these. +const requestEvents = ['connect', 'continue', 'information', 'socket', 'timeout', 'upgrade']; +const responseEvents = ['aborted']; +Miniget.MinigetError = class MinigetError extends Error { + constructor(message, statusCode) { + super(message); + this.statusCode = statusCode; + } +}; +Miniget.defaultOptions = { + maxRedirects: 10, + maxRetries: 2, + maxReconnects: 0, + backoff: { inc: 100, max: 10000 }, +}; +function Miniget(url, options = {}) { + var _a; + const opts = Object.assign({}, Miniget.defaultOptions, options); + const stream = new stream_1.PassThrough({ highWaterMark: opts.highWaterMark }); + stream.destroyed = stream.aborted = false; + let activeRequest; + let activeResponse; + let activeDecodedStream; + let redirects = 0; + let retries = 0; + let retryTimeout; + let reconnects = 0; + let contentLength; + let acceptRanges = false; + let rangeStart = 0, rangeEnd; + let downloaded = 0; + // Check if this is a ranged request. + if ((_a = opts.headers) === null || _a === void 0 ? void 0 : _a.Range) { + let r = /bytes=(\d+)-(\d+)?/.exec(`${opts.headers.Range}`); + if (r) { + rangeStart = parseInt(r[1], 10); + rangeEnd = parseInt(r[2], 10); + } + } + // Add `Accept-Encoding` header. + if (opts.acceptEncoding) { + opts.headers = Object.assign({ + 'Accept-Encoding': Object.keys(opts.acceptEncoding).join(', '), + }, opts.headers); + } + const downloadHasStarted = () => activeDecodedStream && downloaded > 0; + const downloadComplete = () => !acceptRanges || downloaded === contentLength; + const reconnect = (err) => { + activeDecodedStream = null; + retries = 0; + let inc = opts.backoff.inc; + let ms = Math.min(inc, opts.backoff.max); + retryTimeout = setTimeout(doDownload, ms); + stream.emit('reconnect', reconnects, err); + }; + const reconnectIfEndedEarly = (err) => { + if (options.method !== 'HEAD' && !downloadComplete() && reconnects++ < opts.maxReconnects) { + reconnect(err); + return true; + } + return false; + }; + const retryRequest = (retryOptions) => { + if (stream.destroyed) { + return false; + } + if (downloadHasStarted()) { + return reconnectIfEndedEarly(retryOptions.err); + } + else if ((!retryOptions.err || retryOptions.err.message === 'ENOTFOUND') && + retries++ < opts.maxRetries) { + let ms = retryOptions.retryAfter || + Math.min(retries * opts.backoff.inc, opts.backoff.max); + retryTimeout = setTimeout(doDownload, ms); + stream.emit('retry', retries, retryOptions.err); + return true; + } + return false; + }; + const forwardEvents = (ee, events) => { + for (let event of events) { + ee.on(event, stream.emit.bind(stream, event)); + } + }; + const doDownload = () => { + let parsed = {}, httpLib; + try { + let urlObj = typeof url === 'string' ? new URL(url) : url; + parsed = Object.assign({}, { + host: urlObj.host, + hostname: urlObj.hostname, + path: urlObj.pathname + urlObj.search + urlObj.hash, + port: urlObj.port, + protocol: urlObj.protocol, + }); + if (urlObj.username) { + parsed.auth = `${urlObj.username}:${urlObj.password}`; + } + httpLib = httpLibs[String(parsed.protocol)]; + } + catch (err) { + // Let the error be caught by the if statement below. + } + if (!httpLib) { + stream.emit('error', new Miniget.MinigetError(`Invalid URL: ${url}`)); + return; + } + Object.assign(parsed, opts); + if (acceptRanges && downloaded > 0) { + let start = downloaded + rangeStart; + let end = rangeEnd || ''; + parsed.headers = Object.assign({}, parsed.headers, { + Range: `bytes=${start}-${end}`, + }); + } + if (opts.transform) { + try { + parsed = opts.transform(parsed); + } + catch (err) { + stream.emit('error', err); + return; + } + if (!parsed || parsed.protocol) { + httpLib = httpLibs[String(parsed === null || parsed === void 0 ? void 0 : parsed.protocol)]; + if (!httpLib) { + stream.emit('error', new Miniget.MinigetError('Invalid URL object from `transform` function')); + return; + } + } + } + const onError = (err) => { + if (stream.destroyed || stream.readableEnded) { + return; + } + // Needed for node v10. + if (stream._readableState.ended) { + return; + } + cleanup(); + if (!retryRequest({ err })) { + stream.emit('error', err); + } + else { + activeRequest.removeListener('close', onRequestClose); + } + }; + const onRequestClose = () => { + cleanup(); + retryRequest({}); + }; + const cleanup = () => { + activeRequest.removeListener('close', onRequestClose); + activeResponse === null || activeResponse === void 0 ? void 0 : activeResponse.removeListener('data', onData); + activeDecodedStream === null || activeDecodedStream === void 0 ? void 0 : activeDecodedStream.removeListener('end', onEnd); + }; + const onData = (chunk) => { downloaded += chunk.length; }; + const onEnd = () => { + cleanup(); + if (!reconnectIfEndedEarly()) { + stream.end(); + } + }; + activeRequest = httpLib.request(parsed, (res) => { + // Needed for node v10, v12. + // istanbul ignore next + if (stream.destroyed) { + return; + } + if (redirectStatusCodes.has(res.statusCode)) { + if (redirects++ >= opts.maxRedirects) { + stream.emit('error', new Miniget.MinigetError('Too many redirects')); + } + else { + if (res.headers.location) { + url = res.headers.location; + } + else { + let err = new Miniget.MinigetError('Redirect status code given with no location', res.statusCode); + stream.emit('error', err); + cleanup(); + return; + } + setTimeout(doDownload, parseInt(res.headers['retry-after'] || '0', 10) * 1000); + stream.emit('redirect', url); + } + cleanup(); + return; + // Check for rate limiting. + } + else if (retryStatusCodes.has(res.statusCode)) { + if (!retryRequest({ retryAfter: parseInt(res.headers['retry-after'] || '0', 10) })) { + let err = new Miniget.MinigetError(`Status code: ${res.statusCode}`, res.statusCode); + stream.emit('error', err); + } + cleanup(); + return; + } + else if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 400)) { + let err = new Miniget.MinigetError(`Status code: ${res.statusCode}`, res.statusCode); + if (res.statusCode >= 500) { + onError(err); + } + else { + stream.emit('error', err); + } + cleanup(); + return; + } + activeDecodedStream = res; + if (opts.acceptEncoding && res.headers['content-encoding']) { + for (let enc of res.headers['content-encoding'].split(', ').reverse()) { + let fn = opts.acceptEncoding[enc]; + if (fn) { + activeDecodedStream = activeDecodedStream.pipe(fn()); + activeDecodedStream.on('error', onError); + } + } + } + if (!contentLength) { + contentLength = parseInt(`${res.headers['content-length']}`, 10); + acceptRanges = res.headers['accept-ranges'] === 'bytes' && + contentLength > 0 && opts.maxReconnects > 0; + } + res.on('data', onData); + activeDecodedStream.on('end', onEnd); + activeDecodedStream.pipe(stream, { end: !acceptRanges }); + activeResponse = res; + stream.emit('response', res); + res.on('error', onError); + forwardEvents(res, responseEvents); + }); + activeRequest.on('error', onError); + activeRequest.on('close', onRequestClose); + forwardEvents(activeRequest, requestEvents); + if (stream.destroyed) { + streamDestroy(...destroyArgs); + } + stream.emit('request', activeRequest); + activeRequest.end(); + }; + stream.abort = (err) => { + console.warn('`MinigetStream#abort()` has been deprecated in favor of `MinigetStream#destroy()`'); + stream.aborted = true; + stream.emit('abort'); + stream.destroy(err); + }; + let destroyArgs; + const streamDestroy = (err) => { + activeRequest.destroy(err); + activeDecodedStream === null || activeDecodedStream === void 0 ? void 0 : activeDecodedStream.unpipe(stream); + activeDecodedStream === null || activeDecodedStream === void 0 ? void 0 : activeDecodedStream.destroy(); + clearTimeout(retryTimeout); + }; + stream._destroy = (...args) => { + stream.destroyed = true; + if (activeRequest) { + streamDestroy(...args); + } + else { + destroyArgs = args; + } + }; + stream.text = () => new Promise((resolve, reject) => { + let body = ''; + stream.setEncoding('utf8'); + stream.on('data', chunk => body += chunk); + stream.on('end', () => resolve(body)); + stream.on('error', reject); + }); + process.nextTick(doDownload); + return stream; +} +module.exports = Miniget; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/node_modules/miniget/dist/index.js.map b/node_modules/miniget/dist/index.js.map new file mode 100644 index 0000000..34bc626 --- /dev/null +++ b/node_modules/miniget/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/node_modules/miniget/package.json b/node_modules/miniget/package.json new file mode 100644 index 0000000..25b119f --- /dev/null +++ b/node_modules/miniget/package.json @@ -0,0 +1,50 @@ +{ + "name": "miniget", + "description": "A small HTTP(S) GET request library, with redirects and streaming.", + "keywords": [ + "request", + "http", + "https", + "redirect", + "stream" + ], + "version": "4.2.1", + "repository": { + "type": "git", + "url": "git://github.com/fent/node-miniget.git" + }, + "author": "fent (https://github.com/fent)", + "main": "./dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "prepare": "tsc -p tsconfig.build.json", + "build": "tsc -p tsconfig.build.json", + "test": "nyc --extension .ts --reporter=lcov --reporter=text-summary npm run test:unit", + "test:unit": "mocha -- --require ts-node/register test/*-test.ts", + "lint": "eslint ./src ./test", + "lint:fix": "eslint --fix ./src ./test" + }, + "dependencies": {}, + "devDependencies": { + "@types/mocha": "^7.0.0", + "@types/node": "^14.14.9", + "@types/sinon": "^9.0.8", + "@typescript-eslint/eslint-plugin": "^4.8.2", + "@typescript-eslint/parser": "^4.8.2", + "eslint": "^7.14.0", + "longjohn": "^0.2.12", + "mocha": "^7.0.1", + "nock": "^13.0.4", + "nyc": "^15.0.0", + "sinon": "^9.2.0", + "stream-equal": "^1.1.1", + "ts-node": "^8.10.1", + "typescript": "^3.9.7" + }, + "engines": { + "node": ">=10" + }, + "license": "MIT" +} diff --git a/node_modules/prism-media/LICENSE b/node_modules/prism-media/LICENSE new file mode 100644 index 0000000..369db8d --- /dev/null +++ b/node_modules/prism-media/LICENSE @@ -0,0 +1,190 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + Copyright 2019 - 2020 Amish Shah + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/node_modules/prism-media/README.md b/node_modules/prism-media/README.md new file mode 100644 index 0000000..59488db --- /dev/null +++ b/node_modules/prism-media/README.md @@ -0,0 +1,69 @@ +[![Logo](https://hydrabolt.me/assets/prism-media-logo.svg)](https://amishshah.github.io/prism-media/) + +
+ +[![Build Status](https://travis-ci.org/amishshah/prism-media.svg?branch=master)](https://travis-ci.org/hydrabolt/prism-media) +[![dependencies](https://david-dm.org/amishshah/prism-media/status.svg)](https://david-dm.org/hydrabolt/prism-media) +[![npm](https://img.shields.io/npm/dt/prism-media.svg)](https://www.npmjs.com/package/prism-media) +[![Patreon](https://img.shields.io/badge/donate-patreon-F96854.svg)](https://www.patreon.com/discordjs) + +
+ +## What is it? + +An easy-to-use stream-based toolkit that you can use for media processing. All the features provided have predictable +abstractions and join together coherently. + +```js +// This example will demux and decode an Opus-containing OGG file, and then write it to a file. +const prism = require('prism-media'); +const fs = require('fs'); + +fs.createReadStream('./audio.ogg') + .pipe(new prism.opus.OggDemuxer()) + .pipe(new prism.opus.Decoder({ rate: 48000, channels: 2, frameSize: 960 })) + .pipe(fs.createWriteStream('./audio.pcm')); +``` + +The example above can work with either a native or pure JavaScript Opus decoder - you don't need to worry about changing +your code for whichever you install. + +- FFmpeg support (either through npm modules or a normal installation) +- Opus support (native or pure JavaScript) +- Demuxing for WebM/OGG files (no modules required!) +- Volume Altering (no modules required!) + +## Dependencies + +The following dependencies are all optional, and you should only install one from each category (the first listed in +each category is preferred) + +- Opus + - [`@discordjs/opus`](https://github.com/discordjs/opus) + - [`node-opus`](https://github.com/Rantanen/node-opus) + - [`opusscript`](https://github.com/abalabahaha/opusscript) +- FFmpeg + - [`ffmpeg-static`](http://npmjs.com/ffmpeg-static) + - `ffmpeg` from a [normal installation](https://www.ffmpeg.org/download.html) + +## Useful Links + +- [Documentation](https://amishshah.github.io/prism-media) +- [Examples](https://github.com/amishshah/prism-media/tree/master/examples) +- [Patreon](https://www.patreon.com/discordjs) + +## License + +> Copyright 2019 - 2020 Amish Shah +> +> Licensed under the Apache License, Version 2.0 (the "License"); +> you may not use this file except in compliance with the License. +> You may obtain a copy of the License at +> +> http://www.apache.org/licenses/LICENSE-2.0 +> +> Unless required by applicable law or agreed to in writing, software +> distributed under the License is distributed on an "AS IS" BASIS, +> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +> See the License for the specific language governing permissions and +> limitations under the License. \ No newline at end of file diff --git a/node_modules/prism-media/package.json b/node_modules/prism-media/package.json new file mode 100644 index 0000000..17ae3e5 --- /dev/null +++ b/node_modules/prism-media/package.json @@ -0,0 +1,63 @@ +{ + "name": "prism-media", + "version": "1.3.2", + "description": "Easy-to-use stream-based media transcoding", + "main": "src/index.js", + "types": "typings/index.d.ts", + "scripts": { + "lint": "eslint src", + "test": "npm run lint && jest && npm run docs", + "docs": "docma" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/hydrabolt/prism-media.git" + }, + "keywords": [ + "audio", + "media", + "ffmpeg", + "opus", + "pcm", + "webm", + "ogg" + ], + "author": "Amish Shah ", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/hydrabolt/prism-media/issues" + }, + "homepage": "https://github.com/hydrabolt/prism-media#readme", + "devDependencies": { + "docma": "^3.2.2", + "eslint": "^7.32.0", + "jest": "^27.0.6" + }, + "jest": { + "testURL": "http://localhost/" + }, + "peerDependencies": { + "@discordjs/opus": "^0.5.0", + "ffmpeg-static": "^4.2.7 || ^3.0.0 || ^2.4.0", + "node-opus": "^0.3.3", + "opusscript": "^0.0.8" + }, + "peerDependenciesMeta": { + "@discordjs/opus": { + "optional": true + }, + "node-opus": { + "optional": true + }, + "opusscript": { + "optional": true + }, + "ffmpeg-static": { + "optional": true + } + }, + "files": [ + "src/", + "typings/" + ] +} diff --git a/node_modules/prism-media/src/core/FFmpeg.js b/node_modules/prism-media/src/core/FFmpeg.js new file mode 100644 index 0000000..a1d9215 --- /dev/null +++ b/node_modules/prism-media/src/core/FFmpeg.js @@ -0,0 +1,160 @@ +const ChildProcess = require('child_process'); +const { Duplex } = require('stream'); + +let FFMPEG = { + command: null, + output: null, +}; + +const VERSION_REGEX = /version (.+) Copyright/mi; + +Object.defineProperty(FFMPEG, 'version', { + get() { + return VERSION_REGEX.exec(FFMPEG.output)[1]; + }, + enumerable: true, +}); + +/** + * An FFmpeg transform stream that provides an interface to FFmpeg. + * @memberof core + */ +class FFmpeg extends Duplex { + /** + * Creates a new FFmpeg transform stream + * @memberof core + * @param {Object} options Options you would pass to a regular Transform stream, plus an `args` option + * @param {Array} options.args Arguments to pass to FFmpeg + * @param {boolean} [options.shell=false] Whether FFmpeg should be spawned inside a shell + * @example + * // By default, if you don't specify an input (`-i ...`) prism will assume you're piping a stream into it. + * const transcoder = new prism.FFmpeg({ + * args: [ + * '-analyzeduration', '0', + * '-loglevel', '0', + * '-f', 's16le', + * '-ar', '48000', + * '-ac', '2', + * ] + * }); + * const s16le = mp3File.pipe(transcoder); + * const opus = s16le.pipe(new prism.opus.Encoder({ rate: 48000, channels: 2, frameSize: 960 })); + */ + constructor(options = {}) { + super(); + this.process = FFmpeg.create({ shell: false, ...options }); + const EVENTS = { + readable: this._reader, + data: this._reader, + end: this._reader, + unpipe: this._reader, + finish: this._writer, + drain: this._writer, + }; + + this._readableState = this._reader._readableState; + this._writableState = this._writer._writableState; + + this._copy(['write', 'end'], this._writer); + this._copy(['read', 'setEncoding', 'pipe', 'unpipe'], this._reader); + + for (const method of ['on', 'once', 'removeListener', 'removeListeners', 'listeners']) { + this[method] = (ev, fn) => EVENTS[ev] ? EVENTS[ev][method](ev, fn) : Duplex.prototype[method].call(this, ev, fn); + } + + const processError = error => this.emit('error', error); + this._reader.on('error', processError); + this._writer.on('error', processError); + } + + get _reader() { return this.process.stdout; } + get _writer() { return this.process.stdin; } + + _copy(methods, target) { + for (const method of methods) { + this[method] = target[method].bind(target); + } + } + + _destroy(err, cb) { + this._cleanup(); + return cb ? cb(err) : undefined; + } + + _final(cb) { + this._cleanup(); + cb(); + } + + _cleanup() { + if (this.process) { + this.once('error', () => {}); + this.process.kill('SIGKILL'); + this.process = null; + } + } + + + /** + * The available FFmpeg information + * @typedef {Object} FFmpegInfo + * @memberof core + * @property {string} command The command used to launch FFmpeg + * @property {string} output The output from running `ffmpeg -h` + * @property {string} version The version of FFmpeg being used, determined from `output`. + */ + + /** + * Finds a suitable FFmpeg command and obtains the debug information from it. + * @param {boolean} [force=false] If true, will ignore any cached results and search for the command again + * @returns {FFmpegInfo} + * @throws Will throw an error if FFmpeg cannot be found. + * @example + * const ffmpeg = prism.FFmpeg.getInfo(); + * + * console.log(`Using FFmpeg version ${ffmpeg.version}`); + * + * if (ffmpeg.output.includes('--enable-libopus')) { + * console.log('libopus is available!'); + * } else { + * console.log('libopus is unavailable!'); + * } + */ + static getInfo(force = false) { + if (FFMPEG.command && !force) return FFMPEG; + const sources = [() => { + const ffmpegStatic = require('ffmpeg-static'); + return ffmpegStatic.path || ffmpegStatic; + }, 'ffmpeg', 'avconv', './ffmpeg', './avconv']; + for (let source of sources) { + try { + if (typeof source === 'function') source = source(); + const result = ChildProcess.spawnSync(source, ['-h'], { windowsHide: true }); + if (result.error) throw result.error; + Object.assign(FFMPEG, { + command: source, + output: Buffer.concat(result.output.filter(Boolean)).toString(), + }); + return FFMPEG; + } catch (error) { + // Do nothing + } + } + throw new Error('FFmpeg/avconv not found!'); + } + + /** + * Creates a new FFmpeg instance. If you do not include `-i ...` it will be assumed that `-i -` should be prepended + * to the options and that you'll be piping data into the process. + * @param {String[]} [args=[]] Arguments to pass to FFmpeg + * @returns {ChildProcess} + * @private + * @throws Will throw an error if FFmpeg cannot be found. + */ + static create({ args = [], shell = false } = {}) { + if (!args.includes('-i')) args.unshift('-i', '-'); + return ChildProcess.spawn(FFmpeg.getInfo().command, args.concat(['pipe:1']), { windowsHide: true, shell }); + } +} + +module.exports = FFmpeg; diff --git a/node_modules/prism-media/src/core/VolumeTransformer.js b/node_modules/prism-media/src/core/VolumeTransformer.js new file mode 100644 index 0000000..782f8de --- /dev/null +++ b/node_modules/prism-media/src/core/VolumeTransformer.js @@ -0,0 +1,128 @@ +// Based on discord.js' old volume system + +const { Transform } = require('stream'); + +/** + * Transforms a stream of PCM volume. + * @memberof core + * @extends TransformStream + */ +class VolumeTransformer extends Transform { + /** + * @memberof core + * @param {Object} options Any optional TransformStream options plus some extra: + * @param {string} options.type The type of transformer: s16le (signed 16-bit little-endian), s16be, s32le, s32be + * @param {number} [options.volume=1] The output volume of the stream + * @example + * // Half the volume of a signed 16-bit little-endian PCM stream + * input + * .pipe(new prism.VolumeTransformer({ type: 's16le', volume: 0.5 })) + * .pipe(writeStream); + */ + constructor(options = {}) { + super(options); + switch (options.type) { + case 's16le': + this._readInt = (buffer, index) => buffer.readInt16LE(index); + this._writeInt = (buffer, int, index) => buffer.writeInt16LE(int, index); + this._bits = 16; + break; + case 's16be': + this._readInt = (buffer, index) => buffer.readInt16BE(index); + this._writeInt = (buffer, int, index) => buffer.writeInt16BE(int, index); + this._bits = 16; + break; + case 's32le': + this._readInt = (buffer, index) => buffer.readInt32LE(index); + this._writeInt = (buffer, int, index) => buffer.writeInt32LE(int, index); + this._bits = 32; + break; + case 's32be': + this._readInt = (buffer, index) => buffer.readInt32BE(index); + this._writeInt = (buffer, int, index) => buffer.writeInt32BE(int, index); + this._bits = 32; + break; + default: + throw new Error('VolumeTransformer type should be one of s16le, s16be, s32le, s32be'); + } + this._bytes = this._bits / 8; + this._extremum = Math.pow(2, this._bits - 1); + this.volume = typeof options.volume === 'undefined' ? 1 : options.volume; + this._chunk = Buffer.alloc(0); + } + + _readInt(buffer, index) { return index; } + _writeInt(buffer, int, index) { return index; } + + _transform(chunk, encoding, done) { + // If the volume is 1, act like a passthrough stream + if (this.volume === 1) { + this.push(chunk); + return done(); + } + + const { _bytes, _extremum } = this; + + chunk = this._chunk = Buffer.concat([this._chunk, chunk]); + if (chunk.length < _bytes) return done(); + + const complete = Math.floor(chunk.length / _bytes) * _bytes; + + for (let i = 0; i < complete; i += _bytes) { + const int = Math.min(_extremum - 1, Math.max(-_extremum, Math.floor(this.volume * this._readInt(chunk, i)))); + this._writeInt(chunk, int, i); + } + + this._chunk = chunk.slice(complete); + this.push(chunk.slice(0, complete)); + return done(); + } + + _destroy(err, cb) { + super._destroy(err, cb); + this._chunk = null; + } + + /** + * Sets the volume relative to the input stream - i.e. 1 is normal, 0.5 is half, 2 is double. + * @param {number} volume The volume that you want to set + */ + setVolume(volume) { + this.volume = volume; + } + + /** + * Sets the volume in decibels. + * @param {number} db The decibels + */ + setVolumeDecibels(db) { + this.setVolume(Math.pow(10, db / 20)); + } + + /** + * Sets the volume so that a perceived value of 0.5 is half the perceived volume etc. + * @param {number} value The value for the volume + */ + setVolumeLogarithmic(value) { + this.setVolume(Math.pow(value, 1.660964)); + } + + /** + * The current volume of the stream in decibels + * @readonly + * @type {number} + */ + get volumeDecibels() { + return Math.log10(this.volume) * 20; + } + /** + * The current volume of the stream from a logarithmic scale + * @readonly + * @type {number} + */ + get volumeLogarithmic() { + return Math.pow(this.volume, 1 / 1.660964); + } +} + +module.exports = VolumeTransformer; diff --git a/node_modules/prism-media/src/core/WebmBase.js b/node_modules/prism-media/src/core/WebmBase.js new file mode 100644 index 0000000..75f60f8 --- /dev/null +++ b/node_modules/prism-media/src/core/WebmBase.js @@ -0,0 +1,226 @@ +const { Transform } = require('stream'); + +/** + * Base class for WebmOpusDemuxer and WebmVorbisDemuxer. + * **You shouldn't directly instantiate this class, use the opus.WebmDemuxer and vorbis.WebmDemuxer + * implementations instead!** + * @memberof core + * @protected + * @extends TransformStream + */ +class WebmBaseDemuxer extends Transform { + /** + * Creates a new Webm demuxer. + * @private + * @memberof core + * @param {Object} [options] options that you would pass to a regular Transform stream. + */ + constructor(options = {}) { + super(Object.assign({ readableObjectMode: true }, options)); + this._remainder = null; + this._length = 0; + this._count = 0; + this._skipUntil = null; + this._track = null; + this._incompleteTrack = {}; + this._ebmlFound = false; + } + + _transform(chunk, encoding, done) { + this._length += chunk.length; + if (this._remainder) { + chunk = Buffer.concat([this._remainder, chunk]); + this._remainder = null; + } + let offset = 0; + if (this._skipUntil && this._length > this._skipUntil) { + offset = this._skipUntil - this._count; + this._skipUntil = null; + } else if (this._skipUntil) { + this._count += chunk.length; + done(); + return; + } + let result; + while (result !== TOO_SHORT) { + try { + result = this._readTag(chunk, offset); + } catch (error) { + done(error); + return; + } + if (result === TOO_SHORT) break; + if (result._skipUntil) { + this._skipUntil = result._skipUntil; + break; + } + if (result.offset) offset = result.offset; + else break; + } + this._count += offset; + this._remainder = chunk.slice(offset); + done(); + return; + } + + /** + * Reads an EBML ID from a buffer. + * @private + * @param {Buffer} chunk the buffer to read from. + * @param {number} offset the offset in the buffer. + * @returns {Object|Symbol} contains an `id` property (buffer) and the new `offset` (number). + * Returns the TOO_SHORT symbol if the data wasn't big enough to facilitate the request. + */ + _readEBMLId(chunk, offset) { + const idLength = vintLength(chunk, offset); + if (idLength === TOO_SHORT) return TOO_SHORT; + return { + id: chunk.slice(offset, offset + idLength), + offset: offset + idLength, + }; + } + + /** + * Reads a size variable-integer to calculate the length of the data of a tag. + * @private + * @param {Buffer} chunk the buffer to read from. + * @param {number} offset the offset in the buffer. + * @returns {Object|Symbol} contains property `offset` (number), `dataLength` (number) and `sizeLength` (number). + * Returns the TOO_SHORT symbol if the data wasn't big enough to facilitate the request. + */ + _readTagDataSize(chunk, offset) { + const sizeLength = vintLength(chunk, offset); + if (sizeLength === TOO_SHORT) return TOO_SHORT; + const dataLength = expandVint(chunk, offset, offset + sizeLength); + return { offset: offset + sizeLength, dataLength, sizeLength }; + } + + /** + * Takes a buffer and attempts to read and process a tag. + * @private + * @param {Buffer} chunk the buffer to read from. + * @param {number} offset the offset in the buffer. + * @returns {Object|Symbol} contains the new `offset` (number) and optionally the `_skipUntil` property, + * indicating that the stream should ignore any data until a certain length is reached. + * Returns the TOO_SHORT symbol if the data wasn't big enough to facilitate the request. + */ + _readTag(chunk, offset) { + const idData = this._readEBMLId(chunk, offset); + if (idData === TOO_SHORT) return TOO_SHORT; + const ebmlID = idData.id.toString('hex'); + if (!this._ebmlFound) { + if (ebmlID === '1a45dfa3') this._ebmlFound = true; + else throw Error('Did not find the EBML tag at the start of the stream'); + } + offset = idData.offset; + const sizeData = this._readTagDataSize(chunk, offset); + if (sizeData === TOO_SHORT) return TOO_SHORT; + const { dataLength } = sizeData; + offset = sizeData.offset; + // If this tag isn't useful, tell the stream to stop processing data until the tag ends + if (typeof TAGS[ebmlID] === 'undefined') { + if (chunk.length > offset + dataLength) { + return { offset: offset + dataLength }; + } + return { offset, _skipUntil: this._count + offset + dataLength }; + } + + const tagHasChildren = TAGS[ebmlID]; + if (tagHasChildren) { + return { offset }; + } + + if (offset + dataLength > chunk.length) return TOO_SHORT; + const data = chunk.slice(offset, offset + dataLength); + if (!this._track) { + if (ebmlID === 'ae') this._incompleteTrack = {}; + if (ebmlID === 'd7') this._incompleteTrack.number = data[0]; + if (ebmlID === '83') this._incompleteTrack.type = data[0]; + if (this._incompleteTrack.type === 2 && typeof this._incompleteTrack.number !== 'undefined') { + this._track = this._incompleteTrack; + } + } + if (ebmlID === '63a2') { + this._checkHead(data); + this.emit('head', data); + } else if (ebmlID === 'a3') { + if (!this._track) throw Error('No audio track in this webm!'); + if ((data[0] & 0xF) === this._track.number) { + this.push(data.slice(4)); + } + } + return { offset: offset + dataLength }; + } + + _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._incompleteTrack = {}; + } +} + +/** + * A symbol that is returned by some functions that indicates the buffer it has been provided is not large enough + * to facilitate a request. + * @name WebmBaseDemuxer#TOO_SHORT + * @memberof core + * @private + * @type {Symbol} + */ +const TOO_SHORT = WebmBaseDemuxer.TOO_SHORT = Symbol('TOO_SHORT'); + +/** + * A map that takes a value of an EBML ID in hex string form, with the value being a boolean that indicates whether + * this tag has children. + * @name WebmBaseDemuxer#TAGS + * @memberof core + * @private + * @type {Object} + */ +const TAGS = WebmBaseDemuxer.TAGS = { // value is true if the element has children + '1a45dfa3': true, // EBML + '18538067': true, // Segment + '1f43b675': true, // Cluster + '1654ae6b': true, // Tracks + 'ae': true, // TrackEntry + 'd7': false, // TrackNumber + '83': false, // TrackType + 'a3': false, // SimpleBlock + '63a2': false, +}; + +module.exports = WebmBaseDemuxer; + +function vintLength(buffer, index) { + let i = 0; + for (; i < 8; i++) if ((1 << (7 - i)) & buffer[index]) break; + i++; + if (index + i > buffer.length) { + return TOO_SHORT; + } + return i; +} + +function expandVint(buffer, start, end) { + const length = vintLength(buffer, start); + if (end > buffer.length || length === TOO_SHORT) return TOO_SHORT; + let mask = (1 << (8 - length)) - 1; + let value = buffer[start] & mask; + for (let i = start + 1; i < end; i++) { + value = (value << 8) + buffer[i]; + } + return value; +} diff --git a/node_modules/prism-media/src/core/index.js b/node_modules/prism-media/src/core/index.js new file mode 100644 index 0000000..ae9967f --- /dev/null +++ b/node_modules/prism-media/src/core/index.js @@ -0,0 +1,9 @@ +/** + * Core features. + * **You shouldn't prefix imports from this namespace with `core`.** + * @namespace core + */ +module.exports = { + FFmpeg: require('./FFmpeg'), + VolumeTransformer: require('./VolumeTransformer'), +}; diff --git a/node_modules/prism-media/src/index.js b/node_modules/prism-media/src/index.js new file mode 100644 index 0000000..ce34849 --- /dev/null +++ b/node_modules/prism-media/src/index.js @@ -0,0 +1,5 @@ +module.exports = { + opus: require('./opus'), + vorbis: require('./vorbis'), + ...require('./core'), +}; diff --git a/node_modules/prism-media/src/opus/OggDemuxer.js b/node_modules/prism-media/src/opus/OggDemuxer.js new file mode 100644 index 0000000..3ede82a --- /dev/null +++ b/node_modules/prism-media/src/opus/OggDemuxer.js @@ -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; diff --git a/node_modules/prism-media/src/opus/Opus.js b/node_modules/prism-media/src/opus/Opus.js new file mode 100644 index 0000000..d8d6c29 --- /dev/null +++ b/node_modules/prism-media/src/opus/Opus.js @@ -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 (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 (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 }; diff --git a/node_modules/prism-media/src/opus/WebmDemuxer.js b/node_modules/prism-media/src/opus/WebmDemuxer.js new file mode 100644 index 0000000..4d78f9c --- /dev/null +++ b/node_modules/prism-media/src/opus/WebmDemuxer.js @@ -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; diff --git a/node_modules/prism-media/src/opus/index.js b/node_modules/prism-media/src/opus/index.js new file mode 100644 index 0000000..3532fdc --- /dev/null +++ b/node_modules/prism-media/src/opus/index.js @@ -0,0 +1,10 @@ +/** + * Opus features + * @namespace opus + */ +module.exports = { + // Encoder and Decoder + ...require('./Opus'), + OggDemuxer: require('./OggDemuxer'), + WebmDemuxer: require('./WebmDemuxer'), +}; diff --git a/node_modules/prism-media/src/util/loader.js b/node_modules/prism-media/src/util/loader.js new file mode 100644 index 0000000..1b1deab --- /dev/null +++ b/node_modules/prism-media/src/util/loader.js @@ -0,0 +1,13 @@ +exports.require = function loader(list) { + const errorLog = []; + for (const [name, fn] of list) { + try { + const data = fn(require(name)); + data.name = name; + return data; + } catch (e) { + errorLog.push(e); + } + } + throw new Error(errorLog.join('\n')); +}; diff --git a/node_modules/prism-media/src/vorbis/WebmDemuxer.js b/node_modules/prism-media/src/vorbis/WebmDemuxer.js new file mode 100644 index 0000000..9ef84dc --- /dev/null +++ b/node_modules/prism-media/src/vorbis/WebmDemuxer.js @@ -0,0 +1,22 @@ +const WebmBaseDemuxer = require('../core/WebmBase'); + +const VORBIS_HEAD = Buffer.from([...'vorbis'].map(x => x.charCodeAt(0))); + +/** + * Demuxes a Webm stream (containing Vorbis audio) to output a Vorbis stream. + * @memberof vorbis + * @extends core.WebmBaseDemuxer + */ +class WebmDemuxer extends WebmBaseDemuxer { + _checkHead(data) { + if (data.readUInt8(0) !== 2 || !data.slice(4, 10).equals(VORBIS_HEAD)) { + throw Error('Audio codec is not Vorbis!'); + } + + this.push(data.slice(3, 3 + data.readUInt8(1))); + this.push(data.slice(3 + data.readUInt8(1), 3 + data.readUInt8(1) + data.readUInt8(2))); + this.push(data.slice(3 + data.readUInt8(1) + data.readUInt8(2))); + } +} + +module.exports = WebmDemuxer; diff --git a/node_modules/prism-media/src/vorbis/index.js b/node_modules/prism-media/src/vorbis/index.js new file mode 100644 index 0000000..9d06df8 --- /dev/null +++ b/node_modules/prism-media/src/vorbis/index.js @@ -0,0 +1,8 @@ +/** + * Vorbis features + * @namespace vorbis + */ + +module.exports = { + WebmDemuxer: require('./WebmDemuxer'), +}; diff --git a/node_modules/prism-media/typings/index.d.ts b/node_modules/prism-media/typings/index.d.ts new file mode 100644 index 0000000..cd980d3 --- /dev/null +++ b/node_modules/prism-media/typings/index.d.ts @@ -0,0 +1,43 @@ +import { Transform } from 'stream'; + +import { ChildProcess } from 'child_process'; +import { Duplex } from 'stream'; + +import { opus } from './opus'; +import { vorbis } from './vorbis'; + +export interface FFmpegOptions { + args?: string[]; + shell?: boolean; +} + +export interface FFmpegInfo { + command: string; + info: string; + version: string; + output: string; +} + +export class FFmpeg extends Duplex { + public process: ChildProcess; + constructor(options?: FFmpegOptions); + static getInfo(force?: boolean): FFmpegInfo; +} + +export interface VolumeOptions { + type: 's16le' | 's16be' | 's32le' | 's32be', + volume?: number +} + +export class VolumeTransformer extends Transform { + public volume: number; + + constructor(options: VolumeOptions); + public setVolume(volume: number): void; + public setVolumeDecibels(db: number): void; + public setVolumeLogarithmic(value: number): void; + public readonly volumeDecibels: number; + public readonly volumeLogarithmic: number; +} + +export { opus, vorbis }; diff --git a/node_modules/prism-media/typings/opus.d.ts b/node_modules/prism-media/typings/opus.d.ts new file mode 100644 index 0000000..b37facb --- /dev/null +++ b/node_modules/prism-media/typings/opus.d.ts @@ -0,0 +1,30 @@ +import { Transform } from 'stream'; + +interface OpusOptions { + frameSize: number, + channels: number, + rate: number +} + +export class OpusStream extends Transform { + public encoder: any; // TODO: type opusscript/node-opus + + constructor(options?: OpusOptions); + public static readonly type: 'opusscript' | 'node-opus' | '@discordjs/opus'; + public setBitrate(bitrate: number): void; + public setFEC(enabled: boolean): void; + public setPLP(percentage: number): void; +} + +export namespace opus { + interface OpusOptions { + frameSize: number, + channels: number, + rate: number + } + + export class Encoder extends OpusStream {} + export class Decoder extends OpusStream {} + export class OggDemuxer extends Transform {} + export class WebmDemuxer extends Transform {} +} \ No newline at end of file diff --git a/node_modules/prism-media/typings/vorbis.d.ts b/node_modules/prism-media/typings/vorbis.d.ts new file mode 100644 index 0000000..6c18898 --- /dev/null +++ b/node_modules/prism-media/typings/vorbis.d.ts @@ -0,0 +1,5 @@ +import { Transform } from 'stream'; + +export namespace vorbis { + export class WebmDemuxer extends Transform {} +} \ No newline at end of file diff --git a/node_modules/sax/LICENSE b/node_modules/sax/LICENSE new file mode 100644 index 0000000..ccffa08 --- /dev/null +++ b/node_modules/sax/LICENSE @@ -0,0 +1,41 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +==== + +`String.fromCodePoint` by Mathias Bynens used according to terms of MIT +License, as follows: + + Copyright Mathias Bynens + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/sax/README.md b/node_modules/sax/README.md new file mode 100644 index 0000000..afcd3f3 --- /dev/null +++ b/node_modules/sax/README.md @@ -0,0 +1,225 @@ +# sax js + +A sax-style parser for XML and HTML. + +Designed with [node](http://nodejs.org/) in mind, but should work fine in +the browser or other CommonJS implementations. + +## What This Is + +* A very simple tool to parse through an XML string. +* A stepping stone to a streaming HTML parser. +* A handy way to deal with RSS and other mostly-ok-but-kinda-broken XML + docs. + +## What This Is (probably) Not + +* An HTML Parser - That's a fine goal, but this isn't it. It's just + XML. +* A DOM Builder - You can use it to build an object model out of XML, + but it doesn't do that out of the box. +* XSLT - No DOM = no querying. +* 100% Compliant with (some other SAX implementation) - Most SAX + implementations are in Java and do a lot more than this does. +* An XML Validator - It does a little validation when in strict mode, but + not much. +* A Schema-Aware XSD Thing - Schemas are an exercise in fetishistic + masochism. +* A DTD-aware Thing - Fetching DTDs is a much bigger job. + +## Regarding `Hello, world!').close(); + +// stream usage +// takes the same options as the parser +var saxStream = require("sax").createStream(strict, options) +saxStream.on("error", function (e) { + // unhandled errors will throw, since this is a proper node + // event emitter. + console.error("error!", e) + // clear the error + this._parser.error = null + this._parser.resume() +}) +saxStream.on("opentag", function (node) { + // same object as above +}) +// pipe is supported, and it's readable/writable +// same chunks coming in also go out. +fs.createReadStream("file.xml") + .pipe(saxStream) + .pipe(fs.createWriteStream("file-copy.xml")) +``` + + +## Arguments + +Pass the following arguments to the parser function. All are optional. + +`strict` - Boolean. Whether or not to be a jerk. Default: `false`. + +`opt` - Object bag of settings regarding string formatting. All default to `false`. + +Settings supported: + +* `trim` - Boolean. Whether or not to trim text and comment nodes. +* `normalize` - Boolean. If true, then turn any whitespace into a single + space. +* `lowercase` - Boolean. If true, then lowercase tag names and attribute names + in loose mode, rather than uppercasing them. +* `xmlns` - Boolean. If true, then namespaces are supported. +* `position` - Boolean. If false, then don't track line/col/position. +* `strictEntities` - Boolean. If true, only parse [predefined XML + entities](http://www.w3.org/TR/REC-xml/#sec-predefined-ent) + (`&`, `'`, `>`, `<`, and `"`) + +## Methods + +`write` - Write bytes onto the stream. You don't have to do this all at +once. You can keep writing as much as you want. + +`close` - Close the stream. Once closed, no more data may be written until +it is done processing the buffer, which is signaled by the `end` event. + +`resume` - To gracefully handle errors, assign a listener to the `error` +event. Then, when the error is taken care of, you can call `resume` to +continue parsing. Otherwise, the parser will not continue while in an error +state. + +## Members + +At all times, the parser object will have the following members: + +`line`, `column`, `position` - Indications of the position in the XML +document where the parser currently is looking. + +`startTagPosition` - Indicates the position where the current tag starts. + +`closed` - Boolean indicating whether or not the parser can be written to. +If it's `true`, then wait for the `ready` event to write again. + +`strict` - Boolean indicating whether or not the parser is a jerk. + +`opt` - Any options passed into the constructor. + +`tag` - The current tag being dealt with. + +And a bunch of other stuff that you probably shouldn't touch. + +## Events + +All events emit with a single argument. To listen to an event, assign a +function to `on`. Functions get executed in the this-context of +the parser object. The list of supported events are also in the exported +`EVENTS` array. + +When using the stream interface, assign handlers using the EventEmitter +`on` function in the normal fashion. + +`error` - Indication that something bad happened. The error will be hanging +out on `parser.error`, and must be deleted before parsing can continue. By +listening to this event, you can keep an eye on that kind of stuff. Note: +this happens *much* more in strict mode. Argument: instance of `Error`. + +`text` - Text node. Argument: string of text. + +`doctype` - The ``. Argument: +object with `name` and `body` members. Attributes are not parsed, as +processing instructions have implementation dependent semantics. + +`sgmldeclaration` - Random SGML declarations. Stuff like `` +would trigger this kind of event. This is a weird thing to support, so it +might go away at some point. SAX isn't intended to be used to parse SGML, +after all. + +`opentagstart` - Emitted immediately when the tag name is available, +but before any attributes are encountered. Argument: object with a +`name` field and an empty `attributes` set. Note that this is the +same object that will later be emitted in the `opentag` event. + +`opentag` - An opening tag. Argument: object with `name` and `attributes`. +In non-strict mode, tag names are uppercased, unless the `lowercase` +option is set. If the `xmlns` option is set, then it will contain +namespace binding information on the `ns` member, and will have a +`local`, `prefix`, and `uri` member. + +`closetag` - A closing tag. In loose mode, tags are auto-closed if their +parent closes. In strict mode, well-formedness is enforced. Note that +self-closing tags will have `closeTag` emitted immediately after `openTag`. +Argument: tag name. + +`attribute` - An attribute node. Argument: object with `name` and `value`. +In non-strict mode, attribute names are uppercased, unless the `lowercase` +option is set. If the `xmlns` option is set, it will also contains namespace +information. + +`comment` - A comment node. Argument: the string of the comment. + +`opencdata` - The opening tag of a ``) of a `` tags trigger a `"script"` +event, and their contents are not checked for special xml characters. +If you pass `noscript: true`, then this behavior is suppressed. + +## Reporting Problems + +It's best to write a failing test if you find an issue. I will always +accept pull requests with failing tests if they demonstrate intended +behavior, but it is very hard to figure out what issue you're describing +without a test. Writing a test is also the best way for you yourself +to figure out if you really understand the issue you think you have with +sax-js. diff --git a/node_modules/sax/lib/sax.js b/node_modules/sax/lib/sax.js new file mode 100644 index 0000000..795d607 --- /dev/null +++ b/node_modules/sax/lib/sax.js @@ -0,0 +1,1565 @@ +;(function (sax) { // wrapper for non-node envs + sax.parser = function (strict, opt) { return new SAXParser(strict, opt) } + sax.SAXParser = SAXParser + sax.SAXStream = SAXStream + sax.createStream = createStream + + // When we pass the MAX_BUFFER_LENGTH position, start checking for buffer overruns. + // When we check, schedule the next check for MAX_BUFFER_LENGTH - (max(buffer lengths)), + // since that's the earliest that a buffer overrun could occur. This way, checks are + // as rare as required, but as often as necessary to ensure never crossing this bound. + // Furthermore, buffers are only tested at most once per write(), so passing a very + // large string into write() might have undesirable effects, but this is manageable by + // the caller, so it is assumed to be safe. Thus, a call to write() may, in the extreme + // edge case, result in creating at most one complete copy of the string passed in. + // Set to Infinity to have unlimited buffers. + sax.MAX_BUFFER_LENGTH = 64 * 1024 + + var buffers = [ + 'comment', 'sgmlDecl', 'textNode', 'tagName', 'doctype', + 'procInstName', 'procInstBody', 'entity', 'attribName', + 'attribValue', 'cdata', 'script' + ] + + sax.EVENTS = [ + 'text', + 'processinginstruction', + 'sgmldeclaration', + 'doctype', + 'comment', + 'opentagstart', + 'attribute', + 'opentag', + 'closetag', + 'opencdata', + 'cdata', + 'closecdata', + 'error', + 'end', + 'ready', + 'script', + 'opennamespace', + 'closenamespace' + ] + + function SAXParser (strict, opt) { + if (!(this instanceof SAXParser)) { + return new SAXParser(strict, opt) + } + + var parser = this + clearBuffers(parser) + parser.q = parser.c = '' + parser.bufferCheckPosition = sax.MAX_BUFFER_LENGTH + parser.opt = opt || {} + parser.opt.lowercase = parser.opt.lowercase || parser.opt.lowercasetags + parser.looseCase = parser.opt.lowercase ? 'toLowerCase' : 'toUpperCase' + parser.tags = [] + parser.closed = parser.closedRoot = parser.sawRoot = false + parser.tag = parser.error = null + parser.strict = !!strict + parser.noscript = !!(strict || parser.opt.noscript) + parser.state = S.BEGIN + parser.strictEntities = parser.opt.strictEntities + parser.ENTITIES = parser.strictEntities ? Object.create(sax.XML_ENTITIES) : Object.create(sax.ENTITIES) + parser.attribList = [] + + // namespaces form a prototype chain. + // it always points at the current tag, + // which protos to its parent tag. + if (parser.opt.xmlns) { + parser.ns = Object.create(rootNS) + } + + // mostly just for error reporting + parser.trackPosition = parser.opt.position !== false + if (parser.trackPosition) { + parser.position = parser.line = parser.column = 0 + } + emit(parser, 'onready') + } + + if (!Object.create) { + Object.create = function (o) { + function F () {} + F.prototype = o + var newf = new F() + return newf + } + } + + if (!Object.keys) { + Object.keys = function (o) { + var a = [] + for (var i in o) if (o.hasOwnProperty(i)) a.push(i) + return a + } + } + + function checkBufferLength (parser) { + var maxAllowed = Math.max(sax.MAX_BUFFER_LENGTH, 10) + var maxActual = 0 + for (var i = 0, l = buffers.length; i < l; i++) { + var len = parser[buffers[i]].length + if (len > maxAllowed) { + // Text/cdata nodes can get big, and since they're buffered, + // we can get here under normal conditions. + // Avoid issues by emitting the text node now, + // so at least it won't get any bigger. + switch (buffers[i]) { + case 'textNode': + closeText(parser) + break + + case 'cdata': + emitNode(parser, 'oncdata', parser.cdata) + parser.cdata = '' + break + + case 'script': + emitNode(parser, 'onscript', parser.script) + parser.script = '' + break + + default: + error(parser, 'Max buffer length exceeded: ' + buffers[i]) + } + } + maxActual = Math.max(maxActual, len) + } + // schedule the next check for the earliest possible buffer overrun. + var m = sax.MAX_BUFFER_LENGTH - maxActual + parser.bufferCheckPosition = m + parser.position + } + + function clearBuffers (parser) { + for (var i = 0, l = buffers.length; i < l; i++) { + parser[buffers[i]] = '' + } + } + + function flushBuffers (parser) { + closeText(parser) + if (parser.cdata !== '') { + emitNode(parser, 'oncdata', parser.cdata) + parser.cdata = '' + } + if (parser.script !== '') { + emitNode(parser, 'onscript', parser.script) + parser.script = '' + } + } + + SAXParser.prototype = { + end: function () { end(this) }, + write: write, + resume: function () { this.error = null; return this }, + close: function () { return this.write(null) }, + flush: function () { flushBuffers(this) } + } + + var Stream + try { + Stream = require('stream').Stream + } catch (ex) { + Stream = function () {} + } + + var streamWraps = sax.EVENTS.filter(function (ev) { + return ev !== 'error' && ev !== 'end' + }) + + function createStream (strict, opt) { + return new SAXStream(strict, opt) + } + + function SAXStream (strict, opt) { + if (!(this instanceof SAXStream)) { + return new SAXStream(strict, opt) + } + + Stream.apply(this) + + this._parser = new SAXParser(strict, opt) + this.writable = true + this.readable = true + + var me = this + + this._parser.onend = function () { + me.emit('end') + } + + this._parser.onerror = function (er) { + me.emit('error', er) + + // if didn't throw, then means error was handled. + // go ahead and clear error, so we can write again. + me._parser.error = null + } + + this._decoder = null + + streamWraps.forEach(function (ev) { + Object.defineProperty(me, 'on' + ev, { + get: function () { + return me._parser['on' + ev] + }, + set: function (h) { + if (!h) { + me.removeAllListeners(ev) + me._parser['on' + ev] = h + return h + } + me.on(ev, h) + }, + enumerable: true, + configurable: false + }) + }) + } + + SAXStream.prototype = Object.create(Stream.prototype, { + constructor: { + value: SAXStream + } + }) + + SAXStream.prototype.write = function (data) { + if (typeof Buffer === 'function' && + typeof Buffer.isBuffer === 'function' && + Buffer.isBuffer(data)) { + if (!this._decoder) { + var SD = require('string_decoder').StringDecoder + this._decoder = new SD('utf8') + } + data = this._decoder.write(data) + } + + this._parser.write(data.toString()) + this.emit('data', data) + return true + } + + SAXStream.prototype.end = function (chunk) { + if (chunk && chunk.length) { + this.write(chunk) + } + this._parser.end() + return true + } + + SAXStream.prototype.on = function (ev, handler) { + var me = this + if (!me._parser['on' + ev] && streamWraps.indexOf(ev) !== -1) { + me._parser['on' + ev] = function () { + var args = arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments) + args.splice(0, 0, ev) + me.emit.apply(me, args) + } + } + + return Stream.prototype.on.call(me, ev, handler) + } + + // this really needs to be replaced with character classes. + // XML allows all manner of ridiculous numbers and digits. + var CDATA = '[CDATA[' + var DOCTYPE = 'DOCTYPE' + var XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace' + var XMLNS_NAMESPACE = 'http://www.w3.org/2000/xmlns/' + var rootNS = { xml: XML_NAMESPACE, xmlns: XMLNS_NAMESPACE } + + // http://www.w3.org/TR/REC-xml/#NT-NameStartChar + // This implementation works on strings, a single character at a time + // as such, it cannot ever support astral-plane characters (10000-EFFFF) + // without a significant breaking change to either this parser, or the + // JavaScript language. Implementation of an emoji-capable xml parser + // is left as an exercise for the reader. + var nameStart = /[:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/ + + var nameBody = /[:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u00B7\u0300-\u036F\u203F-\u2040.\d-]/ + + var entityStart = /[#:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/ + var entityBody = /[#:_A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u00B7\u0300-\u036F\u203F-\u2040.\d-]/ + + function isWhitespace (c) { + return c === ' ' || c === '\n' || c === '\r' || c === '\t' + } + + function isQuote (c) { + return c === '"' || c === '\'' + } + + function isAttribEnd (c) { + return c === '>' || isWhitespace(c) + } + + function isMatch (regex, c) { + return regex.test(c) + } + + function notMatch (regex, c) { + return !isMatch(regex, c) + } + + var S = 0 + sax.STATE = { + BEGIN: S++, // leading byte order mark or whitespace + BEGIN_WHITESPACE: S++, // leading whitespace + TEXT: S++, // general stuff + TEXT_ENTITY: S++, // & and such. + OPEN_WAKA: S++, // < + SGML_DECL: S++, // + SCRIPT: S++, // ', '{'); + } catch (err) { + let args = findJSON('watch.html', 'player_response', body, /\bytplayer\.config\s*=\s*{/, '', '{'); + info.player_response = findPlayerResponse('watch.html', args); + } + info.response = findJSON('watch.html', 'response', body, /\bytInitialData("\])?\s*=\s*\{/i, '', '{'); + info.html5player = getHTML5player(body); + return info; +}; + + +const INFO_HOST = 'www.youtube.com'; +const INFO_PATH = '/get_video_info'; +const VIDEO_EURL = 'https://youtube.googleapis.com/v/'; +const getVideoInfoPage = async(id, options) => { + const url = new URL(`https://${INFO_HOST}${INFO_PATH}`); + url.searchParams.set('video_id', id); + url.searchParams.set('c', 'TVHTML5'); + url.searchParams.set('cver', `7${cver.substr(1)}`); + url.searchParams.set('eurl', VIDEO_EURL + id); + url.searchParams.set('ps', 'default'); + url.searchParams.set('gl', 'US'); + url.searchParams.set('hl', options.lang || 'en'); + url.searchParams.set('html5', '1'); + const body = await utils.exposedMiniget(url.toString(), options).text(); + let info = querystring.parse(body); + info.player_response = findPlayerResponse('get_video_info', info); + return info; +}; + + +/** + * @param {Object} player_response + * @returns {Array.} + */ +const parseFormats = player_response => { + let formats = []; + if (player_response && player_response.streamingData) { + formats = formats + .concat(player_response.streamingData.formats || []) + .concat(player_response.streamingData.adaptiveFormats || []); + } + return formats; +}; + + +/** + * Gets info from a video additional formats and deciphered URLs. + * + * @param {string} id + * @param {Object} options + * @returns {Promise} + */ +exports.getInfo = async(id, options) => { + let info = await exports.getBasicInfo(id, options); + const hasManifest = + info.player_response && info.player_response.streamingData && ( + info.player_response.streamingData.dashManifestUrl || + info.player_response.streamingData.hlsManifestUrl + ); + let funcs = []; + if (info.formats.length) { + info.html5player = info.html5player || + getHTML5player(await getWatchHTMLPageBody(id, options)) || getHTML5player(await getEmbedPageBody(id, options)); + if (!info.html5player) { + throw Error('Unable to find html5player file'); + } + const html5player = new URL(info.html5player, BASE_URL).toString(); + funcs.push(sig.decipherFormats(info.formats, html5player, options)); + } + if (hasManifest && info.player_response.streamingData.dashManifestUrl) { + let url = info.player_response.streamingData.dashManifestUrl; + funcs.push(getDashManifest(url, options)); + } + if (hasManifest && info.player_response.streamingData.hlsManifestUrl) { + let url = info.player_response.streamingData.hlsManifestUrl; + funcs.push(getM3U8(url, options)); + } + + let results = await Promise.all(funcs); + info.formats = Object.values(Object.assign({}, ...results)); + info.formats = info.formats.map(formatUtils.addFormatMeta); + info.formats.sort(formatUtils.sortFormats); + info.full = true; + return info; +}; + + +/** + * Gets additional DASH formats. + * + * @param {string} url + * @param {Object} options + * @returns {Promise>} + */ +const getDashManifest = (url, options) => new Promise((resolve, reject) => { + let formats = {}; + const parser = sax.parser(false); + parser.onerror = reject; + let adaptationSet; + parser.onopentag = node => { + if (node.name === 'ADAPTATIONSET') { + adaptationSet = node.attributes; + } else if (node.name === 'REPRESENTATION') { + const itag = parseInt(node.attributes.ID); + if (!isNaN(itag)) { + formats[url] = Object.assign({ + itag, url, + bitrate: parseInt(node.attributes.BANDWIDTH), + mimeType: `${adaptationSet.MIMETYPE}; codecs="${node.attributes.CODECS}"`, + }, node.attributes.HEIGHT ? { + width: parseInt(node.attributes.WIDTH), + height: parseInt(node.attributes.HEIGHT), + fps: parseInt(node.attributes.FRAMERATE), + } : { + audioSampleRate: node.attributes.AUDIOSAMPLINGRATE, + }); + } + } + }; + parser.onend = () => { resolve(formats); }; + const req = utils.exposedMiniget(new URL(url, BASE_URL).toString(), options); + req.setEncoding('utf8'); + req.on('error', reject); + req.on('data', chunk => { parser.write(chunk); }); + req.on('end', parser.close.bind(parser)); +}); + + +/** + * Gets additional formats. + * + * @param {string} url + * @param {Object} options + * @returns {Promise>} + */ +const getM3U8 = async(url, options) => { + url = new URL(url, BASE_URL); + const body = await utils.exposedMiniget(url.toString(), options).text(); + let formats = {}; + body + .split('\n') + .filter(line => /^https?:\/\//.test(line)) + .forEach(line => { + const itag = parseInt(line.match(/\/itag\/(\d+)\//)[1]); + formats[line] = { itag, url: line }; + }); + return formats; +}; + + +// Cache get info functions. +// In case a user wants to get a video's info before downloading. +for (let funcName of ['getBasicInfo', 'getInfo']) { + /** + * @param {string} link + * @param {Object} options + * @returns {Promise} + */ + const func = exports[funcName]; + exports[funcName] = async(link, options = {}) => { + utils.checkForUpdates(); + let id = await urlUtils.getVideoID(link); + const key = [funcName, id, options.lang].join('-'); + return exports.cache.getOrSet(key, () => func(id, options)); + }; +} + + +// Export a few helpers. +exports.validateID = urlUtils.validateID; +exports.validateURL = urlUtils.validateURL; +exports.getURLVideoID = urlUtils.getURLVideoID; +exports.getVideoID = urlUtils.getVideoID; diff --git a/node_modules/ytdl-core/lib/sig.js b/node_modules/ytdl-core/lib/sig.js new file mode 100644 index 0000000..2bdccbe --- /dev/null +++ b/node_modules/ytdl-core/lib/sig.js @@ -0,0 +1,246 @@ +const querystring = require('querystring'); +const Cache = require('./cache'); +const utils = require('./utils'); + + +// A shared cache to keep track of html5player.js tokens. +exports.cache = new Cache(); + + +/** + * Extract signature deciphering tokens from html5player file. + * + * @param {string} html5playerfile + * @param {Object} options + * @returns {Promise>} + */ +exports.getTokens = (html5playerfile, options) => exports.cache.getOrSet(html5playerfile, async() => { + const body = await utils.exposedMiniget(html5playerfile, options).text(); + const tokens = exports.extractActions(body); + if (!tokens || !tokens.length) { + throw Error('Could not extract signature deciphering actions'); + } + exports.cache.set(html5playerfile, tokens); + return tokens; +}); + + +/** + * Decipher a signature based on action tokens. + * + * @param {Array.} tokens + * @param {string} sig + * @returns {string} + */ +exports.decipher = (tokens, sig) => { + sig = sig.split(''); + for (let i = 0, len = tokens.length; i < len; i++) { + let token = tokens[i], pos; + switch (token[0]) { + case 'r': + sig = sig.reverse(); + break; + case 'w': + pos = ~~token.slice(1); + sig = swapHeadAndPosition(sig, pos); + break; + case 's': + pos = ~~token.slice(1); + sig = sig.slice(pos); + break; + case 'p': + pos = ~~token.slice(1); + sig.splice(0, pos); + break; + } + } + return sig.join(''); +}; + + +/** + * Swaps the first element of an array with one of given position. + * + * @param {Array.} arr + * @param {number} position + * @returns {Array.} + */ +const swapHeadAndPosition = (arr, position) => { + const first = arr[0]; + arr[0] = arr[position % arr.length]; + arr[position] = first; + return arr; +}; + + +const jsVarStr = '[a-zA-Z_\\$][a-zA-Z_0-9]*'; +const jsSingleQuoteStr = `'[^'\\\\]*(:?\\\\[\\s\\S][^'\\\\]*)*'`; +const jsDoubleQuoteStr = `"[^"\\\\]*(:?\\\\[\\s\\S][^"\\\\]*)*"`; +const jsQuoteStr = `(?:${jsSingleQuoteStr}|${jsDoubleQuoteStr})`; +const jsKeyStr = `(?:${jsVarStr}|${jsQuoteStr})`; +const jsPropStr = `(?:\\.${jsVarStr}|\\[${jsQuoteStr}\\])`; +const jsEmptyStr = `(?:''|"")`; +const reverseStr = ':function\\(a\\)\\{' + + '(?:return )?a\\.reverse\\(\\)' + +'\\}'; +const sliceStr = ':function\\(a,b\\)\\{' + + 'return a\\.slice\\(b\\)' + +'\\}'; +const spliceStr = ':function\\(a,b\\)\\{' + + 'a\\.splice\\(0,b\\)' + +'\\}'; +const swapStr = ':function\\(a,b\\)\\{' + + 'var c=a\\[0\\];a\\[0\\]=a\\[b(?:%a\\.length)?\\];a\\[b(?:%a\\.length)?\\]=c(?:;return a)?' + +'\\}'; +const actionsObjRegexp = new RegExp( + `var (${jsVarStr})=\\{((?:(?:${ + jsKeyStr}${reverseStr}|${ + jsKeyStr}${sliceStr}|${ + jsKeyStr}${spliceStr}|${ + jsKeyStr}${swapStr + }),?\\r?\\n?)+)\\};`); +const actionsFuncRegexp = new RegExp(`${`function(?: ${jsVarStr})?\\(a\\)\\{` + + `a=a\\.split\\(${jsEmptyStr}\\);\\s*` + + `((?:(?:a=)?${jsVarStr}`}${ + jsPropStr +}\\(a,\\d+\\);)+)` + + `return a\\.join\\(${jsEmptyStr}\\)` + + `\\}`); +const reverseRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${reverseStr}`, 'm'); +const sliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${sliceStr}`, 'm'); +const spliceRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${spliceStr}`, 'm'); +const swapRegexp = new RegExp(`(?:^|,)(${jsKeyStr})${swapStr}`, 'm'); + + +/** + * Extracts the actions that should be taken to decipher a signature. + * + * This searches for a function that performs string manipulations on + * the signature. We already know what the 3 possible changes to a signature + * are in order to decipher it. There is + * + * * Reversing the string. + * * Removing a number of characters from the beginning. + * * Swapping the first character with another position. + * + * Note, `Array#slice()` used to be used instead of `Array#splice()`, + * it's kept in case we encounter any older html5player files. + * + * After retrieving the function that does this, we can see what actions + * it takes on a signature. + * + * @param {string} body + * @returns {Array.} + */ +exports.extractActions = body => { + const objResult = actionsObjRegexp.exec(body); + const funcResult = actionsFuncRegexp.exec(body); + if (!objResult || !funcResult) { return null; } + + const obj = objResult[1].replace(/\$/g, '\\$'); + const objBody = objResult[2].replace(/\$/g, '\\$'); + const funcBody = funcResult[1].replace(/\$/g, '\\$'); + + let result = reverseRegexp.exec(objBody); + const reverseKey = result && result[1] + .replace(/\$/g, '\\$') + .replace(/\$|^'|^"|'$|"$/g, ''); + result = sliceRegexp.exec(objBody); + const sliceKey = result && result[1] + .replace(/\$/g, '\\$') + .replace(/\$|^'|^"|'$|"$/g, ''); + result = spliceRegexp.exec(objBody); + const spliceKey = result && result[1] + .replace(/\$/g, '\\$') + .replace(/\$|^'|^"|'$|"$/g, ''); + result = swapRegexp.exec(objBody); + const swapKey = result && result[1] + .replace(/\$/g, '\\$') + .replace(/\$|^'|^"|'$|"$/g, ''); + + const keys = `(${[reverseKey, sliceKey, spliceKey, swapKey].join('|')})`; + const myreg = `(?:a=)?${obj + }(?:\\.${keys}|\\['${keys}'\\]|\\["${keys}"\\])` + + `\\(a,(\\d+)\\)`; + const tokenizeRegexp = new RegExp(myreg, 'g'); + const tokens = []; + while ((result = tokenizeRegexp.exec(funcBody)) !== null) { + let key = result[1] || result[2] || result[3]; + switch (key) { + case swapKey: + tokens.push(`w${result[4]}`); + break; + case reverseKey: + tokens.push('r'); + break; + case sliceKey: + tokens.push(`s${result[4]}`); + break; + case spliceKey: + tokens.push(`p${result[4]}`); + break; + } + } + return tokens; +}; + + +/** + * @param {Object} format + * @param {string} sig + */ +exports.setDownloadURL = (format, sig) => { + let decodedUrl; + if (format.url) { + decodedUrl = format.url; + } else { + return; + } + + try { + decodedUrl = decodeURIComponent(decodedUrl); + } catch (err) { + return; + } + + // Make some adjustments to the final url. + const parsedUrl = new URL(decodedUrl); + + // This is needed for a speedier download. + // See https://github.com/fent/node-ytdl-core/issues/127 + parsedUrl.searchParams.set('ratebypass', 'yes'); + + if (sig) { + // When YouTube provides a `sp` parameter the signature `sig` must go + // into the parameter it specifies. + // See https://github.com/fent/node-ytdl-core/issues/417 + parsedUrl.searchParams.set(format.sp || 'signature', sig); + } + + format.url = parsedUrl.toString(); +}; + + +/** + * Applies `sig.decipher()` to all format URL's. + * + * @param {Array.} formats + * @param {string} html5player + * @param {Object} options + */ +exports.decipherFormats = async(formats, html5player, options) => { + let decipheredFormats = {}; + let tokens = await exports.getTokens(html5player, options); + formats.forEach(format => { + let cipher = format.signatureCipher || format.cipher; + if (cipher) { + Object.assign(format, querystring.parse(cipher)); + delete format.signatureCipher; + delete format.cipher; + } + const sig = tokens && format.s ? exports.decipher(tokens, format.s) : null; + exports.setDownloadURL(format, sig); + decipheredFormats[format.url] = format; + }); + return decipheredFormats; +}; diff --git a/node_modules/ytdl-core/lib/url-utils.js b/node_modules/ytdl-core/lib/url-utils.js new file mode 100644 index 0000000..c60789d --- /dev/null +++ b/node_modules/ytdl-core/lib/url-utils.js @@ -0,0 +1,91 @@ +/** + * Get video ID. + * + * There are a few type of video URL formats. + * - https://www.youtube.com/watch?v=VIDEO_ID + * - https://m.youtube.com/watch?v=VIDEO_ID + * - https://youtu.be/VIDEO_ID + * - https://www.youtube.com/v/VIDEO_ID + * - https://www.youtube.com/embed/VIDEO_ID + * - https://music.youtube.com/watch?v=VIDEO_ID + * - https://gaming.youtube.com/watch?v=VIDEO_ID + * + * @param {string} link + * @return {string} + * @throws {Error} If unable to find a id + * @throws {TypeError} If videoid doesn't match specs + */ +const validQueryDomains = new Set([ + 'youtube.com', + 'www.youtube.com', + 'm.youtube.com', + 'music.youtube.com', + 'gaming.youtube.com', +]); +const validPathDomains = /^https?:\/\/(youtu\.be\/|(www\.)?youtube\.com\/(embed|v|shorts)\/)/; +exports.getURLVideoID = link => { + const parsed = new URL(link); + let id = parsed.searchParams.get('v'); + if (validPathDomains.test(link) && !id) { + const paths = parsed.pathname.split('/'); + id = parsed.host === 'youtu.be' ? paths[1] : paths[2]; + } else if (parsed.hostname && !validQueryDomains.has(parsed.hostname)) { + throw Error('Not a YouTube domain'); + } + if (!id) { + throw Error(`No video id found: ${link}`); + } + id = id.substring(0, 11); + if (!exports.validateID(id)) { + throw TypeError(`Video id (${id}) does not match expected ` + + `format (${idRegex.toString()})`); + } + return id; +}; + + +/** + * Gets video ID either from a url or by checking if the given string + * matches the video ID format. + * + * @param {string} str + * @returns {string} + * @throws {Error} If unable to find a id + * @throws {TypeError} If videoid doesn't match specs + */ +const urlRegex = /^https?:\/\//; +exports.getVideoID = str => { + if (exports.validateID(str)) { + return str; + } else if (urlRegex.test(str)) { + return exports.getURLVideoID(str); + } else { + throw Error(`No video id found: ${str}`); + } +}; + + +/** + * Returns true if given id satifies YouTube's id format. + * + * @param {string} id + * @return {boolean} + */ +const idRegex = /^[a-zA-Z0-9-_]{11}$/; +exports.validateID = id => idRegex.test(id); + + +/** + * Checks wether the input string includes a valid id. + * + * @param {string} string + * @returns {boolean} + */ +exports.validateURL = string => { + try { + exports.getURLVideoID(string); + return true; + } catch (e) { + return false; + } +}; diff --git a/node_modules/ytdl-core/lib/utils.js b/node_modules/ytdl-core/lib/utils.js new file mode 100644 index 0000000..042de5a --- /dev/null +++ b/node_modules/ytdl-core/lib/utils.js @@ -0,0 +1,183 @@ +const miniget = require('miniget'); + + +/** + * Extract string inbetween another. + * + * @param {string} haystack + * @param {string} left + * @param {string} right + * @returns {string} + */ +exports.between = (haystack, left, right) => { + let pos; + if (left instanceof RegExp) { + const match = haystack.match(left); + if (!match) { return ''; } + pos = match.index + match[0].length; + } else { + pos = haystack.indexOf(left); + if (pos === -1) { return ''; } + pos += left.length; + } + haystack = haystack.slice(pos); + pos = haystack.indexOf(right); + if (pos === -1) { return ''; } + haystack = haystack.slice(0, pos); + return haystack; +}; + + +/** + * Get a number from an abbreviated number string. + * + * @param {string} string + * @returns {number} + */ +exports.parseAbbreviatedNumber = string => { + const match = string + .replace(',', '.') + .replace(' ', '') + .match(/([\d,.]+)([MK]?)/); + if (match) { + let [, num, multi] = match; + num = parseFloat(num); + return Math.round(multi === 'M' ? num * 1000000 : + multi === 'K' ? num * 1000 : num); + } + return null; +}; + + +/** + * Match begin and end braces of input JSON, return only json + * + * @param {string} mixedJson + * @returns {string} +*/ +exports.cutAfterJSON = mixedJson => { + let open, close; + if (mixedJson[0] === '[') { + open = '['; + close = ']'; + } else if (mixedJson[0] === '{') { + open = '{'; + close = '}'; + } + + if (!open) { + throw new Error(`Can't cut unsupported JSON (need to begin with [ or { ) but got: ${mixedJson[0]}`); + } + + // States if the loop is currently in a string + let isString = false; + + // States if the current character is treated as escaped or not + let isEscaped = false; + + // Current open brackets to be closed + let counter = 0; + + let i; + for (i = 0; i < mixedJson.length; i++) { + // Toggle the isString boolean when leaving/entering string + if (mixedJson[i] === '"' && !isEscaped) { + isString = !isString; + continue; + } + + // Toggle the isEscaped boolean for every backslash + // Reset for every regular character + isEscaped = mixedJson[i] === '\\' && !isEscaped; + + if (isString) continue; + + if (mixedJson[i] === open) { + counter++; + } else if (mixedJson[i] === close) { + counter--; + } + + // All brackets have been closed, thus end of JSON is reached + if (counter === 0) { + // Return the cut JSON + return mixedJson.substr(0, i + 1); + } + } + + // We ran through the whole string and ended up with an unclosed bracket + throw Error("Can't cut unsupported JSON (no matching closing bracket found)"); +}; + + +/** + * Checks if there is a playability error. + * + * @param {Object} player_response + * @param {Array.} statuses + * @param {Error} ErrorType + * @returns {!Error} + */ +exports.playError = (player_response, statuses, ErrorType = Error) => { + let playability = player_response && player_response.playabilityStatus; + if (playability && statuses.includes(playability.status)) { + return new ErrorType(playability.reason || (playability.messages && playability.messages[0])); + } + return null; +}; + +/** + * Does a miniget request and calls options.requestCallback if present + * + * @param {string} url the request url + * @param {Object} options an object with optional requestOptions and requestCallback parameters + * @param {Object} requestOptionsOverwrite overwrite of options.requestOptions + * @returns {miniget.Stream} + */ +exports.exposedMiniget = (url, options = {}, requestOptionsOverwrite) => { + const req = miniget(url, requestOptionsOverwrite || options.requestOptions); + if (typeof options.requestCallback === 'function') options.requestCallback(req); + return req; +}; + +/** + * Temporary helper to help deprecating a few properties. + * + * @param {Object} obj + * @param {string} prop + * @param {Object} value + * @param {string} oldPath + * @param {string} newPath + */ +exports.deprecate = (obj, prop, value, oldPath, newPath) => { + Object.defineProperty(obj, prop, { + get: () => { + console.warn(`\`${oldPath}\` will be removed in a near future release, ` + + `use \`${newPath}\` instead.`); + return value; + }, + }); +}; + + +// Check for updates. +const pkg = require('../package.json'); +const UPDATE_INTERVAL = 1000 * 60 * 60 * 12; +exports.lastUpdateCheck = 0; +exports.checkForUpdates = () => { + if (!process.env.YTDL_NO_UPDATE && !pkg.version.startsWith('0.0.0-') && + Date.now() - exports.lastUpdateCheck >= UPDATE_INTERVAL) { + exports.lastUpdateCheck = Date.now(); + return miniget('https://api.github.com/repos/fent/node-ytdl-core/releases/latest', { + headers: { 'User-Agent': 'ytdl-core' }, + }).text().then(response => { + if (JSON.parse(response).tag_name !== `v${pkg.version}`) { + console.warn('\x1b[33mWARNING:\x1B[0m ytdl-core is out of date! Update with "npm install ytdl-core@latest".'); + } + }, err => { + console.warn('Error checking for updates:', err.message); + console.warn('You can disable this check by setting the `YTDL_NO_UPDATE` env variable.'); + }); + } + return null; +}; diff --git a/node_modules/ytdl-core/package.json b/node_modules/ytdl-core/package.json new file mode 100644 index 0000000..b1a15e3 --- /dev/null +++ b/node_modules/ytdl-core/package.json @@ -0,0 +1,59 @@ +{ + "name": "ytdl-core", + "description": "YouTube video downloader in pure javascript.", + "keywords": [ + "youtube", + "video", + "download" + ], + "version": "4.9.1", + "repository": { + "type": "git", + "url": "git://github.com/fent/node-ytdl-core.git" + }, + "author": "fent (https://github.com/fent)", + "contributors": [ + "Tobias Kutscha (https://github.com/TimeForANinja)", + "Andrew Kelley (https://github.com/andrewrk)", + "Mauricio Allende (https://github.com/mallendeo)", + "Rodrigo Altamirano (https://github.com/raltamirano)", + "Jim Buck (https://github.com/JimmyBoh)" + ], + "main": "./lib/index.js", + "types": "./typings/index.d.ts", + "files": [ + "lib", + "typings" + ], + "scripts": { + "test": "nyc --reporter=lcov --reporter=text-summary npm run test:unit", + "test:unit": "mocha --ignore test/irl-test.js test/*-test.js --timeout 4000", + "test:irl": "mocha --timeout 16000 test/irl-test.js", + "lint": "eslint ./", + "lint:fix": "eslint --fix ./", + "lint:typings": "tslint typings/index.d.ts", + "lint:typings:fix": "tslint --fix typings/index.d.ts" + }, + "dependencies": { + "m3u8stream": "^0.8.3", + "miniget": "^4.0.0", + "sax": "^1.1.3" + }, + "devDependencies": { + "@types/node": "^13.1.0", + "assert-diff": "^3.0.1", + "dtslint": "^3.6.14", + "eslint": "^6.8.0", + "mocha": "^7.0.0", + "muk-require": "^1.2.0", + "nock": "^13.0.4", + "nyc": "^15.0.0", + "sinon": "^9.0.0", + "stream-equal": "~1.1.0", + "typescript": "^3.9.7" + }, + "engines": { + "node": ">=10" + }, + "license": "MIT" +} diff --git a/node_modules/ytdl-core/typings/index.d.ts b/node_modules/ytdl-core/typings/index.d.ts new file mode 100644 index 0000000..7bd5371 --- /dev/null +++ b/node_modules/ytdl-core/typings/index.d.ts @@ -0,0 +1,426 @@ +declare module 'ytdl-core' { + import { ClientRequest } from 'http'; + import { Readable } from 'stream'; + + namespace ytdl { + type Filter = 'audioandvideo' | 'videoandaudio' | 'video' | 'videoonly' | 'audio' | 'audioonly' | ((format: videoFormat) => boolean); + + interface getInfoOptions { + lang?: string; + requestCallback?: () => {}; + requestOptions?: {}; + } + + interface chooseFormatOptions { + quality?: 'lowest' | 'highest' | 'highestaudio' | 'lowestaudio' | 'highestvideo' | 'lowestvideo' | string | number | string[] | number[]; + filter?: Filter; + format?: videoFormat; + } + + interface downloadOptions extends getInfoOptions, chooseFormatOptions { + range?: { + start?: number; + end?: number; + }; + begin?: string | number | Date; + liveBuffer?: number; + highWaterMark?: number; + dlChunkSize?: number; + } + + interface videoFormat { + itag: number; + url: string; + mimeType?: string; + bitrate?: number; + audioBitrate?: number; + width?: number; + height?: number; + initRange?: { start: string; end: string }; + indexRange?: { start: string; end: string }; + lastModified: string; + contentLength: string; + quality: 'tiny' | 'small' | 'medium' | 'large' | 'hd720' | 'hd1080' | 'hd1440' | 'hd2160' | 'highres' | string; + qualityLabel: '144p' | '144p 15fps' | '144p60 HDR' | '240p' | '240p60 HDR' | '270p' | '360p' | '360p60 HDR' + | '480p' | '480p60 HDR' | '720p' | '720p60' | '720p60 HDR' | '1080p' | '1080p60' | '1080p60 HDR' | '1440p' + | '1440p60' | '1440p60 HDR' | '2160p' | '2160p60' | '2160p60 HDR' | '4320p' | '4320p60'; + projectionType?: 'RECTANGULAR'; + fps?: number; + averageBitrate?: number; + audioQuality?: 'AUDIO_QUALITY_LOW' | 'AUDIO_QUALITY_MEDIUM'; + colorInfo?: { + primaries: string; + transferCharacteristics: string; + matrixCoefficients: string; + }; + highReplication?: boolean; + approxDurationMs?: string; + targetDurationSec?: number; + maxDvrDurationSec?: number; + audioSampleRate?: string; + audioChannels?: number; + + // Added by ytdl-core + container: 'flv' | '3gp' | 'mp4' | 'webm' | 'ts'; + hasVideo: boolean; + hasAudio: boolean; + codecs: string; + videoCodec?: string; + audioCodec?: string; + + isLive: boolean; + isHLS: boolean; + isDashMPD: boolean; + } + + interface thumbnail { + url: string; + width: number; + height: number; + } + + interface captionTrack { + baseUrl: string; + name: { + simpleText: 'Afrikaans' | 'Albanian' | 'Amharic' | 'Arabic' | 'Armenian' | 'Azerbaijani' | 'Bangla' | 'Basque' + | 'Belarusian' | 'Bosnian' | 'Bulgarian' | 'Burmese' | 'Catalan' | 'Cebuano' | 'Chinese (Simplified)' + | 'Chinese (Traditional)' | 'Corsican' | 'Croatian' | 'Czech' | 'Danish' | 'Dutch' | 'English' + | 'English (auto-generated)' | 'Esperanto' | 'Estonian' | 'Filipino' | 'Finnish' | 'French' | 'Galician' + | 'Georgian' | 'German' | 'Greek' | 'Gujarati' | 'Haitian Creole' | 'Hausa' | 'Hawaiian' | 'Hebrew' | 'Hindi' + | 'Hmong' | 'Hungarian' | 'Icelandic' | 'Igbo' | 'Indonesian' | 'Irish' | 'Italian' | 'Japanese' | 'Javanese' + | 'Kannada' | 'Kazakh' | 'Khmer' | 'Korean' | 'Kurdish' | 'Kyrgyz' | 'Lao' | 'Latin' | 'Latvian' | 'Lithuanian' + | 'Luxembourgish' | 'Macedonian' | 'Malagasy' | 'Malay' | 'Malayalam' | 'Maltese' | 'Maori' | 'Marathi' + | 'Mongolian' | 'Nepali' | 'Norwegian' | 'Nyanja' | 'Pashto' | 'Persian' | 'Polish' | 'Portuguese' | 'Punjabi' + | 'Romanian' | 'Russian' | 'Samoan' | 'Scottish Gaelic' | 'Serbian' | 'Shona' | 'Sindhi' | 'Sinhala' | 'Slovak' + | 'Slovenian' | 'Somali' | 'Southern Sotho' | 'Spanish' | 'Spanish (Spain)' | 'Sundanese' | 'Swahili' + | 'Swedish' | 'Tajik' | 'Tamil' | 'Telugu' | 'Thai' | 'Turkish' | 'Ukrainian' | 'Urdu' | 'Uzbek' | 'Vietnamese' + | 'Welsh' | 'Western Frisian' | 'Xhosa' | 'Yiddish' | 'Yoruba' | 'Zulu' | string; + }; + vssId: string; + languageCode: 'af' | 'sq' | 'am' | 'ar' | 'hy' | 'az' | 'bn' | 'eu' | 'be' | 'bs' | 'bg' | 'my' | 'ca' | 'ceb' + | 'zh-Hans' | 'zh-Hant' | 'co' | 'hr' | 'cs' | 'da' | 'nl' | 'en' | 'eo' | 'et' | 'fil' | 'fi' | 'fr' | 'gl' + | 'ka' | 'de' | 'el' | 'gu' | 'ht' | 'ha' | 'haw' | 'iw' | 'hi' | 'hmn' | 'hu' | 'is' | 'ig' | 'id' | 'ga' | 'it' + | 'ja' | 'jv' | 'kn' | 'kk' | 'km' | 'ko' | 'ku' | 'ky' | 'lo' | 'la' | 'lv' | 'lt' | 'lb' | 'mk' | 'mg' | 'ms' + | 'ml' | 'mt' | 'mi' | 'mr' | 'mn' | 'ne' | 'no' | 'ny' | 'ps' | 'fa' | 'pl' | 'pt' | 'pa' | 'ro' | 'ru' | 'sm' + | 'gd' | 'sr' | 'sn' | 'sd' | 'si' | 'sk' | 'sl' | 'so' | 'st' | 'es' | 'su' | 'sw' | 'sv' | 'tg' | 'ta' | 'te' + | 'th' | 'tr' | 'uk' | 'ur' | 'uz' | 'vi' | 'cy' | 'fy' | 'xh' | 'yi' | 'yo' | 'zu' | string; + kind: string; + rtl?: boolean; + isTranslatable: boolean; + } + + interface audioTrack { + captionTrackIndices: number[]; + } + + interface translationLanguage { + languageCode: captionTrack['languageCode']; + languageName: captionTrack['name']; + } + + interface VideoDetails { + videoId: string; + title: string; + shortDescription: string; + lengthSeconds: string; + keywords?: string[]; + channelId: string; + isOwnerViewing: boolean; + isCrawlable: boolean; + thumbnail: { + thumbnails: thumbnail[]; + }; + averageRating: number; + allowRatings: boolean; + viewCount: string; + author: string; + isPrivate: boolean; + isUnpluggedCorpus: boolean; + isLiveContent: boolean; + } + + interface Media { + category: string; + category_url: string; + game?: string; + game_url?: string; + year?: number; + song?: string; + artist?: string; + artist_url?: string; + writers?: string; + licensed_by?: string; + thumbnails: thumbnail[]; + } + + interface Author { + id: string; + name: string; + avatar: string; // to remove later + thumbnails?: thumbnail[]; + verified: boolean; + user?: string; + channel_url: string; + external_channel_url?: string; + user_url?: string; + subscriber_count?: number; + } + + interface MicroformatRenderer { + thumbnail: { + thumbnails: thumbnail[]; + }; + embed: { + iframeUrl: string; + flashUrl: string; + width: number; + height: number; + flashSecureUrl: string; + }; + title: { + simpleText: string; + }; + description: { + simpleText: string; + }; + lengthSeconds: string; + ownerProfileUrl: string; + ownerGplusProfileUrl?: string; + externalChannelId: string; + isFamilySafe: boolean; + availableCountries: string[]; + isUnlisted: boolean; + hasYpcMetadata: boolean; + viewCount: string; + category: string; + publishDate: string; + ownerChannelName: string; + liveBroadcastDetails?: { + isLiveNow: boolean; + startTimestamp: string; + }; + uploadDate: string; + } + + interface storyboard { + templateUrl: string; + thumbnailWidth: number; + thumbnailHeight: number; + thumbnailCount: number; + interval: number; + columns: number; + rows: number; + storyboardCount: number; + } + + interface Chapter { + title: string; + start_time: number; + } + + interface MoreVideoDetails extends Omit, Omit { + published: number; + video_url: string; + age_restricted: boolean; + likes: number | null; + dislikes: number | null; + media: Media; + author: Author; + thumbnails: thumbnail[]; + storyboards: storyboard[]; + chapters: Chapter[]; + description: string | null; + } + + interface videoInfo { + iv_load_policy?: string; + iv_allow_in_place_switch?: string; + iv_endscreen_url?: string; + iv_invideo_url?: string; + iv3_module?: string; + rmktEnabled?: string; + uid?: string; + vid?: string; + focEnabled?: string; + baseUrl?: string; + storyboard_spec?: string; + serialized_ad_ux_config?: string; + player_error_log_fraction?: string; + sffb?: string; + ldpj?: string; + videostats_playback_base_url?: string; + innertube_context_client_version?: string; + t?: string; + fade_in_start_milliseconds: string; + timestamp: string; + ad3_module: string; + relative_loudness: string; + allow_below_the_player_companion: string; + eventid: string; + token: string; + atc: string; + cr: string; + apply_fade_on_midrolls: string; + cl: string; + fexp: string[]; + apiary_host: string; + fade_in_duration_milliseconds: string; + fflags: string; + ssl: string; + pltype: string; + enabled_engage_types: string; + hl: string; + is_listed: string; + gut_tag: string; + apiary_host_firstparty: string; + enablecsi: string; + csn: string; + status: string; + afv_ad_tag: string; + idpj: string; + sfw_player_response: string; + account_playback_token: string; + encoded_ad_safety_reason: string; + tag_for_children_directed: string; + no_get_video_log: string; + ppv_remarketing_url: string; + fmt_list: string[][]; + ad_slots: string; + fade_out_duration_milliseconds: string; + instream_long: string; + allow_html5_ads: string; + core_dbp: string; + ad_device: string; + itct: string; + root_ve_type: string; + excluded_ads: string; + aftv: string; + loeid: string; + cver: string; + shortform: string; + dclk: string; + csi_page_type: string; + ismb: string; + gpt_migration: string; + loudness: string; + ad_tag: string; + of: string; + probe_url: string; + vm: string; + afv_ad_tag_restricted_to_instream: string; + gapi_hint_params: string; + cid: string; + c: string; + oid: string; + ptchn: string; + as_launched_in_country: string; + avg_rating: string; + fade_out_start_milliseconds: string; + midroll_prefetch_size: string; + allow_ratings: string; + thumbnail_url: string; + iurlsd: string; + iurlmq: string; + iurlhq: string; + iurlmaxres: string; + ad_preroll: string; + tmi: string; + trueview: string; + host_language: string; + innertube_api_key: string; + show_content_thumbnail: string; + afv_instream_max: string; + innertube_api_version: string; + mpvid: string; + allow_embed: string; + ucid: string; + plid: string; + midroll_freqcap: string; + ad_logging_flag: string; + ptk: string; + vmap: string; + watermark: string[]; + dbp: string; + ad_flags: string; + html5player: string; + formats: videoFormat[]; + related_videos: relatedVideo[]; + no_embed_allowed?: boolean; + player_response: { + playabilityStatus: { + status: string; + playableInEmbed: boolean; + miniplayer: { + miniplayerRenderer: { + playbackMode: string; + }; + }; + contextParams: string; + }; + streamingData: { + expiresInSeconds: string; + formats: {}[]; + adaptiveFormats: {}[]; + }; + captions?: { + playerCaptionsRenderer: { + baseUrl: string; + visibility: string; + }; + playerCaptionsTracklistRenderer: { + captionTracks: captionTrack[]; + audioTracks: audioTrack[]; + translationLanguages: translationLanguage[]; + defaultAudioTrackIndex: number; + }; + }; + microformat: { + playerMicroformatRenderer: MicroformatRenderer; + }; + videoDetails: VideoDetails; + playerConfig: { + audioConfig: { + loudnessDb: number; + perceptualLoudnessDb: number; + enablePerFormatLoudness: boolean; + }; + streamSelectionConfig: { maxBitrate: string }; + mediaCommonConfig: { dynamicReadaheadConfig: {}[] }; + webPlayerConfig: { webPlayerActionsPorting: {}[] }; + }; + }; + videoDetails: MoreVideoDetails; + } + + interface relatedVideo { + id?: string; + title?: string; + published?: string; + author: Author | 'string'; // to remove the `string` part later + ucid?: string; // to remove later + author_thumbnail?: string; // to remove later + short_view_count_text?: string; + view_count?: string; + length_seconds?: number; + video_thumbnail?: string; // to remove later + thumbnails: thumbnail[]; + richThumbnails: thumbnail[]; + isLive: boolean; + } + + function getBasicInfo(url: string, options?: getInfoOptions): Promise; + function getInfo(url: string, options?: getInfoOptions): Promise; + function downloadFromInfo(info: videoInfo, options?: downloadOptions): Readable; + function chooseFormat(format: videoFormat | videoFormat[], options?: chooseFormatOptions): videoFormat | never; + function filterFormats(formats: videoFormat | videoFormat[], filter?: Filter): videoFormat[]; + function validateID(string: string): boolean; + function validateURL(string: string): boolean; + function getURLVideoID(string: string): string | never; + function getVideoID(string: string): string | never; + const version: number; + } + + function ytdl(link: string, options?: ytdl.downloadOptions): Readable; + + export = ytdl; +} diff --git a/package-lock.json b/package-lock.json index d139031..40a9276 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,8 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "discord.js": "^13.3.1" + "discord.js": "^13.3.1", + "ytdl-core-discord": "^1.3.1" } }, "node_modules/@discordjs/builders": { @@ -192,6 +193,18 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, + "node_modules/m3u8stream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.4.tgz", + "integrity": "sha512-sco80Db+30RvcaIOndenX6E6oQNgTiBKeJbFPc+yDXwPQIkryfboEbCvXPlBRq3mQTCVPQO93TDVlfRwqpD35w==", + "dependencies": { + "miniget": "^4.0.0", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mime-db": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", @@ -211,6 +224,14 @@ "node": ">= 0.6" } }, + "node_modules/miniget": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.1.tgz", + "integrity": "sha512-O/DduzDR6f+oDtVype9S/Qu5hhnx73EDYGyZKwU/qN82lehFZdfhoa4DT51SpsO+8epYrB3gcRmws56ROfTIoQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/node-fetch": { "version": "2.6.6", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", @@ -241,6 +262,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/prism-media": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.2.tgz", + "integrity": "sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g==", + "peerDependencies": { + "@discordjs/opus": "^0.5.0", + "ffmpeg-static": "^4.2.7 || ^3.0.0 || ^2.4.0", + "node-opus": "^0.3.3", + "opusscript": "^0.0.8" + }, + "peerDependenciesMeta": { + "@discordjs/opus": { + "optional": true + }, + "ffmpeg-static": { + "optional": true + }, + "node-opus": { + "optional": true + }, + "opusscript": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -308,6 +359,34 @@ "optional": true } } + }, + "node_modules/ytdl-core": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.9.1.tgz", + "integrity": "sha512-6Jbp5RDhUEozlaJQAR+l8oV8AHsx3WUXxSyPxzE6wOIAaLql7Hjiy0ZM58wZoyj1YEenlEPjEqcJIjKYKxvHtQ==", + "dependencies": { + "m3u8stream": "^0.8.3", + "miniget": "^4.0.0", + "sax": "^1.1.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ytdl-core-discord": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ytdl-core-discord/-/ytdl-core-discord-1.3.1.tgz", + "integrity": "sha512-KW8zYY35jRSkxZTEQtT9EiR2exFwYKhCE8QZbRg5Ge9a0YWDDhBOixSdWb8Cn41B1uHhz8FR15E4E/k0kHNX3w==", + "dependencies": { + "@types/node": "^15.12.2", + "prism-media": "^1.3.1", + "ytdl-core": "^4.8.2" + } + }, + "node_modules/ytdl-core-discord/node_modules/@types/node": { + "version": "15.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", + "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" } }, "dependencies": { @@ -442,6 +521,15 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, + "m3u8stream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.4.tgz", + "integrity": "sha512-sco80Db+30RvcaIOndenX6E6oQNgTiBKeJbFPc+yDXwPQIkryfboEbCvXPlBRq3mQTCVPQO93TDVlfRwqpD35w==", + "requires": { + "miniget": "^4.0.0", + "sax": "^1.2.4" + } + }, "mime-db": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", @@ -455,6 +543,11 @@ "mime-db": "1.51.0" } }, + "miniget": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.1.tgz", + "integrity": "sha512-O/DduzDR6f+oDtVype9S/Qu5hhnx73EDYGyZKwU/qN82lehFZdfhoa4DT51SpsO+8epYrB3gcRmws56ROfTIoQ==" + }, "node-fetch": { "version": "2.6.6", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", @@ -476,6 +569,17 @@ "vali-date": "^1.0.0" } }, + "prism-media": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.2.tgz", + "integrity": "sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g==", + "requires": {} + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -520,6 +624,33 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.3.0.tgz", "integrity": "sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==", "requires": {} + }, + "ytdl-core": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.9.1.tgz", + "integrity": "sha512-6Jbp5RDhUEozlaJQAR+l8oV8AHsx3WUXxSyPxzE6wOIAaLql7Hjiy0ZM58wZoyj1YEenlEPjEqcJIjKYKxvHtQ==", + "requires": { + "m3u8stream": "^0.8.3", + "miniget": "^4.0.0", + "sax": "^1.1.3" + } + }, + "ytdl-core-discord": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ytdl-core-discord/-/ytdl-core-discord-1.3.1.tgz", + "integrity": "sha512-KW8zYY35jRSkxZTEQtT9EiR2exFwYKhCE8QZbRg5Ge9a0YWDDhBOixSdWb8Cn41B1uHhz8FR15E4E/k0kHNX3w==", + "requires": { + "@types/node": "^15.12.2", + "prism-media": "^1.3.1", + "ytdl-core": "^4.8.2" + }, + "dependencies": { + "@types/node": { + "version": "15.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", + "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" + } + } } } } diff --git a/package.json b/package.json index 0e75638..811e5f3 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "author": "", "license": "ISC", "dependencies": { - "discord.js": "^13.3.1" + "discord.js": "^13.3.1", + "ytdl-core-discord": "^1.3.1" } }