mirror of
https://github.com/iptv-org/iptv.git
synced 2025-05-13 02:20:03 -04:00
Merge branch 'master' into Carlinhos027-patch-2
This commit is contained in:
commit
c99694aa7b
225 changed files with 4063 additions and 3092 deletions
9
.github/ISSUE_TEMPLATE/1_streams_add.yml
vendored
9
.github/ISSUE_TEMPLATE/1_streams_add.yml
vendored
|
@ -6,9 +6,9 @@ labels: ['streams:add']
|
|||
body:
|
||||
- type: input
|
||||
attributes:
|
||||
label: Channel ID (required)
|
||||
description: Unique channel ID from [iptv-org.github.io](https://iptv-org.github.io/). If you can't find the channel you want in the list, please let us know through this [form](https://github.com/iptv-org/database/issues/new?assignees=&labels=channels%3Aadd&projects=&template=channels_add.yml&title=Add%3A+) before posting your request.
|
||||
placeholder: 'BBCAmericaEast.us'
|
||||
label: Stream ID (required)
|
||||
description: "ID of the stream consisting of `<channel_id>` or `<channel_id>@<feed_id>`. Full list of supported channels with corresponding ID could be found on [iptv-org.github.io](https://iptv-org.github.io/). If you can't find the channel you want in the list, please let us know through this [form](https://github.com/iptv-org/database/issues/new?assignees=&labels=channels%3Aadd&projects=&template=channels_add.yml&title=Add%3A+) before posting your request."
|
||||
placeholder: 'BBCAmerica.us@East'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
@ -28,9 +28,12 @@ body:
|
|||
- 2160p
|
||||
- 1280p
|
||||
- 1080p
|
||||
- 1080i
|
||||
- 720p
|
||||
- 576p
|
||||
- 576i
|
||||
- 480p
|
||||
- 480i
|
||||
- 360p
|
||||
|
||||
- type: dropdown
|
||||
|
|
9
.github/ISSUE_TEMPLATE/2_streams_edit.yml
vendored
9
.github/ISSUE_TEMPLATE/2_streams_edit.yml
vendored
|
@ -19,9 +19,9 @@ body:
|
|||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Channel ID
|
||||
description: Channel ID from [iptv-org.github.io](https://iptv-org.github.io/).
|
||||
placeholder: 'BBCAmericaEast.us'
|
||||
label: Stream ID
|
||||
description: "ID of the stream consisting of `<channel_id>` or `<channel_id>@<feed_id>`. Full list of supported channels with corresponding ID could be found on [iptv-org.github.io](https://iptv-org.github.io/). If you can't find the channel you want in the list, please let us know through this [form](https://github.com/iptv-org/database/issues/new?assignees=&labels=channels%3Aadd&projects=&template=channels_add.yml&title=Add%3A+) before posting your request."
|
||||
placeholder: 'BBCAmerica.us@East'
|
||||
|
||||
- type: dropdown
|
||||
attributes:
|
||||
|
@ -31,9 +31,12 @@ body:
|
|||
- 2160p
|
||||
- 1280p
|
||||
- 1080p
|
||||
- 1080i
|
||||
- 720p
|
||||
- 576p
|
||||
- 576i
|
||||
- 480p
|
||||
- 480i
|
||||
- 360p
|
||||
- '~'
|
||||
|
||||
|
|
2
.github/ISSUE_TEMPLATE/3_broken-stream.yml
vendored
2
.github/ISSUE_TEMPLATE/3_broken-stream.yml
vendored
|
@ -1,7 +1,7 @@
|
|||
name: 🚧 Report broken stream
|
||||
description: Report a broken or unstable stream
|
||||
title: 'Broken: '
|
||||
labels: ['broken stream']
|
||||
labels: ['broken stream', 'streams:remove']
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
| News | Programming is mostly news |
|
||||
| Outdoor | Programming related to outdoor activities like fishing, hunting, etc. |
|
||||
| Relax | Programming is calm sounding and beautiful views |
|
||||
| Religious | Religious Programming |
|
||||
| Religious | Religious programming |
|
||||
| Science | Science and Technology |
|
||||
| Series | Channels that only show series |
|
||||
| Shop | Programming is for shopping |
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
## Supported Regions
|
||||
|
||||
| Code | Description |
|
||||
| ------------------------------------------------------------------------ | -------------------------------------- |
|
||||
| [AFR](https://en.wikipedia.org/wiki/Africa) | Africa |
|
||||
| [AMER](https://en.wikipedia.org/wiki/Americas) | Americas |
|
||||
| [APAC](https://en.wikipedia.org/wiki/Asia-Pacific) | Asia-Pacific |
|
||||
| [ARAB](https://en.wikipedia.org/wiki/Arab_world) | Arab world |
|
||||
| [ASEAN](https://en.wikipedia.org/wiki/ASEAN) | Association of Southeast Asian Nations |
|
||||
| [ASIA](https://en.wikipedia.org/wiki/Asia) | Asia |
|
||||
| [CARIB](https://en.wikipedia.org/wiki/Caribbean) | Caribbean |
|
||||
| [CAS](https://en.wikipedia.org/wiki/Central_Asia) | Central Asia |
|
||||
| [CENAMER](https://en.wikipedia.org/wiki/Central_America) | Central America |
|
||||
| [CIS](https://en.wikipedia.org/wiki/Commonwealth_of_Independent_States) | Commonwealth of Independent States |
|
||||
| [EMEA](https://en.wikipedia.org/wiki/Europe,_the_Middle_East_and_Africa) | Europe, the Middle East and Africa |
|
||||
| [EUR](https://en.wikipedia.org/wiki/Europe) | Europe |
|
||||
| [HISPAM](https://en.wikipedia.org/wiki/Hispanic_America) | Hispanic America |
|
||||
| [LAC](https://en.wikipedia.org/wiki/Latin_America_and_the_Caribbean) | Latin America and the Caribbean |
|
||||
| [LATAM](https://en.wikipedia.org/wiki/Latin_America) | Latin America |
|
||||
| [MAGHREB](https://en.wikipedia.org/wiki/Maghreb) | Maghreb |
|
||||
| [MENA](https://en.wikipedia.org/wiki/MENA) | Middle East and North Africa |
|
||||
| [MIDEAST](https://en.wikipedia.org/wiki/Middle_East) | Middle East |
|
||||
| [NAM](https://en.wikipedia.org/wiki/Northern_America) | Northern America |
|
||||
| [NORAM](https://en.wikipedia.org/wiki/North_America) | North America |
|
||||
| [NORD](https://en.wikipedia.org/wiki/Nordic_countries) | Nordics |
|
||||
| [OCE](https://en.wikipedia.org/wiki/Oceania) | Oceania |
|
||||
| [SAS](https://en.wikipedia.org/wiki/South_Asia) | South Asia |
|
||||
| [SSA](https://en.wikipedia.org/wiki/Sub-Saharan_Africa) | Sub-Saharan Africa |
|
||||
| [WAFR](https://en.wikipedia.org/wiki/West_Africa) | West Africa |
|
||||
| [INT](https://en.wikipedia.org/wiki/West_Africa) | Worldwide |
|
|
@ -36,12 +36,12 @@ https://iptv-org.github.io/iptv/index.m3u
|
|||
|
||||
### Grouped by category
|
||||
|
||||
Playlists in which channels are grouped by category. A list of all supported categories with descriptions can be found [here](.readme/supported-categories.md).
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
<br>
|
||||
|
||||
Playlist in which each channel has its _category_ as a group title:
|
||||
|
||||
```
|
||||
https://iptv-org.github.io/iptv/index.category.m3u
|
||||
```
|
||||
|
@ -55,12 +55,12 @@ Same thing, but split up into separate files:
|
|||
|
||||
### Grouped by language
|
||||
|
||||
Playlists in which channels are grouped by the language in which they are broadcast.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
<br>
|
||||
|
||||
Playlist in which each channel has its _language_ as a group title:
|
||||
|
||||
```
|
||||
https://iptv-org.github.io/iptv/index.language.m3u
|
||||
```
|
||||
|
@ -74,12 +74,12 @@ Same thing, but split up into separate files:
|
|||
|
||||
### Grouped by country
|
||||
|
||||
Playlists in which channels are grouped by country for which they are broadcasted.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
<br>
|
||||
|
||||
Playlist in which each channel has its _country_ as a group title:
|
||||
|
||||
```
|
||||
https://iptv-org.github.io/iptv/index.country.m3u
|
||||
```
|
||||
|
@ -97,7 +97,7 @@ Same thing, but split up into separate files:
|
|||
<summary>Expand</summary>
|
||||
<br>
|
||||
|
||||
Playlist in which each channel has its _region_ as a group title:
|
||||
Playlists in which channels are grouped by the region for which they are broadcasted.
|
||||
|
||||
```
|
||||
https://iptv-org.github.io/iptv/index.region.m3u
|
||||
|
|
|
@ -20,15 +20,23 @@ Regardless of which option you choose, before posting your request please do the
|
|||
|
||||
- Make sure the link you want to add works stably. To check this, open it in one of the players (for example, [VLC player](https://www.videolan.org/vlc/index.html)) and watch the broadcast for at least a minute (some test streams are interrupted after 15-30 seconds).
|
||||
- Make sure the link is not already in the playlist. This can be done by [searching](https://github.com/search?q=repo%3Aiptv-org%2Fiptv+http%3A%2F%2Fexample.com&type=code) the repository.
|
||||
- Find the ID of the channel you want to add in our [database](https://iptv-org.github.io/). If this particular channel is not in the database, then leave a request to add it [here](https://github.com/iptv-org/database/issues/new/choose) and wait until it is approved before continuing.
|
||||
- Make sure the channel is not blocklisted. This can be done by checking the [blocklist.csv](https://github.com/iptv-org/database/blob/master/data/blocklist.csv) file.
|
||||
- Find the ID of the channel you want on [iptv-org.github.io](https://iptv-org.github.io/). If your desired channel is not on the list you can leave a request to add it [here](https://github.com/iptv-org/database/issues/new/choose).
|
||||
- Make sure the channel is not blocklisted. It can also be done through [iptv-org.github.io](https://iptv-org.github.io/).
|
||||
- The link does not lead to the Xtream Codes server. [Why don't you accept links to Xtream Codes server?](FAQ.md#why-dont-you-accept-links-to-xtream-codes-server)
|
||||
- If you know that the broadcast only works in certain countries or it is periodically interrupted, do not forget to indicate this in the request.
|
||||
|
||||
A requests without a valid channel ID or working link to the stream will be closed immediately.
|
||||
A requests without a valid stream ID or working link to the stream will be closed immediately.
|
||||
|
||||
Note all links in playlists are sorted automatically by scripts so there is no need to sort them manually. For more info, see [Scripts](#scripts).
|
||||
|
||||
### How to fix the stream description?
|
||||
|
||||
Most of the stream description (channel name, categories, languages, broadcast area, logo) we load from the [iptv-org/database](https://github.com/iptv-org/database) using the stream ID.
|
||||
|
||||
So first of all, make sure that the desired stream has the correct ID. A full list of all supported channels and their corresponding IDs can be found on [iptv-org.github.io](https://iptv-org.github.io/). To change the stream ID of any link in the playlist, just fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=streams%3Aedit&projects=&template=2_streams_edit.yml&title=Edit%3A+).
|
||||
|
||||
If, however, you have found an error in the database itself, this is the place to go: [How to edit channel description?](https://github.com/iptv-org/database/blob/master/CONTRIBUTING.md#how-to-edit-channel-description)
|
||||
|
||||
### How to distinguish a link to an Xtream Codes server from a regular one?
|
||||
|
||||
Most of them have this form:
|
||||
|
@ -52,6 +60,37 @@ The only thing before publishing your report is to make sure that:
|
|||
|
||||
An issue without a valid link will be closed immediately.
|
||||
|
||||
### How to find a broken stream?
|
||||
|
||||
For starters, you can just try to open the playlist in [VLC player](https://www.videolan.org/vlc/). The player outputs all errors to the log (Tools -> Messages) so you'll be able to determine pretty accurately why a link isn't working.
|
||||
|
||||
Another way to test links is to use the NPM script. To do this, first make sure you have [Node.js](https://nodejs.org/en) installed on your system. Then go to the `iptv` folder using [Console](https://en.wikipedia.org/wiki/Windows_Console) (or [Terminal](<https://en.wikipedia.org/wiki/Terminal_(macOS)>) if you have macOS) and run the command:
|
||||
|
||||
```sh
|
||||
npm run playlist:test path/to/playlist.m3u
|
||||
```
|
||||
|
||||
This command will run an automatic check of all links in the playlist and display their status:
|
||||
|
||||
```sh
|
||||
npm run playlist:test streams/fr.m3u
|
||||
|
||||
streams/fr.m3u
|
||||
┌─────┬───────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────┬───────────────────────────┐
|
||||
│ │ tvg-id │ url │ status │
|
||||
├─────┼───────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────┼───────────────────────────┤
|
||||
│ 0 │ 6ter.fr │ https://origin-caf900c010ea8046.live.6cloud.fr/out/v1/29c7a579af3348b48230f76cd75699a5/dash_short... │ LOADING... │
|
||||
│ 1 │ 20MinutesTV.fr │ https://lives.digiteka.com/stream/86d3e867-a272-496b-8412-f59aa0104771/index.m3u8 │ FFMPEG_STREAMS_NOT_FOUND │
|
||||
│ 2 │ │ https://video1.getstreamhosting.com:1936/8420/8420/playlist.m3u8 │ OK │
|
||||
│ 3 │ ADNTVPlus.fr │ https://samsunguk-adn-samsung-fre-qfrlc.amagi.tv/playlist/samsunguk-adn-samsung-fre/playlist.m3u8 │ HTTP_FORBIDDEN │
|
||||
│ 4 │ Africa24.fr │ https://edge12.vedge.infomaniak.com/livecast/ik:africa24/manifest.m3u8 │ OK │
|
||||
│ 5 │ Africa24English.fr │ https://edge17.vedge.infomaniak.com/livecast/ik:africa24sport/manifest.m3u8 │ OK │
|
||||
│ 6 │ AfricanewsEnglish.fr │ https://37c774660687468c821a51190046facf.mediatailor.us-east-1.amazonaws.com/v1/master/04fd913bb2... │ HTTP_GATEWAY_TIMEOUT │
|
||||
│ 7 │ AlpedHuezTV.fr │ https://edge.vedge.infomaniak.com/livecast/ik:adhtv/chunklist.m3u8 │ HTTP_NOT_FOUND │
|
||||
```
|
||||
|
||||
After that, all you have to do is report any broken streams you find.
|
||||
|
||||
### How do I remove my channel from playlist?
|
||||
|
||||
To request removal of a link to a channel from the repository, you need to fill out this [form](https://github.com/iptv-org/iptv/issues/new?assignees=&labels=removal+request&projects=&template=-removal-request.yml&title=Remove%3A+) and wait for the request to be reviewed (this usually takes no more than 1 business day). And if the request is approved, links to the channel will be immediately removed from the repository.
|
||||
|
@ -65,22 +104,22 @@ Please note that we only accept removal requests from channel owners and their o
|
|||
For a stream to be approved, its description must follow this template:
|
||||
|
||||
```
|
||||
#EXTINF:-1 tvg-id="CHANNEL_ID",CHANNEL_NAME (RESOLUTION) [LABEL]
|
||||
#EXTINF:-1 tvg-id="STREAM_ID",CHANNEL_NAME (QUALITY) [LABEL]
|
||||
STREAM_URL
|
||||
```
|
||||
|
||||
| Attribute | Description | Required | Valid values |
|
||||
| -------------- | ------------------------------------------------------------------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `CHANNEL_ID` | Channel ID. | Optional | Full list of supported channels with corresponding ID could be found on [iptv-org.github.io](https://iptv-org.github.io/). |
|
||||
| `CHANNEL_NAME` | Full name of the channel. May contain any characters except: `,`, `[`, `]`. | Required | - |
|
||||
| `RESOLUTION` | Maximum stream resolution. | Optional | `2160p`, `1080p`, `720p`, `480p`, `360p` etc |
|
||||
| `LABEL` | Specified in cases where the broadcast for some reason may not be available to some users. | Optional | `Geo-blocked` or `Not 24/7` |
|
||||
| `STREAM_URL` | Stream URL. | Required | - |
|
||||
| Attribute | Description | Required | Valid values |
|
||||
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------- |
|
||||
| `STREAM_ID` | ID of the stream. Full list of supported channels with corresponding ID could be found on [iptv-org.github.io](https://iptv-org.github.io/). | Optional | `<channel_id>` or `<channel_id>@<feed_id>` |
|
||||
| `CHANNEL_NAME` | Full name of the channel. May contain any characters except: `,`, `[`, `]`. | Required | - |
|
||||
| `QUALITY` | Maximum stream quality. | Optional | `2160p`, `1080p`, `720p`, `480p`, `360p` etc |
|
||||
| `LABEL` | Specified in cases where the broadcast for some reason may not be available to some users. | Optional | `Geo-blocked` or `Not 24/7` |
|
||||
| `STREAM_URL` | Stream URL. | Required | - |
|
||||
|
||||
Example:
|
||||
|
||||
```xml
|
||||
#EXTINF:-1 tvg-id="ExampleTV.ua",Example TV (720p) [Not 24/7]
|
||||
#EXTINF:-1 tvg-id="ExampleTV.ua@HD",Example TV (720p) [Not 24/7]
|
||||
https://example.com/playlist.m3u8
|
||||
```
|
||||
|
||||
|
@ -110,7 +149,6 @@ http://example.com/stream.m3u8
|
|||
- `config.json`: config for the `markdown-include` package, which is used to compile everything into one `README.md` file.
|
||||
- `preview.png`: image displayed in the `README.md`.
|
||||
- `supported-categories.md`: list of supported categories.
|
||||
- `supported-regions.md`: list of supported regions.
|
||||
- `template.md`: template for `README.md`.
|
||||
- `scripts/`: contains all scripts used in the repository.
|
||||
- `streams/`: contains all streams broken down by the country from which they are broadcast.
|
||||
|
|
30
package-lock.json
generated
30
package-lock.json
generated
|
@ -10,7 +10,7 @@
|
|||
"dependencies": {
|
||||
"@eslint/eslintrc": "^3.3.0",
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@freearhey/core": "^0.2.1",
|
||||
"@freearhey/core": "^0.7.0",
|
||||
"@octokit/core": "^6.1.4",
|
||||
"@octokit/plugin-paginate-rest": "^11.4.3",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^7.1.3",
|
||||
|
@ -1063,9 +1063,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@freearhey/core": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.2.1.tgz",
|
||||
"integrity": "sha512-kEdIxZClykKhGpgyCSlkwuVuSCCAWr3J5YvOUMJQDPgVAYvT5VbD8MYKPm+OwNi9T4HFmF6qqY90qwKJPoOXCA==",
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.7.0.tgz",
|
||||
"integrity": "sha512-HXkKPYGY7ife7JAc1q/Qxzy0WUdSnyt3rHThCShZHgnH3rz0tpkjHFW7LNegB3he0IKn/Zc95/YSOQ97Fq8ctA==",
|
||||
"dependencies": {
|
||||
"@types/fs-extra": "^11.0.2",
|
||||
"@types/lodash": "^4.14.198",
|
||||
|
@ -1078,6 +1078,7 @@
|
|||
"node-gzip": "^1.1.2",
|
||||
"normalize-url": "^6.1.0",
|
||||
"object-treeify": "^2.1.1",
|
||||
"run-script-os": "^1.1.6",
|
||||
"signale": "^1.4.0"
|
||||
}
|
||||
},
|
||||
|
@ -6059,6 +6060,15 @@
|
|||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/run-script-os": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/run-script-os/-/run-script-os-1.1.6.tgz",
|
||||
"integrity": "sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw==",
|
||||
"bin": {
|
||||
"run-os": "index.js",
|
||||
"run-script-os": "index.js"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
|
@ -7455,9 +7465,9 @@
|
|||
}
|
||||
},
|
||||
"@freearhey/core": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.2.1.tgz",
|
||||
"integrity": "sha512-kEdIxZClykKhGpgyCSlkwuVuSCCAWr3J5YvOUMJQDPgVAYvT5VbD8MYKPm+OwNi9T4HFmF6qqY90qwKJPoOXCA==",
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@freearhey/core/-/core-0.7.0.tgz",
|
||||
"integrity": "sha512-HXkKPYGY7ife7JAc1q/Qxzy0WUdSnyt3rHThCShZHgnH3rz0tpkjHFW7LNegB3he0IKn/Zc95/YSOQ97Fq8ctA==",
|
||||
"requires": {
|
||||
"@types/fs-extra": "^11.0.2",
|
||||
"@types/lodash": "^4.14.198",
|
||||
|
@ -7470,6 +7480,7 @@
|
|||
"node-gzip": "^1.1.2",
|
||||
"normalize-url": "^6.1.0",
|
||||
"object-treeify": "^2.1.1",
|
||||
"run-script-os": "^1.1.6",
|
||||
"signale": "^1.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -11122,6 +11133,11 @@
|
|||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"run-script-os": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/run-script-os/-/run-script-os-1.1.6.tgz",
|
||||
"integrity": "sha512-ql6P2LzhBTTDfzKts+Qo4H94VUKpxKDFz6QxxwaUZN0mwvi7L3lpOI7BqPCq7lgDh3XLl0dpeXwfcVIitlrYrw=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.6.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"dependencies": {
|
||||
"@eslint/eslintrc": "^3.3.0",
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@freearhey/core": "^0.2.1",
|
||||
"@freearhey/core": "^0.7.0",
|
||||
"@octokit/core": "^6.1.4",
|
||||
"@octokit/plugin-paginate-rest": "^11.4.3",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^7.1.3",
|
||||
|
|
|
@ -1,21 +1,37 @@
|
|||
import { Logger, Storage } from '@freearhey/core'
|
||||
import { API_DIR, STREAMS_DIR } from '../../constants'
|
||||
import { Logger, Storage, Collection } from '@freearhey/core'
|
||||
import { API_DIR, STREAMS_DIR, DATA_DIR } from '../../constants'
|
||||
import { PlaylistParser } from '../../core'
|
||||
import { Stream } from '../../models'
|
||||
import { Stream, Channel, Feed } from '../../models'
|
||||
import { uniqueId } from 'lodash'
|
||||
|
||||
async function main() {
|
||||
const logger = new Logger()
|
||||
|
||||
logger.info('loading api data...')
|
||||
const dataStorage = new Storage(DATA_DIR)
|
||||
const channelsData = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsData).map(data => new Channel(data))
|
||||
const channelsGroupedById = channels.keyBy((channel: Channel) => channel.id)
|
||||
const feedsData = await dataStorage.json('feeds.json')
|
||||
const feeds = new Collection(feedsData).map(data =>
|
||||
new Feed(data).withChannel(channelsGroupedById)
|
||||
)
|
||||
const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) =>
|
||||
feed.channel ? feed.channel.id : uniqueId()
|
||||
)
|
||||
|
||||
logger.info('loading streams...')
|
||||
const streamsStorage = new Storage(STREAMS_DIR)
|
||||
const parser = new PlaylistParser({ storage: streamsStorage })
|
||||
const parser = new PlaylistParser({
|
||||
storage: streamsStorage,
|
||||
channelsGroupedById,
|
||||
feedsGroupedByChannelId
|
||||
})
|
||||
const files = await streamsStorage.list('**/*.m3u')
|
||||
let streams = await parser.parse(files)
|
||||
streams = streams
|
||||
.map(data => new Stream(data))
|
||||
.orderBy([(stream: Stream) => stream.channel])
|
||||
.orderBy((stream: Stream) => stream.getId())
|
||||
.map((stream: Stream) => stream.toJSON())
|
||||
|
||||
logger.info(`found ${streams.count()} streams`)
|
||||
|
||||
logger.info('saving to .api/streams.json...')
|
||||
|
|
|
@ -12,7 +12,9 @@ async function main() {
|
|||
client.download('countries.json'),
|
||||
client.download('languages.json'),
|
||||
client.download('regions.json'),
|
||||
client.download('subdivisions.json')
|
||||
client.download('subdivisions.json'),
|
||||
client.download('feeds.json'),
|
||||
client.download('timezones.json')
|
||||
]
|
||||
|
||||
await Promise.all(requests)
|
||||
|
|
|
@ -1,25 +1,36 @@
|
|||
import { Logger, Storage, Collection } from '@freearhey/core'
|
||||
import { STREAMS_DIR, DATA_DIR } from '../../constants'
|
||||
import { PlaylistParser } from '../../core'
|
||||
import { Stream, Playlist, Channel } from '../../models'
|
||||
import { Stream, Playlist, Channel, Feed } from '../../models'
|
||||
import { program } from 'commander'
|
||||
import { uniqueId } from 'lodash'
|
||||
|
||||
program.argument('[filepath]', 'Path to file to validate').parse(process.argv)
|
||||
|
||||
async function main() {
|
||||
const storage = new Storage(STREAMS_DIR)
|
||||
const streamsStorage = new Storage(STREAMS_DIR)
|
||||
const logger = new Logger()
|
||||
|
||||
logger.info('loading channels from api...')
|
||||
logger.info('loading data from api...')
|
||||
const dataStorage = new Storage(DATA_DIR)
|
||||
const channelsContent = await dataStorage.json('channels.json')
|
||||
const groupedChannels = new Collection(channelsContent)
|
||||
.map(data => new Channel(data))
|
||||
.keyBy((channel: Channel) => channel.id)
|
||||
const channelsData = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsData).map(data => new Channel(data))
|
||||
const channelsGroupedById = channels.keyBy((channel: Channel) => channel.id)
|
||||
const feedsData = await dataStorage.json('feeds.json')
|
||||
const feeds = new Collection(feedsData).map(data =>
|
||||
new Feed(data).withChannel(channelsGroupedById)
|
||||
)
|
||||
const feedsGroupedByChannelId = feeds.groupBy(feed =>
|
||||
feed.channel ? feed.channel.id : uniqueId()
|
||||
)
|
||||
|
||||
logger.info('loading streams...')
|
||||
const parser = new PlaylistParser({ storage })
|
||||
const files = program.args.length ? program.args : await storage.list('**/*.m3u')
|
||||
const parser = new PlaylistParser({
|
||||
storage: streamsStorage,
|
||||
channelsGroupedById,
|
||||
feedsGroupedByChannelId
|
||||
})
|
||||
const files = program.args.length ? program.args : await streamsStorage.list('**/*.m3u')
|
||||
let streams = await parser.parse(files)
|
||||
|
||||
logger.info(`found ${streams.count()} streams`)
|
||||
|
@ -35,8 +46,8 @@ async function main() {
|
|||
|
||||
logger.info('removing wrong id...')
|
||||
streams = streams.map((stream: Stream) => {
|
||||
if (groupedChannels.missing(stream.channel)) {
|
||||
stream.channel = ''
|
||||
if (!stream.channel || channelsGroupedById.missing(stream.channel.id)) {
|
||||
stream.id = ''
|
||||
}
|
||||
|
||||
return stream
|
||||
|
@ -46,22 +57,22 @@ async function main() {
|
|||
streams = streams.orderBy(
|
||||
[
|
||||
(stream: Stream) => stream.name,
|
||||
(stream: Stream) => parseInt(stream.quality.replace('p', '')),
|
||||
(stream: Stream) => stream.label,
|
||||
(stream: Stream) => stream.getVerticalResolution(),
|
||||
(stream: Stream) => stream.getLabel(),
|
||||
(stream: Stream) => stream.url
|
||||
],
|
||||
['asc', 'desc', 'asc', 'asc']
|
||||
)
|
||||
|
||||
logger.info('saving...')
|
||||
const groupedStreams = streams.groupBy((stream: Stream) => stream.filepath)
|
||||
const groupedStreams = streams.groupBy((stream: Stream) => stream.getFilepath())
|
||||
for (let filepath of groupedStreams.keys()) {
|
||||
const streams = groupedStreams.get(filepath) || []
|
||||
|
||||
if (!streams.length) return
|
||||
|
||||
const playlist = new Playlist(streams, { public: false })
|
||||
await storage.save(filepath, playlist.toString())
|
||||
await streamsStorage.save(filepath, playlist.toString())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
import { Logger, Storage, Collection, File } from '@freearhey/core'
|
||||
import { Logger, Storage, Collection } from '@freearhey/core'
|
||||
import { PlaylistParser } from '../../core'
|
||||
import { Stream, Category, Channel, Language, Country, Region, Subdivision } from '../../models'
|
||||
import _ from 'lodash'
|
||||
import {
|
||||
Stream,
|
||||
Category,
|
||||
Channel,
|
||||
Language,
|
||||
Country,
|
||||
Region,
|
||||
Subdivision,
|
||||
Feed,
|
||||
Timezone
|
||||
} from '../../models'
|
||||
import { uniqueId } from 'lodash'
|
||||
import {
|
||||
CategoriesGenerator,
|
||||
CountriesGenerator,
|
||||
LanguagesGenerator,
|
||||
RegionsGenerator,
|
||||
IndexGenerator,
|
||||
IndexNsfwGenerator,
|
||||
IndexCategoryGenerator,
|
||||
IndexCountryGenerator,
|
||||
IndexLanguageGenerator,
|
||||
|
@ -19,123 +28,136 @@ import { DATA_DIR, LOGS_DIR, STREAMS_DIR } from '../../constants'
|
|||
async function main() {
|
||||
const logger = new Logger()
|
||||
const dataStorage = new Storage(DATA_DIR)
|
||||
|
||||
logger.info('loading data from api...')
|
||||
const channelsContent = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsContent).map(data => new Channel(data))
|
||||
const categoriesContent = await dataStorage.json('categories.json')
|
||||
const categories = new Collection(categoriesContent).map(data => new Category(data))
|
||||
const countriesContent = await dataStorage.json('countries.json')
|
||||
const countries = new Collection(countriesContent).map(data => new Country(data))
|
||||
const languagesContent = await dataStorage.json('languages.json')
|
||||
const languages = new Collection(languagesContent).map(data => new Language(data))
|
||||
const regionsContent = await dataStorage.json('regions.json')
|
||||
const regions = new Collection(regionsContent).map(data => new Region(data))
|
||||
const subdivisionsContent = await dataStorage.json('subdivisions.json')
|
||||
const subdivisions = new Collection(subdivisionsContent).map(data => new Subdivision(data))
|
||||
|
||||
logger.info('loading streams...')
|
||||
let streams = await loadStreams({ channels, categories, languages })
|
||||
let totalStreams = streams.count()
|
||||
streams = streams.uniqBy((stream: Stream) => (stream.channel || _.uniqueId()) + stream.timeshift)
|
||||
logger.info(`found ${totalStreams} streams (including ${streams.count()} unique)`)
|
||||
|
||||
const generatorsLogger = new Logger({
|
||||
stream: await new Storage(LOGS_DIR).createStream(`generators.log`)
|
||||
})
|
||||
|
||||
logger.info('loading data from api...')
|
||||
const categoriesData = await dataStorage.json('categories.json')
|
||||
const countriesData = await dataStorage.json('countries.json')
|
||||
const languagesData = await dataStorage.json('languages.json')
|
||||
const regionsData = await dataStorage.json('regions.json')
|
||||
const subdivisionsData = await dataStorage.json('subdivisions.json')
|
||||
const timezonesData = await dataStorage.json('timezones.json')
|
||||
const channelsData = await dataStorage.json('channels.json')
|
||||
const feedsData = await dataStorage.json('feeds.json')
|
||||
|
||||
logger.info('preparing data...')
|
||||
const subdivisions = new Collection(subdivisionsData).map(data => new Subdivision(data))
|
||||
const subdivisionsGroupedByCode = subdivisions.keyBy(
|
||||
(subdivision: Subdivision) => subdivision.code
|
||||
)
|
||||
const subdivisionsGroupedByCountryCode = subdivisions.groupBy(
|
||||
(subdivision: Subdivision) => subdivision.countryCode
|
||||
)
|
||||
let regions = new Collection(regionsData).map(data =>
|
||||
new Region(data).withSubdivisions(subdivisions)
|
||||
)
|
||||
const regionsGroupedByCode = regions.keyBy((region: Region) => region.code)
|
||||
const categories = new Collection(categoriesData).map(data => new Category(data))
|
||||
const categoriesGroupedById = categories.keyBy((category: Category) => category.id)
|
||||
const languages = new Collection(languagesData).map(data => new Language(data))
|
||||
const languagesGroupedByCode = languages.keyBy((language: Language) => language.code)
|
||||
const countries = new Collection(countriesData).map(data =>
|
||||
new Country(data)
|
||||
.withRegions(regions)
|
||||
.withLanguage(languagesGroupedByCode)
|
||||
.withSubdivisions(subdivisionsGroupedByCountryCode)
|
||||
)
|
||||
const countriesGroupedByCode = countries.keyBy((country: Country) => country.code)
|
||||
regions = regions.map((region: Region) => region.withCountries(countriesGroupedByCode))
|
||||
|
||||
const timezones = new Collection(timezonesData).map(data =>
|
||||
new Timezone(data).withCountries(countriesGroupedByCode)
|
||||
)
|
||||
const timezonesGroupedById = timezones.keyBy((timezone: Timezone) => timezone.id)
|
||||
|
||||
const channels = new Collection(channelsData).map(data =>
|
||||
new Channel(data)
|
||||
.withCategories(categoriesGroupedById)
|
||||
.withCountry(countriesGroupedByCode)
|
||||
.withSubdivision(subdivisionsGroupedByCode)
|
||||
)
|
||||
const channelsGroupedById = channels.keyBy((channel: Channel) => channel.id)
|
||||
const feeds = new Collection(feedsData).map(data =>
|
||||
new Feed(data)
|
||||
.withChannel(channelsGroupedById)
|
||||
.withLanguages(languagesGroupedByCode)
|
||||
.withTimezones(timezonesGroupedById)
|
||||
.withBroadcastCountries(
|
||||
countriesGroupedByCode,
|
||||
regionsGroupedByCode,
|
||||
subdivisionsGroupedByCode
|
||||
)
|
||||
.withBroadcastRegions(regions)
|
||||
.withBroadcastSubdivisions(subdivisionsGroupedByCode)
|
||||
)
|
||||
const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) =>
|
||||
feed.channel ? feed.channel.id : uniqueId()
|
||||
)
|
||||
|
||||
logger.info('loading streams...')
|
||||
const storage = new Storage(STREAMS_DIR)
|
||||
const parser = new PlaylistParser({
|
||||
storage,
|
||||
channelsGroupedById,
|
||||
feedsGroupedByChannelId
|
||||
})
|
||||
const files = await storage.list('**/*.m3u')
|
||||
let streams = await parser.parse(files)
|
||||
const totalStreams = streams.count()
|
||||
streams = streams.uniqBy((stream: Stream) =>
|
||||
stream.hasId() ? stream.getChannelId() + stream.getFeedId() : uniqueId()
|
||||
)
|
||||
logger.info(`found ${totalStreams} streams (including ${streams.count()} unique)`)
|
||||
|
||||
logger.info('sorting streams...')
|
||||
streams = streams.orderBy(
|
||||
[
|
||||
(stream: Stream) => stream.getId(),
|
||||
(stream: Stream) => stream.getVerticalResolution(),
|
||||
(stream: Stream) => stream.getLabel()
|
||||
],
|
||||
['asc', 'asc', 'desc']
|
||||
)
|
||||
|
||||
logger.info('generating categories/...')
|
||||
await new CategoriesGenerator({ categories, streams, logger: generatorsLogger }).generate()
|
||||
|
||||
logger.info('generating countries/...')
|
||||
await new CountriesGenerator({
|
||||
countries,
|
||||
streams,
|
||||
regions,
|
||||
subdivisions,
|
||||
logger: generatorsLogger
|
||||
}).generate()
|
||||
|
||||
logger.info('generating languages/...')
|
||||
await new LanguagesGenerator({ streams, logger: generatorsLogger }).generate()
|
||||
|
||||
logger.info('generating regions/...')
|
||||
await new RegionsGenerator({
|
||||
streams,
|
||||
regions,
|
||||
subdivisions,
|
||||
logger: generatorsLogger
|
||||
}).generate()
|
||||
|
||||
logger.info('generating index.m3u...')
|
||||
await new IndexGenerator({ streams, logger: generatorsLogger }).generate()
|
||||
|
||||
logger.info('generating index.category.m3u...')
|
||||
await new IndexCategoryGenerator({ streams, logger: generatorsLogger }).generate()
|
||||
|
||||
logger.info('generating index.country.m3u...')
|
||||
await new IndexCountryGenerator({
|
||||
streams,
|
||||
countries,
|
||||
regions,
|
||||
subdivisions,
|
||||
logger: generatorsLogger
|
||||
}).generate()
|
||||
|
||||
logger.info('generating index.language.m3u...')
|
||||
await new IndexLanguageGenerator({ streams, logger: generatorsLogger }).generate()
|
||||
|
||||
logger.info('generating index.region.m3u...')
|
||||
await new IndexRegionGenerator({ streams, regions, logger: generatorsLogger }).generate()
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
async function loadStreams({
|
||||
channels,
|
||||
categories,
|
||||
languages
|
||||
}: {
|
||||
channels: Collection
|
||||
categories: Collection
|
||||
languages: Collection
|
||||
}) {
|
||||
const groupedChannels = channels.keyBy(channel => channel.id)
|
||||
const groupedCategories = categories.keyBy(category => category.id)
|
||||
const groupedLanguages = languages.keyBy(language => language.code)
|
||||
|
||||
const storage = new Storage(STREAMS_DIR)
|
||||
const parser = new PlaylistParser({ storage })
|
||||
const files = await storage.list('**/*.m3u')
|
||||
let streams = await parser.parse(files)
|
||||
|
||||
streams = streams
|
||||
.orderBy(
|
||||
[
|
||||
(stream: Stream) => stream.channel,
|
||||
(stream: Stream) => parseInt(stream.quality.replace('p', '')),
|
||||
(stream: Stream) => stream.label
|
||||
],
|
||||
['asc', 'asc', 'desc', 'asc']
|
||||
)
|
||||
.map((stream: Stream) => {
|
||||
const channel: Channel | undefined = groupedChannels.get(stream.channel)
|
||||
|
||||
if (channel) {
|
||||
const channelCategories = channel.categories
|
||||
.map((id: string) => groupedCategories.get(id))
|
||||
.filter(Boolean)
|
||||
const channelLanguages = channel.languages
|
||||
.map((id: string) => groupedLanguages.get(id))
|
||||
.filter(Boolean)
|
||||
|
||||
stream.categories = channelCategories
|
||||
stream.languages = channelLanguages
|
||||
stream.broadcastArea = channel.broadcastArea
|
||||
stream.isNSFW = channel.isNSFW
|
||||
if (channel.logo) stream.logo = channel.logo
|
||||
} else {
|
||||
const file = new File(stream.filepath)
|
||||
const [_, countryCode] = file.name().match(/^([a-z]{2})(_|$)/) || [null, null]
|
||||
const defaultBroadcastArea = countryCode ? [`c/${countryCode.toUpperCase()}`] : []
|
||||
|
||||
stream.broadcastArea = new Collection(defaultBroadcastArea)
|
||||
}
|
||||
|
||||
return stream
|
||||
})
|
||||
|
||||
return streams
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Logger, Storage, Collection } from '@freearhey/core'
|
||||
import { ROOT_DIR, STREAMS_DIR } from '../../constants'
|
||||
import { ROOT_DIR, STREAMS_DIR, DATA_DIR } from '../../constants'
|
||||
import { PlaylistParser, StreamTester, CliTable } from '../../core'
|
||||
import { Stream } from '../../models'
|
||||
import { Stream, Feed, Channel } from '../../models'
|
||||
import { program } from 'commander'
|
||||
import { eachLimit } from 'async-es'
|
||||
import commandExists from 'command-exists'
|
||||
|
@ -38,8 +38,6 @@ const logger = new Logger()
|
|||
const tester = new StreamTester()
|
||||
|
||||
async function main() {
|
||||
const storage = new Storage(ROOT_DIR)
|
||||
|
||||
if (await isOffline()) {
|
||||
logger.error(chalk.red('Internet connection is required for the script to work'))
|
||||
|
||||
|
@ -56,9 +54,25 @@ async function main() {
|
|||
return
|
||||
}
|
||||
|
||||
logger.info('loading channels from api...')
|
||||
const dataStorage = new Storage(DATA_DIR)
|
||||
const channelsData = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsData).map(data => new Channel(data))
|
||||
const channelsGroupedById = channels.keyBy((channel: Channel) => channel.id)
|
||||
const feedsData = await dataStorage.json('feeds.json')
|
||||
const feeds = new Collection(feedsData).map(data =>
|
||||
new Feed(data).withChannel(channelsGroupedById)
|
||||
)
|
||||
const feedsGroupedByChannelId = feeds.groupBy(feed => feed.channel)
|
||||
|
||||
logger.info('loading streams...')
|
||||
const parser = new PlaylistParser({ storage })
|
||||
const files = program.args.length ? program.args : await storage.list(`${STREAMS_DIR}/*.m3u`)
|
||||
const rootStorage = new Storage(ROOT_DIR)
|
||||
const parser = new PlaylistParser({
|
||||
storage: rootStorage,
|
||||
channelsGroupedById,
|
||||
feedsGroupedByChannelId
|
||||
})
|
||||
const files = program.args.length ? program.args : await rootStorage.list(`${STREAMS_DIR}/*.m3u`)
|
||||
streams = await parser.parse(files)
|
||||
|
||||
logger.info(`found ${streams.count()} streams`)
|
||||
|
@ -89,7 +103,7 @@ async function main() {
|
|||
main()
|
||||
|
||||
async function runTest(stream: Stream) {
|
||||
const key = stream.filepath + stream.channel + stream.url
|
||||
const key = stream.filepath + stream.getId() + stream.url
|
||||
results[key] = chalk.white('LOADING...')
|
||||
|
||||
const result = await tester.test(stream)
|
||||
|
@ -125,11 +139,11 @@ function drawTable() {
|
|||
]
|
||||
})
|
||||
streams.forEach((stream: Stream, index: number) => {
|
||||
const status = results[stream.filepath + stream.channel + stream.url] || chalk.gray('PENDING')
|
||||
const status = results[stream.filepath + stream.getId() + stream.url] || chalk.gray('PENDING')
|
||||
|
||||
const row = {
|
||||
'': index,
|
||||
'tvg-id': stream.channel.length > 25 ? stream.channel.slice(0, 22) + '...' : stream.channel,
|
||||
'tvg-id': stream.getId().length > 25 ? stream.getId().slice(0, 22) + '...' : stream.getId(),
|
||||
url: stream.url.length > 100 ? stream.url.slice(0, 97) + '...' : stream.url,
|
||||
status
|
||||
}
|
||||
|
|
|
@ -1,45 +1,63 @@
|
|||
import { Logger, Storage, Collection, Dictionary } from '@freearhey/core'
|
||||
import { DATA_DIR, STREAMS_DIR } from '../../constants'
|
||||
import { IssueLoader, PlaylistParser } from '../../core'
|
||||
import { Stream, Playlist, Channel, Issue } from '../../models'
|
||||
import { Stream, Playlist, Channel, Feed, Issue } from '../../models'
|
||||
import validUrl from 'valid-url'
|
||||
import { uniqueId } from 'lodash'
|
||||
|
||||
let processedIssues = new Collection()
|
||||
let streams: Collection
|
||||
let groupedChannels: Dictionary
|
||||
let issues: Collection
|
||||
|
||||
async function main() {
|
||||
const logger = new Logger({ disabled: true })
|
||||
const loader = new IssueLoader()
|
||||
|
||||
logger.info('loading issues...')
|
||||
issues = await loader.load()
|
||||
const issues = await loader.load()
|
||||
|
||||
logger.info('loading channels from api...')
|
||||
const dataStorage = new Storage(DATA_DIR)
|
||||
const channelsContent = await dataStorage.json('channels.json')
|
||||
groupedChannels = new Collection(channelsContent)
|
||||
.map(data => new Channel(data))
|
||||
.keyBy((channel: Channel) => channel.id)
|
||||
const channelsData = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsData).map(data => new Channel(data))
|
||||
const channelsGroupedById = channels.keyBy((channel: Channel) => channel.id)
|
||||
const feedsData = await dataStorage.json('feeds.json')
|
||||
const feeds = new Collection(feedsData).map(data =>
|
||||
new Feed(data).withChannel(channelsGroupedById)
|
||||
)
|
||||
const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) =>
|
||||
feed.channel ? feed.channel.id : uniqueId()
|
||||
)
|
||||
|
||||
logger.info('loading streams...')
|
||||
const streamsStorage = new Storage(STREAMS_DIR)
|
||||
const parser = new PlaylistParser({ storage: streamsStorage })
|
||||
const parser = new PlaylistParser({
|
||||
storage: streamsStorage,
|
||||
feedsGroupedByChannelId,
|
||||
channelsGroupedById
|
||||
})
|
||||
const files = await streamsStorage.list('**/*.m3u')
|
||||
streams = await parser.parse(files)
|
||||
const streams = await parser.parse(files)
|
||||
|
||||
logger.info('removing broken streams...')
|
||||
await removeStreams(loader)
|
||||
await removeStreams({ streams, issues })
|
||||
|
||||
logger.info('edit stream description...')
|
||||
await editStreams(loader)
|
||||
await editStreams({
|
||||
streams,
|
||||
issues,
|
||||
channelsGroupedById,
|
||||
feedsGroupedByChannelId
|
||||
})
|
||||
|
||||
logger.info('add new streams...')
|
||||
await addStreams(loader)
|
||||
await addStreams({
|
||||
streams,
|
||||
issues,
|
||||
channelsGroupedById,
|
||||
feedsGroupedByChannelId
|
||||
})
|
||||
|
||||
logger.info('saving...')
|
||||
const groupedStreams = streams.groupBy((stream: Stream) => stream.filepath)
|
||||
const groupedStreams = streams.groupBy((stream: Stream) => stream.getFilepath())
|
||||
for (let filepath of groupedStreams.keys()) {
|
||||
let streams = groupedStreams.get(filepath) || []
|
||||
streams = streams.filter((stream: Stream) => stream.removed === false)
|
||||
|
@ -54,7 +72,7 @@ async function main() {
|
|||
|
||||
main()
|
||||
|
||||
async function removeStreams(loader: IssueLoader) {
|
||||
async function removeStreams({ streams, issues }: { streams: Collection; issues: Collection }) {
|
||||
const requests = issues.filter(
|
||||
issue => issue.labels.includes('streams:remove') && issue.labels.includes('approved')
|
||||
)
|
||||
|
@ -62,22 +80,35 @@ async function removeStreams(loader: IssueLoader) {
|
|||
const data = issue.data
|
||||
if (data.missing('brokenLinks')) return
|
||||
|
||||
const brokenLinks = data.getString('brokenLinks').split(/\r?\n/).filter(Boolean)
|
||||
const brokenLinks = data.getString('brokenLinks') || ''
|
||||
|
||||
let changed = false
|
||||
brokenLinks.forEach(link => {
|
||||
const found: Stream = streams.first((_stream: Stream) => _stream.url === link.trim())
|
||||
if (found) {
|
||||
found.removed = true
|
||||
changed = true
|
||||
}
|
||||
})
|
||||
brokenLinks
|
||||
.split(/\r?\n/)
|
||||
.filter(Boolean)
|
||||
.forEach(link => {
|
||||
const found: Stream = streams.first((_stream: Stream) => _stream.url === link.trim())
|
||||
if (found) {
|
||||
found.removed = true
|
||||
changed = true
|
||||
}
|
||||
})
|
||||
|
||||
if (changed) processedIssues.add(issue.number)
|
||||
})
|
||||
}
|
||||
|
||||
async function editStreams(loader: IssueLoader) {
|
||||
async function editStreams({
|
||||
streams,
|
||||
issues,
|
||||
channelsGroupedById,
|
||||
feedsGroupedByChannelId
|
||||
}: {
|
||||
streams: Collection
|
||||
issues: Collection
|
||||
channelsGroupedById: Dictionary
|
||||
feedsGroupedByChannelId: Dictionary
|
||||
}) {
|
||||
const requests = issues.filter(
|
||||
issue => issue.labels.includes('streams:edit') && issue.labels.includes('approved')
|
||||
)
|
||||
|
@ -86,59 +117,110 @@ async function editStreams(loader: IssueLoader) {
|
|||
|
||||
if (data.missing('streamUrl')) return
|
||||
|
||||
let stream = streams.first(
|
||||
let stream: Stream = streams.first(
|
||||
(_stream: Stream) => _stream.url === data.getString('streamUrl')
|
||||
) as Stream
|
||||
|
||||
)
|
||||
if (!stream) return
|
||||
|
||||
if (data.has('channelId')) {
|
||||
const channel = groupedChannels.get(data.getString('channelId'))
|
||||
const streamId = data.getString('streamId') || ''
|
||||
const [channelId, feedId] = streamId.split('@')
|
||||
|
||||
if (!channel) return
|
||||
|
||||
stream.channel = data.getString('channelId')
|
||||
stream.filepath = `${channel.country.toLowerCase()}.m3u`
|
||||
stream.line = -1
|
||||
stream.name = channel.name
|
||||
if (channelId) {
|
||||
stream
|
||||
.setChannelId(channelId)
|
||||
.setFeedId(feedId)
|
||||
.withChannel(channelsGroupedById)
|
||||
.withFeed(feedsGroupedByChannelId)
|
||||
.updateId()
|
||||
.updateName()
|
||||
.updateFilepath()
|
||||
}
|
||||
|
||||
if (data.has('label')) stream.label = data.getString('label')
|
||||
if (data.has('quality')) stream.quality = data.getString('quality')
|
||||
if (data.has('httpUserAgent')) stream.httpUserAgent = data.getString('httpUserAgent')
|
||||
if (data.has('httpReferrer')) stream.httpReferrer = data.getString('httpReferrer')
|
||||
const label = data.getString('label') || ''
|
||||
const quality = data.getString('quality') || ''
|
||||
const httpUserAgent = data.getString('httpUserAgent') || ''
|
||||
const httpReferrer = data.getString('httpReferrer') || ''
|
||||
|
||||
if (data.has('label')) stream.setLabel(label)
|
||||
if (data.has('quality')) stream.setQuality(quality)
|
||||
if (data.has('httpUserAgent')) stream.setHttpUserAgent(httpUserAgent)
|
||||
if (data.has('httpReferrer')) stream.setHttpReferrer(httpReferrer)
|
||||
|
||||
processedIssues.add(issue.number)
|
||||
})
|
||||
}
|
||||
|
||||
async function addStreams(loader: IssueLoader) {
|
||||
async function addStreams({
|
||||
streams,
|
||||
issues,
|
||||
channelsGroupedById,
|
||||
feedsGroupedByChannelId
|
||||
}: {
|
||||
streams: Collection
|
||||
issues: Collection
|
||||
channelsGroupedById: Dictionary
|
||||
feedsGroupedByChannelId: Dictionary
|
||||
}) {
|
||||
const requests = issues.filter(
|
||||
issue => issue.labels.includes('streams:add') && issue.labels.includes('approved')
|
||||
)
|
||||
requests.forEach((issue: Issue) => {
|
||||
const data = issue.data
|
||||
if (data.missing('channelId') || data.missing('streamUrl')) return
|
||||
if (data.missing('streamId') || data.missing('streamUrl')) return
|
||||
if (streams.includes((_stream: Stream) => _stream.url === data.getString('streamUrl'))) return
|
||||
if (!validUrl.isUri(data.getString('streamUrl'))) return
|
||||
const stringUrl = data.getString('streamUrl') || ''
|
||||
if (!isUri(stringUrl)) return
|
||||
|
||||
const channel = groupedChannels.get(data.getString('channelId'))
|
||||
const streamId = data.getString('streamId') || ''
|
||||
const [channelId] = streamId.split('@')
|
||||
|
||||
const channel: Channel = channelsGroupedById.get(channelId)
|
||||
if (!channel) return
|
||||
|
||||
const label = data.getString('label') || ''
|
||||
const quality = data.getString('quality') || ''
|
||||
const httpUserAgent = data.getString('httpUserAgent') || ''
|
||||
const httpReferrer = data.getString('httpReferrer') || ''
|
||||
|
||||
const stream = new Stream({
|
||||
channel: data.getString('channelId'),
|
||||
url: data.getString('streamUrl'),
|
||||
label: data.getString('label'),
|
||||
quality: data.getString('quality'),
|
||||
httpUserAgent: data.getString('httpUserAgent'),
|
||||
httpReferrer: data.getString('httpReferrer'),
|
||||
filepath: `${channel.country.toLowerCase()}.m3u`,
|
||||
tvg: {
|
||||
id: streamId,
|
||||
name: '',
|
||||
url: '',
|
||||
logo: '',
|
||||
rec: '',
|
||||
shift: ''
|
||||
},
|
||||
name: data.getString('channelName') || channel.name,
|
||||
url: stringUrl,
|
||||
group: {
|
||||
title: ''
|
||||
},
|
||||
http: {
|
||||
'user-agent': httpUserAgent,
|
||||
referrer: httpReferrer
|
||||
},
|
||||
line: -1,
|
||||
name: data.getString('channelName') || channel.name
|
||||
raw: '',
|
||||
timeshift: '',
|
||||
catchup: {
|
||||
type: '',
|
||||
source: '',
|
||||
days: ''
|
||||
}
|
||||
})
|
||||
.withChannel(channelsGroupedById)
|
||||
.withFeed(feedsGroupedByChannelId)
|
||||
.setLabel(label)
|
||||
.setQuality(quality)
|
||||
.updateName()
|
||||
.updateFilepath()
|
||||
|
||||
streams.add(stream)
|
||||
processedIssues.add(issue.number)
|
||||
})
|
||||
}
|
||||
|
||||
function isUri(string: string) {
|
||||
return validUrl.isUri(encodeURI(string))
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Logger, Storage, Collection, Dictionary } from '@freearhey/core'
|
||||
import { PlaylistParser } from '../../core'
|
||||
import { Channel, Stream, Blocked } from '../../models'
|
||||
import { Channel, Stream, Blocked, Feed } from '../../models'
|
||||
import { program } from 'commander'
|
||||
import chalk from 'chalk'
|
||||
import _ from 'lodash'
|
||||
import { uniqueId } from 'lodash'
|
||||
import { DATA_DIR, STREAMS_DIR } from '../../constants'
|
||||
|
||||
program.argument('[filepath]', 'Path to file to validate').parse(process.argv)
|
||||
|
@ -17,41 +17,52 @@ type LogItem = {
|
|||
async function main() {
|
||||
const logger = new Logger()
|
||||
|
||||
logger.info(`loading blocklist...`)
|
||||
logger.info('loading data from api...')
|
||||
const dataStorage = new Storage(DATA_DIR)
|
||||
const channelsContent = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsContent).map(data => new Channel(data))
|
||||
const channelsData = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsData).map(data => new Channel(data))
|
||||
const channelsGroupedById = channels.keyBy((channel: Channel) => channel.id)
|
||||
const feedsData = await dataStorage.json('feeds.json')
|
||||
const feeds = new Collection(feedsData).map(data =>
|
||||
new Feed(data).withChannel(channelsGroupedById)
|
||||
)
|
||||
const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) =>
|
||||
feed.channel ? feed.channel.id : uniqueId()
|
||||
)
|
||||
const blocklistContent = await dataStorage.json('blocklist.json')
|
||||
const blocklist = new Collection(blocklistContent).map(data => new Blocked(data))
|
||||
|
||||
logger.info(`found ${blocklist.count()} records`)
|
||||
const blocklistGroupedByChannelId = blocklist.keyBy((blocked: Blocked) => blocked.channelId)
|
||||
|
||||
logger.info('loading streams...')
|
||||
const streamsStorage = new Storage(STREAMS_DIR)
|
||||
const parser = new PlaylistParser({ storage: streamsStorage })
|
||||
const parser = new PlaylistParser({
|
||||
storage: streamsStorage,
|
||||
channelsGroupedById,
|
||||
feedsGroupedByChannelId
|
||||
})
|
||||
const files = program.args.length ? program.args : await streamsStorage.list('**/*.m3u')
|
||||
const streams = await parser.parse(files)
|
||||
|
||||
logger.info(`found ${streams.count()} streams`)
|
||||
|
||||
let errors = new Collection()
|
||||
let warnings = new Collection()
|
||||
let groupedStreams = streams.groupBy((stream: Stream) => stream.filepath)
|
||||
for (const filepath of groupedStreams.keys()) {
|
||||
const streams = groupedStreams.get(filepath)
|
||||
let streamsGroupedByFilepath = streams.groupBy((stream: Stream) => stream.getFilepath())
|
||||
for (const filepath of streamsGroupedByFilepath.keys()) {
|
||||
const streams = streamsGroupedByFilepath.get(filepath)
|
||||
if (!streams) continue
|
||||
|
||||
const log = new Collection()
|
||||
const buffer = new Dictionary()
|
||||
streams.forEach((stream: Stream) => {
|
||||
const invalidId =
|
||||
stream.channel && !channels.first((channel: Channel) => channel.id === stream.channel)
|
||||
if (invalidId) {
|
||||
log.add({
|
||||
type: 'warning',
|
||||
line: stream.line,
|
||||
message: `"${stream.channel}" is not in the database`
|
||||
})
|
||||
if (stream.channelId) {
|
||||
const channel = channelsGroupedById.get(stream.channelId)
|
||||
if (!channel) {
|
||||
log.add({
|
||||
type: 'warning',
|
||||
line: stream.line,
|
||||
message: `"${stream.id}" is not in the database`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const duplicate = stream.url && buffer.has(stream.url)
|
||||
|
@ -65,19 +76,19 @@ async function main() {
|
|||
buffer.set(stream.url, true)
|
||||
}
|
||||
|
||||
const blocked = blocklist.first(blocked => stream.channel === blocked.channel)
|
||||
const blocked = stream.channel ? blocklistGroupedByChannelId.get(stream.channel.id) : false
|
||||
if (blocked) {
|
||||
if (blocked.reason === 'dmca') {
|
||||
log.add({
|
||||
type: 'error',
|
||||
line: stream.line,
|
||||
message: `"${stream.channel}" is on the blocklist due to claims of copyright holders (${blocked.ref})`
|
||||
message: `"${blocked.channelId}" is on the blocklist due to claims of copyright holders (${blocked.ref})`
|
||||
})
|
||||
} else if (blocked.reason === 'nsfw') {
|
||||
log.add({
|
||||
type: 'error',
|
||||
line: stream.line,
|
||||
message: `"${stream.channel}" is on the blocklist due to NSFW content (${blocked.ref})`
|
||||
message: `"${blocked.channelId}" is on the blocklist due to NSFW content (${blocked.ref})`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,154 +1,164 @@
|
|||
import { Logger, Storage, Collection, Dictionary } from '@freearhey/core'
|
||||
import { DATA_DIR, STREAMS_DIR } from '../../constants'
|
||||
import { IssueLoader, PlaylistParser } from '../../core'
|
||||
import { Blocked, Channel, Issue, Stream } from '../../models'
|
||||
import { Blocked, Channel, Issue, Stream, Feed } from '../../models'
|
||||
import { uniqueId } from 'lodash'
|
||||
|
||||
async function main() {
|
||||
const logger = new Logger()
|
||||
const loader = new IssueLoader()
|
||||
|
||||
const storage = new Storage(DATA_DIR)
|
||||
let report = new Collection()
|
||||
|
||||
logger.info('loading issues...')
|
||||
const issues = await loader.load()
|
||||
|
||||
logger.info('loading data from api...')
|
||||
const dataStorage = new Storage(DATA_DIR)
|
||||
const channelsData = await dataStorage.json('channels.json')
|
||||
const channels = new Collection(channelsData).map(data => new Channel(data))
|
||||
const channelsGroupedById = channels.keyBy((channel: Channel) => channel.id)
|
||||
const feedsData = await dataStorage.json('feeds.json')
|
||||
const feeds = new Collection(feedsData).map(data =>
|
||||
new Feed(data).withChannel(channelsGroupedById)
|
||||
)
|
||||
const feedsGroupedByChannelId = feeds.groupBy((feed: Feed) =>
|
||||
feed.channel ? feed.channel.id : uniqueId()
|
||||
)
|
||||
const blocklistContent = await dataStorage.json('blocklist.json')
|
||||
const blocklist = new Collection(blocklistContent).map(data => new Blocked(data))
|
||||
const blocklistGroupedByChannelId = blocklist.keyBy((blocked: Blocked) => blocked.channelId)
|
||||
|
||||
logger.info('loading streams...')
|
||||
const streamsStorage = new Storage(STREAMS_DIR)
|
||||
const parser = new PlaylistParser({ storage: streamsStorage })
|
||||
const parser = new PlaylistParser({
|
||||
storage: streamsStorage,
|
||||
channelsGroupedById,
|
||||
feedsGroupedByChannelId
|
||||
})
|
||||
const files = await streamsStorage.list('**/*.m3u')
|
||||
const streams = await parser.parse(files)
|
||||
const streamsGroupedByUrl = streams.groupBy((stream: Stream) => stream.url)
|
||||
const streamsGroupedByChannel = streams.groupBy((stream: Stream) => stream.channel)
|
||||
|
||||
logger.info('loading channels from api...')
|
||||
const channelsContent = await storage.json('channels.json')
|
||||
const channelsGroupedById = new Collection(channelsContent)
|
||||
.map(data => new Channel(data))
|
||||
.groupBy((channel: Channel) => channel.id)
|
||||
|
||||
logger.info('loading blocklist from api...')
|
||||
const blocklistContent = await storage.json('blocklist.json')
|
||||
const blocklistGroupedByChannel = new Collection(blocklistContent)
|
||||
.map(data => new Blocked(data))
|
||||
.groupBy((blocked: Blocked) => blocked.channel)
|
||||
|
||||
let report = new Collection()
|
||||
|
||||
logger.info('checking streams:add requests...')
|
||||
const addRequests = issues.filter(issue => issue.labels.includes('streams:add'))
|
||||
const addRequestsBuffer = new Dictionary()
|
||||
addRequests.forEach((issue: Issue) => {
|
||||
const channelId = issue.data.getString('channelId') || undefined
|
||||
const streamUrl = issue.data.getString('streamUrl')
|
||||
|
||||
const result = new Dictionary({
|
||||
issueNumber: issue.number,
|
||||
type: 'streams:add',
|
||||
channelId,
|
||||
streamUrl,
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
if (!channelId) result.set('status', 'missing_id')
|
||||
else if (!streamUrl) result.set('status', 'missing_link')
|
||||
else if (blocklistGroupedByChannel.has(channelId)) result.set('status', 'blocked')
|
||||
else if (channelsGroupedById.missing(channelId)) result.set('status', 'wrong_id')
|
||||
else if (streamsGroupedByUrl.has(streamUrl)) result.set('status', 'on_playlist')
|
||||
else if (addRequestsBuffer.has(streamUrl)) result.set('status', 'duplicate')
|
||||
else result.set('status', 'pending')
|
||||
|
||||
addRequestsBuffer.set(streamUrl, true)
|
||||
|
||||
report.add(result.data())
|
||||
})
|
||||
|
||||
logger.info('checking streams:edit requests...')
|
||||
const editRequests = issues.filter(issue => issue.labels.find(label => label === 'streams:edit'))
|
||||
editRequests.forEach((issue: Issue) => {
|
||||
const channelId = issue.data.getString('channelId') || undefined
|
||||
const streamUrl = issue.data.getString('streamUrl') || undefined
|
||||
|
||||
const result = new Dictionary({
|
||||
issueNumber: issue.number,
|
||||
type: 'streams:edit',
|
||||
channelId,
|
||||
streamUrl,
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
if (!streamUrl) result.set('status', 'missing_link')
|
||||
else if (streamsGroupedByUrl.missing(streamUrl)) result.set('status', 'invalid_link')
|
||||
else if (channelId && channelsGroupedById.missing(channelId)) result.set('status', 'invalid_id')
|
||||
|
||||
report.add(result.data())
|
||||
})
|
||||
const streamsGroupedByChannelId = streams.groupBy((stream: Stream) => stream.channelId)
|
||||
|
||||
logger.info('checking broken streams reports...')
|
||||
const brokenStreamReports = issues.filter(issue =>
|
||||
issue.labels.find(label => label === 'broken stream')
|
||||
issue.labels.find((label: string) => label === 'broken stream')
|
||||
)
|
||||
brokenStreamReports.forEach((issue: Issue) => {
|
||||
const brokenLinks = issue.data.getArray('brokenLinks') || []
|
||||
|
||||
if (!brokenLinks.length) {
|
||||
const result = new Dictionary({
|
||||
const result = {
|
||||
issueNumber: issue.number,
|
||||
type: 'broken stream',
|
||||
channelId: undefined,
|
||||
streamId: undefined,
|
||||
streamUrl: undefined,
|
||||
status: 'missing_link'
|
||||
})
|
||||
}
|
||||
|
||||
report.add(result.data())
|
||||
report.add(result)
|
||||
} else {
|
||||
for (const streamUrl of brokenLinks) {
|
||||
const result = new Dictionary({
|
||||
const result = {
|
||||
issueNumber: issue.number,
|
||||
type: 'broken stream',
|
||||
channelId: undefined,
|
||||
streamUrl: undefined,
|
||||
streamId: undefined,
|
||||
streamUrl: truncate(streamUrl),
|
||||
status: 'pending'
|
||||
})
|
||||
|
||||
if (streamsGroupedByUrl.missing(streamUrl)) {
|
||||
result.set('streamUrl', streamUrl)
|
||||
result.set('status', 'wrong_link')
|
||||
}
|
||||
|
||||
report.add(result.data())
|
||||
if (streamsGroupedByUrl.missing(streamUrl)) {
|
||||
result.status = 'wrong_link'
|
||||
}
|
||||
|
||||
report.add(result)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
logger.info('checking streams:add requests...')
|
||||
const addRequests = issues.filter(issue => issue.labels.includes('streams:add'))
|
||||
const addRequestsBuffer = new Dictionary()
|
||||
addRequests.forEach((issue: Issue) => {
|
||||
const streamId = issue.data.getString('streamId') || ''
|
||||
const streamUrl = issue.data.getString('streamUrl') || ''
|
||||
const [channelId] = streamId.split('@')
|
||||
|
||||
const result = {
|
||||
issueNumber: issue.number,
|
||||
type: 'streams:add',
|
||||
streamId: streamId || undefined,
|
||||
streamUrl: truncate(streamUrl),
|
||||
status: 'pending'
|
||||
}
|
||||
|
||||
if (!channelId) result.status = 'missing_id'
|
||||
else if (!streamUrl) result.status = 'missing_link'
|
||||
else if (blocklistGroupedByChannelId.has(channelId)) result.status = 'blocked'
|
||||
else if (channelsGroupedById.missing(channelId)) result.status = 'wrong_id'
|
||||
else if (streamsGroupedByUrl.has(streamUrl)) result.status = 'on_playlist'
|
||||
else if (addRequestsBuffer.has(streamUrl)) result.status = 'duplicate'
|
||||
else result.status = 'pending'
|
||||
|
||||
addRequestsBuffer.set(streamUrl, true)
|
||||
|
||||
report.add(result)
|
||||
})
|
||||
|
||||
logger.info('checking streams:edit requests...')
|
||||
const editRequests = issues.filter(issue =>
|
||||
issue.labels.find((label: string) => label === 'streams:edit')
|
||||
)
|
||||
editRequests.forEach((issue: Issue) => {
|
||||
const streamId = issue.data.getString('streamId') || ''
|
||||
const streamUrl = issue.data.getString('streamUrl') || ''
|
||||
const [channelId] = streamId.split('@')
|
||||
|
||||
const result = {
|
||||
issueNumber: issue.number,
|
||||
type: 'streams:edit',
|
||||
streamId: streamId || undefined,
|
||||
streamUrl: truncate(streamUrl),
|
||||
status: 'pending'
|
||||
}
|
||||
|
||||
if (!streamUrl) result.status = 'missing_link'
|
||||
else if (streamsGroupedByUrl.missing(streamUrl)) result.status = 'invalid_link'
|
||||
else if (channelId && channelsGroupedById.missing(channelId)) result.status = 'invalid_id'
|
||||
|
||||
report.add(result)
|
||||
})
|
||||
|
||||
logger.info('checking channel search requests...')
|
||||
const channelSearchRequests = issues.filter(issue =>
|
||||
issue.labels.find(label => label === 'channel search')
|
||||
issue.labels.find((label: string) => label === 'channel search')
|
||||
)
|
||||
const channelSearchRequestsBuffer = new Dictionary()
|
||||
channelSearchRequests.forEach((issue: Issue) => {
|
||||
const channelId = issue.data.getString('channelId')
|
||||
const streamId = issue.data.getString('channelId') || ''
|
||||
const [channelId] = streamId.split('@')
|
||||
|
||||
const result = new Dictionary({
|
||||
const result = {
|
||||
issueNumber: issue.number,
|
||||
type: 'channel search',
|
||||
channelId,
|
||||
streamId: streamId || undefined,
|
||||
streamUrl: undefined,
|
||||
status: 'pending'
|
||||
})
|
||||
}
|
||||
|
||||
if (!channelId) result.set('status', 'missing_id')
|
||||
else if (channelsGroupedById.missing(channelId)) result.set('status', 'invalid_id')
|
||||
else if (channelSearchRequestsBuffer.has(channelId)) result.set('status', 'duplicate')
|
||||
else if (blocklistGroupedByChannel.has(channelId)) result.set('status', 'blocked')
|
||||
else if (streamsGroupedByChannel.has(channelId)) result.set('status', 'fulfilled')
|
||||
if (!channelId) result.status = 'missing_id'
|
||||
else if (channelsGroupedById.missing(channelId)) result.status = 'invalid_id'
|
||||
else if (channelSearchRequestsBuffer.has(channelId)) result.status = 'duplicate'
|
||||
else if (blocklistGroupedByChannelId.has(channelId)) result.status = 'blocked'
|
||||
else if (streamsGroupedByChannelId.has(channelId)) result.status = 'fulfilled'
|
||||
else {
|
||||
const channelData = channelsGroupedById.get(channelId)
|
||||
if (channelData.length && channelData[0].closed) result.set('status', 'closed')
|
||||
if (channelData.length && channelData[0].closed) result.status = 'closed'
|
||||
}
|
||||
|
||||
channelSearchRequestsBuffer.set(channelId, true)
|
||||
|
||||
report.add(result.data())
|
||||
report.add(result)
|
||||
})
|
||||
|
||||
report = report.orderBy(item => item.issueNumber).filter(item => item.status !== 'pending')
|
||||
|
@ -157,3 +167,10 @@ async function main() {
|
|||
}
|
||||
|
||||
main()
|
||||
|
||||
function truncate(string: string, limit: number = 100) {
|
||||
if (!string) return string
|
||||
if (string.length < limit) return string
|
||||
|
||||
return string.slice(0, limit) + '...'
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ export class ApiClient {
|
|||
}
|
||||
|
||||
async download(filename: string) {
|
||||
const stream = await this.storage.createStream(`/temp/data/${filename}`)
|
||||
const stream = await this.storage.createStream(`temp/data/${filename}`)
|
||||
|
||||
const bar = this.progressBar.create(0, 0, { filename })
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { Table } from 'console-table-printer'
|
||||
import { ComplexOptions } from 'console-table-printer/dist/src/models/external-table'
|
||||
|
||||
export class CliTable {
|
||||
table: Table
|
||||
|
||||
constructor(options?) {
|
||||
constructor(options?: ComplexOptions | string[]) {
|
||||
this.table = new Table(options)
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ export class IssueData {
|
|||
return Boolean(this._data.get(key))
|
||||
}
|
||||
|
||||
getString(key: string): string {
|
||||
getString(key: string): string | undefined {
|
||||
const deleteSymbol = '~'
|
||||
|
||||
return this._data.get(key) === deleteSymbol ? '' : this._data.get(key)
|
||||
|
|
|
@ -16,7 +16,7 @@ export class IssueLoader {
|
|||
}
|
||||
let issues: object[] = []
|
||||
if (TESTING) {
|
||||
issues = (await import('../../tests/__data__/input/issues/all.js')).default
|
||||
issues = (await import('../../tests/__data__/input/playlist_update/issues.js')).default
|
||||
} else {
|
||||
issues = await octokit.paginate(octokit.rest.issues.listForRepo, {
|
||||
owner: OWNER,
|
||||
|
|
|
@ -3,11 +3,10 @@ import { Issue } from '../models'
|
|||
import { IssueData } from './issueData'
|
||||
|
||||
const FIELDS = new Dictionary({
|
||||
'Stream ID': 'streamId',
|
||||
'Channel ID': 'channelId',
|
||||
'Channel ID (required)': 'channelId',
|
||||
'Feed ID': 'feedId',
|
||||
'Stream URL': 'streamUrl',
|
||||
'Stream URL (optional)': 'streamUrl',
|
||||
'Stream URL (required)': 'streamUrl',
|
||||
'Broken Link': 'brokenLinks',
|
||||
'Broken Links': 'brokenLinks',
|
||||
Label: 'label',
|
||||
|
@ -18,8 +17,7 @@ const FIELDS = new Dictionary({
|
|||
'HTTP Referrer': 'httpReferrer',
|
||||
'What happened to the stream?': 'reason',
|
||||
Reason: 'reason',
|
||||
Notes: 'notes',
|
||||
'Notes (optional)': 'notes'
|
||||
Notes: 'notes'
|
||||
})
|
||||
|
||||
export class IssueParser {
|
||||
|
@ -30,7 +28,7 @@ export class IssueParser {
|
|||
fields.forEach((field: string) => {
|
||||
const parsed = typeof field === 'string' ? field.split(/\r?\n/).filter(Boolean) : []
|
||||
let _label = parsed.shift()
|
||||
_label = _label ? _label.trim() : ''
|
||||
_label = _label ? _label.replace(/ \(optional\)| \(required\)/, '').trim() : ''
|
||||
let _value = parsed.join('\r\n')
|
||||
_value = _value ? _value.trim() : ''
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export type LogItem = {
|
||||
type: string
|
||||
filepath: string
|
||||
count: number
|
||||
}
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
import { Collection, Storage } from '@freearhey/core'
|
||||
import { Collection, Storage, Dictionary } from '@freearhey/core'
|
||||
import parser from 'iptv-playlist-parser'
|
||||
import { Stream } from '../models'
|
||||
|
||||
type PlaylistPareserProps = {
|
||||
storage: Storage
|
||||
feedsGroupedByChannelId: Dictionary
|
||||
channelsGroupedById: Dictionary
|
||||
}
|
||||
|
||||
export class PlaylistParser {
|
||||
storage: Storage
|
||||
feedsGroupedByChannelId: Dictionary
|
||||
channelsGroupedById: Dictionary
|
||||
|
||||
constructor({ storage }: { storage: Storage }) {
|
||||
constructor({ storage, feedsGroupedByChannelId, channelsGroupedById }: PlaylistPareserProps) {
|
||||
this.storage = storage
|
||||
this.feedsGroupedByChannelId = feedsGroupedByChannelId
|
||||
this.channelsGroupedById = channelsGroupedById
|
||||
}
|
||||
|
||||
async parse(files: string[]): Promise<Collection> {
|
||||
|
@ -21,41 +31,18 @@ export class PlaylistParser {
|
|||
}
|
||||
|
||||
async parseFile(filepath: string): Promise<Collection> {
|
||||
const streams = new Collection()
|
||||
|
||||
const content = await this.storage.load(filepath)
|
||||
const parsed: parser.Playlist = parser.parse(content)
|
||||
|
||||
parsed.items.forEach((item: parser.PlaylistItem) => {
|
||||
const { name, label, quality } = parseTitle(item.name)
|
||||
const stream = new Stream({
|
||||
channel: item.tvg.id,
|
||||
name,
|
||||
label,
|
||||
quality,
|
||||
filepath,
|
||||
line: item.line,
|
||||
url: item.url,
|
||||
httpReferrer: item.http.referrer,
|
||||
httpUserAgent: item.http['user-agent']
|
||||
})
|
||||
const streams = new Collection(parsed.items).map((data: parser.PlaylistItem) => {
|
||||
const stream = new Stream(data)
|
||||
.withFeed(this.feedsGroupedByChannelId)
|
||||
.withChannel(this.channelsGroupedById)
|
||||
.setFilepath(filepath)
|
||||
|
||||
streams.add(stream)
|
||||
return stream
|
||||
})
|
||||
|
||||
return streams
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle(title: string): { name: string; label: string; quality: string } {
|
||||
const [, label] = title.match(/ \[(.*)\]$/) || [null, '']
|
||||
title = title.replace(new RegExp(` \\[${escapeRegExp(label)}\\]$`), '')
|
||||
const [, quality] = title.match(/ \(([0-9]+p)\)$/) || [null, '']
|
||||
title = title.replace(new RegExp(` \\(${quality}\\)$`), '')
|
||||
|
||||
return { name: title, label, quality }
|
||||
}
|
||||
|
||||
function escapeRegExp(text) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
|
||||
}
|
||||
|
|
|
@ -11,15 +11,15 @@ export class StreamTester {
|
|||
|
||||
async test(stream: Stream) {
|
||||
if (TESTING) {
|
||||
const results = (await import('../../tests/__data__/input/test_results/all.js')).default
|
||||
const results = (await import('../../tests/__data__/input/playlist_test/results.js')).default
|
||||
|
||||
return results[stream.url]
|
||||
} else {
|
||||
return this.checker.checkStream({
|
||||
url: stream.url,
|
||||
http: {
|
||||
referrer: stream.httpReferrer,
|
||||
'user-agent': stream.httpUserAgent
|
||||
referrer: stream.getHttpReferrer(),
|
||||
'user-agent': stream.getHttpUserAgent()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -29,11 +29,7 @@ export class CategoriesGenerator implements Generator {
|
|||
const categoryStreams = streams
|
||||
.filter((stream: Stream) => stream.hasCategory(category))
|
||||
.map((stream: Stream) => {
|
||||
const streamCategories = stream.categories
|
||||
.map((category: Category) => category.name)
|
||||
.sort()
|
||||
const groupTitle = stream.categories ? streamCategories.join(';') : ''
|
||||
stream.groupTitle = groupTitle
|
||||
stream.groupTitle = stream.getCategoryNames().join(';')
|
||||
|
||||
return stream
|
||||
})
|
||||
|
@ -41,13 +37,17 @@ export class CategoriesGenerator implements Generator {
|
|||
const playlist = new Playlist(categoryStreams, { public: true })
|
||||
const filepath = `categories/${category.id}.m3u`
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
this.logger.info(
|
||||
JSON.stringify({ type: 'category', filepath, count: playlist.streams.count() })
|
||||
)
|
||||
})
|
||||
|
||||
const undefinedStreams = streams.filter((stream: Stream) => stream.noCategories())
|
||||
const undefinedStreams = streams.filter((stream: Stream) => !stream.hasCategories())
|
||||
const playlist = new Playlist(undefinedStreams, { public: true })
|
||||
const filepath = 'categories/undefined.m3u'
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
this.logger.info(
|
||||
JSON.stringify({ type: 'category', filepath, count: playlist.streams.count() })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { Generator } from './generator'
|
||||
import { Collection, Storage, Logger } from '@freearhey/core'
|
||||
import { Country, Region, Subdivision, Stream, Playlist } from '../models'
|
||||
import { Country, Subdivision, Stream, Playlist } from '../models'
|
||||
import { PUBLIC_DIR } from '../constants'
|
||||
|
||||
type CountriesGeneratorProps = {
|
||||
streams: Collection
|
||||
regions: Collection
|
||||
subdivisions: Collection
|
||||
countries: Collection
|
||||
logger: Logger
|
||||
}
|
||||
|
@ -14,55 +12,37 @@ type CountriesGeneratorProps = {
|
|||
export class CountriesGenerator implements Generator {
|
||||
streams: Collection
|
||||
countries: Collection
|
||||
regions: Collection
|
||||
subdivisions: Collection
|
||||
storage: Storage
|
||||
logger: Logger
|
||||
|
||||
constructor({ streams, countries, regions, subdivisions, logger }: CountriesGeneratorProps) {
|
||||
constructor({ streams, countries, logger }: CountriesGeneratorProps) {
|
||||
this.streams = streams
|
||||
this.countries = countries
|
||||
this.regions = regions
|
||||
this.subdivisions = subdivisions
|
||||
this.storage = new Storage(PUBLIC_DIR)
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
async generate(): Promise<void> {
|
||||
const streams = this.streams
|
||||
.orderBy([stream => stream.getTitle()])
|
||||
.orderBy((stream: Stream) => stream.getTitle())
|
||||
.filter((stream: Stream) => stream.isSFW())
|
||||
const regions = this.regions.filter((region: Region) => region.code !== 'INT')
|
||||
|
||||
this.countries.forEach(async (country: Country) => {
|
||||
const countrySubdivisions = this.subdivisions.filter(
|
||||
(subdivision: Subdivision) => subdivision.country === country.code
|
||||
const countryStreams = streams.filter((stream: Stream) =>
|
||||
stream.isBroadcastInCountry(country)
|
||||
)
|
||||
|
||||
const countrySubdivisionsCodes = countrySubdivisions.map(
|
||||
(subdivision: Subdivision) => `s/${subdivision.code}`
|
||||
)
|
||||
|
||||
const countryAreaCodes = regions
|
||||
.filter((region: Region) => region.countries.includes(country.code))
|
||||
.map((region: Region) => `r/${region.code}`)
|
||||
.concat(countrySubdivisionsCodes)
|
||||
.add(`c/${country.code}`)
|
||||
|
||||
const countryStreams = streams.filter(stream =>
|
||||
stream.broadcastArea.intersects(countryAreaCodes)
|
||||
)
|
||||
|
||||
if (countryStreams.isEmpty()) return
|
||||
|
||||
const playlist = new Playlist(countryStreams, { public: true })
|
||||
const filepath = `countries/${country.code.toLowerCase()}.m3u`
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
this.logger.info(
|
||||
JSON.stringify({ type: 'country', filepath, count: playlist.streams.count() })
|
||||
)
|
||||
|
||||
countrySubdivisions.forEach(async (subdivision: Subdivision) => {
|
||||
const subdivisionStreams = streams.filter(stream =>
|
||||
stream.broadcastArea.includes(`s/${subdivision.code}`)
|
||||
country.getSubdivisions().forEach(async (subdivision: Subdivision) => {
|
||||
const subdivisionStreams = streams.filter((stream: Stream) =>
|
||||
stream.isBroadcastInSubdivision(subdivision)
|
||||
)
|
||||
|
||||
if (subdivisionStreams.isEmpty()) return
|
||||
|
@ -70,16 +50,22 @@ export class CountriesGenerator implements Generator {
|
|||
const playlist = new Playlist(subdivisionStreams, { public: true })
|
||||
const filepath = `subdivisions/${subdivision.code.toLowerCase()}.m3u`
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
this.logger.info(
|
||||
JSON.stringify({ type: 'subdivision', filepath, count: playlist.streams.count() })
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
const internationalStreams = streams.filter(stream => stream.isInternational())
|
||||
if (internationalStreams.notEmpty()) {
|
||||
const playlist = new Playlist(internationalStreams, { public: true })
|
||||
const filepath = 'countries/int.m3u'
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
}
|
||||
const undefinedStreams = streams.filter((stream: Stream) => !stream.hasBroadcastArea())
|
||||
const undefinedPlaylist = new Playlist(undefinedStreams, { public: true })
|
||||
const undefinedFilepath = 'countries/undefined.m3u'
|
||||
await this.storage.save(undefinedFilepath, undefinedPlaylist.toString())
|
||||
this.logger.info(
|
||||
JSON.stringify({
|
||||
type: 'country',
|
||||
filepath: undefinedFilepath,
|
||||
count: undefinedPlaylist.streams.count()
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,14 +26,14 @@ export class IndexCategoryGenerator implements Generator {
|
|||
|
||||
let groupedStreams = new Collection()
|
||||
streams.forEach((stream: Stream) => {
|
||||
if (stream.noCategories()) {
|
||||
if (!stream.hasCategories()) {
|
||||
const streamClone = stream.clone()
|
||||
streamClone.groupTitle = 'Undefined'
|
||||
groupedStreams.add(streamClone)
|
||||
return
|
||||
}
|
||||
|
||||
stream.categories.forEach((category: Category) => {
|
||||
stream.getCategories().forEach((category: Category) => {
|
||||
const streamClone = stream.clone()
|
||||
streamClone.groupTitle = category.name
|
||||
groupedStreams.push(streamClone)
|
||||
|
@ -48,6 +48,6 @@ export class IndexCategoryGenerator implements Generator {
|
|||
const playlist = new Playlist(groupedStreams, { public: true })
|
||||
const filepath = 'index.category.m3u'
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
this.logger.info(JSON.stringify({ type: 'index', filepath, count: playlist.streams.count() }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,20 @@
|
|||
import { Generator } from './generator'
|
||||
import { Collection, Storage, Logger } from '@freearhey/core'
|
||||
import { Stream, Playlist, Country, Subdivision, Region } from '../models'
|
||||
import { Stream, Playlist, Country } from '../models'
|
||||
import { PUBLIC_DIR } from '../constants'
|
||||
|
||||
type IndexCountryGeneratorProps = {
|
||||
streams: Collection
|
||||
regions: Collection
|
||||
countries: Collection
|
||||
subdivisions: Collection
|
||||
logger: Logger
|
||||
}
|
||||
|
||||
export class IndexCountryGenerator implements Generator {
|
||||
streams: Collection
|
||||
countries: Collection
|
||||
regions: Collection
|
||||
subdivisions: Collection
|
||||
storage: Storage
|
||||
logger: Logger
|
||||
|
||||
constructor({ streams, regions, countries, subdivisions, logger }: IndexCountryGeneratorProps) {
|
||||
constructor({ streams, logger }: IndexCountryGeneratorProps) {
|
||||
this.streams = streams
|
||||
this.countries = countries
|
||||
this.regions = regions
|
||||
this.subdivisions = subdivisions
|
||||
this.storage = new Storage(PUBLIC_DIR)
|
||||
this.logger = logger
|
||||
}
|
||||
|
@ -32,10 +23,10 @@ export class IndexCountryGenerator implements Generator {
|
|||
let groupedStreams = new Collection()
|
||||
|
||||
this.streams
|
||||
.orderBy(stream => stream.getTitle())
|
||||
.filter(stream => stream.isSFW())
|
||||
.forEach(stream => {
|
||||
if (stream.noBroadcastArea()) {
|
||||
.orderBy((stream: Stream) => stream.getTitle())
|
||||
.filter((stream: Stream) => stream.isSFW())
|
||||
.forEach((stream: Stream) => {
|
||||
if (!stream.hasBroadcastArea()) {
|
||||
const streamClone = stream.clone()
|
||||
streamClone.groupTitle = 'Undefined'
|
||||
groupedStreams.add(streamClone)
|
||||
|
@ -48,7 +39,7 @@ export class IndexCountryGenerator implements Generator {
|
|||
groupedStreams.add(streamClone)
|
||||
}
|
||||
|
||||
this.getStreamBroadcastCountries(stream).forEach((country: Country) => {
|
||||
stream.getBroadcastCountries().forEach((country: Country) => {
|
||||
const streamClone = stream.clone()
|
||||
streamClone.groupTitle = country.name
|
||||
groupedStreams.add(streamClone)
|
||||
|
@ -65,40 +56,6 @@ export class IndexCountryGenerator implements Generator {
|
|||
const playlist = new Playlist(groupedStreams, { public: true })
|
||||
const filepath = 'index.country.m3u'
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
}
|
||||
|
||||
getStreamBroadcastCountries(stream: Stream) {
|
||||
const groupedRegions = this.regions.keyBy((region: Region) => region.code)
|
||||
const groupedCountries = this.countries.keyBy((country: Country) => country.code)
|
||||
const groupedSubdivisions = this.subdivisions.keyBy(
|
||||
(subdivision: Subdivision) => subdivision.code
|
||||
)
|
||||
|
||||
let broadcastCountries = new Collection()
|
||||
|
||||
stream.broadcastArea.forEach(broadcastAreaCode => {
|
||||
const [type, code] = broadcastAreaCode.split('/')
|
||||
switch (type) {
|
||||
case 'c':
|
||||
broadcastCountries.add(code)
|
||||
break
|
||||
case 'r':
|
||||
if (code !== 'INT' && groupedRegions.has(code)) {
|
||||
broadcastCountries = broadcastCountries.concat(groupedRegions.get(code).countries)
|
||||
}
|
||||
break
|
||||
case 's':
|
||||
if (groupedSubdivisions.has(code)) {
|
||||
broadcastCountries.add(groupedSubdivisions.get(code).country)
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
return broadcastCountries
|
||||
.uniq()
|
||||
.map(code => groupedCountries.get(code))
|
||||
.filter(Boolean)
|
||||
this.logger.info(JSON.stringify({ type: 'index', filepath, count: playlist.streams.count() }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,6 @@ export class IndexGenerator implements Generator {
|
|||
const playlist = new Playlist(sfwStreams, { public: true })
|
||||
const filepath = 'index.m3u'
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
this.logger.info(JSON.stringify({ type: 'index', filepath, count: playlist.streams.count() }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,17 +22,17 @@ export class IndexLanguageGenerator implements Generator {
|
|||
async generate(): Promise<void> {
|
||||
let groupedStreams = new Collection()
|
||||
this.streams
|
||||
.orderBy(stream => stream.getTitle())
|
||||
.filter(stream => stream.isSFW())
|
||||
.forEach(stream => {
|
||||
if (stream.noLanguages()) {
|
||||
.orderBy((stream: Stream) => stream.getTitle())
|
||||
.filter((stream: Stream) => stream.isSFW())
|
||||
.forEach((stream: Stream) => {
|
||||
if (!stream.hasLanguages()) {
|
||||
const streamClone = stream.clone()
|
||||
streamClone.groupTitle = 'Undefined'
|
||||
groupedStreams.add(streamClone)
|
||||
return
|
||||
}
|
||||
|
||||
stream.languages.forEach((language: Language) => {
|
||||
stream.getLanguages().forEach((language: Language) => {
|
||||
const streamClone = stream.clone()
|
||||
streamClone.groupTitle = language.name
|
||||
groupedStreams.add(streamClone)
|
||||
|
@ -47,6 +47,6 @@ export class IndexLanguageGenerator implements Generator {
|
|||
const playlist = new Playlist(groupedStreams, { public: true })
|
||||
const filepath = 'index.language.m3u'
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
this.logger.info(JSON.stringify({ type: 'index', filepath, count: playlist.streams.count() }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,6 @@ export class IndexNsfwGenerator implements Generator {
|
|||
const playlist = new Playlist(allStreams, { public: true })
|
||||
const filepath = 'index.nsfw.m3u'
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
this.logger.info(JSON.stringify({ type: 'index', filepath, count: playlist.streams.count() }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,14 +28,21 @@ export class IndexRegionGenerator implements Generator {
|
|||
.orderBy((stream: Stream) => stream.getTitle())
|
||||
.filter((stream: Stream) => stream.isSFW())
|
||||
.forEach((stream: Stream) => {
|
||||
if (stream.noBroadcastArea()) {
|
||||
if (stream.isInternational()) {
|
||||
const streamClone = stream.clone()
|
||||
streamClone.groupTitle = 'International'
|
||||
groupedStreams.push(streamClone)
|
||||
return
|
||||
}
|
||||
|
||||
if (!stream.hasBroadcastArea()) {
|
||||
const streamClone = stream.clone()
|
||||
streamClone.groupTitle = 'Undefined'
|
||||
groupedStreams.push(streamClone)
|
||||
return
|
||||
}
|
||||
|
||||
this.getStreamRegions(stream).forEach((region: Region) => {
|
||||
stream.getBroadcastRegions().forEach((region: Region) => {
|
||||
const streamClone = stream.clone()
|
||||
streamClone.groupTitle = region.name
|
||||
groupedStreams.push(streamClone)
|
||||
|
@ -43,41 +50,14 @@ export class IndexRegionGenerator implements Generator {
|
|||
})
|
||||
|
||||
groupedStreams = groupedStreams.orderBy((stream: Stream) => {
|
||||
if (stream.groupTitle === 'Undefined') return 'ZZ'
|
||||
if (stream.groupTitle === 'International') return 'ZZ'
|
||||
if (stream.groupTitle === 'Undefined') return 'ZZZ'
|
||||
return stream.groupTitle
|
||||
})
|
||||
|
||||
const playlist = new Playlist(groupedStreams, { public: true })
|
||||
const filepath = 'index.region.m3u'
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
}
|
||||
|
||||
getStreamRegions(stream: Stream) {
|
||||
let streamRegions = new Collection()
|
||||
stream.broadcastArea.forEach(broadcastAreaCode => {
|
||||
const [type, code] = broadcastAreaCode.split('/')
|
||||
switch (type) {
|
||||
case 'r':
|
||||
const groupedRegions = this.regions.keyBy((region: Region) => region.code)
|
||||
streamRegions.add(groupedRegions.get(code))
|
||||
break
|
||||
case 's':
|
||||
const [countryCode] = code.split('-')
|
||||
const subdivisionRegions = this.regions.filter((region: Region) =>
|
||||
region.countries.includes(countryCode)
|
||||
)
|
||||
streamRegions = streamRegions.concat(subdivisionRegions)
|
||||
break
|
||||
case 'c':
|
||||
const countryRegions = this.regions.filter((region: Region) =>
|
||||
region.countries.includes(code)
|
||||
)
|
||||
streamRegions = streamRegions.concat(countryRegions)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
return streamRegions
|
||||
this.logger.info(JSON.stringify({ type: 'index', filepath, count: playlist.streams.count() }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,35 +18,40 @@ export class LanguagesGenerator implements Generator {
|
|||
|
||||
async generate(): Promise<void> {
|
||||
const streams = this.streams
|
||||
.orderBy(stream => stream.getTitle())
|
||||
.filter(stream => stream.isSFW())
|
||||
.orderBy((stream: Stream) => stream.getTitle())
|
||||
.filter((stream: Stream) => stream.isSFW())
|
||||
|
||||
let languages = new Collection()
|
||||
streams.forEach((stream: Stream) => {
|
||||
languages = languages.concat(stream.languages)
|
||||
languages = languages.concat(stream.getLanguages())
|
||||
})
|
||||
|
||||
languages
|
||||
.filter(Boolean)
|
||||
.uniqBy((language: Language) => language.code)
|
||||
.orderBy((language: Language) => language.name)
|
||||
.forEach(async (language: Language) => {
|
||||
const languageStreams = streams.filter(stream => stream.hasLanguage(language))
|
||||
const languageStreams = streams.filter((stream: Stream) => stream.hasLanguage(language))
|
||||
|
||||
if (languageStreams.isEmpty()) return
|
||||
|
||||
const playlist = new Playlist(languageStreams, { public: true })
|
||||
const filepath = `languages/${language.code}.m3u`
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
this.logger.info(
|
||||
JSON.stringify({ type: 'language', filepath, count: playlist.streams.count() })
|
||||
)
|
||||
})
|
||||
|
||||
const undefinedStreams = streams.filter(stream => stream.noLanguages())
|
||||
const undefinedStreams = streams.filter((stream: Stream) => !stream.hasLanguages())
|
||||
|
||||
if (undefinedStreams.isEmpty()) return
|
||||
|
||||
const playlist = new Playlist(undefinedStreams, { public: true })
|
||||
const filepath = 'languages/undefined.m3u'
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
this.logger.info(
|
||||
JSON.stringify({ type: 'language', filepath, count: playlist.streams.count() })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,53 +1,61 @@
|
|||
import { Generator } from './generator'
|
||||
import { Collection, Storage, Logger } from '@freearhey/core'
|
||||
import { Playlist, Subdivision, Region } from '../models'
|
||||
import { Playlist, Region, Stream } from '../models'
|
||||
import { PUBLIC_DIR } from '../constants'
|
||||
|
||||
type RegionsGeneratorProps = {
|
||||
streams: Collection
|
||||
regions: Collection
|
||||
subdivisions: Collection
|
||||
logger: Logger
|
||||
}
|
||||
|
||||
export class RegionsGenerator implements Generator {
|
||||
streams: Collection
|
||||
regions: Collection
|
||||
subdivisions: Collection
|
||||
storage: Storage
|
||||
logger: Logger
|
||||
|
||||
constructor({ streams, regions, subdivisions, logger }: RegionsGeneratorProps) {
|
||||
constructor({ streams, regions, logger }: RegionsGeneratorProps) {
|
||||
this.streams = streams
|
||||
this.regions = regions
|
||||
this.subdivisions = subdivisions
|
||||
this.storage = new Storage(PUBLIC_DIR)
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
async generate(): Promise<void> {
|
||||
const streams = this.streams
|
||||
.orderBy(stream => stream.getTitle())
|
||||
.filter(stream => stream.isSFW())
|
||||
.orderBy((stream: Stream) => stream.getTitle())
|
||||
.filter((stream: Stream) => stream.isSFW())
|
||||
|
||||
this.regions.forEach(async (region: Region) => {
|
||||
if (region.code === 'INT') return
|
||||
if (region.isWorldwide()) return
|
||||
|
||||
const regionSubdivisionsCodes = this.subdivisions
|
||||
.filter((subdivision: Subdivision) => region.countries.indexOf(subdivision.country) > -1)
|
||||
.map((subdivision: Subdivision) => `s/${subdivision.code}`)
|
||||
|
||||
const regionCodes = region.countries
|
||||
.map((code: string) => `c/${code}`)
|
||||
.concat(regionSubdivisionsCodes)
|
||||
.add(`r/${region.code}`)
|
||||
|
||||
const regionStreams = streams.filter(stream => stream.broadcastArea.intersects(regionCodes))
|
||||
const regionStreams = streams.filter((stream: Stream) => stream.isBroadcastInRegion(region))
|
||||
|
||||
const playlist = new Playlist(regionStreams, { public: true })
|
||||
const filepath = `regions/${region.code.toLowerCase()}.m3u`
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ filepath, count: playlist.streams.count() }))
|
||||
this.logger.info(
|
||||
JSON.stringify({ type: 'region', filepath, count: playlist.streams.count() })
|
||||
)
|
||||
})
|
||||
|
||||
const internationalStreams = streams.filter((stream: Stream) => stream.isInternational())
|
||||
const internationalPlaylist = new Playlist(internationalStreams, { public: true })
|
||||
const internationalFilepath = 'regions/int.m3u'
|
||||
await this.storage.save(internationalFilepath, internationalPlaylist.toString())
|
||||
this.logger.info(
|
||||
JSON.stringify({
|
||||
type: 'region',
|
||||
filepath: internationalFilepath,
|
||||
count: internationalPlaylist.streams.count()
|
||||
})
|
||||
)
|
||||
|
||||
const undefinedStreams = streams.filter((stream: Stream) => !stream.hasBroadcastArea())
|
||||
const playlist = new Playlist(undefinedStreams, { public: true })
|
||||
const filepath = 'regions/undefined.m3u'
|
||||
await this.storage.save(filepath, playlist.toString())
|
||||
this.logger.info(JSON.stringify({ type: 'region', filepath, count: playlist.streams.count() }))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,13 @@ type BlockedProps = {
|
|||
}
|
||||
|
||||
export class Blocked {
|
||||
channel: string
|
||||
channelId: string
|
||||
reason: string
|
||||
ref: string
|
||||
|
||||
constructor({ ref, reason, channel }: BlockedProps) {
|
||||
this.channel = channel
|
||||
this.reason = reason
|
||||
this.ref = ref
|
||||
constructor(data: BlockedProps) {
|
||||
this.channelId = data.channel
|
||||
this.reason = data.reason
|
||||
this.ref = data.ref
|
||||
}
|
||||
}
|
||||
|
|
11
scripts/models/broadcastArea.ts
Normal file
11
scripts/models/broadcastArea.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
type BroadcastAreaProps = {
|
||||
code: string
|
||||
}
|
||||
|
||||
export class BroadcastArea {
|
||||
code: string
|
||||
|
||||
constructor(data: BroadcastAreaProps) {
|
||||
this.code = data.code
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
type CategoryProps = {
|
||||
type CategoryData = {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
@ -7,8 +7,8 @@ export class Category {
|
|||
id: string
|
||||
name: string
|
||||
|
||||
constructor({ id, name }: CategoryProps) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
constructor(data: CategoryData) {
|
||||
this.id = data.id
|
||||
this.name = data.name
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import { Collection } from '@freearhey/core'
|
||||
import { Collection, Dictionary } from '@freearhey/core'
|
||||
import { Category, Country, Subdivision } from './index'
|
||||
|
||||
type ChannelProps = {
|
||||
type ChannelData = {
|
||||
id: string
|
||||
name: string
|
||||
alt_names: string[]
|
||||
network: string
|
||||
owners: string[]
|
||||
owners: Collection
|
||||
country: string
|
||||
subdivision: string
|
||||
city: string
|
||||
broadcast_area: string[]
|
||||
languages: string[]
|
||||
categories: string[]
|
||||
categories: Collection
|
||||
is_nsfw: boolean
|
||||
launched: string
|
||||
closed: string
|
||||
|
@ -24,56 +23,86 @@ export class Channel {
|
|||
id: string
|
||||
name: string
|
||||
altNames: Collection
|
||||
network: string
|
||||
network?: string
|
||||
owners: Collection
|
||||
country: string
|
||||
subdivision: string
|
||||
city: string
|
||||
broadcastArea: Collection
|
||||
languages: Collection
|
||||
categories: Collection
|
||||
countryCode: string
|
||||
country?: Country
|
||||
subdivisionCode?: string
|
||||
subdivision?: Subdivision
|
||||
cityName?: string
|
||||
categoryIds: Collection
|
||||
categories?: Collection
|
||||
isNSFW: boolean
|
||||
launched: string
|
||||
closed: string
|
||||
replacedBy: string
|
||||
website: string
|
||||
launched?: string
|
||||
closed?: string
|
||||
replacedBy?: string
|
||||
website?: string
|
||||
logo: string
|
||||
|
||||
constructor({
|
||||
id,
|
||||
name,
|
||||
alt_names,
|
||||
network,
|
||||
owners,
|
||||
country,
|
||||
subdivision,
|
||||
city,
|
||||
broadcast_area,
|
||||
languages,
|
||||
categories,
|
||||
is_nsfw,
|
||||
launched,
|
||||
closed,
|
||||
replaced_by,
|
||||
website,
|
||||
logo
|
||||
}: ChannelProps) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.altNames = new Collection(alt_names)
|
||||
this.network = network
|
||||
this.owners = new Collection(owners)
|
||||
this.country = country
|
||||
this.subdivision = subdivision
|
||||
this.city = city
|
||||
this.broadcastArea = new Collection(broadcast_area)
|
||||
this.languages = new Collection(languages)
|
||||
this.categories = new Collection(categories)
|
||||
this.isNSFW = is_nsfw
|
||||
this.launched = launched
|
||||
this.closed = closed
|
||||
this.replacedBy = replaced_by
|
||||
this.website = website
|
||||
this.logo = logo
|
||||
constructor(data: ChannelData) {
|
||||
this.id = data.id
|
||||
this.name = data.name
|
||||
this.altNames = new Collection(data.alt_names)
|
||||
this.network = data.network || undefined
|
||||
this.owners = new Collection(data.owners)
|
||||
this.countryCode = data.country
|
||||
this.subdivisionCode = data.subdivision || undefined
|
||||
this.cityName = data.city || undefined
|
||||
this.categoryIds = new Collection(data.categories)
|
||||
this.isNSFW = data.is_nsfw
|
||||
this.launched = data.launched || undefined
|
||||
this.closed = data.closed || undefined
|
||||
this.replacedBy = data.replaced_by || undefined
|
||||
this.website = data.website || undefined
|
||||
this.logo = data.logo
|
||||
}
|
||||
|
||||
withSubdivision(subdivisionsGroupedByCode: Dictionary): this {
|
||||
if (!this.subdivisionCode) return this
|
||||
|
||||
this.subdivision = subdivisionsGroupedByCode.get(this.subdivisionCode)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
withCountry(countriesGroupedByCode: Dictionary): this {
|
||||
this.country = countriesGroupedByCode.get(this.countryCode)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
withCategories(groupedCategories: Dictionary): this {
|
||||
this.categories = this.categoryIds
|
||||
.map((id: string) => groupedCategories.get(id))
|
||||
.filter(Boolean)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
getCountry(): Country | undefined {
|
||||
return this.country
|
||||
}
|
||||
|
||||
getSubdivision(): Subdivision | undefined {
|
||||
return this.subdivision
|
||||
}
|
||||
|
||||
getCategories(): Collection {
|
||||
return this.categories || new Collection()
|
||||
}
|
||||
|
||||
hasCategories(): boolean {
|
||||
return !!this.categories && this.categories.notEmpty()
|
||||
}
|
||||
|
||||
hasCategory(category: Category): boolean {
|
||||
return (
|
||||
!!this.categories &&
|
||||
this.categories.includes((_category: Category) => _category.id === category.id)
|
||||
)
|
||||
}
|
||||
|
||||
isSFW(): boolean {
|
||||
return this.isNSFW === false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,20 +1,58 @@
|
|||
type CountryProps = {
|
||||
import { Collection, Dictionary } from '@freearhey/core'
|
||||
import { Region, Language } from '.'
|
||||
|
||||
type CountryData = {
|
||||
code: string
|
||||
name: string
|
||||
languages: string[]
|
||||
lang: string
|
||||
flag: string
|
||||
}
|
||||
|
||||
export class Country {
|
||||
code: string
|
||||
name: string
|
||||
languages: string[]
|
||||
flag: string
|
||||
languageCode: string
|
||||
language?: Language
|
||||
subdivisions?: Collection
|
||||
regions?: Collection
|
||||
|
||||
constructor({ code, name, languages, flag }: CountryProps) {
|
||||
this.code = code
|
||||
this.name = name
|
||||
this.languages = languages
|
||||
this.flag = flag
|
||||
constructor(data: CountryData) {
|
||||
this.code = data.code
|
||||
this.name = data.name
|
||||
this.flag = data.flag
|
||||
this.languageCode = data.lang
|
||||
}
|
||||
|
||||
withSubdivisions(subdivisionsGroupedByCountryCode: Dictionary): this {
|
||||
this.subdivisions = subdivisionsGroupedByCountryCode.get(this.code) || new Collection()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
withRegions(regions: Collection): this {
|
||||
this.regions = regions.filter(
|
||||
(region: Region) => region.code !== 'INT' && region.includesCountryCode(this.code)
|
||||
)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
withLanguage(languagesGroupedByCode: Dictionary): this {
|
||||
this.language = languagesGroupedByCode.get(this.languageCode)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
getLanguage(): Language | undefined {
|
||||
return this.language
|
||||
}
|
||||
|
||||
getRegions(): Collection {
|
||||
return this.regions || new Collection()
|
||||
}
|
||||
|
||||
getSubdivisions(): Collection {
|
||||
return this.subdivisions || new Collection()
|
||||
}
|
||||
}
|
||||
|
|
200
scripts/models/feed.ts
Normal file
200
scripts/models/feed.ts
Normal file
|
@ -0,0 +1,200 @@
|
|||
import { Collection, Dictionary } from '@freearhey/core'
|
||||
import { Country, Language, Region, Channel, Subdivision } from './index'
|
||||
|
||||
type FeedData = {
|
||||
channel: string
|
||||
id: string
|
||||
name: string
|
||||
is_main: boolean
|
||||
broadcast_area: Collection
|
||||
languages: Collection
|
||||
timezones: Collection
|
||||
video_format: string
|
||||
}
|
||||
|
||||
export class Feed {
|
||||
channelId: string
|
||||
channel?: Channel
|
||||
id: string
|
||||
name: string
|
||||
isMain: boolean
|
||||
broadcastAreaCodes: Collection
|
||||
broadcastCountryCodes: Collection
|
||||
broadcastCountries?: Collection
|
||||
broadcastRegionCodes: Collection
|
||||
broadcastRegions?: Collection
|
||||
broadcastSubdivisionCodes: Collection
|
||||
broadcastSubdivisions?: Collection
|
||||
languageCodes: Collection
|
||||
languages?: Collection
|
||||
timezoneIds: Collection
|
||||
timezones?: Collection
|
||||
videoFormat: string
|
||||
|
||||
constructor(data: FeedData) {
|
||||
this.channelId = data.channel
|
||||
this.id = data.id
|
||||
this.name = data.name
|
||||
this.isMain = data.is_main
|
||||
this.broadcastAreaCodes = new Collection(data.broadcast_area)
|
||||
this.languageCodes = new Collection(data.languages)
|
||||
this.timezoneIds = new Collection(data.timezones)
|
||||
this.videoFormat = data.video_format
|
||||
this.broadcastCountryCodes = new Collection()
|
||||
this.broadcastRegionCodes = new Collection()
|
||||
this.broadcastSubdivisionCodes = new Collection()
|
||||
|
||||
this.broadcastAreaCodes.forEach((areaCode: string) => {
|
||||
const [type, code] = areaCode.split('/')
|
||||
|
||||
switch (type) {
|
||||
case 'c':
|
||||
this.broadcastCountryCodes.add(code)
|
||||
break
|
||||
case 'r':
|
||||
this.broadcastRegionCodes.add(code)
|
||||
break
|
||||
case 's':
|
||||
this.broadcastSubdivisionCodes.add(code)
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
withChannel(channelsGroupedById: Dictionary): this {
|
||||
this.channel = channelsGroupedById.get(this.channelId)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
withLanguages(languagesGroupedByCode: Dictionary): this {
|
||||
this.languages = this.languageCodes
|
||||
.map((code: string) => languagesGroupedByCode.get(code))
|
||||
.filter(Boolean)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
withTimezones(timezonesGroupedById: Dictionary): this {
|
||||
this.timezones = this.timezoneIds
|
||||
.map((id: string) => timezonesGroupedById.get(id))
|
||||
.filter(Boolean)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
withBroadcastSubdivisions(subdivisionsGroupedByCode: Dictionary): this {
|
||||
this.broadcastSubdivisions = this.broadcastSubdivisionCodes.map((code: string) =>
|
||||
subdivisionsGroupedByCode.get(code)
|
||||
)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
withBroadcastCountries(
|
||||
countriesGroupedByCode: Dictionary,
|
||||
regionsGroupedByCode: Dictionary,
|
||||
subdivisionsGroupedByCode: Dictionary
|
||||
): this {
|
||||
let broadcastCountries = new Collection()
|
||||
|
||||
if (this.isInternational()) {
|
||||
this.broadcastCountries = broadcastCountries
|
||||
return this
|
||||
}
|
||||
|
||||
this.broadcastCountryCodes.forEach((code: string) => {
|
||||
broadcastCountries.add(countriesGroupedByCode.get(code))
|
||||
})
|
||||
|
||||
this.broadcastRegionCodes.forEach((code: string) => {
|
||||
const region: Region = regionsGroupedByCode.get(code)
|
||||
if (region) {
|
||||
region.countryCodes.forEach((countryCode: string) => {
|
||||
broadcastCountries.add(countriesGroupedByCode.get(countryCode))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
this.broadcastSubdivisionCodes.forEach((code: string) => {
|
||||
const subdivision: Subdivision = subdivisionsGroupedByCode.get(code)
|
||||
if (subdivision) {
|
||||
broadcastCountries.add(countriesGroupedByCode.get(subdivision.countryCode))
|
||||
}
|
||||
})
|
||||
|
||||
this.broadcastCountries = broadcastCountries.uniq().filter(Boolean)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
withBroadcastRegions(regions: Collection): this {
|
||||
if (!this.broadcastCountries) return this
|
||||
const countriesCodes = this.broadcastCountries.map((country: Country) => country.code)
|
||||
|
||||
this.broadcastRegions = regions.filter((region: Region) => {
|
||||
if (region.code === 'INT') return false
|
||||
|
||||
return region.countryCodes.intersects(countriesCodes)
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
hasBroadcastArea(): boolean {
|
||||
return (
|
||||
this.isInternational() || (!!this.broadcastCountries && this.broadcastCountries.notEmpty())
|
||||
)
|
||||
}
|
||||
|
||||
getBroadcastCountries(): Collection {
|
||||
return this.broadcastCountries || new Collection()
|
||||
}
|
||||
|
||||
getBroadcastRegions(): Collection {
|
||||
return this.broadcastRegions || new Collection()
|
||||
}
|
||||
|
||||
getTimezones(): Collection {
|
||||
return this.timezones || new Collection()
|
||||
}
|
||||
|
||||
getLanguages(): Collection {
|
||||
return this.languages || new Collection()
|
||||
}
|
||||
|
||||
hasLanguages(): boolean {
|
||||
return !!this.languages && this.languages.notEmpty()
|
||||
}
|
||||
|
||||
hasLanguage(language: Language): boolean {
|
||||
return (
|
||||
!!this.languages &&
|
||||
this.languages.includes((_language: Language) => _language.code === language.code)
|
||||
)
|
||||
}
|
||||
|
||||
isInternational(): boolean {
|
||||
return this.broadcastAreaCodes.includes('r/INT')
|
||||
}
|
||||
|
||||
isBroadcastInSubdivision(subdivision: Subdivision): boolean {
|
||||
if (this.isInternational()) return false
|
||||
|
||||
return this.broadcastSubdivisionCodes.includes(subdivision.code)
|
||||
}
|
||||
|
||||
isBroadcastInCountry(country: Country): boolean {
|
||||
if (this.isInternational()) return false
|
||||
|
||||
return this.getBroadcastCountries().includes(
|
||||
(_country: Country) => _country.code === country.code
|
||||
)
|
||||
}
|
||||
|
||||
isBroadcastInRegion(region: Region): boolean {
|
||||
if (this.isInternational()) return false
|
||||
|
||||
return this.getBroadcastRegions().includes((_region: Region) => _region.code === region.code)
|
||||
}
|
||||
}
|
|
@ -8,3 +8,6 @@ export * from './language'
|
|||
export * from './country'
|
||||
export * from './region'
|
||||
export * from './subdivision'
|
||||
export * from './feed'
|
||||
export * from './broadcastArea'
|
||||
export * from './timezone'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
type LanguageProps = {
|
||||
type LanguageData = {
|
||||
code: string
|
||||
name: string
|
||||
}
|
||||
|
@ -7,8 +7,8 @@ export class Language {
|
|||
code: string
|
||||
name: string
|
||||
|
||||
constructor({ code, name }: LanguageProps) {
|
||||
this.code = code
|
||||
this.name = name
|
||||
constructor(data: LanguageData) {
|
||||
this.code = data.code
|
||||
this.name = data.name
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Collection } from '@freearhey/core'
|
||||
import { Collection, Dictionary } from '@freearhey/core'
|
||||
import { Subdivision } from '.'
|
||||
|
||||
type RegionProps = {
|
||||
type RegionData = {
|
||||
code: string
|
||||
name: string
|
||||
countries: string[]
|
||||
|
@ -9,11 +10,43 @@ type RegionProps = {
|
|||
export class Region {
|
||||
code: string
|
||||
name: string
|
||||
countries: Collection
|
||||
countryCodes: Collection
|
||||
countries?: Collection
|
||||
subdivisions?: Collection
|
||||
|
||||
constructor({ code, name, countries }: RegionProps) {
|
||||
this.code = code
|
||||
this.name = name
|
||||
this.countries = new Collection(countries)
|
||||
constructor(data: RegionData) {
|
||||
this.code = data.code
|
||||
this.name = data.name
|
||||
this.countryCodes = new Collection(data.countries)
|
||||
}
|
||||
|
||||
withCountries(countriesGroupedByCode: Dictionary): this {
|
||||
this.countries = this.countryCodes.map((code: string) => countriesGroupedByCode.get(code))
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
withSubdivisions(subdivisions: Collection): this {
|
||||
this.subdivisions = subdivisions.filter(
|
||||
(subdivision: Subdivision) => this.countryCodes.indexOf(subdivision.countryCode) > -1
|
||||
)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
getSubdivisions(): Collection {
|
||||
return this.subdivisions || new Collection()
|
||||
}
|
||||
|
||||
getCountries(): Collection {
|
||||
return this.countries || new Collection()
|
||||
}
|
||||
|
||||
includesCountryCode(code: string): boolean {
|
||||
return this.countryCodes.includes((countryCode: string) => countryCode === code)
|
||||
}
|
||||
|
||||
isWorldwide(): boolean {
|
||||
return this.code === 'INT'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,64 +1,193 @@
|
|||
import { URL, Collection } from '@freearhey/core'
|
||||
import { Category, Language } from './index'
|
||||
|
||||
type StreamProps = {
|
||||
name: string
|
||||
url: string
|
||||
filepath: string
|
||||
line: number
|
||||
channel?: string
|
||||
httpReferrer?: string
|
||||
httpUserAgent?: string
|
||||
label?: string
|
||||
quality?: string
|
||||
}
|
||||
import { URL, Collection, Dictionary } from '@freearhey/core'
|
||||
import { Feed, Channel, Category, Region, Subdivision, Country, Language } from './index'
|
||||
import parser from 'iptv-playlist-parser'
|
||||
|
||||
export class Stream {
|
||||
channel: string
|
||||
filepath: string
|
||||
line: number
|
||||
httpReferrer: string
|
||||
label: string
|
||||
name: string
|
||||
quality: string
|
||||
url: string
|
||||
httpUserAgent: string
|
||||
logo: string
|
||||
broadcastArea: Collection
|
||||
categories: Collection
|
||||
languages: Collection
|
||||
isNSFW: boolean
|
||||
id?: string
|
||||
groupTitle: string
|
||||
channelId?: string
|
||||
channel?: Channel
|
||||
feedId?: string
|
||||
feed?: Feed
|
||||
filepath?: string
|
||||
line: number
|
||||
label?: string
|
||||
verticalResolution?: number
|
||||
isInterlaced?: boolean
|
||||
httpReferrer?: string
|
||||
httpUserAgent?: string
|
||||
removed: boolean = false
|
||||
|
||||
constructor({
|
||||
channel,
|
||||
filepath,
|
||||
line,
|
||||
httpReferrer,
|
||||
label,
|
||||
name,
|
||||
quality,
|
||||
url,
|
||||
httpUserAgent
|
||||
}: StreamProps) {
|
||||
this.channel = channel || ''
|
||||
this.filepath = filepath
|
||||
this.line = line
|
||||
this.httpReferrer = httpReferrer || ''
|
||||
this.label = label || ''
|
||||
constructor(data: parser.PlaylistItem) {
|
||||
if (!data.name) throw new Error('"name" property is required')
|
||||
if (!data.url) throw new Error('"url" property is required')
|
||||
|
||||
const [channelId, feedId] = data.tvg.id.split('@')
|
||||
const { name, label, quality } = parseTitle(data.name)
|
||||
const { verticalResolution, isInterlaced } = parseQuality(quality)
|
||||
|
||||
this.id = data.tvg.id || undefined
|
||||
this.feedId = feedId || undefined
|
||||
this.channelId = channelId || undefined
|
||||
this.line = data.line
|
||||
this.label = label || undefined
|
||||
this.name = name
|
||||
this.quality = quality || ''
|
||||
this.url = url
|
||||
this.httpUserAgent = httpUserAgent || ''
|
||||
this.logo = ''
|
||||
this.broadcastArea = new Collection()
|
||||
this.categories = new Collection()
|
||||
this.languages = new Collection()
|
||||
this.isNSFW = false
|
||||
this.verticalResolution = verticalResolution || undefined
|
||||
this.isInterlaced = isInterlaced || undefined
|
||||
this.url = data.url
|
||||
this.httpReferrer = data.http.referrer || undefined
|
||||
this.httpUserAgent = data.http['user-agent'] || undefined
|
||||
this.groupTitle = 'Undefined'
|
||||
}
|
||||
|
||||
withChannel(channelsGroupedById: Dictionary): this {
|
||||
if (!this.channelId) return this
|
||||
|
||||
this.channel = channelsGroupedById.get(this.channelId)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
withFeed(feedsGroupedByChannelId: Dictionary): this {
|
||||
if (!this.channelId) return this
|
||||
|
||||
const channelFeeds = feedsGroupedByChannelId.get(this.channelId) || []
|
||||
if (this.feedId) this.feed = channelFeeds.find((feed: Feed) => feed.id === this.feedId)
|
||||
if (!this.feedId && !this.feed) this.feed = channelFeeds.find((feed: Feed) => feed.isMain)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
setId(id: string): this {
|
||||
this.id = id
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
setChannelId(channelId: string): this {
|
||||
this.channelId = channelId
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
setFeedId(feedId: string | undefined): this {
|
||||
this.feedId = feedId
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
setLabel(label: string): this {
|
||||
this.label = label
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
setQuality(quality: string): this {
|
||||
const { verticalResolution, isInterlaced } = parseQuality(quality)
|
||||
|
||||
this.verticalResolution = verticalResolution || undefined
|
||||
this.isInterlaced = isInterlaced || undefined
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
setHttpUserAgent(httpUserAgent: string): this {
|
||||
this.httpUserAgent = httpUserAgent
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
setHttpReferrer(httpReferrer: string): this {
|
||||
this.httpReferrer = httpReferrer
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
setFilepath(filepath: string): this {
|
||||
this.filepath = filepath
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
updateFilepath(): this {
|
||||
if (!this.channel) return this
|
||||
|
||||
this.filepath = `${this.channel.countryCode.toLowerCase()}.m3u`
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
getChannelId(): string {
|
||||
return this.channelId || ''
|
||||
}
|
||||
|
||||
getFeedId(): string {
|
||||
if (this.feedId) return this.feedId
|
||||
if (this.feed) return this.feed.id
|
||||
return ''
|
||||
}
|
||||
|
||||
getFilepath(): string {
|
||||
return this.filepath || ''
|
||||
}
|
||||
|
||||
getHttpReferrer(): string {
|
||||
return this.httpReferrer || ''
|
||||
}
|
||||
|
||||
getHttpUserAgent(): string {
|
||||
return this.httpUserAgent || ''
|
||||
}
|
||||
|
||||
getQuality(): string {
|
||||
if (!this.verticalResolution) return ''
|
||||
|
||||
let quality = this.verticalResolution.toString()
|
||||
|
||||
if (this.isInterlaced) quality += 'i'
|
||||
else quality += 'p'
|
||||
|
||||
return quality
|
||||
}
|
||||
|
||||
hasId(): boolean {
|
||||
return !!this.id
|
||||
}
|
||||
|
||||
hasQuality(): boolean {
|
||||
return !!this.verticalResolution
|
||||
}
|
||||
|
||||
getVerticalResolution(): number {
|
||||
if (!this.hasQuality()) return 0
|
||||
|
||||
return parseInt(this.getQuality().replace(/p|i/, ''))
|
||||
}
|
||||
|
||||
updateName(): this {
|
||||
if (!this.channel) return this
|
||||
|
||||
this.name = this.channel.name
|
||||
if (this.feed && !this.feed.isMain) {
|
||||
this.name += ` ${this.feed.name}`
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
updateId(): this {
|
||||
if (!this.channel) return this
|
||||
if (this.feed) {
|
||||
this.id = `${this.channel.id}@${this.feed.id}`
|
||||
} else {
|
||||
this.id = this.channel.id
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
normalizeURL() {
|
||||
const url = new URL(this.url)
|
||||
|
||||
|
@ -81,43 +210,82 @@ export class Stream {
|
|||
return !!this.channel
|
||||
}
|
||||
|
||||
hasCategories(): boolean {
|
||||
return this.categories.notEmpty()
|
||||
getBroadcastRegions(): Collection {
|
||||
return this.feed ? this.feed.getBroadcastRegions() : new Collection()
|
||||
}
|
||||
|
||||
noCategories(): boolean {
|
||||
return this.categories.isEmpty()
|
||||
getBroadcastCountries(): Collection {
|
||||
return this.feed ? this.feed.getBroadcastCountries() : new Collection()
|
||||
}
|
||||
|
||||
hasCategory(category: Category): boolean {
|
||||
return this.categories.includes((_category: Category) => _category.id === category.id)
|
||||
}
|
||||
|
||||
noLanguages(): boolean {
|
||||
return this.languages.isEmpty()
|
||||
}
|
||||
|
||||
hasLanguage(language: Language): boolean {
|
||||
return this.languages.includes((_language: Language) => _language.code === language.code)
|
||||
}
|
||||
|
||||
noBroadcastArea(): boolean {
|
||||
return this.broadcastArea.isEmpty()
|
||||
}
|
||||
|
||||
isInternational(): boolean {
|
||||
return this.broadcastArea.includes('r/INT')
|
||||
hasBroadcastArea(): boolean {
|
||||
return this.feed ? this.feed.hasBroadcastArea() : false
|
||||
}
|
||||
|
||||
isSFW(): boolean {
|
||||
return this.isNSFW === false
|
||||
return this.channel ? this.channel.isSFW() : true
|
||||
}
|
||||
|
||||
hasCategories(): boolean {
|
||||
return this.channel ? this.channel.hasCategories() : false
|
||||
}
|
||||
|
||||
hasCategory(category: Category): boolean {
|
||||
return this.channel ? this.channel.hasCategory(category) : false
|
||||
}
|
||||
|
||||
getCategoryNames(): string[] {
|
||||
return this.getCategories()
|
||||
.map((category: Category) => category.name)
|
||||
.sort()
|
||||
.all()
|
||||
}
|
||||
|
||||
getCategories(): Collection {
|
||||
return this.channel ? this.channel.getCategories() : new Collection()
|
||||
}
|
||||
|
||||
getLanguages(): Collection {
|
||||
return this.feed ? this.feed.getLanguages() : new Collection()
|
||||
}
|
||||
|
||||
hasLanguages() {
|
||||
return this.feed ? this.feed.hasLanguages() : false
|
||||
}
|
||||
|
||||
hasLanguage(language: Language) {
|
||||
return this.feed ? this.feed.hasLanguage(language) : false
|
||||
}
|
||||
|
||||
getBroadcastAreaCodes(): Collection {
|
||||
return this.feed ? this.feed.broadcastAreaCodes : new Collection()
|
||||
}
|
||||
|
||||
isBroadcastInSubdivision(subdivision: Subdivision): boolean {
|
||||
return this.feed ? this.feed.isBroadcastInSubdivision(subdivision) : false
|
||||
}
|
||||
|
||||
isBroadcastInCountry(country: Country): boolean {
|
||||
return this.feed ? this.feed.isBroadcastInCountry(country) : false
|
||||
}
|
||||
|
||||
isBroadcastInRegion(region: Region): boolean {
|
||||
return this.feed ? this.feed.isBroadcastInRegion(region) : false
|
||||
}
|
||||
|
||||
isInternational(): boolean {
|
||||
return this.feed ? this.feed.isInternational() : false
|
||||
}
|
||||
|
||||
getLogo(): string {
|
||||
return this?.channel?.logo || ''
|
||||
}
|
||||
|
||||
getTitle(): string {
|
||||
let title = `${this.name}`
|
||||
|
||||
if (this.quality) {
|
||||
title += ` (${this.quality})`
|
||||
if (this.getQuality()) {
|
||||
title += ` (${this.getQuality()})`
|
||||
}
|
||||
|
||||
if (this.label) {
|
||||
|
@ -127,15 +295,26 @@ export class Stream {
|
|||
return title
|
||||
}
|
||||
|
||||
getLabel(): string {
|
||||
return this.label || ''
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return this.id || ''
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
id: this.id,
|
||||
channel: this.channel,
|
||||
feed: this.feed,
|
||||
filepath: this.filepath,
|
||||
httpReferrer: this.httpReferrer,
|
||||
label: this.label,
|
||||
name: this.name,
|
||||
quality: this.quality,
|
||||
verticalResolution: this.verticalResolution,
|
||||
isInterlaced: this.isInterlaced,
|
||||
url: this.url,
|
||||
httpReferrer: this.httpReferrer,
|
||||
httpUserAgent: this.httpUserAgent,
|
||||
line: this.line
|
||||
}
|
||||
|
@ -143,18 +322,20 @@ export class Stream {
|
|||
|
||||
toJSON() {
|
||||
return {
|
||||
channel: this.channel || null,
|
||||
channel: this.channelId || null,
|
||||
feed: this.feedId || null,
|
||||
url: this.url,
|
||||
referrer: this.httpReferrer || null,
|
||||
user_agent: this.httpUserAgent || null
|
||||
user_agent: this.httpUserAgent || null,
|
||||
quality: this.getQuality() || null
|
||||
}
|
||||
}
|
||||
|
||||
toString(options: { public: boolean }) {
|
||||
let output = `#EXTINF:-1 tvg-id="${this.channel}"`
|
||||
let output = `#EXTINF:-1 tvg-id="${this.getId()}"`
|
||||
|
||||
if (options.public) {
|
||||
output += ` tvg-logo="${this.logo}" group-title="${this.groupTitle}"`
|
||||
output += ` tvg-logo="${this.getLogo()}" group-title="${this.groupTitle}"`
|
||||
}
|
||||
|
||||
if (this.httpReferrer) {
|
||||
|
@ -180,3 +361,29 @@ export class Stream {
|
|||
return output
|
||||
}
|
||||
}
|
||||
|
||||
function parseTitle(title: string): {
|
||||
name: string
|
||||
label: string
|
||||
quality: string
|
||||
} {
|
||||
const [, label] = title.match(/ \[(.*)\]$/) || [null, '']
|
||||
title = title.replace(new RegExp(` \\[${escapeRegExp(label)}\\]$`), '')
|
||||
const [, quality] = title.match(/ \(([0-9]+p)\)$/) || [null, '']
|
||||
title = title.replace(new RegExp(` \\(${quality}\\)$`), '')
|
||||
|
||||
return { name: title, label, quality }
|
||||
}
|
||||
|
||||
function escapeRegExp(text) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
|
||||
}
|
||||
|
||||
function parseQuality(quality: string): { verticalResolution: number; isInterlaced: boolean } {
|
||||
let [, verticalResolutionString] = quality.match(/^(\d+)/) || [null, undefined]
|
||||
const isInterlaced = /i$/i.test(quality)
|
||||
let verticalResolution = 0
|
||||
if (verticalResolutionString) verticalResolution = parseInt(verticalResolutionString)
|
||||
|
||||
return { verticalResolution, isInterlaced }
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
type SubdivisionProps = {
|
||||
import { Dictionary } from '@freearhey/core'
|
||||
import { Country } from '.'
|
||||
|
||||
type SubdivisionData = {
|
||||
code: string
|
||||
name: string
|
||||
country: string
|
||||
|
@ -7,11 +10,18 @@ type SubdivisionProps = {
|
|||
export class Subdivision {
|
||||
code: string
|
||||
name: string
|
||||
country: string
|
||||
countryCode: string
|
||||
country?: Country
|
||||
|
||||
constructor({ code, name, country }: SubdivisionProps) {
|
||||
this.code = code
|
||||
this.name = name
|
||||
this.country = country
|
||||
constructor(data: SubdivisionData) {
|
||||
this.code = data.code
|
||||
this.name = data.name
|
||||
this.countryCode = data.country
|
||||
}
|
||||
|
||||
withCountry(countriesGroupedByCode: Dictionary): this {
|
||||
this.country = countriesGroupedByCode.get(this.countryCode)
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
30
scripts/models/timezone.ts
Normal file
30
scripts/models/timezone.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { Collection, Dictionary } from '@freearhey/core'
|
||||
|
||||
type TimezoneData = {
|
||||
id: string
|
||||
utc_offset: string
|
||||
countries: string[]
|
||||
}
|
||||
|
||||
export class Timezone {
|
||||
id: string
|
||||
utcOffset: string
|
||||
countryCodes: Collection
|
||||
countries?: Collection
|
||||
|
||||
constructor(data: TimezoneData) {
|
||||
this.id = data.id
|
||||
this.utcOffset = data.utc_offset
|
||||
this.countryCodes = new Collection(data.countries)
|
||||
}
|
||||
|
||||
withCountries(countriesGroupedByCode: Dictionary): this {
|
||||
this.countries = this.countryCodes.map((code: string) => countriesGroupedByCode.get(code))
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
getCountries(): Collection {
|
||||
return this.countries || new Collection()
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ export class CategoryTable implements Table {
|
|||
const dataStorage = new Storage(DATA_DIR)
|
||||
const categoriesContent = await dataStorage.json('categories.json')
|
||||
const categories = new Collection(categoriesContent).map(data => new Category(data))
|
||||
const categoriesGroupedById = categories.keyBy((category: Category) => category.id)
|
||||
|
||||
const parser = new LogParser()
|
||||
const logsStorage = new Storage(LOGS_DIR)
|
||||
|
@ -19,13 +20,12 @@ export class CategoryTable implements Table {
|
|||
let data = new Collection()
|
||||
parser
|
||||
.parse(generatorsLog)
|
||||
.filter((logItem: LogItem) => logItem.filepath.includes('categories/'))
|
||||
.filter((logItem: LogItem) => logItem.type === 'category')
|
||||
.forEach((logItem: LogItem) => {
|
||||
const file = new File(logItem.filepath)
|
||||
const categoryId = file.name()
|
||||
const category: Category = categories.first(
|
||||
(category: Category) => category.id === categoryId
|
||||
)
|
||||
const category: Category = categoriesGroupedById.get(categoryId)
|
||||
|
||||
data.add([
|
||||
category ? category.name : 'ZZ',
|
||||
category ? category.name : 'Undefined',
|
||||
|
|
|
@ -12,34 +12,31 @@ export class CountryTable implements Table {
|
|||
|
||||
const countriesContent = await dataStorage.json('countries.json')
|
||||
const countries = new Collection(countriesContent).map(data => new Country(data))
|
||||
|
||||
const countriesGroupedByCode = countries.keyBy((country: Country) => country.code)
|
||||
const subdivisionsContent = await dataStorage.json('subdivisions.json')
|
||||
const subdivisions = new Collection(subdivisionsContent).map(data => new Subdivision(data))
|
||||
const subdivisionsGroupedByCode = subdivisions.keyBy(
|
||||
(subdivision: Subdivision) => subdivision.code
|
||||
)
|
||||
|
||||
const parser = new LogParser()
|
||||
const logsStorage = new Storage(LOGS_DIR)
|
||||
const generatorsLog = await logsStorage.load('generators.log')
|
||||
const parsed = parser.parse(generatorsLog)
|
||||
|
||||
let data = new Collection()
|
||||
parser
|
||||
.parse(generatorsLog)
|
||||
.filter(
|
||||
(logItem: LogItem) =>
|
||||
logItem.filepath.includes('countries/') || logItem.filepath.includes('subdivisions/')
|
||||
)
|
||||
|
||||
parsed
|
||||
.filter((logItem: LogItem) => logItem.type === 'subdivision')
|
||||
.forEach((logItem: LogItem) => {
|
||||
const file = new File(logItem.filepath)
|
||||
const code = file.name().toUpperCase()
|
||||
const [countryCode, subdivisionCode] = code.split('-') || ['', '']
|
||||
const country = countriesGroupedByCode.get(countryCode)
|
||||
|
||||
if (subdivisionCode) {
|
||||
const subdivision = subdivisions.first(
|
||||
(subdivision: Subdivision) => subdivision.code === code
|
||||
)
|
||||
if (country && subdivisionCode) {
|
||||
const subdivision = subdivisionsGroupedByCode.get(code)
|
||||
if (subdivision) {
|
||||
const country = countries.first(
|
||||
(country: Country) => country.code === subdivision.country
|
||||
)
|
||||
data.add([
|
||||
`${country.name}/${subdivision.name}`,
|
||||
` ${subdivision.name}`,
|
||||
|
@ -47,18 +44,28 @@ export class CountryTable implements Table {
|
|||
`<code>https://iptv-org.github.io/iptv/${logItem.filepath}</code>`
|
||||
])
|
||||
}
|
||||
} else if (countryCode === 'INT') {
|
||||
}
|
||||
})
|
||||
|
||||
parsed
|
||||
.filter((logItem: LogItem) => logItem.type === 'country')
|
||||
.forEach((logItem: LogItem) => {
|
||||
const file = new File(logItem.filepath)
|
||||
const code = file.name().toUpperCase()
|
||||
const [countryCode] = code.split('-') || ['', '']
|
||||
const country = countriesGroupedByCode.get(countryCode)
|
||||
|
||||
if (country) {
|
||||
data.add([
|
||||
'ZZ',
|
||||
'🌍 International',
|
||||
country.name,
|
||||
`${country.flag} ${country.name}`,
|
||||
logItem.count,
|
||||
`<code>https://iptv-org.github.io/iptv/${logItem.filepath}</code>`
|
||||
])
|
||||
} else {
|
||||
const country = countries.first((country: Country) => country.code === countryCode)
|
||||
data.add([
|
||||
country.name,
|
||||
`${country.flag} ${country.name}`,
|
||||
'ZZ',
|
||||
'Undefined',
|
||||
logItem.count,
|
||||
`<code>https://iptv-org.github.io/iptv/${logItem.filepath}</code>`
|
||||
])
|
||||
|
|
|
@ -11,6 +11,7 @@ export class LanguageTable implements Table {
|
|||
const dataStorage = new Storage(DATA_DIR)
|
||||
const languagesContent = await dataStorage.json('languages.json')
|
||||
const languages = new Collection(languagesContent).map(data => new Language(data))
|
||||
const languagesGroupedByCode = languages.keyBy((language: Language) => language.code)
|
||||
|
||||
const parser = new LogParser()
|
||||
const logsStorage = new Storage(LOGS_DIR)
|
||||
|
@ -19,13 +20,11 @@ export class LanguageTable implements Table {
|
|||
let data = new Collection()
|
||||
parser
|
||||
.parse(generatorsLog)
|
||||
.filter((logItem: LogItem) => logItem.filepath.includes('languages/'))
|
||||
.filter((logItem: LogItem) => logItem.type === 'language')
|
||||
.forEach((logItem: LogItem) => {
|
||||
const file = new File(logItem.filepath)
|
||||
const languageCode = file.name()
|
||||
const language: Language = languages.first(
|
||||
(language: Language) => language.code === languageCode
|
||||
)
|
||||
const language: Language = languagesGroupedByCode.get(languageCode)
|
||||
|
||||
data.add([
|
||||
language ? language.name : 'ZZ',
|
||||
|
|
|
@ -11,6 +11,7 @@ export class RegionTable implements Table {
|
|||
const dataStorage = new Storage(DATA_DIR)
|
||||
const regionsContent = await dataStorage.json('regions.json')
|
||||
const regions = new Collection(regionsContent).map(data => new Region(data))
|
||||
const regionsGroupedByCode = regions.keyBy((region: Region) => region.code)
|
||||
|
||||
const parser = new LogParser()
|
||||
const logsStorage = new Storage(LOGS_DIR)
|
||||
|
@ -19,22 +20,35 @@ export class RegionTable implements Table {
|
|||
let data = new Collection()
|
||||
parser
|
||||
.parse(generatorsLog)
|
||||
.filter((logItem: LogItem) => logItem.filepath.includes('regions/'))
|
||||
.filter((logItem: LogItem) => logItem.type === 'region')
|
||||
.forEach((logItem: LogItem) => {
|
||||
const file = new File(logItem.filepath)
|
||||
const regionCode = file.name().toUpperCase()
|
||||
const region: Region = regions.first((region: Region) => region.code === regionCode)
|
||||
const region: Region = regionsGroupedByCode.get(regionCode)
|
||||
|
||||
if (region) {
|
||||
data.add([
|
||||
region.name,
|
||||
region.name,
|
||||
logItem.count,
|
||||
`<code>https://iptv-org.github.io/iptv/${logItem.filepath}</code>`
|
||||
])
|
||||
} else {
|
||||
data.add([
|
||||
'ZZZ',
|
||||
'Undefined',
|
||||
logItem.count,
|
||||
`<code>https://iptv-org.github.io/iptv/${logItem.filepath}</code>`
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
data = data.orderBy(item => item[0])
|
||||
data = data
|
||||
.orderBy(item => item[0])
|
||||
.map(item => {
|
||||
item.shift()
|
||||
return item
|
||||
})
|
||||
|
||||
const table = new HTMLTable(data.all(), [
|
||||
{ name: 'Region', align: 'left' },
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="AndorraTV.ad",ATV (720p)
|
||||
https://videos.rtva.ad/live/rtva/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AndorraTV.ad@Web",Andorra TV (1080p)
|
||||
https://live-edge-eu-1.cdn.enetres.net/56495F77FD124FECA75590A906965F2C022/live-3000/index.m3u8
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
https://vo-live.cdb.cdn.orange.com/Content/Channel/AbuDhabiChannel/HLS/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AbuDhabiEmirates.ae",Abu Dhabi Emirates (1080p)
|
||||
https://vo-live.cdb.cdn.orange.com/Content/Channel/EmiratesChannel/HLS/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AbuDhabiSports1.ae",Abu Dhabi Sports 1
|
||||
#EXTINF:-1 tvg-id="AbuDhabiSports1.ae",Abu Dhabi Sports 1 (1080p)
|
||||
https://vo-live-media.cdb.cdn.orange.com/Content/Channel/AbuDhabiSportsChannel1/HLS/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AbuDhabiSports2.ae",Abu Dhabi Sports 2
|
||||
#EXTINF:-1 tvg-id="AbuDhabiSports2.ae",Abu Dhabi Sports 2 (1080p)
|
||||
https://tr-live-route.adm.tcon.hlit.hvds.tv/Content/Channel/AbuDhabiSportsChannel2/DASH/master.mpd
|
||||
#EXTINF:-1 tvg-id="AbuDhabiSports2.ae",Abu Dhabi Sports 2 (1080p)
|
||||
https://vo-live.cdb.cdn.orange.com/Content/Channel/AbuDhabiSportsChannel2/HLS/index.m3u8
|
||||
|
@ -19,6 +19,8 @@ https://mbc1-enc.edgenextcdn.net/out/v1/f5f319206ed740f9a831f2097c2ead23/index.m
|
|||
https://live.alarabiya.net/alarabiapublish/aswaaq.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AlArabiyaPrograms.ae",Al Arabiya Programs (1080p)
|
||||
https://d1j4r34gq3qw9y.cloudfront.net/out/v1/96804f3a14864641a21c25e8ca9afb74/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AlDafrahTV.ae",Al Dafrah TV (720p)
|
||||
https://rtmp-live-ingest-eu-west-3-universe-dacast-com.akamaized.net/transmuxv1/streams/dbb8ac05-a020-784c-3a95-6ed027941532.m3u8
|
||||
#EXTINF:-1 tvg-id="AlMashhad.ae",Al Mashhad (1080p)
|
||||
https://bcovlive-a.akamaihd.net/20c3ca22be3c4f03b30afbf3c92cfd14/ap-south-1/6313884884001/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AlQamarTV.ae",Al Qamar TV (1080p)
|
||||
|
@ -31,8 +33,6 @@ https://svs.itworkscdn.net/kablatvlive/kabtv1.smil/playlist.m3u8
|
|||
https://svs.itworkscdn.net/alwoustalive/alwoustatv.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AlYaumTV.ae",Al Yaum TV (1080p)
|
||||
https://iko-live.akamaized.net/AlyuamTV/master.m3u8
|
||||
#EXTINF:-1 tvg-id="AlYaumTV.ae",Al Yaum TV (720p)
|
||||
https://alyaum-tv.akamaized.net/hls/alyaum-tv.m3u8
|
||||
#EXTINF:-1 tvg-id="Alarabiya.ae",Alarabiya (1080p)
|
||||
https://d35j504z0x2vu2.cloudfront.net/v1/master/0bc8e8376bd8417a1b6761138aa41c26c7309312/al-arabiya/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Alarabiya.ae",Alarabiya (1080p)
|
||||
|
@ -63,7 +63,7 @@ https://dmieigthvllta.cdn.mgmlcdn.com/dubaitvht/smil:dubaitv.stream.smil/chunkli
|
|||
https://dmiffthftl.cdn.mangomolo.com/dubaizaman/smil:dubaizaman.stream.smil/chunklist.m3u8
|
||||
#EXTINF:-1 tvg-id="FujairahTV.ae",Fujairah TV (720p)
|
||||
https://live.kwikmotion.com/fujairahlive/fujairah.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="JustVogueTV.ae",Just Vogue TV
|
||||
#EXTINF:-1 tvg-id="JustVogueTV.ae",Just Vogue TV (1080p)
|
||||
https://ip100.radyotelekomtv.com:3873/stream/play.m3u8
|
||||
#EXTINF:-1 tvg-id="Majid.ae",Majid TV (1080p)
|
||||
https://vo-live.cdb.cdn.orange.com/Content/Channel/MajidChildrenChannel/HLS/index.m3u8
|
||||
|
@ -151,9 +151,9 @@ https://weyyak-live.akamaized.net/weyyak_mix/index.m3u8
|
|||
https://weyyak-live.akamaized.net/weyyak_nawaem/index.m3u8
|
||||
#EXTINF:-1 tvg-id="YasTV.ae",Yas TV (1080p)
|
||||
https://vo-live.cdb.cdn.orange.com/Content/Channel/YASSportsChannel/HLS/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AlDafrahTV.ae",Al Dafrah TV (720p)
|
||||
https://rtmp-live-ingest-eu-west-3-universe-dacast-com.akamaized.net/transmuxv1/streams/dbb8ac05-a020-784c-3a95-6ed027941532.m3u8
|
||||
#EXTINF:-1 tvg-id="ZeeAlwan.ae",Zee Alwan (720p) [Geo-blocked]
|
||||
https://weyyak-live.akamaized.net/weyyak_zee_alwan/index.m3u8
|
||||
#EXTINF:-1 tvg-id="ZeeAflam.ae",Zee Aflam (720p) [Geo-blocked]
|
||||
https://weyyak-live.akamaized.net/weyyak_zee_aflam/index.m3u8
|
||||
#EXTINF:-1 tvg-id="ZeeAlwan.ae",Zee Alwan (720p) [Geo-blocked]
|
||||
https://weyyak-live.akamaized.net/weyyak_zee_alwan/index.m3u8
|
||||
#EXTINF:-1 tvg-id="SpacetoonArabic.ae",Spacetoon Arabic (1080p)
|
||||
https://shd-gcp-live.edgenextcdn.net/live/bitmovin-spacetoon/d8382fb9ab4b2307058f12c7ea90db54/index.m3u8
|
||||
|
|
|
@ -19,6 +19,8 @@ https://live1.mediadesk.al/oranews.m3u8
|
|||
http://198.244.188.94/panorama/livestream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ReportTV.al",Report TV (720p)
|
||||
https://deb10stream.duckdns.org/hls/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="Syri.al",Syri (720p) [Not 24/7]
|
||||
https://stream.syritv.al/SyriTV/index.m3u8
|
||||
#EXTINF:-1 tvg-id="TopChannel.al",Top News (720p)
|
||||
https://trueodin.serv00.net/?t=topnewsal
|
||||
#EXTINF:-1 tvg-id="TropojaTelevizion.al",Tropoja TV (1080p)
|
||||
|
@ -27,5 +29,3 @@ https://live.prostream.al/al/smil:tropojatv.smil/playlist.m3u8
|
|||
https://fe.tring.al/delta/105/out/u/rdghfhsfhfshs.m3u8
|
||||
#EXTINF:-1 tvg-id="ZjarrTV.al",Zjarr TV (720p) [Not 24/7]
|
||||
https://cdn.jwplayer.com/live/events/r2qgHu7W.m3u8
|
||||
#EXTINF:-1 tvg-id="Syri.al",Syri (720p) [Not 24/7]
|
||||
https://stream.syritv.al/SyriTV/index.m3u8
|
||||
|
|
|
@ -5,32 +5,8 @@ http://stream01.vnet.am/AmediaPremium/mono.m3u8
|
|||
http://stream02.vnet.am/Kinoman/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="FightBox.nl",FightBox
|
||||
http://stream01.vnet.am/Fightbox/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="Fox.ru",Fox
|
||||
https://stream01.vnet.am/Fox/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="Hollywood.ru",Hollywood
|
||||
http://stream01.vnet.am/ParamountChannel/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="NationalGeographicWild.ru",National Geographic Wild
|
||||
http://stream02.vnet.am/NatGeoWild/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="NicktoonsCIS.ru",Nicktoons
|
||||
https://stream01.vnet.am/Boomerang/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="BoksTV.ru",Бокс ТВ
|
||||
http://stream01.vnet.am/BoksTv/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="Detskimir.ru",Детский мир
|
||||
http://stream01.vnet.am/CartoonNetwork/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="Domkino.ru",Дом Кино
|
||||
https://stream01.vnet.am/DomKino/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="India.ru",Индия
|
||||
https://stream01.vnet.am/ZeeTV/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="CarouselInternational.ru",Карусель Int
|
||||
http://stream02.vnet.am/Karusel/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="KinopremyeraHD.ru",Кинопремьера HD
|
||||
http://stream02.vnet.am/Kinopremera/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="KukhnyaTV.ru",Кухня ТВ HD
|
||||
http://stream01.vnet.am/KukhnyaTv/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="MuzTV.ru",Муз ТВ
|
||||
http://stream01.vnet.am/MuzTv/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="Mult.ru",Мульт
|
||||
http://stream01.vnet.am/Mult/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="Nauka.ru",Наука 2.0
|
||||
http://stream01.vnet.am/Nauka/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="NTVMir.ru",НТВ Мир
|
||||
|
@ -41,7 +17,5 @@ http://stream01.vnet.am/Evrika/mono.m3u8
|
|||
http://stream01.vnet.am/Perec/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="FridayInternational.ru",Пятница International
|
||||
https://stream01.vnet.am/Pyatnica/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="STSkids.ru",СТС Kids
|
||||
http://stream02.vnet.am/DisneyChannel/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="Telecafe.ru",Телекафе
|
||||
http://stream01.vnet.am/Telekafe/mono.m3u8
|
||||
|
|
|
@ -16,9 +16,9 @@ https://panel.dattalive.com/6605140/smil:6605140.smil/playlist.m3u8
|
|||
#EXTVLCOPT:http-user-agent=iPhone
|
||||
https://g1.vxral-hor.transport.edge-access.net/a15/ngrp:a24-100056_all/a24-100056.m3u8
|
||||
#EXTINF:-1 tvg-id="AiredeSantaFe.ar",Aire de Santa Fe (1080p)
|
||||
https://unlimited1-us.dps.live/airedesantafetv/airedesantafetv.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AlternaTV.ar",Alterna TV (720p)
|
||||
https://alternatv.ar/stream/hls/live.m3u8
|
||||
https://unlimited1-us.dps.live/airedesantafetv/airedesantafetv.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Am1020.ar",Am1020 (360p) [Geo-blocked]
|
||||
http://51.79.83.93:9998/live/am1020/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AmericaTV.ar",America TV (480p)
|
||||
|
|
|
@ -27,6 +27,8 @@ https://bitcdn-kronehit.bitmovin.com/v2/hls/playlist.m3u8
|
|||
https://streaming13.huberwebmedia.at/LiveApp/streams/985585225397790082777809.m3u8
|
||||
#EXTINF:-1 tvg-id="Okto.at",Okto TV (1080p)
|
||||
https://cdn3.wowza.com/1/MHFtazJReW5rOFhP/N2NWNDZ2/hls/live/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ORF1HD.at",ORF 1 HD
|
||||
https://s6.hopslan.com/orfx11/index.m3u8
|
||||
#EXTINF:-1 tvg-id="ORF1HD.at" http-referrer="https://livestreamde.com/",ORF 1 HD
|
||||
#EXTVLCOPT:http-referrer=https://livestreamde.com/
|
||||
https://strm.hdtvizlecanli.com/live/orf1.m3u8
|
||||
|
@ -48,6 +50,8 @@ http://iptv.rtv-ooe.at/stream.m3u8
|
|||
https://m317.video-stream-hosting.de/gzSoftware-live/_definst_/smil:livestream.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ServusTV.at",ServusTV (1080p) [Geo-blocked]
|
||||
https://stv-live.akamaized.net/hls/live/2031011/lingeoSTVATwebPri/master.m3u8
|
||||
#EXTINF:-1 tvg-id="SteiermarkTV.at",Steiermark TV (1080p)
|
||||
https://h056.video-stream-hosting.de/easycast8-live/_definst_/mp4:livestreamhd4/playlist.m3u8?ref=
|
||||
#EXTINF:-1 tvg-id="SwamijiTVAmerican.at",Swamiji TV American (1080p) [Not 24/7]
|
||||
https://stream.swamiji.tv/YogaIPTV/smil:YogaStreamUS.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="SwamijiTVAustralian.at",Swamiji TV Australian (1080p) [Not 24/7]
|
||||
|
@ -64,9 +68,3 @@ http://89.187.168.245:8080/live/sUPPERchannel2/index.m3u8
|
|||
https://live1.markenfunk.com/t1/ngrp:live_all/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="W24.at",W24 (720p) [Not 24/7]
|
||||
https://ms01.w24.at/W24/smil:liveevent.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ORF1HD.at",ORF 1 HD
|
||||
https://s6.hopslan.com/orfx1/index.m3u8
|
||||
#EXTINF:-1 tvg-id="ORF1HD.at",ORF 1 HD
|
||||
https://s6.hopslan.com/orfx11/index.m3u8
|
||||
#EXTINF:-1 tvg-id="SteiermarkTV.at",Steiermark TV (1080p)
|
||||
https://h056.video-stream-hosting.de/easycast8-live/_definst_/mp4:livestreamhd4/playlist.m3u8?ref=
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
https://2gblive.akamaized.net/hls/live/2033805/2GB/index.m3u8
|
||||
#EXTINF:-1 tvg-id="3AW.au",3AW Melbourne (1080p)
|
||||
https://3awlive.akamaized.net/hls/live/2032295/3AW/index.m3u8
|
||||
#EXTINF:-1 tvg-id="3TamilTV.au",3 Tamil TV (720p) [Not 24/7]
|
||||
https://6n3yogbnd9ok-hls-live.5centscdn.com/threetamil/d0dbe915091d400bd8ee7f27f0791303.sdp/index.m3u8
|
||||
#EXTINF:-1 tvg-id="6PR.au",6PR Perth (1080p)
|
||||
https://6prlive.akamaized.net/hls/live/2033806/6PR/index.m3u8
|
||||
#EXTINF:-1 tvg-id="9GemSydney.au",9Gem (720p) [Geo-blocked]
|
||||
|
@ -13,12 +11,8 @@ https://9now-livestreams.akamaized.net/hls/live/2008311/gem-syd/master.m3u8
|
|||
https://9now-livestreams.akamaized.net/hls/live/2008312/go-syd/master.m3u8
|
||||
#EXTINF:-1 tvg-id="9LifeSydney.au",9Life (720p) [Geo-blocked]
|
||||
https://9now-livestreams.akamaized.net/hls/live/2008313/life-syd/master.m3u8
|
||||
#EXTINF:-1 tvg-id="10BoldSydney.au",10 Bold (720p) [Geo-blocked]
|
||||
https://i.mjh.nz/10bold-nsw.m3u8
|
||||
#EXTINF:-1 tvg-id="10BoldAdelaide.au",10 Bold Adelaide (1080p)
|
||||
https://dce3793146fef017.mediapackage.us-west-2.amazonaws.com/out/v1/55cdf73af7894775ba6de8f57482b66a/CMAF_HLS/index.m3u8
|
||||
#EXTINF:-1 tvg-id="10PeachSydney.au",10 Peach (720p) [Geo-blocked]
|
||||
https://i.mjh.nz/10peach-nsw.m3u8
|
||||
#EXTINF:-1 tvg-id="ABCAustralia.au",ABC Australia
|
||||
https://abc-news-dmd-streams-1.akamaized.net/out/v1/701126012d044971b3fa89406a440133/index.m3u8
|
||||
#EXTINF:-1 tvg-id="ABCMESydney.au",ABC Me (720p)
|
||||
|
@ -41,8 +35,6 @@ https://c.mjh.nz/abc-wa.m3u8
|
|||
https://c.mjh.nz/abc-tv-plus.m3u8
|
||||
#EXTINF:-1 tvg-id="ABCTVNSW.au",ABC TV Sydney (720p)
|
||||
https://c.mjh.nz/abc-nsw.m3u8
|
||||
#EXTINF:-1 tvg-id="AUSTamilTV.au",AUS Tamil TV (720p) [Not 24/7]
|
||||
https://bk7l2pn7dx53-hls-live.5centscdn.com/austamil/fe01ce2a7fbac8fafaed7c982a04e229.sdp/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AusbizTV.au",ausbiz TV (720p) [Not 24/7]
|
||||
https://d9quh89lh7dtw.cloudfront.net/public-output/index.m3u8
|
||||
#EXTINF:-1 tvg-id="BloombergTVAustralia.au",Bloomberg TV Australia (270p)
|
||||
|
@ -61,8 +53,6 @@ https://9now-livestreams-fhd-t.akamaized.net/u/prod/simulcast/mel/ch9/hls/r1/ind
|
|||
https://9now-livestreams-fhd-t.akamaized.net/u/prod/simulcast/per/ch9/hls/r1/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Channel9Sydney.au",Channel 9 Sydney (720p) [Geo-blocked]
|
||||
https://9now-livestreams-fhd-t.akamaized.net/u/prod/simulcast/syd/ch9/hls/r1/index.m3u8
|
||||
#EXTINF:-1 tvg-id="10Sydney.au",Channel 10 (720p) [Geo-blocked]
|
||||
https://i.mjh.nz/10-nsw.m3u8
|
||||
#EXTINF:-1 tvg-id="Channel44.au",Channel 44 (480p)
|
||||
https://d1k6kax80wecy5.cloudfront.net/WFqZJc/index.m3u8
|
||||
#EXTINF:-1 tvg-id="CTBPerth.au",CTB Perth (720p)
|
||||
|
@ -73,16 +63,10 @@ https://movies.ctbperth.net.au/hls/stream.m3u8
|
|||
https://news.ctbperth.net.au/hls/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="ExpoChannel.au",Expo Channel (360p)
|
||||
https://tvsnhlslivetest.akamaized.net/hls/live/2034711/EXPO-MSL4/master.m3u8
|
||||
#EXTINF:-1 tvg-id="GOOD.au",GOOD.
|
||||
https://i.mjh.nz/.r/good-dot.m3u8
|
||||
#EXTINF:-1 tvg-id="HopeChannelAustralia.au",Hope Channel Australia (1080p)
|
||||
https://videodelivery.net/9fb3596948ddf463fde0ec4b85625b24/manifest/video.m3u8
|
||||
#EXTINF:-1 tvg-id="IndoOzTV.au",Indo Oz TV (720p)
|
||||
https://stream.e2is.in/hls/indoztv.m3u8
|
||||
#EXTINF:-1 tvg-id="JonmoBhumiTV.au",JonmoBhumi TV (720p) [Not 24/7]
|
||||
https://us170.jagobd.com:447/c3VydmVyX8RpbEU9Mi8xNy8yMDE0GIDU6RgzQ6NTAgdEoaeFzbF92YWxIZTO0U0ezN1IzMyfvcGVMZEJCTEFWeVN3PTOmdFsaWRtaW51aiPhnPTI/jonmobhumitv.stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="M4TVMalayalam.au",M4TV Malayalam (1080p) [Not 24/7]
|
||||
https://app.m4stream.live/mfourmalayalamhls/live.m3u8
|
||||
#EXTINF:-1 tvg-id="",Race Central TV (720p) [Not 24/7]
|
||||
https://nrpus.bozztv.com/36bay2/gusa-racecentral/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Racingcom.au",Racing.com (720p)
|
||||
|
@ -99,15 +83,11 @@ https://skylivetab-new.akamaized.net/hls/live/2038780/sky1/index.m3u8
|
|||
https://skylivetab-new.akamaized.net/hls/live/2038781/sky2/index.m3u8
|
||||
#EXTINF:-1 tvg-id="SkyThoroughbredCentral.au",Sky Thoroughbred Central (720p) [Geo-blocked]
|
||||
https://skylivetab-new.akamaized.net/hls/live/2038782/stcsd/index.m3u8
|
||||
#EXTINF:-1 tvg-id="tickerNews.au",Ticker News (1080p)
|
||||
https://cdn-uw2-prod.tsv2.amagi.tv/linear/amg01486-tickernews-tickernewsweb-ono/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="",Travel & Food TV (720p)
|
||||
https://nrpus.bozztv.com/36bay2/gusa-moviemagictv/index.m3u8
|
||||
#EXTINF:-1 tvg-id="TVSN.au",TVSN (1080p)
|
||||
https://tvsnhlslivetest.akamaized.net/hls/live/2034711/TVSN-MSL4/master.m3u8
|
||||
#EXTINF:-1 tvg-id="TVSNBeauty.au",TVSN Beauty (1080p)
|
||||
https://live-tvsn.simplestreamcdn.com/live12/tvsnbeauty/bitrate1.isml/.m3u8
|
||||
#EXTINF:-1 tvg-id="",TVSN Catchup TV (1080p)
|
||||
https://tvsnshowsvod.akamaized.net/CatchUpTV/TVSN-AU/2411171630/TVSN_2411171630.m3u8
|
||||
#EXTINF:-1 tvg-id="TVSNJewellery.au",TVSN Jewellery (1080p)
|
||||
https://live-tvsn.simplestreamcdn.com/live13/tvsnjewellery/bitrate1.isml/.m3u8
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="EuronewsEnglish.fr",Euronews English (720p)
|
||||
https://euronews-euronews-world-1-au.samsung.wurl.tv/manifest/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="HorseCountryTV.uk",Horse and Country (720p)
|
||||
https://hncfree-samsungau.amagi.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="InsightTV.nl",Insight TV (720p)
|
||||
|
@ -11,18 +9,12 @@ https://introuble-samsungau.amagi.tv/playlist.m3u8
|
|||
https://inwild-samsungau.amagi.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="InWonder.nl",InWonder (720p)
|
||||
https://inwonder-samsungau.amagi.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RealFamilies.au",Real Families (Australia) (720p)
|
||||
https://lds-realfamilies-samsunguau.amagi.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RealStories.uk",Real Stories (720p)
|
||||
https://lds-realstories-samsungau.amagi.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Rialto.nz",Rialto (1080p)
|
||||
https://rialto-rialto-samsungaustralia.amagi.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RyanandFriends.us",Ryan and Friends (1080p)
|
||||
https://ryanandfriends-samsungau.amagi.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Tastemade.au",Tastemade Australia (1080p)
|
||||
https://tmint-aus-samsungau.amagi.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Timeline.us",Time Line Australia (720p)
|
||||
https://lds-timeline-samsungau.amagi.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TraceSportStars.fr",Trace Sport Stars (Australia) (1080p)
|
||||
https://lightning-tracesport-samsungau.amagi.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TraceUrban.fr",Trace Urban (Australia) (1080p)
|
||||
|
|
|
@ -3,14 +3,10 @@
|
|||
https://cdn01.setar.aw/Canal49/canal49/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ArubaTVPlus.aw",ArubaTV + (1080p)
|
||||
https://livertmptwo.com:19360/atvplusrelay/atvplusrelay.m3u8
|
||||
#EXTINF:-1 tvg-id="BalchiTV.aw",Balchi TV (720p)
|
||||
https://livertmptwo.com:19360/balchirelaytv/balchirelaytv.m3u8
|
||||
#EXTINF:-1 tvg-id="CoolFM989.aw",Cool FM 98.9 (720p)
|
||||
https://live2.tensila.com/cool-v-1.arubara/hls/master.m3u8
|
||||
#EXTINF:-1 tvg-id="",Dushi TV (720p)
|
||||
https://livertmptwo.com:19360/dushitvrelay/dushitvrelay.m3u8
|
||||
#EXTINF:-1 tvg-id="HeartRadioAruba.aw",Heart Radio Aruba (720p) [Not 24/7]
|
||||
https://live2.tensila.com/heart-v-1.heartar/hls/live/mystream.m3u8
|
||||
#EXTINF:-1 tvg-id="Hit94FM.aw",Hit 94 FM (720p)
|
||||
https://565280.gvideo.io/cmaf/565280_2069313/master.m3u8
|
||||
#EXTINF:-1 tvg-id="NosIslaTV.aw" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36",Nos Isla TV (1080p) [Not 24/7]
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36
|
||||
https://backend-server-dot-telearuba-app.appspot.com/media/livestream23/playlist.m3u8
|
||||
|
@ -25,3 +21,6 @@ https://backend-server-dot-telearuba-app.appspot.com/media/livestream13/playlist
|
|||
https://cdn01.setar.aw/Telearuba/telearuba/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="VIPTV.aw",VIP TV (720p)
|
||||
https://ed5ov1.live.opencaster.com/bkyqeDgfaukC/index.m3u8
|
||||
#EXTINF:-1 tvg-id="X1027FM.aw" http-referrer="https://player.castr.com/live_21e811c0d60d11eeaa1a471c2c967e4a",X 102.7 FM (720p) [Not 24/7]
|
||||
#EXTVLCOPT:http-referrer=https://player.castr.com/live_21e811c0d60d11eeaa1a471c2c967e4a
|
||||
https://stream.castr.com/65dee3aad6beacddbd6cd1af/live_21e811c0d60d11eeaa1a471c2c967e4a/index.m3u8
|
||||
|
|
|
@ -35,8 +35,6 @@ https://raw.githubusercontent.com/UzunMuhalefet/streams/refs/heads/main/myvideo-
|
|||
https://str.yodacdn.net/medeniyyet/index.m3u8
|
||||
#EXTINF:-1 tvg-id="",MTV TV
|
||||
https://raw.githubusercontent.com/UzunMuhalefet/streams/refs/heads/main/myvideo-az/mtv-azerbaycan.m3u8
|
||||
#EXTINF:-1 tvg-id="ShowPlusTV.az",Show Plus TV (720p)
|
||||
https://glb.bozztv.com/glb/ssh101/showplus/index.m3u8
|
||||
#EXTINF:-1 tvg-id="SpaceTV.az",Space TV
|
||||
https://raw.githubusercontent.com/UzunMuhalefet/streams/main/myvideo-az/space-tv.m3u8
|
||||
#EXTINF:-1 tvg-id="",TMB TV
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="AlJazeeraBalkans.ba",Al Jazeera Balkans (1080p)
|
||||
https://live-hls-apps-ajb-v3-fa.getaj.net/AJB/index.m3u8
|
||||
#EXTINF:-1 tvg-id="B1TV.ba",B1 TV (1080p) [Not 24/7]
|
||||
http://wowza.bihnet.net:88/hls/b1-live.m3u8
|
||||
#EXTINF:-1 tvg-id="BHRT.ba",BHRT (720p) [Geo-blocked]
|
||||
https://bhrtstream.bhtelecom.ba/bhrtportal.m3u8
|
||||
#EXTINF:-1 tvg-id="BHRT.ba",BHRT (270p) [Geo-blocked]
|
||||
|
@ -25,8 +23,6 @@ http://glasdrine.cutuk.net:8081/433ssdsw/GlasDrineSD/playlist.m3u8
|
|||
https://prd-hometv-live-open.spectar.tv/ERO_1_083/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RTVZenica.ba",RTV Zenica (720p)
|
||||
https://stream.rtvze.ba/live/123/123.m3u8
|
||||
#EXTINF:-1 tvg-id="SevdahTV.ba",Sevdah TV (288p)
|
||||
https://restreamer2.tnt.ba/hls/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="SuperTV.ba",Super TV Media (720p)
|
||||
https://mirtv.club/live/mirtv/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Televizija5.ba",Televizija 5 (576p)
|
||||
|
@ -37,3 +33,5 @@ https://live.tv-m.net/hls/stream.m3u8
|
|||
https://restreamer1.tnt.ba/hls/tntkids.m3u8
|
||||
#EXTINF:-1 tvg-id="TVSlonExtra.ba",TV Slon Extra (1080p) [Not 24/7]
|
||||
http://31.47.0.130:8082
|
||||
#EXTINF:-1 tvg-id="NTVICKakanj.ba",NTV IC Kakanj (720p)
|
||||
https://lon.rtsp.me/dEqnY-myGj84bKrieCIPfA/1743271667/hls/3dH3YAD6.m3u8
|
||||
|
|
|
@ -11,14 +11,10 @@ https://www.btvlive.gov.bd/streams/ef8b8bbc-98b7-4ba7-a49d-a0adaf259d35/ES/d96eb
|
|||
https://amigofx.com:1936/channelsreporter/channelsreporter/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="DeshiTV.ca",Deshi TV (720p)
|
||||
https://deshitv.deshitv24.net/live/myStream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ETenTV.bd",E Ten TV (1080p) [Not 24/7]
|
||||
https://iptvbd.live/test/1080.m3u8
|
||||
#EXTINF:-1 tvg-id="EkusheyTV.bd",Ekushey TV (480p)
|
||||
https://ekusheyserver.com/etvlivesn.m3u8
|
||||
#EXTINF:-1 tvg-id="JamunaTV.bd",Jamuna TV (720p) [Geo-blocked]
|
||||
http://113.212.111.246:8080/hls/col12.m3u8
|
||||
#EXTINF:-1 tvg-id="",JaTV (720p)
|
||||
https://cloud2.smartsolbd.com/live/jatvbd/index.m3u8
|
||||
#EXTINF:-1 tvg-id="ProbashiTVNews.ca",Probashi TV News (720p)
|
||||
http://probashi.alvegroups.com:8081/probashitv/probashi/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="SangsadTV.bd",Sangsad TV (1080p)
|
||||
|
|
|
@ -7,32 +7,24 @@ https://live.zendzend.com/streams/29375_107244/playlist.m3u8
|
|||
https://bel-live-hls.akamaized.net/hls/live/2038650/BEL-Live-HLS/master.m3u8
|
||||
#EXTINF:-1 tvg-id="BAMTV.be",Bel'Afrika Media TV (1080p)
|
||||
https://goccn.cloud/hls/belafrikatv/index.m3u8
|
||||
#EXTINF:-1 tvg-id="BelgianFederalParliament.be",Belgian Federal Parliament (720p) [Not 24/7]
|
||||
http://livestream.parolis.be:1935/live/PLN_NL/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Bouke.be",Bouke [Geo-blocked]
|
||||
https://tvlocales-live.freecaster.com/live/95d2f70d-9229-478b-9aed-bc4fa220316d/95d2f70d-9229-478b-9aed-bc4fa220316d.isml/master.m3u8
|
||||
#EXTINF:-1 tvg-id="BX1.be",BX1 (720p) [Not 24/7]
|
||||
https://59959724487e3.streamlock.net/stream/live/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="CanalZoom.be",Canal Zoom [Geo-blocked]
|
||||
https://tvlocales-live.freecaster.com/live/95d2e3af-5ab8-45a9-9dc9-f544d006b5d5/95d2e3af-5ab8-45a9-9dc9-f544d006b5d5.isml/master.m3u8
|
||||
#EXTINF:-1 tvg-id="CityMusicTV.be",City Music TV (720p)
|
||||
https://5592f056abba8.streamlock.net/citytv/citytv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="EbS.be",EbS (1080p)
|
||||
https://streams.prd.commavservices.eu/live/ebs/index.m3u8
|
||||
#EXTINF:-1 tvg-id="EbSPlus.be",EbS+ (1080p)
|
||||
https://streams.prd.commavservices.eu/live/ebsplus/index.m3u8
|
||||
#EXTINF:-1 tvg-id="FunVision.be",Fun Radio Vision (720p) [Not 24/7]
|
||||
https://raw.githubusercontent.com/Sphinxroot/HSL/main/DM/be/FunRadio.m3u8
|
||||
#EXTINF:-1 tvg-id="HLNLive.be",HLN Live (720p)
|
||||
https://dpg-eventstreams.akamaized.net/hlnlivesrt-xmr/streamx/hlnlivesrt_720p.m3u8
|
||||
#EXTINF:-1 tvg-id="JapanimTV.be",Japanim TV (1080p)
|
||||
https://foxkidstv.be:3369/stream/play.m3u8
|
||||
#EXTINF:-1 tvg-id="JoeFM.be",Joe FM (720p) [Not 24/7]
|
||||
https://dpp-streamlive-plain.medialaancdn.be/joe_kijklive/plain/hls_hd.m3u8
|
||||
#EXTINF:-1 tvg-id="KetnetJunior.be",Ketnet Junior (720p)
|
||||
https://content.uplynk.com/channel/e11a05356cc44198977436418ad71832.m3u8
|
||||
#EXTINF:-1 tvg-id="KetnetJunior.be",Ketnet Junior (720p)
|
||||
https://content.uplynk.com/channel/e11a05356cc44198977436418ad71832.mpd
|
||||
#EXTINF:-1 tvg-id="LaUne.be",La Une (1080p)
|
||||
http://41.205.93.154/LA-UNE/mpegts
|
||||
#EXTINF:-1 tvg-id="LN24.be",LN24
|
||||
https://live-ln24.digiteka.com/1911668011/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Matele.be",MaTele (1080p) [Not 24/7]
|
||||
|
@ -47,10 +39,6 @@ https://streaming01.divercom.be/notele_live/direct.stream/playlist.m3u8
|
|||
https://live-video.dpgmedia.net/f1d26a28c95485cc/out/v1/5f60a245c110454fba652900ecf30ea2/index.m3u8
|
||||
#EXTINF:-1 tvg-id="RadioContact.be",Radio Contact (1080p)
|
||||
https://contact-live-hls.akamaized.net/hls/live/2038650/CONTACT-Live-HLS/master.m3u8
|
||||
#EXTINF:-1 tvg-id="RadioPROS.be",Radio PROS (720p) [Not 24/7]
|
||||
http://highvolume04.streampartner.nl/radiopros/livestream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RadioPROS.be",Radio PROS (720p) [Not 24/7]
|
||||
https://558bd16067b67.streamlock.net/radiopros/livestream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="",RTC Télé Liège [Geo-blocked]
|
||||
https://tvlocales-live.freecaster.com/live/95d2f6eb-6f01-4d1d-8543-d14966de7b04/95d2f6eb-6f01-4d1d-8543-d14966de7b04.isml/master.m3u8
|
||||
#EXTINF:-1 tvg-id="RTLTVI.be",RTL-TVI (1080p) [Not 24/7]
|
||||
|
@ -77,5 +65,3 @@ https://live-radio-cf-vrt.akamaized.net/groupb/live/0f394a26-c87d-475e-8590-e9c6
|
|||
https://dpp-live-events.medialaancdn.be/events/hls/aes/webstream1.m3u8
|
||||
#EXTINF:-1 tvg-id="VTM2.be",VTM 2 (720p)
|
||||
https://dpp-live-events.medialaancdn.be/events/hls/aes/webstream3.m3u8
|
||||
#EXTINF:-1 tvg-id="LaUne.be",La Une (1080p)
|
||||
http://41.205.93.154/LA-UNE/mpegts
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="AfricanewsFrench.fr",AfricaNews Français (720p)
|
||||
https://rakuten-africanews-2-be.samsung.wurl.tv/manifest/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AFVFamily.us",AFV Family
|
||||
https://futuretoday-afv-family-2-be.samsung.wurl.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="BloombergQuicktake.us",Bloomberg Quicktake
|
||||
https://bloomberg-quicktake-1-be.samsung.wurl.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="BloombergTV.us",Bloomberg TV
|
||||
https://bloomberg-bloomberg-1-be.samsung.wurl.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="EuronewsFrench.fr",Euronews
|
||||
https://rakuten-euronews-10-be.samsung.wurl.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RakutenTVFamilyMovies.fr",Rakuten Family
|
||||
https://rakuten-family-16-be.samsung.wurl.tv/playlist.m3u8
|
||||
|
|
|
@ -5,20 +5,12 @@ https://ms4.sedemosmi.tv/live/M3E5ajhtdjJkaXBscmZubmUxMmh1cjN1bjZrbm5wZW8/index.
|
|||
http://100automoto.tv:1935/bgtv1/autotv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AgroTV.bg",Agro TV (480p)
|
||||
https://restr2.bgtv.bg/agro/hls/agro.m3u8
|
||||
#EXTINF:-1 tvg-id="b1bbox.bg",B1B Box (720p)
|
||||
https://e105-ts.cdn.bg/b1b/fls/b1btv.stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="BalkanikaTV.bg",Balkanika TV (270p)
|
||||
rtsp://stream.teracomm.bg/balkanika
|
||||
#EXTINF:-1 tvg-id="CityTV.bg",City TV (576p) [Not 24/7]
|
||||
https://tv.city.bg/play/tshls/citytv/index.m3u8
|
||||
#EXTINF:-1 tvg-id="CodeFashionTV.bg",Code Fashion TV (1080p)
|
||||
https://cdn3.invivo.bg/Codefashion_5500/index.m3u8
|
||||
#EXTINF:-1 tvg-id="CodeHealthTV.bg",Code Health TV (1080p)
|
||||
https://cdn3.invivo.bg/codehealth_test/index.m3u8
|
||||
#EXTINF:-1 tvg-id="DSTV.bg",DSTV (614p) [Not 24/7]
|
||||
http://46.249.95.140:8081/hls/data.m3u8
|
||||
#EXTINF:-1 tvg-id="EuroFolkTV.bg",EuroFolk TV (720p)
|
||||
https://eurofolk.cdn.netbadgers.com/bg040/bg041/bg041.m3u8
|
||||
#EXTINF:-1 tvg-id="Eurocom.bg",Evrokom (360p)
|
||||
https://live.ecomservice.bg/hls/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="HopeChannelBulgaria.bg",Hope Channel Bulgaria
|
||||
|
@ -48,11 +40,7 @@ https://streamer103.neterra.tv/tiankov-orient/live.m3u8
|
|||
https://streamer103.neterra.tv/travel/live.m3u8
|
||||
#EXTINF:-1 tvg-id="TV1.bg",TV 1 (720p)
|
||||
https://tv1.cloudcdn.bg:8081/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="TVDarts.bg",TV Darts (576p)
|
||||
https://streamer103.neterra.tv/darts/live.m3u8
|
||||
#EXTINF:-1 tvg-id="TVZagora.bg",TV Zagora (576p)
|
||||
http://zagoratv.ddns.net:8080/tvzagora.m3u8
|
||||
#EXTINF:-1 tvg-id="TVart.bg",TVart (1080p)
|
||||
https://stream.osc.bg/tvart/Stream3/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TVNBulgaria.bg",TVN-Bulgaria (1080p)
|
||||
https://obs.friendshipchurch.eu/tvn/mystream.m3u8
|
||||
|
|
|
@ -153,3 +153,9 @@ https://fl1004.bozztv.com/ssh101/zoytvsports4/index.m3u8
|
|||
https://fl1004.bozztv.com/ssh101/zoytvsports5/index.m3u8
|
||||
#EXTINF:-1 tvg-id="ZoyTVTurcas.bo",Zoy TV Turcas
|
||||
https://fl1004.bozztv.com/ssh101/zoytvturcas/index.m3u8
|
||||
#EXTINF:-1 tvg-id="BoliviaTV.bo",Bolivia TV (720p)
|
||||
https://5fe2654d6127d.streamlock.net:443/boliviatv/videoboliviatv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ZoyTVSports1.bo",Zoy TV Sports 1 (1080p)
|
||||
https://ssh101stream.ssh101.com/akamaissh101/ssh101/zoytvsports/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ZoyTVTurcas.bo",Zoy TV Turcas (720p)
|
||||
https://ssh101stream.ssh101.com/akamaissh101/ssh101/zoytvturcas/playlist.m3u8
|
||||
|
|
|
@ -9,3 +9,5 @@ https://sincerecloud.stream/loadbalancer/public/actMdyoE.m3u8
|
|||
https://media.streambrothers.com:1936/8014/8014/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="NosTVBonaire.bq",Nos TV Bonaire (1080p)
|
||||
http://streaming.flamingotv.net/nostv/live.m3u8
|
||||
#EXTINF:-1 tvg-id="VozDiBonaireTV.bq",Voz Di Bonaire TV (720p)
|
||||
https://tv.westream.cloud/VozdiBonaireTV/VozdiBonaireTV.m3u8
|
||||
|
|
|
@ -599,3 +599,11 @@ https://video01.kshost.com.br/seap8272/seap8272/playlist.m3u8
|
|||
http://painelvj.com.br/tvaguaboa2/tvaguaboa2.sdp/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="WTVBrasil.br",WTV Brasil (720p)
|
||||
https://stmv1.srvstm.com/wtvbrasil/wtvbrasil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TVCancaoNova.br",TV Cancao Nova (720p)
|
||||
https://5c65286fc6ace.streamlock.net/cancaonova/CancaoNova.stream_720p/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TVMAX.br",TV MAX (720p)
|
||||
https://5cf4a2c2512a2.streamlock.net/tvmax/tvmax/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TVBrasil.br",TV Brasil (720p)
|
||||
https://tvbrasil-stream.ebc.com.br/index.m3u8
|
||||
#EXTINF:-1 tvg-id="RedeTV.br",Rede TV! (720p)
|
||||
https://cdn.jmvstream.com/w/AVJ-15235/playlist/playlist.m3u8
|
||||
|
|
|
@ -1 +1,7 @@
|
|||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="GuardianTalkRadio.bs",Guardian Talk Radio (1080p)
|
||||
https://cdn-edge1.streamcomedia.com/abr_tngr969fm/abr-tngr969fm_streams/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="IslandLuckTV.bs",Island Luck TV (1080p) [Geo-blocked]
|
||||
https://islandluck-edge1.streamcomedia.com/abr_islandluck-ott/abr-islandluck-ott_streams/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TheParliamentaryChannel.bs",The Parliamentary Channel (720p) [Not 24/7]
|
||||
https://zns-edge1.streamcomedia.cloud/abr_parliamentarychannel/abr-parliament_streams/playlist.m3u8
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
https://edge13.vedge.infomaniak.com/livecast/ik:alpen-wellelivestream/manifest.m3u8
|
||||
#EXTINF:-1 tvg-id="AlpenlandTV.ch",Alpenland TV (720p)
|
||||
https://edge14.vedge.infomaniak.com/livecast/ik:alpen-wellelivestream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="BlueSport2.ch",Blue Sport 2 (720p)
|
||||
http://62.210.211.188:2095/play/a00f
|
||||
#EXTINF:-1 tvg-id="Canal9.ch",Canal 9 en Français (1080p)
|
||||
https://livehd.vedge.infomaniak.com/livecast/livehd/master.m3u8
|
||||
#EXTINF:-1 tvg-id="CanalAlphaJura.ch",Canal Alpha Jura (1080p)
|
||||
|
@ -77,5 +79,3 @@ https://livevideo.infomaniak.com/streaming/livecast/tvm3/playlist.m3u8
|
|||
https://cdnapisec.kaltura.com/p/1719221/sp/171922100/playManifest/entryId/1_t5h46v64/format/applehttp/protocol/https/a.m3u8
|
||||
#EXTINF:-1 tvg-id="WedoBigStories.ch",Wedo Big Stories (1080p)
|
||||
https://weyyak-live.akamaized.net/wedo_big_stories/index.m3u8
|
||||
#EXTINF:-1 tvg-id="BlueSport2.ch",Blue Sport 2 (720p)
|
||||
http://62.210.211.188:2095/play/a00f
|
||||
|
|
|
@ -443,7 +443,7 @@ https://mdstrm.com/live-stream-playlist/6046495ddf98b007fa2fe807.m3u8
|
|||
https://mediacpstreamchile.com:1936/8028/8028/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ZappingMusic.cl",Zapping Music (720p)
|
||||
https://zmlive.zappingtv.com/zm-free/zm.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ZonaPlayTV.cl",Zona Play TV (720p)
|
||||
https://paneltv.online:1936/8100/8100/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ZonaLatina.cl",Zona Latina (480p)
|
||||
http://190.2.212.209:8050/play/a0oj
|
||||
#EXTINF:-1 tvg-id="ZonaPlayTV.cl",Zona Play TV (720p)
|
||||
https://paneltv.online:1936/8100/8100/playlist.m3u8
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="AndoTV.cn",Ando TV
|
||||
http://stream.qhbtv.com/adws/sd/live.m3u8
|
||||
#EXTINF:-1 tvg-id="AndoTV.cn",Ando TV (576p)
|
||||
http://119.1.122.186:4022/rtp/238.255.2.189:5999
|
||||
#EXTINF:-1 tvg-id="AnimationShowChannel.cn",Animation Show Channel
|
||||
|
@ -23,46 +25,10 @@ http://49.113.179.174:4022/udp/238.125.3.121:5140
|
|||
http://gslbservzqhsw.itv.cmvideo.cn/index.m3u8?Contentid=reallive-hdcctv1&channel-id=ystenlive&livemode=1&stbId=3
|
||||
#EXTINF:-1 tvg-id="CCTV1.cn",CCTV1 (576p)
|
||||
http://117.161.133.51:81/gitv_live/G_CCTV-1/G_CCTV-1.m3u8
|
||||
#EXTINF:-1 tvg-id="CCTV2.cn",CCTV2 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.102:10250
|
||||
#EXTINF:-1 tvg-id="CCTV2.cn",CCTV2 (576p)
|
||||
http://117.161.133.51:81/gitv_live/G_CCTV-2/G_CCTV-2.m3u8
|
||||
#EXTINF:-1 tvg-id="CCTV3.cn",CCTV3 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.122:10370
|
||||
#EXTINF:-1 tvg-id="",CCTV4 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.138:10466
|
||||
#EXTINF:-1 tvg-id="CCTV5.cn",CCTV5 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.123:10376
|
||||
#EXTINF:-1 tvg-id="CCTV5Plus.cn",CCTV5+ (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.254.96.234:9484
|
||||
#EXTINF:-1 tvg-id="CCTV6.cn",CCTV6 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.124:10382
|
||||
#EXTINF:-1 tvg-id="CCTV7.cn",CCTV7 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.103:10256
|
||||
#EXTINF:-1 tvg-id="CCTV8.cn",CCTV8 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.125:10388
|
||||
#EXTINF:-1 tvg-id="CCTV9.cn",CCTV9 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.104:10262
|
||||
#EXTINF:-1 tvg-id="CCTV10.cn",CCTV10 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.105:10268
|
||||
#EXTINF:-1 tvg-id="CCTV11.cn",CCTV11 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.154:10560
|
||||
#EXTINF:-1 tvg-id="CCTV12.cn",CCTV12 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.106:10274
|
||||
#EXTINF:-1 tvg-id="CCTV13.cn",CCTV13 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.254.96.161:9040
|
||||
#EXTINF:-1 tvg-id="CCTV14.cn",CCTV14 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.107:10280
|
||||
#EXTINF:-1 tvg-id="CCTV15.cn",CCTV15 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.155:10566
|
||||
#EXTINF:-1 tvg-id="CCTV15.cn",CCTV15音乐
|
||||
http://hwrr.jx.chinamobile.com:8080/PLTV/88888888/224/3221225641/index.m3u8
|
||||
#EXTINF:-1 tvg-id="CCTV16.cn",CCTV16 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.247:11124
|
||||
#EXTINF:-1 tvg-id="",CCTV16-4K (2160p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.249:11136
|
||||
#EXTINF:-1 tvg-id="CCTV17.cn",CCTV17 (1080p)
|
||||
http://bl.dchwtq.asia:10000/rtp/239.69.1.152:10548
|
||||
#EXTINF:-1 tvg-id="",CCTV TV Guide (576p)
|
||||
http://117.161.133.51:81/gitv_live/G_DIANSHIZN-CQ/G_DIANSHIZN-CQ.m3u8?p=GITV
|
||||
#EXTINF:-1 tvg-id="CCTVWorldGeography.cn",CCTV 世界地理
|
||||
|
@ -157,6 +123,8 @@ http://118.122.2.29:9999/hls/47/index.m3u8
|
|||
http://1.183.141.194:8001/hls/55/index.m3u8
|
||||
#EXTINF:-1 tvg-id="NeiMonggolTV.cn",Nei Monggol TV
|
||||
http://49.113.179.174:4022/udp/238.125.7.93:5140
|
||||
#EXTINF:-1 tvg-id="NeiMonggolTV.cn",Nei Monggol TV
|
||||
http://110.19.156.172:9901/tsfile/live/1003_1.m3u8
|
||||
#EXTINF:-1 tvg-id="NeiMonggolTV2MongolianCultureChannel.cn",Nei Monggol TV 2 Mongolian Culture Channel
|
||||
http://1.183.141.194:8001/hls/54/index.m3u8
|
||||
#EXTINF:-1 tvg-id="QTV1.cn",QTV-1
|
||||
|
@ -227,6 +195,8 @@ http://49.113.179.174:4022/udp/238.125.7.153:5140
|
|||
http://106.124.91.222:85/tsfile/live/21220_1.m3u8?authid=0&key=txiptv&playlive=1
|
||||
#EXTINF:-1 tvg-id="XinjiangTV12.cn",Xinjiang TV 12
|
||||
http://49.113.179.174:4022/udp/238.125.3.185:5140
|
||||
#EXTINF:-1 tvg-id="XizangTVChinese.cn",Xizang TV Chinese
|
||||
http://php.jdshipin.com/xztv.php?id=ws
|
||||
#EXTINF:-1 tvg-id="XizangTVTibetan.cn",Xizang TV Tibetan
|
||||
http://49.113.179.174:4022/udp/238.125.3.94:5140
|
||||
#EXTINF:-1 tvg-id="XizangTVTibetan.cn",Xizang TV Tibetan
|
||||
|
@ -1565,7 +1535,3 @@ http://yslk.chinashadt.com:1635/live/stream:di1.stream/playlist.m3u8
|
|||
http://yslk.chinashadt.com:1635/live/stream:di2.stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="",龙岩综合 (540p)
|
||||
http://stream.lytv.net.cn/2/sd/live.m3u8
|
||||
#EXTINF:-1 tvg-id="AndoTV.cn",Ando TV
|
||||
http://stream.qhbtv.com/adws/sd/live.m3u8
|
||||
#EXTINF:-1 tvg-id="XizangTVChinese.cn",Xizang TV Chinese
|
||||
http://php.jdshipin.com/xztv.php?id=ws
|
||||
|
|
|
@ -65,6 +65,8 @@ https://canaldos.internetinalambrico.com.co:1936/live/canal2.stream/playlist.m3u
|
|||
https://glb.bozztv.com/glb/ssh101/infotv01/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="CanalInstitucional.co",Canal Institucional (720p) [Not 24/7]
|
||||
https://streaming.rtvc.gov.co/TV_CanalInstitucional_live/smil:live.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="CanalInstitucional.co",Canal Institucional (480p)
|
||||
http://190.2.212.209:8050/play/a0lw
|
||||
#EXTINF:-1 tvg-id="CanalMasTelevision.co",Canal Más Televisión (720p)
|
||||
https://movil.ejeserver.com/live/teledoradahd.m3u8
|
||||
#EXTINF:-1 tvg-id="CanalMasTelevision.co",Canal Más Televisión (720p)
|
||||
|
@ -81,6 +83,8 @@ https://video.ejeserver.com/live/nets.m3u8
|
|||
https://canal.mediaserver.com.co/live/oracionconson.m3u8
|
||||
#EXTINF:-1 tvg-id="CanalPyC.co",Canal PyC (480p) [Not 24/7]
|
||||
https://glb.bozztv.com/glb/ssh101/pyctelevision/index.m3u8
|
||||
#EXTINF:-1 tvg-id="CanalRCN.co",Canal RCN (480p)
|
||||
http://190.2.212.209:8050/play/a0lt
|
||||
#EXTINF:-1 tvg-id="CanalRegionalFTV.co",Canal Regional FTV (720p) [Not 24/7]
|
||||
https://tvlatina.live:1936/8024/8024/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="CanalSantaMartaTV.co",Canal Santa Marta TV (720p) [Not 24/7]
|
||||
|
@ -92,6 +96,8 @@ https://play.amelbasoluciones.co:3976/live/telepalmartvlive.m3u8
|
|||
#EXTINF:-1 tvg-id="CanalTRO.co" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160",Canal TRO (1080p)
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160
|
||||
https://liveingesta118.cdnmedia.tv/canaltro2live/smil:live.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="CanalTRO.co",Canal TRO (480p)
|
||||
http://190.2.212.209:8050/play/a0m5
|
||||
#EXTINF:-1 tvg-id="CanalTROPlus.co" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160",Canal TRO Plus (1080p) [Geo-blocked]
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160
|
||||
https://liveingesta118.cdnmedia.tv/canaltro2live/smil:troplus.smil/playlist.m3u8
|
||||
|
@ -196,6 +202,8 @@ https://xhateaec.com/livestreams/19.crKOSL8FRzpid0MA.m3u8
|
|||
https://stmv4.voxtvhd.com.br/psctv/psctv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="",PyC Televisión (720p) [Not 24/7]
|
||||
https://ssh101-fl.bozztv.com/ssh101/pyctelevision/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RCNNovelas.co",RCN Novelas (480p)
|
||||
http://190.2.212.209:8050/play/a0ox
|
||||
#EXTINF:-1 tvg-id="RCNXtra.co",RCN Xtra (1080p)
|
||||
https://latv-rcn-xtra-1-mx.tcl.wurl.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RDMTelevision.co",RDM Televisión (720p) [Not 24/7]
|
||||
|
@ -212,6 +220,8 @@ https://tvlatina.live:1936/8004/8004/playlist.m3u8
|
|||
https://server.asilivehd.com:3802/live/canal4live.m3u8
|
||||
#EXTINF:-1 tvg-id="SenalColombia.co",Señal Colombia (1080p)
|
||||
https://streaming.rtvc.gov.co/TV_Senal_Colombia_live/smil:live.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="SinLimites.co",Sin Limites (480p)
|
||||
http://190.2.212.209:8050/play/a0pa
|
||||
#EXTINF:-1 tvg-id="SuramTV.co",Suram TV (1080p)
|
||||
https://livetv.305streamhd.com:3111/live/suramtvlive.m3u8
|
||||
#EXTINF:-1 tvg-id="TamesisTeVe.co",Támesis TeVe (614p) [Not 24/7]
|
||||
|
@ -228,18 +238,24 @@ https://video.ejeserver.com/live/telesanjacinto.m3u8
|
|||
#EXTINF:-1 tvg-id="TeleVid.co" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160",Tele Vid (1080p)
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160
|
||||
https://liveingesta118.cdnmedia.tv/televidtvlive/smil:dvrlive.smil/playlist.m3u8?DVR=
|
||||
#EXTINF:-1 tvg-id="Teleantioquia.co",Teleantioquia (480p)
|
||||
http://190.2.212.209:8050/play/a0n1
|
||||
#EXTINF:-1 tvg-id="Teleantioquia.co" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160",Teleantioquia (360p) [Not 24/7]
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160
|
||||
https://liveingesta118.cdnmedia.tv/teleantioquialive/smil:dvrlive.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Teleantioquia2.co" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160",Teleantioquia 2 (720p)
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160
|
||||
https://liveingesta118.cdnmedia.tv/teleantioquialive/smil:live.smil/playlist.m3u8?DVR=
|
||||
#EXTINF:-1 tvg-id="Telecafe.co",Telecafe (480p)
|
||||
http://190.2.212.209:8050/play/a0ne
|
||||
#EXTINF:-1 tvg-id="Telecafe.co" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160",Telecafé (720p)
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160
|
||||
https://liveingesta118.cdnmedia.tv/telecafelive/smil:dvrlive.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Telecaribe.co" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160",Telecaribe (720p)
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160
|
||||
https://liveingesta118.cdnmedia.tv/telecaribetvlive/smil:rtmp01.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Telecaribe.co",Telecaribe (480p)
|
||||
http://190.2.212.209:8050/play/a0nc
|
||||
#EXTINF:-1 tvg-id="TelecaribePlus.co" http-user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160",Telecaribe Plus (720p)
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36 CrKey/1.44.191160
|
||||
https://liveingesta118.cdnmedia.tv/telecaribetvlive/smil:rtmp02.smil/playlist.m3u8
|
||||
|
@ -247,10 +263,14 @@ https://liveingesta118.cdnmedia.tv/telecaribetvlive/smil:rtmp02.smil/playlist.m3
|
|||
https://cp.panelchs.com:1936/8094/8094/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Teleislas.co",Teleislas (486p) [Not 24/7]
|
||||
https://5ab772334c39c.streamlock.net/live-teleislas/teleislas/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Teleislas.co",Teleislas (480p)
|
||||
http://190.2.212.209:8050/play/a0m6
|
||||
#EXTINF:-1 tvg-id="TelemusicaTV.co",Telemúsica TV (540p) [Geo-blocked]
|
||||
https://canal.mediaserver.com.co/live/telemusica.m3u8
|
||||
#EXTINF:-1 tvg-id="TelenetTelevision.co",Telenet Televisión (478p)
|
||||
https://cp.panelchs.com:1936/8058/8058/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Telepacifico.co",Telepacifico (480p)
|
||||
http://190.2.212.209:8050/play/a0n3
|
||||
#EXTINF:-1 tvg-id="Telepacifico.co",Telepacífico (1080p) [Not 24/7]
|
||||
https://stream.logicideas.media/telepacifico-live/smil:live.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Telepasto.co",Telepasto (360p) [Geo-blocked]
|
||||
|
@ -263,6 +283,8 @@ https://cp.panelchs.com:1936/8060/8060/playlist.m3u8
|
|||
https://stream.logicideas.media/canaltrece-live/smil:live.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TrecePlus.co",Trece + (720p)
|
||||
https://stream.logicideas.media/canaltreceplus-live/smil:live1plus.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TrecePlus.co",Trece+ (480p)
|
||||
http://190.2.212.209:8050/play/a0nf
|
||||
#EXTINF:-1 tvg-id="TropicalTV.co",Tropical TV (480p) [Not 24/7]
|
||||
https://rpn3.bozztv.com/ssh101/ssh101/estvco/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TuKanal.co",Tu Kanal (1080p)
|
||||
|
@ -293,30 +315,8 @@ https://video.ejeserver.com/live/veotv.m3u8
|
|||
https://eu1.servers10.com:8081/vidanuevatv/index.m3u8
|
||||
#EXTINF:-1 tvg-id="ViveTV.co",Vive TV Colombia (1080p)
|
||||
http://192.144.113.132:1935/live/ViveTV/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="WinSports.co",Win Sports (480p)
|
||||
http://190.2.212.209:8050/play/a0n2
|
||||
#EXTINF:-1 tvg-id="Zoom.co" http-referrer="https://canalzoom.org/senal-en-vivo",Zoom (1080p)
|
||||
#EXTVLCOPT:http-referrer=https://canalzoom.org/senal-en-vivo
|
||||
https://canalzoom.smoothcloud.co:3027/live/canalzoombr1live.m3u8
|
||||
#EXTINF:-1 tvg-id="CanalRCN.co",Canal RCN (480p)
|
||||
http://190.2.212.209:8050/play/a0lt
|
||||
#EXTINF:-1 tvg-id="WinSports.co",Win Sports (480p)
|
||||
http://190.2.212.209:8050/play/a0n2
|
||||
#EXTINF:-1 tvg-id="CanalInstitucional.co",Canal Institucional (480p)
|
||||
http://190.2.212.209:8050/play/a0lw
|
||||
#EXTINF:-1 tvg-id="RCNNovelas.co",RCN Novelas (480p)
|
||||
http://190.2.212.209:8050/play/a0ox
|
||||
#EXTINF:-1 tvg-id="SinLimites.co",Sin Limites (480p)
|
||||
http://190.2.212.209:8050/play/a0pa
|
||||
#EXTINF:-1 tvg-id="CanalTRO.co",Canal TRO (480p)
|
||||
http://190.2.212.209:8050/play/a0m5
|
||||
#EXTINF:-1 tvg-id="Teleantioquia.co",Teleantioquia (480p)
|
||||
http://190.2.212.209:8050/play/a0n1
|
||||
#EXTINF:-1 tvg-id="Telecafe.co",Telecafe (480p)
|
||||
http://190.2.212.209:8050/play/a0ne
|
||||
#EXTINF:-1 tvg-id="Telecaribe.co",Telecaribe (480p)
|
||||
http://190.2.212.209:8050/play/a0nc
|
||||
#EXTINF:-1 tvg-id="Teleislas.co",Teleislas (480p)
|
||||
http://190.2.212.209:8050/play/a0m6
|
||||
#EXTINF:-1 tvg-id="Telepacifico.co",Telepacifico (480p)
|
||||
http://190.2.212.209:8050/play/a0n3
|
||||
#EXTINF:-1 tvg-id="TrecePlus.co",Trece+ (480p)
|
||||
http://190.2.212.209:8050/play/a0nf
|
||||
|
|
|
@ -36,6 +36,10 @@ https://sc-kuzeykibrissmarttv.ercdn.net/kanalt/bantp1/playlist.m3u8
|
|||
https://sc-kuzeykibrissmarttv.ercdn.net/kibristv/bant1/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="OmegaChannel.cy",Omega Channel (1080p)
|
||||
http://l1.cloudskep.com/tst/omcy/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="OMONOIATV.cy",OMONOIA TV (684p)
|
||||
http://62.233.57.226:8001/play/a00b00
|
||||
#EXTINF:-1 tvg-id="OneChannelCyprus.cy",One Channel Cyprus (576p)
|
||||
http://62.210.211.188:2095/play/a00e
|
||||
#EXTINF:-1 tvg-id="RIK1.cy",RIK 1
|
||||
http://l6.cloudskep.com/tvb6/rik1-1/mpeg.2ts
|
||||
#EXTINF:-1 tvg-id="RIK2.cy",RIK 2
|
||||
|
@ -60,7 +64,3 @@ https://sc-kuzeykibrissmarttv.ercdn.net/tv2020/bantp1/playlist.m3u8
|
|||
https://dev.aftermind.xyz/edge-hls/unitrust/voulitv/index.m3u8?token=8TXWzhY3h6jrzqEqx
|
||||
#EXTINF:-1 tvg-id="VouliTV.cy",Vouli TV (1080p) [Not 24/7]
|
||||
https://dev.aftermind.xyz/hls/unitrust/voulitv/index.m3u8?token=8TXWzhY3h6jrzqEqx
|
||||
#EXTINF:-1 tvg-id="OMONOIATV.cy",OMONOIA TV (684p)
|
||||
http://62.233.57.226:8001/play/a00b00
|
||||
#EXTINF:-1 tvg-id="OneChannelCyprus.cy",One Channel Cyprus (576p)
|
||||
http://62.210.211.188:2095/play/a00e
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
https://123tv-mx1.flex-cdn.net/index.m3u8
|
||||
#EXTINF:-1 tvg-id="3sat.de",3sat (720p) [Geo-blocked]
|
||||
https://zdf-hls-18.akamaized.net/hls/live/2016501/dach/high/master.m3u8
|
||||
#EXTINF:-1 tvg-id="AlexBerlin.de",Alex Berlin (1080p) [Not 24/7]
|
||||
https://alex-stream.rosebud-media.de/live/alexlivetv40.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AllgauTV.de",Allgäu TV (1080p)
|
||||
https://stream01.welocal.stream/stream/fhd-allgaeutv_25679/ngrp:stream_all/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AltenburgTV.de",Altenburg TV (1080p)
|
||||
|
@ -197,6 +195,8 @@ https://mcdn.ndr.de/ndr/hls/ndr_fs/ndr_nds/master.m3u8
|
|||
https://mcdn.ndr.de/ndr/hls/ndr_fs/ndr_sh/master.m3u8
|
||||
#EXTINF:-1 tvg-id="Nickelodeon.de",Nick Germany (1080p) [Geo-blocked]
|
||||
https://0d26a00dfbb1.airspace-cdn.cbsivideo.com/nick1999/master/nick1999.m3u8
|
||||
#EXTINF:-1 tvg-id="Nickelodeon.de",Nickelodeon
|
||||
https://ma.anixa.tv/clips/stream/nickelodeon/playlist.php
|
||||
#EXTINF:-1 tvg-id="Nickelodeon.de",Nickelodeon Deutschland [Geo-blocked]
|
||||
https://unilivemtveu-lh.akamaihd.net/i/nickde_1@448749/master.m3u8
|
||||
#EXTINF:-1 tvg-id="NiederbayernTV.de",Niederbayern TV (720p)
|
||||
|
@ -214,13 +214,7 @@ https://hls1.wtnet.de/noa4hh/apple/wifi6500.m3u8
|
|||
#EXTINF:-1 tvg-id="noa4Norderstedt.de",Noa 4 Norderstedt (1080p)
|
||||
https://hls1.wtnet.de/noa4/apple/wifi6500.m3u8
|
||||
#EXTINF:-1 tvg-id="NRWision.de",NRWision (1080p)
|
||||
https://fms.nrwision.de/live/livestreamHD.stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="NRWision.de",NRWISION (1080p)
|
||||
https://fms.nrwision.de/live/livestreamHD.stream_source/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="NRWision.de",NRWision (720p)
|
||||
https://fms.nrwision.de/live/livestreamHD.stream_1080p/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="NRWision.de",NRWision (360p)
|
||||
https://fms.nrwision.de/live/livestreamHD.stream_360p/playlist.m3u8
|
||||
https://fms.nrwision.de/live/ngrp:livestreamHD.stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="OberpfalzTV.de",Oberpfalz TV (1080p)
|
||||
https://oberpfalztv.iptv-playoutcenter.de/oberpfalztv/oberpfalztv.stream_1/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Oeins.de",oeins (Oldenburg) (1080p) [Not 24/7]
|
||||
|
@ -371,17 +365,12 @@ https://live.creacast.com/rockland-radio/smil:rockland-radio.smil/playlist.m3u8
|
|||
https://rt-ger.rttv.com/dvr/rtdeutsch/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RTDE.de",RT DE (1080p) [Not 24/7]
|
||||
https://rt-ger.rttv.com/live/rtdeutsch/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RTL.de",RTL (576p)
|
||||
https://s6.hopslan.com/rtlc1/tracks-v1a1/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="RTL.de",RTL (Germany) (576p)
|
||||
http://178.219.128.68:64888/RTL
|
||||
#EXTINF:-1 tvg-id="RTLSuper.de",RTL Super (576p)
|
||||
http://178.219.128.68:64888/SUPERTL
|
||||
#EXTINF:-1 tvg-id="RTLZwei.de",RTL Zwei (1080p)
|
||||
http://178.219.128.68:64888/RTL2
|
||||
#EXTINF:-1 tvg-id="RTLZwei.de" http-referrer="https://www.2ix2.com/rtl2-live/",RTL Zwei (576p)
|
||||
#EXTVLCOPT:http-referrer=https://www.2ix2.com/rtl2-live/
|
||||
https://s6.hopslan.com/rtl2x1/index.m3u8
|
||||
#EXTINF:-1 tvg-id="SaarlandFernsehen1.de",Saarland Fernsehen 1 (1080p)
|
||||
https://saarland1.iptv-playoutcenter.de/saarland1/saarland1.stream_1/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="SaarlandFernsehen2.de",Saarland Fernsehen 2 (720p) [Not 24/7]
|
||||
|
@ -492,6 +481,8 @@ https://wdrlokalzeit.akamaized.net/hls/live/2018025-b/wdrlz_muensterland/master.
|
|||
https://wdrlokalzeit.akamaized.net/hls/live/2018020-b/wdrlz_siegen/master.m3u8
|
||||
#EXTINF:-1 tvg-id="WDRFernsehenWuppertal.de",WDR Fernsehen Wuppertal (720p) [Geo-blocked]
|
||||
https://wdrlokalzeit.akamaized.net/hls/live/2018028-b/wdrlz_wuppertal/master.m3u8
|
||||
#EXTINF:-1 tvg-id="WELT.de",WELT
|
||||
https://s6.hopslan.com/n24X/index.m3u8
|
||||
#EXTINF:-1 tvg-id="WeltderWunderTV.de",Welt der Wunder TV (576p)
|
||||
https://wdw.iptv-playoutcenter.de/wdw/wdw1/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Wir24TV.de",Wir24 TV (1080p) [Geo-blocked]
|
||||
|
@ -510,7 +501,9 @@ https://zdf-hls-16.akamaized.net/hls/live/2016499/de/high/master.m3u8
|
|||
https://ef56ef401101403a8b06f1dec29ef1eb.mediatailor.us-east-1.amazonaws.com/v1/master/44f73ba4d03e9607dcd9bebdcb8494d86964f1d8/Samsung-de_ZeeOne/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ZweiMusicTelevision.de",ZWEI2 Music (1080p)
|
||||
https://cdne.folxplay.tv/folx-trz/streams/ch-2/master.m3u8
|
||||
#EXTINF:-1 tvg-id="WELT.de",WELT
|
||||
https://s6.hopslan.com/n24X/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Nickelodeon.de",Nickelodeon
|
||||
https://ma.anixa.tv/clips/stream/nickelodeon/playlist.php
|
||||
#EXTINF:-1 tvg-id="MCTV.de",MC TV (720p)
|
||||
https://rrr.sz.xlcdn.com/?account=mceutv&file=mc2&type=live&service=wowza&protocol=https&output=playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RFH.de",RFH (1080p)
|
||||
https://h056.video-stream-hosting.de/medienasa-live/_definst_/mp4:RFH_high/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AlexBerlin.de",Alex Berlin (1080p)
|
||||
https://alex-stream.rosebud-media.de/bounce/alexlivetv50.smil/index.m3u8
|
||||
|
|
|
@ -185,6 +185,8 @@ https://ss3.domint.net:3136/gtv_str/globalhd/playlist.m3u8
|
|||
https://imagenuniversaltv.net:3820/live/guacaratvlive.m3u8
|
||||
#EXTINF:-1 tvg-id="",Guaymate [Not 24/7]
|
||||
https://5790d294af2dc.streamlock.net/8100/8100/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="GuaymateTV.do",Guaymate TV (720p)
|
||||
https://ssh101stream.ssh101.com/akamaissh101/ssh101/guaymatetv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="HainaVision.do",Haina Vision (720p)
|
||||
https://cdn.streamingcpanel.com:3447/live/hainavisionlive.m3u8
|
||||
#EXTINF:-1 tvg-id="HermanasMirabalTV.do",Hermanas Mirabal TV (720p) [Not 24/7]
|
||||
|
@ -517,5 +519,3 @@ https://ss3.domint.net:3108/zol_str/vzol/playlist.m3u8
|
|||
https://5790d294af2dc.streamlock.net/Zonavisiontv/Zonavisiontv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ZTV.do",ZTV (720p) [Not 24/7]
|
||||
https://lb00zdigital.streamprolive.com/mnt/hls/live.m3u8
|
||||
#EXTINF:-1 tvg-id="GuaymateTV.do",Guaymate TV (720p)
|
||||
https://ssh101stream.ssh101.com/akamaissh101/ssh101/guaymatetv/playlist.m3u8
|
||||
|
|
|
@ -130,8 +130,6 @@ https://ssh101-fl.bozztv.com/ssh101/scandalotv/index.m3u8
|
|||
https://eu1.servers10.com:8081/8108/index.m3u8
|
||||
#EXTINF:-1 tvg-id="SonoOndaTV.ec",Sono Onda TV (720p)
|
||||
https://live.obslivestream.com/sonoondatv/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Teleamazonas.ec",Teleamazonas (1080p) [Geo-blocked]
|
||||
https://teleamazonas-live.cdn.vustreams.com/live/0fc97608-6057-4db8-9af7-102c21ac18af/live.isml/0fc97608-6057-4db8-9af7-102c21ac18af.m3u8
|
||||
#EXTINF:-1 tvg-id="Teledigital.ec",Teledigital (240p) [Not 24/7]
|
||||
https://tv.portalexpress.es:3182/hybrid/play.m3u8
|
||||
#EXTINF:-1 tvg-id="Telerama.ec",Telerama (240p) [Not 24/7]
|
||||
|
@ -154,3 +152,5 @@ https://cloud37.ecuatel.com/vostv/live/manifest.m3u8
|
|||
https://video2.makrodigital.com/wuanplus/wuanplus/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ZaracayTV.ec",Zaracay TV (1080p) [Not 24/7]
|
||||
https://video2.makrodigital.com/zaracay/zaracay/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Teleamazonas.ec",Teleamazonas (1080p)
|
||||
https://teleamazonas-live.cdn.vustreams.com/live/fd4ab346-b4e3-4628-abf0-b5a1bc192428/live.isml/playlist.m3u8
|
||||
|
|
|
@ -45,9 +45,13 @@ https://shls-masr2-ak.akamaized.net/out/v1/f683685242b549f48ea8a5171e3e993a/inde
|
|||
https://nogoumtv.nrpstream.com/hls/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="PNCDrama.eg",PNC Drama (1080p)
|
||||
https://d35j504z0x2vu2.cloudfront.net/v1/master/0bc8e8376bd8417a1b6761138aa41c26c7309312/pnc-drama/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TeN.eg",TeN (720p) [Geo-blocked]
|
||||
https://weyyak-live.akamaized.net/weyyak_ten_tv/index.m3u8
|
||||
#EXTINF:-1 tvg-id="TheKingdomSat.eg",The Kingdom Sat (720p)
|
||||
https://bcovlive-a.akamaihd.net/0e524e1838ed411dad0a674d18e07914/eu-central-1/6415808954001/playlist_dvr.m3u8
|
||||
#EXTINF:-1 tvg-id="WatanTV.eg",Watan TV (1080p)
|
||||
https://rp.tactivemedia.com/watantv_source/live/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TeN.eg",TeN (720p) [Geo-blocked]
|
||||
https://weyyak-live.akamaized.net/weyyak_ten_tv/index.m3u8
|
||||
#EXTINF:-1 tvg-id="RotanaCinemaEgypt.eg",Rotana Cinema Egypt (1080p)
|
||||
https://rotana.hibridcdn.net/rotana/cinemamasr_net-7Y83PP5adWixDF93/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="OnE.eg",On E (1080p)
|
||||
https://bcovlive-a.akamaihd.net/3dc60bab470f4c9fbf00408ecb7c3d7a/eu-west-1/6057955906001/playlist_dvr.m3u8
|
||||
|
|
|
@ -52,10 +52,20 @@ https://cls.alcarria.tv/alcarriatv/livestream/playlist.m3u8
|
|||
http://vegafibratv.com:8085/AMC/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Antena3.es",Antena 3 (720p)
|
||||
http://185.189.225.150:85/Antena3HD/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AquiNoHayQuienViva.es",Aqui No Hay Quien Viva (720p)
|
||||
https://fast-channels.atresmedia.com/648ef3951756b0e425af83cc/648ef3951756b0e425af83cc.m3u8
|
||||
#EXTINF:-1 tvg-id="ArabiTV.es",Arabí TV (1080p)
|
||||
https://streamtv2.elitecomunicacion.cloud:3956/live/arabitvlive.m3u8
|
||||
#EXTINF:-1 tvg-id="AragonTV.es",Aragón TV (720p) [Not 24/7]
|
||||
https://cartv.streaming.aranova.es/hls/live/aragontv_canal1.m3u8
|
||||
#EXTINF:-1 tvg-id="AtresplayerClasicos.es",Atresplayer Clasicos (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef12c2bfab0e4507e0d61/648ef12c2bfab0e4507e0d61.m3u8
|
||||
#EXTINF:-1 tvg-id="AtresplayerComedia.es",Atresplayer Comedia (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef23d2bfab0e4557e0d61/648ef23d2bfab0e4557e0d61.m3u8
|
||||
#EXTINF:-1 tvg-id="AtresplayerInquietos.es",Atresplayer Inquietos (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef3162bfab0e4587e0d61/648ef3162bfab0e4587e0d61.m3u8
|
||||
#EXTINF:-1 tvg-id="AtresplayerMulticine.es",Atresplayer Multicine (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef18c1756b0e41daf83cc/648ef18c1756b0e41daf83cc.m3u8
|
||||
#EXTINF:-1 tvg-id="BabyTV.es",BabyTV (Spain) (1080p)
|
||||
http://185.189.225.150:85/BabyTV/index.m3u8
|
||||
#EXTINF:-1 tvg-id="BailenTV.es",Bailén TV (720p) [Not 24/7]
|
||||
|
@ -69,6 +79,8 @@ https://cdnapisec.kaltura.com/p/2346171/sp/234617100/playManifest/entryId/1_n644
|
|||
https://cdnapisec.kaltura.com/p/2346171/sp/234617100/playManifest/entryId/1_n6442jz0/format/applehttp/protocol/https/uiConfId/42816492/a.m3u8?referrer=aHR0cHM6Ly9iZXRldmUuY2F0
|
||||
#EXTINF:-1 tvg-id="BiosferaTV.es",Biosfera TV (720p) [Not 24/7]
|
||||
https://tvdatta.com:3021/live/biosferatvlive.m3u8
|
||||
#EXTINF:-1 tvg-id="Boing.es",Boing
|
||||
http://185.189.225.150:85/boing/index.m3u8
|
||||
#EXTINF:-1 tvg-id="BomCine.es",Bom Cine (576p)
|
||||
http://185.189.225.150:85/BOM/index.m3u8
|
||||
#EXTINF:-1 tvg-id="BonDiaTV.es",Bon Dia TV (1080p)
|
||||
|
@ -223,8 +235,12 @@ https://liveingesta318.cdnmedia.tv/9tvlive/smil:live.smil/playlist.m3u8?DVR=
|
|||
https://directes-tv-cat.3catdirectes.cat/live-origin/c33-super3-hls/master.m3u8
|
||||
#EXTINF:-1 tvg-id="El33SX3.es",El 33 SX3 (1080p) [Geo-blocked]
|
||||
https://directes-tv-es.3catdirectes.cat/live-origin/c33-super3-hls/master.m3u8
|
||||
#EXTINF:-1 tvg-id="ElClubdelaComedia.es",El Club de la Comedia (1080p)
|
||||
https://fast-channels.atresmedia.com/648f47f7a2ffb0e40aeff3ad/648f47f7a2ffb0e40aeff3ad.m3u8
|
||||
#EXTINF:-1 tvg-id="ElConfidencialTV.es",El Confidencial TV (1080p)
|
||||
https://daqnsnf5phf17.cloudfront.net/v1/master/3722c60a815c199d9c0ef36c5b73da68a62b09d1/cc-sde7fypd1420w-prod/fast-channel-elconfidencial/fast-channel-elconfidencial.m3u8
|
||||
#EXTINF:-1 tvg-id="ElHormiguero.es",El Hormiguero (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef5882bfab0e4627e0d61/648ef5882bfab0e4627e0d61.m3u8
|
||||
#EXTINF:-1 tvg-id="ElPaisTV.es",EL PAÍS TV (1080p)
|
||||
https://d2xqbi89ghm9hh.cloudfront.net/v1/master/3722c60a815c199d9c0ef36c5b73da68a62b09d1/cc-79fx3huimw4xc-ssai-prd/fast-channel-el-pais.m3u8
|
||||
#EXTINF:-1 tvg-id="ElToroTV.es",El Toro TV (720p)
|
||||
|
@ -233,6 +249,8 @@ https://streaming-1.eltorotv.com/lb0/eltorotv-streaming-web/index.m3u8
|
|||
https://elche7tv.gestec-video.com/hls/canal2.m3u8
|
||||
#EXTINF:-1 tvg-id="EmpordaTV.es",Empordà TV (1080p)
|
||||
https://video3.lhdserver.es/empordatv2/live.m3u8
|
||||
#EXTINF:-1 tvg-id="EquipodeInvestigacion.es",Equipo de Investigacion (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef5551756b0e429af83cc/648ef5551756b0e429af83cc.m3u8
|
||||
#EXTINF:-1 tvg-id="ErloTelebista.es",Erlo Telebista (720p)
|
||||
https://5940924978228.streamlock.net/8159/8159/master.m3u8
|
||||
#EXTINF:-1 tvg-id="Esport3.es",Esport3 (1080p) [Geo-blocked]
|
||||
|
@ -261,8 +279,12 @@ https://media2.streambrothers.com:1936/8150/8150/playlist.m3u8
|
|||
http://185.189.225.150:85/fdf/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Fibwi.es",Fibwi (1080p) [Not 24/7]
|
||||
https://hostcdn3.fibwi.com/fibwi_diario/index.fmp4.m3u8
|
||||
#EXTINF:-1 tvg-id="FisicaoQuimica.es",Fisica o Quimica (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef50a2bfab0e4607e0d61/648ef50a2bfab0e4607e0d61.m3u8
|
||||
#EXTINF:-1 tvg-id="FitelTV.es",Fitel TV (1080p)
|
||||
https://tv.mywifisocial.es/live.m3u8
|
||||
#EXTINF:-1 tvg-id="Flooxer.es",Flooxer (1080p)
|
||||
https://fast-channels.atresmedia.com/5c1285e47ed1a861f8125285/5c1285e47ed1a861f8125285.m3u8
|
||||
#EXTINF:-1 tvg-id="FuengirolaTV.es",Fuengirola TV (360p) [Not 24/7]
|
||||
https://secure.todostreaming.es/live/nerja-livestream.m3u8
|
||||
#EXTINF:-1 tvg-id="FuerteventuraTV.es",Fuerteventura TV (1080p)
|
||||
|
@ -442,6 +464,8 @@ https://tv.portalexpress.es:3731/stream/play.m3u8
|
|||
https://tvmelilla-hls-rm-lw.flumotion.com/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RadioTelevisionMogan.es",Radio Televisión Mogán (1080p)
|
||||
https://cloudvideo.servers10.com:8081/8028/index.m3u8
|
||||
#EXTINF:-1 tvg-id="RakutenViki.es",Rakuten Viki (1080p)
|
||||
https://newidco-rakutenviki-2-eu.xiaomi.wurl.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RakutenViki.es",Rakuten Viki (720p)
|
||||
https://fd18f1cadd404894a31a3362c5f319bd.mediatailor.us-east-1.amazonaws.com/v1/manifest/04fd913bb278d8775298c26fdca9d9841f37601f/RakutenTV-eu_RakutenViki-1/048a962c-e84b-4a0e-aeb3-98376f4b9953/2.m3u8
|
||||
#EXTINF:-1 tvg-id="RealMadridTV.es",Real Madrid TV (404p)
|
||||
|
@ -655,27 +679,13 @@ https://janus.xpbroadcasting.com:8443/hls/xptvUS.m3u8
|
|||
#EXTINF:-1 tvg-id="ZafraTV.es" http-referrer="https://player.streamingconnect.com/",Zafra TV (1080p)
|
||||
#EXTVLCOPT:http-referrer=https://player.streamingconnect.com/
|
||||
https://cloud.fastchannel.es/mic/manifiest/hls/radiotvzafra/radiotvzafra.m3u8
|
||||
#EXTINF:-1 tvg-id="RakutenViki.es",Rakuten Viki (1080p)
|
||||
https://newidco-rakutenviki-2-eu.xiaomi.wurl.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AtresplayerInquietos.es",Atresplayer Inquietos (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef3162bfab0e4587e0d61/648ef3162bfab0e4587e0d61.m3u8
|
||||
#EXTINF:-1 tvg-id="AtresplayerMulticine.es",Atresplayer Multicine (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef18c1756b0e41daf83cc/648ef18c1756b0e41daf83cc.m3u8
|
||||
#EXTINF:-1 tvg-id="AtresplayerComedia.es",Atresplayer Comedia (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef23d2bfab0e4557e0d61/648ef23d2bfab0e4557e0d61.m3u8
|
||||
#EXTINF:-1 tvg-id="AtresplayerClasicos.es",Atresplayer Clasicos (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef12c2bfab0e4507e0d61/648ef12c2bfab0e4507e0d61.m3u8
|
||||
#EXTINF:-1 tvg-id="Flooxer.es",Flooxer (1080p)
|
||||
https://fast-channels.atresmedia.com/5c1285e47ed1a861f8125285/5c1285e47ed1a861f8125285.m3u8
|
||||
#EXTINF:-1 tvg-id="FisicaoQuimica.es",Fisica o Quimica (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef50a2bfab0e4607e0d61/648ef50a2bfab0e4607e0d61.m3u8
|
||||
#EXTINF:-1 tvg-id="EquipodeInvestigacion.es",Equipo de Investigacion (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef5551756b0e429af83cc/648ef5551756b0e429af83cc.m3u8
|
||||
#EXTINF:-1 tvg-id="ElHormiguero.es",El Hormiguero (1080p)
|
||||
https://fast-channels.atresmedia.com/648ef5882bfab0e4627e0d61/648ef5882bfab0e4627e0d61.m3u8
|
||||
#EXTINF:-1 tvg-id="ElClubdelaComedia.es",El Club de la Comedia (1080p)
|
||||
https://fast-channels.atresmedia.com/648f47f7a2ffb0e40aeff3ad/648f47f7a2ffb0e40aeff3ad.m3u8
|
||||
#EXTINF:-1 tvg-id="AquiNoHayQuienViva.es",Aqui No Hay Quien Viva (720p)
|
||||
https://fast-channels.atresmedia.com/648ef3951756b0e425af83cc/648ef3951756b0e425af83cc.m3u8
|
||||
#EXTINF:-1 tvg-id="Boing.es",Boing
|
||||
http://185.189.225.150:85/boing/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Atreseries.es",Atreseries (480p)
|
||||
http://181.78.109.48:8000/play/a00l/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Antena3.es",Antena 3 (480p)
|
||||
http://181.78.109.48:8000/play/a00f/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AXN.es",AXN (1080p)
|
||||
http://181.78.109.48:8000/play/a05u/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AXN.es",AXN (480p)
|
||||
http://181.78.109.48:8000/play/a023/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Nickelodeon.es",Nickelodeon (480p)
|
||||
http://181.78.109.48:8000/play/a05a/index.m3u8
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
https://dhx-caillou-1-es.samsung.wurl.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="EuronewsSpanish.fr",Euronews en Español (720p)
|
||||
https://rakuten-euronews-4-es.samsung.wurl.tv/manifest/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="FashionTVEurope.fr",Fashion TV (Spain) (1080p)
|
||||
https://fashiontv-fashiontv-2-es.samsung.wurl.tv/manifest/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="",iHola Play
|
||||
https://rakuten-hola-2-es.samsung.wurl.tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="PeopleAreAwesome.us",People are Awesome
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="6ter.fr",6ter
|
||||
https://origin-caf900c010ea8046.live.6cloud.fr/out/v1/29c7a579af3348b48230f76cd75699a5/dash_short_cenc10_6ter_hd_index.mpd
|
||||
#EXTINF:-1 tvg-id="20MinutesTV.fr",20 Minutes TV (1080p)
|
||||
https://lives.digiteka.com/stream/86d3e867-a272-496b-8412-f59aa0104771/index.m3u8
|
||||
#EXTINF:-1 tvg-id="",A12 TV (720p)
|
||||
|
@ -59,6 +61,8 @@ https://raw.githubusercontent.com/Paradise-91/ParaTV/main/streams/equidia/live2.
|
|||
https://raw.githubusercontent.com/Paradise-91/ParaTV/main/streams/equidia/racingmag.m3u8
|
||||
#EXTINF:-1 tvg-id="EreTV.fr",Ère TV (1080p)
|
||||
https://mn-nl.mncdn.com/awraastv/awraastv_hd.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="EuronewsEnglishHD.fr",Euronews English HD (1080p)
|
||||
http://stream01.vnet.am/Channel_119/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="FashionTVCzechSlovak.fr",FashionTV Czech&Slovak (450p) [Not 24/7]
|
||||
http://lb.streaming.sk/fashiontv/stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="France2.fr",France 2 (1080p)
|
||||
|
@ -185,6 +189,8 @@ http://178.170.47.109/MTVHITS/index.m3u8
|
|||
http://190.2.155.162:8080/mtvhit/mpegts
|
||||
#EXTINF:-1 tvg-id="MuseumTVFrench.fr",Museum TV (1080p)
|
||||
https://live2.creacast.com/museum-france/smil:museum-france.smil/master.m3u8
|
||||
#EXTINF:-1 tvg-id="MuseumTVEnglish.fr",Museum TV English (1080p)
|
||||
https://cdn-ue1-prod.tsv2.amagi.tv/linear/amg01492-secomsasmediart-museumtven-xiaomi/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="MyZenTV.fr",MyZen TV (1080p)
|
||||
https://cdn-ue1-prod.tsv2.amagi.tv/linear/amg01255-secomcofites-my-myzen-en-plex/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="NancyWebTV.fr",Nancy Web TV (394p) [Not 24/7]
|
||||
|
@ -223,6 +229,10 @@ https://raw.githubusercontent.com/Paradise-91/ParaTV/main/streams/tf1plus/tf1.m3
|
|||
https://raw.githubusercontent.com/Paradise-91/ParaTV/main/streams/tf1plus/tf1sf.m3u8
|
||||
#EXTINF:-1 tvg-id="TFX.fr",TFX (720p) [Geo-blocked]
|
||||
https://raw.githubusercontent.com/Paradise-91/ParaTV/main/streams/tf1plus/tfx.m3u8
|
||||
#EXTINF:-1 tvg-id="TiVi5Monde.fr",TiVi5 Monde [Geo-blocked]
|
||||
https://ott.tv5monde.com/Content/HLS/Live/channel(tivi5)/variant.m3u8
|
||||
#EXTINF:-1 tvg-id="TiVi5Monde.fr",TiVi5Monde (1080p)
|
||||
http://154.197.91.168:7001/play/a0dz/index.m3u8
|
||||
#EXTINF:-1 tvg-id="TMC.fr",TMC (720p) [Geo-blocked]
|
||||
https://raw.githubusercontent.com/Paradise-91/ParaTV/main/streams/tf1plus/tmc.m3u8
|
||||
#EXTINF:-1 tvg-id="TraceLatina.fr",Trace Latina
|
||||
|
@ -231,8 +241,6 @@ http://185.234.217.27:8002/play/a02f/index.m3u8
|
|||
https://amg01131-tracetv-amg01131c1-rakuten-us-1081.playouts.now.amagi.tv/playlist/amg01131-tracetvfast-traceurban-rakutenus/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TV3V.fr",TV3V (720p)
|
||||
https://tv3v.hdr-tv.com/live/tv3v/livestream/master.m3u8
|
||||
#EXTINF:-1 tvg-id="TiVi5Monde.fr",TiVi5Monde (1080p)
|
||||
http://154.197.91.168:7001/play/a0dz/index.m3u8
|
||||
#EXTINF:-1 tvg-id="TV5MondeAsia.fr",TV5 Monde Asia (Asie) (1080p) [Geo-blocked]
|
||||
https://ott.tv5monde.com/Content/HLS/Live/channel(seasie)/variant.m3u8
|
||||
#EXTINF:-1 tvg-id="TV5MondeEurope.fr",TV5Monde Europe (1080p) [Geo-blocked]
|
||||
|
@ -275,11 +283,3 @@ https://vosgestv.live-kd.com/live/vosgestv/vosgestv/playlist.m3u8
|
|||
https://live.digiteka.com/1/WGQ1NnhEN0lzM0NU/dk1EOHhw/hls/live/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Weo.fr",Wéo (Picardie) (480p) [Not 24/7]
|
||||
https://live.digiteka.com/1/Zks2L0VsM2V0T242/QTBqcFly/hls/live/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="6ter.fr",6ter
|
||||
https://origin-caf900c010ea8046.live.6cloud.fr/out/v1/29c7a579af3348b48230f76cd75699a5/dash_short_cenc10_6ter_hd_index.mpd
|
||||
#EXTINF:-1 tvg-id="EuronewsEnglishHD.fr",Euronews English HD (1080p)
|
||||
http://stream01.vnet.am/Channel_119/mono.m3u8
|
||||
#EXTINF:-1 tvg-id="MuseumTVEnglish.fr",Museum TV English (1080p)
|
||||
https://cdn-ue1-prod.tsv2.amagi.tv/linear/amg01492-secomsasmediart-museumtven-xiaomi/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TiVi5Monde.fr",TiVi5 Monde [Geo-blocked]
|
||||
https://ott.tv5monde.com/Content/HLS/Live/channel(tivi5)/variant.m3u8
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="ETV.gp",ETV (1080p)
|
||||
https://edge.vedge.infomaniak.com/livecast/ik:etvgp/manifest.m3u8
|
||||
https://edge12.vedge.infomaniak.com/livecast/ik:etvgp/manifest.m3u8
|
||||
#EXTINF:-1 tvg-id="MadrasFMTV.gp",Madras FM TV (1080p)
|
||||
https://edge12.vedge.infomaniak.com/livecast/ik:madrasfmtv/manifest.m3u8
|
||||
#EXTINF:-1 tvg-id="RadioTVBasseTerre.gp",Radio TV Basse-Terre (720p)
|
||||
|
|
|
@ -23,6 +23,10 @@ https://www.hellasnet.tv/rest2.live.hn/w2r.alf/playlist.m3u8
|
|||
http://alphatvlive.siliconweb.com/1/Y2Rsd1lUcUVoajcv/UVdCN25h/hls/live/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ANT1.gr",ANT1 (1080p) [Geo-blocked]
|
||||
http://d1nfykbwa3n98t.cloudfront.net/out/v1/6e5667da5a6843899a337dea72adb61b/antenna.m3u8
|
||||
#EXTINF:-1 tvg-id="ANT1.gr" http-referrer="http://watch.antennaplus.gr" http-user-agent="Chrome",ANT1 (1080p) [Geo-blocked]
|
||||
#EXTVLCOPT:http-referrer=http://watch.antennaplus.gr
|
||||
#EXTVLCOPT:http-user-agent=Chrome
|
||||
https://mcdn.antennaplus.gr/live/media0/Ant1/HLS/Ant1.m3u8
|
||||
#EXTINF:-1 tvg-id="ART.gr",APT (1080p)
|
||||
https://hugh.cdn.rumble.cloud/live/k5e12sb4/slot-82/fdd0-tbln/chunklist_DVR.m3u8
|
||||
#EXTINF:-1 tvg-id="ARTTV.gr",ART TV (720p)
|
||||
|
@ -63,8 +67,6 @@ http://live.streams.ovh:1935/tvcreta/tvcreta/playlist.m3u8
|
|||
http://81.171.10.42:554/liveD/DStream.sdp/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="DiavataTV.gr",Diavata TV (720p)
|
||||
https://ssh101.bozztv.com/ssh101/diavatatvweb/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="DiavataTV.gr",Diavata TV (720p)
|
||||
https://video.streams.ovh:1936/DiavataTV/DiavataTV/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="DiktyoTV.gr",Diktyo TV (576p)
|
||||
https://5d00db0e0fcd5.streamlock.net/7322/7322/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="DipsoTV.gr",Dipso TV (720p) [Not 24/7]
|
||||
|
@ -156,6 +158,8 @@ https://til.pp.ua:3872/live/mesogeiostvlive.m3u8
|
|||
https://vod.streams.ovh:3876/stream/play.m3u8
|
||||
#EXTINF:-1 tvg-id="NaftemporikiTV.gr",Naftemporiki TV (1080p)
|
||||
https://naftemporiki-live.cdn.vustreams.com/live/a4b4a88a-681c-4a2d-8e74-33daa5f2cb61/live.isml/.m3u8
|
||||
#EXTINF:-1 tvg-id="NationalGeographic.gr",National Geographic (1080p)
|
||||
http://62.210.211.188:2095/play/a00d
|
||||
#EXTINF:-1 tvg-id="NeaTV.gr",Nea TV (720p)
|
||||
https://live.neatv.gr:8888/hls/neatv.m3u8
|
||||
#EXTINF:-1 tvg-id="NeaTV.gr",Nea TV (720p)
|
||||
|
@ -277,9 +281,3 @@ http://live.cretetv.gr:1935/cretetv/myStream/f1tv.m3u8
|
|||
https://vod.streams.ovh:3037/stream/play.m3u8
|
||||
#EXTINF:-1 tvg-id="RIKSat.cy",ΡΙΚ Sat (720p) [Not 24/7]
|
||||
http://l3.cloudskep.com/cybcsat/abr/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="NationalGeographic.gr",National Geographic (1080p)
|
||||
http://62.210.211.188:2095/play/a00d
|
||||
#EXTINF:-1 tvg-id="ANT1.gr" http-referrer="http://watch.antennaplus.gr" http-user-agent="Chrome",ANT1 (1080p) [Geo-blocked]
|
||||
#EXTVLCOPT:http-referrer=http://watch.antennaplus.gr
|
||||
#EXTVLCOPT:http-user-agent=Chrome
|
||||
https://mcdn.antennaplus.gr/live/media0/Ant1/HLS/Ant1.m3u8
|
||||
|
|
|
@ -199,12 +199,6 @@ http://194.76.186.33:8000/play/a05h/index.m3u8
|
|||
http://194.76.186.33:8000/play/a02a/index.m3u8
|
||||
#EXTINF:-1 tvg-id="RTLHarom.hu",RTL Harom (576p)
|
||||
http://194.76.186.33:8000/play/a05d/index.m3u8
|
||||
#EXTINF:-1 tvg-id="RTL.hu",RTL Hungary (1080p)
|
||||
http://194.76.186.33:8000/play/a041/index.m3u8
|
||||
#EXTINF:-1 tvg-id="RTL.hu",RTL Hungary (576p)
|
||||
http://194.76.186.33:8000/play/a01e/index.m3u8
|
||||
#EXTINF:-1 tvg-id="RTL.hu",RTL Hungary (576p)
|
||||
http://194.76.186.33:8000/play/a04g/index.m3u8
|
||||
#EXTINF:-1 tvg-id="RTLKetto.hu",RTL Ketto (1080p)
|
||||
http://194.76.186.33:8000/play/a01z/index.m3u8
|
||||
#EXTINF:-1 tvg-id="RTLKetto.hu",RTL Ketto (576p)
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="7SMusic.in",7S Music (576p) [Not 24/7]
|
||||
http://103.199.161.254/Content/7smusic/Live/Channel(7smusic)/index.m3u8
|
||||
#EXTINF:-1 tvg-id="9XJalwa.in",9X Jalwa
|
||||
https://amg01281-9xmediapvtltd-9xjalwa-samsungin-goszf.amagi.tv/playlist/amg01281-9xmediapvtltd-9xjalwa-samsungin/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="9XJhakaas.in",9x Jhakaas
|
||||
https://amg01281-9xmediapvtltd-9xjhakaas-samsungin-ci2cs.amagi.tv/playlist/amg01281-9xmediapvtltd-9xjhakaas-samsungin/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="9XTashan.in",9X Tashan
|
||||
https://amg01281-9xmediapvtltd-9xtashan-samsungin-xz1sd.amagi.tv/playlist/amg01281-9xmediapvtltd-9xtashan-samsungin/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="9XM.in",9XM (576p)
|
||||
https://d35j504z0x2vu2.cloudfront.net/v1/manifest/0bc8e8376bd8417a1b6761138aa41c26c7309312/9xm/23886666-8fc5-470f-aab1-bd637ed607b1/3.m3u8
|
||||
#EXTINF:-1 tvg-id="A1TVRajasthan.in",A1 TV Rajasthan (720p)
|
||||
https://5b48d7e1b4bce.streamlock.net/myapp/a1live/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AajTak.in",Aaj Tak
|
||||
|
@ -646,6 +646,8 @@ https://yoganadam.cinesoftcdn.com/yoganadam/live/index.m3u8
|
|||
https://zainabia.livebox.co.in/ZainabiaChannelhls/channel.m3u8
|
||||
#EXTINF:-1 tvg-id="Zee24Kalak.in",Zee 24 Kalak (720p)
|
||||
https://livetv-channels.b-cdn.net/8077/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="",Zee Alwan (576p) [Not 24/7]
|
||||
https://tgn.bozztv.com/gin-dvrfl05/ga-zeealwan/index.m3u8
|
||||
#EXTINF:-1 tvg-id="ZeeBiharJharkhand.in",Zee Bihar Jharkhand (720p)
|
||||
https://d3dxf2v5wg5rcy.cloudfront.net/out/v1/349f643193e347609b16671d8e0bfb4a/index.m3u8
|
||||
#EXTINF:-1 tvg-id="ZeeBiharJharkhand.in",Zee Bihar Jharkhand (720p)
|
||||
|
@ -674,7 +676,13 @@ https://livetv-channels.b-cdn.net/8076/playlist.m3u8
|
|||
http://183.89.246.119:8881/play/a09a/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Zoom.in",Zoom (1080p)
|
||||
http://103.81.104.118/hls/stream8.m3u8
|
||||
#EXTINF:-1 tvg-id="ZeeAlwan.in",Zee Alwan (576p) [Not 24/7]
|
||||
https://tgn.bozztv.com/gin-dvrfl05/ga-zeealwan/index.m3u8
|
||||
#EXTINF:-1 tvg-id="9XM.in",9XM (576p)
|
||||
https://d35j504z0x2vu2.cloudfront.net/v1/manifest/0bc8e8376bd8417a1b6761138aa41c26c7309312/9xm/23886666-8fc5-470f-aab1-bd637ed607b1/3.m3u8
|
||||
#EXTINF:-1 tvg-id="ShowBox.in",ShowBox
|
||||
https://epiconvh.akamaized.net/live/showbox/master.m3u8
|
||||
#EXTINF:-1 tvg-id="RajTV.in",Raj TV
|
||||
https://d3qs3d2rkhfqrt.cloudfront.net/out/v1/2839e3d1e0f84a2e821c1708d5fdfdf0/index.m3u8
|
||||
#EXTINF:-1 tvg-id="DDSahyadri.in",DD Sahyadri
|
||||
https://d3qs3d2rkhfqrt.cloudfront.net/out/v1/66dcc3ebe182447ba42837e746cf0c7c/index.m3u8
|
||||
#EXTINF:-1 tvg-id="DDSaptagiri.in",DD Saptagiri
|
||||
https://d2lk5u59tns74c.cloudfront.net/out/v1/26e915d6d12b4a06822c5e33c088ed56/index.m3u8
|
||||
#EXTINF:-1 tvg-id="DDHaryana.in",DD Haryana
|
||||
https://d2lk5u59tns74c.cloudfront.net/out/v1/950fc69666474351bde0a32b9600c804/index.m3u8
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
https://mediaserver.abnvideos.com/streams/abnsat.m3u8
|
||||
#EXTINF:-1 tvg-id="AfaqTV.iq",Afaq TV
|
||||
http://63b03f7689049.streamlock.net:1935/live/1/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AfarinBaxcha.iq",Afarin Baxcha (1080p)
|
||||
https://5dcabf026b188.streamlock.net/afarinTV/livestream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AfarinTV.iq",Afarin TV (720p) [Not 24/7]
|
||||
https://65f16f0fdfc51.streamlock.net/afarinTV/livestream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AlahadTV.iq",Al Ahad TV
|
||||
|
@ -32,7 +34,7 @@ https://arrafidain.tvplayer.online/arrafidaintv/source2/playlist.m3u8
|
|||
https://arrafidain.tvplayer.online/arrafidaintv/source/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AlRasheedTV.iq",Al Rasheed TV (1080p) [Not 24/7]
|
||||
https://media1.livaat.com/static/AL-RASHEED-HD/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="AlShabab.iq",Al Shabab TV (1080p)
|
||||
#EXTINF:-1 tvg-id="",Al Shabab TV (1080p)
|
||||
http://149.100.11.244:8001/play/a07n/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AlAimmaTV.iq" http-referrer="https://alaimma.tv",Al-Aimma TV (1080p)
|
||||
#EXTVLCOPT:http-referrer=https://alaimma.tv
|
||||
|
@ -138,6 +140,8 @@ http://stream.nubar.tv:1935/private/NUBARtv/playlist.m3u8
|
|||
https://media2.streambrothers.com:1936/8218/8218/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RudawTV.iq",Rudaw TV (1080p)
|
||||
https://svs.itworkscdn.net/rudawlive/rudawlive.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ShamsTV.iq",Shams TV (1080p)
|
||||
https://stream.shams.tv/hls/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="UTV.iq",UTV (1080p)
|
||||
https://mn-nl.mncdn.com/utviraqi2/64c80359/index.m3u8
|
||||
#EXTINF:-1 tvg-id="WaarTV.iq",Waar TV
|
||||
|
@ -146,7 +150,3 @@ https://ca-rt.onetv.app/Waar/index-0.m3u8
|
|||
https://5a3ed7a72ed4b.streamlock.net/zagrostv/SMIL:myStream.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="",Zarok TV Sorani (720p)
|
||||
https://zindisorani.zaroktv.com.tr/hls/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="AfarinBaxcha.iq",Afarin Baxcha (1080p)
|
||||
https://5dcabf026b188.streamlock.net/afarinTV/livestream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ShamsTV.iq",Shams TV (1080p)
|
||||
https://stream.shams.tv/hls/stream.m3u8
|
||||
|
|
|
@ -71,3 +71,5 @@ https://hls.nejat.live/hls/stream.m3u8
|
|||
https://hls.vox1.live/hls/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="YourTimeTV.ir",YourTime TV (720p) [Not 24/7]
|
||||
https://hls.yourtime.live/hls/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="VarzeshTV.ir",Varzesh TV (480p)
|
||||
https://stream.sainaertebat.com/hls2/varzeshtest.m3u8
|
||||
|
|
|
@ -37,6 +37,8 @@ https://59d7d6f47d7fc.streamlock.net/auroraarte/auroraarte/playlist.m3u8
|
|||
https://ed05.top-ix.org/avtvlive/azzurra/streaming/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="BikeChannel.it",Bike (720p)
|
||||
http://backup.superstreaming.inaria.me/BikeSmartMobilityDTT/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Boing.it",Boing (720p)
|
||||
https://liveturner.akamaized.net/75a4d1a90e744fa5b9901a1853d2c47f/eu-central-1/6284318116001/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Boing.it",Boing Italy [Geo-blocked]
|
||||
https://live2.msf.cdn.mediaset.net/content/hls_h0_clr_vos/live/channel(kb)/index.m3u8
|
||||
#EXTINF:-1 tvg-id="BomChannel.it",Bom Channel
|
||||
|
@ -314,6 +316,9 @@ https://mediapolisevent.rai.it/relinker/relinkerServlet.htm?cont=2606803
|
|||
https://mediapolis.rai.it/relinker/relinkerServlet.htm?cont=308718
|
||||
#EXTINF:-1 tvg-id="Rai2.it",Rai 2 (302p) [Geo-blocked]
|
||||
http://stream.tvtap.net:8081/live/it-rai2.stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Rai2HD.it" http-referrer="https://babaktv.com/",Rai 2 HD
|
||||
#EXTVLCOPT:http-referrer=https://babaktv.com/
|
||||
https://m3u.iranvids.com/rai02/output.m3u8
|
||||
#EXTINF:-1 tvg-id="Rai3.it",Rai 3 (720p)
|
||||
https://dash2.antik.sk/live/test_rai_tre_tizen/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Rai3.it",Rai 3 (720p) [Not 24/7]
|
||||
|
@ -662,8 +667,11 @@ https://stream.cp.ets-sistemi.it:1936/profservtv/profservtv/playlist.m3u8
|
|||
https://5f22d76e220e1.streamlock.net/canale5/canale5/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ZerounoTVNews.it",Zerouno TV News (720p)
|
||||
https://5db313b643fd8.streamlock.net/ZerounoTVEventi/ZerounoTVEventi/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Boing.it",Boing (720p)
|
||||
https://liveturner.akamaized.net/75a4d1a90e744fa5b9901a1853d2c47f/eu-central-1/6284318116001/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Rai2HD.it" http-referrer="https://babaktv.com/",Rai 2 HD
|
||||
#EXTVLCOPT:http-referrer=https://babaktv.com/
|
||||
https://m3u.iranvids.com/rai02/output.m3u8
|
||||
#EXTINF:-1 tvg-id="Telenova.it",Telenova (720p)
|
||||
https://64b16f23efbee.streamlock.net/telenova/telenova/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TeleQuattro.it",Tele Quattro (720p)
|
||||
https://59d7d6f47d7fc.streamlock.net/telequattro/telequattro/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="7RadioVisione.it",7 RadioVisione (720p)
|
||||
https://stream10.xdevel.com/video1s976543-1932/stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RTV38.it",RTV38 (576p)
|
||||
https://streamcdne1-845d8509d2cb4f249dd0b2ae5755b6c2.msvdn.net/rtv38/rtv38_live_main/mainabr/rtv38_live_main/main_576/chunks_dvr.m3u8
|
||||
|
|
|
@ -32,10 +32,6 @@ https://cdn.skygo.mn/live/disk1/NHK_World_Premium/HLSv3-FTA/NHK_World_Premium.m3
|
|||
https://master.nhkworld.jp/nhkworld-tv/playlist/live.m3u8
|
||||
#EXTINF:-1 tvg-id="JOAXDTV.jp",Nippon TV (540p) [Not 24/7]
|
||||
https://ntv4.mov3.co/hls/ntv.m3u8
|
||||
#EXTINF:-1 tvg-id="NTVNEWS24.jp",NTV News24 (480p)
|
||||
https://n24-cdn-live.ntv.co.jp/ch01/index.m3u8
|
||||
#EXTINF:-1 tvg-id="NTVNEWS24.jp",NTV News24 (480p)
|
||||
https://n24-cdn-live.ntv.co.jp/ch02/index.m3u8
|
||||
#EXTINF:-1 tvg-id="QVC.jp",QVC Japan (720p)
|
||||
https://cdn-live1.qvc.jp/iPhone/1501/1501.m3u8
|
||||
#EXTINF:-1 tvg-id="ShopChannel.jp",Shop Channel (1080p) [Not 24/7]
|
||||
|
|
|
@ -17,8 +17,6 @@ http://cdns.jp-primehome.com:8000/zhongying/live/playlist.m3u8?cid=bs04&isp=4
|
|||
http://cdns.jp-primehome.com:8000/zhongying/live/playlist.m3u8?cid=bs05&isp=4
|
||||
#EXTINF:-1 tvg-id="ChannelGinga.jp",Channel Ginga (1080p)
|
||||
http://cdns.jp-primehome.com:8000/zhongying/live/playlist.m3u8?cid=cs29&isp=4
|
||||
#EXTINF:-1 tvg-id="CNNj.jp",CNNj (544p)
|
||||
http://cdns.jp-primehome.com:8000/zhongying/live/playlist.m3u8?cid=cs16&isp=4
|
||||
#EXTINF:-1 tvg-id="DisneyChannel.jp",Disney Channel Japan (544p)
|
||||
http://cdns.jp-primehome.com:8000/zhongying/live/playlist.m3u8?cid=bs24&isp=4
|
||||
#EXTINF:-1 tvg-id="EiseiGekijo.jp",Eisei Gekijo (544p)
|
||||
|
|
|
@ -17,6 +17,8 @@ https://streaming.freshnewsasia.com/live/ngrp:myStream_all/playlist.m3u8
|
|||
http://clive.malisresidences.com:1935/hm_hdtv/_definst_/smil:HMHDTV.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="iTVHD.kh",iTV HD
|
||||
http://43.252.18.195:5080/live/streams/itv.khmeretv.m3u8
|
||||
#EXTINF:-1 tvg-id="KomsanTV.kh",Komsan TV [Not 24/7]
|
||||
http://tv.cootel.com.kh:8077/streams/d/Komsan/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="MSJTV.kh" http-referrer="https://www.iptvservice.site/",MSJ TV (1080p)
|
||||
#EXTVLCOPT:http-referrer=https://www.iptvservice.site/
|
||||
https://live-ali7.tv360.metfone.com.kh/live/myStream/playlist.m3u8
|
||||
|
@ -38,10 +40,14 @@ https://fmseatv.netlinkbroadcaster.com/hls/test.m3u8
|
|||
https://live-evg13.tv360.metfone.com.kh/live/towntv.m3u8
|
||||
#EXTINF:-1 tvg-id="TownTV.kh",Town TV (720p)
|
||||
https://live.kh.malimarcdn.com/live/towntv.stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TV3.kh",TV 3
|
||||
http://206.189.93.160:1935/live/myStream_720p/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TV3.kh",TV 3 (720p)
|
||||
https://edge6a.v2h-cdn.com/tv3cam/tv3cam.stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TV5Cambodia.kh",TV5 Cambodia
|
||||
http://live.happywatch99.com/livehd14/77bbe9df6a93cf229cd40f1400af00fa.sdp/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TV5Cambodia.kh",TV5 Cambodia (1080p)
|
||||
https://es1-p1-netcdn.metfone.com.kh/netcdn-live-36/36/output/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TV5Cambodia.kh",TV5 Cambodia (720p)
|
||||
https://live-evg3.tv360.metfone.com.kh/live/tv5.m3u8
|
||||
#EXTINF:-1 tvg-id="TVK.kh",TVK (720p)
|
||||
|
@ -50,9 +56,7 @@ https://live.kh.malimarcdn.com/live/tvk.stream/playlist.m3u8
|
|||
https://live.kh.malimarcdn.com/live/tvk2.stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="WikiTV.kh",WIKI TV (720p)
|
||||
https://stream.wikitv.asia/live/ngrp:myStream_all/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TV5Cambodia.kh",TV5 Cambodia (1080p)
|
||||
https://es1-p1-netcdn.metfone.com.kh/netcdn-live-36/36/output/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="KomsanTV.kh",Komsan TV [Not 24/7]
|
||||
http://tv.cootel.com.kh:8077/streams/d/Komsan/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TV3.kh",TV 3
|
||||
http://206.189.93.160:1935/live/myStream_720p/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="MyTV.kh",My TV
|
||||
http://43.252.18.195:5080/live/streams/mytv.m3u8
|
||||
#EXTINF:-1 tvg-id="CTN.kh",CTN (480p)
|
||||
http://43.252.18.195:5080/live/streams/ctntv.m3u8
|
||||
|
|
|
@ -35,6 +35,8 @@ https://btn.nowcdn.co.kr/btn/btnlive2m/playlist.m3u8
|
|||
https://du35ivadp6cxj.cloudfront.net/out/v1/81781d23cbbf490990b2aa9181d4ce19/CGNWebLiveKR.m3u8
|
||||
#EXTINF:-1 tvg-id="ChannelA.kr",Channel A [Geo-blocked]
|
||||
http://channelalive.ktcdn.co.kr/chalivepc/_definst_/atv2/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ChannelA.kr",Channel A (360p)
|
||||
http://www.hwado.net/webtv/catv/52_440DDPPJ.php
|
||||
#EXTINF:-1 tvg-id="CJOnStyle.kr",CJ OnStyle (540p)
|
||||
https://live-ch1.cjonstyle.net/cjmalllive/stream2/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="CJOnStylePlus.kr",CJ OnStyle Plus (540p)
|
||||
|
@ -86,8 +88,6 @@ https://live.jobplustv.or.kr/live/wowtvlive1.sdp/playlist.m3u8
|
|||
#EXTINF:-1 tvg-id="KBS1TV.kr",KBS 1TV [Not 24/7]
|
||||
http://mytv.dothome.co.kr/ch/public/1.php
|
||||
#EXTINF:-1 tvg-id="KBS1TV.kr",KBS 1 UHD (720p)
|
||||
http://202.60.106.14:8080/200/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="KBS1TV.kr",KBS 1 UHD (720p)
|
||||
http://202.60.106.14:21585/200/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="KBS2TV.kr",KBS 2TV [Not 24/7]
|
||||
http://mytv.dothome.co.kr/ch/public/3.php
|
||||
|
@ -105,8 +105,6 @@ http://kbs-dokdo.gscdn.com/dokdo_300/dokdo_300.stream/playlist.m3u8
|
|||
http://mytv.dothome.co.kr/ch/catv/4.php
|
||||
#EXTINF:-1 tvg-id="KBSWorld.kr",KBS World
|
||||
http://mytv.dothome.co.kr/ch/catv/7.php
|
||||
#EXTINF:-1 tvg-id="KBSWorld.kr",KBS World (Vietnamese Subtitles) (720p)
|
||||
https://livecdn.fptplay.net/sdb/kbs_hls.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="KCTV.kr",KCTV 광주 CH05 (720p) [Not 24/7]
|
||||
http://119.77.96.184:1935/chn05/chn05/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="KTV.kr",Korea TV (1080p)
|
||||
|
@ -147,6 +145,8 @@ https://5ee9633b25727.streamlock.net/jmbc_tv/_definst_/jmbc_tv.stream/playlist.m
|
|||
http://vod.mpmbc.co.kr:1935/live/encoder-tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="MBCNet.kr",MBC Net (480p) [Geo-blocked]
|
||||
http://mytv.dothome.co.kr/ch/catv/28.php
|
||||
#EXTINF:-1 tvg-id="MBCTV.kr",MBC TV (720p)
|
||||
http://www.hwado.net/webtv/catv/503_CFEA7803.php
|
||||
#EXTINF:-1 tvg-id="HLATDTV.kr",MBC Yeosu (여수 MBC) (1080p) [Not 24/7]
|
||||
https://5c3639aa99149.streamlock.net/live_TV/tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="MTN.kr",MTN (720p)
|
||||
|
@ -173,6 +173,8 @@ https://live.knou.ac.kr/knou1/live1/playlist.m3u8
|
|||
https://rtv-stream2.a04f922e9e85c8d25ebfeae3dfd22a67.com/rtv/rtv.m3u8
|
||||
#EXTINF:-1 tvg-id="RUTCTV.kr",RUTC TV (720p)
|
||||
http://d26sxnc75smwvh.cloudfront.net/livehttporigin/rutclive_720p2.stream/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="SBS.kr",SBS (480p)
|
||||
http://www.hwado.net/webtv/catv/502_76142D8F.php
|
||||
#EXTINF:-1 tvg-id="HLDRDTV.kr",SBS CJB (540p) [Not 24/7]
|
||||
http://1.222.207.80:1935/live/cjbtv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="HLCGDTV.kr",SBS G1 (360p) [Not 24/7]
|
||||
|
@ -221,9 +223,3 @@ http://157.245.196.186/live/livestream.m3u8
|
|||
http://202.60.106.14:8080/214/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="YTN.kr",YTN (720p)
|
||||
http://202.60.106.14:21585/214/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="SBS.kr",SBS (480p)
|
||||
http://www.hwado.net/webtv/catv/502_76142D8F.php
|
||||
#EXTINF:-1 tvg-id="ChannelA.kr",Channel A (360p)
|
||||
http://www.hwado.net/webtv/catv/52_440DDPPJ.php
|
||||
#EXTINF:-1 tvg-id="MBCTV.kr",MBC TV (720p)
|
||||
http://www.hwado.net/webtv/catv/503_CFEA7803.php
|
||||
|
|
|
@ -31,8 +31,6 @@ https://stream.kaztrk.kz/regional/kokshetautv/index.m3u8
|
|||
http://212.42.111.152:8080/hls/manas.m3u8
|
||||
#EXTINF:-1 tvg-id="Mangystay.kz",Mańǵystaý (540p) [Not 24/7]
|
||||
https://stream.kaztrk.kz/regional/mangystautv/index.m3u8
|
||||
#EXTINF:-1 tvg-id="MuzzOne.kz",MuzzOne (1080p)
|
||||
https://muzzone-stream.daitsuna.net/muzzondvr/muzzone/playlist_dvr.m3u8
|
||||
#EXTINF:-1 tvg-id="Ontustik.kz",Ontústik (360p)
|
||||
https://stream.kaztrk.kz/regional/shymkenttv/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Qazaqstan.kz",Qazaqstan TV (720p) [Not 24/7]
|
||||
|
@ -63,3 +61,5 @@ https://tvcdn01.oktv.kz/tv/mtrk/playlist.m3u8
|
|||
http://serv25.vintera.tv:8081/novoetv/nov_tv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ChannelOneEurasia.kz",Первый канал Евразия (720p)
|
||||
https://1tvkz-stream.daitsuna.net/1tvkz/1tvkz/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="MuzzOne.kz",MuzzOne (1080p)
|
||||
https://streams.qazcdn.net/muzzone/muzzone/playlist_dvr.m3u8
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
https://tv.hiruhost.com:1936/8012/8012/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ImaiTV.lk",Imai TV (720p)
|
||||
https://rpn3.bozztv.com/ssh101/ssh101/imaitv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ITN.lk",ITN (720p) [Not 24/7]
|
||||
https://j78dp2pnlq5r-hls-live.comcities.net/ITNDigital/cf467ddf13ba30dd3c71435cafa6fd6e.sdp/playlist_dvr.m3u8
|
||||
#EXTINF:-1 tvg-id="MonaraTV.lk",Monara TV (720p) [Not 24/7]
|
||||
https://jk3lz8xklw79-hls-live.5centscdn.com/lpl/d0dbe915091d400bd8ee7f27f0791303.sdp/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Rupavahini.lk",Rupavahini (480p)
|
||||
|
@ -19,3 +17,7 @@ https://jk3lz8xklw79-hls-live.5centscdn.com/live/6226f7cbe59e99a90b5cef6f94f966f
|
|||
https://j78dp2pnlq5r-hls-live.comcities.net/ITNDigital/20a317b0496a4930b375290505e5d628.sdp/playlist_dvr.m3u8
|
||||
#EXTINF:-1 tvg-id="VerbumTV.lk",Verbum TV (414p) [Not 24/7]
|
||||
https://verbumtv.livebox.co.in/verbumtvhls/live.m3u8
|
||||
#EXTINF:-1 tvg-id="VasanthamTV.lk",Vasantham TV (720p)
|
||||
https://222103-hls.akamaized.net/668828a00bf80aa436254876/live_2cdb4ef03c1311efadcf7986aa245789/rewind-3600.m3u8
|
||||
#EXTINF:-1 tvg-id="ITN.lk",ITN (1080p)
|
||||
https://222103-hls.akamaized.net/668828a00bf80aa436254876/live_aabd3d003af211efadcf7986aa245789/rewind-3600.m3u8
|
||||
|
|
|
@ -3,36 +3,26 @@
|
|||
#EXTVLCOPT:http-referrer=https://2m.ma
|
||||
#EXTVLCOPT:http-user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0
|
||||
https://cdn-globecast.akamaized.net/live/eds/2m_monde/hls_video_ts_tuhawxpiemz257adfc/2m_monde.m3u8
|
||||
#EXTINF:-1 tvg-id="2MNational.ma",2M National (1080p)
|
||||
http://154.197.91.168:7001/play/a0fn/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AlAoulaInter.ma",Al Aoula International (1080p)
|
||||
http://154.197.91.168:7001/play/a0f5/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AlAoulaInter.ma",Al Aoula International (480p)
|
||||
https://cdn.live.easybroadcast.io/abr_corp/73_aloula_w1dqfwm/playlist_dvr.m3u8
|
||||
#EXTINF:-1 tvg-id="AlAoula.ma",Al Aoula Laâyoune (1080p)
|
||||
http://154.197.91.168:7001/play/a0f6/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AlAoula.ma",Al Aoula Laâyoune (480p)
|
||||
#EXTINF:-1 tvg-id="LaayouneTV.ma",Al Aoula Laâyoune (480p)
|
||||
https://cdn.live.easybroadcast.io/abr_corp/73_laayoune_pgagr52/playlist_dvr.m3u8
|
||||
#EXTINF:-1 tvg-id="AlMaghribia.ma",Al Maghribia (1080p)
|
||||
http://154.197.91.168:7001/play/a0f9/index.m3u8
|
||||
#EXTINF:-1 tvg-id="AlMaghribia.ma",Al Maghribia (480p)
|
||||
https://cdn.live.easybroadcast.io/abr_corp/73_almaghribia_83tz85q/playlist_dvr.m3u8
|
||||
#EXTINF:-1 tvg-id="",Al Rahman (480p)
|
||||
http://149.100.11.244:8001/play/a06j/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Arryadia.ma",Arryadia (1080p)
|
||||
http://154.197.91.168:7001/play/a0f7/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Arryadia.ma",Arryadia (480p)
|
||||
https://cdn.live.easybroadcast.io/abr_corp/73_arryadia_k2tgcj0/playlist_dvr.m3u8
|
||||
#EXTINF:-1 tvg-id="Arryadia.ma",Arryadia (1080p)
|
||||
http://154.197.91.168:7001/play/a0fa/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Assadissa.ma",Assadissa (480p)
|
||||
https://cdn.live.easybroadcast.io/abr_corp/73_assadissa_7b7u5n1/playlist_dvr.m3u8
|
||||
#EXTINF:-1 tvg-id="Athaqafia.ma",Athaqafia (1080p)
|
||||
http://154.197.91.168:7001/play/a0f8/index.m3u8
|
||||
#EXTINF:-1 tvg-id="Athaqafia.ma",Athaqafia (480p)
|
||||
https://cdn.live.easybroadcast.io/abr_corp/73_arrabia_hthcj4p/playlist_dvr.m3u8
|
||||
#EXTINF:-1 tvg-id="ChadaTV.ma",Chada TV (720p)
|
||||
https://chadatv.vedge.infomaniak.com/livecast/chadatv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="ChadaTV.ma",Chada TV (720p)
|
||||
https://edge19.vedge.infomaniak.com/livecast/ik:chadatv/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="M24TV.ma",M24 TV (1080p)
|
||||
https://67aac8c668349.streamlock.net/live/ngrp:Live2.stream_all/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Medi1TVAfrique.ma",Medi 1 TV Afrique (1080p) [Not 24/7]
|
||||
https://streaming1.medi1tv.com/live/smil:medi1fr.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Medi1TVAfrique.ma",Medi 1 TV Afrique (1080p) [Not 24/7]
|
||||
|
@ -45,7 +35,9 @@ https://streaming2.medi1tv.com/live/smil:medi1ar.smil/playlist.m3u8
|
|||
https://streaming1.medi1tv.com/live/smil:medi1tv.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="Medi1TVMaghreb.ma",Medi 1 TV Maghreb (1080p) [Not 24/7]
|
||||
https://streaming2.medi1tv.com/live/smil:medi1tv.smil/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="TamazightTV.ma",Tamazight (1080p)
|
||||
http://154.197.91.168:7001/play/a0fb/index.m3u8
|
||||
#EXTINF:-1 tvg-id="TamazightTV.ma",Tamazight (480p)
|
||||
https://cdn.live.easybroadcast.io/abr_corp/73_tamazight_tccybxt/playlist_dvr.m3u8
|
||||
#EXTINF:-1 tvg-id="2MMonde.ma",2M Monde (720p)
|
||||
https://d3g87jnubafe6a.cloudfront.net/out/v1/1fa0fb3c8dec402994a6f7a7f6492b82/index.m3u8
|
||||
#EXTINF:-1 tvg-id="TeleMaroc.ma",Tele Maroc (720p)
|
||||
https://raw.githubusercontent.com/ipstreet312/freeiptv/master/ressources/kuw/telmar.m3u8
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#EXTM3U
|
||||
#EXTINF:-1 tvg-id="MonacoInfo.mc",Monaco Info (720p) [Not 24/7]
|
||||
https://webtvmonacoinfo.mc/live/prod_720/index.m3u8
|
||||
#EXTINF:-1 tvg-id="SuperyachtTV.mc",Superyacht TV (1080p)
|
||||
https://sy.wns.live/hls/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="TVMonaco.mc",TV Monaco (1080p)
|
||||
https://production-fast-mcrtv.content.okast.tv/channels/2116dc08-1959-465d-857f-3619daefb66b/b702b2b9-aebd-436c-be69-2118f56f3d86/2024/media.m3u8
|
||||
#EXTINF:-1 tvg-id="MonacoInfo.mc",Monaco Info (1080p)
|
||||
https://webtv.monacoinfo.com/live/prod/index.m3u8
|
||||
|
|
|
@ -3,29 +3,61 @@
|
|||
http://hls.protv.md/acasatv/acasatv.m3u8
|
||||
#EXTINF:-1 tvg-id="BaltiTV.md",Bălţi TV (1080p) [Geo-blocked]
|
||||
http://77.89.199.174:8000/play/1024/index.m3u8
|
||||
#EXTINF:-1 tvg-id="BusuiocTV.md",Busuioc TV (1080p)
|
||||
http://62.233.57.226:8001/play/a005
|
||||
#EXTINF:-1 tvg-id="BusuiocTV.md",Busuioc TV (540p) [Not 24/7]
|
||||
https://busuioctv.iforward.eu/hls/busuioc.m3u8
|
||||
#EXTINF:-1 tvg-id="Cinema1.md",Cinema 1 (1080p)
|
||||
http://62.233.57.226:8001/play/a00l00
|
||||
#EXTINF:-1 tvg-id="DrochiaTV.md",Drochia TV (1080p) [Not 24/7]
|
||||
https://hls.drochia.tv/tv/web.m3u8
|
||||
#EXTINF:-1 tvg-id="DuniaSinema.my",Dunia Sinema (1080p)
|
||||
https://unifi-live05.secureswiftcontent.com/UnifiHD/live27-1080FHD.m3u8
|
||||
#EXTINF:-1 tvg-id="ExclusivTV.md",Exclusiv TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00f
|
||||
#EXTINF:-1 tvg-id="GRT.md",GRT (1080p)
|
||||
http://62.233.57.226:8001/play/a00f00
|
||||
#EXTINF:-1 tvg-id="JurnalTV.md",Jurnal TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00i
|
||||
#EXTINF:-1 tvg-id="Moldova1.md",Moldova 1 (1080p)
|
||||
https://v0.trm.md/static/streaming-playlists/hls/9b79338b-1870-4cd7-91d4-0f6ce5cac7ca/master.m3u8
|
||||
#EXTINF:-1 tvg-id="Moldova2.md",Moldova 2 (1080p)
|
||||
https://v0.trm.md/static/streaming-playlists/hls/d5fafab0-9c37-4746-9e7a-b2d6c0427015/master.m3u8
|
||||
#EXTINF:-1 tvg-id="Moldova2.md",Moldova 2 (1080p)
|
||||
http://62.233.57.226:8001/play/a00a
|
||||
#EXTINF:-1 tvg-id="MoldovaTV.md",Moldova TV (576p) [Not 24/7]
|
||||
http://89.38.8.130:39435
|
||||
#EXTINF:-1 tvg-id="N4.md",N4 (1080p)
|
||||
http://62.233.57.226:8001/play/a007
|
||||
#EXTINF:-1 tvg-id="N4.md",N4 (360p) [Not 24/7]
|
||||
https://web.sats.gstv.tech/cpl11/da8df281-1165-440a-8c86-959f71b695b6/N4web.m3u8
|
||||
#EXTINF:-1 tvg-id="NextTV.md",Next TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00h
|
||||
#EXTINF:-1 tvg-id="NorocTV.md",Noroc TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00i00
|
||||
#EXTINF:-1 tvg-id="NorocTV.md",Noroc TV (576p) [Not 24/7]
|
||||
https://live.noroc.tv/noroc/noroc.m3u8
|
||||
#EXTINF:-1 tvg-id="NTSTV.md",NTS TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00d
|
||||
#EXTINF:-1 tvg-id="PremieraTV.md",Premiera TV (1080p)
|
||||
http://62.233.57.226:8001/play/a009
|
||||
#EXTINF:-1 tvg-id="PrivescEuTV.md",Privesc.Eu TV (2160p)
|
||||
https://cachestar.privesc.eu/liniar/moldova/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="PROTVChisinau.md",PRO TV Chisinau (1080p)
|
||||
http://62.233.57.226:8001/play/a00g
|
||||
#EXTINF:-1 tvg-id="PublikaTV.md",Publika TV (720p)
|
||||
https://livebeta.publika.press/LIVE/P/6810.m3u8
|
||||
#EXTINF:-1 tvg-id="RealitateaTV.md",Rlive TV (406p)
|
||||
https://realitatealive.md/tv/rlive.m3u8
|
||||
#EXTINF:-1 tvg-id="SorTV.md",Sor TV (720p)
|
||||
http://188.237.212.16:8888/live/cameraFeed.m3u8
|
||||
#EXTINF:-1 tvg-id="StarTV.md",Star TV (1080p)
|
||||
http://62.233.57.226:8001/play/a008
|
||||
#EXTINF:-1 tvg-id="TeleM.md",TeleM (576p)
|
||||
https://tv.streambox.ro/hls/telem/index.m3u8
|
||||
#EXTINF:-1 tvg-id="TezaurTV.md",Tezaur TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00j
|
||||
#EXTINF:-1 tvg-id="TezaurTV.md",Tezaur TV (1080p)
|
||||
https://tezaurtv.md/wp-content/uploads/live/index.m3u8
|
||||
#EXTINF:-1 tvg-id="TVNord.md",TV-Nord (1080p)
|
||||
https://6065d3147e895.streamlock.net:4444/npcl/live/playlist.m3u8
|
||||
|
@ -33,33 +65,5 @@ https://6065d3147e895.streamlock.net:4444/npcl/live/playlist.m3u8
|
|||
https://tvr-tvrmoldova.cdn.zitec.com/live/tvrmoldova/main.m3u8
|
||||
#EXTINF:-1 tvg-id="VoceaBasarabieiTV.md",Vocea Basarabiei TV (720p) [Not 24/7]
|
||||
https://storage.voceabasarabiei.md/vocea/vocea.m3u8
|
||||
#EXTINF:-1 tvg-id="ExclusivTV.md",Exclusiv TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00f
|
||||
#EXTINF:-1 tvg-id="TezaurTV.md",Tezaur TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00j
|
||||
#EXTINF:-1 tvg-id="StarTV.md",Star TV (1080p)
|
||||
http://62.233.57.226:8001/play/a008
|
||||
#EXTINF:-1 tvg-id="ZonaM.md",Zona M (576p)
|
||||
http://62.233.57.226:8001/play/a00e
|
||||
#EXTINF:-1 tvg-id="PROTVChisinau.md",PRO TV Chisinau (1080p)
|
||||
http://62.233.57.226:8001/play/a00g
|
||||
#EXTINF:-1 tvg-id="PremieraTV.md",Premiera TV (1080p)
|
||||
http://62.233.57.226:8001/play/a009
|
||||
#EXTINF:-1 tvg-id="NTSTV.md",NTS TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00d
|
||||
#EXTINF:-1 tvg-id="NorocTV.md",Noroc TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00i00
|
||||
#EXTINF:-1 tvg-id="NextTV.md",Next TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00h
|
||||
#EXTINF:-1 tvg-id="N4.md",N4 (1080p)
|
||||
http://62.233.57.226:8001/play/a007
|
||||
#EXTINF:-1 tvg-id="Moldova2.md",Moldova 2 (1080p)
|
||||
http://62.233.57.226:8001/play/a00a
|
||||
#EXTINF:-1 tvg-id="JurnalTV.md",Jurnal TV (1080p)
|
||||
http://62.233.57.226:8001/play/a00i
|
||||
#EXTINF:-1 tvg-id="GRT.md",GRT (1080p)
|
||||
http://62.233.57.226:8001/play/a00f00
|
||||
#EXTINF:-1 tvg-id="Cinema1.md",Cinema 1 (1080p)
|
||||
http://62.233.57.226:8001/play/a00l00
|
||||
#EXTINF:-1 tvg-id="BusuiocTV.md",Busuioc TV (1080p)
|
||||
http://62.233.57.226:8001/play/a005
|
||||
|
|
|
@ -14,18 +14,20 @@ http://210.210.155.35/dr9445/h/h04/index.m3u8
|
|||
http://210.210.155.37/uq2663/h/h22/index.m3u8
|
||||
#EXTINF:-1 tvg-id="MaahTV.my",Maah TV (720p) [Not 24/7]
|
||||
https://hls.maahtv.live/hls/stream.m3u8
|
||||
#EXTINF:-1 tvg-id="Okey.my" http-referrer="https://rtm-player.glueapi.io/",Okey RTM [Geo-blocked]
|
||||
#EXTINF:-1 tvg-id="Okey.my" http-referrer="https://rtm-player.glueapi.io/",Okey [Geo-blocked]
|
||||
#EXTVLCOPT:http-referrer=https://rtm-player.glueapi.io/
|
||||
https://d25tgymtnqzu8s.cloudfront.net/smil:okey/playlist.m3u8?id=3
|
||||
#EXTINF:-1 tvg-id="ParlimenMalaysia.my" http-referrer="https://rtm-player.glueapi.io/",RTM Parlimen (Dewan Negara) [Geo-blocked]
|
||||
#EXTINF:-1 tvg-id="RTMASEAN.my",RTM ASEAN
|
||||
https://d25tgymtnqzu8s.cloudfront.net/event/smil:event1/chunklist_b2596000_slENG.m3u8
|
||||
#EXTINF:-1 tvg-id="RTMParlimenDewanNegara.my" http-referrer="https://rtm-player.glueapi.io/",RTM Parlimen (Dewan Negara) [Geo-blocked]
|
||||
#EXTVLCOPT:http-referrer=https://rtm-player.glueapi.io/
|
||||
https://d25tgymtnqzu8s.cloudfront.net/smil:negara/playlist.m3u8?id=8
|
||||
#EXTINF:-1 tvg-id="ParlimenMalaysia.my" http-referrer="https://rtm-player.glueapi.io/",RTM Parlimen (Dewan Rakyat) [Geo-blocked]
|
||||
#EXTINF:-1 tvg-id="RTMParlimenDewanRakyat.my" http-referrer="https://rtm-player.glueapi.io/",RTM Parlimen (Dewan Rakyat) [Geo-blocked]
|
||||
#EXTVLCOPT:http-referrer=https://rtm-player.glueapi.io/
|
||||
https://d25tgymtnqzu8s.cloudfront.net/smil:rakyat/playlist.m3u8?id=7
|
||||
#EXTINF:-1 tvg-id="TV1.my",RTM TV 1 [Geo-blocked]
|
||||
#EXTINF:-1 tvg-id="TV1.my",TV1 [Geo-blocked]
|
||||
https://d25tgymtnqzu8s.cloudfront.net/smil:tv1/manifest.mpd
|
||||
#EXTINF:-1 tvg-id="TV2.my",RTM TV 2 [Geo-blocked]
|
||||
#EXTINF:-1 tvg-id="TV2.my",TV2 [Geo-blocked]
|
||||
https://d25tgymtnqzu8s.cloudfront.net/smil:tv2/manifest.mpd
|
||||
#EXTINF:-1 tvg-id="SukanRTM.my",Sukan RTM [Geo-blocked]
|
||||
https://d25tgymtnqzu8s.cloudfront.net/smil:sukan/manifest.mpd
|
||||
|
@ -42,7 +44,9 @@ https://tonton-live-switch-ssar.akamaized.net/stream-tv3/master.m3u8?bpkio_servi
|
|||
https://d25tgymtnqzu8s.cloudfront.net/smil:tv6/playlist.m3u8?id=6
|
||||
#EXTINF:-1 tvg-id="TV9.my",TV9
|
||||
https://tonton-live-switch-ssar.akamaized.net/stream-tv9/master.m3u8?bpkio_serviceid=6c0958d82a830a02ca0936d9cfab8311
|
||||
#EXTINF:-1 tvg-id="8TV.my",8TV
|
||||
https://tonton-live-switch-ssar.akamaized.net/stream-8tv/master.m3u8?bpkio_serviceid=6c0958d82a830a02ca0936d9cfab8311
|
||||
#EXTINF:-1 tvg-id="NTV7.my",NTV7
|
||||
https://tonton-live-switch-ssar.akamaized.net/stream-ntv7/master.m3u8?bpkio_serviceid=6c0958d82a830a02ca0936d9cfab8311
|
||||
#EXTINF:-1 tvg-id="TVIKIM.my",TVIKIM
|
||||
https://edge-sg1.vediostream.com/abr/tvikim/playlist.m3u8
|
||||
#EXTINF:-1 tvg-id="RTMASEAN.my",RTM ASEAN
|
||||
https://d25tgymtnqzu8s.cloudfront.net/event/smil:event1/chunklist_b2596000_slENG.m3u8
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue