diff --git a/.eslintrc.js b/.eslintrc.js index cff9b38..1479b1e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -23,5 +23,6 @@ module.exports = { ], 'no-console': 'off', 'prefer-arrow-callback': 'error' - } + }, + 'ignorePatterns': [ '**/*.js' ] }; diff --git a/.gitignore b/.gitignore index d6fbad3..15dc38b 100644 --- a/.gitignore +++ b/.gitignore @@ -72,4 +72,11 @@ ffmpeg/ # Editors and IDE's *.swp -.vscode/ +ts/*.js +ts/*.d.ts +examples/pnpm-lock.yaml +examples/capture/ +*.torrent +*.jpg +scratch/*.h264 +scratch/*.wav diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 404edc6..cacd253 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -21,6 +21,20 @@ "cStandard": "c11", "cppStandard": "c++17", "intelliSenseMode": "msvc-x64" + }, + { + "name": "Mac", + "includePath": [ + "${workspaceFolder}/src", + "${HOME}/.nvm/versions/node/v16.14.2/include/node", + "/opt/homebrew/Cellar/ffmpeg/5.0/include" + ], + "defines": [], + "macFrameworkPath": [], + "compilerPath": "/opt/homebrew/bin/gcc-11", + "cStandard": "gnu17", + "cppStandard": "gnu++17", + "intelliSenseMode": "macos-gcc-arm64" } ], "version": 4 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..b9eaa7b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,279 @@ +{ + "version": "0.2.0", + "configurations": [ + + + + { + "name": "scratch streamTest.ts", + "type": "node", + "request": "launch", + "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], + "args": [ "./examples/streamTest.ts" ], + "cwd": "${workspaceFolder}/", + "internalConsoleOptions": "openOnSessionStart", + // "skipFiles": ["/**", "node_modules/**"], + "skipFiles": [ + "/s//**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + + + + + { + "name": "scratch decode_aac", + "type": "node", + "request": "launch", + "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], + "args": [ "./decode_aac.ts" ], + "cwd": "${workspaceFolder}/scratch/", + "internalConsoleOptions": "openOnSessionStart", + // "skipFiles": ["/**", "node_modules/**"], + "skipFiles": [ + "/s//**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + + + { + "name": "scratch decode_avci", + "type": "node", + "request": "launch", + "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], + "args": [ "./decode_avci.ts" ], + "cwd": "${workspaceFolder}/scratch/", + "internalConsoleOptions": "openOnSessionStart", + // "skipFiles": ["/**", "node_modules/**"], + "skipFiles": [ + "/s//**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + + + { + "name": "scratch decode_hevc", + "type": "node", + "request": "launch", + "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], + "args": [ "./decode_hevc.ts" ], + "cwd": "${workspaceFolder}/scratch/", + "internalConsoleOptions": "openOnSessionStart", + // "skipFiles": ["/**", "node_modules/**"], + "skipFiles": [ + "/s//**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + + + + { + "name": "scratch decode_pcm", + "type": "node", + "request": "launch", + "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], + "args": [ "./decode_pcm.ts" ], + "cwd": "${workspaceFolder}/scratch/", + "internalConsoleOptions": "openOnSessionStart", + // "skipFiles": ["/**", "node_modules/**"], + "skipFiles": [ + "/s//**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + + + + + + + + { + "name": "scratch stream_avci", + "type": "node", + "request": "launch", + "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], + "args": [ "./stream_avci.ts" ], + "cwd": "${workspaceFolder}/scratch/", + "internalConsoleOptions": "openOnSessionStart", + // "skipFiles": ["/**", "node_modules/**"], + "skipFiles": [ + "/s//**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + + + { + "name": "scratch stream_mux", + "type": "node", + "request": "launch", + "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], + "args": [ "./stream_mux.ts" ], + "cwd": "${workspaceFolder}/scratch/", + "internalConsoleOptions": "openOnSessionStart", + // "skipFiles": ["/**", "node_modules/**"], + "skipFiles": [ + "/s//**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + + + { + "name": "scratch stream_mp4", + "type": "node", + "request": "launch", + "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], + "args": [ "./stream_mp4.ts" ], + "cwd": "${workspaceFolder}/scratch/", + "internalConsoleOptions": "openOnSessionStart", + // "skipFiles": ["/**", "node_modules/**"], + "skipFiles": [ + "/**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + { + "name": "Launch streamTest", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register" + ], + "args": [ + "./examples/streamTest.ts" + ], + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "openOnSessionStart", + // "skipFiles": ["/**", "node_modules/**"], + "skipFiles": [ + "/**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + { + "name": "Launch jpeg_filter_app", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register" + ], + "args": [ + "./examples/jpeg_filter_app.ts" + ], + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": [ + "/**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + { + "name": "Launch make_mp4", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register" + ], + "args": [ + "./examples/make_mp4.ts", + "test.mp4" + ], + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": [ + "/**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + { + "name": "Launch jpeg_app", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register" + ], + "args": [ + "./examples/jpeg_app.ts" + ], + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": [ + "/**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + { + "name": "tape test", + "type": "node", + "request": "launch", + "runtimeArgs": [ + "--nolazy", + "-r", + "ts-node/register" + ], + "args": [ + "node_modules/tape/bin/tape", + "test/*.ts" + ], + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "openOnSessionStart", + "skipFiles": [ + "/**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + } + ] +} \ No newline at end of file diff --git a/beamstreams.js b/beamstreams.js deleted file mode 100644 index 90e5163..0000000 --- a/beamstreams.js +++ /dev/null @@ -1,692 +0,0 @@ -/* - Aerostat Beam Coder - Node.js native bindings to FFmpeg - Copyright (C) 2019 Streampunk Media Ltd. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - https://www.streampunk.media/ mailto:furnace@streampunk.media - 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. -*/ - -const beamcoder = require('bindings')('beamcoder'); -const { Writable, Readable, Transform } = require('stream'); - -const doTimings = false; -const timings = []; - -function frameDicer(encoder, isAudio) { - let sampleBytes = 4; // Assume floating point 4 byte samples for now... - const numChannels = encoder.channels; - const dstNumSamples = encoder.frame_size; - let dstFrmBytes = dstNumSamples * sampleBytes; - const doDice = false === beamcoder.encoders()[encoder.name].capabilities.VARIABLE_FRAME_SIZE; - - let lastFrm = null; - let lastBuf = []; - const nullBuf = []; - for (let b = 0; b < numChannels; ++b) - nullBuf.push(Buffer.alloc(0)); - - const addFrame = srcFrm => { - let result = []; - let dstFrm; - let curStart = 0; - if (!lastFrm) { - lastFrm = beamcoder.frame(srcFrm.toJSON()); - lastBuf = nullBuf; - dstFrmBytes = dstNumSamples * sampleBytes; - } - - if (lastBuf[0].length > 0) - dstFrm = beamcoder.frame(lastFrm.toJSON()); - else - dstFrm = beamcoder.frame(srcFrm.toJSON()); - dstFrm.nb_samples = dstNumSamples; - dstFrm.pkt_duration = dstNumSamples; - - while (curStart + dstFrmBytes - lastBuf[0].length <= srcFrm.nb_samples * sampleBytes) { - const resFrm = beamcoder.frame(dstFrm.toJSON()); - resFrm.data = lastBuf.map((d, i) => - Buffer.concat([ - d, srcFrm.data[i].slice(curStart, curStart + dstFrmBytes - d.length)], - dstFrmBytes)); - result.push(resFrm); - - dstFrm.pts += dstNumSamples; - dstFrm.pkt_dts += dstNumSamples; - curStart += dstFrmBytes - lastBuf[0].length; - lastFrm.pts = 0; - lastFrm.pkt_dts = 0; - lastBuf = nullBuf; - } - - lastFrm.pts = dstFrm.pts; - lastFrm.pkt_dts = dstFrm.pkt_dts; - lastBuf = srcFrm.data.map(d => d.slice(curStart, srcFrm.nb_samples * sampleBytes)); - - return result; - }; - - const getLast = () => { - let result = []; - if (lastBuf[0].length > 0) { - const resFrm = beamcoder.frame(lastFrm.toJSON()); - resFrm.data = lastBuf.map(d => d.slice(0)); - resFrm.nb_samples = lastBuf[0].length / sampleBytes; - resFrm.pkt_duration = resFrm.nb_samples; - lastFrm.pts = 0; - lastBuf = nullBuf; - result.push(resFrm); - } - return result; - }; - - this.dice = (frames, flush = false) => { - if (isAudio && doDice) { - let result = frames.reduce((muxFrms, frm) => { - addFrame(frm).forEach(f => muxFrms.push(f)); - return muxFrms; - }, []); - - if (flush) - getLast().forEach(f => result.push(f)); - - return result; - } - - return frames; - }; -} - -function serialBalancer(numStreams) { - let pending = []; - // initialise with negative ts and no pkt - // - there should be no output until each stream has sent its first packet - for (let s = 0; s < numStreams; ++s) - pending.push({ ts: -Number.MAX_VALUE, streamIndex: s }); - - const adjustTS = (pkt, srcTB, dstTB) => { - const adj = (srcTB[0] * dstTB[1]) / (srcTB[1] * dstTB[0]); - pkt.pts = Math.round(pkt.pts * adj); - pkt.dts = Math.round(pkt.dts * adj); - pkt.duration > 0 ? Math.round(pkt.duration * adj) : Math.round(adj); - }; - - const pullPkts = (pkt, streamIndex, ts) => { - return new Promise(resolve => { - Object.assign(pending[streamIndex], { pkt: pkt, ts: ts, resolve: resolve }); - const minTS = pending.reduce((acc, pend) => Math.min(acc, pend.ts), Number.MAX_VALUE); - // console.log(streamIndex, pending.map(p => p.ts), minTS); - const nextPend = pending.find(pend => pend.pkt && (pend.ts === minTS)); - if (nextPend) nextPend.resolve(nextPend.pkt); - if (!pkt) resolve(); - }); - }; - - this.writePkts = (packets, srcStream, dstStream, writeFn, final = false) => { - if (packets && packets.packets.length) { - return packets.packets.reduce(async (promise, pkt) => { - await promise; - pkt.stream_index = dstStream.index; - adjustTS(pkt, srcStream.time_base, dstStream.time_base); - const pktTS = pkt.pts * dstStream.time_base[0] / dstStream.time_base[1]; - return writeFn(await pullPkts(pkt, dstStream.index, pktTS)); - }, Promise.resolve()); - } else if (final) - return pullPkts(null, dstStream.index, Number.MAX_VALUE); - }; -} - -function parallelBalancer(params, streamType, numStreams) { - let resolveGet = null; - const tag = 'video' === streamType ? 'v' : 'a'; - const pending = []; - // initialise with negative ts and no pkt - // - there should be no output until each stream has sent its first packet - for (let s = 0; s < numStreams; ++s) - pending.push({ ts: -Number.MAX_VALUE, streamIndex: s }); - - const makeSet = resolve => { - if (resolve) { - // console.log('makeSet', pending.map(p => p.ts)); - const nextPends = pending.every(pend => pend.pkt) ? pending : null; - const final = pending.filter(pend => true === pend.final); - if (nextPends) { - nextPends.forEach(pend => pend.resolve()); - resolve({ - value: nextPends.map(pend => { - return { name: `in${pend.streamIndex}:${tag}`, frames: [ pend.pkt ] }; }), - done: false }); - resolveGet = null; - pending.forEach(pend => Object.assign(pend, { pkt: null, ts: Number.MAX_VALUE })); - } else if (final.length > 0) { - final.forEach(f => f.resolve()); - resolve({ done: true }); - } else { - resolveGet = resolve; - } - } - }; - - const pushPkt = async (pkt, streamIndex, ts) => - new Promise(resolve => { - Object.assign(pending[streamIndex], { pkt: pkt, ts: ts, final: pkt ? false : true, resolve: resolve }); - makeSet(resolveGet); - }); - - const pullSet = async () => new Promise(resolve => makeSet(resolve)); - - const readStream = new Readable({ - objectMode: true, - highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - read() { - (async () => { - const start = process.hrtime(); - const reqTime = start[0] * 1e3 + start[1] / 1e6; - const result = await pullSet(); - if (result.done) - this.push(null); - else { - result.value.timings = result.value[0].frames[0].timings; - result.value.timings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; - this.push(result.value); - } - })(); - }, - }); - - readStream.pushPkts = (packets, stream, streamIndex, final = false) => { - if (packets && packets.frames.length) { - return packets.frames.reduce(async (promise, pkt) => { - await promise; - const ts = pkt.pts * stream.time_base[0] / stream.time_base[1]; - pkt.timings = packets.timings; - return pushPkt(pkt, streamIndex, ts); - }, Promise.resolve()); - } else if (final) { - return pushPkt(null, streamIndex, Number.MAX_VALUE); - } - }; - - return readStream; -} - -function teeBalancer(params, numStreams) { - let resolvePush = null; - const pending = []; - for (let s = 0; s < numStreams; ++s) - pending.push({ frames: null, resolve: null, final: false }); - - const pullFrame = async index => { - return new Promise(resolve => { - if (pending[index].frames) { - resolve({ value: pending[index].frames, done: false }); - Object.assign(pending[index], { frames: null, resolve: null }); - } else if (pending[index].final) - resolve({ done: true }); - else - pending[index].resolve = resolve; - - if (resolvePush && pending.every(p => null === p.frames)) { - resolvePush(); - resolvePush = null; - } - }); - }; - - const readStreams = []; - for (let s = 0; s < numStreams; ++s) - readStreams.push(new Readable({ - objectMode: true, - highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - read() { - (async () => { - const start = process.hrtime(); - const reqTime = start[0] * 1e3 + start[1] / 1e6; - const result = await pullFrame(s); - if (result.done) - this.push(null); - else { - result.value.timings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; - this.push(result.value); - } - })(); - }, - })); - - readStreams.pushFrames = frames => { - return new Promise(resolve => { - pending.forEach((p, index) => { - if (frames.length) - p.frames = frames[index].frames; - else - p.final = true; - }); - - pending.forEach(p => { - if (p.resolve) { - if (p.frames) { - p.frames.timings = frames.timings; - p.resolve({ value: p.frames, done: false }); - } else if (p.final) - p.resolve({ done: true }); - } - Object.assign(p, { frames: null, resolve: null }); - }); - resolvePush = resolve; - }); - }; - - return readStreams; -} - -function transformStream(params, processFn, flushFn, reject) { - return new Transform({ - objectMode: true, - highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - transform(val, encoding, cb) { - (async () => { - const start = process.hrtime(); - const reqTime = start[0] * 1e3 + start[1] / 1e6; - const result = await processFn(val); - result.timings = val.timings; - if (result.timings) - result.timings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; - cb(null, result); - })().catch(cb); - }, - flush(cb) { - (async () => { - const result = flushFn ? await flushFn() : null; - if (result) result.timings = {}; - cb(null, result); - })().catch(cb); - } - }).on('error', err => reject(err)); -} - -const calcStats = (arr, elem, prop) => { - const mean = arr.reduce((acc, cur) => cur[elem] ? acc + cur[elem][prop] : acc, 0) / arr.length; - const stdDev = Math.pow(arr.reduce((acc, cur) => cur[elem] ? acc + Math.pow(cur[elem][prop] - mean, 2) : acc, 0) / arr.length, 0.5); - const max = arr.reduce((acc, cur) => cur[elem] ? Math.max(cur[elem][prop], acc) : acc, 0); - const min = arr.reduce((acc, cur) => cur[elem] ? Math.min(cur[elem][prop], acc) : acc, Number.MAX_VALUE); - return { mean: mean, stdDev: stdDev, max: max, min: min }; -}; - -function writeStream(params, processFn, finalFn, reject) { - return new Writable({ - objectMode: true, - highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - write(val, encoding, cb) { - (async () => { - const start = process.hrtime(); - const reqTime = start[0] * 1e3 + start[1] / 1e6; - const result = await processFn(val); - if ('mux' === params.name) { - const pktTimings = val.timings; - pktTimings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; - if (doTimings) - timings.push(pktTimings); - } - cb(null, result); - })().catch(cb); - }, - final(cb) { - (async () => { - const result = finalFn ? await finalFn() : null; - if (doTimings && ('mux' === params.name)) { - const elapsedStats = {}; - Object.keys(timings[0]).forEach(k => elapsedStats[k] = calcStats(timings.slice(10, -10), k, 'elapsed')); - console.log('elapsed:'); - console.table(elapsedStats); - - const absArr = timings.map(t => { - const absDelays = {}; - const keys = Object.keys(t); - keys.forEach((k, i) => absDelays[k] = { reqDelta: i > 0 ? t[k].reqTime - t[keys[i-1]].reqTime : 0 }); - return absDelays; - }); - const absStats = {}; - Object.keys(absArr[0]).forEach(k => absStats[k] = calcStats(absArr.slice(10, -10), k, 'reqDelta')); - console.log('request time delta:'); - console.table(absStats); - - const totalsArr = timings.map(t => { - const total = (t.mux && t.read) ? t.mux.reqTime - t.read.reqTime + t.mux.elapsed : 0; - return { total: { total: total }}; - }); - console.log('total time:'); - console.table(calcStats(totalsArr.slice(10, -10), 'total', 'total')); - } - cb(null, result); - })().catch(cb); - } - }).on('error', err => reject(err)); -} - -function readStream(params, demuxer, ms, index) { - const time_base = demuxer.streams[index].time_base; - const end_pts = ms ? ms.end * time_base[1] / time_base[0] : Number.MAX_SAFE_INTEGER; - async function getPacket() { - let packet = null; - do { packet = await demuxer.read(); } - while (packet && packet.stream_index !== index); - return packet; - } - - return new Readable({ - objectMode: true, - highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - read() { - (async () => { - const start = process.hrtime(); - const reqTime = start[0] * 1e3 + start[1] / 1e6; - const packet = await getPacket(); - if (packet && (packet.pts < end_pts)) { - packet.timings = {}; - packet.timings.read = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; - this.push(packet); - } else - this.push(null); - })(); - } - }); -} - -function createBeamWritableStream(params, governor) { - const beamStream = new Writable({ - highWaterMark: params.highwaterMark || 16384, - write: (chunk, encoding, cb) => { - (async () => { - await governor.write(chunk); - cb(); - })(); - } - }); - return beamStream; -} - -function demuxerStream(params) { - const governor = new beamcoder.governor({}); - const stream = createBeamWritableStream(params, governor); - stream.on('finish', () => governor.finish()); - stream.on('error', console.error); - stream.demuxer = options => { - options.governor = governor; - // delay initialisation of demuxer until stream has been written to - avoids lock-up - return new Promise(resolve => setTimeout(async () => resolve(await beamcoder.demuxer(options)), 20)); - }; - return stream; -} - -function createBeamReadableStream(params, governor) { - const beamStream = new Readable({ - highWaterMark: params.highwaterMark || 16384, - read: size => { - (async () => { - const chunk = await governor.read(size); - if (0 === chunk.length) - beamStream.push(null); - else - beamStream.push(chunk); - })(); - } - }); - return beamStream; -} - -function muxerStream(params) { - const governor = new beamcoder.governor({ highWaterMark: 1 }); - const stream = createBeamReadableStream(params, governor); - stream.on('end', () => governor.finish()); - stream.on('error', console.error); - stream.muxer = options => { - options.governor = governor; - return beamcoder.muxer(options); - }; - return stream; -} - -async function makeSources(params) { - if (!params.video) params.video = []; - if (!params.audio) params.audio = []; - - params.video.forEach(p => p.sources.forEach(src => { - if (src.input_stream) { - const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 }); - src.input_stream.pipe(demuxerStream); - src.format = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); - } else - src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options }); - })); - params.audio.forEach(p => p.sources.forEach(src => { - if (src.input_stream) { - const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 }); - src.input_stream.pipe(demuxerStream); - src.format = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); - } else - src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options }); - })); - - await params.video.reduce(async (promise, p) => { - await promise; - return p.sources.reduce(async (promise, src) => { - await promise; - src.format = await src.format; - if (src.ms && !src.input_stream) - src.format.seek({ time: src.ms.start }); - return src.format; - }, Promise.resolve()); - }, Promise.resolve()); - await params.audio.reduce(async (promise, p) => { - await promise; - return p.sources.reduce(async (promise, src) => { - await promise; - src.format = await src.format; - if (src.ms && !src.input_stream) - src.format.seek({ time: src.ms.start }); - return src.format; - }, Promise.resolve()); - }, Promise.resolve()); - - params.video.forEach(p => p.sources.forEach(src => - src.stream = readStream({ highWaterMark : 1 }, src.format, src.ms, src.streamIndex))); - params.audio.forEach(p => p.sources.forEach(src => - src.stream = readStream({ highWaterMark : 1 }, src.format, src.ms, src.streamIndex))); -} - -function runStreams(streamType, sources, filterer, streams, mux, muxBalancer) { - return new Promise((resolve, reject) => { - if (!sources.length) - return resolve(); - - const timeBaseStream = sources[0].format.streams[sources[0].streamIndex]; - const filterBalancer = parallelBalancer({ name: 'filterBalance', highWaterMark : 1 }, streamType, sources.length); - - sources.forEach((src, srcIndex) => { - const decStream = transformStream({ name: 'decode', highWaterMark : 1 }, - pkts => src.decoder.decode(pkts), () => src.decoder.flush(), reject); - const filterSource = writeStream({ name: 'filterSource', highWaterMark : 1 }, - pkts => filterBalancer.pushPkts(pkts, src.format.streams[src.streamIndex], srcIndex), - () => filterBalancer.pushPkts(null, src.format.streams[src.streamIndex], srcIndex, true), reject); - - src.stream.pipe(decStream).pipe(filterSource); - }); - - const streamTee = teeBalancer({ name: 'streamTee', highWaterMark : 1 }, streams.length); - const filtStream = transformStream({ name: 'filter', highWaterMark : 1 }, frms => { - if (filterer.cb) filterer.cb(frms[0].frames[0].pts); - return filterer.filter(frms); - }, () => {}, reject); - const streamSource = writeStream({ name: 'streamSource', highWaterMark : 1 }, - frms => streamTee.pushFrames(frms), () => streamTee.pushFrames([], true), reject); - - filterBalancer.pipe(filtStream).pipe(streamSource); - - streams.forEach((str, i) => { - const dicer = new frameDicer(str.encoder, 'audio' === streamType); - const diceStream = transformStream({ name: 'dice', highWaterMark : 1 }, - frms => dicer.dice(frms), () => dicer.dice([], true), reject); - const encStream = transformStream({ name: 'encode', highWaterMark : 1 }, - frms => str.encoder.encode(frms), () => str.encoder.flush(), reject); - const muxStream = writeStream({ name: 'mux', highWaterMark : 1 }, - pkts => muxBalancer.writePkts(pkts, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts)), - () => muxBalancer.writePkts(null, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts), true), reject); - muxStream.on('finish', resolve); - - streamTee[i].pipe(diceStream).pipe(encStream).pipe(muxStream); - }); - }); -} - -async function makeStreams(params) { - params.video.forEach(p => { - p.sources.forEach(src => - src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); - }); - params.audio.forEach(p => { - p.sources.forEach(src => - src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); - }); - - params.video.forEach(p => { - p.filter = beamcoder.filterer({ - filterType: 'video', - inputParams: p.sources.map((src, i) => { - const stream = src.format.streams[src.streamIndex]; - return { - name: `in${i}:v`, - width: stream.codecpar.width, - height: stream.codecpar.height, - pixelFormat: stream.codecpar.format, - timeBase: stream.time_base, - pixelAspect: stream.sample_aspect_ratio }; - }), - outputParams: p.streams.map((str, i) => { return { name: `out${i}:v`, pixelFormat: str.codecpar.format }; }), - filterSpec: p.filterSpec }); - }); - const vidFilts = await Promise.all(params.video.map(p => p.filter)); - params.video.forEach((p, i) => p.filter = vidFilts[i]); - // params.video.forEach(p => console.log(p.filter.graph.dump())); - - params.audio.forEach(p => { - p.filter = beamcoder.filterer({ - filterType: 'audio', - inputParams: p.sources.map((src, i) => { - const stream = src.format.streams[src.streamIndex]; - return { - name: `in${i}:a`, - sampleRate: src.decoder.sample_rate, - sampleFormat: src.decoder.sample_fmt, - channelLayout: src.decoder.channel_layout, - timeBase: stream.time_base }; - }), - outputParams: p.streams.map((str, i) => { - return { - name: `out${i}:a`, - sampleRate: str.codecpar.sample_rate, - sampleFormat: str.codecpar.format, - channelLayout: str.codecpar.channel_layout }; }), - filterSpec: p.filterSpec }); - }); - const audFilts = await Promise.all(params.audio.map(p => p.filter)); - params.audio.forEach((p, i) => p.filter = audFilts[i]); - // params.audio.forEach(p => console.log(p.filter.graph.dump())); - - let mux; - if (params.out.output_stream) { - let muxerStream = beamcoder.muxerStream({ highwaterMark: 1024 }); - muxerStream.pipe(params.out.output_stream); - mux = muxerStream.muxer({ format_name: params.out.formatName }); - } else - mux = beamcoder.muxer({ format_name: params.out.formatName }); - - params.video.forEach(p => { - p.streams.forEach((str, i) => { - const encParams = p.filter.graph.filters.find(f => f.name === `out${i}:v`).inputs[0]; - str.encoder = beamcoder.encoder({ - name: str.name, - width: encParams.w, - height: encParams.h, - pix_fmt: encParams.format, - sample_aspect_ratio: encParams.sample_aspect_ratio, - time_base: encParams.time_base, - // framerate: [encParams.time_base[1], encParams.time_base[0]], - // bit_rate: 2000000, - // gop_size: 10, - // max_b_frames: 1, - // priv_data: { preset: 'slow' } - priv_data: { crf: 23 } }); // ... more required ... - }); - }); - - params.audio.forEach(p => { - p.streams.forEach((str, i) => { - const encParams = p.filter.graph.filters.find(f => f.name === `out${i}:a`).inputs[0]; - str.encoder = beamcoder.encoder({ - name: str.name, - sample_fmt: encParams.format, - sample_rate: encParams.sample_rate, - channel_layout: encParams.channel_layout, - flags: { GLOBAL_HEADER: mux.oformat.flags.GLOBALHEADER } }); - - str.codecpar.frame_size = str.encoder.frame_size; - }); - }); - - params.video.forEach(p => { - p.streams.forEach(str => { - str.stream = mux.newStream({ - name: str.name, - time_base: str.time_base, - interleaved: true }); // Set to false for manual interleaving, true for automatic - Object.assign(str.stream.codecpar, str.codecpar); - }); - }); - - params.audio.forEach(p => { - p.streams.forEach(str => { - str.stream = mux.newStream({ - name: str.name, - time_base: str.time_base, - interleaved: true }); // Set to false for manual interleaving, true for automatic - Object.assign(str.stream.codecpar, str.codecpar); - }); - }); - - return { - run: async () => { - await mux.openIO({ - url: params.out.url ? params.out.url : '', - flags: params.out.flags ? params.out.flags : {} - }); - await mux.writeHeader({ options: params.out.options ? params.out.options : {} }); - - const muxBalancer = new serialBalancer(mux.streams.length); - const muxStreamPromises = []; - params.video.forEach(p => muxStreamPromises.push(runStreams('video', p.sources, p.filter, p.streams, mux, muxBalancer))); - params.audio.forEach(p => muxStreamPromises.push(runStreams('audio', p.sources, p.filter, p.streams, mux, muxBalancer))); - await Promise.all(muxStreamPromises); - - await mux.writeTrailer(); - } - }; -} - -module.exports = { - demuxerStream, - muxerStream, - makeSources, - makeStreams -}; diff --git a/examples/encode_h264.js b/examples/encode_h264.ts similarity index 92% rename from examples/encode_h264.js rename to examples/encode_h264.ts index 8041baf..dce52f3 100644 --- a/examples/encode_h264.js +++ b/examples/encode_h264.ts @@ -26,8 +26,8 @@ Output can be viewed in VLC. Make sure "All Files" is selected to see the file. */ -const beamcoder = require('../index.js'); // Use require('beamcoder') externally -const fs = require('fs'); +import beamcoder from '..'; // Use require('beamcoder') externally +import fs from 'fs'; let endcode = Buffer.from([0, 0, 1, 0xb7]); @@ -38,13 +38,13 @@ async function run() { width: 1920, height: 1080, bit_rate: 2000000, - time_base: [1, 25], - framerate: [25, 1], + time_base: [1, 25] as [number, number], + framerate: [25, 1] as [number, number], gop_size: 10, max_b_frames: 1, pix_fmt: 'yuv420p', - priv_data: { preset: 'slow' } - }; + priv_data: { preset: 'slow' }, + } as const; let encoder = beamcoder.encoder(encParams); console.log('Encoder', encoder); diff --git a/examples/jpeg_app.js b/examples/jpeg_app.js deleted file mode 100644 index 1768ce2..0000000 --- a/examples/jpeg_app.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - Aerostat Beam Coder - Node.js native bindings for FFmpeg. - Copyright (C) 2019 Streampunk Media Ltd. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - https://www.streampunk.media/ mailto:furnace@streampunk.media - 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. -*/ - -/* Main example from the README.md. Run in a folder of media files. - - Only supports 8-bit YUV 4:2:2 or 4:2:0 pixel formats. -*/ - -const beamcoder = require('../index.js'); // Use require('beamcoder') externally -const Koa = require('koa'); // Add koa to package.json dependencies -const app = new Koa(); - -app.use(async (ctx) => { // Assume HTTP GET with path // - let parts = ctx.path.split('/'); // Split the path into filename and time - if ((parts.length < 3) || (isNaN(+parts[2]))) return; // Ignore favicon etc.. - let dm = await beamcoder.demuxer('file:' + parts[1]); // Probe the file - await dm.seek({ time: +parts[2] }); // Seek to the closest keyframe to time - let packet = await dm.read(); // Find the next video packet (assumes stream 0) - for ( ; packet.stream_index !== 0 ; packet = await dm.read() ); - let dec = beamcoder.decoder({ demuxer: dm, stream_index: 0 }); // Create a decoder - let decResult = await dec.decode(packet); // Decode the frame - if (decResult.frames.length === 0) // Frame may be buffered, so flush it out - decResult = await dec.flush(); - // Filtering could be used to transform the picture here, e.g. scaling - let enc = beamcoder.encoder({ // Create an encoder for JPEG data - name : 'mjpeg', // FFmpeg does not have an encoder called 'jpeg' - width : dec.width, - height: dec.height, - pix_fmt: dec.pix_fmt.indexOf('422') >= 0 ? 'yuvj422p' : 'yuvj420p', - time_base: [1, 1] }); - let jpegResult = await enc.encode(decResult.frames[0]); // Encode the frame - await enc.flush(); // Tidy the encoder - ctx.type = 'image/jpeg'; // Set the Content-Type of the data - ctx.body = jpegResult.packets[0].data; // Return the JPEG image data -}); - -app.listen(3000); // Start the server on port 3000 diff --git a/examples/jpeg_app.ts b/examples/jpeg_app.ts new file mode 100644 index 0000000..84dcbca --- /dev/null +++ b/examples/jpeg_app.ts @@ -0,0 +1,85 @@ +/* + Aerostat Beam Coder - Node.js native bindings for FFmpeg. + Copyright (C) 2019 Streampunk Media Ltd. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ + +/* Main example from the README.md. Run in a folder of media files. + + Only supports 8-bit YUV 4:2:2 or 4:2:0 pixel formats. +*/ + +import beamcoder from '..'; // Use require('beamcoder') externally +import Koa from 'koa'; // Add koa to package.json dependencies +import fs from 'fs'; +import path from 'path'; +const app = new Koa(); + +app.use(async (ctx) => { // Assume HTTP GET with path // + + if (ctx.path === '/') { + let list = await fs.promises.readdir('.') + list = list.filter(a=> a.toLowerCase().endsWith('.mp4')); + ctx.type = 'text/html'; + ctx.body = ` + currenty available files:
    ${list.map(f => `
  • ${f}
  • `).join(' ')}
