openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
369 lines (368 loc) • 13.1 kB
JavaScript
import { o as __toESM } from "./chunk-CNf5ZN-e.js";
import { t as require_src } from "./src-6PhJB9jV.js";
import { E as Uint8ArrayType, O as textDecode, S as UINT32_LE, T as UINT8, _ as UINT16_BE, g as StringType, o as makeUnexpectedFileContentError, x as UINT32_BE, y as UINT24_BE } from "./BasicParser-CSYy5xVJ.js";
import { a as getBit, o as getBitAllignedNumber } from "./Util--vOO_7DL.js";
import { t as AttachedPictureType } from "./ID3v2Token-Vj5Brwlp.js";
import { i as FourCcToken } from "./APEv2Parser-Drjv2OIw.js";
import { t as AbstractID3Parser } from "./AbstractID3Parser-B0YuPNia.js";
//#region node_modules/music-metadata/lib/ogg/vorbis/Vorbis.js
var import_src = /* @__PURE__ */ __toESM(require_src(), 1);
/**
* Parse the METADATA_BLOCK_PICTURE
* Ref: https://wiki.xiph.org/VorbisComment#METADATA_BLOCK_PICTURE
* Ref: https://xiph.org/flac/format.html#metadata_block_picture
* // ToDo: move to ID3 / APIC?
*/
var VorbisPictureToken = class VorbisPictureToken {
static fromBase64(base64str) {
return VorbisPictureToken.fromBuffer(Uint8Array.from(atob(base64str), (c) => c.charCodeAt(0)));
}
static fromBuffer(buffer) {
return new VorbisPictureToken(buffer.length).get(buffer, 0);
}
constructor(len) {
this.len = len;
}
get(buffer, offset) {
const type = AttachedPictureType[UINT32_BE.get(buffer, offset)];
offset += 4;
const mimeLen = UINT32_BE.get(buffer, offset);
offset += 4;
const format = new StringType(mimeLen, "utf-8").get(buffer, offset);
offset += mimeLen;
const descLen = UINT32_BE.get(buffer, offset);
offset += 4;
const description = new StringType(descLen, "utf-8").get(buffer, offset);
offset += descLen;
const width = UINT32_BE.get(buffer, offset);
offset += 4;
const height = UINT32_BE.get(buffer, offset);
offset += 4;
const colour_depth = UINT32_BE.get(buffer, offset);
offset += 4;
const indexed_color = UINT32_BE.get(buffer, offset);
offset += 4;
const picDataLen = UINT32_BE.get(buffer, offset);
offset += 4;
return {
type,
format,
description,
width,
height,
colour_depth,
indexed_color,
data: buffer.slice(offset, offset + picDataLen)
};
}
};
/**
* Comment header decoder
* Ref: https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-620004.2.1
*/
const CommonHeader = {
len: 7,
get: (buf, off) => {
return {
packetType: UINT8.get(buf, off),
vorbis: new StringType(6, "ascii").get(buf, off + 1)
};
}
};
/**
* Identification header decoder
* Ref: https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-630004.2.2
*/
const IdentificationHeader = {
len: 23,
get: (uint8Array, off) => {
return {
version: UINT32_LE.get(uint8Array, off + 0),
channelMode: UINT8.get(uint8Array, off + 4),
sampleRate: UINT32_LE.get(uint8Array, off + 5),
bitrateMax: UINT32_LE.get(uint8Array, off + 9),
bitrateNominal: UINT32_LE.get(uint8Array, off + 13),
bitrateMin: UINT32_LE.get(uint8Array, off + 17)
};
}
};
//#endregion
//#region node_modules/music-metadata/lib/ogg/vorbis/VorbisDecoder.js
var VorbisDecoder = class {
constructor(data, offset) {
this.data = data;
this.offset = offset;
}
readInt32() {
const value = UINT32_LE.get(this.data, this.offset);
this.offset += 4;
return value;
}
readStringUtf8() {
const len = this.readInt32();
const value = textDecode(this.data.subarray(this.offset, this.offset + len), "utf-8");
this.offset += len;
return value;
}
parseUserComment() {
const offset0 = this.offset;
const v = this.readStringUtf8();
const idx = v.indexOf("=");
return {
key: v.substring(0, idx).toUpperCase(),
value: v.substring(idx + 1),
len: this.offset - offset0
};
}
};
//#endregion
//#region node_modules/music-metadata/lib/ogg/vorbis/VorbisStream.js
const debug$1 = (0, import_src.default)("music-metadata:parser:ogg:vorbis1");
var VorbisContentError = class extends makeUnexpectedFileContentError("Vorbis") {};
/**
* Vorbis 1 Parser.
* Used by OggStream
*/
var VorbisStream = class VorbisStream {
constructor(metadata, options) {
this.pageSegments = [];
this.durationOnLastPage = true;
this.metadata = metadata;
this.options = options;
}
/**
* Vorbis 1 parser
* @param header Ogg Page Header
* @param pageData Page data
*/
async parsePage(header, pageData) {
this.lastPageHeader = header;
if (header.headerType.firstPage) this.parseFirstPage(header, pageData);
else {
if (header.headerType.continued) {
if (this.pageSegments.length === 0) throw new VorbisContentError("Cannot continue on previous page");
this.pageSegments.push(pageData);
}
if (header.headerType.lastPage || !header.headerType.continued) {
if (this.pageSegments.length > 0) {
const fullPage = VorbisStream.mergeUint8Arrays(this.pageSegments);
await this.parseFullPage(fullPage);
}
this.pageSegments = header.headerType.lastPage ? [] : [pageData];
}
}
}
static mergeUint8Arrays(arrays) {
const totalSize = arrays.reduce((acc, e) => acc + e.length, 0);
const merged = new Uint8Array(totalSize);
arrays.forEach((array, i, _arrays) => {
const offset = _arrays.slice(0, i).reduce((acc, e) => acc + e.length, 0);
merged.set(array, offset);
});
return merged;
}
async flush() {
await this.parseFullPage(VorbisStream.mergeUint8Arrays(this.pageSegments));
}
async parseUserComment(pageData, offset) {
const tag = new VorbisDecoder(pageData, offset).parseUserComment();
await this.addTag(tag.key, tag.value);
return tag.len;
}
async addTag(id, value) {
if (id === "METADATA_BLOCK_PICTURE" && typeof value === "string") {
if (this.options.skipCovers) {
debug$1("Ignore picture");
return;
}
value = VorbisPictureToken.fromBase64(value);
debug$1(`Push picture: id=${id}, format=${value.format}`);
} else debug$1(`Push tag: id=${id}, value=${value}`);
await this.metadata.addTag("vorbis", id, value);
}
calculateDuration(enfOfStream) {
if (this.lastPageHeader && (enfOfStream || this.lastPageHeader.headerType.lastPage) && this.metadata.format.sampleRate && this.lastPageHeader.absoluteGranulePosition >= 0) {
this.metadata.setFormat("numberOfSamples", this.lastPageHeader.absoluteGranulePosition);
this.metadata.setFormat("duration", this.lastPageHeader.absoluteGranulePosition / this.metadata.format.sampleRate);
}
}
/**
* Parse first Ogg/Vorbis page
* @param _header
* @param pageData
*/
parseFirstPage(_header, pageData) {
this.metadata.setFormat("codec", "Vorbis I");
this.metadata.setFormat("hasAudio", true);
debug$1("Parse first page");
const commonHeader = CommonHeader.get(pageData, 0);
if (commonHeader.vorbis !== "vorbis") throw new VorbisContentError("Metadata does not look like Vorbis");
if (commonHeader.packetType === 1) {
const idHeader = IdentificationHeader.get(pageData, CommonHeader.len);
this.metadata.setFormat("sampleRate", idHeader.sampleRate);
this.metadata.setFormat("bitrate", idHeader.bitrateNominal);
this.metadata.setFormat("numberOfChannels", idHeader.channelMode);
debug$1("sample-rate=%s[hz], bitrate=%s[b/s], channel-mode=%s", idHeader.sampleRate, idHeader.bitrateNominal, idHeader.channelMode);
} else throw new VorbisContentError("First Ogg page should be type 1: the identification header");
}
async parseFullPage(pageData) {
const commonHeader = CommonHeader.get(pageData, 0);
debug$1("Parse full page: type=%s, byteLength=%s", commonHeader.packetType, pageData.byteLength);
switch (commonHeader.packetType) {
case 3: return this.parseUserCommentList(pageData, CommonHeader.len);
case 1:
case 5: break;
}
}
/**
* Ref: https://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-840005.2
*/
async parseUserCommentList(pageData, offset) {
const strLen = UINT32_LE.get(pageData, offset);
offset += 4;
offset += strLen;
let userCommentListLength = UINT32_LE.get(pageData, offset);
offset += 4;
while (userCommentListLength-- > 0) offset += await this.parseUserComment(pageData, offset);
}
};
//#endregion
//#region node_modules/music-metadata/lib/flac/FlacToken.js
/**
* FLAC supports up to 128 kinds of metadata blocks; currently the following are defined:
* ref: https://xiph.org/flac/format.html#metadata_block
*/
const BlockType = {
STREAMINFO: 0,
PADDING: 1,
APPLICATION: 2,
SEEKTABLE: 3,
VORBIS_COMMENT: 4,
CUESHEET: 5,
PICTURE: 6
};
const BlockHeader = {
len: 4,
get: (buf, off) => {
return {
lastBlock: getBit(buf, off, 7),
type: getBitAllignedNumber(buf, off, 1, 7),
length: UINT24_BE.get(buf, off + 1)
};
}
};
/**
* METADATA_BLOCK_DATA
* Ref: https://xiph.org/flac/format.html#metadata_block_streaminfo
*/
const BlockStreamInfo = {
len: 34,
get: (buf, off) => {
return {
minimumBlockSize: UINT16_BE.get(buf, off),
maximumBlockSize: UINT16_BE.get(buf, off + 2) / 1e3,
minimumFrameSize: UINT24_BE.get(buf, off + 4),
maximumFrameSize: UINT24_BE.get(buf, off + 7),
sampleRate: UINT24_BE.get(buf, off + 10) >> 4,
channels: getBitAllignedNumber(buf, off + 12, 4, 3) + 1,
bitsPerSample: getBitAllignedNumber(buf, off + 12, 7, 5) + 1,
totalSamples: getBitAllignedNumber(buf, off + 13, 4, 36),
fileMD5: new Uint8ArrayType(16).get(buf, off + 18)
};
}
};
//#endregion
//#region node_modules/music-metadata/lib/flac/FlacParser.js
const debug = (0, import_src.default)("music-metadata:parser:FLAC");
var FlacContentError = class extends makeUnexpectedFileContentError("FLAC") {};
var FlacParser = class extends AbstractID3Parser {
constructor() {
super(...arguments);
this.vorbisParser = new VorbisStream(this.metadata, this.options);
}
async postId3v2Parse() {
if ((await this.tokenizer.readToken(FourCcToken)).toString() !== "fLaC") throw new FlacContentError("Invalid FLAC preamble");
let blockHeader;
do {
blockHeader = await this.tokenizer.readToken(BlockHeader);
await this.parseDataBlock(blockHeader);
} while (!blockHeader.lastBlock);
if (this.tokenizer.fileInfo.size && this.metadata.format.duration) {
const dataSize = this.tokenizer.fileInfo.size - this.tokenizer.position;
this.metadata.setFormat("bitrate", 8 * dataSize / this.metadata.format.duration);
}
}
async parseDataBlock(blockHeader) {
debug(`blockHeader type=${blockHeader.type}, length=${blockHeader.length}`);
switch (blockHeader.type) {
case BlockType.STREAMINFO: return this.readBlockStreamInfo(blockHeader.length);
case BlockType.PADDING: break;
case BlockType.APPLICATION: break;
case BlockType.SEEKTABLE: break;
case BlockType.VORBIS_COMMENT: return this.readComment(blockHeader.length);
case BlockType.CUESHEET: break;
case BlockType.PICTURE:
await this.parsePicture(blockHeader.length);
return;
default: this.metadata.addWarning(`Unknown block type: ${blockHeader.type}`);
}
return this.tokenizer.ignore(blockHeader.length).then();
}
/**
* Parse STREAMINFO
*/
async readBlockStreamInfo(dataLen) {
if (dataLen !== BlockStreamInfo.len) throw new FlacContentError("Unexpected block-stream-info length");
const streamInfo = await this.tokenizer.readToken(BlockStreamInfo);
this.metadata.setFormat("container", "FLAC");
this.processsStreamInfo(streamInfo);
}
/**
* Parse STREAMINFO
*/
processsStreamInfo(streamInfo) {
this.metadata.setFormat("codec", "FLAC");
this.metadata.setFormat("hasAudio", true);
this.metadata.setFormat("lossless", true);
this.metadata.setFormat("numberOfChannels", streamInfo.channels);
this.metadata.setFormat("bitsPerSample", streamInfo.bitsPerSample);
this.metadata.setFormat("sampleRate", streamInfo.sampleRate);
if (streamInfo.totalSamples > 0) this.metadata.setFormat("duration", streamInfo.totalSamples / streamInfo.sampleRate);
}
/**
* Read VORBIS_COMMENT from tokenizer
* Ref: https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-640004.2.3
*/
async readComment(dataLen) {
const data = await this.tokenizer.readToken(new Uint8ArrayType(dataLen));
return this.parseComment(data);
}
/**
* Parse VORBIS_COMMENT
* Ref: https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-640004.2.3
*/
async parseComment(data) {
const decoder = new VorbisDecoder(data, 0);
const vendor = decoder.readStringUtf8();
if (vendor.length > 0) this.metadata.setFormat("tool", vendor);
const commentListLength = decoder.readInt32();
const tags = new Array(commentListLength);
for (let i = 0; i < commentListLength; i++) tags[i] = decoder.parseUserComment();
await Promise.all(tags.map((tag) => {
if (tag.key === "ENCODER") this.metadata.setFormat("tool", tag.value);
return this.addTag(tag.key, tag.value);
}));
}
async parsePicture(dataLen) {
if (this.options.skipCovers) return this.tokenizer.ignore(dataLen);
return this.addPictureTag(await this.tokenizer.readToken(new VorbisPictureToken(dataLen)));
}
addPictureTag(picture) {
return this.addTag("METADATA_BLOCK_PICTURE", picture);
}
addTag(id, value) {
return this.vorbisParser.addTag(id, value);
}
};
//#endregion
export { VorbisStream as a, BlockType as i, BlockHeader as n, VorbisPictureToken as o, BlockStreamInfo as r, FlacParser as t };