From c8f8627a984551088f9c5109a40920db648040cd Mon Sep 17 00:00:00 2001 From: UrielCh Date: Fri, 15 Apr 2022 17:29:01 +0300 Subject: [PATCH 01/68] convert to TS --- beamstreams.js => beamstreams.ts | 120 +-- index.js => index.ts | 21 +- install_ffmpeg.js => install_ffmpeg.ts | 51 +- package.json | 6 +- pnpm-lock.yaml | 1074 ++++++++++++++++++++++++ tsconfig.json | 101 +++ index.d.ts => types.d.ts | 0 7 files changed, 1291 insertions(+), 82 deletions(-) rename beamstreams.js => beamstreams.ts (89%) rename index.js => index.ts (72%) rename install_ffmpeg.js => install_ffmpeg.ts (84%) create mode 100644 pnpm-lock.yaml create mode 100644 tsconfig.json rename index.d.ts => types.d.ts (100%) diff --git a/beamstreams.js b/beamstreams.ts similarity index 89% rename from beamstreams.js rename to beamstreams.ts index 90e5163..21ee38b 100644 --- a/beamstreams.js +++ b/beamstreams.ts @@ -19,28 +19,39 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('bindings')('beamcoder'); -const { Writable, Readable, Transform } = require('stream'); +import bindings from 'bindings'; +import { Frame, Stream, Muxer, Codec, CodecPar } from '.'; // CodecContext, +const beamcoder = bindings('beamcoder'); +import { Writable, Readable, Transform } from 'stream'; const doTimings = false; const timings = []; -function frameDicer(encoder, isAudio) { + +type FrameJSONable = Frame & { toJSON(): string }; + +class frameDicer { + + private addFrame: (srcFrm: FrameJSONable) => any[]; + private getLast: () => any[]; + private doDice: boolean; + + constructor (encoder: CodecPar, private isAudio: boolean) { 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; + this.doDice = false === beamcoder.encoders()[encoder.name].capabilities.VARIABLE_FRAME_SIZE; - let lastFrm = null; - let lastBuf = []; - const nullBuf = []; + let lastFrm: FrameJSONable = null as any as FrameJSONable; + let lastBuf: Buffer[] = []; + const nullBuf: Buffer[] = []; for (let b = 0; b < numChannels; ++b) nullBuf.push(Buffer.alloc(0)); - const addFrame = srcFrm => { + this.addFrame = (srcFrm: FrameJSONable): any[] => { let result = []; - let dstFrm; + let dstFrm: Frame; let curStart = 0; if (!lastFrm) { lastFrm = beamcoder.frame(srcFrm.toJSON()); @@ -56,7 +67,7 @@ function frameDicer(encoder, isAudio) { dstFrm.pkt_duration = dstNumSamples; while (curStart + dstFrmBytes - lastBuf[0].length <= srcFrm.nb_samples * sampleBytes) { - const resFrm = beamcoder.frame(dstFrm.toJSON()); + const resFrm = beamcoder.frame((dstFrm as any as Stream).toJSON()); resFrm.data = lastBuf.map((d, i) => Buffer.concat([ d, srcFrm.data[i].slice(curStart, curStart + dstFrmBytes - d.length)], @@ -66,19 +77,19 @@ function frameDicer(encoder, isAudio) { dstFrm.pts += dstNumSamples; dstFrm.pkt_dts += dstNumSamples; curStart += dstFrmBytes - lastBuf[0].length; - lastFrm.pts = 0; - lastFrm.pkt_dts = 0; + (lastFrm as Frame).pts = 0; + (lastFrm as Frame).pkt_dts = 0; lastBuf = nullBuf; } - lastFrm.pts = dstFrm.pts; - lastFrm.pkt_dts = dstFrm.pkt_dts; + (lastFrm as Frame).pts = dstFrm.pts; + (lastFrm as Frame).pkt_dts = dstFrm.pkt_dts; lastBuf = srcFrm.data.map(d => d.slice(curStart, srcFrm.nb_samples * sampleBytes)); return result; }; - const getLast = () => { + this.getLast = (): any[] => { let result = []; if (lastBuf[0].length > 0) { const resFrm = beamcoder.frame(lastFrm.toJSON()); @@ -91,17 +102,16 @@ function frameDicer(encoder, isAudio) { } return result; }; - - this.dice = (frames, flush = false) => { - if (isAudio && doDice) { + } + public dice(frames: FrameJSONable[], flush = false): any { + if (this.isAudio && this.doDice) { let result = frames.reduce((muxFrms, frm) => { - addFrame(frm).forEach(f => muxFrms.push(f)); + this.addFrame(frm).forEach(f => muxFrms.push(f)); return muxFrms; }, []); if (flush) - getLast().forEach(f => result.push(f)); - + this.getLast().forEach(f => result.push(f)); return result; } @@ -109,46 +119,63 @@ function frameDicer(encoder, isAudio) { }; } -function serialBalancer(numStreams) { - let pending = []; + + + + + + + +class serialBalancer { + pending = []; + + constructor(numStreams: any) { // 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 }); + this.pending.push({ ts: -Number.MAX_VALUE, streamIndex: s }); + } - const adjustTS = (pkt, srcTB, dstTB) => { + 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); + + pullPkts(pkt, streamIndex, ts): Promise { + return new Promise(resolve => { + Object.assign(this.pending[streamIndex], { pkt: pkt, ts: ts, resolve: 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 = pending.find(pend => pend.pkt && (pend.ts === minTS)); + const nextPend = this.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) => { + 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); + this.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)); + return writeFn(await this.pullPkts(pkt, dstStream.index, pktTS)); }, Promise.resolve()); } else if (final) - return pullPkts(null, dstStream.index, Number.MAX_VALUE); + return this.pullPkts(null, dstStream.index, Number.MAX_VALUE); }; } -function parallelBalancer(params, streamType, numStreams) { + + + + + + +function parallelBalancer(params: {name: string, highWaterMark: number}, streamType, numStreams) { let resolveGet = null; const tag = 'video' === streamType ? 'v' : 'a'; const pending = []; @@ -291,7 +318,7 @@ function teeBalancer(params, numStreams) { return readStreams; } -function transformStream(params, processFn, flushFn, reject) { +function transformStream(params: { name: 'decode'| 'filter', highWaterMark : number }, processFn, flushFn, reject) { return new Transform({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, @@ -417,7 +444,7 @@ function createBeamWritableStream(params, governor) { return beamStream; } -function demuxerStream(params) { +export function demuxerStream(params) { const governor = new beamcoder.governor({}); const stream = createBeamWritableStream(params, governor); stream.on('finish', () => governor.finish()); @@ -446,7 +473,7 @@ function createBeamReadableStream(params, governor) { return beamStream; } -function muxerStream(params) { +export function muxerStream(params) { const governor = new beamcoder.governor({ highWaterMark: 1 }); const stream = createBeamReadableStream(params, governor); stream.on('end', () => governor.finish()); @@ -458,7 +485,7 @@ function muxerStream(params) { return stream; } -async function makeSources(params) { +export async function makeSources(params) { if (!params.video) params.video = []; if (!params.audio) params.audio = []; @@ -507,7 +534,7 @@ async function makeSources(params) { } function runStreams(streamType, sources, filterer, streams, mux, muxBalancer) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { if (!sources.length) return resolve(); @@ -534,7 +561,7 @@ function runStreams(streamType, sources, filterer, streams, mux, muxBalancer) { filterBalancer.pipe(filtStream).pipe(streamSource); - streams.forEach((str, i) => { + streams.forEach((str: Codec, i) => { const dicer = new frameDicer(str.encoder, 'audio' === streamType); const diceStream = transformStream({ name: 'dice', highWaterMark : 1 }, frms => dicer.dice(frms), () => dicer.dice([], true), reject); @@ -550,7 +577,7 @@ function runStreams(streamType, sources, filterer, streams, mux, muxBalancer) { }); } -async function makeStreams(params) { +export async function makeStreams(params) { params.video.forEach(p => { p.sources.forEach(src => src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); @@ -604,7 +631,7 @@ async function makeStreams(params) { params.audio.forEach((p, i) => p.filter = audFilts[i]); // params.audio.forEach(p => console.log(p.filter.graph.dump())); - let mux; + let mux: Muxer; if (params.out.output_stream) { let muxerStream = beamcoder.muxerStream({ highwaterMark: 1024 }); muxerStream.pipe(params.out.output_stream); @@ -683,10 +710,3 @@ async function makeStreams(params) { } }; } - -module.exports = { - demuxerStream, - muxerStream, - makeSources, - makeStreams -}; diff --git a/index.js b/index.ts similarity index 72% rename from index.js rename to index.ts index c9ee041..667043e 100644 --- a/index.js +++ b/index.ts @@ -19,11 +19,12 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('bindings')('beamcoder'); -const beamstreams = require('./beamstreams.js'); +import bindings from 'bindings'; +const beamcoder = bindings('beamcoder') +// import * as beamstreams from './beamstreams.js'; // Provide useful debug on segfault-related crash -const SegfaultHandler = require('segfault-handler'); +import SegfaultHandler from 'segfault-handler'; SegfaultHandler.registerHandler('crash.log'); const splash = `Aerostat Beam Coder Copyright (C) 2019 Streampunk Media Ltd @@ -35,10 +36,16 @@ 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; +import {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams.js'; -beamcoder.makeSources = beamstreams.makeSources; -beamcoder.makeStreams = beamstreams.makeStreams; +// export {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams.js'; + +beamcoder.demuxerStream = demuxerStream; +beamcoder.muxerStream = muxerStream; + +beamcoder.makeSources = makeSources; +beamcoder.makeStreams = makeStreams; + +// export default beamcoder; module.exports = beamcoder; diff --git a/install_ffmpeg.js b/install_ffmpeg.ts similarity index 84% rename from install_ffmpeg.js rename to install_ffmpeg.ts index 6e375c9..1b4000d 100644 --- a/install_ffmpeg.js +++ b/install_ffmpeg.ts @@ -19,15 +19,21 @@ 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) { +import os from 'os'; +import fs from 'fs'; +import util from 'util'; +import https from 'https'; +import cp from 'child_process'; + +//const [ mkdir, access, rename, execFile, exec ] = // eslint-disable-line +// [ fs.mkdir, fs.access, fs.rename, cp.execFile, cp.exec ].map(util.promisify); + +const { mkdir, access, rename } = fs.promises; + +const [ execFile, exec ] = [ cp.execFile, cp.exec ].map(util.promisify); + + +async function get(ws: NodeJS.WritableStream, url: string, name: string): Promise { let received = 0; let totalLength = 0; return new Promise((comp, err) => { @@ -37,7 +43,7 @@ async function get(ws, url, name) { } else { res.pipe(ws); if (totalLength == 0) { - totalLength = +res.headers['content-length']; + totalLength = +(res.headers['content-length'] as string); } res.on('end', () => { process.stdout.write(`Downloaded 100% of '${name}'. Total length ${received} bytes.\n`); @@ -53,14 +59,14 @@ async function get(ws, url, name) { }); } -async function getHTML(url, name) { +async function getHTML(url: string, name: string): Promise { let received = 0; let totalLength = 0; return new Promise((resolve, reject) => { https.get(url, res => { - const chunks = []; + const chunks: Array = []; if (totalLength == 0) { - totalLength = +res.headers['content-length']; + totalLength = +(res.headers['content-length'] as string); } res.on('end', () => { process.stdout.write(`Downloaded 100% of '${name}'. Total length ${received} bytes.\n`); @@ -76,23 +82,22 @@ async function getHTML(url, name) { }); } -async function inflate(rs, folder, name) { +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 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.pipe(unzip.Extract({ path: folder }).on('close', async () => { + await rename(`./${folder}/${directoryName}`, `./${folder}/${name}`) + console.log(`Unzipping of '${folder}/${name}.zip' completed.`); + comp(); })); rs.on('error', err); }); } -async function win32() { +async function win32(): Promise { console.log('Checking/Installing FFmpeg dependencies for Beam Coder on Windows.'); await mkdir('ffmpeg').catch(e => { @@ -129,7 +134,7 @@ async function win32() { }); } -async function linux() { +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; @@ -176,7 +181,7 @@ sudo apt-get install libavcodec-dev libavformat-dev libavdevice-dev libavfilter- return result; } -async function darwin() { +async function darwin(): Promise<0> { console.log('Checking for FFmpeg dependencies via HomeBrew.'); let output; let returnMessage; @@ -185,7 +190,7 @@ async function darwin() { output = await exec('brew list ffmpeg'); returnMessage = 'FFmpeg already present via Homebrew.'; } catch (err) { - if (err.stderr !== 'Error: No such keg: /usr/local/Cellar/ffmpeg\n') { + 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); diff --git a/package.json b/package.json index 483f547..35ae67e 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.7.1", "description": "Node.js native bindings to FFmpeg.", "main": "index.js", - "types": "index.d.ts", + "types": "types.d.ts", "scripts": { "preinstall": "node install_ffmpeg.js", "install": "node-gyp rebuild", @@ -38,8 +38,10 @@ "segfault-handler": "^1.3.0" }, "devDependencies": { + "@types/bindings": "^1.5.1", "eslint": "^8.9.0", - "tape": "^5.5.2" + "tape": "^5.5.2", + "typescript": "^4.6.3" }, "gypfile": true } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..9ebe599 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1074 @@ +lockfileVersion: 5.3 + +specifiers: + '@types/bindings': ^1.5.1 + bindings: ^1.5.0 + eslint: ^8.9.0 + segfault-handler: ^1.3.0 + tape: ^5.5.2 + typescript: ^4.6.3 + +dependencies: + bindings: 1.5.0 + segfault-handler: 1.3.0 + +devDependencies: + '@types/bindings': 1.5.1 + eslint: 8.13.0 + tape: 5.5.3 + typescript: 4.6.3 + +packages: + + /@eslint/eslintrc/1.2.1: + resolution: {integrity: sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==} + 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 + + /@types/bindings/1.5.1: + resolution: {integrity: sha512-8HzueDeoxGXdsJ0Ep7TOXHGN+woRTWa1bAds30r5we7PCC3P5zrSTRknePLn/KYAubgQv5t/1zkonnStHLCWOg==} + dependencies: + '@types/node': 17.0.24 + dev: true + + /@types/node/17.0.24: + resolution: {integrity: sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==} + 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/8.7.0: + resolution: {integrity: sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==} + engines: {node: '>=0.4.0'} + hasBin: true + 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 + + /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 + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /bindings/1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: false + + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + 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 + + /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 + + /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 + + /concat-map/0.0.1: + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + 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 + + /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 + + /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 + + /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-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.13.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.13.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.13.0: + resolution: {integrity: sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.2.1 + '@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.13.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 + + /fast-deep-equal/3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + 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 + + /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 + + /fs.realpath/1.0.0: + resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} + dev: true + + /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-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-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 + dev: true + + /globals/13.13.0: + resolution: {integrity: sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /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 + + /ignore/5.2.0: + resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} + engines: {node: '>= 4'} + 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 + dev: true + + /inherits/2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + dev: true + + /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 + + /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-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-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-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/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 + + /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 + + /lodash.merge/4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /minimatch/3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.11 + dev: true + + /minimist/1.2.6: + resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} + dev: true + + /ms/2.1.2: + resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true + + /nan/2.15.0: + resolution: {integrity: sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==} + dev: false + + /natural-compare/1.4.0: + resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} + dev: 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 + dev: true + + /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 + + /parent-module/1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /path-is-absolute/1.0.1: + resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} + engines: {node: '>=0.10.0'} + dev: true + + /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 + + /prelude-ls/1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true + + /punycode/2.1.1: + resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + engines: {node: '>=6'} + 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 + + /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/3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true + dependencies: + glob: 7.2.0 + 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 + + /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 + + /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 + + /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 + + /through/2.3.8: + resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=} + 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 + + /typescript/4.6.3: + resolution: {integrity: sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==} + engines: {node: '>=4.2.0'} + hasBin: true + 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 + + /uri-js/4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.1.1 + dev: true + + /v8-compile-cache/2.3.0: + resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} + 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=} + dev: true diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..b55edd3 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,101 @@ +{ + "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": "es2016", /* 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. */ + } +} diff --git a/index.d.ts b/types.d.ts similarity index 100% rename from index.d.ts rename to types.d.ts From 3fd52f77246c96bd35f04d2691f40972cd3ca1ae Mon Sep 17 00:00:00 2001 From: UrielCh Date: Fri, 15 Apr 2022 18:22:35 +0300 Subject: [PATCH 02/68] convert TS --- beamstreams.ts | 79 ++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/beamstreams.ts b/beamstreams.ts index 21ee38b..5e8d024 100644 --- a/beamstreams.ts +++ b/beamstreams.ts @@ -20,14 +20,13 @@ */ import bindings from 'bindings'; -import { Frame, Stream, Muxer, Codec, CodecPar } from '.'; // CodecContext, +import { Frame, Stream, Muxer, CodecPar } from '.'; // Codec, CodecContext, const beamcoder = bindings('beamcoder'); import { Writable, Readable, Transform } from 'stream'; const doTimings = false; const timings = []; - type FrameJSONable = Frame & { toJSON(): string }; class frameDicer { @@ -119,17 +118,10 @@ class frameDicer { }; } - - - - - - - class serialBalancer { pending = []; - constructor(numStreams: any) { + 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) @@ -170,12 +162,11 @@ class serialBalancer { } +type parallelBalancerType = Readable & { + pushPkts: (packets, stream, streamIndex, final?: boolean) => any +}; - - - - -function parallelBalancer(params: {name: string, highWaterMark: number}, streamType, numStreams) { +function parallelBalancer(params: {name: string, highWaterMark: number}, streamType, numStreams): parallelBalancerType { let resolveGet = null; const tag = 'video' === streamType ? 'v' : 'a'; const pending = []; @@ -212,9 +203,9 @@ function parallelBalancer(params: {name: string, highWaterMark: number}, streamT makeSet(resolveGet); }); - const pullSet = async () => new Promise(resolve => makeSet(resolve)); + const pullSet = async () => new Promise<{done: any, value: {timings: any}}>(resolve => makeSet(resolve)); - const readStream = new Readable({ + const readStream: parallelBalancerType = new Readable({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, read() { @@ -231,9 +222,9 @@ function parallelBalancer(params: {name: string, highWaterMark: number}, streamT } })(); }, - }); + }) as parallelBalancerType; - readStream.pushPkts = (packets, stream, streamIndex, final = false) => { + readStream.pushPkts = (packets, stream, streamIndex, final = false): any => { if (packets && packets.frames.length) { return packets.frames.reduce(async (promise, pkt) => { await promise; @@ -249,14 +240,16 @@ function parallelBalancer(params: {name: string, highWaterMark: number}, streamT return readStream; } -function teeBalancer(params, numStreams) { + type teeBalancerType = Readable[] & {pushFrames: (frames: any, unusedFlag?: boolean) => any}; + +function teeBalancer(params: {name: 'streamTee', highWaterMark?: number}, numStreams: number): teeBalancerType { 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 => { + return new Promise<{done: boolean, value?: any}>(resolve => { if (pending[index].frames) { resolve({ value: pending[index].frames, done: false }); Object.assign(pending[index], { frames: null, resolve: null }); @@ -272,7 +265,7 @@ function teeBalancer(params, numStreams) { }); }; - const readStreams = []; + const readStreams: teeBalancerType = [] as teeBalancerType; for (let s = 0; s < numStreams; ++s) readStreams.push(new Readable({ objectMode: true, @@ -293,7 +286,7 @@ function teeBalancer(params, numStreams) { })); readStreams.pushFrames = frames => { - return new Promise(resolve => { + return new Promise<{value: {timings: any}, done: boolean }>(resolve => { pending.forEach((p, index) => { if (frames.length) p.frames = frames[index].frames; @@ -318,7 +311,7 @@ function teeBalancer(params, numStreams) { return readStreams; } -function transformStream(params: { name: 'decode'| 'filter', highWaterMark : number }, processFn, flushFn, reject) { +function transformStream(params: { name: 'encode' | 'dice' | 'decode'| 'filter', highWaterMark : number }, processFn, flushFn, reject) { return new Transform({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, @@ -351,11 +344,11 @@ const calcStats = (arr, elem, prop) => { return { mean: mean, stdDev: stdDev, max: max, min: min }; }; -function writeStream(params, processFn, finalFn, reject) { +function writeStream(params: {name: string, highWaterMark?: number}, processFn: (val: {timings: any}) => Promise, finalFn: ()=> Promise, reject: (err: Error) => void) { return new Writable({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - write(val, encoding, cb) { + write(val: {timings: any}, encoding: BufferEncoding, cb: (error?: Error | null, result?: any) => void) { (async () => { const start = process.hrtime(); const reqTime = start[0] * 1e3 + start[1] / 1e6; @@ -369,7 +362,7 @@ function writeStream(params, processFn, finalFn, reject) { cb(null, result); })().catch(cb); }, - final(cb) { + final(cb: (error?: Error | null, result?: any) => void) { (async () => { const result = finalFn ? await finalFn() : null; if (doTimings && ('mux' === params.name)) { @@ -431,7 +424,7 @@ function readStream(params, demuxer, ms, index) { }); } -function createBeamWritableStream(params, governor) { +function createBeamWritableStream(params: {highwaterMark?: number}, governor: governorType): Writable { const beamStream = new Writable({ highWaterMark: params.highwaterMark || 16384, write: (chunk, encoding, cb) => { @@ -443,13 +436,14 @@ function createBeamWritableStream(params, governor) { }); return beamStream; } +type demuxerStreamType = Writable & {demuxer: (options: {governor: governorType})=> Promise }; export function demuxerStream(params) { const governor = new beamcoder.governor({}); - const stream = createBeamWritableStream(params, governor); + const stream: demuxerStreamType = createBeamWritableStream(params, governor) as demuxerStreamType; stream.on('finish', () => governor.finish()); stream.on('error', console.error); - stream.demuxer = options => { + stream.demuxer = (options: {governor: governorType}) => { 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)); @@ -457,7 +451,8 @@ export function demuxerStream(params) { return stream; } -function createBeamReadableStream(params, governor) { + +function createBeamReadableStream(params: {highwaterMark: number}, governor: governorType) { const beamStream = new Readable({ highWaterMark: params.highwaterMark || 16384, read: size => { @@ -473,19 +468,27 @@ function createBeamReadableStream(params, governor) { return beamStream; } -export function muxerStream(params) { +type governorType = { + read(len: number): Promise; + write(data: Buffer): Promise; + finish(): undefined; +}; + +type muxerStreamType = Readable & {muxer: (options: {governor: governorType}) => any}; + +export function muxerStream(params: {highwaterMark: number}): muxerStreamType { const governor = new beamcoder.governor({ highWaterMark: 1 }); - const stream = createBeamReadableStream(params, governor); + const stream: muxerStreamType = createBeamReadableStream(params, governor) as muxerStreamType; stream.on('end', () => governor.finish()); stream.on('error', console.error); - stream.muxer = options => { + stream.muxer = (options) => { options.governor = governor; return beamcoder.muxer(options); }; return stream; } -export async function makeSources(params) { +export async function makeSources(params: {video?: any[], audio?: any[]}) { if (!params.video) params.video = []; if (!params.audio) params.audio = []; @@ -533,7 +536,7 @@ export async function makeSources(params) { src.stream = readStream({ highWaterMark : 1 }, src.format, src.ms, src.streamIndex))); } -function runStreams(streamType, sources, filterer, streams, mux, muxBalancer) { +function runStreams(streamType, sources: {decoder: any, format: any, streamIndex: any, stream: any}[], filterer, streams, mux, muxBalancer) { return new Promise((resolve, reject) => { if (!sources.length) return resolve(); @@ -541,7 +544,7 @@ function runStreams(streamType, sources, filterer, streams, mux, muxBalancer) { const timeBaseStream = sources[0].format.streams[sources[0].streamIndex]; const filterBalancer = parallelBalancer({ name: 'filterBalance', highWaterMark : 1 }, streamType, sources.length); - sources.forEach((src, srcIndex) => { + sources.forEach((src: {decoder: any, format: any, streamIndex: any, stream: any}, srcIndex: number) => { const decStream = transformStream({ name: 'decode', highWaterMark : 1 }, pkts => src.decoder.decode(pkts), () => src.decoder.flush(), reject); const filterSource = writeStream({ name: 'filterSource', highWaterMark : 1 }, @@ -561,7 +564,7 @@ function runStreams(streamType, sources, filterer, streams, mux, muxBalancer) { filterBalancer.pipe(filtStream).pipe(streamSource); - streams.forEach((str: Codec, i) => { + streams.forEach((str: {encoder: any, stream: any}, i) => { const dicer = new frameDicer(str.encoder, 'audio' === streamType); const diceStream = transformStream({ name: 'dice', highWaterMark : 1 }, frms => dicer.dice(frms), () => dicer.dice([], true), reject); From d775f497419b1cd6a67cbba846ff2cead621a59d Mon Sep 17 00:00:00 2001 From: UrielCh Date: Fri, 15 Apr 2022 18:23:54 +0300 Subject: [PATCH 03/68] update tsconfig --- .gitignore | 3 +++ tsconfig.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d6fbad3..db2b58c 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,6 @@ ffmpeg/ # Editors and IDE's *.swp .vscode/ +beamstreams.js +index.js +install_ffmpeg.js diff --git a/tsconfig.json b/tsconfig.json index b55edd3..27930d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "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. */ From 5e66c00d8834f20a4facce2e744eebb1dac69373 Mon Sep 17 00:00:00 2001 From: UrielCh Date: Mon, 18 Apr 2022 14:45:53 +0300 Subject: [PATCH 04/68] fix typing --- beamstreams.ts | 427 +++++++++++++++++++++++++++-------------- install_ffmpeg.ts | 4 - src/demux.cc | 6 +- types/Beamstreams.d.ts | 20 +- types/Frame.d.ts | 3 + 5 files changed, 303 insertions(+), 157 deletions(-) diff --git a/beamstreams.ts b/beamstreams.ts index 5e8d024..ebe9b78 100644 --- a/beamstreams.ts +++ b/beamstreams.ts @@ -18,102 +18,201 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ - import bindings from 'bindings'; -import { Frame, Stream, Muxer, CodecPar } from '.'; // Codec, CodecContext, -const beamcoder = bindings('beamcoder'); +import { Frame, Stream, Muxer, CodecPar, Codec, Governor, WritableDemuxerStream, ReadableMuxerStream, FiltererVideoOptions, FiltererAudioOptions, Filterer, Decoder, Demuxer, Encoder, InputFormat } from '.'; // Codec, CodecContext, import { Writable, Readable, Transform } from 'stream'; +type governorType = { + read(len: number): Promise; + write(data: Buffer): Promise; + finish(): undefined; +}; + +interface BeamcoderType extends ReadableMuxerStream { + /** Create object for AVIOContext based buffered I/O */ + governor: typeof Governor; + + + /** + * 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?: governorType, url?: string, iformat?: InputFormat, options?: { governor: governorType } } | string): Promise + // url: src.url, iformat: src.iformat, options: src.options + /** + * 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 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; + + /** + * 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 + + /** + * 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: { 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: { 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 +} + +const beamcoder = bindings('beamcoder') as BeamcoderType; + const doTimings = false; const timings = []; -type FrameJSONable = Frame & { toJSON(): string }; - class frameDicer { - private addFrame: (srcFrm: FrameJSONable) => any[]; + private addFrame: (srcFrm: Frame) => any[]; private getLast: () => any[]; private doDice: boolean; - constructor (encoder: CodecPar, private isAudio: boolean) { - let sampleBytes = 4; // Assume floating point 4 byte samples for now... - const numChannels = encoder.channels; - const dstNumSamples = encoder.frame_size; - let dstFrmBytes = dstNumSamples * sampleBytes; - this.doDice = false === beamcoder.encoders()[encoder.name].capabilities.VARIABLE_FRAME_SIZE; - - let lastFrm: FrameJSONable = null as any as FrameJSONable; - let lastBuf: Buffer[] = []; - const nullBuf: Buffer[] = []; - for (let b = 0; b < numChannels; ++b) - nullBuf.push(Buffer.alloc(0)); - - this.addFrame = (srcFrm: FrameJSONable): any[] => { - let result = []; - let dstFrm: Frame; - 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 as any as Stream).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 as Frame).pts = 0; - (lastFrm as Frame).pkt_dts = 0; - lastBuf = nullBuf; - } + constructor(encoder: CodecPar, private isAudio: boolean) { + let sampleBytes = 4; // Assume floating point 4 byte samples for now... + const numChannels = encoder.channels; + const dstNumSamples = encoder.frame_size; + let dstFrmBytes = dstNumSamples * sampleBytes; + this.doDice = false === beamcoder.encoders()[encoder.name].capabilities.VARIABLE_FRAME_SIZE; + + let lastFrm: Frame = null as any as Frame; + let lastBuf: Buffer[] = []; + const nullBuf: Buffer[] = []; + for (let b = 0; b < numChannels; ++b) + nullBuf.push(Buffer.alloc(0)); + + this.addFrame = (srcFrm: Frame): any[] => { + let result = []; + let dstFrm: Frame; + let curStart = 0; + if (!lastFrm) { + lastFrm = beamcoder.frame(srcFrm.toJSON()) as Frame; + lastBuf = nullBuf; + dstFrmBytes = dstNumSamples * sampleBytes; + } - (lastFrm as Frame).pts = dstFrm.pts; - (lastFrm as Frame).pkt_dts = dstFrm.pkt_dts; - lastBuf = srcFrm.data.map(d => d.slice(curStart, srcFrm.nb_samples * 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 as any as Stream).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 as Frame).pts = 0; + (lastFrm as Frame).pkt_dts = 0; + lastBuf = nullBuf; + } - return result; - }; + (lastFrm as Frame).pts = dstFrm.pts; + (lastFrm as Frame).pkt_dts = dstFrm.pkt_dts; + lastBuf = srcFrm.data.map(d => d.slice(curStart, srcFrm.nb_samples * sampleBytes)); - this.getLast = (): any[] => { - 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; - }; + return result; + }; + + this.getLast = (): any[] => { + 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; + }; } - public dice(frames: FrameJSONable[], flush = false): any { + public dice(frames: Frame[], flush = false): any { if (this.isAudio && this.doDice) { let result = frames.reduce((muxFrms, frm) => { this.addFrame(frm).forEach(f => muxFrms.push(f)); return muxFrms; }, []); - + if (flush) this.getLast().forEach(f => result.push(f)); return result; } - + return frames; }; } @@ -122,23 +221,23 @@ class serialBalancer { pending = []; 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 }); + // 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 }); } - adjustTS(pkt, srcTB, dstTB) { + adjustTS(pkt: { pts: number, dts: number, duration: number }, srcTB: [number, number], dstTB: [number, number]): void { 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); }; - - pullPkts(pkt, streamIndex, ts): Promise { + + pullPkts(pkt: {}, streamIndex: number, ts: number): Promise { return new Promise(resolve => { - Object.assign(this.pending[streamIndex], { pkt: pkt, ts: ts, resolve: 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)); @@ -147,7 +246,15 @@ class serialBalancer { }); }; - writePkts(packets, srcStream, dstStream, writeFn, final = false) { + writePkts(packets: { + packets: Array<{ stream_index: number, pts: number, dts: number, duration: number }> + } | null, + srcStream: { time_base: [number, number] }, + dstStream: { + time_base: [number, number], + index: number + }, + writeFn: (r: void) => void, final = false) { if (packets && packets.packets.length) { return packets.packets.reduce(async (promise, pkt) => { await promise; @@ -163,10 +270,10 @@ class serialBalancer { type parallelBalancerType = Readable & { - pushPkts: (packets, stream, streamIndex, final?: boolean) => any + pushPkts: (packets, stream, streamIndex: number, final?: boolean) => any }; -function parallelBalancer(params: {name: string, highWaterMark: number}, streamType, numStreams): parallelBalancerType { +function parallelBalancer(params: { name: string, highWaterMark: number }, streamType: 'video', numStreams: number): parallelBalancerType { let resolveGet = null; const tag = 'video' === streamType ? 'v' : 'a'; const pending = []; @@ -184,8 +291,10 @@ function parallelBalancer(params: {name: string, highWaterMark: number}, streamT nextPends.forEach(pend => pend.resolve()); resolve({ value: nextPends.map(pend => { - return { name: `in${pend.streamIndex}:${tag}`, frames: [ pend.pkt ] }; }), - done: false }); + 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) { @@ -197,13 +306,13 @@ function parallelBalancer(params: {name: string, highWaterMark: number}, streamT } }; - const pushPkt = async (pkt, streamIndex, ts) => + const pushPkt = async (pkt, streamIndex: number, ts: number) => new Promise(resolve => { - Object.assign(pending[streamIndex], { pkt: pkt, ts: ts, final: pkt ? false : true, resolve: resolve }); + Object.assign(pending[streamIndex], { pkt, ts, final: pkt ? false : true, resolve }); makeSet(resolveGet); }); - const pullSet = async () => new Promise<{done: any, value: {timings: any}}>(resolve => makeSet(resolve)); + const pullSet = async () => new Promise<{ done: any, value: { timings: any } }>(resolve => makeSet(resolve)); const readStream: parallelBalancerType = new Readable({ objectMode: true, @@ -224,7 +333,7 @@ function parallelBalancer(params: {name: string, highWaterMark: number}, streamT }, }) as parallelBalancerType; - readStream.pushPkts = (packets, stream, streamIndex, final = false): any => { + readStream.pushPkts = (packets, stream, streamIndex: number, final = false): any => { if (packets && packets.frames.length) { return packets.frames.reduce(async (promise, pkt) => { await promise; @@ -240,16 +349,16 @@ function parallelBalancer(params: {name: string, highWaterMark: number}, streamT return readStream; } - type teeBalancerType = Readable[] & {pushFrames: (frames: any, unusedFlag?: boolean) => any}; +type teeBalancerType = Readable[] & { pushFrames: (frames: any, unusedFlag?: boolean) => any }; -function teeBalancer(params: {name: 'streamTee', highWaterMark?: number}, numStreams: number): teeBalancerType { +function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, numStreams: number): teeBalancerType { 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<{done: boolean, value?: any}>(resolve => { + return new Promise<{ done: boolean, value?: any }>(resolve => { if (pending[index].frames) { resolve({ value: pending[index].frames, done: false }); Object.assign(pending[index], { frames: null, resolve: null }); @@ -286,7 +395,7 @@ function teeBalancer(params: {name: 'streamTee', highWaterMark?: number}, numStr })); readStreams.pushFrames = frames => { - return new Promise<{value: {timings: any}, done: boolean }>(resolve => { + return new Promise<{ value: { timings: any }, done: boolean }>(resolve => { pending.forEach((p, index) => { if (frames.length) p.frames = frames[index].frames; @@ -311,7 +420,7 @@ function teeBalancer(params: {name: 'streamTee', highWaterMark?: number}, numStr return readStreams; } -function transformStream(params: { name: 'encode' | 'dice' | 'decode'| 'filter', highWaterMark : number }, processFn, flushFn, reject) { +function transformStream(params: { name: 'encode' | 'dice' | 'decode' | 'filter', highWaterMark: number }, processFn, flushFn, reject) { return new Transform({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, @@ -336,7 +445,7 @@ function transformStream(params: { name: 'encode' | 'dice' | 'decode'| 'filter', }).on('error', err => reject(err)); } -const calcStats = (arr, elem, prop) => { +const calcStats = (arr: Array, elem: string, prop: string) => { 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); @@ -344,11 +453,11 @@ const calcStats = (arr, elem, prop) => { return { mean: mean, stdDev: stdDev, max: max, min: min }; }; -function writeStream(params: {name: string, highWaterMark?: number}, processFn: (val: {timings: any}) => Promise, finalFn: ()=> Promise, reject: (err: Error) => void) { +function writeStream(params: { name: string, highWaterMark?: number }, processFn: (val: { timings: any }) => Promise, finalFn: () => Promise, reject: (err: Error) => void) { return new Writable({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - write(val: {timings: any}, encoding: BufferEncoding, cb: (error?: Error | null, result?: any) => void) { + write(val: { timings: any }, encoding: BufferEncoding, cb: (error?: Error | null, result?: any) => void) { (async () => { const start = process.hrtime(); const reqTime = start[0] * 1e3 + start[1] / 1e6; @@ -374,7 +483,7 @@ function writeStream(params: {name: string, highWaterMark?: number}, processFn: 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 }); + keys.forEach((k, i) => absDelays[k] = { reqDelta: i > 0 ? t[k].reqTime - t[keys[i - 1]].reqTime : 0 }); return absDelays; }); const absStats = {}; @@ -382,9 +491,9 @@ function writeStream(params: {name: string, highWaterMark?: number}, processFn: console.log('request time delta:'); console.table(absStats); - const totalsArr = timings.map(t => { + 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 }}; + return { total: { total: total } }; }); console.log('total time:'); console.table(calcStats(totalsArr.slice(10, -10), 'total', 'total')); @@ -395,7 +504,7 @@ function writeStream(params: {name: string, highWaterMark?: number}, processFn: }).on('error', err => reject(err)); } -function readStream(params, demuxer, ms, index) { +function readStream(params: {highWaterMark?: number}, demuxer: { read: () => Promise, streams: Array<{time_base: [number, number]}> }, ms: { end: number }, index: number) { 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() { @@ -424,7 +533,7 @@ function readStream(params, demuxer, ms, index) { }); } -function createBeamWritableStream(params: {highwaterMark?: number}, governor: governorType): Writable { +function createBeamWritableStream(params: { highwaterMark?: number }, governor: governorType): Writable { const beamStream = new Writable({ highWaterMark: params.highwaterMark || 16384, write: (chunk, encoding, cb) => { @@ -436,14 +545,14 @@ function createBeamWritableStream(params: {highwaterMark?: number}, governor: go }); return beamStream; } -type demuxerStreamType = Writable & {demuxer: (options: {governor: governorType})=> Promise }; +type demuxerStreamType = Writable & { demuxer: (options: { governor: governorType }) => Promise }; export function demuxerStream(params) { const governor = new beamcoder.governor({}); const stream: demuxerStreamType = createBeamWritableStream(params, governor) as demuxerStreamType; stream.on('finish', () => governor.finish()); stream.on('error', console.error); - stream.demuxer = (options: {governor: governorType}) => { + stream.demuxer = (options: { governor: governorType }) => { 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)); @@ -452,7 +561,7 @@ export function demuxerStream(params) { } -function createBeamReadableStream(params: {highwaterMark: number}, governor: governorType) { +function createBeamReadableStream(params: { highwaterMark?: number }, governor: governorType) { const beamStream = new Readable({ highWaterMark: params.highwaterMark || 16384, read: size => { @@ -468,15 +577,9 @@ function createBeamReadableStream(params: {highwaterMark: number}, governor: gov return beamStream; } -type governorType = { - read(len: number): Promise; - write(data: Buffer): Promise; - finish(): undefined; -}; - -type muxerStreamType = Readable & {muxer: (options: {governor: governorType}) => any}; +type muxerStreamType = Readable & { muxer: (options: { governor: governorType }) => any }; -export function muxerStream(params: {highwaterMark: number}): muxerStreamType { +export function muxerStream(params: { highwaterMark: number }): muxerStreamType { const governor = new beamcoder.governor({ highWaterMark: 1 }); const stream: muxerStreamType = createBeamReadableStream(params, governor) as muxerStreamType; stream.on('end', () => governor.finish()); @@ -488,7 +591,7 @@ export function muxerStream(params: {highwaterMark: number}): muxerStreamType { return stream; } -export async function makeSources(params: {video?: any[], audio?: any[]}) { +export async function makeSources(params: { video?: Array<{ sources: any[] }>, audio?: Array<{ sources: any[] }> }) { if (!params.video) params.video = []; if (!params.audio) params.audio = []; @@ -530,47 +633,53 @@ export async function makeSources(params: {video?: any[], audio?: any[]}) { }, 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))); + 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: {decoder: any, format: any, streamIndex: any, stream: any}[], filterer, streams, mux, muxBalancer) { +function runStreams( + streamType, + sources: Array<{ decoder: { decode: (pkts: any) => void, flush: () => void }, format: { streams: Array<{}> }, streamIndex: any, stream: any }>, + filterer: { cb?: (result: any) => void, filter: (stream: any) => any }, + streams, + mux, + muxBalancer: { writePkts: (packets, srcStream, dstStream, writeFn, final?: boolean) => any }) { 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); + const filterBalancer = parallelBalancer({ name: 'filterBalance', highWaterMark: 1 }, streamType, sources.length); - sources.forEach((src: {decoder: any, format: any, streamIndex: any, stream: any}, srcIndex: number) => { - const decStream = transformStream({ name: 'decode', highWaterMark : 1 }, + sources.forEach((src, srcIndex: number) => { + const decStream = transformStream({ name: 'decode', highWaterMark: 1 }, pkts => src.decoder.decode(pkts), () => src.decoder.flush(), reject); - const filterSource = writeStream({ name: 'filterSource', highWaterMark : 1 }, + 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 => { + 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 }, + }, () => { }, reject); + const streamSource = writeStream({ name: 'streamSource', highWaterMark: 1 }, frms => streamTee.pushFrames(frms), () => streamTee.pushFrames([], true), reject); filterBalancer.pipe(filtStream).pipe(streamSource); - streams.forEach((str: {encoder: any, stream: any}, i) => { + streams.forEach((str: { encoder: any, stream: any }, i) => { const dicer = new frameDicer(str.encoder, 'audio' === streamType); - const diceStream = transformStream({ name: 'dice', highWaterMark : 1 }, + const diceStream = transformStream({ name: 'dice', highWaterMark: 1 }, frms => dicer.dice(frms), () => dicer.dice([], true), reject); - const encStream = transformStream({ name: 'encode', highWaterMark : 1 }, + const encStream = transformStream({ name: 'encode', highWaterMark: 1 }, frms => str.encoder.encode(frms), () => str.encoder.flush(), reject); - const muxStream = writeStream({ name: 'mux', highWaterMark : 1 }, + 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); @@ -580,7 +689,22 @@ function runStreams(streamType, sources: {decoder: any, format: any, streamIndex }); } -export async function makeStreams(params) { +export async function makeStreams(params: { + video: Array<{ + filter?: any; + streams: any[], + sources: any[], + filterSpec: string, + }>, + audio: Array<{ + filter?: any; + sources: any[], + streams: any[], + filterSpec: string, + }>, + out: { output_stream: any, formatName: string, url?: string, flags: any, options: any } +} +) { params.video.forEach(p => { p.sources.forEach(src => src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); @@ -592,6 +716,7 @@ export async function makeStreams(params) { params.video.forEach(p => { p.filter = beamcoder.filterer({ + // FiltererVideoOptions filterType: 'video', inputParams: p.sources.map((src, i) => { const stream = src.format.streams[src.streamIndex]; @@ -601,10 +726,12 @@ export async function makeStreams(params) { height: stream.codecpar.height, pixelFormat: stream.codecpar.format, timeBase: stream.time_base, - pixelAspect: stream.sample_aspect_ratio }; + pixelAspect: stream.sample_aspect_ratio + }; }), outputParams: p.streams.map((str, i) => { return { name: `out${i}:v`, pixelFormat: str.codecpar.format }; }), - filterSpec: p.filterSpec }); + filterSpec: p.filterSpec + }); }); const vidFilts = await Promise.all(params.video.map(p => p.filter)); params.video.forEach((p, i) => p.filter = vidFilts[i]); @@ -620,15 +747,19 @@ export async function makeStreams(params) { sampleRate: src.decoder.sample_rate, sampleFormat: src.decoder.sample_fmt, channelLayout: src.decoder.channel_layout, - timeBase: stream.time_base }; + timeBase: stream.time_base + }; }), - outputParams: p.streams.map((str, i) => { - return { + 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 }); + 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]); @@ -657,7 +788,8 @@ export async function makeStreams(params) { // gop_size: 10, // max_b_frames: 1, // priv_data: { preset: 'slow' } - priv_data: { crf: 23 } }); // ... more required ... + priv_data: { crf: 23 } + }); // ... more required ... }); }); @@ -669,18 +801,20 @@ export async function makeStreams(params) { sample_fmt: encParams.format, sample_rate: encParams.sample_rate, channel_layout: encParams.channel_layout, - flags: { GLOBAL_HEADER: mux.oformat.flags.GLOBALHEADER } }); - + 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({ + str.stream = mux.newStream({ name: str.name, time_base: str.time_base, - interleaved: true }); // Set to false for manual interleaving, true for automatic + interleaved: true + }); // Set to false for manual interleaving, true for automatic Object.assign(str.stream.codecpar, str.codecpar); }); }); @@ -690,7 +824,8 @@ export async function makeStreams(params) { str.stream = mux.newStream({ name: str.name, time_base: str.time_base, - interleaved: true }); // Set to false for manual interleaving, true for automatic + interleaved: true + }); // Set to false for manual interleaving, true for automatic Object.assign(str.stream.codecpar, str.codecpar); }); }); diff --git a/install_ffmpeg.ts b/install_ffmpeg.ts index 1b4000d..9834c7e 100644 --- a/install_ffmpeg.ts +++ b/install_ffmpeg.ts @@ -25,14 +25,10 @@ import util from 'util'; import https from 'https'; import cp from 'child_process'; -//const [ mkdir, access, rename, execFile, exec ] = // eslint-disable-line -// [ fs.mkdir, fs.access, fs.rename, cp.execFile, cp.exec ].map(util.promisify); - const { mkdir, access, rename } = fs.promises; const [ execFile, exec ] = [ cp.execFile, cp.exec ].map(util.promisify); - async function get(ws: NodeJS.WritableStream, url: string, name: string): Promise { let received = 0; let totalLength = 0; 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/types/Beamstreams.d.ts b/types/Beamstreams.d.ts index 0a5bd6d..a916089 100644 --- a/types/Beamstreams.d.ts +++ b/types/Beamstreams.d.ts @@ -14,7 +14,7 @@ export interface WritableDemuxerStream extends NodeJS.WritableStream { * format details by consuming data from the source. The promise will wait indefinitely * until sufficient source data has been read. */ - demuxer(options: DemuxerCreateOptions): Promise + demuxer(options: DemuxerCreateOptions | string): Promise } /** * Create a WritableDemuxerStream to allow streaming to a Demuxer @@ -31,7 +31,7 @@ export interface ReadableMuxerStream extends NodeJS.ReadableStream { /** * Create a muxer for this source * @param options a MuxerCreateOptions object - * @returns A Muxer object + * @returns A Muxer object */ muxer(options: MuxerCreateOptions): Muxer } @@ -43,12 +43,20 @@ export interface ReadableMuxerStream extends NodeJS.ReadableStream { 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 +// export function governor(options: { highWaterMark: number }): { +// read(len: number): Promise +// write(data: Buffer): Promise +// finish(): undefined +// } +export class Governor { + constructor(options: { highWaterMark?: number }); + read(len: number): Promise + write(data: Buffer): Promise + finish(): undefined + private _adaptor: unknown; } + /** Source definition for a beamstream channel, from either a file or NodeJS ReadableStream */ export interface BeamstreamSource { url?: string diff --git a/types/Frame.d.ts b/types/Frame.d.ts index 5dac86c..87a1d39 100644 --- a/types/Frame.d.ts +++ b/types/Frame.d.ts @@ -175,6 +175,9 @@ export interface Frame { * `let f = beamcoder.frame({ width: 1920, height: 1080, format: 'yuv422p' }).alloc()` */ alloc(): Frame + + /** Retun a JSON string containing the object properties. */ + toJSON(): string } /** From 54ff08653cde75a4e905aae5100ccc249a2a54b6 Mon Sep 17 00:00:00 2001 From: UrielCh Date: Mon, 18 Apr 2022 16:09:08 +0300 Subject: [PATCH 05/68] more types --- beamstreams.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/beamstreams.ts b/beamstreams.ts index ebe9b78..3c9f521 100644 --- a/beamstreams.ts +++ b/beamstreams.ts @@ -273,7 +273,7 @@ type parallelBalancerType = Readable & { pushPkts: (packets, stream, streamIndex: number, final?: boolean) => any }; -function parallelBalancer(params: { name: string, highWaterMark: number }, streamType: 'video', numStreams: number): parallelBalancerType { +function parallelBalancer(params: { name: string, highWaterMark: number }, streamType: 'video' | 'audio', numStreams: number): parallelBalancerType { let resolveGet = null; const tag = 'video' === streamType ? 'v' : 'a'; const pending = []; @@ -333,7 +333,7 @@ function parallelBalancer(params: { name: string, highWaterMark: number }, strea }, }) as parallelBalancerType; - readStream.pushPkts = (packets, stream, streamIndex: number, final = false): any => { + readStream.pushPkts = (packets: {frames: Array, timings: [number, number]}, stream: {time_base: [number, number]}, streamIndex: number, final = false): any => { if (packets && packets.frames.length) { return packets.frames.reduce(async (promise, pkt) => { await promise; @@ -640,11 +640,11 @@ export async function makeSources(params: { video?: Array<{ sources: any[] }>, a } function runStreams( - streamType, + streamType: 'video' | 'audio', sources: Array<{ decoder: { decode: (pkts: any) => void, flush: () => void }, format: { streams: Array<{}> }, streamIndex: any, stream: any }>, filterer: { cb?: (result: any) => void, filter: (stream: any) => any }, - streams, - mux, + streams : Array<{}>, + mux: {writeFrame: (pkts: any)=> void}, muxBalancer: { writePkts: (packets, srcStream, dstStream, writeFn, final?: boolean) => any }) { return new Promise((resolve, reject) => { if (!sources.length) From f5038203ba216e0bc85d46e1bc6993823b33f1bb Mon Sep 17 00:00:00 2001 From: UrielCh Date: Mon, 18 Apr 2022 16:23:00 +0300 Subject: [PATCH 06/68] more types --- beamstreams.ts | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/beamstreams.ts b/beamstreams.ts index 3c9f521..dcc2687 100644 --- a/beamstreams.ts +++ b/beamstreams.ts @@ -32,7 +32,6 @@ interface BeamcoderType extends ReadableMuxerStream { /** Create object for AVIOContext based buffered I/O */ governor: typeof Governor; - /** * Create a demuxer for this source * @param options a DemuxerCreateOptions object @@ -689,17 +688,36 @@ function runStreams( }); } +export interface BeamstreamStream { + name: string; + time_base: any; + encoder: any; + stream: any; + codecpar: { + sample_rate: number; + frame_size: number; + format: any, + channel_layout: any; + }; +} +export interface BeamstreamSource { + decoder: any; + format: any; + streamIndex: number; + stream: any; +} + export async function makeStreams(params: { video: Array<{ filter?: any; - streams: any[], - sources: any[], + sources: Array, + streams: Array, filterSpec: string, }>, audio: Array<{ filter?: any; - sources: any[], - streams: any[], + sources: Array, + streams: Array, filterSpec: string, }>, out: { output_stream: any, formatName: string, url?: string, flags: any, options: any } From f3947e412b0dba1fd8dde6c656659c4547d26d2e Mon Sep 17 00:00:00 2001 From: UrielCh Date: Mon, 18 Apr 2022 16:29:19 +0300 Subject: [PATCH 07/68] move files --- .vscode/c_cpp_properties.json | 14 +++++++------- package.json | 2 +- test/codecParamsSpec.js | 2 +- test/decoderSpec.js | 2 +- test/demuxerSpec.js | 2 +- test/encoderSpec.js | 2 +- test/filtererSpec.js | 2 +- test/formatSpec.js | 2 +- test/frameSpec.js | 2 +- test/introspectionSpec.js | 2 +- test/muxerSpec.js | 2 +- test/packetSpec.js | 2 +- beamstreams.ts => ts/beamstreams.ts | 2 +- index.ts => ts/index.ts | 0 install_ffmpeg.ts => ts/install_ffmpeg.ts | 0 tsconfig.json | 3 ++- 16 files changed, 21 insertions(+), 20 deletions(-) rename beamstreams.ts => ts/beamstreams.ts (99%) rename index.ts => ts/index.ts (100%) rename install_ffmpeg.ts => ts/install_ffmpeg.ts (100%) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 404edc6..2ae81b7 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -3,13 +3,13 @@ { "name": "Win32", "includePath": [ - "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*", - "C:/Program Files (x86)/Windows Kits/8.1/Include/um", - "C:/Program Files (x86)/Windows Kits/10/Include/10.0.10240.0/ucrt", - "C:/Program Files (x86)/Windows Kits/8.1/Include/shared", - "C:/Program Files (x86)/Windows Kits/8.1/Include/winrt", + // "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/um", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/ucrt", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/shared", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/winrt", "${workspaceFolder}/ffmpeg/ffmpeg-5.x-win64-shared/include/**", - "${env:USERPROFILE}/AppData/Local/node-gyp/Cache/16.14.0/include/node" + "${env:USERPROFILE}/AppData/Local/node-gyp/Cache/16.13.1/include/node" ], "defines": [ "_DEBUG", @@ -17,7 +17,7 @@ "_UNICODE" ], "windowsSdkVersion": "8.1", - "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/cl.exe", + "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/VC/Tools/MSVC/14.29.30133/bin/Hostx86/x64/cl.exe", "cStandard": "c11", "cppStandard": "c++17", "intelliSenseMode": "msvc-x64" diff --git a/package.json b/package.json index 35ae67e..414e912 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "beamcoder", "version": "0.7.1", "description": "Node.js native bindings to FFmpeg.", - "main": "index.js", + "main": "ts/index.js", "types": "types.d.ts", "scripts": { "preinstall": "node install_ffmpeg.js", diff --git a/test/codecParamsSpec.js b/test/codecParamsSpec.js index f7a83e0..288557c 100644 --- a/test/codecParamsSpec.js +++ b/test/codecParamsSpec.js @@ -20,7 +20,7 @@ */ const test = require('tape'); -const beamcoder = require('../index.js'); +const beamcoder = require('../ts/index.js'); test('Creating codec parameters', t => { let cps = beamcoder.codecParameters(); diff --git a/test/decoderSpec.js b/test/decoderSpec.js index c427353..0d7dfc2 100644 --- a/test/decoderSpec.js +++ b/test/decoderSpec.js @@ -20,7 +20,7 @@ */ const test = require('tape'); -const beamcoder = require('../index.js'); +const beamcoder = require('../ts/index.js'); test('Creating a decoder', t => { let dec = beamcoder.decoder({ name: 'h264' }); diff --git a/test/demuxerSpec.js b/test/demuxerSpec.js index 8ccc400..d539e28 100644 --- a/test/demuxerSpec.js +++ b/test/demuxerSpec.js @@ -20,7 +20,7 @@ */ const test = require('tape'); -const beamcoder = require('../index.js'); +const beamcoder = require('../ts/index.js'); test('Creating a demuxer', async t => { let dm = await beamcoder.demuxer('https://www.elecard.com/storage/video/bbb_1080p_c.ts'); diff --git a/test/encoderSpec.js b/test/encoderSpec.js index 77b98eb..07e4477 100644 --- a/test/encoderSpec.js +++ b/test/encoderSpec.js @@ -20,7 +20,7 @@ */ const test = require('tape'); -const beamcoder = require('../index.js'); +const beamcoder = require('../ts/index.js'); test('Creating a video encoder', t => { let enc = beamcoder.encoder({ name: 'h264' }); diff --git a/test/filtererSpec.js b/test/filtererSpec.js index b1c018c..aaf9472 100644 --- a/test/filtererSpec.js +++ b/test/filtererSpec.js @@ -20,7 +20,7 @@ */ const test = require('tape'); -const beamcoder = require('../index.js'); +const beamcoder = require('../ts/index.js'); test('Create a filterer', async t => { let flt = await beamcoder.filterer({ diff --git a/test/formatSpec.js b/test/formatSpec.js index 8b8ba7e..6ca570e 100644 --- a/test/formatSpec.js +++ b/test/formatSpec.js @@ -20,7 +20,7 @@ */ const test = require('tape'); -const beamcoder = require('../index.js'); +const beamcoder = require('../ts/index.js'); const isExternal = o => Object.toString(o).indexOf('native code') >= 0; diff --git a/test/frameSpec.js b/test/frameSpec.js index 7f61896..81f3073 100644 --- a/test/frameSpec.js +++ b/test/frameSpec.js @@ -20,7 +20,7 @@ */ const test = require('tape'); -const beamcoder = require('../index.js'); +const beamcoder = require('../ts/index.js'); const util = require('util'); test('Create a frame', t => { diff --git a/test/introspectionSpec.js b/test/introspectionSpec.js index 87d3375..cc75414 100644 --- a/test/introspectionSpec.js +++ b/test/introspectionSpec.js @@ -20,7 +20,7 @@ */ const test = require('tape'); -const beamcoder = require('../index.js'); +const beamcoder = require('../ts/index.js'); test('Version information', t => { const verPos = beamcoder.avVersionInfo().indexOf('5.'); diff --git a/test/muxerSpec.js b/test/muxerSpec.js index b7069f2..c143d17 100644 --- a/test/muxerSpec.js +++ b/test/muxerSpec.js @@ -20,7 +20,7 @@ */ const test = require('tape'); -const beamcoder = require('../index.js'); +const beamcoder = require('../ts/index.js'); test('Creating a muxer', t => { let mx = beamcoder.muxer({ name: 'mpegts' }); diff --git a/test/packetSpec.js b/test/packetSpec.js index b65a5e5..42f38e0 100644 --- a/test/packetSpec.js +++ b/test/packetSpec.js @@ -20,7 +20,7 @@ */ const test = require('tape'); -const beamcoder = require('../index.js'); +const beamcoder = require('../ts/index.js'); test('Create a packet', t => { let pkt = beamcoder.packet(); diff --git a/beamstreams.ts b/ts/beamstreams.ts similarity index 99% rename from beamstreams.ts rename to ts/beamstreams.ts index dcc2687..983fde2 100644 --- a/beamstreams.ts +++ b/ts/beamstreams.ts @@ -19,7 +19,7 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ import bindings from 'bindings'; -import { Frame, Stream, Muxer, CodecPar, Codec, Governor, WritableDemuxerStream, ReadableMuxerStream, FiltererVideoOptions, FiltererAudioOptions, Filterer, Decoder, Demuxer, Encoder, InputFormat } from '.'; // Codec, CodecContext, +import { Frame, Stream, Muxer, CodecPar, Codec, Governor, WritableDemuxerStream, ReadableMuxerStream, FiltererVideoOptions, FiltererAudioOptions, Filterer, Decoder, Demuxer, Encoder, InputFormat } from '..'; // Codec, CodecContext, import { Writable, Readable, Transform } from 'stream'; type governorType = { diff --git a/index.ts b/ts/index.ts similarity index 100% rename from index.ts rename to ts/index.ts diff --git a/install_ffmpeg.ts b/ts/install_ffmpeg.ts similarity index 100% rename from install_ffmpeg.ts rename to ts/install_ffmpeg.ts diff --git a/tsconfig.json b/tsconfig.json index 27930d9..005b1da 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -97,5 +97,6 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + }, + "files": [ "ts/beamstreams.ts", "ts/index.ts", "ts/install_ffmpeg.ts" ] } From 021133d13804420c74061156b6e04b49e72c7ad1 Mon Sep 17 00:00:00 2001 From: UrielCh Date: Mon, 18 Apr 2022 16:36:41 +0300 Subject: [PATCH 08/68] split TS file --- .gitignore | 1 + ts/beamstreams.ts | 273 +---------------------------------------- ts/parallelBalancer.ts | 81 ++++++++++++ ts/teeBalancer.ts | 72 +++++++++++ ts/types.ts | 122 ++++++++++++++++++ 5 files changed, 280 insertions(+), 269 deletions(-) create mode 100644 ts/parallelBalancer.ts create mode 100644 ts/teeBalancer.ts create mode 100644 ts/types.ts diff --git a/.gitignore b/.gitignore index db2b58c..a4b352e 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,4 @@ ffmpeg/ beamstreams.js index.js install_ffmpeg.js +ts/*.js diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index 983fde2..2d458a4 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -19,108 +19,12 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ import bindings from 'bindings'; -import { Frame, Stream, Muxer, CodecPar, Codec, Governor, WritableDemuxerStream, ReadableMuxerStream, FiltererVideoOptions, FiltererAudioOptions, Filterer, Decoder, Demuxer, Encoder, InputFormat } from '..'; // Codec, CodecContext, +import { Frame, Stream, Muxer, CodecPar } from '..'; // Codec, CodecContext, import { Writable, Readable, Transform } from 'stream'; +import type { BeamcoderType, governorType, BeamstreamStream, BeamstreamSource } from './types'; -type governorType = { - read(len: number): Promise; - write(data: Buffer): Promise; - finish(): undefined; -}; - -interface BeamcoderType extends ReadableMuxerStream { - /** Create object for AVIOContext based buffered I/O */ - governor: typeof Governor; - - /** - * 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?: governorType, url?: string, iformat?: InputFormat, options?: { governor: governorType } } | string): Promise - // url: src.url, iformat: src.iformat, options: src.options - /** - * 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 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; - - /** - * 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 - - /** - * 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: { 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: { 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 -} +import { teeBalancer } from './teeBalancer'; +import { parallelBalancer } from './parallelBalancer'; const beamcoder = bindings('beamcoder') as BeamcoderType; @@ -268,156 +172,6 @@ class serialBalancer { } -type parallelBalancerType = Readable & { - pushPkts: (packets, stream, streamIndex: number, final?: boolean) => any -}; - -function parallelBalancer(params: { name: string, highWaterMark: number }, streamType: 'video' | 'audio', numStreams: number): parallelBalancerType { - 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: number, ts: number) => - new Promise(resolve => { - Object.assign(pending[streamIndex], { pkt, ts, final: pkt ? false : true, resolve }); - makeSet(resolveGet); - }); - - const pullSet = async () => new Promise<{ done: any, value: { timings: any } }>(resolve => makeSet(resolve)); - - const readStream: parallelBalancerType = 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); - } - })(); - }, - }) as parallelBalancerType; - - readStream.pushPkts = (packets: {frames: Array, timings: [number, number]}, stream: {time_base: [number, number]}, streamIndex: number, final = false): any => { - 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; -} - -type teeBalancerType = Readable[] & { pushFrames: (frames: any, unusedFlag?: boolean) => any }; - -function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, numStreams: number): teeBalancerType { - 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<{ done: boolean, value?: any }>(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: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; - this.push(result.value); - } - })(); - }, - })); - - readStreams.pushFrames = frames => { - return new Promise<{ value: { timings: any }, done: boolean }>(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: { name: 'encode' | 'dice' | 'decode' | 'filter', highWaterMark: number }, processFn, flushFn, reject) { return new Transform({ @@ -688,25 +442,6 @@ function runStreams( }); } -export interface BeamstreamStream { - name: string; - time_base: any; - encoder: any; - stream: any; - codecpar: { - sample_rate: number; - frame_size: number; - format: any, - channel_layout: any; - }; -} -export interface BeamstreamSource { - decoder: any; - format: any; - streamIndex: number; - stream: any; -} - export async function makeStreams(params: { video: Array<{ filter?: any; diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts new file mode 100644 index 0000000..951c3a4 --- /dev/null +++ b/ts/parallelBalancer.ts @@ -0,0 +1,81 @@ +import { Readable } from "stream"; + +type parallelBalancerType = Readable & { + pushPkts: (packets, stream, streamIndex: number, final?: boolean) => any +}; + +export function parallelBalancer(params: { name: string, highWaterMark: number }, streamType: 'video' | 'audio', numStreams: number): parallelBalancerType { + 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: number, ts: number) => + new Promise(resolve => { + Object.assign(pending[streamIndex], { pkt, ts, final: pkt ? false : true, resolve }); + makeSet(resolveGet); + }); + + const pullSet = async () => new Promise<{ done: any, value: { timings: any } }>(resolve => makeSet(resolve)); + + const readStream: parallelBalancerType = 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); + } + })(); + }, + }) as parallelBalancerType; + + readStream.pushPkts = (packets: { frames: Array, timings: [number, number] }, stream: { time_base: [number, number] }, streamIndex: number, final = false): any => { + 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; +} diff --git a/ts/teeBalancer.ts b/ts/teeBalancer.ts new file mode 100644 index 0000000..ac275d3 --- /dev/null +++ b/ts/teeBalancer.ts @@ -0,0 +1,72 @@ +import { Readable } from "stream"; + +type teeBalancerType = Readable[] & { pushFrames: (frames: any, unusedFlag?: boolean) => any }; + +export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, numStreams: number): teeBalancerType { + 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<{ done: boolean, value?: any }>(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: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; + this.push(result.value); + } + })(); + }, + })); + + readStreams.pushFrames = frames => { + return new Promise<{ value: { timings: any }, done: boolean }>(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/types.ts b/ts/types.ts new file mode 100644 index 0000000..814621c --- /dev/null +++ b/ts/types.ts @@ -0,0 +1,122 @@ +import type { Frame, Stream, Muxer, CodecPar, Codec, Governor, WritableDemuxerStream, ReadableMuxerStream, FiltererVideoOptions, FiltererAudioOptions, Filterer, Decoder, Demuxer, Encoder, InputFormat } from '..'; // Codec, CodecContext, + +export type governorType = { + read(len: number): Promise; + write(data: Buffer): Promise; + finish(): undefined; +}; + +export interface BeamcoderType extends ReadableMuxerStream { + /** Create object for AVIOContext based buffered I/O */ + governor: typeof Governor; + + /** + * 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?: governorType, url?: string, iformat?: InputFormat, options?: { governor: governorType } } | string): Promise + // url: src.url, iformat: src.iformat, options: src.options + /** + * 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 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; + + /** + * 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 + + /** + * 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: { 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: { 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 +} + + +export interface BeamstreamStream { + name: string; + time_base: any; + encoder: any; + stream: any; + codecpar: { + sample_rate: number; + frame_size: number; + format: any, + channel_layout: any; + }; +} +export interface BeamstreamSource { + decoder: any; + format: any; + streamIndex: number; + stream: any; +} + From 919141ade2506b875e8e8c6840686aaf46ec8974 Mon Sep 17 00:00:00 2001 From: UrielCh Date: Mon, 18 Apr 2022 17:56:04 +0300 Subject: [PATCH 09/68] more types --- ts/beamstreams.ts | 121 +++++++++++++++---------------------------- ts/serialBalancer.ts | 52 +++++++++++++++++++ ts/types.ts | 11 ++++ 3 files changed, 106 insertions(+), 78 deletions(-) create mode 100644 ts/serialBalancer.ts diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index 2d458a4..f619fb2 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -21,15 +21,16 @@ import bindings from 'bindings'; import { Frame, Stream, Muxer, CodecPar } from '..'; // Codec, CodecContext, import { Writable, Readable, Transform } from 'stream'; -import type { BeamcoderType, governorType, BeamstreamStream, BeamstreamSource } from './types'; +import type { BeamcoderType, governorType, BeamstreamStream, BeamstreamSource, Timing, ffStats } from './types'; import { teeBalancer } from './teeBalancer'; import { parallelBalancer } from './parallelBalancer'; +import { serialBalancer } from './serialBalancer'; const beamcoder = bindings('beamcoder') as BeamcoderType; const doTimings = false; -const timings = []; +const timings = [] as Array<{[key: string]: Timing}>; class frameDicer { @@ -120,60 +121,11 @@ class frameDicer { }; } -class serialBalancer { - pending = []; - - 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 }); - } - - adjustTS(pkt: { pts: number, dts: number, duration: number }, srcTB: [number, number], dstTB: [number, number]): void { - 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); - }; - - - pullPkts(pkt: {}, 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(); - }); - }; - - writePkts(packets: { - packets: Array<{ stream_index: number, pts: number, dts: number, duration: number }> - } | null, - srcStream: { time_base: [number, number] }, - dstStream: { - time_base: [number, number], - index: number - }, - writeFn: (r: void) => void, final = false) { - if (packets && packets.packets.length) { - return packets.packets.reduce(async (promise, pkt) => { - await promise; - 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]; - return writeFn(await this.pullPkts(pkt, dstStream.index, pktTS)); - }, Promise.resolve()); - } else if (final) - return this.pullPkts(null, dstStream.index, Number.MAX_VALUE); - }; -} - - - -function transformStream(params: { name: 'encode' | 'dice' | 'decode' | 'filter', highWaterMark: number }, processFn, flushFn, reject) { +function transformStream( + params: { name: 'encode' | 'dice' | 'decode' | 'filter', highWaterMark: number }, + processFn: (val: { stream_index: number, pts: number, dts: number, duration: number, timings: any }) => Promise<{timings: {[key: string]: Timing}}>, + flushFn: () => (Promise<{ timings: { [key: string]: Timing; }}>) | null | void, + reject: (err?: Error) => void) { return new Transform({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, @@ -184,7 +136,7 @@ function transformStream(params: { name: 'encode' | 'dice' | 'decode' | 'filter' const result = await processFn(val); result.timings = val.timings; if (result.timings) - result.timings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; + result.timings[params.name] = { reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; cb(null, result); })().catch(cb); }, @@ -198,19 +150,19 @@ function transformStream(params: { name: 'encode' | 'dice' | 'decode' | 'filter' }).on('error', err => reject(err)); } -const calcStats = (arr: Array, elem: string, prop: string) => { - 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 }; +const calcStats = (arr: Array, elem: string, prop: string): ffStats => { + const mean: number = arr.reduce((acc, cur) => cur[elem] ? acc + cur[elem][prop] : acc, 0) / arr.length; + const stdDev: number = Math.pow(arr.reduce((acc, cur) => cur[elem] ? acc + Math.pow(cur[elem][prop] - mean, 2) : acc, 0) / arr.length, 0.5); + const max: number = arr.reduce((acc, cur) => cur[elem] ? Math.max(cur[elem][prop], acc) : acc, 0); + const min: number = arr.reduce((acc, cur) => cur[elem] ? Math.min(cur[elem][prop], acc) : acc, Number.MAX_VALUE); + return { mean, stdDev, max, min }; }; -function writeStream(params: { name: string, highWaterMark?: number }, processFn: (val: { timings: any }) => Promise, finalFn: () => Promise, reject: (err: Error) => void) { +function writeStream(params: { name: string, highWaterMark?: number }, processFn: (val: { stream_index: number, pts: number, dts: number, duration: number, timings: any }) => Promise, finalFn: () => Promise, reject: (err: Error) => void) { return new Writable({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - write(val: { timings: any }, encoding: BufferEncoding, cb: (error?: Error | null, result?: any) => void) { + write(val: { stream_index: number, pts: number, dts: number, duration: number, timings: any }, encoding: BufferEncoding, cb: (error?: Error | null, result?: any) => void) { (async () => { const start = process.hrtime(); const reqTime = start[0] * 1e3 + start[1] / 1e6; @@ -228,18 +180,18 @@ function writeStream(params: { name: string, highWaterMark?: number }, processFn (async () => { const result = finalFn ? await finalFn() : null; if (doTimings && ('mux' === params.name)) { - const elapsedStats = {}; + 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 = {}; + 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 = {}; + 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); @@ -257,11 +209,20 @@ function writeStream(params: { name: string, highWaterMark?: number }, processFn }).on('error', err => reject(err)); } -function readStream(params: {highWaterMark?: number}, demuxer: { read: () => Promise, streams: Array<{time_base: [number, number]}> }, ms: { end: number }, index: number) { +interface Packet { + stream_index: number; + pts: number; + timings: { + read?: Timing; + }; +} + + +function readStream(params: {highWaterMark?: number}, demuxer: { read: () => Promise, streams: Array<{time_base: [number, number]}> }, ms: { end: number }, index: number) { 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; + async function getPacket(): Promise { + let packet: Packet | null = null; do { packet = await demuxer.read(); } while (packet && packet.stream_index !== index); return packet; @@ -300,7 +261,7 @@ function createBeamWritableStream(params: { highwaterMark?: number }, governor: } type demuxerStreamType = Writable & { demuxer: (options: { governor: governorType }) => Promise }; -export function demuxerStream(params) { +export function demuxerStream(params: { highwaterMark?: number }) { const governor = new beamcoder.governor({}); const stream: demuxerStreamType = createBeamWritableStream(params, governor) as demuxerStreamType; stream.on('finish', () => governor.finish()); @@ -394,16 +355,20 @@ export async function makeSources(params: { video?: Array<{ sources: any[] }>, a function runStreams( streamType: 'video' | 'audio', - sources: Array<{ decoder: { decode: (pkts: any) => void, flush: () => void }, format: { streams: Array<{}> }, streamIndex: any, stream: any }>, + sources: Array<{ decoder: { + decode: (pkts: any) => any, + flush: () => any + }, format: { streams: Array<{}> }, streamIndex: any, stream: any }>, filterer: { cb?: (result: any) => void, filter: (stream: any) => any }, streams : Array<{}>, mux: {writeFrame: (pkts: any)=> void}, - muxBalancer: { writePkts: (packets, srcStream, dstStream, writeFn, final?: boolean) => any }) { + muxBalancer: serialBalancer) { + // serialBalancer // { writePkts: (packets: {timings: any; }, srcStream: {}, dstStream: {}, writeFn: {}, final?: boolean) => any } return new Promise((resolve, reject) => { if (!sources.length) return resolve(); - const timeBaseStream = sources[0].format.streams[sources[0].streamIndex]; + const timeBaseStream: any = sources[0].format.streams[sources[0].streamIndex]; const filterBalancer = parallelBalancer({ name: 'filterBalance', highWaterMark: 1 }, streamType, sources.length); sources.forEach((src, srcIndex: number) => { @@ -429,11 +394,11 @@ function runStreams( streams.forEach((str: { encoder: any, stream: any }, i) => { const dicer = new frameDicer(str.encoder, 'audio' === streamType); const diceStream = transformStream({ name: 'dice', highWaterMark: 1 }, - frms => dicer.dice(frms), () => dicer.dice([], true), reject); + frms => dicer.dice(frms as any), () => 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)), + pkts => muxBalancer.writePkts(pkts as any, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts)), () => muxBalancer.writePkts(null, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts), true), reject); muxStream.on('finish', resolve); @@ -592,7 +557,7 @@ export async function makeStreams(params: { await mux.writeHeader({ options: params.out.options ? params.out.options : {} }); const muxBalancer = new serialBalancer(mux.streams.length); - const muxStreamPromises = []; + const muxStreamPromises: Promise[] = []; 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); diff --git a/ts/serialBalancer.ts b/ts/serialBalancer.ts new file mode 100644 index 0000000..e34743a --- /dev/null +++ b/ts/serialBalancer.ts @@ -0,0 +1,52 @@ +export class serialBalancer { + pending = [] as { ts:number, streamIndex: number, resolve?: (result: any) => void, pkt?: any }[]; + + 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 }); + } + + adjustTS(pkt: { pts: number, dts: number, duration: number }, srcTB: [number, number], dstTB: [number, number]): void { + 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); + }; + + + pullPkts(pkt: {}, 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(); + }); + }; + + writePkts(packets: { + packets: Array<{ stream_index: number, pts: number, dts: number, duration: number }> + } | null, + srcStream: { time_base: [number, number] }, + dstStream: { + time_base: [number, number], + index: number + }, + writeFn: (r: void) => void, final = false) { + if (packets && packets.packets.length) { + return packets.packets.reduce(async (promise, pkt) => { + await promise; + 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]; + return writeFn(await this.pullPkts(pkt, dstStream.index, pktTS)); + }, Promise.resolve()); + } else if (final) + return this.pullPkts(null, dstStream.index, Number.MAX_VALUE); + }; + } + + \ No newline at end of file diff --git a/ts/types.ts b/ts/types.ts index 814621c..74a8e02 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -120,3 +120,14 @@ export interface BeamstreamSource { stream: any; } +export interface Timing { + reqTime: number; + elapsed: number; +} + +export interface ffStats { + mean: number; + stdDev: number; + max: number; + min: number; +} \ No newline at end of file From ddb65366f73fc19d8c8b3cd6b5d8a836cdc7443c Mon Sep 17 00:00:00 2001 From: UrielCh Date: Tue, 19 Apr 2022 09:17:48 +0300 Subject: [PATCH 10/68] migrate example --- examples/{encode_h264.js => encode_h264.ts} | 8 +- examples/{jpeg_app.js => jpeg_app.ts} | 35 +++++- examples/jpeg_filter_app.js | 101 ---------------- examples/jpeg_filter_app.ts | 123 ++++++++++++++++++++ examples/{make_mp4.js => make_mp4.ts} | 20 ++-- src/decode.cc | 15 ++- ts/beamstreams.ts | 83 ++++++------- ts/index.ts | 9 +- ts/types.ts | 58 +++++++-- types/Beamstreams.d.ts | 29 ++++- types/Demuxer.d.ts | 3 + types/Filter.d.ts | 4 +- types/FormatContext.d.ts | 2 +- types/Muxer.d.ts | 8 +- types/Packet.d.ts | 35 +++--- 15 files changed, 325 insertions(+), 208 deletions(-) rename examples/{encode_h264.js => encode_h264.ts} (93%) rename examples/{jpeg_app.js => jpeg_app.ts} (69%) delete mode 100644 examples/jpeg_filter_app.js create mode 100644 examples/jpeg_filter_app.ts rename examples/{make_mp4.js => make_mp4.ts} (89%) diff --git a/examples/encode_h264.js b/examples/encode_h264.ts similarity index 93% rename from examples/encode_h264.js rename to examples/encode_h264.ts index 8041baf..d93d569 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 '../ts/index'; // 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', diff --git a/examples/jpeg_app.js b/examples/jpeg_app.ts similarity index 69% rename from examples/jpeg_app.js rename to examples/jpeg_app.ts index 1768ce2..e1d3133 100644 --- a/examples/jpeg_app.js +++ b/examples/jpeg_app.ts @@ -24,13 +24,37 @@ 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 +import beamcoder from '../ts/index'; // Use require('beamcoder') externally +import Koa from 'koa'; // Add koa to package.json dependencies +import fs from 'fs'; // Add koa to package.json dependencies + +// 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 // + + 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(' ')}
+ `; + return; + } + let parts = ctx.path.split('/'); // Split the path into filename and time - if ((parts.length < 3) || (isNaN(+parts[2]))) return; // Ignore favicon etc.. + if ((parts.length < 3) || (isNaN(+parts[2]))) { + ctx.status = 404; + ctx.type = 'text/html'; + ctx.body = ` + expected path: // + `; + return; // Ignore favicon etc.. + } + + try { 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) @@ -50,6 +74,11 @@ app.use(async (ctx) => { // Assume HTTP GET with path // 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 diff --git a/examples/jpeg_filter_app.js b/examples/jpeg_filter_app.js deleted file mode 100644 index e9d45f3..0000000 --- a/examples/jpeg_filter_app.js +++ /dev/null @@ -1,101 +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 with added filtering. Run in a folder of media files. - - 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 -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(); - - // audio test - const aindex = 2; - const audStr = dm.streams[aindex]; - // console.log(audStr); - let adec = beamcoder.decoder({ demuxer: dm, stream_index: aindex }); // Create a decoder - // console.log(adec); - let apkt = await dm.read(); - let afrm = await adec.decode(apkt); - console.log(afrm.frames); - const audEnc = beamcoder.encoder({ - name: 'aac', - sample_fmt: 'fltp', - sample_rate: 48000, - channels: 1, - 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 }], - outputParams: [{ - sampleRate: 1024, - sampleFormat: 'fltp', - channelLayout: 'mono' }], - filterSpec: 'aresample=1024' }); - - const audFiltPkt = await audFilt.filter([{ frames: afrm }]); - const encPkt = await audEnc.encode(audFiltPkt[0].frames[0]); - console.log(encPkt); - - let vstr = dm.streams[0]; // Select the video stream (assumes stream 0) - let filt = await beamcoder.filterer({ // Create a filterer for video - filterType: 'video', - inputParams: [{ - width: vstr.codecpar.width, - height: vstr.codecpar.height, - pixelFormat: vstr.codecpar.format, - timeBase: vstr.time_base, - 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 - 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, - height: filtFrame.height, - pix_fmt: 'yuvj422p', - 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 -}); - -app.listen(3000); // Start the server on port 3000 diff --git a/examples/jpeg_filter_app.ts b/examples/jpeg_filter_app.ts new file mode 100644 index 0000000..d4d634a --- /dev/null +++ b/examples/jpeg_filter_app.ts @@ -0,0 +1,123 @@ +/* + 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 with added filtering. Run in a folder of media files. + + Will convert source pixel formats to 8-bit YUV 4:2:2 +*/ + +import beamcoder from '../ts/index'; // Use require('beamcoder') externally +import Koa from 'koa'; // Add koa to package.json dependencies +import { DecodedFrames } from '../types'; +const app = new Koa(); + +app.use(async (ctx) => { // Assume HTTP GET with path // + 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 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: 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(); + + // audio test + // console.log(demuxer); + // const stream_index = demuxer.streams.length - 1; + // console.log(demuxer.streams); + // console.log(demuxer.max_streams); + + const streamSoundIdx = demuxer.streams.findIndex(stream => stream.codecpar.codec_type === 'audio'); + if (streamSoundIdx >= 3) { + const audStr = demuxer.streams[streamSoundIdx]; + // console.log(audStr); + let adec = beamcoder.decoder({ demuxer, stream_index: streamSoundIdx }); // Create a decoder + // console.log(adec); + let apkt = await demuxer.read(); + let afrm: DecodedFrames = await adec.decode(apkt); + console.log(afrm.frames); + const audEnc = beamcoder.encoder({ + name: 'aac', + sample_fmt: 'fltp', + sample_rate: 48000, + channels: 1, + 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 + }], + outputParams: [{ + sampleRate: 1024, + sampleFormat: 'fltp', + channelLayout: 'mono' + }], + filterSpec: 'aresample=1024' + }); + const audFiltPkt = await audFilt.filter([{ frames: afrm as any }]); + const encPkt = await audEnc.encode(audFiltPkt[0].frames[0]); + console.log(encPkt); + } + + const streamVideoIdx = demuxer.streams.findIndex(stream => stream.codecpar.codec_type === 'video'); + let vstr = demuxer.streams[streamVideoIdx]; // Select the video stream (assumes stream 0) + let filt = await beamcoder.filterer({ // Create a filterer for video + filterType: 'video', + inputParams: [{ + width: vstr.codecpar.width, + height: vstr.codecpar.height, + pixelFormat: vstr.codecpar.format, + timeBase: vstr.time_base, + pixelAspect: vstr.sample_aspect_ratio + }], + outputParams: [{ pixelFormat: 'yuv422p' }], + filterSpec: 'scale=640:360, colorspace=range=jpeg:all=bt709' + }); + let filtResult = await filt.filter([{ frames: decResult as any }]); // 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, + height: filtFrame.height, + pix_fmt: 'yuvj422p', + 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 + }; + // console.log(err); + } +}); + +app.listen(3001); // Start the server on port 3000 diff --git a/examples/make_mp4.js b/examples/make_mp4.ts similarity index 89% rename from examples/make_mp4.js rename to examples/make_mp4.ts index eaddbc9..8579e0c 100644 --- a/examples/make_mp4.js +++ b/examples/make_mp4.ts @@ -25,9 +25,12 @@ Output can be viewed in VLC. Make sure "All Files" is selected to see the file. */ +import fs from 'fs'; -const beamcoder = require('../index.js'); // Use require('beamcoder') externally -const fs = require('fs'); +import beamcoder from '../ts/index'; + +// const beamcoder = require('../index.js'); // Use require('beamcoder') externally +// const fs = require('fs'); let endcode = Buffer.from([0, 0, 1, 0xb7]); @@ -38,15 +41,15 @@ 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' } }; - let encoder = await beamcoder.encoder(encParams); + let encoder = beamcoder.encoder(encParams); console.log('Encoder', encoder); const mux = beamcoder.muxer({ format_name: 'mp4' }); @@ -60,10 +63,7 @@ async function run() { format: 'yuv420p' }); console.log(vstr); - await mux.openIO({ - url: 'file:test.mp4' - }); - await mux.writeHeader(); + await mux.openIO({ url: 'file:test.mp4' }); let outFile = fs.createWriteStream(process.argv[2]); @@ -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/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/ts/beamstreams.ts b/ts/beamstreams.ts index f619fb2..03de2a0 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -19,9 +19,9 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ import bindings from 'bindings'; -import { Frame, Stream, Muxer, CodecPar } from '..'; // Codec, CodecContext, +import { Frame, Stream, Muxer, CodecPar, WritableDemuxerStream, BeamstreamParams, Demuxer, Packet, Filterer, BeamstreamSource } from '..'; // Codec, CodecContext, import { Writable, Readable, Transform } from 'stream'; -import type { BeamcoderType, governorType, BeamstreamStream, BeamstreamSource, Timing, ffStats } from './types'; +import type { BeamcoderType, governorType, Timing, ffStats } from './types'; import { teeBalancer } from './teeBalancer'; import { parallelBalancer } from './parallelBalancer'; @@ -209,16 +209,16 @@ function writeStream(params: { name: string, highWaterMark?: number }, processFn }).on('error', err => reject(err)); } -interface Packet { - stream_index: number; - pts: number; - timings: { - read?: Timing; - }; -} +// interface Packet { +// stream_index: number; +// pts: number; +// timings: { +// read?: Timing; +// }; +// } -function readStream(params: {highWaterMark?: number}, demuxer: { read: () => Promise, streams: Array<{time_base: [number, number]}> }, ms: { end: number }, index: number) { +function readStream(params: {highWaterMark?: number}, demuxer: Demuxer, ms: { end: number }, index: number) { 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 { @@ -261,7 +261,7 @@ function createBeamWritableStream(params: { highwaterMark?: number }, governor: } type demuxerStreamType = Writable & { demuxer: (options: { governor: governorType }) => Promise }; -export function demuxerStream(params: { highwaterMark?: number }) { +export function demuxerStream(params: { highwaterMark?: number }): WritableDemuxerStream { const governor = new beamcoder.governor({}); const stream: demuxerStreamType = createBeamWritableStream(params, governor) as demuxerStreamType; stream.on('finish', () => governor.finish()); @@ -271,7 +271,7 @@ export function demuxerStream(params: { highwaterMark?: number }) { // 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; + return stream as WritableDemuxerStream; } @@ -304,18 +304,18 @@ export function muxerStream(params: { highwaterMark: number }): muxerStreamType }; return stream; } - -export async function makeSources(params: { video?: Array<{ sources: any[] }>, audio?: Array<{ sources: any[] }> }) { +// params: { video?: Array<{ sources: any[] }>, audio?: Array<{ sources: any[] }> } +export async function makeSources(params: BeamstreamParams): Promise { if (!params.video) params.video = []; if (!params.audio) params.audio = []; - + debugger; 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 }); + src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options as any }); // FIXME })); params.audio.forEach(p => p.sources.forEach(src => { if (src.input_stream) { @@ -323,10 +323,10 @@ export async function makeSources(params: { video?: Array<{ sources: any[] }>, a 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 }); + src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options as any }); // FIXME })); - await params.video.reduce(async (promise, p) => { + await (params.video.reduce as any)(async (promise, p) => { // FIXME await promise; return p.sources.reduce(async (promise, src) => { await promise; @@ -336,7 +336,8 @@ export async function makeSources(params: { video?: Array<{ sources: any[] }>, a return src.format; }, Promise.resolve()); }, Promise.resolve()); - await params.audio.reduce(async (promise, p) => { + + await (params.audio.reduce as any)(async (promise, p) => { await promise; return p.sources.reduce(async (promise, src) => { await promise; @@ -348,17 +349,15 @@ export async function makeSources(params: { video?: Array<{ sources: any[] }>, a }, Promise.resolve()); params.video.forEach(p => p.sources.forEach(src => - src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex))); + src.stream = readStream({ highWaterMark: 1 }, src.format as Demuxer, src.ms, src.streamIndex))); params.audio.forEach(p => p.sources.forEach(src => - src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex))); + src.stream = readStream({ highWaterMark: 1 }, src.format as Demuxer, src.ms, src.streamIndex))); } function runStreams( streamType: 'video' | 'audio', - sources: Array<{ decoder: { - decode: (pkts: any) => any, - flush: () => any - }, format: { streams: Array<{}> }, streamIndex: any, stream: any }>, + // sources: Array<{ decoder: { decode: (pkts: any) => any, flush: () => any }, format: { streams: Array<{}> }, streamIndex: any, stream: any }>, + sources: Array, filterer: { cb?: (result: any) => void, filter: (stream: any) => any }, streams : Array<{}>, mux: {writeFrame: (pkts: any)=> void}, @@ -368,15 +367,15 @@ function runStreams( if (!sources.length) return resolve(); - const timeBaseStream: any = sources[0].format.streams[sources[0].streamIndex]; + const timeBaseStream: any = (sources[0].format as Demuxer).streams[sources[0].streamIndex]; const filterBalancer = parallelBalancer({ name: 'filterBalance', highWaterMark: 1 }, streamType, sources.length); sources.forEach((src, srcIndex: number) => { 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); + pkts => filterBalancer.pushPkts(pkts, (src.format as Demuxer).streams[src.streamIndex], srcIndex), + () => filterBalancer.pushPkts(null, (src.format as Demuxer).streams[src.streamIndex], srcIndex, true), reject); src.stream.pipe(decStream).pipe(filterSource); }); @@ -407,22 +406,7 @@ function runStreams( }); } -export async function makeStreams(params: { - video: Array<{ - filter?: any; - sources: Array, - streams: Array, - filterSpec: string, - }>, - audio: Array<{ - filter?: any; - sources: Array, - streams: Array, - filterSpec: string, - }>, - out: { output_stream: any, formatName: string, url?: string, flags: any, options: any } -} -) { +export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Promise }> { params.video.forEach(p => { p.sources.forEach(src => src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); @@ -430,6 +414,7 @@ export async function makeStreams(params: { params.audio.forEach(p => { p.sources.forEach(src => src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); + // {demuxer: Demuxer | Promise, stream_index: number} }); params.video.forEach(p => { @@ -437,7 +422,7 @@ export async function makeStreams(params: { // FiltererVideoOptions filterType: 'video', inputParams: p.sources.map((src, i) => { - const stream = src.format.streams[src.streamIndex]; + const stream = (src.format as Demuxer).streams[src.streamIndex]; return { name: `in${i}:v`, width: stream.codecpar.width, @@ -459,7 +444,7 @@ export async function makeStreams(params: { p.filter = beamcoder.filterer({ filterType: 'audio', inputParams: p.sources.map((src, i) => { - const stream = src.format.streams[src.streamIndex]; + const stream = (src.format as Demuxer).streams[src.streamIndex]; return { name: `in${i}:a`, sampleRate: src.decoder.sample_rate, @@ -493,14 +478,14 @@ export async function makeStreams(params: { params.video.forEach(p => { p.streams.forEach((str, i) => { - const encParams = p.filter.graph.filters.find(f => f.name === `out${i}:v`).inputs[0]; + const encParams = (p.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, + time_base: encParams.time_base as [number, number], // framerate: [encParams.time_base[1], encParams.time_base[0]], // bit_rate: 2000000, // gop_size: 10, @@ -513,7 +498,7 @@ export async function makeStreams(params: { params.audio.forEach(p => { p.streams.forEach((str, i) => { - const encParams = p.filter.graph.filters.find(f => f.name === `out${i}:a`).inputs[0]; + const encParams: { format : string, sample_rate: number, channel_layout: string} = p.filter.graph.filters.find(f => f.name === `out${i}:a`).inputs[0]; str.encoder = beamcoder.encoder({ name: str.name, sample_fmt: encParams.format, diff --git a/ts/index.ts b/ts/index.ts index 667043e..86022ed 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -20,7 +20,7 @@ */ import bindings from 'bindings'; -const beamcoder = bindings('beamcoder') +const beamcoder = bindings('beamcoder') as BeamcoderType; // import * as beamstreams from './beamstreams.js'; // Provide useful debug on segfault-related crash @@ -36,7 +36,8 @@ https://github.com/Streampunk/beamcoder/blob/master/LICENSE`; console.log(splash); console.log('Using FFmpeg version', beamcoder.avVersionInfo()); -import {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams.js'; +import {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams'; +import { BeamcoderType } from './types'; // export {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams.js'; @@ -46,6 +47,6 @@ beamcoder.muxerStream = muxerStream; beamcoder.makeSources = makeSources; beamcoder.makeStreams = makeStreams; -// export default beamcoder; +export default beamcoder; -module.exports = beamcoder; +// module.exports = beamcoder; diff --git a/ts/types.ts b/ts/types.ts index 74a8e02..0200209 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -1,4 +1,4 @@ -import type { Frame, Stream, Muxer, CodecPar, Codec, Governor, WritableDemuxerStream, ReadableMuxerStream, FiltererVideoOptions, FiltererAudioOptions, Filterer, Decoder, Demuxer, Encoder, InputFormat } from '..'; // Codec, CodecContext, +import type { Frame, Stream, Muxer, CodecPar, Codec, Governor, WritableDemuxerStream, ReadableMuxerStream, FiltererVideoOptions, FiltererAudioOptions, Filterer, Decoder, Demuxer, Encoder, InputFormat, BeamstreamParams } from '..'; // Codec, CodecContext, export type governorType = { read(len: number): Promise; @@ -6,10 +6,42 @@ export type governorType = { finish(): undefined; }; +export type pixelFormat = 'yuv420p' | 'yuyv422' | 'rgb24' | 'bgr24' | 'yuv422p' | 'yuv444p' | 'yuv410p' | 'yuv411p' | 'gray' | 'monow' | 'monob' | 'pal8' | 'yuvj420p' | 'yuvj422p' | 'yuvj444p' | 'uyvy422' | 'uyyvyy411' | 'bgr8' | 'bgr4' | 'bgr4_byte' | 'rgb8' | 'rgb4' | 'rgb4_byte' | 'nv12' | 'nv21' | 'argb' | 'rgba' | 'abgr' | 'bgra' | 'gray16be' | 'gray16le' | 'yuv440p' | 'yuvj440p' | 'yuva420p' | 'rgb48be' | 'rgb48le' | 'rgb565be' | 'rgb565le' | 'rgb555be' | 'rgb555le' | 'bgr565be' | 'bgr565le' | 'bgr555be' | 'bgr555le' | 'vaapi_moco' | 'vaapi_idct' | 'vaapi_vld' | 'yuv420p16le' | 'yuv420p16be' | 'yuv422p16le' | 'yuv422p16be' | 'yuv444p16le' | 'yuv444p16be' | 'dxva2_vld' | 'rgb444le' | 'rgb444be' | 'bgr444le' | 'bgr444be' | 'ya8' | 'bgr48be' | 'bgr48le' | 'yuv420p9be' | 'yuv420p9le' | 'yuv420p10be' | 'yuv420p10le' | 'yuv422p10be' | 'yuv422p10le' | 'yuv444p9be' | 'yuv444p9le' | 'yuv444p10be' | 'yuv444p10le' | 'yuv422p9be' | 'yuv422p9le' | 'gbrp' | 'gbrp9be' | 'gbrp9le' | 'gbrp10be' | 'gbrp10le' | 'gbrp16be' | 'gbrp16le' | 'yuva422p' | 'yuva444p' | 'yuva420p9be' | 'yuva420p9le' | 'yuva422p9be' | 'yuva422p9le' | 'yuva444p9be' | 'yuva444p9le' | 'yuva420p10be' | 'yuva420p10le' | 'yuva422p10be' | 'yuva422p10le' | 'yuva444p10be' | 'yuva444p10le' | 'yuva420p16be' | 'yuva420p16le' | 'yuva422p16be' | 'yuva422p16le' | 'yuva444p16be' | 'yuva444p16le' | 'vdpau' | 'xyz12le' | 'xyz12be' | 'nv16' | 'nv20le' | 'nv20be' | 'rgba64be' | 'rgba64le' | 'bgra64be' | 'bgra64le' | 'yvyu422' | 'ya16be' | 'ya16le' | 'gbrap' | 'gbrap16be' | 'gbrap16le' | 'qsv' | 'mmal' | 'd3d11va_vld' | 'cuda' | '0rgb' | 'rgb0' | '0bgr' | 'bgr0' | 'yuv420p12be' | 'yuv420p12le' | 'yuv420p14be' | 'yuv420p14le' | 'yuv422p12be' | 'yuv422p12le' | 'yuv422p14be' | 'yuv422p14le' | 'yuv444p12be' | 'yuv444p12le' | 'yuv444p14be' | 'yuv444p14le' | 'gbrp12be' | 'gbrp12le' | 'gbrp14be' | 'gbrp14le' | 'yuvj411p' | 'bayer_bggr8' | 'bayer_rggb8' | 'bayer_gbrg8' | 'bayer_grbg8' | 'bayer_bggr16le' | 'bayer_bggr16be' | 'bayer_rggb16le' | 'bayer_rggb16be' | 'bayer_gbrg16le' | 'bayer_gbrg16be' | 'bayer_grbg16le' | 'bayer_grbg16be' | 'xvmc' | 'yuv440p10le' | 'yuv440p10be' | 'yuv440p12le' | 'yuv440p12be' | 'ayuv64le' | 'ayuv64be' | 'videotoolbox_vld' | 'p010le' | 'p010be' | 'gbrap12be' | 'gbrap12le' | 'gbrap10be' | 'gbrap10le' | 'mediacodec' | 'gray12be' | 'gray12le' | 'gray10be' | 'gray10le' | 'p016le' | 'p016be' | 'd3d11' | 'gray9be' | 'gray9le' | 'gbrpf32be' | 'gbrpf32le' | 'gbrapf32be' | 'gbrapf32le' | 'drm_prime' | 'opencl' | 'gray14be' | 'gray14le' | 'grayf32be' | 'grayf32le' | 'yuva422p12be' | 'yuva422p12le' | 'yuva444p12be' | 'yuva444p12le' | 'nv24' | 'nv42'; + + +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 + +} + 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 @@ -20,11 +52,6 @@ export interface BeamcoderType extends ReadableMuxerStream { // this code look to have error.... demuxer(options: { governor?: governorType, url?: string, iformat?: InputFormat, options?: { governor: governorType } } | string): Promise // url: src.url, iformat: src.iformat, options: src.options - /** - * 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 a WritableDemuxerStream to allow streaming to a Demuxer * @param options.highwaterMark Buffer level when `stream.write()` starts returng false. @@ -32,6 +59,19 @@ export interface BeamcoderType extends ReadableMuxerStream { */ 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. @@ -51,7 +91,7 @@ export interface BeamcoderType extends ReadableMuxerStream { * @param ... Any non-readonly parameters from the Decoder object as required * @returns A Decoder object - note creation is synchronous */ - decoder(options: { name: string, [key: string]: any }): Decoder + decoder(options: { demuxer: Demuxer | Promise, stream_index: number }): Decoder // { name: string, [key: string]: any } /** * Create a decoder by codec_id * @param codec_id The codec ID from AV_CODEC_ID_xxx @@ -90,14 +130,14 @@ export interface BeamcoderType extends ReadableMuxerStream { * @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 + 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: { codec_id: number, [key: string]: any }): Encoder + encoder(options: commonEncoderParms & { codec_id: number }): Encoder } diff --git a/types/Beamstreams.d.ts b/types/Beamstreams.d.ts index a916089..f21510e 100644 --- a/types/Beamstreams.d.ts +++ b/types/Beamstreams.d.ts @@ -31,7 +31,7 @@ export interface ReadableMuxerStream extends NodeJS.ReadableStream { /** * Create a muxer for this source * @param options a MuxerCreateOptions object - * @returns A Muxer object + * @returns A Muxer object */ muxer(options: MuxerCreateOptions): Muxer } @@ -64,19 +64,29 @@ export interface BeamstreamSource { ms?: { start: number, end: number } streamIndex?: number iformat?: InputFormat - options?: { [key: string]: any } + options?: { [key: string]: any } + format?: Demuxer | Promise; + stream: any; // FIXME + decoder?: Decoder; // FIXME + + + } /** Codec definition for the destination channel */ export interface BeamstreamStream { name: string time_base: Array codecpar: { [key: string]: any } + // added later + encoder?: Encoder; + stream?: Stream; } /** Definition for a channel of beamstream processing */ export interface BeamstreamChannel { sources: Array filterSpec: string streams: Array + filter?: Filterer | Promise; } /** * Definition for a beamstream process consisting of a number of audio and video sources @@ -85,22 +95,29 @@ export interface BeamstreamChannel { export interface BeamstreamParams { video?: Array audio?: Array - /** Destination definition for the beamstream process, to either a file or NodeJS WritableStream */ - out: { + /** 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 } } } /** * Initialise the sources for the beamstream process. * Note - the params object is updated by the function. */ -export function makeSources(params: BeamstreamParams): Promise +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} > +export function makeStreams(params: BeamstreamParams): Promise<{ run(): Promise }> diff --git a/types/Demuxer.d.ts b/types/Demuxer.d.ts index 6bcb223..d6d364d 100644 --- a/types/Demuxer.d.ts +++ b/types/Demuxer.d.ts @@ -47,6 +47,9 @@ export interface Demuxer extends Omit { + // { read: () => Promise, streams: Array<{time_base: [number, number]}> } + + /** Object name. */ readonly type: 'demuxer' readonly iformat: InputFormat diff --git a/types/Filter.d.ts b/types/Filter.d.ts index 13d56d6..c312af4 100644 --- a/types/Filter.d.ts +++ b/types/Filter.d.ts @@ -106,7 +106,7 @@ export interface FilterLink { readonly format: string /** * Define the time base used by the PTS of the frames/samples which will pass through this link. - * During the configuration stage, each filter is supposed to change only the output timebase, + * During the configuration stage, each filter is supposed to change only the output timebase, * while the timebase of the input link is assumed to be an unchangeable property. */ readonly time_base: ReadonlyArray @@ -231,7 +231,7 @@ 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 }> } /** diff --git a/types/FormatContext.d.ts b/types/FormatContext.d.ts index eeac3a8..9aaddff 100644 --- a/types/FormatContext.d.ts +++ b/types/FormatContext.d.ts @@ -11,7 +11,7 @@ export interface InputFormat { readonly name: string /** Descriptive name for the format, meant to be more human-readable */ readonly long_name: string - readonly flags: { + readonly flags: { NOFILE: boolean /** Needs '%d' in filename. */ NEEDNUMBER: boolean diff --git a/types/Muxer.d.ts b/types/Muxer.d.ts index fed21bf..87b8ce5 100644 --- a/types/Muxer.d.ts +++ b/types/Muxer.d.ts @@ -31,10 +31,10 @@ export interface Muxer extends Omit diff --git a/types/Packet.d.ts b/types/Packet.d.ts index 1e043ce..2b6cf62 100644 --- a/types/Packet.d.ts +++ b/types/Packet.d.ts @@ -8,6 +8,12 @@ * packets, with no compressed data, containing only side data * (e.g. to update some stream parameters at the end of encoding). */ + + export interface Timing { + reqTime: number; + elapsed: number; +} + export interface Packet { /** Object name. */ readonly type: 'Packet' @@ -32,22 +38,22 @@ export interface Packet { * Packet data buffers are shared between C and Javascript so can be written to and modified without having to write the buffer back into the packet */ data: Buffer - /** The size in bytes of the raw data */ + /** The size in bytes of the raw data */ size: number /** The index in the format's stream array that this packet belongs to */ stream_index: number - /** A combination of AV_PKT_FLAG values */ + /** A combination of AV_PKT_FLAG values */ flags: { - /** The packet contains a keyframe */ - KEY: boolean - /** The packet content is corrupted */ - CORRUPT: boolean - /** - * Flag is used to discard packets which are required to maintain valid - * decoder state but are not required for output and should be dropped - * after decoding. - **/ - DISCARD: boolean + /** The packet contains a keyframe */ + KEY: boolean + /** The packet content is corrupted */ + CORRUPT: boolean + /** + * Flag is used to discard packets which are required to maintain valid + * decoder state but are not required for output and should be dropped + * after decoding. + **/ + DISCARD: boolean /** * The packet comes from a trusted source. * @@ -72,7 +78,10 @@ export interface Packet { */ duration: number /** byte position in stream, -1 if unknown */ - pos: number + pos: number, + timings: { + read?: Timing; + }; } /** From b0fa9fdcb93558b5f6f712e98bf545d41e093ce5 Mon Sep 17 00:00:00 2001 From: UrielCh Date: Tue, 19 Apr 2022 09:47:57 +0300 Subject: [PATCH 11/68] add package.json for example --- examples/package.json | 15 ++ examples/pnpm-lock.yaml | 383 ++++++++++++++++++++++++++++++++++++++++ package.json | 8 +- pnpm-lock.yaml | 104 ++++++++++- 4 files changed, 507 insertions(+), 3 deletions(-) create mode 100644 examples/package.json create mode 100644 examples/pnpm-lock.yaml diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 0000000..0980b3d --- /dev/null +++ b/examples/package.json @@ -0,0 +1,15 @@ +{ + "name": "examples", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@types/koa": "^2.13.4", + "koa": "^2.13.4" + } +} diff --git a/examples/pnpm-lock.yaml b/examples/pnpm-lock.yaml new file mode 100644 index 0000000..0097bcb --- /dev/null +++ b/examples/pnpm-lock.yaml @@ -0,0 +1,383 @@ +lockfileVersion: 5.3 + +specifiers: + '@types/koa': ^2.13.4 + koa: ^2.13.4 + +dependencies: + '@types/koa': 2.13.4 + koa: 2.13.4 + +packages: + + /@types/accepts/1.3.5: + resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==} + dependencies: + '@types/node': 17.0.25 + dev: false + + /@types/body-parser/1.19.2: + resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} + dependencies: + '@types/connect': 3.4.35 + '@types/node': 17.0.25 + dev: false + + /@types/connect/3.4.35: + resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} + dependencies: + '@types/node': 17.0.25 + dev: false + + /@types/content-disposition/0.5.4: + resolution: {integrity: sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==} + dev: false + + /@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.25 + dev: false + + /@types/express-serve-static-core/4.17.28: + resolution: {integrity: sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==} + dependencies: + '@types/node': 17.0.25 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 + dev: false + + /@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: false + + /@types/http-assert/1.5.3: + resolution: {integrity: sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==} + dev: false + + /@types/http-errors/1.8.2: + resolution: {integrity: sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==} + dev: false + + /@types/keygrip/1.0.2: + resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==} + dev: false + + /@types/koa-compose/3.2.5: + resolution: {integrity: sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==} + dependencies: + '@types/koa': 2.13.4 + dev: false + + /@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.25 + dev: false + + /@types/mime/1.3.2: + resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} + dev: false + + /@types/node/17.0.25: + resolution: {integrity: sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==} + dev: false + + /@types/qs/6.9.7: + resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} + dev: false + + /@types/range-parser/1.2.4: + resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} + dev: false + + /@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.25 + dev: false + + /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/package.json b/package.json index 414e912..1a5370f 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "main": "ts/index.js", "types": "types.d.ts", "scripts": { - "preinstall": "node install_ffmpeg.js", + "preinstall": "node ts/install_ffmpeg.js", "install": "node-gyp rebuild", + "clean": "rimraf ts/*.js", "test": "tape test/*.js", "lint": "eslint **/*.js", "lint-html": "eslint **/*.js -f html -o ./reports/lint-results.html", @@ -39,8 +40,11 @@ }, "devDependencies": { "@types/bindings": "^1.5.1", + "@types/node": "^17.0.24", + "@types/tape": "^4.13.2", "eslint": "^8.9.0", - "tape": "^5.5.2", + "tape": "^5.5.3", + "ts-node": "^10.7.0", "typescript": "^4.6.3" }, "gypfile": true diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ebe599..728ddab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,10 +2,13 @@ lockfileVersion: 5.3 specifiers: '@types/bindings': ^1.5.1 + '@types/node': ^17.0.24 + '@types/tape': ^4.13.2 bindings: ^1.5.0 eslint: ^8.9.0 segfault-handler: ^1.3.0 - tape: ^5.5.2 + tape: ^5.5.3 + ts-node: ^10.7.0 typescript: ^4.6.3 dependencies: @@ -14,12 +17,27 @@ dependencies: devDependencies: '@types/bindings': 1.5.1 + '@types/node': 17.0.24 + '@types/tape': 4.13.2 eslint: 8.13.0 tape: 5.5.3 + ts-node: 10.7.0_17a82b5ac88a5de7094eac76b4edda13 typescript: 4.6.3 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.1: resolution: {integrity: sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -52,6 +70,22 @@ packages: 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: @@ -62,6 +96,12 @@ packages: resolution: {integrity: sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==} dev: true + /@types/tape/4.13.2: + resolution: {integrity: sha512-V1ez/RtYRGN9cNYApw5xf27DpMkTB0033X6a2i3KUmKhSojBfbWN0i3EgZxboUG96WJLHLdOyZ01aiZwVW5aSA==} + dependencies: + '@types/node': 17.0.24 + dev: true + /acorn-jsx/5.3.2_acorn@8.7.0: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -70,6 +110,11 @@ packages: 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'} @@ -97,6 +142,10 @@ packages: 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 @@ -168,6 +217,10 @@ packages: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} dev: true + /create-require/1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + dev: true + /cross-spawn/7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -225,6 +278,11 @@ packages: 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'} @@ -746,6 +804,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /make-error/1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + dev: true + /minimatch/3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: @@ -988,6 +1050,37 @@ packages: resolution: {integrity: sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=} dev: true + /ts-node/10.7.0_17a82b5ac88a5de7094eac76b4edda13: + 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.24 + 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.3 + 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'} @@ -1021,6 +1114,10 @@ packages: punycode: 2.1.1 dev: 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 @@ -1072,3 +1169,8 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} dev: true + + /yn/3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + dev: true From da6e5b59f84d176896dce8869e0f275d1828f4eb Mon Sep 17 00:00:00 2001 From: UrielCh Date: Tue, 19 Apr 2022 10:27:50 +0300 Subject: [PATCH 12/68] convert Test to TS --- package.json | 2 +- ...{codecParamsSpec.js => codecParamsSpec.ts} | 4 +- test/{decoderSpec.js => decoderSpec.ts} | 6 +- test/{demuxerSpec.js => demuxerSpec.ts} | 5 +- test/{encoderSpec.js => encoderSpec.ts} | 9 +- test/{filtererSpec.js => filtererSpec.ts} | 4 +- test/{formatSpec.js => formatSpec.ts} | 16 ++- test/{frameSpec.js => frameSpec.ts} | 6 +- ...rospectionSpec.js => introspectionSpec.ts} | 4 +- test/{muxerSpec.js => muxerSpec.ts} | 5 +- test/{packetSpec.js => packetSpec.ts} | 4 +- types/CodecContext.d.ts | 4 +- types/CodecPar.d.ts | 17 +-- types/FormatContext.d.ts | 108 +++++++++--------- types/Frame.d.ts | 2 +- types/Packet.d.ts | 67 +++++++---- types/Stream.d.ts | 2 + 17 files changed, 151 insertions(+), 114 deletions(-) rename test/{codecParamsSpec.js => codecParamsSpec.ts} (98%) rename test/{decoderSpec.js => decoderSpec.ts} (94%) rename test/{demuxerSpec.js => demuxerSpec.ts} (94%) rename test/{encoderSpec.js => encoderSpec.ts} (93%) rename test/{filtererSpec.js => filtererSpec.ts} (94%) rename test/{formatSpec.js => formatSpec.ts} (95%) rename test/{frameSpec.js => frameSpec.ts} (97%) rename test/{introspectionSpec.js => introspectionSpec.ts} (96%) rename test/{muxerSpec.js => muxerSpec.ts} (93%) rename test/{packetSpec.js => packetSpec.ts} (98%) diff --git a/package.json b/package.json index 1a5370f..f8a6c3c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "preinstall": "node ts/install_ffmpeg.js", "install": "node-gyp rebuild", "clean": "rimraf ts/*.js", - "test": "tape test/*.js", + "test": "ts-node node_modules/tape/bin/tape test/*.ts", "lint": "eslint **/*.js", "lint-html": "eslint **/*.js -f html -o ./reports/lint-results.html", "lint-fix": "eslint --fix **/*.js" diff --git a/test/codecParamsSpec.js b/test/codecParamsSpec.ts similarity index 98% rename from test/codecParamsSpec.js rename to test/codecParamsSpec.ts index 288557c..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('../ts/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 0d7dfc2..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('../ts/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 d539e28..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('../ts/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 07e4477..27c5469 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('../ts/index.js'); +import test from 'tape'; +import beamcoder from '..'; test('Creating a video encoder', t => { let enc = beamcoder.encoder({ name: 'h264' }); @@ -47,10 +47,12 @@ 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 +62,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 94% rename from test/filtererSpec.js rename to test/filtererSpec.ts index aaf9472..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('../ts/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 95% rename from test/formatSpec.js rename to test/formatSpec.ts index 6ca570e..126f237 100644 --- a/test/formatSpec.js +++ b/test/formatSpec.ts @@ -19,10 +19,12 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const test = require('tape'); -const beamcoder = require('../ts/index.js'); +import test from 'tape'; +import beamcoder from '..'; -const isExternal = o => Object.toString(o).indexOf('native code') >= 0; +const isExternal = o => (Object as any).toString(o).indexOf('native code') >= 0; +//const isExternal = o => Object.toString.apply(o).indexOf('native code') >= 0; +// Object.toString.apply(Object) test('Creating a format', 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.'); @@ -332,12 +334,16 @@ test('Can set IO formats on construction', t => { t.equal(ofmt.type, 'muxer', 'has turned into a muxer.'); t.equal(ofmt.oformat.name, 'hevc', 'oformat has the expected name.'); + debugger; + // @ts-ignore ofmt.oformat = 'wav'; t.equal(ofmt.oformat.name, 'wav', 'oformat has the expected name.'); t.ok(ofmt.priv_data, 'has private data.'); t.equal(typeof ofmt.priv_data.write_bext, 'boolean', 'private data appears as expected.'); + // @ts-expect-error:next-line t.throws(() => { ifmt.iformat = 'wibble'; }, /Unable/, 'bad iformat name throws.'); + // @ts-expect-error:next-line t.throws(() => { ofmt.oformat = 'wibble'; }, /Unable/, 'bad oformat name throws.'); t.end(); }); diff --git a/test/frameSpec.js b/test/frameSpec.ts similarity index 97% rename from test/frameSpec.js rename to test/frameSpec.ts index 81f3073..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('../ts/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 cc75414..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('../ts/index.js'); +import test from 'tape'; +import beamcoder from '..'; test('Version information', t => { const verPos = beamcoder.avVersionInfo().indexOf('5.'); diff --git a/test/muxerSpec.js b/test/muxerSpec.ts similarity index 93% rename from test/muxerSpec.js rename to test/muxerSpec.ts index c143d17..d7b3da0 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('../ts/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:next-line 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 42f38e0..0ccf6d4 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('../ts/index.js'); +import test from 'tape'; +import beamcoder from '..'; test('Create a packet', t => { let pkt = beamcoder.packet(); diff --git a/types/CodecContext.d.ts b/types/CodecContext.d.ts index 5f90969..214e535 100644 --- a/types/CodecContext.d.ts +++ b/types/CodecContext.d.ts @@ -6,9 +6,11 @@ export type FrameSkipString = 'none' | 'default' | 'nonref' | 'bidir' | 'nonintr /** The CodecContext object */ export interface CodecContext { + // private field + readonly _CodecContext: {}; /** Object name. */ readonly type: string - /** see AV_CODEC_ID_xxx */ + /** see AV_CODEC_ID_xxx */ readonly codec_id: number /** * Name of the codec implementation. diff --git a/types/CodecPar.d.ts b/types/CodecPar.d.ts index 6f06836..cb30734 100644 --- a/types/CodecPar.d.ts +++ b/types/CodecPar.d.ts @@ -2,8 +2,11 @@ * CodecPar describes the properties of an encoded stream. */ export interface CodecPar { + + // native code; + readonly _codecPar: {}; /** Object name. */ - readonly type: 'CodecParameters' + readonly type: 'CodecParameters' /** General type of the encoded data. */ codec_type: string /** Specific type of the encoded data (the codec used). */ @@ -51,7 +54,7 @@ export interface CodecPar { /** Video only. The video frame width in pixels. */ width: number /** Video only. The video frame height in pixels. */ - height: number + height: number /** * Video only. The aspect ratio (width / height) which a single pixel * should have when displayed. @@ -64,10 +67,10 @@ export interface CodecPar { field_order: string /** Video only. Additional colorspace characteristics. */ color_range: string - color_primaries: string - color_trc: string - color_space: string - chroma_location: string + color_primaries: string + color_trc: string + color_space: string + chroma_location: string /** Video only. Number of delayed frames. */ video_delay: number /** Audio only. A description of the channel layout. */ @@ -105,4 +108,4 @@ export interface CodecPar { toJSON(): string } -export function codecParameters(options?: { [key: string]: any }): CodecPar; +export function codecParameters(options?: string | { [key: string]: any }): CodecPar; diff --git a/types/FormatContext.d.ts b/types/FormatContext.d.ts index 9aaddff..a22486d 100644 --- a/types/FormatContext.d.ts +++ b/types/FormatContext.d.ts @@ -11,23 +11,23 @@ export interface InputFormat { readonly name: string /** Descriptive name for the format, meant to be more human-readable */ readonly long_name: string - readonly flags: { + readonly flags: { NOFILE: boolean - /** Needs '%d' in filename. */ + /** Needs '%d' in filename. */ NEEDNUMBER: boolean - /** Show format stream IDs numbers. */ + /** Show format stream IDs numbers. */ SHOW_IDS: boolean - /** Use generic index building code. */ - GENERIC_INDEX: boolean + /** Use generic index building code. */ + GENERIC_INDEX: boolean /** Format allows timestamp discontinuities. Note, muxers always require valid (monotone) timestamps */ TS_DISCONT: boolean - /** Format does not allow to fall back on binary search via read_timestamp */ + /** Format does not allow to fall back on binary search via read_timestamp */ NOBINSEARCH: boolean - /** Format does not allow to fall back on generic search */ + /** Format does not allow to fall back on generic search */ NOGENSEARCH: boolean - /** Format does not allow seeking by bytes */ + /** Format does not allow seeking by bytes */ NO_BYTE_SEEK: boolean - /** Seeking is based on PTS */ + /** Seeking is based on PTS */ SEEK_TO_PTS: boolean } /** @@ -36,11 +36,11 @@ export interface InputFormat { * reliable enough */ readonly extensions: string - /** - * List of supported codec_id-codec_tag pairs, ordered by "better - * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. - */ - readonly codec_tag: ReadonlyArray<{ + /** + * List of supported codec_id-codec_tag pairs, ordered by "better + * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. + */ + readonly codec_tag: ReadonlyArray<{ id: number tag: string | number }> @@ -51,8 +51,8 @@ export interface InputFormat { * It is used check for matching mime types while probing. */ readonly mime_type: string - /** Raw demuxers store their codec ID here. */ - readonly raw_codec_id: string + /** Raw demuxers store their codec ID here. */ + readonly raw_codec_id: string /** Size of private data so that it can be allocated. */ readonly priv_data_size: number } @@ -72,19 +72,19 @@ export interface OutputFormat { * It is used check for matching mime types while probing. */ mime_type: string - /** comma-separated filename extensions */ + /** comma-separated filename extensions */ extensions: string - /** default audio codec */ + /** default audio codec */ audio_codec: string - /** default video codec */ + /** default video codec */ video_codec: string - /** default subtitle codec */ - subtitle_codec: string + /** default subtitle codec */ + subtitle_codec: string flags: { NOFILE?: boolean - /** Needs '%d' in filename. */ + /** Needs '%d' in filename. */ NEEDNUMBER?: boolean - /** Format wants global header. */ + /** Format wants global header. */ GLOBALHEADER?: boolean /** Format does not need / have any timestamps. */ NOTIMESTAMPS?: boolean @@ -98,25 +98,25 @@ export interface OutputFormat { ALLOW_FLUSH?: boolean /** Format does not require strictly increasing timestamps, but they must still be monotonic */ TS_NONSTRICT?: boolean - /** - * Format allows muxing negative timestamps. If not set the timestamp will be shifted in av_write_frame and - * av_interleaved_write_frame so they start from 0. - * The user or muxer can override this through AVFormatContext.avoid_negative_ts - */ + /** + * Format allows muxing negative timestamps. If not set the timestamp will be shifted in av_write_frame and + * av_interleaved_write_frame so they start from 0. + * The user or muxer can override this through AVFormatContext.avoid_negative_ts + */ TS_NEGATIVE?: boolean } - /** - * List of supported codec_id-codec_tag pairs, ordered by "better - * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. - */ - codec_tag: Array<{ + /** + * List of supported codec_id-codec_tag pairs, ordered by "better + * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. + */ + codec_tag: Array<{ id: number tag: string | number }> /** Class for private context */ priv_class: PrivClass | null - /** size of private data so that it can be allocated */ - priv_data_size: number + /** size of private data so that it can be allocated */ + priv_data_size: number } /** @@ -174,7 +174,7 @@ export interface FormatContext { flags: { /** Generate missing pts even if it requires parsing future frames. */ GENPTS?: boolean - /** Ignore index. */ + /** Ignore index. */ IGNIDX?: boolean /** Do not block when reading packets from input. */ NONBLOCK?: boolean @@ -280,17 +280,17 @@ export interface FormatContext { event_flags: { METADATA_UPDATED?: boolean } /** Maximum number of packets to read while waiting for the first timestamp. */ max_ts_probe: 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 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. */ + /** 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 + /** 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 /** * forces the use of wallclock timestamps as pts/dts of packets * This has undefined results in the presence of B frames. @@ -314,7 +314,7 @@ export interface FormatContext { correct_ts_overflow: boolean /** Force seeking to any (also non key) frames. */ seek2any: boolean - /** Flush the I/O context after each packet. */ + /** Flush the I/O context after each packet. */ flush_packets: number /** * format probing score. @@ -341,10 +341,10 @@ export interface FormatContext { * Demuxers can use the flag to detect such changes. */ readonly io_repositioned: boolean - /** Number of bytes to be written as padding in a metadata header. */ + /** Number of bytes to be written as padding in a metadata header. */ metadata_header_padding: number // not exposing opaque - /** Output timestamp offset, in microseconds. */ + /** Output timestamp offset, in microseconds. */ output_ts_offset: number /** * dump format separator. @@ -360,21 +360,21 @@ export interface FormatContext { /** Skip duration calcuation in estimate_timings_from_pts. */ skip_estimate_duration_from_pts: boolean - /** + /** * Add a stream to the format with the next available stream index. * @param options Object including the codec name for the stream and any other parameters that need * to be initialised in the Stream object * @returns A Stream object */ - newStream(options: { name: string, [key: string]: any }): Stream - /** + newStream(options: string | { 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 * @returns A Stream object */ newStream(stream: Stream): Stream - /** Retun a JSON string containing the object properties. */ - toJSON(): string + /** Retun a JSON string containing the object properties. */ + toJSON(): string } -export function format(options: { [key: string]: any }): FormatContext +export function format(options?: string | { [key: string]: any }): FormatContext diff --git a/types/Frame.d.ts b/types/Frame.d.ts index 87a1d39..6c32442 100644 --- a/types/Frame.d.ts +++ b/types/Frame.d.ts @@ -184,7 +184,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?: string | { [key: string]: any, data?: Array }): Frame /** Pixel format description */ export interface PixelFormat { diff --git a/types/Packet.d.ts b/types/Packet.d.ts index 2b6cf62..0e4d7c0 100644 --- a/types/Packet.d.ts +++ b/types/Packet.d.ts @@ -14,9 +14,40 @@ elapsed: number; } +export interface PacketFlags { + /** The packet contains a keyframe */ + KEY: boolean + /** The packet content is corrupted */ + CORRUPT: boolean + /** + * Flag is used to discard packets which are required to maintain valid + * decoder state but are not required for output and should be dropped + * after decoding. + **/ + DISCARD: boolean + /** + * The packet comes from a trusted source. + * + * Otherwise-unsafe constructs such as arbitrary pointers to data + * outside the packet may be followed. + */ + TRUSTED: boolean + /** + * Flag is used to indicate packets that contain frames that can + * be discarded by the decoder. I.e. Non-reference frames. + */ + DISPOSABLE: boolean // Frames that can be discarded by the decoder +} + export interface Packet { /** Object name. */ readonly type: 'Packet' + + /** Retun a JSON string containing the object properties. */ + toJSON(): string + + // internal data + readonly _packet: {}; /** * Presentation timestamp in AVStream->time_base units the time at which * the decompressed packet will be presented to the user. @@ -43,30 +74,7 @@ export interface Packet { /** The index in the format's stream array that this packet belongs to */ stream_index: number /** A combination of AV_PKT_FLAG values */ - flags: { - /** The packet contains a keyframe */ - KEY: boolean - /** The packet content is corrupted */ - CORRUPT: boolean - /** - * Flag is used to discard packets which are required to maintain valid - * decoder state but are not required for output and should be dropped - * after decoding. - **/ - DISCARD: boolean - /** - * The packet comes from a trusted source. - * - * Otherwise-unsafe constructs such as arbitrary pointers to data - * outside the packet may be followed. - */ - TRUSTED: boolean - /** - * Flag is used to indicate packets that contain frames that can - * be discarded by the decoder. I.e. Non-reference frames. - */ - DISPOSABLE: boolean // Frames that can be discarded by the decoder - } + flags: PacketFlags; /** * Additional packet data that can be provided by the container. * Packet can contain several types of side information. @@ -88,4 +96,13 @@ 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 | { + flags?: Partial, + pts?: number, + dts?: number, + stream_index?: number, + data: Buffer, + size?: number, + side_data?: any, + [key: string]: any, +}): Packet diff --git a/types/Stream.d.ts b/types/Stream.d.ts index feb8da0..256f81f 100644 --- a/types/Stream.d.ts +++ b/types/Stream.d.ts @@ -51,6 +51,8 @@ export interface EventFlags { * Stream describes the properties of a stream. */ export interface Stream { + // native code; + readonly _stream: {}; /** Object name. */ readonly type: 'Stream' /** The stream index in the container. */ From bdc29dba69f4913cba91353dabb1b93c637a16f1 Mon Sep 17 00:00:00 2001 From: UrielCh Date: Tue, 19 Apr 2022 10:33:28 +0300 Subject: [PATCH 13/68] improve model --- test/formatSpec.ts | 4 ---- types/FormatContext.d.ts | 7 +++++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/formatSpec.ts b/test/formatSpec.ts index 126f237..c8561a9 100644 --- a/test/formatSpec.ts +++ b/test/formatSpec.ts @@ -334,16 +334,12 @@ test('Can set IO formats on construction', t => { t.equal(ofmt.type, 'muxer', 'has turned into a muxer.'); t.equal(ofmt.oformat.name, 'hevc', 'oformat has the expected name.'); - debugger; - // @ts-ignore ofmt.oformat = 'wav'; t.equal(ofmt.oformat.name, 'wav', 'oformat has the expected name.'); t.ok(ofmt.priv_data, 'has private data.'); t.equal(typeof ofmt.priv_data.write_bext, 'boolean', 'private data appears as expected.'); - // @ts-expect-error:next-line t.throws(() => { ifmt.iformat = 'wibble'; }, /Unable/, 'bad iformat name throws.'); - // @ts-expect-error:next-line t.throws(() => { ofmt.oformat = 'wibble'; }, /Unable/, 'bad oformat name throws.'); t.end(); }); diff --git a/types/FormatContext.d.ts b/types/FormatContext.d.ts index a22486d..d5a3948 100644 --- a/types/FormatContext.d.ts +++ b/types/FormatContext.d.ts @@ -132,9 +132,12 @@ export interface FormatContext { /** Object name. */ readonly type: string /** The input format description. */ - iformat: InputFormat + set iformat(string: format): string; + get iformat(): InputFormat + /** The output format description. */ - oformat: OutputFormat + set oformat(string: format): string; + get oformat(): OutputFormat /** Format private data. */ priv_data: { [key: string]: any From 10df56f6ada6c97e4d4beccb675b776afae25465 Mon Sep 17 00:00:00 2001 From: UrielCh Date: Tue, 19 Apr 2022 12:10:56 +0300 Subject: [PATCH 14/68] refacrtor --- types/CodecContext.d.ts | 489 +++++++++++++++++++++------------------- types/CodecPar.d.ts | 165 ++++++++------ types/Decoder.d.ts | 49 ++-- 3 files changed, 374 insertions(+), 329 deletions(-) diff --git a/types/CodecContext.d.ts b/types/CodecContext.d.ts index 214e535..ecd69de 100644 --- a/types/CodecContext.d.ts +++ b/types/CodecContext.d.ts @@ -1,16 +1,45 @@ import { HWDeviceContext, HWFramesContext } from "./HWContext" export type MotionEstimationString = 'sad' | 'sse' | 'satd' | 'dct' | 'psnr' | 'bit' | 'rd' | 'zero' | 'vsad' | - 'vsse' | 'nsse' | 'w53' | 'w97' | 'dctmax' | 'dct264' | 'median_sad' | 'chroma' + 'vsse' | 'nsse' | 'w53' | 'w97' | 'dctmax' | 'dct264' | 'median_sad' | 'chroma' export type FrameSkipString = 'none' | 'default' | 'nonref' | 'bidir' | 'nonintra' | 'nonkey' | 'all' -/** The CodecContext object */ -export interface CodecContext { + +export interface CodecContext_base { + /** Which multithreading methods are in use by the codec. */ + readonly active_thread_type: { FRAME?: boolean, SLICE?: boolean } + /** + * Video decoding only. Certain video codecs support cropping, meaning that + * only a sub-rectangle of the decoded frame is intended for display. This + * option controls how cropping is handled by libavcodec. + * + * When set to 1 (the default), libavcodec will apply cropping internally. + * I.e. it will modify the output frame width/height fields and offset the + * data pointers (only by as much as possible while preserving alignment, or + * by the full amount if the AV_CODEC_FLAG_UNALIGNED flag is set) so that + * the frames output by the decoder refer only to the cropped area. The + * crop_* fields of the output frames will be zero. + * + * When set to 0, the width/height fields of the output frames will be set + * to the coded dimensions and the crop_* fields will describe the cropping + * rectangle. Applying the cropping is left to the caller. + * + * @warning When hardware acceleration with opaque output frames is used, + * libavcodec is unable to apply cropping from the top/left border. + * + * @note when this option is set to zero, the width/height fields of the + * AVCodecContext and output AVFrames have different meanings. The codec + * context fields store display dimensions (with the coded dimensions in + * coded_width/height), while the frame fields store the coded dimensions + * (with the display dimensions being determined by the crop_* fields). + */ + apply_cropping: number + // private field readonly _CodecContext: {}; /** Object name. */ readonly type: string - /** see AV_CODEC_ID_xxx */ + /** see AV_CODEC_ID_xxx */ readonly codec_id: number /** * Name of the codec implementation. @@ -19,7 +48,7 @@ export interface CodecContext { * This is the primary way to find a codec from the user perspective. */ readonly name: string - /** Descriptive name for the codec, meant to be more human readable than name. */ + /** Descriptive name for the codec, meant to be more human readable than name. */ readonly long_name: string /** * A fourcc string by default, will be a number if not recognised @@ -28,16 +57,8 @@ export interface CodecContext { readonly codec_tag: string | number /** Codec private data. */ priv_data: { [key: string]: any } | null - /** The average bitrate */ + /** The average bitrate */ bit_rate: number - /** - * Number of bits the bitstream is allowed to diverge from the reference. - * The reference can be CBR (for CBR pass1) or VBR (for pass2) - */ - bit_rate_tolerance: number - /** Global quality for codecs which cannot change it per frame. This should be proportional to MPEG-1/2/4 qscale. */ - global_quality: number - compression_level: number /** AV_CODEC_FLAG_*. */ flags: { [key: string]: boolean } /** AV_CODEC_FLAG2_*. */ @@ -124,42 +145,13 @@ export interface CodecContext { * Frame output due to frame reordering. */ pix_fmt: string | null - /** - * Maximum number of B-frames between non-B-frames - * Note: The output will be delayed by max_b_frames+1 relative to the input. - */ - max_b_frames: number - /** qscale factor between IP and B-frames - * If > 0 then the last P-frame quantizer will be used (q= lastp_q*factor+offset). - * If < 0 then normal ratecontrol will be done (q= -normal_q*factor+offset). - */ - b_quant_factor: number - /** qscale offset between IP and B-frames */ - b_quant_offset: number /** * Size of the frame reordering buffer in the decoder. * For MPEG-2 it is 1 IPB or 0 low delay IP. */ readonly has_b_frames: number - /** qscale factor between P- and I-frames - * If > 0 then the last P-frame quantizer will be used (q = lastp_q * factor + offset). - * If < 0 then normal ratecontrol will be done (q= -normal_q*factor+offset). - */ - i_quant_factor: number - /** qscale offset between P and I-frames */ - i_quant_offset: number - /** luminance masking (0-> disabled) */ - lumi_masking: number - /** temporal complexity masking (0-> disabled) */ - temporal_cplx_masking: number - /** spatial complexity masking (0-> disabled) */ - spatial_cplx_masking: number - /** p block masking (0-> disabled) */ - p_masking: number - /** darkness masking (0-> disabled) */ - dark_masking: number /** slice count */ - slice_count: number + slice_count: number /** slice offsets in the frame in bytes */ slice_offset: Array | null /** @@ -168,63 +160,38 @@ export interface CodecContext { * Numerator and denominator must be relatively prime and smaller than 256 for some video standards. */ sample_aspect_ratio: Array - /** motion estimation comparison function */ - me_cmp: MotionEstimationString - /** subpixel motion estimation comparison function */ - me_sub_cmp: MotionEstimationString - /** macroblock comparison function (not supported yet) */ - mb_cmp: MotionEstimationString - /** interlaced DCT comparison function */ - ildct_cmp: MotionEstimationString - /** ME diamond size & shape */ - dia_size: number - /** amount of previous MV predictors (2a+1 x 2a+1 square) */ - last_predictor_count: number - /** motion estimation prepass comparison function */ - me_pre_cmp: MotionEstimationString - /** ME prepass diamond size & shape */ - pre_dia_size: number - /** subpel ME quality */ - me_subpel_quality: number - /** maximum motion estimation search range in subpel units. If 0 then no limit. */ - me_range: number + + + + + + + slice_flags: { /** draw_horiz_band() is called in coded order instead of display */ CODED_ORDER: boolean /** allow draw_horiz_band() with field slices (MPEG-2 field pics) */ - ALLOW_FIELD: boolean + ALLOW_FIELD: boolean /** allow draw_horiz_band() with 1 component at a time (SVQ1) */ ALLOW_PLANE: boolean } - /** - * macroblock decision mode - * simple: uses mb_cmp - * bits: chooses the one which needs the fewest bits - * rd: rate distortion - */ - mb_decision: 'simple' | 'bits' | 'rd' + /** custom intra quantization matrix */ intra_matrix: Array | null /** custom inter quantization matrix */ inter_matrix: Array | null - /** precision of the intra DC coefficient - 8 */ + /** precision of the intra DC coefficient - 8 */ intra_dc_precision: number - /** Number of macroblock rows at the top which are skipped. */ + /** Number of macroblock rows at the top which are skipped. */ skip_top: number - /** Number of macroblock rows at the bottom which are skipped. */ + /** Number of macroblock rows at the bottom which are skipped. */ skip_bottom: number - /** minimum MB Lagrange multiplier */ - mb_lmin: number - /** maximum MB Lagrange multiplier */ - mb_lmax: number - /** */ - bidir_refine: number - /** minimum GOP size */ - keyint_min: number + + + + /** number of reference frames */ refs: number - /** Value depends upon the compare function used for fullpel ME. */ - mv0_threshold: number /** Chromaticity coordinates of the source primaries. */ color_primaries?: string /** Color Transfer Characteristic. */ @@ -250,19 +217,18 @@ export interface CodecContext { *``` */ chroma_sample_location: 'unspecified' | 'left' | 'center' | 'topleft' | 'top' | 'bottomleft' | 'bottom' - /** Number of slices. Indicates number of picture subdivisions. Used for parallelized decoding. */ - slices: number + field_order: 'progressive' | - 'top coded first, top displayed first' | - 'bottom coded first, bottom displayed first' | - 'top coded first, bottom displayed first' | - 'bottom coded first, top displayed first' | - 'unknown' - /** Audio only - samples per second */ + 'top coded first, top displayed first' | + 'bottom coded first, bottom displayed first' | + 'top coded first, bottom displayed first' | + 'bottom coded first, top displayed first' | + 'unknown' + /** Audio only - samples per second */ sample_rate: number - /** Audio only - number of audio channels */ + /** Audio only - number of audio channels */ channels: number - /** audio sample format */ + /** audio sample format */ sample_fmt: string | null /** * Number of samples per channel in an audio frame. @@ -274,57 +240,18 @@ export interface CodecContext { * @note the counter is not incremented if encoding/decoding resulted in an error. */ readonly frame_number: number - /** number of bytes per packet if constant and known or 0. Used by some WAV based audio codecs. */ - block_align: number - /** Audio cutoff bandwidth (0 means "automatic") */ + /** Audio cutoff bandwidth (0 means "automatic") */ cutoff: number - /** Audio channel layout. */ + /** Audio channel layout. */ channel_layout: string - /** Request decoder to use this channel layout if it can (0 for default) */ + /** Request decoder to use this channel layout if it can (0 for default) */ request_channel_layout: string - /** Type of service that the audio stream conveys. */ - audio_service_type: 'main' | 'effects' | 'visually-impaired' | 'hearing-impaired' | 'dialogue' | - 'commentary' | 'emergency' | 'voice-over' | 'karaoke' | 'nb' /** Desired sample format - decoder will decode to this format if it can. */ request_sample_fmt: string | null - /** amount of qscale change between easy & hard scenes (0.0-1.0) */ - qcompress: number - /** amount of qscale smoothing over time (0.0-1.0) */ - qblur: number - /** minimum quantizer */ - qmin: number - /** maximum quantizer */ - qmax: number - /** maximum quantizer difference between frames */ - max_qdiff: number - /** decoder bitstream buffer size */ - rc_buffer_size: number - /** ratecontrol override */ - rc_override: Array<{ - type: 'RcOverride' - start_frame: number - end_frame: number - /** If this is 0 then quality_factor will be used instead. */ - qscale: number - quality_factor: number - }> /** maximum bitrate */ rc_max_rate: number - /** minimum bitrate */ - rc_min_rate: number - /** Ratecontrol attempt to use, at maximum, of what can be used without an underflow. */ - rc_max_available_vbv_use: number - /** Ratecontrol attempt to use, at least, times the amount needed to prevent a vbv overflow. */ - rc_min_vbv_overflow_use: number - /** Number of bits which should be loaded into the rc buffer before decoding starts. */ - rc_initial_buffer_occupancy: number - /** trellis RD quantization */ - trellis: number - /** pass1 encoding statistics output buffer */ - readonly stats_out: string | null - /** pass2 encoding statistics input buffer. Concatenated stuff from stats_out of pass1 should be placed here. */ - stats_in: string | null - /** Work around bugs in codecs which sometimes cannot be detected automatically. */ + + /** Work around bugs in codecs which sometimes cannot be detected automatically. */ workaround_bugs: { [key: string]: boolean } /** * strictly follow the standard (MPEG-4, ...). @@ -337,36 +264,31 @@ export interface CodecContext { * (=strictly conform to the specs) */ strict_std_compliance: 'very-strict' | 'strict' | 'normal' | 'unofficial' | 'experimental' - /** Error concealment flags */ + /** Error concealment flags */ error_concealment: { GUESS_MVS?: boolean DEBLOCK?: boolean FAVOR_INTER?: boolean } debug: { [key: string]: boolean } - /** Error recognition - may misdetect some more or less valid parts as errors. */ + /** Error recognition - may misdetect some more or less valid parts as errors. */ err_recognition: { [key: string]: boolean } - /** Opaque 64-bit number (generally a PTS) that will be reordered and output in Frame.reordered_opaque */ + /** Opaque 64-bit number (generally a PTS) that will be reordered and output in Frame.reordered_opaque */ reordered_opaque: number - readonly error: ReadonlyArray | null - /** DCT algorithm */ - dct_algo: 'auto' | 'fastint' | 'int' | 'mmx' | 'altivec' | 'faan' /** IDCT algorithm */ idct_algo: 'auto' | 'int' | 'simple' | 'simplemmx' | 'arm' | 'altivec' | 'simplearm' | 'xvid' | 'simplearmv5te' | 'simplearmv6' | 'faan' | 'simpleneon' | 'none' | 'simpleauto' - /** Bits per sample/pixel from the demuxer (needed for huffyuv). */ + /** Bits per sample/pixel from the demuxer (needed for huffyuv). */ bits_per_coded_sample: number - /** Bits per sample/pixel of internal libavcodec pixel/sample format. */ + /** Bits per sample/pixel of internal libavcodec pixel/sample format. */ bits_per_raw_sample: number - /** Thread count is used to decide how many independent tasks should be passed to execute() */ + /** Thread count is used to decide how many independent tasks should be passed to execute() */ thread_count: number - /** - * Which multithreading methods to use. - * Use of FRAME will increase decoding delay by one frame per thread, - * so clients which cannot provide future frames should not use it. - */ + /** + * Which multithreading methods to use. + * Use of FRAME will increase decoding delay by one frame per thread, + * so clients which cannot provide future frames should not use it. + */ thread_type: { FRAME?: boolean, SLICE?: boolean } - /** Which multithreading methods are in use by the codec. */ - readonly active_thread_type: { FRAME?: boolean, SLICE?: boolean } /** * Set by the client if its custom get_buffer() callback can be called * synchronously from another thread, which allows faster multithreaded decoding. @@ -374,16 +296,14 @@ export interface CodecContext { * Ignored if the default get_buffer() is used. */ thread_safe_callbacks: number - /** nsse_weight */ - nsse_weight: number profile: string | number level: number - /** Skip loop filtering for selected frames. */ + /** Skip loop filtering for selected frames. */ skip_loop_filter: FrameSkipString - /** Skip IDCT/dequantization for selected frames. */ + /** Skip IDCT/dequantization for selected frames. */ skip_idct: FrameSkipString - /** Skip decoding for selected frames. */ + /** Skip decoding for selected frames. */ skip_frame: FrameSkipString /** * Header containing style information for text subtitles. @@ -393,26 +313,13 @@ export interface CodecContext { */ subtitle_header: Buffer | null /** - * Audio only. The number of "priming" samples (padding) inserted by the - * encoder at the beginning of the audio. I.e. this number of leading - * decoded samples must be discarded by the caller to get the original audio - * without leading padding. - * - * The timestamps on the output packets are adjusted by the encoder so that - * they always refer to the first sample of the data actually contained in the packet, - * including any added padding. E.g. if the timebase is 1/samplerate and - * the timestamp of the first input sample is 0, the timestamp of the - * first output packet will be -initial_padding. - */ - readonly inital_padding: number - /** - * For codecs that store a framerate value in the compressed - * bitstream, the decoder may export it here. [ 0, 1 ] when unknown. - */ + * For codecs that store a framerate value in the compressed + * bitstream, the decoder may export it here. [ 0, 1 ] when unknown. + */ framerate: Array - /** Nominal unaccelerated pixel format, see AV_PIX_FMT_xxx. */ + /** 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. */ + /** Timebase in which pkt_dts/pts and Packet dts/pts are. */ pkt_timebase: Array readonly codec_descriptor: { @@ -423,12 +330,12 @@ export interface CodecContext { BITMAP_SUB: boolean TEXT_SUB: boolean } | null - /** Character encoding of the input subtitles file. */ + /** Character encoding of the input subtitles file. */ sub_charenc: string | null /** * Subtitles character encoding mode. Formats or codecs might be adjusting * this setting (if they are doing the conversion themselves for instance). - */ + */ readonly sub_charenc_mode: 'do-nothing' | 'automatic' | 'pre-decoder' | 'ignore' /** * Skip processing alpha if supported by codec. @@ -438,23 +345,14 @@ export interface CodecContext { * However for formats that do not use pre-multiplied alpha * there might be serious artefacts (though e.g. libswscale currently * assumes pre-multiplied alpha anyway). - */ + */ skip_alpha: number - /** Number of samples to skip after a discontinuity */ - readonly seek_preroll: number - /** custom intra quantization matrix */ - chroma_intra_matrix: Array /** Dump format separator - can be ", " or "\n " or anything else */ dump_separator: string | null - /** ',' separated list of allowed decoders - if null then all are allowed */ + /** ',' separated list of allowed decoders - if null then all are allowed */ codec_whitelist: string | null - /** Properties of the stream that gets decoded */ + /** Properties of the stream that gets decoded */ readonly properties: { LOSSLESS: boolean, CLOSED_CAPTIONS: boolean } - /** Additional data associated with the entire coded stream. */ - readonly coded_side_data: { - type: 'PacketSideData' - [key: string]: Buffer | string - } /** * A reference to the AVHWFramesContext describing the input (for encoding) * or output (decoding) frames. The reference is set by the caller and @@ -485,9 +383,9 @@ export interface CodecContext { * the end of the audio. I.e. this number of decoded samples must be * discarded by the caller from the end of the stream to get the original * audio without any trailing padding. - */ + */ trailing_padding: number - /** The number of pixels per image to maximally accept. */ + /** The number of pixels per image to maximally accept. */ max_pixels: number /** * A reference to the HWDeviceContext describing the device which will @@ -513,45 +411,174 @@ export interface CodecContext { /** * Bit set of AV_HWACCEL_FLAG_* flags, which affect hardware accelerated * decoding (if active). - */ + */ hwaccel_flags: { IGNORE_LEVEL?: boolean, ALLOW_HIGH_DEPTH?: boolean, ALLOW_PROFILE_MISMATCH?: boolean } - /** - * Video decoding only. Certain video codecs support cropping, meaning that - * only a sub-rectangle of the decoded frame is intended for display. This - * option controls how cropping is handled by libavcodec. - * - * When set to 1 (the default), libavcodec will apply cropping internally. - * I.e. it will modify the output frame width/height fields and offset the - * data pointers (only by as much as possible while preserving alignment, or - * by the full amount if the AV_CODEC_FLAG_UNALIGNED flag is set) so that - * the frames output by the decoder refer only to the cropped area. The - * crop_* fields of the output frames will be zero. - * - * When set to 0, the width/height fields of the output frames will be set - * to the coded dimensions and the crop_* fields will describe the cropping - * rectangle. Applying the cropping is left to the caller. - * - * @warning When hardware acceleration with opaque output frames is used, - * libavcodec is unable to apply cropping from the top/left border. + /* + * Video decoding only. Sets the number of extra hardware frames which + * the decoder will allocate for use by the caller. This must be set + * before avcodec_open2() is called. * - * @note when this option is set to zero, the width/height fields of the - * AVCodecContext and output AVFrames have different meanings. The codec - * context fields store display dimensions (with the coded dimensions in - * coded_width/height), while the frame fields store the coded dimensions - * (with the display dimensions being determined by the crop_* fields). + * Some hardware decoders require all frames that they will use for + * output to be defined in advance before decoding starts. For such + * decoders, the hardware frame pool must therefore be of a fixed size. + * The extra frames set here are on top of any number that the decoder + * needs internally in order to operate normally (for example, frames + * used as reference pictures). */ - apply_cropping: number - /* - * Video decoding only. Sets the number of extra hardware frames which - * the decoder will allocate for use by the caller. This must be set - * before avcodec_open2() is called. - * - * Some hardware decoders require all frames that they will use for - * output to be defined in advance before decoding starts. For such - * decoders, the hardware frame pool must therefore be of a fixed size. - * The extra frames set here are on top of any number that the decoder - * needs internally in order to operate normally (for example, frames - * used as reference pictures). - */ extra_hw_frames: number } + + +/** The CodecContext object */ +export interface CodecContext extends CodecContext_base { + /** + * Number of bits the bitstream is allowed to diverge from the reference. + * The reference can be CBR (for CBR pass1) or VBR (for pass2) + */ + bit_rate_tolerance: number + /** Global quality for codecs which cannot change it per frame. This should be proportional to MPEG-1/2/4 qscale. */ + global_quality: number + + compression_level: number + /** + * Maximum number of B-frames between non-B-frames + * Note: The output will be delayed by max_b_frames+1 relative to the input. + */ + max_b_frames: number + /** qscale factor between IP and B-frames + * If > 0 then the last P-frame quantizer will be used (q= lastp_q*factor+offset). + * If < 0 then normal ratecontrol will be done (q= -normal_q*factor+offset). + */ + b_quant_factor: number + /** qscale offset between IP and B-frames */ + b_quant_offset: number + /** qscale factor between P- and I-frames + * If > 0 then the last P-frame quantizer will be used (q = lastp_q * factor + offset). + * If < 0 then normal ratecontrol will be done (q= -normal_q*factor+offset). + */ + i_quant_factor: number + /** qscale offset between P and I-frames */ + i_quant_offset: number + /** luminance masking (0-> disabled) */ + lumi_masking: number + /** temporal complexity masking (0-> disabled) */ + temporal_cplx_masking: number + /** spatial complexity masking (0-> disabled) */ + spatial_cplx_masking: number + /** p block masking (0-> disabled) */ + p_masking: number + /** darkness masking (0-> disabled) */ + dark_masking: number + /** motion estimation comparison function */ + me_cmp: MotionEstimationString + /** subpixel motion estimation comparison function */ + me_sub_cmp: MotionEstimationString + /** macroblock comparison function (not supported yet) */ + mb_cmp: MotionEstimationString + /** interlaced DCT comparison function */ + ildct_cmp: MotionEstimationString + /** ME diamond size & shape */ + dia_size: number + /** amount of previous MV predictors (2a+1 x 2a+1 square) */ + last_predictor_count: number + + /** motion estimation prepass comparison function */ + me_pre_cmp: MotionEstimationString + /** ME prepass diamond size & shape */ + pre_dia_size: number + /** subpel ME quality */ + me_subpel_quality: number + /** maximum motion estimation search range in subpel units. If 0 then no limit. */ + me_range: number + /** + * macroblock decision mode + * simple: uses mb_cmp + * bits: chooses the one which needs the fewest bits + * rd: rate distortion + */ + mb_decision: 'simple' | 'bits' | 'rd' + + /** minimum MB Lagrange multiplier */ + mb_lmin: number + /** maximum MB Lagrange multiplier */ + mb_lmax: number + /** */ + bidir_refine: number + /** minimum GOP size */ + keyint_min: number + + /** Value depends upon the compare function used for fullpel ME. */ + mv0_threshold: number + + /** Number of slices. Indicates number of picture subdivisions. Used for parallelized decoding. */ + slices: number + /** number of bytes per packet if constant and known or 0. Used by some WAV based audio codecs. */ + block_align: number + + /** Type of service that the audio stream conveys. */ + audio_service_type: 'main' | 'effects' | 'visually-impaired' | 'hearing-impaired' | 'dialogue' | + 'commentary' | 'emergency' | 'voice-over' | 'karaoke' | 'nb' + + /** amount of qscale change between easy & hard scenes (0.0-1.0) */ + qcompress: number + /** amount of qscale smoothing over time (0.0-1.0) */ + qblur: number + /** minimum quantizer */ + qmin: number + /** maximum quantizer */ + qmax: number + /** maximum quantizer difference between frames */ + max_qdiff: number + /** decoder bitstream buffer size */ + rc_buffer_size: number + /** ratecontrol override */ + rc_override: Array<{ + type: 'RcOverride' + start_frame: number + end_frame: number + /** If this is 0 then quality_factor will be used instead. */ + qscale: number + quality_factor: number + }> + /** minimum bitrate */ + rc_min_rate: number + /** Ratecontrol attempt to use, at maximum, of what can be used without an underflow. */ + rc_max_available_vbv_use: number + /** Ratecontrol attempt to use, at least, times the amount needed to prevent a vbv overflow. */ + rc_min_vbv_overflow_use: number + /** Number of bits which should be loaded into the rc buffer before decoding starts. */ + rc_initial_buffer_occupancy: number + /** trellis RD quantization */ + trellis: number + /** pass1 encoding statistics output buffer */ + readonly stats_out: string | null + /** pass2 encoding statistics input buffer. Concatenated stuff from stats_out of pass1 should be placed here. */ + stats_in: string | null + readonly error: ReadonlyArray | null + /** DCT algorithm */ + dct_algo: 'auto' | 'fastint' | 'int' | 'mmx' | 'altivec' | 'faan' + /** nsse_weight */ + nsse_weight: number + /** + * Audio only. The number of "priming" samples (padding) inserted by the + * encoder at the beginning of the audio. I.e. this number of leading + * decoded samples must be discarded by the caller to get the original audio + * without leading padding. + * + * The timestamps on the output packets are adjusted by the encoder so that + * they always refer to the first sample of the data actually contained in the packet, + * including any added padding. E.g. if the timebase is 1/samplerate and + * the timestamp of the first input sample is 0, the timestamp of the + * first output packet will be -initial_padding. + */ + readonly initial_padding: number + /** Number of samples to skip after a discontinuity */ + readonly seek_preroll: number + /** custom intra quantization matrix */ + chroma_intra_matrix: Array + /** Additional data associated with the entire coded stream. */ + readonly coded_side_data: { + type: 'PacketSideData' + [key: string]: Buffer | string + } +} diff --git a/types/CodecPar.d.ts b/types/CodecPar.d.ts index cb30734..e1597d3 100644 --- a/types/CodecPar.d.ts +++ b/types/CodecPar.d.ts @@ -2,40 +2,22 @@ * CodecPar describes the properties of an encoded stream. */ export interface CodecPar { - - // native code; - readonly _codecPar: {}; - /** Object name. */ - readonly type: 'CodecParameters' - /** General type of the encoded data. */ - codec_type: string - /** Specific type of the encoded data (the codec used). */ - codec_id: number - /** The name corresponding to the codec_id. */ - name: string - /** Additional information about the codec (corresponds to the AVI FOURCC). */ - codec_tag: string - /** Extra binary data needed for initializing the decoder, codec-dependent. */ - extradata: Buffer - /** - * - video: the pixel format. - * - audio: the sample format. - */ - format: string /** The average bitrate of the encoded data (in bits per second). */ bit_rate: number + /** - * The number of bits per sample in the codedwords. - * - * This is basically the bitrate per sample. It is mandatory for a bunch of - * formats to actually decode them. It's the number of bits for one sample in - * the actual coded bitstream. - * - * This could be for example 4 for ADPCM - * For PCM formats this matches bits_per_raw_sample - * Can be 0 - */ + * The number of bits per sample in the codedwords. + * + * This is basically the bitrate per sample. It is mandatory for a bunch of + * formats to actually decode them. It's the number of bits for one sample in + * the actual coded bitstream. + * + * This could be for example 4 for ADPCM + * For PCM formats this matches bits_per_raw_sample + * Can be 0 + */ bits_per_coded_sample: number + /** * This is the number of valid bits in each output sample. If the * sample format has more bits, the least significant bits are additional @@ -47,47 +29,67 @@ export interface CodecPar { * For ADPCM this might be 12 or 16 or similar * Can be 0 */ - bits_per_raw_sample: number - /** Codec-specific bitstream restrictions that the stream conforms to. */ - profile: string | number - level: number - /** Video only. The video frame width in pixels. */ - width: number - /** Video only. The video frame height in pixels. */ - height: number + bits_per_raw_sample: number + + /** + * Audio only. The number of bytes per coded audio frame, required by some + * formats. + * + * Corresponds to nBlockAlign in WAVEFORMATEX. + */ + block_align: number + /** - * Video only. The aspect ratio (width / height) which a single pixel - * should have when displayed. - * - * When the aspect ratio is unknown / undefined, the numerator should be - * set to 0 (the denominator may have any value). + * Audio only. A description of the channel layout. + * ex: "0 channels" */ - sample_aspect_ratio: Array - /** Video only. The order of the fields in interlaced video. */ - field_order: string - /** Video only. Additional colorspace characteristics. */ - color_range: string - color_primaries: string - color_trc: string - color_space: string - chroma_location: string - /** Video only. Number of delayed frames. */ - video_delay: number - /** Audio only. A description of the channel layout. */ channel_layout: string + /** Audio only. The number of audio channels. */ channels: number - /** Audio only. The number of audio samples per second. */ - sample_rate: number + + /** Video only. Additional colorspace characteristics. */ + chroma_location: string | 'unspecified' + + /** Specific type of the encoded data (the codec used). */ + codec_id: number + + /** Additional information about the codec (corresponds to the AVI FOURCC). */ + codec_tag: number + + /** General type of the encoded data. */ + codec_type: string | 'data' + + /** Video only. Additional colorspace characteristics. */ + color_primaries: string | 'unknown' + + /** Video only. Additional colorspace characteristics. */ + color_range: string | 'unknown' + + /** Video only. Additional colorspace characteristics. */ + color_space: string | 'unknown' + + /** Video only. Additional colorspace characteristics. */ + color_trc: string | 'unknown' + + /** Extra binary data needed for initializing the decoder, codec-dependent. */ + extradata: Buffer | null + + /** Video only. The order of the fields in interlaced video. */ + field_order: string | 'unknown' + /** - * Audio only. The number of bytes per coded audio frame, required by some - * formats. - * - * Corresponds to nBlockAlign in WAVEFORMATEX. + * - video: the pixel format. + * - audio: the sample format. */ - block_align: number + format: 'video' | 'audio' | null + /** Audio only. Audio frame size, if known. Required by some formats to be static. */ frame_size: number + + /** Video only. The video frame height in pixels. */ + height: number + /** * Audio only. The amount of padding (in samples) inserted by the encoder at * the beginning of the audio. I.e. this number of leading decoded samples @@ -95,16 +97,45 @@ export interface CodecPar { * padding. */ initial_padding: number + + level: number + + /** The name corresponding to the codec_id. */ + name: 'node' | string + + /** Codec-specific bitstream restrictions that the stream conforms to. */ + profile: string | number + /** - * Audio only. The amount of padding (in samples) appended by the encoder to - * the end of the audio. I.e. this number of decoded samples must be - * discarded by the caller from the end of the stream to get the original - * audio without any trailing padding. + * Video only. The aspect ratio (width / height) which a single pixel + * should have when displayed. + * + * When the aspect ratio is unknown / undefined, the numerator should be + * set to 0 (the denominator may have any value). */ - trailing_padding: number + sample_aspect_ratio: [number, number] + + /** Audio only. The number of audio samples per second. */ + sample_rate: number + /** Audio only. Number of samples to skip after a discontinuity. */ seek_preroll: number + + trailing_padding: number /** Retun a JSON string containing the object properties. */ + + /** Object name. */ + readonly type: 'CodecParameters' + + /** Video only. Number of delayed frames. */ + video_delay: number + + /** Video only. The video frame width in pixels. */ + width: number + + // native code; + readonly _codecPar: {}; + toJSON(): string } diff --git a/types/Decoder.d.ts b/types/Decoder.d.ts index 488cb72..f97b205 100644 --- a/types/Decoder.d.ts +++ b/types/Decoder.d.ts @@ -2,36 +2,23 @@ import { CodecPar } from "./CodecPar" import { Packet } from "./Packet" import { Frame } from "./Frame" import { Codec } from "./Codec" -import { CodecContext } from "./CodecContext" +import { CodecContext, CodecContext_base } from "./CodecContext" import { Demuxer } from "./Demuxer" /** The DecodedFrames object is returned as the result of a decode operation */ export interface DecodedFrames { /** 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 - */ + /** + * 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 /** Total time in microseconds that the decode operation took to complete */ readonly total_time: number } -export interface Decoder extends Omit { +export interface Decoder extends CodecContext_base { readonly type: 'decoder' readonly time_base: Array readonly sample_aspect_ratio: Array @@ -45,14 +32,14 @@ export interface Decoder extends Omit + decode(packet: Packet | Packet[]): Promise /** * Decode a number of packets passed as separate parameters and create uncompressed frames * (may be a frames-worth of audio). * Decoders may need more than one packet to produce a frame and may subsequently * produce more than one frame per packet. This is particularly the case for long-GOP formats. * @param packets An arbitrary number of packets to be decoded - * @returns a promise that resolves to a DecodedFrames object when the decode has completed successfully + * @returns a promise that resolves to a DecodedFrames object when the decode has completed successfully */ decode(...packets: Packet[]): Promise - /** + /** * Once all packets have been passed to the decoder, it is necessary to call its * asynchronous flush() method. If any frames are yet to be delivered by the decoder * they will be provided in the resolved value. @@ -85,7 +72,7 @@ export interface Decoder extends Omit /** @@ -97,7 +84,7 @@ export interface Decoder extends Omit Date: Tue, 19 Apr 2022 14:04:08 +0300 Subject: [PATCH 15/68] types --- test/codecParamsSpec.ts | 4 +- test/decoderSpec.ts | 5 +- test/demuxerSpec.ts | 4 +- test/encoderSpec.ts | 6 +- test/filtererSpec.ts | 4 +- test/formatSpec.ts | 8 +- test/frameSpec.ts | 8 +- types/CodecContext.d.ts | 489 ++++++++++++++++++++------------------- types/CodecPar.d.ts | 164 ++++++------- types/Decoder.d.ts | 52 ++--- types/Demuxer.d.ts | 18 +- types/Encoder.d.ts | 22 +- types/Filter.d.ts | 243 +++++++++---------- types/FormatContext.d.ts | 276 +++++++++++----------- types/Frame.d.ts | 32 +-- 15 files changed, 672 insertions(+), 663 deletions(-) diff --git a/test/codecParamsSpec.ts b/test/codecParamsSpec.ts index 78fbf30..cf9ff63 100644 --- a/test/codecParamsSpec.ts +++ b/test/codecParamsSpec.ts @@ -20,7 +20,7 @@ */ import test from 'tape'; -import beamcoder from '..'; +import beamcoder, { CodecPar } from '..'; test('Creating codec parameters', t => { let cps = beamcoder.codecParameters(); @@ -80,7 +80,7 @@ test('Minimal JSON serialization', t => { }); test('Maximal JSON serialization', t=> { - let cp = beamcoder.codecParameters({ + let cp: CodecPar = beamcoder.codecParameters({ codec_type: 'video', codec_id: 27, codec_tag: 'avc1', diff --git a/test/decoderSpec.ts b/test/decoderSpec.ts index faff7d3..cbc9920 100644 --- a/test/decoderSpec.ts +++ b/test/decoderSpec.ts @@ -20,10 +20,10 @@ */ import test from 'tape'; -import beamcoder from '..'; +import beamcoder, { Decoder } from '..'; test('Creating a decoder', t => { - let dec = beamcoder.decoder({ name: 'h264' }); + let dec: Decoder = beamcoder.decoder({ name: 'h264' }); t.ok(dec, 'is truthy.'); t.equal(dec.name, 'h264', 'has the expected name.'); t.equal(dec.codec_id, 27, 'has the expected codec_id.'); @@ -48,7 +48,6 @@ 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.ts b/test/demuxerSpec.ts index 5675e26..0cc58fb 100644 --- a/test/demuxerSpec.ts +++ b/test/demuxerSpec.ts @@ -20,10 +20,10 @@ */ import test from 'tape'; -import beamcoder from '..'; +import beamcoder, { Demuxer } from '..'; test('Creating a demuxer', async t => { - let dm = await beamcoder.demuxer('https://www.elecard.com/storage/video/bbb_1080p_c.ts'); + let dm: Demuxer = 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 diff --git a/test/encoderSpec.ts b/test/encoderSpec.ts index 27c5469..dc1478f 100644 --- a/test/encoderSpec.ts +++ b/test/encoderSpec.ts @@ -20,10 +20,10 @@ */ import test from 'tape'; -import beamcoder from '..'; +import beamcoder, { Encoder } from '..'; test('Creating a video encoder', t => { - let enc = beamcoder.encoder({ name: 'h264' }); + let enc: Encoder = beamcoder.encoder({ name: 'h264' }); t.ok(enc, 'is truthy.'); t.equal(enc.name, 'libx264', 'has the expected name.'); t.equal(enc.codec_id, 27, 'has the expected codec_id.'); @@ -43,7 +43,7 @@ test('Creating an audio encoder', t => { }); test('Checking the A properties:', t => { - let enc = beamcoder.encoder({ name: 'h264' }); + let enc: Encoder = beamcoder.encoder({ name: 'h264' }); t.deepEqual(enc.active_thread_type, { FRAME: false, SLICE: false}, 'active_thread_type has expected default.'); diff --git a/test/filtererSpec.ts b/test/filtererSpec.ts index 080e127..258c231 100644 --- a/test/filtererSpec.ts +++ b/test/filtererSpec.ts @@ -20,10 +20,10 @@ */ import test from 'tape'; -import beamcoder from '..'; +import beamcoder, { Filterer } from '..'; test('Create a filterer', async t => { - let flt = await beamcoder.filterer({ + let flt: Filterer = await beamcoder.filterer({ filterType: 'audio', inputParams: [ { diff --git a/test/formatSpec.ts b/test/formatSpec.ts index c8561a9..16f4dbe 100644 --- a/test/formatSpec.ts +++ b/test/formatSpec.ts @@ -20,14 +20,14 @@ */ import test from 'tape'; -import beamcoder from '..'; +import beamcoder, { FormatContext } from '..'; const isExternal = o => (Object as any).toString(o).indexOf('native code') >= 0; //const isExternal = o => Object.toString.apply(o).indexOf('native code') >= 0; // Object.toString.apply(Object) test('Creating a format', t => { - let fmt = beamcoder.format(); + let fmt: FormatContext = beamcoder.format(); t.ok(fmt, 'is truthy.'); t.equal(fmt.type, 'format', 'calls itself type format.'); t.equal(fmt.iformat, null, 'has no input format.'); @@ -44,7 +44,7 @@ test('Creating a format', t => { const stripNewStream = ({ newStream, ...others }) => ({ ...others }); // eslint-disable-line no-unused-vars test('Minimal JSON serialization', t => { - let fmt = beamcoder.format(); + let fmt: FormatContext = beamcoder.format(); let fmts = JSON.stringify(fmt); t.equal(typeof fmts, 'string', 'stringify creates a string.'); let fmtj = JSON.parse(fmts); @@ -127,7 +127,7 @@ test('Minimal JSON serialization', t => { }); test('Maximal JSON serialization', t => { - let fmt = beamcoder.format({ + let fmt: FormatContext = beamcoder.format({ type: 'format', oformat: null, iformat: null, diff --git a/test/frameSpec.ts b/test/frameSpec.ts index 46aa0dc..b8b0331 100644 --- a/test/frameSpec.ts +++ b/test/frameSpec.ts @@ -20,17 +20,17 @@ */ import test from 'tape'; -import beamcoder from '..'; +import beamcoder, { Frame } from '..'; import util from 'util'; test('Create a frame', t => { - let fr = beamcoder.frame(); + let fr: Frame = beamcoder.frame(); t.ok(fr, 'is truthy.'); t.end(); }); test('Minimal JSON serialization', t => { - let fr = beamcoder.frame({}); + let fr: Frame = beamcoder.frame({}); let fp = JSON.stringify(fr); t.ok(fp, 'JSON serialization is truthy.'); let pfp = JSON.parse(fp); @@ -42,7 +42,7 @@ test('Minimal JSON serialization', t => { }); test('Maximal JSON serialization', t => { - let fr = beamcoder.frame({ type: 'Frame', + let fr: Frame = beamcoder.frame({ type: 'Frame', linesize: [42], width: 43, height: 44, diff --git a/types/CodecContext.d.ts b/types/CodecContext.d.ts index ecd69de..2bdb497 100644 --- a/types/CodecContext.d.ts +++ b/types/CodecContext.d.ts @@ -5,60 +5,29 @@ export type MotionEstimationString = 'sad' | 'sse' | 'satd' | 'dct' | 'psnr' | ' export type FrameSkipString = 'none' | 'default' | 'nonref' | 'bidir' | 'nonintra' | 'nonkey' | 'all' -export interface CodecContext_base { - /** Which multithreading methods are in use by the codec. */ - readonly active_thread_type: { FRAME?: boolean, SLICE?: boolean } - /** - * Video decoding only. Certain video codecs support cropping, meaning that - * only a sub-rectangle of the decoded frame is intended for display. This - * option controls how cropping is handled by libavcodec. - * - * When set to 1 (the default), libavcodec will apply cropping internally. - * I.e. it will modify the output frame width/height fields and offset the - * data pointers (only by as much as possible while preserving alignment, or - * by the full amount if the AV_CODEC_FLAG_UNALIGNED flag is set) so that - * the frames output by the decoder refer only to the cropped area. The - * crop_* fields of the output frames will be zero. - * - * When set to 0, the width/height fields of the output frames will be set - * to the coded dimensions and the crop_* fields will describe the cropping - * rectangle. Applying the cropping is left to the caller. - * - * @warning When hardware acceleration with opaque output frames is used, - * libavcodec is unable to apply cropping from the top/left border. - * - * @note when this option is set to zero, the width/height fields of the - * AVCodecContext and output AVFrames have different meanings. The codec - * context fields store display dimensions (with the coded dimensions in - * coded_width/height), while the frame fields store the coded dimensions - * (with the display dimensions being determined by the crop_* fields). - */ - apply_cropping: number - - // private field - readonly _CodecContext: {}; +export interface CodecContextBaseMin { /** Object name. */ - readonly type: string + readonly type: 'decoder' | 'encoder'; // string /** see AV_CODEC_ID_xxx */ readonly codec_id: number /** * Name of the codec implementation. - * The name is globally unique among encoders and among decoders (but an - * encoder and a decoder can share the same name). - * This is the primary way to find a codec from the user perspective. - */ - readonly name: string - /** Descriptive name for the codec, meant to be more human readable than name. */ - readonly long_name: string - /** - * A fourcc string by default, will be a number if not recognised - * - LSB first, so "ABCD" -> ('D'<<24) + ('C'<<16) + ('B'<<8) + 'A'). + * The name is globally unique among encoders and among decoders (but an + * encoder and a decoder can share the same name). + * This is the primary way to find a codec from the user perspective. */ - readonly codec_tag: string | number - /** Codec private data. */ - priv_data: { [key: string]: any } | null - /** The average bitrate */ - bit_rate: number + readonly name: string + /** Descriptive name for the codec, meant to be more human readable than name. */ + readonly long_name: string + /** + * A fourcc string by default, will be a number if not recognised + * - LSB first, so "ABCD" -> ('D'<<24) + ('C'<<16) + ('B'<<8) + 'A'). + */ + readonly codec_tag: string | number + /** Codec private data. */ + priv_data: { [key: string]: any } | null + /** The average bitrate */ + bit_rate: number /** AV_CODEC_FLAG_*. */ flags: { [key: string]: boolean } /** AV_CODEC_FLAG2_*. */ @@ -71,8 +40,8 @@ export interface CodecContext_base { * The allocated memory should be AV_INPUT_BUFFER_PADDING_SIZE bytes larger * than extradata_size to avoid problems if it is read with the bitstream reader. * The bytewise contents of extradata must not depend on the architecture or CPU endianness. - */ - extradata: Buffer | null + */ + extradata: Buffer | null /** * This is the fundamental unit of time (in seconds) in terms * of which frame timestamps are represented. For fixed-fps content, @@ -87,8 +56,8 @@ export interface CodecContext_base { * As example of such codec time base see ISO/IEC 14496-2:2001(E) * 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 @@ -96,7 +65,7 @@ export interface CodecContext_base { * * Set to time_base ticks per frame. Default 1, e.g., H.264/MPEG-2 set it to 2. */ - ticks_per_frame: number + ticks_per_frame: number /** * Number of frames delay in addition to what a standard decoder * as specified in the spec would produce. @@ -109,7 +78,7 @@ export interface CodecContext_base { * Number of samples the decoder needs to output before the decoder's output is valid. * When seeking, you should start decoding this many samples prior to your desired seek point. */ - readonly delay: number + readonly delay: number /** * picture width / height. * @@ -121,22 +90,9 @@ export interface CodecContext_base { * to be set by the caller. During decoding, the decoder may * overwrite those values as required while parsing the data. */ - width: number - height: number - /** - * Bitstream width / height, may be different from width/height e.g. when - * the decoded frame is cropped before being output or lowres is enabled. - * - * @note Those field may not match the value of the last - * Frame output due to frame reordering. - * - * May be set by the user before opening the decoder if known - * e.g. from the container. During decoding, the decoder may - * overwrite those values as required while parsing the data. - */ - coded_width: any - coded_height: any - /** + width: number + height: number + /** * Pixel format, see AV_PIX_FMT_xxx. * May be set by the demuxer if known from headers. * May be overridden by the decoder if it knows better. @@ -144,63 +100,43 @@ export interface CodecContext_base { * @note This field may not match the value of the last * Frame output due to frame reordering. */ - pix_fmt: string | null + pix_fmt: string | null /** * Size of the frame reordering buffer in the decoder. * For MPEG-2 it is 1 IPB or 0 low delay IP. */ - readonly has_b_frames: number + readonly has_b_frames: number /** slice count */ slice_count: number /** slice offsets in the frame in bytes */ slice_offset: Array | null - /** - * sample aspect ratio (0/1 if unknown) - * That is the width of a pixel divided by the height of the pixel. - * Numerator and denominator must be relatively prime and smaller than 256 for some video standards. - */ - sample_aspect_ratio: Array - - - - - - slice_flags: { - /** draw_horiz_band() is called in coded order instead of display */ - CODED_ORDER: boolean - /** allow draw_horiz_band() with field slices (MPEG-2 field pics) */ - ALLOW_FIELD: boolean - /** allow draw_horiz_band() with 1 component at a time (SVQ1) */ - ALLOW_PLANE: boolean - } - /** custom intra quantization matrix */ intra_matrix: Array | null /** custom inter quantization matrix */ inter_matrix: Array | null /** precision of the intra DC coefficient - 8 */ intra_dc_precision: number - /** Number of macroblock rows at the top which are skipped. */ - skip_top: number - /** Number of macroblock rows at the bottom which are skipped. */ - skip_bottom: number - - - + /** + * sample aspect ratio (0/1 if unknown) + * That is the width of a pixel divided by the height of the pixel. + * Numerator and denominator must be relatively prime and smaller than 256 for some video standards. + */ + sample_aspect_ratio: [number, number] /** number of reference frames */ refs: number /** Chromaticity coordinates of the source primaries. */ - color_primaries?: string + color_primaries?: string | "unknown" /** Color Transfer Characteristic. */ - color_trc: string + color_trc: string | "unknown" /** YUV colorspace type. */ - colorspace: string + colorspace: string | "unknown" /** MPEG vs JPEG YUV range. */ - color_range: string - /** + color_range: string | "unknown" + + /** * Location of chroma samples. * * Illustration showing the location of the first (top left) chroma sample of the @@ -212,147 +148,103 @@ export interface CodecContext_base { * v v v v * ______ ______ *1st luma line > |X X ... |3 4 X ... X are luma samples, - *. | |1 2 1-6 are possible chroma positions + *. | |1 2 1-6 are possible chroma positions *2nd luma line > |X X ... |5 6 X ... 0 is undefined/unknown position *``` */ - chroma_sample_location: 'unspecified' | 'left' | 'center' | 'topleft' | 'top' | 'bottomleft' | 'bottom' + chroma_sample_location: 'unspecified' | 'left' | 'center' | 'topleft' | 'top' | 'bottomleft' | 'bottom' - field_order: 'progressive' | - 'top coded first, top displayed first' | - 'bottom coded first, bottom displayed first' | - 'top coded first, bottom displayed first' | - 'bottom coded first, top displayed first' | - 'unknown' - /** Audio only - samples per second */ - sample_rate: number - /** Audio only - number of audio channels */ - channels: number - /** audio sample format */ - sample_fmt: string | null - /** - * Number of samples per channel in an audio frame. - * May be set by some decoders to indicate constant frame size - */ - readonly frame_size: number - /** - * Frame counter - total number of frames returned from the decoder so far. - * @note the counter is not incremented if encoding/decoding resulted in an error. - */ - readonly frame_number: number - /** Audio cutoff bandwidth (0 means "automatic") */ - cutoff: number - /** Audio channel layout. */ - channel_layout: string - /** Request decoder to use this channel layout if it can (0 for default) */ - request_channel_layout: string - /** Desired sample format - decoder will decode to this format if it can. */ - request_sample_fmt: string | null + + field_order: 'progressive' | + 'top coded first, top displayed first' | + 'bottom coded first, bottom displayed first' | + 'top coded first, bottom displayed first' | + 'bottom coded first, top displayed first' | + 'unknown' + /** Audio only - samples per second */ + sample_rate: number + /** Audio only - number of audio channels */ + channels: number + /** audio sample format */ + sample_fmt: string | null + /** + * Number of samples per channel in an audio frame. + * May be set by some decoders to indicate constant frame size + */ + readonly frame_size: number + /** + * Frame counter - total number of frames returned from the decoder so far. + * @note the counter is not incremented if encoding/decoding resulted in an error. + */ + readonly frame_number: number + /** Audio cutoff bandwidth (0 means "automatic") */ + cutoff: number + /** Audio channel layout. */ + channel_layout: string | "0 channels" + + // readonly + audio_service_type: 'main' | 'effects' | 'visually-impaired' | 'hearing-impaired' | 'dialogue' | + 'commentary' | 'emergency' | 'voice-over' | 'karaoke' | 'nb' /** maximum bitrate */ rc_max_rate: number - /** Work around bugs in codecs which sometimes cannot be detected automatically. */ - workaround_bugs: { [key: string]: boolean } - /** - * strictly follow the standard (MPEG-4, ...). - * Setting this to STRICT or higher means the encoder and decoder will - * generally do stupid things, whereas setting it to unofficial or lower - * will mean the encoder might produce output that is not supported by all - * spec-compliant decoders. Decoders don't differentiate between normal, - * unofficial and experimental (that is, they always try to decode things - * when they can) unless they are explicitly asked to behave stupidly - * (=strictly conform to the specs) - */ - strict_std_compliance: 'very-strict' | 'strict' | 'normal' | 'unofficial' | 'experimental' - /** Error concealment flags */ - error_concealment: { - GUESS_MVS?: boolean - DEBLOCK?: boolean - FAVOR_INTER?: boolean - } - debug: { [key: string]: boolean } - /** Error recognition - may misdetect some more or less valid parts as errors. */ - err_recognition: { [key: string]: boolean } - /** Opaque 64-bit number (generally a PTS) that will be reordered and output in Frame.reordered_opaque */ - reordered_opaque: number + /** Work around bugs in codecs which sometimes cannot be detected automatically. */ + workaround_bugs: { [key: string]: boolean } + /** + * strictly follow the standard (MPEG-4, ...). + * Setting this to STRICT or higher means the encoder and decoder will + * generally do stupid things, whereas setting it to unofficial or lower + * will mean the encoder might produce output that is not supported by all + * spec-compliant decoders. Decoders don't differentiate between normal, + * unofficial and experimental (that is, they always try to decode things + * when they can) unless they are explicitly asked to behave stupidly + * (=strictly conform to the specs) + */ + strict_std_compliance: 'very-strict' | 'strict' | 'normal' | 'unofficial' | 'experimental' + debug: { [key: string]: boolean } /** IDCT algorithm */ idct_algo: 'auto' | 'int' | 'simple' | 'simplemmx' | 'arm' | 'altivec' | 'simplearm' | 'xvid' | 'simplearmv5te' | 'simplearmv6' | 'faan' | 'simpleneon' | 'none' | 'simpleauto' /** Bits per sample/pixel from the demuxer (needed for huffyuv). */ bits_per_coded_sample: number + /** Bits per sample/pixel of internal libavcodec pixel/sample format. */ bits_per_raw_sample: number /** Thread count is used to decide how many independent tasks should be passed to execute() */ thread_count: number + /** * Which multithreading methods to use. * Use of FRAME will increase decoding delay by one frame per thread, * so clients which cannot provide future frames should not use it. */ - thread_type: { FRAME?: boolean, SLICE?: boolean } - /** - * Set by the client if its custom get_buffer() callback can be called - * synchronously from another thread, which allows faster multithreaded decoding. - * draw_horiz_band() will be called from other threads regardless of this setting. - * Ignored if the default get_buffer() is used. - */ - thread_safe_callbacks: number + thread_type: { FRAME?: boolean, SLICE?: boolean } - profile: string | number - level: number - /** Skip loop filtering for selected frames. */ - skip_loop_filter: FrameSkipString - /** Skip IDCT/dequantization for selected frames. */ - skip_idct: FrameSkipString - /** Skip decoding for selected frames. */ - skip_frame: FrameSkipString - /** - * Header containing style information for text subtitles. - * For SUBTITLE_ASS subtitle type, it should contain the whole ASS - * [Script Info] and [V4+ Styles] section, plus the [Events] line and - * the Format line following. It shouldn't include any Dialogue line. - */ - subtitle_header: Buffer | null - /** + /** Which multithreading methods are in use by the codec. */ + readonly active_thread_type: { FRAME?: boolean, SLICE?: boolean } + /** + * Set by the client if its custom get_buffer() callback can be called + * synchronously from another thread, which allows faster multithreaded decoding. + * draw_horiz_band() will be called from other threads regardless of this setting. + * Ignored if the default get_buffer() is used. + */ + thread_safe_callbacks: number + + profile: string | number + level: number + /** + * Header containing style information for text subtitles. + * For SUBTITLE_ASS subtitle type, it should contain the whole ASS + * [Script Info] and [V4+ Styles] section, plus the [Events] line and + * the Format line following. It shouldn't include any Dialogue line. + */ + subtitle_header: Buffer | null + /** * For codecs that store a framerate value in the compressed * bitstream, the decoder may export it here. [ 0, 1 ] when unknown. */ - framerate: Array - /** 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. */ - pkt_timebase: Array - - readonly codec_descriptor: { - INTRA_ONLY: boolean - LOSSY: boolean - LOSSLESS: boolean - REORDER: boolean - BITMAP_SUB: boolean - TEXT_SUB: boolean - } | null - /** Character encoding of the input subtitles file. */ - sub_charenc: string | null - /** - * Subtitles character encoding mode. Formats or codecs might be adjusting - * this setting (if they are doing the conversion themselves for instance). - */ - readonly sub_charenc_mode: 'do-nothing' | 'automatic' | 'pre-decoder' | 'ignore' - /** - * Skip processing alpha if supported by codec. - * Note that if the format uses pre-multiplied alpha (common with VP6, - * and recommended due to better video quality/compression) - * the image will look as if alpha-blended onto a black background. - * However for formats that do not use pre-multiplied alpha - * there might be serious artefacts (though e.g. libswscale currently - * assumes pre-multiplied alpha anyway). - */ - skip_alpha: number + framerate: [number, number] /** Dump format separator - can be ", " or "\n " or anything else */ dump_separator: string | null - /** ',' separated list of allowed decoders - if null then all are allowed */ - codec_whitelist: string | null - /** Properties of the stream that gets decoded */ - readonly properties: { LOSSLESS: boolean, CLOSED_CAPTIONS: boolean } /** * A reference to the AVHWFramesContext describing the input (for encoding) * or output (decoding) frames. The reference is set by the caller and @@ -375,44 +267,151 @@ export interface CodecContext_base { * * This field should be set before avcodec_open2() is called. */ - hw_frames_ctx: HWFramesContext - /** Control the form of AVSubtitle.rects[N]->ass */ - sub_text_format: number + hw_frames_ctx: HWFramesContext | null /** * Audio only. The amount of padding (in samples) appended by the encoder to * the end of the audio. I.e. this number of decoded samples must be * discarded by the caller from the end of the stream to get the original * audio without any trailing padding. */ - trailing_padding: number - /** The number of pixels per image to maximally accept. */ - max_pixels: number - /** - * A reference to the HWDeviceContext describing the device which will - * be used by a hardware encoder/decoder. The reference is set by the - * caller and afterwards owned (and freed) by libavcodec. - * - * This should be used if either the codec device does not require - * hardware frames or any that are used are to be allocated internally by - * libavcodec. If the user wishes to supply any of the frames used as - * encoder input or decoder output then hw_frames_ctx should be used - * instead. When hw_frames_ctx is set in get_format() for a decoder, this - * field will be ignored while decoding the associated stream segment, but - * may again be used on a following one after another get_format() call. + trailing_padding: number + /** The number of pixels per image to maximally accept. */ + max_pixels: number + /** + * A reference to the HWDeviceContext describing the device which will + * be used by a hardware encoder/decoder. The reference is set by the + * caller and afterwards owned (and freed) by libavcodec. + * + * This should be used if either the codec device does not require + * hardware frames or any that are used are to be allocated internally by + * libavcodec. If the user wishes to supply any of the frames used as + * encoder input or decoder output then hw_frames_ctx should be used + * instead. When hw_frames_ctx is set in get_format() for a decoder, this + * field will be ignored while decoding the associated stream segment, but + * may again be used on a following one after another get_format() call. + * + * For both encoders and decoders this field should be set before + * avcodec_open2() is called and must not be written to thereafter. + * + * Note that some decoders may require this field to be set initially in + * order to support hw_frames_ctx at all - in that case, all frames + * contexts used must be created on the same device. + */ + hw_device_ctx: HWDeviceContext | null + // private field + readonly _CodecContext: {}; +} +export interface CodecContext_base extends CodecContextBaseMin { + /** + * Bitstream width / height, may be different from width/height e.g. when + * the decoded frame is cropped before being output or lowres is enabled. * - * For both encoders and decoders this field should be set before - * avcodec_open2() is called and must not be written to thereafter. + * @note Those field may not match the value of the last + * Frame output due to frame reordering. * - * Note that some decoders may require this field to be set initially in - * order to support hw_frames_ctx at all - in that case, all frames - * contexts used must be created on the same device. + * May be set by the user before opening the decoder if known + * e.g. from the container. During decoding, the decoder may + * overwrite those values as required while parsing the data. + */ + coded_width: any + coded_height: any + slice_flags: { + /** draw_horiz_band() is called in coded order instead of display */ + CODED_ORDER: boolean + /** allow draw_horiz_band() with field slices (MPEG-2 field pics) */ + ALLOW_FIELD: boolean + /** allow draw_horiz_band() with 1 component at a time (SVQ1) */ + ALLOW_PLANE: boolean + } + /** Number of macroblock rows at the top which are skipped. */ + skip_top: number + /** Number of macroblock rows at the bottom which are skipped. */ + skip_bottom: number + /** Request decoder to use this channel layout if it can (0 for default) */ + request_channel_layout: string | 'default' + /** Desired sample format - decoder will decode to this format if it can. */ + request_sample_fmt: string | null + /** Error concealment flags */ + error_concealment: { + GUESS_MVS?: boolean + DEBLOCK?: boolean + FAVOR_INTER?: boolean + } + /** Error recognition - may misdetect some more or less valid parts as errors. */ + err_recognition: { [key: string]: boolean } + /** Opaque 64-bit number (generally a PTS) that will be reordered and output in Frame.reordered_opaque */ + reordered_opaque: number + /** Skip loop filtering for selected frames. */ + skip_loop_filter: FrameSkipString + /** Skip IDCT/dequantization for selected frames. */ + skip_idct: FrameSkipString + /** Skip decoding for selected frames. */ + skip_frame: FrameSkipString + /** 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. */ + pkt_timebase: [number, number] + readonly codec_descriptor: { + INTRA_ONLY: boolean + LOSSY: boolean + LOSSLESS: boolean + REORDER: boolean + BITMAP_SUB: boolean + TEXT_SUB: boolean + } | null + /** Character encoding of the input subtitles file. */ + sub_charenc: string | null + /** + * Subtitles character encoding mode. Formats or codecs might be adjusting + * this setting (if they are doing the conversion themselves for instance). */ - hw_device_ctx: HWDeviceContext + readonly sub_charenc_mode: 'do-nothing' | 'automatic' | 'pre-decoder' | 'ignore' /** + * Skip processing alpha if supported by codec. + * Note that if the format uses pre-multiplied alpha (common with VP6, + * and recommended due to better video quality/compression) + * the image will look as if alpha-blended onto a black background. + * However for formats that do not use pre-multiplied alpha + * there might be serious artefacts (though e.g. libswscale currently + * assumes pre-multiplied alpha anyway). + */ + skip_alpha: number + /** ',' separated list of allowed decoders - if null then all are allowed */ + codec_whitelist: string | null + /** Properties of the stream that gets decoded */ + readonly properties: { LOSSLESS: boolean, CLOSED_CAPTIONS: boolean } + /** * Bit set of AV_HWACCEL_FLAG_* flags, which affect hardware accelerated * decoding (if active). */ - hwaccel_flags: { IGNORE_LEVEL?: boolean, ALLOW_HIGH_DEPTH?: boolean, ALLOW_PROFILE_MISMATCH?: boolean } + hwaccel_flags: { IGNORE_LEVEL?: boolean, ALLOW_HIGH_DEPTH?: boolean, ALLOW_PROFILE_MISMATCH?: boolean } + + /** + * Video decoding only. Certain video codecs support cropping, meaning that + * only a sub-rectangle of the decoded frame is intended for display. This + * option controls how cropping is handled by libavcodec. + * + * When set to 1 (the default), libavcodec will apply cropping internally. + * I.e. it will modify the output frame width/height fields and offset the + * data pointers (only by as much as possible while preserving alignment, or + * by the full amount if the AV_CODEC_FLAG_UNALIGNED flag is set) so that + * the frames output by the decoder refer only to the cropped area. The + * crop_* fields of the output frames will be zero. + * + * When set to 0, the width/height fields of the output frames will be set + * to the coded dimensions and the crop_* fields will describe the cropping + * rectangle. Applying the cropping is left to the caller. + * + * @warning When hardware acceleration with opaque output frames is used, + * libavcodec is unable to apply cropping from the top/left border. + * + * @note when this option is set to zero, the width/height fields of the + * AVCodecContext and output AVFrames have different meanings. The codec + * context fields store display dimensions (with the coded dimensions in + * coded_width/height), while the frame fields store the coded dimensions + * (with the display dimensions being determined by the crop_* fields). + */ + apply_cropping: number /* * Video decoding only. Sets the number of extra hardware frames which * the decoder will allocate for use by the caller. This must be set @@ -426,6 +425,8 @@ export interface CodecContext_base { * used as reference pictures). */ extra_hw_frames: number + /** Control the form of AVSubtitle.rects[N]->ass */ + sub_text_format: number } diff --git a/types/CodecPar.d.ts b/types/CodecPar.d.ts index e1597d3..4a58f4d 100644 --- a/types/CodecPar.d.ts +++ b/types/CodecPar.d.ts @@ -2,20 +2,44 @@ * CodecPar describes the properties of an encoded stream. */ export interface CodecPar { + /** Object name. */ + readonly type: 'CodecParameters' + + /** General type of the encoded data. */ + codec_type: string | 'data' | 'video' + + /** Specific type of the encoded data (the codec used). */ + codec_id: number + + /** The name corresponding to the codec_id. */ + name: 'node' | 'h264' | string + + /** Additional information about the codec (corresponds to the AVI FOURCC). */ + codec_tag: number | string + + /** Extra binary data needed for initializing the decoder, codec-dependent. */ + extradata: Buffer | null + + /** + * - video: the pixel format. + * - audio: the sample format. + */ + format: 'video' | 'audio' | null + /** The average bitrate of the encoded data (in bits per second). */ bit_rate: number /** - * The number of bits per sample in the codedwords. - * - * This is basically the bitrate per sample. It is mandatory for a bunch of - * formats to actually decode them. It's the number of bits for one sample in - * the actual coded bitstream. - * - * This could be for example 4 for ADPCM - * For PCM formats this matches bits_per_raw_sample - * Can be 0 - */ + * The number of bits per sample in the codedwords. + * + * This is basically the bitrate per sample. It is mandatory for a bunch of + * formats to actually decode them. It's the number of bits for one sample in + * the actual coded bitstream. + * + * This could be for example 4 for ADPCM + * For PCM formats this matches bits_per_raw_sample + * Can be 0 + */ bits_per_coded_sample: number /** @@ -29,67 +53,73 @@ export interface CodecPar { * For ADPCM this might be 12 or 16 or similar * Can be 0 */ - bits_per_raw_sample: number - - /** - * Audio only. The number of bytes per coded audio frame, required by some - * formats. - * - * Corresponds to nBlockAlign in WAVEFORMATEX. - */ - block_align: number + bits_per_raw_sample: number - /** - * Audio only. A description of the channel layout. - * ex: "0 channels" - */ - channel_layout: string + /** Codec-specific bitstream restrictions that the stream conforms to. */ + profile: string | number - /** Audio only. The number of audio channels. */ - channels: number + level: number - /** Video only. Additional colorspace characteristics. */ - chroma_location: string | 'unspecified' + /** Video only. The video frame width in pixels. */ + width: number - /** Specific type of the encoded data (the codec used). */ - codec_id: number + /** Video only. The video frame height in pixels. */ + height: number - /** Additional information about the codec (corresponds to the AVI FOURCC). */ - codec_tag: number + /** + * Video only. The aspect ratio (width / height) which a single pixel + * should have when displayed. + * + * When the aspect ratio is unknown / undefined, the numerator should be + * set to 0 (the denominator may have any value). + */ + sample_aspect_ratio: [number, number] - /** General type of the encoded data. */ - codec_type: string | 'data' + /** Video only. The order of the fields in interlaced video. */ + field_order: string | 'unknown' | 'progressive' /** Video only. Additional colorspace characteristics. */ - color_primaries: string | 'unknown' - + color_range: string | 'unknown' | 'pc' + /** Video only. Additional colorspace characteristics. */ - color_range: string | 'unknown' + color_primaries: string | 'unknown' | 'bt709' /** Video only. Additional colorspace characteristics. */ - color_space: string | 'unknown' + color_trc: string | 'unknown' | 'bt709' /** Video only. Additional colorspace characteristics. */ - color_trc: string | 'unknown' + color_space: string | 'unknown' | 'bt709' - /** Extra binary data needed for initializing the decoder, codec-dependent. */ - extradata: Buffer | null + /** Video only. Additional colorspace characteristics. */ + chroma_location: string | 'unspecified' | 'left' - /** Video only. The order of the fields in interlaced video. */ - field_order: string | 'unknown' + /** Video only. Number of delayed frames. */ + video_delay: number /** - * - video: the pixel format. - * - audio: the sample format. + * Audio only. A description of the channel layout. + * ex: "0 channels" */ - format: 'video' | 'audio' | null + channel_layout: string | '0 channels' + + + /** Audio only. The number of audio channels. */ + channels: number + + /** Audio only. The number of audio samples per second. */ + sample_rate: number + + /** + * Audio only. The number of bytes per coded audio frame, required by some + * formats. + * + * Corresponds to nBlockAlign in WAVEFORMATEX. + */ + block_align: number /** Audio only. Audio frame size, if known. Required by some formats to be static. */ frame_size: number - /** Video only. The video frame height in pixels. */ - height: number - /** * Audio only. The amount of padding (in samples) inserted by the encoder at * the beginning of the audio. I.e. this number of leading decoded samples @@ -98,45 +128,17 @@ export interface CodecPar { */ initial_padding: number - level: number - - /** The name corresponding to the codec_id. */ - name: 'node' | string - - /** Codec-specific bitstream restrictions that the stream conforms to. */ - profile: string | number - - /** - * Video only. The aspect ratio (width / height) which a single pixel - * should have when displayed. - * - * When the aspect ratio is unknown / undefined, the numerator should be - * set to 0 (the denominator may have any value). - */ - sample_aspect_ratio: [number, number] - - /** Audio only. The number of audio samples per second. */ - sample_rate: number + trailing_padding: number /** Audio only. Number of samples to skip after a discontinuity. */ seek_preroll: number - trailing_padding: number - /** Retun a JSON string containing the object properties. */ - - /** Object name. */ - readonly type: 'CodecParameters' - - /** Video only. Number of delayed frames. */ - video_delay: number - - /** Video only. The video frame width in pixels. */ - width: number - // native code; readonly _codecPar: {}; + /** Retun a JSON string containing the object properties. */ toJSON(): string } -export function codecParameters(options?: string | { [key: string]: any }): CodecPar; +export function codecParameters(options?: string | Partial>): CodecPar; +// { [key: string]: any } \ No newline at end of file diff --git a/types/Decoder.d.ts b/types/Decoder.d.ts index f97b205..f07e937 100644 --- a/types/Decoder.d.ts +++ b/types/Decoder.d.ts @@ -19,32 +19,32 @@ export interface DecodedFrames { } export interface Decoder extends CodecContext_base { - readonly type: 'decoder' - readonly time_base: Array - readonly sample_aspect_ratio: Array - readonly intra_matrix: Array | null - readonly inter_matrix: Array | null - readonly intra_dc_precision: number - readonly refs: number - readonly color_primaries?: string - readonly color_trc: string - readonly colorspace: string - readonly color_range: string - readonly chroma_sample_location: 'unspecified' | 'left' | 'center' | 'topleft' | 'top' | 'bottomleft' | 'bottom' - readonly field_order: 'progressive' | - 'top coded first, top displayed first' | - 'bottom coded first, bottom displayed first' | - 'top coded first, bottom displayed first' | - 'bottom coded first, top displayed first' | - 'unknown' - readonly sample_fmt: string | null - readonly audio_service_type: 'main' | 'effects' | 'visually-impaired' | 'hearing-impaired' | 'dialogue' | - 'commentary' | 'emergency' | 'voice-over' | 'karaoke' | 'nb' - readonly bits_per_raw_sample: number - readonly profile: string | number - readonly level: number - readonly subtitle_header: Buffer | null - readonly framerate: Array + // readonly type: 'decoder' + // readonly time_base: [number, number] + // readonly sample_aspect_ratio: [number, number] + // readonly intra_matrix: Array | null + // readonly inter_matrix: Array | null + // readonly intra_dc_precision: number + // readonly refs: number + // readonly color_primaries?: string + // readonly color_trc: string + // readonly colorspace: string + // readonly color_range: string + // readonly chroma_sample_location: 'unspecified' | 'left' | 'center' | 'topleft' | 'top' | 'bottomleft' | 'bottom' + // readonly field_order: 'progressive' | + // 'top coded first, top displayed first' | + // 'bottom coded first, bottom displayed first' | + // 'top coded first, bottom displayed first' | + // 'bottom coded first, top displayed first' | + // 'unknown' + // readonly sample_fmt: string | null + // readonly audio_service_type: 'main' | 'effects' | 'visually-impaired' | 'hearing-impaired' | 'dialogue' | + // 'commentary' | 'emergency' | 'voice-over' | 'karaoke' | 'nb' + // readonly bits_per_raw_sample: number + // readonly profile: string | number + // readonly level: number + // readonly subtitle_header: Buffer | null + // readonly framerate: [number, number] /** * Decode an encoded data packet or array of packets and create an uncompressed frame diff --git a/types/Demuxer.d.ts b/types/Demuxer.d.ts index d6d364d..ad923bc 100644 --- a/types/Demuxer.d.ts +++ b/types/Demuxer.d.ts @@ -1,5 +1,5 @@ import { Packet } from "./Packet" -import { InputFormat, FormatContext } from "./FormatContext" +import { InputFormat, FormatContext, FormatContextBase } from "./FormatContext" export interface SeekOptions { /** @@ -43,19 +43,15 @@ export interface SeekOptions { * The process of demuxing (de-multiplexing) extracts time-labelled packets of data * contained in a media stream or file. */ -export interface Demuxer extends Omit { +export interface Demuxer extends FormatContextBase { // { read: () => Promise, streams: Array<{time_base: [number, number]}> } - - /** Object name. */ - readonly type: 'demuxer' - readonly iformat: InputFormat - readonly url: string - readonly duration: number + // readonly type: 'demuxer' + // readonly iformat: InputFormat + // readonly url: string + // readonly duration: number + interleaved: boolean, /** * Beam coder offers FFmpeg's many options for seeking a particular frame in a file, * either by time reference, frame count or file position. diff --git a/types/Encoder.d.ts b/types/Encoder.d.ts index 35d5f74..1abcec3 100644 --- a/types/Encoder.d.ts +++ b/types/Encoder.d.ts @@ -2,7 +2,7 @@ import { CodecPar } from "./CodecPar" import { Packet } from "./Packet"; import { Frame } from "./Frame"; import { Codec } from "./Codec" -import { CodecContext } from "./CodecContext" +import { CodecContext, CodecContextBaseMin } from "./CodecContext" /** The EncodedPackets object is returned as the result of a encode operation */ export interface EncodedPackets { @@ -10,7 +10,7 @@ export interface EncodedPackets { readonly type: 'packets' /** * Encoded packets that are now available. If the array is empty, the encoder has buffered - * the frame as part of the process of producing future packets + * the frame as part of the process of prodfcodec_tagucing future packets */ readonly packets: Array /** Total time in microseconds that the encode operation took to complete */ @@ -20,18 +20,12 @@ export interface EncodedPackets { * Encoder takes a stream of uncompressed data in the form of Frames and converts them into coded Packets. * Encoding takes place on a single type of stream, for example audio or video. */ -export interface Encoder extends Omit { +export interface Encoder extends CodecContextBaseMin { readonly type: 'encoder' - readonly extradata: Buffer | null - readonly slice_count: number - readonly slice_offset: Array | null - readonly bits_per_coded_sample: number + // readonly extradata: Buffer | null + // readonly slice_count: number + // readonly slice_offset: Array | null + // readonly bits_per_coded_sample: number /** * Encode a Frame or array of Frames and create a compressed Packet or Packets. @@ -64,7 +58,7 @@ export interface Encoder extends Omitis_disabled value. + */ + SUPPORT_TIMELINE_INTERNAL: boolean + /** + * Handy mask to test whether the filter supports or no the timeline feature + * (internally or generically). + */ + SUPPORT_TIMELINE: boolean\ +} + export interface Filter { readonly type: 'Filter' - /** Filter name. Must be non-NULL and unique among filters. */ + /** Filter name. Must be non-NULL and unique among filters. */ readonly name: string /** A description of the filter. May be NULL. */ readonly description: string @@ -29,47 +71,7 @@ export interface Filter { */ readonly priv_class: PrivClass | null /** A combination of AVFILTER_FLAG_* */ - readonly flags: { - /** - * The number of the filter inputs is not determined just by AVFilter.inputs. - * The filter might add additional inputs during initialization depending on the - * options supplied to it. - */ - DYNAMIC_INPUTS: boolean - /** - * The number of the filter outputs is not determined just by AVFilter.outputs. - * The filter might add additional outputs during initialization depending on - * the options supplied to it. - */ - DYNAMIC_OUTPUTS: boolean - /** - * The filter supports multithreading by splitting frames into multiple parts and - * processing them concurrently. - */ - SLICE_THREADS: boolean - /** - * Some filters support a generic "enable" expression option that can be used - * to enable or disable a filter in the timeline. Filters supporting this - * option have this flag set. When the enable expression is false, the default - * no-op filter_frame() function is called in place of the filter_frame() - * callback defined on each input pad, thus the frame is passed unchanged to - * the next filters. - */ - SUPPORT_TIMELINE_GENERIC: boolean - /** - * Same as AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, except that the filter will - * have its filter_frame() callback(s) called as usual even when the enable - * expression is false. The filter will disable filtering within the - * filter_frame() callback(s) itself, for example executing code depending on - * the AVFilterContext->is_disabled value. - */ - SUPPORT_TIMELINE_INTERNAL: boolean - /** - * Handy mask to test whether the filter supports or no the timeline feature - * (internally or generically). - */ - SUPPORT_TIMELINE: boolean - } + readonly flags: FilterFlags } export type MediaType = 'unknown' | 'video' | 'audio' | 'data' | 'subtitle' | 'attachment' | 'nb' @@ -80,7 +82,7 @@ export interface FilterPad { } export interface FilterLink { - /** source filter name */ + /** source filter name */ readonly src: string /** output pad on the source filter */ readonly srcpad: string @@ -106,7 +108,7 @@ export interface FilterLink { readonly format: string /** * Define the time base used by the PTS of the frames/samples which will pass through this link. - * During the configuration stage, each filter is supposed to change only the output timebase, + * During the configuration stage, each filter is supposed to change only the output timebase, * while the timebase of the input link is assumed to be an unchangeable property. */ readonly time_base: ReadonlyArray @@ -114,71 +116,71 @@ export interface FilterLink { export interface FilterContext { readonly type: 'FilterContext' - /** the AVFilter of which this is an instance */ + /** the AVFilter of which this is an instance */ readonly filter: Filter - /** name of this filter instance */ + /** name of this filter instance */ readonly name: string - /** array of input pads */ + /** array of input pads */ readonly input_pads: ReadonlyArray - /** array of pointers to input links */ + /** array of pointers to input links */ readonly inputs: ReadonlyArray | null - /** array of output pads */ + /** array of output pads */ readonly output_pads: ReadonlyArray - /** array of pointers to output links */ + /** array of pointers to output links */ readonly outputs: ReadonlyArray | null - /** private data for use by the filter */ + /** private data for use by the filter */ priv: { [key: string]: any } | null - /** - * Type of multithreading being allowed/used. A combination of - * AVFILTER_THREAD_* flags. - * - * May be set by the caller before initializing the filter to forbid some - * or all kinds of multithreading for this filter. The default is allowing - * everything. - * - * When the filter is initialized, this field is combined using bit AND with - * AVFilterGraph.thread_type to get the final mask used for determining - * allowed threading types. I.e. a threading type needs to be set in both - * to be allowed. - * - * After the filter is initialized, libavfilter sets this field to the - * threading type that is actually used (0 for no multithreading). - */ + /** + * Type of multithreading being allowed/used. A combination of + * AVFILTER_THREAD_* flags. + * + * May be set by the caller before initializing the filter to forbid some + * or all kinds of multithreading for this filter. The default is allowing + * everything. + * + * When the filter is initialized, this field is combined using bit AND with + * AVFilterGraph.thread_type to get the final mask used for determining + * allowed threading types. I.e. a threading type needs to be set in both + * to be allowed. + * + * After the filter is initialized, libavfilter sets this field to the + * threading type that is actually used (0 for no multithreading). + */ readonly thread_type: number - /** - * Max number of threads allowed in this filter instance. - * If <= 0, its value is ignored. - * Overrides global number of threads set per filter graph. - */ + /** + * Max number of threads allowed in this filter instance. + * If <= 0, its value is ignored. + * Overrides global number of threads set per filter graph. + */ readonly nb_threads: number - /** - * Ready status of the filter. - * A non-0 value means that the filter needs activating, - * a higher value suggests a more urgent activation. - */ + /** + * Ready status of the filter. + * A non-0 value means that the filter needs activating, + * a higher value suggests a more urgent activation. + */ readonly ready: number - /** - * Sets the number of extra hardware frames which the filter will - * allocate on its output links for use in following filters or by - * the caller. - * - * Some hardware filters require all frames that they will use for - * output to be defined in advance before filtering starts. For such - * filters, any hardware frame pools used for output must therefore be - * of fixed size. The extra frames set here are on top of any number - * that the filter needs internally in order to operate normally. - * - * This field must be set before the graph containing this filter is - * configured. - */ - readonly extra_hw_frames: number + /** + * Sets the number of extra hardware frames which the filter will + * allocate on its output links for use in following filters or by + * the caller. + * + * Some hardware filters require all frames that they will use for + * output to be defined in advance before filtering starts. For such + * filters, any hardware frame pools used for output must therefore be + * of fixed size. The extra frames set here are on top of any number + * that the filter needs internally in order to operate normally. + * + * This field must be set before the graph containing this filter is + * configured. + */ + readonly extra_hw_frames: number } export interface FilterGraph { readonly type: 'FilterGraph' readonly filters: ReadonlyArray - /** sws options to use for the auto-inserted scale filters */ + /** sws options to use for the auto-inserted scale filters */ readonly scale_sws_opts: string | null /** * Type of multithreading allowed for filters in this graph. A combination of AVFILTER_THREAD_* flags. @@ -190,16 +192,16 @@ export interface FilterGraph { * I.e. a threading type needs to be set in both to be allowed. */ readonly thread_type: number - /** - * Maximum number of threads used by filters in this graph. May be set by - * the caller before adding any filters to the filtergraph. Zero (the - * default) means that the number of threads is determined automatically. - */ + /** + * Maximum number of threads used by filters in this graph. May be set by + * the caller before adding any filters to the filtergraph. Zero (the + * default) means that the number of threads is determined automatically. + */ readonly nb_threads: number - /** - * Dump a graph into a human-readable string representation. - * @returns: String representation of the filter graph - */ + /** + * Dump a graph into a human-readable string representation. + * @returns: String representation of the filter graph + */ dump(): string } @@ -214,23 +216,23 @@ export interface Filterer { readonly type: 'Filterer' readonly graph: FilterGraph - /** - * Filter an array of frames - * For a filter that has only one input pass an array of frame objects directly - * and the filter input will have a default name applied. - * This name will match a filter specification that doesn't name its inputs. - * @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 an array of frames + * For a filter that has only one input pass an array of frame objects directly + * and the filter input will have a default name applied. + * This name will match a filter specification that doesn't name its inputs. + * @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 an array of frames - * Pass an array of objects, one per filter input, each with a name string property - * and a frames property that contains an array of frame objects - * The name must match the input name in the filter specification - * @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 an array of frames + * Pass an array of objects, one per filter input, each with a name string property + * and a frames property that contains an array of frame objects + * The name must match the input name in the filter specification + * @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 }> } @@ -242,10 +244,11 @@ export function filters(): { [key: string]: Filter } /** List the available bitstream filters */ export function bsfs(): { - [key: string]: { + [key: string]: { name: string codec_ids: Array - priv_class: PrivClass | null } + priv_class: PrivClass | null + } } /** The required parameters for setting up filter inputs */ diff --git a/types/FormatContext.d.ts b/types/FormatContext.d.ts index d5a3948..2db9a36 100644 --- a/types/FormatContext.d.ts +++ b/types/FormatContext.d.ts @@ -125,56 +125,9 @@ export interface OutputFormat { */ export function guessFormat(name: string): OutputFormat | null; -/** -* Format I/O context. - */ -export interface FormatContext { - /** Object name. */ - readonly type: string - /** The input format description. */ - set iformat(string: format): string; - get iformat(): InputFormat - - /** The output format description. */ - set oformat(string: format): string; - get oformat(): OutputFormat - /** Format private data. */ - priv_data: { - [key: string]: any - } - /** Flags signalling stream properties. */ - readonly ctx_flags: { - NOHEADER: boolean - UNSEEKABLE: boolean - } - /** An array of all streams in the file. */ - streams: Array - /** input or output URL. Unlike the old filename field, this field has no length restriction. */ - url: string - /** - * 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 - /** - * Duration of the stream, in AV_TIME_BASE fractional - * seconds. Only set this value if you know none of the individual stream - * durations and also do not set any of them. This is deduced from the - * Stream values if not set. - */ - duration: number - /** - * Total stream bitrate in bit/s, 0 if not - * available. Never set it directly if the file_size and the - * duration are known as FFmpeg can compute it automatically. - */ - bit_rate: number - packet_size: number - max_delay: number - /** Flags modifying the demuxer behaviour. A combination of AVFMT_FLAG_*. */ - flags: { + +export interface FormatContextFlags { /** Generate missing pts even if it requires parsing future frames. */ GENPTS?: boolean /** Ignore index. */ @@ -207,20 +160,67 @@ export interface FormatContext { SHORTEST?: boolean /** Add bitstream filters as requested by the muxer */ AUTO_BSF?: boolean +} + +export interface FormatContextBase { + /** Object name. */ + readonly type: 'demuxer'| 'format' + + /** The input format description. */ + set iformat(string: format): string; + get iformat(): InputFormat + /** Format private data. */ + priv_data: { + [key: string]: any } - /** Maximum size of the data read from input for determining the input container format. */ - probesize: number + /** Flags signalling stream properties. */ + readonly ctx_flags: { + NOHEADER: boolean + UNSEEKABLE: boolean + } + /** An array of all streams in the file. */ + streams: Array + /** input or output URL. Unlike the old filename field, this field has no length restriction. */ + url: string /** - * 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. + * 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 */ - max_analyze_duration: number - readonly key: Buffer - readonly programs: ReadonlyArray - // video_codec_id / audio_codec_id / subtitle_codec_id + readonly start_time: number /** + * Duration of the stream, in AV_TIME_BASE fractional + * seconds. Only set this value if you know none of the individual stream + * durations and also do not set any of them. This is deduced from the + * Stream values if not set. + */ + duration: number + /** + * Total stream bitrate in bit/s, 0 if not + * available. Never set it directly if the file_size and the + * duration are known as FFmpeg can compute it automatically. + */ + bit_rate: number + + packet_size: number + + max_delay: number + /** Flags modifying the demuxer behaviour. A combination of AVFMT_FLAG_*. */ + flags: FormatContextFlags + /** 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 + readonly key: Buffer + readonly programs: ReadonlyArray + // video_codec_id / audio_codec_id / subtitle_codec_id + + /** * 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 @@ -246,33 +246,17 @@ export interface FormatContext { * AV_NOPTS_VALUE if unknown. Note that the value may become known after * some number of frames have been received. */ - start_time_realtime: number | null - /** The number of frames used for determining the framerate */ - fps_probe_size: number + start_time_realtime: number | null + /** 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 - /** Flags to enable debugging. */ - debug: { TS: boolean } - /** - * 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 + error_recognition: number + /** Flags to enable debugging. */ + debug: { TS: boolean } + /** Allow non-standard and experimental extension */ strict_std_compliance: 'very-strict' | 'strict' | 'normal' | 'unofficial' | 'experimental' /** @@ -283,43 +267,30 @@ export interface FormatContext { event_flags: { METADATA_UPDATED?: boolean } /** Maximum number of packets to read while waiting for the first timestamp. */ max_ts_probe: 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 /** * 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 - } - /** + 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 - /** Flush the I/O context after each packet. */ - flush_packets: number - /** + 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. @@ -344,38 +315,79 @@ export interface FormatContext { * Demuxers can use the flag to detect such changes. */ readonly io_repositioned: boolean - /** Number of bytes to be written as padding in a metadata header. */ - metadata_header_padding: number - // not exposing opaque - /** Output timestamp offset, in microseconds. */ - output_ts_offset: number /** * dump format separator. * can be ", " or "\n " or anything else */ - dump_separator: string - /** ',' 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 - + dump_separator: string + /** ',' 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, /** * Add a stream to the format with the next available stream index. * @param options Object including the codec name for the stream and any other parameters that need * to be initialised in the Stream object * @returns A Stream object */ - newStream(options: string | { name: string, [key: string]: any }): Stream + newStream(options: string | { 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 + * @returns A Stream object + */ + newStream(stream: Stream): Stream + } + +/** +* Format I/O context. + */ +export interface FormatContext extends FormatContextBase { + readonly type: 'format'; + /** The output format description. */ + set oformat(string: format): string; + get oformat(): OutputFormat /** - * 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 - * @returns A Stream object + * 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(). */ - newStream(stream: Stream): Stream + 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. */ + output_ts_offset: number + /** Retun a JSON string containing the object properties. */ toJSON(): string } diff --git a/types/Frame.d.ts b/types/Frame.d.ts index 6c32442..431e4f1 100644 --- a/types/Frame.d.ts +++ b/types/Frame.d.ts @@ -33,23 +33,23 @@ export interface Frame { width: number height: number /** number of audio samples (per channel) described by this frame */ - nb_samples: number + nb_samples: number /** format of the frame, null if unknown or unset */ format: string | null /** Whether this frame is a keyframe */ key_frame: boolean - /** Picture type of the frame. */ + /** Picture type of the frame. */ pict_type: 'I' | 'P' | 'B' | 'S' | 'SI' | 'SP' | 'BI' | null - /** Sample aspect ratio for the video frame, 0/1 if unknown/unspecified. */ - sample_aspect_ratio: Array - /** Presentation timestamp in time_base units (time when frame should be shown to user). */ - pts: number + /** Sample aspect ratio for the video frame, 0/1 if unknown/unspecified. */ + sample_aspect_ratio: [number, number] + /** Presentation timestamp in time_base units (time when frame should be shown to user). */ + pts: number | null /** * DTS copied from the Packet that triggered returning this frame. (if frame threading isn't used) * This is also the Presentation time of this Frame calculated from * only Packet.dts values without pts values. */ - pkt_dts: number + pkt_dts: number | null /** picture number in bitstream order */ coded_picture_number: number /** picture number in display order */ @@ -80,7 +80,7 @@ export interface Frame { /** Sample rate of the audio data. */ sample_rate: number /** Channel layout of the audio data. */ - channel_layout: string + channel_layout: string | '0 channels' /** * Raw data for the picture/channel planes. * @@ -101,13 +101,13 @@ export interface Frame { DISCARD?: boolean } /** MPEG vs JPEG YUV range. */ - color_range: string + color_range: string | "unknown" /** Chromaticity coordinates of the source primaries. */ - color_primaries?: string + color_primaries: string | "unknown" /** Color Transfer Characteristic. */ - color_trc: string + color_trc: string | "unknown" /** YUV colorspace type. */ - colorspace: string + colorspace: string | "unknown" /** * Location of chroma samples. * @@ -126,12 +126,12 @@ export interface Frame { */ chroma_location: 'unspecified' | 'left' | 'center' | 'topleft' | 'top' | 'bottomleft' | 'bottom' /** frame timestamp estimated using various heuristics, in stream time base */ - best_effort_timestamp: number + best_effort_timestamp: number | null /** reordered pos from the last AVPacket that has been input into the decoder */ pkt_pos: number /** duration of the corresponding packet, expressed in Stream->time_base units, 0 if unknown. */ pkt_duration: number - metadata: { [key: string]: string } + metadata: { [key: string]: string } | null /** * decode error flags of the frame, set if the decoder produced a frame, but there * were errors during the decoding. @@ -151,7 +151,7 @@ export interface Frame { * For hwaccel-format frames, this should be a reference to the * HWFramesContext describing the frame. */ - hw_frames_ctx: HWFramesContext + hw_frames_ctx: HWFramesContext | null /** * Video frames only. The number of pixels to discard from the the * top/bottom/left/right border of the frame to obtain the sub-rectangle of @@ -176,6 +176,8 @@ export interface Frame { */ alloc(): Frame + // internal + readonly _frame: {}; /** Retun a JSON string containing the object properties. */ toJSON(): string } From 2168e0b59160d028814e3fd34a1dd2349cdfce54 Mon Sep 17 00:00:00 2001 From: UrielCh Date: Tue, 19 Apr 2022 17:24:37 +0300 Subject: [PATCH 16/68] refactor --- ts/beamstreams.ts | 64 ++-- ts/models/BeamcoderType.ts | 276 ++++++++++++++++++ .../models/Beamstreams.ts | 39 +-- types/Codec.d.ts => ts/models/Codec.ts | 73 +++-- .../models/CodecContext.ts | 0 types/CodecPar.d.ts => ts/models/CodecPar.ts | 2 - types/Decoder.d.ts => ts/models/Decoder.ts | 43 +-- types/Demuxer.d.ts => ts/models/Demuxer.ts | 23 -- types/Encoder.d.ts => ts/models/Encoder.ts | 19 -- types/Filter.d.ts => ts/models/Filter.ts | 22 +- .../models/FormatContext.ts | 14 +- types/Frame.d.ts => ts/models/Frame.ts | 16 - ts/models/Governor.ts | 9 + .../HWContext.d.ts => ts/models/HWContext.ts | 0 types/Muxer.d.ts => ts/models/Muxer.ts | 80 +++-- types/Packet.d.ts => ts/models/Packet.ts | 46 +-- .../PrivClass.d.ts => ts/models/PrivClass.ts | 0 types/Stream.d.ts => ts/models/Stream.ts | 0 ts/models/params.ts | 19 ++ ts/types.ts | 158 +--------- types.d.ts | 16 +- 21 files changed, 461 insertions(+), 458 deletions(-) create mode 100644 ts/models/BeamcoderType.ts rename types/Beamstreams.d.ts => ts/models/Beamstreams.ts (68%) rename types/Codec.d.ts => ts/models/Codec.ts (67%) rename types/CodecContext.d.ts => ts/models/CodecContext.ts (100%) rename types/CodecPar.d.ts => ts/models/CodecPar.ts (96%) rename types/Decoder.d.ts => ts/models/Decoder.ts (70%) rename types/Demuxer.d.ts => ts/models/Demuxer.ts (72%) rename types/Encoder.d.ts => ts/models/Encoder.ts (79%) rename types/Filter.d.ts => ts/models/Filter.ts (94%) rename types/FormatContext.d.ts => ts/models/FormatContext.ts (97%) rename types/Frame.d.ts => ts/models/Frame.ts (93%) create mode 100644 ts/models/Governor.ts rename types/HWContext.d.ts => ts/models/HWContext.ts (100%) rename types/Muxer.d.ts => ts/models/Muxer.ts (58%) rename types/Packet.d.ts => ts/models/Packet.ts (73%) rename types/PrivClass.d.ts => ts/models/PrivClass.ts (100%) rename types/Stream.d.ts => ts/models/Stream.ts (100%) create mode 100644 ts/models/params.ts diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index 03de2a0..8168cf1 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -19,14 +19,25 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ import bindings from 'bindings'; -import { Frame, Stream, Muxer, CodecPar, WritableDemuxerStream, BeamstreamParams, Demuxer, Packet, Filterer, BeamstreamSource } from '..'; // Codec, CodecContext, +// import { Frame, Stream, Muxer, CodecPar, WritableDemuxerStream, BeamstreamParams, Demuxer, Packet, Filterer, BeamstreamSource } from ''; // Codec, CodecContext, import { Writable, Readable, Transform } from 'stream'; -import type { BeamcoderType, governorType, Timing, ffStats } from './types'; +import type { BeamstreamStream, ffStats } from './types'; import { teeBalancer } from './teeBalancer'; import { parallelBalancer } from './parallelBalancer'; import { serialBalancer } from './serialBalancer'; +import { BeamcoderType } from './models/BeamcoderType'; +import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, FilterContext, Filterer, FilterLink, Frame, Packet, Timing, WritableDemuxerStream } from '../types'; +import { CodecPar } from './models/CodecPar'; +import { Demuxer } from './models/Demuxer'; +import { Muxer } from './models/Muxer'; +import { Governor } from './models/Governor'; +import { Stream } from './models/Stream'; +import { DecodedFrames } from './models/Decoder'; + +// import { Governor } from './models/BeamcoderType'; + const beamcoder = bindings('beamcoder') as BeamcoderType; const doTimings = false; @@ -123,13 +134,13 @@ class frameDicer { function transformStream( params: { name: 'encode' | 'dice' | 'decode' | 'filter', highWaterMark: number }, - processFn: (val: { stream_index: number, pts: number, dts: number, duration: number, timings: any }) => Promise<{timings: {[key: string]: Timing}}>, - flushFn: () => (Promise<{ timings: { [key: string]: Timing; }}>) | null | void, + processFn: (val: Packet) => Promise, + flushFn: () => (Promise) | null | void, reject: (err?: Error) => void) { return new Transform({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - transform(val, encoding, cb) { + transform(val: Packet, encoding, cb) { (async () => { const start = process.hrtime(); const reqTime = start[0] * 1e3 + start[1] / 1e6; @@ -247,7 +258,7 @@ function readStream(params: {highWaterMark?: number}, demuxer: Demuxer, ms: { en }); } -function createBeamWritableStream(params: { highwaterMark?: number }, governor: governorType): Writable { +function createBeamWritableStream(params: { highwaterMark?: number }, governor: Governor): Writable { const beamStream = new Writable({ highWaterMark: params.highwaterMark || 16384, write: (chunk, encoding, cb) => { @@ -259,14 +270,14 @@ function createBeamWritableStream(params: { highwaterMark?: number }, governor: }); return beamStream; } -type demuxerStreamType = Writable & { demuxer: (options: { governor: governorType }) => Promise }; +type demuxerStreamType = Writable & { demuxer: (options: { governor: Governor }) => Promise }; export function demuxerStream(params: { highwaterMark?: number }): WritableDemuxerStream { const governor = new beamcoder.governor({}); const stream: demuxerStreamType = createBeamWritableStream(params, governor) as demuxerStreamType; stream.on('finish', () => governor.finish()); stream.on('error', console.error); - stream.demuxer = (options: { governor: governorType }) => { + stream.demuxer = (options: { governor: Governor }) => { 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)); @@ -275,7 +286,7 @@ export function demuxerStream(params: { highwaterMark?: number }): WritableDemux } -function createBeamReadableStream(params: { highwaterMark?: number }, governor: governorType) { +function createBeamReadableStream(params: { highwaterMark?: number }, governor: Governor) { const beamStream = new Readable({ highWaterMark: params.highwaterMark || 16384, read: size => { @@ -291,7 +302,7 @@ function createBeamReadableStream(params: { highwaterMark?: number }, governor: return beamStream; } -type muxerStreamType = Readable & { muxer: (options: { governor: governorType }) => any }; +type muxerStreamType = Readable & { muxer: (options: { governor: Governor }) => any }; export function muxerStream(params: { highwaterMark: number }): muxerStreamType { const governor = new beamcoder.governor({ highWaterMark: 1 }); @@ -371,8 +382,10 @@ function runStreams( const filterBalancer = parallelBalancer({ name: 'filterBalance', highWaterMark: 1 }, streamType, sources.length); sources.forEach((src, srcIndex: number) => { - const decStream = transformStream({ name: 'decode', highWaterMark: 1 }, - pkts => src.decoder.decode(pkts), () => src.decoder.flush(), reject); + 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 => filterBalancer.pushPkts(pkts, (src.format as Demuxer).streams[src.streamIndex], srcIndex), () => filterBalancer.pushPkts(null, (src.format as Demuxer).streams[src.streamIndex], srcIndex, true), reject); @@ -407,13 +420,13 @@ function runStreams( } export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Promise }> { - params.video.forEach(p => { - p.sources.forEach(src => - src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); + params.video.forEach((p: BeamstreamChannel) => { + p.sources.forEach((src: BeamstreamSource) => + src.decoder = beamcoder.decoder({ demuxer: src.format as Demuxer, stream_index: src.streamIndex })); }); params.audio.forEach(p => { p.sources.forEach(src => - src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); + src.decoder = beamcoder.decoder({ demuxer: src.format as Demuxer, stream_index: src.streamIndex })); // {demuxer: Demuxer | Promise, stream_index: number} }); @@ -476,8 +489,8 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr } else mux = beamcoder.muxer({ format_name: params.out.formatName }); - params.video.forEach(p => { - p.streams.forEach((str, i) => { + params.video.forEach((p: BeamstreamChannel) => { + p.streams.forEach((str: BeamstreamStream, i: number) => { const encParams = (p.filter as Filterer).graph.filters.find(f => f.name === `out${i}:v`).inputs[0]; str.encoder = beamcoder.encoder({ name: str.name, @@ -496,9 +509,9 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr }); }); - params.audio.forEach(p => { - p.streams.forEach((str, i) => { - const encParams: { format : string, sample_rate: number, channel_layout: string} = p.filter.graph.filters.find(f => f.name === `out${i}:a`).inputs[0]; + params.audio.forEach((p: BeamstreamChannel) => { + p.streams.forEach((str: BeamstreamStream, i: number) => { + const encParams: FilterLink = (p.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, @@ -506,13 +519,12 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr 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 => { + params.video.forEach((p: BeamstreamChannel) => { + p.streams.forEach((str: BeamstreamStream) => { str.stream = mux.newStream({ name: str.name, time_base: str.time_base, @@ -543,8 +555,8 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr const muxBalancer = new serialBalancer(mux.streams.length); const muxStreamPromises: Promise[] = []; - 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))); + params.video.forEach(p => muxStreamPromises.push(runStreams('video', p.sources, p.filter as Filterer, p.streams, mux, muxBalancer))); + params.audio.forEach(p => muxStreamPromises.push(runStreams('audio', p.sources, p.filter as Filterer, p.streams, mux, muxBalancer))); await Promise.all(muxStreamPromises); await mux.writeTrailer(); diff --git a/ts/models/BeamcoderType.ts b/ts/models/BeamcoderType.ts new file mode 100644 index 0000000..81969f6 --- /dev/null +++ b/ts/models/BeamcoderType.ts @@ -0,0 +1,276 @@ +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 { Governor } from './Governor'; + +export interface BeamcoderType extends ReadableMuxerStream { + /** Create object for AVIOContext based buffered I/O */ + governor: 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 +} + diff --git a/types/Beamstreams.d.ts b/ts/models/Beamstreams.ts similarity index 68% rename from types/Beamstreams.d.ts rename to ts/models/Beamstreams.ts index f21510e..44ab5df 100644 --- a/types/Beamstreams.d.ts +++ b/ts/models/Beamstreams.ts @@ -1,6 +1,10 @@ import { Demuxer, DemuxerCreateOptions } from "./Demuxer" import { Muxer, MuxerCreateOptions } from "./Muxer" import { InputFormat } from "./FormatContext" +import { Stream } from "./Stream" +import { Filterer } from "./Filter" +import { Decoder } from "./Decoder" +import { Encoder } from "./Encoder" /** * A [Node.js Writable stream](https://nodejs.org/docs/latest-v12.x/api/stream.html#stream_writable_streams) @@ -16,12 +20,6 @@ export interface WritableDemuxerStream extends NodeJS.WritableStream { */ demuxer(options: DemuxerCreateOptions | string): 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) @@ -35,12 +33,6 @@ export interface ReadableMuxerStream extends NodeJS.ReadableStream { */ 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 }): { @@ -48,13 +40,6 @@ export function muxerStream(options: { highwaterMark?: number }): ReadableMuxerS // write(data: Buffer): Promise // finish(): undefined // } -export class Governor { - constructor(options: { highWaterMark?: number }); - read(len: number): Promise - write(data: Buffer): Promise - finish(): undefined - private _adaptor: unknown; -} /** Source definition for a beamstream channel, from either a file or NodeJS ReadableStream */ @@ -68,10 +53,8 @@ export interface BeamstreamSource { format?: Demuxer | Promise; stream: any; // FIXME decoder?: Decoder; // FIXME - - - } + /** Codec definition for the destination channel */ export interface BeamstreamStream { name: string @@ -109,15 +92,3 @@ export interface BeamstreamParams { options?: { [key:string]: any } } } -/** - * 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 }> - diff --git a/types/Codec.d.ts b/ts/models/Codec.ts similarity index 67% rename from types/Codec.d.ts rename to ts/models/Codec.ts index 35079dd..1c9fdd9 100644 --- a/types/Codec.d.ts +++ b/ts/models/Codec.ts @@ -10,17 +10,17 @@ export interface Codec { * This is the primary way to find a codec from the user perspective. */ readonly name: string - /** Descriptive name for the codec, meant to be more human readable than name. */ + /** Descriptive name for the codec, meant to be more human readable than name. */ readonly long_name: string - /** String describing the media type */ + /** String describing the media type */ readonly codec_type: 'unknown' | 'video' | 'audio' | 'data' | 'subtitle' | 'attachment' | 'nb' - /** Number that identifies the syntax and semantics of the bitstream. */ + /** Number that identifies the syntax and semantics of the bitstream. */ readonly id: number - /** true if codec is an decoder */ + /** true if codec is an decoder */ readonly decoder: boolean - /** true if codec is an encoder */ + /** true if codec is an encoder */ readonly encoder: boolean - /** Codec capabilities - see AV_CODEC_CAP_* */ + /** Codec capabilities - see AV_CODEC_CAP_* */ readonly capabilities: { /** Decoder can use draw_horiz_band callback. */ DRAW_HORIZ_BAND: boolean @@ -29,7 +29,7 @@ export interface Codec { TRUNCATED: boolean /** * Decoder requires flushing with NULL input at the end in order to - * give the complete and correct output. + * give the complete and correct output. * NOTE: If this flag is not set, the codec is guaranteed to never be fed with * with NULL data. The user can still send NULL data to the decode function, @@ -40,7 +40,7 @@ export interface Codec { * returns frames. */ DELAY: boolean - /** Codec can be fed a final frame with a smaller size. This can be used to prevent truncation of the last audio samples. */ + /** Codec can be fed a final frame with a smaller size. This can be used to prevent truncation of the last audio samples. */ SMALL_LAST_FRAME: boolean /** * Codec can output multiple frames per APacket @@ -52,11 +52,11 @@ export interface Codec { * may return multiple frames in a packet. This has many disadvantages like * prohibiting stream copy in many cases thus it should only be considered * as a last resort. - */ + */ SUBFRAMES: boolean - /** Codec is experimental and is thus avoided in favor of non experimental codecs */ + /** Codec is experimental and is thus avoided in favor of non experimental codecs */ EXPERIMENTAL: boolean - /** Codec should fill in channel configuration and samplerate instead of container */ + /** Codec should fill in channel configuration and samplerate instead of container */ CHANNEL_CONF: boolean /** Codec supports frame-level multithreading. */ FRAME_THREADS: boolean @@ -68,17 +68,17 @@ export interface Codec { AUTO_THREADS: boolean /** Audio encoder supports receiving a different number of samples in each call. */ VARIABLE_FRAME_SIZE: boolean - /** - * Decoder is not a preferred choice for probing. - * This indicates that the decoder is not a good choice for probing. - * It could for example be an expensive to spin up hardware decoder, - * or it could simply not provide a lot of useful information about - * the stream. - * A decoder marked with this flag should only be used as last resort - * choice for probing. - */ + /** + * Decoder is not a preferred choice for probing. + * This indicates that the decoder is not a good choice for probing. + * It could for example be an expensive to spin up hardware decoder, + * or it could simply not provide a lot of useful information about + * the stream. + * A decoder marked with this flag should only be used as last resort + * choice for probing. + */ AVOID_PROBING: boolean - /** Codec is intra only. */ + /** Codec is intra only. */ INTRA_ONLY: boolean /** Codec is lossless. */ LOSSLESS: boolean @@ -90,34 +90,31 @@ export interface Codec { */ HYBRID: boolean } - /** Array of supported framerates (as a rational [num, den]), or null if unknown. */ + /** Array of supported framerates (as a rational [num, den]), or null if unknown. */ readonly supported_framerates: ReadonlyArray> | null - /** Array of supported pixel formats, or null if unknown. */ + /** Array of supported pixel formats, or null if unknown. */ readonly pix_fmts: ReadonlyArray | null - /** Array of supported audio samplerates, or null if unknown */ + /** Array of supported audio samplerates, or null if unknown */ readonly supported_samplerates: ReadonlyArray | null - /** Array of supported sample formats, or NULL if unknown, */ + /** Array of supported sample formats, or NULL if unknown, */ readonly sample_fmts: ReadonlyArray - /** */ + /** */ readonly channel_layouts: ReadonlyArray - /** */ + /** */ readonly max_lowres: number /** Class for private context */ readonly priv_class: PrivClass - /** */ + /** */ readonly profiles: ReadonlyArray | null - /** */ + /** */ readonly wrapper_name?: string - /** */ + /** */ readonly descriptor: { - INTRA_ONLY: boolean - LOSSY: boolean - LOSSLESS: boolean - REORDER: boolean - BITMAP_SUB: boolean + INTRA_ONLY: boolean + LOSSY: boolean + LOSSLESS: boolean + REORDER: boolean + BITMAP_SUB: boolean TEXT_SUB: boolean } } - -/** List the available codecs */ -export function codecs(): { [key: string]: { encoder?: Codec, decoder?: Codec }} diff --git a/types/CodecContext.d.ts b/ts/models/CodecContext.ts similarity index 100% rename from types/CodecContext.d.ts rename to ts/models/CodecContext.ts diff --git a/types/CodecPar.d.ts b/ts/models/CodecPar.ts similarity index 96% rename from types/CodecPar.d.ts rename to ts/models/CodecPar.ts index 4a58f4d..49154e8 100644 --- a/types/CodecPar.d.ts +++ b/ts/models/CodecPar.ts @@ -140,5 +140,3 @@ export interface CodecPar { toJSON(): string } -export function codecParameters(options?: string | Partial>): CodecPar; -// { [key: string]: any } \ No newline at end of file diff --git a/types/Decoder.d.ts b/ts/models/Decoder.ts similarity index 70% rename from types/Decoder.d.ts rename to ts/models/Decoder.ts index f07e937..f762ca3 100644 --- a/types/Decoder.d.ts +++ b/ts/models/Decoder.ts @@ -1,5 +1,5 @@ import { CodecPar } from "./CodecPar" -import { Packet } from "./Packet" +import { Packet, Timing } from "./Packet" import { Frame } from "./Frame" import { Codec } from "./Codec" import { CodecContext, CodecContext_base } from "./CodecContext" @@ -16,6 +16,12 @@ export interface DecodedFrames { readonly frames: Array /** Total time in microseconds that the decode operation took to complete */ readonly total_time: number + + + timings?: { + // "encode" | "dice" | "decode" | "filter" + [key: string]: Timing; }; + } export interface Decoder extends CodecContext_base { @@ -87,38 +93,3 @@ export interface Decoder extends CodecContext_base { */ useParams(params: CodecPar): Decoder } - -/** - * Provides a list and details of all the available decoders - * @returns an object with name and details of each of the available decoders - */ -export function 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 - */ -export function 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 - */ -export function 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 - */ -export function 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 - */ -export function decoder(options: { params: CodecPar, [key: string]: any }): Decoder diff --git a/types/Demuxer.d.ts b/ts/models/Demuxer.ts similarity index 72% rename from types/Demuxer.d.ts rename to ts/models/Demuxer.ts index ad923bc..317bb61 100644 --- a/types/Demuxer.d.ts +++ b/ts/models/Demuxer.ts @@ -77,20 +77,6 @@ export interface Demuxer extends FormatContextBase { forceClose(): undefined } -/** - * Provides a list and details of all the available demuxer input formats - * @returns an object with details of all the available demuxer input formats - */ -export function 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. - */ -export function demuxer(url: string): Promise /** Object to provide additional metadata on Demuxer creation */ export interface DemuxerCreateOptions { @@ -101,12 +87,3 @@ export interface DemuxerCreateOptions { /** Object allowing additional information to be provided */ options?: { [key: string]: any } } -/** - * 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. - */ -export function demuxer(options: DemuxerCreateOptions): Promise diff --git a/types/Encoder.d.ts b/ts/models/Encoder.ts similarity index 79% rename from types/Encoder.d.ts rename to ts/models/Encoder.ts index 1abcec3..8710bea 100644 --- a/types/Encoder.d.ts +++ b/ts/models/Encoder.ts @@ -67,22 +67,3 @@ export interface Encoder extends CodecContextBaseMin { useParams(params: CodecPar): Encoder } -/** - * Provides a list and details of all the available encoders - * @returns an object with name and details of each of the available encoders - */ -export function 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 - */ -export function 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 - */ -export function encoder(options: { codec_id: number, [key: string]: any }): Encoder diff --git a/types/Filter.d.ts b/ts/models/Filter.ts similarity index 94% rename from types/Filter.d.ts rename to ts/models/Filter.ts index fc3b734..4d67bce 100644 --- a/types/Filter.d.ts +++ b/ts/models/Filter.ts @@ -40,7 +40,7 @@ export interface FilterFlags { * Handy mask to test whether the filter supports or no the timeline feature * (internally or generically). */ - SUPPORT_TIMELINE: boolean\ + SUPPORT_TIMELINE: boolean; } export interface Filter { @@ -236,20 +236,6 @@ export interface Filterer { filter(framesArr: Array<{ name?: string, frames: Array }>): Promise & { total_time: number }> } -/** - * Provides a list and details of all the available filters - * @returns an object with name and details of each of the available filters - */ -export function filters(): { [key: string]: Filter } - -/** List the available bitstream filters */ -export function bsfs(): { - [key: string]: { - name: string - codec_ids: Array - priv_class: PrivClass | null - } -} /** The required parameters for setting up filter inputs */ export interface InputParam { @@ -316,9 +302,3 @@ export interface FiltererAudioOptions extends FiltererOptions { outputParams: Array } -/** - * 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 - */ -export function filterer(options: FiltererVideoOptions | FiltererAudioOptions): Promise diff --git a/types/FormatContext.d.ts b/ts/models/FormatContext.ts similarity index 97% rename from types/FormatContext.d.ts rename to ts/models/FormatContext.ts index 2db9a36..d6fc9cf 100644 --- a/types/FormatContext.d.ts +++ b/ts/models/FormatContext.ts @@ -119,11 +119,6 @@ export interface OutputFormat { priv_data_size: number } -/** - * 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. - */ -export function guessFormat(name: string): OutputFormat | null; @@ -164,10 +159,11 @@ export interface FormatContextFlags { export interface FormatContextBase { /** Object name. */ - readonly type: 'demuxer'| 'format' + readonly type: 'demuxer'| 'format' | 'muxer' /** The input format description. */ - set iformat(string: format): string; + set iformat(format: string); + //@ts-ignore get iformat(): InputFormat /** Format private data. */ priv_data: { @@ -350,7 +346,8 @@ export interface FormatContextBase { export interface FormatContext extends FormatContextBase { readonly type: 'format'; /** The output format description. */ - set oformat(string: format): string; + set oformat(format: string); + // @ts-ignore get oformat(): OutputFormat /** * Maximum buffering duration for interleaving. @@ -392,4 +389,3 @@ export interface FormatContext extends FormatContextBase { toJSON(): string } -export function format(options?: string | { [key: string]: any }): FormatContext diff --git a/types/Frame.d.ts b/ts/models/Frame.ts similarity index 93% rename from types/Frame.d.ts rename to ts/models/Frame.ts index 431e4f1..43e27f1 100644 --- a/types/Frame.d.ts +++ b/ts/models/Frame.ts @@ -182,12 +182,6 @@ export interface Frame { toJSON(): string } -/** - * Create a frame for encoding or filtering - * Set parameters as required from the Frame object - */ -export function frame(options?: string | { [key: string]: any, data?: Array }): Frame - /** Pixel format description */ export interface PixelFormat { name: string @@ -273,8 +267,6 @@ export interface PixelFormat { /** Alternative comma-separated names. */ alias: string } -/** Format details for all supported pixel format names */ -export function pix_fmts(): { [key: string]: PixelFormat } /** Audio sample formats */ export interface SampleFormat { @@ -289,12 +281,4 @@ export interface SampleFormat { /** Whether the sample format is planar. */ is_planar: boolean } -/** Format details for all supported sample format names */ -export function 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 - */ -export const AV_INPUT_BUFFER_PADDING_SIZE: number diff --git a/ts/models/Governor.ts b/ts/models/Governor.ts new file mode 100644 index 0000000..6070750 --- /dev/null +++ b/ts/models/Governor.ts @@ -0,0 +1,9 @@ +declare class GovernorClass { + constructor(options: { highWaterMark?: number }); + read(len: number): Promise + write(data: Buffer): Promise + finish(): undefined + private _adaptor: unknown; +} + +export type Governor = GovernorClass; diff --git a/types/HWContext.d.ts b/ts/models/HWContext.ts similarity index 100% rename from types/HWContext.d.ts rename to ts/models/HWContext.ts diff --git a/types/Muxer.d.ts b/ts/models/Muxer.ts similarity index 58% rename from types/Muxer.d.ts rename to ts/models/Muxer.ts index 87b8ce5..7247325 100644 --- a/types/Muxer.d.ts +++ b/ts/models/Muxer.ts @@ -7,8 +7,8 @@ export interface Muxer extends Omit { /** Object name. */ type: 'muxer' @@ -36,7 +36,7 @@ export interface Muxer extends Omit + }): Promise /** * In some cases, it is necessary to initialize the structures of the muxer before writing the header. @@ -44,9 +44,9 @@ export interface Muxer extends Omit /** * Write the header to the file, optionally passing in private data to set private options of the muxer. @@ -55,38 +55,38 @@ export interface Muxer extends Omit - /** - * Write media data to the file by sending a packet containing data for a media stream. - * @param packet Packet of compressed data, must contain the stream index and timestamps measured in the - * `time_base` of the stream. - * @returns Promise that resolves to _undefined_ on success - */ - writeFrame(packet: Packet) : Promise - /** - * Write media data to the file by sending a packet containing data for a media stream. - * @param options Object containing a packet property of a compressed data Packet, must contain the - * stream index and timestamps measured in the `time_base` of the stream. - * @returns Promise that resolves to _undefined_ on success - */ - writeFrame(options: { packet: Packet }) : Promise - /** - * Write media data to the file by sending a packet containing data for a media stream. - * @param options Object containing a stream index property and a frame property of an - * uncompressed Frame, which must contain the timestamps measured in the `time_base` of the stream. - * @returns Promise that resolves to _undefined_ on success - */ - writeFrame(options: { frame: Frame, stream_index: number }) : Promise + /** + * Write media data to the file by sending a packet containing data for a media stream. + * @param packet Packet of compressed data, must contain the stream index and timestamps measured in the + * `time_base` of the stream. + * @returns Promise that resolves to _undefined_ on success + */ + writeFrame(packet: Packet): Promise + /** + * Write media data to the file by sending a packet containing data for a media stream. + * @param options Object containing a packet property of a compressed data Packet, must contain the + * stream index and timestamps measured in the `time_base` of the stream. + * @returns Promise that resolves to _undefined_ on success + */ + writeFrame(options: { packet: Packet }): Promise + /** + * Write media data to the file by sending a packet containing data for a media stream. + * @param options Object containing a stream index property and a frame property of an + * uncompressed Frame, which must contain the timestamps measured in the `time_base` of the stream. + * @returns Promise that resolves to _undefined_ on success + */ + writeFrame(options: { frame: Frame, stream_index: number }): Promise - /** - * Write the trailer at the end of the file or stream. It is written after the muxer has drained its - * buffers of all remaining packets and frames. Writing the trailer also closes the file or stream. - * @returns Promise that resolves to _undefined_ on success - */ + /** + * Write the trailer at the end of the file or stream. It is written after the muxer has drained its + * buffers of all remaining packets and frames. Writing the trailer also closes the file or stream. + * @returns Promise that resolves to _undefined_ on success + */ writeTrailer(): Promise /** @@ -95,16 +95,10 @@ export interface Muxer extends Omit, - pts?: number, - dts?: number, - stream_index?: number, - data: Buffer, - size?: number, - side_data?: any, - [key: string]: any, -}): Packet diff --git a/types/PrivClass.d.ts b/ts/models/PrivClass.ts similarity index 100% rename from types/PrivClass.d.ts rename to ts/models/PrivClass.ts diff --git a/types/Stream.d.ts b/ts/models/Stream.ts similarity index 100% rename from types/Stream.d.ts rename to ts/models/Stream.ts diff --git a/ts/models/params.ts b/ts/models/params.ts new file mode 100644 index 0000000..9a1db0c --- /dev/null +++ b/ts/models/params.ts @@ -0,0 +1,19 @@ +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.ts b/ts/types.ts index 0200209..77d0b2f 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -1,145 +1,14 @@ -import type { Frame, Stream, Muxer, CodecPar, Codec, Governor, WritableDemuxerStream, ReadableMuxerStream, FiltererVideoOptions, FiltererAudioOptions, Filterer, Decoder, Demuxer, Encoder, InputFormat, BeamstreamParams } from '..'; // Codec, CodecContext, +// import type { Frame, Stream, Muxer, CodecPar, Codec, Governor, WritableDemuxerStream, ReadableMuxerStream, FiltererVideoOptions, FiltererAudioOptions, Filterer, Decoder, Demuxer, Encoder, InputFormat, BeamstreamParams } from '..'; // Codec, CodecContext, -export type governorType = { - read(len: number): Promise; - write(data: Buffer): Promise; - finish(): undefined; -}; +// import { Governor, ReadableMuxerStream } from "../types"; -export type pixelFormat = 'yuv420p' | 'yuyv422' | 'rgb24' | 'bgr24' | 'yuv422p' | 'yuv444p' | 'yuv410p' | 'yuv411p' | 'gray' | 'monow' | 'monob' | 'pal8' | 'yuvj420p' | 'yuvj422p' | 'yuvj444p' | 'uyvy422' | 'uyyvyy411' | 'bgr8' | 'bgr4' | 'bgr4_byte' | 'rgb8' | 'rgb4' | 'rgb4_byte' | 'nv12' | 'nv21' | 'argb' | 'rgba' | 'abgr' | 'bgra' | 'gray16be' | 'gray16le' | 'yuv440p' | 'yuvj440p' | 'yuva420p' | 'rgb48be' | 'rgb48le' | 'rgb565be' | 'rgb565le' | 'rgb555be' | 'rgb555le' | 'bgr565be' | 'bgr565le' | 'bgr555be' | 'bgr555le' | 'vaapi_moco' | 'vaapi_idct' | 'vaapi_vld' | 'yuv420p16le' | 'yuv420p16be' | 'yuv422p16le' | 'yuv422p16be' | 'yuv444p16le' | 'yuv444p16be' | 'dxva2_vld' | 'rgb444le' | 'rgb444be' | 'bgr444le' | 'bgr444be' | 'ya8' | 'bgr48be' | 'bgr48le' | 'yuv420p9be' | 'yuv420p9le' | 'yuv420p10be' | 'yuv420p10le' | 'yuv422p10be' | 'yuv422p10le' | 'yuv444p9be' | 'yuv444p9le' | 'yuv444p10be' | 'yuv444p10le' | 'yuv422p9be' | 'yuv422p9le' | 'gbrp' | 'gbrp9be' | 'gbrp9le' | 'gbrp10be' | 'gbrp10le' | 'gbrp16be' | 'gbrp16le' | 'yuva422p' | 'yuva444p' | 'yuva420p9be' | 'yuva420p9le' | 'yuva422p9be' | 'yuva422p9le' | 'yuva444p9be' | 'yuva444p9le' | 'yuva420p10be' | 'yuva420p10le' | 'yuva422p10be' | 'yuva422p10le' | 'yuva444p10be' | 'yuva444p10le' | 'yuva420p16be' | 'yuva420p16le' | 'yuva422p16be' | 'yuva422p16le' | 'yuva444p16be' | 'yuva444p16le' | 'vdpau' | 'xyz12le' | 'xyz12be' | 'nv16' | 'nv20le' | 'nv20be' | 'rgba64be' | 'rgba64le' | 'bgra64be' | 'bgra64le' | 'yvyu422' | 'ya16be' | 'ya16le' | 'gbrap' | 'gbrap16be' | 'gbrap16le' | 'qsv' | 'mmal' | 'd3d11va_vld' | 'cuda' | '0rgb' | 'rgb0' | '0bgr' | 'bgr0' | 'yuv420p12be' | 'yuv420p12le' | 'yuv420p14be' | 'yuv420p14le' | 'yuv422p12be' | 'yuv422p12le' | 'yuv422p14be' | 'yuv422p14le' | 'yuv444p12be' | 'yuv444p12le' | 'yuv444p14be' | 'yuv444p14le' | 'gbrp12be' | 'gbrp12le' | 'gbrp14be' | 'gbrp14le' | 'yuvj411p' | 'bayer_bggr8' | 'bayer_rggb8' | 'bayer_gbrg8' | 'bayer_grbg8' | 'bayer_bggr16le' | 'bayer_bggr16be' | 'bayer_rggb16le' | 'bayer_rggb16be' | 'bayer_gbrg16le' | 'bayer_gbrg16be' | 'bayer_grbg16le' | 'bayer_grbg16be' | 'xvmc' | 'yuv440p10le' | 'yuv440p10be' | 'yuv440p12le' | 'yuv440p12be' | 'ayuv64le' | 'ayuv64be' | 'videotoolbox_vld' | 'p010le' | 'p010be' | 'gbrap12be' | 'gbrap12le' | 'gbrap10be' | 'gbrap10le' | 'mediacodec' | 'gray12be' | 'gray12le' | 'gray10be' | 'gray10le' | 'p016le' | 'p016be' | 'd3d11' | 'gray9be' | 'gray9le' | 'gbrpf32be' | 'gbrpf32le' | 'gbrapf32be' | 'gbrapf32le' | 'drm_prime' | 'opencl' | 'gray14be' | 'gray14le' | 'grayf32be' | 'grayf32le' | 'yuva422p12be' | 'yuva422p12le' | 'yuva444p12be' | 'yuva444p12le' | 'nv24' | 'nv42'; - - -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 - -} - -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?: governorType, url?: string, iformat?: InputFormat, options?: { governor: governorType } } | 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 - - /** - * 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 } - /** - * 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 -} +// export type governorType = { +// read(len: number): Promise; +// write(data: Buffer): Promise; +// finish(): undefined; +// }; +export type pixelFormat = 'yuv420p' | 'yuyv422' | 'rgb24' | 'bgr24' | 'yuv422p' | 'yuv444p' | 'yuv410p' | 'yuv411p' | 'gray' | 'monow' | 'monob' | 'pal8' | 'yuvj420p' | 'yuvj422p' | 'yuvj444p' | 'uyvy422' | 'uyyvyy411' | 'bgr8' | 'bgr4' | 'bgr4_byte' | 'rgb8' | 'rgb4' | 'rgb4_byte' | 'nv12' | 'nv21' | 'argb' | 'rgba' | 'abgr' | 'bgra' | 'gray16be' | 'gray16le' | 'yuv440p' | 'yuvj440p' | 'yuva420p' | 'rgb48be' | 'rgb48le' | 'rgb565be' | 'rgb565le' | 'rgb555be' | 'rgb555le' | 'bgr565be' | 'bgr565le' | 'bgr555be' | 'bgr555le' | 'vaapi_moco' | 'vaapi_idct' | 'vaapi_vld' | 'yuv420p16le' | 'yuv420p16be' | 'yuv422p16le' | 'yuv422p16be' | 'yuv444p16le' | 'yuv444p16be' | 'dxva2_vld' | 'rgb444le' | 'rgb444be' | 'bgr444le' | 'bgr444be' | 'ya8' | 'bgr48be' | 'bgr48le' | 'yuv420p9be' | 'yuv420p9le' | 'yuv420p10be' | 'yuv420p10le' | 'yuv422p10be' | 'yuv422p10le' | 'yuv444p9be' | 'yuv444p9le' | 'yuv444p10be' | 'yuv444p10le' | 'yuv422p9be' | 'yuv422p9le' | 'gbrp' | 'gbrp9be' | 'gbrp9le' | 'gbrp10be' | 'gbrp10le' | 'gbrp16be' | 'gbrp16le' | 'yuva422p' | 'yuva444p' | 'yuva420p9be' | 'yuva420p9le' | 'yuva422p9be' | 'yuva422p9le' | 'yuva444p9be' | 'yuva444p9le' | 'yuva420p10be' | 'yuva420p10le' | 'yuva422p10be' | 'yuva422p10le' | 'yuva444p10be' | 'yuva444p10le' | 'yuva420p16be' | 'yuva420p16le' | 'yuva422p16be' | 'yuva422p16le' | 'yuva444p16be' | 'yuva444p16le' | 'vdpau' | 'xyz12le' | 'xyz12be' | 'nv16' | 'nv20le' | 'nv20be' | 'rgba64be' | 'rgba64le' | 'bgra64be' | 'bgra64le' | 'yvyu422' | 'ya16be' | 'ya16le' | 'gbrap' | 'gbrap16be' | 'gbrap16le' | 'qsv' | 'mmal' | 'd3d11va_vld' | 'cuda' | '0rgb' | 'rgb0' | '0bgr' | 'bgr0' | 'yuv420p12be' | 'yuv420p12le' | 'yuv420p14be' | 'yuv420p14le' | 'yuv422p12be' | 'yuv422p12le' | 'yuv422p14be' | 'yuv422p14le' | 'yuv444p12be' | 'yuv444p12le' | 'yuv444p14be' | 'yuv444p14le' | 'gbrp12be' | 'gbrp12le' | 'gbrp14be' | 'gbrp14le' | 'yuvj411p' | 'bayer_bggr8' | 'bayer_rggb8' | 'bayer_gbrg8' | 'bayer_grbg8' | 'bayer_bggr16le' | 'bayer_bggr16be' | 'bayer_rggb16le' | 'bayer_rggb16be' | 'bayer_gbrg16le' | 'bayer_gbrg16be' | 'bayer_grbg16le' | 'bayer_grbg16be' | 'xvmc' | 'yuv440p10le' | 'yuv440p10be' | 'yuv440p12le' | 'yuv440p12be' | 'ayuv64le' | 'ayuv64be' | 'videotoolbox_vld' | 'p010le' | 'p010be' | 'gbrap12be' | 'gbrap12le' | 'gbrap10be' | 'gbrap10le' | 'mediacodec' | 'gray12be' | 'gray12le' | 'gray10be' | 'gray10le' | 'p016le' | 'p016be' | 'd3d11' | 'gray9be' | 'gray9le' | 'gbrpf32be' | 'gbrpf32le' | 'gbrapf32be' | 'gbrapf32le' | 'drm_prime' | 'opencl' | 'gray14be' | 'gray14le' | 'grayf32be' | 'grayf32le' | 'yuva422p12be' | 'yuva422p12le' | 'yuva444p12be' | 'yuva444p12le' | 'nv24' | 'nv42'; export interface BeamstreamStream { name: string; @@ -153,17 +22,6 @@ export interface BeamstreamStream { channel_layout: any; }; } -export interface BeamstreamSource { - decoder: any; - format: any; - streamIndex: number; - stream: any; -} - -export interface Timing { - reqTime: number; - elapsed: number; -} export interface ffStats { mean: number; diff --git a/types.d.ts b/types.d.ts index a52bcf1..a86fcea 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,15 +1,15 @@ -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 "./ts/models/CodecPar.ts" +export * from "./ts/models/Packet" +export * from "./ts/models/Frame" +export * from "./ts/models/Stream" +export * from "./ts/models/Codec" +export * from "./ts/models/FormatContext" export * from "./types/Demuxer" export * from "./types/Decoder" -export * from "./types/Filter" +export * from "./ts/models/Filter" export * from "./types/Encoder" export * from "./types/Muxer" -export * from "./types/Beamstreams" +export * from "./ts/models/Beamstreams" export * from "./types/HWContext" export const AV_NOPTS_VALUE: number From a8d21458da8348ba945f5bfdc605d07e4c96db2a Mon Sep 17 00:00:00 2001 From: UrielCh Date: Tue, 19 Apr 2022 17:33:06 +0300 Subject: [PATCH 17/68] fix last type error --- ts/beamstreams.ts | 4 +--- ts/index.ts | 2 +- ts/models/BeamcoderType.ts | 4 ++-- ts/models/Governor.ts | 18 +++++++++--------- ts/models/type/Governor.d.ts | 8 ++++++++ ts/types.ts | 5 ----- 6 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 ts/models/type/Governor.d.ts diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index 8168cf1..6efc5fb 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -32,11 +32,9 @@ import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, FilterContext, F import { CodecPar } from './models/CodecPar'; import { Demuxer } from './models/Demuxer'; import { Muxer } from './models/Muxer'; -import { Governor } from './models/Governor'; import { Stream } from './models/Stream'; import { DecodedFrames } from './models/Decoder'; - -// import { Governor } from './models/BeamcoderType'; +import type { Governor } from './models/type/Governor'; const beamcoder = bindings('beamcoder') as BeamcoderType; diff --git a/ts/index.ts b/ts/index.ts index 86022ed..e935e24 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -37,7 +37,7 @@ console.log(splash); console.log('Using FFmpeg version', beamcoder.avVersionInfo()); import {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams'; -import { BeamcoderType } from './types'; +import { BeamcoderType } from './models/BeamcoderType'; // export {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams.js'; diff --git a/ts/models/BeamcoderType.ts b/ts/models/BeamcoderType.ts index 81969f6..80eca29 100644 --- a/ts/models/BeamcoderType.ts +++ b/ts/models/BeamcoderType.ts @@ -11,11 +11,11 @@ import { WritableDemuxerStream, ReadableMuxerStream, BeamstreamParams } from './ import { Filter, Filterer, FiltererAudioOptions, FiltererVideoOptions } from "./Filter"; import { commonEncoderParms } from './params'; import { Muxer, MuxerCreateOptions } from "./Muxer"; -import { Governor } from './Governor'; +import type { Governor } from './type/Governor'; export interface BeamcoderType extends ReadableMuxerStream { /** Create object for AVIOContext based buffered I/O */ - governor: Governor; + 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 diff --git a/ts/models/Governor.ts b/ts/models/Governor.ts index 6070750..93e5965 100644 --- a/ts/models/Governor.ts +++ b/ts/models/Governor.ts @@ -1,9 +1,9 @@ -declare class GovernorClass { - constructor(options: { highWaterMark?: number }); - read(len: number): Promise - write(data: Buffer): Promise - finish(): undefined - private _adaptor: unknown; -} - -export type Governor = GovernorClass; +// declare class GovernorClass { +// constructor(options: { highWaterMark?: number }); +// read(len: number): Promise +// write(data: Buffer): Promise +// finish(): undefined +// private _adaptor: unknown; +// } +// +// export type Governor = GovernorClass; diff --git a/ts/models/type/Governor.d.ts b/ts/models/type/Governor.d.ts new file mode 100644 index 0000000..9070e6f --- /dev/null +++ b/ts/models/type/Governor.d.ts @@ -0,0 +1,8 @@ + +export class Governor { + constructor(options: { highWaterMark?: number }); + read(len: number): Promise + write(data: Buffer): Promise + finish(): undefined + private _adaptor: unknown; +} \ No newline at end of file diff --git a/ts/types.ts b/ts/types.ts index 77d0b2f..6729ed9 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -2,11 +2,6 @@ // import { Governor, ReadableMuxerStream } from "../types"; -// export type governorType = { -// read(len: number): Promise; -// write(data: Buffer): Promise; -// finish(): undefined; -// }; export type pixelFormat = 'yuv420p' | 'yuyv422' | 'rgb24' | 'bgr24' | 'yuv422p' | 'yuv444p' | 'yuv410p' | 'yuv411p' | 'gray' | 'monow' | 'monob' | 'pal8' | 'yuvj420p' | 'yuvj422p' | 'yuvj444p' | 'uyvy422' | 'uyyvyy411' | 'bgr8' | 'bgr4' | 'bgr4_byte' | 'rgb8' | 'rgb4' | 'rgb4_byte' | 'nv12' | 'nv21' | 'argb' | 'rgba' | 'abgr' | 'bgra' | 'gray16be' | 'gray16le' | 'yuv440p' | 'yuvj440p' | 'yuva420p' | 'rgb48be' | 'rgb48le' | 'rgb565be' | 'rgb565le' | 'rgb555be' | 'rgb555le' | 'bgr565be' | 'bgr565le' | 'bgr555be' | 'bgr555le' | 'vaapi_moco' | 'vaapi_idct' | 'vaapi_vld' | 'yuv420p16le' | 'yuv420p16be' | 'yuv422p16le' | 'yuv422p16be' | 'yuv444p16le' | 'yuv444p16be' | 'dxva2_vld' | 'rgb444le' | 'rgb444be' | 'bgr444le' | 'bgr444be' | 'ya8' | 'bgr48be' | 'bgr48le' | 'yuv420p9be' | 'yuv420p9le' | 'yuv420p10be' | 'yuv420p10le' | 'yuv422p10be' | 'yuv422p10le' | 'yuv444p9be' | 'yuv444p9le' | 'yuv444p10be' | 'yuv444p10le' | 'yuv422p9be' | 'yuv422p9le' | 'gbrp' | 'gbrp9be' | 'gbrp9le' | 'gbrp10be' | 'gbrp10le' | 'gbrp16be' | 'gbrp16le' | 'yuva422p' | 'yuva444p' | 'yuva420p9be' | 'yuva420p9le' | 'yuva422p9be' | 'yuva422p9le' | 'yuva444p9be' | 'yuva444p9le' | 'yuva420p10be' | 'yuva420p10le' | 'yuva422p10be' | 'yuva422p10le' | 'yuva444p10be' | 'yuva444p10le' | 'yuva420p16be' | 'yuva420p16le' | 'yuva422p16be' | 'yuva422p16le' | 'yuva444p16be' | 'yuva444p16le' | 'vdpau' | 'xyz12le' | 'xyz12be' | 'nv16' | 'nv20le' | 'nv20be' | 'rgba64be' | 'rgba64le' | 'bgra64be' | 'bgra64le' | 'yvyu422' | 'ya16be' | 'ya16le' | 'gbrap' | 'gbrap16be' | 'gbrap16le' | 'qsv' | 'mmal' | 'd3d11va_vld' | 'cuda' | '0rgb' | 'rgb0' | '0bgr' | 'bgr0' | 'yuv420p12be' | 'yuv420p12le' | 'yuv420p14be' | 'yuv420p14le' | 'yuv422p12be' | 'yuv422p12le' | 'yuv422p14be' | 'yuv422p14le' | 'yuv444p12be' | 'yuv444p12le' | 'yuv444p14be' | 'yuv444p14le' | 'gbrp12be' | 'gbrp12le' | 'gbrp14be' | 'gbrp14le' | 'yuvj411p' | 'bayer_bggr8' | 'bayer_rggb8' | 'bayer_gbrg8' | 'bayer_grbg8' | 'bayer_bggr16le' | 'bayer_bggr16be' | 'bayer_rggb16le' | 'bayer_rggb16be' | 'bayer_gbrg16le' | 'bayer_gbrg16be' | 'bayer_grbg16le' | 'bayer_grbg16be' | 'xvmc' | 'yuv440p10le' | 'yuv440p10be' | 'yuv440p12le' | 'yuv440p12be' | 'ayuv64le' | 'ayuv64be' | 'videotoolbox_vld' | 'p010le' | 'p010be' | 'gbrap12be' | 'gbrap12le' | 'gbrap10be' | 'gbrap10le' | 'mediacodec' | 'gray12be' | 'gray12le' | 'gray10be' | 'gray10le' | 'p016le' | 'p016be' | 'd3d11' | 'gray9be' | 'gray9le' | 'gbrpf32be' | 'gbrpf32le' | 'gbrapf32be' | 'gbrapf32le' | 'drm_prime' | 'opencl' | 'gray14be' | 'gray14le' | 'grayf32be' | 'grayf32le' | 'yuva422p12be' | 'yuva422p12le' | 'yuva444p12be' | 'yuva444p12le' | 'nv24' | 'nv42'; From a6330fdebcdb5c2f5cf4a0276d544c4c1e8c4777 Mon Sep 17 00:00:00 2001 From: UrielCh Date: Tue, 19 Apr 2022 18:31:43 +0300 Subject: [PATCH 18/68] clean --- ts/models/BeamcoderType.ts | 1 - ts/models/params.ts | 1 - ts/models/type/Governor.d.ts | 4 +++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/models/BeamcoderType.ts b/ts/models/BeamcoderType.ts index 80eca29..6c26443 100644 --- a/ts/models/BeamcoderType.ts +++ b/ts/models/BeamcoderType.ts @@ -38,7 +38,6 @@ export interface BeamcoderType extends ReadableMuxerStream { * @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. diff --git a/ts/models/params.ts b/ts/models/params.ts index 9a1db0c..e75400a 100644 --- a/ts/models/params.ts +++ b/ts/models/params.ts @@ -15,5 +15,4 @@ export interface commonEncoderParms { priv_data?: any; // { preset?: 'slow' }; flags?: any; // [key: string]: any - } diff --git a/ts/models/type/Governor.d.ts b/ts/models/type/Governor.d.ts index 9070e6f..f74493b 100644 --- a/ts/models/type/Governor.d.ts +++ b/ts/models/type/Governor.d.ts @@ -1,4 +1,6 @@ - +/** + * Type only definition for Governor class + */ export class Governor { constructor(options: { highWaterMark?: number }); read(len: number): Promise From 4283a96ebb14ae3df44aaf7e1a9985b564f27d0f Mon Sep 17 00:00:00 2001 From: UrielCh Date: Tue, 19 Apr 2022 19:33:47 +0300 Subject: [PATCH 19/68] build ok --- .gitignore | 1 + package.json | 2 +- test/demuxerSpec.ts | 1 - ts/beamstreams.ts | 18 +- ts/index.ts | 16 +- ts/models/FormatContext.ts | 391 ------------------ ts/models/Governor.ts | 9 - ts/types.ts | 17 - .../BeamcoderType.d.ts} | 71 +++- .../Beamstreams.ts => types/Beamstreams.d.ts} | 0 ts/{models/Codec.ts => types/Codec.d.ts} | 0 .../CodecContext.d.ts} | 0 .../CodecPar.ts => types/CodecPar.d.ts} | 2 +- ts/{models/Decoder.ts => types/Decoder.d.ts} | 0 ts/{models/Demuxer.ts => types/Demuxer.d.ts} | 6 +- ts/{models/Encoder.ts => types/Encoder.d.ts} | 3 +- ts/{models/Filter.ts => types/Filter.d.ts} | 0 ts/types/FormatContext.d.ts | 260 ++++++++++++ ts/types/FormatContextIn.d.ts | 93 +++++ ts/types/FormatContextOut.d.ts | 42 ++ ts/{models/Frame.ts => types/Frame.d.ts} | 0 ts/{models/type => types}/Governor.d.ts | 0 .../HWContext.ts => types/HWContext.d.ts} | 0 ts/{models/Muxer.ts => types/Muxer.d.ts} | 13 +- ts/{models/Packet.ts => types/Packet.d.ts} | 0 .../PrivClass.ts => types/PrivClass.d.ts} | 0 ts/{models/Stream.ts => types/Stream.d.ts} | 0 ts/{models/params.ts => types/params.d.ts} | 0 tsconfig.json | 2 +- types.d.ts | 83 ---- 30 files changed, 499 insertions(+), 531 deletions(-) delete mode 100644 ts/models/FormatContext.ts delete mode 100644 ts/models/Governor.ts rename ts/{models/BeamcoderType.ts => types/BeamcoderType.d.ts} (85%) rename ts/{models/Beamstreams.ts => types/Beamstreams.d.ts} (100%) rename ts/{models/Codec.ts => types/Codec.d.ts} (100%) rename ts/{models/CodecContext.ts => types/CodecContext.d.ts} (100%) rename ts/{models/CodecPar.ts => types/CodecPar.d.ts} (99%) rename ts/{models/Decoder.ts => types/Decoder.d.ts} (100%) rename ts/{models/Demuxer.ts => types/Demuxer.d.ts} (92%) rename ts/{models/Encoder.ts => types/Encoder.d.ts} (96%) rename ts/{models/Filter.ts => types/Filter.d.ts} (100%) create mode 100644 ts/types/FormatContext.d.ts create mode 100644 ts/types/FormatContextIn.d.ts create mode 100644 ts/types/FormatContextOut.d.ts rename ts/{models/Frame.ts => types/Frame.d.ts} (100%) rename ts/{models/type => types}/Governor.d.ts (100%) rename ts/{models/HWContext.ts => types/HWContext.d.ts} (100%) rename ts/{models/Muxer.ts => types/Muxer.d.ts} (87%) rename ts/{models/Packet.ts => types/Packet.d.ts} (100%) rename ts/{models/PrivClass.ts => types/PrivClass.d.ts} (100%) rename ts/{models/Stream.ts => types/Stream.d.ts} (100%) rename ts/{models/params.ts => types/params.d.ts} (100%) delete mode 100644 types.d.ts diff --git a/.gitignore b/.gitignore index a4b352e..af00bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,4 @@ beamstreams.js index.js install_ffmpeg.js ts/*.js +ts/*.d.ts diff --git a/package.json b/package.json index f8a6c3c..947c731 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.7.1", "description": "Node.js native bindings to FFmpeg.", "main": "ts/index.js", - "types": "types.d.ts", + "types": "ts/index.d.ts", "scripts": { "preinstall": "node ts/install_ffmpeg.js", "install": "node-gyp rebuild", diff --git a/test/demuxerSpec.ts b/test/demuxerSpec.ts index 0cc58fb..fe84b98 100644 --- a/test/demuxerSpec.ts +++ b/test/demuxerSpec.ts @@ -26,7 +26,6 @@ test('Creating a demuxer', async t => { let dm: Demuxer = 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/ts/beamstreams.ts b/ts/beamstreams.ts index 6efc5fb..3546c59 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -21,20 +21,20 @@ import bindings from 'bindings'; // import { Frame, Stream, Muxer, CodecPar, WritableDemuxerStream, BeamstreamParams, Demuxer, Packet, Filterer, BeamstreamSource } from ''; // Codec, CodecContext, import { Writable, Readable, Transform } from 'stream'; -import type { BeamstreamStream, ffStats } from './types'; +import type { ffStats } from './types'; import { teeBalancer } from './teeBalancer'; import { parallelBalancer } from './parallelBalancer'; import { serialBalancer } from './serialBalancer'; -import { BeamcoderType } from './models/BeamcoderType'; -import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, FilterContext, Filterer, FilterLink, Frame, Packet, Timing, WritableDemuxerStream } from '../types'; -import { CodecPar } from './models/CodecPar'; -import { Demuxer } from './models/Demuxer'; -import { Muxer } from './models/Muxer'; -import { Stream } from './models/Stream'; -import { DecodedFrames } from './models/Decoder'; -import type { Governor } from './models/type/Governor'; +import { BeamcoderType } from './types/BeamcoderType'; +import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, Filterer, FilterLink, Frame, Packet, Timing, WritableDemuxerStream } from '../types'; +import { CodecPar } from './types/CodecPar'; +import { Demuxer } from './types/Demuxer'; +import { Muxer } from './types/Muxer'; +import { Stream } from './types/Stream'; +import { DecodedFrames } from './types/Decoder'; +import type { Governor } from './types/Governor'; const beamcoder = bindings('beamcoder') as BeamcoderType; diff --git a/ts/index.ts b/ts/index.ts index e935e24..78f0a5d 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -19,7 +19,10 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ +import {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams'; +import { BeamcoderType } from './types/BeamcoderType'; import bindings from 'bindings'; + const beamcoder = bindings('beamcoder') as BeamcoderType; // import * as beamstreams from './beamstreams.js'; @@ -36,9 +39,6 @@ https://github.com/Streampunk/beamcoder/blob/master/LICENSE`; console.log(splash); console.log('Using FFmpeg version', beamcoder.avVersionInfo()); -import {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams'; -import { BeamcoderType } from './models/BeamcoderType'; - // export {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams.js'; beamcoder.demuxerStream = demuxerStream; @@ -49,4 +49,12 @@ beamcoder.makeStreams = makeStreams; export default beamcoder; -// module.exports = beamcoder; +export { CodecPar } from './types/CodecPar'; +export { Decoder } from './types/Decoder'; +export { Demuxer } from './types/Demuxer'; +export { Muxer } from './types/Muxer'; +export { Stream } from './types/Stream'; +export { Encoder } from './types/Encoder'; +export { Filterer, Filter } from './types/Filter'; +export { FormatContext } from './types/FormatContext'; +export { Frame } from './types/Frame'; diff --git a/ts/models/FormatContext.ts b/ts/models/FormatContext.ts deleted file mode 100644 index d6fc9cf..0000000 --- a/ts/models/FormatContext.ts +++ /dev/null @@ -1,391 +0,0 @@ -import { Stream } from "./Stream" -import { PrivClass } from "./PrivClass" - -/** - * Describes a supported input format - */ -export interface InputFormat { - /** Object name. */ - readonly type: 'InputFormat' - /** A comma separated list of short names for the format. */ - readonly name: string - /** Descriptive name for the format, meant to be more human-readable */ - readonly long_name: string - readonly flags: { - NOFILE: boolean - /** Needs '%d' in filename. */ - NEEDNUMBER: boolean - /** Show format stream IDs numbers. */ - SHOW_IDS: boolean - /** Use generic index building code. */ - GENERIC_INDEX: boolean - /** Format allows timestamp discontinuities. Note, muxers always require valid (monotone) timestamps */ - TS_DISCONT: boolean - /** Format does not allow to fall back on binary search via read_timestamp */ - NOBINSEARCH: boolean - /** Format does not allow to fall back on generic search */ - NOGENSEARCH: boolean - /** Format does not allow seeking by bytes */ - NO_BYTE_SEEK: boolean - /** Seeking is based on PTS */ - SEEK_TO_PTS: boolean - } - /** - * If extensions are defined, then no probe is done. You should - * usually not use extension format guessing because it is not - * reliable enough - */ - readonly extensions: string - /** - * List of supported codec_id-codec_tag pairs, ordered by "better - * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. - */ - readonly codec_tag: ReadonlyArray<{ - id: number - tag: string | number - }> - /** Class for private context */ - readonly priv_class: PrivClass | null - /** - * Comma-separated list of mime types. - * It is used check for matching mime types while probing. - */ - readonly mime_type: string - /** Raw demuxers store their codec ID here. */ - readonly raw_codec_id: string - /** Size of private data so that it can be allocated. */ - readonly priv_data_size: number -} - -/** - * Describes a supported input format - */ -export interface OutputFormat { - /** Object name. */ - readonly type: 'OutputFormat' - /** A comma separated list of short names for the format. */ - name: string - /** Descriptive name for the format, meant to be more human-readable */ - long_name: string - /** - * Comma-separated list of mime types. - * It is used check for matching mime types while probing. - */ - mime_type: string - /** comma-separated filename extensions */ - extensions: string - /** default audio codec */ - audio_codec: string - /** default video codec */ - video_codec: string - /** default subtitle codec */ - subtitle_codec: string - flags: { - NOFILE?: boolean - /** Needs '%d' in filename. */ - NEEDNUMBER?: boolean - /** Format wants global header. */ - GLOBALHEADER?: boolean - /** Format does not need / have any timestamps. */ - NOTIMESTAMPS?: boolean - /** Format allows variable fps. */ - VARIABLE_FPS?: boolean - /** Format does not need width/height */ - NODIMENSIONS?: boolean - /** Format does not require any streams */ - NOSTREAMS?: boolean - /** Format allows flushing. If not set, the muxer will not receive a NULL packet in the write_packet function. */ - ALLOW_FLUSH?: boolean - /** Format does not require strictly increasing timestamps, but they must still be monotonic */ - TS_NONSTRICT?: boolean - /** - * Format allows muxing negative timestamps. If not set the timestamp will be shifted in av_write_frame and - * av_interleaved_write_frame so they start from 0. - * The user or muxer can override this through AVFormatContext.avoid_negative_ts - */ - TS_NEGATIVE?: boolean - } - /** - * List of supported codec_id-codec_tag pairs, ordered by "better - * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. - */ - codec_tag: Array<{ - id: number - tag: string | number - }> - /** Class for private context */ - priv_class: PrivClass | null - /** size of private data so that it can be allocated */ - priv_data_size: number -} - - - - -export interface FormatContextFlags { - /** Generate missing pts even if it requires parsing future frames. */ - GENPTS?: boolean - /** Ignore index. */ - IGNIDX?: boolean - /** Do not block when reading packets from input. */ - NONBLOCK?: boolean - /** Ignore DTS on frames that contain both DTS & PTS */ - IGNDTS?: boolean - /** Do not infer any values from other values, just return what is stored in the container */ - NOFILLIN?: boolean - /** Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fillin code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled */ - NOPARSE?: boolean - /** Do not buffer frames when possible */ - NOBUFFER?: boolean - /** The caller has supplied a custom AVIOContext, don't avio_close() it. */ - CUSTOM_IO?: boolean - /** Discard frames marked corrupted */ - DISCARD_CORRUPT?: boolean - /** Flush the AVIOContext every packet. */ - FLUSH_PACKETS?: boolean - /** This flag is mainly intended for testing. */ - BITEXACT?: boolean - /** try to interleave outputted packets by dts (using this flag can slow demuxing down) */ - SORT_DTS?: boolean - /** Enable use of private options by delaying codec open (this could be made default once all code is converted) */ - PRIV_OPT?: boolean - /** Enable fast, but inaccurate seeks for some formats */ - FAST_SEEK?: boolean - /** Stop muxing when the shortest stream stops. */ - SHORTEST?: boolean - /** Add bitstream filters as requested by the muxer */ - AUTO_BSF?: boolean -} - -export interface FormatContextBase { - /** Object name. */ - readonly type: 'demuxer'| 'format' | 'muxer' - - /** The input format description. */ - set iformat(format: string); - //@ts-ignore - get iformat(): InputFormat - /** Format private data. */ - priv_data: { - [key: string]: any - } - /** Flags signalling stream properties. */ - readonly ctx_flags: { - NOHEADER: boolean - UNSEEKABLE: boolean - } - /** An array of all streams in the file. */ - streams: Array - /** input or output URL. Unlike the old filename field, this field has no length restriction. */ - url: string - /** - * 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 - /** - * Duration of the stream, in AV_TIME_BASE fractional - * seconds. Only set this value if you know none of the individual stream - * durations and also do not set any of them. This is deduced from the - * Stream values if not set. - */ - duration: number - /** - * Total stream bitrate in bit/s, 0 if not - * available. Never set it directly if the file_size and the - * duration are known as FFmpeg can compute it automatically. - */ - bit_rate: number - - packet_size: number - - max_delay: number - /** Flags modifying the demuxer behaviour. A combination of AVFMT_FLAG_*. */ - flags: FormatContextFlags - /** 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 - readonly key: Buffer - readonly programs: ReadonlyArray - // video_codec_id / audio_codec_id / subtitle_codec_id - - /** - * 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 - /** - * Maximum amount of memory in bytes to use for buffering frames - * obtained from realtime capture devices. - */ - max_picture_buffer: number - // chapters? - /** - * Metadata that applies to the whole file. - */ - metadata: { [key: string]: string } - /** - * Start time of the stream in real world time, in microseconds - * since the Unix epoch (00:00 1st January 1970). That is, pts=0 in the - * stream was captured at this real world time. - * AV_NOPTS_VALUE if unknown. Note that the value may become known after - * some number of frames have been received. - */ - start_time_realtime: number | null - /** 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 - /** Flags to enable debugging. */ - debug: { TS: boolean } - - /** Allow non-standard and experimental extension */ - strict_std_compliance: 'very-strict' | 'strict' | 'normal' | 'unofficial' | 'experimental' - /** - * Flags for the user to detect events happening on the file. Flags must - * be cleared by the user once the event has been handled. - * A combination of AVFMT_EVENT_FLAG_*. - */ - event_flags: { METADATA_UPDATED?: boolean } - /** 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 - /** - * IO repositioned flag. - * This is set by avformat when the underlaying IO context read pointer - * is repositioned, for example when doing byte based seeking. - * Demuxers can use the flag to detect such changes. - */ - readonly io_repositioned: boolean - /** - * dump format separator. - * can be ", " or "\n " or anything else - */ - dump_separator: string - /** ',' 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, - /** - * Add a stream to the format with the next available stream index. - * @param options Object including the codec name for the stream and any other parameters that need - * to be initialised in the Stream object - * @returns A Stream object - */ - newStream(options: string | { 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 - * @returns A Stream object - */ - newStream(stream: Stream): Stream - } - -/** -* Format I/O context. - */ -export interface FormatContext extends FormatContextBase { - readonly type: 'format'; - /** 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. */ - output_ts_offset: number - - /** Retun a JSON string containing the object properties. */ - toJSON(): string -} - diff --git a/ts/models/Governor.ts b/ts/models/Governor.ts deleted file mode 100644 index 93e5965..0000000 --- a/ts/models/Governor.ts +++ /dev/null @@ -1,9 +0,0 @@ -// declare class GovernorClass { -// constructor(options: { highWaterMark?: number }); -// read(len: number): Promise -// write(data: Buffer): Promise -// finish(): undefined -// private _adaptor: unknown; -// } -// -// export type Governor = GovernorClass; diff --git a/ts/types.ts b/ts/types.ts index 6729ed9..c6e36e3 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -1,23 +1,6 @@ -// import type { Frame, Stream, Muxer, CodecPar, Codec, Governor, WritableDemuxerStream, ReadableMuxerStream, FiltererVideoOptions, FiltererAudioOptions, Filterer, Decoder, Demuxer, Encoder, InputFormat, BeamstreamParams } from '..'; // Codec, CodecContext, - -// import { Governor, ReadableMuxerStream } from "../types"; - export type pixelFormat = 'yuv420p' | 'yuyv422' | 'rgb24' | 'bgr24' | 'yuv422p' | 'yuv444p' | 'yuv410p' | 'yuv411p' | 'gray' | 'monow' | 'monob' | 'pal8' | 'yuvj420p' | 'yuvj422p' | 'yuvj444p' | 'uyvy422' | 'uyyvyy411' | 'bgr8' | 'bgr4' | 'bgr4_byte' | 'rgb8' | 'rgb4' | 'rgb4_byte' | 'nv12' | 'nv21' | 'argb' | 'rgba' | 'abgr' | 'bgra' | 'gray16be' | 'gray16le' | 'yuv440p' | 'yuvj440p' | 'yuva420p' | 'rgb48be' | 'rgb48le' | 'rgb565be' | 'rgb565le' | 'rgb555be' | 'rgb555le' | 'bgr565be' | 'bgr565le' | 'bgr555be' | 'bgr555le' | 'vaapi_moco' | 'vaapi_idct' | 'vaapi_vld' | 'yuv420p16le' | 'yuv420p16be' | 'yuv422p16le' | 'yuv422p16be' | 'yuv444p16le' | 'yuv444p16be' | 'dxva2_vld' | 'rgb444le' | 'rgb444be' | 'bgr444le' | 'bgr444be' | 'ya8' | 'bgr48be' | 'bgr48le' | 'yuv420p9be' | 'yuv420p9le' | 'yuv420p10be' | 'yuv420p10le' | 'yuv422p10be' | 'yuv422p10le' | 'yuv444p9be' | 'yuv444p9le' | 'yuv444p10be' | 'yuv444p10le' | 'yuv422p9be' | 'yuv422p9le' | 'gbrp' | 'gbrp9be' | 'gbrp9le' | 'gbrp10be' | 'gbrp10le' | 'gbrp16be' | 'gbrp16le' | 'yuva422p' | 'yuva444p' | 'yuva420p9be' | 'yuva420p9le' | 'yuva422p9be' | 'yuva422p9le' | 'yuva444p9be' | 'yuva444p9le' | 'yuva420p10be' | 'yuva420p10le' | 'yuva422p10be' | 'yuva422p10le' | 'yuva444p10be' | 'yuva444p10le' | 'yuva420p16be' | 'yuva420p16le' | 'yuva422p16be' | 'yuva422p16le' | 'yuva444p16be' | 'yuva444p16le' | 'vdpau' | 'xyz12le' | 'xyz12be' | 'nv16' | 'nv20le' | 'nv20be' | 'rgba64be' | 'rgba64le' | 'bgra64be' | 'bgra64le' | 'yvyu422' | 'ya16be' | 'ya16le' | 'gbrap' | 'gbrap16be' | 'gbrap16le' | 'qsv' | 'mmal' | 'd3d11va_vld' | 'cuda' | '0rgb' | 'rgb0' | '0bgr' | 'bgr0' | 'yuv420p12be' | 'yuv420p12le' | 'yuv420p14be' | 'yuv420p14le' | 'yuv422p12be' | 'yuv422p12le' | 'yuv422p14be' | 'yuv422p14le' | 'yuv444p12be' | 'yuv444p12le' | 'yuv444p14be' | 'yuv444p14le' | 'gbrp12be' | 'gbrp12le' | 'gbrp14be' | 'gbrp14le' | 'yuvj411p' | 'bayer_bggr8' | 'bayer_rggb8' | 'bayer_gbrg8' | 'bayer_grbg8' | 'bayer_bggr16le' | 'bayer_bggr16be' | 'bayer_rggb16le' | 'bayer_rggb16be' | 'bayer_gbrg16le' | 'bayer_gbrg16be' | 'bayer_grbg16le' | 'bayer_grbg16be' | 'xvmc' | 'yuv440p10le' | 'yuv440p10be' | 'yuv440p12le' | 'yuv440p12be' | 'ayuv64le' | 'ayuv64be' | 'videotoolbox_vld' | 'p010le' | 'p010be' | 'gbrap12be' | 'gbrap12le' | 'gbrap10be' | 'gbrap10le' | 'mediacodec' | 'gray12be' | 'gray12le' | 'gray10be' | 'gray10le' | 'p016le' | 'p016be' | 'd3d11' | 'gray9be' | 'gray9le' | 'gbrpf32be' | 'gbrpf32le' | 'gbrapf32be' | 'gbrapf32le' | 'drm_prime' | 'opencl' | 'gray14be' | 'gray14le' | 'grayf32be' | 'grayf32le' | 'yuva422p12be' | 'yuva422p12le' | 'yuva444p12be' | 'yuva444p12le' | 'nv24' | 'nv42'; -export interface BeamstreamStream { - name: string; - time_base: any; - encoder: any; - stream: any; - codecpar: { - sample_rate: number; - frame_size: number; - format: any, - channel_layout: any; - }; -} - export interface ffStats { mean: number; stdDev: number; diff --git a/ts/models/BeamcoderType.ts b/ts/types/BeamcoderType.d.ts similarity index 85% rename from ts/models/BeamcoderType.ts rename to ts/types/BeamcoderType.d.ts index 6c26443..e0245be 100644 --- a/ts/models/BeamcoderType.ts +++ b/ts/types/BeamcoderType.d.ts @@ -11,7 +11,7 @@ import { WritableDemuxerStream, ReadableMuxerStream, BeamstreamParams } from './ import { Filter, Filterer, FiltererAudioOptions, FiltererVideoOptions } from "./Filter"; import { commonEncoderParms } from './params'; import { Muxer, MuxerCreateOptions } from "./Muxer"; -import type { Governor } from './type/Governor'; +import type { Governor } from './Governor'; export interface BeamcoderType extends ReadableMuxerStream { /** Create object for AVIOContext based buffered I/O */ @@ -271,5 +271,74 @@ export interface BeamcoderType extends ReadableMuxerStream { * @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/models/Beamstreams.ts b/ts/types/Beamstreams.d.ts similarity index 100% rename from ts/models/Beamstreams.ts rename to ts/types/Beamstreams.d.ts diff --git a/ts/models/Codec.ts b/ts/types/Codec.d.ts similarity index 100% rename from ts/models/Codec.ts rename to ts/types/Codec.d.ts diff --git a/ts/models/CodecContext.ts b/ts/types/CodecContext.d.ts similarity index 100% rename from ts/models/CodecContext.ts rename to ts/types/CodecContext.d.ts diff --git a/ts/models/CodecPar.ts b/ts/types/CodecPar.d.ts similarity index 99% rename from ts/models/CodecPar.ts rename to ts/types/CodecPar.d.ts index 49154e8..7250556 100644 --- a/ts/models/CodecPar.ts +++ b/ts/types/CodecPar.d.ts @@ -24,7 +24,7 @@ export interface CodecPar { * - video: the pixel format. * - audio: the sample format. */ - format: 'video' | 'audio' | null + format: string | null /** The average bitrate of the encoded data (in bits per second). */ bit_rate: number diff --git a/ts/models/Decoder.ts b/ts/types/Decoder.d.ts similarity index 100% rename from ts/models/Decoder.ts rename to ts/types/Decoder.d.ts diff --git a/ts/models/Demuxer.ts b/ts/types/Demuxer.d.ts similarity index 92% rename from ts/models/Demuxer.ts rename to ts/types/Demuxer.d.ts index 317bb61..8ebb5f7 100644 --- a/ts/models/Demuxer.ts +++ b/ts/types/Demuxer.d.ts @@ -1,5 +1,7 @@ import { Packet } from "./Packet" -import { InputFormat, FormatContext, FormatContextBase } from "./FormatContext" +import { InputFormat, FormatContextBase } from "./FormatContext" +import { FormatContextIn } from "./FormatContextIn" +import { FormatContextOut } from "./FormatContextOut" export interface SeekOptions { /** @@ -43,7 +45,7 @@ export interface SeekOptions { * The process of demuxing (de-multiplexing) extracts time-labelled packets of data * contained in a media stream or file. */ -export interface Demuxer extends FormatContextBase { +export interface Demuxer extends FormatContextBase, FormatContextOut , FormatContextIn { // { read: () => Promise, streams: Array<{time_base: [number, number]}> } /** Object name. */ // readonly type: 'demuxer' diff --git a/ts/models/Encoder.ts b/ts/types/Encoder.d.ts similarity index 96% rename from ts/models/Encoder.ts rename to ts/types/Encoder.d.ts index 8710bea..483bf52 100644 --- a/ts/models/Encoder.ts +++ b/ts/types/Encoder.d.ts @@ -1,8 +1,7 @@ import { CodecPar } from "./CodecPar" import { Packet } from "./Packet"; import { Frame } from "./Frame"; -import { Codec } from "./Codec" -import { CodecContext, CodecContextBaseMin } from "./CodecContext" +import { CodecContextBaseMin } from "./CodecContext" /** The EncodedPackets object is returned as the result of a encode operation */ export interface EncodedPackets { diff --git a/ts/models/Filter.ts b/ts/types/Filter.d.ts similarity index 100% rename from ts/models/Filter.ts rename to ts/types/Filter.d.ts diff --git a/ts/types/FormatContext.d.ts b/ts/types/FormatContext.d.ts new file mode 100644 index 0000000..a428bb1 --- /dev/null +++ b/ts/types/FormatContext.d.ts @@ -0,0 +1,260 @@ +import { Stream } from "./Stream" +import { PrivClass } from "./PrivClass" +import { FormatContextIn } from "./FormatContextIn" +import { FormatContextOut } from "./FormatContextOut" + +/** + * Describes a supported input format + */ +export interface InputFormat { + /** Object name. */ + readonly type: 'InputFormat' + /** A comma separated list of short names for the format. */ + readonly name: string + /** Descriptive name for the format, meant to be more human-readable */ + readonly long_name: string + readonly flags: { + NOFILE: boolean + /** Needs '%d' in filename. */ + NEEDNUMBER: boolean + /** Show format stream IDs numbers. */ + SHOW_IDS: boolean + /** Use generic index building code. */ + GENERIC_INDEX: boolean + /** Format allows timestamp discontinuities. Note, muxers always require valid (monotone) timestamps */ + TS_DISCONT: boolean + /** Format does not allow to fall back on binary search via read_timestamp */ + NOBINSEARCH: boolean + /** Format does not allow to fall back on generic search */ + NOGENSEARCH: boolean + /** Format does not allow seeking by bytes */ + NO_BYTE_SEEK: boolean + /** Seeking is based on PTS */ + SEEK_TO_PTS: boolean + } + /** + * If extensions are defined, then no probe is done. You should + * usually not use extension format guessing because it is not + * reliable enough + */ + readonly extensions: string + /** + * List of supported codec_id-codec_tag pairs, ordered by "better + * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. + */ + readonly codec_tag: ReadonlyArray<{ + id: number + tag: string | number + }> + /** Class for private context */ + readonly priv_class: PrivClass | null + /** + * Comma-separated list of mime types. + * It is used check for matching mime types while probing. + */ + readonly mime_type: string + /** Raw demuxers store their codec ID here. */ + readonly raw_codec_id: string + /** Size of private data so that it can be allocated. */ + readonly priv_data_size: number +} + +/** + * Describes a supported input format + */ +export interface OutputFormat { + /** Object name. */ + readonly type: 'OutputFormat' + /** A comma separated list of short names for the format. */ + name: string + /** Descriptive name for the format, meant to be more human-readable */ + long_name: string + /** + * Comma-separated list of mime types. + * It is used check for matching mime types while probing. + */ + mime_type: string + /** comma-separated filename extensions */ + extensions: string + /** default audio codec */ + audio_codec: string + /** default video codec */ + video_codec: string + /** default subtitle codec */ + subtitle_codec: string + flags: { + NOFILE?: boolean + /** Needs '%d' in filename. */ + NEEDNUMBER?: boolean + /** Format wants global header. */ + GLOBALHEADER?: boolean + /** Format does not need / have any timestamps. */ + NOTIMESTAMPS?: boolean + /** Format allows variable fps. */ + VARIABLE_FPS?: boolean + /** Format does not need width/height */ + NODIMENSIONS?: boolean + /** Format does not require any streams */ + NOSTREAMS?: boolean + /** Format allows flushing. If not set, the muxer will not receive a NULL packet in the write_packet function. */ + ALLOW_FLUSH?: boolean + /** Format does not require strictly increasing timestamps, but they must still be monotonic */ + TS_NONSTRICT?: boolean + /** + * Format allows muxing negative timestamps. If not set the timestamp will be shifted in av_write_frame and + * av_interleaved_write_frame so they start from 0. + * The user or muxer can override this through AVFormatContext.avoid_negative_ts + */ + TS_NEGATIVE?: boolean + } + /** + * List of supported codec_id-codec_tag pairs, ordered by "better + * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. + */ + codec_tag: Array<{ + id: number + tag: string | number + }> + /** Class for private context */ + priv_class: PrivClass | null + /** size of private data so that it can be allocated */ + priv_data_size: number +} + +export interface FormatContextFlags { + /** Generate missing pts even if it requires parsing future frames. */ + GENPTS?: boolean + /** Ignore index. */ + IGNIDX?: boolean + /** Do not block when reading packets from input. */ + NONBLOCK?: boolean + /** Ignore DTS on frames that contain both DTS & PTS */ + IGNDTS?: boolean + /** Do not infer any values from other values, just return what is stored in the container */ + NOFILLIN?: boolean + /** Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fillin code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled */ + NOPARSE?: boolean + /** Do not buffer frames when possible */ + NOBUFFER?: boolean + /** The caller has supplied a custom AVIOContext, don't avio_close() it. */ + CUSTOM_IO?: boolean + /** Discard frames marked corrupted */ + DISCARD_CORRUPT?: boolean + /** Flush the AVIOContext every packet. */ + FLUSH_PACKETS?: boolean + /** This flag is mainly intended for testing. */ + BITEXACT?: boolean + /** try to interleave outputted packets by dts (using this flag can slow demuxing down) */ + SORT_DTS?: boolean + /** Enable use of private options by delaying codec open (this could be made default once all code is converted) */ + PRIV_OPT?: boolean + /** Enable fast, but inaccurate seeks for some formats */ + FAST_SEEK?: boolean + /** Stop muxing when the shortest stream stops. */ + SHORTEST?: boolean + /** Add bitstream filters as requested by the muxer */ + AUTO_BSF?: boolean +} +// extends FormatContextIn +export interface FormatContextBase { + /** Object name. */ + readonly type: 'demuxer' | 'format' | 'muxer' + + /** Format private data. */ + priv_data: { + [key: string]: any + } + /** Flags signalling stream properties. */ + readonly ctx_flags: { + NOHEADER: boolean + UNSEEKABLE: boolean + } + /** An array of all streams in the file. */ + streams: Array + /** input or output URL. Unlike the old filename field, this field has no length restriction. */ + url: string + /** + * Duration of the stream, in AV_TIME_BASE fractional + * seconds. Only set this value if you know none of the individual stream + * durations and also do not set any of them. This is deduced from the + * Stream values if not set. + */ + duration: number + /** + * Total stream bitrate in bit/s, 0 if not + * available. Never set it directly if the file_size and the + * duration are known as FFmpeg can compute it automatically. + */ + bit_rate: number + + packet_size: number + + max_delay: number + /** Flags modifying the demuxer behaviour. A combination of AVFMT_FLAG_*. */ + flags: FormatContextFlags + readonly key: Buffer + readonly programs: ReadonlyArray + // video_codec_id / audio_codec_id / subtitle_codec_id + + /** + * Maximum amount of memory in bytes to use for buffering frames + * obtained from realtime capture devices. + */ + max_picture_buffer: number + // chapters? + /** + * Metadata that applies to the whole file. + */ + metadata: { [key: string]: string } + /** + * Start time of the stream in real world time, in microseconds + * since the Unix epoch (00:00 1st January 1970). That is, pts=0 in the + * stream was captured at this real world time. + * AV_NOPTS_VALUE if unknown. Note that the value may become known after + * some number of frames have been received. + */ + start_time_realtime: number | null + /** Flags to enable debugging. */ + debug: { TS: boolean } + + /** Allow non-standard and experimental extension */ + strict_std_compliance: 'very-strict' | 'strict' | 'normal' | 'unofficial' | 'experimental' + /** + * Flags for the user to detect events happening on the file. Flags must + * be cleared by the user once the event has been handled. + * A combination of AVFMT_EVENT_FLAG_*. + */ + event_flags: { METADATA_UPDATED?: boolean } + /** + * IO repositioned flag. + * This is set by avformat when the underlaying IO context read pointer + * is repositioned, for example when doing byte based seeking. + * Demuxers can use the flag to detect such changes. + */ + readonly io_repositioned: boolean + /** + * dump format separator. + * can be ", " or "\n " or anything else + */ + dump_separator: string + /** + * Add a stream to the format with the next available stream index. + * @param options Object including the codec name for the stream and any other parameters that need + * to be initialised in the Stream object + * @returns A Stream object + */ + newStream(options: string | { 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 + * @returns A Stream object + */ + newStream(stream: Stream): Stream +} + + +/** +* Format I/O context. + */ +export interface FormatContext extends FormatContextBase, FormatContextOut, FormatContextIn { +} 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/ts/models/Frame.ts b/ts/types/Frame.d.ts similarity index 100% rename from ts/models/Frame.ts rename to ts/types/Frame.d.ts diff --git a/ts/models/type/Governor.d.ts b/ts/types/Governor.d.ts similarity index 100% rename from ts/models/type/Governor.d.ts rename to ts/types/Governor.d.ts diff --git a/ts/models/HWContext.ts b/ts/types/HWContext.d.ts similarity index 100% rename from ts/models/HWContext.ts rename to ts/types/HWContext.d.ts diff --git a/ts/models/Muxer.ts b/ts/types/Muxer.d.ts similarity index 87% rename from ts/models/Muxer.ts rename to ts/types/Muxer.d.ts index 7247325..a4debed 100644 --- a/ts/models/Muxer.ts +++ b/ts/types/Muxer.d.ts @@ -1,15 +1,10 @@ import { Packet } from "./Packet" import { Frame } from "./Frame" -import { OutputFormat, FormatContext } from "./FormatContext" +import { OutputFormat } from "./FormatContext" +import { FormatContextBase } from "./FormatContext" +import { FormatContextOut } from "./FormatContextOut" -export interface Muxer extends Omit { +export interface Muxer extends FormatContextBase, FormatContextOut { /** Object name. */ type: 'muxer' diff --git a/ts/models/Packet.ts b/ts/types/Packet.d.ts similarity index 100% rename from ts/models/Packet.ts rename to ts/types/Packet.d.ts diff --git a/ts/models/PrivClass.ts b/ts/types/PrivClass.d.ts similarity index 100% rename from ts/models/PrivClass.ts rename to ts/types/PrivClass.d.ts diff --git a/ts/models/Stream.ts b/ts/types/Stream.d.ts similarity index 100% rename from ts/models/Stream.ts rename to ts/types/Stream.d.ts diff --git a/ts/models/params.ts b/ts/types/params.d.ts similarity index 100% rename from ts/models/params.ts rename to ts/types/params.d.ts diff --git a/tsconfig.json b/tsconfig.json index 005b1da..4d85352 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -42,7 +42,7 @@ // "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. */ + "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. */ diff --git a/types.d.ts b/types.d.ts deleted file mode 100644 index a86fcea..0000000 --- a/types.d.ts +++ /dev/null @@ -1,83 +0,0 @@ -export * from "./ts/models/CodecPar.ts" -export * from "./ts/models/Packet" -export * from "./ts/models/Frame" -export * from "./ts/models/Stream" -export * from "./ts/models/Codec" -export * from "./ts/models/FormatContext" -export * from "./types/Demuxer" -export * from "./types/Decoder" -export * from "./ts/models/Filter" -export * from "./types/Encoder" -export * from "./types/Muxer" -export * from "./ts/models/Beamstreams" -export * from "./types/HWContext" - -export const AV_NOPTS_VALUE: number - -/** The LIBAV**_VERSION_INT for each FFmpeg library */ -export function 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. - */ -export function avVersionInfo(): string -/** Informative version strings for each FFmpeg library */ -export function versionStrings(): { - avcodec: string - avdevice: string - avfilter: string - avformat: string - avutil: string - postproc: string - swresample: string - swscale: string -} -/** Build configuration strings for each FFmpeg library */ -export function configurations(): { - avcodec: string - avdevice: string - avfilter: string - avformat: string - avutil: string - postproc: string - swresample: string - swscale: string -} -/** License strings for each FFmpeg library */ -export function licenses(): { - avcodec: string - avdevice: string - avfilter: string - avformat: string - avutil: string - postproc: string - swresample: string - swscale: string -} -/** List the available protocols */ -export function 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 - */ -export function logging(level?: string): string | undefined - -export as namespace Beamcoder From d3e5de7a32d70559938d380808ea3ce90f424953 Mon Sep 17 00:00:00 2001 From: UrielCh Date: Tue, 19 Apr 2022 21:02:00 +0300 Subject: [PATCH 20/68] fix imports --- examples/encode_h264.ts | 2 +- examples/jpeg_app.ts | 2 +- examples/jpeg_filter_app.ts | 2 +- ts/beamstreams.ts | 10 +++++----- ts/index.ts | 1 + ts/types/DecodedFrames.d.ts | 18 ++++++++++++++++++ ts/types/Decoder.d.ts | 19 ------------------- 7 files changed, 27 insertions(+), 27 deletions(-) create mode 100644 ts/types/DecodedFrames.d.ts diff --git a/examples/encode_h264.ts b/examples/encode_h264.ts index d93d569..6209c6f 100644 --- a/examples/encode_h264.ts +++ b/examples/encode_h264.ts @@ -26,7 +26,7 @@ Output can be viewed in VLC. Make sure "All Files" is selected to see the file. */ -import beamcoder from '../ts/index'; // Use require('beamcoder') externally +import beamcoder from '..'; // Use require('beamcoder') externally import fs from 'fs'; let endcode = Buffer.from([0, 0, 1, 0xb7]); diff --git a/examples/jpeg_app.ts b/examples/jpeg_app.ts index e1d3133..295a611 100644 --- a/examples/jpeg_app.ts +++ b/examples/jpeg_app.ts @@ -24,7 +24,7 @@ Only supports 8-bit YUV 4:2:2 or 4:2:0 pixel formats. */ -import beamcoder from '../ts/index'; // Use require('beamcoder') externally +import beamcoder from '..'; // Use require('beamcoder') externally import Koa from 'koa'; // Add koa to package.json dependencies import fs from 'fs'; // Add koa to package.json dependencies diff --git a/examples/jpeg_filter_app.ts b/examples/jpeg_filter_app.ts index d4d634a..e54ae49 100644 --- a/examples/jpeg_filter_app.ts +++ b/examples/jpeg_filter_app.ts @@ -26,7 +26,7 @@ import beamcoder from '../ts/index'; // Use require('beamcoder') externally import Koa from 'koa'; // Add koa to package.json dependencies -import { DecodedFrames } from '../types'; +import { DecodedFrames } from '..'; const app = new Koa(); app.use(async (ctx) => { // Assume HTTP GET with path // diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index 3546c59..f929fb1 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -19,22 +19,22 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ import bindings from 'bindings'; -// import { Frame, Stream, Muxer, CodecPar, WritableDemuxerStream, BeamstreamParams, Demuxer, Packet, Filterer, BeamstreamSource } from ''; // Codec, CodecContext, import { Writable, Readable, Transform } from 'stream'; import type { ffStats } from './types'; - import { teeBalancer } from './teeBalancer'; import { parallelBalancer } from './parallelBalancer'; import { serialBalancer } from './serialBalancer'; - import { BeamcoderType } from './types/BeamcoderType'; -import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, Filterer, FilterLink, Frame, Packet, Timing, WritableDemuxerStream } from '../types'; import { CodecPar } from './types/CodecPar'; import { Demuxer } from './types/Demuxer'; import { Muxer } from './types/Muxer'; import { Stream } from './types/Stream'; -import { DecodedFrames } from './types/Decoder'; +import { DecodedFrames } from './types/DecodedFrames'; import type { Governor } from './types/Governor'; +import { Frame } from './types/Frame'; +import { Packet, Timing } from './types/Packet'; +import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, WritableDemuxerStream } from './types/Beamstreams'; +import { Filterer, FilterLink } from './types/Filter'; const beamcoder = bindings('beamcoder') as BeamcoderType; diff --git a/ts/index.ts b/ts/index.ts index 78f0a5d..b4dd7ba 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -58,3 +58,4 @@ export { Encoder } from './types/Encoder'; export { Filterer, Filter } from './types/Filter'; export { FormatContext } from './types/FormatContext'; export { Frame } from './types/Frame'; +export { DecodedFrames } from './types/DecodedFrames'; diff --git a/ts/types/DecodedFrames.d.ts b/ts/types/DecodedFrames.d.ts new file mode 100644 index 0000000..fa06a38 --- /dev/null +++ b/ts/types/DecodedFrames.d.ts @@ -0,0 +1,18 @@ +/** The DecodedFrames object is returned as the result of a decode operation */ +export interface DecodedFrames { + /** 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 + /** Total time in microseconds that the decode operation took to complete */ + readonly total_time: number + + + timings?: { + // "encode" | "dice" | "decode" | "filter" + [key: string]: Timing; }; + +} diff --git a/ts/types/Decoder.d.ts b/ts/types/Decoder.d.ts index f762ca3..b231b81 100644 --- a/ts/types/Decoder.d.ts +++ b/ts/types/Decoder.d.ts @@ -5,25 +5,6 @@ import { Codec } from "./Codec" import { CodecContext, CodecContext_base } from "./CodecContext" import { Demuxer } from "./Demuxer" -/** The DecodedFrames object is returned as the result of a decode operation */ -export interface DecodedFrames { - /** 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 - /** Total time in microseconds that the decode operation took to complete */ - readonly total_time: number - - - timings?: { - // "encode" | "dice" | "decode" | "filter" - [key: string]: Timing; }; - -} - export interface Decoder extends CodecContext_base { // readonly type: 'decoder' // readonly time_base: [number, number] From d11042a734ffd82a3b1e5a7814939be25d6eb7d7 Mon Sep 17 00:00:00 2001 From: urielCh Date: Wed, 20 Apr 2022 19:20:46 +0300 Subject: [PATCH 21/68] test --- scratch/{stream_mp4.js => stream_mp4.ts} | 5 +++-- ts/beamstreams.ts | 14 ++++++++----- ts/install_ffmpeg.ts | 8 ++++---- ts/teeBalancer.ts | 25 ++++++++++++++++-------- ts/types/Beamstreams.d.ts | 2 +- ts/types/Packet.d.ts | 5 ----- ts/types/Timing.d.ts | 4 ++++ 7 files changed, 38 insertions(+), 25 deletions(-) rename scratch/{stream_mp4.js => stream_mp4.ts} (92%) create mode 100644 ts/types/Timing.d.ts diff --git a/scratch/stream_mp4.js b/scratch/stream_mp4.ts similarity index 92% rename from scratch/stream_mp4.js rename to scratch/stream_mp4.ts index 4d0041b..114947f 100644 --- a/scratch/stream_mp4.js +++ b/scratch/stream_mp4.ts @@ -19,9 +19,10 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '..'; async function run() { + // https://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_1080p_h264.mov const urls = [ 'file:../../Media/big_buck_bunny_1080p_h264.mov' ]; const spec = { start: 0, end: 24 }; @@ -36,7 +37,7 @@ async function run() { { name: 'h264', time_base: [1, 90000], codecpar: { width: 1280, height: 720, format: 'yuv422p', color_space: 'bt709', - sample_aspect_ratio: [1, 1] + sample_aspect_ratio: [1, 1] as [number, number] } } ] diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index f929fb1..ad15c7d 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -32,7 +32,8 @@ import { Stream } from './types/Stream'; import { DecodedFrames } from './types/DecodedFrames'; import type { Governor } from './types/Governor'; import { Frame } from './types/Frame'; -import { Packet, Timing } from './types/Packet'; +import { Packet } from './types/Packet'; +import { Timing } from './types/Timing'; import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, WritableDemuxerStream } from './types/Beamstreams'; import { Filterer, FilterLink } from './types/Filter'; @@ -335,9 +336,9 @@ export async function makeSources(params: BeamstreamParams): Promise { src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options as any }); // FIXME })); - await (params.video.reduce as any)(async (promise, p) => { // FIXME + await (params.video.reduce as any)(async (promise: Promise, p: BeamstreamChannel) => { // FIXME await promise; - return p.sources.reduce(async (promise, src) => { + return p.sources.reduce(async (promise: Promise, src) => { await promise; src.format = await src.format; if (src.ms && !src.input_stream) @@ -346,9 +347,9 @@ export async function makeSources(params: BeamstreamParams): Promise { }, Promise.resolve()); }, Promise.resolve()); - await (params.audio.reduce as any)(async (promise, p) => { + await (params.audio.reduce as any)(async (promise: Promise, p: BeamstreamChannel) => { await promise; - return p.sources.reduce(async (promise, src) => { + return p.sources.reduce(async (promise: Promise, src) => { await promise; src.format = await src.format; if (src.ms && !src.input_stream) @@ -393,10 +394,13 @@ function runStreams( const streamTee = teeBalancer({ name: 'streamTee', highWaterMark: 1 }, streams.length); const filtStream = transformStream({ name: 'filter', highWaterMark: 1 }, frms => { + debugger; + // @ts-ignore if (filterer.cb) filterer.cb(frms[0].frames[0].pts); return filterer.filter(frms); }, () => { }, reject); const streamSource = writeStream({ name: 'streamSource', highWaterMark: 1 }, + // @ts-ignore frms => streamTee.pushFrames(frms), () => streamTee.pushFrames([], true), reject); filterBalancer.pipe(filtStream).pipe(streamSource); diff --git a/ts/install_ffmpeg.ts b/ts/install_ffmpeg.ts index 9834c7e..b35f528 100644 --- a/ts/install_ffmpeg.ts +++ b/ts/install_ffmpeg.ts @@ -23,11 +23,11 @@ import os from 'os'; import fs from 'fs'; import util from 'util'; import https from 'https'; -import cp from 'child_process'; +import child_process, { ChildProcess } from 'child_process'; const { mkdir, access, rename } = fs.promises; -const [ execFile, exec ] = [ cp.execFile, cp.exec ].map(util.promisify); +const [ execFile, exec ] = [ child_process.execFile, child_process.exec ].map(util.promisify); async function get(ws: NodeJS.WritableStream, url: string, name: string): Promise { let received = 0; @@ -179,8 +179,8 @@ sudo apt-get install libavcodec-dev libavformat-dev libavdevice-dev libavfilter- async function darwin(): Promise<0> { console.log('Checking for FFmpeg dependencies via HomeBrew.'); - let output; - let returnMessage; + let output: ChildProcess; + let returnMessage: string; try { output = await exec('brew list ffmpeg'); diff --git a/ts/teeBalancer.ts b/ts/teeBalancer.ts index ac275d3..722ac0d 100644 --- a/ts/teeBalancer.ts +++ b/ts/teeBalancer.ts @@ -1,15 +1,21 @@ import { Readable } from "stream"; +import { Frame } from "./types/Frame"; +import { Timing } from "./types/Timing"; -type teeBalancerType = Readable[] & { pushFrames: (frames: any, unusedFlag?: boolean) => any }; +type BalanceResult = { value: { timings: Timing }, done: boolean, final?: boolean }; + +type teeBalancerType = Readable[] & { pushFrames: (frames: Frame[] & {timings: Timing}, unusedFlag?: boolean) => any }; export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, numStreams: number): teeBalancerType { - let resolvePush = null; - const pending = []; + debugger; + let resolvePush: (result?: BalanceResult) => void = null; + const pending: Array<{ frames: Frame, resolve: (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 => { - return new Promise<{ done: boolean, value?: any }>(resolve => { + const pullFrame = async (index: number) => { + + return new Promise<{ done: boolean, value?: Frame }>(resolve => { if (pending[index].frames) { resolve({ value: pending[index].frames, done: false }); Object.assign(pending[index], { frames: null, resolve: null }); @@ -38,7 +44,8 @@ export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number if (result.done) this.push(null); else { - result.value.timings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; + // @ts-ignore + result.value.timings[params.name] = { reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; this.push(result.value); } })(); @@ -46,10 +53,11 @@ export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number })); readStreams.pushFrames = frames => { - return new Promise<{ value: { timings: any }, done: boolean }>(resolve => { + return new Promise(resolve => { pending.forEach((p, index) => { if (frames.length) - p.frames = frames[index].frames; + // @ts-ignore + p.frames = frames[index].frames; else p.final = true; }); @@ -57,6 +65,7 @@ export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number pending.forEach(p => { if (p.resolve) { if (p.frames) { + // @ts-ignore p.frames.timings = frames.timings; p.resolve({ value: p.frames, done: false }); } else if (p.final) diff --git a/ts/types/Beamstreams.d.ts b/ts/types/Beamstreams.d.ts index 44ab5df..5ecc542 100644 --- a/ts/types/Beamstreams.d.ts +++ b/ts/types/Beamstreams.d.ts @@ -51,7 +51,7 @@ export interface BeamstreamSource { iformat?: InputFormat options?: { [key: string]: any } format?: Demuxer | Promise; - stream: any; // FIXME + stream?: any; // FIXME decoder?: Decoder; // FIXME } diff --git a/ts/types/Packet.d.ts b/ts/types/Packet.d.ts index 8dfcf5c..623694c 100644 --- a/ts/types/Packet.d.ts +++ b/ts/types/Packet.d.ts @@ -9,11 +9,6 @@ * (e.g. to update some stream parameters at the end of encoding). */ -export interface Timing { - reqTime: number; - elapsed: number; -} - export interface PacketFlags { /** The packet contains a keyframe */ KEY: boolean diff --git a/ts/types/Timing.d.ts b/ts/types/Timing.d.ts new file mode 100644 index 0000000..cf71dd7 --- /dev/null +++ b/ts/types/Timing.d.ts @@ -0,0 +1,4 @@ +export interface Timing { + reqTime: number; + elapsed: number; +} From 875975aa7173cf1efee324785e83d0a36220ae95 Mon Sep 17 00:00:00 2001 From: urielCh Date: Wed, 20 Apr 2022 19:28:19 +0300 Subject: [PATCH 22/68] add link --- scratch/stream_mp4.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/scratch/stream_mp4.ts b/scratch/stream_mp4.ts index 114947f..8fc8087 100644 --- a/scratch/stream_mp4.ts +++ b/scratch/stream_mp4.ts @@ -23,6 +23,7 @@ import beamcoder from '..'; async function run() { // https://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_1080p_h264.mov + // http://www.legittorrents.info/index.php?page=torrent-details&id=7f34612e0fac5e7b051b78bdf1060113350ebfe0 const urls = [ 'file:../../Media/big_buck_bunny_1080p_h264.mov' ]; const spec = { start: 0, end: 24 }; From b568caf52e633927385d4d116ac2cd5dbd4cbd4d Mon Sep 17 00:00:00 2001 From: UrielCh Date: Wed, 20 Apr 2022 20:23:55 +0300 Subject: [PATCH 23/68] commit launcher --- .gitignore | 2 +- .vscode/launch.json | 118 ++++++++++++++++++++++++++++++++++++++ ts/types/Beamstreams.d.ts | 2 +- 3 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore index af00bd2..fb2b499 100644 --- a/.gitignore +++ b/.gitignore @@ -72,9 +72,9 @@ ffmpeg/ # Editors and IDE's *.swp -.vscode/ beamstreams.js index.js install_ffmpeg.js ts/*.js ts/*.d.ts +examples/capture \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..cdcbc22 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,118 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "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/ts/types/Beamstreams.d.ts b/ts/types/Beamstreams.d.ts index 44ab5df..bc79ae7 100644 --- a/ts/types/Beamstreams.d.ts +++ b/ts/types/Beamstreams.d.ts @@ -14,7 +14,7 @@ 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 + * @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. */ From a0a195dcb8916ad467759264b0406c90db727e97 Mon Sep 17 00:00:00 2001 From: urielCh Date: Thu, 21 Apr 2022 08:32:18 +0300 Subject: [PATCH 24/68] TS --- .vscode/launch.json | 17 +++++ package.json | 1 + pnpm-lock.yaml | 2 + scratch/stream_mp4.ts | 2 +- ts/beamstreams.ts | 130 +++++++++++++++++++----------------- ts/parallelBalancer.ts | 10 +-- ts/serialBalancer.ts | 2 +- ts/teeBalancer.ts | 1 - ts/types/DecodedFrames.d.ts | 9 +-- ts/types/Packet.d.ts | 10 +-- ts/types/Stream.d.ts | 2 +- ts/types/Timable.d.ts | 7 ++ 12 files changed, 112 insertions(+), 81 deletions(-) create mode 100644 ts/types/Timable.d.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index cdcbc22..edc2042 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,6 +1,23 @@ { "version": "0.2.0", "configurations": [ + { + "name": "scratch stream_mp4", + "type": "node", + "request": "launch", + "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], + "args": [ "./scratch/stream_mp4.ts" ], + "cwd": "${workspaceFolder}", + "internalConsoleOptions": "openOnSessionStart", + // "skipFiles": ["/**", "node_modules/**"], + "skipFiles": [ + "/**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, { "name": "Launch streamTest", "type": "node", diff --git a/package.json b/package.json index 947c731..3c02cbd 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@types/node": "^17.0.24", "@types/tape": "^4.13.2", "eslint": "^8.9.0", + "rimraf": "^3.0.2", "tape": "^5.5.3", "ts-node": "^10.7.0", "typescript": "^4.6.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 728ddab..1347755 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,7 @@ specifiers: '@types/tape': ^4.13.2 bindings: ^1.5.0 eslint: ^8.9.0 + rimraf: ^3.0.2 segfault-handler: ^1.3.0 tape: ^5.5.3 ts-node: ^10.7.0 @@ -20,6 +21,7 @@ devDependencies: '@types/node': 17.0.24 '@types/tape': 4.13.2 eslint: 8.13.0 + rimraf: 3.0.2 tape: 5.5.3 ts-node: 10.7.0_17a82b5ac88a5de7094eac76b4edda13 typescript: 4.6.3 diff --git a/scratch/stream_mp4.ts b/scratch/stream_mp4.ts index 8fc8087..6cadf33 100644 --- a/scratch/stream_mp4.ts +++ b/scratch/stream_mp4.ts @@ -19,7 +19,7 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '..'; +import beamcoder from '../ts/index'; async function run() { // https://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_1080p_h264.mov diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index ad15c7d..e18a063 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -35,34 +35,37 @@ import { Frame } from './types/Frame'; import { Packet } from './types/Packet'; import { Timing } from './types/Timing'; import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, WritableDemuxerStream } from './types/Beamstreams'; -import { Filterer, FilterLink } from './types/Filter'; +import { Filterer, FilterGraph, FilterLink } from './types/Filter'; +import { CodecContextBaseMin } from './types/CodecContext'; +import { EncodedPackets, Encoder } from './types/Encoder'; +import { Timable, Timables } from './types/Timable' const beamcoder = bindings('beamcoder') as BeamcoderType; const doTimings = false; -const timings = [] as Array<{[key: string]: Timing}>; +const timings = [] as Array<{ [key: string]: Timing }>; class frameDicer { private addFrame: (srcFrm: Frame) => any[]; private getLast: () => any[]; private doDice: boolean; - - constructor(encoder: CodecPar, private isAudio: boolean) { + // CodecPar + constructor(encoder: CodecContextBaseMin, private isAudio: boolean) { let sampleBytes = 4; // Assume floating point 4 byte samples for now... const numChannels = encoder.channels; const dstNumSamples = encoder.frame_size; let dstFrmBytes = dstNumSamples * sampleBytes; this.doDice = false === beamcoder.encoders()[encoder.name].capabilities.VARIABLE_FRAME_SIZE; - let lastFrm: Frame = null as any as Frame; + let lastFrm: Frame = null as Frame; let lastBuf: Buffer[] = []; const nullBuf: Buffer[] = []; for (let b = 0; b < numChannels; ++b) nullBuf.push(Buffer.alloc(0)); - this.addFrame = (srcFrm: Frame): any[] => { - let result = []; + this.addFrame = (srcFrm: Frame): Frame[] => { + let result: Frame[] = []; let dstFrm: Frame; let curStart = 0; if (!lastFrm) { @@ -89,13 +92,13 @@ class frameDicer { dstFrm.pts += dstNumSamples; dstFrm.pkt_dts += dstNumSamples; curStart += dstFrmBytes - lastBuf[0].length; - (lastFrm as Frame).pts = 0; - (lastFrm as Frame).pkt_dts = 0; + lastFrm.pts = 0; + lastFrm.pkt_dts = 0; lastBuf = nullBuf; } - (lastFrm as Frame).pts = dstFrm.pts; - (lastFrm as Frame).pkt_dts = dstFrm.pkt_dts; + lastFrm.pts = dstFrm.pts; + lastFrm.pkt_dts = dstFrm.pkt_dts; lastBuf = srcFrm.data.map(d => d.slice(curStart, srcFrm.nb_samples * sampleBytes)); return result; @@ -115,12 +118,12 @@ class frameDicer { return result; }; } - public dice(frames: Frame[], flush = false): any { + public dice(frames: Frame[], flush = false): Frame[] { if (this.isAudio && this.doDice) { - let result = frames.reduce((muxFrms, frm) => { + let result: Frame[] = frames.reduce((muxFrms: Frame[], frm: Frame) => { this.addFrame(frm).forEach(f => muxFrms.push(f)); return muxFrms; - }, []); + }, [] as Frame[]); if (flush) this.getLast().forEach(f => result.push(f)); @@ -130,16 +133,17 @@ class frameDicer { return frames; }; } - -function transformStream( - params: { name: 'encode' | 'dice' | 'decode' | 'filter', highWaterMark: number }, - processFn: (val: Packet) => Promise, - flushFn: () => (Promise) | null | void, +// T = Frame | Frame[] | Packet +// D = Promise, DecodedFrames +function transformStream( + params: { name: 'encode' | 'dice' | 'decode' | 'filter', highWaterMark: number }, + processFn: (val: T) => D, + flushFn: () => D | null | void, reject: (err?: Error) => void) { return new Transform({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - transform(val: Packet, encoding, cb) { + transform(val: T, encoding, cb) { (async () => { const start = process.hrtime(); const reqTime = start[0] * 1e3 + start[1] / 1e6; @@ -153,7 +157,7 @@ function transformStream( flush(cb) { (async () => { const result = flushFn ? await flushFn() : null; - if (result) result.timings = {}; + if (result) (result as any).timings = {}; cb(null, result); })().catch(cb); } @@ -190,18 +194,18 @@ function writeStream(params: { name: string, highWaterMark?: number }, processFn (async () => { const result = finalFn ? await finalFn() : null; if (doTimings && ('mux' === params.name)) { - const elapsedStats = {} as {[key: string]: ffStats}; + 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 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}; + 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); @@ -228,7 +232,7 @@ function writeStream(params: { name: string, highWaterMark?: number }, processFn // } -function readStream(params: {highWaterMark?: number}, demuxer: Demuxer, ms: { end: number }, index: number) { +function readStream(params: { highWaterMark?: number }, demuxer: Demuxer, ms: { end: number }, index: number) { 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 { @@ -318,8 +322,7 @@ export function muxerStream(params: { highwaterMark: number }): muxerStreamType export async function makeSources(params: BeamstreamParams): Promise { if (!params.video) params.video = []; if (!params.audio) params.audio = []; - debugger; - params.video.forEach(p => p.sources.forEach(src => { + params.video.forEach(p => p.sources.forEach((src: BeamstreamSource) => { if (src.input_stream) { const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 }); src.input_stream.pipe(demuxerStream); @@ -327,7 +330,7 @@ export async function makeSources(params: BeamstreamParams): Promise { } else src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options as any }); // FIXME })); - params.audio.forEach(p => p.sources.forEach(src => { + params.audio.forEach(p => p.sources.forEach((src: BeamstreamSource) => { if (src.input_stream) { const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 }); src.input_stream.pipe(demuxerStream); @@ -336,9 +339,9 @@ export async function makeSources(params: BeamstreamParams): Promise { src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options as any }); // FIXME })); - await (params.video.reduce as any)(async (promise: Promise, p: BeamstreamChannel) => { // FIXME + await (params.video.reduce)(async (promise: Promise, p: BeamstreamChannel) => { // FIXME await promise; - return p.sources.reduce(async (promise: Promise, src) => { + return p.sources.reduce(async (promise: Promise, src: BeamstreamSource) => { await promise; src.format = await src.format; if (src.ms && !src.input_stream) @@ -346,10 +349,10 @@ export async function makeSources(params: BeamstreamParams): Promise { return src.format; }, Promise.resolve()); }, Promise.resolve()); - + await (params.audio.reduce as any)(async (promise: Promise, p: BeamstreamChannel) => { await promise; - return p.sources.reduce(async (promise: Promise, src) => { + return p.sources.reduce(async (promise: Promise, src: BeamstreamSource) => { await promise; src.format = await src.format; if (src.ms && !src.input_stream) @@ -358,9 +361,9 @@ export async function makeSources(params: BeamstreamParams): Promise { }, Promise.resolve()); }, Promise.resolve()); - params.video.forEach(p => p.sources.forEach(src => + params.video.forEach((p: BeamstreamChannel) => p.sources.forEach(src => src.stream = readStream({ highWaterMark: 1 }, src.format as Demuxer, src.ms, src.streamIndex))); - params.audio.forEach(p => p.sources.forEach(src => + params.audio.forEach((p: BeamstreamChannel) => p.sources.forEach(src => src.stream = readStream({ highWaterMark: 1 }, src.format as Demuxer, src.ms, src.streamIndex))); } @@ -368,20 +371,20 @@ function runStreams( streamType: 'video' | 'audio', // sources: Array<{ decoder: { decode: (pkts: any) => any, flush: () => any }, format: { streams: Array<{}> }, streamIndex: any, stream: any }>, sources: Array, - filterer: { cb?: (result: any) => void, filter: (stream: any) => any }, - streams : Array<{}>, - mux: {writeFrame: (pkts: any)=> void}, + filterer: { cb?: (result: any) => void, filter: (stream: Array) => any, graph: FilterGraph }, // Filterer? + streams: Array, + mux: { writeFrame: (pkts: any) => void }, muxBalancer: serialBalancer) { - // serialBalancer // { writePkts: (packets: {timings: any; }, srcStream: {}, dstStream: {}, writeFn: {}, final?: boolean) => any } + // serialBalancer // { writePkts: (packets: {timings: any; }, srcStream: {}, dstStream: {}, writeFn: {}, final?: boolean) => any } return new Promise((resolve, reject) => { if (!sources.length) return resolve(); - const timeBaseStream: any = (sources[0].format as Demuxer).streams[sources[0].streamIndex]; + const timeBaseStream: Stream = (sources[0].format as Demuxer).streams[sources[0].streamIndex]; const filterBalancer = parallelBalancer({ name: 'filterBalance', highWaterMark: 1 }, streamType, sources.length); - sources.forEach((src, srcIndex: number) => { - const decStream = transformStream( + sources.forEach((src: BeamstreamSource, srcIndex: number) => { + const decStream = transformStream & Timable>( { name: 'decode', highWaterMark: 1 }, (pkts: Packet) => src.decoder.decode(pkts), () => src.decoder.flush(), reject); @@ -393,8 +396,7 @@ function runStreams( }); const streamTee = teeBalancer({ name: 'streamTee', highWaterMark: 1 }, streams.length); - const filtStream = transformStream({ name: 'filter', highWaterMark: 1 }, frms => { - debugger; + const filtStream = transformStream({ name: 'filter', highWaterMark: 1 }, (frms: Timables) => { // @ts-ignore if (filterer.cb) filterer.cb(frms[0].frames[0].pts); return filterer.filter(frms); @@ -404,15 +406,17 @@ function runStreams( frms => streamTee.pushFrames(frms), () => streamTee.pushFrames([], true), reject); filterBalancer.pipe(filtStream).pipe(streamSource); - - streams.forEach((str: { encoder: any, stream: any }, i) => { + streams.forEach((str: BeamstreamStream, i: number) => { const dicer = new frameDicer(str.encoder, 'audio' === streamType); - const diceStream = transformStream({ name: 'dice', highWaterMark: 1 }, - frms => dicer.dice(frms as any), () => dicer.dice([], true), reject); - const encStream = transformStream({ name: 'encode', highWaterMark: 1 }, - frms => str.encoder.encode(frms), () => str.encoder.flush(), reject); + + const diceStream = transformStream, Timables>({ name: 'dice', highWaterMark: 1 }, + (frms) => dicer.dice(frms), () => dicer.dice([], true), reject); + + const encStream = transformStream, any>({ name: 'encode', highWaterMark: 1 }, + (frms) => str.encoder.encode(frms), () => str.encoder.flush(), reject); + const muxStream = writeStream({ name: 'mux', highWaterMark: 1 }, - pkts => muxBalancer.writePkts(pkts as any, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts)), + (pkts: any) => 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); @@ -426,13 +430,13 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr p.sources.forEach((src: BeamstreamSource) => src.decoder = beamcoder.decoder({ demuxer: src.format as Demuxer, stream_index: src.streamIndex })); }); - params.audio.forEach(p => { - p.sources.forEach(src => + params.audio.forEach((p: BeamstreamChannel) => { + p.sources.forEach((src: BeamstreamSource) => src.decoder = beamcoder.decoder({ demuxer: src.format as Demuxer, stream_index: src.streamIndex })); - // {demuxer: Demuxer | Promise, stream_index: number} + // {demuxer: Demuxer | Promise, stream_index: number} }); - params.video.forEach(p => { + params.video.forEach((p: BeamstreamChannel) => { p.filter = beamcoder.filterer({ // FiltererVideoOptions filterType: 'video', @@ -447,18 +451,18 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr pixelAspect: stream.sample_aspect_ratio }; }), - outputParams: p.streams.map((str, i) => { return { name: `out${i}:v`, pixelFormat: str.codecpar.format }; }), + outputParams: p.streams.map((str: BeamstreamStream, i: number) => ({ 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: BeamstreamChannel, i: number) => p.filter = vidFilts[i]); // params.video.forEach(p => console.log(p.filter.graph.dump())); - params.audio.forEach(p => { + params.audio.forEach((p: BeamstreamChannel) => { p.filter = beamcoder.filterer({ filterType: 'audio', - inputParams: p.sources.map((src, i) => { + inputParams: p.sources.map((src: BeamstreamSource, i: number) => { const stream = (src.format as Demuxer).streams[src.streamIndex]; return { name: `in${i}:a`, @@ -468,7 +472,7 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr timeBase: stream.time_base }; }), - outputParams: p.streams.map((str, i) => { + outputParams: p.streams.map((str: BeamstreamStream, i: number) => { return { name: `out${i}:a`, sampleRate: str.codecpar.sample_rate, @@ -479,8 +483,8 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr filterSpec: p.filterSpec }); }); - const audFilts = await Promise.all(params.audio.map(p => p.filter)); - params.audio.forEach((p, i) => p.filter = audFilts[i]); + const audFilts = await Promise.all(params.audio.map((p: BeamstreamChannel) => p.filter)); + params.audio.forEach((p: BeamstreamChannel, i: number) => p.filter = audFilts[i]); // params.audio.forEach(p => console.log(p.filter.graph.dump())); let mux: Muxer; @@ -536,8 +540,8 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr }); }); - params.audio.forEach(p => { - p.streams.forEach(str => { + params.audio.forEach((p: BeamstreamChannel) => { + p.streams.forEach((str: BeamstreamStream) => { str.stream = mux.newStream({ name: str.name, time_base: str.time_base, diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts index 951c3a4..8dcef99 100644 --- a/ts/parallelBalancer.ts +++ b/ts/parallelBalancer.ts @@ -1,4 +1,5 @@ import { Readable } from "stream"; +import { Frame } from './types/Frame'; type parallelBalancerType = Readable & { pushPkts: (packets, stream, streamIndex: number, final?: boolean) => any @@ -13,8 +14,9 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } for (let s = 0; s < numStreams; ++s) pending.push({ ts: -Number.MAX_VALUE, streamIndex: s }); - const makeSet = resolve => { - if (resolve) { + // const makeSet = (resolve: (result: {value?: { name: string, frames: any[] }, done: boolean}) => void) => { + const makeSet = (resolve: (result: {value?: any, done: boolean}) => void) => { + 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); @@ -37,13 +39,13 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } } }; - const pushPkt = async (pkt, streamIndex: number, ts: number) => + const pushPkt = async (pkt: Frame, streamIndex: number, ts: number): Promise<{ pkt, ts, final: boolean, resolve }> => new Promise(resolve => { Object.assign(pending[streamIndex], { pkt, ts, final: pkt ? false : true, resolve }); makeSet(resolveGet); }); - const pullSet = async () => new Promise<{ done: any, value: { timings: any } }>(resolve => makeSet(resolve)); + const pullSet = async () => new Promise<{ done: any, value: { timings: any } }>(resolve => makeSet(resolve as any)); const readStream: parallelBalancerType = new Readable({ objectMode: true, diff --git a/ts/serialBalancer.ts b/ts/serialBalancer.ts index e34743a..26145a2 100644 --- a/ts/serialBalancer.ts +++ b/ts/serialBalancer.ts @@ -1,5 +1,5 @@ export class serialBalancer { - pending = [] as { ts:number, streamIndex: number, resolve?: (result: any) => void, pkt?: any }[]; + pending = [] as { ts: number, streamIndex: number, resolve?: (result: any) => void, pkt?: any }[]; constructor(numStreams: number) { // initialise with negative ts and no pkt diff --git a/ts/teeBalancer.ts b/ts/teeBalancer.ts index 722ac0d..935e8b2 100644 --- a/ts/teeBalancer.ts +++ b/ts/teeBalancer.ts @@ -7,7 +7,6 @@ type BalanceResult = { value: { timings: Timing }, done: boolean, final?: boolea type teeBalancerType = Readable[] & { pushFrames: (frames: Frame[] & {timings: Timing}, unusedFlag?: boolean) => any }; export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, numStreams: number): teeBalancerType { - debugger; let resolvePush: (result?: BalanceResult) => void = null; const pending: Array<{ frames: Frame, resolve: (result: { value?: Frame, done: boolean }) => void, final: boolean }> = []; for (let s = 0; s < numStreams; ++s) diff --git a/ts/types/DecodedFrames.d.ts b/ts/types/DecodedFrames.d.ts index fa06a38..14845f0 100644 --- a/ts/types/DecodedFrames.d.ts +++ b/ts/types/DecodedFrames.d.ts @@ -1,5 +1,5 @@ /** The DecodedFrames object is returned as the result of a decode operation */ -export interface DecodedFrames { +export interface DecodedFrames extends Timable { /** Object name. */ readonly type: 'frames' /** @@ -10,9 +10,6 @@ export interface DecodedFrames { /** Total time in microseconds that the decode operation took to complete */ readonly total_time: number - - timings?: { - // "encode" | "dice" | "decode" | "filter" - [key: string]: Timing; }; - + // "encode" | "dice" | "decode" | "filter" + // timings?: { [key: string]: Timing; }; } diff --git a/ts/types/Packet.d.ts b/ts/types/Packet.d.ts index 623694c..e174a3b 100644 --- a/ts/types/Packet.d.ts +++ b/ts/types/Packet.d.ts @@ -9,6 +9,8 @@ * (e.g. to update some stream parameters at the end of encoding). */ +import { Timable } from "./Timable" + export interface PacketFlags { /** The packet contains a keyframe */ KEY: boolean @@ -34,7 +36,7 @@ export interface PacketFlags { DISPOSABLE: boolean // Frames that can be discarded by the decoder } -export interface Packet { +export interface Packet extends Timable { /** Object name. */ readonly type: 'Packet' @@ -82,8 +84,8 @@ export interface Packet { duration: number /** byte position in stream, -1 if unknown */ pos: number, - timings: { - read?: Timing; - }; + // timings: { + // read?: Timing; + // }; } diff --git a/ts/types/Stream.d.ts b/ts/types/Stream.d.ts index 256f81f..343d2a6 100644 --- a/ts/types/Stream.d.ts +++ b/ts/types/Stream.d.ts @@ -75,7 +75,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 diff --git a/ts/types/Timable.d.ts b/ts/types/Timable.d.ts new file mode 100644 index 0000000..672ea4c --- /dev/null +++ b/ts/types/Timable.d.ts @@ -0,0 +1,7 @@ +import { Timing } from './Timing' + +export interface Timable { + timings?: { [key: string]: Timing; }; +} + +export type Timables = Array & Timable; \ No newline at end of file From ae6547aa3272638fa0ca476fde6312c73d0113e3 Mon Sep 17 00:00:00 2001 From: urielch Date: Thu, 21 Apr 2022 10:51:44 -0700 Subject: [PATCH 25/68] 'TS' --- .vscode/launch.json | 22 ++++++++++++++- examples/streamTest.ts | 31 ++++++++++++++++++++++ scratch/{decode_aac.js => decode_aac.ts} | 5 ++-- scratch/{decode_avci.js => decode_avci.ts} | 5 ++-- scratch/{decode_hevc.js => decode_hevc.ts} | 2 +- scratch/{decode_pcm.js => decode_pcm.ts} | 3 ++- scratch/{make_a_mux.js => make_a_mux.ts} | 5 ++-- scratch/{muxer.js => muxer.ts} | 4 ++- scratch/{read_wav.js => read_wav.ts} | 2 +- scratch/{simple_mux.js => simple_mux.ts} | 5 ++-- scratch/{stream_avci.js => stream_avci.ts} | 11 +++++--- scratch/{stream_mux.js => stream_mux.ts} | 7 ++--- scratch/{stream_pcm.js => stream_pcm.ts} | 9 ++++--- scratch/{stream_wav.js => stream_wav.ts} | 2 +- ts/beamstreams.ts | 4 +-- ts/types/Beamstreams.d.ts | 2 +- 16 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 examples/streamTest.ts rename scratch/{decode_aac.js => decode_aac.ts} (92%) rename scratch/{decode_avci.js => decode_avci.ts} (94%) rename scratch/{decode_hevc.js => decode_hevc.ts} (96%) rename scratch/{decode_pcm.js => decode_pcm.ts} (96%) rename scratch/{make_a_mux.js => make_a_mux.ts} (95%) rename scratch/{muxer.js => muxer.ts} (96%) rename scratch/{read_wav.js => read_wav.ts} (96%) rename scratch/{simple_mux.js => simple_mux.ts} (92%) rename scratch/{stream_avci.js => stream_avci.ts} (94%) rename scratch/{stream_mux.js => stream_mux.ts} (91%) rename scratch/{stream_pcm.js => stream_pcm.ts} (93%) rename scratch/{stream_wav.js => stream_wav.ts} (97%) diff --git a/.vscode/launch.json b/.vscode/launch.json index edc2042..06a841b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,13 +1,33 @@ { "version": "0.2.0", "configurations": [ + + { + "name": "scratch stream_avci", + "type": "node", + "request": "launch", + "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], + "args": [ "./scratch/stream_avci.ts" ], + "cwd": "${workspaceFolder}/scratch/", + "internalConsoleOptions": "openOnSessionStart", + // "skipFiles": ["/**", "node_modules/**"], + "skipFiles": [ + "/**" + ], + "env": { + "TS_NODE_PROJECT": "${workspaceFolder}/tsconfig.json", + "DEBUG": "*" + } + }, + + { "name": "scratch stream_mp4", "type": "node", "request": "launch", "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], "args": [ "./scratch/stream_mp4.ts" ], - "cwd": "${workspaceFolder}", + "cwd": "${workspaceFolder}/scratch/", "internalConsoleOptions": "openOnSessionStart", // "skipFiles": ["/**", "node_modules/**"], "skipFiles": [ diff --git a/examples/streamTest.ts b/examples/streamTest.ts new file mode 100644 index 0000000..9192ab1 --- /dev/null +++ b/examples/streamTest.ts @@ -0,0 +1,31 @@ +import beamcoder from '..'; // Use require('beamcoder') externally +import path, { resolve } from 'path'; +import fs from 'fs'; + + +const delay = (ms: number) => new Promise((resolve) => { setTimeout(resolve, ms)}) + + +async function process() { +} + +async function run() { + debugger; + const stream = beamcoder.demuxerStream({highwaterMark: 360000}); + console.log(stream); + console.log('stream'); + const demuxPromise = stream.demuxer({}) + demuxPromise.then(demux => console.log(demux)); + const src = path.join(__dirname, 'capture'); + const filelist = fs.readdirSync(src); + filelist.sort(); + for (const f of filelist) { + const fullname = path.join(src, f); + const buf = fs.readFileSync(fullname); + // console.log(fullname); + stream.write(buf); + await delay(100); + } +} + +run(); diff --git a/scratch/decode_aac.js b/scratch/decode_aac.ts similarity index 92% rename from scratch/decode_aac.js rename to scratch/decode_aac.ts index 2264f3a..b705fec 100644 --- a/scratch/decode_aac.js +++ b/scratch/decode_aac.ts @@ -19,12 +19,13 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '../ts/index'; +import { Packet } from '../ts/types/Packet'; 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 = null; for ( let x = 0 ; packet !== null && x < 100 ; x++ ) { packet = await demuxer.read(); if (packet.stream_index == 1) { diff --git a/scratch/decode_avci.js b/scratch/decode_avci.ts similarity index 94% rename from scratch/decode_avci.js rename to scratch/decode_avci.ts index 1a6365b..322a287 100644 --- a/scratch/decode_avci.js +++ b/scratch/decode_avci.ts @@ -19,7 +19,8 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '../ts/index'; +import { Packet } from '../ts/types/Packet'; async function run() { // let demuxer = await beamcoder.demuxer('../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); @@ -30,7 +31,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 = null; for ( let x = 0 ; x < 2000 && packet != null; x++ ) { packet = await demuxer.read(); if (packet && packet.stream_index === 0) { 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..6208cf9 100644 --- a/scratch/decode_hevc.js +++ b/scratch/decode_hevc.ts @@ -19,7 +19,7 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '../ts/index'; 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 96% rename from scratch/decode_pcm.js rename to scratch/decode_pcm.ts index 5be1d2e..7e18b26 100644 --- a/scratch/decode_pcm.js +++ b/scratch/decode_pcm.ts @@ -19,7 +19,7 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '../ts/index'; async function run() { let demuxer = await beamcoder.demuxer('../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); @@ -28,6 +28,7 @@ async function run() { console.log(decoder); for ( let x = 0 ; x < 100 ; x++ ) { let packet = await demuxer.read(); + // @ts-ignore if (packet.stream == 1) { //console.log(packet); let frames = await decoder.decode(packet); diff --git a/scratch/make_a_mux.js b/scratch/make_a_mux.ts similarity index 95% rename from scratch/make_a_mux.js rename to scratch/make_a_mux.ts index b9f42d8..ed1f69b 100644 --- a/scratch/make_a_mux.js +++ b/scratch/make_a_mux.ts @@ -19,7 +19,8 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '../ts/index'; +import { Packet } from '../ts/types/Packet'; async function run() { let demuxer = await beamcoder.demuxer('../media/sound/BBCNewsCountdown.wav'); @@ -57,7 +58,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: 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 96% rename from scratch/muxer.js rename to scratch/muxer.ts index 8883697..47614fa 100644 --- a/scratch/muxer.js +++ b/scratch/muxer.ts @@ -21,7 +21,7 @@ // Work in progress -const beamcoder = require('../index.js'); +import beamcoder from '../ts/index'; const STREAM_FRAME_RATE = 25; @@ -49,8 +49,10 @@ async function addStream(stream, muxer, codecID) { // eslint-disable-line 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 96% rename from scratch/read_wav.js rename to scratch/read_wav.ts index 965bfdc..eb9de74 100644 --- a/scratch/read_wav.js +++ b/scratch/read_wav.ts @@ -19,7 +19,7 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '../ts/index'; async function run() { let demuxer = await beamcoder.demuxer('../media/sound/BBCNewsCountdown.wav'); diff --git a/scratch/simple_mux.js b/scratch/simple_mux.ts similarity index 92% rename from scratch/simple_mux.js rename to scratch/simple_mux.ts index 3d3762f..8a736bd 100644 --- a/scratch/simple_mux.js +++ b/scratch/simple_mux.ts @@ -18,7 +18,8 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); +import beamcoder from '../ts/index'; +import { Packet } from '../ts/types/Packet'; async function run() { let demuxer = await beamcoder.demuxer('../media/sound/BBCNewsCountdown.wav'); @@ -28,7 +29,7 @@ async function run() { // stream.codecpar = demuxer.streams[0].codecpar; await muxer.openIO(); await muxer.writeHeader(); - let packet = {}; + let packet: Packet = null; 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 94% rename from scratch/stream_avci.js rename to scratch/stream_avci.ts index 622f6f4..a0a049b 100644 --- a/scratch/stream_avci.js +++ b/scratch/stream_avci.ts @@ -19,12 +19,14 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); -const fs = require('fs'); +import beamcoder from '../ts/index'; +import fs from 'fs'; +import { Packet } from '../ts/types/Packet'; // const util = require('util'); async function run() { // let demuxer = await createDemuxer('../../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); + // http://ftp.kw.bbc.co.uk/dppdownload/dpp_example_files/AS11_DPP_HD_EXAMPLE_1.mxf let demuxerStream = beamcoder.demuxerStream({ highwaterMark: 65536 }); fs.createReadStream('../../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf').pipe(demuxerStream); let demuxer = await demuxerStream.demuxer({}); @@ -100,7 +102,7 @@ async function run() { // await demuxer.seek({ frame: 4200, stream_index: 0}); - let packet = {}; + let packet: Packet | null = null; for ( let x = 0 ; x < 10 && packet !== null; x++ ) { packet = await demuxer.read(); if (packet.stream_index == 0) { @@ -120,7 +122,8 @@ async function run() { } let frames = await decoder.flush(); console.log('flush', frames.total_time, frames.frames.length); - + debugger; + // @ts-ignore demuxerStream.destroy(); } diff --git a/scratch/stream_mux.js b/scratch/stream_mux.ts similarity index 91% rename from scratch/stream_mux.js rename to scratch/stream_mux.ts index c876fe6..2765586 100644 --- a/scratch/stream_mux.js +++ b/scratch/stream_mux.ts @@ -19,8 +19,9 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -const beamcoder = require('../index.js'); -const fs = require('fs'); +import beamcoder from '../ts/index'; +import fs from 'fs'; +import { Packet } from '../ts/types/Packet'; async function run() { let demuxer = await beamcoder.demuxer('../../media/sound/BBCNewsCountdown.wav'); @@ -35,7 +36,7 @@ async function run() { await muxer.openIO(); await muxer.writeHeader(); - let packet = {}; + let packet: Packet | null = null; 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 93% rename from scratch/stream_pcm.js rename to scratch/stream_pcm.ts index 771e30b..5955354 100644 --- a/scratch/stream_pcm.js +++ b/scratch/stream_pcm.ts @@ -19,9 +19,10 @@ 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 from '../ts/index'; +import fs from 'fs'; +import util from 'util'; // eslint-disable-line +import { Packet } from '../ts/types/Packet'; async function run() { let demuxerStream = beamcoder.demuxerStream({ highwaterMark: 65536 }); @@ -63,7 +64,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: Packet | null = null; 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..5cb33c1 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 '../ts/index'; async function run() { const urls = [ 'file:../Media/sound/Countdown.wav' ]; diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index e18a063..aa7ad34 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -318,7 +318,7 @@ export function muxerStream(params: { highwaterMark: number }): muxerStreamType }; return stream; } -// params: { video?: Array<{ sources: any[] }>, audio?: Array<{ sources: any[] }> } + export async function makeSources(params: BeamstreamParams): Promise { if (!params.video) params.video = []; if (!params.audio) params.audio = []; @@ -339,7 +339,7 @@ export async function makeSources(params: BeamstreamParams): Promise { src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options as any }); // FIXME })); - await (params.video.reduce)(async (promise: Promise, p: BeamstreamChannel) => { // FIXME + await (params.video.reduce)(async (promise: Promise, p: BeamstreamChannel) => { await promise; return p.sources.reduce(async (promise: Promise, src: BeamstreamSource) => { await promise; diff --git a/ts/types/Beamstreams.d.ts b/ts/types/Beamstreams.d.ts index b52731e..3aa7a11 100644 --- a/ts/types/Beamstreams.d.ts +++ b/ts/types/Beamstreams.d.ts @@ -18,7 +18,7 @@ export interface WritableDemuxerStream extends NodeJS.WritableStream { * format details by consuming data from the source. The promise will wait indefinitely * until sufficient source data has been read. */ - demuxer(options: DemuxerCreateOptions | string): Promise + demuxer(options?: DemuxerCreateOptions | string): Promise } /** From 4f3ecda17998e0e6236eaf4d7d6e5ed5279f0753 Mon Sep 17 00:00:00 2001 From: urielch Date: Thu, 21 Apr 2022 11:28:21 -0700 Subject: [PATCH 26/68] TS --- .vscode/launch.json | 4 ++-- scratch/decode_aac.ts | 2 +- scratch/decode_avci.ts | 2 +- scratch/make_a_mux.ts | 2 +- scratch/simple_mux.ts | 2 +- scratch/stream_avci.ts | 5 ++--- scratch/stream_mux.ts | 2 +- scratch/stream_pcm.ts | 2 +- ts/beamstreams.ts | 2 +- ts/types/Beamstreams.d.ts | 1 + 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 06a841b..5fd604e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "type": "node", "request": "launch", "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], - "args": [ "./scratch/stream_avci.ts" ], + "args": [ "./stream_avci.ts" ], "cwd": "${workspaceFolder}/scratch/", "internalConsoleOptions": "openOnSessionStart", // "skipFiles": ["/**", "node_modules/**"], @@ -26,7 +26,7 @@ "type": "node", "request": "launch", "runtimeArgs": [ "--nolazy", "-r", "ts-node/register" ], - "args": [ "./scratch/stream_mp4.ts" ], + "args": [ "./stream_mp4.ts" ], "cwd": "${workspaceFolder}/scratch/", "internalConsoleOptions": "openOnSessionStart", // "skipFiles": ["/**", "node_modules/**"], diff --git a/scratch/decode_aac.ts b/scratch/decode_aac.ts index b705fec..b648aba 100644 --- a/scratch/decode_aac.ts +++ b/scratch/decode_aac.ts @@ -25,7 +25,7 @@ import { Packet } from '../ts/types/Packet'; async function run() { let demuxer = await beamcoder.demuxer({ url: '../media/bbb_1080p_c.ts'}); let decoder = beamcoder.decoder({ name: 'aac' }); - let packet: Packet = null; + let packet: Packet = {} as Packet; for ( let x = 0 ; packet !== null && x < 100 ; x++ ) { packet = await demuxer.read(); if (packet.stream_index == 1) { diff --git a/scratch/decode_avci.ts b/scratch/decode_avci.ts index 322a287..b03cc50 100644 --- a/scratch/decode_avci.ts +++ b/scratch/decode_avci.ts @@ -31,7 +31,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: Packet = null; + let packet: Packet = {} as Packet; for ( let x = 0 ; x < 2000 && packet != null; x++ ) { packet = await demuxer.read(); if (packet && packet.stream_index === 0) { diff --git a/scratch/make_a_mux.ts b/scratch/make_a_mux.ts index ed1f69b..ff85fbf 100644 --- a/scratch/make_a_mux.ts +++ b/scratch/make_a_mux.ts @@ -58,7 +58,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: Packet; + let packet: Packet = {} as Packet; for ( let x = 0 ; x < 100 && packet !== null ; x++ ) { packet = await demuxer.read(); await muxer.writeFrame(packet); diff --git a/scratch/simple_mux.ts b/scratch/simple_mux.ts index 8a736bd..908c4d1 100644 --- a/scratch/simple_mux.ts +++ b/scratch/simple_mux.ts @@ -29,7 +29,7 @@ async function run() { // stream.codecpar = demuxer.streams[0].codecpar; await muxer.openIO(); await muxer.writeHeader(); - let packet: Packet = null; + let packet: 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.ts b/scratch/stream_avci.ts index a0a049b..7bda067 100644 --- a/scratch/stream_avci.ts +++ b/scratch/stream_avci.ts @@ -31,8 +31,7 @@ async function run() { 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]; @@ -102,7 +101,7 @@ async function run() { // await demuxer.seek({ frame: 4200, stream_index: 0}); - let packet: Packet | null = null; + let packet: Packet = {} as Packet; for ( let x = 0 ; x < 10 && packet !== null; x++ ) { packet = await demuxer.read(); if (packet.stream_index == 0) { diff --git a/scratch/stream_mux.ts b/scratch/stream_mux.ts index 2765586..ae2a784 100644 --- a/scratch/stream_mux.ts +++ b/scratch/stream_mux.ts @@ -36,7 +36,7 @@ async function run() { await muxer.openIO(); await muxer.writeHeader(); - let packet: Packet | null = null; + 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.ts b/scratch/stream_pcm.ts index 5955354..401d9f1 100644 --- a/scratch/stream_pcm.ts +++ b/scratch/stream_pcm.ts @@ -64,7 +64,7 @@ async function run() { // const abuffersink = filterer.graph.filters.find(f => 'abuffersink' === f.filter.name); // console.log(util.inspect(abuffersink, {depth: null})); - let packet: Packet | null = null; + let packet: 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/ts/beamstreams.ts b/ts/beamstreams.ts index aa7ad34..5520c51 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -236,7 +236,7 @@ function readStream(params: { highWaterMark?: number }, demuxer: Demuxer, ms: { 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 | null = null; + let packet: Packet = {} as Packet; do { packet = await demuxer.read(); } while (packet && packet.stream_index !== index); return packet; diff --git a/ts/types/Beamstreams.d.ts b/ts/types/Beamstreams.d.ts index 3aa7a11..b019da5 100644 --- a/ts/types/Beamstreams.d.ts +++ b/ts/types/Beamstreams.d.ts @@ -11,6 +11,7 @@ import { Encoder } from "./Encoder" * 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 { +// export interface WritableDemuxerStream implements WritableStream extends NodeJS.Writable { /** * Create a demuxer for this source * @param options a DemuxerCreateOptions object From ce490fa13ae432ea95ca1fc55d59c37df5a3f78e Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 25 Apr 2022 11:44:21 +0300 Subject: [PATCH 27/68] add missing import --- ts/types/Decoder.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ts/types/Decoder.d.ts b/ts/types/Decoder.d.ts index b231b81..7856184 100644 --- a/ts/types/Decoder.d.ts +++ b/ts/types/Decoder.d.ts @@ -4,6 +4,7 @@ import { Frame } from "./Frame" import { Codec } from "./Codec" import { CodecContext, CodecContext_base } from "./CodecContext" import { Demuxer } from "./Demuxer" +import { DecodedFrames } from "./DecodedFrames" export interface Decoder extends CodecContext_base { // readonly type: 'decoder' From c245c59a7ff940ed0fcd5d8ef96b4a37350b3546 Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 25 Apr 2022 14:36:30 +0300 Subject: [PATCH 28/68] improve types --- .gitignore | 4 +- .vscode/launch.json | 104 +++++++++++++++++++++++++++++++++++++- package.json | 3 +- scratch/common.ts | 34 +++++++++++++ scratch/decode_aac.ts | 7 +-- scratch/decode_avci.ts | 8 +-- scratch/decode_hevc.ts | 3 +- scratch/decode_pcm.ts | 3 +- scratch/make_a_mux.ts | 3 +- scratch/read_wav.ts | 3 +- scratch/simple_mux.ts | 4 +- scratch/stream_avci.ts | 8 +-- scratch/stream_mp4.ts | 3 +- scratch/stream_mux.ts | 12 +++-- scratch/stream_wav.ts | 3 +- ts/beamstreams.ts | 28 +++------- ts/types/Beamstreams.d.ts | 63 +++++++++++++++++++---- 17 files changed, 236 insertions(+), 57 deletions(-) create mode 100644 scratch/common.ts diff --git a/.gitignore b/.gitignore index fb2b499..8e96137 100644 --- a/.gitignore +++ b/.gitignore @@ -77,4 +77,6 @@ index.js install_ffmpeg.js ts/*.js ts/*.d.ts -examples/capture \ No newline at end of file +examples/capture +scratch/test.wav +scratch/wibble.h264 diff --git a/.vscode/launch.json b/.vscode/launch.json index 5fd604e..a034f67 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,6 +2,89 @@ "version": "0.2.0", "configurations": [ + + { + "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", @@ -12,7 +95,26 @@ "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", diff --git a/package.json b/package.json index 3c02cbd..2f0a9a4 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,7 @@ "main": "ts/index.js", "types": "ts/index.d.ts", "scripts": { - "preinstall": "node ts/install_ffmpeg.js", - "install": "node-gyp rebuild", + "postinstall": "tsc -p . && node ts/install_ffmpeg.js && node-gyp rebuild", "clean": "rimraf ts/*.js", "test": "ts-node node_modules/tape/bin/tape test/*.ts", "lint": "eslint **/*.js", diff --git a/scratch/common.ts b/scratch/common.ts new file mode 100644 index 0000000..04b964e --- /dev/null +++ b/scratch/common.ts @@ -0,0 +1,34 @@ +import fs, { Stats } from 'fs'; +import path from 'path'; +import url from 'url'; + +export function getMedia(fileName: string, asURL?: boolean): string { + return `../../media/${fileName}`; + //let media = path.join('..', 'media'); + //let stats: Stats | null = null; + //try { + // stats = fs.statSync(media); + //} catch (e) { + // // ignore + //} + //if (stats && !stats.isDirectory()) { + // media = path.join('..', '..', 'media'); + // stats = fs.statSync(media); + //} + //if (stats && !stats.isDirectory()) { + // media = path.join('..', '..', '..', 'media'); + // stats = fs.statSync(media); + //} + //if (stats && !stats.isDirectory()) { + // throw Error(`media directory not found`); + //} + //const ret = path.resolve(media, fileName); + //if (fs.existsSync(ret)) + // throw Error(`missing ${ret} file`); + // + //// return ret.replace(/\\/g, '/'); + //if (asURL) + // return url.pathToFileURL(ret).toString(); + //else + // return ret.replace(/\\/g, '/'); +} \ No newline at end of file diff --git a/scratch/decode_aac.ts b/scratch/decode_aac.ts index b648aba..34b274c 100644 --- a/scratch/decode_aac.ts +++ b/scratch/decode_aac.ts @@ -21,14 +21,15 @@ import beamcoder from '../ts/index'; import { Packet } from '../ts/types/Packet'; - +import { getMedia } from './common'; +// Ok async function run() { - let demuxer = await beamcoder.demuxer({ url: '../media/bbb_1080p_c.ts'}); + let demuxer = await beamcoder.demuxer({ url: getMedia('bbb_1080p_c.ts')}); let decoder = beamcoder.decoder({ name: 'aac' }); 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.ts b/scratch/decode_avci.ts index b03cc50..e3c30c2 100644 --- a/scratch/decode_avci.ts +++ b/scratch/decode_avci.ts @@ -21,11 +21,13 @@ import beamcoder from '../ts/index'; import { Packet } from '../ts/types/Packet'; - +import { getMedia } from './common'; +// Ok async function run() { // let demuxer = await beamcoder.demuxer('../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); // console.log(JSON.stringify(demuxer, null, 2)); - let demuxer = await beamcoder.demuxer('M:/dpp/AS11_4K_8.mxf'); + // let demuxer = await beamcoder.demuxer(getMedia('dpp/AS11_4K_8.mxf')); + let demuxer = await beamcoder.demuxer(getMedia('dpp/sample_1280x720_surfing_with_audio.mxf')); // let demuxer = await beamcoder.demuxer('M:/dpp/AS11.mxf'); demuxer.streams.forEach(s => s.discard = (0 == s.index) ? 'default' : 'all'); // let decoder = beamcoder.decoder({ name: 'h264', thread_count: 4, thread_type: { FRAME: false, SLICE: true } }); @@ -45,7 +47,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.ts b/scratch/decode_hevc.ts index 6208cf9..64695bb 100644 --- a/scratch/decode_hevc.ts +++ b/scratch/decode_hevc.ts @@ -20,9 +20,10 @@ */ import beamcoder from '../ts/index'; +import { getMedia } from './common'; async function run() { - let demuxer = await beamcoder.demuxer('../media/bbb_1080p_c.ts'); + let demuxer = await beamcoder.demuxer(getMedia('bbb_1080p_c.ts')); console.log(demuxer); let decoder = await beamcoder.decoder({ name: 'hevc' }); for ( let x = 0 ; x < 100 ; x++ ) { diff --git a/scratch/decode_pcm.ts b/scratch/decode_pcm.ts index 7e18b26..ebb1cb0 100644 --- a/scratch/decode_pcm.ts +++ b/scratch/decode_pcm.ts @@ -20,9 +20,10 @@ */ import beamcoder from '../ts/index'; +import { getMedia } from './common'; async function run() { - let demuxer = await beamcoder.demuxer('../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); + let demuxer = await beamcoder.demuxer(getMedia('dpp/AS11_DPP_HD_EXAMPLE_1.mxf')); console.log(demuxer.streams[1]); let decoder = await beamcoder.decoder({ demuxer: demuxer, stream_index : 1 }); console.log(decoder); diff --git a/scratch/make_a_mux.ts b/scratch/make_a_mux.ts index ff85fbf..43c5048 100644 --- a/scratch/make_a_mux.ts +++ b/scratch/make_a_mux.ts @@ -21,9 +21,10 @@ import beamcoder from '../ts/index'; import { Packet } from '../ts/types/Packet'; +import { getMedia } from './common'; async function run() { - let demuxer = await beamcoder.demuxer('../media/sound/BBCNewsCountdown.wav'); + let demuxer = await beamcoder.demuxer(getMedia('sound/BBCNewsCountdown.wav')); console.log(demuxer.streams[0].codecpar); let muxer = beamcoder.muxer({ filename: 'file:test.wav' }); let stream = muxer.newStream({ diff --git a/scratch/read_wav.ts b/scratch/read_wav.ts index eb9de74..cae81c8 100644 --- a/scratch/read_wav.ts +++ b/scratch/read_wav.ts @@ -20,9 +20,10 @@ */ import beamcoder from '../ts/index'; +import { getMedia } from './common'; async function run() { - let demuxer = await beamcoder.demuxer('../media/sound/BBCNewsCountdown.wav'); + let demuxer = await beamcoder.demuxer(getMedia('sound/BBCNewsCountdown.wav')); let packet = {}; for ( let x = 0 ; x < 100 && packet !== null ; x++ ) { packet = await demuxer.read(); diff --git a/scratch/simple_mux.ts b/scratch/simple_mux.ts index 908c4d1..9ffe2c4 100644 --- a/scratch/simple_mux.ts +++ b/scratch/simple_mux.ts @@ -20,9 +20,11 @@ */ import beamcoder from '../ts/index'; import { Packet } from '../ts/types/Packet'; +import { getMedia } from './common'; async function run() { - let demuxer = await beamcoder.demuxer('../media/sound/BBCNewsCountdown.wav'); + // let demuxer = await beamcoder.demuxer(getMedia('sound/BBCNewsCountdown.wav')); + let demuxer = await beamcoder.demuxer(getMedia('sound/BBCNewsCountdown.wav')); let muxer = beamcoder.muxer({ filename: 'file:test.wav' }); let stream = muxer.newStream(demuxer.streams[0]); // eslint-disable-line // stream.time_base = demuxer.streams[0].time_base; diff --git a/scratch/stream_avci.ts b/scratch/stream_avci.ts index 7bda067..81476ac 100644 --- a/scratch/stream_avci.ts +++ b/scratch/stream_avci.ts @@ -22,13 +22,15 @@ import beamcoder from '../ts/index'; import fs from 'fs'; import { Packet } from '../ts/types/Packet'; +import { WritableDemuxerStream } from '../ts/types/Beamstreams'; +import { getMedia } from './common'; // const util = require('util'); async function run() { // let demuxer = await createDemuxer('../../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); // http://ftp.kw.bbc.co.uk/dppdownload/dpp_example_files/AS11_DPP_HD_EXAMPLE_1.mxf - let demuxerStream = beamcoder.demuxerStream({ highwaterMark: 65536 }); - fs.createReadStream('../../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf').pipe(demuxerStream); + let demuxerStream: WritableDemuxerStream = beamcoder.demuxerStream({ highwaterMark: 65536 }); + fs.createReadStream(getMedia('dpp/AS11_DPP_HD_EXAMPLE_1.mxf')).pipe(demuxerStream); let demuxer = await demuxerStream.demuxer({}); // console.log(demuxer); let decoder = beamcoder.decoder({ name: 'h264' }); @@ -121,8 +123,6 @@ async function run() { } let frames = await decoder.flush(); console.log('flush', frames.total_time, frames.frames.length); - debugger; - // @ts-ignore demuxerStream.destroy(); } diff --git a/scratch/stream_mp4.ts b/scratch/stream_mp4.ts index 6cadf33..fb4cbd8 100644 --- a/scratch/stream_mp4.ts +++ b/scratch/stream_mp4.ts @@ -20,11 +20,12 @@ */ import beamcoder from '../ts/index'; +import { getMedia } from './common'; async function run() { // https://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_1080p_h264.mov // http://www.legittorrents.info/index.php?page=torrent-details&id=7f34612e0fac5e7b051b78bdf1060113350ebfe0 - const urls = [ 'file:../../Media/big_buck_bunny_1080p_h264.mov' ]; + const urls = [ `file:${getMedia('big_buck_bunny_1080p_h264.mov')}` ]; const spec = { start: 0, end: 24 }; const params = { diff --git a/scratch/stream_mux.ts b/scratch/stream_mux.ts index ae2a784..8aaffda 100644 --- a/scratch/stream_mux.ts +++ b/scratch/stream_mux.ts @@ -22,13 +22,14 @@ import beamcoder from '../ts/index'; import fs from 'fs'; import { Packet } from '../ts/types/Packet'; - +import { ReadableMuxerStream } from '../ts/types/Beamstreams'; +import { getMedia } from './common'; +// OK async function run() { - let demuxer = await beamcoder.demuxer('../../media/sound/BBCNewsCountdown.wav'); - - let muxerStream = beamcoder.muxerStream({ highwaterMark: 65536 }); + let file = getMedia('sound/BBCNewsCountdown.wav'); + let demuxer = await beamcoder.demuxer(file); + let muxerStream: ReadableMuxerStream = beamcoder.muxerStream({ highwaterMark: 65536 }); muxerStream.pipe(fs.createWriteStream('test.wav')); - let muxer = muxerStream.muxer({ format_name: 'wav' }); let stream = muxer.newStream(demuxer.streams[0]); // eslint-disable-line // stream.time_base = demuxer.streams[0].time_base; @@ -43,6 +44,7 @@ async function run() { await muxer.writeFrame(packet); } await muxer.writeTrailer(); + muxerStream.destroy(); } run(); diff --git a/scratch/stream_wav.ts b/scratch/stream_wav.ts index 5cb33c1..76aaa63 100644 --- a/scratch/stream_wav.ts +++ b/scratch/stream_wav.ts @@ -20,9 +20,10 @@ */ import beamcoder from '../ts/index'; +import { getMedia } from './common'; async function run() { - const urls = [ 'file:../Media/sound/Countdown.wav' ]; + const urls = [ `file:${getMedia('sound/Countdown.wav')}` ]; const spec = { start: 50, end: 58 }; const params = { video: [], diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index 5520c51..6660485 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -25,7 +25,6 @@ import { teeBalancer } from './teeBalancer'; import { parallelBalancer } from './parallelBalancer'; import { serialBalancer } from './serialBalancer'; import { BeamcoderType } from './types/BeamcoderType'; -import { CodecPar } from './types/CodecPar'; import { Demuxer } from './types/Demuxer'; import { Muxer } from './types/Muxer'; import { Stream } from './types/Stream'; @@ -34,10 +33,9 @@ import type { Governor } from './types/Governor'; import { Frame } from './types/Frame'; import { Packet } from './types/Packet'; import { Timing } from './types/Timing'; -import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, WritableDemuxerStream } from './types/Beamstreams'; +import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, ReadableMuxerStream, WritableDemuxerStream } from './types/Beamstreams'; import { Filterer, FilterGraph, FilterLink } from './types/Filter'; import { CodecContextBaseMin } from './types/CodecContext'; -import { EncodedPackets, Encoder } from './types/Encoder'; import { Timable, Timables } from './types/Timable' const beamcoder = bindings('beamcoder') as BeamcoderType; @@ -223,15 +221,6 @@ function writeStream(params: { name: string, highWaterMark?: number }, processFn }).on('error', err => reject(err)); } -// interface Packet { -// stream_index: number; -// pts: number; -// timings: { -// read?: Timing; -// }; -// } - - function readStream(params: { highWaterMark?: number }, demuxer: Demuxer, ms: { end: number }, index: number) { const time_base = demuxer.streams[index].time_base; const end_pts = ms ? ms.end * time_base[1] / time_base[0] : Number.MAX_SAFE_INTEGER; @@ -273,23 +262,22 @@ function createBeamWritableStream(params: { highwaterMark?: number }, governor: }); return beamStream; } -type demuxerStreamType = Writable & { demuxer: (options: { governor: Governor }) => Promise }; export function demuxerStream(params: { highwaterMark?: number }): WritableDemuxerStream { const governor = new beamcoder.governor({}); - const stream: demuxerStreamType = createBeamWritableStream(params, governor) as demuxerStreamType; + const stream = createBeamWritableStream(params, governor) as WritableDemuxerStream; stream.on('finish', () => governor.finish()); stream.on('error', console.error); - stream.demuxer = (options: { governor: Governor }) => { + stream.demuxer = (options: { governor?: Governor }) => { 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 as WritableDemuxerStream; + return stream; } -function createBeamReadableStream(params: { highwaterMark?: number }, governor: Governor) { +function createBeamReadableStream(params: { highwaterMark?: number }, governor: Governor): Readable { const beamStream = new Readable({ highWaterMark: params.highwaterMark || 16384, read: size => { @@ -305,11 +293,9 @@ function createBeamReadableStream(params: { highwaterMark?: number }, governor: return beamStream; } -type muxerStreamType = Readable & { muxer: (options: { governor: Governor }) => any }; - -export function muxerStream(params: { highwaterMark: number }): muxerStreamType { +export function muxerStream(params: { highwaterMark: number }): ReadableMuxerStream { const governor = new beamcoder.governor({ highWaterMark: 1 }); - const stream: muxerStreamType = createBeamReadableStream(params, governor) as muxerStreamType; + const stream = createBeamReadableStream(params, governor) as ReadableMuxerStream; stream.on('end', () => governor.finish()); stream.on('error', console.error); stream.muxer = (options) => { diff --git a/ts/types/Beamstreams.d.ts b/ts/types/Beamstreams.d.ts index b019da5..04fa6f3 100644 --- a/ts/types/Beamstreams.d.ts +++ b/ts/types/Beamstreams.d.ts @@ -5,13 +5,35 @@ import { Stream } from "./Stream" import { Filterer } from "./Filter" import { Decoder } from "./Decoder" import { Encoder } from "./Encoder" +import { Readable, Writable } from "stream" + /** + * OLD Typing + * * 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 { -// export interface WritableDemuxerStream implements WritableStream extends NodeJS.Writable { +// export interface WritableDemuxerStream extends NodeJS.WritableStream { +// // export interface WritableDemuxerStream implements WritableStream extends NodeJS.Writable { +// /** +// * 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 | string): Promise +// } + + + +/** + * WritableDemuxerStream is not a Writable Class augmented by a demuxer function, should be replace by a new class + * 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 type WritableDemuxerStream = Writable & { /** * Create a demuxer for this source * @param options a DemuxerCreateOptions object @@ -19,21 +41,42 @@ export interface WritableDemuxerStream extends NodeJS.WritableStream { * format details by consuming data from the source. The promise will wait indefinitely * until sufficient source data has been read. */ - demuxer(options?: DemuxerCreateOptions | string): Promise -} + demuxer: (options?: { iformat?: InputFormat, options?: { [key: string]: any }, governor?: Governor }) => Promise +}; + +/** + * OLD TYPING + * + * 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 +// } + /** * 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 { +export type ReadableMuxerStream = Readable & { /** - * Create a muxer for this source - * @param options a MuxerCreateOptions object - * @returns A Muxer object + * 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. */ - muxer(options: MuxerCreateOptions): Muxer -} + muxer: (options?: MuxerCreateOptions & {governor?: Governor }) => Muxer +}; + + + /** Create object for AVIOContext based buffered I/O */ // export function governor(options: { highWaterMark: number }): { From f108cc93699507efa1d2672922804a6692af7504 Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 25 Apr 2022 14:46:19 +0300 Subject: [PATCH 29/68] add types --- scratch/muxer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scratch/muxer.ts b/scratch/muxer.ts index 47614fa..6cf547b 100644 --- a/scratch/muxer.ts +++ b/scratch/muxer.ts @@ -25,7 +25,7 @@ import beamcoder from '../ts/index'; 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,7 +44,7 @@ function allocPicture(pixelFmt, width, height) { // eslint-disable-line }).alloc(); } -async function addStream(stream, muxer, codecID) { // eslint-disable-line +async function addStream(stream, muxer, codecID: number) { // eslint-disable-line let codec = await beamcoder.encoder({ codec_id: codecID }); stream.st = muxer.newStream(); From 8302c309a6c0248f2725c427e81d4bebbe9cd40f Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 25 Apr 2022 15:12:53 +0300 Subject: [PATCH 30/68] more types --- ts/beamstreams.ts | 3 ++- ts/parallelBalancer.ts | 11 +++++++---- ts/types/DecodedFrames.d.ts | 3 +++ ts/types/Frame.d.ts | 3 ++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index 6660485..52088d7 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -221,7 +221,7 @@ function writeStream(params: { name: string, highWaterMark?: number }, processFn }).on('error', err => reject(err)); } -function readStream(params: { highWaterMark?: number }, demuxer: Demuxer, ms: { end: number }, index: number) { +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 { @@ -374,6 +374,7 @@ function runStreams( { name: 'decode', highWaterMark: 1 }, (pkts: Packet) => src.decoder.decode(pkts), () => src.decoder.flush(), reject); + debugger; const filterSource = writeStream({ name: 'filterSource', highWaterMark: 1 }, pkts => filterBalancer.pushPkts(pkts, (src.format as Demuxer).streams[src.streamIndex], srcIndex), () => filterBalancer.pushPkts(null, (src.format as Demuxer).streams[src.streamIndex], srcIndex, true), reject); diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts index 8dcef99..a834201 100644 --- a/ts/parallelBalancer.ts +++ b/ts/parallelBalancer.ts @@ -1,5 +1,7 @@ import { Readable } from "stream"; +import { DecodedFrames } from "./types/DecodedFrames"; import { Frame } from './types/Frame'; +import { Timable } from "./types/Timable"; type parallelBalancerType = Readable & { pushPkts: (packets, stream, streamIndex: number, final?: boolean) => any @@ -39,7 +41,7 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } } }; - const pushPkt = async (pkt: Frame, streamIndex: number, ts: number): Promise<{ pkt, ts, final: boolean, resolve }> => + const pushPkt = async (pkt: Frame, streamIndex: number, ts: number): Promise<{ pkt: Frame, ts: number, final: boolean, resolve: () => void }> => new Promise(resolve => { Object.assign(pending[streamIndex], { pkt, ts, final: pkt ? false : true, resolve }); makeSet(resolveGet); @@ -66,12 +68,13 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } }, }) as parallelBalancerType; - readStream.pushPkts = (packets: { frames: Array, timings: [number, number] }, stream: { time_base: [number, number] }, streamIndex: number, final = false): any => { + readStream.pushPkts = (packets: DecodedFrames, stream: { time_base: [number, number] }, streamIndex: number, final = false): Promise<{ pkt: Frame, ts: number, final: boolean, resolve: () => void }> => { if (packets && packets.frames.length) { - return packets.frames.reduce(async (promise, pkt) => { + // @ts-ignore + return packets.frames.reduce(async (promise, pkt: Frame) => { await promise; const ts = pkt.pts * stream.time_base[0] / stream.time_base[1]; - pkt.timings = packets.timings; + (pkt as Timable).timings = packets.timings; return pushPkt(pkt, streamIndex, ts); }, Promise.resolve()); } else if (final) { diff --git a/ts/types/DecodedFrames.d.ts b/ts/types/DecodedFrames.d.ts index 14845f0..8a9fa75 100644 --- a/ts/types/DecodedFrames.d.ts +++ b/ts/types/DecodedFrames.d.ts @@ -1,3 +1,6 @@ +import { Frame } from "./Frame" +import { Timable } from "./Timable" + /** The DecodedFrames object is returned as the result of a decode operation */ export interface DecodedFrames extends Timable { /** Object name. */ diff --git a/ts/types/Frame.d.ts b/ts/types/Frame.d.ts index 43e27f1..cbcf28d 100644 --- a/ts/types/Frame.d.ts +++ b/ts/types/Frame.d.ts @@ -1,9 +1,10 @@ import { HWFramesContext } from "./HWContext"; +import { Timable } from "./Timable"; /** * This object describes decoded (raw) audio or video data. */ -export interface Frame { +export interface Frame implements Timable { /** Object name. */ readonly type: 'Frame' /** From e3ea9042a7aa2015805c441d1a3e9da83229d015 Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 25 Apr 2022 16:01:34 +0300 Subject: [PATCH 31/68] typings --- package.json | 1 + ts/beamstreams.ts | 28 +++++++++++++++++++++------- ts/parallelBalancer.ts | 31 ++++++++++++++++++++----------- ts/serialBalancer.ts | 7 ++++--- ts/types/Encoder.d.ts | 3 ++- ts/types/Frame.d.ts | 2 +- 6 files changed, 49 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index 2f0a9a4..84f03f4 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "types": "ts/index.d.ts", "scripts": { "postinstall": "tsc -p . && node ts/install_ffmpeg.js && node-gyp rebuild", + "build": "tsc -p .", "clean": "rimraf ts/*.js", "test": "ts-node node_modules/tape/bin/tape test/*.ts", "lint": "eslint **/*.js", diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index 52088d7..d9c4ff0 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -37,6 +37,7 @@ import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream import { Filterer, FilterGraph, FilterLink } from './types/Filter'; import { CodecContextBaseMin } from './types/CodecContext'; import { Timable, Timables } from './types/Timable' +import { EncodedPackets } from './types/Encoder'; const beamcoder = bindings('beamcoder') as BeamcoderType; @@ -170,14 +171,15 @@ const calcStats = (arr: Array, elem: string, prop: string): ffStats => { return { mean, stdDev, max, min }; }; -function writeStream(params: { name: string, highWaterMark?: number }, processFn: (val: { stream_index: number, pts: number, dts: number, duration: number, timings: any }) => Promise, finalFn: () => Promise, reject: (err: Error) => void) { +function writeStream(params: { name: string, highWaterMark?: number }, processFn: (val: EncodedPackets) => Promise, finalFn: () => Promise, reject: (err: Error) => void) { return new Writable({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - write(val: { stream_index: number, pts: number, dts: number, duration: number, timings: any }, encoding: BufferEncoding, cb: (error?: Error | null, result?: any) => void) { + write(val: EncodedPackets, encoding: BufferEncoding, cb: (error?: Error | null, result?: any) => void) { (async () => { const start = process.hrtime(); const reqTime = start[0] * 1e3 + start[1] / 1e6; + debugger const result = await processFn(val); if ('mux' === params.name) { const pktTimings = val.timings; @@ -374,9 +376,9 @@ function runStreams( { name: 'decode', highWaterMark: 1 }, (pkts: Packet) => src.decoder.decode(pkts), () => src.decoder.flush(), reject); - debugger; const filterSource = writeStream({ name: 'filterSource', highWaterMark: 1 }, - pkts => filterBalancer.pushPkts(pkts, (src.format as Demuxer).streams[src.streamIndex], srcIndex), + // @ts-ignore + (pkts: DecodedFrames) => filterBalancer.pushPkts(pkts, (src.format as Demuxer).streams[src.streamIndex], srcIndex), () => filterBalancer.pushPkts(null, (src.format as Demuxer).streams[src.streamIndex], srcIndex, true), reject); src.stream.pipe(decStream).pipe(filterSource); @@ -402,9 +404,21 @@ function runStreams( const encStream = transformStream, any>({ name: 'encode', highWaterMark: 1 }, (frms) => str.encoder.encode(frms), () => str.encoder.flush(), reject); - const muxStream = writeStream({ name: 'mux', highWaterMark: 1 }, - (pkts: any) => muxBalancer.writePkts(pkts, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts)), - () => muxBalancer.writePkts(null, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts), true), reject); + + + const muxStream = writeStream( + { name: 'mux', highWaterMark: 1 }, + (pkts: EncodedPackets) => { + debugger; + return 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); diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts index a834201..487726b 100644 --- a/ts/parallelBalancer.ts +++ b/ts/parallelBalancer.ts @@ -1,23 +1,29 @@ import { Readable } from "stream"; import { DecodedFrames } from "./types/DecodedFrames"; import { Frame } from './types/Frame'; -import { Timable } from "./types/Timable"; +import { Stream } from "./types/Stream"; +import { Timable, Timables } from "./types/Timable"; + + +type localFrame = { pkt?: Frame, ts: number, streamIndex: number, final?: boolean, resolve?: () => void }; +type localResult = { done: boolean, value?: {name: string, frames: Timables}[] & Timable }; + type parallelBalancerType = Readable & { - pushPkts: (packets, stream, streamIndex: number, final?: boolean) => any + pushPkts: (packets: DecodedFrames, stream: Stream, streamIndex: number, final?: boolean) => any }; export function parallelBalancer(params: { name: string, highWaterMark: number }, streamType: 'video' | 'audio', numStreams: number): parallelBalancerType { - let resolveGet = null; + let resolveGet: null | ((result: {value?: {name: string, frames: Frame[]}[] & Timable, done: boolean }) => void) = null; const tag = 'video' === streamType ? 'v' : 'a'; - const pending = []; + const pending: Array = []; // 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: (result: {value?: { name: string, frames: any[] }, done: boolean}) => void) => { - const makeSet = (resolve: (result: {value?: any, done: boolean}) => void) => { + const makeSet = (resolve: (result: localResult) => void) => { if (resolve) { // console.log('makeSet', pending.map(p => p.ts)); const nextPends = pending.every(pend => pend.pkt) ? pending : null; @@ -26,8 +32,11 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } nextPends.forEach(pend => pend.resolve()); resolve({ value: nextPends.map(pend => { - return { name: `in${pend.streamIndex}:${tag}`, frames: [pend.pkt] }; - }), + return { + name: `in${pend.streamIndex}:${tag}`, + frames: [pend.pkt] + }; + }), // as any[] & Timable, done: false }); resolveGet = null; @@ -41,13 +50,13 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } } }; - const pushPkt = async (pkt: Frame, streamIndex: number, ts: number): Promise<{ pkt: Frame, ts: number, final: boolean, resolve: () => void }> => + const pushPkt = async (pkt: Frame, streamIndex: number, ts: number): Promise => new Promise(resolve => { Object.assign(pending[streamIndex], { pkt, ts, final: pkt ? false : true, resolve }); makeSet(resolveGet); }); - const pullSet = async () => new Promise<{ done: any, value: { timings: any } }>(resolve => makeSet(resolve as any)); + const pullSet = async () => new Promise(resolve => makeSet(resolve as any)); const readStream: parallelBalancerType = new Readable({ objectMode: true, @@ -68,13 +77,13 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } }, }) as parallelBalancerType; - readStream.pushPkts = (packets: DecodedFrames, stream: { time_base: [number, number] }, streamIndex: number, final = false): Promise<{ pkt: Frame, ts: number, final: boolean, resolve: () => void }> => { + readStream.pushPkts = (packets: DecodedFrames, stream: Stream, streamIndex: number, final = false): Promise => { if (packets && packets.frames.length) { // @ts-ignore return packets.frames.reduce(async (promise, pkt: Frame) => { await promise; const ts = pkt.pts * stream.time_base[0] / stream.time_base[1]; - (pkt as Timable).timings = packets.timings; + pkt.timings = packets.timings; return pushPkt(pkt, streamIndex, ts); }, Promise.resolve()); } else if (final) { diff --git a/ts/serialBalancer.ts b/ts/serialBalancer.ts index 26145a2..ba5fc6b 100644 --- a/ts/serialBalancer.ts +++ b/ts/serialBalancer.ts @@ -1,3 +1,5 @@ +import { EncodedPackets } from "./types/Encoder"; + export class serialBalancer { pending = [] as { ts: number, streamIndex: number, resolve?: (result: any) => void, pkt?: any }[]; @@ -27,15 +29,14 @@ export class serialBalancer { }); }; - writePkts(packets: { - packets: Array<{ stream_index: number, pts: number, dts: number, duration: number }> - } | null, + writePkts(packets: EncodedPackets | null, srcStream: { time_base: [number, number] }, dstStream: { time_base: [number, number], index: number }, writeFn: (r: void) => void, final = false) { + debugger; if (packets && packets.packets.length) { return packets.packets.reduce(async (promise, pkt) => { await promise; diff --git a/ts/types/Encoder.d.ts b/ts/types/Encoder.d.ts index 483bf52..1ace128 100644 --- a/ts/types/Encoder.d.ts +++ b/ts/types/Encoder.d.ts @@ -2,9 +2,10 @@ import { CodecPar } from "./CodecPar" import { Packet } from "./Packet"; import { Frame } from "./Frame"; import { CodecContextBaseMin } from "./CodecContext" +import { Timable } from "./Timable"; /** The EncodedPackets object is returned as the result of a encode operation */ -export interface EncodedPackets { +export interface EncodedPackets extends Timable { /** Object name. */ readonly type: 'packets' /** diff --git a/ts/types/Frame.d.ts b/ts/types/Frame.d.ts index cbcf28d..b3a229d 100644 --- a/ts/types/Frame.d.ts +++ b/ts/types/Frame.d.ts @@ -4,7 +4,7 @@ import { Timable } from "./Timable"; /** * This object describes decoded (raw) audio or video data. */ -export interface Frame implements Timable { +export interface Frame extends Timable { /** Object name. */ readonly type: 'Frame' /** From 61d920d5814b863d35530c24dd3421d9556edd1c Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 25 Apr 2022 16:15:51 +0300 Subject: [PATCH 32/68] more types --- ts/beamstreams.ts | 7 +------ ts/parallelBalancer.ts | 4 ++-- ts/serialBalancer.ts | 6 ++++-- ts/teeBalancer.ts | 10 +++++----- ts/types/Beamstreams.d.ts | 4 ++-- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index d9c4ff0..933218f 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -301,6 +301,7 @@ export function muxerStream(params: { highwaterMark: number }): ReadableMuxerStr stream.on('end', () => governor.finish()); stream.on('error', console.error); stream.muxer = (options) => { + options = options || {}; options.governor = governor; return beamcoder.muxer(options); }; @@ -404,8 +405,6 @@ function runStreams( const encStream = transformStream, any>({ name: 'encode', highWaterMark: 1 }, (frms) => str.encoder.encode(frms), () => str.encoder.flush(), reject); - - const muxStream = writeStream( { name: 'mux', highWaterMark: 1 }, (pkts: EncodedPackets) => { @@ -415,10 +414,6 @@ function runStreams( () => muxBalancer.writePkts(null, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts), true), reject); - - - - muxStream.on('finish', resolve); streamTee[i].pipe(diceStream).pipe(encStream).pipe(muxStream); diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts index 487726b..8d03456 100644 --- a/ts/parallelBalancer.ts +++ b/ts/parallelBalancer.ts @@ -50,13 +50,13 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } } }; - const pushPkt = async (pkt: Frame, streamIndex: number, ts: number): Promise => + const pushPkt = async (pkt: null | Frame, streamIndex: number, ts: number): Promise => new Promise(resolve => { Object.assign(pending[streamIndex], { pkt, ts, final: pkt ? false : true, resolve }); makeSet(resolveGet); }); - const pullSet = async () => new Promise(resolve => makeSet(resolve as any)); + const pullSet = async () => new Promise(resolve => makeSet(resolve)); const readStream: parallelBalancerType = new Readable({ objectMode: true, diff --git a/ts/serialBalancer.ts b/ts/serialBalancer.ts index ba5fc6b..3330a9e 100644 --- a/ts/serialBalancer.ts +++ b/ts/serialBalancer.ts @@ -1,7 +1,8 @@ import { EncodedPackets } from "./types/Encoder"; +import { Packet } from "./types/Packet"; export class serialBalancer { - pending = [] as { ts: number, streamIndex: number, resolve?: (result: any) => void, pkt?: any }[]; + pending = [] as { ts: number, streamIndex: number, resolve?: (result: any) => void, pkt?: Packet }[]; constructor(numStreams: number) { // initialise with negative ts and no pkt @@ -18,12 +19,13 @@ export class serialBalancer { }; - pullPkts(pkt: {}, streamIndex: number, ts: number): Promise { + 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)); + // @ts-ignore if (nextPend) nextPend.resolve(nextPend.pkt); if (!pkt) resolve(); }); diff --git a/ts/teeBalancer.ts b/ts/teeBalancer.ts index 935e8b2..858a746 100644 --- a/ts/teeBalancer.ts +++ b/ts/teeBalancer.ts @@ -7,14 +7,14 @@ type BalanceResult = { value: { timings: Timing }, done: boolean, final?: boolea type teeBalancerType = Readable[] & { pushFrames: (frames: Frame[] & {timings: Timing}, unusedFlag?: boolean) => any }; export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, numStreams: number): teeBalancerType { - let resolvePush: (result?: BalanceResult) => void = null; - const pending: Array<{ frames: Frame, resolve: (result: { value?: Frame, done: boolean }) => void, final: boolean }> = []; + 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 }>(resolve => { + 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 }); @@ -30,7 +30,7 @@ export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }); }; - const readStreams: teeBalancerType = [] as teeBalancerType; + const readStreams: teeBalancerType = [] as any as teeBalancerType; for (let s = 0; s < numStreams; ++s) readStreams.push(new Readable({ objectMode: true, @@ -52,7 +52,7 @@ export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number })); readStreams.pushFrames = frames => { - return new Promise(resolve => { + return new Promise(resolve => { pending.forEach((p, index) => { if (frames.length) // @ts-ignore diff --git a/ts/types/Beamstreams.d.ts b/ts/types/Beamstreams.d.ts index 04fa6f3..29e4dc0 100644 --- a/ts/types/Beamstreams.d.ts +++ b/ts/types/Beamstreams.d.ts @@ -120,8 +120,8 @@ export interface BeamstreamChannel { * that are to be processed and multiplexed into an output file or stream */ export interface BeamstreamParams { - video?: Array - audio?: Array + video: Array + audio: Array /** Destination definition for the beamstream process, to either a file or NodeJS WritableStream */ out: { formatName: string From 752c468ee040dc44e94e7c6a570f58bac3a89c67 Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 25 Apr 2022 16:26:26 +0300 Subject: [PATCH 33/68] drop debuggers --- examples/streamTest.ts | 1 - ts/beamstreams.ts | 2 -- ts/serialBalancer.ts | 1 - ts/types/Beamstreams.d.ts | 4 ++-- tsconfig.json | 4 ++-- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/streamTest.ts b/examples/streamTest.ts index 9192ab1..16bdbb7 100644 --- a/examples/streamTest.ts +++ b/examples/streamTest.ts @@ -10,7 +10,6 @@ async function process() { } async function run() { - debugger; const stream = beamcoder.demuxerStream({highwaterMark: 360000}); console.log(stream); console.log('stream'); diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index 933218f..fc198ad 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -179,7 +179,6 @@ function writeStream(params: { name: string, highWaterMark?: number }, processFn (async () => { const start = process.hrtime(); const reqTime = start[0] * 1e3 + start[1] / 1e6; - debugger const result = await processFn(val); if ('mux' === params.name) { const pktTimings = val.timings; @@ -408,7 +407,6 @@ function runStreams( const muxStream = writeStream( { name: 'mux', highWaterMark: 1 }, (pkts: EncodedPackets) => { - debugger; return muxBalancer.writePkts(pkts, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts)) }, () => muxBalancer.writePkts(null, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts), true), diff --git a/ts/serialBalancer.ts b/ts/serialBalancer.ts index 3330a9e..c21e599 100644 --- a/ts/serialBalancer.ts +++ b/ts/serialBalancer.ts @@ -38,7 +38,6 @@ export class serialBalancer { index: number }, writeFn: (r: void) => void, final = false) { - debugger; if (packets && packets.packets.length) { return packets.packets.reduce(async (promise, pkt) => { await promise; diff --git a/ts/types/Beamstreams.d.ts b/ts/types/Beamstreams.d.ts index 29e4dc0..3c0b8cf 100644 --- a/ts/types/Beamstreams.d.ts +++ b/ts/types/Beamstreams.d.ts @@ -91,7 +91,7 @@ export interface BeamstreamSource { url?: string input_stream?: NodeJS.ReadableStream ms?: { start: number, end: number } - streamIndex?: number + streamIndex: number iformat?: InputFormat options?: { [key: string]: any } format?: Demuxer | Promise; @@ -105,7 +105,7 @@ export interface BeamstreamStream { time_base: Array codecpar: { [key: string]: any } // added later - encoder?: Encoder; + encoder?: Encoder; // TODO add default value stream?: Stream; } /** Definition for a channel of beamstream processing */ diff --git a/tsconfig.json b/tsconfig.json index 4d85352..30ec57d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -74,8 +74,8 @@ "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.. */ + // "strict": true, /* 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. */ From 1ce9c194702609c2d633abc3bd397be279d72c1f Mon Sep 17 00:00:00 2001 From: urielch Date: Sat, 30 Apr 2022 19:21:57 +0300 Subject: [PATCH 34/68] minbor cleanup --- scratch/stream_wav.ts | 4 ++-- test/formatSpec.ts | 7 +++++-- ts/parallelBalancer.ts | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/scratch/stream_wav.ts b/scratch/stream_wav.ts index 76aaa63..dc84d79 100644 --- a/scratch/stream_wav.ts +++ b/scratch/stream_wav.ts @@ -23,14 +23,14 @@ import beamcoder from '../ts/index'; import { getMedia } from './common'; async function run() { - const urls = [ `file:${getMedia('sound/Countdown.wav')}` ]; + const url = `file:${getMedia('sound/Countdown.wav')}`; const spec = { start: 50, end: 58 }; const params = { video: [], audio: [ { sources: [ - { url: urls[0], ms: spec, streamIndex: 0 } + { url, ms: spec, streamIndex: 0 } ], filterSpec: '[in0:a] \ volume=precision=float:volume=0.8 \ diff --git a/test/formatSpec.ts b/test/formatSpec.ts index 16f4dbe..3a3a73a 100644 --- a/test/formatSpec.ts +++ b/test/formatSpec.ts @@ -22,7 +22,7 @@ import test from 'tape'; import beamcoder, { FormatContext } from '..'; -const isExternal = o => (Object as any).toString(o).indexOf('native code') >= 0; +const isExternal = (o: any) => (Object as any).toString(o).indexOf('native code') >= 0; //const isExternal = o => Object.toString.apply(o).indexOf('native code') >= 0; // Object.toString.apply(Object) @@ -41,7 +41,10 @@ test('Creating a format', t => { t.end(); }); -const stripNewStream = ({ newStream, ...others }) => ({ ...others }); // eslint-disable-line no-unused-vars +const stripNewStream = (ctxt: FormatContext) => { + const { newStream, ...others } = ctxt; + return { ...others } +}; test('Minimal JSON serialization', t => { let fmt: FormatContext = beamcoder.format(); diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts index 8d03456..8dd1229 100644 --- a/ts/parallelBalancer.ts +++ b/ts/parallelBalancer.ts @@ -69,8 +69,9 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } 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 }; + const value = result.value; + value.timings = value[0].frames[0].timings; + value.timings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; this.push(result.value); } })(); From cc3db5db8c0866dafe4d9f4639fa65f88c31b2dc Mon Sep 17 00:00:00 2001 From: urielch Date: Sat, 30 Apr 2022 20:41:10 +0300 Subject: [PATCH 35/68] drop any types --- ts/beamstreams.ts | 20 ++++++++++---------- ts/parallelBalancer.ts | 2 +- ts/teeBalancer.ts | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index fc198ad..b45725d 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -81,7 +81,7 @@ class frameDicer { dstFrm.pkt_duration = dstNumSamples; while (curStart + dstFrmBytes - lastBuf[0].length <= srcFrm.nb_samples * sampleBytes) { - const resFrm = beamcoder.frame((dstFrm as any as Stream).toJSON()); + const resFrm = beamcoder.frame(dstFrm.toJSON()); resFrm.data = lastBuf.map((d, i) => Buffer.concat([ d, srcFrm.data[i].slice(curStart, curStart + dstFrmBytes - d.length)], @@ -156,7 +156,7 @@ function transformStream( flush(cb) { (async () => { const result = flushFn ? await flushFn() : null; - if (result) (result as any).timings = {}; + if (result) result.timings = {}; cb(null, result); })().catch(cb); } @@ -316,7 +316,7 @@ export async function makeSources(params: BeamstreamParams): Promise { 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 as any }); // FIXME + src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options}); })); params.audio.forEach(p => p.sources.forEach((src: BeamstreamSource) => { if (src.input_stream) { @@ -324,12 +324,12 @@ export async function makeSources(params: BeamstreamParams): Promise { 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 as any }); // FIXME + src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options}); })); - await (params.video.reduce)(async (promise: Promise, p: BeamstreamChannel) => { + await (params.video.reduce)(async (promise: Promise, p: BeamstreamChannel) => { await promise; - return p.sources.reduce(async (promise: Promise, src: BeamstreamSource) => { + return p.sources.reduce(async (promise: Promise, src: BeamstreamSource) => { await promise; src.format = await src.format; if (src.ms && !src.input_stream) @@ -338,9 +338,9 @@ export async function makeSources(params: BeamstreamParams): Promise { }, Promise.resolve()); }, Promise.resolve()); - await (params.audio.reduce as any)(async (promise: Promise, p: BeamstreamChannel) => { + await params.audio.reduce(async (promise: Promise, p: BeamstreamChannel) => { await promise; - return p.sources.reduce(async (promise: Promise, src: BeamstreamSource) => { + return p.sources.reduce(async (promise: Promise, src: BeamstreamSource) => { await promise; src.format = await src.format; if (src.ms && !src.input_stream) @@ -362,7 +362,7 @@ function runStreams( filterer: { cb?: (result: any) => void, filter: (stream: Array) => any, graph: FilterGraph }, // Filterer? streams: Array, mux: { writeFrame: (pkts: any) => void }, - muxBalancer: serialBalancer) { + muxBalancer: serialBalancer): Promise { // serialBalancer // { writePkts: (packets: {timings: any; }, srcStream: {}, dstStream: {}, writeFn: {}, final?: boolean) => any } return new Promise((resolve, reject) => { if (!sources.length) @@ -554,7 +554,7 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr await mux.writeHeader({ options: params.out.options ? params.out.options : {} }); const muxBalancer = new serialBalancer(mux.streams.length); - const muxStreamPromises: Promise[] = []; + const muxStreamPromises: Promise[] = []; params.video.forEach(p => muxStreamPromises.push(runStreams('video', p.sources, p.filter as Filterer, p.streams, mux, muxBalancer))); params.audio.forEach(p => muxStreamPromises.push(runStreams('audio', p.sources, p.filter as Filterer, p.streams, mux, muxBalancer))); await Promise.all(muxStreamPromises); diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts index 8dd1229..8c08427 100644 --- a/ts/parallelBalancer.ts +++ b/ts/parallelBalancer.ts @@ -36,7 +36,7 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } name: `in${pend.streamIndex}:${tag}`, frames: [pend.pkt] }; - }), // as any[] & Timable, + }), done: false }); resolveGet = null; diff --git a/ts/teeBalancer.ts b/ts/teeBalancer.ts index 858a746..a7a843a 100644 --- a/ts/teeBalancer.ts +++ b/ts/teeBalancer.ts @@ -30,7 +30,7 @@ export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }); }; - const readStreams: teeBalancerType = [] as any as teeBalancerType; + const readStreams: teeBalancerType = [] as teeBalancerType; for (let s = 0; s < numStreams; ++s) readStreams.push(new Readable({ objectMode: true, From cb71dac7fc73f1a103536ffd57440a560a6f94c9 Mon Sep 17 00:00:00 2001 From: urielch Date: Sat, 30 Apr 2022 21:15:36 +0300 Subject: [PATCH 36/68] improve typings --- scratch/decode_pcm.ts | 2 +- ts/beamstreams.ts | 80 +++++++++++++++++++++------------------ ts/teeBalancer.ts | 5 +-- ts/types/Beamstreams.d.ts | 12 +++--- 4 files changed, 53 insertions(+), 46 deletions(-) diff --git a/scratch/decode_pcm.ts b/scratch/decode_pcm.ts index ebb1cb0..d9bbe70 100644 --- a/scratch/decode_pcm.ts +++ b/scratch/decode_pcm.ts @@ -25,7 +25,7 @@ import { getMedia } from './common'; async function run() { let demuxer = await beamcoder.demuxer(getMedia('dpp/AS11_DPP_HD_EXAMPLE_1.mxf')); console.log(demuxer.streams[1]); - let decoder = await beamcoder.decoder({ demuxer: demuxer, stream_index : 1 }); + let decoder = beamcoder.decoder({ demuxer: demuxer, stream_index : 1 }); console.log(decoder); for ( let x = 0 ; x < 100 ; x++ ) { let packet = await demuxer.read(); diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index b45725d..dde2cdf 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -34,7 +34,7 @@ import { Frame } from './types/Frame'; import { Packet } from './types/Packet'; import { Timing } from './types/Timing'; import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, ReadableMuxerStream, WritableDemuxerStream } from './types/Beamstreams'; -import { Filterer, FilterGraph, FilterLink } from './types/Filter'; +import { FilterGraph, FilterLink } from './types/Filter'; import { CodecContextBaseMin } from './types/CodecContext'; import { Timable, Timables } from './types/Timable' import { EncodedPackets } from './types/Encoder'; @@ -171,11 +171,11 @@ const calcStats = (arr: Array, elem: string, prop: string): ffStats => { return { mean, stdDev, max, min }; }; -function writeStream(params: { name: string, highWaterMark?: number }, processFn: (val: EncodedPackets) => Promise, finalFn: () => Promise, reject: (err: Error) => void) { +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: EncodedPackets, encoding: BufferEncoding, cb: (error?: Error | null, result?: any) => void) { + write(val: T, encoding: BufferEncoding, cb: (error?: Error | null, result?: any) => void) { (async () => { const start = process.hrtime(); const reqTime = start[0] * 1e3 + start[1] / 1e6; @@ -314,24 +314,24 @@ export async function makeSources(params: BeamstreamParams): Promise { 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 }); + src.formatP = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); } else - src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options}); + src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options}); })); params.audio.forEach(p => p.sources.forEach((src: BeamstreamSource) => { 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 }); + src.formatP = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); } else - src.format = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options}); + src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options}); })); await (params.video.reduce)(async (promise: Promise, p: BeamstreamChannel) => { await promise; return p.sources.reduce(async (promise: Promise, src: BeamstreamSource) => { await promise; - src.format = await src.format; + src.format = await src.formatP; if (src.ms && !src.input_stream) src.format.seek({ time: src.ms.start }); return src.format; @@ -342,7 +342,7 @@ export async function makeSources(params: BeamstreamParams): Promise { await promise; return p.sources.reduce(async (promise: Promise, src: BeamstreamSource) => { await promise; - src.format = await src.format; + src.format = await src.formatP; if (src.ms && !src.input_stream) src.format.seek({ time: src.ms.start }); return src.format; @@ -350,9 +350,9 @@ export async function makeSources(params: BeamstreamParams): Promise { }, Promise.resolve()); params.video.forEach((p: BeamstreamChannel) => p.sources.forEach(src => - src.stream = readStream({ highWaterMark: 1 }, src.format as Demuxer, src.ms, src.streamIndex))); + src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex))); params.audio.forEach((p: BeamstreamChannel) => p.sources.forEach(src => - src.stream = readStream({ highWaterMark: 1 }, src.format as Demuxer, src.ms, src.streamIndex))); + src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex))); } function runStreams( @@ -368,7 +368,7 @@ function runStreams( if (!sources.length) return resolve(); - const timeBaseStream: Stream = (sources[0].format as Demuxer).streams[sources[0].streamIndex]; + const timeBaseStream: Stream = sources[0].format.streams[sources[0].streamIndex]; const filterBalancer = parallelBalancer({ name: 'filterBalance', highWaterMark: 1 }, streamType, sources.length); sources.forEach((src: BeamstreamSource, srcIndex: number) => { @@ -376,23 +376,32 @@ function runStreams( { name: 'decode', highWaterMark: 1 }, (pkts: Packet) => src.decoder.decode(pkts), () => src.decoder.flush(), reject); - const filterSource = writeStream({ name: 'filterSource', highWaterMark: 1 }, - // @ts-ignore - (pkts: DecodedFrames) => filterBalancer.pushPkts(pkts, (src.format as Demuxer).streams[src.streamIndex], srcIndex), - () => filterBalancer.pushPkts(null, (src.format as Demuxer).streams[src.streamIndex], srcIndex, true), reject); + const filterSource = writeStream( + { name: 'filterSource', highWaterMark: 1 }, + (pkts: DecodedFrames) => { + console.log(pkts) + return 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({ name: 'filter', highWaterMark: 1 }, (frms: Timables) => { - // @ts-ignore if (filterer.cb) filterer.cb(frms[0].frames[0].pts); return filterer.filter(frms); }, () => { }, reject); - const streamSource = writeStream({ name: 'streamSource', highWaterMark: 1 }, - // @ts-ignore - frms => streamTee.pushFrames(frms), () => streamTee.pushFrames([], true), reject); + + const streamSource = writeStream>( + { name: 'streamSource', highWaterMark: 1 }, + frms => streamTee.pushFrames(frms), + () => streamTee.pushFrames([], true), + reject + ); filterBalancer.pipe(filtStream).pipe(streamSource); streams.forEach((str: BeamstreamStream, i: number) => { @@ -404,11 +413,9 @@ function runStreams( const encStream = transformStream, any>({ name: 'encode', highWaterMark: 1 }, (frms) => str.encoder.encode(frms), () => str.encoder.flush(), reject); - const muxStream = writeStream( + const muxStream = writeStream( { name: 'mux', highWaterMark: 1 }, - (pkts: EncodedPackets) => { - return muxBalancer.writePkts(pkts, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts)) - }, + (pkts: EncodedPackets) => muxBalancer.writePkts(pkts, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts)), () => muxBalancer.writePkts(null, timeBaseStream, str.stream, pkts => mux.writeFrame(pkts), true), reject); @@ -422,20 +429,19 @@ function runStreams( export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Promise }> { params.video.forEach((p: BeamstreamChannel) => { p.sources.forEach((src: BeamstreamSource) => - src.decoder = beamcoder.decoder({ demuxer: src.format as Demuxer, stream_index: src.streamIndex })); + src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); }); params.audio.forEach((p: BeamstreamChannel) => { p.sources.forEach((src: BeamstreamSource) => - src.decoder = beamcoder.decoder({ demuxer: src.format as Demuxer, stream_index: src.streamIndex })); - // {demuxer: Demuxer | Promise, stream_index: number} + src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); }); params.video.forEach((p: BeamstreamChannel) => { - p.filter = beamcoder.filterer({ + p.filterP = beamcoder.filterer({ // FiltererVideoOptions filterType: 'video', inputParams: p.sources.map((src, i) => { - const stream = (src.format as Demuxer).streams[src.streamIndex]; + const stream = src.format.streams[src.streamIndex]; return { name: `in${i}:v`, width: stream.codecpar.width, @@ -449,15 +455,15 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr filterSpec: p.filterSpec }); }); - const vidFilts = await Promise.all(params.video.map(p => p.filter)); + const vidFilts = await Promise.all(params.video.map(p => p.filterP)); params.video.forEach((p: BeamstreamChannel, i: number) => p.filter = vidFilts[i]); // params.video.forEach(p => console.log(p.filter.graph.dump())); params.audio.forEach((p: BeamstreamChannel) => { - p.filter = beamcoder.filterer({ + p.filterP = beamcoder.filterer({ filterType: 'audio', inputParams: p.sources.map((src: BeamstreamSource, i: number) => { - const stream = (src.format as Demuxer).streams[src.streamIndex]; + const stream = src.format.streams[src.streamIndex]; return { name: `in${i}:a`, sampleRate: src.decoder.sample_rate, @@ -477,7 +483,7 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr filterSpec: p.filterSpec }); }); - const audFilts = await Promise.all(params.audio.map((p: BeamstreamChannel) => p.filter)); + const audFilts = await Promise.all(params.audio.map((p: BeamstreamChannel) => p.filterP)); params.audio.forEach((p: BeamstreamChannel, i: number) => p.filter = audFilts[i]); // params.audio.forEach(p => console.log(p.filter.graph.dump())); @@ -491,7 +497,7 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr params.video.forEach((p: BeamstreamChannel) => { p.streams.forEach((str: BeamstreamStream, i: number) => { - const encParams = (p.filter as Filterer).graph.filters.find(f => f.name === `out${i}:v`).inputs[0]; + 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, @@ -511,7 +517,7 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr params.audio.forEach((p: BeamstreamChannel) => { p.streams.forEach((str: BeamstreamStream, i: number) => { - const encParams: FilterLink = (p.filter as Filterer).graph.filters.find(f => f.name === `out${i}:a`).inputs[0]; + const encParams: FilterLink = p.filter.graph.filters.find(f => f.name === `out${i}:a`).inputs[0]; str.encoder = beamcoder.encoder({ name: str.name, sample_fmt: encParams.format, @@ -555,8 +561,8 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr const muxBalancer = new serialBalancer(mux.streams.length); const muxStreamPromises: Promise[] = []; - params.video.forEach(p => muxStreamPromises.push(runStreams('video', p.sources, p.filter as Filterer, p.streams, mux, muxBalancer))); - params.audio.forEach(p => muxStreamPromises.push(runStreams('audio', p.sources, p.filter as Filterer, p.streams, mux, muxBalancer))); + 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(); diff --git a/ts/teeBalancer.ts b/ts/teeBalancer.ts index a7a843a..1f7a588 100644 --- a/ts/teeBalancer.ts +++ b/ts/teeBalancer.ts @@ -1,10 +1,11 @@ import { Readable } from "stream"; import { Frame } from "./types/Frame"; +import { Timables } from "./types/Timable"; import { Timing } from "./types/Timing"; type BalanceResult = { value: { timings: Timing }, done: boolean, final?: boolean }; -type teeBalancerType = Readable[] & { pushFrames: (frames: Frame[] & {timings: Timing}, unusedFlag?: boolean) => any }; +type teeBalancerType = Readable[] & { pushFrames: (frames: Timables, unusedFlag?: boolean) => any }; export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, numStreams: number): teeBalancerType { let resolvePush: null | ((result?: BalanceResult) => void) = null; @@ -43,7 +44,6 @@ export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number if (result.done) this.push(null); else { - // @ts-ignore result.value.timings[params.name] = { reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; this.push(result.value); } @@ -64,7 +64,6 @@ export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number pending.forEach(p => { if (p.resolve) { if (p.frames) { - // @ts-ignore p.frames.timings = frames.timings; p.resolve({ value: p.frames, done: false }); } else if (p.final) diff --git a/ts/types/Beamstreams.d.ts b/ts/types/Beamstreams.d.ts index 3c0b8cf..aa38290 100644 --- a/ts/types/Beamstreams.d.ts +++ b/ts/types/Beamstreams.d.ts @@ -94,7 +94,8 @@ export interface BeamstreamSource { streamIndex: number iformat?: InputFormat options?: { [key: string]: any } - format?: Demuxer | Promise; + formatP?: Promise; + format?: Demuxer; stream?: any; // FIXME decoder?: Decoder; // FIXME } @@ -110,10 +111,11 @@ export interface BeamstreamStream { } /** Definition for a channel of beamstream processing */ export interface BeamstreamChannel { - sources: Array - filterSpec: string - streams: Array - filter?: Filterer | Promise; + sources: Array; + filterSpec: string; + streams: Array; + filterP?: Promise; + filter?: Filterer; } /** * Definition for a beamstream process consisting of a number of audio and video sources From baa2bf9983495595efcbd00837aa65f4106d08a0 Mon Sep 17 00:00:00 2001 From: urielch Date: Sat, 30 Apr 2022 22:05:19 +0300 Subject: [PATCH 37/68] droreplace reduce promise loop by simple loop --- ts/beamstreams.ts | 48 ++++++++++++++++++------------------------ ts/parallelBalancer.ts | 22 +++++++++---------- 2 files changed, 31 insertions(+), 39 deletions(-) diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index dde2cdf..ba5388f 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -46,8 +46,8 @@ const timings = [] as Array<{ [key: string]: Timing }>; class frameDicer { - private addFrame: (srcFrm: Frame) => any[]; - private getLast: () => any[]; + private addFrame: (srcFrm: Frame) => Frame[]; + private getLast: () => Frame[]; private doDice: boolean; // CodecPar constructor(encoder: CodecContextBaseMin, private isAudio: boolean) { @@ -103,8 +103,8 @@ class frameDicer { return result; }; - this.getLast = (): any[] => { - let result = []; + this.getLast = (): Frame[] => { + let result: Frame[] = []; if (lastBuf[0].length > 0) { const resFrm = beamcoder.frame(lastFrm.toJSON()); resFrm.data = lastBuf.map(d => d.slice(0)); @@ -119,11 +119,9 @@ class frameDicer { } public dice(frames: Frame[], flush = false): Frame[] { if (this.isAudio && this.doDice) { - let result: Frame[] = frames.reduce((muxFrms: Frame[], frm: Frame) => { - this.addFrame(frm).forEach(f => muxFrms.push(f)); - return muxFrms; - }, [] as Frame[]); - + 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; @@ -327,27 +325,24 @@ export async function makeSources(params: BeamstreamParams): Promise { src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options}); })); - await (params.video.reduce)(async (promise: Promise, p: BeamstreamChannel) => { - await promise; - return p.sources.reduce(async (promise: Promise, src: BeamstreamSource) => { - await promise; + + for (const video of params.video) { + for (const src of video.sources) { src.format = await src.formatP; 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: Promise, p: BeamstreamChannel) => { - await promise; - return p.sources.reduce(async (promise: Promise, src: BeamstreamSource) => { - await promise; + await src.formatP; + } + } + + for (const audio of params.audio) { + for (const src of audio.sources) { src.format = await src.formatP; if (src.ms && !src.input_stream) src.format.seek({ time: src.ms.start }); - return src.format; - }, Promise.resolve()); - }, Promise.resolve()); + await src.formatP; + } + } params.video.forEach((p: BeamstreamChannel) => p.sources.forEach(src => src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex))); @@ -379,10 +374,7 @@ function runStreams( const filterSource = writeStream( { name: 'filterSource', highWaterMark: 1 }, - (pkts: DecodedFrames) => { - console.log(pkts) - return filterBalancer.pushPkts(pkts, src.format.streams[src.streamIndex], srcIndex, false) - }, + (pkts: DecodedFrames) => filterBalancer.pushPkts(pkts, src.format.streams[src.streamIndex], srcIndex, false), () => filterBalancer.pushPkts(null, src.format.streams[src.streamIndex], srcIndex, true), reject ); diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts index 8c08427..efc090b 100644 --- a/ts/parallelBalancer.ts +++ b/ts/parallelBalancer.ts @@ -6,15 +6,15 @@ import { Timable, Timables } from "./types/Timable"; type localFrame = { pkt?: Frame, ts: number, streamIndex: number, final?: boolean, resolve?: () => void }; -type localResult = { done: boolean, value?: {name: string, frames: Timables}[] & Timable }; +type localResult = { done: boolean, value?: { name: string, frames: Timables }[] & Timable }; type parallelBalancerType = Readable & { - pushPkts: (packets: DecodedFrames, stream: Stream, streamIndex: number, final?: boolean) => any + pushPkts: (packets: DecodedFrames, stream: Stream, streamIndex: number, final?: boolean) => Promise }; export function parallelBalancer(params: { name: string, highWaterMark: number }, streamType: 'video' | 'audio', numStreams: number): parallelBalancerType { - let resolveGet: null | ((result: {value?: {name: string, frames: Frame[]}[] & Timable, done: boolean }) => void) = null; + let resolveGet: null | ((result: { value?: { name: string, frames: Frame[] }[] & Timable, done: boolean }) => void) = null; const tag = 'video' === streamType ? 'v' : 'a'; const pending: Array = []; // initialise with negative ts and no pkt @@ -24,7 +24,7 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } // const makeSet = (resolve: (result: {value?: { name: string, frames: any[] }, done: boolean}) => void) => { const makeSet = (resolve: (result: localResult) => void) => { - if (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); @@ -50,7 +50,7 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } } }; - const pushPkt = async (pkt: null | Frame, streamIndex: number, ts: number): Promise => + const pushPkt = async (pkt: Frame, streamIndex: number, ts: number): Promise => new Promise(resolve => { Object.assign(pending[streamIndex], { pkt, ts, final: pkt ? false : true, resolve }); makeSet(resolveGet); @@ -78,15 +78,15 @@ export function parallelBalancer(params: { name: string, highWaterMark: number } }, }) as parallelBalancerType; - readStream.pushPkts = (packets: DecodedFrames, stream: Stream, streamIndex: number, final = false): Promise => { + readStream.pushPkts = async (packets: DecodedFrames, stream: Stream, streamIndex: number, final = false): Promise => { if (packets && packets.frames.length) { - // @ts-ignore - return packets.frames.reduce(async (promise, pkt: Frame) => { - await promise; + let lst: localFrame = {} as localFrame; + for (const pkt of packets.frames) { const ts = pkt.pts * stream.time_base[0] / stream.time_base[1]; pkt.timings = packets.timings; - return pushPkt(pkt, streamIndex, ts); - }, Promise.resolve()); + lst = await pushPkt(pkt, streamIndex, ts); + } + return lst; } else if (final) { return pushPkt(null, streamIndex, Number.MAX_VALUE); } From a115ad9d42df1616aefaffb9010b4bcb9981e6cc Mon Sep 17 00:00:00 2001 From: urielch Date: Sat, 30 Apr 2022 22:20:05 +0300 Subject: [PATCH 38/68] fix type + drop reduce --- ts/serialBalancer.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/ts/serialBalancer.ts b/ts/serialBalancer.ts index c21e599..15580d8 100644 --- a/ts/serialBalancer.ts +++ b/ts/serialBalancer.ts @@ -19,8 +19,8 @@ export class serialBalancer { }; - pullPkts(pkt: null | Packet, streamIndex: number, ts: number): Promise { - return new Promise(resolve => { + pullPkts(pkt: void | 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); @@ -31,21 +31,24 @@ export class serialBalancer { }); }; - writePkts(packets: EncodedPackets | null, + async writePkts( + packets: EncodedPackets | null, srcStream: { time_base: [number, number] }, dstStream: { time_base: [number, number], index: number }, - writeFn: (r: void) => void, final = false) { + writeFn: (r: void | Packet) => void, + final = false + ): Promise { if (packets && packets.packets.length) { - return packets.packets.reduce(async (promise, pkt) => { - await promise; + 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]; - return writeFn(await this.pullPkts(pkt, dstStream.index, pktTS)); - }, Promise.resolve()); + const packet = await this.pullPkts(pkt, dstStream.index, pktTS) + writeFn(packet); + } } else if (final) return this.pullPkts(null, dstStream.index, Number.MAX_VALUE); }; From 25d7f509833d82e86de0d59394190d5de0ac12c2 Mon Sep 17 00:00:00 2001 From: urielch Date: Sun, 1 May 2022 00:38:11 +0300 Subject: [PATCH 39/68] more types --- ts/beamstreams.ts | 133 ++++++++++++++++++++--------------------- ts/parallelBalancer.ts | 2 +- ts/serialBalancer.ts | 16 ++--- ts/teeBalancer.ts | 8 +-- 4 files changed, 80 insertions(+), 79 deletions(-) diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index ba5388f..0baf7d2 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -21,8 +21,8 @@ import bindings from 'bindings'; import { Writable, Readable, Transform } from 'stream'; import type { ffStats } from './types'; -import { teeBalancer } from './teeBalancer'; -import { parallelBalancer } from './parallelBalancer'; +import { BalanceResult, teeBalancer } from './teeBalancer'; +import { localFrame, parallelBalancer } from './parallelBalancer'; import { serialBalancer } from './serialBalancer'; import { BeamcoderType } from './types/BeamcoderType'; import { Demuxer } from './types/Demuxer'; @@ -169,7 +169,7 @@ const calcStats = (arr: Array, elem: string, prop: string): ffStats => { return { mean, stdDev, max, min }; }; -function writeStream(params: { name: string, highWaterMark?: number }, processFn: (val: T) => Promise, finalFn: () => Promise, reject: (err: Error) => void) { +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, @@ -314,7 +314,7 @@ export async function makeSources(params: BeamstreamParams): Promise { src.input_stream.pipe(demuxerStream); src.formatP = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); } else - src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options}); + src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options }); })); params.audio.forEach(p => p.sources.forEach((src: BeamstreamSource) => { if (src.input_stream) { @@ -322,10 +322,9 @@ export async function makeSources(params: BeamstreamParams): Promise { src.input_stream.pipe(demuxerStream); src.formatP = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); } else - src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options}); + src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options }); })); - for (const video of params.video) { for (const src of video.sources) { src.format = await src.formatP; @@ -354,9 +353,9 @@ function runStreams( streamType: 'video' | 'audio', // sources: Array<{ decoder: { decode: (pkts: any) => any, flush: () => any }, format: { streams: Array<{}> }, streamIndex: any, stream: any }>, sources: Array, - filterer: { cb?: (result: any) => void, filter: (stream: Array) => any, graph: FilterGraph }, // Filterer? + filterer: { cb?: (result: number | null) => void, filter: (stream: Array) => any, graph: FilterGraph }, // Filterer? streams: Array, - mux: { writeFrame: (pkts: any) => void }, + mux: { writeFrame: (pkts: void | Packet) => void }, muxBalancer: serialBalancer): Promise { // serialBalancer // { writePkts: (packets: {timings: any; }, srcStream: {}, dstStream: {}, writeFn: {}, final?: boolean) => any } return new Promise((resolve, reject) => { @@ -372,13 +371,13 @@ function runStreams( (pkts: Packet) => src.decoder.decode(pkts), () => src.decoder.flush(), reject); - const filterSource = writeStream( + 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); }); @@ -388,7 +387,7 @@ function runStreams( return filterer.filter(frms); }, () => { }, reject); - const streamSource = writeStream>( + const streamSource = writeStream, BalanceResult | void>( { name: 'streamSource', highWaterMark: 1 }, frms => streamTee.pushFrames(frms), () => streamTee.pushFrames([], true), @@ -405,7 +404,7 @@ function runStreams( const encStream = transformStream, any>({ name: 'encode', highWaterMark: 1 }, (frms) => str.encoder.encode(frms), () => str.encoder.flush(), reject); - const muxStream = writeStream( + 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), @@ -419,64 +418,64 @@ function runStreams( } export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Promise }> { - params.video.forEach((p: BeamstreamChannel) => { - p.sources.forEach((src: BeamstreamSource) => + params.video.forEach((channel: BeamstreamChannel) => { + channel.sources.forEach((src: BeamstreamSource) => src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); }); - params.audio.forEach((p: BeamstreamChannel) => { - p.sources.forEach((src: BeamstreamSource) => + params.audio.forEach((channel: BeamstreamChannel) => { + channel.sources.forEach((src: BeamstreamSource) => src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); }); - params.video.forEach((p: BeamstreamChannel) => { - p.filterP = beamcoder.filterer({ + params.video.forEach((channel: BeamstreamChannel) => { + const inputParams = channel.sources.map((src, i) => { + 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 + }; + }); + + channel.filterP = beamcoder.filterer({ // FiltererVideoOptions 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: BeamstreamStream, i: number) => ({ name: `out${i}:v`, pixelFormat: str.codecpar.format })), - filterSpec: p.filterSpec + inputParams, + outputParams: channel.streams.map((str: BeamstreamStream, i: number) => ({ name: `out${i}:v`, pixelFormat: str.codecpar.format })), + filterSpec: channel.filterSpec }); }); const vidFilts = await Promise.all(params.video.map(p => p.filterP)); - params.video.forEach((p: BeamstreamChannel, i: number) => p.filter = vidFilts[i]); + params.video.forEach((channel: BeamstreamChannel, i: number) => channel.filter = vidFilts[i]); // params.video.forEach(p => console.log(p.filter.graph.dump())); - params.audio.forEach((p: BeamstreamChannel) => { - p.filterP = beamcoder.filterer({ - filterType: 'audio', - inputParams: p.sources.map((src: BeamstreamSource, i: number) => { - 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: BeamstreamStream, i: number) => { - return { - name: `out${i}:a`, - sampleRate: str.codecpar.sample_rate, - sampleFormat: str.codecpar.format, - channelLayout: str.codecpar.channel_layout - }; - }), - filterSpec: p.filterSpec + 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 + }; + }); + channel.filterP = beamcoder.filterer({ filterType: 'audio', inputParams, outputParams, filterSpec: channel.filterSpec }); }); const audFilts = await Promise.all(params.audio.map((p: BeamstreamChannel) => p.filterP)); - params.audio.forEach((p: BeamstreamChannel, i: number) => p.filter = audFilts[i]); + params.audio.forEach((channel: BeamstreamChannel, i: number) => channel.filter = audFilts[i]); // params.audio.forEach(p => console.log(p.filter.graph.dump())); let mux: Muxer; @@ -484,12 +483,12 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr let muxerStream = beamcoder.muxerStream({ highwaterMark: 1024 }); muxerStream.pipe(params.out.output_stream); mux = muxerStream.muxer({ format_name: params.out.formatName }); - } else + } else { mux = beamcoder.muxer({ format_name: params.out.formatName }); - - params.video.forEach((p: BeamstreamChannel) => { - p.streams.forEach((str: BeamstreamStream, i: number) => { - const encParams = p.filter.graph.filters.find(f => f.name === `out${i}:v`).inputs[0]; + } + params.video.forEach((channel: BeamstreamChannel) => { + channel.streams.forEach((str: BeamstreamStream, i: number) => { + const encParams = channel.filter.graph.filters.find(f => f.name === `out${i}:v`).inputs[0]; str.encoder = beamcoder.encoder({ name: str.name, width: encParams.w, @@ -507,9 +506,9 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr }); }); - params.audio.forEach((p: BeamstreamChannel) => { - p.streams.forEach((str: BeamstreamStream, i: number) => { - const encParams: FilterLink = p.filter.graph.filters.find(f => f.name === `out${i}:a`).inputs[0]; + params.audio.forEach((channel: BeamstreamChannel) => { + channel.streams.forEach((str: BeamstreamStream, i: number) => { + const encParams: FilterLink = channel.filter.graph.filters.find(f => f.name === `out${i}:a`).inputs[0]; str.encoder = beamcoder.encoder({ name: str.name, sample_fmt: encParams.format, @@ -521,8 +520,8 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr }); }); - params.video.forEach((p: BeamstreamChannel) => { - p.streams.forEach((str: BeamstreamStream) => { + params.video.forEach((channel: BeamstreamChannel) => { + channel.streams.forEach((str: BeamstreamStream) => { str.stream = mux.newStream({ name: str.name, time_base: str.time_base, @@ -532,8 +531,8 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr }); }); - params.audio.forEach((p: BeamstreamChannel) => { - p.streams.forEach((str: BeamstreamStream) => { + params.audio.forEach((channel: BeamstreamChannel) => { + channel.streams.forEach((str: BeamstreamStream) => { str.stream = mux.newStream({ name: str.name, time_base: str.time_base, diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts index efc090b..1aff531 100644 --- a/ts/parallelBalancer.ts +++ b/ts/parallelBalancer.ts @@ -5,7 +5,7 @@ import { Stream } from "./types/Stream"; import { Timable, Timables } from "./types/Timable"; -type localFrame = { pkt?: Frame, ts: number, streamIndex: number, final?: boolean, resolve?: () => void }; +export type localFrame = { pkt?: Frame, ts: number, streamIndex: number, final?: boolean, resolve?: () => void }; type localResult = { done: boolean, value?: { name: string, frames: Timables }[] & Timable }; diff --git a/ts/serialBalancer.ts b/ts/serialBalancer.ts index 15580d8..1f88153 100644 --- a/ts/serialBalancer.ts +++ b/ts/serialBalancer.ts @@ -2,13 +2,13 @@ import { EncodedPackets } from "./types/Encoder"; import { Packet } from "./types/Packet"; export class serialBalancer { - pending = [] as { ts: number, streamIndex: number, resolve?: (result: any) => void, pkt?: Packet }[]; + pending = [] as { 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 }); + for (let streamIndex = 0; streamIndex < numStreams; ++streamIndex) + this.pending.push({ ts: -Number.MAX_VALUE, streamIndex, pkt: null }); } adjustTS(pkt: { pts: number, dts: number, duration: number }, srcTB: [number, number], dstTB: [number, number]): void { @@ -18,14 +18,16 @@ export class serialBalancer { pkt.duration > 0 ? Math.round(pkt.duration * adj) : Math.round(adj); }; - - pullPkts(pkt: void | Packet, streamIndex: number, ts: number): Promise { + pullPkts(pkt: null | Packet, streamIndex: number, ts: number): Promise { return new Promise(resolve => { - Object.assign(this.pending[streamIndex], { pkt, ts, resolve }); + const pending = this.pending[streamIndex]; + pending.pkt = pkt; + pending.ts = ts; + pending.resolve = resolve; + // TODO loop only once 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)); - // @ts-ignore if (nextPend) nextPend.resolve(nextPend.pkt); if (!pkt) resolve(); }); diff --git a/ts/teeBalancer.ts b/ts/teeBalancer.ts index 1f7a588..cbfb917 100644 --- a/ts/teeBalancer.ts +++ b/ts/teeBalancer.ts @@ -3,9 +3,9 @@ import { Frame } from "./types/Frame"; import { Timables } from "./types/Timable"; import { Timing } from "./types/Timing"; -type BalanceResult = { value: { timings: Timing }, done: boolean, final?: boolean }; +export type BalanceResult = { value: { timings: Timing }, done: boolean, final?: boolean }; -type teeBalancerType = Readable[] & { pushFrames: (frames: Timables, unusedFlag?: boolean) => any }; +type teeBalancerType = Readable[] & { pushFrames: (frames: Timables, unusedFlag?: boolean) => Promise }; export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, numStreams: number): teeBalancerType { let resolvePush: null | ((result?: BalanceResult) => void) = null; @@ -51,8 +51,8 @@ export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, })); - readStreams.pushFrames = frames => { - return new Promise(resolve => { + readStreams.pushFrames = (frames): Promise => { + return new Promise(resolve => { pending.forEach((p, index) => { if (frames.length) // @ts-ignore From dbcad711fd8ec54fd2f3774dba0cd61e6bb4b2a8 Mon Sep 17 00:00:00 2001 From: urielch Date: Sun, 1 May 2022 08:39:30 +0300 Subject: [PATCH 40/68] clean --- ts/beamcoder.ts | 5 ++ ts/beamstreams.ts | 116 ++++++---------------------------------------- ts/frameDicer.ts | 93 +++++++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 101 deletions(-) create mode 100644 ts/beamcoder.ts create mode 100644 ts/frameDicer.ts diff --git a/ts/beamcoder.ts b/ts/beamcoder.ts new file mode 100644 index 0000000..0d77ae5 --- /dev/null +++ b/ts/beamcoder.ts @@ -0,0 +1,5 @@ +import bindings from 'bindings'; +import { BeamcoderType } from './types/BeamcoderType'; + +const beamcoder = bindings('beamcoder') as BeamcoderType; +export default beamcoder; \ No newline at end of file diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index 0baf7d2..19f47b9 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -18,13 +18,11 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import bindings from 'bindings'; import { Writable, Readable, Transform } from 'stream'; import type { ffStats } from './types'; import { BalanceResult, teeBalancer } from './teeBalancer'; import { localFrame, parallelBalancer } from './parallelBalancer'; import { serialBalancer } from './serialBalancer'; -import { BeamcoderType } from './types/BeamcoderType'; import { Demuxer } from './types/Demuxer'; import { Muxer } from './types/Muxer'; import { Stream } from './types/Stream'; @@ -35,112 +33,26 @@ import { Packet } from './types/Packet'; import { Timing } from './types/Timing'; import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, ReadableMuxerStream, WritableDemuxerStream } from './types/Beamstreams'; import { FilterGraph, FilterLink } from './types/Filter'; -import { CodecContextBaseMin } from './types/CodecContext'; import { Timable, Timables } from './types/Timable' import { EncodedPackets } from './types/Encoder'; -const beamcoder = bindings('beamcoder') as BeamcoderType; +import beamcoder from './beamcoder' +import frameDicer from './frameDicer'; const doTimings = false; const timings = [] as Array<{ [key: string]: Timing }>; -class frameDicer { - - private addFrame: (srcFrm: Frame) => Frame[]; - private getLast: () => Frame[]; - private doDice: boolean; - // CodecPar - constructor(encoder: CodecContextBaseMin, private isAudio: boolean) { - let sampleBytes = 4; // Assume floating point 4 byte samples for now... - const numChannels = encoder.channels; - const dstNumSamples = encoder.frame_size; - let dstFrmBytes = dstNumSamples * sampleBytes; - this.doDice = false === beamcoder.encoders()[encoder.name].capabilities.VARIABLE_FRAME_SIZE; - - let lastFrm: Frame = null as Frame; - let lastBuf: Buffer[] = []; - const nullBuf: Buffer[] = []; - for (let b = 0; b < numChannels; ++b) - nullBuf.push(Buffer.alloc(0)); - - this.addFrame = (srcFrm: Frame): Frame[] => { - let result: Frame[] = []; - let dstFrm: Frame; - let curStart = 0; - if (!lastFrm) { - lastFrm = beamcoder.frame(srcFrm.toJSON()) as Frame; - 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; - }; - - this.getLast = (): Frame[] => { - let result: Frame[] = []; - 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; - }; - } - 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; - }; -} -// T = Frame | Frame[] | Packet -// D = Promise, DecodedFrames -function transformStream( +// SRC = Frame | Frame[] | Packet +// DST = Promise, DecodedFrames +function transformStream( params: { name: 'encode' | 'dice' | 'decode' | 'filter', highWaterMark: number }, - processFn: (val: T) => D, - flushFn: () => D | null | void, + 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: T, encoding, cb) { + transform(val: SRC, encoding, cb) { (async () => { const start = process.hrtime(); const reqTime = start[0] * 1e3 + start[1] / 1e6; @@ -162,10 +74,13 @@ function transformStream( } const calcStats = (arr: Array, elem: string, prop: string): ffStats => { - const mean: number = arr.reduce((acc, cur) => cur[elem] ? acc + cur[elem][prop] : acc, 0) / arr.length; - const stdDev: number = Math.pow(arr.reduce((acc, cur) => cur[elem] ? acc + Math.pow(cur[elem][prop] - mean, 2) : acc, 0) / arr.length, 0.5); - const max: number = arr.reduce((acc, cur) => cur[elem] ? Math.max(cur[elem][prop], acc) : acc, 0); - const min: number = arr.reduce((acc, cur) => cur[elem] ? Math.min(cur[elem][prop], acc) : acc, Number.MAX_VALUE); + 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 }; }; @@ -275,7 +190,6 @@ export function demuxerStream(params: { highwaterMark?: number }): WritableDemux return stream; } - function createBeamReadableStream(params: { highwaterMark?: number }, governor: Governor): Readable { const beamStream = new Readable({ highWaterMark: params.highwaterMark || 16384, diff --git a/ts/frameDicer.ts b/ts/frameDicer.ts new file mode 100644 index 0000000..28c3590 --- /dev/null +++ b/ts/frameDicer.ts @@ -0,0 +1,93 @@ +import { CodecContextBaseMin } from "./types/CodecContext"; +import { Frame } from "./types/Frame"; +import beamcoder from './beamcoder' + +export default class frameDicer { + private addFrame: (srcFrm: Frame) => Frame[]; + private getLast: () => Frame[]; + private doDice: boolean; + // CodecPar + constructor(encoder: CodecContextBaseMin, private isAudio: boolean) { + let sampleBytes = 4; // Assume floating point 4 byte samples for now... + const numChannels = encoder.channels; + const dstNumSamples = encoder.frame_size; + let dstFrmBytes = dstNumSamples * sampleBytes; + this.doDice = false === beamcoder.encoders()[encoder.name].capabilities.VARIABLE_FRAME_SIZE; + + let lastFrm: Frame = null as Frame; + let lastBuf: Buffer[] = []; + const nullBuf: Buffer[] = []; + for (let b = 0; b < numChannels; ++b) + nullBuf.push(Buffer.alloc(0)); + + this.addFrame = (srcFrm: Frame): Frame[] => { + let result: Frame[] = []; + let dstFrm: Frame; + let curStart = 0; + if (!lastFrm) { + lastFrm = beamcoder.frame(srcFrm.toJSON()) as Frame; + 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; + }; + + this.getLast = (): Frame[] => { + let result: Frame[] = []; + 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; + }; + } + 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; + }; + } + + + + \ No newline at end of file From 0558121fd66e30d6af62895ea7dd86f98bd68d3c Mon Sep 17 00:00:00 2001 From: urielch Date: Sun, 1 May 2022 08:55:28 +0300 Subject: [PATCH 41/68] drop any types --- ts/beamstreams.ts | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index 19f47b9..e95a763 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -73,7 +73,7 @@ function transformStream( }).on('error', err => reject(err)); } -const calcStats = (arr: Array, elem: string, prop: string): ffStats => { +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) @@ -107,7 +107,7 @@ function writeStream(params: { name: string, highWaterMark 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')); + Object.keys(timings[0]).forEach(k => elapsedStats[k] = calcStats(timings.slice(10, -10), k, 'elapsed')); console.log('elapsed:'); console.table(elapsedStats); @@ -118,7 +118,7 @@ function writeStream(params: { name: string, highWaterMark return absDelays; }); const absStats = {} as { [key: string]: ffStats }; - Object.keys(absArr[0]).forEach(k => absStats[k] = calcStats(absArr.slice(10, -10), k, 'reqDelta')); + Object.keys(absArr[0]).forEach(k => absStats[k] = calcStats(absArr.slice(10, -10), k, 'reqDelta')); console.log('request time delta:'); console.table(absStats); @@ -127,7 +127,7 @@ function writeStream(params: { name: string, highWaterMark return { total: { total: total } }; }); console.log('total time:'); - console.table(calcStats(totalsArr.slice(10, -10), 'total', 'total')); + console.table(calcStats<'total', 'total'>(totalsArr.slice(10, -10), 'total', 'total')); } cb(null, result); })().catch(cb); @@ -265,13 +265,11 @@ export async function makeSources(params: BeamstreamParams): Promise { function runStreams( streamType: 'video' | 'audio', - // sources: Array<{ decoder: { decode: (pkts: any) => any, flush: () => any }, format: { streams: Array<{}> }, streamIndex: any, stream: any }>, sources: Array, filterer: { cb?: (result: number | null) => void, filter: (stream: Array) => any, graph: FilterGraph }, // Filterer? streams: Array, mux: { writeFrame: (pkts: void | Packet) => void }, muxBalancer: serialBalancer): Promise { - // serialBalancer // { writePkts: (packets: {timings: any; }, srcStream: {}, dstStream: {}, writeFn: {}, final?: boolean) => any } return new Promise((resolve, reject) => { if (!sources.length) return resolve(); @@ -280,7 +278,7 @@ function runStreams( const filterBalancer = parallelBalancer({ name: 'filterBalance', highWaterMark: 1 }, streamType, sources.length); sources.forEach((src: BeamstreamSource, srcIndex: number) => { - const decStream = transformStream & Timable>( + const decStream = transformStream>( { name: 'decode', highWaterMark: 1 }, (pkts: Packet) => src.decoder.decode(pkts), () => src.decoder.flush(), reject); @@ -312,17 +310,26 @@ function runStreams( 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 diceStream = transformStream, Timables>( + { name: 'dice', highWaterMark: 1 }, + (frms) => dicer.dice(frms), + () => dicer.dice([], true), + reject + ); - const encStream = transformStream, any>({ name: 'encode', highWaterMark: 1 }, - (frms) => str.encoder.encode(frms), () => str.encoder.flush(), 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); + reject + ); muxStream.on('finish', resolve); From ad28e996a1af5abdad8aeae2d4324035cacf6a07 Mon Sep 17 00:00:00 2001 From: urielch Date: Sun, 1 May 2022 10:24:37 +0300 Subject: [PATCH 42/68] fix types --- ts/beamstreams.ts | 12 ++++++++---- ts/types/DecodedFrames.d.ts | 5 ++--- ts/types/Encoder.d.ts | 5 ++--- ts/types/Filter.d.ts | 11 ++++++++--- ts/types/Timing.d.ts | 5 +++++ 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index e95a763..f44a035 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -30,9 +30,9 @@ import { DecodedFrames } from './types/DecodedFrames'; import type { Governor } from './types/Governor'; import { Frame } from './types/Frame'; import { Packet } from './types/Packet'; -import { Timing } from './types/Timing'; +import { Timing, TotalTimeed } from './types/Timing'; import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, ReadableMuxerStream, WritableDemuxerStream } from './types/Beamstreams'; -import { FilterGraph, FilterLink } from './types/Filter'; +import { Filterer, FiltererResult, FilterLink } from './types/Filter'; import { Timable, Timables } from './types/Timable' import { EncodedPackets } from './types/Encoder'; @@ -266,7 +266,7 @@ export async function makeSources(params: BeamstreamParams): Promise { function runStreams( streamType: 'video' | 'audio', sources: Array, - filterer: { cb?: (result: number | null) => void, filter: (stream: Array) => any, graph: FilterGraph }, // Filterer? + filterer: Filterer, streams: Array, mux: { writeFrame: (pkts: void | Packet) => void }, muxBalancer: serialBalancer): Promise { @@ -294,7 +294,8 @@ function runStreams( }); const streamTee = teeBalancer({ name: 'streamTee', highWaterMark: 1 }, streams.length); - const filtStream = transformStream({ name: 'filter', highWaterMark: 1 }, (frms: Timables) => { + + const filtStream = transformStream, Timable & Promise & TotalTimeed>>({ name: 'filter', highWaterMark: 1 }, (frms: Timables) => { if (filterer.cb) filterer.cb(frms[0].frames[0].pts); return filterer.filter(frms); }, () => { }, reject); @@ -370,6 +371,9 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr }); }); const vidFilts = await Promise.all(params.video.map(p => p.filterP)); + + console.log(vidFilts); + params.video.forEach((channel: BeamstreamChannel, i: number) => channel.filter = vidFilts[i]); // params.video.forEach(p => console.log(p.filter.graph.dump())); diff --git a/ts/types/DecodedFrames.d.ts b/ts/types/DecodedFrames.d.ts index 8a9fa75..8029ca5 100644 --- a/ts/types/DecodedFrames.d.ts +++ b/ts/types/DecodedFrames.d.ts @@ -1,8 +1,9 @@ import { Frame } from "./Frame" import { Timable } from "./Timable" +import { TotalTimeed } from "./Timing" /** The DecodedFrames object is returned as the result of a decode operation */ -export interface DecodedFrames extends Timable { +export interface DecodedFrames extends Timable, TotalTimeed { /** Object name. */ readonly type: 'frames' /** @@ -10,8 +11,6 @@ export interface DecodedFrames extends Timable { * the packet as part of the process of producing future frames */ readonly frames: Array - /** Total time in microseconds that the decode operation took to complete */ - readonly total_time: number // "encode" | "dice" | "decode" | "filter" // timings?: { [key: string]: Timing; }; diff --git a/ts/types/Encoder.d.ts b/ts/types/Encoder.d.ts index 1ace128..7eddf56 100644 --- a/ts/types/Encoder.d.ts +++ b/ts/types/Encoder.d.ts @@ -3,9 +3,10 @@ import { Packet } from "./Packet"; import { Frame } from "./Frame"; import { CodecContextBaseMin } from "./CodecContext" import { Timable } from "./Timable"; +import { TotalTimeed } from "./Timing"; /** The EncodedPackets object is returned as the result of a encode operation */ -export interface EncodedPackets extends Timable { +export interface EncodedPackets extends Timable, TotalTimeed { /** Object name. */ readonly type: 'packets' /** @@ -13,8 +14,6 @@ export interface EncodedPackets extends Timable { * the frame as part of the process of prodfcodec_tagucing future packets */ readonly packets: Array - /** Total time in microseconds that the encode operation took to complete */ - readonly total_time: number } /** * Encoder takes a stream of uncompressed data in the form of Frames and converts them into coded Packets. diff --git a/ts/types/Filter.d.ts b/ts/types/Filter.d.ts index 4d67bce..20c054a 100644 --- a/ts/types/Filter.d.ts +++ b/ts/types/Filter.d.ts @@ -1,5 +1,7 @@ import { Frame } from "./Frame" import { PrivClass } from "./PrivClass" +import { Timable } from "./Timable" +import { TotalTimeed } from "./Timing" export interface FilterFlags { /** @@ -212,7 +214,7 @@ export interface FiltererResult { readonly frames: Array } -export interface Filterer { +export interface Filterer extends Timable { readonly type: 'Filterer' readonly graph: FilterGraph @@ -224,7 +226,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 & TotalTimeed> /** * Filter an array of frames * Pass an array of objects, one per filter input, each with a name string property @@ -233,7 +235,10 @@ 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 }>): Timable & Promise & TotalTimeed> + + // may add a callback + cb?: (pts: number | null) => void; } diff --git a/ts/types/Timing.d.ts b/ts/types/Timing.d.ts index cf71dd7..cbe3a27 100644 --- a/ts/types/Timing.d.ts +++ b/ts/types/Timing.d.ts @@ -2,3 +2,8 @@ export interface Timing { reqTime: number; elapsed: number; } + +export interface TotalTimeed { + /** Total time in microseconds that the decode operation took to complete */ + total_time: number +} \ No newline at end of file From 0508165f6a58567a0119cb8e1ccbd980870f9bc7 Mon Sep 17 00:00:00 2001 From: urielch Date: Sun, 1 May 2022 13:47:09 +0300 Subject: [PATCH 43/68] drop promise storage --- ts/beamstreams.ts | 29 ++++++++++++++--------------- ts/teeBalancer.ts | 8 ++++---- ts/types/Beamstreams.d.ts | 1 - 3 files changed, 18 insertions(+), 20 deletions(-) diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts index f44a035..0cd7575 100644 --- a/ts/beamstreams.ts +++ b/ts/beamstreams.ts @@ -73,9 +73,9 @@ function transformStream( }).on('error', err => reject(err)); } -function calcStats(arr: Array<{[key in K]: {[prop in P]: number}}>, elem: K, prop: P): ffStats { +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 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 @@ -294,7 +294,7 @@ function runStreams( }); const streamTee = teeBalancer({ name: 'streamTee', highWaterMark: 1 }, streams.length); - + const filtStream = transformStream, Timable & Promise & TotalTimeed>>({ name: 'filter', highWaterMark: 1 }, (frms: Timables) => { if (filterer.cb) filterer.cb(frms[0].frames[0].pts); return filterer.filter(frms); @@ -349,6 +349,8 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); }); + const promises: Promise[] = []; + params.video.forEach((channel: BeamstreamChannel) => { const inputParams = channel.sources.map((src, i) => { const { codecpar, time_base, sample_aspect_ratio } = src.format.streams[src.streamIndex]; @@ -362,20 +364,14 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr }; }); - channel.filterP = beamcoder.filterer({ - // FiltererVideoOptions + const p = beamcoder.filterer({ filterType: 'video', inputParams, outputParams: channel.streams.map((str: BeamstreamStream, i: number) => ({ name: `out${i}:v`, pixelFormat: str.codecpar.format })), filterSpec: channel.filterSpec - }); + }).then(filter => channel.filter = filter); + promises.push(p); }); - const vidFilts = await Promise.all(params.video.map(p => p.filterP)); - - console.log(vidFilts); - - params.video.forEach((channel: BeamstreamChannel, i: number) => channel.filter = vidFilts[i]); - // params.video.forEach(p => console.log(p.filter.graph.dump())); params.audio.forEach((channel: BeamstreamChannel) => { const inputParams = channel.sources.map((src: BeamstreamSource, i: number) => { @@ -397,10 +393,13 @@ export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Pr channelLayout: channel_layout }; }); - channel.filterP = beamcoder.filterer({ filterType: 'audio', inputParams, outputParams, filterSpec: channel.filterSpec }); + const p = beamcoder.filterer({ filterType: 'audio', inputParams, outputParams, filterSpec: channel.filterSpec }) + .then(filter => channel.filter = filter); + promises.push(p); }); - const audFilts = await Promise.all(params.audio.map((p: BeamstreamChannel) => p.filterP)); - params.audio.forEach((channel: BeamstreamChannel, i: number) => channel.filter = audFilts[i]); + await Promise.all(promises); + + // params.video.forEach(p => console.log(p.filter.graph.dump())); // params.audio.forEach(p => console.log(p.filter.graph.dump())); let mux: Muxer; diff --git a/ts/teeBalancer.ts b/ts/teeBalancer.ts index cbfb917..a45bb1a 100644 --- a/ts/teeBalancer.ts +++ b/ts/teeBalancer.ts @@ -1,7 +1,7 @@ import { Readable } from "stream"; import { Frame } from "./types/Frame"; -import { Timables } from "./types/Timable"; -import { Timing } from "./types/Timing"; +import { Timable, Timables } from "./types/Timable"; +import { Timing, TotalTimeed } from "./types/Timing"; export type BalanceResult = { value: { timings: Timing }, done: boolean, final?: boolean }; @@ -51,11 +51,11 @@ export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, })); - readStreams.pushFrames = (frames): Promise => { + // {frames: Frame[], name: string} + readStreams.pushFrames = (frames: Array & TotalTimeed & Timable): Promise => { return new Promise(resolve => { pending.forEach((p, index) => { if (frames.length) - // @ts-ignore p.frames = frames[index].frames; else p.final = true; diff --git a/ts/types/Beamstreams.d.ts b/ts/types/Beamstreams.d.ts index aa38290..1537bb1 100644 --- a/ts/types/Beamstreams.d.ts +++ b/ts/types/Beamstreams.d.ts @@ -114,7 +114,6 @@ export interface BeamstreamChannel { sources: Array; filterSpec: string; streams: Array; - filterP?: Promise; filter?: Filterer; } /** From b45abf1651234d5820a21d6190c78a85cc9ac171 Mon Sep 17 00:00:00 2001 From: urielch Date: Sun, 1 May 2022 19:52:40 +0300 Subject: [PATCH 44/68] big slpit --- scratch/stream_mp4.ts | 2 + ts/beamcoder.ts | 21 ++ ts/beamstreams.ts | 486 --------------------------------- ts/calcStats.ts | 35 +++ ts/createBeamReadableStream.ts | 40 +++ ts/createBeamWritableStream.ts | 37 +++ ts/demuxerStream.ts | 40 +++ ts/frameDicer.ts | 21 ++ ts/index.ts | 6 +- ts/install_ffmpeg.ts | 1 + ts/makeSources.ts | 69 +++++ ts/makeStreams.ts | 174 ++++++++++++ ts/muxerStream.ts | 38 +++ ts/parallelBalancer.ts | 22 +- ts/readStream.ts | 72 +++++ ts/runStreams.ts | 115 ++++++++ ts/serialBalancer.ts | 25 +- ts/teeBalancer.ts | 21 ++ ts/transformStream.ts | 55 ++++ ts/writeStream.ts | 81 ++++++ tsconfig.json | 2 +- 21 files changed, 872 insertions(+), 491 deletions(-) delete mode 100644 ts/beamstreams.ts create mode 100644 ts/calcStats.ts create mode 100644 ts/createBeamReadableStream.ts create mode 100644 ts/createBeamWritableStream.ts create mode 100644 ts/demuxerStream.ts create mode 100644 ts/makeSources.ts create mode 100644 ts/makeStreams.ts create mode 100644 ts/muxerStream.ts create mode 100644 ts/readStream.ts create mode 100644 ts/runStreams.ts create mode 100644 ts/transformStream.ts create mode 100644 ts/writeStream.ts diff --git a/scratch/stream_mp4.ts b/scratch/stream_mp4.ts index fb4cbd8..f687638 100644 --- a/scratch/stream_mp4.ts +++ b/scratch/stream_mp4.ts @@ -19,6 +19,7 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ +import path from 'path'; import beamcoder from '../ts/index'; import { getMedia } from './common'; @@ -71,6 +72,7 @@ async function run() { const beamStreams = await beamcoder.makeStreams(params); await beamStreams.run(); + console.log('output result to ', path.resolve('temp.mp4')); } console.log('Running mp4 maker'); diff --git a/ts/beamcoder.ts b/ts/beamcoder.ts index 0d77ae5..ae5a978 100644 --- a/ts/beamcoder.ts +++ b/ts/beamcoder.ts @@ -1,3 +1,24 @@ +/* + 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 { BeamcoderType } from './types/BeamcoderType'; diff --git a/ts/beamstreams.ts b/ts/beamstreams.ts deleted file mode 100644 index 0cd7575..0000000 --- a/ts/beamstreams.ts +++ /dev/null @@ -1,486 +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. -*/ -import { Writable, Readable, Transform } from 'stream'; -import type { ffStats } from './types'; -import { BalanceResult, teeBalancer } from './teeBalancer'; -import { localFrame, parallelBalancer } from './parallelBalancer'; -import { serialBalancer } from './serialBalancer'; -import { Demuxer } from './types/Demuxer'; -import { Muxer } from './types/Muxer'; -import { Stream } from './types/Stream'; -import { DecodedFrames } from './types/DecodedFrames'; -import type { Governor } from './types/Governor'; -import { Frame } from './types/Frame'; -import { Packet } from './types/Packet'; -import { Timing, TotalTimeed } from './types/Timing'; -import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, ReadableMuxerStream, WritableDemuxerStream } from './types/Beamstreams'; -import { Filterer, FiltererResult, FilterLink } from './types/Filter'; -import { Timable, Timables } from './types/Timable' -import { EncodedPackets } from './types/Encoder'; - -import beamcoder from './beamcoder' -import frameDicer from './frameDicer'; - -const doTimings = false; -const timings = [] as Array<{ [key: string]: Timing }>; - -// SRC = Frame | Frame[] | Packet -// DST = Promise, DecodedFrames -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)); -} - -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 }; -}; - -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?: any) => 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; - 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?: any) => 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)); -} - -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); - })(); - } - }); -} - -function createBeamWritableStream(params: { highwaterMark?: number }, governor: Governor): Writable { - const beamStream = new Writable({ - highWaterMark: params.highwaterMark || 16384, - write: (chunk, encoding, cb) => { - (async () => { - await governor.write(chunk); - cb(); - })(); - } - }); - return beamStream; -} - -export function demuxerStream(params: { highwaterMark?: number }): WritableDemuxerStream { - const governor = new beamcoder.governor({}); - const stream = createBeamWritableStream(params, governor) as WritableDemuxerStream; - stream.on('finish', () => governor.finish()); - stream.on('error', console.error); - stream.demuxer = (options: { governor?: Governor }) => { - 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: { highwaterMark?: number }, governor: Governor): Readable { - 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; -} - -export function muxerStream(params: { highwaterMark: number }): ReadableMuxerStream { - const governor = new beamcoder.governor({ highWaterMark: 1 }); - const stream = createBeamReadableStream(params, governor) as ReadableMuxerStream; - stream.on('end', () => governor.finish()); - stream.on('error', console.error); - stream.muxer = (options) => { - options = options || {}; - options.governor = governor; - return beamcoder.muxer(options); - }; - return stream; -} - -export async function makeSources(params: BeamstreamParams): Promise { - if (!params.video) params.video = []; - if (!params.audio) params.audio = []; - params.video.forEach(p => p.sources.forEach((src: BeamstreamSource) => { - if (src.input_stream) { - const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 }); - src.input_stream.pipe(demuxerStream); - src.formatP = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); - } else - src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options }); - })); - params.audio.forEach(p => p.sources.forEach((src: BeamstreamSource) => { - if (src.input_stream) { - const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 }); - src.input_stream.pipe(demuxerStream); - src.formatP = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); - } else - src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options }); - })); - - for (const video of params.video) { - for (const src of video.sources) { - src.format = await src.formatP; - if (src.ms && !src.input_stream) - src.format.seek({ time: src.ms.start }); - await src.formatP; - } - } - - for (const audio of params.audio) { - for (const src of audio.sources) { - src.format = await src.formatP; - if (src.ms && !src.input_stream) - src.format.seek({ time: src.ms.start }); - await src.formatP; - } - } - - params.video.forEach((p: BeamstreamChannel) => p.sources.forEach(src => - src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex))); - params.audio.forEach((p: BeamstreamChannel) => p.sources.forEach(src => - src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex))); -} - -function runStreams( - streamType: 'video' | 'audio', - sources: Array, - filterer: Filterer, - streams: Array, - mux: { writeFrame: (pkts: void | Packet) => void }, - 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 = 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 & TotalTimeed>>({ name: 'filter', highWaterMark: 1 }, (frms: Timables) => { - if (filterer.cb) filterer.cb(frms[0].frames[0].pts); - 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); - }); - }); -} - -export async function makeStreams(params: BeamstreamParams): Promise<{ run(): Promise }> { - 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[] = []; - - params.video.forEach((channel: BeamstreamChannel) => { - const inputParams = channel.sources.map((src, i) => { - 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 p = beamcoder.filterer({ - filterType: 'video', - inputParams, - outputParams: channel.streams.map((str: BeamstreamStream, i: number) => ({ name: `out${i}:v`, pixelFormat: str.codecpar.format })), - filterSpec: channel.filterSpec - }).then(filter => channel.filter = filter); - promises.push(p); - }); - - 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 p = beamcoder.filterer({ filterType: 'audio', inputParams, outputParams, filterSpec: channel.filterSpec }) - .then(filter => channel.filter = filter); - promises.push(p); - }); - await Promise.all(promises); - - // 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 = 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((channel: BeamstreamChannel) => { - channel.streams.forEach((str: BeamstreamStream, i: number) => { - const encParams = channel.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 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 ... - }); - }); - - params.audio.forEach((channel: BeamstreamChannel) => { - channel.streams.forEach((str: BeamstreamStream, i: number) => { - const encParams: FilterLink = channel.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((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); - }); - }); - - 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); - }); - }); - - 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: Promise[] = []; - 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(); - } - }; -} diff --git a/ts/calcStats.ts b/ts/calcStats.ts new file mode 100644 index 0000000..95578e0 --- /dev/null +++ b/ts/calcStats.ts @@ -0,0 +1,35 @@ +/* + 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 type { ffStats } from './types'; + +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/createBeamReadableStream.ts b/ts/createBeamReadableStream.ts new file mode 100644 index 0000000..ce36912 --- /dev/null +++ b/ts/createBeamReadableStream.ts @@ -0,0 +1,40 @@ +/* + 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 type { Governor } from './types/Governor'; + +export default function createBeamReadableStream(params: { highwaterMark?: number }, governor: Governor): Readable { + 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; + } + \ No newline at end of file diff --git a/ts/createBeamWritableStream.ts b/ts/createBeamWritableStream.ts new file mode 100644 index 0000000..daf393a --- /dev/null +++ b/ts/createBeamWritableStream.ts @@ -0,0 +1,37 @@ +/* + 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 { Governor } from './types/Governor'; + +export default function createBeamWritableStream(params: { highwaterMark?: number }, governor: Governor): Writable { + const beamStream = new Writable({ + highWaterMark: params.highwaterMark || 16384, + write: (chunk, encoding, cb) => { + (async () => { + await governor.write(chunk); + cb(); + })(); + } + }); + return beamStream; + } + \ No newline at end of file diff --git a/ts/demuxerStream.ts b/ts/demuxerStream.ts new file mode 100644 index 0000000..55b27f1 --- /dev/null +++ b/ts/demuxerStream.ts @@ -0,0 +1,40 @@ +/* + 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 type { Governor } from './types/Governor'; +import { WritableDemuxerStream } from './types/Beamstreams'; + +import beamcoder from './beamcoder' +import createBeamWritableStream from './createBeamWritableStream'; + +export default function demuxerStream(params: { highwaterMark?: number }): WritableDemuxerStream { + const governor = new beamcoder.governor({}); + const stream = createBeamWritableStream(params, governor) as WritableDemuxerStream; + stream.on('finish', () => governor.finish()); + stream.on('error', console.error); + stream.demuxer = (options: { governor?: Governor }) => { + 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; + } + diff --git a/ts/frameDicer.ts b/ts/frameDicer.ts index 28c3590..8543724 100644 --- a/ts/frameDicer.ts +++ b/ts/frameDicer.ts @@ -1,3 +1,24 @@ +/* + 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 { CodecContextBaseMin } from "./types/CodecContext"; import { Frame } from "./types/Frame"; import beamcoder from './beamcoder' diff --git a/ts/index.ts b/ts/index.ts index b4dd7ba..18c8295 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -1,6 +1,7 @@ /* 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 @@ -19,7 +20,10 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams'; +import demuxerStream from './demuxerStream'; +import muxerStream from './muxerStream'; +import makeSources from './makeSources'; +import makeStreams from './makeStreams'; import { BeamcoderType } from './types/BeamcoderType'; import bindings from 'bindings'; diff --git a/ts/install_ffmpeg.ts b/ts/install_ffmpeg.ts index b35f528..3d3c65a 100644 --- a/ts/install_ffmpeg.ts +++ b/ts/install_ffmpeg.ts @@ -1,6 +1,7 @@ /* 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 diff --git a/ts/makeSources.ts b/ts/makeSources.ts new file mode 100644 index 0000000..f4781e5 --- /dev/null +++ b/ts/makeSources.ts @@ -0,0 +1,69 @@ +/* + 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 { BeamstreamChannel, BeamstreamParams, BeamstreamSource } from './types/Beamstreams'; +import beamcoder from './beamcoder' +import { readStream } from './readStream'; + +export default async function makeSources(params: BeamstreamParams): Promise { + if (!params.video) params.video = []; + if (!params.audio) params.audio = []; + params.video.forEach(p => p.sources.forEach((src: BeamstreamSource) => { + if (src.input_stream) { + const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 }); + src.input_stream.pipe(demuxerStream); + src.formatP = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); + } else + src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options }); + })); + params.audio.forEach(p => p.sources.forEach((src: BeamstreamSource) => { + if (src.input_stream) { + const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 }); + src.input_stream.pipe(demuxerStream); + src.formatP = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); + } else + src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options }); + })); + + for (const video of params.video) { + for (const src of video.sources) { + src.format = await src.formatP; + if (src.ms && !src.input_stream) + src.format.seek({ time: src.ms.start }); + await src.formatP; + } + } + + for (const audio of params.audio) { + for (const src of audio.sources) { + src.format = await src.formatP; + if (src.ms && !src.input_stream) + src.format.seek({ time: src.ms.start }); + await src.formatP; + } + } + + params.video.forEach((p: BeamstreamChannel) => p.sources.forEach(src => + src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex))); + params.audio.forEach((p: BeamstreamChannel) => p.sources.forEach(src => + src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex))); + } + \ No newline at end of file diff --git a/ts/makeStreams.ts b/ts/makeStreams.ts new file mode 100644 index 0000000..fee3c19 --- /dev/null +++ b/ts/makeStreams.ts @@ -0,0 +1,174 @@ +/* + 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 { serialBalancer } from './serialBalancer'; +import { Muxer } from './types/Muxer'; +import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, ReadableMuxerStream, WritableDemuxerStream } from './types/Beamstreams'; +import { Filterer, FilterLink } from './types/Filter'; +import beamcoder from './beamcoder' +import runStreams from './runStreams'; + +export default async function makeStreams(params: BeamstreamParams): Promise<{ run(): Promise }> { + 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[] = []; + + params.video.forEach((channel: BeamstreamChannel) => { + const inputParams = channel.sources.map((src, i) => { + 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 p = beamcoder.filterer({ + filterType: 'video', + inputParams, + outputParams: channel.streams.map((str: BeamstreamStream, i: number) => ({ name: `out${i}:v`, pixelFormat: str.codecpar.format })), + filterSpec: channel.filterSpec + }).then(filter => channel.filter = filter); + promises.push(p); + }); + + 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 p = beamcoder.filterer({ filterType: 'audio', inputParams, outputParams, filterSpec: channel.filterSpec }) + .then(filter => channel.filter = filter); + promises.push(p); + }); + await Promise.all(promises); + + // 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 = 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((channel: BeamstreamChannel) => { + channel.streams.forEach((str: BeamstreamStream, i: number) => { + const encParams = channel.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 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 ... + }); + }); + + params.audio.forEach((channel: BeamstreamChannel) => { + channel.streams.forEach((str: BeamstreamStream, i: number) => { + const encParams: FilterLink = channel.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((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); + }); + }); + + 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); + }); + }); + + 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: Promise[] = []; + 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(); + } + }; + } + \ No newline at end of file diff --git a/ts/muxerStream.ts b/ts/muxerStream.ts new file mode 100644 index 0000000..877e16d --- /dev/null +++ b/ts/muxerStream.ts @@ -0,0 +1,38 @@ +/* + 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 { ReadableMuxerStream } from './types/Beamstreams'; +import beamcoder from './beamcoder' +import createBeamReadableStream from './createBeamReadableStream'; + +export default function muxerStream(params: { highwaterMark: number }): ReadableMuxerStream { + const governor = new beamcoder.governor({ highWaterMark: 1 }); + const stream = createBeamReadableStream(params, governor) as ReadableMuxerStream; + stream.on('end', () => governor.finish()); + stream.on('error', console.error); + stream.muxer = (options) => { + options = options || {}; + options.governor = governor; + return beamcoder.muxer(options); + }; + return stream; + } + \ No newline at end of file diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts index 1aff531..e3ce3b9 100644 --- a/ts/parallelBalancer.ts +++ b/ts/parallelBalancer.ts @@ -1,10 +1,30 @@ +/* + 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 } from "./types/DecodedFrames"; import { Frame } from './types/Frame'; import { Stream } from "./types/Stream"; import { Timable, Timables } from "./types/Timable"; - export type localFrame = { pkt?: Frame, ts: number, streamIndex: number, final?: boolean, resolve?: () => void }; type localResult = { done: boolean, value?: { name: string, frames: Timables }[] & Timable }; diff --git a/ts/readStream.ts b/ts/readStream.ts new file mode 100644 index 0000000..486485f --- /dev/null +++ b/ts/readStream.ts @@ -0,0 +1,72 @@ +/* + 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, Readable, Transform } from 'stream'; +import type { ffStats } from './types'; +import { BalanceResult, teeBalancer } from './teeBalancer'; +import { localFrame, parallelBalancer } from './parallelBalancer'; +import { serialBalancer } from './serialBalancer'; +import { Demuxer } from './types/Demuxer'; +import { Muxer } from './types/Muxer'; +import { Stream } from './types/Stream'; +import { DecodedFrames } from './types/DecodedFrames'; +import type { Governor } from './types/Governor'; +import { Frame } from './types/Frame'; +import { Packet } from './types/Packet'; +import { Timing, TotalTimeed } from './types/Timing'; +import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, ReadableMuxerStream, WritableDemuxerStream } from './types/Beamstreams'; +import { Filterer, FiltererResult, FilterLink } from './types/Filter'; +import { Timable, Timables } from './types/Timable' +import { EncodedPackets } from './types/Encoder'; + +import beamcoder from './beamcoder' +import frameDicer from './frameDicer'; + + +export 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..9b44561 --- /dev/null +++ b/ts/runStreams.ts @@ -0,0 +1,115 @@ +/* + 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 { BalanceResult, teeBalancer } from './teeBalancer'; +import { localFrame, parallelBalancer } from './parallelBalancer'; +import { serialBalancer } from './serialBalancer'; +import { Stream } from './types/Stream'; +import { DecodedFrames } from './types/DecodedFrames'; +import { Frame } from './types/Frame'; +import { Packet } from './types/Packet'; +import { TotalTimeed } from './types/Timing'; +import { BeamstreamSource, BeamstreamStream } from './types/Beamstreams'; +import { Filterer, FiltererResult } from './types/Filter'; +import { Timable, Timables } from './types/Timable' +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 = 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 & TotalTimeed>>({ name: 'filter', highWaterMark: 1 }, (frms: Timables) => { + if (filterer.cb) filterer.cb(frms[0].frames[0].pts); + 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 index 1f88153..de31199 100644 --- a/ts/serialBalancer.ts +++ b/ts/serialBalancer.ts @@ -1,3 +1,24 @@ +/* + 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 } from "./types/Encoder"; import { Packet } from "./types/Packet"; @@ -40,7 +61,7 @@ export class serialBalancer { time_base: [number, number], index: number }, - writeFn: (r: void | Packet) => void, + writeFn: (r: Packet) => void, final = false ): Promise { if (packets && packets.packets.length) { @@ -49,7 +70,7 @@ export class serialBalancer { 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) - writeFn(packet); + 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 index a45bb1a..80249af 100644 --- a/ts/teeBalancer.ts +++ b/ts/teeBalancer.ts @@ -1,3 +1,24 @@ +/* + 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 } from "./types/Timable"; diff --git a/ts/transformStream.ts b/ts/transformStream.ts new file mode 100644 index 0000000..5566272 --- /dev/null +++ b/ts/transformStream.ts @@ -0,0 +1,55 @@ +/* + 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/Timable' + +// SRC = Frame | Frame[] | Packet +// DST = Promise, DecodedFrames +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/writeStream.ts b/ts/writeStream.ts new file mode 100644 index 0000000..0f1b07d --- /dev/null +++ b/ts/writeStream.ts @@ -0,0 +1,81 @@ +/* + 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 './types'; +import { Timing } from './types/Timing'; +import { Timable } from './types/Timable' + +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?: any) => 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; + 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?: any) => 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 index 30ec57d..631f515 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -98,5 +98,5 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "files": [ "ts/beamstreams.ts", "ts/index.ts", "ts/install_ffmpeg.ts" ] + "include": ["ts/*.ts"], } From d74db6c93d401075af60326990ce09a76b67bb60 Mon Sep 17 00:00:00 2001 From: urielch Date: Sun, 1 May 2022 22:31:07 +0300 Subject: [PATCH 45/68] refactor --- ts/parallelBalancer.ts | 2 +- ts/readStream.ts | 19 +------------------ ts/runStreams.ts | 4 ++-- ts/teeBalancer.ts | 3 +-- ts/transformStream.ts | 2 +- ts/types/DecodedFrames.d.ts | 3 +-- ts/types/Encoder.d.ts | 2 +- ts/types/Filter.d.ts | 3 +-- ts/types/Frame.d.ts | 2 +- ts/types/Packet.d.ts | 2 +- ts/types/Timable.d.ts | 7 ------- ts/types/{Timing.d.ts => time.d.ts} | 8 +++++++- ts/writeStream.ts | 3 +-- 13 files changed, 19 insertions(+), 41 deletions(-) delete mode 100644 ts/types/Timable.d.ts rename ts/types/{Timing.d.ts => time.d.ts} (62%) diff --git a/ts/parallelBalancer.ts b/ts/parallelBalancer.ts index e3ce3b9..b236150 100644 --- a/ts/parallelBalancer.ts +++ b/ts/parallelBalancer.ts @@ -23,7 +23,7 @@ import { Readable } from "stream"; import { DecodedFrames } from "./types/DecodedFrames"; import { Frame } from './types/Frame'; import { Stream } from "./types/Stream"; -import { Timable, Timables } from "./types/Timable"; +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 }; diff --git a/ts/readStream.ts b/ts/readStream.ts index 486485f..d006fd6 100644 --- a/ts/readStream.ts +++ b/ts/readStream.ts @@ -19,26 +19,9 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import { Writable, Readable, Transform } from 'stream'; -import type { ffStats } from './types'; -import { BalanceResult, teeBalancer } from './teeBalancer'; -import { localFrame, parallelBalancer } from './parallelBalancer'; -import { serialBalancer } from './serialBalancer'; +import { Readable } from 'stream'; import { Demuxer } from './types/Demuxer'; -import { Muxer } from './types/Muxer'; -import { Stream } from './types/Stream'; -import { DecodedFrames } from './types/DecodedFrames'; -import type { Governor } from './types/Governor'; -import { Frame } from './types/Frame'; import { Packet } from './types/Packet'; -import { Timing, TotalTimeed } from './types/Timing'; -import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, ReadableMuxerStream, WritableDemuxerStream } from './types/Beamstreams'; -import { Filterer, FiltererResult, FilterLink } from './types/Filter'; -import { Timable, Timables } from './types/Timable' -import { EncodedPackets } from './types/Encoder'; - -import beamcoder from './beamcoder' -import frameDicer from './frameDicer'; export function readStream(params: { highWaterMark?: number }, demuxer: Demuxer, ms: { end: number }, index: number): Readable { diff --git a/ts/runStreams.ts b/ts/runStreams.ts index 9b44561..0e80e05 100644 --- a/ts/runStreams.ts +++ b/ts/runStreams.ts @@ -26,10 +26,10 @@ import { Stream } from './types/Stream'; import { DecodedFrames } from './types/DecodedFrames'; import { Frame } from './types/Frame'; import { Packet } from './types/Packet'; -import { TotalTimeed } from './types/Timing'; +import { TotalTimeed } from './types/time'; import { BeamstreamSource, BeamstreamStream } from './types/Beamstreams'; import { Filterer, FiltererResult } from './types/Filter'; -import { Timable, Timables } from './types/Timable' +import { Timable, Timables } from './types/time' import { EncodedPackets } from './types/Encoder'; import frameDicer from './frameDicer'; diff --git a/ts/teeBalancer.ts b/ts/teeBalancer.ts index 80249af..7076f4b 100644 --- a/ts/teeBalancer.ts +++ b/ts/teeBalancer.ts @@ -21,8 +21,7 @@ */ import { Readable } from "stream"; import { Frame } from "./types/Frame"; -import { Timable, Timables } from "./types/Timable"; -import { Timing, TotalTimeed } from "./types/Timing"; +import { Timable, Timables, Timing, TotalTimeed } from "./types/time"; export type BalanceResult = { value: { timings: Timing }, done: boolean, final?: boolean }; diff --git a/ts/transformStream.ts b/ts/transformStream.ts index 5566272..444129d 100644 --- a/ts/transformStream.ts +++ b/ts/transformStream.ts @@ -20,7 +20,7 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ import { Transform } from 'stream'; -import { Timable } from './types/Timable' +import { Timable } from './types/time' // SRC = Frame | Frame[] | Packet // DST = Promise, DecodedFrames diff --git a/ts/types/DecodedFrames.d.ts b/ts/types/DecodedFrames.d.ts index 8029ca5..eda2d49 100644 --- a/ts/types/DecodedFrames.d.ts +++ b/ts/types/DecodedFrames.d.ts @@ -1,6 +1,5 @@ import { Frame } from "./Frame" -import { Timable } from "./Timable" -import { TotalTimeed } from "./Timing" +import { Timable, TotalTimeed } from "./time" /** The DecodedFrames object is returned as the result of a decode operation */ export interface DecodedFrames extends Timable, TotalTimeed { diff --git a/ts/types/Encoder.d.ts b/ts/types/Encoder.d.ts index 7eddf56..8948d8a 100644 --- a/ts/types/Encoder.d.ts +++ b/ts/types/Encoder.d.ts @@ -3,7 +3,7 @@ import { Packet } from "./Packet"; import { Frame } from "./Frame"; import { CodecContextBaseMin } from "./CodecContext" import { Timable } from "./Timable"; -import { TotalTimeed } from "./Timing"; +import { TotalTimeed } from "./time"; /** The EncodedPackets object is returned as the result of a encode operation */ export interface EncodedPackets extends Timable, TotalTimeed { diff --git a/ts/types/Filter.d.ts b/ts/types/Filter.d.ts index 20c054a..2e07268 100644 --- a/ts/types/Filter.d.ts +++ b/ts/types/Filter.d.ts @@ -1,7 +1,6 @@ import { Frame } from "./Frame" import { PrivClass } from "./PrivClass" -import { Timable } from "./Timable" -import { TotalTimeed } from "./Timing" +import { Timable, TotalTimeed } from "./time" export interface FilterFlags { /** diff --git a/ts/types/Frame.d.ts b/ts/types/Frame.d.ts index b3a229d..67fffd9 100644 --- a/ts/types/Frame.d.ts +++ b/ts/types/Frame.d.ts @@ -1,5 +1,5 @@ import { HWFramesContext } from "./HWContext"; -import { Timable } from "./Timable"; +import { Timable } from "./time"; /** * This object describes decoded (raw) audio or video data. diff --git a/ts/types/Packet.d.ts b/ts/types/Packet.d.ts index e174a3b..a1717c1 100644 --- a/ts/types/Packet.d.ts +++ b/ts/types/Packet.d.ts @@ -9,7 +9,7 @@ * (e.g. to update some stream parameters at the end of encoding). */ -import { Timable } from "./Timable" +import { Timable } from "./time" export interface PacketFlags { /** The packet contains a keyframe */ diff --git a/ts/types/Timable.d.ts b/ts/types/Timable.d.ts deleted file mode 100644 index 672ea4c..0000000 --- a/ts/types/Timable.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Timing } from './Timing' - -export interface Timable { - timings?: { [key: string]: Timing; }; -} - -export type Timables = Array & Timable; \ No newline at end of file diff --git a/ts/types/Timing.d.ts b/ts/types/time.d.ts similarity index 62% rename from ts/types/Timing.d.ts rename to ts/types/time.d.ts index cbe3a27..f8aa79d 100644 --- a/ts/types/Timing.d.ts +++ b/ts/types/time.d.ts @@ -6,4 +6,10 @@ export interface Timing { export interface TotalTimeed { /** Total time in microseconds that the decode operation took to complete */ total_time: number -} \ No newline at end of file +} + +export interface Timable { + timings?: { [key: string]: Timing; }; +} + +export type Timables = Array & Timable; \ No newline at end of file diff --git a/ts/writeStream.ts b/ts/writeStream.ts index 0f1b07d..631fe9e 100644 --- a/ts/writeStream.ts +++ b/ts/writeStream.ts @@ -21,8 +21,7 @@ */ import { Writable } from 'stream'; import type { ffStats } from './types'; -import { Timing } from './types/Timing'; -import { Timable } from './types/Timable' +import { Timing, Timable } from './types/time'; import calcStats from './calcStats'; From 72f0515f935b0a825049435436439fc9d0f71809 Mon Sep 17 00:00:00 2001 From: urielch Date: Sun, 1 May 2022 22:35:43 +0300 Subject: [PATCH 46/68] patch teeBalancer --- ts/runStreams.ts | 3 ++- ts/teeBalancer.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ts/runStreams.ts b/ts/runStreams.ts index 0e80e05..2710868 100644 --- a/ts/runStreams.ts +++ b/ts/runStreams.ts @@ -19,7 +19,8 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import { BalanceResult, teeBalancer } from './teeBalancer'; +import teeBalancer from './teeBalancer'; +import { BalanceResult } from './teeBalancer'; import { localFrame, parallelBalancer } from './parallelBalancer'; import { serialBalancer } from './serialBalancer'; import { Stream } from './types/Stream'; diff --git a/ts/teeBalancer.ts b/ts/teeBalancer.ts index 7076f4b..424eae9 100644 --- a/ts/teeBalancer.ts +++ b/ts/teeBalancer.ts @@ -27,7 +27,7 @@ export type BalanceResult = { value: { timings: Timing }, done: boolean, final?: type teeBalancerType = Readable[] & { pushFrames: (frames: Timables, unusedFlag?: boolean) => Promise }; -export function teeBalancer(params: { name: 'streamTee', highWaterMark?: number }, numStreams: number): teeBalancerType { +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) From 9e27c5d72c8c7425a762f0ec5ffe9279c30015c2 Mon Sep 17 00:00:00 2001 From: urielch Date: Sun, 1 May 2022 23:06:35 +0300 Subject: [PATCH 47/68] misc fiox --- ts/makeStreams.ts | 2 +- ts/runStreams.ts | 2 +- ts/serialBalancer.ts | 2 +- ts/types/CodecPar.d.ts | 5 +---- ts/types/Frame.d.ts | 6 ++---- ts/types/Packet.d.ts | 7 ++----- ts/types/Stream.d.ts | 6 ++---- ts/types/time.d.ts | 5 +++++ 8 files changed, 15 insertions(+), 20 deletions(-) diff --git a/ts/makeStreams.ts b/ts/makeStreams.ts index fee3c19..9131be8 100644 --- a/ts/makeStreams.ts +++ b/ts/makeStreams.ts @@ -19,7 +19,7 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import { serialBalancer } from './serialBalancer'; +import serialBalancer from './serialBalancer'; import { Muxer } from './types/Muxer'; import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, ReadableMuxerStream, WritableDemuxerStream } from './types/Beamstreams'; import { Filterer, FilterLink } from './types/Filter'; diff --git a/ts/runStreams.ts b/ts/runStreams.ts index 2710868..c7ed132 100644 --- a/ts/runStreams.ts +++ b/ts/runStreams.ts @@ -22,7 +22,7 @@ import teeBalancer from './teeBalancer'; import { BalanceResult } from './teeBalancer'; import { localFrame, parallelBalancer } from './parallelBalancer'; -import { serialBalancer } from './serialBalancer'; +import serialBalancer from './serialBalancer'; import { Stream } from './types/Stream'; import { DecodedFrames } from './types/DecodedFrames'; import { Frame } from './types/Frame'; diff --git a/ts/serialBalancer.ts b/ts/serialBalancer.ts index de31199..1a4e4b3 100644 --- a/ts/serialBalancer.ts +++ b/ts/serialBalancer.ts @@ -22,7 +22,7 @@ import { EncodedPackets } from "./types/Encoder"; import { Packet } from "./types/Packet"; -export class serialBalancer { +export default class serialBalancer { pending = [] as { ts: number, streamIndex: number, resolve?: (result: any) => void, pkt: Packet | null }[]; constructor(numStreams: number) { diff --git a/ts/types/CodecPar.d.ts b/ts/types/CodecPar.d.ts index 7250556..947df1c 100644 --- a/ts/types/CodecPar.d.ts +++ b/ts/types/CodecPar.d.ts @@ -1,7 +1,7 @@ /** * CodecPar describes the properties of an encoded stream. */ -export interface CodecPar { +export interface CodecPar extends toJSONAble { /** Object name. */ readonly type: 'CodecParameters' @@ -135,8 +135,5 @@ export interface CodecPar { // native code; readonly _codecPar: {}; - - /** Retun a JSON string containing the object properties. */ - toJSON(): string } diff --git a/ts/types/Frame.d.ts b/ts/types/Frame.d.ts index 67fffd9..92ddeec 100644 --- a/ts/types/Frame.d.ts +++ b/ts/types/Frame.d.ts @@ -1,10 +1,10 @@ import { HWFramesContext } from "./HWContext"; -import { Timable } from "./time"; +import { Timable, toJSONAble } from "./time"; /** * This object describes decoded (raw) audio or video data. */ -export interface Frame extends Timable { +export interface Frame extends Timable, toJSONAble { /** Object name. */ readonly type: 'Frame' /** @@ -179,8 +179,6 @@ export interface Frame extends Timable { // internal readonly _frame: {}; - /** Retun a JSON string containing the object properties. */ - toJSON(): string } /** Pixel format description */ diff --git a/ts/types/Packet.d.ts b/ts/types/Packet.d.ts index a1717c1..a8732cf 100644 --- a/ts/types/Packet.d.ts +++ b/ts/types/Packet.d.ts @@ -9,7 +9,7 @@ * (e.g. to update some stream parameters at the end of encoding). */ -import { Timable } from "./time" +import { Timable, toJSONAble } from "./time" export interface PacketFlags { /** The packet contains a keyframe */ @@ -36,13 +36,10 @@ export interface PacketFlags { DISPOSABLE: boolean // Frames that can be discarded by the decoder } -export interface Packet extends Timable { +export interface Packet extends Timable, toJSONAble { /** Object name. */ readonly type: 'Packet' - /** Retun a JSON string containing the object properties. */ - toJSON(): string - // internal data readonly _packet: {}; /** diff --git a/ts/types/Stream.d.ts b/ts/types/Stream.d.ts index 343d2a6..b5a77ad 100644 --- a/ts/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,7 @@ export interface EventFlags { /** * Stream describes the properties of a stream. */ -export interface Stream { +export interface Stream extends toJSONAble { // native code; readonly _stream: {}; /** Object name. */ @@ -161,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/ts/types/time.d.ts b/ts/types/time.d.ts index f8aa79d..0ba3811 100644 --- a/ts/types/time.d.ts +++ b/ts/types/time.d.ts @@ -12,4 +12,9 @@ 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 From 1e90a60f8f6e7abe0923acf36bcc0c3b192f0c85 Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 2 May 2022 09:46:29 +0300 Subject: [PATCH 48/68] merge v2 --- ts/runStreams.ts | 4 ++-- ts/serialBalancer.ts | 14 ++++++-------- ts/teeBalancer.ts | 4 ++-- ts/types/DecodedFrames.d.ts | 4 ++-- ts/types/Encoder.d.ts | 5 ++--- ts/types/Filter.d.ts | 6 +++--- ts/types/time.d.ts | 2 +- 7 files changed, 18 insertions(+), 21 deletions(-) diff --git a/ts/runStreams.ts b/ts/runStreams.ts index c7ed132..ed49e5b 100644 --- a/ts/runStreams.ts +++ b/ts/runStreams.ts @@ -27,7 +27,7 @@ import { Stream } from './types/Stream'; import { DecodedFrames } from './types/DecodedFrames'; import { Frame } from './types/Frame'; import { Packet } from './types/Packet'; -import { TotalTimeed } from './types/time'; +import { TotalTimed } from './types/time'; import { BeamstreamSource, BeamstreamStream } from './types/Beamstreams'; import { Filterer, FiltererResult } from './types/Filter'; import { Timable, Timables } from './types/time' @@ -70,7 +70,7 @@ export default function runStreams( const streamTee = teeBalancer({ name: 'streamTee', highWaterMark: 1 }, streams.length); - const filtStream = transformStream, Timable & Promise & TotalTimeed>>({ name: 'filter', highWaterMark: 1 }, (frms: Timables) => { + const filtStream = transformStream, Timable & Promise & TotalTimed>>({ name: 'filter', highWaterMark: 1 }, (frms: Timables) => { if (filterer.cb) filterer.cb(frms[0].frames[0].pts); return filterer.filter(frms); }, () => { }, reject); diff --git a/ts/serialBalancer.ts b/ts/serialBalancer.ts index 1a4e4b3..41e0ac6 100644 --- a/ts/serialBalancer.ts +++ b/ts/serialBalancer.ts @@ -21,6 +21,7 @@ */ import { EncodedPackets } from "./types/Encoder"; import { Packet } from "./types/Packet"; +import { Stream } from "./types/Stream"; export default class serialBalancer { pending = [] as { ts: number, streamIndex: number, resolve?: (result: any) => void, pkt: Packet | null }[]; @@ -54,14 +55,11 @@ export default class serialBalancer { }); }; - async writePkts( + public async writePkts( packets: EncodedPackets | null, - srcStream: { time_base: [number, number] }, - dstStream: { - time_base: [number, number], - index: number - }, - writeFn: (r: Packet) => void, + srcStream: Stream, + dstStream: Stream, + writeFn: (r: Packet) => Promise, final = false ): Promise { if (packets && packets.packets.length) { @@ -70,7 +68,7 @@ export default class serialBalancer { 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) - writeFn(packet as Packet); + 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 index 424eae9..ec7027a 100644 --- a/ts/teeBalancer.ts +++ b/ts/teeBalancer.ts @@ -21,7 +21,7 @@ */ import { Readable } from "stream"; import { Frame } from "./types/Frame"; -import { Timable, Timables, Timing, TotalTimeed } from "./types/time"; +import { Timable, Timables, Timing, TotalTimed } from "./types/time"; export type BalanceResult = { value: { timings: Timing }, done: boolean, final?: boolean }; @@ -72,7 +72,7 @@ export default function teeBalancer(params: { name: 'streamTee', highWaterMark?: })); // {frames: Frame[], name: string} - readStreams.pushFrames = (frames: Array & TotalTimeed & Timable): Promise => { + readStreams.pushFrames = (frames: Array & TotalTimed & Timable): Promise => { return new Promise(resolve => { pending.forEach((p, index) => { if (frames.length) diff --git a/ts/types/DecodedFrames.d.ts b/ts/types/DecodedFrames.d.ts index eda2d49..0d15c3b 100644 --- a/ts/types/DecodedFrames.d.ts +++ b/ts/types/DecodedFrames.d.ts @@ -1,8 +1,8 @@ import { Frame } from "./Frame" -import { Timable, TotalTimeed } from "./time" +import { Timable, TotalTimed } from "./time" /** The DecodedFrames object is returned as the result of a decode operation */ -export interface DecodedFrames extends Timable, TotalTimeed { +export interface DecodedFrames extends Timable, TotalTimed { /** Object name. */ readonly type: 'frames' /** diff --git a/ts/types/Encoder.d.ts b/ts/types/Encoder.d.ts index 8948d8a..ad94f36 100644 --- a/ts/types/Encoder.d.ts +++ b/ts/types/Encoder.d.ts @@ -2,11 +2,10 @@ import { CodecPar } from "./CodecPar" import { Packet } from "./Packet"; import { Frame } from "./Frame"; import { CodecContextBaseMin } from "./CodecContext" -import { Timable } from "./Timable"; -import { TotalTimeed } from "./time"; +import { TotalTimed, Timable } from "./time"; /** The EncodedPackets object is returned as the result of a encode operation */ -export interface EncodedPackets extends Timable, TotalTimeed { +export interface EncodedPackets extends Timable, TotalTimed { /** Object name. */ readonly type: 'packets' /** diff --git a/ts/types/Filter.d.ts b/ts/types/Filter.d.ts index 2e07268..6f7403e 100644 --- a/ts/types/Filter.d.ts +++ b/ts/types/Filter.d.ts @@ -1,6 +1,6 @@ import { Frame } from "./Frame" import { PrivClass } from "./PrivClass" -import { Timable, TotalTimeed } from "./time" +import { Timable, TotalTimed } from "./time" export interface FilterFlags { /** @@ -225,7 +225,7 @@ export interface Filterer extends Timable { * @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 & TotalTimeed> + 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 @@ -234,7 +234,7 @@ export interface Filterer extends Timable { * @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 }>): Timable & Promise & TotalTimeed> + filter(framesArr: Array<{ name?: string, frames: Array }>): Timable & Promise & TotalTimed> // may add a callback cb?: (pts: number | null) => void; diff --git a/ts/types/time.d.ts b/ts/types/time.d.ts index 0ba3811..6cea04a 100644 --- a/ts/types/time.d.ts +++ b/ts/types/time.d.ts @@ -3,7 +3,7 @@ export interface Timing { elapsed: number; } -export interface TotalTimeed { +export interface TotalTimed { /** Total time in microseconds that the decode operation took to complete */ total_time: number } From f99336d7a2f235b52955be9f3c62171f6f7f8398 Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 2 May 2022 19:22:41 +0300 Subject: [PATCH 49/68] sync v2 --- .eslintrc.js | 3 ++- package.json | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) 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/package.json b/package.json index 84f03f4..3dd0dfd 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,9 @@ "scripts": { "postinstall": "tsc -p . && node ts/install_ffmpeg.js && node-gyp rebuild", "build": "tsc -p .", - "clean": "rimraf ts/*.js", + "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" @@ -40,13 +41,14 @@ }, "devDependencies": { "@types/bindings": "^1.5.1", - "@types/node": "^17.0.24", + "@types/node": "^17.0.30", "@types/tape": "^4.13.2", "eslint": "^8.9.0", + "md5-file": "^5.0.0", "rimraf": "^3.0.2", "tape": "^5.5.3", "ts-node": "^10.7.0", - "typescript": "^4.6.3" + "typescript": "^4.6.4" }, "gypfile": true } From eaaa1180d4e030f69f3c98e607bae0a8636c545e Mon Sep 17 00:00:00 2001 From: urielCh Date: Thu, 5 May 2022 09:20:24 +0300 Subject: [PATCH 50/68] merge v2 to v1 --- ts/beamcoder.ts | 5 +- ts/calcStats.ts | 29 ++-- ts/createBeamReadableStream.ts | 40 ----- ts/createBeamWritableStream.ts | 37 ----- ts/demuxerStream.ts | 52 ++++-- ts/frameDicer.ts | 175 ++++++++++---------- ts/index.ts | 62 +++---- ts/install_ffmpeg.ts | 55 +----- ts/makeSources.ts | 80 ++++----- ts/makeStreams.ts | 294 +++++++++++++++++---------------- ts/muxerStream.ts | 57 +++++-- ts/parallelBalancer.ts | 154 ++++++++--------- ts/readStream.ts | 3 +- ts/runStreams.ts | 7 +- ts/serialBalancer.ts | 101 ++++++----- ts/transformStream.ts | 2 - ts/utils.ts | 60 +++++++ ts/writeStream.ts | 14 +- 18 files changed, 597 insertions(+), 630 deletions(-) delete mode 100644 ts/createBeamReadableStream.ts delete mode 100644 ts/createBeamWritableStream.ts create mode 100644 ts/utils.ts diff --git a/ts/beamcoder.ts b/ts/beamcoder.ts index ae5a978..7ce2834 100644 --- a/ts/beamcoder.ts +++ b/ts/beamcoder.ts @@ -19,8 +19,9 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import bindings from 'bindings'; -import { BeamcoderType } from './types/BeamcoderType'; +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 index 95578e0..31a8296 100644 --- a/ts/calcStats.ts +++ b/ts/calcStats.ts @@ -19,17 +19,20 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import type { ffStats } from './types'; - +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 + 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 }; +}; + diff --git a/ts/createBeamReadableStream.ts b/ts/createBeamReadableStream.ts deleted file mode 100644 index ce36912..0000000 --- a/ts/createBeamReadableStream.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - 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 type { Governor } from './types/Governor'; - -export default function createBeamReadableStream(params: { highwaterMark?: number }, governor: Governor): Readable { - 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; - } - \ No newline at end of file diff --git a/ts/createBeamWritableStream.ts b/ts/createBeamWritableStream.ts deleted file mode 100644 index daf393a..0000000 --- a/ts/createBeamWritableStream.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - 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 { Governor } from './types/Governor'; - -export default function createBeamWritableStream(params: { highwaterMark?: number }, governor: Governor): Writable { - const beamStream = new Writable({ - highWaterMark: params.highwaterMark || 16384, - write: (chunk, encoding, cb) => { - (async () => { - await governor.write(chunk); - cb(); - })(); - } - }); - return beamStream; - } - \ No newline at end of file diff --git a/ts/demuxerStream.ts b/ts/demuxerStream.ts index 55b27f1..f46c70e 100644 --- a/ts/demuxerStream.ts +++ b/ts/demuxerStream.ts @@ -19,22 +19,44 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import type { Governor } from './types/Governor'; -import { WritableDemuxerStream } from './types/Beamstreams'; - +import { governor as Governor } from './types/governor'; import beamcoder from './beamcoder' -import createBeamWritableStream from './createBeamWritableStream'; +import { Writable } from 'stream'; +import { Demuxer, InputFormat } from './types'; -export default function demuxerStream(params: { highwaterMark?: number }): WritableDemuxerStream { - const governor = new beamcoder.governor({}); - const stream = createBeamWritableStream(params, governor) as WritableDemuxerStream; - stream.on('finish', () => governor.finish()); - stream.on('error', console.error); - stream.demuxer = (options: { governor?: Governor }) => { - 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; +/** + * 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 index 8543724..acfa5d3 100644 --- a/ts/frameDicer.ts +++ b/ts/frameDicer.ts @@ -19,96 +19,95 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import { CodecContextBaseMin } from "./types/CodecContext"; +import { CodecContext } from "./types/CodecContext"; import { Frame } from "./types/Frame"; import beamcoder from './beamcoder' export default class frameDicer { - private addFrame: (srcFrm: Frame) => Frame[]; - private getLast: () => Frame[]; - private doDice: boolean; - // CodecPar - constructor(encoder: CodecContextBaseMin, private isAudio: boolean) { - let sampleBytes = 4; // Assume floating point 4 byte samples for now... - const numChannels = encoder.channels; - const dstNumSamples = encoder.frame_size; - let dstFrmBytes = dstNumSamples * sampleBytes; - this.doDice = false === beamcoder.encoders()[encoder.name].capabilities.VARIABLE_FRAME_SIZE; - - let lastFrm: Frame = null as Frame; - let lastBuf: Buffer[] = []; - const nullBuf: Buffer[] = []; - for (let b = 0; b < numChannels; ++b) - nullBuf.push(Buffer.alloc(0)); - - this.addFrame = (srcFrm: Frame): Frame[] => { - let result: Frame[] = []; - let dstFrm: Frame; - let curStart = 0; - if (!lastFrm) { - lastFrm = beamcoder.frame(srcFrm.toJSON()) as Frame; - 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; - }; - - this.getLast = (): Frame[] => { - let result: Frame[] = []; - 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; - }; - } - 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; - }; + 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)); } - - - - \ No newline at end of file + + 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 index 18c8295..2f3a2a4 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -1,7 +1,6 @@ /* 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 @@ -20,46 +19,37 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import demuxerStream from './demuxerStream'; -import muxerStream from './muxerStream'; -import makeSources from './makeSources'; -import makeStreams from './makeStreams'; -import { BeamcoderType } from './types/BeamcoderType'; -import bindings from 'bindings'; +import beamcoder from './beamcoder'; -const beamcoder = bindings('beamcoder') as BeamcoderType; -// import * as beamstreams from './beamstreams.js'; +export { default as demuxerStream } from './DemuxerStream'; +export { default as muxerStream } from './MuxerStream'; -// Provide useful debug on segfault-related crash -import SegfaultHandler from '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`; +export { default as DemuxerStream } from './DemuxerStream'; +export { default as MuxerStream } from './MuxerStream'; -console.log(splash); -console.log('Using FFmpeg version', beamcoder.avVersionInfo()); +export { default as makeSources } from './makeSources'; +export { default as makeStreams } from './makeStreams'; -// export {demuxerStream, muxerStream, makeSources, makeStreams} from './beamstreams.js'; +export { getRaw, getHTML } from './utils'; -beamcoder.demuxerStream = demuxerStream; -beamcoder.muxerStream = muxerStream; +// Provide useful debug on segfault-related crash +import SegfaultHandler from 'segfault-handler'; +SegfaultHandler.registerHandler('crash.log'); -beamcoder.makeSources = makeSources; -beamcoder.makeStreams = makeStreams; +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; - -export { CodecPar } from './types/CodecPar'; -export { Decoder } from './types/Decoder'; -export { Demuxer } from './types/Demuxer'; -export { Muxer } from './types/Muxer'; -export { Stream } from './types/Stream'; -export { Encoder } from './types/Encoder'; -export { Filterer, Filter } from './types/Filter'; -export { FormatContext } from './types/FormatContext'; -export { Frame } from './types/Frame'; -export { DecodedFrames } from './types/DecodedFrames'; diff --git a/ts/install_ffmpeg.ts b/ts/install_ffmpeg.ts index 3d3c65a..262161b 100644 --- a/ts/install_ffmpeg.ts +++ b/ts/install_ffmpeg.ts @@ -23,62 +23,13 @@ import os from 'os'; import fs from 'fs'; import util from 'util'; -import https from 'https'; 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); -async function get(ws: NodeJS.WritableStream, url: string, name: string): Promise { - 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'] as string); - } - 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: string, name: string): Promise { - let received = 0; - let totalLength = 0; - return new Promise((resolve, reject) => { - https.get(url, res => { - const chunks: Array = []; - if (totalLength == 0) { - totalLength = +(res.headers['content-length'] as string); - } - 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: NodeJS.ReadableStream, folder: string, name: string): Promise { const unzip = require('unzipper'); const directory = await unzip.Open.file(`${folder}/${name}.zip`); @@ -117,11 +68,11 @@ async function win32(): Promise { const downloadSource = autoStr.substring(sharedStartPos, sharedEndPos); let ws_shared = fs.createWriteStream(`ffmpeg/${ffmpegFilename}.zip`); - await get(ws_shared, downloadSource, `${ffmpegFilename}.zip`) + await getRaw(ws_shared, downloadSource) .catch(async (err) => { if (err.name === 'RedirectError') { const redirectURL = err.message; - await get(ws_shared, redirectURL, `${ffmpegFilename}.zip`); + await getRaw(ws_shared, redirectURL, `${ffmpegFilename}.zip`); } else console.error(err); }); diff --git a/ts/makeSources.ts b/ts/makeSources.ts index f4781e5..e0a194f 100644 --- a/ts/makeSources.ts +++ b/ts/makeSources.ts @@ -1,3 +1,4 @@ + /* Aerostat Beam Coder - Node.js native bindings to FFmpeg Copyright (C) 2019 Streampunk Media Ltd. @@ -19,51 +20,42 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import { BeamstreamChannel, BeamstreamParams, BeamstreamSource } from './types/Beamstreams'; + import beamcoder from './beamcoder' -import { readStream } from './readStream'; +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 = []; - params.video.forEach(p => p.sources.forEach((src: BeamstreamSource) => { - if (src.input_stream) { - const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 }); - src.input_stream.pipe(demuxerStream); - src.formatP = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); - } else - src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options }); - })); - params.audio.forEach(p => p.sources.forEach((src: BeamstreamSource) => { - if (src.input_stream) { - const demuxerStream = beamcoder.demuxerStream({ highwaterMark: 1024 }); - src.input_stream.pipe(demuxerStream); - src.formatP = demuxerStream.demuxer({ iformat: src.iformat, options: src.options }); - } else - src.formatP = beamcoder.demuxer({ url: src.url, iformat: src.iformat, options: src.options }); - })); - - for (const video of params.video) { - for (const src of video.sources) { - src.format = await src.formatP; - if (src.ms && !src.input_stream) - src.format.seek({ time: src.ms.start }); - await src.formatP; - } - } - - for (const audio of params.audio) { - for (const src of audio.sources) { - src.format = await src.formatP; - if (src.ms && !src.input_stream) - src.format.seek({ time: src.ms.start }); - await src.formatP; - } - } - - params.video.forEach((p: BeamstreamChannel) => p.sources.forEach(src => - src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex))); - params.audio.forEach((p: BeamstreamChannel) => p.sources.forEach(src => - src.stream = readStream({ highWaterMark: 1 }, src.format, src.ms, src.streamIndex))); + 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)); } - \ No newline at end of file + // 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 index 9131be8..8dc2c34 100644 --- a/ts/makeStreams.ts +++ b/ts/makeStreams.ts @@ -2,7 +2,7 @@ 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 @@ -19,156 +19,170 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import serialBalancer from './serialBalancer'; -import { Muxer } from './types/Muxer'; -import { BeamstreamChannel, BeamstreamParams, BeamstreamSource, BeamstreamStream, ReadableMuxerStream, WritableDemuxerStream } from './types/Beamstreams'; -import { Filterer, FilterLink } from './types/Filter'; + 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 }> { - params.video.forEach((channel: BeamstreamChannel) => { - channel.sources.forEach((src: BeamstreamSource) => - src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); + 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 + }; }); - params.audio.forEach((channel: BeamstreamChannel) => { - channel.sources.forEach((src: BeamstreamSource) => - src.decoder = beamcoder.decoder({ demuxer: src.format, stream_index: src.streamIndex })); + 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 promises: Promise[] = []; - - params.video.forEach((channel: BeamstreamChannel) => { - const inputParams = channel.sources.map((src, i) => { - 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 p = beamcoder.filterer({ - filterType: 'video', - inputParams, - outputParams: channel.streams.map((str: BeamstreamStream, i: number) => ({ name: `out${i}:v`, pixelFormat: str.codecpar.format })), - filterSpec: channel.filterSpec - }).then(filter => channel.filter = filter); - promises.push(p); + 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 + }; }); - - 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 p = beamcoder.filterer({ filterType: 'audio', inputParams, outputParams, filterSpec: channel.filterSpec }) - .then(filter => channel.filter = filter); - promises.push(p); + 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 ... }); - await Promise.all(promises); - - // 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 = 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((channel: BeamstreamChannel) => { - channel.streams.forEach((str: BeamstreamStream, i: number) => { - const encParams = channel.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 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); }); - - params.audio.forEach((channel: BeamstreamChannel) => { - channel.streams.forEach((str: BeamstreamStream, i: number) => { - const encParams: FilterLink = channel.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; + }); + + // 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.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); - }); + }); + + 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); }); - - 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 : {} }); - - 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: Promise[] = []; - 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(); - } - }; + 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(); } - \ No newline at end of file + return { run }; +} diff --git a/ts/muxerStream.ts b/ts/muxerStream.ts index 877e16d..0f8089f 100644 --- a/ts/muxerStream.ts +++ b/ts/muxerStream.ts @@ -19,20 +19,49 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import { ReadableMuxerStream } from './types/Beamstreams'; import beamcoder from './beamcoder' -import createBeamReadableStream from './createBeamReadableStream'; +import { Readable } from 'stream'; +import { governor, Muxer, MuxerCreateOptions } from './types'; -export default function muxerStream(params: { highwaterMark: number }): ReadableMuxerStream { - const governor = new beamcoder.governor({ highWaterMark: 1 }); - const stream = createBeamReadableStream(params, governor) as ReadableMuxerStream; - stream.on('end', () => governor.finish()); - stream.on('error', console.error); - stream.muxer = (options) => { - options = options || {}; - options.governor = governor; - return beamcoder.muxer(options); - }; - return stream; +/** + * 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); } - \ No newline at end of file + + + /** + * 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 index b236150..281a9f0 100644 --- a/ts/parallelBalancer.ts +++ b/ts/parallelBalancer.ts @@ -19,98 +19,88 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import { Readable } from "stream"; -import { DecodedFrames } from "./types/DecodedFrames"; -import { Frame } from './types/Frame'; -import { Stream } from "./types/Stream"; -import { Timable, Timables } from "./types/time"; + +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; -type parallelBalancerType = Readable & { - pushPkts: (packets: DecodedFrames, stream: Stream, streamIndex: number, final?: boolean) => Promise -}; + get tag(): 'a' | 'v' { + return 'video' === this.streamType ? 'v' : 'a'; + } -export function parallelBalancer(params: { name: string, highWaterMark: number }, streamType: 'video' | 'audio', numStreams: number): parallelBalancerType { - let resolveGet: null | ((result: { value?: { name: string, frames: Frame[] }[] & Timable, done: boolean }) => void) = null; - const tag = 'video' === streamType ? 'v' : 'a'; - const pending: Array = []; + 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) - pending.push({ ts: -Number.MAX_VALUE, streamIndex: s }); - - // const makeSet = (resolve: (result: {value?: { name: string, frames: any[] }, done: boolean}) => void) => { - const makeSet = (resolve: (result: localResult) => void) => { - 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; - } - } - }; + this.pending.push({ ts: -Number.MAX_VALUE, streamIndex: s }); + } - const pushPkt = async (pkt: Frame, streamIndex: number, ts: number): Promise => - new Promise(resolve => { - Object.assign(pending[streamIndex], { pkt, ts, final: pkt ? false : true, resolve }); - makeSet(resolveGet); - }); + 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; + } + }; - const pullSet = async () => new Promise(resolve => makeSet(resolve)); - - const readStream: parallelBalancerType = 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 { - const value = result.value; - value.timings = value[0].frames[0].timings; - value.timings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; - this.push(result.value); - } - })(); - }, - }) as parallelBalancerType; - - readStream.pushPkts = async (packets: DecodedFrames, stream: Stream, streamIndex: number, final = false): Promise => { - if (packets && packets.frames.length) { - let lst: localFrame = {} as localFrame; - for (const pkt of packets.frames) { - const ts = pkt.pts * stream.time_base[0] / stream.time_base[1]; - pkt.timings = packets.timings; - lst = await pushPkt(pkt, streamIndex, ts); - } - return lst; - } else if (final) { - return pushPkt(null, streamIndex, Number.MAX_VALUE); - } - }; + 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); + }) + } - return readStream; + 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 index d006fd6..66e401a 100644 --- a/ts/readStream.ts +++ b/ts/readStream.ts @@ -23,8 +23,7 @@ import { Readable } from 'stream'; import { Demuxer } from './types/Demuxer'; import { Packet } from './types/Packet'; - -export function readStream(params: { highWaterMark?: number }, demuxer: Demuxer, ms: { end: number }, index: number): Readable { +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 { diff --git a/ts/runStreams.ts b/ts/runStreams.ts index ed49e5b..deaa8c5 100644 --- a/ts/runStreams.ts +++ b/ts/runStreams.ts @@ -21,10 +21,10 @@ */ import teeBalancer from './teeBalancer'; import { BalanceResult } from './teeBalancer'; -import { localFrame, parallelBalancer } from './parallelBalancer'; +import { default as parallelBalancer, localFrame } from './parallelBalancer'; import serialBalancer from './serialBalancer'; import { Stream } from './types/Stream'; -import { DecodedFrames } from './types/DecodedFrames'; +import { DecodedFrames } from './types/Decoder'; import { Frame } from './types/Frame'; import { Packet } from './types/Packet'; import { TotalTimed } from './types/time'; @@ -50,7 +50,7 @@ export default function runStreams( return resolve(); const timeBaseStream: Stream = sources[0].format.streams[sources[0].streamIndex]; - const filterBalancer = parallelBalancer({ name: 'filterBalance', highWaterMark: 1 }, streamType, sources.length); + const filterBalancer = new parallelBalancer({ name: 'filterBalance', highWaterMark: 1 }, streamType, sources.length); sources.forEach((src: BeamstreamSource, srcIndex: number) => { const decStream = transformStream>( @@ -72,6 +72,7 @@ export default function runStreams( 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); diff --git a/ts/serialBalancer.ts b/ts/serialBalancer.ts index 41e0ac6..5d1d91a 100644 --- a/ts/serialBalancer.ts +++ b/ts/serialBalancer.ts @@ -19,60 +19,53 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import { EncodedPackets } from "./types/Encoder"; -import { Packet } from "./types/Packet"; -import { Stream } from "./types/Stream"; + +import { EncodedPackets, Packet, Stream } from "./types"; export default class serialBalancer { - pending = [] as { 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 streamIndex = 0; streamIndex < numStreams; ++streamIndex) - this.pending.push({ ts: -Number.MAX_VALUE, streamIndex, pkt: null }); - } - - adjustTS(pkt: { pts: number, dts: number, duration: number }, srcTB: [number, number], dstTB: [number, number]): void { - 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); - }; - - pullPkts(pkt: null | Packet, streamIndex: number, ts: number): Promise { - return new Promise(resolve => { - const pending = this.pending[streamIndex]; - pending.pkt = pkt; - pending.ts = ts; - pending.resolve = resolve; - // TODO loop only once - 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(); - }); - }; - - 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); - }; + 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 }); } - - \ No newline at end of file + + 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/transformStream.ts b/ts/transformStream.ts index 444129d..f3957b6 100644 --- a/ts/transformStream.ts +++ b/ts/transformStream.ts @@ -22,8 +22,6 @@ import { Transform } from 'stream'; import { Timable } from './types/time' -// SRC = Frame | Frame[] | Packet -// DST = Promise, DecodedFrames export default function transformStream( params: { name: 'encode' | 'dice' | 'decode' | 'filter', highWaterMark: number }, processFn: (val: SRC) => DST, diff --git a/ts/utils.ts b/ts/utils.ts new file mode 100644 index 0000000..e59574e --- /dev/null +++ b/ts/utils.ts @@ -0,0 +1,60 @@ +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) => { + 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 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); + }); + } + +export async function getHTML(url: string, name: string): Promise { + let received = 0; + let totalLength = 0; + return new Promise((resolve, reject) => { + https.get(url, res => { + const chunks: Array = []; + if (totalLength == 0) { + totalLength = +(res.headers['content-length'] as string); + } + 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); + }); + } + + \ No newline at end of file diff --git a/ts/writeStream.ts b/ts/writeStream.ts index 631fe9e..65cf9ce 100644 --- a/ts/writeStream.ts +++ b/ts/writeStream.ts @@ -20,7 +20,7 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ import { Writable } from 'stream'; -import type { ffStats } from './types'; +import type { ffStats } from './calcStats'; import { Timing, Timable } from './types/time'; import calcStats from './calcStats'; @@ -32,21 +32,23 @@ export default function writeStream(params: { name: string return new Writable({ objectMode: true, highWaterMark: params.highWaterMark ? params.highWaterMark || 4 : 4, - write(val: T, encoding: BufferEncoding, cb: (error?: Error | null, result?: any) => void) { + 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; - pktTimings[params.name] = { reqTime: reqTime, elapsed: process.hrtime(start)[1] / 1000000 }; - if (doTimings) - timings.push(pktTimings); + 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?: any) => void) { + final(cb: (error?: Error | null, result?: R | null) => void) { (async () => { const result = finalFn ? await finalFn() : null; if (doTimings && ('mux' === params.name)) { From 6524a2a854c73da9cf24833d344a1406a0daa04c Mon Sep 17 00:00:00 2001 From: urielch Date: Thu, 5 May 2022 16:25:05 +0300 Subject: [PATCH 51/68] merge example from v2 --- examples/encode_h264.ts | 4 +- examples/jpeg_app.ts | 47 +++++----- examples/jpeg_filter_app.ts | 168 +++++++++++++++++------------------- examples/make_mp4.ts | 14 +-- examples/streamTest.ts | 86 +++++++++++++----- 5 files changed, 179 insertions(+), 140 deletions(-) diff --git a/examples/encode_h264.ts b/examples/encode_h264.ts index 6209c6f..dce52f3 100644 --- a/examples/encode_h264.ts +++ b/examples/encode_h264.ts @@ -43,8 +43,8 @@ async function run() { 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.ts b/examples/jpeg_app.ts index 295a611..84dcbca 100644 --- a/examples/jpeg_app.ts +++ b/examples/jpeg_app.ts @@ -26,10 +26,8 @@ import beamcoder from '..'; // Use require('beamcoder') externally import Koa from 'koa'; // Add koa to package.json dependencies -import fs from 'fs'; // Add koa to package.json dependencies - -// const beamcoder = require('../index.js'); // Use require('beamcoder') externally -// const Koa = require('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 // @@ -40,6 +38,7 @@ app.use(async (ctx) => { // Assume HTTP GET with path // ctx.type = 'text/html'; ctx.body = ` currenty available files:
    ${list.map(f => `
  • ${f}
  • `).join(' ')}
+ Add files in : ${path.resolve('.')} for more tests `; return; } @@ -55,25 +54,26 @@ app.use(async (ctx) => { // Assume HTTP GET with path // } try { - 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 + 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; @@ -82,3 +82,4 @@ app.use(async (ctx) => { // Assume HTTP GET with path // }); app.listen(3000); // Start the server on port 3000 +console.log('listening port 3000'); diff --git a/examples/jpeg_filter_app.ts b/examples/jpeg_filter_app.ts index e54ae49..bb9686d 100644 --- a/examples/jpeg_filter_app.ts +++ b/examples/jpeg_filter_app.ts @@ -24,100 +24,92 @@ Will convert source pixel formats to 8-bit YUV 4:2:2 */ -import beamcoder from '../ts/index'; // Use require('beamcoder') externally +import beamcoder from '..'; // Use require('beamcoder') externally import Koa from 'koa'; // Add koa to package.json dependencies -import { DecodedFrames } from '..'; const app = new Koa(); app.use(async (ctx) => { // Assume HTTP GET with path // 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 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: 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(); + 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(); - // audio test - // console.log(demuxer); - // const stream_index = demuxer.streams.length - 1; - // console.log(demuxer.streams); - // console.log(demuxer.max_streams); - - const streamSoundIdx = demuxer.streams.findIndex(stream => stream.codecpar.codec_type === 'audio'); - if (streamSoundIdx >= 3) { - const audStr = demuxer.streams[streamSoundIdx]; - // console.log(audStr); - let adec = beamcoder.decoder({ demuxer, stream_index: streamSoundIdx }); // Create a decoder - // console.log(adec); - let apkt = await demuxer.read(); - let afrm: DecodedFrames = await adec.decode(apkt); - console.log(afrm.frames); - const audEnc = beamcoder.encoder({ - name: 'aac', - sample_fmt: 'fltp', - sample_rate: 48000, - channels: 1, - 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 - }], - outputParams: [{ - sampleRate: 1024, - sampleFormat: 'fltp', - channelLayout: 'mono' - }], - filterSpec: 'aresample=1024' - }); - const audFiltPkt = await audFilt.filter([{ frames: afrm as any }]); - const encPkt = await audEnc.encode(audFiltPkt[0].frames[0]); - console.log(encPkt); - } - - const streamVideoIdx = demuxer.streams.findIndex(stream => stream.codecpar.codec_type === 'video'); - let vstr = demuxer.streams[streamVideoIdx]; // Select the video stream (assumes stream 0) - let filt = await beamcoder.filterer({ // Create a filterer for video - filterType: 'video', - inputParams: [{ - width: vstr.codecpar.width, - height: vstr.codecpar.height, - pixelFormat: vstr.codecpar.format, - timeBase: vstr.time_base, - pixelAspect: vstr.sample_aspect_ratio - }], - outputParams: [{ pixelFormat: 'yuv422p' }], - filterSpec: 'scale=640:360, colorspace=range=jpeg:all=bt709' - }); - let filtResult = await filt.filter([{ frames: decResult as any }]); // 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, - height: filtFrame.height, - pix_fmt: 'yuvj422p', - 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 - }; - // console.log(err); - } + // audio test + 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: streamSoundIdx }); // Create a decoder + // console.log(adec); + let apkt = await dm.read(); + let afrm = await adec.decode(apkt); + console.log(afrm.frames); + const audEnc = beamcoder.encoder({ + name: 'aac', + sample_fmt: 'fltp', + sample_rate: 48000, + channels: 1, + 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 + }], + outputParams: [{ + sampleRate: 1024, + sampleFormat: 'fltp', + 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); + // 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: [{ + width: vstr.codecpar.width, + height: vstr.codecpar.height, + pixelFormat: vstr.codecpar.format, + timeBase: vstr.time_base, + pixelAspect: vstr.sample_aspect_ratio + }], + outputParams: [{ pixelFormat: 'yuv422p' }], + 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, + height: filtFrame.height, + pix_fmt: 'yuvj422p', + 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 + }; + // console.log(err); +} }); -app.listen(3001); // Start the server on port 3000 +app.listen(3000); // Start the server on port 3000 diff --git a/examples/make_mp4.ts b/examples/make_mp4.ts index 8579e0c..c9bdffd 100644 --- a/examples/make_mp4.ts +++ b/examples/make_mp4.ts @@ -25,12 +25,9 @@ Output can be viewed in VLC. Make sure "All Files" is selected to see the file. */ -import fs from 'fs'; - -import beamcoder from '../ts/index'; -// 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]); @@ -49,7 +46,7 @@ async function run() { priv_data: { preset: 'slow' } }; - let encoder = beamcoder.encoder(encParams); + let encoder = await beamcoder.encoder(encParams); console.log('Encoder', encoder); const mux = beamcoder.muxer({ format_name: 'mp4' }); @@ -63,7 +60,10 @@ async function run() { format: 'yuv420p' }); console.log(vstr); - await mux.openIO({ url: 'file:test.mp4' }); + await mux.openIO({ + url: 'file:test.mp4' + }); + await mux.writeHeader(); let outFile = fs.createWriteStream(process.argv[2]); diff --git a/examples/streamTest.ts b/examples/streamTest.ts index 16bdbb7..77aad34 100644 --- a/examples/streamTest.ts +++ b/examples/streamTest.ts @@ -1,30 +1,76 @@ -import beamcoder from '..'; // Use require('beamcoder') externally -import path, { resolve } from 'path'; +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 delay = (ms: number) => new Promise((resolve) => { setTimeout(resolve, ms)}) - - -async function process() { +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 stream = beamcoder.demuxerStream({highwaterMark: 360000}); - console.log(stream); - console.log('stream'); + const filelist = await getFiles(); + const stream = new demuxerStream({ highwaterMark: 3600 }); const demuxPromise = stream.demuxer({}) - demuxPromise.then(demux => console.log(demux)); - const src = path.join(__dirname, 'capture'); - const filelist = fs.readdirSync(src); - filelist.sort(); - for (const f of filelist) { - const fullname = path.join(src, f); - const buf = fs.readFileSync(fullname); - // console.log(fullname); + demuxPromise.then(async (demuxer: Demuxer) => { + const 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 + const jpgDest = 'capture.jpg'; + fs.writeFileSync(jpgDest, jpegResult.packets[0].data); + const sumDest = await md5File(jpgDest); + 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}`); + 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); - await delay(100); } + stream.emit('finish'); } -run(); +run().catch(e => console.error(e)); From 07b12fbccadf9121d44062b1e0c08122308e8eb3 Mon Sep 17 00:00:00 2001 From: urielch Date: Thu, 5 May 2022 16:26:29 +0300 Subject: [PATCH 52/68] merge v2 => v2 --- examples/package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/package.json b/examples/package.json index 0980b3d..5709ee9 100644 --- a/examples/package.json +++ b/examples/package.json @@ -2,14 +2,16 @@ "name": "examples", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "jpeg_filter_app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { - "@types/koa": "^2.13.4", "koa": "^2.13.4" + }, + "devDependencies": { + "@types/koa": "^2.13.4" } } From fdf7040a401840b34bd6de9d502377abee802d12 Mon Sep 17 00:00:00 2001 From: urielch Date: Thu, 5 May 2022 21:22:36 +0300 Subject: [PATCH 53/68] merge v2 to v1 --- scratch/common.ts | 34 ------------------------ scratch/decode_aac.ts | 8 +++--- scratch/decode_avci.ts | 9 +++---- scratch/decode_hevc.ts | 6 ++--- scratch/decode_pcm.ts | 19 ++++++------- scratch/make_a_mux.ts | 8 +++--- scratch/muxer.ts | 4 +-- scratch/read_wav.ts | 7 +++-- scratch/simple_mux.ts | 9 +++---- scratch/stream_avci.ts | 15 +++++------ scratch/stream_mp4.ts | 60 +++++++++++++++++++++++++++++++++++------- scratch/stream_mux.ts | 13 +++------ scratch/stream_pcm.ts | 8 +++--- scratch/stream_wav.ts | 7 +++-- 14 files changed, 96 insertions(+), 111 deletions(-) delete mode 100644 scratch/common.ts diff --git a/scratch/common.ts b/scratch/common.ts deleted file mode 100644 index 04b964e..0000000 --- a/scratch/common.ts +++ /dev/null @@ -1,34 +0,0 @@ -import fs, { Stats } from 'fs'; -import path from 'path'; -import url from 'url'; - -export function getMedia(fileName: string, asURL?: boolean): string { - return `../../media/${fileName}`; - //let media = path.join('..', 'media'); - //let stats: Stats | null = null; - //try { - // stats = fs.statSync(media); - //} catch (e) { - // // ignore - //} - //if (stats && !stats.isDirectory()) { - // media = path.join('..', '..', 'media'); - // stats = fs.statSync(media); - //} - //if (stats && !stats.isDirectory()) { - // media = path.join('..', '..', '..', 'media'); - // stats = fs.statSync(media); - //} - //if (stats && !stats.isDirectory()) { - // throw Error(`media directory not found`); - //} - //const ret = path.resolve(media, fileName); - //if (fs.existsSync(ret)) - // throw Error(`missing ${ret} file`); - // - //// return ret.replace(/\\/g, '/'); - //if (asURL) - // return url.pathToFileURL(ret).toString(); - //else - // return ret.replace(/\\/g, '/'); -} \ No newline at end of file diff --git a/scratch/decode_aac.ts b/scratch/decode_aac.ts index 34b274c..3918524 100644 --- a/scratch/decode_aac.ts +++ b/scratch/decode_aac.ts @@ -19,12 +19,10 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '../ts/index'; -import { Packet } from '../ts/types/Packet'; -import { getMedia } from './common'; -// Ok +import beamcoder, { Packet } from '..'; + async function run() { - let demuxer = await beamcoder.demuxer({ url: getMedia('bbb_1080p_c.ts')}); + let demuxer = await beamcoder.demuxer({ url: '../media/bbb_1080p_c.ts'}); let decoder = beamcoder.decoder({ name: 'aac' }); let packet: Packet = {} as Packet; for ( let x = 0 ; packet !== null && x < 100 ; x++ ) { diff --git a/scratch/decode_avci.ts b/scratch/decode_avci.ts index e3c30c2..21625aa 100644 --- a/scratch/decode_avci.ts +++ b/scratch/decode_avci.ts @@ -19,15 +19,12 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '../ts/index'; -import { Packet } from '../ts/types/Packet'; -import { getMedia } from './common'; -// Ok +import beamcoder, { Packet } from '..'; + async function run() { // let demuxer = await beamcoder.demuxer('../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); // console.log(JSON.stringify(demuxer, null, 2)); - // let demuxer = await beamcoder.demuxer(getMedia('dpp/AS11_4K_8.mxf')); - let demuxer = await beamcoder.demuxer(getMedia('dpp/sample_1280x720_surfing_with_audio.mxf')); + let demuxer = await beamcoder.demuxer('M:/dpp/AS11_4K_8.mxf'); // let demuxer = await beamcoder.demuxer('M:/dpp/AS11.mxf'); demuxer.streams.forEach(s => s.discard = (0 == s.index) ? 'default' : 'all'); // let decoder = beamcoder.decoder({ name: 'h264', thread_count: 4, thread_type: { FRAME: false, SLICE: true } }); diff --git a/scratch/decode_hevc.ts b/scratch/decode_hevc.ts index 64695bb..6e5df94 100644 --- a/scratch/decode_hevc.ts +++ b/scratch/decode_hevc.ts @@ -19,11 +19,11 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '../ts/index'; -import { getMedia } from './common'; + +import beamcoder from '..'; async function run() { - let demuxer = await beamcoder.demuxer(getMedia('bbb_1080p_c.ts')); + let demuxer = await beamcoder.demuxer('../media/bbb_1080p_c.ts'); console.log(demuxer); let decoder = await beamcoder.decoder({ name: 'hevc' }); for ( let x = 0 ; x < 100 ; x++ ) { diff --git a/scratch/decode_pcm.ts b/scratch/decode_pcm.ts index d9bbe70..6dc51ee 100644 --- a/scratch/decode_pcm.ts +++ b/scratch/decode_pcm.ts @@ -19,21 +19,22 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '../ts/index'; -import { getMedia } from './common'; +import beamcoder from '..'; async function run() { - let demuxer = await beamcoder.demuxer(getMedia('dpp/AS11_DPP_HD_EXAMPLE_1.mxf')); - console.log(demuxer.streams[1]); - let decoder = beamcoder.decoder({ demuxer: demuxer, stream_index : 1 }); - console.log(decoder); + let demuxer = await beamcoder.demuxer('../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); + console.log('streams cnt:', demuxer.streams.length); + let decoder = await beamcoder.decoder({ demuxer: demuxer, stream_index : 1 }); + console.log('decoder type:', decoder.type); for ( let x = 0 ; x < 100 ; x++ ) { let packet = await demuxer.read(); - // @ts-ignore - 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.ts b/scratch/make_a_mux.ts index 43c5048..599b7d9 100644 --- a/scratch/make_a_mux.ts +++ b/scratch/make_a_mux.ts @@ -19,12 +19,10 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '../ts/index'; -import { Packet } from '../ts/types/Packet'; -import { getMedia } from './common'; +import beamcoder, { Packet } from '..'; async function run() { - let demuxer = await beamcoder.demuxer(getMedia('sound/BBCNewsCountdown.wav')); + let demuxer = await beamcoder.demuxer('../media/sound/BBCNewsCountdown.wav'); console.log(demuxer.streams[0].codecpar); let muxer = beamcoder.muxer({ filename: 'file:test.wav' }); let stream = muxer.newStream({ @@ -59,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: Packet = {} as 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.ts b/scratch/muxer.ts index 6cf547b..c73a845 100644 --- a/scratch/muxer.ts +++ b/scratch/muxer.ts @@ -21,7 +21,7 @@ // Work in progress -import beamcoder from '../ts/index'; +import beamcoder from '..'; const STREAM_FRAME_RATE = 25; @@ -52,7 +52,7 @@ async function addStream(stream, muxer, codecID: number) { // eslint-disable-lin // @ts-ignore switch (codec.media_type) { case 'video': - // @ts-ignore + // @ts-ignore codec.setParameters({ codec_id: codecID, bit_rate: 400000, diff --git a/scratch/read_wav.ts b/scratch/read_wav.ts index cae81c8..d85876b 100644 --- a/scratch/read_wav.ts +++ b/scratch/read_wav.ts @@ -19,15 +19,14 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '../ts/index'; -import { getMedia } from './common'; +import beamcoder from '..'; async function run() { - let demuxer = await beamcoder.demuxer(getMedia('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.ts b/scratch/simple_mux.ts index 9ffe2c4..a6ce12e 100644 --- a/scratch/simple_mux.ts +++ b/scratch/simple_mux.ts @@ -18,20 +18,17 @@ https://www.streampunk.media/ mailto:furnace@streampunk.media 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '../ts/index'; -import { Packet } from '../ts/types/Packet'; -import { getMedia } from './common'; +import beamcoder, { Packet } from '..'; async function run() { - // let demuxer = await beamcoder.demuxer(getMedia('sound/BBCNewsCountdown.wav')); - let demuxer = await beamcoder.demuxer(getMedia('sound/BBCNewsCountdown.wav')); + let demuxer = await beamcoder.demuxer('../media/sound/BBCNewsCountdown.wav'); let muxer = beamcoder.muxer({ filename: 'file:test.wav' }); let stream = muxer.newStream(demuxer.streams[0]); // eslint-disable-line // stream.time_base = demuxer.streams[0].time_base; // stream.codecpar = demuxer.streams[0].codecpar; await muxer.openIO(); await muxer.writeHeader(); - let packet: Packet = {} as 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.ts b/scratch/stream_avci.ts index 81476ac..24ee45c 100644 --- a/scratch/stream_avci.ts +++ b/scratch/stream_avci.ts @@ -19,18 +19,15 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '../ts/index'; +import beamcoder, { DemuxerStream, Packet } from '..'; import fs from 'fs'; -import { Packet } from '../ts/types/Packet'; -import { WritableDemuxerStream } from '../ts/types/Beamstreams'; -import { getMedia } from './common'; // const util = require('util'); async function run() { // let demuxer = await createDemuxer('../../media/dpp/AS11_DPP_HD_EXAMPLE_1.mxf'); // http://ftp.kw.bbc.co.uk/dppdownload/dpp_example_files/AS11_DPP_HD_EXAMPLE_1.mxf - let demuxerStream: WritableDemuxerStream = beamcoder.demuxerStream({ highwaterMark: 65536 }); - fs.createReadStream(getMedia('dpp/AS11_DPP_HD_EXAMPLE_1.mxf')).pipe(demuxerStream); + 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 = beamcoder.decoder({ name: 'h264' }); @@ -83,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', @@ -103,7 +100,7 @@ async function run() { // await demuxer.seek({ frame: 4200, stream_index: 0}); - let packet: Packet = {} as Packet; + let packet = {} as Packet; for ( let x = 0 ; x < 10 && packet !== null; x++ ) { packet = await demuxer.read(); if (packet.stream_index == 0) { diff --git a/scratch/stream_mp4.ts b/scratch/stream_mp4.ts index f687638..f498edf 100644 --- a/scratch/stream_mp4.ts +++ b/scratch/stream_mp4.ts @@ -19,14 +19,44 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import path from 'path'; -import beamcoder from '../ts/index'; -import { getMedia } from './common'; +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() { - // https://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_1080p_h264.mov - // http://www.legittorrents.info/index.php?page=torrent-details&id=7f34612e0fac5e7b051b78bdf1060113350ebfe0 - const urls = [ `file:${getMedia('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 = { @@ -37,10 +67,11 @@ 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] as [number, number] + sample_aspect_ratio: [1, 1] } } ] @@ -53,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' @@ -72,7 +104,15 @@ async function run() { const beamStreams = await beamcoder.makeStreams(params); await beamStreams.run(); - console.log('output result to ', path.resolve('temp.mp4')); + + 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.ts b/scratch/stream_mux.ts index 8aaffda..7c2fcb2 100644 --- a/scratch/stream_mux.ts +++ b/scratch/stream_mux.ts @@ -19,16 +19,12 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '../ts/index'; +import beamcoder, { MuxerStream, Packet } from '..'; import fs from 'fs'; -import { Packet } from '../ts/types/Packet'; -import { ReadableMuxerStream } from '../ts/types/Beamstreams'; -import { getMedia } from './common'; -// OK + async function run() { - let file = getMedia('sound/BBCNewsCountdown.wav'); - let demuxer = await beamcoder.demuxer(file); - let muxerStream: ReadableMuxerStream = beamcoder.muxerStream({ highwaterMark: 65536 }); + let demuxer = await beamcoder.demuxer('../../media/sound/BBCNewsCountdown.wav'); + let muxerStream = new MuxerStream({ highwaterMark: 65536 }); muxerStream.pipe(fs.createWriteStream('test.wav')); let muxer = muxerStream.muxer({ format_name: 'wav' }); let stream = muxer.newStream(demuxer.streams[0]); // eslint-disable-line @@ -44,7 +40,6 @@ async function run() { await muxer.writeFrame(packet); } await muxer.writeTrailer(); - muxerStream.destroy(); } run(); diff --git a/scratch/stream_pcm.ts b/scratch/stream_pcm.ts index 401d9f1..bed9908 100644 --- a/scratch/stream_pcm.ts +++ b/scratch/stream_pcm.ts @@ -19,13 +19,11 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '../ts/index'; +import beamcoder, { DemuxerStream, Packet } from '..'; import fs from 'fs'; -import util from 'util'; // eslint-disable-line -import { Packet } from '../ts/types/Packet'; 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); @@ -64,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: Packet = {} as 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.ts b/scratch/stream_wav.ts index dc84d79..6d4918b 100644 --- a/scratch/stream_wav.ts +++ b/scratch/stream_wav.ts @@ -19,18 +19,17 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ -import beamcoder from '../ts/index'; -import { getMedia } from './common'; +import beamcoder from '..'; async function run() { - const url = `file:${getMedia('sound/Countdown.wav')}`; + const urls = [ 'file:../Media/sound/Countdown.wav' ]; const spec = { start: 50, end: 58 }; const params = { video: [], audio: [ { sources: [ - { url, ms: spec, streamIndex: 0 } + { url: urls[0], ms: spec, streamIndex: 0 } ], filterSpec: '[in0:a] \ volume=precision=float:volume=0.8 \ From ea8dd498f4ac4a568d8c2598e990823aa68059b8 Mon Sep 17 00:00:00 2001 From: urielch Date: Thu, 5 May 2022 21:56:45 +0300 Subject: [PATCH 54/68] merge v2 -> v1 --- package.json | 8 +- ts/calcStats.ts | 29 +- ts/types/Beamstreams.d.ts | 119 +----- ts/types/Codec.d.ts | 73 ++-- ts/types/CodecContext.d.ts | 784 +++++++++++++++++------------------- ts/types/CodecPar.d.ts | 81 ++-- ts/types/Decoder.d.ts | 135 +++++-- ts/types/Demuxer.d.ts | 47 ++- ts/types/Encoder.d.ts | 47 ++- ts/types/Filter.d.ts | 266 ++++++------ ts/types/FormatContext.d.ts | 300 ++++++++++---- ts/types/Frame.d.ts | 49 ++- ts/types/Governor.d.ts | 6 +- ts/types/Muxer.d.ts | 89 ++-- ts/types/Packet.d.ts | 71 ++-- ts/types/index.d.ts | 86 ++++ tsconfig.json | 2 +- 17 files changed, 1219 insertions(+), 973 deletions(-) create mode 100644 ts/types/index.d.ts diff --git a/package.json b/package.json index 3dd0dfd..034fea1 100644 --- a/package.json +++ b/package.json @@ -41,14 +41,16 @@ }, "devDependencies": { "@types/bindings": "^1.5.1", - "@types/node": "^17.0.30", + "@types/node": "^17.0.31", "@types/tape": "^4.13.2", - "eslint": "^8.9.0", + "@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" + "typescript": "^4.6.4", + "webtorrent": "^1.8.16" }, "gypfile": true } diff --git a/ts/calcStats.ts b/ts/calcStats.ts index 31a8296..f368c26 100644 --- a/ts/calcStats.ts +++ b/ts/calcStats.ts @@ -20,19 +20,20 @@ 14 Ormiscaig, Aultbea, Achnasheen, IV22 2JJ U.K. */ export interface ffStats { - mean: number; - stdDev: number; - max: number; - min: number; + 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 }; -}; - + 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/types/Beamstreams.d.ts b/ts/types/Beamstreams.d.ts index 1537bb1..9fa71c9 100644 --- a/ts/types/Beamstreams.d.ts +++ b/ts/types/Beamstreams.d.ts @@ -1,130 +1,46 @@ import { Demuxer, DemuxerCreateOptions } from "./Demuxer" import { Muxer, MuxerCreateOptions } from "./Muxer" import { InputFormat } from "./FormatContext" -import { Stream } from "./Stream" -import { Filterer } from "./Filter" -import { Decoder } from "./Decoder" -import { Encoder } from "./Encoder" -import { Readable, Writable } from "stream" - - -/** - * OLD Typing - * - * 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 { -// // export interface WritableDemuxerStream implements WritableStream extends NodeJS.Writable { -// /** -// * 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 | string): Promise -// } - - - -/** - * WritableDemuxerStream is not a Writable Class augmented by a demuxer function, should be replace by a new class - * 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 type WritableDemuxerStream = Writable & { - /** - * 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?: { iformat?: InputFormat, options?: { [key: string]: any }, governor?: Governor }) => Promise -}; - -/** - * OLD TYPING - * - * 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 -// } - - -/** - * 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 type ReadableMuxerStream = Readable & { - /** - * 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. - */ - muxer: (options?: MuxerCreateOptions & {governor?: Governor }) => Muxer -}; - - - - -/** 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 + streamIndex?: number iformat?: InputFormat - options?: { [key: string]: any } - formatP?: Promise; - format?: Demuxer; - stream?: any; // FIXME + 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 } - // added later - encoder?: Encoder; // TODO add default value - stream?: Stream; + + 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; + 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: { + 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 @@ -137,3 +53,4 @@ export interface BeamstreamParams { options?: { [key:string]: any } } } + diff --git a/ts/types/Codec.d.ts b/ts/types/Codec.d.ts index 1c9fdd9..35079dd 100644 --- a/ts/types/Codec.d.ts +++ b/ts/types/Codec.d.ts @@ -10,17 +10,17 @@ export interface Codec { * This is the primary way to find a codec from the user perspective. */ readonly name: string - /** Descriptive name for the codec, meant to be more human readable than name. */ + /** Descriptive name for the codec, meant to be more human readable than name. */ readonly long_name: string - /** String describing the media type */ + /** String describing the media type */ readonly codec_type: 'unknown' | 'video' | 'audio' | 'data' | 'subtitle' | 'attachment' | 'nb' - /** Number that identifies the syntax and semantics of the bitstream. */ + /** Number that identifies the syntax and semantics of the bitstream. */ readonly id: number - /** true if codec is an decoder */ + /** true if codec is an decoder */ readonly decoder: boolean - /** true if codec is an encoder */ + /** true if codec is an encoder */ readonly encoder: boolean - /** Codec capabilities - see AV_CODEC_CAP_* */ + /** Codec capabilities - see AV_CODEC_CAP_* */ readonly capabilities: { /** Decoder can use draw_horiz_band callback. */ DRAW_HORIZ_BAND: boolean @@ -29,7 +29,7 @@ export interface Codec { TRUNCATED: boolean /** * Decoder requires flushing with NULL input at the end in order to - * give the complete and correct output. + * give the complete and correct output. * NOTE: If this flag is not set, the codec is guaranteed to never be fed with * with NULL data. The user can still send NULL data to the decode function, @@ -40,7 +40,7 @@ export interface Codec { * returns frames. */ DELAY: boolean - /** Codec can be fed a final frame with a smaller size. This can be used to prevent truncation of the last audio samples. */ + /** Codec can be fed a final frame with a smaller size. This can be used to prevent truncation of the last audio samples. */ SMALL_LAST_FRAME: boolean /** * Codec can output multiple frames per APacket @@ -52,11 +52,11 @@ export interface Codec { * may return multiple frames in a packet. This has many disadvantages like * prohibiting stream copy in many cases thus it should only be considered * as a last resort. - */ + */ SUBFRAMES: boolean - /** Codec is experimental and is thus avoided in favor of non experimental codecs */ + /** Codec is experimental and is thus avoided in favor of non experimental codecs */ EXPERIMENTAL: boolean - /** Codec should fill in channel configuration and samplerate instead of container */ + /** Codec should fill in channel configuration and samplerate instead of container */ CHANNEL_CONF: boolean /** Codec supports frame-level multithreading. */ FRAME_THREADS: boolean @@ -68,17 +68,17 @@ export interface Codec { AUTO_THREADS: boolean /** Audio encoder supports receiving a different number of samples in each call. */ VARIABLE_FRAME_SIZE: boolean - /** - * Decoder is not a preferred choice for probing. - * This indicates that the decoder is not a good choice for probing. - * It could for example be an expensive to spin up hardware decoder, - * or it could simply not provide a lot of useful information about - * the stream. - * A decoder marked with this flag should only be used as last resort - * choice for probing. - */ + /** + * Decoder is not a preferred choice for probing. + * This indicates that the decoder is not a good choice for probing. + * It could for example be an expensive to spin up hardware decoder, + * or it could simply not provide a lot of useful information about + * the stream. + * A decoder marked with this flag should only be used as last resort + * choice for probing. + */ AVOID_PROBING: boolean - /** Codec is intra only. */ + /** Codec is intra only. */ INTRA_ONLY: boolean /** Codec is lossless. */ LOSSLESS: boolean @@ -90,31 +90,34 @@ export interface Codec { */ HYBRID: boolean } - /** Array of supported framerates (as a rational [num, den]), or null if unknown. */ + /** Array of supported framerates (as a rational [num, den]), or null if unknown. */ readonly supported_framerates: ReadonlyArray> | null - /** Array of supported pixel formats, or null if unknown. */ + /** Array of supported pixel formats, or null if unknown. */ readonly pix_fmts: ReadonlyArray | null - /** Array of supported audio samplerates, or null if unknown */ + /** Array of supported audio samplerates, or null if unknown */ readonly supported_samplerates: ReadonlyArray | null - /** Array of supported sample formats, or NULL if unknown, */ + /** Array of supported sample formats, or NULL if unknown, */ readonly sample_fmts: ReadonlyArray - /** */ + /** */ readonly channel_layouts: ReadonlyArray - /** */ + /** */ readonly max_lowres: number /** Class for private context */ readonly priv_class: PrivClass - /** */ + /** */ readonly profiles: ReadonlyArray | null - /** */ + /** */ readonly wrapper_name?: string - /** */ + /** */ readonly descriptor: { - INTRA_ONLY: boolean - LOSSY: boolean - LOSSLESS: boolean - REORDER: boolean - BITMAP_SUB: boolean + INTRA_ONLY: boolean + LOSSY: boolean + LOSSLESS: boolean + REORDER: boolean + BITMAP_SUB: boolean TEXT_SUB: boolean } } + +/** List the available codecs */ +export function codecs(): { [key: string]: { encoder?: Codec, decoder?: Codec }} diff --git a/ts/types/CodecContext.d.ts b/ts/types/CodecContext.d.ts index 2bdb497..1e483ac 100644 --- a/ts/types/CodecContext.d.ts +++ b/ts/types/CodecContext.d.ts @@ -1,33 +1,41 @@ import { HWDeviceContext, HWFramesContext } from "./HWContext" export type MotionEstimationString = 'sad' | 'sse' | 'satd' | 'dct' | 'psnr' | 'bit' | 'rd' | 'zero' | 'vsad' | - 'vsse' | 'nsse' | 'w53' | 'w97' | 'dctmax' | 'dct264' | 'median_sad' | 'chroma' + 'vsse' | 'nsse' | 'w53' | 'w97' | 'dctmax' | 'dct264' | 'median_sad' | 'chroma' export type FrameSkipString = 'none' | 'default' | 'nonref' | 'bidir' | 'nonintra' | 'nonkey' | 'all' - -export interface CodecContextBaseMin { +/** The CodecContext object */ +export interface CodecContext { /** Object name. */ - readonly type: 'decoder' | 'encoder'; // string - /** see AV_CODEC_ID_xxx */ + readonly type: string + /** see AV_CODEC_ID_xxx */ readonly codec_id: number /** * Name of the codec implementation. - * The name is globally unique among encoders and among decoders (but an - * encoder and a decoder can share the same name). - * This is the primary way to find a codec from the user perspective. + * The name is globally unique among encoders and among decoders (but an + * encoder and a decoder can share the same name). + * This is the primary way to find a codec from the user perspective. */ - readonly name: string - /** Descriptive name for the codec, meant to be more human readable than name. */ - readonly long_name: string - /** - * A fourcc string by default, will be a number if not recognised - * - LSB first, so "ABCD" -> ('D'<<24) + ('C'<<16) + ('B'<<8) + 'A'). - */ - readonly codec_tag: string | number - /** Codec private data. */ - priv_data: { [key: string]: any } | null - /** The average bitrate */ - bit_rate: number + readonly name: string + /** Descriptive name for the codec, meant to be more human readable than name. */ + readonly long_name: string + /** + * A fourcc string by default, will be a number if not recognised + * - LSB first, so "ABCD" -> ('D'<<24) + ('C'<<16) + ('B'<<8) + 'A'). + */ + readonly codec_tag: string | number + /** Codec private data. */ + priv_data: { [key: string]: any } | null + /** The average bitrate */ + bit_rate: number + /** + * Number of bits the bitstream is allowed to diverge from the reference. + * The reference can be CBR (for CBR pass1) or VBR (for pass2) + */ + bit_rate_tolerance: number + /** Global quality for codecs which cannot change it per frame. This should be proportional to MPEG-1/2/4 qscale. */ + global_quality: number + compression_level: number /** AV_CODEC_FLAG_*. */ flags: { [key: string]: boolean } /** AV_CODEC_FLAG2_*. */ @@ -40,8 +48,8 @@ export interface CodecContextBaseMin { * The allocated memory should be AV_INPUT_BUFFER_PADDING_SIZE bytes larger * than extradata_size to avoid problems if it is read with the bitstream reader. * The bytewise contents of extradata must not depend on the architecture or CPU endianness. - */ - extradata: Buffer | null + */ + extradata: Buffer | null /** * This is the fundamental unit of time (in seconds) in terms * of which frame timestamps are represented. For fixed-fps content, @@ -56,8 +64,8 @@ export interface CodecContextBaseMin { * As example of such codec time base see ISO/IEC 14496-2:2001(E) * vop_time_increment_resolution and fixed_vop_rate * (fixed_vop_rate == 0 implies that it is different from the framerate) - */ - time_base: [number, number] + */ + 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 @@ -65,7 +73,7 @@ export interface CodecContextBaseMin { * * Set to time_base ticks per frame. Default 1, e.g., H.264/MPEG-2 set it to 2. */ - ticks_per_frame: number + ticks_per_frame: number /** * Number of frames delay in addition to what a standard decoder * as specified in the spec would produce. @@ -78,7 +86,7 @@ export interface CodecContextBaseMin { * Number of samples the decoder needs to output before the decoder's output is valid. * When seeking, you should start decoding this many samples prior to your desired seek point. */ - readonly delay: number + readonly delay: number /** * picture width / height. * @@ -90,9 +98,22 @@ export interface CodecContextBaseMin { * to be set by the caller. During decoding, the decoder may * overwrite those values as required while parsing the data. */ - width: number - height: number - /** + width: number + height: number + /** + * Bitstream width / height, may be different from width/height e.g. when + * the decoded frame is cropped before being output or lowres is enabled. + * + * @note Those field may not match the value of the last + * Frame output due to frame reordering. + * + * May be set by the user before opening the decoder if known + * e.g. from the container. During decoding, the decoder may + * overwrite those values as required while parsing the data. + */ + coded_width: any + coded_height: any + /** * Pixel format, see AV_PIX_FMT_xxx. * May be set by the demuxer if known from headers. * May be overridden by the decoder if it knows better. @@ -100,43 +121,117 @@ export interface CodecContextBaseMin { * @note This field may not match the value of the last * Frame output due to frame reordering. */ - pix_fmt: string | null + pix_fmt: string | null + /** + * Maximum number of B-frames between non-B-frames + * Note: The output will be delayed by max_b_frames+1 relative to the input. + */ + max_b_frames: number + /** qscale factor between IP and B-frames + * If > 0 then the last P-frame quantizer will be used (q= lastp_q*factor+offset). + * If < 0 then normal ratecontrol will be done (q= -normal_q*factor+offset). + */ + b_quant_factor: number + /** qscale offset between IP and B-frames */ + b_quant_offset: number /** * Size of the frame reordering buffer in the decoder. * For MPEG-2 it is 1 IPB or 0 low delay IP. */ - readonly has_b_frames: number + readonly has_b_frames: number + /** qscale factor between P- and I-frames + * If > 0 then the last P-frame quantizer will be used (q = lastp_q * factor + offset). + * If < 0 then normal ratecontrol will be done (q= -normal_q*factor+offset). + */ + i_quant_factor: number + /** qscale offset between P and I-frames */ + i_quant_offset: number + /** luminance masking (0-> disabled) */ + lumi_masking: number + /** temporal complexity masking (0-> disabled) */ + temporal_cplx_masking: number + /** spatial complexity masking (0-> disabled) */ + spatial_cplx_masking: number + /** p block masking (0-> disabled) */ + p_masking: number + /** darkness masking (0-> disabled) */ + dark_masking: number /** slice count */ - slice_count: number + slice_count: number /** slice offsets in the frame in bytes */ slice_offset: Array | null - - + /** + * sample aspect ratio (0/1 if unknown) + * That is the width of a pixel divided by the height of the pixel. + * Numerator and denominator must be relatively prime and smaller than 256 for some video standards. + */ + sample_aspect_ratio: Array + /** motion estimation comparison function */ + me_cmp: MotionEstimationString + /** subpixel motion estimation comparison function */ + me_sub_cmp: MotionEstimationString + /** macroblock comparison function (not supported yet) */ + mb_cmp: MotionEstimationString + /** interlaced DCT comparison function */ + ildct_cmp: MotionEstimationString + /** ME diamond size & shape */ + dia_size: number + /** amount of previous MV predictors (2a+1 x 2a+1 square) */ + last_predictor_count: number + /** motion estimation prepass comparison function */ + me_pre_cmp: MotionEstimationString + /** ME prepass diamond size & shape */ + pre_dia_size: number + /** subpel ME quality */ + me_subpel_quality: number + /** maximum motion estimation search range in subpel units. If 0 then no limit. */ + me_range: number + slice_flags: { + /** draw_horiz_band() is called in coded order instead of display */ + CODED_ORDER: boolean + /** allow draw_horiz_band() with field slices (MPEG-2 field pics) */ + ALLOW_FIELD: boolean + /** allow draw_horiz_band() with 1 component at a time (SVQ1) */ + ALLOW_PLANE: boolean + } + /** + * macroblock decision mode + * simple: uses mb_cmp + * bits: chooses the one which needs the fewest bits + * rd: rate distortion + */ + mb_decision: 'simple' | 'bits' | 'rd' /** custom intra quantization matrix */ intra_matrix: Array | null /** custom inter quantization matrix */ inter_matrix: Array | null - /** precision of the intra DC coefficient - 8 */ + /** precision of the intra DC coefficient - 8 */ intra_dc_precision: number - - /** - * sample aspect ratio (0/1 if unknown) - * That is the width of a pixel divided by the height of the pixel. - * Numerator and denominator must be relatively prime and smaller than 256 for some video standards. - */ - sample_aspect_ratio: [number, number] + /** Number of macroblock rows at the top which are skipped. */ + skip_top: number + /** Number of macroblock rows at the bottom which are skipped. */ + skip_bottom: number + /** minimum MB Lagrange multiplier */ + mb_lmin: number + /** maximum MB Lagrange multiplier */ + mb_lmax: number + /** */ + bidir_refine: number + /** minimum GOP size */ + keyint_min: number /** number of reference frames */ refs: number + /** Value depends upon the compare function used for fullpel ME. */ + mv0_threshold: number /** Chromaticity coordinates of the source primaries. */ - color_primaries?: string | "unknown" + color_primaries?: string /** Color Transfer Characteristic. */ - color_trc: string | "unknown" + color_trc: string /** YUV colorspace type. */ - colorspace: string | "unknown" + colorspace: string /** MPEG vs JPEG YUV range. */ - color_range: string | "unknown" - - /** + color_range: string + /** * Location of chroma samples. * * Illustration showing the location of the first (top left) chroma sample of the @@ -148,103 +243,216 @@ export interface CodecContextBaseMin { * v v v v * ______ ______ *1st luma line > |X X ... |3 4 X ... X are luma samples, - *. | |1 2 1-6 are possible chroma positions + *. | |1 2 1-6 are possible chroma positions *2nd luma line > |X X ... |5 6 X ... 0 is undefined/unknown position *``` */ - chroma_sample_location: 'unspecified' | 'left' | 'center' | 'topleft' | 'top' | 'bottomleft' | 'bottom' - - - field_order: 'progressive' | - 'top coded first, top displayed first' | - 'bottom coded first, bottom displayed first' | - 'top coded first, bottom displayed first' | - 'bottom coded first, top displayed first' | - 'unknown' - /** Audio only - samples per second */ - sample_rate: number - /** Audio only - number of audio channels */ - channels: number - /** audio sample format */ - sample_fmt: string | null - /** - * Number of samples per channel in an audio frame. - * May be set by some decoders to indicate constant frame size - */ - readonly frame_size: number - /** - * Frame counter - total number of frames returned from the decoder so far. - * @note the counter is not incremented if encoding/decoding resulted in an error. - */ - readonly frame_number: number - /** Audio cutoff bandwidth (0 means "automatic") */ - cutoff: number - /** Audio channel layout. */ - channel_layout: string | "0 channels" - - // readonly - audio_service_type: 'main' | 'effects' | 'visually-impaired' | 'hearing-impaired' | 'dialogue' | - 'commentary' | 'emergency' | 'voice-over' | 'karaoke' | 'nb' + chroma_sample_location: 'unspecified' | 'left' | 'center' | 'topleft' | 'top' | 'bottomleft' | 'bottom' + /** Number of slices. Indicates number of picture subdivisions. Used for parallelized decoding. */ + slices: number + field_order: 'progressive' | + 'top coded first, top displayed first' | + 'bottom coded first, bottom displayed first' | + 'top coded first, bottom displayed first' | + 'bottom coded first, top displayed first' | + 'unknown' + /** Audio only - samples per second */ + sample_rate: number + /** Audio only - number of audio channels */ + channels: number + /** audio sample format */ + sample_fmt: string | null + /** + * Number of samples per channel in an audio frame. + * May be set by some decoders to indicate constant frame size + */ + readonly frame_size: number + /** + * Frame counter - total number of frames returned from the decoder so far. + * @note the counter is not incremented if encoding/decoding resulted in an error. + */ + readonly frame_number: number + /** number of bytes per packet if constant and known or 0. Used by some WAV based audio codecs. */ + block_align: number + /** Audio cutoff bandwidth (0 means "automatic") */ + cutoff: number + /** Audio channel layout. */ + channel_layout: string + /** Request decoder to use this channel layout if it can (0 for default) */ + request_channel_layout: string + /** Type of service that the audio stream conveys. */ + audio_service_type: 'main' | 'effects' | 'visually-impaired' | 'hearing-impaired' | 'dialogue' | + 'commentary' | 'emergency' | 'voice-over' | 'karaoke' | 'nb' + /** Desired sample format - decoder will decode to this format if it can. */ + request_sample_fmt: string | null + /** amount of qscale change between easy & hard scenes (0.0-1.0) */ + qcompress: number + /** amount of qscale smoothing over time (0.0-1.0) */ + qblur: number + /** minimum quantizer */ + qmin: number + /** maximum quantizer */ + qmax: number + /** maximum quantizer difference between frames */ + max_qdiff: number + /** decoder bitstream buffer size */ + rc_buffer_size: number + /** ratecontrol override */ + rc_override: Array<{ + type: 'RcOverride' + start_frame: number + end_frame: number + /** If this is 0 then quality_factor will be used instead. */ + qscale: number + quality_factor: number + }> /** maximum bitrate */ rc_max_rate: number - - /** Work around bugs in codecs which sometimes cannot be detected automatically. */ - workaround_bugs: { [key: string]: boolean } - /** - * strictly follow the standard (MPEG-4, ...). - * Setting this to STRICT or higher means the encoder and decoder will - * generally do stupid things, whereas setting it to unofficial or lower - * will mean the encoder might produce output that is not supported by all - * spec-compliant decoders. Decoders don't differentiate between normal, - * unofficial and experimental (that is, they always try to decode things - * when they can) unless they are explicitly asked to behave stupidly - * (=strictly conform to the specs) - */ - strict_std_compliance: 'very-strict' | 'strict' | 'normal' | 'unofficial' | 'experimental' - debug: { [key: string]: boolean } + /** minimum bitrate */ + rc_min_rate: number + /** Ratecontrol attempt to use, at maximum, of what can be used without an underflow. */ + rc_max_available_vbv_use: number + /** Ratecontrol attempt to use, at least, times the amount needed to prevent a vbv overflow. */ + rc_min_vbv_overflow_use: number + /** Number of bits which should be loaded into the rc buffer before decoding starts. */ + rc_initial_buffer_occupancy: number + /** trellis RD quantization */ + trellis: number + /** pass1 encoding statistics output buffer */ + readonly stats_out: string | null + /** pass2 encoding statistics input buffer. Concatenated stuff from stats_out of pass1 should be placed here. */ + stats_in: string | null + /** Work around bugs in codecs which sometimes cannot be detected automatically. */ + workaround_bugs: { [key: string]: boolean } + /** + * strictly follow the standard (MPEG-4, ...). + * Setting this to STRICT or higher means the encoder and decoder will + * generally do stupid things, whereas setting it to unofficial or lower + * will mean the encoder might produce output that is not supported by all + * spec-compliant decoders. Decoders don't differentiate between normal, + * unofficial and experimental (that is, they always try to decode things + * when they can) unless they are explicitly asked to behave stupidly + * (=strictly conform to the specs) + */ + strict_std_compliance: 'very-strict' | 'strict' | 'normal' | 'unofficial' | 'experimental' + /** Error concealment flags */ + error_concealment: { + GUESS_MVS?: boolean + DEBLOCK?: boolean + FAVOR_INTER?: boolean + } + debug: { [key: string]: boolean } + /** Error recognition - may misdetect some more or less valid parts as errors. */ + err_recognition: { [key: string]: boolean } + /** Opaque 64-bit number (generally a PTS) that will be reordered and output in Frame.reordered_opaque */ + reordered_opaque: number + readonly error: ReadonlyArray | null + /** DCT algorithm */ + dct_algo: 'auto' | 'fastint' | 'int' | 'mmx' | 'altivec' | 'faan' /** IDCT algorithm */ idct_algo: 'auto' | 'int' | 'simple' | 'simplemmx' | 'arm' | 'altivec' | 'simplearm' | 'xvid' | 'simplearmv5te' | 'simplearmv6' | 'faan' | 'simpleneon' | 'none' | 'simpleauto' - /** Bits per sample/pixel from the demuxer (needed for huffyuv). */ + /** Bits per sample/pixel from the demuxer (needed for huffyuv). */ bits_per_coded_sample: number - - /** Bits per sample/pixel of internal libavcodec pixel/sample format. */ + /** Bits per sample/pixel of internal libavcodec pixel/sample format. */ bits_per_raw_sample: number - /** Thread count is used to decide how many independent tasks should be passed to execute() */ + /** Thread count is used to decide how many independent tasks should be passed to execute() */ thread_count: number - + /** + * Which multithreading methods to use. + * Use of FRAME will increase decoding delay by one frame per thread, + * so clients which cannot provide future frames should not use it. + */ + thread_type: { FRAME?: boolean, SLICE?: boolean } + /** Which multithreading methods are in use by the codec. */ + readonly active_thread_type: { FRAME?: boolean, SLICE?: boolean } + /** + * Set by the client if its custom get_buffer() callback can be called + * synchronously from another thread, which allows faster multithreaded decoding. + * draw_horiz_band() will be called from other threads regardless of this setting. + * Ignored if the default get_buffer() is used. + */ + thread_safe_callbacks: number + /** nsse_weight */ + nsse_weight: number + + profile: string | number + level: number + /** Skip loop filtering for selected frames. */ + skip_loop_filter: FrameSkipString + /** Skip IDCT/dequantization for selected frames. */ + skip_idct: FrameSkipString + /** Skip decoding for selected frames. */ + skip_frame: FrameSkipString /** - * Which multithreading methods to use. - * Use of FRAME will increase decoding delay by one frame per thread, - * so clients which cannot provide future frames should not use it. + * Header containing style information for text subtitles. + * For SUBTITLE_ASS subtitle type, it should contain the whole ASS + * [Script Info] and [V4+ Styles] section, plus the [Events] line and + * the Format line following. It shouldn't include any Dialogue line. */ - thread_type: { FRAME?: boolean, SLICE?: boolean } + subtitle_header: Buffer | null + /** + * Audio only. The number of "priming" samples (padding) inserted by the + * encoder at the beginning of the audio. I.e. this number of leading + * decoded samples must be discarded by the caller to get the original audio + * without leading padding. + * + * The timestamps on the output packets are adjusted by the encoder so that + * they always refer to the first sample of the data actually contained in the packet, + * including any added padding. E.g. if the timebase is 1/samplerate and + * the timestamp of the first input sample is 0, the timestamp of the + * first output packet will be -initial_padding. + */ + readonly inital_padding: number + /** + * For codecs that store a framerate value in the compressed + * bitstream, the decoder may export it here. [ 0, 1 ] when unknown. + */ + 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. */ + pkt_timebase: Array - /** Which multithreading methods are in use by the codec. */ - readonly active_thread_type: { FRAME?: boolean, SLICE?: boolean } - /** - * Set by the client if its custom get_buffer() callback can be called - * synchronously from another thread, which allows faster multithreaded decoding. - * draw_horiz_band() will be called from other threads regardless of this setting. - * Ignored if the default get_buffer() is used. - */ - thread_safe_callbacks: number - - profile: string | number - level: number - /** - * Header containing style information for text subtitles. - * For SUBTITLE_ASS subtitle type, it should contain the whole ASS - * [Script Info] and [V4+ Styles] section, plus the [Events] line and - * the Format line following. It shouldn't include any Dialogue line. - */ - subtitle_header: Buffer | null - /** - * For codecs that store a framerate value in the compressed - * bitstream, the decoder may export it here. [ 0, 1 ] when unknown. - */ - framerate: [number, number] + readonly codec_descriptor: { + INTRA_ONLY: boolean + LOSSY: boolean + LOSSLESS: boolean + REORDER: boolean + BITMAP_SUB: boolean + TEXT_SUB: boolean + } | null + /** Character encoding of the input subtitles file. */ + sub_charenc: string | null + /** + * Subtitles character encoding mode. Formats or codecs might be adjusting + * this setting (if they are doing the conversion themselves for instance). + */ + readonly sub_charenc_mode: 'do-nothing' | 'automatic' | 'pre-decoder' | 'ignore' + /** + * Skip processing alpha if supported by codec. + * Note that if the format uses pre-multiplied alpha (common with VP6, + * and recommended due to better video quality/compression) + * the image will look as if alpha-blended onto a black background. + * However for formats that do not use pre-multiplied alpha + * there might be serious artefacts (though e.g. libswscale currently + * assumes pre-multiplied alpha anyway). + */ + skip_alpha: number + /** Number of samples to skip after a discontinuity */ + readonly seek_preroll: number + /** custom intra quantization matrix */ + chroma_intra_matrix: Array /** Dump format separator - can be ", " or "\n " or anything else */ dump_separator: string | null + /** ',' separated list of allowed decoders - if null then all are allowed */ + codec_whitelist: string | null + /** Properties of the stream that gets decoded */ + readonly properties: { LOSSLESS: boolean, CLOSED_CAPTIONS: boolean } + /** Additional data associated with the entire coded stream. */ + readonly coded_side_data: { + type: 'PacketSideData' + [key: string]: Buffer | string + } /** * A reference to the AVHWFramesContext describing the input (for encoding) * or output (decoding) frames. The reference is set by the caller and @@ -267,125 +475,44 @@ export interface CodecContextBaseMin { * * This field should be set before avcodec_open2() is called. */ - hw_frames_ctx: HWFramesContext | null + hw_frames_ctx: HWFramesContext + /** Control the form of AVSubtitle.rects[N]->ass */ + sub_text_format: number /** * Audio only. The amount of padding (in samples) appended by the encoder to * the end of the audio. I.e. this number of decoded samples must be * discarded by the caller from the end of the stream to get the original * audio without any trailing padding. - */ - trailing_padding: number - /** The number of pixels per image to maximally accept. */ - max_pixels: number - /** - * A reference to the HWDeviceContext describing the device which will - * be used by a hardware encoder/decoder. The reference is set by the - * caller and afterwards owned (and freed) by libavcodec. - * - * This should be used if either the codec device does not require - * hardware frames or any that are used are to be allocated internally by - * libavcodec. If the user wishes to supply any of the frames used as - * encoder input or decoder output then hw_frames_ctx should be used - * instead. When hw_frames_ctx is set in get_format() for a decoder, this - * field will be ignored while decoding the associated stream segment, but - * may again be used on a following one after another get_format() call. - * - * For both encoders and decoders this field should be set before - * avcodec_open2() is called and must not be written to thereafter. - * - * Note that some decoders may require this field to be set initially in - * order to support hw_frames_ctx at all - in that case, all frames - * contexts used must be created on the same device. - */ - hw_device_ctx: HWDeviceContext | null - // private field - readonly _CodecContext: {}; -} -export interface CodecContext_base extends CodecContextBaseMin { - /** - * Bitstream width / height, may be different from width/height e.g. when - * the decoded frame is cropped before being output or lowres is enabled. + */ + trailing_padding: number + /** The number of pixels per image to maximally accept. */ + max_pixels: number + /** + * A reference to the HWDeviceContext describing the device which will + * be used by a hardware encoder/decoder. The reference is set by the + * caller and afterwards owned (and freed) by libavcodec. * - * @note Those field may not match the value of the last - * Frame output due to frame reordering. + * This should be used if either the codec device does not require + * hardware frames or any that are used are to be allocated internally by + * libavcodec. If the user wishes to supply any of the frames used as + * encoder input or decoder output then hw_frames_ctx should be used + * instead. When hw_frames_ctx is set in get_format() for a decoder, this + * field will be ignored while decoding the associated stream segment, but + * may again be used on a following one after another get_format() call. * - * May be set by the user before opening the decoder if known - * e.g. from the container. During decoding, the decoder may - * overwrite those values as required while parsing the data. - */ - coded_width: any - coded_height: any - slice_flags: { - /** draw_horiz_band() is called in coded order instead of display */ - CODED_ORDER: boolean - /** allow draw_horiz_band() with field slices (MPEG-2 field pics) */ - ALLOW_FIELD: boolean - /** allow draw_horiz_band() with 1 component at a time (SVQ1) */ - ALLOW_PLANE: boolean - } - /** Number of macroblock rows at the top which are skipped. */ - skip_top: number - /** Number of macroblock rows at the bottom which are skipped. */ - skip_bottom: number - /** Request decoder to use this channel layout if it can (0 for default) */ - request_channel_layout: string | 'default' - /** Desired sample format - decoder will decode to this format if it can. */ - request_sample_fmt: string | null - /** Error concealment flags */ - error_concealment: { - GUESS_MVS?: boolean - DEBLOCK?: boolean - FAVOR_INTER?: boolean - } - /** Error recognition - may misdetect some more or less valid parts as errors. */ - err_recognition: { [key: string]: boolean } - /** Opaque 64-bit number (generally a PTS) that will be reordered and output in Frame.reordered_opaque */ - reordered_opaque: number - /** Skip loop filtering for selected frames. */ - skip_loop_filter: FrameSkipString - /** Skip IDCT/dequantization for selected frames. */ - skip_idct: FrameSkipString - /** Skip decoding for selected frames. */ - skip_frame: FrameSkipString - /** 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. */ - pkt_timebase: [number, number] - readonly codec_descriptor: { - INTRA_ONLY: boolean - LOSSY: boolean - LOSSLESS: boolean - REORDER: boolean - BITMAP_SUB: boolean - TEXT_SUB: boolean - } | null - /** Character encoding of the input subtitles file. */ - sub_charenc: string | null - /** - * Subtitles character encoding mode. Formats or codecs might be adjusting - * this setting (if they are doing the conversion themselves for instance). + * For both encoders and decoders this field should be set before + * avcodec_open2() is called and must not be written to thereafter. + * + * Note that some decoders may require this field to be set initially in + * order to support hw_frames_ctx at all - in that case, all frames + * contexts used must be created on the same device. */ - readonly sub_charenc_mode: 'do-nothing' | 'automatic' | 'pre-decoder' | 'ignore' + hw_device_ctx: HWDeviceContext /** - * Skip processing alpha if supported by codec. - * Note that if the format uses pre-multiplied alpha (common with VP6, - * and recommended due to better video quality/compression) - * the image will look as if alpha-blended onto a black background. - * However for formats that do not use pre-multiplied alpha - * there might be serious artefacts (though e.g. libswscale currently - * assumes pre-multiplied alpha anyway). - */ - skip_alpha: number - /** ',' separated list of allowed decoders - if null then all are allowed */ - codec_whitelist: string | null - /** Properties of the stream that gets decoded */ - readonly properties: { LOSSLESS: boolean, CLOSED_CAPTIONS: boolean } - /** * Bit set of AV_HWACCEL_FLAG_* flags, which affect hardware accelerated * decoding (if active). - */ - hwaccel_flags: { IGNORE_LEVEL?: boolean, ALLOW_HIGH_DEPTH?: boolean, ALLOW_PROFILE_MISMATCH?: boolean } - + */ + hwaccel_flags: { IGNORE_LEVEL?: boolean, ALLOW_HIGH_DEPTH?: boolean, ALLOW_PROFILE_MISMATCH?: boolean } /** * Video decoding only. Certain video codecs support cropping, meaning that * only a sub-rectangle of the decoded frame is intended for display. This @@ -413,173 +540,18 @@ export interface CodecContext_base extends CodecContextBaseMin { */ apply_cropping: number /* - * Video decoding only. Sets the number of extra hardware frames which - * the decoder will allocate for use by the caller. This must be set - * before avcodec_open2() is called. - * - * Some hardware decoders require all frames that they will use for - * output to be defined in advance before decoding starts. For such - * decoders, the hardware frame pool must therefore be of a fixed size. - * The extra frames set here are on top of any number that the decoder - * needs internally in order to operate normally (for example, frames - * used as reference pictures). - */ + * Video decoding only. Sets the number of extra hardware frames which + * the decoder will allocate for use by the caller. This must be set + * before avcodec_open2() is called. + * + * Some hardware decoders require all frames that they will use for + * output to be defined in advance before decoding starts. For such + * decoders, the hardware frame pool must therefore be of a fixed size. + * The extra frames set here are on top of any number that the decoder + * needs internally in order to operate normally (for example, frames + * used as reference pictures). + */ extra_hw_frames: number - /** Control the form of AVSubtitle.rects[N]->ass */ - sub_text_format: number -} - - -/** The CodecContext object */ -export interface CodecContext extends CodecContext_base { - /** - * Number of bits the bitstream is allowed to diverge from the reference. - * The reference can be CBR (for CBR pass1) or VBR (for pass2) - */ - bit_rate_tolerance: number - /** Global quality for codecs which cannot change it per frame. This should be proportional to MPEG-1/2/4 qscale. */ - global_quality: number - - compression_level: number - /** - * Maximum number of B-frames between non-B-frames - * Note: The output will be delayed by max_b_frames+1 relative to the input. - */ - max_b_frames: number - /** qscale factor between IP and B-frames - * If > 0 then the last P-frame quantizer will be used (q= lastp_q*factor+offset). - * If < 0 then normal ratecontrol will be done (q= -normal_q*factor+offset). - */ - b_quant_factor: number - /** qscale offset between IP and B-frames */ - b_quant_offset: number - /** qscale factor between P- and I-frames - * If > 0 then the last P-frame quantizer will be used (q = lastp_q * factor + offset). - * If < 0 then normal ratecontrol will be done (q= -normal_q*factor+offset). - */ - i_quant_factor: number - /** qscale offset between P and I-frames */ - i_quant_offset: number - /** luminance masking (0-> disabled) */ - lumi_masking: number - /** temporal complexity masking (0-> disabled) */ - temporal_cplx_masking: number - /** spatial complexity masking (0-> disabled) */ - spatial_cplx_masking: number - /** p block masking (0-> disabled) */ - p_masking: number - /** darkness masking (0-> disabled) */ - dark_masking: number - /** motion estimation comparison function */ - me_cmp: MotionEstimationString - /** subpixel motion estimation comparison function */ - me_sub_cmp: MotionEstimationString - /** macroblock comparison function (not supported yet) */ - mb_cmp: MotionEstimationString - /** interlaced DCT comparison function */ - ildct_cmp: MotionEstimationString - /** ME diamond size & shape */ - dia_size: number - /** amount of previous MV predictors (2a+1 x 2a+1 square) */ - last_predictor_count: number - - /** motion estimation prepass comparison function */ - me_pre_cmp: MotionEstimationString - /** ME prepass diamond size & shape */ - pre_dia_size: number - /** subpel ME quality */ - me_subpel_quality: number - /** maximum motion estimation search range in subpel units. If 0 then no limit. */ - me_range: number - /** - * macroblock decision mode - * simple: uses mb_cmp - * bits: chooses the one which needs the fewest bits - * rd: rate distortion - */ - mb_decision: 'simple' | 'bits' | 'rd' - - /** minimum MB Lagrange multiplier */ - mb_lmin: number - /** maximum MB Lagrange multiplier */ - mb_lmax: number - /** */ - bidir_refine: number - /** minimum GOP size */ - keyint_min: number - - /** Value depends upon the compare function used for fullpel ME. */ - mv0_threshold: number - - /** Number of slices. Indicates number of picture subdivisions. Used for parallelized decoding. */ - slices: number - /** number of bytes per packet if constant and known or 0. Used by some WAV based audio codecs. */ - block_align: number - - /** Type of service that the audio stream conveys. */ - audio_service_type: 'main' | 'effects' | 'visually-impaired' | 'hearing-impaired' | 'dialogue' | - 'commentary' | 'emergency' | 'voice-over' | 'karaoke' | 'nb' - - /** amount of qscale change between easy & hard scenes (0.0-1.0) */ - qcompress: number - /** amount of qscale smoothing over time (0.0-1.0) */ - qblur: number - /** minimum quantizer */ - qmin: number - /** maximum quantizer */ - qmax: number - /** maximum quantizer difference between frames */ - max_qdiff: number - /** decoder bitstream buffer size */ - rc_buffer_size: number - /** ratecontrol override */ - rc_override: Array<{ - type: 'RcOverride' - start_frame: number - end_frame: number - /** If this is 0 then quality_factor will be used instead. */ - qscale: number - quality_factor: number - }> - /** minimum bitrate */ - rc_min_rate: number - /** Ratecontrol attempt to use, at maximum, of what can be used without an underflow. */ - rc_max_available_vbv_use: number - /** Ratecontrol attempt to use, at least, times the amount needed to prevent a vbv overflow. */ - rc_min_vbv_overflow_use: number - /** Number of bits which should be loaded into the rc buffer before decoding starts. */ - rc_initial_buffer_occupancy: number - /** trellis RD quantization */ - trellis: number - /** pass1 encoding statistics output buffer */ - readonly stats_out: string | null - /** pass2 encoding statistics input buffer. Concatenated stuff from stats_out of pass1 should be placed here. */ - stats_in: string | null - readonly error: ReadonlyArray | null - /** DCT algorithm */ - dct_algo: 'auto' | 'fastint' | 'int' | 'mmx' | 'altivec' | 'faan' - /** nsse_weight */ - nsse_weight: number - /** - * Audio only. The number of "priming" samples (padding) inserted by the - * encoder at the beginning of the audio. I.e. this number of leading - * decoded samples must be discarded by the caller to get the original audio - * without leading padding. - * - * The timestamps on the output packets are adjusted by the encoder so that - * they always refer to the first sample of the data actually contained in the packet, - * including any added padding. E.g. if the timebase is 1/samplerate and - * the timestamp of the first input sample is 0, the timestamp of the - * first output packet will be -initial_padding. - */ - readonly initial_padding: number - /** Number of samples to skip after a discontinuity */ - readonly seek_preroll: number - /** custom intra quantization matrix */ - chroma_intra_matrix: Array - /** Additional data associated with the entire coded stream. */ - readonly coded_side_data: { - type: 'PacketSideData' - [key: string]: Buffer | string - } + // private field + readonly _CodecContext: {}; } diff --git a/ts/types/CodecPar.d.ts b/ts/types/CodecPar.d.ts index 947df1c..1389b75 100644 --- a/ts/types/CodecPar.d.ts +++ b/ts/types/CodecPar.d.ts @@ -1,34 +1,28 @@ +import { toJSONAble } from "./time" + /** * CodecPar describes the properties of an encoded stream. */ export interface CodecPar extends toJSONAble { /** Object name. */ - readonly type: 'CodecParameters' - + readonly type: 'CodecParameters' /** General type of the encoded data. */ - codec_type: string | 'data' | 'video' - + codec_type: string /** Specific type of the encoded data (the codec used). */ codec_id: number - /** The name corresponding to the codec_id. */ - name: 'node' | 'h264' | string - + name: string /** Additional information about the codec (corresponds to the AVI FOURCC). */ - codec_tag: number | string - + codec_tag: string /** Extra binary data needed for initializing the decoder, codec-dependent. */ - extradata: Buffer | null - + extradata: Buffer /** * - video: the pixel format. * - audio: the sample format. */ - format: string | null - + format: string /** The average bitrate of the encoded data (in bits per second). */ bit_rate: number - /** * The number of bits per sample in the codedwords. * @@ -41,7 +35,6 @@ export interface CodecPar extends toJSONAble { * Can be 0 */ bits_per_coded_sample: number - /** * This is the number of valid bits in each output sample. If the * sample format has more bits, the least significant bits are additional @@ -54,18 +47,13 @@ export interface CodecPar extends toJSONAble { * Can be 0 */ bits_per_raw_sample: number - /** Codec-specific bitstream restrictions that the stream conforms to. */ profile: string | number - level: number - /** Video only. The video frame width in pixels. */ width: number - /** Video only. The video frame height in pixels. */ - height: number - + height: number /** * Video only. The aspect ratio (width / height) which a single pixel * should have when displayed. @@ -73,42 +61,23 @@ export interface CodecPar extends toJSONAble { * When the aspect ratio is unknown / undefined, the numerator should be * set to 0 (the denominator may have any value). */ - sample_aspect_ratio: [number, number] - + sample_aspect_ratio: Array /** Video only. The order of the fields in interlaced video. */ - field_order: string | 'unknown' | 'progressive' - - /** Video only. Additional colorspace characteristics. */ - color_range: string | 'unknown' | 'pc' - - /** Video only. Additional colorspace characteristics. */ - color_primaries: string | 'unknown' | 'bt709' - + field_order: string /** Video only. Additional colorspace characteristics. */ - color_trc: string | 'unknown' | 'bt709' - - /** Video only. Additional colorspace characteristics. */ - color_space: string | 'unknown' | 'bt709' - - /** Video only. Additional colorspace characteristics. */ - chroma_location: string | 'unspecified' | 'left' - + color_range: string + color_primaries: string + color_trc: string + color_space: string + chroma_location: string /** Video only. Number of delayed frames. */ video_delay: number - - /** - * Audio only. A description of the channel layout. - * ex: "0 channels" - */ - channel_layout: string | '0 channels' - - + /** Audio only. A description of the channel layout. */ + channel_layout: string /** Audio only. The number of audio channels. */ channels: number - /** Audio only. The number of audio samples per second. */ sample_rate: number - /** * Audio only. The number of bytes per coded audio frame, required by some * formats. @@ -116,10 +85,8 @@ export interface CodecPar extends toJSONAble { * Corresponds to nBlockAlign in WAVEFORMATEX. */ block_align: number - /** Audio only. Audio frame size, if known. Required by some formats to be static. */ frame_size: number - /** * Audio only. The amount of padding (in samples) inserted by the encoder at * the beginning of the audio. I.e. this number of leading decoded samples @@ -127,13 +94,19 @@ export interface CodecPar extends toJSONAble { * padding. */ initial_padding: number - + /** + * Audio only. The amount of padding (in samples) appended by the encoder to + * the end of the audio. I.e. this number of decoded samples must be + * discarded by the caller from the end of the stream to get the original + * audio without any trailing padding. + */ trailing_padding: number - /** Audio only. Number of samples to skip after a discontinuity. */ seek_preroll: number - // native code; readonly _codecPar: {}; + } +export function codecParameters(options?: string | { [key: string]: any }): CodecPar; +// (options?: string | Partial>) \ No newline at end of file diff --git a/ts/types/Decoder.d.ts b/ts/types/Decoder.d.ts index 7856184..dd33bde 100644 --- a/ts/types/Decoder.d.ts +++ b/ts/types/Decoder.d.ts @@ -1,38 +1,64 @@ import { CodecPar } from "./CodecPar" -import { Packet, Timing } from "./Packet" +import { Packet } from "./Packet" import { Frame } from "./Frame" import { Codec } from "./Codec" -import { CodecContext, CodecContext_base } from "./CodecContext" +import { CodecContext } from "./CodecContext" import { Demuxer } from "./Demuxer" -import { DecodedFrames } from "./DecodedFrames" +import { Timable, TotalTimed } from "./time" -export interface Decoder extends CodecContext_base { - // readonly type: 'decoder' - // readonly time_base: [number, number] - // readonly sample_aspect_ratio: [number, number] - // readonly intra_matrix: Array | null - // readonly inter_matrix: Array | null - // readonly intra_dc_precision: number - // readonly refs: number - // readonly color_primaries?: string - // readonly color_trc: string - // readonly colorspace: string - // readonly color_range: string - // readonly chroma_sample_location: 'unspecified' | 'left' | 'center' | 'topleft' | 'top' | 'bottomleft' | 'bottom' - // readonly field_order: 'progressive' | - // 'top coded first, top displayed first' | - // 'bottom coded first, bottom displayed first' | - // 'top coded first, bottom displayed first' | - // 'bottom coded first, top displayed first' | - // 'unknown' - // readonly sample_fmt: string | null - // readonly audio_service_type: 'main' | 'effects' | 'visually-impaired' | 'hearing-impaired' | 'dialogue' | - // 'commentary' | 'emergency' | 'voice-over' | 'karaoke' | 'nb' - // readonly bits_per_raw_sample: number - // readonly profile: string | number - // readonly level: number - // readonly subtitle_header: Buffer | null - // readonly framerate: [number, number] +/** 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 + /** Total time in microseconds that the decode operation took to complete */ + // readonly total_time: number +} + +export interface Decoder extends Omit { + readonly type: 'decoder' + readonly time_base: Array + readonly sample_aspect_ratio: Array + readonly intra_matrix: Array | null + readonly inter_matrix: Array | null + readonly intra_dc_precision: number + readonly refs: number + readonly color_primaries?: string + readonly color_trc: string + readonly colorspace: string + readonly color_range: string + readonly chroma_sample_location: 'unspecified' | 'left' | 'center' | 'topleft' | 'top' | 'bottomleft' | 'bottom' + readonly field_order: 'progressive' | + 'top coded first, top displayed first' | + 'bottom coded first, bottom displayed first' | + 'top coded first, bottom displayed first' | + 'bottom coded first, top displayed first' | + 'unknown' + readonly sample_fmt: string | null + readonly audio_service_type: 'main' | 'effects' | 'visually-impaired' | 'hearing-impaired' | 'dialogue' | + 'commentary' | 'emergency' | 'voice-over' | 'karaoke' | 'nb' + readonly bits_per_raw_sample: number + readonly profile: string | number + readonly level: number + readonly subtitle_header: Buffer | null + readonly framerate: Array /** * Decode an encoded data packet or array of packets and create an uncompressed frame @@ -40,19 +66,19 @@ export interface Decoder extends CodecContext_base { * Decoders may need more than one packet to produce a frame and may subsequently * produce more than one frame per packet. This is particularly the case for long-GOP formats. * @param packet A packet or an array of packets to be decoded - * @returns a promise that resolves to a DecodedFrames object when the decode has completed successfully + * @returns a promise that resolves to a DecodedFrames object when the decode has completed successfully */ - decode(packet: Packet | Packet[]): Promise + decode(packet: Packet | Packet[]): Promise /** * Decode a number of packets passed as separate parameters and create uncompressed frames * (may be a frames-worth of audio). * Decoders may need more than one packet to produce a frame and may subsequently * produce more than one frame per packet. This is particularly the case for long-GOP formats. * @param packets An arbitrary number of packets to be decoded - * @returns a promise that resolves to a DecodedFrames object when the decode has completed successfully + * @returns a promise that resolves to a DecodedFrames object when the decode has completed successfully */ decode(...packets: Packet[]): Promise - /** + /** * Once all packets have been passed to the decoder, it is necessary to call its * asynchronous flush() method. If any frames are yet to be delivered by the decoder * they will be provided in the resolved value. @@ -60,7 +86,7 @@ export interface Decoder extends CodecContext_base { * Call the flush operation once and do not use the decoder for further decoding once it has * been flushed. The resources held by the decoder will be cleaned up as part of the Javascript * garbage collection process, so make sure that the reference to the decoder goes out of scope. - * @returns a promise that resolves to a DecodedFrames object when the flush has completed successfully + * @returns a promise that resolves to a DecodedFrames object when the flush has completed successfully */ flush(): Promise /** @@ -72,6 +98,43 @@ export interface Decoder extends CodecContext_base { * Initialise the decoder with parameters from a CodecPar object * @param param The CodecPar object that is to be used to override the current Decoder parameters * @returns the modified Decoder object - */ + */ useParams(params: CodecPar): Decoder + // private field + readonly _CodecContext: {} } + +/** + * Provides a list and details of all the available decoders + * @returns an object with name and details of each of the available decoders + */ +export function 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 + */ +export function 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 + */ +export function 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 + */ +export function 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 + */ +export function decoder(options: { params: CodecPar, [key: string]: any }): Decoder diff --git a/ts/types/Demuxer.d.ts b/ts/types/Demuxer.d.ts index 8ebb5f7..7eb3b17 100644 --- a/ts/types/Demuxer.d.ts +++ b/ts/types/Demuxer.d.ts @@ -1,7 +1,5 @@ import { Packet } from "./Packet" -import { InputFormat, FormatContextBase } from "./FormatContext" -import { FormatContextIn } from "./FormatContextIn" -import { FormatContextOut } from "./FormatContextOut" +import { InputFormat, FormatContext } from "./FormatContext" export interface SeekOptions { /** @@ -45,15 +43,16 @@ export interface SeekOptions { * The process of demuxing (de-multiplexing) extracts time-labelled packets of data * contained in a media stream or file. */ -export interface Demuxer extends FormatContextBase, FormatContextOut , FormatContextIn { - // { read: () => Promise, streams: Array<{time_base: [number, number]}> } +export interface Demuxer extends Omit { /** Object name. */ - // readonly type: 'demuxer' - // readonly iformat: InputFormat - // readonly url: string - // readonly duration: number + readonly type: 'demuxer' + readonly iformat: InputFormat + readonly url: string + readonly duration: number - interleaved: boolean, /** * Beam coder offers FFmpeg's many options for seeking a particular frame in a file, * either by time reference, frame count or file position. @@ -79,6 +78,25 @@ export interface Demuxer extends FormatContextBase, FormatContextOut , FormatCon forceClose(): undefined } +/** + * Provides a list and details of all the available demuxer input formats + * @returns an object with details of all the available demuxer input formats + */ +export function 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. + */ + // 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 { @@ -89,3 +107,12 @@ export interface DemuxerCreateOptions { /** Object allowing additional information to be provided */ options?: { [key: string]: any } } +/** + * 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. + */ +export function demuxer(options: DemuxerCreateOptions): Promise diff --git a/ts/types/Encoder.d.ts b/ts/types/Encoder.d.ts index ad94f36..cf6c912 100644 --- a/ts/types/Encoder.d.ts +++ b/ts/types/Encoder.d.ts @@ -1,8 +1,9 @@ import { CodecPar } from "./CodecPar" import { Packet } from "./Packet"; import { Frame } from "./Frame"; -import { CodecContextBaseMin } from "./CodecContext" -import { TotalTimed, Timable } from "./time"; +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 extends Timable, TotalTimed { @@ -10,20 +11,28 @@ export interface EncodedPackets extends Timable, TotalTimed { readonly type: 'packets' /** * Encoded packets that are now available. If the array is empty, the encoder has buffered - * the frame as part of the process of prodfcodec_tagucing future packets + * the frame as part of the process of producing future packets */ readonly packets: Array + /** Total time in microseconds that the encode operation took to complete */ + readonly total_time: number } /** * Encoder takes a stream of uncompressed data in the form of Frames and converts them into coded Packets. * Encoding takes place on a single type of stream, for example audio or video. */ -export interface Encoder extends CodecContextBaseMin { +export interface Encoder extends Omit { readonly type: 'encoder' - // readonly extradata: Buffer | null - // readonly slice_count: number - // readonly slice_offset: Array | null - // readonly bits_per_coded_sample: number + readonly extradata: Buffer | null + readonly slice_count: number + readonly slice_offset: Array | null + readonly bits_per_coded_sample: number /** * Encode a Frame or array of Frames and create a compressed Packet or Packets. @@ -56,7 +65,7 @@ export interface Encoder extends CodecContextBaseMin { * Extract the CodecPar object for the Encoder * @returns A CodecPar object */ - extractParams(): CodecPar + extractParams(): any /** * Initialise the encoder with parameters from a CodecPar object * @param param The CodecPar object that is to be used to override the current Encoder parameters @@ -65,3 +74,23 @@ export interface Encoder extends CodecContextBaseMin { useParams(params: CodecPar): Encoder } +/** + * Provides a list and details of all the available encoders + * @returns an object with name and details of each of the available encoders + */ +export function 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 + */ +// 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 } & Partial>): Encoder diff --git a/ts/types/Filter.d.ts b/ts/types/Filter.d.ts index 6f7403e..9544f2b 100644 --- a/ts/types/Filter.d.ts +++ b/ts/types/Filter.d.ts @@ -1,52 +1,10 @@ import { Frame } from "./Frame" import { PrivClass } from "./PrivClass" -import { Timable, TotalTimed } from "./time" - -export interface FilterFlags { - /** - * The number of the filter inputs is not determined just by AVFilter.inputs. - * The filter might add additional inputs during initialization depending on the - * options supplied to it. - */ - DYNAMIC_INPUTS: boolean - /** - * The number of the filter outputs is not determined just by AVFilter.outputs. - * The filter might add additional outputs during initialization depending on - * the options supplied to it. - */ - DYNAMIC_OUTPUTS: boolean - /** - * The filter supports multithreading by splitting frames into multiple parts and - * processing them concurrently. - */ - SLICE_THREADS: boolean - /** - * Some filters support a generic "enable" expression option that can be used - * to enable or disable a filter in the timeline. Filters supporting this - * option have this flag set. When the enable expression is false, the default - * no-op filter_frame() function is called in place of the filter_frame() - * callback defined on each input pad, thus the frame is passed unchanged to - * the next filters. - */ - SUPPORT_TIMELINE_GENERIC: boolean - /** - * Same as AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, except that the filter will - * have its filter_frame() callback(s) called as usual even when the enable - * expression is false. The filter will disable filtering within the - * filter_frame() callback(s) itself, for example executing code depending on - * the AVFilterContext->is_disabled value. - */ - SUPPORT_TIMELINE_INTERNAL: boolean - /** - * Handy mask to test whether the filter supports or no the timeline feature - * (internally or generically). - */ - SUPPORT_TIMELINE: boolean; -} +import { Timable } from "./time" export interface Filter { readonly type: 'Filter' - /** Filter name. Must be non-NULL and unique among filters. */ + /** Filter name. Must be non-NULL and unique among filters. */ readonly name: string /** A description of the filter. May be NULL. */ readonly description: string @@ -72,7 +30,47 @@ export interface Filter { */ readonly priv_class: PrivClass | null /** A combination of AVFILTER_FLAG_* */ - readonly flags: FilterFlags + readonly flags: { + /** + * The number of the filter inputs is not determined just by AVFilter.inputs. + * The filter might add additional inputs during initialization depending on the + * options supplied to it. + */ + DYNAMIC_INPUTS: boolean + /** + * The number of the filter outputs is not determined just by AVFilter.outputs. + * The filter might add additional outputs during initialization depending on + * the options supplied to it. + */ + DYNAMIC_OUTPUTS: boolean + /** + * The filter supports multithreading by splitting frames into multiple parts and + * processing them concurrently. + */ + SLICE_THREADS: boolean + /** + * Some filters support a generic "enable" expression option that can be used + * to enable or disable a filter in the timeline. Filters supporting this + * option have this flag set. When the enable expression is false, the default + * no-op filter_frame() function is called in place of the filter_frame() + * callback defined on each input pad, thus the frame is passed unchanged to + * the next filters. + */ + SUPPORT_TIMELINE_GENERIC: boolean + /** + * Same as AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, except that the filter will + * have its filter_frame() callback(s) called as usual even when the enable + * expression is false. The filter will disable filtering within the + * filter_frame() callback(s) itself, for example executing code depending on + * the AVFilterContext->is_disabled value. + */ + SUPPORT_TIMELINE_INTERNAL: boolean + /** + * Handy mask to test whether the filter supports or no the timeline feature + * (internally or generically). + */ + SUPPORT_TIMELINE: boolean + } } export type MediaType = 'unknown' | 'video' | 'audio' | 'data' | 'subtitle' | 'attachment' | 'nb' @@ -83,7 +81,7 @@ export interface FilterPad { } export interface FilterLink { - /** source filter name */ + /** source filter name */ readonly src: string /** output pad on the source filter */ readonly srcpad: string @@ -98,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 */ @@ -109,7 +107,7 @@ export interface FilterLink { readonly format: string /** * Define the time base used by the PTS of the frames/samples which will pass through this link. - * During the configuration stage, each filter is supposed to change only the output timebase, + * During the configuration stage, each filter is supposed to change only the output timebase, * while the timebase of the input link is assumed to be an unchangeable property. */ readonly time_base: ReadonlyArray @@ -117,71 +115,71 @@ export interface FilterLink { export interface FilterContext { readonly type: 'FilterContext' - /** the AVFilter of which this is an instance */ + /** the AVFilter of which this is an instance */ readonly filter: Filter - /** name of this filter instance */ + /** name of this filter instance */ readonly name: string - /** array of input pads */ + /** array of input pads */ readonly input_pads: ReadonlyArray - /** array of pointers to input links */ + /** array of pointers to input links */ readonly inputs: ReadonlyArray | null - /** array of output pads */ + /** array of output pads */ readonly output_pads: ReadonlyArray - /** array of pointers to output links */ + /** array of pointers to output links */ readonly outputs: ReadonlyArray | null - /** private data for use by the filter */ + /** private data for use by the filter */ priv: { [key: string]: any } | null - /** - * Type of multithreading being allowed/used. A combination of - * AVFILTER_THREAD_* flags. - * - * May be set by the caller before initializing the filter to forbid some - * or all kinds of multithreading for this filter. The default is allowing - * everything. - * - * When the filter is initialized, this field is combined using bit AND with - * AVFilterGraph.thread_type to get the final mask used for determining - * allowed threading types. I.e. a threading type needs to be set in both - * to be allowed. - * - * After the filter is initialized, libavfilter sets this field to the - * threading type that is actually used (0 for no multithreading). - */ + /** + * Type of multithreading being allowed/used. A combination of + * AVFILTER_THREAD_* flags. + * + * May be set by the caller before initializing the filter to forbid some + * or all kinds of multithreading for this filter. The default is allowing + * everything. + * + * When the filter is initialized, this field is combined using bit AND with + * AVFilterGraph.thread_type to get the final mask used for determining + * allowed threading types. I.e. a threading type needs to be set in both + * to be allowed. + * + * After the filter is initialized, libavfilter sets this field to the + * threading type that is actually used (0 for no multithreading). + */ readonly thread_type: number - /** - * Max number of threads allowed in this filter instance. - * If <= 0, its value is ignored. - * Overrides global number of threads set per filter graph. - */ + /** + * Max number of threads allowed in this filter instance. + * If <= 0, its value is ignored. + * Overrides global number of threads set per filter graph. + */ readonly nb_threads: number - /** - * Ready status of the filter. - * A non-0 value means that the filter needs activating, - * a higher value suggests a more urgent activation. - */ + /** + * Ready status of the filter. + * A non-0 value means that the filter needs activating, + * a higher value suggests a more urgent activation. + */ readonly ready: number - /** - * Sets the number of extra hardware frames which the filter will - * allocate on its output links for use in following filters or by - * the caller. - * - * Some hardware filters require all frames that they will use for - * output to be defined in advance before filtering starts. For such - * filters, any hardware frame pools used for output must therefore be - * of fixed size. The extra frames set here are on top of any number - * that the filter needs internally in order to operate normally. - * - * This field must be set before the graph containing this filter is - * configured. - */ - readonly extra_hw_frames: number + /** + * Sets the number of extra hardware frames which the filter will + * allocate on its output links for use in following filters or by + * the caller. + * + * Some hardware filters require all frames that they will use for + * output to be defined in advance before filtering starts. For such + * filters, any hardware frame pools used for output must therefore be + * of fixed size. The extra frames set here are on top of any number + * that the filter needs internally in order to operate normally. + * + * This field must be set before the graph containing this filter is + * configured. + */ + readonly extra_hw_frames: number } export interface FilterGraph { readonly type: 'FilterGraph' readonly filters: ReadonlyArray - /** sws options to use for the auto-inserted scale filters */ + /** sws options to use for the auto-inserted scale filters */ readonly scale_sws_opts: string | null /** * Type of multithreading allowed for filters in this graph. A combination of AVFILTER_THREAD_* flags. @@ -193,16 +191,16 @@ export interface FilterGraph { * I.e. a threading type needs to be set in both to be allowed. */ readonly thread_type: number - /** - * Maximum number of threads used by filters in this graph. May be set by - * the caller before adding any filters to the filtergraph. Zero (the - * default) means that the number of threads is determined automatically. - */ + /** + * Maximum number of threads used by filters in this graph. May be set by + * the caller before adding any filters to the filtergraph. Zero (the + * default) means that the number of threads is determined automatically. + */ readonly nb_threads: number - /** - * Dump a graph into a human-readable string representation. - * @returns: String representation of the filter graph - */ + /** + * Dump a graph into a human-readable string representation. + * @returns: String representation of the filter graph + */ dump(): string } @@ -217,29 +215,43 @@ export interface Filterer extends Timable { readonly type: 'Filterer' readonly graph: FilterGraph - /** - * Filter an array of frames - * For a filter that has only one input pass an array of frame objects directly - * and the filter input will have a default name applied. - * This name will match a filter specification that doesn't name its inputs. - * @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 & TotalTimed> - /** - * Filter an array of frames - * Pass an array of objects, one per filter input, each with a name string property - * and a frames property that contains an array of frame objects - * The name must match the input name in the filter specification - * @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 an array of frames + * For a filter that has only one input pass an array of frame objects directly + * and the filter input will have a default name applied. + * This name will match a filter specification that doesn't name its inputs. + * @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(framesArr: Array<{ name?: string, frames: Array }>): Timable & Promise & TotalTimed> - + 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 + * and a frames property that contains an array of frame objects + * The name must match the input name in the filter specification + * @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 }> + // may add a callback cb?: (pts: number | null) => void; + } +/** + * Provides a list and details of all the available filters + * @returns an object with name and details of each of the available filters + */ +export function filters(): { [key: string]: Filter } + +/** List the available bitstream filters */ +export function bsfs(): { + [key: string]: { + name: string + codec_ids: Array + priv_class: PrivClass | null } +} /** The required parameters for setting up filter inputs */ export interface InputParam { @@ -306,3 +318,9 @@ export interface FiltererAudioOptions extends FiltererOptions { outputParams: Array } +/** + * 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 + */ +export function filterer(options: FiltererVideoOptions | FiltererAudioOptions): Promise diff --git a/ts/types/FormatContext.d.ts b/ts/types/FormatContext.d.ts index a428bb1..2ced8a8 100644 --- a/ts/types/FormatContext.d.ts +++ b/ts/types/FormatContext.d.ts @@ -1,7 +1,5 @@ import { Stream } from "./Stream" import { PrivClass } from "./PrivClass" -import { FormatContextIn } from "./FormatContextIn" -import { FormatContextOut } from "./FormatContextOut" /** * Describes a supported input format @@ -13,23 +11,23 @@ export interface InputFormat { readonly name: string /** Descriptive name for the format, meant to be more human-readable */ readonly long_name: string - readonly flags: { + readonly flags: { NOFILE: boolean - /** Needs '%d' in filename. */ + /** Needs '%d' in filename. */ NEEDNUMBER: boolean - /** Show format stream IDs numbers. */ + /** Show format stream IDs numbers. */ SHOW_IDS: boolean - /** Use generic index building code. */ - GENERIC_INDEX: boolean + /** Use generic index building code. */ + GENERIC_INDEX: boolean /** Format allows timestamp discontinuities. Note, muxers always require valid (monotone) timestamps */ TS_DISCONT: boolean - /** Format does not allow to fall back on binary search via read_timestamp */ + /** Format does not allow to fall back on binary search via read_timestamp */ NOBINSEARCH: boolean - /** Format does not allow to fall back on generic search */ + /** Format does not allow to fall back on generic search */ NOGENSEARCH: boolean - /** Format does not allow seeking by bytes */ + /** Format does not allow seeking by bytes */ NO_BYTE_SEEK: boolean - /** Seeking is based on PTS */ + /** Seeking is based on PTS */ SEEK_TO_PTS: boolean } /** @@ -38,11 +36,11 @@ export interface InputFormat { * reliable enough */ readonly extensions: string - /** - * List of supported codec_id-codec_tag pairs, ordered by "better - * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. - */ - readonly codec_tag: ReadonlyArray<{ + /** + * List of supported codec_id-codec_tag pairs, ordered by "better + * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. + */ + readonly codec_tag: ReadonlyArray<{ id: number tag: string | number }> @@ -53,8 +51,8 @@ export interface InputFormat { * It is used check for matching mime types while probing. */ readonly mime_type: string - /** Raw demuxers store their codec ID here. */ - readonly raw_codec_id: string + /** Raw demuxers store their codec ID here. */ + readonly raw_codec_id: string /** Size of private data so that it can be allocated. */ readonly priv_data_size: number } @@ -74,19 +72,19 @@ export interface OutputFormat { * It is used check for matching mime types while probing. */ mime_type: string - /** comma-separated filename extensions */ + /** comma-separated filename extensions */ extensions: string - /** default audio codec */ + /** default audio codec */ audio_codec: string - /** default video codec */ + /** default video codec */ video_codec: string - /** default subtitle codec */ - subtitle_codec: string + /** default subtitle codec */ + subtitle_codec: string flags: { NOFILE?: boolean - /** Needs '%d' in filename. */ + /** Needs '%d' in filename. */ NEEDNUMBER?: boolean - /** Format wants global header. */ + /** Format wants global header. */ GLOBALHEADER?: boolean /** Format does not need / have any timestamps. */ NOTIMESTAMPS?: boolean @@ -100,66 +98,47 @@ export interface OutputFormat { ALLOW_FLUSH?: boolean /** Format does not require strictly increasing timestamps, but they must still be monotonic */ TS_NONSTRICT?: boolean - /** - * Format allows muxing negative timestamps. If not set the timestamp will be shifted in av_write_frame and - * av_interleaved_write_frame so they start from 0. - * The user or muxer can override this through AVFormatContext.avoid_negative_ts - */ + /** + * Format allows muxing negative timestamps. If not set the timestamp will be shifted in av_write_frame and + * av_interleaved_write_frame so they start from 0. + * The user or muxer can override this through AVFormatContext.avoid_negative_ts + */ TS_NEGATIVE?: boolean } - /** - * List of supported codec_id-codec_tag pairs, ordered by "better - * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. - */ - codec_tag: Array<{ + /** + * List of supported codec_id-codec_tag pairs, ordered by "better + * choice first". The arrays are all terminated by AV_CODEC_ID_NONE. + */ + codec_tag: Array<{ id: number tag: string | number }> /** Class for private context */ priv_class: PrivClass | null - /** size of private data so that it can be allocated */ - priv_data_size: number + /** size of private data so that it can be allocated */ + priv_data_size: number } -export interface FormatContextFlags { - /** Generate missing pts even if it requires parsing future frames. */ - GENPTS?: boolean - /** Ignore index. */ - IGNIDX?: boolean - /** Do not block when reading packets from input. */ - NONBLOCK?: boolean - /** Ignore DTS on frames that contain both DTS & PTS */ - IGNDTS?: boolean - /** Do not infer any values from other values, just return what is stored in the container */ - NOFILLIN?: boolean - /** Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fillin code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled */ - NOPARSE?: boolean - /** Do not buffer frames when possible */ - NOBUFFER?: boolean - /** The caller has supplied a custom AVIOContext, don't avio_close() it. */ - CUSTOM_IO?: boolean - /** Discard frames marked corrupted */ - DISCARD_CORRUPT?: boolean - /** Flush the AVIOContext every packet. */ - FLUSH_PACKETS?: boolean - /** This flag is mainly intended for testing. */ - BITEXACT?: boolean - /** try to interleave outputted packets by dts (using this flag can slow demuxing down) */ - SORT_DTS?: boolean - /** Enable use of private options by delaying codec open (this could be made default once all code is converted) */ - PRIV_OPT?: boolean - /** Enable fast, but inaccurate seeks for some formats */ - FAST_SEEK?: boolean - /** Stop muxing when the shortest stream stops. */ - SHORTEST?: boolean - /** Add bitstream filters as requested by the muxer */ - AUTO_BSF?: boolean -} -// extends FormatContextIn -export interface FormatContextBase { - /** Object name. */ - readonly type: 'demuxer' | 'format' | 'muxer' +/** + * 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. + */ +export function guessFormat(name: string): OutputFormat | null; +/** +* Format I/O context. + */ +export interface FormatContext { + /** Object name. */ + readonly type: string + /** The input format description. */ + set iformat(format: string); + //@ts-ignore + get iformat(): InputFormat + /** The output format description. */ + set oformat(format: string); + // @ts-ignore + get oformat(): OutputFormat /** Format private data. */ priv_data: { [key: string]: any @@ -173,6 +152,12 @@ export interface FormatContextBase { streams: Array /** input or output URL. Unlike the old filename field, this field has no length restriction. */ url: string + /** + * 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 /** * Duration of the stream, in AV_TIME_BASE fractional * seconds. Only set this value if you know none of the individual stream @@ -188,14 +173,63 @@ export interface FormatContextBase { bit_rate: number packet_size: number - max_delay: number /** Flags modifying the demuxer behaviour. A combination of AVFMT_FLAG_*. */ - flags: FormatContextFlags + flags: { + /** Generate missing pts even if it requires parsing future frames. */ + GENPTS?: boolean + /** Ignore index. */ + IGNIDX?: boolean + /** Do not block when reading packets from input. */ + NONBLOCK?: boolean + /** Ignore DTS on frames that contain both DTS & PTS */ + IGNDTS?: boolean + /** Do not infer any values from other values, just return what is stored in the container */ + NOFILLIN?: boolean + /** Do not use AVParsers, you also must set AVFMT_FLAG_NOFILLIN as the fillin code works on frames and no parsing -> no frames. Also seeking to frames can not work if parsing to find frame boundaries has been disabled */ + NOPARSE?: boolean + /** Do not buffer frames when possible */ + NOBUFFER?: boolean + /** The caller has supplied a custom AVIOContext, don't avio_close() it. */ + CUSTOM_IO?: boolean + /** Discard frames marked corrupted */ + DISCARD_CORRUPT?: boolean + /** Flush the AVIOContext every packet. */ + FLUSH_PACKETS?: boolean + /** This flag is mainly intended for testing. */ + BITEXACT?: boolean + /** try to interleave outputted packets by dts (using this flag can slow demuxing down) */ + SORT_DTS?: boolean + /** Enable use of private options by delaying codec open (this could be made default once all code is converted) */ + PRIV_OPT?: boolean + /** Enable fast, but inaccurate seeks for some formats */ + FAST_SEEK?: boolean + /** Stop muxing when the shortest stream stops. */ + SHORTEST?: boolean + /** Add bitstream filters as requested by the muxer */ + AUTO_BSF?: boolean + } + /** 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 readonly key: Buffer readonly programs: ReadonlyArray // video_codec_id / audio_codec_id / subtitle_codec_id - + /** + * 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 /** * Maximum amount of memory in bytes to use for buffering frames * obtained from realtime capture devices. @@ -214,9 +248,32 @@ export interface FormatContextBase { * some number of frames have been received. */ start_time_realtime: number | null + /** 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 /** Flags to enable debugging. */ debug: { TS: boolean } - + /** + * 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 /** Allow non-standard and experimental extension */ strict_std_compliance: 'very-strict' | 'strict' | 'normal' | 'unofficial' | 'experimental' /** @@ -225,6 +282,62 @@ export interface FormatContextBase { * A combination of AVFMT_EVENT_FLAG_*. */ event_flags: { METADATA_UPDATED?: boolean } + /** Maximum number of packets to read while waiting for the first timestamp. */ + max_ts_probe: 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 + /** + * 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 + /** Flush the I/O context after each packet. */ + flush_packets: number + /** + * 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 /** * IO repositioned flag. * This is set by avformat when the underlaying IO context read pointer @@ -232,29 +345,40 @@ export interface FormatContextBase { * Demuxers can use the flag to detect such changes. */ readonly io_repositioned: boolean + /** Number of bytes to be written as padding in a metadata header. */ + metadata_header_padding: number + // not exposing opaque + /** Output timestamp offset, in microseconds. */ + output_ts_offset: number /** * dump format separator. * can be ", " or "\n " or anything else */ dump_separator: string - /** + /** ',' 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 + + /** * Add a stream to the format with the next available stream index. * @param options Object including the codec name for the stream and any other parameters that need * to be initialised in the Stream object * @returns A Stream object */ - newStream(options: string | { 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 * @returns A Stream object */ newStream(stream: Stream): Stream + /** Retun a JSON string containing the object properties. */ + toJSON(): string } - -/** -* Format I/O context. - */ -export interface FormatContext extends FormatContextBase, FormatContextOut, FormatContextIn { -} +export function format(options?: string | { [key: string]: any }): FormatContext diff --git a/ts/types/Frame.d.ts b/ts/types/Frame.d.ts index 92ddeec..525f8a8 100644 --- a/ts/types/Frame.d.ts +++ b/ts/types/Frame.d.ts @@ -34,23 +34,23 @@ export interface Frame extends Timable, toJSONAble { width: number height: number /** number of audio samples (per channel) described by this frame */ - nb_samples: number + nb_samples: number /** format of the frame, null if unknown or unset */ format: string | null /** Whether this frame is a keyframe */ key_frame: boolean - /** Picture type of the frame. */ + /** Picture type of the frame. */ pict_type: 'I' | 'P' | 'B' | 'S' | 'SI' | 'SP' | 'BI' | null - /** Sample aspect ratio for the video frame, 0/1 if unknown/unspecified. */ - sample_aspect_ratio: [number, number] - /** Presentation timestamp in time_base units (time when frame should be shown to user). */ - pts: number | null + /** Sample aspect ratio for the video frame, 0/1 if unknown/unspecified. */ + sample_aspect_ratio: Array + /** Presentation timestamp in time_base units (time when frame should be shown to user). */ + pts: number /** * DTS copied from the Packet that triggered returning this frame. (if frame threading isn't used) * This is also the Presentation time of this Frame calculated from * only Packet.dts values without pts values. */ - pkt_dts: number | null + pkt_dts: number /** picture number in bitstream order */ coded_picture_number: number /** picture number in display order */ @@ -81,7 +81,7 @@ export interface Frame extends Timable, toJSONAble { /** Sample rate of the audio data. */ sample_rate: number /** Channel layout of the audio data. */ - channel_layout: string | '0 channels' + channel_layout: string /** * Raw data for the picture/channel planes. * @@ -102,13 +102,13 @@ export interface Frame extends Timable, toJSONAble { DISCARD?: boolean } /** MPEG vs JPEG YUV range. */ - color_range: string | "unknown" + color_range: string /** Chromaticity coordinates of the source primaries. */ - color_primaries: string | "unknown" + color_primaries?: string /** Color Transfer Characteristic. */ - color_trc: string | "unknown" + color_trc: string /** YUV colorspace type. */ - colorspace: string | "unknown" + colorspace: string /** * Location of chroma samples. * @@ -127,12 +127,12 @@ export interface Frame extends Timable, toJSONAble { */ chroma_location: 'unspecified' | 'left' | 'center' | 'topleft' | 'top' | 'bottomleft' | 'bottom' /** frame timestamp estimated using various heuristics, in stream time base */ - best_effort_timestamp: number | null + best_effort_timestamp: number /** reordered pos from the last AVPacket that has been input into the decoder */ pkt_pos: number /** duration of the corresponding packet, expressed in Stream->time_base units, 0 if unknown. */ pkt_duration: number - metadata: { [key: string]: string } | null + metadata: { [key: string]: string } /** * decode error flags of the frame, set if the decoder produced a frame, but there * were errors during the decoding. @@ -152,7 +152,7 @@ export interface Frame extends Timable, toJSONAble { * For hwaccel-format frames, this should be a reference to the * HWFramesContext describing the frame. */ - hw_frames_ctx: HWFramesContext | null + hw_frames_ctx: HWFramesContext /** * Video frames only. The number of pixels to discard from the the * top/bottom/left/right border of the frame to obtain the sub-rectangle of @@ -176,11 +176,14 @@ export interface Frame extends Timable, toJSONAble { * `let f = beamcoder.frame({ width: 1920, height: 1080, format: 'yuv422p' }).alloc()` */ alloc(): Frame - - // internal - readonly _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 } | string): Frame + /** Pixel format description */ export interface PixelFormat { name: string @@ -266,6 +269,8 @@ export interface PixelFormat { /** Alternative comma-separated names. */ alias: string } +/** Format details for all supported pixel format names */ +export function pix_fmts(): { [key: string]: PixelFormat } /** Audio sample formats */ export interface SampleFormat { @@ -280,4 +285,12 @@ export interface SampleFormat { /** Whether the sample format is planar. */ is_planar: boolean } +/** Format details for all supported sample format names */ +export function 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 + */ +export const AV_INPUT_BUFFER_PADDING_SIZE: number diff --git a/ts/types/Governor.d.ts b/ts/types/Governor.d.ts index f74493b..8d3d8bb 100644 --- a/ts/types/Governor.d.ts +++ b/ts/types/Governor.d.ts @@ -1,10 +1,12 @@ /** + * Create object for AVIOContext based buffered I/O + * * Type only definition for Governor class */ -export class Governor { + export class governor { constructor(options: { highWaterMark?: number }); read(len: number): Promise write(data: Buffer): Promise finish(): undefined private _adaptor: unknown; -} \ No newline at end of file +} diff --git a/ts/types/Muxer.d.ts b/ts/types/Muxer.d.ts index a4debed..87b8ce5 100644 --- a/ts/types/Muxer.d.ts +++ b/ts/types/Muxer.d.ts @@ -1,10 +1,15 @@ import { Packet } from "./Packet" import { Frame } from "./Frame" -import { OutputFormat } from "./FormatContext" -import { FormatContextBase } from "./FormatContext" -import { FormatContextOut } from "./FormatContextOut" +import { OutputFormat, FormatContext } from "./FormatContext" -export interface Muxer extends FormatContextBase, FormatContextOut { +export interface Muxer extends Omit { /** Object name. */ type: 'muxer' @@ -31,7 +36,7 @@ export interface Muxer extends FormatContextBase, FormatContextOut { NONBLOCK?: boolean DIRECT?: boolean } - }): Promise + }): Promise /** * In some cases, it is necessary to initialize the structures of the muxer before writing the header. @@ -39,9 +44,9 @@ export interface Muxer extends FormatContextBase, FormatContextOut { * @returns Promise that resolves to an object that indicates whether the stream parameters were * intialised in writeHeader or initOutput, together with an unset property if any properties could not be set */ - initOutput(options?: { [key: string]: any }): Promise<{ + initOutput(options?: { [key:string]: any }) : Promise<{ INIT_IN: 'WRITE_HEADER' | 'INIT_OUTPUT' - unset?: { [key: string]: any } + unset?: {[key: string]: any} }> /** * Write the header to the file, optionally passing in private data to set private options of the muxer. @@ -50,38 +55,38 @@ export interface Muxer extends FormatContextBase, FormatContextOut { * @returns Promise that resolves to an object that indicates whether the stream parameters were * intialised in writeHeader or initOutput, together with an unset property if any properties could not be set */ - writeHeader(options?: { [key: string]: any }): Promise<{ + writeHeader(options?: { [key:string]: any }) : Promise<{ INIT_IN: 'WRITE_HEADER' | 'INIT_OUTPUT' - unset?: { [key: string]: any } + unset?: {[key: string]: any} }> - /** - * Write media data to the file by sending a packet containing data for a media stream. - * @param packet Packet of compressed data, must contain the stream index and timestamps measured in the - * `time_base` of the stream. - * @returns Promise that resolves to _undefined_ on success - */ - writeFrame(packet: Packet): Promise - /** - * Write media data to the file by sending a packet containing data for a media stream. - * @param options Object containing a packet property of a compressed data Packet, must contain the - * stream index and timestamps measured in the `time_base` of the stream. - * @returns Promise that resolves to _undefined_ on success - */ - writeFrame(options: { packet: Packet }): Promise - /** - * Write media data to the file by sending a packet containing data for a media stream. - * @param options Object containing a stream index property and a frame property of an - * uncompressed Frame, which must contain the timestamps measured in the `time_base` of the stream. - * @returns Promise that resolves to _undefined_ on success - */ - writeFrame(options: { frame: Frame, stream_index: number }): Promise + /** + * Write media data to the file by sending a packet containing data for a media stream. + * @param packet Packet of compressed data, must contain the stream index and timestamps measured in the + * `time_base` of the stream. + * @returns Promise that resolves to _undefined_ on success + */ + writeFrame(packet: Packet) : Promise + /** + * Write media data to the file by sending a packet containing data for a media stream. + * @param options Object containing a packet property of a compressed data Packet, must contain the + * stream index and timestamps measured in the `time_base` of the stream. + * @returns Promise that resolves to _undefined_ on success + */ + writeFrame(options: { packet: Packet }) : Promise + /** + * Write media data to the file by sending a packet containing data for a media stream. + * @param options Object containing a stream index property and a frame property of an + * uncompressed Frame, which must contain the timestamps measured in the `time_base` of the stream. + * @returns Promise that resolves to _undefined_ on success + */ + writeFrame(options: { frame: Frame, stream_index: number }) : Promise - /** - * Write the trailer at the end of the file or stream. It is written after the muxer has drained its - * buffers of all remaining packets and frames. Writing the trailer also closes the file or stream. - * @returns Promise that resolves to _undefined_ on success - */ + /** + * Write the trailer at the end of the file or stream. It is written after the muxer has drained its + * buffers of all remaining packets and frames. Writing the trailer also closes the file or stream. + * @returns Promise that resolves to _undefined_ on success + */ writeTrailer(): Promise /** @@ -90,10 +95,16 @@ export interface Muxer extends FormatContextBase, FormatContextOut { forceClose(): undefined } +/** + * Provides a list and details of all the available muxer output formats + * @returns an object with details of all the available muxer output formats + */ +export function muxers(): { [key: string]: OutputFormat } + /** Object to provide additional metadata on Muxer creation */ export interface MuxerCreateOptions { /** The name of a chosen OutputFormat */ - name?: string + name?: string format_name?: string /** String describing the destinatione to be written to (may contain %d for a sequence of numbered files). */ filename?: string @@ -102,3 +113,9 @@ export interface MuxerCreateOptions { /** Object allowing additional information to be provided */ [key: string]: any } +/** + * Create a muxer to write to a URL or filename + * @param options a MuxerCreateOptions object + * @returns A Muxer object + */ +export function muxer(options: MuxerCreateOptions): Muxer diff --git a/ts/types/Packet.d.ts b/ts/types/Packet.d.ts index a8732cf..2d0e9da 100644 --- a/ts/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,40 +10,12 @@ * packets, with no compressed data, containing only side data * (e.g. to update some stream parameters at the end of encoding). */ - -import { Timable, toJSONAble } from "./time" - -export interface PacketFlags { - /** The packet contains a keyframe */ - KEY: boolean - /** The packet content is corrupted */ - CORRUPT: boolean - /** - * Flag is used to discard packets which are required to maintain valid - * decoder state but are not required for output and should be dropped - * after decoding. - **/ - DISCARD: boolean - /** - * The packet comes from a trusted source. - * - * Otherwise-unsafe constructs such as arbitrary pointers to data - * outside the packet may be followed. - */ - TRUSTED: boolean - /** - * Flag is used to indicate packets that contain frames that can - * be discarded by the decoder. I.e. Non-reference frames. - */ - DISPOSABLE: boolean // Frames that can be discarded by the decoder -} - 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. @@ -63,12 +37,35 @@ export interface Packet extends Timable, toJSONAble { * Packet data buffers are shared between C and Javascript so can be written to and modified without having to write the buffer back into the packet */ data: Buffer - /** The size in bytes of the raw data */ + /** The size in bytes of the raw data */ size: number /** The index in the format's stream array that this packet belongs to */ stream_index: number - /** A combination of AV_PKT_FLAG values */ - flags: PacketFlags; + /** A combination of AV_PKT_FLAG values */ + flags: { + /** The packet contains a keyframe */ + KEY: boolean + /** The packet content is corrupted */ + CORRUPT: boolean + /** + * Flag is used to discard packets which are required to maintain valid + * decoder state but are not required for output and should be dropped + * after decoding. + **/ + DISCARD: boolean + /** + * The packet comes from a trusted source. + * + * Otherwise-unsafe constructs such as arbitrary pointers to data + * outside the packet may be followed. + */ + TRUSTED: boolean + /** + * Flag is used to indicate packets that contain frames that can + * be discarded by the decoder. I.e. Non-reference frames. + */ + DISPOSABLE: boolean // Frames that can be discarded by the decoder + } /** * Additional packet data that can be provided by the container. * Packet can contain several types of side information. @@ -80,9 +77,11 @@ export interface Packet extends Timable, toJSONAble { */ duration: number /** byte position in stream, -1 if unknown */ - pos: number, - // timings: { - // read?: Timing; - // }; + pos: number } +/** + * 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?:string | { [key: string]: any, data: Buffer, size: number }): Packet diff --git a/ts/types/index.d.ts b/ts/types/index.d.ts new file mode 100644 index 0000000..3a0543d --- /dev/null +++ b/ts/types/index.d.ts @@ -0,0 +1,86 @@ +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 + +/** The LIBAV**_VERSION_INT for each FFmpeg library */ +export function 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. + */ +export function avVersionInfo(): string +/** Informative version strings for each FFmpeg library */ +export function versionStrings(): { + avcodec: string + avdevice: string + avfilter: string + avformat: string + avutil: string + postproc: string + swresample: string + swscale: string +} +/** Build configuration strings for each FFmpeg library */ +export function configurations(): { + avcodec: string + avdevice: string + avfilter: string + avformat: string + avutil: string + postproc: string + swresample: string + swscale: string +} +/** License strings for each FFmpeg library */ +export function licenses(): { + avcodec: string + avdevice: string + avfilter: string + avformat: string + avutil: string + postproc: string + swresample: string + swscale: string +} +/** List the available protocols */ +export function 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 + */ +export function logging(level?: string): string | undefined + +// export as namespace Beamcoder diff --git a/tsconfig.json b/tsconfig.json index 631f515..971c04d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -74,7 +74,7 @@ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */ - // "strict": true, /* Enable all strict type-checking options. */ + "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. */ From f97e47ff335fd75fd1d11d51585abdea9323b434 Mon Sep 17 00:00:00 2001 From: urielch Date: Thu, 5 May 2022 22:04:04 +0300 Subject: [PATCH 55/68] remove old file --- ts/types.ts | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 ts/types.ts diff --git a/ts/types.ts b/ts/types.ts deleted file mode 100644 index c6e36e3..0000000 --- a/ts/types.ts +++ /dev/null @@ -1,9 +0,0 @@ - -export type pixelFormat = 'yuv420p' | 'yuyv422' | 'rgb24' | 'bgr24' | 'yuv422p' | 'yuv444p' | 'yuv410p' | 'yuv411p' | 'gray' | 'monow' | 'monob' | 'pal8' | 'yuvj420p' | 'yuvj422p' | 'yuvj444p' | 'uyvy422' | 'uyyvyy411' | 'bgr8' | 'bgr4' | 'bgr4_byte' | 'rgb8' | 'rgb4' | 'rgb4_byte' | 'nv12' | 'nv21' | 'argb' | 'rgba' | 'abgr' | 'bgra' | 'gray16be' | 'gray16le' | 'yuv440p' | 'yuvj440p' | 'yuva420p' | 'rgb48be' | 'rgb48le' | 'rgb565be' | 'rgb565le' | 'rgb555be' | 'rgb555le' | 'bgr565be' | 'bgr565le' | 'bgr555be' | 'bgr555le' | 'vaapi_moco' | 'vaapi_idct' | 'vaapi_vld' | 'yuv420p16le' | 'yuv420p16be' | 'yuv422p16le' | 'yuv422p16be' | 'yuv444p16le' | 'yuv444p16be' | 'dxva2_vld' | 'rgb444le' | 'rgb444be' | 'bgr444le' | 'bgr444be' | 'ya8' | 'bgr48be' | 'bgr48le' | 'yuv420p9be' | 'yuv420p9le' | 'yuv420p10be' | 'yuv420p10le' | 'yuv422p10be' | 'yuv422p10le' | 'yuv444p9be' | 'yuv444p9le' | 'yuv444p10be' | 'yuv444p10le' | 'yuv422p9be' | 'yuv422p9le' | 'gbrp' | 'gbrp9be' | 'gbrp9le' | 'gbrp10be' | 'gbrp10le' | 'gbrp16be' | 'gbrp16le' | 'yuva422p' | 'yuva444p' | 'yuva420p9be' | 'yuva420p9le' | 'yuva422p9be' | 'yuva422p9le' | 'yuva444p9be' | 'yuva444p9le' | 'yuva420p10be' | 'yuva420p10le' | 'yuva422p10be' | 'yuva422p10le' | 'yuva444p10be' | 'yuva444p10le' | 'yuva420p16be' | 'yuva420p16le' | 'yuva422p16be' | 'yuva422p16le' | 'yuva444p16be' | 'yuva444p16le' | 'vdpau' | 'xyz12le' | 'xyz12be' | 'nv16' | 'nv20le' | 'nv20be' | 'rgba64be' | 'rgba64le' | 'bgra64be' | 'bgra64le' | 'yvyu422' | 'ya16be' | 'ya16le' | 'gbrap' | 'gbrap16be' | 'gbrap16le' | 'qsv' | 'mmal' | 'd3d11va_vld' | 'cuda' | '0rgb' | 'rgb0' | '0bgr' | 'bgr0' | 'yuv420p12be' | 'yuv420p12le' | 'yuv420p14be' | 'yuv420p14le' | 'yuv422p12be' | 'yuv422p12le' | 'yuv422p14be' | 'yuv422p14le' | 'yuv444p12be' | 'yuv444p12le' | 'yuv444p14be' | 'yuv444p14le' | 'gbrp12be' | 'gbrp12le' | 'gbrp14be' | 'gbrp14le' | 'yuvj411p' | 'bayer_bggr8' | 'bayer_rggb8' | 'bayer_gbrg8' | 'bayer_grbg8' | 'bayer_bggr16le' | 'bayer_bggr16be' | 'bayer_rggb16le' | 'bayer_rggb16be' | 'bayer_gbrg16le' | 'bayer_gbrg16be' | 'bayer_grbg16le' | 'bayer_grbg16be' | 'xvmc' | 'yuv440p10le' | 'yuv440p10be' | 'yuv440p12le' | 'yuv440p12be' | 'ayuv64le' | 'ayuv64be' | 'videotoolbox_vld' | 'p010le' | 'p010be' | 'gbrap12be' | 'gbrap12le' | 'gbrap10be' | 'gbrap10le' | 'mediacodec' | 'gray12be' | 'gray12le' | 'gray10be' | 'gray10le' | 'p016le' | 'p016be' | 'd3d11' | 'gray9be' | 'gray9le' | 'gbrpf32be' | 'gbrpf32le' | 'gbrapf32be' | 'gbrapf32le' | 'drm_prime' | 'opencl' | 'gray14be' | 'gray14le' | 'grayf32be' | 'grayf32le' | 'yuva422p12be' | 'yuva422p12le' | 'yuva444p12be' | 'yuva444p12le' | 'nv24' | 'nv42'; - -export interface ffStats { - mean: number; - stdDev: number; - max: number; - min: number; -} \ No newline at end of file From 546fd053d19fe9b093e721ab49f9f504895343ca Mon Sep 17 00:00:00 2001 From: urielch Date: Sat, 7 May 2022 20:47:56 +0300 Subject: [PATCH 56/68] commit lock --- pnpm-lock.yaml | 1003 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 979 insertions(+), 24 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1347755..8019a66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,15 +2,18 @@ lockfileVersion: 5.3 specifiers: '@types/bindings': ^1.5.1 - '@types/node': ^17.0.24 + '@types/node': ^17.0.31 '@types/tape': ^4.13.2 + '@types/webtorrent': ^0.109.3 bindings: ^1.5.0 - eslint: ^8.9.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.3 + typescript: ^4.6.4 + webtorrent: ^1.8.16 dependencies: bindings: 1.5.0 @@ -18,13 +21,16 @@ dependencies: devDependencies: '@types/bindings': 1.5.1 - '@types/node': 17.0.24 + '@types/node': 17.0.31 '@types/tape': 4.13.2 - eslint: 8.13.0 + '@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_17a82b5ac88a5de7094eac76b4edda13 - typescript: 4.6.3 + ts-node: 10.7.0_5f3e12794cebfbf3197131903b74d233 + typescript: 4.6.4 + webtorrent: 1.8.16 packages: @@ -40,8 +46,8 @@ packages: '@cspotcode/source-map-consumer': 0.8.0 dev: true - /@eslint/eslintrc/1.2.1: - resolution: {integrity: sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==} + /@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 @@ -91,17 +97,65 @@ packages: /@types/bindings/1.5.1: resolution: {integrity: sha512-8HzueDeoxGXdsJ0Ep7TOXHGN+woRTWa1bAds30r5we7PCC3P5zrSTRknePLn/KYAubgQv5t/1zkonnStHLCWOg==} dependencies: - '@types/node': 17.0.24 + '@types/node': 17.0.31 dev: true - /@types/node/17.0.24: - resolution: {integrity: sha512-aveCYRQbgTH9Pssp1voEP7HiuWlD2jW2BO56w+bVrJn04i61yh6mRfoKO6hEYQD9vF+W8Chkwc6j1M36uPkx4g==} + /@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.24 + '@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: @@ -123,6 +177,10 @@ packages: 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: @@ -167,16 +225,136 @@ packages: 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==} dev: true + /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 + + /binary-search/1.3.6: + resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==} + dev: true + /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 + + /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: @@ -184,6 +362,48 @@ packages: concat-map: 0.0.1 dev: true + /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/6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: true + + /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: @@ -204,6 +424,37 @@ packages: 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'} @@ -215,14 +466,44 @@ packages: 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=} dev: true + /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'} @@ -244,6 +525,13 @@ packages: 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: @@ -299,6 +587,16 @@ packages: minimatch: 3.1.2 dev: true + /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'} @@ -347,6 +645,10 @@ packages: 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'} @@ -360,13 +662,13 @@ packages: estraverse: 5.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.13.0: + /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.13.0 + eslint: 8.14.0 eslint-visitor-keys: 2.1.0 dev: true @@ -380,12 +682,12 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.13.0: - resolution: {integrity: sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==} + /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.1 + '@eslint/eslintrc': 1.2.2 '@humanwhocodes/config-array': 0.9.5 ajv: 6.12.6 chalk: 4.1.2 @@ -394,7 +696,7 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.13.0 + eslint-utils: 3.0.0_eslint@8.14.0 eslint-visitor-keys: 3.3.0 espree: 9.3.1 esquery: 1.4.0 @@ -457,10 +759,19 @@ packages: 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 @@ -480,6 +791,13 @@ packages: 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} @@ -502,6 +820,21 @@ packages: 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=} dev: true @@ -518,6 +851,10 @@ packages: 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: @@ -531,6 +868,11 @@ packages: 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'} @@ -605,11 +947,25 @@ packages: 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'} @@ -643,6 +999,21 @@ packages: 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'} @@ -651,6 +1022,10 @@ packages: 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: @@ -688,6 +1063,10 @@ packages: 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'} @@ -754,6 +1133,10 @@ packages: 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 @@ -794,6 +1177,38 @@ packages: 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'} @@ -802,14 +1217,82 @@ packages: type-check: 0.4.0 dev: true + /limiter/1.1.5: + resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==} + dev: true + + /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: @@ -820,18 +1303,64 @@ packages: resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} dev: true + /mkdirp-classic/0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + dev: true + + /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 @@ -877,6 +1406,12 @@ packages: 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'} @@ -884,6 +1419,19 @@ packages: 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'} @@ -898,16 +1446,91 @@ packages: 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 + /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/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'} @@ -922,6 +1545,18 @@ packages: 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'} @@ -947,6 +1582,30 @@ packages: 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.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 @@ -975,6 +1634,107 @@ packages: 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'} @@ -998,6 +1758,19 @@ packages: 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.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'} @@ -1048,11 +1821,45 @@ packages: 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 - /ts-node/10.7.0_17a82b5ac88a5de7094eac76b4edda13: + /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 + + /ts-node/10.7.0_5f3e12794cebfbf3197131903b74d233: resolution: {integrity: sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==} hasBin: true peerDependencies: @@ -1071,14 +1878,14 @@ packages: '@tsconfig/node12': 1.0.9 '@tsconfig/node14': 1.0.1 '@tsconfig/node16': 1.0.2 - '@types/node': 17.0.24 + '@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.3 + typescript: 4.6.4 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 dev: true @@ -1095,12 +1902,24 @@ packages: engines: {node: '>=10'} dev: true - /typescript/4.6.3: - resolution: {integrity: sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==} + /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: @@ -1110,12 +1929,67 @@ packages: 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 + /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=} + dev: true + + /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 @@ -1124,6 +1998,71 @@ packages: 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: @@ -1172,6 +2111,22 @@ packages: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} dev: true + /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'} From 26e35e5110157c26cd72a758d8358dc0d5baaf00 Mon Sep 17 00:00:00 2001 From: urielch Date: Sat, 7 May 2022 20:50:12 +0300 Subject: [PATCH 57/68] sync gitignore --- .gitignore | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 8e96137..9b8cabb 100644 --- a/.gitignore +++ b/.gitignore @@ -72,11 +72,10 @@ ffmpeg/ # Editors and IDE's *.swp -beamstreams.js -index.js -install_ffmpeg.js ts/*.js ts/*.d.ts -examples/capture -scratch/test.wav +examples/pnpm-lock.yaml +examples/capture/ +*.torrent +*.jpg scratch/wibble.h264 From 6358227e85a9e62139e392b81b31a99d73d32176 Mon Sep 17 00:00:00 2001 From: urielch Date: Sat, 7 May 2022 20:52:35 +0300 Subject: [PATCH 58/68] sync --- examples/jpeg_filter_app.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/jpeg_filter_app.ts b/examples/jpeg_filter_app.ts index bb9686d..3d66960 100644 --- a/examples/jpeg_filter_app.ts +++ b/examples/jpeg_filter_app.ts @@ -108,7 +108,6 @@ app.use(async (ctx) => { // Assume HTTP GET with path // ctx.body = { message: err.message }; - // console.log(err); } }); From a0e9cf9545f84a492cf76baa9e0c203174e47b2e Mon Sep 17 00:00:00 2001 From: urielch Date: Sat, 7 May 2022 20:52:44 +0300 Subject: [PATCH 59/68] sync --- .gitignore | 3 ++- examples/pnpm-lock.yaml | 56 +++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 9b8cabb..15dc38b 100644 --- a/.gitignore +++ b/.gitignore @@ -78,4 +78,5 @@ examples/pnpm-lock.yaml examples/capture/ *.torrent *.jpg -scratch/wibble.h264 +scratch/*.h264 +scratch/*.wav diff --git a/examples/pnpm-lock.yaml b/examples/pnpm-lock.yaml index 0097bcb..b7be1c2 100644 --- a/examples/pnpm-lock.yaml +++ b/examples/pnpm-lock.yaml @@ -5,33 +5,35 @@ specifiers: koa: ^2.13.4 dependencies: - '@types/koa': 2.13.4 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.25 - dev: false + '@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.25 - dev: false + '@types/node': 17.0.31 + dev: true /@types/connect/3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 17.0.25 - dev: false + '@types/node': 17.0.31 + dev: true /@types/content-disposition/0.5.4: resolution: {integrity: sha512-0mPF08jn9zYI0n0Q/Pnz7C4kThdSt+6LD4amsrYDDpgBfrVWa3TcCOxKX1zkGgYniGagRv8heN2cbh+CAn+uuQ==} - dev: false + dev: true /@types/cookies/0.7.7: resolution: {integrity: sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==} @@ -39,16 +41,16 @@ packages: '@types/connect': 3.4.35 '@types/express': 4.17.13 '@types/keygrip': 1.0.2 - '@types/node': 17.0.25 - dev: false + '@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.25 + '@types/node': 17.0.31 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 - dev: false + dev: true /@types/express/4.17.13: resolution: {integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==} @@ -57,25 +59,25 @@ packages: '@types/express-serve-static-core': 4.17.28 '@types/qs': 6.9.7 '@types/serve-static': 1.13.10 - dev: false + dev: true /@types/http-assert/1.5.3: resolution: {integrity: sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==} - dev: false + dev: true /@types/http-errors/1.8.2: resolution: {integrity: sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==} - dev: false + dev: true /@types/keygrip/1.0.2: resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==} - dev: false + dev: true /@types/koa-compose/3.2.5: resolution: {integrity: sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==} dependencies: '@types/koa': 2.13.4 - dev: false + dev: true /@types/koa/2.13.4: resolution: {integrity: sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==} @@ -87,31 +89,31 @@ packages: '@types/http-errors': 1.8.2 '@types/keygrip': 1.0.2 '@types/koa-compose': 3.2.5 - '@types/node': 17.0.25 - dev: false + '@types/node': 17.0.31 + dev: true /@types/mime/1.3.2: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} - dev: false + dev: true - /@types/node/17.0.25: - resolution: {integrity: sha512-wANk6fBrUwdpY4isjWrKTufkrXdu1D2YHCot2fD/DfWxF5sMrVSA+KN7ydckvaTCh0HiqX9IVl0L5/ZoXg5M7w==} - dev: false + /@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: false + dev: true /@types/range-parser/1.2.4: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} - dev: false + 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.25 - dev: false + '@types/node': 17.0.31 + dev: true /accepts/1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} From f27e6ba735856771456ac9168d92044698724ae4 Mon Sep 17 00:00:00 2001 From: urielch Date: Sat, 7 May 2022 20:53:44 +0300 Subject: [PATCH 60/68] sync --- scratch/muxer.ts | 2 +- scratch/stream_avci.ts | 1 + scratch/stream_mux.ts | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/scratch/muxer.ts b/scratch/muxer.ts index c73a845..284db90 100644 --- a/scratch/muxer.ts +++ b/scratch/muxer.ts @@ -45,7 +45,7 @@ function allocPicture(pixelFmt: string, width: number, height: number) { // esli } async function addStream(stream, muxer, codecID: number) { // eslint-disable-line - let codec = await beamcoder.encoder({ codec_id: codecID }); + let codec = beamcoder.encoder({ codec_id: codecID }); stream.st = muxer.newStream(); stream.enc = codec; diff --git a/scratch/stream_avci.ts b/scratch/stream_avci.ts index 24ee45c..760f877 100644 --- a/scratch/stream_avci.ts +++ b/scratch/stream_avci.ts @@ -120,6 +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_mux.ts b/scratch/stream_mux.ts index 7c2fcb2..77b18d0 100644 --- a/scratch/stream_mux.ts +++ b/scratch/stream_mux.ts @@ -24,8 +24,10 @@ import fs from 'fs'; async function run() { let demuxer = await beamcoder.demuxer('../../media/sound/BBCNewsCountdown.wav'); + let muxerStream = new MuxerStream({ highwaterMark: 65536 }); muxerStream.pipe(fs.createWriteStream('test.wav')); + let muxer = muxerStream.muxer({ format_name: 'wav' }); let stream = muxer.newStream(demuxer.streams[0]); // eslint-disable-line // stream.time_base = demuxer.streams[0].time_base; From a496a82f2c9c11741d3602661fd6412647a65f4f Mon Sep 17 00:00:00 2001 From: urielch Date: Sat, 7 May 2022 20:58:48 +0300 Subject: [PATCH 61/68] sync --- test/codecParamsSpec.ts | 4 +- test/decoderSpec.ts | 5 +- test/demuxerSpec.ts | 5 +- test/encoderSpec.ts | 7 ++- test/filtererSpec.ts | 4 +- test/formatSpec.ts | 17 +++--- test/frameSpec.ts | 8 +-- test/mp4FullTest.ts | 120 ++++++++++++++++++++++++++++++++++++++++ test/muxerSpec.ts | 2 +- test/packetSpec.ts | 1 + 10 files changed, 147 insertions(+), 26 deletions(-) create mode 100644 test/mp4FullTest.ts diff --git a/test/codecParamsSpec.ts b/test/codecParamsSpec.ts index cf9ff63..78fbf30 100644 --- a/test/codecParamsSpec.ts +++ b/test/codecParamsSpec.ts @@ -20,7 +20,7 @@ */ import test from 'tape'; -import beamcoder, { CodecPar } from '..'; +import beamcoder from '..'; test('Creating codec parameters', t => { let cps = beamcoder.codecParameters(); @@ -80,7 +80,7 @@ test('Minimal JSON serialization', t => { }); test('Maximal JSON serialization', t=> { - let cp: CodecPar = beamcoder.codecParameters({ + let cp = beamcoder.codecParameters({ codec_type: 'video', codec_id: 27, codec_tag: 'avc1', diff --git a/test/decoderSpec.ts b/test/decoderSpec.ts index cbc9920..faff7d3 100644 --- a/test/decoderSpec.ts +++ b/test/decoderSpec.ts @@ -20,10 +20,10 @@ */ import test from 'tape'; -import beamcoder, { Decoder } from '..'; +import beamcoder from '..'; test('Creating a decoder', t => { - let dec: Decoder = beamcoder.decoder({ name: 'h264' }); + let dec = beamcoder.decoder({ name: 'h264' }); t.ok(dec, 'is truthy.'); t.equal(dec.name, 'h264', 'has the expected name.'); t.equal(dec.codec_id, 27, 'has the expected codec_id.'); @@ -48,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.ts b/test/demuxerSpec.ts index fe84b98..5675e26 100644 --- a/test/demuxerSpec.ts +++ b/test/demuxerSpec.ts @@ -20,12 +20,13 @@ */ import test from 'tape'; -import beamcoder, { Demuxer } from '..'; +import beamcoder from '..'; test('Creating a demuxer', async t => { - let dm: Demuxer = await beamcoder.demuxer('https://www.elecard.com/storage/video/bbb_1080p_c.ts'); + 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.ts b/test/encoderSpec.ts index dc1478f..64f6f22 100644 --- a/test/encoderSpec.ts +++ b/test/encoderSpec.ts @@ -20,10 +20,10 @@ */ import test from 'tape'; -import beamcoder, { Encoder } from '..'; +import beamcoder from '..'; test('Creating a video encoder', t => { - let enc: Encoder = beamcoder.encoder({ name: 'h264' }); + let enc = beamcoder.encoder({ name: 'h264' }); t.ok(enc, 'is truthy.'); t.equal(enc.name, 'libx264', 'has the expected name.'); t.equal(enc.codec_id, 27, 'has the expected codec_id.'); @@ -43,13 +43,14 @@ test('Creating an audio encoder', t => { }); test('Checking the A properties:', t => { - let enc: Encoder = beamcoder.encoder({ name: 'h264' }); + let enc = beamcoder.encoder({ name: 'h264' }); 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 diff --git a/test/filtererSpec.ts b/test/filtererSpec.ts index 258c231..080e127 100644 --- a/test/filtererSpec.ts +++ b/test/filtererSpec.ts @@ -20,10 +20,10 @@ */ import test from 'tape'; -import beamcoder, { Filterer } from '..'; +import beamcoder from '..'; test('Create a filterer', async t => { - let flt: Filterer = await beamcoder.filterer({ + let flt = await beamcoder.filterer({ filterType: 'audio', inputParams: [ { diff --git a/test/formatSpec.ts b/test/formatSpec.ts index 3a3a73a..4246a26 100644 --- a/test/formatSpec.ts +++ b/test/formatSpec.ts @@ -20,14 +20,13 @@ */ import test from 'tape'; -import beamcoder, { FormatContext } from '..'; +import beamcoder from '..'; const isExternal = (o: any) => (Object as any).toString(o).indexOf('native code') >= 0; -//const isExternal = o => Object.toString.apply(o).indexOf('native code') >= 0; -// Object.toString.apply(Object) +// const isExternal = o => Object.toString(o).indexOf('native code') >= 0; test('Creating a format', t => { - let fmt: FormatContext = beamcoder.format(); + let fmt = beamcoder.format(); t.ok(fmt, 'is truthy.'); t.equal(fmt.type, 'format', 'calls itself type format.'); t.equal(fmt.iformat, null, 'has no input format.'); @@ -41,13 +40,11 @@ test('Creating a format', t => { t.end(); }); -const stripNewStream = (ctxt: FormatContext) => { - const { newStream, ...others } = ctxt; - return { ...others } -}; +// @ts-ignore +const stripNewStream = ({ newStream, ...others }) => ({ ...others }); test('Minimal JSON serialization', t => { - let fmt: FormatContext = beamcoder.format(); + let fmt = beamcoder.format(); let fmts = JSON.stringify(fmt); t.equal(typeof fmts, 'string', 'stringify creates a string.'); let fmtj = JSON.parse(fmts); @@ -130,7 +127,7 @@ test('Minimal JSON serialization', t => { }); test('Maximal JSON serialization', t => { - let fmt: FormatContext = beamcoder.format({ + let fmt = beamcoder.format({ type: 'format', oformat: null, iformat: null, diff --git a/test/frameSpec.ts b/test/frameSpec.ts index b8b0331..46aa0dc 100644 --- a/test/frameSpec.ts +++ b/test/frameSpec.ts @@ -20,17 +20,17 @@ */ import test from 'tape'; -import beamcoder, { Frame } from '..'; +import beamcoder from '..'; import util from 'util'; test('Create a frame', t => { - let fr: Frame = beamcoder.frame(); + let fr = beamcoder.frame(); t.ok(fr, 'is truthy.'); t.end(); }); test('Minimal JSON serialization', t => { - let fr: Frame = beamcoder.frame({}); + let fr = beamcoder.frame({}); let fp = JSON.stringify(fr); t.ok(fp, 'JSON serialization is truthy.'); let pfp = JSON.parse(fp); @@ -42,7 +42,7 @@ test('Minimal JSON serialization', t => { }); test('Maximal JSON serialization', t => { - let fr: Frame = beamcoder.frame({ type: 'Frame', + let fr = beamcoder.frame({ type: 'Frame', linesize: [42], width: 43, height: 44, 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.ts b/test/muxerSpec.ts index d7b3da0..5a54c10 100644 --- a/test/muxerSpec.ts +++ b/test/muxerSpec.ts @@ -25,7 +25,7 @@ import beamcoder from '..'; test('Creating a muxer', t => { let mx = beamcoder.muxer({ name: 'mpegts' }); t.ok(mx, 'is truthy.'); - // @ts-expect-error:next-line + // @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.ts b/test/packetSpec.ts index 0ccf6d4..06c2287 100644 --- a/test/packetSpec.ts +++ b/test/packetSpec.ts @@ -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, From 7525f566e77c9661dca24f752196810194b34091 Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 16 May 2022 15:35:00 +0300 Subject: [PATCH 62/68] fix livecycle --- package.json | 5 +++-- ts/index.ts | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 034fea1..b3225cb 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "beamcoder", - "version": "0.7.1", + "version": "0.7.2", "description": "Node.js native bindings to FFmpeg.", "main": "ts/index.js", "types": "ts/index.d.ts", "scripts": { - "postinstall": "tsc -p . && node ts/install_ffmpeg.js && node-gyp rebuild", + "prepack": "tsc -p .", + "postinstall": "node ts/install_ffmpeg.js && node-gyp rebuild", "build": "tsc -p .", "clean": "rimraf ts/*.js ts/*.d.ts temp.mp4", "test": "ts-node node_modules/tape/bin/tape test/*.ts", diff --git a/ts/index.ts b/ts/index.ts index 2f3a2a4..9051d6f 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -1,6 +1,7 @@ /* 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 From cc539857c1b64d601d857d6476f707a4efda013d Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 16 May 2022 15:42:27 +0300 Subject: [PATCH 63/68] merge v2 => v1 final --- package-lock.json | 393 ++++++++++++++++++++++++++++++++++++++++++---- package.json | 18 ++- tsconfig.json | 203 ++++++++++++------------ 3 files changed, 478 insertions(+), 136 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1f8e9ed..a7ca2e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,14 +14,42 @@ "segfault-handler": "^1.3.0" }, "devDependencies": { - "eslint": "^8.9.0", - "tape": "^5.5.2" + "@types/bindings": "^1.5.1", + "@types/node": "^17.0.31", + "@types/tape": "^4.13.2", + "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" + } + }, + "node_modules/@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, + "engines": { + "node": ">= 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 b3225cb..4f7f232 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "beamcoder", + "name": "@u4/beamcoder", "version": "0.7.2", - "description": "Node.js native bindings to FFmpeg.", + "description": "Node.js native bindings to FFmpeg in Typescript", "main": "ts/index.js", "types": "ts/index.d.ts", "scripts": { @@ -15,6 +15,10 @@ "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" @@ -33,9 +37,9 @@ "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" @@ -53,5 +57,9 @@ "typescript": "^4.6.4", "webtorrent": "^1.8.16" }, - "gypfile": true + "gypfile": true, + "files": [ + "src", + "ts" + ] } diff --git a/tsconfig.json b/tsconfig.json index 971c04d..46a1b4a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,102 +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"], -} + "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 From 71882a374f0993de11409fdd032904d21aae16c3 Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 16 May 2022 15:43:52 +0300 Subject: [PATCH 64/68] marge .vscode files --- .vscode/c_cpp_properties.json | 28 +++++++++++++++++++++------- .vscode/launch.json | 22 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 2ae81b7..cacd253 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -3,13 +3,13 @@ { "name": "Win32", "includePath": [ - // "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*", - "C:/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/um", - "C:/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/ucrt", - "C:/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/shared", - "C:/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/winrt", + "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/include/*", + "C:/Program Files (x86)/Windows Kits/8.1/Include/um", + "C:/Program Files (x86)/Windows Kits/10/Include/10.0.10240.0/ucrt", + "C:/Program Files (x86)/Windows Kits/8.1/Include/shared", + "C:/Program Files (x86)/Windows Kits/8.1/Include/winrt", "${workspaceFolder}/ffmpeg/ffmpeg-5.x-win64-shared/include/**", - "${env:USERPROFILE}/AppData/Local/node-gyp/Cache/16.13.1/include/node" + "${env:USERPROFILE}/AppData/Local/node-gyp/Cache/16.14.0/include/node" ], "defines": [ "_DEBUG", @@ -17,10 +17,24 @@ "_UNICODE" ], "windowsSdkVersion": "8.1", - "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio/2019/BuildTools/VC/Tools/MSVC/14.29.30133/bin/Hostx86/x64/cl.exe", + "compilerPath": "C:/Program Files (x86)/Microsoft Visual Studio 14.0/VC/bin/cl.exe", "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 index a034f67..b9eaa7b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,6 +3,28 @@ "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", From 588202233374644e0af26ae492c02d8017655673 Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 16 May 2022 15:57:11 +0300 Subject: [PATCH 65/68] limit download log volume. --- ts/utils.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ts/utils.ts b/ts/utils.ts index e59574e..2de60c7 100644 --- a/ts/utils.ts +++ b/ts/utils.ts @@ -12,6 +12,7 @@ export async function getRaw(ws: NodeJS.WritableStream, url: string, name?: stri 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 }); @@ -27,7 +28,11 @@ export async function getRaw(ws: NodeJS.WritableStream, url: string, name?: stri res.on('error', err); res.on('data', x => { received += x.length; - process.stdout.write(`Downloaded ${received * 100/ totalLength | 0 }% of '${name}'.\r`); + const msg = `Downloaded ${received * 100 / totalLength | 0 }% of '${name}'.\r`; + if (msg !== prevMsg) { + prevMsg = msg; + process.stdout.write(msg); + } }); } }).on('error', err); @@ -39,6 +44,7 @@ export async function getHTML(url: string, name: string): Promise { 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); @@ -51,7 +57,11 @@ export async function getHTML(url: string, name: string): Promise { res.on('data', (chunk) => { chunks.push(chunk); received += chunk.length; - process.stdout.write(`Downloaded ${received * 100/ totalLength | 0 }% of '${name}'.\r`); + const msg = `Downloaded ${received * 100 / totalLength | 0 }% of '${name}'.\r`; + if (msg !== prevMsg) { + prevMsg = msg; + process.stdout.write(msg); + } }); }).on('error', reject); }); From 7f13d600bf554e509c72e71cb9e3ef1e696d2fcc Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 16 May 2022 15:59:46 +0300 Subject: [PATCH 66/68] add missing binding.gyp in package.zip --- package.json | 5 +++-- ts/utils.ts | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 4f7f232..d147ffb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@u4/beamcoder", - "version": "0.7.2", + "version": "0.7.3", "description": "Node.js native bindings to FFmpeg in Typescript", "main": "ts/index.js", "types": "ts/index.d.ts", @@ -60,6 +60,7 @@ "gypfile": true, "files": [ "src", - "ts" + "ts", + "binding.gyp" ] } diff --git a/ts/utils.ts b/ts/utils.ts index 2de60c7..fb1d996 100644 --- a/ts/utils.ts +++ b/ts/utils.ts @@ -22,7 +22,7 @@ export async function getRaw(ws: NodeJS.WritableStream, url: string, name?: stri totalLength = +(res.headers['content-length'] as string); } res.on('end', () => { - process.stdout.write(`Downloaded 100% of '${name}'. Total length ${received} bytes.\n`); + process.stdout.write(`Downloaded Done of '${name}'. Total length ${received} bytes.\n`); comp(); }); res.on('error', err); @@ -50,7 +50,7 @@ export async function getHTML(url: string, name: string): Promise { totalLength = +(res.headers['content-length'] as string); } res.on('end', () => { - process.stdout.write(`Downloaded 100% of '${name}'. Total length ${received} bytes.\n`); + process.stdout.write(`Downloaded Done of '${name}'. Total length ${received} bytes.\n`); resolve(Buffer.concat(chunks)); }); res.on('error', reject); From 7dcdd86eead6b2a862211662aa09ca97ae357de0 Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 16 May 2022 17:36:16 +0300 Subject: [PATCH 67/68] improve install_ffmpeg --- package.json | 9 ++- pnpm-lock.yaml | 150 +++++++++++++++++++++++++++++++++---- ts/install_ffmpeg.ts | 171 ++++++++++++++++++------------------------- 3 files changed, 215 insertions(+), 115 deletions(-) diff --git a/package.json b/package.json index d147ffb..c5f52d1 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "@u4/beamcoder", - "version": "0.7.3", + "version": "0.7.4", "description": "Node.js native bindings to FFmpeg in Typescript", "main": "ts/index.js", "types": "ts/index.d.ts", "scripts": { "prepack": "tsc -p .", - "postinstall": "node ts/install_ffmpeg.js && node-gyp rebuild", + "preinstall": "node ts/install_ffmpeg.js", + "install": "node-gyp rebuild", "build": "tsc -p .", "clean": "rimraf ts/*.js ts/*.d.ts temp.mp4", "test": "ts-node node_modules/tape/bin/tape test/*.ts", @@ -42,12 +43,14 @@ "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": { "@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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8019a66..9394453 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,7 @@ 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 @@ -13,16 +14,19 @@ specifiers: 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 @@ -142,6 +146,12 @@ packages: '@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: @@ -231,7 +241,6 @@ packages: /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true /base64-js/1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -245,10 +254,22 @@ packages: 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: @@ -351,6 +372,10 @@ packages: 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 @@ -360,7 +385,6 @@ packages: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: true /browserify-package-json/1.0.1: resolution: {integrity: sha1-mN3oqlxWH9bT/km7qhArdLOW/eo=} @@ -381,6 +405,11 @@ packages: 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: @@ -388,6 +417,11 @@ packages: 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'} @@ -416,6 +450,12 @@ packages: 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'} @@ -474,7 +514,10 @@ packages: /concat-map/0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} - dev: true + + /core-util-is/1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + dev: false /cpus/1.0.3: resolution: {integrity: sha512-PXHBvGLuL69u55IkLa5e5838fLhIMHxmkV4ge42a8alGyn7BtawYgI0hQ849EedvtHIOLNNH3i6eQU1BiE9SUA==} @@ -587,6 +630,12 @@ packages: 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: @@ -837,7 +886,16 @@ packages: /fs.realpath/1.0.0: resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} - dev: true + + /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==} @@ -897,7 +955,6 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true /globals/13.13.0: resolution: {integrity: sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==} @@ -906,6 +963,10 @@ packages: 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 @@ -984,11 +1045,9 @@ packages: dependencies: once: 1.4.0 wrappy: 1.0.2 - dev: true /inherits/2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true /internal-slot/1.0.3: resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} @@ -1154,6 +1213,10 @@ packages: 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 @@ -1221,6 +1284,10 @@ packages: 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: @@ -1297,16 +1364,21 @@ packages: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 - dev: true /minimist/1.2.6: resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} - dev: true /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: @@ -1392,7 +1464,6 @@ packages: resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} dependencies: wrappy: 1.0.2 - dev: true /optionator/0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} @@ -1435,7 +1506,6 @@ packages: /path-is-absolute/1.0.1: resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} engines: {node: '>=0.10.0'} - dev: true /path-key/3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} @@ -1455,6 +1525,10 @@ packages: 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: @@ -1516,6 +1590,18 @@ packages: 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'} @@ -1575,6 +1661,13 @@ packages: 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 @@ -1602,6 +1695,10 @@ packages: 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 @@ -1614,6 +1711,10 @@ packages: 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'} @@ -1765,6 +1866,12 @@ packages: 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: @@ -1859,6 +1966,10 @@ packages: 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 @@ -1938,6 +2049,21 @@ packages: 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: @@ -1974,7 +2100,6 @@ packages: /util-deprecate/1.0.2: resolution: {integrity: sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=} - dev: true /utp-native/2.5.3: resolution: {integrity: sha512-sWTrWYXPhhWJh+cS2baPzhaZc89zwlWCfwSthUjGhLkZztyPhcQllo+XVVCbNGi7dhyRlxkWxN4NKU6FbA9Y8w==} @@ -2109,7 +2234,6 @@ packages: /wrappy/1.0.2: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} - dev: true /ws/7.5.7_d6955b83f926115bf12ffeabab6deaae: resolution: {integrity: sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==} diff --git a/ts/install_ffmpeg.ts b/ts/install_ffmpeg.ts index 262161b..00d4517 100644 --- a/ts/install_ffmpeg.ts +++ b/ts/install_ffmpeg.ts @@ -22,23 +22,24 @@ 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); +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 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}`, `./${folder}/${name}`) - console.log(`Unzipping of '${folder}/${name}.zip' completed.`); + await rename(`./${folder}/${directoryName}`, dest) + console.log(`Unzipping of '${folder}/${name}.zip' to ${dest} completed.`); comp(); })); rs.on('error', err); @@ -47,79 +48,54 @@ async function inflate(rs: NodeJS.ReadableStream, folder: string, name: string): async function win32(): Promise { console.log('Checking/Installing FFmpeg dependencies for Beam Coder on Windows.'); - - await mkdir('ffmpeg').catch(e => { - if (e.code === 'EEXIST') return; - else throw e; - }); - + try { + await mkdir('ffmpeg'); + } catch (e) { + if (e.code !== 'EEXIST') { + throw e; + } + }; const ffmpegFilename = 'ffmpeg-5.x-win64-shared'; - await access(`ffmpeg/${ffmpegFilename}`, fs.constants.R_OK).catch(async () => { + 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 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 = '

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; - - if (stdout.indexOf('libavcodec.so.59') < 0) { - console.error('libavcodec.so.59 is not installed.'); - result = 1; - } - if (stdout.indexOf('libavformat.so.59') < 0) { - console.error('libavformat.so.59 is not installed.'); - result = 1; - } - if (stdout.indexOf('libavdevice.so.59') < 0) { - console.error('libavdevice.so.59 is not installed.'); - result = 1; - } - if (stdout.indexOf('libavfilter.so.8') < 0) { - console.error('libavfilter.so.8 is not installed.'); - result = 1; - } - if (stdout.indexOf('libavutil.so.57') < 0) { - console.error('libavutil.so.57 is not installed.'); - result = 1; - } - if (stdout.indexOf('libpostproc.so.56') < 0) { - console.error('libpostproc.so.56 is not installed.'); - result = 1; - } - if (stdout.indexOf('libswresample.so.4') < 0) { - console.error('libswresample.so.4 is not installed.'); - result = 1; - } - if (stdout.indexOf('libswscale.so.6') < 0) { - console.error('libswscale.so.6 is not installed.'); - result = 1; + 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 @@ -133,7 +109,7 @@ 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.'; @@ -143,7 +119,6 @@ async function darwin(): Promise<0> { 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'); @@ -154,39 +129,37 @@ async function darwin(): Promise<0> { 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; + 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; } From 1b9b6618a83509bded6a1874924966faac1ada7e Mon Sep 17 00:00:00 2001 From: urielch Date: Mon, 16 May 2022 19:29:42 +0300 Subject: [PATCH 68/68] improve stream test --- examples/streamTest.ts | 55 ++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/examples/streamTest.ts b/examples/streamTest.ts index 77aad34..b052122 100644 --- a/examples/streamTest.ts +++ b/examples/streamTest.ts @@ -1,4 +1,4 @@ -import beamcoder, { demuxerStream } from '..'; // Use require('beamcoder') externally +import beamcoder, { DemuxerStream } from '..'; // Use require('beamcoder') externally import path from 'path'; import fs from 'fs'; import { Demuxer, getRaw } from '..'; @@ -36,33 +36,40 @@ async function getFiles(): Promise { async function run() { const filelist = await getFiles(); - const stream = new demuxerStream({ highwaterMark: 3600 }); + const stream = new DemuxerStream({ highwaterMark: 3600 }); const demuxPromise = stream.demuxer({}) demuxPromise.then(async (demuxer: Demuxer) => { - const 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 - const jpgDest = 'capture.jpg'; - fs.writeFileSync(jpgDest, jpegResult.packets[0].data); - const sumDest = await md5File(jpgDest); - const expectedMd5Mac = '63a5031f882ad85a964441f61333240c'; - const expectedMd5PC = 'e16f49626e71b4be46a3211ed1d4e471'; - if (expectedMd5Mac !== sumDest && expectedMd5PC !== sumDest) { - console.error(`MD5 missmatch get ${sumDest}`) + 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}`); } - console.log(`saving in stream img as ${jpgDest}`); demuxer.forceClose(); }); // https://github.com/awslabs/amazon-kinesis-video-streams-producer-c/raw/master/samples/h264SampleFrames/frame-001.h264