+ Add files in : ${path.resolve('.')} for more tests + `; + return; + } + + let parts = ctx.path.split('/'); // Split the path into filename and time + if ((parts.length < 3) || (isNaN(+parts[2]))) { + ctx.status = 404; + ctx.type = 'text/html'; + ctx.body = ` + expected path: // + `; + return; // Ignore favicon etc.. + } + + try { + if ((parts.length < 3) || (isNaN(+parts[2]))) return; // Ignore favicon etc.. + let demuxer = await beamcoder.demuxer('file:' + parts[1]); // Probe the file + await demuxer.seek({ time: +parts[2] }); // Seek to the closest keyframe to time + let packet = await demuxer.read(); // Find the next video packet (assumes stream 0) + for ( ; packet.stream_index !== 0 ; packet = await demuxer.read() ); + let dec = beamcoder.decoder({ demuxer, stream_index: 0 }); // Create a decoder + let decResult = await dec.decode(packet); // Decode the frame + if (decResult.frames.length === 0) // Frame may be buffered, so flush it out + decResult = await dec.flush(); + // Filtering could be used to transform the picture here, e.g. scaling + let enc = beamcoder.encoder({ // Create an encoder for JPEG data + name : 'mjpeg', // FFmpeg does not have an encoder called 'jpeg' + width : dec.width, + height: dec.height, + pix_fmt: dec.pix_fmt.indexOf('422') >= 0 ? 'yuvj422p' : 'yuvj420p', + time_base: [1, 1] }); + let jpegResult = await enc.encode(decResult.frames[0]); // Encode the frame + await enc.flush(); // Tidy the encoder + ctx.type = 'image/jpeg'; // Set the Content-Type of the data + ctx.body = jpegResult.packets[0].data; // Return the JPEG image data + } catch (e) { + ctx.type = 'application/json'; + ctx.status = 500; + ctx.body = JSON.stringify(e); + } +}); + +app.listen(3000); // Start the server on port 3000 +console.log('listening port 3000'); diff --git a/examples/jpeg_filter_app.js b/examples/jpeg_filter_app.ts similarity index 72% rename from examples/jpeg_filter_app.js rename to examples/jpeg_filter_app.ts index e9d45f3..3d66960 100644 --- a/examples/jpeg_filter_app.js +++ b/examples/jpeg_filter_app.ts @@ -24,12 +24,13 @@ Will convert source pixel formats to 8-bit YUV 4:2:2 */ -const beamcoder = require('../index.js'); // Use require('beamcoder') externally -const Koa = require('koa'); // Add koa to package.json dependencies +import beamcoder from '..'; // Use require('beamcoder') externally +import Koa from 'koa'; // Add koa to package.json dependencies const app = new Koa(); app.use(async (ctx) => { // Assume HTTP GET with path // - let parts = ctx.path.split('/'); // Split the path into filename and time + try { + let parts = ctx.path.split('/'); // Split the path into filename and time if ((parts.length < 3) || (isNaN(+parts[2]))) return; // Ignore favicon etc.. let dm = await beamcoder.demuxer('file:' + parts[1]); // Probe the file await dm.seek({ time: +parts[2] }); // Seek to the closest keyframe to time @@ -41,10 +42,10 @@ app.use(async (ctx) => { // Assume HTTP GET with path // decResult = await dec.flush(); // audio test - const aindex = 2; - const audStr = dm.streams[aindex]; + const streamSoundIdx = dm.streams.findIndex(stream => stream.codecpar.codec_type === 'audio'); + const audStr = dm.streams[streamSoundIdx]; // console.log(audStr); - let adec = beamcoder.decoder({ demuxer: dm, stream_index: aindex }); // Create a decoder + let adec = beamcoder.decoder({ demuxer: dm, stream_index: streamSoundIdx }); // Create a decoder // console.log(adec); let apkt = await dm.read(); let afrm = await adec.decode(apkt); @@ -54,26 +55,29 @@ app.use(async (ctx) => { // Assume HTTP GET with path // sample_fmt: 'fltp', sample_rate: 48000, channels: 1, - channel_layout: 'mono', }); - + channel_layout: 'mono' + }); const audFilt = await beamcoder.filterer({ // Create a filterer for audio filterType: 'audio', inputParams: [{ sampleRate: audStr.codecpar.sample_rate, sampleFormat: adec.sample_fmt, channelLayout: audStr.codecpar.channel_layout, - timeBase: audStr.time_base }], + timeBase: audStr.time_base + }], outputParams: [{ sampleRate: 1024, sampleFormat: 'fltp', - channelLayout: 'mono' }], - filterSpec: 'aresample=1024' }); - - const audFiltPkt = await audFilt.filter([{ frames: afrm }]); + channelLayout: 'mono' + }], + filterSpec: 'aresample=1024' + }); + const audFiltPkt = await audFilt.filter([{ frames: afrm.frames }]); const encPkt = await audEnc.encode(audFiltPkt[0].frames[0]); console.log(encPkt); - - let vstr = dm.streams[0]; // Select the video stream (assumes stream 0) + // find video stream + const streamVideoIdx = dm.streams.findIndex(stream => stream.codecpar.codec_type === 'video'); + let vstr = dm.streams[streamVideoIdx]; // Select the video stream let filt = await beamcoder.filterer({ // Create a filterer for video filterType: 'video', inputParams: [{ @@ -81,21 +85,30 @@ app.use(async (ctx) => { // Assume HTTP GET with path // height: vstr.codecpar.height, pixelFormat: vstr.codecpar.format, timeBase: vstr.time_base, - pixelAspect: vstr.sample_aspect_ratio }], + pixelAspect: vstr.sample_aspect_ratio + }], outputParams: [{ pixelFormat: 'yuv422p' }], - filterSpec: 'scale=640:360, colorspace=range=jpeg:all=bt709' }); - let filtResult = await filt.filter([{ frames: decResult }]); // Filter the frame + filterSpec: 'scale=640:360, colorspace=range=jpeg:all=bt709' + }); + let filtResult = await filt.filter([{ frames: decResult.frames }]); // Filter the frame let filtFrame = filtResult[0].frames[0]; let enc = beamcoder.encoder({ // Create an encoder for JPEG data - name : 'mjpeg', // FFmpeg does not have an encoder called 'jpeg' - width : filtFrame.width, + name: 'mjpeg', // FFmpeg does not have an encoder called 'jpeg' + width: filtFrame.width, height: filtFrame.height, pix_fmt: 'yuvj422p', - time_base: [1, 1] }); + time_base: [1, 1] + }); let jpegResult = await enc.encode(filtFrame); // Encode the filtered frame await enc.flush(); // Tidy the encoder ctx.type = 'image/jpeg'; // Set the Content-Type of the data ctx.body = jpegResult.packets[0].data; // Return the JPEG image data +} catch (err) { + ctx.status = err.statusCode || err.status || 500; + ctx.body = { + message: err.message + }; +} }); app.listen(3000); // Start the server on port 3000 diff --git a/examples/make_mp4.js b/examples/make_mp4.ts similarity index 94% rename from examples/make_mp4.js rename to examples/make_mp4.ts index eaddbc9..c9bdffd 100644 --- a/examples/make_mp4.js +++ b/examples/make_mp4.ts @@ -26,8 +26,8 @@ Output can be viewed in VLC. Make sure "All Files" is selected to see the file. */ -const beamcoder = require('../index.js'); // Use require('beamcoder') externally -const fs = require('fs'); +import beamcoder from '..'; // Use require('beamcoder') externally +import fs from 'fs'; let endcode = Buffer.from([0, 0, 1, 0xb7]); @@ -38,8 +38,8 @@ async function run() { width: 1920, height: 1080, bit_rate: 2000000, - time_base: [1, 25], - framerate: [25, 1], + time_base: [1, 25] as [number, number], + framerate: [25, 1] as [number, number], gop_size: 10, max_b_frames: 1, pix_fmt: 'yuv420p', @@ -76,7 +76,7 @@ async function run() { let linesize = frame.linesize; let [ ydata, bdata, cdata ] = frame.data; - frame.pts = i+100; + frame.pts = i + 100; for ( let y = 0 ; y < frame.height ; y++ ) { for ( let x = 0 ; x < linesize[0] ; x++ ) { diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 0000000..5709ee9 --- /dev/null +++ b/examples/package.json @@ -0,0 +1,17 @@ +{ + "name": "examples", + "version": "1.0.0", + "description": "", + "main": "jpeg_filter_app.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "koa": "^2.13.4" + }, + "devDependencies": { + "@types/koa": "^2.13.4" + } +} diff --git a/examples/pnpm-lock.yaml b/examples/pnpm-lock.yaml new file mode 100644 index 0000000..b7be1c2 --- /dev/null +++ b/examples/pnpm-lock.yaml @@ -0,0 +1,385 @@ +lockfileVersion: 5.3 + +specifiers: + '@types/koa': ^2.13.4 + koa: ^2.13.4 + +dependencies: + koa: 2.13.4 + +devDependencies: + '@types/koa': 2.13.4 + +packages: + + /@types/accepts/1.3.5: + resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==} + dependencies: + '@types/node': 17.0.31 + dev: true + + /@types/body-parser/1.19.2: + resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} + dependencies: + '@types/connect': 3.4.35 + '@types/node': 17.0.31 + dev: true + + /@types/connect/3.4.35: + resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} + dependencies: + '@types/node': 17.0.31 + dev: true + + /@types/content-disposition/0.5.4: + resolution: {integrity: sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==} + dev: true + + /@types/cookies/0.7.7: + resolution: {integrity: sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==} + dependencies: + '@types/connect': 3.4.35 + '@types/express': 4.17.13 + '@types/keygrip': 1.0.2 + '@types/node': 17.0.31 + dev: true + + /@types/express-serve-static-core/4.17.28: + resolution: {integrity: sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==} + dependencies: + '@types/node': 17.0.31 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 + dev: true + + /@types/express/4.17.13: + resolution: {integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==} + dependencies: + '@types/body-parser': 1.19.2 + '@types/express-serve-static-core': 4.17.28 + '@types/qs': 6.9.7 + '@types/serve-static': 1.13.10 + dev: true + + /@types/http-assert/1.5.3: + resolution: {integrity: sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==} + dev: true + + /@types/http-errors/1.8.2: + resolution: {integrity: sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==} + dev: true + + /@types/keygrip/1.0.2: + resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==} + dev: true + + /@types/koa-compose/3.2.5: + resolution: {integrity: sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==} + dependencies: + '@types/koa': 2.13.4 + dev: true + + /@types/koa/2.13.4: + resolution: {integrity: sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==} + dependencies: + '@types/accepts': 1.3.5 + '@types/content-disposition': 0.5.4 + '@types/cookies': 0.7.7 + '@types/http-assert': 1.5.3 + '@types/http-errors': 1.8.2 + '@types/keygrip': 1.0.2 + '@types/koa-compose': 3.2.5 + '@types/node': 17.0.31 + dev: true + + /@types/mime/1.3.2: + resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} + dev: true + + /@types/node/17.0.31: + resolution: {integrity: sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==} + dev: true + + /@types/qs/6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + dev: true + + /@types/range-parser/1.2.4: + resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} + dev: true + + /@types/serve-static/1.13.10: + resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==} + dependencies: + '@types/mime': 1.3.2 + '@types/node': 17.0.31 + dev: true + + /accepts/1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + + /cache-content-type/1.0.1: + resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} + engines: {node: '>= 6.0.0'} + dependencies: + mime-types: 2.1.35 + ylru: 1.3.2 + dev: false + + /co/4.6.0: + resolution: {integrity: sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + dev: false + + /content-disposition/0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type/1.0.4: + resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + engines: {node: '>= 0.6'} + dev: false + + /cookies/0.8.0: + resolution: {integrity: sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + keygrip: 1.1.0 + dev: false + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: false + + /deep-equal/1.0.1: + resolution: {integrity: sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=} + dev: false + + /delegates/1.0.0: + resolution: {integrity: sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=} + dev: false + + /depd/1.1.2: + resolution: {integrity: sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=} + engines: {node: '>= 0.6'} + dev: false + + /depd/2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /destroy/1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: false + + /ee-first/1.1.1: + resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} + dev: false + + /encodeurl/1.0.2: + resolution: {integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=} + engines: {node: '>= 0.8'} + dev: false + + /escape-html/1.0.3: + resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=} + dev: false + + /fresh/0.5.2: + resolution: {integrity: sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=} + engines: {node: '>= 0.6'} + dev: false + + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: false + + /has-tostringtag/1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: false + + /http-assert/1.5.0: + resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} + engines: {node: '>= 0.8'} + dependencies: + deep-equal: 1.0.1 + http-errors: 1.8.1 + dev: false + + /http-errors/1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + dev: false + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: false + + /is-generator-function/1.0.10: + resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: false + + /keygrip/1.1.0: + resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} + engines: {node: '>= 0.6'} + dependencies: + tsscmp: 1.0.6 + dev: false + + /koa-compose/4.1.0: + resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} + dev: false + + /koa-convert/2.0.0: + resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} + engines: {node: '>= 10'} + dependencies: + co: 4.6.0 + koa-compose: 4.1.0 + dev: false + + /koa/2.13.4: + resolution: {integrity: sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==} + engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} + dependencies: + accepts: 1.3.8 + cache-content-type: 1.0.1 + content-disposition: 0.5.4 + content-type: 1.0.4 + cookies: 0.8.0 + debug: 4.3.4 + delegates: 1.0.0 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + fresh: 0.5.2 + http-assert: 1.5.0 + http-errors: 1.8.1 + is-generator-function: 1.0.10 + koa-compose: 4.1.0 + koa-convert: 2.0.0 + on-finished: 2.4.1 + only: 0.0.2 + parseurl: 1.3.3 + statuses: 1.5.0 + type-is: 1.6.18 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /media-typer/0.3.0: + resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=} + engines: {node: '>= 0.6'} + dev: false + + /mime-db/1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types/2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: false + + /negotiator/0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + + /on-finished/2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + + /only/0.0.2: + resolution: {integrity: sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=} + dev: false + + /parseurl/1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: false + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: false + + /setprototypeof/1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + + /statuses/1.5.0: + resolution: {integrity: sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=} + engines: {node: '>= 0.6'} + dev: false + + /toidentifier/1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + + /tsscmp/1.0.6: + resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} + engines: {node: '>=0.6.x'} + dev: false + + /type-is/1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + + /vary/1.1.2: + resolution: {integrity: sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=} + engines: {node: '>= 0.8'} + dev: false + + /ylru/1.3.2: + resolution: {integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==} + engines: {node: '>= 4.0.0'} + dev: false diff --git a/examples/streamTest.ts b/examples/streamTest.ts new file mode 100644 index 0000000..b052122 --- /dev/null +++ b/examples/streamTest.ts @@ -0,0 +1,83 @@ +import beamcoder, { DemuxerStream } from '..'; // Use require('beamcoder') externally +import path from 'path'; +import fs from 'fs'; +import { Demuxer, getRaw } from '..'; +import md5File from 'md5-file'; + +const streamUrl = 'https://github.com/awslabs/amazon-kinesis-video-streams-producer-c/raw/master/samples/h264SampleFrames/'; +async function getFiles(): Promise { + const src = path.join(__dirname, 'capture', 'h264SampleFrames'); + if (!fs.existsSync(src)) { + fs.mkdirSync(src, { recursive: true }); + } + let filelist = (await fs.promises.readdir(src)).filter(f => f.endsWith('.h264')); + const toto: Promise[] = []; + if (filelist.length < 403) { + for (let i = 1; i < 404; i++) { + const fn = `frame-${i.toFixed().padStart(3, '0')}.h264`; + const url = `${streamUrl}${fn}`; + const dest = path.join(src, fn) + if (!fs.existsSync(dest)) { + let ws = fs.createWriteStream(dest); + toto.push(getRaw(ws, url).catch(async (err) => { + if (err.name === 'RedirectError') { + const redirectURL = err.message; + await getRaw(ws, redirectURL, fn); + } else throw err; + })) + } + } + await Promise.all(toto); + filelist = (await fs.promises.readdir(src)).filter(f => f.endsWith('.h264')); + } + filelist.sort(); + return filelist.map(f => path.join(src, f)); +} + +async function run() { + const filelist = await getFiles(); + const stream = new DemuxerStream({ highwaterMark: 3600 }); + const demuxPromise = stream.demuxer({}) + demuxPromise.then(async (demuxer: Demuxer) => { + let dec = beamcoder.decoder({ demuxer, stream_index: 0 }); // Create a decoder + let frameId = 0; + while (true) { + const packet = await demuxer.read(); + let decResult = await dec.decode(packet); // Decode the frame + if (decResult.frames.length === 0) // Frame may be buffered, so flush it out + decResult = await dec.flush(); + // Filtering could be used to transform the picture here, e.g. scaling + let enc = beamcoder.encoder({ // Create an encoder for JPEG data + name: 'mjpeg', // FFmpeg does not have an encoder called 'jpeg' + width: dec.width, + height: dec.height, + pix_fmt: dec.pix_fmt.indexOf('422') >= 0 ? 'yuvj422p' : 'yuvj420p', + time_base: [1, 1] + }); + let jpegResult = await enc.encode(decResult.frames[0]); // Encode the frame + await enc.flush(); // Tidy the encoder + frameId++; + const jpgDest = `capture${frameId % 10}.jpg`; + // if (frameId % 10 === 1) + fs.writeFileSync(jpgDest, jpegResult.packets[0].data); + const sumDest = await md5File(jpgDest); + if (frameId === 1) { + const expectedMd5Mac = '63a5031f882ad85a964441f61333240c'; + const expectedMd5PC = 'e16f49626e71b4be46a3211ed1d4e471'; + if (expectedMd5Mac !== sumDest && expectedMd5PC !== sumDest) { + console.error(`MD5 missmatch get ${sumDest}`) + } + } + console.log(`saving in stream img as ${jpgDest} pos: ${packet.pos} md5: ${sumDest}`); + } + demuxer.forceClose(); + }); + // https://github.com/awslabs/amazon-kinesis-video-streams-producer-c/raw/master/samples/h264SampleFrames/frame-001.h264 + for (const fullname of filelist) { + const buf = await fs.promises.readFile(fullname); + stream.write(buf); + } + stream.emit('finish'); +} + +run().catch(e => console.error(e)); diff --git a/index.js b/index.js deleted file mode 100644 index c9ee041..0000000 --- a/index.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - Aerostat Beam Coder - Node.js native bindings to FFmpeg - Copyright (C) 2019 Streampunk Media Ltd. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - https://www.streampunk.media/ mailto:furnace@streampunk.media - 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. -*/ - -const beamcoder = require('bindings')('beamcoder'); -const beamstreams = require('./beamstreams.js'); - -// Provide useful debug on segfault-related crash -const SegfaultHandler = require('segfault-handler'); -SegfaultHandler.registerHandler('crash.log'); - -const splash = `Aerostat Beam Coder Copyright (C) 2019 Streampunk Media Ltd -GPL v3.0 or later license. This program comes with ABSOLUTELY NO WARRANTY. -This is free software, and you are welcome to redistribute it -under certain conditions. Conditions and warranty at: -https://github.com/Streampunk/beamcoder/blob/master/LICENSE`; - -console.log(splash); -console.log('Using FFmpeg version', beamcoder.avVersionInfo()); - -beamcoder.demuxerStream = beamstreams.demuxerStream; -beamcoder.muxerStream = beamstreams.muxerStream; - -beamcoder.makeSources = beamstreams.makeSources; -beamcoder.makeStreams = beamstreams.makeStreams; - -module.exports = beamcoder; diff --git a/install_ffmpeg.js b/install_ffmpeg.js deleted file mode 100644 index 6e375c9..0000000 --- a/install_ffmpeg.js +++ /dev/null @@ -1,239 +0,0 @@ -/* - Aerostat Beam Coder - Node.js native bindings to FFmpeg. - Copyright (C) 2019 Streampunk Media Ltd. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - https://www.streampunk.media/ mailto:furnace@streampunk.media - 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. -*/ - -const os = require('os'); -const fs = require('fs'); -const util = require('util'); -const https = require('https'); -const cp = require('child_process'); -const [ mkdir, access, rename, execFile, exec ] = // eslint-disable-line - [ fs.mkdir, fs.access, fs.rename, cp.execFile, cp.exec ].map(util.promisify); - -async function get(ws, url, name) { - let received = 0; - let totalLength = 0; - return new Promise((comp, err) => { - https.get(url, res => { - if (res.statusCode === 301 || res.statusCode === 302) { - err({ name: 'RedirectError', message: res.headers.location }); - } else { - res.pipe(ws); - if (totalLength == 0) { - totalLength = +res.headers['content-length']; - } - res.on('end', () => { - process.stdout.write(`Downloaded 100% of '${name}'. Total length ${received} bytes.\n`); - comp(); - }); - res.on('error', err); - res.on('data', x => { - received += x.length; - process.stdout.write(`Downloaded ${received * 100/ totalLength | 0 }% of '${name}'.\r`); - }); - } - }).on('error', err); - }); -} - -async function getHTML(url, name) { - let received = 0; - let totalLength = 0; - return new Promise((resolve, reject) => { - https.get(url, res => { - const chunks = []; - if (totalLength == 0) { - totalLength = +res.headers['content-length']; - } - res.on('end', () => { - process.stdout.write(`Downloaded 100% of '${name}'. Total length ${received} bytes.\n`); - resolve(Buffer.concat(chunks)); - }); - res.on('error', reject); - res.on('data', (chunk) => { - chunks.push(chunk); - received += chunk.length; - process.stdout.write(`Downloaded ${received * 100/ totalLength | 0 }% of '${name}'.\r`); - }); - }).on('error', reject); - }); -} - -async function inflate(rs, folder, name) { - const unzip = require('unzipper'); - const directory = await unzip.Open.file(`${folder}/${name}.zip`); - const directoryName = directory.files[0].path; - return new Promise((comp, err) => { - console.log(`Unzipping '${folder}/${name}.zip'.`); - rs.pipe(unzip.Extract({ path: folder }).on('close', () => { - fs.rename(`./${folder}/${directoryName}`, `./${folder}/${name}`, () => { - console.log(`Unzipping of '${folder}/${name}.zip' completed.`); - comp(); - }); - })); - rs.on('error', err); - }); -} - -async function win32() { - console.log('Checking/Installing FFmpeg dependencies for Beam Coder on Windows.'); - - await mkdir('ffmpeg').catch(e => { - if (e.code === 'EEXIST') return; - else throw e; - }); - - const ffmpegFilename = 'ffmpeg-5.x-win64-shared'; - await access(`ffmpeg/${ffmpegFilename}`, fs.constants.R_OK).catch(async () => { - const html = await getHTML('https://github.com/BtbN/FFmpeg-Builds/wiki/Latest', 'latest autobuilds'); - const htmlStr = html.toString('utf-8'); - const autoPos = htmlStr.indexOf('

', autoPos); - const autoStr = htmlStr.substring(autoPos, endPos); - const sharedEndPos = autoStr.lastIndexOf('">win64-gpl-shared-5.'); - if (sharedEndPos === -1) - throw new Error('Failed to find latest v4.x autobuild from "https://github.com/BtbN/FFmpeg-Builds/wiki/Latest"'); - const startStr = '

