iptv/scripts/parser.js

223 lines
5.5 KiB
JavaScript

const playlistParser = require('iptv-playlist-parser')
const epgParser = require('epg-parser')
const utils = require('./utils')
const categories = require('./categories')
const path = require('path');
const parser = {}
parser.parseIndex = function () {
const content = utils.readFile('index.m3u')
const result = playlistParser.parse(content)
return result.items
}
parser.parsePlaylist = function (filename) {
const content = utils.readFile(filename)
const result = playlistParser.parse(content)
const name = path.parse(filename).name
const country = utils.code2name(name)
return new Playlist({ header: result.header, items: result.items, url: filename, country, name })
}
parser.parseEPG = async function (url) {
return utils.loadEPG(url).then(content => {
const result = epgParser.parse(content)
const channels = {}
for (let channel of result.channels) {
channels[channel.id] = channel
}
return { url, channels }
})
}
class Playlist {
constructor({ header, items, url, name, country }) {
this.url = url
this.name = name
this.country = country
this.header = header
this.channels = items
.map(item => new Channel({ data: item, header, sourceUrl: url }))
.filter(channel => channel.url)
}
toString(short = false) {
let parts = ['#EXTM3U']
for (let key in this.header.attrs) {
let value = this.header.attrs[key]
if (value) {
parts.push(`${key}="${value}"`)
}
}
let output = `${parts.join(' ')}\n`
for (let channel of this.channels) {
output += channel.toString(short)
}
return output
}
}
class Channel {
constructor({ data, header, sourceUrl }) {
this.parseData(data)
if (!this.countries.length) {
const filename = utils.getBasename(sourceUrl)
const countryName = utils.code2name(filename)
this.countries = countryName ? [{ code: filename.toLowerCase(), name: countryName }] : []
this.tvg.country = this.countries.map(c => c.code.toUpperCase()).join(';')
}
this.tvg.url = header.attrs['x-tvg-url'] || ''
}
parseData(data) {
const title = this.parseTitle(data.name)
this.tvg = data.tvg
this.http = data.http
this.url = data.url
this.logo = data.tvg.logo
this.name = title.channelName
this.status = title.streamStatus
this.resolution = title.streamResolution
this.countries = this.parseCountries(data.tvg.country)
this.languages = this.parseLanguages(data.tvg.language)
this.category = this.parseCategory(data.group.title)
}
parseCountries(string) {
let arr = string
.split(';')
.reduce((acc, curr) => {
const codes = utils.region2codes(curr)
if (codes.length) {
for (let code of codes) {
if (!acc.includes(code)) {
acc.push(code)
}
}
} else {
acc.push(curr)
}
return acc
}, [])
.filter(code => code && utils.code2name(code))
return arr.map(code => {
return { code: code.toLowerCase(), name: utils.code2name(code) }
})
}
parseLanguages(string) {
return string
.split(';')
.map(name => {
const code = name ? utils.language2code(name) : null
if (!code) return null
return {
code,
name
}
})
.filter(l => l)
}
parseCategory(string) {
const category = categories.find(c => c.id === string.toLowerCase())
return category ? category.name : ''
}
parseTitle(title) {
const channelName = title
.trim()
.split(' ')
.map(s => s.trim())
.filter(s => {
return !/\[|\]/i.test(s) && !/\((\d+)P\)/i.test(s)
})
.join(' ')
const streamStatusMatch = title.match(/\[(.*)\]/i)
const streamStatus = streamStatusMatch ? streamStatusMatch[1] : null
const streamResolutionMatch = title.match(/\((\d+)P\)/i)
const streamResolutionHeight = streamResolutionMatch ? parseInt(streamResolutionMatch[1]) : null
const streamResolution = { width: null, height: streamResolutionHeight }
return { channelName, streamStatus, streamResolution }
}
get tvgCountry() {
return this.tvg.country
.split(';')
.map(code => utils.code2name(code))
.join(';')
}
get tvgLanguage() {
return this.tvg.language
}
get tvgUrl() {
return (this.tvg.id || this.tvg.name) && this.tvg.url ? this.tvg.url : ''
}
toString(short = false) {
this.tvg.country = this.tvg.country.toUpperCase()
let info = `-1 tvg-id="${this.tvg.id}" tvg-name="${this.tvg.name}" tvg-country="${this.tvg.country}" tvg-language="${this.tvg.language}" tvg-logo="${this.logo}"`
if (!short) {
info += ` tvg-url="${this.tvgUrl}"`
}
info += ` group-title="${this.category}",${this.name}`
if (this.resolution.height) {
info += ` (${this.resolution.height}p)`
}
if (this.status) {
info += ` [${this.status}]`
}
if (this.http['referrer']) {
info += `\n#EXTVLCOPT:http-referrer=${this.http['referrer']}`
}
if (this.http['user-agent']) {
info += `\n#EXTVLCOPT:http-user-agent=${this.http['user-agent']}`
}
return '#EXTINF:' + info + '\n' + this.url + '\n'
}
toJSON() {
return {
name: this.name,
logo: this.logo || null,
url: this.url,
category: this.category || null,
languages: this.languages,
countries: this.countries,
tvg: {
id: this.tvg.id || null,
name: this.tvg.name || null,
url: this.tvg.url || null
}
}
}
}
module.exports = parser