2021-10-23 00:18:51 +03:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
#
|
|
|
|
|
# The MIT License (MIT)
|
|
|
|
|
#
|
2022-02-12 14:22:48 +03:00
|
|
|
# Copyright (c) 2018-2022 Dmitriy Yefremov
|
2021-10-23 00:18:51 +03:00
|
|
|
#
|
|
|
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
# in the Software without restriction, including without limitation the rights
|
|
|
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
# furnished to do so, subject to the following conditions:
|
|
|
|
|
#
|
|
|
|
|
# The above copyright notice and this permission notice shall be included in
|
|
|
|
|
# all copies or substantial portions of the Software.
|
|
|
|
|
#
|
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
|
# THE SOFTWARE.
|
|
|
|
|
#
|
|
|
|
|
# Author: Dmitriy Yefremov
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
|
2018-03-12 22:47:43 +03:00
|
|
|
""" Module for IPTV and streams support """
|
2019-04-18 21:43:35 +03:00
|
|
|
import re
|
2018-03-12 22:47:43 +03:00
|
|
|
from enum import Enum
|
2020-06-13 01:01:20 +03:00
|
|
|
from urllib.parse import unquote, quote
|
2018-03-12 22:47:43 +03:00
|
|
|
|
2021-01-31 16:27:35 +03:00
|
|
|
from app.commons import log
|
|
|
|
|
from app.eparser.ecommons import BqServiceType, Service
|
2019-12-22 20:42:29 +03:00
|
|
|
from app.settings import SettingsType
|
2018-04-16 18:50:48 +03:00
|
|
|
from app.ui.uicommons import IPTV_ICON
|
2017-12-08 18:32:28 +03:00
|
|
|
|
2018-02-12 13:34:00 +03:00
|
|
|
# url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group
|
|
|
|
|
NEUTRINO_FAV_ID_FORMAT = "{}::{}::{}::{}::{}::{}::{}::{}::{}::{}"
|
2021-10-23 00:18:51 +03:00
|
|
|
ENIGMA2_FAV_ID_FORMAT = " {}:{}:{}:{:X}:{:X}:{:X}:{:X}:0:0:0:{}:{}\n#DESCRIPTION: {}\n"
|
2018-11-16 23:08:40 +03:00
|
|
|
MARKER_FORMAT = " 1:64:{}:0:0:0:0:0:0:0::{}\n#DESCRIPTION {}\n"
|
2022-02-21 12:22:44 +03:00
|
|
|
PICON_FORMAT = "{}_{}_{:X}_{:X}_{:X}_{:X}_{:X}_0_0_0.png"
|
2018-03-12 22:47:43 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class StreamType(Enum):
|
|
|
|
|
DVB_TS = "1"
|
|
|
|
|
NONE_TS = "4097"
|
2019-04-14 00:03:52 +03:00
|
|
|
NONE_REC_1 = "5001"
|
|
|
|
|
NONE_REC_2 = "5002"
|
2020-06-09 18:38:18 +03:00
|
|
|
E_SERVICE_URI = "8193"
|
2021-01-11 12:30:44 +03:00
|
|
|
E_SERVICE_HLS = "8739"
|
2022-02-12 14:22:48 +03:00
|
|
|
UNKNOWN = "0"
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def _missing_(cls, value):
|
|
|
|
|
return cls.UNKNOWN
|
2017-12-08 18:32:28 +03:00
|
|
|
|
2018-02-12 13:34:00 +03:00
|
|
|
|
2021-01-31 16:27:35 +03:00
|
|
|
def parse_m3u(path, s_type, detect_encoding=True, params=None):
|
2020-12-07 09:30:03 +03:00
|
|
|
with open(path, "rb") as file:
|
|
|
|
|
data = file.read()
|
|
|
|
|
encoding = "utf-8"
|
|
|
|
|
|
|
|
|
|
if detect_encoding:
|
|
|
|
|
try:
|
|
|
|
|
import chardet
|
|
|
|
|
except ModuleNotFoundError:
|
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
enc = chardet.detect(data)
|
|
|
|
|
encoding = enc.get("encoding", "utf-8")
|
|
|
|
|
|
2018-01-31 00:13:42 +03:00
|
|
|
aggr = [None] * 10
|
2021-01-31 16:27:35 +03:00
|
|
|
s_aggr = aggr[: -3]
|
2018-11-16 23:08:40 +03:00
|
|
|
services = []
|
|
|
|
|
groups = set()
|
2021-01-31 16:27:35 +03:00
|
|
|
marker_counter = 1
|
|
|
|
|
sid_counter = 1
|
2017-12-08 18:32:28 +03:00
|
|
|
name = None
|
2021-01-31 16:27:35 +03:00
|
|
|
picon = None
|
|
|
|
|
p_id = "1_0_1_0_0_0_0_0_0_0.png"
|
|
|
|
|
st = BqServiceType.IPTV.name
|
|
|
|
|
params = params or [0, 0, 0, 0]
|
2022-02-12 14:22:48 +03:00
|
|
|
m_name = BqServiceType.MARKER.name
|
2019-12-22 20:42:29 +03:00
|
|
|
|
2020-12-07 09:30:03 +03:00
|
|
|
for line in str(data, encoding=encoding, errors="ignore").splitlines():
|
2017-12-08 18:32:28 +03:00
|
|
|
if line.startswith("#EXTINF"):
|
2021-01-31 16:27:35 +03:00
|
|
|
line, sep, name = line.rpartition(",")
|
|
|
|
|
|
|
|
|
|
data = re.split('"', line)
|
|
|
|
|
size = len(data)
|
|
|
|
|
if size < 3:
|
|
|
|
|
continue
|
|
|
|
|
d = {data[i].lower().strip(" ="): data[i + 1] for i in range(0, len(data) - 1, 2)}
|
|
|
|
|
picon = d.get("tvg-logo", None)
|
|
|
|
|
|
2022-02-12 14:22:48 +03:00
|
|
|
if s_type is SettingsType.ENIGMA_2:
|
|
|
|
|
grp_name = d.get("group-title", None)
|
|
|
|
|
if grp_name not in groups:
|
|
|
|
|
groups.add(grp_name)
|
|
|
|
|
fav_id = MARKER_FORMAT.format(marker_counter, grp_name, grp_name)
|
|
|
|
|
marker_counter += 1
|
|
|
|
|
mr = Service(None, None, None, grp_name, *aggr[0:3], m_name, *aggr, fav_id, None)
|
|
|
|
|
services.append(mr)
|
2019-12-22 20:42:29 +03:00
|
|
|
elif line.startswith("#EXTGRP") and s_type is SettingsType.ENIGMA_2:
|
2018-11-16 23:08:40 +03:00
|
|
|
grp_name = line.strip("#EXTGRP:").strip()
|
|
|
|
|
if grp_name not in groups:
|
|
|
|
|
groups.add(grp_name)
|
2021-01-31 16:27:35 +03:00
|
|
|
fav_id = MARKER_FORMAT.format(marker_counter, grp_name, grp_name)
|
|
|
|
|
marker_counter += 1
|
2022-02-12 14:22:48 +03:00
|
|
|
mr = Service(None, None, None, grp_name, *aggr[0:3], m_name, *aggr, fav_id, None)
|
2018-11-16 23:08:40 +03:00
|
|
|
services.append(mr)
|
2018-11-16 22:35:47 +03:00
|
|
|
elif not line.startswith("#"):
|
2019-01-27 23:59:06 +03:00
|
|
|
url = line.strip()
|
2021-01-31 16:27:35 +03:00
|
|
|
params[0] = sid_counter
|
|
|
|
|
sid_counter += 1
|
|
|
|
|
fav_id = get_fav_id(url, name, s_type, params)
|
2022-02-21 12:22:44 +03:00
|
|
|
if s_type is SettingsType.ENIGMA_2:
|
|
|
|
|
p_id = get_picon_id(params)
|
|
|
|
|
|
2021-01-31 16:27:35 +03:00
|
|
|
if all((name, url, fav_id)):
|
|
|
|
|
srv = Service(None, None, IPTV_ICON, name, *aggr[0:3], st, picon, p_id, *s_aggr, url, fav_id, None)
|
2019-01-27 23:59:06 +03:00
|
|
|
services.append(srv)
|
2021-01-31 16:27:35 +03:00
|
|
|
else:
|
2021-12-28 15:17:39 +03:00
|
|
|
log(f"*.m3u* parse error ['{path}']: name[{name}], url[{url}], fav id[{fav_id}]")
|
2017-12-08 18:32:28 +03:00
|
|
|
|
2018-11-16 23:08:40 +03:00
|
|
|
return services
|
2017-12-08 18:32:28 +03:00
|
|
|
|
|
|
|
|
|
2021-12-28 15:17:39 +03:00
|
|
|
def export_to_m3u(path, bouquet, s_type, url=None):
|
2019-12-22 20:42:29 +03:00
|
|
|
pattern = re.compile(".*:(http.*):.*") if s_type is SettingsType.ENIGMA_2 else re.compile("(http.*?)::::.*")
|
2019-04-18 21:43:35 +03:00
|
|
|
lines = ["#EXTM3U\n"]
|
2019-05-01 17:21:51 +03:00
|
|
|
current_grp = None
|
2019-04-18 21:43:35 +03:00
|
|
|
|
|
|
|
|
for s in bouquet.services:
|
2019-05-01 17:21:51 +03:00
|
|
|
s_type = s.type
|
|
|
|
|
if s_type is BqServiceType.IPTV:
|
2019-04-18 21:43:35 +03:00
|
|
|
res = re.match(pattern, s.data)
|
|
|
|
|
if not res:
|
|
|
|
|
continue
|
2021-12-28 15:17:39 +03:00
|
|
|
lines.append(f"#EXTINF:-1,{s.name}\n")
|
|
|
|
|
lines.append(current_grp) if current_grp else None
|
|
|
|
|
lines.append(f"{unquote(res.group(1).strip())}\n")
|
2019-05-01 17:21:51 +03:00
|
|
|
elif s_type is BqServiceType.MARKER:
|
2021-12-28 15:17:39 +03:00
|
|
|
current_grp = f"#EXTGRP:{s.name}\n"
|
|
|
|
|
elif s_type is BqServiceType.DEFAULT and url:
|
|
|
|
|
lines.append(f"#EXTINF:-1,{s.name}\n")
|
|
|
|
|
lines.append(current_grp) if current_grp else None
|
|
|
|
|
lines.append(f"{url}{s.data}\n")
|
2019-04-18 21:43:35 +03:00
|
|
|
|
2021-12-28 15:17:39 +03:00
|
|
|
with open(f"{path}{bouquet.name}.m3u", "w", encoding="utf-8") as file:
|
2019-04-18 21:43:35 +03:00
|
|
|
file.writelines(lines)
|
|
|
|
|
|
|
|
|
|
|
2021-10-23 00:18:51 +03:00
|
|
|
def get_fav_id(url, name, settings_type, params=None, st_type=None, s_id=0, srv_type=1):
|
2019-06-23 23:25:03 +03:00
|
|
|
""" Returns fav id depending on the profile. """
|
2021-01-31 16:27:35 +03:00
|
|
|
if settings_type is SettingsType.ENIGMA_2:
|
2021-10-23 00:18:51 +03:00
|
|
|
st_type = st_type or StreamType.NONE_TS.value
|
2021-01-31 16:27:35 +03:00
|
|
|
params = params or (0, 0, 0, 0)
|
2021-10-23 00:18:51 +03:00
|
|
|
return ENIGMA2_FAV_ID_FORMAT.format(st_type, s_id, srv_type, *params, quote(url), name, name, None)
|
2021-01-31 16:27:35 +03:00
|
|
|
elif settings_type is SettingsType.NEUTRINO_MP:
|
2019-06-23 23:25:03 +03:00
|
|
|
return NEUTRINO_FAV_ID_FORMAT.format(url, "", 0, None, None, None, None, "", "", 1)
|
|
|
|
|
|
|
|
|
|
|
2022-02-21 12:22:44 +03:00
|
|
|
def get_picon_id(params=None, st_type=None, s_id=0, srv_type=1):
|
|
|
|
|
st_type = st_type or StreamType.NONE_TS.value
|
|
|
|
|
params = params or (0, 0, 0, 0)
|
|
|
|
|
return PICON_FORMAT.format(st_type, s_id, srv_type, *params)
|
|
|
|
|
|
|
|
|
|
|
2017-12-08 18:32:28 +03:00
|
|
|
if __name__ == "__main__":
|
|
|
|
|
pass
|