UNPKG

m3u-parser-generator

Version:

Library to parse and generate m3u or m3u8 IPTV playlist files

298 lines (297 loc) 10.2 kB
class P { /** * Generate is static method to generate m3u playlist string from playlist object * @param playlist - playlist object to generate m3u playlist string * @returns final m3u playlist string * @example * ```ts * const playlist = new M3uPlaylist(); * playlist.title = 'Test playlist'; * M3uGenerator.generate(playlist); * ``` */ static generate(t) { const r = t.title ? `${n.PLAYLIST}:${t.title}` : void 0, s = this.getCustomDataDirective(t.customData), e = t.medias.map((i) => this.getMedia(i)).join(` `), o = this.getAttributes(t.attributes); return [n.EXTM3U + o, r, s, e].filter((i) => i).join(` `); } /** * Get generated media part string from m3u playlist media object * @param media - media object * @returns media part string with info, group and location each on separated line * @private */ static getMedia(t) { const r = this.getAttributes(t.attributes), s = this.shouldAddInfoDirective(t, r) ? `${n.EXTINF}:${t.duration}${r},${t.name}` : null, e = t.group ? `${n.EXTGRP}:${t.group}` : null, o = t.bytes ? `${n.EXTBYT}:${t.bytes}` : null, i = t.image ? `${n.EXTIMG}:${t.image}` : null, u = t.album ? `${n.EXTALB}:${t.album}` : null, c = t.artist ? `${n.EXTART}:${t.artist}` : null, l = t.genre ? `${n.EXTGENRE}:${t.genre}` : null, h = t.extraAttributesFromUrl ? `${n.EXTATTRFROMURL}:${t.extraAttributesFromUrl}` : null, p = t.extraHttpHeaders ? `${n.EXTHTTP}:${JSON.stringify(t.extraHttpHeaders)}` : null, X = t.kodiProps ? [...t.kodiProps].map(([T, A]) => `${n.KODIPROP}:${T}=${A}`).join(` `) : null, f = this.getCustomDataDirective(t.customData); return [ s, e, o, i, u, c, l, h, p, X, f, t.location ].filter((T) => T).join(` `); } /** * Get generated string of custom directives for both, playlist and media * @param customData - custom data object, that represents unknown directives * @private */ static getCustomDataDirective(t) { return t.map((r) => `${r.directive}:${r.value}`).join(` `); } /** * Get generated attributes media part string from m3u attributes object * @param attributes - attributes object * @returns attributes generated string (attributeName="attributeValue" ...) * @private */ static getAttributes(t) { const r = Object.keys(t); return r.length ? " " + r.map((s) => `${s}="${t[s]}"`).join(" ") : ""; } /** * Method to determine if we need to add info directive or not based on media object and attributes string. * At least media duration, media name or some attributes must be present to return true * @param media - m3u media object * @param attributesString - m3u attributes string * @returns boolean if we should add info directive into final media * @private */ static shouldAddInfoDirective(t, r) { return t.duration !== b || r !== "" || t.name !== void 0; } } const $ = "#", b = -1; var n = /* @__PURE__ */ ((a) => (a.EXTM3U = "#EXTM3U", a.EXTINF = "#EXTINF", a.PLAYLIST = "#PLAYLIST", a.EXTGRP = "#EXTGRP", a.EXTBYT = "#EXTBYT", a.EXTIMG = "#EXTIMG", a.EXTALB = "#EXTALB", a.EXTART = "#EXTART", a.EXTGENRE = "#EXTGENRE", a.EXTATTRFROMURL = "#EXTATTRFROMURL", a.EXTHTTP = "#EXTHTTP", a.KODIPROP = "#KODIPROP", a))(n || {}); class d { constructor() { this.title = "", this.attributes = new g(), this.medias = [], this.customData = []; } /** * Get url-tvg url * @returns url-tvg url * @deprecated The method should not be used, use playlist.attributes['url-tvg'] instead */ get urlTvg() { return this.attributes["url-tvg"]; } /** * Set url-tvg url * @param urlTvg - url-tvg url * @deprecated The method should not be used, use playlist.attributes['url-tvg'] instead */ set urlTvg(t) { this.attributes = { ...this.attributes, "url-tvg": t }; } /** * Get m3u string method to get m3u playlist string of current playlist object * @returns m3u playlist string */ getM3uString() { return P.generate(this); } } class E { /** * Constructor * @param location - location of stream */ constructor(t) { this.location = t, this.duration = b, this.attributes = new g(), this.extraAttributesFromUrl = void 0, this.extraHttpHeaders = void 0, this.bytes = void 0, this.image = void 0, this.album = void 0, this.artist = void 0, this.genre = void 0, this.customData = []; } } class g { } class R { /** * Constructor of class * @param config - to configure parser behaviour */ constructor(t) { this.config = t; } /** * Get m3u attributes object from attributes string * @param attributesString e.g. 'tvg-id="" group-title=""' * @returns attributes object e.g. {"tvg-id": "", "group-title": ""} * @private */ getAttributes(t) { const r = new g(); return t && (t.match(/[^ ]*?=".*?"/g) ?? []).forEach((e) => { const [o, i] = e.split('="'); r[o] = i.replace('"', ""); }), r; } /** * Process media method parse trackInformation and fill media with parsed info * @param trackInformation - media substring of m3u string line e.g. '-1 tvg-id="" group-title="",Tv Name' * @param media - actual m3u media object * @private */ processMedia(t, r) { const s = t.indexOf(","), e = t.substring(0, s); r.name = t.substring(s + 1); const o = e.indexOf(" "), i = o > 0 ? o : e.length; r.duration = Number(e.substring(0, i)); const u = e.substring(i + 1); r.attributes = this.getAttributes(u); } /** * Process directive method detects directive on line and call proper method to another processing * @param item - actual line of m3u playlist string e.g. '#EXTINF:-1 tvg-id="" group-title="",Tv Name' * @param playlist - m3u playlist object processed until now * @param media - actual m3u media object * @private */ processDirective(t, r, s) { const e = t.indexOf(":"), o = t.substring(0, e), i = t.substring(e + 1); switch (o) { case n.EXTINF: { this.processMedia(i, s); break; } case n.EXTGRP: { s.group = i; break; } case n.EXTBYT: { s.bytes = Number(i); break; } case n.EXTIMG: { s.image = i; break; } case n.EXTALB: { s.album = i; break; } case n.EXTART: { s.artist = i; break; } case n.EXTGENRE: { s.genre = i; break; } case n.PLAYLIST: { r.title = i; break; } case n.EXTATTRFROMURL: { s.extraAttributesFromUrl = i; break; } case n.EXTHTTP: { s.extraHttpHeaders = JSON.parse(i); break; } case n.KODIPROP: { const [u, ...c] = i.split("="), l = c.join("="); s.kodiProps || (s.kodiProps = /* @__PURE__ */ new Map()), s.kodiProps.set(u, l); break; } default: this.processCustomData(r, s, i, o); } } /** * Process custom unknown directive and add it into playlist or media object, based on mapping configuration * @param playlist - m3u playlist object processed until now * @param media - actual m3u media object * @param trackInformation - track information, whole part of string after directive and semicolon * @param directive - unknown directive e.g. #EXT-CUSTOM * @private */ processCustomData(t, r, s, e) { var o, i, u, c; (i = (o = this.config) == null ? void 0 : o.customDataMapping) != null && i.media && this.config.customDataMapping.media.includes(e) ? r.customData.push({ directive: e, value: s }) : (c = (u = this.config) == null ? void 0 : u.customDataMapping) != null && c.playlist && this.config.customDataMapping.playlist.includes(e) && t.customData.push({ directive: e, value: s }); } /** * Process attributes in #EXTM3U line * @param item - first line of m3u playlist string e.g. '#EXTM3U url-tvg="http://example.com/tvg.xml"' * @param playlist - m3u playlist object processed until now * @private */ processExtM3uAttributes(t, r) { if (t.startsWith(n.EXTM3U)) { const s = t.indexOf(" "); if (s > 0) { const e = t.substring(s + 1); r.attributes = this.getAttributes(e); } } } /** * Get playlist returns m3u playlist object parsed from m3u string lines * @param lines - m3u string lines * @returns parsed m3u playlist object * @private */ getPlaylist(t) { const r = new d(); let s = new E(""); return this.processExtM3uAttributes(t[0], r), t.forEach((e) => { this.isDirective(e) ? this.processDirective(e, r, s) : (s.location = e, r.medias.push(s), s = new E("")); }), r; } /** * Is directive method detect if line contains m3u directive * @param item - string line of playlist * @returns true if it is line with directive, otherwise false * @private */ isDirective(t) { return t[0] === $; } /** * Is valid m3u method detect if first line of playlist contains #EXTM3U directive * @param firstLine - first line of m3u playlist string * @returns true if line starts with #EXTM3U, false otherwise * @private */ isValidM3u(t) { return t[0].startsWith(n.EXTM3U); } /** * Parse is method to parse m3u playlist string into m3u playlist object. * Playlist need to contain #EXTM3U directive on first line. * All lines are trimmed and blank ones are removed. * @param m3uString - whole m3u playlist string * @returns parsed m3u playlist object * @example * ```ts * const playlist = M3uParser.parse(m3uString); * playlist.medias.forEach(media => media.location); * ``` */ parse(t) { var s, e; if (!((s = this.config) != null && s.ignoreErrors) && !t) throw new Error("m3uString can't be null!"); const r = t.split(` `).map((o) => o.trim()).filter((o) => o != ""); if (!((e = this.config) != null && e.ignoreErrors) && !this.isValidM3u(r)) throw new Error(`Missing ${n.EXTM3U} directive!`); return this.getPlaylist(r); } } export { g as M3uAttributes, P as M3uGenerator, E as M3uMedia, R as M3uParser, d as M3uPlaylist };