//#region src/media/hls/parse-attributes.ts /** * Parse HLS attribute list from a tag line. * Handles both quoted and unquoted values. */ function parseAttributeList(line) { const attributes = /* @__PURE__ */ new Map(); for (const match of line.matchAll(/([A-Z0-9-]+)=(?:"([^"]*)"|([^,]*))/g)) { const key = match[1]; const value = match[2] ?? match[3] ?? ""; if (key) attributes.set(key, value); } return attributes; } /** * Parse RESOLUTION attribute value (WIDTHxHEIGHT). */ function parseResolution(value) { const match = /^(\d+)x(\d+)$/.exec(value); if (!match) return null; return { width: Number.parseInt(match[1], 10), height: Number.parseInt(match[2], 10) }; } /** * Parse FRAME-RATE attribute to rational frame rate. */ function parseFrameRate(value) { const fps = Number.parseFloat(value); if (Number.isNaN(fps) || fps <= 0) return void 0; if (Math.abs(fps - 23.976) < .01) return { frameRateNumerator: 24e3, frameRateDenominator: 1001 }; if (Math.abs(fps - 29.97) < .01) return { frameRateNumerator: 3e4, frameRateDenominator: 1001 }; if (Math.abs(fps - 59.94) < .01) return { frameRateNumerator: 6e4, frameRateDenominator: 1001 }; if (fps % 1 === 0) return { frameRateNumerator: Math.round(fps) }; return { frameRateNumerator: Math.round(fps) }; } const AUDIO_CODEC_PREFIXES = [ "mp4a.", "ac-3", "ec-3", "ac-4", "opus", "flac", "dts", "alac", "vorbis" ]; /** * Parse CODECS attribute into separate video and audio codecs. */ function parseCodecs(codecs) { const parts = codecs.split(",").map((s) => s.trim()); const result = {}; for (const codec of parts) { const lower = codec.toLowerCase(); if (codec.startsWith("avc1.") || codec.startsWith("hvc1.") || codec.startsWith("hev1.")) result.video = codec; else if (AUDIO_CODEC_PREFIXES.some((prefix) => lower.startsWith(prefix))) result.audio = codec; } return result; } /** * Parse #EXTINF duration value. */ function parseExtInfDuration(value) { const durationPart = value.split(",")[0] ?? value; const duration = Number.parseFloat(durationPart); return Number.isNaN(duration) ? 0 : duration; } /** * Parse BYTERANGE attribute value. * Format: "length[@offset]" * If offset is omitted, it continues from the previous byte range end. */ function parseByteRange(value, previousEnd) { const match = /^(\d+)(?:@(\d+))?$/.exec(value); if (!match) return null; const length = Number.parseInt(match[1], 10); if (Number.isNaN(length)) return null; let start; if (match[2] !== void 0) { start = Number.parseInt(match[2], 10); if (Number.isNaN(start)) return null; } else if (previousEnd !== void 0) start = previousEnd; else return null; return { start, end: start + length - 1 }; } /** * Create AttributeList from raw attribute string. */ function createAttributeList(line) { const map = parseAttributeList(line); return { get(key) { return map.get(key); }, getInt(key, defaultValue) { const value = map.get(key); if (value === void 0) return defaultValue; const parsed = Number.parseInt(value, 10); return Number.isNaN(parsed) ? defaultValue : parsed; }, getFloat(key, defaultValue) { const value = map.get(key); if (value === void 0) return defaultValue; const parsed = Number.parseFloat(value); return Number.isNaN(parsed) ? defaultValue : parsed; }, getBool(key) { return map.get(key) === "YES"; }, getResolution(key) { const value = map.get(key); if (!value) return void 0; return parseResolution(value) ?? void 0; }, getFrameRate(key) { const value = map.get(key); if (!value) return void 0; return parseFrameRate(value); } }; } /** * Match a tag and extract its attributes. * Returns null if the line doesn't match the tag. */ function matchTag(line, tag) { const prefix = `#${tag}:`; if (!line.startsWith(prefix)) return null; return createAttributeList(line.slice(prefix.length)); } //#endregion export { createAttributeList, matchTag, parseAttributeList, parseByteRange, parseCodecs, parseExtInfDuration, parseFrameRate, parseResolution }; //# sourceMappingURL=parse-attributes.js.map