UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

754 lines (753 loc) 21.6 kB
import { o as __toESM } from "./chunk-CNf5ZN-e.js"; import { t as require_src } from "./src-6PhJB9jV.js"; import { n as EndOfStreamError } from "./lib-X3TAZBh2.js"; import { E as Uint8ArrayType, T as UINT8, _ as UINT16_BE, g as StringType, l as INT16_BE, o as makeUnexpectedFileContentError, x as UINT32_BE } from "./BasicParser-CSYy5xVJ.js"; import { c as stripNulls, o as getBitAllignedNumber, s as isBitSet } from "./Util--vOO_7DL.js"; import { t as AbstractID3Parser } from "./AbstractID3Parser-B0YuPNia.js"; //#region node_modules/music-metadata/lib/mpeg/ReplayGainDataFormat.js var import_src = /* @__PURE__ */ __toESM(require_src(), 1); /** * Replay Gain Data Format * * https://github.com/Borewit/music-metadata/wiki/Replay-Gain-Data-Format */ const ReplayGain = { len: 2, get: (buf, off) => { const gain_type = getBitAllignedNumber(buf, off, 0, 3); const sign = getBitAllignedNumber(buf, off, 6, 1); const gain_adj = getBitAllignedNumber(buf, off, 7, 9) / 10; if (gain_type > 0) return { type: getBitAllignedNumber(buf, off, 0, 3), origin: getBitAllignedNumber(buf, off, 3, 3), adjustment: sign ? -gain_adj : gain_adj }; } }; //#endregion //#region node_modules/music-metadata/lib/mpeg/ExtendedLameHeader.js /** * Extended Lame Header */ /** * Info Tag * @link http://gabriel.mp3-tech.org/mp3infotag.html * @link https://github.com/quodlibet/mutagen/blob/abd58ee58772224334a18817c3fb31103572f70e/mutagen/mp3/_util.py#L112 */ const ExtendedLameHeader = { len: 27, get: (buf, off) => { const track_peak = UINT32_BE.get(buf, off + 2); return { revision: getBitAllignedNumber(buf, off, 0, 4), vbr_method: getBitAllignedNumber(buf, off, 4, 4), lowpass_filter: 100 * UINT8.get(buf, off + 1), track_peak: track_peak === 0 ? null : track_peak / 2 ** 23, track_gain: ReplayGain.get(buf, 6), album_gain: ReplayGain.get(buf, 8), music_length: UINT32_BE.get(buf, off + 20), music_crc: UINT8.get(buf, off + 24), header_crc: UINT16_BE.get(buf, off + 24) }; } }; //#endregion //#region node_modules/music-metadata/lib/mpeg/XingTag.js /** * Info Tag: Xing, LAME */ const InfoTagHeaderTag = new StringType(4, "ascii"); /** * LAME TAG value * Did not find any official documentation for this * Value e.g.: "3.98.4" */ const LameEncoderVersion = new StringType(6, "ascii"); /** * Info Tag * Ref: http://gabriel.mp3-tech.org/mp3infotag.html */ const XingHeaderFlags = { len: 4, get: (buf, off) => { return { frames: isBitSet(buf, off, 31), bytes: isBitSet(buf, off, 30), toc: isBitSet(buf, off, 29), vbrScale: isBitSet(buf, off, 28) }; } }; async function readXingHeader(tokenizer) { const flags = await tokenizer.readToken(XingHeaderFlags); const xingInfoTag = { numFrames: null, streamSize: null, vbrScale: null }; if (flags.frames) xingInfoTag.numFrames = await tokenizer.readToken(UINT32_BE); if (flags.bytes) xingInfoTag.streamSize = await tokenizer.readToken(UINT32_BE); if (flags.toc) { xingInfoTag.toc = new Uint8Array(100); await tokenizer.readBuffer(xingInfoTag.toc); } if (flags.vbrScale) xingInfoTag.vbrScale = await tokenizer.readToken(UINT32_BE); if (await tokenizer.peekToken(new StringType(4, "ascii")) === "LAME") { await tokenizer.ignore(4); xingInfoTag.lame = { version: await tokenizer.readToken(new StringType(5, "ascii")) }; const match = xingInfoTag.lame.version.match(/\d+.\d+/g); if (match !== null) { const version = match[0].split(".").map((n) => Number.parseInt(n, 10)); if (version[0] >= 3 && version[1] >= 90) xingInfoTag.lame.extended = await tokenizer.readToken(ExtendedLameHeader); } } return xingInfoTag; } //#endregion //#region node_modules/music-metadata/lib/mpeg/MpegParser.js const debug = (0, import_src.default)("music-metadata:parser:mpeg"); var MpegContentError = class extends makeUnexpectedFileContentError("MPEG") {}; /** * Cache buffer size used for searching synchronization preabmle */ const maxPeekLen = 1024; /** * MPEG-4 Audio definitions * Ref: https://wiki.multimedia.cx/index.php/MPEG-4_Audio */ const MPEG4 = { /** * Audio Object Types */ AudioObjectTypes: [ "AAC Main", "AAC LC", "AAC SSR", "AAC LTP" ], /** * Sampling Frequencies * https://wiki.multimedia.cx/index.php/MPEG-4_Audio#Sampling_Frequencies */ SamplingFrequencies: [ 96e3, 88200, 64e3, 48e3, 44100, 32e3, 24e3, 22050, 16e3, 12e3, 11025, 8e3, 7350, null, null, -1 ] }; const MPEG4_ChannelConfigurations = [ void 0, ["front-center"], ["front-left", "front-right"], [ "front-center", "front-left", "front-right" ], [ "front-center", "front-left", "front-right", "back-center" ], [ "front-center", "front-left", "front-right", "back-left", "back-right" ], [ "front-center", "front-left", "front-right", "back-left", "back-right", "LFE-channel" ], [ "front-center", "front-left", "front-right", "side-left", "side-right", "back-left", "back-right", "LFE-channel" ] ]; /** * MPEG Audio Layer I/II/III frame header * Ref: https://www.mp3-tech.org/programmer/frame_header.html * Bit layout: AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM * Ref: https://wiki.multimedia.cx/index.php/ADTS */ var MpegFrameHeader = class MpegFrameHeader { constructor(buf, off) { this.bitrateIndex = null; this.sampRateFreqIndex = null; this.padding = null; this.privateBit = null; this.channelModeIndex = null; this.modeExtension = null; this.isOriginalMedia = null; this.version = null; this.bitrate = null; this.samplingRate = null; this.frameLength = 0; this.versionIndex = getBitAllignedNumber(buf, off + 1, 3, 2); this.layer = MpegFrameHeader.LayerDescription[getBitAllignedNumber(buf, off + 1, 5, 2)]; if (this.versionIndex > 1 && this.layer === 0) this.parseAdtsHeader(buf, off); else this.parseMpegHeader(buf, off); this.isProtectedByCRC = !isBitSet(buf, off + 1, 7); } calcDuration(numFrames) { return this.samplingRate == null ? null : numFrames * this.calcSamplesPerFrame() / this.samplingRate; } calcSamplesPerFrame() { return MpegFrameHeader.samplesInFrameTable[this.version === 1 ? 0 : 1][this.layer]; } calculateSideInfoLength() { if (this.layer !== 3) return 2; if (this.channelModeIndex === 3) { if (this.version === 1) return 17; if (this.version === 2 || this.version === 2.5) return 9; } else { if (this.version === 1) return 32; if (this.version === 2 || this.version === 2.5) return 17; } return null; } calcSlotSize() { return [ null, 4, 1, 1 ][this.layer]; } parseMpegHeader(buf, off) { this.container = "MPEG"; this.bitrateIndex = getBitAllignedNumber(buf, off + 2, 0, 4); this.sampRateFreqIndex = getBitAllignedNumber(buf, off + 2, 4, 2); this.padding = isBitSet(buf, off + 2, 6); this.privateBit = isBitSet(buf, off + 2, 7); this.channelModeIndex = getBitAllignedNumber(buf, off + 3, 0, 2); this.modeExtension = getBitAllignedNumber(buf, off + 3, 2, 2); this.isCopyrighted = isBitSet(buf, off + 3, 4); this.isOriginalMedia = isBitSet(buf, off + 3, 5); this.emphasis = getBitAllignedNumber(buf, off + 3, 7, 2); this.version = MpegFrameHeader.VersionID[this.versionIndex]; this.channelMode = MpegFrameHeader.ChannelMode[this.channelModeIndex]; this.codec = `MPEG ${this.version} Layer ${this.layer}`; const bitrateInKbps = this.calcBitrate(); if (!bitrateInKbps) throw new MpegContentError("Cannot determine bit-rate"); this.bitrate = bitrateInKbps * 1e3; this.samplingRate = this.calcSamplingRate(); if (this.samplingRate == null) throw new MpegContentError("Cannot determine sampling-rate"); } parseAdtsHeader(buf, off) { debug("layer=0 => ADTS"); this.version = this.versionIndex === 2 ? 4 : 2; this.container = `ADTS/MPEG-${this.version}`; const profileIndex = getBitAllignedNumber(buf, off + 2, 0, 2); this.codec = "AAC"; this.codecProfile = MPEG4.AudioObjectTypes[profileIndex]; debug(`MPEG-4 audio-codec=${this.codec}`); const samplingFrequencyIndex = getBitAllignedNumber(buf, off + 2, 2, 4); this.samplingRate = MPEG4.SamplingFrequencies[samplingFrequencyIndex]; debug(`sampling-rate=${this.samplingRate}`); const channelIndex = getBitAllignedNumber(buf, off + 2, 7, 3); this.mp4ChannelConfig = MPEG4_ChannelConfigurations[channelIndex]; debug(`channel-config=${this.mp4ChannelConfig ? this.mp4ChannelConfig.join("+") : "?"}`); this.frameLength = getBitAllignedNumber(buf, off + 3, 6, 2) << 11; } calcBitrate() { if (this.bitrateIndex === 0 || this.bitrateIndex === 15) return null; if (this.version && this.bitrateIndex) { const codecIndex = 10 * Math.floor(this.version) + this.layer; return MpegFrameHeader.bitrate_index[this.bitrateIndex][codecIndex]; } return null; } calcSamplingRate() { if (this.sampRateFreqIndex === 3 || this.version === null || this.sampRateFreqIndex == null) return null; return MpegFrameHeader.sampling_rate_freq_index[this.version][this.sampRateFreqIndex]; } }; MpegFrameHeader.SyncByte1 = 255; MpegFrameHeader.SyncByte2 = 224; MpegFrameHeader.VersionID = [ 2.5, null, 2, 1 ]; MpegFrameHeader.LayerDescription = [ 0, 3, 2, 1 ]; MpegFrameHeader.ChannelMode = [ "stereo", "joint_stereo", "dual_channel", "mono" ]; MpegFrameHeader.bitrate_index = { 1: { 11: 32, 12: 32, 13: 32, 21: 32, 22: 8, 23: 8 }, 2: { 11: 64, 12: 48, 13: 40, 21: 48, 22: 16, 23: 16 }, 3: { 11: 96, 12: 56, 13: 48, 21: 56, 22: 24, 23: 24 }, 4: { 11: 128, 12: 64, 13: 56, 21: 64, 22: 32, 23: 32 }, 5: { 11: 160, 12: 80, 13: 64, 21: 80, 22: 40, 23: 40 }, 6: { 11: 192, 12: 96, 13: 80, 21: 96, 22: 48, 23: 48 }, 7: { 11: 224, 12: 112, 13: 96, 21: 112, 22: 56, 23: 56 }, 8: { 11: 256, 12: 128, 13: 112, 21: 128, 22: 64, 23: 64 }, 9: { 11: 288, 12: 160, 13: 128, 21: 144, 22: 80, 23: 80 }, 10: { 11: 320, 12: 192, 13: 160, 21: 160, 22: 96, 23: 96 }, 11: { 11: 352, 12: 224, 13: 192, 21: 176, 22: 112, 23: 112 }, 12: { 11: 384, 12: 256, 13: 224, 21: 192, 22: 128, 23: 128 }, 13: { 11: 416, 12: 320, 13: 256, 21: 224, 22: 144, 23: 144 }, 14: { 11: 448, 12: 384, 13: 320, 21: 256, 22: 160, 23: 160 } }; MpegFrameHeader.sampling_rate_freq_index = { 1: { 0: 44100, 1: 48e3, 2: 32e3 }, 2: { 0: 22050, 1: 24e3, 2: 16e3 }, 2.5: { 0: 11025, 1: 12e3, 2: 8e3 } }; MpegFrameHeader.samplesInFrameTable = [[ 0, 384, 1152, 1152 ], [ 0, 384, 1152, 576 ]]; /** * MPEG Audio Layer I/II/III */ const FrameHeader = { len: 4, get: (buf, off) => { return new MpegFrameHeader(buf, off); } }; function getVbrCodecProfile(vbrScale) { return `V${Math.floor((100 - vbrScale) / 10)}`; } var MpegParser = class extends AbstractID3Parser { constructor() { super(...arguments); this.frameCount = 0; this.syncFrameCount = -1; this.totalDataLength = 0; this.bitrates = []; this.offset = 0; this.frame_size = 0; this.calculateEofDuration = false; this.samplesPerFrame = null; this.buf_frame_header = new Uint8Array(4); /** * Number of bytes already parsed since beginning of stream / file */ this.mpegOffset = null; this.syncPeek = { buf: new Uint8Array(maxPeekLen), len: 0 }; } /** * Called after ID3 headers have been parsed */ async postId3v2Parse() { this.metadata.setFormat("lossless", false); this.metadata.setAudioOnly(); try { let quit = false; while (!quit) { await this.sync(); quit = await this.parseCommonMpegHeader(); } } catch (err) { if (err instanceof EndOfStreamError) { debug("End-of-stream"); if (this.calculateEofDuration) { if (this.samplesPerFrame !== null) { const numberOfSamples = this.frameCount * this.samplesPerFrame; this.metadata.setFormat("numberOfSamples", numberOfSamples); if (this.metadata.format.sampleRate) { const duration = numberOfSamples / this.metadata.format.sampleRate; debug(`Calculate duration at EOF: ${duration} sec.`, duration); this.metadata.setFormat("duration", duration); } } } } else throw err; } } /** * Called after file has been fully parsed, this allows, if present, to exclude the ID3v1.1 header length */ finalize() { const format = this.metadata.format; const hasID3v1 = !!this.metadata.native.ID3v1; if (this.mpegOffset !== null) { if (format.duration && this.tokenizer.fileInfo.size) { const mpegSize = this.tokenizer.fileInfo.size - this.mpegOffset - (hasID3v1 ? 128 : 0); if (format.codecProfile && format.codecProfile[0] === "V") this.metadata.setFormat("bitrate", mpegSize * 8 / format.duration); } if (this.tokenizer.fileInfo.size && format.codecProfile === "CBR") { const mpegSize = this.tokenizer.fileInfo.size - this.mpegOffset - (hasID3v1 ? 128 : 0); if (this.frame_size !== null && this.samplesPerFrame !== null) { const numberOfSamples = Math.round(mpegSize / this.frame_size) * this.samplesPerFrame; this.metadata.setFormat("numberOfSamples", numberOfSamples); if (format.sampleRate && !format.duration) { const duration = numberOfSamples / format.sampleRate; debug("Calculate CBR duration based on file size: %s", duration); this.metadata.setFormat("duration", duration); } } } } } async sync() { let gotFirstSync = false; while (true) { let bo = 0; this.syncPeek.len = await this.tokenizer.peekBuffer(this.syncPeek.buf, { length: maxPeekLen, mayBeLess: true }); if (this.syncPeek.len <= 163) throw new EndOfStreamError(); while (true) { if (gotFirstSync && (this.syncPeek.buf[bo] & 224) === 224) { this.buf_frame_header[0] = MpegFrameHeader.SyncByte1; this.buf_frame_header[1] = this.syncPeek.buf[bo]; await this.tokenizer.ignore(bo); debug(`Sync at offset=${this.tokenizer.position - 1}, frameCount=${this.frameCount}`); if (this.syncFrameCount === this.frameCount) { debug(`Re-synced MPEG stream, frameCount=${this.frameCount}`); this.frameCount = 0; this.frame_size = 0; } this.syncFrameCount = this.frameCount; return; } gotFirstSync = false; bo = this.syncPeek.buf.indexOf(MpegFrameHeader.SyncByte1, bo); if (bo === -1) { if (this.syncPeek.len < this.syncPeek.buf.length) throw new EndOfStreamError(); await this.tokenizer.ignore(this.syncPeek.len); break; } ++bo; gotFirstSync = true; } } } /** * Combined ADTS & MPEG (MP2 & MP3) header handling * @return {Promise<boolean>} true if parser should quit */ async parseCommonMpegHeader() { if (this.frameCount === 0) this.mpegOffset = this.tokenizer.position - 1; await this.tokenizer.peekBuffer(this.buf_frame_header.subarray(1), { length: 3 }); let header; try { header = FrameHeader.get(this.buf_frame_header, 0); } catch (err) { await this.tokenizer.ignore(1); if (err instanceof Error) { this.metadata.addWarning(`Parse error: ${err.message}`); return false; } throw err; } await this.tokenizer.ignore(3); this.metadata.setFormat("container", header.container); this.metadata.setFormat("codec", header.codec); this.metadata.setFormat("lossless", false); this.metadata.setFormat("sampleRate", header.samplingRate); this.frameCount++; return header.version !== null && header.version >= 2 && header.layer === 0 ? this.parseAdts(header) : this.parseAudioFrameHeader(header); } /** * @return {Promise<boolean>} true if parser should quit */ async parseAudioFrameHeader(header) { this.metadata.setFormat("numberOfChannels", header.channelMode === "mono" ? 1 : 2); this.metadata.setFormat("bitrate", header.bitrate); if (this.frameCount < 20 * 1e4) debug("offset=%s MP%s bitrate=%s sample-rate=%s", this.tokenizer.position - 4, header.layer, header.bitrate, header.samplingRate); const slot_size = header.calcSlotSize(); if (slot_size === null) throw new MpegContentError("invalid slot_size"); const samples_per_frame = header.calcSamplesPerFrame(); debug(`samples_per_frame=${samples_per_frame}`); const bps = samples_per_frame / 8; if (header.bitrate !== null && header.samplingRate != null) { const fsize = bps * header.bitrate / header.samplingRate + (header.padding ? slot_size : 0); this.frame_size = Math.floor(fsize); } this.audioFrameHeader = header; if (header.bitrate !== null) this.bitrates.push(header.bitrate); if (this.frameCount === 1) { this.offset = FrameHeader.len; await this.skipSideInformation(); return false; } if (this.frameCount === 4) { if (this.areAllSame(this.bitrates)) { this.samplesPerFrame = samples_per_frame; this.metadata.setFormat("codecProfile", "CBR"); if (this.tokenizer.fileInfo.size) return true; } else if (this.metadata.format.duration) return true; if (!this.options.duration) return true; } if (this.options.duration && this.frameCount === 4) { this.samplesPerFrame = samples_per_frame; this.calculateEofDuration = true; } this.offset = 4; if (header.isProtectedByCRC) { await this.parseCrc(); return false; } await this.skipSideInformation(); return false; } async parseAdts(header) { const buf = new Uint8Array(3); await this.tokenizer.readBuffer(buf); header.frameLength += getBitAllignedNumber(buf, 0, 0, 11); this.totalDataLength += header.frameLength; this.samplesPerFrame = 1024; if (header.samplingRate !== null) { const framesPerSec = header.samplingRate / this.samplesPerFrame; const bitrate = 8 * (this.frameCount === 0 ? 0 : this.totalDataLength / this.frameCount) * framesPerSec + .5; this.metadata.setFormat("bitrate", bitrate); debug(`frame-count=${this.frameCount}, size=${header.frameLength} bytes, bit-rate=${bitrate}`); } await this.tokenizer.ignore(header.frameLength > 7 ? header.frameLength - 7 : 1); if (this.frameCount === 3) { this.metadata.setFormat("codecProfile", header.codecProfile); if (header.mp4ChannelConfig) this.metadata.setFormat("numberOfChannels", header.mp4ChannelConfig.length); if (this.options.duration) this.calculateEofDuration = true; else return true; } return false; } async parseCrc() { await this.tokenizer.ignore(INT16_BE.len); this.offset += INT16_BE.len; return this.skipSideInformation(); } async skipSideInformation() { if (this.audioFrameHeader) { const sideinfo_length = this.audioFrameHeader.calculateSideInfoLength(); if (sideinfo_length !== null) { await this.tokenizer.readToken(new Uint8ArrayType(sideinfo_length)); this.offset += sideinfo_length; await this.readXtraInfoHeader(); return; } } } async readXtraInfoHeader() { const headerTag = await this.tokenizer.readToken(InfoTagHeaderTag); this.offset += InfoTagHeaderTag.len; switch (headerTag) { case "Info": this.metadata.setFormat("codecProfile", "CBR"); return this.readXingInfoHeader(); case "Xing": { const infoTag = await this.readXingInfoHeader(); if (infoTag.vbrScale !== null) { const codecProfile = getVbrCodecProfile(infoTag.vbrScale); this.metadata.setFormat("codecProfile", codecProfile); } return null; } case "Xtra": break; case "LAME": { const version = await this.tokenizer.readToken(LameEncoderVersion); if (this.frame_size !== null && this.frame_size >= this.offset + LameEncoderVersion.len) { this.offset += LameEncoderVersion.len; this.metadata.setFormat("tool", `LAME ${version}`); await this.skipFrameData(this.frame_size - this.offset); return null; } this.metadata.addWarning("Corrupt LAME header"); break; } } const frameDataLeft = this.frame_size - this.offset; if (frameDataLeft < 0) this.metadata.addWarning(`Frame ${this.frameCount}corrupt: negative frameDataLeft`); else await this.skipFrameData(frameDataLeft); return null; } /** * Ref: http://gabriel.mp3-tech.org/mp3infotag.html * @returns {Promise<string>} */ async readXingInfoHeader() { const offset = this.tokenizer.position; const infoTag = await readXingHeader(this.tokenizer); this.offset += this.tokenizer.position - offset; if (infoTag.lame) { this.metadata.setFormat("tool", `LAME ${stripNulls(infoTag.lame.version)}`); if (infoTag.lame.extended) { this.metadata.setFormat("trackPeakLevel", infoTag.lame.extended.track_peak); if (infoTag.lame.extended.track_gain) this.metadata.setFormat("trackGain", infoTag.lame.extended.track_gain.adjustment); if (infoTag.lame.extended.album_gain) this.metadata.setFormat("albumGain", infoTag.lame.extended.album_gain.adjustment); this.metadata.setFormat("duration", infoTag.lame.extended.music_length / 1e3); } } if (infoTag.streamSize && this.audioFrameHeader && infoTag.numFrames !== null) { const duration = this.audioFrameHeader.calcDuration(infoTag.numFrames); this.metadata.setFormat("duration", duration); debug("Get duration from Xing header: %s", this.metadata.format.duration); return infoTag; } const frameDataLeft = this.frame_size - this.offset; await this.skipFrameData(frameDataLeft); return infoTag; } async skipFrameData(frameDataLeft) { if (frameDataLeft < 0) throw new MpegContentError("frame-data-left cannot be negative"); await this.tokenizer.ignore(frameDataLeft); } areAllSame(array) { const first = array[0]; return array.every((element) => { return element === first; }); } }; //#endregion export { MpegParser };