mirror of
https://github.com/iptv-org/iptv.git
synced 2026-03-31 12:23:44 -04:00
Merge pull request #34913 from iptv-org/patch-2026.03.6
Patch 2026.03.6
This commit is contained in:
113
package-lock.json
generated
113
package-lock.json
generated
@@ -36,15 +36,18 @@
|
||||
"commander": "^14.0.0",
|
||||
"console-table-printer": "^2.14.6",
|
||||
"cross-env": "^10.0.0",
|
||||
"es-toolkit": "^1.45.1",
|
||||
"eslint": "^9.32.0",
|
||||
"glob": "^11.0.3",
|
||||
"globals": "^16.3.0",
|
||||
"hls-parser": "^0.16.0",
|
||||
"iptv-playlist-parser": "^0.15.1",
|
||||
"jest": "^30.0.5",
|
||||
"jest-expect-message": "^1.1.3",
|
||||
"lodash.uniqueid": "^4.0.1",
|
||||
"m3u-linter": "^0.4.2",
|
||||
"mediainfo.js": "^0.3.6",
|
||||
"mpd-parser": "^1.3.1",
|
||||
"node-cleanup": "^2.1.2",
|
||||
"normalize-url": "^8.1.0",
|
||||
"socks-proxy-agent": "^8.0.5",
|
||||
@@ -497,6 +500,15 @@
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.29.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
|
||||
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
|
||||
@@ -3453,6 +3465,30 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@videojs/vhs-utils": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/vhs-utils/-/vhs-utils-4.0.0.tgz",
|
||||
"integrity": "sha512-xJp7Yd4jMLwje2vHCUmi8MOUU76nxiwII3z4Eg3Ucb+6rrkFVGosrXlMgGnaLjq724j3wzNElRZ71D/CKrTtxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"global": "^4.4.0",
|
||||
"url-toolkit": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8",
|
||||
"npm": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.11",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
|
||||
"integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
@@ -4107,6 +4143,11 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-walk": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
@@ -4204,6 +4245,16 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-toolkit": {
|
||||
"version": "1.45.1",
|
||||
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz",
|
||||
"integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"docs",
|
||||
"benchmarks"
|
||||
]
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.25.3",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz",
|
||||
@@ -4635,9 +4686,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
|
||||
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
@@ -4897,6 +4949,16 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/global": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
|
||||
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"min-document": "^2.19.0",
|
||||
"process": "^0.11.10"
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "16.3.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz",
|
||||
@@ -4979,6 +5041,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/hls-parser": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/hls-parser/-/hls-parser-0.16.0.tgz",
|
||||
"integrity": "sha512-jb8tX36Nn49MtvSBTWcQjD4ktwJtzrfOlnI8zpo4zQjTq0ZGmG0RsoCQXwvKQGvt4CBFrs8Q3znq0HoNCpt5BA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -6617,6 +6685,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/min-document": {
|
||||
"version": "2.19.2",
|
||||
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz",
|
||||
"integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dom-walk": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
@@ -6637,6 +6714,21 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/mpd-parser": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/mpd-parser/-/mpd-parser-1.3.1.tgz",
|
||||
"integrity": "sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@videojs/vhs-utils": "^4.0.0",
|
||||
"@xmldom/xmldom": "^0.8.3",
|
||||
"global": "^4.4.0"
|
||||
},
|
||||
"bin": {
|
||||
"mpd-to-m3u8-json": "bin/parse.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -7024,6 +7116,15 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
@@ -7719,6 +7820,12 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-toolkit": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/url-toolkit/-/url-toolkit-2.2.5.tgz",
|
||||
"integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/v8-to-istanbul": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
|
||||
|
||||
@@ -60,15 +60,18 @@
|
||||
"commander": "^14.0.0",
|
||||
"console-table-printer": "^2.14.6",
|
||||
"cross-env": "^10.0.0",
|
||||
"es-toolkit": "^1.45.1",
|
||||
"eslint": "^9.32.0",
|
||||
"glob": "^11.0.3",
|
||||
"globals": "^16.3.0",
|
||||
"hls-parser": "^0.16.0",
|
||||
"iptv-playlist-parser": "^0.15.1",
|
||||
"jest": "^30.0.5",
|
||||
"jest-expect-message": "^1.1.3",
|
||||
"lodash.uniqueid": "^4.0.1",
|
||||
"m3u-linter": "^0.4.2",
|
||||
"mediainfo.js": "^0.3.6",
|
||||
"mpd-parser": "^1.3.1",
|
||||
"node-cleanup": "^2.1.2",
|
||||
"normalize-url": "^8.1.0",
|
||||
"socks-proxy-agent": "^8.0.5",
|
||||
|
||||
@@ -1,13 +1,34 @@
|
||||
import { Collection, Logger } from '@freearhey/core'
|
||||
import { OptionValues, program } from 'commander'
|
||||
import { Stream, Playlist } from '../../models'
|
||||
import { Storage } from '@freearhey/storage-js'
|
||||
import { STREAMS_DIR } from '../../constants'
|
||||
import { PlaylistParser } from '../../core'
|
||||
import { getStreamInfo } from '../../utils'
|
||||
import cliProgress from 'cli-progress'
|
||||
import { loadData } from '../../api'
|
||||
import { program } from 'commander'
|
||||
import { eachLimit } from 'async'
|
||||
import path from 'node:path'
|
||||
import os from 'node:os'
|
||||
|
||||
program.argument('[filepath...]', 'Path to file to format').parse(process.argv)
|
||||
program
|
||||
.argument('[filepath...]', 'Path to file to format')
|
||||
.option(
|
||||
'-p, --parallel <number>',
|
||||
'Batch size of streams to test concurrently',
|
||||
(value: string) => parseInt(value),
|
||||
os.cpus().length
|
||||
)
|
||||
.option('-x, --proxy <url>', 'Use the specified proxy')
|
||||
.option(
|
||||
'-t, --timeout <number>',
|
||||
'The number of milliseconds before the request will be aborted',
|
||||
(value: string) => parseInt(value),
|
||||
1000
|
||||
)
|
||||
.parse(process.argv)
|
||||
|
||||
const options: OptionValues = program.opts()
|
||||
|
||||
async function main() {
|
||||
const logger = new Logger()
|
||||
@@ -58,6 +79,33 @@ async function main() {
|
||||
return stream
|
||||
})
|
||||
|
||||
logger.info('adding the missing quality...')
|
||||
const progressBar = new cliProgress.SingleBar({
|
||||
clearOnComplete: true,
|
||||
format: `[{bar}] {percentage}% | {value}/{total}`
|
||||
})
|
||||
progressBar.start(streams.count(), 0)
|
||||
await eachLimit(streams.all(), options.parallel, async (stream: Stream) => {
|
||||
progressBar.increment()
|
||||
if (stream.quality) return
|
||||
|
||||
const streamInfo = await getStreamInfo(stream.url, {
|
||||
httpUserAgent: stream.user_agent,
|
||||
httpReferrer: stream.referrer,
|
||||
timeout: options.timeout,
|
||||
proxy: options.proxy
|
||||
})
|
||||
|
||||
if (streamInfo) {
|
||||
const height = streamInfo?.resolution?.height
|
||||
|
||||
if (height) {
|
||||
stream.quality = `${height}p`
|
||||
}
|
||||
}
|
||||
})
|
||||
progressBar.stop()
|
||||
|
||||
logger.info('sorting links...')
|
||||
streams = streams.sortBy(
|
||||
[
|
||||
|
||||
@@ -2,10 +2,10 @@ import { IssueLoader, PlaylistParser } from '../../core'
|
||||
import { Playlist, Issue, Stream } from '../../models'
|
||||
import { loadData, data as apiData } from '../../api'
|
||||
import { Logger, Collection } from '@freearhey/core'
|
||||
import { isURI, getStreamInfo } from '../../utils'
|
||||
import { Storage } from '@freearhey/storage-js'
|
||||
import { STREAMS_DIR } from '../../constants'
|
||||
import * as sdk from '@iptv-org/sdk'
|
||||
import { isURI } from '../../utils'
|
||||
|
||||
const processedIssues = new Collection()
|
||||
|
||||
@@ -136,24 +136,38 @@ async function addStreams({
|
||||
const requests = issues.filter(
|
||||
issue => issue.labels.includes('streams:add') && issue.labels.includes('approved')
|
||||
)
|
||||
requests.forEach((issue: Issue) => {
|
||||
|
||||
for (const issue of requests.all()) {
|
||||
const data = issue.data
|
||||
if (data.missing('stream_id') || data.missing('stream_url')) return
|
||||
if (streams.includes((_stream: Stream) => _stream.url === data.getString('stream_url'))) return
|
||||
if (data.missing('stream_id') || data.missing('stream_url')) continue
|
||||
if (streams.includes((_stream: Stream) => _stream.url === data.getString('stream_url')))
|
||||
continue
|
||||
const streamUrl = data.getString('stream_url') || ''
|
||||
if (!isURI(streamUrl)) return
|
||||
if (!isURI(streamUrl)) continue
|
||||
|
||||
const streamId = data.getString('stream_id') || ''
|
||||
const [channelId, feedId] = streamId.split('@')
|
||||
|
||||
const channel: sdk.Models.Channel | undefined = apiData.channelsKeyById.get(channelId)
|
||||
if (!channel) return
|
||||
if (!channel) continue
|
||||
|
||||
const label = data.getString('label') || ''
|
||||
const quality = data.getString('quality') || null
|
||||
const httpUserAgent = data.getString('http_user_agent') || null
|
||||
const httpReferrer = data.getString('http_referrer') || null
|
||||
|
||||
let quality = data.getString('quality') || null
|
||||
if (!quality) {
|
||||
const streamInfo = await getStreamInfo(streamUrl, { httpUserAgent, httpReferrer })
|
||||
|
||||
if (streamInfo) {
|
||||
const height = streamInfo?.resolution?.height
|
||||
|
||||
if (height) {
|
||||
quality = `${height}p`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const stream = new Stream({
|
||||
channel: channelId,
|
||||
feed: feedId,
|
||||
@@ -169,5 +183,5 @@ async function addStreams({
|
||||
|
||||
streams.add(stream)
|
||||
processedIssues.add(issue.number)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
117
scripts/utils.ts
117
scripts/utils.ts
@@ -1,4 +1,13 @@
|
||||
import axios, { AxiosProxyConfig, AxiosRequestConfig } from 'axios'
|
||||
import { parse as parsePlaylist, setOptions } from 'hls-parser'
|
||||
import { parse as parseManifest } from 'mpd-parser'
|
||||
import { SocksProxyAgent } from 'socks-proxy-agent'
|
||||
import { ProxyParser } from './core/proxyParser.js'
|
||||
import { TESTING } from './constants.js'
|
||||
import normalizeUrl from 'normalize-url'
|
||||
import { orderBy } from 'es-toolkit'
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
|
||||
export function isURI(string: string): boolean {
|
||||
try {
|
||||
@@ -21,3 +30,111 @@ export function truncate(string: string, limit: number = 100) {
|
||||
|
||||
return string.slice(0, limit - 3) + '...'
|
||||
}
|
||||
|
||||
type StreamInfo = {
|
||||
resolution: { width: number; height: number }
|
||||
bandwidth: number
|
||||
frameRate: number
|
||||
codecs: string
|
||||
}
|
||||
|
||||
export async function getStreamInfo(
|
||||
url: string,
|
||||
options: {
|
||||
httpUserAgent?: string | null
|
||||
httpReferrer?: string | null
|
||||
timeout?: number
|
||||
proxy?: string
|
||||
}
|
||||
): Promise<StreamInfo | undefined> {
|
||||
let data: string | undefined
|
||||
if (TESTING) {
|
||||
if (url.includes('.m3u8')) {
|
||||
data = fs.readFileSync(
|
||||
path.resolve(__dirname, '../tests/__data__/input/playlist_update/playlist.m3u8'),
|
||||
'utf8'
|
||||
)
|
||||
} else if (url.includes('.mpd')) {
|
||||
data = fs.readFileSync(
|
||||
path.resolve(__dirname, '../tests/__data__/input/playlist_update/manifest.mpd'),
|
||||
'utf8'
|
||||
)
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const timeout = options.timeout || 1000
|
||||
let request: AxiosRequestConfig = {
|
||||
signal: AbortSignal.timeout(timeout),
|
||||
responseType: 'text',
|
||||
headers: {
|
||||
'User-Agent': options.httpUserAgent || 'Mozilla/5.0',
|
||||
Referer: options.httpReferrer
|
||||
}
|
||||
}
|
||||
|
||||
if (options.proxy !== undefined) {
|
||||
const proxyParser = new ProxyParser()
|
||||
const proxy = proxyParser.parse(options.proxy) as AxiosProxyConfig
|
||||
if (
|
||||
proxy.protocol &&
|
||||
['socks', 'socks5', 'socks5h', 'socks4', 'socks4a'].includes(String(proxy.protocol))
|
||||
) {
|
||||
const socksProxyAgent = new SocksProxyAgent(options.proxy)
|
||||
|
||||
request = { ...request, ...{ httpAgent: socksProxyAgent, httpsAgent: socksProxyAgent } }
|
||||
} else {
|
||||
request = { ...request, ...{ proxy } }
|
||||
}
|
||||
}
|
||||
|
||||
const response = await axios(url, request)
|
||||
|
||||
data = response.data
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (!data) return undefined
|
||||
|
||||
let info: StreamInfo | undefined
|
||||
|
||||
if (url.includes('.m3u8')) {
|
||||
setOptions({ silent: true })
|
||||
|
||||
try {
|
||||
const playlist = parsePlaylist(data)
|
||||
|
||||
if (playlist && playlist.isMasterPlaylist && playlist.variants.length) {
|
||||
const v = orderBy(playlist.variants, ['bandwidth'], ['desc'])[0]
|
||||
|
||||
if (v && v.resolution && v.frameRate && v.codecs) {
|
||||
info = {
|
||||
resolution: { width: v.resolution.width, height: v.resolution.height },
|
||||
bandwidth: v.bandwidth,
|
||||
frameRate: v.frameRate,
|
||||
codecs: v.codecs
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
} else if (url.includes('.mpd')) {
|
||||
const manifest = parseManifest(data, {
|
||||
manifestUri: url,
|
||||
eventHandler: ({ type, message }) => console.log(`${type}: ${message}`)
|
||||
})
|
||||
|
||||
const playlist = orderBy(manifest.playlists, [p => p.attributes.BANDWIDTH], ['desc'])[0]
|
||||
|
||||
if (playlist) {
|
||||
const attr = playlist.attributes
|
||||
|
||||
info = {
|
||||
resolution: { width: attr.RESOLUTION.width, height: attr.RESOLUTION.height },
|
||||
bandwidth: attr.BANDWIDTH,
|
||||
frameRate: attr['FRAME-RATE'],
|
||||
codecs: attr.CODECS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
5
tests/__data__/expected/playlist_format/at.m3u
Normal file
5
tests/__data__/expected/playlist_format/at.m3u
Normal file
@@ -0,0 +1,5 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="",Hitradio O3 (1080p)
|
||||
https://studiocam-oe3.mdn.ors.at/orf/studiocam_oe3/q6a/master.m3u8
|
||||
#EXTINF:-1 tvg-id="",Hitradio O3 (720p)
|
||||
https://studiocam-oe3.mdn.ors.at/orf/studiocam_oe3/q6a/manifest.mpd
|
||||
@@ -1,5 +1,5 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="",Manorama News -2 [U3] (480p) [Geo-blocked] [Not 24/7]
|
||||
#EXTINF:-1 tvg-id="",Manorama News -2 (1080p) [U3] (Alt) [Geo-blocked] [Not 24/7]
|
||||
#EXTVLCOPT:http-referrer=http://test.com
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0
|
||||
https://ythls.onrender.com/channel/UCP0uG-mcMImgKnJz-VjJZmQ.m3u8
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="NPO1.nl@SD",NPO 1 (342p) [Geo-blocked]
|
||||
http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo1/npo1.isml/.m3u8
|
||||
#EXTINF:-1 tvg-id="NPO2.nl@SD",NPO 2 (1080p) [Geo-blocked]
|
||||
http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo2/npo22.isml/.m3u8
|
||||
#EXTINF:-1 tvg-id="NPO2.nl@SD",NPO 2 (342p)
|
||||
http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo2/npo2.isml/.m3u8
|
||||
#EXTINF:-1 tvg-id="NPO2.nl@SD",NPO 2 (302p) [Geo-blocked]
|
||||
#EXTVLCOPT:http-referrer=http://imn.iq
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 12_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
|
||||
http://stream.tvtap.net:8081/live/nl-npo2.stream/playlist.m3u8?|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0+(iPhone;+CPU+iPhone+OS+17_7+like+Mac+OS+X)+AppleWebKit/605.1.15+(KHTML,+like+Gecko)+Version/18.0+Mobile/15E148+Safari/604.1"|Origin="https://origin.xyz"
|
||||
#EXTINF:-1 tvg-id="NPO2.nl@SD",NPO 2 [Geo-blocked]
|
||||
http://resolver.streaming.api.nos.nl/livestream?url=/live/npo/tvlive/npo2/npo22.isml/.m3u8
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="TFX.fr@SD",TFX
|
||||
#EXTINF:-1 tvg-id="TFX.fr@SD",TFX (720p)
|
||||
#EXTVLCOPT:http-referrer=https://pkpakiplay.xyz/
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1
|
||||
https://stitcher-ipv4.pluto.tv/v1/stitch/embed/hls/channel/64c109a4798def0008a6e03e/master.m3u8?advertisingId={PSID}&appVersion=unknown&deviceDNT={TARGETOPT}&deviceId={PSID}&deviceLat=0&deviceLon=0&deviceMake=samsung&deviceModel=samsung&deviceType=samsung-tvplus&deviceVersion=unknown&embedPartner=samsung-tvplus&profileFloor=&profileLimit=&samsung_app_domain={APP_DOMAIN}&samsung_app_name={APP_NAME}&us_privacy=1YNY
|
||||
https://stitcher-ipv4.pluto.tv/v1/stitch/embed/hls/channel/64c109a4798def0008a6e03e/master.mpd?advertisingId={PSID}&appVersion=unknown&deviceDNT={TARGETOPT}&deviceId={PSID}&deviceLat=0&deviceLon=0&deviceMake=samsung&deviceModel=samsung&deviceType=samsung-tvplus&deviceVersion=unknown&embedPartner=samsung-tvplus&profileFloor=&profileLimit=&samsung_app_domain={APP_DOMAIN}&samsung_app_name={APP_NAME}&us_privacy=1YNY
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/index.m3u8
|
||||
#EXTINF:-1 tvg-id="BBCNews.uk",BBC News HD (480p) [Geo-blocked]
|
||||
http://1111296894.rsc.cdn77.org/LS-ATL-54548-6/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="BeanoTV.uk@SD",Beano TV
|
||||
#EXTINF:-1 tvg-id="BeanoTV.uk@SD",Beano TV (1080p)
|
||||
https://a5b4bacecd47433dad06d3189fc7422e.mediatailor.us-east-1.amazonaws.com/v1/manifest/04fd913bb278d8775298c26fdca9d9841f37601f/RakutenTV-eu_BeanoTV/b1f233d5-847c-437d-aa4f-f73e67a85323/2.m3u8|Referer="https://referer.xyz/"|User-Agent="Mozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1"|Origin="https://origin.xyz"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="FastTV.us@SD",Fast TV
|
||||
#EXTINF:-1 tvg-id="FastTV.us@SD",Fast TV (1080p)
|
||||
https://3fa797d5.wurl.com/manifest/f36d25e7e52f1ba8d7e56eb859c636563214f541/T05PX01vdG9yVHJlbmRGYXN0VFZfSExT/b5e5e0e2-12b3-4312-93c9-c0a7c50b41ca/4.m3u8
|
||||
|
||||
@@ -538,7 +538,7 @@ module.exports = [
|
||||
closed_at: null,
|
||||
author_association: 'COLLABORATOR',
|
||||
active_lock_reason: null,
|
||||
body: '### Stream ID\n\nTFX.fr\n\n### Stream URL\n\nhttps://stitcher-ipv4.pluto.tv/v1/stitch/embed/hls/channel/64c109a4798def0008a6e03e/master.m3u8?advertisingId={PSID}&appVersion=unknown&deviceDNT={TARGETOPT}&deviceId={PSID}&deviceLat=0&deviceLon=0&deviceMake=samsung&deviceModel=samsung&deviceType=samsung-tvplus&deviceVersion=unknown&embedPartner=samsung-tvplus&profileFloor=&profileLimit=&samsung_app_domain={APP_DOMAIN}&samsung_app_name={APP_NAME}&us_privacy=1YNY\n\n### Label\n\nNone\n\n### HTTP User Agent\n\nMozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1\n\n### HTTP Referrer\n\nhttps://pkpakiplay.xyz/\n\n### Notes (optional)\n\nSource: https://github.com/iptv-org/iptv-org.github.io/issues/1381\n\n### Contributing Guide\n\n- [X] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)',
|
||||
body: '### Stream ID\n\nTFX.fr\n\n### Stream URL\n\nhttps://stitcher-ipv4.pluto.tv/v1/stitch/embed/hls/channel/64c109a4798def0008a6e03e/master.mpd?advertisingId={PSID}&appVersion=unknown&deviceDNT={TARGETOPT}&deviceId={PSID}&deviceLat=0&deviceLon=0&deviceMake=samsung&deviceModel=samsung&deviceType=samsung-tvplus&deviceVersion=unknown&embedPartner=samsung-tvplus&profileFloor=&profileLimit=&samsung_app_domain={APP_DOMAIN}&samsung_app_name={APP_NAME}&us_privacy=1YNY\n\n### Label\n\nNone\n\n### HTTP User Agent\n\nMozilla/5.0 (iPhone; CPU iPhone OS 17_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Mobile/15E148 Safari/604.1\n\n### HTTP Referrer\n\nhttps://pkpakiplay.xyz/\n\n### Notes (optional)\n\nSource: https://github.com/iptv-org/iptv-org.github.io/issues/1381\n\n### Contributing Guide\n\n- [X] I have read [Contributing Guide](https://github.com/iptv-org/iptv/blob/master/CONTRIBUTING.md)',
|
||||
reactions: {
|
||||
url: 'https://api.github.com/repos/iptv-org/iptv/issues/14175/reactions',
|
||||
total_count: 0,
|
||||
|
||||
5
tests/__data__/input/playlist_format/at.m3u
Normal file
5
tests/__data__/input/playlist_format/at.m3u
Normal file
@@ -0,0 +1,5 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="HitradioO3.at@SD",Hitradio O3
|
||||
https://studiocam-oe3.mdn.ors.at/orf/studiocam_oe3/q6a/manifest.mpd
|
||||
#EXTINF:-1 tvg-id="HitradioO3.at@SD",Hitradio O3
|
||||
https://studiocam-oe3.mdn.ors.at/orf/studiocam_oe3/q6a/master.m3u8
|
||||
@@ -1,3 +1,3 @@
|
||||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="mn.in" http-referrer="http://test.com" http-user-agent="Mozilla/5.0",Manorama News -2 [U3] (480p) [Geo-blocked] [Not 24/7]
|
||||
#EXTINF:-1 tvg-id="mn.in" http-referrer="http://test.com" http-user-agent="Mozilla/5.0",Manorama News -2 [U3] (Alt) [Geo-blocked] [Not 24/7]
|
||||
https://ythls.onrender.com/channel/UCP0uG-mcMImgKnJz-VjJZmQ.m3u8
|
||||
|
||||
45
tests/__data__/input/playlist_update/manifest.mpd
Normal file
45
tests/__data__/input/playlist_update/manifest.mpd
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- -->
|
||||
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:dash:schema:mpd:2011" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd" type="dynamic" availabilityStartTime="1970-01-01T00:00:00Z" publishTime="2026-03-24T17:45:06.924442Z" minimumUpdatePeriod="PT4S" timeShiftBufferDepth="PT31.840S" maxSegmentDuration="PT5S" minBufferTime="PT2S" profiles="urn:hbbtv:dash:profile:isoff-live:2012,urn:hbbtv:dash:profile:isoff-live:2012">
|
||||
<Period id="1" start="PT0S">
|
||||
<BaseURL>dash/</BaseURL>
|
||||
<AdaptationSet id="1" group="1" contentType="audio" segmentAlignment="true" audioSamplingRate="48000" mimeType="audio/mp4" codecs="mp4a.40.2" startWithSAP="1">
|
||||
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
|
||||
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
|
||||
<SegmentTemplate timescale="48000" initialization="studiocam_oe3-$RepresentationID$.dash" media="studiocam_oe3-$RepresentationID$-$Time$.dash">
|
||||
<!-- 2026-03-24T17:44:28.465875Z / 1774374268 - 2026-03-24T17:45:00.316541Z -->
|
||||
<SegmentTimeline>
|
||||
<S t="85169964886362" d="230400"/>
|
||||
<S d="207872"/>
|
||||
<S d="160768"/>
|
||||
<S d="219136"/>
|
||||
<S d="189440"/>
|
||||
<S d="179200"/>
|
||||
<S d="160768"/>
|
||||
<S d="181248"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
<Representation id="audio_192257_und=192000" bandwidth="192000">
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
<AdaptationSet id="2" group="2" contentType="video" par="16:9" segmentAlignment="true" width="1280" height="720" sar="1:1" frameRate="50" mimeType="video/mp4" codecs="avc1.4D4028" startWithSAP="1">
|
||||
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="main"/>
|
||||
<SegmentTemplate timescale="600" initialization="studiocam_oe3-$RepresentationID$.dash" media="studiocam_oe3-$RepresentationID$-$Time$.dash">
|
||||
<!-- 2026-03-24T17:44:28.456666Z / 1774374268 - 2026-03-24T17:45:00.296666Z -->
|
||||
<SegmentTimeline>
|
||||
<S t="1064624561074" d="2880"/>
|
||||
<S d="2592"/>
|
||||
<S d="2016"/>
|
||||
<S d="2736"/>
|
||||
<S d="2376"/>
|
||||
<S d="2232"/>
|
||||
<S d="2016"/>
|
||||
<S d="2256"/>
|
||||
</SegmentTimeline>
|
||||
</SegmentTemplate>
|
||||
<Representation id="video=2000000" bandwidth="2000000" scanType="progressive">
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
<UTCTiming schemeIdUri="urn:mpeg:dash:utc:http-iso:2014" value="https://time.akamai.com/?iso"/>
|
||||
</MPD>
|
||||
12
tests/__data__/input/playlist_update/playlist.m3u8
Normal file
12
tests/__data__/input/playlist_update/playlist.m3u8
Normal file
@@ -0,0 +1,12 @@
|
||||
#EXTM3U
|
||||
## Generated with https://github.com/shaka-project/shaka-packager version v3.4.2-c819dea-release
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-MEDIA:TYPE=AUDIO,URI="stream_4.m3u8",GROUP-ID="default-audio-group",LANGUAGE="pl",NAME="stream_4",DEFAULT=NO,AUTOSELECT=YES,CHANNELS="2"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=3688787,AVERAGE-BANDWIDTH=8948756,CODECS="avc1.424028,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=25.000,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE
|
||||
stream_0.m3u8
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1988787,AVERAGE-BANDWIDTH=3688345,CODECS="avc1.42401f,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=25.000,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE
|
||||
stream_1.m3u8
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=1188787,AVERAGE-BANDWIDTH=1774276,CODECS="avc1.42401e,mp4a.40.2",RESOLUTION=853x480,FRAME-RATE=25.000,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE
|
||||
stream_2.m3u8
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=688787,AVERAGE-BANDWIDTH=766090,CODECS="avc1.42401e,mp4a.40.2",RESOLUTION=640x360,FRAME-RATE=25.000,AUDIO="default-audio-group",CLOSED-CAPTIONS=NONE
|
||||
stream_3.m3u8
|
||||
@@ -8,7 +8,7 @@ const ENV_VAR =
|
||||
|
||||
beforeEach(() => {
|
||||
fs.emptyDirSync('tests/__data__/output')
|
||||
fs.copySync('tests/__data__/input/playlist_update', 'tests/__data__/output/streams')
|
||||
fs.copySync('tests/__data__/input/playlist_update/streams', 'tests/__data__/output/streams')
|
||||
})
|
||||
|
||||
describe('playlist:update', () => {
|
||||
|
||||
Reference in New Issue
Block a user