= 12" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-consumer": "0.8.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/@eslint/eslintrc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", - "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz", + "integrity": "sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -58,10 +86,58 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "node_modules/@types/bindings": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@types/bindings/-/bindings-1.5.1.tgz", + "integrity": "sha512-8HzueDeoxGXdsJ0Ep7TOXHGN+woRTWa1bAds30r5we7PCC3P5zrSTRknePLn/KYAubgQv5t/1zkonnStHLCWOg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "17.0.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", + "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==", + "dev": true + }, + "node_modules/@types/tape": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@types/tape/-/tape-4.13.2.tgz", + "integrity": "sha512-V1ez/RtYRGN9cNYApw5xf27DpMkTB0033X6a2i3KUmKhSojBfbWN0i3EgZxboUG96WJLHLdOyZ01aiZwVW5aSA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -79,6 +155,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -119,6 +204,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -241,6 +332,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -322,6 +419,15 @@ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", "dev": true }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -429,12 +535,12 @@ } }, "node_modules/eslint": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.12.0.tgz", - "integrity": "sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz", + "integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.2.1", + "@eslint/eslintrc": "^1.2.2", "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", @@ -1204,6 +1310,24 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/md5-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz", + "integrity": "sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==", + "dev": true, + "bin": { + "md5-file": "cli.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1570,9 +1694,9 @@ } }, "node_modules/tape": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/tape/-/tape-5.5.2.tgz", - "integrity": "sha512-N9Ss672dFE3QlppiXGh2ieux8Ophau/HSAQguW5cXQworKxV0QvnZCYI35W1OYySTJk0OC9OPuS+0xNO6lhiTQ==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/tape/-/tape-5.5.3.tgz", + "integrity": "sha512-hPBJZBL9S7bH9vECg/KSM24slGYV589jJr4dmtiJrLD71AL66+8o4b9HdZazXZyvnilqA7eE8z5/flKiy0KsBg==", "dev": true, "dependencies": { "array.prototype.every": "^1.1.3", @@ -1587,7 +1711,7 @@ "has-dynamic-import": "^2.0.1", "inherits": "^2.0.4", "is-regex": "^1.1.4", - "minimist": "^1.2.5", + "minimist": "^1.2.6", "object-inspect": "^1.12.0", "object-is": "^1.1.5", "object-keys": "^1.1.1", @@ -1613,6 +1737,49 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, + "node_modules/ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -1637,6 +1804,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typescript": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -1667,6 +1847,12 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1747,13 +1933,37 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } } }, "dependencies": { + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, "@eslint/eslintrc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", - "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.2.tgz", + "integrity": "sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -1784,10 +1994,58 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@types/bindings": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@types/bindings/-/bindings-1.5.1.tgz", + "integrity": "sha512-8HzueDeoxGXdsJ0Ep7TOXHGN+woRTWa1bAds30r5we7PCC3P5zrSTRknePLn/KYAubgQv5t/1zkonnStHLCWOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "17.0.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.31.tgz", + "integrity": "sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==", + "dev": true + }, + "@types/tape": { + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/@types/tape/-/tape-4.13.2.tgz", + "integrity": "sha512-V1ez/RtYRGN9cNYApw5xf27DpMkTB0033X6a2i3KUmKhSojBfbWN0i3EgZxboUG96WJLHLdOyZ01aiZwVW5aSA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", "dev": true }, "acorn-jsx": { @@ -1797,6 +2055,12 @@ "dev": true, "requires": {} }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1824,6 +2088,12 @@ "color-convert": "^2.0.1" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1919,6 +2189,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1983,6 +2259,12 @@ "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", "dev": true }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2063,12 +2345,12 @@ "dev": true }, "eslint": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.12.0.tgz", - "integrity": "sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.14.0.tgz", + "integrity": "sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.2.1", + "@eslint/eslintrc": "^1.2.2", "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", @@ -2630,6 +2912,18 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "md5-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/md5-file/-/md5-file-5.0.0.tgz", + "integrity": "sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==", + "dev": true + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2899,9 +3193,9 @@ } }, "tape": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/tape/-/tape-5.5.2.tgz", - "integrity": "sha512-N9Ss672dFE3QlppiXGh2ieux8Ophau/HSAQguW5cXQworKxV0QvnZCYI35W1OYySTJk0OC9OPuS+0xNO6lhiTQ==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/tape/-/tape-5.5.3.tgz", + "integrity": "sha512-hPBJZBL9S7bH9vECg/KSM24slGYV589jJr4dmtiJrLD71AL66+8o4b9HdZazXZyvnilqA7eE8z5/flKiy0KsBg==", "dev": true, "requires": { "array.prototype.every": "^1.1.3", @@ -2916,7 +3210,7 @@ "has-dynamic-import": "^2.0.1", "inherits": "^2.0.4", "is-regex": "^1.1.4", - "minimist": "^1.2.5", + "minimist": "^1.2.6", "object-inspect": "^1.12.0", "object-is": "^1.1.5", "object-keys": "^1.1.1", @@ -2939,6 +3233,27 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, + "ts-node": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", + "integrity": "sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.0", + "yn": "3.1.1" + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2954,6 +3269,12 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "typescript": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.4.tgz", + "integrity": "sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==", + "dev": true + }, "unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -2981,6 +3302,12 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3040,6 +3367,12 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true } } } diff --git a/package.json b/package.json index 483f547..c5f52d1 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,25 @@ { - "name": "beamcoder", - "version": "0.7.1", - "description": "Node.js native bindings to FFmpeg.", - "main": "index.js", - "types": "index.d.ts", + "name": "@u4/beamcoder", + "version": "0.7.4", + "description": "Node.js native bindings to FFmpeg in Typescript", + "main": "ts/index.js", + "types": "ts/index.d.ts", "scripts": { - "preinstall": "node install_ffmpeg.js", + "prepack": "tsc -p .", + "preinstall": "node ts/install_ffmpeg.js", "install": "node-gyp rebuild", - "test": "tape test/*.js", + "build": "tsc -p .", + "clean": "rimraf ts/*.js ts/*.d.ts temp.mp4", + "test": "ts-node node_modules/tape/bin/tape test/*.ts", + "retest": "npm run clean && npm run build && npm run test", "lint": "eslint **/*.js", "lint-html": "eslint **/*.js -f html -o ./reports/lint-results.html", "lint-fix": "eslint --fix **/*.js" }, + "contributors": [ + "Streampunk Media Ltd (https://www.streampunk.media/)", + "Uriel Chemouni (https://urielch.github.io/)" + ], "repository": { "type": "git", "url": "git+https://github.com/Streampunk/beamcoder.git" @@ -30,16 +38,32 @@ "author": "Streampunk Media Ltd", "license": "GPL-3.0-or-later", "bugs": { - "url": "https://github.com/Streampunk/beamcoder/issues" + "url": "https://github.com/UrielCh/beamcoder/issues" }, - "homepage": "https://github.com/Streampunk/beamcoder#readme", + "homepage": "https://github.com/UrielCh/beamcoder#readme", "dependencies": { "bindings": "^1.5.0", - "segfault-handler": "^1.3.0" + "segfault-handler": "^1.3.0", + "unzipper": "^0.10.11" }, "devDependencies": { - "eslint": "^8.9.0", - "tape": "^5.5.2" + "@types/bindings": "^1.5.1", + "@types/node": "^17.0.31", + "@types/tape": "^4.13.2", + "@types/unzipper": "^0.10.5", + "@types/webtorrent": "^0.109.3", + "eslint": "^8.14.0", + "md5-file": "^5.0.0", + "rimraf": "^3.0.2", + "tape": "^5.5.3", + "ts-node": "^10.7.0", + "typescript": "^4.6.4", + "webtorrent": "^1.8.16" }, - "gypfile": true + "gypfile": true, + "files": [ + "src", + "ts", + "binding.gyp" + ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..9394453 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2257 @@ +lockfileVersion: 5.3 + +specifiers: + '@types/bindings': ^1.5.1 + '@types/node': ^17.0.31 + '@types/tape': ^4.13.2 + '@types/unzipper': ^0.10.5 + '@types/webtorrent': ^0.109.3 + bindings: ^1.5.0 + eslint: ^8.14.0 + md5-file: ^5.0.0 + rimraf: ^3.0.2 + segfault-handler: ^1.3.0 + tape: ^5.5.3 + ts-node: ^10.7.0 + typescript: ^4.6.4 + unzipper: ^0.10.11 + webtorrent: ^1.8.16 + +dependencies: + bindings: 1.5.0 + segfault-handler: 1.3.0 + unzipper: 0.10.11 + +devDependencies: + '@types/bindings': 1.5.1 + '@types/node': 17.0.31 + '@types/tape': 4.13.2 + '@types/unzipper': 0.10.5 + '@types/webtorrent': 0.109.3 + eslint: 8.14.0 + md5-file: 5.0.0 + rimraf: 3.0.2 + tape: 5.5.3 + ts-node: 10.7.0_5f3e12794cebfbf3197131903b74d233 + typescript: 4.6.4 + webtorrent: 1.8.16 + +packages: + + /@cspotcode/source-map-consumer/0.8.0: + resolution: {integrity: sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==} + engines: {node: '>= 12'} + dev: true + + /@cspotcode/source-map-support/0.7.0: + resolution: {integrity: sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==} + engines: {node: '>=12'} + dependencies: + '@cspotcode/source-map-consumer': 0.8.0 + dev: true + + /@eslint/eslintrc/1.2.2: + resolution: {integrity: sha512-lTVWHs7O2hjBFZunXTZYnYqtB9GakA1lnxIf+gKq2nY5gxkkNi/lQvveW6t8gFdOHTg6nG50Xs95PrLqVpcaLg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.3.1 + globals: 13.13.0 + ignore: 5.2.0 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/config-array/0.9.5: + resolution: {integrity: sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + + /@humanwhocodes/object-schema/1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + dev: true + + /@tsconfig/node10/1.0.8: + resolution: {integrity: sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==} + dev: true + + /@tsconfig/node12/1.0.9: + resolution: {integrity: sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==} + dev: true + + /@tsconfig/node14/1.0.1: + resolution: {integrity: sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==} + dev: true + + /@tsconfig/node16/1.0.2: + resolution: {integrity: sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==} + dev: true + + /@types/bindings/1.5.1: + resolution: {integrity: sha512-8HzueDeoxGXdsJ0Ep7TOXHGN+woRTWa1bAds30r5we7PCC3P5zrSTRknePLn/KYAubgQv5t/1zkonnStHLCWOg==} + dependencies: + '@types/node': 17.0.31 + dev: true + + /@types/bittorrent-protocol/3.1.2: + resolution: {integrity: sha512-7k9nivNeG7Sc8wVuBs+XjBp2u7pH8tqW3BB93/SAg3xht/cZEK+Rqkj79xSyJqyj86eA0F6n85EKkkyGki8afg==} + dependencies: + '@types/node': 17.0.31 + dev: true + + /@types/magnet-uri/5.1.3: + resolution: {integrity: sha512-FvJN1yYdLhvU6zWJ2YnWQ2GnpFLsA8bt+85WY0tLh6ehzGNrvBorjlcc53/zY43r/IKn+ctFs1nt7andwGnQCQ==} + dependencies: + '@types/node': 17.0.31 + dev: true + + /@types/node/17.0.31: + resolution: {integrity: sha512-AR0x5HbXGqkEx9CadRH3EBYx/VkiUgZIhP4wvPn/+5KIsgpNoyFaRlVe0Zlx9gRtg8fA06a9tskE2MSN7TcG4Q==} + dev: true + + /@types/parse-torrent-file/4.0.3: + resolution: {integrity: sha512-dFkPnJPKiFWiGX+HXmyTVt2js3k0d9dThmUxX8nfGC22hbyZ5BTmetsEl45sQhHLcFo43njVrIKMXM3F1ahXRw==} + dependencies: + '@types/node': 17.0.31 + dev: true + + /@types/parse-torrent/5.8.4: + resolution: {integrity: sha512-FdKs5yN5iYO5Cu9gVz1Zl30CbZe6HTsqloWmCf+LfbImgSzlsUkov2+npQWCQSQ3zi/a2G5C824K0UpZ2sRufA==} + dependencies: + '@types/magnet-uri': 5.1.3 + '@types/node': 17.0.31 + '@types/parse-torrent-file': 4.0.3 + dev: true + + /@types/simple-peer/9.11.4: + resolution: {integrity: sha512-Elje14YvM47k+XEaoyRAeUSvZN7TOLWYL233QCckUaXjT4lRESHnYs0iOK2JoosO5DnCvWu/0Vpl9qnw4KCLWw==} + dependencies: + '@types/node': 17.0.31 + dev: true + + /@types/tape/4.13.2: + resolution: {integrity: sha512-V1ez/RtYRGN9cNYApw5xf27DpMkTB0033X6a2i3KUmKhSojBfbWN0i3EgZxboUG96WJLHLdOyZ01aiZwVW5aSA==} + dependencies: + '@types/node': 17.0.31 + dev: true + + /@types/unzipper/0.10.5: + resolution: {integrity: sha512-NrLJb29AdnBARpg9S/4ktfPEisbJ0AvaaAr3j7Q1tg8AgcEUsq2HqbNzvgLRoWyRtjzeLEv7vuL39u1mrNIyNA==} + dependencies: + '@types/node': 17.0.31 + dev: true + + /@types/webtorrent/0.109.3: + resolution: {integrity: sha512-EJLsxMEcEjPXHcBqL6TRAbUwIpxAul5ULrXHJ0zwig7Oe70FS6dAzCWLq4MBafX3QrQG1DzGAS0fS8iJEOjD0g==} + dependencies: + '@types/bittorrent-protocol': 3.1.2 + '@types/node': 17.0.31 + '@types/parse-torrent': 5.8.4 + '@types/simple-peer': 9.11.4 + dev: true + + /@webtorrent/http-node/1.3.0: + resolution: {integrity: sha512-GWZQKroPES4z91Ijx6zsOsb7+USOxjy66s8AoTWg0HiBBdfnbtf9aeh3Uav0MgYn4BL8Q7tVSUpd0gGpngKGEQ==} + dependencies: + freelist: 1.0.3 + http-parser-js: 0.4.13 + dev: true + + /acorn-jsx/5.3.2_acorn@8.7.0: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.7.0 + dev: true + + /acorn-walk/8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + + /acorn/8.7.0: + resolution: {integrity: sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /addr-to-ip-port/1.5.4: + resolution: {integrity: sha512-ByxmJgv8vjmDcl3IDToxL2yrWFrRtFpZAToY0f46XFXl8zS081t7El5MXIodwm7RC6DhHBRoOSMLFSPKCtHukg==} + dev: true + + /ajv/6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: true + + /ansi-regex/5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + dev: true + + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: true + + /arg/4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + dev: true + + /argparse/2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: true + + /array.prototype.every/1.1.3: + resolution: {integrity: sha512-vWnriJI//SOMOWtXbU/VXhJ/InfnNHPF6BLKn5WfY8xXy+NWql0fUy20GO3sdqBhCAO+qw8S/E5nJiZX+QFdCA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.19.5 + is-string: 1.0.7 + dev: true + + /available-typed-arrays/1.0.5: + resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} + engines: {node: '>= 0.4'} + dev: true + + /b4a/1.5.0: + resolution: {integrity: sha512-J20PbRmSy38jW9TmqGEwd8xINUCuOm2I2bPQ1sK8LWLxKTbhPh0H48DJ27ff2qmSXvI30WYV0tKzSmGb+oCsXg==} + dev: true + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /base64-js/1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + dev: true + + /bencode/2.0.2: + resolution: {integrity: sha512-0ilVjnE2diLdbec/3KN14SP0KE85wh8v/FceNRMbAB2ioc3yTj9tgqdoK9tFEH++TZ10JreTS29qTwg7+SpTiQ==} + dev: true + + /bep53-range/1.1.1: + resolution: {integrity: sha512-ct6s33iiwRCUPp9KXnJ4QMWDgHIgaw36caK/5XEQ9L8dCzSQlJt1Vk6VmHh1VD4AlGCAI4C2zmtfItifBBPrhQ==} + dev: true + + /big-integer/1.6.51: + resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} + engines: {node: '>=0.6'} + dev: false + + /binary-search/1.3.6: + resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==} + dev: true + + /binary/0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + dev: false + + /bindings/1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: false + + /bitfield/4.1.0: + resolution: {integrity: sha512-6cEDG3K+PK9f+B7WyhWYjp09bqSa+uaAaecVA7Y5giFixyVe1s6HKGnvOqYNR4Mi4fBMjfDPLBpHkKvzzgP7kg==} + engines: {node: '>=8'} + dev: true + + /bittorrent-dht/10.0.2: + resolution: {integrity: sha512-V7+V6ZCfxHtn/wvaRuUvxucJhocb8StgKurQJUdHboVjNGWjALVG+VAYuZqz5iN+/j4vmd4GwqjR1ixYCMkyVA==} + engines: {node: '>=10'} + dependencies: + bencode: 2.0.2 + debug: 4.3.4 + k-bucket: 5.1.0 + k-rpc: 5.1.0 + last-one-wins: 1.0.4 + lru: 3.1.0 + randombytes: 2.1.0 + record-cache: 1.2.0 + simple-sha1: 3.1.0 + transitivePeerDependencies: + - supports-color + dev: true + + /bittorrent-lsd/1.1.1: + resolution: {integrity: sha512-dWxU2Mr2lU6jzIKgZrTsXgeXDCIcYpR1b6f2n89fn7juwPAYbNU04OgWjcQPLiNliY0filsX5CQAWntVErpk+Q==} + dependencies: + chrome-dgram: 3.0.6 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + + /bittorrent-peerid/1.3.4: + resolution: {integrity: sha512-Xzk1FJFHmsc9H8IKFtDUkfAZIT1HW8r6UqajfZBBxWmpA1v7FsPO8xPFtnFzCqcXlPN3yi8dDmlqZCemyB7P8w==} + dev: true + + /bittorrent-protocol/3.5.5: + resolution: {integrity: sha512-cfzO//WtJGNLHXS58a4exJCSq1U0dkP2DZCQxgADInYFPdOfV1EmtpEN9toLOluVCXJRYAdwW5H6Li/hrn697A==} + dependencies: + bencode: 2.0.2 + bitfield: 4.1.0 + debug: 4.3.4 + randombytes: 2.1.0 + rc4: 0.1.5 + readable-stream: 3.6.0 + simple-sha1: 3.1.0 + speedometer: 1.1.0 + unordered-array-remove: 1.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /bittorrent-tracker/9.18.5: + resolution: {integrity: sha512-yh/lGHLNuTNZJJlUAmikYOX+njHIr8BFqCxJiXSQ3FaEmhj2xhe1OXF9DcrHpmMYmAQEKWC6+P/uNWGRKdkH9g==} + engines: {node: '>=12'} + hasBin: true + dependencies: + bencode: 2.0.2 + bittorrent-peerid: 1.3.4 + bn.js: 5.2.0 + chrome-dgram: 3.0.6 + clone: 2.1.2 + compact2string: 1.4.1 + debug: 4.3.4 + ip: 1.1.5 + lru: 3.1.0 + minimist: 1.2.6 + once: 1.4.0 + queue-microtask: 1.2.3 + random-iterate: 1.0.1 + randombytes: 2.1.0 + run-parallel: 1.2.0 + run-series: 1.1.9 + simple-get: 4.0.1 + simple-peer: 9.11.1 + simple-websocket: 9.1.0_d6955b83f926115bf12ffeabab6deaae + socks: 2.6.2 + string2compact: 1.3.2 + unordered-array-remove: 1.0.2 + ws: 7.5.7_d6955b83f926115bf12ffeabab6deaae + optionalDependencies: + bufferutil: 4.0.6 + utf-8-validate: 5.0.9 + transitivePeerDependencies: + - supports-color + dev: true + + /blob-to-buffer/1.2.9: + resolution: {integrity: sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==} + dev: true + + /block-stream2/2.1.0: + resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==} + dependencies: + readable-stream: 3.6.0 + dev: true + + /bluebird/3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + dev: false + + /bn.js/5.2.0: + resolution: {integrity: sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==} + dev: true + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + /browserify-package-json/1.0.1: + resolution: {integrity: sha1-mN3oqlxWH9bT/km7qhArdLOW/eo=} + dev: true + + /buffer-alloc-unsafe/1.1.0: + resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} + dev: true + + /buffer-alloc/1.2.0: + resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} + dependencies: + buffer-alloc-unsafe: 1.1.0 + buffer-fill: 1.0.0 + dev: true + + /buffer-fill/1.0.0: + resolution: {integrity: sha1-+PeLdniYiO858gXNY39o5wISKyw=} + dev: true + + /buffer-indexof-polyfill/1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + dev: false + + /buffer/6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + + /buffers/0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + dev: false + + /bufferutil/4.0.6: + resolution: {integrity: sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.4.0 + dev: true + optional: true + + /cache-chunk-store/3.2.2: + resolution: {integrity: sha512-2lJdWbgHFFxcSth9s2wpId3CR3v1YC63KjP4T9WhpW7LWlY7Hiiei3QwwqzkWqlJTfR8lSy9F5kRQECeyj+yQA==} + dependencies: + lru: 3.1.0 + queue-microtask: 1.2.3 + dev: true + + /call-bind/1.0.2: + resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} + dependencies: + function-bind: 1.1.1 + get-intrinsic: 1.1.1 + dev: true + + /callsites/3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true + + /chainsaw/0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + dependencies: + traverse: 0.3.9 + dev: false + + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: true + + /chrome-dgram/3.0.6: + resolution: {integrity: sha512-bqBsUuaOiXiqxXt/zA/jukNJJ4oaOtc7ciwqJpZVEaaXwwxqgI2/ZdG02vXYWUhHGziDlvGMQWk0qObgJwVYKA==} + dependencies: + inherits: 2.0.4 + run-series: 1.1.9 + dev: true + + /chrome-dns/1.0.1: + resolution: {integrity: sha512-HqsYJgIc8ljJJOqOzLphjAs79EUuWSX3nzZi2LNkzlw3GIzAeZbaSektC8iT/tKvLqZq8yl1GJu5o6doA4TRbg==} + dependencies: + chrome-net: 3.3.4 + dev: true + + /chrome-net/3.3.4: + resolution: {integrity: sha512-Jzy2EnzmE+ligqIZUsmWnck9RBXLuUy6CaKyuNMtowFG3ZvLt8d+WBJCTPEludV0DHpIKjAOlwjFmTaEdfdWCw==} + dependencies: + inherits: 2.0.4 + dev: true + + /chunk-store-stream/4.3.0: + resolution: {integrity: sha512-qby+/RXoiMoTVtPiylWZt7KFF1jy6M829TzMi2hxZtBIH9ptV19wxcft6zGiXLokJgCbuZPGNGab6DWHqiSEKw==} + dependencies: + block-stream2: 2.1.0 + readable-stream: 3.6.0 + dev: true + + /clone/2.1.2: + resolution: {integrity: sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=} + engines: {node: '>=0.8'} + dev: true + + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: true + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: true + + /compact2string/1.4.1: + resolution: {integrity: sha512-3D+EY5nsRhqnOwDxveBv5T8wGo4DEvYxjDtPGmdOX+gfr5gE92c2RC0w2wa+xEefm07QuVqqcF3nZJUZ92l/og==} + dependencies: + ipaddr.js: 2.0.1 + dev: true + + /concat-map/0.0.1: + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + + /core-util-is/1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: false + + /cpus/1.0.3: + resolution: {integrity: sha512-PXHBvGLuL69u55IkLa5e5838fLhIMHxmkV4ge42a8alGyn7BtawYgI0hQ849EedvtHIOLNNH3i6eQU1BiE9SUA==} + dev: true + + /create-require/1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + + /create-torrent/5.0.2: + resolution: {integrity: sha512-tNelixVeEkjiyeAuCW7uWFl1ARA+YapyZvdSWw6U3AXe/VXpxR4ihFNfjOzmvc5TBqK5EkGdsoKXAEKfQ8xlmQ==} + engines: {node: '>=12'} + hasBin: true + dependencies: + bencode: 2.0.2 + block-stream2: 2.1.0 + filestream: 5.0.0 + is-file: 1.0.0 + junk: 3.1.0 + minimist: 1.2.6 + multistream: 4.1.0 + once: 1.4.0 + piece-length: 2.0.1 + queue-microtask: 1.2.3 + readable-stream: 3.6.0 + run-parallel: 1.2.0 + simple-sha1: 3.1.0 + dev: true + + /cross-spawn/7.0.3: + resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + dev: true + + /debug/4.3.4: + resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.1.2 + dev: true + + /decompress-response/6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dependencies: + mimic-response: 3.1.0 + dev: true + + /deep-equal/2.0.5: + resolution: {integrity: sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==} + dependencies: + call-bind: 1.0.2 + es-get-iterator: 1.1.2 + get-intrinsic: 1.1.1 + is-arguments: 1.1.1 + is-date-object: 1.0.5 + is-regex: 1.1.4 + isarray: 2.0.5 + object-is: 1.1.5 + object-keys: 1.1.1 + object.assign: 4.1.2 + regexp.prototype.flags: 1.4.3 + side-channel: 1.0.4 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.1 + which-typed-array: 1.1.7 + dev: true + + /deep-is/0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true + + /define-properties/1.1.4: + resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + engines: {node: '>= 0.4'} + dependencies: + has-property-descriptors: 1.0.0 + object-keys: 1.1.1 + dev: true + + /defined/1.0.0: + resolution: {integrity: sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=} + dev: true + + /diff/4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + dev: true + + /doctrine/3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + dependencies: + esutils: 2.0.3 + dev: true + + /dotignore/0.1.2: + resolution: {integrity: sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==} + hasBin: true + dependencies: + minimatch: 3.1.2 + dev: true + + /duplexer2/0.1.4: + resolution: {integrity: sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=} + dependencies: + readable-stream: 2.3.7 + dev: false + + /end-of-stream/1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: true + + /err-code/3.0.1: + resolution: {integrity: sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==} + dev: true + + /es-abstract/1.19.5: + resolution: {integrity: sha512-Aa2G2+Rd3b6kxEUKTF4TaW67czBLyAv3z7VOhYRU50YBx+bbsYZ9xQP4lMNazePuFlybXI0V4MruPos7qUo5fA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + es-to-primitive: 1.2.1 + function-bind: 1.1.1 + get-intrinsic: 1.1.1 + get-symbol-description: 1.0.0 + has: 1.0.3 + has-symbols: 1.0.3 + internal-slot: 1.0.3 + is-callable: 1.2.4 + is-negative-zero: 2.0.2 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-weakref: 1.0.2 + object-inspect: 1.12.0 + object-keys: 1.1.1 + object.assign: 4.1.2 + string.prototype.trimend: 1.0.4 + string.prototype.trimstart: 1.0.4 + unbox-primitive: 1.0.1 + dev: true + + /es-get-iterator/1.1.2: + resolution: {integrity: sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.1 + has-symbols: 1.0.3 + is-arguments: 1.1.1 + is-map: 2.0.2 + is-set: 2.0.2 + is-string: 1.0.7 + isarray: 2.0.5 + dev: true + + /es-to-primitive/1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.4 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + + /escape-html/1.0.3: + resolution: {integrity: sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=} + dev: true + + /escape-string-regexp/4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true + + /eslint-scope/7.1.1: + resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-utils/3.0.0_eslint@8.14.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.14.0 + eslint-visitor-keys: 2.1.0 + dev: true + + /eslint-visitor-keys/2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + dev: true + + /eslint-visitor-keys/3.3.0: + resolution: {integrity: sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint/8.14.0: + resolution: {integrity: sha512-3/CE4aJX7LNEiE3i6FeodHmI/38GZtWCsAtsymScmzYapx8q1nVVb+eLcLSzATmCPXw5pT4TqVs1E0OmxAd9tw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.2.2 + '@humanwhocodes/config-array': 0.9.5 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.14.0 + eslint-visitor-keys: 3.3.0 + espree: 9.3.1 + esquery: 1.4.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + functional-red-black-tree: 1.0.1 + glob-parent: 6.0.2 + globals: 13.13.0 + ignore: 5.2.0 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + v8-compile-cache: 2.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /espree/9.3.1: + resolution: {integrity: sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.7.0 + acorn-jsx: 5.3.2_acorn@8.7.0 + eslint-visitor-keys: 3.3.0 + dev: true + + /esquery/1.4.0: + resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse/4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse/5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils/2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /events/3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + dev: true + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true + + /fast-fifo/1.1.0: + resolution: {integrity: sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g==} + dev: true + + /fast-json-stable-stringify/2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true + + /fast-levenshtein/2.0.6: + resolution: {integrity: sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=} + dev: true + + /file-entry-cache/6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.0.4 + dev: true + + /file-uri-to-path/1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: false + + /filestream/5.0.0: + resolution: {integrity: sha512-5H3RqSaJp12THfZiNWodYM7TiKfQvrpX+EIOrB1XvCceTys4yvfEIl8wDp+/yI8qj6Bxym8m0NYWwVXDAet/+A==} + dependencies: + readable-stream: 3.6.0 + typedarray-to-buffer: 3.1.5 + dev: true + + /flat-cache/3.0.4: + resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.2.5 + rimraf: 3.0.2 + dev: true + + /flatted/3.2.5: + resolution: {integrity: sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==} + dev: true + + /for-each/0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + dependencies: + is-callable: 1.2.4 + dev: true + + /foreach/2.0.5: + resolution: {integrity: sha1-C+4AUBiusmDQo6865ljdATbsG5k=} + dev: true + + /freelist/1.0.3: + resolution: {integrity: sha1-AGd1UJ85NXAXhNPtL8nxLJ3xurI=} + dev: true + + /fs-chunk-store/2.0.5: + resolution: {integrity: sha512-z3c2BmyaHdQTtIVXJDQOvwZVWN2gNU//0IYKK2LuPr+cZyGoIrgDwI4iDASaTUyQbOBtyg/k6GuDZepB6jQIPw==} + dependencies: + queue-microtask: 1.2.3 + random-access-file: 2.2.1 + randombytes: 2.1.0 + rimraf: 3.0.2 + run-parallel: 1.2.0 + thunky: 1.1.0 + dev: true + + /fs.realpath/1.0.0: + resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} + + /fstream/1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + dependencies: + graceful-fs: 4.2.10 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + dev: false + + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true + + /functional-red-black-tree/1.0.1: + resolution: {integrity: sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=} + dev: true + + /functions-have-names/1.2.2: + resolution: {integrity: sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA==} + dev: true + + /get-browser-rtc/1.1.0: + resolution: {integrity: sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==} + dev: true + + /get-intrinsic/1.1.1: + resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==} + dependencies: + function-bind: 1.1.1 + has: 1.0.3 + has-symbols: 1.0.3 + dev: true + + /get-package-type/0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + dev: true + + /get-stdin/8.0.0: + resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} + engines: {node: '>=10'} + dev: true + + /get-symbol-description/1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.1 + dev: true + + /glob-parent/6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob/7.2.0: + resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + /globals/13.13.0: + resolution: {integrity: sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /graceful-fs/4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + dev: false + + /has-bigints/1.0.1: + resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==} + dev: true + + /has-dynamic-import/2.0.1: + resolution: {integrity: sha512-X3fbtsZmwb6W7fJGR9o7x65fZoodygCrZ3TVycvghP62yYQfS0t4RS0Qcz+j5tQYUKeSWS09tHkWW6WhFV3XhQ==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.1 + dev: true + + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: true + + /has-property-descriptors/1.0.0: + resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + dependencies: + get-intrinsic: 1.1.1 + dev: true + + /has-symbols/1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: true + + /has-tostringtag/1.0.0: + resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: true + + /http-parser-js/0.4.13: + resolution: {integrity: sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=} + dev: true + + /ieee754/1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + dev: true + + /ignore/5.2.0: + resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} + engines: {node: '>= 4'} + dev: true + + /immediate-chunk-store/2.2.0: + resolution: {integrity: sha512-1bHBna0hCa6arRXicu91IiL9RvvkbNYLVq+mzWdaLGZC3hXvX4doh8e1dLhMKez5siu63CYgO5NrGJbRX5lbPA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /import-fresh/3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /imurmurhash/0.1.4: + resolution: {integrity: sha1-khi5srkoojixPcT7a21XbyMUU+o=} + engines: {node: '>=0.8.19'} + dev: true + + /inflight/1.0.6: + resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + /internal-slot/1.0.3: + resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.1.1 + has: 1.0.3 + side-channel: 1.0.4 + dev: true + + /ip-set/2.1.0: + resolution: {integrity: sha512-JdHz4tSMx1IeFj8yEcQU0i58qiSkOlmZXkZ8+HJ0ROV5KcgLRDO9F703oJ1GeZCvqggrcCbmagD/V7hghY62wA==} + dependencies: + ip: 1.1.5 + dev: true + + /ip/1.1.5: + resolution: {integrity: sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=} + dev: true + + /ipaddr.js/2.0.1: + resolution: {integrity: sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==} + engines: {node: '>= 10'} + dev: true + + /is-arguments/1.1.1: + resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-ascii/1.0.0: + resolution: {integrity: sha1-8CrQJZoJIc0Zn/Ic4bCeD2tOOSk=} + dev: true + + /is-bigint/1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.1 + dev: true + + /is-boolean-object/1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-callable/1.2.4: + resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==} + engines: {node: '>= 0.4'} + dev: true + + /is-core-module/2.8.1: + resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==} + dependencies: + has: 1.0.3 + dev: true + + /is-date-object/1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-extglob/2.1.1: + resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} + engines: {node: '>=0.10.0'} + dev: true + + /is-file/1.0.0: + resolution: {integrity: sha1-KKRM+9nT2xkwRfIrZfzo7fliBZY=} + dev: true + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-map/2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + dev: true + + /is-negative-zero/2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + dev: true + + /is-number-object/1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-regex/1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-set/2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + dev: true + + /is-shared-array-buffer/1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-string/1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol/1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.3 + dev: true + + /is-typed-array/1.1.8: + resolution: {integrity: sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + es-abstract: 1.19.5 + foreach: 2.0.5 + has-tostringtag: 1.0.0 + dev: true + + /is-typedarray/1.0.0: + resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=} + dev: true + + /is-weakmap/2.0.1: + resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} + dev: true + + /is-weakref/1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + dependencies: + call-bind: 1.0.2 + dev: true + + /is-weakset/2.0.2: + resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.1 + dev: true + + /isarray/1.0.0: + resolution: {integrity: sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=} + dev: false + + /isarray/2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + dev: true + + /isexe/2.0.0: + resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} + dev: true + + /js-yaml/4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: true + + /json-schema-traverse/0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true + + /json-stable-stringify-without-jsonify/1.0.1: + resolution: {integrity: sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=} + dev: true + + /junk/3.1.0: + resolution: {integrity: sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==} + engines: {node: '>=8'} + dev: true + + /k-bucket/5.1.0: + resolution: {integrity: sha512-Fac7iINEovXIWU20GPnOMLUbjctiS+cnmyjC4zAUgvs3XPf1vo9akfCHkigftSic/jiKqKl+KA3a/vFcJbHyCg==} + dependencies: + randombytes: 2.1.0 + dev: true + + /k-rpc-socket/1.11.1: + resolution: {integrity: sha512-8xtA8oqbZ6v1Niryp2/g4GxW16EQh5MvrUylQoOG+zcrDff5CKttON2XUXvMwlIHq4/2zfPVFiinAccJ+WhxoA==} + dependencies: + bencode: 2.0.2 + chrome-dgram: 3.0.6 + chrome-dns: 1.0.1 + chrome-net: 3.3.4 + dev: true + + /k-rpc/5.1.0: + resolution: {integrity: sha512-FGc+n70Hcjoa/X2JTwP+jMIOpBz+pkRffHnSl9yrYiwUxg3FIgD50+u1ePfJUOnRCnx6pbjmVk5aAeB1wIijuQ==} + dependencies: + k-bucket: 5.1.0 + k-rpc-socket: 1.11.1 + randombytes: 2.1.0 + dev: true + + /last-one-wins/1.0.4: + resolution: {integrity: sha1-wb/Qy8tGeQ7JFWuNGu6Py4bNoio=} + dev: true + + /levn/0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: true + + /limiter/1.1.5: + resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + dev: true + + /listenercount/1.0.1: + resolution: {integrity: sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=} + dev: false + + /load-ip-set/2.2.1: + resolution: {integrity: sha512-G3hQXehU2LTOp52e+lPffpK4EvidfjwbvHaGqmFcp4ptiZagR4xFdL+D08kMX906dxeqZyWhfonEjdUxrWcldg==} + dependencies: + ip-set: 2.1.0 + netmask: 2.0.2 + once: 1.4.0 + simple-get: 4.0.1 + split: 1.0.1 + dev: true + + /lodash.merge/4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /lru/3.1.0: + resolution: {integrity: sha1-6n+4VG2DczOWoTCR12z+tMBoN9U=} + engines: {node: '>= 0.4.0'} + dependencies: + inherits: 2.0.4 + dev: true + + /lt_donthave/1.0.1: + resolution: {integrity: sha512-PfOXfDN9GnUjlNHjjxKQuMxPC8s12iSrnmg+Ff1BU1uLn7S1BFAKzpZCu6Gwg3WsCUvTZrZoDSHvy6B/j+N4/Q==} + dependencies: + debug: 4.3.4 + unordered-array-remove: 1.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /magnet-uri/6.2.0: + resolution: {integrity: sha512-O9AgdDwT771fnUj0giPYu/rACpz8173y8UXCSOdLITjOVfBenZ9H9q3FqQmveK+ORUMuD+BkKNSZP8C3+IMAKQ==} + dependencies: + bep53-range: 1.1.1 + thirty-two: 1.0.2 + dev: true + + /make-error/1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + + /md5-file/5.0.0: + resolution: {integrity: sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw==} + engines: {node: '>=10.13.0'} + hasBin: true + dev: true + + /mediasource/2.4.0: + resolution: {integrity: sha512-SKUMrbFMHgiCUZFOWZcL0aiF/KgHx9SPIKzxrl6+7nMUMDK/ZnOmJdY/9wKzYeM0g3mybt3ueg+W+/mrYfmeFQ==} + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.0 + to-arraybuffer: 1.0.1 + dev: true + + /memory-chunk-store/1.3.5: + resolution: {integrity: sha512-E1Xc1U4ifk/FkC2ZsWhCaW1xg9HbE/OBmQTLe2Tr9c27YPSLbW7kw1cnb3kQWD1rDtErFJHa7mB9EVrs7aTx9g==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /mime/3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + dev: true + + /mimic-response/3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + dev: true + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + + /minimist/1.2.6: + resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} + + /mkdirp-classic/0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: true + + /mkdirp/0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + dependencies: + minimist: 1.2.6 + dev: false + + /mp4-box-encoding/1.4.1: + resolution: {integrity: sha512-2/PRtGGiqPc/VEhbm7xAQ+gbb7yzHjjMAv6MpAifr5pCpbh3fQUdj93uNgwPiTppAGu8HFKe3PeU+OdRyAxStA==} + dependencies: + uint64be: 2.0.2 + dev: true + + /mp4-stream/3.1.3: + resolution: {integrity: sha512-DUT8f0x2jHbZjNMdqe9h6lZdt6RENWTTdGn8z3TXa4uEsoltuNY9lCCij84mdm0q7xcV0E2W25WRxlKBMo4hSw==} + dependencies: + mp4-box-encoding: 1.4.1 + next-event: 1.0.0 + queue-microtask: 1.2.3 + readable-stream: 3.6.0 + dev: true + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /multistream/4.1.0: + resolution: {integrity: sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==} + dependencies: + once: 1.4.0 + readable-stream: 3.6.0 + dev: true + + /nan/2.15.0: + resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==} + dev: false + + /napi-macros/2.0.0: + resolution: {integrity: sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==} + dev: true + optional: true + + /natural-compare/1.4.0: + resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} + dev: true + + /netmask/2.0.2: + resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} + engines: {node: '>= 0.4.0'} + dev: true + + /next-event/1.0.0: + resolution: {integrity: sha1-53eKzeLlWALgrRh5w5z2917aYdg=} + dev: true + + /node-gyp-build/4.4.0: + resolution: {integrity: sha512-amJnQCcgtRVw9SvoebO3BKGESClrfXGCUTX9hSn1OuGQTQBOZmVd0Z0OlecpuRksKvbsUqALE8jls/ErClAPuQ==} + hasBin: true + dev: true + optional: true + + /object-inspect/1.12.0: + resolution: {integrity: sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==} + dev: true + + /object-is/1.1.5: + resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + dev: true + + /object-keys/1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign/4.1.2: + resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + has-symbols: 1.0.3 + object-keys: 1.1.1 + dev: true + + /once/1.4.0: + resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} + dependencies: + wrappy: 1.0.2 + + /optionator/0.9.1: + resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.3 + dev: true + + /package-json-versionify/1.0.4: + resolution: {integrity: sha1-WGBYepRIc6a35tJujlH/siMVvxc=} + dependencies: + browserify-package-json: 1.0.1 + dev: true + + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parse-torrent/9.1.5: + resolution: {integrity: sha512-K8FXRwTOaZMI0/xuv0dpng1MVHZRtMJ0jRWBJ3qZWVNTrC1MzWUxm9QwaXDz/2qPhV2XC4UIHI92IGHwseAwaA==} + hasBin: true + dependencies: + bencode: 2.0.2 + blob-to-buffer: 1.2.9 + get-stdin: 8.0.0 + magnet-uri: 6.2.0 + queue-microtask: 1.2.3 + simple-get: 4.0.1 + simple-sha1: 3.1.0 + dev: true + + /path-is-absolute/1.0.1: + resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} + engines: {node: '>=0.10.0'} + + /path-key/3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + dev: true + + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + + /piece-length/2.0.1: + resolution: {integrity: sha512-dBILiDmm43y0JPISWEmVGKBETQjwJe6mSU9GND+P9KW0SJGUwoU/odyH1nbalOP9i8WSYuqf1lQnaj92Bhw+Ug==} + dev: true + + /prelude-ls/1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /process-nextick-args/2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + dev: false + + /pump/3.0.0: + resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: true + + /punycode/2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + engines: {node: '>=6'} + dev: true + + /queue-microtask/1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /queue-tick/1.0.0: + resolution: {integrity: sha512-ULWhjjE8BmiICGn3G8+1L9wFpERNxkf8ysxkAer4+TFdRefDaXOCV5m92aMB9FtBVmn/8sETXLXY6BfW7hyaWQ==} + dev: true + + /random-access-file/2.2.1: + resolution: {integrity: sha512-RGU0xmDqdOyEiynob1KYSeh8+9c9Td1MJ74GT1viMEYAn8SJ9oBtWCXLsYZukCF46yududHOdM449uRYbzBrZQ==} + dependencies: + mkdirp-classic: 0.5.3 + random-access-storage: 1.4.3 + dev: true + + /random-access-storage/1.4.3: + resolution: {integrity: sha512-D5e2iIC5dNENWyBxsjhEnNOMCwZZ64TARK6dyMN+3g4OTC4MJxyjh9hKLjTGoNhDOPrgjI+YlFEHFnrp/cSnzQ==} + dependencies: + events: 3.3.0 + inherits: 2.0.4 + queue-tick: 1.0.0 + dev: true + + /random-iterate/1.0.1: + resolution: {integrity: sha1-99l9kt7mZl7F9toIx/ljytSyrJk=} + dev: true + + /randombytes/2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /range-parser/1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: true + + /range-slice-stream/2.0.0: + resolution: {integrity: sha512-PPYLwZ63lXi6Tv2EZ8w3M4FzC0rVqvxivaOVS8pXSp5FMIHFnvi4MWHL3UdFLhwSy50aNtJsgjY0mBC6oFL26Q==} + dependencies: + readable-stream: 3.6.0 + dev: true + + /rc4/0.1.5: + resolution: {integrity: sha1-CMbgSgFo9utiHCKrbLEVG9n0pk0=} + engines: {node: '>=0.10.0'} + dev: true + + /readable-stream/2.3.7: + resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + dev: false + + /readable-stream/3.6.0: + resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + dev: true + + /record-cache/1.2.0: + resolution: {integrity: sha512-kyy3HWCez2WrotaL3O4fTn0rsIdfRKOdQQcEJ9KpvmKmbffKVvwsloX063EgRUlpJIXHiDQFhJcTbZequ2uTZw==} + dependencies: + b4a: 1.5.0 + dev: true + + /regexp.prototype.flags/1.4.3: + resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + functions-have-names: 1.2.2 + dev: true + + /regexpp/3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + dev: true + + /render-media/4.1.0: + resolution: {integrity: sha512-F5BMWDmgATEoyPCtKjmGNTGN1ghoZlfRQ3MJh8dS/MrvIUIxupiof/Y9uahChipXcqQ57twVbgMmyQmuO1vokw==} + dependencies: + debug: 4.3.4 + is-ascii: 1.0.0 + mediasource: 2.4.0 + stream-to-blob-url: 3.0.2 + videostream: 3.2.2 + transitivePeerDependencies: + - supports-color + dev: true + + /resolve-from/4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true + + /resolve/2.0.0-next.3: + resolution: {integrity: sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==} + dependencies: + is-core-module: 2.8.1 + path-parse: 1.0.7 + dev: true + + /resumer/0.0.0: + resolution: {integrity: sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=} + dependencies: + through: 2.3.8 + dev: true + + /rimraf/2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + hasBin: true + dependencies: + glob: 7.2.0 + dev: false + + /rimraf/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.0 + dev: true + + /run-parallel-limit/1.1.0: + resolution: {integrity: sha512-jJA7irRNM91jaKc3Hcl1npHsFLOXOoTkPCUL1JEa1R82O2miplXXRaGdjW/KM/98YQWDhJLiSs793CnXfblJUw==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /run-parallel/1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /run-series/1.1.9: + resolution: {integrity: sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g==} + dev: true + + /rusha/0.8.14: + resolution: {integrity: sha512-cLgakCUf6PedEu15t8kbsjnwIFFR2D4RfL+W3iWFJ4iac7z4B0ZI8fxy4R3J956kAI68HclCFGL8MPoUVC3qVA==} + dev: true + + /safe-buffer/5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + dev: false + + /safe-buffer/5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + dev: true + + /segfault-handler/1.3.0: + resolution: {integrity: sha512-p7kVHo+4uoYkr0jmIiTBthwV5L2qmWtben/KDunDZ834mbos+tY+iO0//HpAJpOFSQZZ+wxKWuRo4DxV02B7Lg==} + requiresBuild: true + dependencies: + bindings: 1.5.0 + nan: 2.15.0 + dev: false + + /setimmediate/1.0.5: + resolution: {integrity: sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=} + dev: false + + /shebang-command/2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + dev: true + + /shebang-regex/3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + dev: true + + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.1 + object-inspect: 1.12.0 + dev: true + + /simple-concat/1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: true + + /simple-get/4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: true + + /simple-peer/9.11.1: + resolution: {integrity: sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==} + dependencies: + buffer: 6.0.3 + debug: 4.3.4 + err-code: 3.0.1 + get-browser-rtc: 1.1.0 + queue-microtask: 1.2.3 + randombytes: 2.1.0 + readable-stream: 3.6.0 + transitivePeerDependencies: + - supports-color + dev: true + + /simple-sha1/3.1.0: + resolution: {integrity: sha512-ArTptMRC1v08H8ihPD6l0wesKvMfF9e8XL5rIHPanI7kGOsSsbY514MwVu6X1PITHCTB2F08zB7cyEbfc4wQjg==} + dependencies: + queue-microtask: 1.2.3 + rusha: 0.8.14 + dev: true + + /simple-websocket/9.1.0_d6955b83f926115bf12ffeabab6deaae: + resolution: {integrity: sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==} + dependencies: + debug: 4.3.4 + queue-microtask: 1.2.3 + randombytes: 2.1.0 + readable-stream: 3.6.0 + ws: 7.5.7_d6955b83f926115bf12ffeabab6deaae + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + dev: true + + /smart-buffer/4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + dev: true + + /socks/2.6.2: + resolution: {integrity: sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==} + engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} + dependencies: + ip: 1.1.5 + smart-buffer: 4.2.0 + dev: true + + /speed-limiter/1.0.2: + resolution: {integrity: sha512-Ax+TbUOho84bWUc3AKqWtkIvAIVws7d6QI4oJkgH4yQ5Yil+lR3vjd/7qd51dHKGzS5bFxg0++QwyNRN7s6rZA==} + dependencies: + limiter: 1.1.5 + streamx: 2.12.4 + dev: true + + /speedometer/1.1.0: + resolution: {integrity: sha512-z/wAiTESw2XVPssY2XRcme4niTc4S5FkkJ4gknudtVoc33Zil8TdTxHy5torRcgqMqksJV2Yz8HQcvtbsnw0mQ==} + dev: true + + /split/1.0.1: + resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + dependencies: + through: 2.3.8 + dev: true + + /stream-to-blob-url/3.0.2: + resolution: {integrity: sha512-PS6wT2ZyyR38Cy+lE6PBEI1ZmO2HdzZoLeDGG0zZbYikCZd0dh8FUoSeFzgWLItpBYw1WJmPVRLpykRV+lAWLQ==} + dependencies: + stream-to-blob: 2.0.1 + dev: true + + /stream-to-blob/2.0.1: + resolution: {integrity: sha512-GXlqXt3svqwIVWoICenix5Poxi4KbCF0BdXXUbpU1X4vq1V8wmjiEIU3aFJzCGNFpKxfbnG0uoowS3nKUgSPYg==} + engines: {node: '>=8'} + dev: true + + /stream-with-known-length-to-buffer/1.0.4: + resolution: {integrity: sha512-ztP79ug6S+I7td0Nd2GBeIKCm+vA54c+e60FY87metz5n/l6ydPELd2lxsljz8OpIhsRM9HkIiAwz85+S5G5/A==} + dependencies: + once: 1.4.0 + dev: true + + /streamx/2.12.4: + resolution: {integrity: sha512-K3xdIp8YSkvbdI0PrCcP0JkniN8cPCyeKlcZgRFSl1o1xKINCYM93FryvTSOY57x73pz5/AjO5B8b9BYf21wWw==} + dependencies: + fast-fifo: 1.1.0 + queue-tick: 1.0.0 + dev: true + + /string.prototype.trim/1.2.5: + resolution: {integrity: sha512-Lnh17webJVsD6ECeovpVN17RlAKjmz4rF9S+8Y45CkMc/ufVpTkU3vZIyIC7sllQ1FCvObZnnCdNs/HXTUOTlg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + es-abstract: 1.19.5 + dev: true + + /string.prototype.trimend/1.0.4: + resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + dev: true + + /string.prototype.trimstart/1.0.4: + resolution: {integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.4 + dev: true + + /string2compact/1.3.2: + resolution: {integrity: sha512-3XUxUgwhj7Eqh2djae35QHZZT4mN3fsO7kagZhSGmhhlrQagVvWSFuuFIWnpxFS0CdTB2PlQcaL16RDi14I8uw==} + dependencies: + addr-to-ip-port: 1.5.4 + ipaddr.js: 2.0.1 + dev: true + + /string_decoder/1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + dependencies: + safe-buffer: 5.1.2 + dev: false + + /string_decoder/1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + dependencies: + safe-buffer: 5.2.1 + dev: true + + /strip-ansi/6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + dev: true + + /strip-json-comments/3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true + + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: true + + /tape/5.5.3: + resolution: {integrity: sha512-hPBJZBL9S7bH9vECg/KSM24slGYV589jJr4dmtiJrLD71AL66+8o4b9HdZazXZyvnilqA7eE8z5/flKiy0KsBg==} + hasBin: true + dependencies: + array.prototype.every: 1.1.3 + call-bind: 1.0.2 + deep-equal: 2.0.5 + defined: 1.0.0 + dotignore: 0.1.2 + for-each: 0.3.3 + get-package-type: 0.1.0 + glob: 7.2.0 + has: 1.0.3 + has-dynamic-import: 2.0.1 + inherits: 2.0.4 + is-regex: 1.1.4 + minimist: 1.2.6 + object-inspect: 1.12.0 + object-is: 1.1.5 + object-keys: 1.1.1 + object.assign: 4.1.2 + resolve: 2.0.0-next.3 + resumer: 0.0.0 + string.prototype.trim: 1.2.5 + through: 2.3.8 + dev: true + + /text-table/0.2.0: + resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=} + dev: true + + /thirty-two/1.0.2: + resolution: {integrity: sha1-TKL//AKlEpDSdEueP1V2k8prYno=} + engines: {node: '>=0.2.6'} + dev: true + + /through/2.3.8: + resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=} + dev: true + + /thunky/1.1.0: + resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} + dev: true + + /timeout-refresh/1.0.3: + resolution: {integrity: sha512-Mz0CX4vBGM5lj8ttbIFt7o4ZMxk/9rgudJRh76EvB7xXZMur7T/cjRiH2w4Fmkq0zxf2QpM8IFvOSRn8FEu3gA==} + dev: true + optional: true + + /to-arraybuffer/1.0.1: + resolution: {integrity: sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=} + dev: true + + /torrent-discovery/9.4.10: + resolution: {integrity: sha512-Sx2BdYYRCWXkxenGlbt3ZRSH6rcrt8ii6O6Aub9vqeS+RzzyNn2w68QVGClpctExoLle3T2eXO9gdlPANckMkA==} + dependencies: + bittorrent-dht: 10.0.2 + bittorrent-lsd: 1.1.1 + bittorrent-tracker: 9.18.5 + debug: 4.3.4 + run-parallel: 1.2.0 + transitivePeerDependencies: + - supports-color + dev: true + + /torrent-piece/2.0.1: + resolution: {integrity: sha512-JLSOyvQVLI6JTWqioY4vFL0JkEUKQcaHQsU3loxkCvPTSttw8ePs2tFwsP4XIjw99Fz8EdOzt/4faykcbnPbCQ==} + dev: true + + /traverse/0.3.9: + resolution: {integrity: sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=} + dev: false + + /ts-node/10.7.0_5f3e12794cebfbf3197131903b74d233: + resolution: {integrity: sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.7.0 + '@tsconfig/node10': 1.0.8 + '@tsconfig/node12': 1.0.9 + '@tsconfig/node14': 1.0.1 + '@tsconfig/node16': 1.0.2 + '@types/node': 17.0.31 + acorn: 8.7.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 4.6.4 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + + /type-check/0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: true + + /type-fest/0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true + + /typedarray-to-buffer/3.1.5: + resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} + dependencies: + is-typedarray: 1.0.0 + dev: true + + /typescript/4.6.4: + resolution: {integrity: sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /uint64be/2.0.2: + resolution: {integrity: sha512-9QqdvpGQTXgxthP+lY4e/gIBy+RuqcBaC6JVwT5I3bDLgT/btL6twZMR0pI3/Fgah9G/pdwzIprE5gL6v9UvyQ==} + dependencies: + buffer-alloc: 1.2.0 + dev: true + + /unbox-primitive/1.0.1: + resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==} + dependencies: + function-bind: 1.1.1 + has-bigints: 1.0.1 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + dev: true + + /unordered-array-remove/1.0.2: + resolution: {integrity: sha1-xUbo+I4xegzyZEyX7LV9umbSUO8=} + dev: true + + /unordered-set/2.0.1: + resolution: {integrity: sha512-eUmNTPzdx+q/WvOHW0bgGYLWvWHNT3PTKEQLg0MAQhc0AHASHVHoP/9YytYd4RBVariqno/mEUhVZN98CmD7bg==} + dev: true + optional: true + + /unzipper/0.10.11: + resolution: {integrity: sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==} + dependencies: + big-integer: 1.6.51 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.10 + listenercount: 1.0.1 + readable-stream: 2.3.7 + setimmediate: 1.0.5 + dev: false + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.1.1 + dev: true + + /ut_metadata/3.5.2: + resolution: {integrity: sha512-3XZZuJSeoIUyMYSuDbTbVtP4KAVGHPfU8nmHFkr8LJc+THCaUXwnu/2AV+LCSLarET/hL9IlbNfYTGrt6fOVuQ==} + dependencies: + bencode: 2.0.2 + bitfield: 4.1.0 + debug: 4.3.4 + simple-sha1: 3.1.0 + transitivePeerDependencies: + - supports-color + dev: true + + /ut_pex/3.0.2: + resolution: {integrity: sha512-3xM88t+AVU5GR0sIY3tmRMLUS+YKiwStc7U7+ZFQ+UHQpX7BjVJOomhmtm0Bs+8R2n812Dt2ymXm01EqDrOOpQ==} + dependencies: + bencode: 2.0.2 + compact2string: 1.4.1 + string2compact: 1.3.2 + dev: true + + /utf-8-validate/5.0.9: + resolution: {integrity: sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q==} + engines: {node: '>=6.14.2'} + requiresBuild: true + dependencies: + node-gyp-build: 4.4.0 + dev: true + optional: true + + /util-deprecate/1.0.2: + resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} + + /utp-native/2.5.3: + resolution: {integrity: sha512-sWTrWYXPhhWJh+cS2baPzhaZc89zwlWCfwSthUjGhLkZztyPhcQllo+XVVCbNGi7dhyRlxkWxN4NKU6FbA9Y8w==} + engines: {node: '>=8.12'} + hasBin: true + requiresBuild: true + dependencies: + napi-macros: 2.0.0 + node-gyp-build: 4.4.0 + readable-stream: 3.6.0 + timeout-refresh: 1.0.3 + unordered-set: 2.0.1 + dev: true + optional: true + + /v8-compile-cache-lib/3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + dev: true + + /v8-compile-cache/2.3.0: + resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} + dev: true + + /videostream/3.2.2: + resolution: {integrity: sha512-4tz23yGGeATmbzj/ZnUm6wgQ4E1lzmMXu2mUA/c0G6adtWKxm1Di5YejdZdRsK6SdkLjKjhplFFYT7r+UUDKvA==} + dependencies: + binary-search: 1.3.6 + mediasource: 2.4.0 + mp4-box-encoding: 1.4.1 + mp4-stream: 3.1.3 + pump: 3.0.0 + range-slice-stream: 2.0.0 + dev: true + + /webtorrent/1.8.16: + resolution: {integrity: sha512-cRiCUn+B62KHN+BtQLZ8+9eGJ8aPQtrk9OVnLDehudxY1u/1ajwuE+5p/ho1vT3ysmro0UtCRNnEor0+QmNaDA==} + engines: {node: '>=12'} + dependencies: + '@webtorrent/http-node': 1.3.0 + addr-to-ip-port: 1.5.4 + bitfield: 4.1.0 + bittorrent-dht: 10.0.2 + bittorrent-protocol: 3.5.5 + cache-chunk-store: 3.2.2 + chrome-net: 3.3.4 + chunk-store-stream: 4.3.0 + cpus: 1.0.3 + create-torrent: 5.0.2 + debug: 4.3.4 + end-of-stream: 1.4.4 + escape-html: 1.0.3 + fs-chunk-store: 2.0.5 + immediate-chunk-store: 2.2.0 + load-ip-set: 2.2.1 + lt_donthave: 1.0.1 + memory-chunk-store: 1.3.5 + mime: 3.0.0 + multistream: 4.1.0 + package-json-versionify: 1.0.4 + parse-torrent: 9.1.5 + pump: 3.0.0 + queue-microtask: 1.2.3 + random-iterate: 1.0.1 + randombytes: 2.1.0 + range-parser: 1.2.1 + render-media: 4.1.0 + run-parallel: 1.2.0 + run-parallel-limit: 1.1.0 + simple-concat: 1.0.1 + simple-get: 4.0.1 + simple-peer: 9.11.1 + simple-sha1: 3.1.0 + speed-limiter: 1.0.2 + speedometer: 1.1.0 + stream-to-blob: 2.0.1 + stream-to-blob-url: 3.0.2 + stream-with-known-length-to-buffer: 1.0.4 + torrent-discovery: 9.4.10 + torrent-piece: 2.0.1 + unordered-array-remove: 1.0.2 + ut_metadata: 3.5.2 + ut_pex: 3.0.2 + optionalDependencies: + utp-native: 2.5.3 + transitivePeerDependencies: + - supports-color + dev: true + + /which-boxed-primitive/1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + + /which-collection/1.0.1: + resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + dependencies: + is-map: 2.0.2 + is-set: 2.0.2 + is-weakmap: 2.0.1 + is-weakset: 2.0.2 + dev: true + + /which-typed-array/1.1.7: + resolution: {integrity: sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==} + engines: {node: '>= 0.4'} + dependencies: + available-typed-arrays: 1.0.5 + call-bind: 1.0.2 + es-abstract: 1.19.5 + foreach: 2.0.5 + has-tostringtag: 1.0.0 + is-typed-array: 1.1.8 + dev: true + + /which/2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + + /word-wrap/1.2.3: + resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} + engines: {node: '>=0.10.0'} + dev: true + + /wrappy/1.0.2: + resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} + + /ws/7.5.7_d6955b83f926115bf12ffeabab6deaae: + resolution: {integrity: sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dependencies: + bufferutil: 4.0.6 + utf-8-validate: 5.0.9 + dev: true + + /yn/3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true diff --git a/scratch/decode_aac.js b/scratch/decode_aac.ts similarity index 91% rename from scratch/decode_aac.js rename to scratch/decode_aac.ts index 2264f3a..3918524 100644 --- a/scratch/decode_aac.js +++ b/scratch/decode_aac.ts @@ -19,15 +19,15 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder, { Packet } from '..'; async function run() { let demuxer = await beamcoder.demuxer({ url: '../media/bbb_1080p_c.ts'}); let decoder = beamcoder.decoder({ name: 'aac' }); - let packet = {}; + let packet: Packet = {} as Packet; for ( let x = 0 ; packet !== null && x < 100 ; x++ ) { packet = await demuxer.read(); - if (packet.stream_index == 1) { + if (packet.stream_index === 1) { console.log(JSON.stringify(packet, null, 2)); let frames = await decoder.decode(packet); console.log(JSON.stringify(frames.frames[0], null, 2)); diff --git a/scratch/decode_avci.js b/scratch/decode_avci.ts similarity index 93% rename from scratch/decode_avci.js rename to scratch/decode_avci.ts index 1a6365b..21625aa 100644 --- a/scratch/decode_avci.js +++ b/scratch/decode_avci.ts @@ -19,7 +19,7 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder, { Packet } from '..'; async function run() { // let demuxer = await beamcoder.demuxer('../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); @@ -30,7 +30,7 @@ async function run() { // let decoder = beamcoder.decoder({ name: 'h264', thread_count: 4, thread_type: { FRAME: false, SLICE: true } }); let decoder = beamcoder.decoder({ name: 'h264', thread_count: 1, hwaccel: true }); // console.dir(decoder, { getters: true, depth: 3 }); - let packet = {}; + let packet: Packet = {} as Packet; for ( let x = 0 ; x < 2000 && packet != null; x++ ) { packet = await demuxer.read(); if (packet && packet.stream_index === 0) { @@ -44,7 +44,7 @@ async function run() { } } let frames = await decoder.flush(); - console.log('flush', frames.total_time, frames.length); + console.log('flush', frames.total_time, frames.frames.length); } run(); diff --git a/scratch/decode_hevc.js b/scratch/decode_hevc.ts similarity index 96% rename from scratch/decode_hevc.js rename to scratch/decode_hevc.ts index f58b768..6e5df94 100644 --- a/scratch/decode_hevc.js +++ b/scratch/decode_hevc.ts @@ -19,7 +19,8 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); + +import beamcoder from '..'; async function run() { let demuxer = await beamcoder.demuxer('../media/bbb_1080p_c.ts'); diff --git a/scratch/decode_pcm.js b/scratch/decode_pcm.ts similarity index 73% rename from scratch/decode_pcm.js rename to scratch/decode_pcm.ts index 5be1d2e..6dc51ee 100644 --- a/scratch/decode_pcm.js +++ b/scratch/decode_pcm.ts @@ -19,19 +19,22 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '..'; async function run() { let demuxer = await beamcoder.demuxer('../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); - console.log(demuxer.streams[1]); + console.log('streams cnt:', demuxer.streams.length); let decoder = await beamcoder.decoder({ demuxer: demuxer, stream_index : 1 }); - console.log(decoder); + console.log('decoder type:', decoder.type); for ( let x = 0 ; x < 100 ; x++ ) { let packet = await demuxer.read(); - if (packet.stream == 1) { + if (packet.stream_index == 1) { //console.log(packet); let frames = await decoder.decode(packet); - console.log(frames.frames); + console.log(`i: ${x} total framews: ${frames.frames.length}`); + console.log(`nb Channels: ${frames.frames[0].channels}`); + console.log(`channel_layout: ${frames.frames[0].channel_layout}`); + console.log(`sample_rate: ${frames.frames[0].sample_rate}`); } } } diff --git a/scratch/make_a_mux.js b/scratch/make_a_mux.ts similarity index 96% rename from scratch/make_a_mux.js rename to scratch/make_a_mux.ts index b9f42d8..599b7d9 100644 --- a/scratch/make_a_mux.js +++ b/scratch/make_a_mux.ts @@ -19,7 +19,7 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder, { Packet } from '..'; async function run() { let demuxer = await beamcoder.demuxer('../media/sound/BBCNewsCountdown.wav'); @@ -57,7 +57,7 @@ async function run() { // stream.codecpar = demuxer.streams[0].codecpar; await muxer.openIO({ options: { blocksize: 8192 }}).then(console.log); await muxer.writeHeader({ options: { write_bext: true, write_peak: 'on', peak_format: 2 }}).then(console.log); - let packet = {}; + let packet = {} as Packet; for ( let x = 0 ; x < 100 && packet !== null ; x++ ) { packet = await demuxer.read(); await muxer.writeFrame(packet); diff --git a/scratch/muxer.js b/scratch/muxer.ts similarity index 79% rename from scratch/muxer.js rename to scratch/muxer.ts index 8883697..284db90 100644 --- a/scratch/muxer.js +++ b/scratch/muxer.ts @@ -21,11 +21,11 @@ // Work in progress -const beamcoder = require('../index.js'); +import beamcoder from '..'; const STREAM_FRAME_RATE = 25; -function allocAudioFrame(sampleFormat, channelLayout, sampleRate, nbSamples) { +function allocAudioFrame(sampleFormat: string, channelLayout: 'stereo' | 'mono', sampleRate: number, nbSamples: number) { return beamcoder.frame({ format: sampleFormat, @@ -35,7 +35,7 @@ function allocAudioFrame(sampleFormat, channelLayout, sampleRate, nbSamples) { }).alloc(); } -function allocPicture(pixelFmt, width, height) { // eslint-disable-line +function allocPicture(pixelFmt: string, width: number, height: number) { // eslint-disable-line return beamcoder.frame({ format: pixelFmt, @@ -44,13 +44,15 @@ function allocPicture(pixelFmt, width, height) { // eslint-disable-line }).alloc(); } -async function addStream(stream, muxer, codecID) { // eslint-disable-line - let codec = await beamcoder.encoder({ codec_id: codecID }); +async function addStream(stream, muxer, codecID: number) { // eslint-disable-line + let codec = beamcoder.encoder({ codec_id: codecID }); stream.st = muxer.newStream(); stream.enc = codec; + // @ts-ignore switch (codec.media_type) { case 'video': + // @ts-ignore codec.setParameters({ codec_id: codecID, bit_rate: 400000, diff --git a/scratch/read_wav.js b/scratch/read_wav.ts similarity index 87% rename from scratch/read_wav.js rename to scratch/read_wav.ts index 965bfdc..d85876b 100644 --- a/scratch/read_wav.js +++ b/scratch/read_wav.ts @@ -19,14 +19,14 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '..'; async function run() { - let demuxer = await beamcoder.demuxer('../media/sound/BBCNewsCountdown.wav'); + let demuxer = await beamcoder.demuxer('../../media/sound/BBCNewsCountdown.wav'); let packet = {}; for ( let x = 0 ; x < 100 && packet !== null ; x++ ) { packet = await demuxer.read(); - console.log(x, packet); + console.log(x, JSON.stringify(packet)); } console.log(await demuxer.seek({ frame : 120 })); console.log(await demuxer.read()); diff --git a/scratch/simple_mux.js b/scratch/simple_mux.ts similarity index 95% rename from scratch/simple_mux.js rename to scratch/simple_mux.ts index 3d3762f..a6ce12e 100644 --- a/scratch/simple_mux.js +++ b/scratch/simple_mux.ts @@ -18,7 +18,7 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder, { Packet } from '..'; async function run() { let demuxer = await beamcoder.demuxer('../media/sound/BBCNewsCountdown.wav'); @@ -28,7 +28,7 @@ async function run() { // stream.codecpar = demuxer.streams[0].codecpar; await muxer.openIO(); await muxer.writeHeader(); - let packet = {}; + let packet = {} as Packet; for ( let x = 0 ; x < 100 && packet !== null ; x++ ) { packet = await demuxer.read(); await muxer.writeFrame(packet); diff --git a/scratch/stream_avci.js b/scratch/stream_avci.ts similarity index 90% rename from scratch/stream_avci.js rename to scratch/stream_avci.ts index 622f6f4..760f877 100644 --- a/scratch/stream_avci.js +++ b/scratch/stream_avci.ts @@ -19,18 +19,18 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); -const fs = require('fs'); +import beamcoder, { DemuxerStream, Packet } from '..'; +import fs from 'fs'; // const util = require('util'); async function run() { // let demuxer = await createDemuxer('../../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); - let demuxerStream = beamcoder.demuxerStream({ highwaterMark: 65536 }); + // http://ftp.kw.bbc.co.uk/dppdownload/dpp_example_files/AS11_DPP_HD_EXAMPLE_1.mxf + let demuxerStream = new DemuxerStream({ highwaterMark: 65536 }); fs.createReadStream('../../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf').pipe(demuxerStream); let demuxer = await demuxerStream.demuxer({}); // console.log(demuxer); - - let decoder = await beamcoder.decoder({ name: 'h264' }); + let decoder = beamcoder.decoder({ name: 'h264' }); // console.log(decoder); const vidStream = demuxer.streams[0]; @@ -80,8 +80,8 @@ async function run() { width: 1280, height: 720, // bit_rate: 10000000, - time_base: [1, 25], - framerate: [25, 1], + time_base: [1, 25] as [number, number], + framerate: [25, 1] as [number, number], // gop_size: 50, // max_b_frames: 1, pix_fmt: 'yuv422p', @@ -100,7 +100,7 @@ async function run() { // await demuxer.seek({ frame: 4200, stream_index: 0}); - let packet = {}; + let packet = {} as Packet; for ( let x = 0 ; x < 10 && packet !== null; x++ ) { packet = await demuxer.read(); if (packet.stream_index == 0) { @@ -120,7 +120,7 @@ async function run() { } let frames = await decoder.flush(); console.log('flush', frames.total_time, frames.frames.length); - + demuxerStream.destroy(); } diff --git a/scratch/stream_mp4.js b/scratch/stream_mp4.ts similarity index 50% rename from scratch/stream_mp4.js rename to scratch/stream_mp4.ts index 4d0041b..f498edf 100644 --- a/scratch/stream_mp4.js +++ b/scratch/stream_mp4.ts @@ -19,10 +19,44 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '..'; +import md5File from 'md5-file'; +import WebTorrent from 'webtorrent'; +import fs from 'fs'; +import os from 'os'; +// https://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4 +// https://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_1080p_h264.mov +// http://www.legittorrents.info/download.php?id=7f34612e0fac5e7b051b78bdf1060113350ebfe0&f=Big%20Buck%20Bunny%20(1920x1080%20h.264).torrent async function run() { - const urls = [ 'file:../../Media/big_buck_bunny_1080p_h264.mov' ]; + const mediaFile = '../big_buck_bunny_1080p_h264.mov' + if (!fs.existsSync(mediaFile)) { + console.log(`${mediaFile} is missing Downloading if now`); + const client = new WebTorrent() + const magnetURI = 'magnet:?xt=urn:btih:P42GCLQPVRPHWBI3PC67CBQBCM2Q5P7A&dn=big_buck_bunny_1080p_h264.mov&xl=725106140&tr=http%3A%2F%2Fblender.waag.org%3A6969%2Fannounce' + await new Promise((done) => { + client.add(magnetURI, { path: '..' }, function (torrent) { + // Got torrent metadata! + console.log('Client is downloading:', torrent.infoHash) + torrent.files.forEach(function (file) { + // Display the file by appending it to the DOM. Supports video, audio, images, and + // more. Specify a container element (CSS selector or reference to DOM node). + console.log(file); + }) + torrent.on("done", () => done()); + }) + }) + console.log(`${mediaFile} Downloaded`); + } + if (!fs.existsSync(mediaFile)) { + console.log(`${mediaFile} still missing`); + return; + } + const sumSrc = await md5File(mediaFile); + if (sumSrc !== 'c23ab2ff12023c684f46fcc02c57b585') + throw ('invalid Src md5'); + + const urls = [`file:${mediaFile}`]; const spec = { start: 0, end: 24 }; const params = { @@ -33,7 +67,8 @@ async function run() { ], filterSpec: '[in0:v] scale=1280:720, colorspace=all=bt709 [out0:v]', streams: [ - { name: 'h264', time_base: [1, 90000], + { + name: 'h264', time_base: [1, 90000], codecpar: { width: 1280, height: 720, format: 'yuv422p', color_space: 'bt709', sample_aspect_ratio: [1, 1] @@ -49,7 +84,8 @@ async function run() { ], filterSpec: '[in0:a] aformat=sample_fmts=fltp:channel_layouts=mono [out0:a]', streams: [ - { name: 'aac', time_base: [1, 90000], + { + name: 'aac', time_base: [1, 90000], codecpar: { sample_rate: 48000, format: 'fltp', frame_size: 1024, channels: 1, channel_layout: 'mono' @@ -68,6 +104,15 @@ async function run() { const beamStreams = await beamcoder.makeStreams(params); await beamStreams.run(); + + const sumDest = await md5File('temp.mp4'); + if (os.platform() === 'darwin') { + if (sumDest !== '784983c8128db6797be07076570aa179') + throw ('invalid Dst md5'); + } else { + if (sumDest !== 'f08742dd1982073c2eb01ba6faf86d63') + throw ('invalid Dst md5'); + } } console.log('Running mp4 maker'); diff --git a/scratch/stream_mux.js b/scratch/stream_mux.ts similarity index 89% rename from scratch/stream_mux.js rename to scratch/stream_mux.ts index c876fe6..77b18d0 100644 --- a/scratch/stream_mux.js +++ b/scratch/stream_mux.ts @@ -19,13 +19,13 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); -const fs = require('fs'); +import beamcoder, { MuxerStream, Packet } from '..'; +import fs from 'fs'; async function run() { let demuxer = await beamcoder.demuxer('../../media/sound/BBCNewsCountdown.wav'); - let muxerStream = beamcoder.muxerStream({ highwaterMark: 65536 }); + let muxerStream = new MuxerStream({ highwaterMark: 65536 }); muxerStream.pipe(fs.createWriteStream('test.wav')); let muxer = muxerStream.muxer({ format_name: 'wav' }); @@ -35,7 +35,7 @@ async function run() { await muxer.openIO(); await muxer.writeHeader(); - let packet = {}; + let packet: Packet = {} as Packet; for ( let x = 0 ; x < 10000 && packet !== null ; x++ ) { packet = await demuxer.read(); if (packet) diff --git a/scratch/stream_pcm.js b/scratch/stream_pcm.ts similarity index 92% rename from scratch/stream_pcm.js rename to scratch/stream_pcm.ts index 771e30b..bed9908 100644 --- a/scratch/stream_pcm.js +++ b/scratch/stream_pcm.ts @@ -19,12 +19,11 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); -const fs = require('fs'); -const util = require('util'); // eslint-disable-line +import beamcoder, { DemuxerStream, Packet } from '..'; +import fs from 'fs'; async function run() { - let demuxerStream = beamcoder.demuxerStream({ highwaterMark: 65536 }); + let demuxerStream = new DemuxerStream({ highwaterMark: 65536 }); // fs.createReadStream('../../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf').pipe(demuxerStream); fs.createReadStream('../../media/sound/BBCNewsCountdown.wav').pipe(demuxerStream); @@ -63,7 +62,7 @@ async function run() { // const abuffersink = filterer.graph.filters.find(f => 'abuffersink' === f.filter.name); // console.log(util.inspect(abuffersink, {depth: null})); - let packet = {}; + let packet = {} as Packet; for ( let x = 0 ; x < 10000 && packet !== null ; x++ ) { packet = await demuxer.read(); if (packet && packet.stream_index == 0) { diff --git a/scratch/stream_wav.js b/scratch/stream_wav.ts similarity index 97% rename from scratch/stream_wav.js rename to scratch/stream_wav.ts index c6406c6..6d4918b 100644 --- a/scratch/stream_wav.js +++ b/scratch/stream_wav.ts @@ -19,7 +19,7 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '..'; async function run() { const urls = [ 'file:../Media/sound/Countdown.wav' ]; diff --git a/src/decode.cc b/src/decode.cc index b196ddb..ad6bfab 100644 --- a/src/decode.cc +++ b/src/decode.cc @@ -114,9 +114,20 @@ napi_value decoder(napi_env env, napi_callback_info info) { CHECK_STATUS; status = napi_get_value_int32(env, value, &streamIdx); CHECK_STATUS; - if (streamIdx < 0 || streamIdx >= (int) format->nb_streams) { - NAPI_THROW_ERROR("Stream index is out of bounds for the given format."); + if (streamIdx < 0) { + char errorMsg[256]; + sprintf(errorMsg, "Stream index:%d must be positif", streamIdx); + napi_throw_error(env, nullptr, errorMsg); + return nullptr; } + + if (streamIdx >= (int) format->nb_streams) { + char errorMsg[256]; + sprintf(errorMsg, "Stream index is out of bounds for the given format. %d must be < nb_streams(%d)", streamIdx, (int)format->nb_streams); + napi_throw_error(env, nullptr, errorMsg); + return nullptr; + } + params = format->streams[streamIdx]->codecpar; codecID = params->codec_id; codecName = (char*) avcodec_get_name(params->codec_id); diff --git a/src/demux.cc b/src/demux.cc index 6978087..830adf6 100644 --- a/src/demux.cc +++ b/src/demux.cc @@ -115,6 +115,9 @@ void demuxerComplete(napi_env env, napi_status asyncStatus, void* data) { tidyCarrier(env, c); } +/** + * demuxer(options: DemuxerCreateOptions | string): Promise + */ napi_value demuxer(napi_env env, napi_callback_info info) { napi_value resourceName, promise, value, subValue; napi_valuetype type; @@ -142,6 +145,7 @@ napi_value demuxer(napi_env env, napi_callback_info info) { REJECT_RETURN; if (type == napi_string) { + // demuxer(options: string): Promise c->status = napi_get_value_string_utf8(env, args[0], nullptr, 0, &strLen); REJECT_RETURN; c->filename = (const char *) malloc((strLen + 1) * sizeof(char)); @@ -208,7 +212,7 @@ napi_value demuxer(napi_env env, napi_callback_info info) { REJECT_RETURN; } } - + // end of arg reading if ((c->filename == nullptr) && (c->adaptor == nullptr)) { REJECT_ERROR_RETURN("Neither a filename nor an adaptor have been provided.", BEAMCODER_INVALID_ARGS); diff --git a/test/codecParamsSpec.js b/test/codecParamsSpec.ts similarity index 98% rename from test/codecParamsSpec.js rename to test/codecParamsSpec.ts index f7a83e0..78fbf30 100644 --- a/test/codecParamsSpec.js +++ b/test/codecParamsSpec.ts @@ -19,8 +19,8 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const test = require('tape'); -const beamcoder = require('../index.js'); +import test from 'tape'; +import beamcoder from '..'; test('Creating codec parameters', t => { let cps = beamcoder.codecParameters(); diff --git a/test/decoderSpec.js b/test/decoderSpec.ts similarity index 94% rename from test/decoderSpec.js rename to test/decoderSpec.ts index c427353..faff7d3 100644 --- a/test/decoderSpec.js +++ b/test/decoderSpec.ts @@ -19,8 +19,8 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const test = require('tape'); -const beamcoder = require('../index.js'); +import test from 'tape'; +import beamcoder from '..'; test('Creating a decoder', t => { let dec = beamcoder.decoder({ name: 'h264' }); @@ -37,6 +37,7 @@ test('Checking the A properties:', t => { t.deepEqual(dec.active_thread_type, { FRAME: false, SLICE: false}, 'active_thread_type has expected default.'); + // @ts-expect-error:next-line t.throws(() => { dec.active_thread_type = { FRAME: true }; }, /User cannot/, 'active_thread_type cannot be set.'); @@ -47,6 +48,7 @@ test('Checking the A properties:', t => { t.equals(dec.audio_service_type, 'main', 'audio_service_type has expected default value.'); + // @ts-expect-error:next-line t.throws(() => { dec.audio_service_type = 'dialogue'; }, /decoding/, 'cannot be updated when deocoding.'); diff --git a/test/demuxerSpec.js b/test/demuxerSpec.ts similarity index 94% rename from test/demuxerSpec.js rename to test/demuxerSpec.ts index 8ccc400..5675e26 100644 --- a/test/demuxerSpec.js +++ b/test/demuxerSpec.ts @@ -19,13 +19,14 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const test = require('tape'); -const beamcoder = require('../index.js'); +import test from 'tape'; +import beamcoder from '..'; test('Creating a demuxer', async t => { let dm = await beamcoder.demuxer('https://www.elecard.com/storage/video/bbb_1080p_c.ts'); t.ok(dm, 'is truthy.'); t.equal(dm.type, 'demuxer', 'type name says demuxer.'); + // @ts-expect-error:next-line t.equal(typeof dm.oformat, 'undefined', 'output format is undefined.'); t.ok(dm.iformat, 'has an input format.'); t.equal(dm.iformat.name, 'mpegts', 'input format is mpegts.'); diff --git a/test/encoderSpec.js b/test/encoderSpec.ts similarity index 93% rename from test/encoderSpec.js rename to test/encoderSpec.ts index 77b98eb..64f6f22 100644 --- a/test/encoderSpec.js +++ b/test/encoderSpec.ts @@ -19,8 +19,8 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const test = require('tape'); -const beamcoder = require('../index.js'); +import test from 'tape'; +import beamcoder from '..'; test('Creating a video encoder', t => { let enc = beamcoder.encoder({ name: 'h264' }); @@ -47,10 +47,13 @@ test('Checking the A properties:', t => { t.deepEqual(enc.active_thread_type, { FRAME: false, SLICE: false}, 'active_thread_type has expected default.'); + // @ts-expect-error:next-line t.throws(() => { enc.active_thread_type = { FRAME: true }; }, /User cannot/, 'active_thread_type cannot be set.'); + // @ts-expect-error:next-line t.notOk(enc.apply_cropping, 'apply_cropping not defined for encoding.'); + // @ts-expect-error:next-line t.throws(() => { enc.apply_cropping = 0; }, /encoding/, 'apply_cropping setting does not throw.'); @@ -60,6 +63,7 @@ test('Checking the A properties:', t => { 'audio_service_type can be updated.'); t.equals(enc.audio_service_type, 'dialogue', 'audio_service_type has been updated.'); + // @ts-expect-error:next-line t.throws(() => { enc.audio_service_type = 'wibble'; }, 'audio_service_type throws with unknown value.'); diff --git a/test/filtererSpec.js b/test/filtererSpec.ts similarity index 95% rename from test/filtererSpec.js rename to test/filtererSpec.ts index b1c018c..080e127 100644 --- a/test/filtererSpec.js +++ b/test/filtererSpec.ts @@ -19,8 +19,8 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const test = require('tape'); -const beamcoder = require('../index.js'); +import test from 'tape'; +import beamcoder from '..'; test('Create a filterer', async t => { let flt = await beamcoder.filterer({ diff --git a/test/formatSpec.js b/test/formatSpec.ts similarity index 96% rename from test/formatSpec.js rename to test/formatSpec.ts index 8b8ba7e..4246a26 100644 --- a/test/formatSpec.js +++ b/test/formatSpec.ts @@ -19,10 +19,11 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const test = require('tape'); -const beamcoder = require('../index.js'); +import test from 'tape'; +import beamcoder from '..'; -const isExternal = o => Object.toString(o).indexOf('native code') >= 0; +const isExternal = (o: any) => (Object as any).toString(o).indexOf('native code') >= 0; +// const isExternal = o => Object.toString(o).indexOf('native code') >= 0; test('Creating a format', t => { let fmt = beamcoder.format(); @@ -39,7 +40,8 @@ test('Creating a format', t => { t.end(); }); -const stripNewStream = ({ newStream, ...others }) => ({ ...others }); // eslint-disable-line no-unused-vars +// @ts-ignore +const stripNewStream = ({ newStream, ...others }) => ({ ...others }); test('Minimal JSON serialization', t => { let fmt = beamcoder.format(); @@ -306,8 +308,8 @@ test('Test minimal JSON stream', t => { let fmt2 = beamcoder.format(JSON.stringify(fmt)); t.ok(fmt2, 'construction of new format form JSON is truthy.'); t.equal(fmt2.streams.length, 3, 'has expected number of streams.'); - t.deepEqual(fmt2.streams.map(JSON.stringify).map(JSON.parse), - [s1, s2, s3].map(JSON.stringify).map(JSON.parse), 'has expected streams.'); + t.deepEqual(fmt2.streams.map(d => JSON.stringify(d)).map(s => JSON.parse(s)), + [s1, s2, s3].map(d => JSON.stringify(d)).map(s => JSON.parse(s)), 'has expected streams.'); t.throws(() => fmt2.streams = [], /construction/, 'cannot set streams after construction.'); diff --git a/test/frameSpec.js b/test/frameSpec.ts similarity index 97% rename from test/frameSpec.js rename to test/frameSpec.ts index 7f61896..46aa0dc 100644 --- a/test/frameSpec.js +++ b/test/frameSpec.ts @@ -19,9 +19,9 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const test = require('tape'); -const beamcoder = require('../index.js'); -const util = require('util'); +import test from 'tape'; +import beamcoder from '..'; +import util from 'util'; test('Create a frame', t => { let fr = beamcoder.frame(); diff --git a/test/introspectionSpec.js b/test/introspectionSpec.ts similarity index 96% rename from test/introspectionSpec.js rename to test/introspectionSpec.ts index 87d3375..31a5137 100644 --- a/test/introspectionSpec.js +++ b/test/introspectionSpec.ts @@ -19,8 +19,8 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const test = require('tape'); -const beamcoder = require('../index.js'); +import test from 'tape'; +import beamcoder from '..'; test('Version information', t => { const verPos = beamcoder.avVersionInfo().indexOf('5.'); diff --git a/test/mp4FullTest.ts b/test/mp4FullTest.ts new file mode 100644 index 0000000..9e1dda8 --- /dev/null +++ b/test/mp4FullTest.ts @@ -0,0 +1,120 @@ +import test from 'tape'; +import { makeSources, makeStreams } from '..'; +import md5File from 'md5-file'; +import WebTorrent from 'webtorrent'; +import fs from 'fs'; +import os from 'os'; + +test('recompress mp4', async t => { + async function run() { + const mediaFile = '../big_buck_bunny_1080p_h264.mov' + if (!fs.existsSync(mediaFile)) { + console.log(`${mediaFile} is missing Downloading it, now using torrent magnet link.`); + const client = new WebTorrent() + const magnetURI = 'magnet:?xt=urn:btih:P42GCLQPVRPHWBI3PC67CBQBCM2Q5P7A&dn=big_buck_bunny_1080p_h264.mov&xl=725106140&tr=http%3A%2F%2Fblender.waag.org%3A6969%2Fannounce' + await new Promise((done) => { + client.add(magnetURI, { path: '..' }, function (torrent) { + let len = 0; + let ready = '0'; + // Got torrent metadata! + console.log('Client is downloading:', torrent.infoHash) + torrent.files.forEach(function (file) { + // Display the file by appending it to the DOM. Supports video, audio, images, and + // more. Specify a container element (CSS selector or reference to DOM node). + console.log(file.length); + }) + torrent.on("done", () => done()); + torrent.on("wire", (wire, addr) => console.log(`wire ${wire} addr: ${addr}`)); + torrent.on('download', (bytes: number) => { + len += bytes; + let ready2 = (len / (1024 * 1024)).toFixed(2); + if (ready != ready2) { + ready = ready2; + console.log(`${ready2} downloaded to ${mediaFile}`); + } + }); + }) + }) + console.log(`${mediaFile} Downloaded`); + client.destroy(); + } + if (!fs.existsSync(mediaFile)) { + console.log(`${mediaFile} still missing`); + return; + } + + const src = mediaFile; + const sumSrc = await md5File(src); + + t.equal(sumSrc, 'c23ab2ff12023c684f46fcc02c57b585', 'source File have incrrrect md5sum'); + + const urls = [`file:${src}`]; + const spec = { + start: 0, + end: 24 + }; + + const params = { + video: [{ + sources: [{ + url: urls[0], + ms: spec, + streamIndex: 0 + }], + filterSpec: '[in0:v] scale=1280:720, colorspace=all=bt709 [out0:v]', + streams: [{ + name: 'h264', + time_base: [1, 90000], + codecpar: { + width: 1280, + height: 720, + format: 'yuv422p', + color_space: 'bt709', + sample_aspect_ratio: [1, 1] + } + }] + }], + audio: [{ + sources: [{ + url: urls[0], + ms: spec, + streamIndex: 2 + }], + filterSpec: '[in0:a] aformat=sample_fmts=fltp:channel_layouts=mono [out0:a]', + streams: [{ + name: 'aac', + time_base: [1, 90000], + codecpar: { + sample_rate: 48000, + format: 'fltp', + frame_size: 1024, + channels: 1, + channel_layout: 'mono' + } + }] + },], + out: { + formatName: 'mp4', + url: 'file:temp.mp4' + } + }; + + await makeSources(params); + const beamStreams = await makeStreams(params); + + await beamStreams.run(); + + const sumDest = await md5File('temp.mp4'); + + if (os.platform() === 'darwin') { + t.equal(sumDest, '784983c8128db6797be07076570aa179', 'dest File have incorrect md5sum'); + } else { + t.equal(sumDest, 'f08742dd1982073c2eb01ba6faf86d63', 'dest File have incorrect md5sum'); + } + } + + console.log('Running mp4 maker'); + return run(); + // .then(() => console.log(`Finished ${Date.now() - start}ms`)) + //.catch(console.error); +}); diff --git a/test/muxerSpec.js b/test/muxerSpec.ts similarity index 94% rename from test/muxerSpec.js rename to test/muxerSpec.ts index b7069f2..5a54c10 100644 --- a/test/muxerSpec.js +++ b/test/muxerSpec.ts @@ -19,12 +19,13 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const test = require('tape'); -const beamcoder = require('../index.js'); +import test from 'tape'; +import beamcoder from '..'; test('Creating a muxer', t => { let mx = beamcoder.muxer({ name: 'mpegts' }); t.ok(mx, 'is truthy.'); + // @ts-expect-error t.equal(typeof mx.iformat, 'undefined', 'input format is undefined.'); t.ok(mx.oformat, 'has output format.'); t.equal(mx.oformat.name, 'mpegts', 'output format is mpegts.'); diff --git a/test/packetSpec.js b/test/packetSpec.ts similarity index 98% rename from test/packetSpec.js rename to test/packetSpec.ts index b65a5e5..06c2287 100644 --- a/test/packetSpec.js +++ b/test/packetSpec.ts @@ -19,8 +19,8 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const test = require('tape'); -const beamcoder = require('../index.js'); +import test from 'tape'; +import beamcoder from '..'; test('Create a packet', t => { let pkt = beamcoder.packet(); @@ -72,6 +72,7 @@ test('Minimal JSON serialization', t => { }); test('Maximal JSON serialization', t => { + //@ts-ignore let pkt = beamcoder.packet({ type: 'Packet', pts: 42, dts: 43, diff --git a/ts/beamcoder.ts b/ts/beamcoder.ts new file mode 100644 index 0000000..7ce2834 --- /dev/null +++ b/ts/beamcoder.ts @@ -0,0 +1,27 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ + +import bindings from 'bindings'; +import type * as Beamcodere from './types'; +declare type BeamcoderType = typeof Beamcodere; +const beamcoder = bindings('beamcoder') as BeamcoderType; +export default beamcoder; \ No newline at end of file diff --git a/ts/calcStats.ts b/ts/calcStats.ts new file mode 100644 index 0000000..f368c26 --- /dev/null +++ b/ts/calcStats.ts @@ -0,0 +1,39 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ +export interface ffStats { + mean: number; + stdDev: number; + max: number; + min: number; +} +export default function calcStats(arr: Array<{ [key in K]: { [prop in P]: number } }>, elem: K, prop: P): ffStats { + const values: number[] = arr.filter(cur => cur[elem]).map(cur => cur[elem][prop]); + const mean: number = values.reduce((acc, cur) => acc + cur, 0) / arr.length; + const max: number = Math.max(...values) + const min: number = Math.min(...values) + // standard deviation + const sumDelta: number = values.reduce((acc, cur) => acc + Math.pow(cur - mean, 2), 0); + const stdDev: number = Math.pow(sumDelta / arr.length, 0.5); + return { mean, stdDev, max, min }; + }; + + \ No newline at end of file diff --git a/ts/demuxerStream.ts b/ts/demuxerStream.ts new file mode 100644 index 0000000..f46c70e --- /dev/null +++ b/ts/demuxerStream.ts @@ -0,0 +1,62 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ +import { governor as Governor } from './types/governor'; +import beamcoder from './beamcoder' +import { Writable } from 'stream'; +import { Demuxer, InputFormat } from './types'; + +/** + * Create a WritableDemuxerStream to allow streaming to a Demuxer + * @param options.highwaterMark Buffer level when `stream.write()` starts returng false. + * @returns A WritableDemuxerStream that can be streamed to. + */ +export default class DemuxerStream extends Writable { + private governor = new beamcoder.governor({}); + constructor(params: { highwaterMark?: number }) { + super({ + highWaterMark: params.highwaterMark || 16384, + write: (chunk, encoding, cb) => { + (async () => { + await this.governor.write(chunk); + cb(); + })(); + } + }) + this.on('finish', () => this.governor.finish()); + this.on('error', console.error); + } + + /** + * Create a demuxer for this source + * @param options a DemuxerCreateOptions object + * @returns a promise that resolves to a Demuxer when it has determined sufficient + * format details by consuming data from the source. The promise will wait indefinitely + * until sufficient source data has been read. + */ + public demuxer(options?: { iformat?: InputFormat, options?: { [key: string]: any }, governor?: Governor }): Promise{ + options.governor = this.governor; + // delay initialisation of demuxer until stream has been written to - avoids lock-up + return new Promise(resolve => setTimeout(async () => resolve(await beamcoder.demuxer(options)), 20)); + }; + +} + diff --git a/ts/frameDicer.ts b/ts/frameDicer.ts new file mode 100644 index 0000000..acfa5d3 --- /dev/null +++ b/ts/frameDicer.ts @@ -0,0 +1,113 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ +import { CodecContext } from "./types/CodecContext"; +import { Frame } from "./types/Frame"; +import beamcoder from './beamcoder' + +export default class frameDicer { + private doDice: boolean; + private lastBuf: Buffer[] = []; + private lastFrm: Frame = null as Frame; + private sampleBytes = 4; // Assume floating point 4 byte samples for now... + private nullBuf: Buffer[] = []; + private dstFrmBytes: number;// = dstNumSamples * this.sampleBytes; + private dstNumSamples: number;// = encoder.frame_size; + + constructor(encoder: CodecContext, private isAudio: boolean) { + const numChannels = encoder.channels; + this.dstNumSamples = encoder.frame_size; + this.dstFrmBytes = this.dstNumSamples * this.sampleBytes; + this.doDice = false === beamcoder.encoders()[encoder.name].capabilities.VARIABLE_FRAME_SIZE; + + for (let b = 0; b < numChannels; ++b) + this.nullBuf.push(Buffer.alloc(0)); + } + + private addFrame(srcFrm: Frame): Frame[] { + let result: Frame[] = []; + let dstFrm: Frame; + let curStart = 0; + if (!this.lastFrm) { + this.lastFrm = beamcoder.frame(srcFrm.toJSON()); + this.lastBuf = this.nullBuf; + this.dstFrmBytes = this.dstNumSamples * this.sampleBytes; + } + + if (this.lastBuf[0].length > 0) + dstFrm = beamcoder.frame(this.lastFrm.toJSON()); + else + dstFrm = beamcoder.frame(srcFrm.toJSON()); + dstFrm.nb_samples = this.dstNumSamples; + dstFrm.pkt_duration = this.dstNumSamples; + + while (curStart + this.dstFrmBytes - this.lastBuf[0].length <= srcFrm.nb_samples * this.sampleBytes) { + const resFrm = beamcoder.frame(dstFrm.toJSON()); + resFrm.data = this.lastBuf.map((d, i) => + Buffer.concat([ + d, srcFrm.data[i].slice(curStart, curStart + this.dstFrmBytes - d.length)], + this.dstFrmBytes)); + result.push(resFrm); + + dstFrm.pts += this.dstNumSamples; + dstFrm.pkt_dts += this.dstNumSamples; + curStart += this.dstFrmBytes - this.lastBuf[0].length; + this.lastFrm.pts = 0; + this.lastFrm.pkt_dts = 0; + this.lastBuf = this.nullBuf; + } + + this.lastFrm.pts = dstFrm.pts; + this.lastFrm.pkt_dts = dstFrm.pkt_dts; + this.lastBuf = srcFrm.data.map(d => d.slice(curStart, srcFrm.nb_samples * this.sampleBytes)); + + return result; + }; + + private getLast(): Frame[] { + let result: Frame[] = []; + if (this.lastBuf[0].length > 0) { + const resFrm = beamcoder.frame(this.lastFrm.toJSON()); + resFrm.data = this.lastBuf.map(d => d.slice(0)); + resFrm.nb_samples = this.lastBuf[0].length / this.sampleBytes; + resFrm.pkt_duration = resFrm.nb_samples; + this.lastFrm.pts = 0; + this.lastBuf = this.nullBuf; + result.push(resFrm); + } + return result; + }; + + public dice(frames: Frame[], flush = false): Frame[] { + if (this.isAudio && this.doDice) { + let result: Frame[] = []; + for (const frm of frames) + this.addFrame(frm).forEach(f => result.push(f)); + if (flush) + this.getLast().forEach(f => result.push(f)); + return result; + } + return frames; + }; +} + + + diff --git a/ts/index.ts b/ts/index.ts new file mode 100644 index 0000000..9051d6f --- /dev/null +++ b/ts/index.ts @@ -0,0 +1,56 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ + +import beamcoder from './beamcoder'; + +export { default as demuxerStream } from './DemuxerStream'; +export { default as muxerStream } from './MuxerStream'; + +export { default as DemuxerStream } from './DemuxerStream'; +export { default as MuxerStream } from './MuxerStream'; + +export { default as makeSources } from './makeSources'; +export { default as makeStreams } from './makeStreams'; + +export { getRaw, getHTML } from './utils'; + +// Provide useful debug on segfault-related crash +import SegfaultHandler from 'segfault-handler'; +SegfaultHandler.registerHandler('crash.log'); + +export function splash() { + const splash = `Aerostat Beam Coder Copyright (C) 2019 Streampunk Media Ltd + GPL v3.0 or later license. This program comes with ABSOLUTELY NO WARRANTY. + This is free software, and you are welcome to redistribute it + under certain conditions. Conditions and warranty at: + https://github.com/Streampunk/beamcoder/blob/master/LICENSE`; + + console.log(splash); + console.log('Using FFmpeg version', beamcoder.avVersionInfo()); +} + +export { Codec, CodecContext, CodecPar, Decoder, DecodedFrames, Demuxer, EncodedPackets, Encoder, Filter } from './types'; +export { MediaType, FilterLink, FilterContext, FilterGraph, Filterer, InputFormat, OutputFormat, Frame } from './types'; +export { SampleFormat, HWDeviceContext, HWFramesContext, Muxer, Packet, PrivClass, Disposition, Stream } from './types'; +export type { governor} from './types'; + +export default beamcoder; diff --git a/ts/install_ffmpeg.ts b/ts/install_ffmpeg.ts new file mode 100644 index 0000000..00d4517 --- /dev/null +++ b/ts/install_ffmpeg.ts @@ -0,0 +1,165 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg. + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ + +import os from 'os'; +import fs from 'fs'; +import path from 'path'; +import util from 'util'; +import child_process, { ChildProcess } from 'child_process'; +import { getHTML, getRaw } from './utils'; +const { mkdir, access, rename } = fs.promises; +const [execFile, exec] = [child_process.execFile, child_process.exec].map(util.promisify); +import unzip from 'unzipper'; + +async function inflate(rs: NodeJS.ReadableStream, folder: string, name: string): Promise { + // const unzip = require('unzipper'); + const directory = await unzip.Open.file(`${folder}/${name}.zip`); + const dest = path.resolve(`./${folder}/${name}`); + const directoryName = directory.files[0].path; + return new Promise((comp, err) => { + console.log(`Unzipping '${folder}/${name}.zip'.`); + rs.pipe(unzip.Extract({ path: folder }).on('close', async () => { + await rename(`./${folder}/${directoryName}`, dest) + console.log(`Unzipping of '${folder}/${name}.zip' to ${dest} completed.`); + comp(); + })); + rs.on('error', err); + }); +} + +async function win32(): Promise { + console.log('Checking/Installing FFmpeg dependencies for Beam Coder on Windows.'); + try { + await mkdir('ffmpeg'); + } catch (e) { + if (e.code !== 'EEXIST') { + throw e; + } + }; + const ffmpegFilename = 'ffmpeg-5.x-win64-shared'; + try { + const file = `ffmpeg/${ffmpegFilename}`; + await access(file, fs.constants.R_OK) + console.log(`${path.resolve(file)} Present, Ok`) + } catch (e) { + const html = await getHTML('https://github.com/BtbN/FFmpeg-Builds/wiki/Latest', 'latest autobuilds'); + const htmlStr = html.toString('utf-8'); + const m = htmlStr.match(/

win64-gpl-shared-5[0-9.]+<\/a><\/p>/); + if (!m) { + throw new Error('Failed to find latest v5.x autobuild from "https://github.com/BtbN/FFmpeg-Builds/wiki/Latest"'); + } + const downloadSource = m[1]; + const destZip = path.resolve(`ffmpeg/${ffmpegFilename}.zip`); + console.log(`Downloading ffmpeg zip to ${destZip}`); + let ws_shared = fs.createWriteStream(destZip); + try { + await getRaw(ws_shared, downloadSource, `${ffmpegFilename}.zip`) + } catch (err) { + if (err.name === 'RedirectError') { + const redirectURL = err.message; + await getRaw(ws_shared, redirectURL, `${ffmpegFilename}.zip`); + } else + throw err; + } + // await exec('npm install unzipper --no-save'); + let rs_shared = fs.createReadStream(destZip); + await inflate(rs_shared, 'ffmpeg', `${ffmpegFilename}`); + }; +} + +async function linux(): Promise { + console.log('Checking FFmpeg dependencies for Beam Coder on Linux.'); + const { stdout } = await execFile('ldconfig', ['-p']).catch(console.error); + let result = 0; + for (const fn of ['avcodec.so.59', 'avformat.so.59', 'avdevice.so.59', 'avfilter.so.8', 'avutil.so.57', 'postproc.so.56', 'swresample.so.4', 'swscale.so.6']) { + if (stdout.indexOf(`lib${fn}`) < 0) { + console.error(`lib${fn} is not installed.`); + result = 1; + } + } + if (result === 1) { + console.log(`Try running the following (Ubuntu/Debian): +sudo add-apt-repository ppa:jonathonf/ffmpeg-4 +sudo apt-get install libavcodec-dev libavformat-dev libavdevice-dev libavfilter-dev libavutil-dev libpostproc-dev libswresample-dev libswscale-dev`); + process.exit(1); + } + return result; +} + +async function darwin(): Promise<0> { + console.log('Checking for FFmpeg dependencies via HomeBrew.'); + let output: ChildProcess; + let returnMessage: string; + + try { + output = await exec('brew list ffmpeg'); + returnMessage = 'FFmpeg already present via Homebrew.'; + } catch (err) { + if ((err as { stderr: string }).stderr !== 'Error: No such keg: /usr/local/Cellar/ffmpeg\n') { + console.error(err); + console.log('Either Homebrew is not installed or something else is wrong.\nExiting'); + process.exit(1); + } + console.log('FFmpeg not installed. Attempting to install via Homebrew.'); + try { + output = await exec('brew install nasm pkg-config texi2html ffmpeg'); + returnMessage = 'FFmpeg installed via Homebrew.'; + } catch (err) { + console.log('Failed to install ffmpeg:\n'); + console.error(err); + process.exit(1); + } + } + console.log(output.stdout); + console.log(returnMessage); + return 0; +} + +switch (os.platform()) { + case 'win32': + if (os.arch() != 'x64') { + console.error('Only 64-bit platforms are supported.'); + process.exit(1); + } else { + win32().catch(console.error); + } + break; + case 'linux': + if (os.arch() != 'x64' && os.arch() != 'arm64') { + console.error('Only 64-bit platforms are supported.'); + process.exit(1); + } else { + linux(); + } + break; + case 'darwin': + if (os.arch() != 'x64' && os.arch() != 'arm64') { + console.error('Only 64-bit platforms are supported.'); + process.exit(1); + } else { + darwin(); + } + break; + default: + console.error(`Platfrom ${os.platform()} is not supported.`); + break; +} diff --git a/ts/makeSources.ts b/ts/makeSources.ts new file mode 100644 index 0000000..e0a194f --- /dev/null +++ b/ts/makeSources.ts @@ -0,0 +1,61 @@ + +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ + +import beamcoder from './beamcoder' +import { BeamstreamParams, BeamstreamSource, Demuxer } from './types'; +import readStream from './readStream'; +import DemuxerStream from './DemuxerStream'; + +/** + * Initialise the sources for the beamstream process. + * Note - the params object is updated by the function. + */ +export default async function makeSources(params: BeamstreamParams): Promise { + if (!params.video) params.video = []; + if (!params.audio) params.audio = []; + // collect All source stream from video and audio channels + const sources: Array = []; + for (const channel of [...params.video, ...params.audio]){ + channel.sources.forEach(src => sources.push(src)); + } + // demult all channels + const promises = sources.map((src: BeamstreamSource) => { + let p: Promise; + if (src.input_stream) { + const demuxerStream = new DemuxerStream({ highwaterMark: 1024 }); + src.input_stream.pipe(demuxerStream); + p = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); + } else { + p = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options }); + } + p = p.then((fmt) => { + src.format = fmt; + if (src.ms && !src.input_stream) + src.format.seek({ time: src.ms.start }); + return fmt; + }) + return p; + }); + await Promise.all(promises); + sources.forEach((src) => src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex)); +} diff --git a/ts/makeStreams.ts b/ts/makeStreams.ts new file mode 100644 index 0000000..8dc2c34 --- /dev/null +++ b/ts/makeStreams.ts @@ -0,0 +1,188 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ + +import beamcoder from './beamcoder' +import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, Filterer, FilterLink, Muxer } from './types'; +import serialBalancer from './serialBalancer'; +import runStreams from './runStreams'; +import MuxerStream from './MuxerStream'; + +/** + * Initialise the output streams for the beamstream process. + * Note - the params object is updated by the function. + * @returns Promise which resolves to an object with a run function that starts the processing + */ +export default async function makeStreams(params: BeamstreamParams): Promise<{ run(): Promise }> { + if (!params.video) params.video = []; + if (!params.audio) params.audio = []; + + params.video.forEach((channel: BeamstreamChannel) => { + channel.sources.forEach((src: BeamstreamSource) => + src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); + }); + params.audio.forEach((channel: BeamstreamChannel) => { + channel.sources.forEach((src: BeamstreamSource) => + src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); + }); + + const promises: Promise[] = []; + + // VIDEO + ////////// + params.video.forEach((channel: BeamstreamChannel) => { + const inputParams = channel.sources.map((src: BeamstreamSource, i: number) => { + const { codecpar, time_base, sample_aspect_ratio } = src.format.streams[src.streamIndex]; + return { + name: `in${i}:v`, + width: codecpar.width, + height: codecpar.height, + pixelFormat: codecpar.format, + timeBase: time_base, + pixelAspect: sample_aspect_ratio + }; + }); + const outputParams = channel.streams.map((str: BeamstreamStream, i: number) => ({ name: `out${i}:v`, pixelFormat: str.codecpar.format })); + const { filterSpec } = channel; + const prms = beamcoder.filterer({ filterType: 'video', inputParams, outputParams, filterSpec }) + promises.push(prms.then(filter => channel.filter = filter)); + }); + + // AUDIO + ////////// + params.audio.forEach((channel: BeamstreamChannel) => { + const inputParams = channel.sources.map((src: BeamstreamSource, i: number) => { + const { sample_rate, sample_fmt, channel_layout } = src.decoder; + return { + name: `in${i}:a`, + sampleRate: sample_rate, + sampleFormat: sample_fmt, + channelLayout: channel_layout, + timeBase: src.format.streams[src.streamIndex].time_base + }; + }); + const outputParams = channel.streams.map((str: BeamstreamStream, i: number) => { + const { sample_rate, format, channel_layout } = str.codecpar; + return { + name: `out${i}:a`, + sampleRate: sample_rate, + sampleFormat: format, + channelLayout: channel_layout + }; + }); + const { filterSpec } = channel; + const prms = beamcoder.filterer({ filterType: 'audio', inputParams, outputParams, filterSpec }) + promises.push(prms.then(filter => channel.filter = filter)); + }); + await Promise.all(promises); + + /** + * all channel filter are no filled + */ + + // params.video.forEach(p => console.log(p.filter.graph.dump())); + // params.audio.forEach(p => console.log(p.filter.graph.dump())); + + let mux: Muxer; + if (params.out.output_stream) { + let muxerStream = new MuxerStream({ highwaterMark: 1024 }); + muxerStream.pipe(params.out.output_stream); + mux = muxerStream.muxer({ format_name: params.out.formatName }); + } else { + mux = beamcoder.muxer({ format_name: params.out.formatName }); + } + params.video.forEach((channel: BeamstreamChannel) => { + channel.streams.forEach((str: BeamstreamStream, i: number) => { + const encParams = (channel.filter as Filterer).graph.filters.find(f => f.name === `out${i}:v`).inputs[0]; + str.encoder = beamcoder.encoder({ + name: str.name, + width: encParams.w, + height: encParams.h, + pix_fmt: encParams.format, + sample_aspect_ratio: encParams.sample_aspect_ratio, + time_base: encParams.time_base as [number, number], + // framerate: [encParams.time_base[1], encParams.time_base[0]], + // bit_rate: 2000000, + // gop_size: 10, + // max_b_frames: 1, + // priv_data: { preset: 'slow' } + priv_data: { crf: 23 } + }); // ... more required ... + }); + }); + + // VIDEO + ////////// + params.video.forEach((channel: BeamstreamChannel) => { + channel.streams.forEach((str: BeamstreamStream) => { + str.stream = mux.newStream({ + name: str.name, + time_base: str.time_base, + interleaved: true + }); // Set to false for manual interleaving, true for automatic + Object.assign(str.stream.codecpar, str.codecpar); + }); + }); + + // AUDIO + ////////// + params.audio.forEach((channel: BeamstreamChannel) => { + channel.streams.forEach((str: BeamstreamStream, i: number) => { + const encParams: FilterLink = (channel.filter as Filterer).graph.filters.find(f => f.name === `out${i}:a`).inputs[0]; + str.encoder = beamcoder.encoder({ + name: str.name, + sample_fmt: encParams.format, + sample_rate: encParams.sample_rate, + channel_layout: encParams.channel_layout, + flags: { GLOBAL_HEADER: mux.oformat.flags.GLOBALHEADER } + }); + str.codecpar.frame_size = str.encoder.frame_size; + }); + }); + + params.audio.forEach((channel: BeamstreamChannel) => { + channel.streams.forEach((str: BeamstreamStream) => { + str.stream = mux.newStream({ + name: str.name, + time_base: str.time_base, + interleaved: true + }); // Set to false for manual interleaving, true for automatic + Object.assign(str.stream.codecpar, str.codecpar); + }); + }); + // create runner + const run = async () => { + await mux.openIO({ + url: params.out.url ? params.out.url : '', + flags: params.out.flags ? params.out.flags : {} + }); + await mux.writeHeader({ options: params.out.options ? params.out.options : {} }); + + const muxBalancer = new serialBalancer(mux.streams.length); + const muxStreamPromises: Promise[] = []; + params.video.forEach(ch => muxStreamPromises.push(runStreams('video', ch.sources, ch.filter as Filterer, ch.streams, mux, muxBalancer))); + params.audio.forEach(ch => muxStreamPromises.push(runStreams('audio', ch.sources, ch.filter as Filterer, ch.streams, mux, muxBalancer))); + await Promise.all(muxStreamPromises); + + await mux.writeTrailer(); + } + return { run }; +} diff --git a/ts/muxerStream.ts b/ts/muxerStream.ts new file mode 100644 index 0000000..0f8089f --- /dev/null +++ b/ts/muxerStream.ts @@ -0,0 +1,67 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ +import beamcoder from './beamcoder' +import { Readable } from 'stream'; +import { governor, Muxer, MuxerCreateOptions } from './types'; + +/** + * Create a ReadableMuxerStream to allow streaming from a Muxer + * + * A [Node.js Readable stream](https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_readable_streams) + * allowing data to be streamed from the muxer to a file or other stream destination such as a network connection + * + * @param options.highwaterMark The maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource. + * @returns A ReadableMuxerStream that can be streamed from. + */ +export default class MuxerStream extends Readable { + private governor = new beamcoder.governor({ highWaterMark: 1 }); + constructor(params: { highwaterMark: number }) { + super({ + highWaterMark: params.highwaterMark || 16384, + read: size => { + (async () => { + const chunk = await this.governor.read(size); + if (0 === chunk.length) + this.push(null); + else + this.push(chunk); + }) + } + }) + this.on('end', () => this.governor.finish()); + this.on('error', console.error); + } + + + /** + * Create a demuxer for this source + * @param options a DemuxerCreateOptions object + * @returns a promise that resolves to a Demuxer when it has determined sufficient + * format details by consuming data from the source. The promise will wait indefinitely + * until sufficient source data has been read. + */ + public muxer(options?: MuxerCreateOptions & { governor?: governor }): Muxer { + options = options || {}; + options.governor = this.governor; + return beamcoder.muxer(options); + }; +} diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts new file mode 100644 index 0000000..281a9f0 --- /dev/null +++ b/ts/parallelBalancer.ts @@ -0,0 +1,106 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ + +import { Readable } from 'stream'; +import { DecodedFrames, Frame, Stream } from './types'; +import { Timable, Timables } from './types/time'; + +export type localFrame = { pkt?: Frame, ts: number, streamIndex: number, final?: boolean, resolve?: () => void }; +type localResult = { done: boolean, value?: { name: string, frames: Timables }[] & Timable }; + +export default class parallelBalancer extends Readable { + private pending: localFrame[] = []; + private resolveGet: null | ((result: localResult) => void) = null; + + get tag(): 'a' | 'v' { + return 'video' === this.streamType ? 'v' : 'a'; + } + + constructor(params: { name: string, highWaterMark: number }, private streamType: 'video' | 'audio', numStreams: number) { + const pullSet = () => new Promise(resolve => this.makeSet(resolve)); + super({ + objectMode: true, + highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, + async read() { + const start = process.hrtime(); + const reqTime = start[0] * 1e3 + start[1] / 1e6; + const result = await pullSet(); + if (result.done) + this.push(null); + else { + result.value.timings = result.value[0].frames[0].timings; + result.value.timings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; + this.push(result.value); + } + }, + }) + // initialise with negative ts and no pkt + // - there should be no output until each stream has sent its first packet + for (let s = 0; s < numStreams; ++s) + this.pending.push({ ts: -Number.MAX_VALUE, streamIndex: s }); + } + + private makeSet(resolve: (result: localResult) => void): void { + if (!resolve) + return; + // console.log('makeSet', pending.map(p => p.ts)); + const nextPends = this.pending.every(pend => pend.pkt) ? this.pending : null; + const final = this.pending.filter(pend => true === pend.final); + if (nextPends) { + nextPends.forEach(pend => pend.resolve()); + resolve({ + value: nextPends.map(pend => { + return { name: `in${pend.streamIndex}:${this.tag}`, frames: [pend.pkt] }; + }), + done: false + }); + this.resolveGet = null; + this.pending.forEach(pend => Object.assign(pend, { pkt: null, ts: Number.MAX_VALUE })); + } else if (final.length > 0) { + final.forEach(f => f.resolve()); + resolve({ done: true }); + } else { + this.resolveGet = resolve; + } + }; + + private async pushPkt(pkt: Frame, streamIndex: number, ts: number): Promise { + return new Promise(resolve => { + Object.assign(this.pending[streamIndex], { pkt, ts, final: pkt ? false : true, resolve }); + this.makeSet(this.resolveGet); + }) + } + + public async pushPkts(packets: null | DecodedFrames, stream: Stream, streamIndex: number, final = false): Promise { + if (packets && packets.frames.length) { + let lst: localFrame = { ts: -Number.MAX_VALUE, streamIndex: 0 }; + for (const pkt of packets.frames) { + const ts = pkt.pts * stream.time_base[0] / stream.time_base[1]; + pkt.timings = packets.timings; + lst = await this.pushPkt(pkt, streamIndex, ts); + } + return lst; + } else if (final) { + return this.pushPkt(null, streamIndex, Number.MAX_VALUE); + } + } +} diff --git a/ts/readStream.ts b/ts/readStream.ts new file mode 100644 index 0000000..66e401a --- /dev/null +++ b/ts/readStream.ts @@ -0,0 +1,54 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ +import { Readable } from 'stream'; +import { Demuxer } from './types/Demuxer'; +import { Packet } from './types/Packet'; + +export default function readStream(params: { highWaterMark?: number }, demuxer: Demuxer, ms: { end: number }, index: number): Readable { + const time_base = demuxer.streams[index].time_base; + const end_pts = ms ? ms.end * time_base[1] / time_base[0] : Number.MAX_SAFE_INTEGER; + async function getPacket(): Promise { + let packet: Packet = {} as Packet; + do { packet = await demuxer.read(); } + while (packet && packet.stream_index !== index); + return packet; + } + + return new Readable({ + objectMode: true, + highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, + read() { + (async () => { + const start = process.hrtime(); + const reqTime = start[0] * 1e3 + start[1] / 1e6; + const packet = await getPacket(); + if (packet && (packet.pts < end_pts)) { + packet.timings = {}; + packet.timings.read = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; + this.push(packet); + } else + this.push(null); + })(); + } + }); + } + \ No newline at end of file diff --git a/ts/runStreams.ts b/ts/runStreams.ts new file mode 100644 index 0000000..deaa8c5 --- /dev/null +++ b/ts/runStreams.ts @@ -0,0 +1,117 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ +import teeBalancer from './teeBalancer'; +import { BalanceResult } from './teeBalancer'; +import { default as parallelBalancer, localFrame } from './parallelBalancer'; +import serialBalancer from './serialBalancer'; +import { Stream } from './types/Stream'; +import { DecodedFrames } from './types/Decoder'; +import { Frame } from './types/Frame'; +import { Packet } from './types/Packet'; +import { TotalTimed } from './types/time'; +import { BeamstreamSource, BeamstreamStream } from './types/Beamstreams'; +import { Filterer, FiltererResult } from './types/Filter'; +import { Timable, Timables } from './types/time' +import { EncodedPackets } from './types/Encoder'; + +import frameDicer from './frameDicer'; +import transformStream from './transformStream'; +import writeStream from './writeStream'; +import { Muxer } from './types/Muxer'; + +export default function runStreams( + streamType: 'video' | 'audio', + sources: Array, + filterer: Filterer, + streams: Array, + mux: Muxer, + muxBalancer: serialBalancer): Promise { + return new Promise((resolve, reject) => { + if (!sources.length) + return resolve(); + + const timeBaseStream: Stream = sources[0].format.streams[sources[0].streamIndex]; + const filterBalancer = new parallelBalancer({ name: 'filterBalance', highWaterMark: 1 }, streamType, sources.length); + + sources.forEach((src: BeamstreamSource, srcIndex: number) => { + const decStream = transformStream>( + { name: 'decode', highWaterMark: 1 }, + (pkts: Packet) => src.decoder.decode(pkts), + () => src.decoder.flush(), reject); + + const filterSource = writeStream( + { name: 'filterSource', highWaterMark: 1 }, + (pkts: DecodedFrames) => filterBalancer.pushPkts(pkts, src.format.streams[src.streamIndex], srcIndex, false), + () => filterBalancer.pushPkts(null, src.format.streams[src.streamIndex], srcIndex, true), + reject + ); + + src.stream.pipe(decStream).pipe(filterSource); + }); + + const streamTee = teeBalancer({ name: 'streamTee', highWaterMark: 1 }, streams.length); + + const filtStream = transformStream, Timable & Promise & TotalTimed>>({ name: 'filter', highWaterMark: 1 }, (frms: Timables) => { + if (filterer.cb) filterer.cb(frms[0].frames[0].pts); + // @ts-ignore + return filterer.filter(frms); + }, () => { }, reject); + + const streamSource = writeStream, BalanceResult | void>( + { name: 'streamSource', highWaterMark: 1 }, + frms => streamTee.pushFrames(frms), + () => streamTee.pushFrames([], true), + reject + ); + + filterBalancer.pipe(filtStream).pipe(streamSource); + streams.forEach((str: BeamstreamStream, i: number) => { + const dicer = new frameDicer(str.encoder, 'audio' === streamType); + + const diceStream = transformStream, Timables>( + { name: 'dice', highWaterMark: 1 }, + (frms) => dicer.dice(frms), + () => dicer.dice([], true), + reject + ); + + const encStream = transformStream, Timable & Promise>( + { name: 'encode', highWaterMark: 1 }, + (frms) => str.encoder.encode(frms), + () => str.encoder.flush(), + reject + ); + + const muxStream = writeStream( + { name: 'mux', highWaterMark: 1 }, + (pkts: EncodedPackets) => muxBalancer.writePkts(pkts, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts)), + () => muxBalancer.writePkts(null, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts), true), + reject + ); + + muxStream.on('finish', resolve); + + streamTee[i].pipe(diceStream).pipe(encStream).pipe(muxStream); + }); + }); + } + \ No newline at end of file diff --git a/ts/serialBalancer.ts b/ts/serialBalancer.ts new file mode 100644 index 0000000..5d1d91a --- /dev/null +++ b/ts/serialBalancer.ts @@ -0,0 +1,71 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ + +import { EncodedPackets, Packet, Stream } from "./types"; + +export default class serialBalancer { + pending: { ts: number, streamIndex: number, resolve?: (result: any) => void, pkt?: Packet | null }[] = []; + + constructor(numStreams: number) { + // initialise with negative ts and no pkt + // - there should be no output until each stream has sent its first packet + for (let s = 0; s < numStreams; ++s) + this.pending.push({ ts: -Number.MAX_VALUE, streamIndex: s }); + } + + private adjustTS(pkt: { pts: number, dts: number, duration: number }, srcTB: [number, number], dstTB: [number, number]) { + const adj = (srcTB[0] * dstTB[1]) / (srcTB[1] * dstTB[0]); + pkt.pts = Math.round(pkt.pts * adj); + pkt.dts = Math.round(pkt.dts * adj); + pkt.duration > 0 ? Math.round(pkt.duration * adj) : Math.round(adj); + }; + + private pullPkts(pkt: null | Packet, streamIndex: number, ts: number): Promise { + return new Promise(resolve => { + Object.assign(this.pending[streamIndex], { pkt, ts, resolve }); + const minTS = this.pending.reduce((acc, pend) => Math.min(acc, pend.ts), Number.MAX_VALUE); + // console.log(streamIndex, pending.map(p => p.ts), minTS); + const nextPend = this.pending.find(pend => pend.pkt && (pend.ts === minTS)); + if (nextPend) nextPend.resolve(nextPend.pkt); + if (!pkt) resolve(undefined); + }); + }; + + public async writePkts( + packets: EncodedPackets | null, + srcStream: Stream, + dstStream: Stream, + writeFn: (r: Packet) => Promise, + final = false + ): Promise { + if (packets && packets.packets.length) { + for (const pkt of packets.packets) { + pkt.stream_index = dstStream.index; + this.adjustTS(pkt, srcStream.time_base, dstStream.time_base); + const pktTS = pkt.pts * dstStream.time_base[0] / dstStream.time_base[1]; + const packet = await this.pullPkts(pkt, dstStream.index, pktTS); + await writeFn(packet as Packet); + } + } else if (final) + return this.pullPkts(null, dstStream.index, Number.MAX_VALUE); + }; +} diff --git a/ts/teeBalancer.ts b/ts/teeBalancer.ts new file mode 100644 index 0000000..ec7027a --- /dev/null +++ b/ts/teeBalancer.ts @@ -0,0 +1,99 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ +import { Readable } from "stream"; +import { Frame } from "./types/Frame"; +import { Timable, Timables, Timing, TotalTimed } from "./types/time"; + +export type BalanceResult = { value: { timings: Timing }, done: boolean, final?: boolean }; + +type teeBalancerType = Readable[] & { pushFrames: (frames: Timables, unusedFlag?: boolean) => Promise }; + +export default function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, numStreams: number): teeBalancerType { + let resolvePush: null | ((result?: BalanceResult) => void) = null; + const pending: Array<{ frames: null | Frame, resolve: null | ((result: { value?: Frame, done: boolean }) => void), final: boolean }> = []; + for (let s = 0; s < numStreams; ++s) + pending.push({ frames: null, resolve: null, final: false }); + + const pullFrame = async (index: number) => { + + return new Promise<{ done: boolean, value?: Frame | null }>(resolve => { + if (pending[index].frames) { + resolve({ value: pending[index].frames, done: false }); + Object.assign(pending[index], { frames: null, resolve: null }); + } else if (pending[index].final) + resolve({ done: true }); + else + pending[index].resolve = resolve; + + if (resolvePush && pending.every(p => null === p.frames)) { + resolvePush(); + resolvePush = null; + } + }); + }; + + const readStreams: teeBalancerType = [] as teeBalancerType; + for (let s = 0; s < numStreams; ++s) + readStreams.push(new Readable({ + objectMode: true, + highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, + read() { + (async () => { + const start = process.hrtime(); + const reqTime = start[0] * 1e3 + start[1] / 1e6; + const result = await pullFrame(s); + if (result.done) + this.push(null); + else { + result.value.timings[params.name] = { reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; + this.push(result.value); + } + })(); + }, + })); + + // {frames: Frame[], name: string} + readStreams.pushFrames = (frames: Array & TotalTimed & Timable): Promise => { + return new Promise(resolve => { + pending.forEach((p, index) => { + if (frames.length) + p.frames = frames[index].frames; + else + p.final = true; + }); + + pending.forEach(p => { + if (p.resolve) { + if (p.frames) { + p.frames.timings = frames.timings; + p.resolve({ value: p.frames, done: false }); + } else if (p.final) + p.resolve({ done: true }); + } + Object.assign(p, { frames: null, resolve: null }); + }); + resolvePush = resolve; + }); + }; + + return readStreams; +} diff --git a/ts/transformStream.ts b/ts/transformStream.ts new file mode 100644 index 0000000..f3957b6 --- /dev/null +++ b/ts/transformStream.ts @@ -0,0 +1,53 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ +import { Transform } from 'stream'; +import { Timable } from './types/time' + +export default function transformStream( + params: { name: 'encode' | 'dice' | 'decode' | 'filter', highWaterMark: number }, + processFn: (val: SRC) => DST, + flushFn: () => DST | null | void, + reject: (err?: Error) => void) { + return new Transform({ + objectMode: true, + highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, + transform(val: SRC, encoding, cb) { + (async () => { + const start = process.hrtime(); + const reqTime = start[0] * 1e3 + start[1] / 1e6; + const result = await processFn(val); + result.timings = val.timings; + if (result.timings) + result.timings[params.name] = { reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; + cb(null, result); + })().catch(cb); + }, + flush(cb) { + (async () => { + const result = flushFn ? await flushFn() : null; + if (result) result.timings = {}; + cb(null, result); + })().catch(cb); + } + }).on('error', err => reject(err)); + } + \ No newline at end of file diff --git a/ts/types/BeamcoderType.d.ts b/ts/types/BeamcoderType.d.ts new file mode 100644 index 0000000..e0245be --- /dev/null +++ b/ts/types/BeamcoderType.d.ts @@ -0,0 +1,344 @@ +import { Codec } from "./Codec"; +import { CodecPar } from "./CodecPar"; +import { Decoder } from "./Decoder"; +import { Demuxer, DemuxerCreateOptions } from "./Demuxer"; +import { Encoder } from "./Encoder"; +import { FormatContext, InputFormat, OutputFormat } from "./FormatContext"; +import { Frame, PixelFormat, SampleFormat } from "./Frame"; +import { Packet, PacketFlags } from "./Packet"; +import { PrivClass } from "./PrivClass"; +import { WritableDemuxerStream, ReadableMuxerStream, BeamstreamParams } from './Beamstreams'; +import { Filter, Filterer, FiltererAudioOptions, FiltererVideoOptions } from "./Filter"; +import { commonEncoderParms } from './params'; +import { Muxer, MuxerCreateOptions } from "./Muxer"; +import type { Governor } from './Governor'; + +export interface BeamcoderType extends ReadableMuxerStream { + /** Create object for AVIOContext based buffered I/O */ + governor: typeof Governor; + /** + * FFmpeg version string. This usually is the actual release + * version number or a git commit description. This string has no fixed format + * and can change any time. It should never be parsed by code. + */ + avVersionInfo(): string + /** + * Create a demuxer for this source + * @param options a DemuxerCreateOptions object + * @returns a promise that resolves to a Demuxer when it has determined sufficient + * format details by consuming data from the source. The promise will wait indefinitely + * until sufficient source data has been read. + */ + // this code look to have error.... + demuxer(options: { governor?: Governor, url?: string, iformat?: InputFormat, options?: { governor: Governor } } | string): Promise + // url: src.url, iformat: src.iformat, options: src.options + /** + * Create a WritableDemuxerStream to allow streaming to a Demuxer + * @param options.highwaterMark Buffer level when `stream.write()` starts returng false. + * @returns A WritableDemuxerStream that can be streamed to. + */ + demuxerStream(options: { highwaterMark?: number }): WritableDemuxerStream; + /** + * Initialise the sources for the beamstream process. + * Note - the params object is updated by the function. + */ + makeSources(params: BeamstreamParams): Promise + /** + * Initialise the output streams for the beamstream process. + * Note - the params object is updated by the function. + * @returns Promise which resolves to an object with a run function that starts the processing + */ + makeStreams(params: BeamstreamParams): Promise<{ run(): Promise }> + /** + * Create a ReadableMuxerStream to allow streaming from a Muxer + * @param options.highwaterMark The maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource. + * @returns A ReadableMuxerStream that can be streamed from. + */ + muxerStream(options: { highwaterMark?: number }): ReadableMuxerStream; + /** + * Create a filterer + * @param options parameters to set up the type, inputs, outputs and spec of the filter + * @returns Promise that resolve to a Filterer on success + */ + filterer(options: FiltererVideoOptions | FiltererAudioOptions): Promise + + /** + * Provides a list and details of all the available decoders + * @returns an object with name and details of each of the available decoders + */ + decoders(): { [key: string]: Codec } + /** + * Create a decoder by name + * @param name The codec name required + * @param ... Any non-readonly parameters from the Decoder object as required + * @returns A Decoder object - note creation is synchronous + */ + // decoder(options: { demuxer: Demuxer | Promise, stream_index: number }): Decoder // { name: string, [key: string]: any } + decoder(options: { name: string, [key: string]: any }): Decoder + + /** + * Create a decoder by codec_id + * @param codec_id The codec ID from AV_CODEC_ID_xxx + * @param ... Any non-readonly parameters from the Decoder object as required + * @returns A Decoder object - note creation is synchronous + */ + decoder(options: { codec_id: number, [key: string]: any }): Decoder + /** + * Create a decoder from a demuxer and a stream_index + * @param demuxer An initialised Demuxer object + * @param stream_index The stream number of the demuxer object to be used to initialise the decoder + * @param ... Any non-readonly parameters from the Decoder object as required + * @returns A Decoder object - note creation is synchronous + */ + decoder(options: { demuxer: Demuxer, stream_index: number, [key: string]: any }): Decoder + /** + * Create a decoder from a CodecPar object + * @param params CodecPar object whose codec name or id will be used to initialise the decoder + * @param ... Any non-readonly parameters from the Decoder object as required + * @returns A Decoder object - note creation is synchronous + */ + decoder(options: { params: CodecPar, [key: string]: any }): Decoder + /** + * Create a frame for encoding or filtering + * Set parameters as required from the Frame object + */ + frame(options: { [key: string]: any, data?: Array } | string): Frame; + /** + * Provides a list and details of all the available encoders + * @returns an object with name and details of each of the available encoders + */ + encoders(): { [key: string]: Codec }; + /** + * Create an encoder by name + * @param name The codec name required + * @param ... Any non-readonly parameters from the Encoder object as required + * @returns An Encoder object - note creation is synchronous + */ + encoder(options: commonEncoderParms & { name: string }): Encoder + /** + * Create an encoder by codec_id + * @param codec_id The codec ID from AV_CODEC_ID_xxx + * @param ... Any non-readonly parameters from the Encoder object as required + * @returns An Encoder object - note creation is synchronous + */ + encoder(options: commonEncoderParms & { codec_id: number }): Encoder + /** + * Packets for decoding can be created without reading them from a demuxer + * Set parameters as required from the Packet object, passing in a buffer and the required size in bytes + */ + packet(options?: string | { + flags?: Partial, + pts?: number, + dts?: number, + stream_index?: number, + data: Buffer, + size?: number, + side_data?: any, + [key: string]: any, + }): Packet + + /** List the available codecs */ + codecs(): { [key: string]: { encoder?: Codec, decoder?: Codec } } + + /** + * Provides a list and details of all the available demuxer input formats + * @returns an object with details of all the available demuxer input formats + */ + demuxers(): { [key: string]: InputFormat } + + /** + * Create a demuxer to read from a URL or filename + * @param url a string describing the source to be read from (may contain %d for a sequence of numbered files). + * @returns a promise that resolves to a Demuxer when it has determined sufficient + * format details by consuming data from the source. The promise will wait indefinitely + * until sufficient source data has been read. + */ + demuxer(url: string): Promise + + /** + * For formats that require additional metadata, such as the rawvideo format, + * it may be necessary to pass additional information such as image size or pixel format to Demuxer creation. + * @param options a DemuxerCreateOptions object + * @returns a promise that resolves to a Demuxer when it has determined sufficient + * format details by consuming data from the source. The promise will wait indefinitely + * until sufficient source data has been read. + */ + demuxer(options: DemuxerCreateOptions): Promise + + /** + * Provides a list and details of all the available encoders + * @returns an object with name and details of each of the available encoders + */ + encoders(): { [key: string]: Codec } + /** + * Create an encoder by name + * @param name The codec name required + * @param ... Any non-readonly parameters from the Encoder object as required + * @returns An Encoder object - note creation is synchronous + */ + encoder(options: { name: string, [key: string]: any }): Encoder + /** + * Create an encoder by codec_id + * @param codec_id The codec ID from AV_CODEC_ID_xxx + * @param ... Any non-readonly parameters from the Encoder object as required + * @returns An Encoder object - note creation is synchronous + */ + encoder(options: { codec_id: number, [key: string]: any }): Encoder + /** + * Return the output format in the list of registered output formats which best matches the provided name, + * or return null if there is no match. + */ + guessFormat(name: string): OutputFormat | null; + + format(options?: string | { [key: string]: any }): FormatContext; + /** + * Create a frame for encoding or filtering + * Set parameters as required from the Frame object + */ + frame(options?: string | { [key: string]: any, data?: Array }): Frame + + /** Format details for all supported pixel format names */ + pix_fmts(): { [key: string]: PixelFormat } + /** Format details for all supported sample format names */ + sample_fmts(): { [key: string]: SampleFormat } + /** + * Note that when creating buffers from Javascript, + * FFmpeg recommends that a small amount of headroom is added to the minimum length of each buffer. + * The minimum amount of padding is exposed to Javascript as constant + */ + AV_INPUT_BUFFER_PADDING_SIZE: number; + + /** + * Create a muxer to write to a URL or filename + * @param options a MuxerCreateOptions object + * @returns A Muxer object + */ + muxer(options: MuxerCreateOptions): Muxer + + + /** + * Provides a list and details of all the available muxer output formats + * @returns an object with details of all the available muxer output formats + */ + muxers(): { [key: string]: OutputFormat } + + + /** + * Provides a list and details of all the available filters + * @returns an object with name and details of each of the available filters + */ + filters(): { [key: string]: Filter } + + /** List the available bitstream filters */ + bsfs(): { + [key: string]: { + name: string + codec_ids: Array + priv_class: PrivClass | null + } + } + + /** + * Create a ReadableMuxerStream to allow streaming from a Muxer + * @param options.highwaterMark The maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource. + * @returns A ReadableMuxerStream that can be streamed from. + */ + muxerStream(options: { highwaterMark?: number }): ReadableMuxerStream + /** + * Initialise the sources for the beamstream process. + * Note - the params object is updated by the function. + */ + makeSources(params: BeamstreamParams): Promise + /** + * Initialise the output streams for the beamstream process. + * Note - the params object is updated by the function. + * @returns Promise which resolves to an object with a run function that starts the processing + */ + makeStreams(params: BeamstreamParams): Promise<{ run(): Promise }> + /** + * Create a WritableDemuxerStream to allow streaming to a Demuxer + * @param options.highwaterMark Buffer level when `stream.write()` starts returng false. + * @returns A WritableDemuxerStream that can be streamed to. + */ + demuxerStream(options: { highwaterMark?: number }): WritableDemuxerStream + + codecParameters(options?: string | Partial>): CodecPar; + // { [key: string]: any } + + /** + * Create a filterer + * @param options parameters to set up the type, inputs, outputs and spec of the filter + * @returns Promise that resolve to a Filterer on success + */ + filterer(options: FiltererVideoOptions | FiltererAudioOptions): Promise + + + AV_NOPTS_VALUE: number + + /** The LIBAV**_VERSION_INT for each FFmpeg library */ + versions(): { + avcodec: number + avdevice: number + avfilter: number + avformat: number + avutil: number + postproc: number + swresample: number + swscale: number + } + /** + * FFmpeg version string. This usually is the actual release + * version number or a git commit description. This string has no fixed format + * and can change any time. It should never be parsed by code. + */ + avVersionInfo(): string + /** Informative version strings for each FFmpeg library */ + versionStrings(): { + avcodec: string + avdevice: string + avfilter: string + avformat: string + avutil: string + postproc: string + swresample: string + swscale: string + } + /** Build configuration strings for each FFmpeg library */ + configurations(): { + avcodec: string + avdevice: string + avfilter: string + avformat: string + avutil: string + postproc: string + swresample: string + swscale: string + } + /** License strings for each FFmpeg library */ + licenses(): { + avcodec: string + avdevice: string + avfilter: string + avformat: string + avutil: string + postproc: string + swresample: string + swscale: string + } + /** List the available protocols */ + protocols(): { inputs: Array, outputs: Array } + + /** Read or set the logging level + * `quiet` - print no output. + * `panic` - something went really wrong - crash will follow + * `fatal` - recovery not possible + * `error` - lossless recovery not possible + * `warning` - something doesn't look correct + * `info` - standard information - the default + * `verbose` - detailed information + * `debug` - stuff which is only useful for libav* developers + * `trace` - extremely verbose debugging for libav* developers + */ + logging(level?: string): string | undefined +} + diff --git a/ts/types/Beamstreams.d.ts b/ts/types/Beamstreams.d.ts new file mode 100644 index 0000000..9fa71c9 --- /dev/null +++ b/ts/types/Beamstreams.d.ts @@ -0,0 +1,56 @@ +import { Demuxer, DemuxerCreateOptions } from "./Demuxer" +import { Muxer, MuxerCreateOptions } from "./Muxer" +import { InputFormat } from "./FormatContext" + +/** Source definition for a beamstream channel, from either a file or NodeJS ReadableStream */ +export interface BeamstreamSource { + url?: string + input_stream?: NodeJS.ReadableStream + ms?: { start: number, end: number } + streamIndex?: number + iformat?: InputFormat + options?: { [key: string]: any } + + format?: Demuxer; // filled by makeSources + stream?: Readable; // filled by makeSources + decoder?: Decoder; // FIXME +} +/** Codec definition for the destination channel */ +export interface BeamstreamStream { + name: string + time_base: Array + codecpar: { [key: string]: any } + + encoder?: Encoder; // filled by runStreams + stream?: Stream; // filled by runStreams + +} +/** Definition for a channel of beamstream processing */ +export interface BeamstreamChannel { + sources: Array + filterSpec: string + streams: Array + filter?: Filterer; // filled by makeStreams +} +/** + * Definition for a beamstream process consisting of a number of audio and video sources + * that are to be processed and multiplexed into an output file or stream + */ +export interface BeamstreamParams { + video?: Array + audio?: Array + /** Destination definition for the beamstream process, to either a file or NodeJS WritableStream */ + out: { + formatName: string + url?: string + output_stream?: NodeJS.WritableStream + flags?: { + READ?: boolean + WRITE?: boolean + NONBLOCK?: boolean + DIRECT?: boolean + } + options?: { [key:string]: any } + } +} + diff --git a/types/Codec.d.ts b/ts/types/Codec.d.ts similarity index 100% rename from types/Codec.d.ts rename to ts/types/Codec.d.ts diff --git a/types/CodecContext.d.ts b/ts/types/CodecContext.d.ts similarity index 99% rename from types/CodecContext.d.ts rename to ts/types/CodecContext.d.ts index 5f90969..1e483ac 100644 --- a/types/CodecContext.d.ts +++ b/ts/types/CodecContext.d.ts @@ -65,7 +65,7 @@ export interface CodecContext { * vop_time_increment_resolution and fixed_vop_rate * (fixed_vop_rate == 0 implies that it is different from the framerate) */ - time_base: Array + time_base: [number, number] /** * For some codecs, the time base is closer to the field rate than the frame rate. * Most notably, H.264 and MPEG-2 specify time_base as half of frame duration @@ -407,7 +407,7 @@ export interface CodecContext { * For codecs that store a framerate value in the compressed * bitstream, the decoder may export it here. [ 0, 1 ] when unknown. */ - framerate: Array + framerate: [number, number] /** Nominal unaccelerated pixel format, see AV_PIX_FMT_xxx. */ readonly sw_pix_fmt: string | null /** Timebase in which pkt_dts/pts and Packet dts/pts are. */ @@ -552,4 +552,6 @@ export interface CodecContext { * used as reference pictures). */ extra_hw_frames: number + // private field + readonly _CodecContext: {}; } diff --git a/types/CodecPar.d.ts b/ts/types/CodecPar.d.ts similarity index 91% rename from types/CodecPar.d.ts rename to ts/types/CodecPar.d.ts index 6f06836..1389b75 100644 --- a/types/CodecPar.d.ts +++ b/ts/types/CodecPar.d.ts @@ -1,7 +1,9 @@ +import { toJSONAble } from "./time" + /** * CodecPar describes the properties of an encoded stream. */ -export interface CodecPar { +export interface CodecPar extends toJSONAble { /** Object name. */ readonly type: 'CodecParameters' /** General type of the encoded data. */ @@ -101,8 +103,10 @@ export interface CodecPar { trailing_padding: number /** Audio only. Number of samples to skip after a discontinuity. */ seek_preroll: number - /** Retun a JSON string containing the object properties. */ - toJSON(): string + // native code; + readonly _codecPar: {}; + } -export function codecParameters(options?: { [key: string]: any }): CodecPar; +export function codecParameters(options?: string | { [key: string]: any }): CodecPar; +// (options?: string | Partial>) \ No newline at end of file diff --git a/ts/types/DecodedFrames.d.ts b/ts/types/DecodedFrames.d.ts new file mode 100644 index 0000000..0d15c3b --- /dev/null +++ b/ts/types/DecodedFrames.d.ts @@ -0,0 +1,16 @@ +import { Frame } from "./Frame" +import { Timable, TotalTimed } from "./time" + +/** The DecodedFrames object is returned as the result of a decode operation */ +export interface DecodedFrames extends Timable, TotalTimed { + /** Object name. */ + readonly type: 'frames' + /** + * Decoded frames that are now available. If the array is empty, the decoder has buffered + * the packet as part of the process of producing future frames + */ + readonly frames: Array + + // "encode" | "dice" | "decode" | "filter" + // timings?: { [key: string]: Timing; }; +} diff --git a/types/Decoder.d.ts b/ts/types/Decoder.d.ts similarity index 97% rename from types/Decoder.d.ts rename to ts/types/Decoder.d.ts index 488cb72..dd33bde 100644 --- a/types/Decoder.d.ts +++ b/ts/types/Decoder.d.ts @@ -4,9 +4,10 @@ import { Frame } from "./Frame" import { Codec } from "./Codec" import { CodecContext } from "./CodecContext" import { Demuxer } from "./Demuxer" +import { Timable, TotalTimed } from "./time" /** The DecodedFrames object is returned as the result of a decode operation */ -export interface DecodedFrames { +export interface DecodedFrames extends Timable, TotalTimed { /** Object name. */ readonly type: 'frames' /** @@ -15,7 +16,7 @@ export interface DecodedFrames { */ readonly frames: Array /** Total time in microseconds that the decode operation took to complete */ - readonly total_time: number + // readonly total_time: number } export interface Decoder extends Omit + // this code look to have error.... + // duplicate governor splot + // TODO FIX ME + export function demuxer(options: { governor?: Governor, url?: string, iformat?: InputFormat, options?: { governor: Governor } } | string): Promise +//export function demuxer(url: string): Promise + /** Object to provide additional metadata on Demuxer creation */ export interface DemuxerCreateOptions { diff --git a/types/Encoder.d.ts b/ts/types/Encoder.d.ts similarity index 91% rename from types/Encoder.d.ts rename to ts/types/Encoder.d.ts index 35d5f74..cf6c912 100644 --- a/types/Encoder.d.ts +++ b/ts/types/Encoder.d.ts @@ -3,9 +3,10 @@ import { Packet } from "./Packet"; import { Frame } from "./Frame"; import { Codec } from "./Codec" import { CodecContext } from "./CodecContext" +import { Timable, TotalTimed } from "./time"; /** The EncodedPackets object is returned as the result of a encode operation */ -export interface EncodedPackets { +export interface EncodedPackets extends Timable, TotalTimed { /** Object name. */ readonly type: 'packets' /** @@ -84,11 +85,12 @@ export function encoders(): { [key: string]: Codec } * @param ... Any non-readonly parameters from the Encoder object as required * @returns An Encoder object - note creation is synchronous */ -export function encoder(options: { name: string, [key: string]: any }): Encoder +// export function encoder(options: { name: string, [key: string]: any }): Encoder +export function encoder(options: { name: string } & Partial>): Encoder /** * Create an encoder by codec_id * @param codec_id The codec ID from AV_CODEC_ID_xxx * @param ... Any non-readonly parameters from the Encoder object as required * @returns An Encoder object - note creation is synchronous */ -export function encoder(options: { codec_id: number, [key: string]: any }): Encoder +export function encoder(options: { codec_id: number } & Partial>): Encoder diff --git a/types/Filter.d.ts b/ts/types/Filter.d.ts similarity index 96% rename from types/Filter.d.ts rename to ts/types/Filter.d.ts index 13d56d6..9544f2b 100644 --- a/types/Filter.d.ts +++ b/ts/types/Filter.d.ts @@ -1,5 +1,6 @@ import { Frame } from "./Frame" import { PrivClass } from "./PrivClass" +import { Timable } from "./time" export interface Filter { readonly type: 'Filter' @@ -95,7 +96,7 @@ export interface FilterLink { /** video only - agreed upon image height */ readonly h?: number /** video only - agreed upon sample aspect ratio */ - readonly sample_aspect_ratio?: ReadonlyArray + readonly sample_aspect_ratio?: [number, number]; // ReadonlyArray /** audio only - number of channels in the channel layout. */ readonly channel_count?: number /** audio only - channel layout of current buffer */ @@ -210,7 +211,7 @@ export interface FiltererResult { readonly frames: Array } -export interface Filterer { +export interface Filterer extends Timable { readonly type: 'Filterer' readonly graph: FilterGraph @@ -222,7 +223,7 @@ export interface Filterer { * @param frames Array of Frame objects to be applied to the single input pad * @returns Array of objects containing Frame arrays for each output pad of the filter */ - filter(frames: Array): Promise & { total_time: number }> + filter(frames: Array): Promise & TotalTimed> /** * Filter an array of frames * Pass an array of objects, one per filter input, each with a name string property @@ -231,7 +232,11 @@ export interface Filterer { * @param framesArr Array of objects with name and Frame array for each input pad * @returns Array of objects containing Frame arrays for each output pad of the filter */ - filter(framesArr: Array<{ name: string, frames: Array }>): Promise & { total_time: number }> + filter(framesArr: Array<{ name?: string, frames: Array }>): Promise & { total_time: number }> + + // may add a callback + cb?: (pts: number | null) => void; + } /** diff --git a/types/FormatContext.d.ts b/ts/types/FormatContext.d.ts similarity index 97% rename from types/FormatContext.d.ts rename to ts/types/FormatContext.d.ts index eeac3a8..2ced8a8 100644 --- a/types/FormatContext.d.ts +++ b/ts/types/FormatContext.d.ts @@ -131,10 +131,14 @@ export function guessFormat(name: string): OutputFormat | null; export interface FormatContext { /** Object name. */ readonly type: string - /** The input format description. */ - iformat: InputFormat + /** The input format description. */ + set iformat(format: string); + //@ts-ignore + get iformat(): InputFormat /** The output format description. */ - oformat: OutputFormat + set oformat(format: string); + // @ts-ignore + get oformat(): OutputFormat /** Format private data. */ priv_data: { [key: string]: any @@ -366,7 +370,7 @@ export interface FormatContext { * to be initialised in the Stream object * @returns A Stream object */ - newStream(options: { name: string, [key: string]: any }): Stream + newStream(options: stinrg | { name: string, [key: string]: any }): Stream /** * Add a stream to the format with the next available stream index. * @param stream Source stream from which to copy the parameters for the new stream @@ -377,4 +381,4 @@ export interface FormatContext { toJSON(): string } -export function format(options: { [key: string]: any }): FormatContext +export function format(options?: string | { [key: string]: any }): FormatContext diff --git a/ts/types/FormatContextIn.d.ts b/ts/types/FormatContextIn.d.ts new file mode 100644 index 0000000..4f540f9 --- /dev/null +++ b/ts/types/FormatContextIn.d.ts @@ -0,0 +1,93 @@ +export interface FormatContextIn { + /** The input format description. */ + set iformat(format: string); + //@ts-ignore + get iformat(): InputFormat + /** + * Position of the first frame of the component, in + * AV_TIME_BASE fractional seconds. NEVER set this value directly: + * It is deduced from the AVStream values. Demuxing only + */ + readonly start_time: number + /** Maximum size of the data read from input for determining the input container format. */ + probesize: number + /** + * Maximum duration (in AV_TIME_BASE units) of the data read + * from input in avformat_find_stream_info(). + * Demuxing only, set by the caller before avformat_find_stream_info(). + * Can be set to 0 to let avformat choose using a heuristic. + */ + max_analyze_duration: number + + /** +* Maximum amount of memory in bytes to use for the index of each stream. +* If the index exceeds this size, entries will be discarded as +* needed to maintain a smaller size. This can lead to slower or less +* accurate seeking (depends on demuxer). +* Demuxers for which a full in-memory index is mandatory will ignore +* this. +*/ + max_index_size: number + /** The number of frames used for determining the framerate */ + fps_probe_size: number + /** + * Error recognition - higher values will detect more errors but may + * misdetect some more or less valid parts as errors. + */ + error_recognition: number + + /** Maximum number of packets to read while waiting for the first timestamp. */ + max_ts_probe: number + + /** + * forces the use of wallclock timestamps as pts/dts of packets + * This has undefined results in the presence of B frames. + */ + use_wallclock_as_timestamps: boolean + /** avio flags, used to force AVIO_FLAG_DIRECT. */ + avio_flags: { + READ?: boolean + WRITE?: boolean + NONBLOCK?: boolean + DIRECT?: boolean + } + /** + * The duration field can be estimated through various ways, and this field can be used + * to know how the duration was estimated. + */ + readonly duration_estimation_method: 'from_pts' | 'from_stream' | 'from_bitrate' + /** Skip initial bytes when opening stream */ + skip_initial_bytes: number + /** Correct single timestamp overflows */ + correct_ts_overflow: boolean + /** Force seeking to any (also non key) frames. */ + seek2any: boolean + /** + * format probing score. + * The maximal score is AVPROBE_SCORE_MAX, its set when the demuxer probes + * the format. + */ + readonly probe_score: number + /** number of bytes to read maximally to identify format. */ + format_probesize: number + /** + * ',' separated list of allowed decoders. + * If NULL then all are allowed + */ + codec_whitelist: string | null + /** + * ',' separated list of allowed demuxers. + * If NULL then all are allowed + */ + format_whitelist: string | null + /** ',' separated list of allowed protocols. */ + protocol_whitelist: string + /** ',' separated list of disallowed protocols. */ + protocol_blacklist: string + /** The maximum number of streams. */ + max_streams: number + /** Skip duration calcuation in estimate_timings_from_pts. */ + skip_estimate_duration_from_pts: boolean + // interleaved: true, + +} diff --git a/ts/types/FormatContextOut.d.ts b/ts/types/FormatContextOut.d.ts new file mode 100644 index 0000000..7dc9ace --- /dev/null +++ b/ts/types/FormatContextOut.d.ts @@ -0,0 +1,42 @@ +export interface FormatContextOut { + /** The output format description. */ + set oformat(format: string); + // @ts-ignore + get oformat(): OutputFormat + /** + * Maximum buffering duration for interleaving. + * + * To ensure all the streams are interleaved correctly, + * av_interleaved_write_frame() will wait until it has at least one packet + * for each stream before actually writing any packets to the output file. + * When some streams are "sparse" (i.e. there are large gaps between + * successive packets), this can result in excessive buffering. + * + * This field specifies the maximum difference between the timestamps of the + * first and the last packet in the muxing queue, above which libavformat + * will output a packet regardless of whether it has queued a packet for all + * the streams. + * + * Muxing only, set by the caller before avformat_write_header(). + */ + max_interleave_delta: number + /** + * Avoid negative timestamps during muxing. Any value of the AVFMT_AVOID_NEG_TS_* constants. + * Note, this only works when using av_interleaved_write_frame. (interleave_packet_per_dts is in use) + */ + avoid_negative_ts: 'auto' | 'make_non_negative' | 'make_zero' + /** Audio preload in microseconds. Note, not all formats support this and unpredictable things may happen if it is used when not supported. */ + audio_preload: number + /** Max chunk time in microseconds. Note, not all formats support this and unpredictable things may happen if it is used when not supported. */ + max_chunk_duration: number + /** Max chunk size in bytes Note, not all formats support this and unpredictable things may happen if it is used when not supported. */ + max_chunk_size: number + /** Flush the I/O context after each packet. */ + flush_packets: number + /** Number of bytes to be written as padding in a metadata header. */ + metadata_header_padding: number + // not exposing opaque + /** Output timestamp offset, in microseconds. */ + // may need to be remove ?? + output_ts_offset: number +} diff --git a/types/Frame.d.ts b/ts/types/Frame.d.ts similarity index 98% rename from types/Frame.d.ts rename to ts/types/Frame.d.ts index 5dac86c..525f8a8 100644 --- a/types/Frame.d.ts +++ b/ts/types/Frame.d.ts @@ -1,9 +1,10 @@ import { HWFramesContext } from "./HWContext"; +import { Timable, toJSONAble } from "./time"; /** * This object describes decoded (raw) audio or video data. */ -export interface Frame { +export interface Frame extends Timable, toJSONAble { /** Object name. */ readonly type: 'Frame' /** @@ -181,7 +182,7 @@ export interface Frame { * Create a frame for encoding or filtering * Set parameters as required from the Frame object */ -export function frame(options: { [key: string]: any, data?: Array }): Frame +export function frame(options?: { [key: string]: any, data?: Array } | string): Frame /** Pixel format description */ export interface PixelFormat { diff --git a/ts/types/Governor.d.ts b/ts/types/Governor.d.ts new file mode 100644 index 0000000..8d3d8bb --- /dev/null +++ b/ts/types/Governor.d.ts @@ -0,0 +1,12 @@ +/** + * Create object for AVIOContext based buffered I/O + * + * Type only definition for Governor class + */ + export class governor { + constructor(options: { highWaterMark?: number }); + read(len: number): Promise + write(data: Buffer): Promise + finish(): undefined + private _adaptor: unknown; +} diff --git a/types/HWContext.d.ts b/ts/types/HWContext.d.ts similarity index 100% rename from types/HWContext.d.ts rename to ts/types/HWContext.d.ts diff --git a/types/Muxer.d.ts b/ts/types/Muxer.d.ts similarity index 98% rename from types/Muxer.d.ts rename to ts/types/Muxer.d.ts index fed21bf..87b8ce5 100644 --- a/types/Muxer.d.ts +++ b/ts/types/Muxer.d.ts @@ -31,10 +31,10 @@ export interface Muxer extends Omit diff --git a/types/Packet.d.ts b/ts/types/Packet.d.ts similarity index 91% rename from types/Packet.d.ts rename to ts/types/Packet.d.ts index 1e043ce..2d0e9da 100644 --- a/types/Packet.d.ts +++ b/ts/types/Packet.d.ts @@ -1,3 +1,5 @@ +import { Timable, toJSONAble } from "./time" + /** * This object stores compressed data. It is typically exported by demuxers * and then passed as input to decoders, or received as output from encoders and @@ -8,9 +10,12 @@ * packets, with no compressed data, containing only side data * (e.g. to update some stream parameters at the end of encoding). */ -export interface Packet { +export interface Packet extends Timable, toJSONAble { /** Object name. */ readonly type: 'Packet' + // internal data + readonly _packet: {}; + /** * Presentation timestamp in AVStream->time_base units the time at which * the decompressed packet will be presented to the user. @@ -47,7 +52,7 @@ export interface Packet { * decoder state but are not required for output and should be dropped * after decoding. **/ - DISCARD: boolean + DISCARD: boolean /** * The packet comes from a trusted source. * @@ -79,4 +84,4 @@ export interface Packet { * Packets for decoding can be created without reading them from a demuxer * Set parameters as required from the Packet object, passing in a buffer and the required size in bytes */ -export function packet(options: { [key: string]: any, data: Buffer, size: number }): Packet +export function packet(options?:string | { [key: string]: any, data: Buffer, size: number }): Packet diff --git a/types/PrivClass.d.ts b/ts/types/PrivClass.d.ts similarity index 100% rename from types/PrivClass.d.ts rename to ts/types/PrivClass.d.ts diff --git a/types/Stream.d.ts b/ts/types/Stream.d.ts similarity index 97% rename from types/Stream.d.ts rename to ts/types/Stream.d.ts index feb8da0..b5a77ad 100644 --- a/types/Stream.d.ts +++ b/ts/types/Stream.d.ts @@ -1,5 +1,6 @@ import { CodecPar } from "./CodecPar"; import { Packet } from "./Packet" +import { toJSONAble } from "./time"; export interface Disposition { DEFAULT?: boolean @@ -50,7 +51,9 @@ export interface EventFlags { /** * Stream describes the properties of a stream. */ -export interface Stream { +export interface Stream extends toJSONAble { + // native code; + readonly _stream: {}; /** Object name. */ readonly type: 'Stream' /** The stream index in the container. */ @@ -73,7 +76,7 @@ export interface Stream { * written into the file (which may or may not be related to the * user-provided one, depending on the format). */ - time_base: Array + time_base: [ number, number ] /** * Decoding: pts of the first frame of the stream in presentation order, in stream time base. * Only set this if you are absolutely 100% sure that the value you set @@ -159,7 +162,4 @@ export interface Stream { * - muxing: filled by the caller before writeHeader() */ codecpar: CodecPar - - /** Retun a JSON string containing the object properties. */ - toJSON(): string } diff --git a/index.d.ts b/ts/types/index.d.ts similarity index 80% rename from index.d.ts rename to ts/types/index.d.ts index a52bcf1..3a0543d 100644 --- a/index.d.ts +++ b/ts/types/index.d.ts @@ -1,16 +1,19 @@ -export * from "./types/CodecPar" -export * from "./types/Packet" -export * from "./types/Frame" -export * from "./types/Stream" -export * from "./types/Codec" -export * from "./types/FormatContext" -export * from "./types/Demuxer" -export * from "./types/Decoder" -export * from "./types/Filter" -export * from "./types/Encoder" -export * from "./types/Muxer" -export * from "./types/Beamstreams" -export * from "./types/HWContext" +export * from "./Beamstreams" +export * from "./Codec" +export * from "./CodecContext" +export * from "./CodecPar" +export * from "./Decoder" +export * from "./Demuxer" +export * from "./Encoder" +export * from "./Filter" +export * from "./FormatContext" +export * from "./Frame" +export * from "./governor" +export * from "./HWContext" +export * from "./Muxer" +export * from "./Packet" +export * from "./PrivClass" +export * from "./Stream" export const AV_NOPTS_VALUE: number @@ -80,4 +83,4 @@ export function protocols(): { inputs: Array, outputs: Array } */ export function logging(level?: string): string | undefined -export as namespace Beamcoder +// export as namespace Beamcoder diff --git a/ts/types/params.d.ts b/ts/types/params.d.ts new file mode 100644 index 0000000..e75400a --- /dev/null +++ b/ts/types/params.d.ts @@ -0,0 +1,18 @@ +export interface commonEncoderParms { + width?: number; + height?: number; + sample_rate?: number, + sample_fmt?: string, + sample_aspect_ratio?: ReadonlyArray, + channels?: number; + bit_rate?: number; + channel_layout?: string; + time_base?: [number, number]; + framerate?: [number, number]; + gop_size?: number; + max_b_frames?: number; + pix_fmt?: string;// pixelFormat, + priv_data?: any; // { preset?: 'slow' }; + flags?: any; + // [key: string]: any +} diff --git a/ts/types/time.d.ts b/ts/types/time.d.ts new file mode 100644 index 0000000..6cea04a --- /dev/null +++ b/ts/types/time.d.ts @@ -0,0 +1,20 @@ +export interface Timing { + reqTime: number; + elapsed: number; +} + +export interface TotalTimed { + /** Total time in microseconds that the decode operation took to complete */ + total_time: number +} + +export interface Timable { + timings?: { [key: string]: Timing; }; +} + +export interface toJSONAble { + /** Retun a JSON string containing the object properties. */ + toJSON(): string +} + +export type Timables = Array & Timable; \ No newline at end of file diff --git a/ts/utils.ts b/ts/utils.ts new file mode 100644 index 0000000..fb1d996 --- /dev/null +++ b/ts/utils.ts @@ -0,0 +1,70 @@ +import https from 'https'; + +/** + * @param ws writable stream to pipe datastream + * @param url url to download + * @param name name to display in screen + * @returns Promise + */ +export async function getRaw(ws: NodeJS.WritableStream, url: string, name?: string): Promise { + let received = 0; + let totalLength = 0; + if (!name) + name = new URL(url).pathname.replace(/.*\//g, '') + return new Promise((comp, err) => { + let prevMsg = ''; + https.get(url, res => { + if (res.statusCode === 301 || res.statusCode === 302) { + err({ name: 'RedirectError', message: res.headers.location }); + } else { + res.pipe(ws); + if (totalLength == 0) { + totalLength = +(res.headers['content-length'] as string); + } + res.on('end', () => { + process.stdout.write(`Downloaded Done of '${name}'. Total length ${received} bytes.\n`); + comp(); + }); + res.on('error', err); + res.on('data', x => { + received += x.length; + const msg = `Downloaded ${received * 100 / totalLength | 0 }% of '${name}'.\r`; + if (msg !== prevMsg) { + prevMsg = msg; + process.stdout.write(msg); + } + }); + } + }).on('error', err); + }); + } + +export async function getHTML(url: string, name: string): Promise { + let received = 0; + let totalLength = 0; + return new Promise((resolve, reject) => { + https.get(url, res => { + let prevMsg = ''; + const chunks: Array = []; + if (totalLength == 0) { + totalLength = +(res.headers['content-length'] as string); + } + res.on('end', () => { + process.stdout.write(`Downloaded Done of '${name}'. Total length ${received} bytes.\n`); + resolve(Buffer.concat(chunks)); + }); + res.on('error', reject); + res.on('data', (chunk) => { + chunks.push(chunk); + received += chunk.length; + const msg = `Downloaded ${received * 100 / totalLength | 0 }% of '${name}'.\r`; + if (msg !== prevMsg) { + prevMsg = msg; + process.stdout.write(msg); + } + }); + }).on('error', reject); + }); + } + + \ No newline at end of file diff --git a/ts/writeStream.ts b/ts/writeStream.ts new file mode 100644 index 0000000..65cf9ce --- /dev/null +++ b/ts/writeStream.ts @@ -0,0 +1,82 @@ +/* + Aerostat Beam Coder - Node.js native bindings to FFmpeg + Copyright (C) 2019 Streampunk Media Ltd. + Copyright (C) 2022 Chemouni Uriel. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://www.streampunk.media/ mailto:furnace@streampunk.media + 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. +*/ +import { Writable } from 'stream'; +import type { ffStats } from './calcStats'; +import { Timing, Timable } from './types/time'; + +import calcStats from './calcStats'; + +const doTimings = false; +const timings = [] as Array<{ [key: string]: Timing }>; + +export default function writeStream(params: { name: string, highWaterMark?: number }, processFn: (val: T) => Promise, finalFn: () => Promise, reject: (err: Error) => void) { + return new Writable({ + objectMode: true, + highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, + write(val: T, encoding: BufferEncoding, cb: (error?: Error | null, result?: R) => void) { + (async () => { + const start = process.hrtime(); + const reqTime = start[0] * 1e3 + start[1] / 1e6; + const result = await processFn(val); + if ('mux' === params.name) { + const pktTimings = val.timings; + if (pktTimings) { + pktTimings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; + if (doTimings) + timings.push(pktTimings); + } + } + cb(null, result); + })().catch(cb); + }, + final(cb: (error?: Error | null, result?: R | null) => void) { + (async () => { + const result = finalFn ? await finalFn() : null; + if (doTimings && ('mux' === params.name)) { + const elapsedStats = {} as { [key: string]: ffStats }; + Object.keys(timings[0]).forEach(k => elapsedStats[k] = calcStats(timings.slice(10, -10), k, 'elapsed')); + console.log('elapsed:'); + console.table(elapsedStats); + + const absArr = timings.map(t => { + const absDelays = {} as { [key: string]: { reqDelta: number } }; + const keys = Object.keys(t); + keys.forEach((k, i) => absDelays[k] = { reqDelta: i > 0 ? t[k].reqTime - t[keys[i - 1]].reqTime : 0 }); + return absDelays; + }); + const absStats = {} as { [key: string]: ffStats }; + Object.keys(absArr[0]).forEach(k => absStats[k] = calcStats(absArr.slice(10, -10), k, 'reqDelta')); + console.log('request time delta:'); + console.table(absStats); + + const totalsArr = timings.map(t => { + const total = (t.mux && t.read) ? t.mux.reqTime - t.read.reqTime + t.mux.elapsed : 0; + return { total: { total: total } }; + }); + console.log('total time:'); + console.table(calcStats<'total', 'total'>(totalsArr.slice(10, -10), 'total', 'total')); + } + cb(null, result); + })().catch(cb); + } + }).on('error', err => reject(err)); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..46a1b4a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,103 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": false, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": ["ts/*.ts"], + } + \ No newline at end of file diff --git a/types/Beamstreams.d.ts b/types/Beamstreams.d.ts deleted file mode 100644 index 0a5bd6d..0000000 --- a/types/Beamstreams.d.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { Demuxer, DemuxerCreateOptions } from "./Demuxer" -import { Muxer, MuxerCreateOptions } from "./Muxer" -import { InputFormat } from "./FormatContext" - -/** - * A [Node.js Writable stream](https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_writable_streams) - * allowing source data to be streamed to the demuxer from a file or other stream source such as a network connection - */ -export interface WritableDemuxerStream extends NodeJS.WritableStream { - /** - * Create a demuxer for this source - * @param options a DemuxerCreateOptions object - * @returns a promise that resolves to a Demuxer when it has determined sufficient - * format details by consuming data from the source. The promise will wait indefinitely - * until sufficient source data has been read. - */ - demuxer(options: DemuxerCreateOptions): Promise -} -/** - * Create a WritableDemuxerStream to allow streaming to a Demuxer - * @param options.highwaterMark Buffer level when `stream.write()` starts returng false. - * @returns A WritableDemuxerStream that can be streamed to. - */ -export function demuxerStream(options: { highwaterMark?: number }): WritableDemuxerStream - -/** - * A [Node.js Readable stream](https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_readable_streams) - * allowing data to be streamed from the muxer to a file or other stream destination such as a network connection - */ -export interface ReadableMuxerStream extends NodeJS.ReadableStream { - /** - * Create a muxer for this source - * @param options a MuxerCreateOptions object - * @returns A Muxer object - */ - muxer(options: MuxerCreateOptions): Muxer -} -/** - * Create a ReadableMuxerStream to allow streaming from a Muxer - * @param options.highwaterMark The maximum number of bytes to store in the internal buffer before ceasing to read from the underlying resource. - * @returns A ReadableMuxerStream that can be streamed from. - */ -export function muxerStream(options: { highwaterMark?: number }): ReadableMuxerStream - -/** Create object for AVIOContext based buffered I/O */ -export function governor(options: { highWaterMark: number }): { - read(len: number): Promise - write(data: Buffer): Promise - finish(): undefined -} - -/** Source definition for a beamstream channel, from either a file or NodeJS ReadableStream */ -export interface BeamstreamSource { - url?: string - input_stream?: NodeJS.ReadableStream - ms?: { start: number, end: number } - streamIndex?: number - iformat?: InputFormat - options?: { [key: string]: any } -} -/** Codec definition for the destination channel */ -export interface BeamstreamStream { - name: string - time_base: Array - codecpar: { [key: string]: any } -} -/** Definition for a channel of beamstream processing */ -export interface BeamstreamChannel { - sources: Array - filterSpec: string - streams: Array -} -/** - * Definition for a beamstream process consisting of a number of audio and video sources - * that are to be processed and multiplexed into an output file or stream - */ -export interface BeamstreamParams { - video?: Array - audio?: Array - /** Destination definition for the beamstream process, to either a file or NodeJS WritableStream */ - out: { - formatName: string - url?: string - output_stream?: NodeJS.WritableStream - } -} -/** - * Initialise the sources for the beamstream process. - * Note - the params object is updated by the function. - */ -export function makeSources(params: BeamstreamParams): Promise -/** - * Initialise the output streams for the beamstream process. - * Note - the params object is updated by the function. - * @returns Promise which resolves to an object with a run function that starts the processing - */ -export function makeStreams(params: BeamstreamParams): Promise<{ run(): Promise} > -