Files
v10/dist/dev/media/hls/parse-attributes.js
T

148 lines
4.0 KiB
JavaScript

//#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