diff --git a/app/eparser/iptv.py b/app/eparser/iptv.py index 519a0385..c5d520f3 100644 --- a/app/eparser/iptv.py +++ b/app/eparser/iptv.py @@ -2,7 +2,7 @@ # # The MIT License (MIT) # -# Copyright (c) 2018-2023 Dmitriy Yefremov +# Copyright (c) 2018-2024 Dmitriy Yefremov # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -58,6 +58,9 @@ class StreamType(Enum): def parse_m3u(path, s_type, detect_encoding=True, params=None): + """ Parses *m3u* file and returns tuple with EPG src URLs and services list. """ + pattern = re.compile(r'(\S+)="(.*?)"') + with open(path, "rb") as file: data = file.read() encoding = "utf-8" @@ -73,8 +76,10 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None): aggr = [None] * 10 s_aggr = aggr[: -3] - services = [] + epg_src = None + group = None groups = set() + services = [] marker_counter = 1 sid_counter = 1 name = None @@ -85,47 +90,42 @@ def parse_m3u(path, s_type, detect_encoding=True, params=None): m_name = BqServiceType.MARKER.name for line in str(data, encoding=encoding, errors="ignore").splitlines(): + if line.startswith("#EXTM3U"): + data = dict(pattern.findall(line)) + epg_src = data.get("x-tvg-url", data.get("url-tvg", None)) + epg_src = epg_src.split(",") if epg_src else None if line.startswith("#EXTINF"): 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) + data = dict(pattern.findall(line)) + name = data.get("tvg-name", name) + picon = data.get("tvg-logo", None) 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) + group = data.get("group-title", None) elif line.startswith("#EXTGRP") and s_type is SettingsType.ENIGMA_2: - grp_name = line.strip("#EXTGRP:").strip() - 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) - elif not line.startswith("#"): + group = line.strip("#EXTGRP:").strip() + elif not line.startswith("#") and "://" in line: url = line.strip() params[0] = sid_counter sid_counter += 1 fav_id = get_fav_id(url, name, s_type, params) if s_type is SettingsType.ENIGMA_2: p_id = get_picon_id(params) + if group not in groups: + # Some playlists have "random" of group names. + # We will take only the first one we found on the list! + groups.add(group) + m_id = MARKER_FORMAT.format(marker_counter, group, group) + marker_counter += 1 + services.append(Service(None, None, None, group, *aggr[0:3], m_name, *aggr, m_id, None)) 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) - services.append(srv) + services.append(Service(None, None, IPTV_ICON, name, *aggr[0:2], group, + st, picon, p_id, *s_aggr, url, fav_id, None)) else: log(f"*.m3u* parse error ['{path}']: name[{name}], url[{url}], fav id[{fav_id}]") - return services + return epg_src, services def export_to_m3u(path, bouquet, s_type, url=None): diff --git a/app/ui/iptv.py b/app/ui/iptv.py index 5ce1be7f..05caa4d8 100644 --- a/app/ui/iptv.py +++ b/app/ui/iptv.py @@ -2,7 +2,7 @@ # # The MIT License (MIT) # -# Copyright (c) 2018-2023 Dmitriy Yefremov +# Copyright (c) 2018-2024 Dmitriy Yefremov # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -691,6 +691,7 @@ class M3uImportDialog(IptvListDialog): self._picons = app.picons self._pic_path = app._settings.profile_picons_path self._services = None + self._epg_src = None self._url_count = 0 self._errors_count = 0 self._max_count = 0 @@ -708,6 +709,7 @@ class M3uImportDialog(IptvListDialog): self._spinner.bind_property("active", self._start_values_grid, "sensitive", 4) self._picon_switch = builder.get_object("picon_switch") self._picon_box = builder.get_object("picon_box") + builder.get_object("import_type_box").set_visible(False) self.get_m3u(m3_path, s_type) @@ -715,7 +717,7 @@ class M3uImportDialog(IptvListDialog): def get_m3u(self, path, s_type): try: GLib.idle_add(self._spinner.set_property, "active", True) - self._services = parse_m3u(path, s_type) + self._epg_src, self._services = parse_m3u(path, s_type) for s in self._services: if s.picon: GLib.idle_add(self._picon_box.set_sensitive, True) @@ -765,8 +767,7 @@ class M3uImportDialog(IptvListDialog): self.download_picons(picons) else: - GLib.idle_add(self._ok_button.set_visible, True) - GLib.idle_add(self._info_bar.set_visible, True, priority=GLib.PRIORITY_LOW) + self.on_apply_done() self._app.append_imported_services(services) @@ -850,9 +851,14 @@ class M3uImportDialog(IptvListDialog): model.set_value(r.iter, Column.FAV_PICON, picons.get(s.picon_id, None)) yield True + self.on_apply_done() + yield True + + @run_idle + def on_apply_done(self): self._info_bar.set_visible(True) self._ok_button.set_visible(True) - yield True + self._picon_box.set_sensitive(False) def on_response(self, dialog, response): if response == Gtk.ResponseType.APPLY: diff --git a/app/ui/m3u.glade b/app/ui/m3u.glade index 17b80a90..ffed2da3 100644 --- a/app/ui/m3u.glade +++ b/app/ui/m3u.glade @@ -1,5 +1,5 @@ -