const { Readable } = require("stream"); const binding = require("./js-binding"); class OpusEncoder { constructor(sampleRate, channels) { return binding.OpusEncoder.create(sampleRate, channels); } static create(sampleRate, channels) { return binding.OpusEncoder.create(sampleRate, channels); } static [Symbol.hasInstance](target) { return target instanceof binding.OpusEncoder; } } /** * @typedef StreamProbeResult * @property {Readable} stream The source stream * @property {binding.ProbeResult} result The probe result */ /** * Attempt to probe a Readable stream * @param {Readable} stream The readable stream to probe * @param {Object} options The options * @param {number} [options.probeSize=2 * 1024 * 1024] The amount of bytes to read from the stream. Defaults to 2 MB. * @param {boolean} [options.sync=false] Whether to probe synchronously. Defaults to false. * @returns {Promise} */ async function probeStream( stream, options = { probeSize: 2 * 1024 * 1024, sync: false, }, ) { const { probeSize = 2 * 1024 * 1024, sync = false } = options; return new Promise((resolve, reject) => { if (stream.readableObjectMode) { reject(new Error("Cannot probe a readable stream in object mode")); return; } if (stream.readableEnded) { reject(new Error("Cannot probe a stream that has ended")); return; } let readBuffer = Buffer.alloc(0); /** * @type {binding.ProbeResult} */ let resolved = null; const finish = (data) => { stream.off("data", onData); stream.off("close", onClose); stream.off("end", onClose); stream.pause(); resolved = data; if (stream.readableEnded) { resolve({ stream: Readable.from(readBuffer), result: data, }); } else { if (readBuffer.length > 0) { stream.push(readBuffer); } resolve({ stream, result: data, }); } }; const onClose = async () => { try { const probeInner = sync ? probeSync : probe; const result = await probeInner(readBuffer); if (result != null) resolved = result; } catch {} finish(resolved || null); }; const onData = (buffer) => { readBuffer = Buffer.concat([readBuffer, buffer]); if (readBuffer.length >= probeSize) { stream.off("data", onData); stream.pause(); process.nextTick(onClose); } }; stream.once("error", reject); stream.on("data", onData); stream.once("close", onClose); stream.once("end", onClose); }); } /** * Reads metadata tags * @param {import('./js-binding').ProbeResult} result The probe result to read metadata from */ function readMetadata(result) { const res = { title: null, author: null, album: null, genre: null, year: null, duration: null, composer: null, bpm: null, }; if (!result.metadata?.length) return res; result.metadata.forEach((m) => { if (!m.value) return; if (m.name === "TIT2") { res.title = m.value; } else if (m.name === "TPE1" || m.name === "TPUB") { res.author = m.value; } else if (m.name === "TALB") { res.album = m.value; } else if (m.name === "TCON") { res.genre = m.value; } else if (m.name === "TYER") { res.year = m.value; } else if (m.name === "TLEN") { res.duration = parseInt(m.value); } else if (m.name === "TCOM") { res.composer = m.value; } else if (m.name === "TBPM") { res.bpm = parseInt(m.value); } }); return res; } const { CodecType, probe, probeSync, getOpusVersion } = binding; const { version } = require("./package.json"); module.exports = { CodecType, probe, probeSync, probeStream, readMetadata, OpusEncoder, getOpusVersion, version, };