m3u-parser-generator
Version:
Library to parse and generate m3u or m3u8 IPTV playlist files
298 lines (297 loc) • 10.2 kB
JavaScript
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
};