Compare commits

..

41 Commits

Author SHA1 Message Date
DYefremov
3dab8ef7b7 version update -> 3.10.2 2024-06-17 16:16:41 +03:00
DYefremov
dd1a543e5c allowed data extraction from startup page 2024-06-16 18:12:32 +03:00
DYefremov
0966489024 fix reading some bouquets (#202)
* additional format for reading bouquet file name
2024-06-16 18:06:22 +03:00
DYefremov
052187359d version update -> 3.10.1 2024-06-08 17:44:11 +03:00
DYefremov
6ca6867ea9 small adjustment (#201) 2024-06-08 17:38:06 +03:00
DYefremov
d9cdc6458c fix services duplication (#201)
* Fixes service double loading when changing profile on start page.
2024-06-08 15:48:55 +03:00
DYefremov
70b9851324 minor ui correction
* IPTV list config dialog adjustment for macOS
2024-05-17 22:52:46 +03:00
DYefremov
2a3b558d83 adjustment for LyngSat web source
* default values for ONID-TID if not present (#200)
2024-05-09 19:13:52 +03:00
DYefremov
21ea841f34 version update -> 3.10.0 2024-05-07 18:53:52 +03:00
DYefremov
1db0ce3fc5 README update 2024-05-07 18:42:01 +03:00
DYefremov
2804a9bc54 change playback keyboard shortcuts 2024-05-07 18:41:36 +03:00
DYefremov
8976f42974 enabled playback mode for the main services list (#198) 2024-05-07 17:56:00 +03:00
DYefremov
8330104f3c add last config load flag 2024-03-29 22:22:05 +03:00
DYefremov
3ede2e2b07 fix -> mark not present in bouquets (#197) 2024-03-29 20:35:48 +03:00
DYefremov
dd796c0f88 version update -> 3.9.2-b 2024-03-16 17:58:52 +03:00
DYefremov
f3a432c002 add optional keep for backup
* prevent *.xml deletion (#196)
2024-03-11 00:38:25 +03:00
DYefremov
4c1cdc4850 increase pos width for sat dialog 2024-03-06 20:53:14 +03:00
DYefremov
a062d74e0e BUILD_WIN.md update 2024-03-05 22:53:41 +03:00
DYefremov
7bf36c8d6d minor fix to add a timer 2024-03-04 13:38:36 +03:00
DYefremov
6aad0344c8 timer dialog adjustment 2024-03-03 19:47:54 +03:00
DYefremov
bb4665d180 default emblem adjustment 2024-03-03 11:53:57 +03:00
DYefremov
a455c4569d version update -> 3.9.1-b 2024-03-03 10:05:23 +03:00
DYefremov
8d5af301fb dialogs ui adjustments 2024-03-02 13:08:54 +03:00
DYefremov
f342b99769 update *.desktop file for deb package 2024-03-02 12:29:42 +03:00
DYefremov
a0612e6a98 separate data loading (#196) 2024-03-02 11:57:20 +03:00
DYefremov
60b8f7642d lamedb5 reading correction (#194) 2024-03-01 22:19:34 +03:00
DYefremov
8730cbdb7c README update 2024-02-26 22:19:01 +03:00
mapi68
cefb96ea20 Update languages in demon-editor.desktop (#195) 2024-02-26 10:39:50 +03:00
DYefremov
b383c2572a minor fixes for EPG cache 2024-02-25 14:02:36 +03:00
DYefremov
ee2fcf8082 changed some icons for picons tab 2024-02-25 13:35:37 +03:00
DYefremov
44234fa534 set "Save as" element visibility 2024-02-22 22:58:53 +03:00
DYefremov
4bb66b5cc1 version update -> 3.9.0-b 2024-02-22 15:33:59 +03:00
DYefremov
3b1ecbfbbf get webtv name for neutrino 2024-02-22 15:28:44 +03:00
DYefremov
a7f7a59c8a enable ctrl+u shortcut globally 2024-02-21 22:08:51 +03:00
DYefremov
9068028662 it *.mo file update 2024-02-21 10:14:08 +03:00
mapi68
40fbc7809f Italian translation update (#193)
* Update demon-editor.po

* small corrections
2024-02-21 10:10:26 +03:00
DYefremov
6397c2c7f3 translations update -> [be, de, ru] 2024-02-20 17:14:21 +03:00
DYefremov
81e8e30682 add manual port configuration for *.m3u export 2024-02-20 15:24:46 +03:00
DYefremov
9b4c6ab14a fix *.m3u export for neutrino 2024-02-20 14:00:35 +03:00
DYefremov
cad1437c33 small fix 2024-02-18 23:36:41 +03:00
DYefremov
4799a0d464 add additional bouquet name checking (#192) 2024-02-17 19:04:55 +03:00
38 changed files with 443 additions and 152 deletions

View File

@@ -41,10 +41,7 @@ Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
* **Ctrl + Alt + R** - rename for bouquet.
* **Ctrl + S, T** in Satellites edit tool for create satellite or transponder.
* **Ctrl + L** - parental lock.
* **Ctrl + H** - hide/skip.
* **Ctrl + P** - start play IPTV or other stream in the bouquet list.
* **Ctrl + Z** - switch(**zap**) the channel(works when the HTTP API is enabled, Enigma2 only).
* **Ctrl + W** - switch to the channel and watch in the program.
* **Ctrl + H** - hide/skip.
* **Space** - select/deselect.
* **Left/Right** - remove selection.
* **Ctrl + Up, Down, PageUp, PageDown, Home, End**- move selected items in the list.
@@ -56,6 +53,9 @@ Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
* **Ctrl + Shift + F** - show/hide filter bar.
* **Ctrl + T** - show/hide built-in Telnet client.
* **Ctrl + Shift + L** - show/hide logging panel.
* **Shift + P** - start play IPTV or other stream in the bouquet list.
* **Shift + Z** - switch(**zap**) the channel(works when the HTTP API is enabled, Enigma2 only).
* **Shift + W** - switch to the channel and watch in the program.
For **multiple** selection with the mouse, press and hold the **Ctrl** key!
@@ -75,10 +75,11 @@ Users of **LTS** versions of [Ubuntu](https://ubuntu.com/) or distributions base
A ready-made [package](https://aur.archlinux.org/packages/demoneditor-bin) is also available for [Arch Linux](https://archlinux.org/) users in the [AUR](https://aur.archlinux.org/) repository.
* ### macOS
**This program can be run on macOS.**
To run the program on macOS, you need to install [brew](https://brew.sh/).
To run the program on macOS, you need to install [Homebrew](https://brew.sh/).
Then install the required components via terminal:
```brew install python3 gtk+3 pygobject3 adwaita-icon-theme```
```pip3 install requests, pillow```
```brew install python3 gtk+3 pygobject3 adwaita-icon-theme python-requests gtksourceview3```
*Optional:* ```brew install pillow python-chardet```
Launch is similar to Linux.

View File

@@ -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
@@ -59,10 +59,15 @@ PICONS_MAX_NUM = 1000 # Maximum picon number for sending without compression.
class DownloadType(Enum):
ALL = 0
BOUQUETS = 1
SATELLITES = 2
PICONS = 3
WEBTV = 4
EPG = 5
SERVICES = 2
SATELLITES = 3
PICONS = 4
WEBTV = 5
EPG = 6
@classmethod
def _missing_(cls, value):
return cls.ALL
class TestException(Exception):
@@ -373,9 +378,11 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=log, fil
save_path = settings.profile_data_path
os.makedirs(os.path.dirname(save_path), exist_ok=True)
# bouquets
if download_type is DownloadType.ALL or download_type is DownloadType.BOUQUETS:
if download_type in (DownloadType.ALL, DownloadType.BOUQUETS, DownloadType.SERVICES):
ftp.cwd(settings.services_path)
file_list = BQ_FILES_LIST + DATA_FILES_LIST if download_type is DownloadType.ALL else BQ_FILES_LIST
file_list = BQ_FILES_LIST
if download_type is DownloadType.ALL or DownloadType.SERVICES:
file_list += DATA_FILES_LIST
ftp.download_files(save_path, file_list, callback)
# *.xml and webtv
if download_type in (DownloadType.ALL, DownloadType.SATELLITES):
@@ -426,7 +433,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
ht.send((f"{url}message?{params}", "Sending info message... "))
if s_type is SettingsType.ENIGMA_2 and download_type is DownloadType.ALL:
if s_type is SettingsType.ENIGMA_2 and download_type in (DownloadType.ALL, DownloadType.SERVICES):
time.sleep(5)
if not settings.keep_power_mode:
ht.send((f"{url}powerstate?newstate=0", "Toggle Standby "))
@@ -457,8 +464,10 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
ftp.cwd(services_path)
ftp.upload_bouquets(data_path, settings.remove_unused_bouquets, callback)
if download_type is DownloadType.ALL:
ftp.upload_xml(data_path, sat_xml_path, STC_XML_FILE, callback)
if download_type is DownloadType.ALL or download_type is DownloadType.SERVICES:
if download_type is DownloadType.ALL:
ftp.upload_xml(data_path, sat_xml_path, STC_XML_FILE, callback)
if s_type is SettingsType.NEUTRINO_MP:
ftp.upload_xml(data_path, sat_xml_path, WEB_TV_XML_FILE, callback)
@@ -518,7 +527,7 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
if s_type is SettingsType.ENIGMA_2:
if download_type is DownloadType.BOUQUETS:
ht.send((f"{url}servicelistreload?mode=2", "Reloading Userbouquets."))
elif download_type is DownloadType.ALL:
elif download_type is DownloadType.ALL or download_type is DownloadType.SERVICES:
ht.send((f"{url}servicelistreload?mode=0", "Reloading lamedb and Userbouquets."))
if not settings.keep_power_mode:
ht.send((f"{url}powerstate?newstate=4", "Wakeup from Standby."))
@@ -537,6 +546,8 @@ def upload_data(*, settings, download_type=DownloadType.ALL, callback=log, done_
def get_upload_info_message(download_type):
if download_type is DownloadType.BOUQUETS:
return "User bouquets will be updated!"
if download_type is DownloadType.SERVICES:
return "User bouquets and services list will be updated!"
elif download_type is DownloadType.ALL:
return "All user data will be reloaded!"
elif download_type is DownloadType.SATELLITES:

View File

@@ -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
@@ -181,6 +181,8 @@ class ServiceType(Enum):
class BouquetsReader:
""" Class for reading and parsing bouquets. """
_BQ_PAT = re.compile(r".*FROM BOUQUET\s+\"((.*bouquet|alternatives)?\.?([\w-]+)\.?(\w+)?)\"\s+.*$", re.IGNORECASE)
_BQ_PAT2 = re.compile(r"#SERVICE:+\s+(?:[0-9a-f]+:+)+([^:]+[.](?:tv|radio))$", re.IGNORECASE)
_BQ_POST_PAT = re.compile(r".*FROM BOUQUET\s+\"((.*bouquet|alternatives)?\.?(.*)\.?(\w+)?)\"\s+.*$", re.IGNORECASE)
_STREAM_TYPES = {"4097", "5001", "5002", "8193", "8739"}
__slots__ = ["_path"]
@@ -206,11 +208,24 @@ class BouquetsReader:
for line in file.readlines():
if "#SERVICE" in line:
mt = re.match(self._BQ_PAT, line)
s_data = line.split(":")
s_type = ServiceType(s_data[1])
s_type = ServiceType.BOUQUET
mt = re.match(self._BQ_PAT, line) or re.match(self._BQ_PAT2, line)
if not mt:
# Additional file name checking.
mt = re.match(self._BQ_POST_PAT, line)
if mt:
log(f"Warning: The bouquet file name may be formed incorrectly. -> {mt.group(1)}")
if mt:
file_name, prefix, b_name = mt.group(1), mt.group(2), mt.group(3)
if len(mt.groups()) > 1:
file_name, prefix, b_name = mt.group(1), mt.group(2), mt.group(3)
s_data[:2] = "10"
else:
file_name, prefix, b_name = mt.group(1), "", ""
s_type = ServiceType(s_data[2])
if b_name in b_names:
log(f"The list of bouquets contains duplicate [{b_name}] names!")
else:
@@ -224,7 +239,6 @@ class BouquetsReader:
else:
real_b_names[rb_name] = 0
# Locked, hidden.
s_data[:2] = "10"
locked = ":".join(s_data).rstrip()
hidden = s_type is ServiceType.HIDDEN
bouquets[2].append(Bouquet(rb_name, bq_type, services, locked, hidden, file_name))

View File

@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
# Copyright (c) 2018-2022 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
@@ -119,7 +119,10 @@ class LameDbReader:
srv_data[1] = srv_data[1].strip("\"\n")
data_len = len(srv_data)
if data_len == 3:
srv_data[2] = srv_data[2].strip()
s_data = srv_data[2].strip()
if not s_data.startswith("p:"):
s_data = f"p:,{s_data}"
srv_data[2] = s_data
elif data_len == 2:
srv_data.append("p:")
srvs.extend(srv_data)

View File

@@ -134,18 +134,18 @@ def export_to_m3u(path, bouquet, s_type, url=None):
current_grp = None
for s in bouquet.services:
s_type = s.type
if s_type is BqServiceType.IPTV:
srv_type = s.type
if srv_type is BqServiceType.IPTV:
res = re.match(pattern, s.data)
if not res:
continue
lines.append(f"#EXTINF:-1,{s.name}\n")
lines.append(current_grp) if current_grp else None
u = res.group(1)
lines.append(f"{unquote(u[:u.rfind(':')])}\n")
elif s_type is BqServiceType.MARKER:
lines.append(f"{unquote(u[:u.rfind(':')]) if s_type is SettingsType.ENIGMA_2 else u}\n")
elif srv_type is BqServiceType.MARKER:
current_grp = f"#EXTGRP:{s.name}\n"
elif s_type is BqServiceType.DEFAULT and url:
elif srv_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")

View File

@@ -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
@@ -37,6 +37,7 @@ from ..ecommons import Bouquets, Bouquet, BouquetService, BqServiceType, PROVIDE
_FILE = "bouquets.xml"
_U_FILE = "ubouquets.xml"
_W_FILE = "webtv_usr.xml"
_WEB_TV_NAME = "[Web TV]"
_COMMENT = " File was created in DemonEditor. Enjoy watching! "
@@ -100,13 +101,20 @@ def parse_webtv(path, name, bq_type):
return bouquets
dom = XmlHandler.parse(path)
# Display name.
name = None
for e in dom.childNodes:
if e.nodeType == e.ELEMENT_NODE:
name = e.getAttribute("name")
break
services = []
for elem in dom.getElementsByTagName("webtv"):
if elem.hasAttributes():
web_attrs = get_xml_attributes(elem)
services.append(get_webtv_service(web_attrs))
bouquet = Bouquet(name="default", type=bq_type, services=services, locked=None, hidden=None, file=None)
bouquet = Bouquet(name=name or _WEB_TV_NAME, type=bq_type, services=services, locked=None, hidden=None, file=None)
bouquets[2].append(bouquet)
return bouquets
@@ -195,6 +203,7 @@ def write_webtv(file, bouquet):
doc.appendChild(comment)
for bq in bouquet.bouquets:
root.setAttribute("name", bq.name or _WEB_TV_NAME)
for srv in bq.services:
url, description, urlkey, account, usrname, psw, s_type, iconsrc, iconsrc_b, group = srv.fav_id.split("::")
srv_elem = doc.createElement("webtv")

View File

@@ -443,9 +443,9 @@ class ServicesParser(HTMLParser):
self._POS_PAT = re.compile(r".*?(\d+\.\d°[EW]).*")
# LyngSat.
self._TR_PAT = re.compile((r".*?(\d+)\.?\d?\s+([RLHV]).*(DVB-S[2]?)/?(.*PSK)?\s"
self._TR_PAT = re.compile((r".*?(\d{4,5})\.?\d?\s+([RLHV]).*(DVB-S[2]?)/?(.*PSK)?\s"
r"?(T2-MI)?\s?(PLS\s+Multistream)?\s?"
r"SR-FEC:\s(\d+)-(\d/\d)\s+.*ONID-TID:\s+(\d+)-(\d+).*"))
r"SR-FEC:\s(\d+)-(\d+/\d+)\s+?(?:.*ONID-TID:\s+(\d+)-(\d+))?"))
self._MULTI_PAT = re.compile(r"PLS\s+(Root|Gold|Combo)+\s(\d+)?\s+(?:Stream\s(\d+))")
# KingOfSat.
@@ -635,12 +635,18 @@ class ServicesParser(HTMLParser):
if pos_found:
text = " ".join(c.text for c in r[1:])
td = re.match(self._TR_PAT, text)
if td:
freq, pol = int(td.group(1)), get_key_by_value(POLARIZATION, td.group(2))
sys, mod, sr, _fec, = td.group(3), td.group(4), td.group(7), td.group(8)
nid, tid = td.group(9), td.group(10)
sys, mod, fec, nsp, s2_flags, roll_off, pilot, inv = self.get_transponder_data(pos, _fec, sys, mod)
nid, tid = int(nid), int(tid)
# The ONID-TID values may not present!
_nid, _tid = td.group(9), td.group(10)
if _nid and _tid:
nid, tid = int(_nid), int(_tid)
else:
log((f"Values 'ONID-TID' for transponder [{self._t_url}] are not present."
" Default values are used."))
if td.group(5):
log(f"Detected T2-MI transponder! [{freq} {sr} {pol}]")

View File

@@ -41,6 +41,10 @@ from app.ui.dialogs import show_dialog, DialogType, get_builder
from app.ui.main_helper import append_text_to_tview, show_info_bar_message
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, KeyboardKey, MOD_MASK, HeaderBar
KEEP_DATA = {"satellites.xml",
"terrestrial.xml",
"cables.xml"}
class RestoreType(Enum):
BOUQUETS = 0
@@ -232,18 +236,19 @@ class BackupDialog:
self.restore(RestoreType.BOUQUETS)
def backup_data(path, backup_path, move=True):
def backup_data(path, backup_path, move=True, keep=None):
""" Creating data backup from a folder at the specified path
Returns full path to the compressed file.
"""
keep = keep or KEEP_DATA
backup_path = f"{backup_path}{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}{SEP}"
os.makedirs(os.path.dirname(backup_path), exist_ok=True)
os.makedirs(os.path.dirname(path), exist_ok=True)
# Backup files in data dir.
for file in filter(lambda f: os.path.isfile(os.path.join(path, f)), os.listdir(path)):
src, dst = os.path.join(path, file), backup_path + file
shutil.move(src, dst) if move else shutil.copy(src, dst)
shutil.move(src, dst) if move and file not in keep else shutil.copy(src, dst)
# Compressing to zip and delete remaining files.
zip_file = shutil.make_archive(backup_path.rstrip(SEP), "zip", backup_path)
shutil.rmtree(backup_path)

View File

@@ -3,7 +3,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
@@ -40,7 +40,7 @@ Author: Dmitriy Yefremov
<property name="icon_name">system-help</property>
<property name="type_hint">normal</property>
<property name="program_name">DemonEditor</property>
<property name="version">3.9.0 Alpha</property>
<property name="version">3.10.2 Beta</property>
<property name="copyright">2018-2024 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellite list editor.</property>

View File

@@ -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
@@ -47,7 +47,7 @@ class BaseDialog(Gtk.Dialog):
title=translate(title),
modal=True,
resizable=False,
default_width=240,
default_width=255,
skip_taskbar_hint=True,
skip_pager_hint=True,
destroy_with_parent=True,
@@ -61,12 +61,12 @@ class Dialog(Enum):
MESSAGE = """
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<requires lib="gtk+" version="3.16"/>
<requires lib="gtk+" version="3.22"/>
<object class="GtkMessageDialog" id="message_dialog">
<property name="use-header-bar">{use_header}</property>
<property name="can_focus">False</property>
<property name="modal">True</property>
<property name="width_request">250</property>
<property name="width_request">255</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">dialog</property>
<property name="skip_taskbar_hint">True</property>

View File

@@ -211,7 +211,7 @@ class FavEpgCache(EpgCache):
@run_task
def update_xml_data(self):
services = self._app.current_services
names = {services[s].service for s in self._app.current_bouquets.get(self._current_bq, [])}
names = {services[s].service for s in self._app.current_bouquets.get(self._current_bq, []) if s in services}
for name, events in self._reader.get_current_events(names).items():
ev = min(events, key=lambda x: x.start, default=None)
if ev:
@@ -311,7 +311,7 @@ class TabEpgCache(EpgCache):
def update_epg_data(self) -> bool:
services = self._app.current_services
names = {services[s].service for s in chain.from_iterable(self._app.current_bouquets.values())}
names = {services[s].service for s in chain.from_iterable(self._app.current_bouquets.values()) if s in services}
for name, events in self._reader.get_current_events(names).items():
self.events[name] = events
@@ -747,7 +747,8 @@ class EpgTool(Gtk.Box):
api.send(HttpAPI.Request.EPG_MULTI, f'1:7:1:0:0:0:0:0:0:0:{req}', self.update_http_epg_data, timeout=15)
else:
srvs = self._app.current_services
self.update_xmltv_epg_data(srvs[s].service for s in self._app.current_bouquets.get(self._current_bq, []))
bq_names = (srvs[s].service for s in self._app.current_bouquets.get(self._current_bq, []) if s in srvs)
self.update_xmltv_epg_data(bq_names)
# ****************** Timers ***************** #

View File

@@ -3,7 +3,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
@@ -32,7 +32,7 @@ Author: Dmitriy Yefremov
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkImage" id="remove_selection_image">
<property name="visible">True</property>
@@ -270,13 +270,14 @@ Author: Dmitriy Yefremov
</object>
<object class="GtkDialog" id="iptv_list_configuration_dialog">
<property name="use-header-bar">{use_header}</property>
<property name="width-request">400</property>
<property name="width-request">680</property>
<property name="can-focus">False</property>
<property name="title" translatable="yes">IPTV streams list configuration</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="window-position">center</property>
<property name="destroy-with-parent">True</property>
<property name="icon-name">demon-editor</property>
<property name="type-hint">dialog</property>
<property name="skip-taskbar-hint">True</property>
<property name="skip-pager-hint">True</property>
@@ -512,9 +513,9 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkEntry" id="list_namespace_entry">
<property name="width-request">120</property>
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="hexpand">True</property>
<property name="can-focus">True</property>
<property name="width-chars">5</property>
<property name="max-width-chars">5</property>
@@ -879,6 +880,7 @@ Author: Dmitriy Yefremov
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="destroy-with-parent">True</property>
<property name="icon-name">demon-editor</property>
<property name="type-hint">dialog</property>
<property name="skip-taskbar-hint">True</property>
<property name="skip-pager-hint">True</property>

View File

@@ -42,7 +42,7 @@ from gi.repository import GLib, Gio, GdkPixbuf
from app.commons import run_idle, run_task, log
from app.eparser.ecommons import BqServiceType, BouquetService, Service
from app.eparser.iptv import (NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT,
parse_m3u, PICON_FORMAT, export_to_m3u)
parse_m3u, PICON_FORMAT)
from app.settings import SettingsType
from app.tools.yt import YouTubeException, YouTube
from app.ui.dialogs import Action, show_dialog, DialogType, translate, get_builder, BaseDialog
@@ -52,7 +52,8 @@ from app.ui.uicommons import (Gtk, Gdk, UI_RESOURCES_PATH, IPTV_ICON, Column, Ke
_DIGIT_ENTRY_NAME = "digit-entry"
_ENIGMA2_REFERENCE = "{}:{}:{:X}:{:X}:{:X}:{:X}:{:X}:0:0:0"
_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
_UI_PATH = UI_RESOURCES_PATH + "iptv.glade"
_UI_PATH = f"{UI_RESOURCES_PATH}iptv.glade"
_CSS_PATH = f"{UI_RESOURCES_PATH}style.css"
_URL_PREFIXES = {"YT-DLP": "YT-DLP://", "YT-DL": "YT-DL://", "STREAMLINK": "streamlink://", "No": None}
@@ -126,7 +127,7 @@ class IptvDialog:
self._model, self._paths = view.get_selection().get_selected_rows()
# Style.
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._style_provider.load_from_path(_CSS_PATH)
self._digit_elems = (self._srv_id_entry, self._srv_type_entry, self._sid_entry, self._tr_id_entry,
self._net_id_entry, self._namespace_entry)
for el in self._digit_elems:
@@ -249,11 +250,8 @@ class IptvDialog:
return get_stream_type(self._stream_type_combobox)
def on_entry_changed(self, entry):
if _PATTERN.search(entry.get_text()):
entry.set_name(_DIGIT_ENTRY_NAME)
else:
entry.set_name("GtkEntry")
self.update_reference_entry()
entry.set_name(_DIGIT_ENTRY_NAME if _PATTERN.search(entry.get_text()) else "GtkEntry")
self.update_reference_entry()
def on_url_changed(self, entry):
url_str = entry.get_text()
@@ -532,7 +530,7 @@ class IptvListDialog:
self._ok_button.bind_property("visible", self._cancel_button, "visible", 4)
# Style
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
style_provider.load_from_path(_CSS_PATH)
self._default_elems = (self._stream_type_check_button, self._id_default_check_button, self._type_check_button,
self._sid_auto_check_button, self._tid_check_button, self._nid_check_button,
self._namespace_check_button)
@@ -830,7 +828,7 @@ class M3uImportDialog(IptvListDialog):
def_bq_name = gen_bouquet_name(bqs, f"IPTV {date.today()} ", bq_type)
if self._single_bq_button.get_active():
self.append_bouquet(def_bq_name, bq_type, bqs, model, itr, services)
self.append_bouquet(def_bq_name, bq_type, bqs, model, itr, services)
else:
# Sub-bouquets.
if self._sub_bq_button.get_active():
@@ -841,7 +839,7 @@ class M3uImportDialog(IptvListDialog):
gr = self.get_services_groups(filter(lambda s: s.service_type != m_name, services), def_bq_name)
[self.append_bouquet(gen_bouquet_name(bqs, g, bq_type), bq_type, bqs, model, itr, s) for g, s in gr.items()]
def append_bouquet(self, bq_name, bq_type, bqs, model, itr, services):
def append_bouquet(self, bq_name, bq_type, bqs, model, itr, services):
""" Adds new bouquet and returns iter of appended row. """
cur_services = self._app.current_services
bqs[f"{bq_name}:{bq_type}"] = [s.fav_id for s in services]
@@ -981,6 +979,7 @@ class ExportM3uDialog(BaseDialog):
self._app = app
self._bouquets = bouquets
self._url = None
self._default_port = "8001"
builder = get_builder(f"{UI_RESOURCES_PATH}m3u.glade", use_str=True, objects=("export_m3u_box",))
self._main_grid = builder.get_object("export_m3u_grid")
@@ -1007,12 +1006,36 @@ class ExportM3uDialog(BaseDialog):
self._bq_count_label.set_text(str(len(self._bouquets)))
self._services_count_label.set_text(str(len(list(chain.from_iterable(self._bouquets.values())))))
if self._app.is_enigma:
self._port_entry.connect("changed", self.on_port_changed)
self._port_auto_button.connect("toggled", self.on_port_auto_toggled)
# Add style for the port entry.
style_provider = Gtk.CssProvider()
style_provider.load_from_path(_CSS_PATH)
context = self._port_entry.get_style_context()
context.add_provider_for_screen(Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
def on_port_changed(self, entry):
entry.set_name(_DIGIT_ENTRY_NAME if _PATTERN.search(entry.get_text()) else "GtkEntry")
def on_port_auto_toggled(self, button):
if not button.get_active() and not self._port_entry.get_text():
self._port_entry.set_text(self._default_port)
def on_response(self, dialog, response):
if response != Gtk.ResponseType.OK:
self.destroy()
else:
if self._app.is_enigma and self._port_auto_button.get_active():
self.do_export_auto()
if self._app.is_enigma:
if self._port_auto_button.get_active():
self.do_export_auto()
else:
if self._port_entry.get_name() == _DIGIT_ENTRY_NAME:
self._app.show_error_message("Error. Verify the data!")
else:
st = self._app.app_settings
self._url = f"http{'s' if st.http_use_ssl else ''}://{st.host}:{self._port_entry.get_text()}/"
self.do_export()
else:
self.do_export()
return True
@@ -1142,7 +1165,7 @@ class YtListImportDialog:
self._dialog.resize(*window_size)
# Style.
style_provider = Gtk.CssProvider()
style_provider.load_from_path(f"{UI_RESOURCES_PATH}style.css")
style_provider.load_from_path(_CSS_PATH)
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)

View File

@@ -749,6 +749,7 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkModelButton" id="save_as_menu_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="action-name">app.on_data_save_as</property>
@@ -1721,7 +1722,7 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="app_ver_label">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label">3.9.0 Alpha</property>
<property name="label">3.10.2 Beta</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>

View File

@@ -77,6 +77,8 @@ from .xml.edit import SatellitesTool
class Application(Gtk.Application):
""" Main application class. """
VERSION = "3.10.2"
SERVICE_MODEL = "services_list_store"
FAV_MODEL = "fav_list_store"
BQ_MODEL = "bouquets_tree_store"
@@ -623,7 +625,7 @@ class Application(Gtk.Application):
self._main_window.set_wmclass("DemonEditor", "DemonEditor")
self._main_window.present()
self.init_profiles()
self.init_profiles(True)
gen = self.init_http_api()
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
@@ -784,7 +786,7 @@ class Application(Gtk.Application):
self.set_action("on_locked", self.on_locked)
# Open and download/upload data.
self.set_action("open_data", lambda a, v: self.open_data())
self.set_action("upload_all", lambda a, v: self.on_upload_data(DownloadType.ALL))
self.set_action("upload_all", lambda a, v: self.emit("data-send", self._page))
self.set_action("upload_bouquets", lambda a, v: self.on_upload_data(DownloadType.BOUQUETS))
sa = self.set_action("on_data_save", lambda a, v: self.emit("data-save", self._page), False)
self.bind_property("is-data-save-enabled", sa, "enabled")
@@ -875,9 +877,9 @@ class Application(Gtk.Application):
self.set_accels_for_action("app.on_logs_show", ["<shift><primary>l"])
self.set_accels_for_action("win.filter", ["<shift><primary>f"])
def init_profiles(self):
def init_profiles(self, last_config=False):
self.update_profiles()
if self._settings.load_last_config:
if last_config and self._settings.load_last_config:
config = self._settings.get("last_config") or {}
if config.get("last_bouquet", None):
self.connect("data-load-done", self.open_last_bouquet)
@@ -1170,7 +1172,10 @@ class Application(Gtk.Application):
self._stack.set_visible_child_name(page_name)
def on_page_changed(self, app, page):
if not len(self._bouquets_model):
if page is not Page.SERVICES:
return
if all((not self._settings.load_last_config, not self.is_data_loading(), not len(self._bouquets_model))):
self.open_data()
def set_use_alt_layout(self, action, value):
@@ -2143,12 +2148,16 @@ class Application(Gtk.Application):
self.show_error_message("Not allowed in this context!")
def on_download(self, app, page):
if page is Page.SERVICES or page is Page.INFO:
if page is Page.INFO:
self.on_download_data()
elif page is Page.SERVICES:
self.on_download_data(DownloadType.SERVICES)
def on_upload(self, app, page):
if page is Page.SERVICES or page is Page.INFO:
if page is Page.INFO:
self.on_upload_data()
elif page is Page.SERVICES:
self.on_upload_data(DownloadType.SERVICES)
def on_bg_task_add(self, app, task):
if len(self._task_box) <= self.BG_TASK_LIMIT:
@@ -2220,7 +2229,7 @@ class Application(Gtk.Application):
def on_data_extract(self, app, page):
""" Opening the data archive via "File/Extract...". """
if page is Page.SERVICES:
if page is Page.INFO or page is Page.SERVICES:
file_filter = None
if IS_DARWIN:
file_filter = Gtk.FileFilter()
@@ -2285,6 +2294,7 @@ class Application(Gtk.Application):
return tmp_path
def update_data(self, data_path, callback=None):
self._services_load_spinner.start()
self._profile_combo_box.set_sensitive(False)
self._alt_revealer.set_visible(False)
self._filter_services_button.set_active(False)
@@ -2477,7 +2487,6 @@ class Application(Gtk.Application):
self._services[srv.fav_id] = srv
self.update_services_counts(len(self._services.values()))
self._wait_dialog.hide()
self._services_load_spinner.start()
factor = self.DEL_FACTOR / 4
for index, srv in enumerate(to_add):
@@ -2945,21 +2954,33 @@ class Application(Gtk.Application):
key = KeyboardKey(key_code)
ctrl = event.state & MOD_MASK
shift = event.state & Gdk.ModifierType.SHIFT_MASK
model_name, model = get_model_data(view)
if key is KeyboardKey.LEFT or key is KeyboardKey.RIGHT:
view.do_unselect_all(view)
elif ctrl and model_name == self.FAV_MODEL:
if key is KeyboardKey.P:
self.emit("fav-clicked", PlaybackMode.STREAM)
if key is KeyboardKey.W:
self.emit("fav-clicked", PlaybackMode.ZAP_PLAY)
if key is KeyboardKey.Z:
self.emit("fav-clicked", PlaybackMode.ZAP)
elif key is KeyboardKey.CTRL_L or key is KeyboardKey.CTRL_R:
if model_name == self.FAV_MODEL:
if ctrl and key in (KeyboardKey.CTRL_L, KeyboardKey.CTRL_R):
self.update_fav_num_column(model)
self.update_bouquet_list()
if shift:
if key is KeyboardKey.P:
self.emit("fav-clicked", PlaybackMode.STREAM)
if key is KeyboardKey.W:
self.emit("fav-clicked", PlaybackMode.ZAP_PLAY)
if key is KeyboardKey.Z:
self.emit("fav-clicked", PlaybackMode.ZAP)
elif model_name == self.SERVICE_MODEL:
if shift:
if key is KeyboardKey.P:
self.emit("srv-clicked", PlaybackMode.STREAM)
if key is KeyboardKey.W:
self.emit("srv-clicked", PlaybackMode.ZAP_PLAY)
if key is KeyboardKey.Z:
self.emit("srv-clicked", PlaybackMode.ZAP)
def on_view_focus(self, view, focus_event=None):
# Preventing focus lack for some cases.
if not focus_event and not view.is_focus():
@@ -4101,8 +4122,8 @@ class Application(Gtk.Application):
for index, row in enumerate(self._services_model):
fav_id = row[Column.SRV_FAV_ID]
if fav_id not in ids:
row[Column.SRV_BACKGROUND] = self._EXTRA_COLOR
bg = self.get_new_background(row[Column.SRV_CAS_FLAGS]) if fav_id in ids else self._EXTRA_COLOR
row[Column.SRV_BACKGROUND] = bg
if index % self.FAV_FACTOR == 0:
yield True

View File

@@ -3,7 +3,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
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkMenu" id="add_menu">
<property name="visible">True</property>
@@ -428,8 +428,7 @@ Author: Dmitriy Yefremov
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">list-add</property>
<property name="icon_size">0</property>
<property name="icon-name">list-add-symbolic</property>
</object>
</child>
</object>
@@ -451,7 +450,7 @@ Author: Dmitriy Yefremov
<object class="GtkImage" id="receive_button_image">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="stock">gtk-goto-bottom</property>
<property name="icon-name">go-bottom-symbolic</property>
</object>
</child>
</object>
@@ -499,7 +498,7 @@ Author: Dmitriy Yefremov
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="icon-name">window-new</property>
<property name="icon-name">window-new-symbolic</property>
</object>
</child>
</object>

View File

@@ -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
@@ -158,7 +158,14 @@ class PlayerBox(Gtk.Overlay):
return
ref = self._app.get_service_ref_data(srv)
self.zap(ref, self.play_current)
if mode is PlaybackMode.PLAY:
self.play_service(ref)
elif mode is PlaybackMode.ZAP:
self.zap(ref)
elif mode is PlaybackMode.ZAP_PLAY:
self.zap(ref, self.play_current)
elif mode is PlaybackMode.STREAM:
self._app.show_error_message("Not allowed in this context!")
def on_iptv_clicked(self, app, mode):
if not self._app.http_api:
@@ -439,15 +446,17 @@ class PlayerBox(Gtk.Overlay):
self.play(url) if url else self.on_error(None, "No reference is present!")
def on_play_service(self, item=None):
""" Playback without switching channel on the Box [returns current reference]"""
""" Playback without switching channel on the Box."""
ref, path = self.get_ref()
if not ref:
return
self.play_service(ref)
def play_service(self, ref):
s_type = self._app.app_settings.setting_type
req = HttpAPI.Request.STREAM if s_type is SettingsType.ENIGMA_2 else HttpAPI.Request.N_STREAM
self._app.http_api.send(req, ref, self.watch)
return ref
def on_zap(self, callback=None):
""" Switch(zap) the channel. """

View File

@@ -3,7 +3,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
@@ -32,7 +32,7 @@ Author: Dmitriy Yefremov
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellite list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="fec_list_store">
<columns>
@@ -267,6 +267,7 @@ Author: Dmitriy Yefremov
<property name="title" translatable="yes">Service data</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="width-request">800</property>
<property name="window-position">center-on-parent</property>
<property name="destroy-with-parent">True</property>
<property name="icon-name">document-properties-symbolic</property>

View File

@@ -119,6 +119,7 @@ Author: Dmitriy Yefremov
<property name="modal">True</property>
<property name="window-position">center-on-parent</property>
<property name="destroy-with-parent">True</property>
<property name="icon-name">demon-editor</property>
<property name="type-hint">dialog</property>
<property name="skip-taskbar-hint">True</property>
<property name="skip-pager-hint">True</property>

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2022 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
@@ -27,12 +27,12 @@ Author: Dmitriy Yefremov
-->
<interface domain="demon-editor">
<requires lib="gtk+" version="3.20"/>
<requires lib="gtk+" version="3.22"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor. -->
<!-- interface-copyright 2018-2022 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkAdjustment" id="begins_hour_adjustment">
<property name="upper">23</property>
@@ -307,8 +307,11 @@ Author: Dmitriy Yefremov
<object class="GtkFrame" id="timer_dialog_frame">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="width-request">325</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="label-xalign">0</property>
<property name="shadow-type">in</property>
<child>

View File

@@ -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
@@ -31,9 +31,8 @@ from datetime import datetime, timedelta
from enum import Enum
from urllib.parse import quote
from app.settings import USE_HEADER_BAR
from app.ui.main_helper import on_popup_menu
from .dialogs import get_builder, translate, show_dialog, DialogType
from .dialogs import get_builder, translate, show_dialog, DialogType, BaseDialog
from .uicommons import Gtk, Gdk, GLib, UI_RESOURCES_PATH, Page, Column, KeyboardKey, MOD_MASK
from ..commons import run_idle, log
from ..connections import HttpAPI
@@ -55,9 +54,11 @@ class TimerTool(Gtk.Box):
EVENT = 1
CHANGE = 2
class TimerDialog(Gtk.Dialog):
class TimerDialog(BaseDialog):
def __init__(self, parent, action=None, timer_data=None, *args, **kwargs):
super().__init__(use_header_bar=USE_HEADER_BAR, *args, **kwargs)
super().__init__(parent=parent, title="Timer",
buttons=(translate("Cancel"), Gtk.ResponseType.CANCEL,
translate("Save"), Gtk.ResponseType.OK), *args, **kwargs)
self._action = action or TimerTool.TimerAction.ADD
self._timer_data = timer_data or {}
@@ -71,14 +72,6 @@ class TimerTool(Gtk.Box):
"min_end_adjustment", "timer_begins_popover", "begins_hour_adjustment",
"min_begins_adjustment"))
self.set_title(translate("Timer"))
self.set_modal(True)
self.set_skip_pager_hint(True)
self.set_skip_taskbar_hint(True)
self.set_transient_for(parent)
self.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
self.set_resizable(False)
self._timer_name_entry = builder.get_object("timer_name_entry")
self._timer_desc_entry = builder.get_object("timer_desc_entry")
self._timer_service_entry = builder.get_object("timer_service_entry")
@@ -111,8 +104,7 @@ class TimerTool(Gtk.Box):
self._timer_desc_entry.drag_dest_unset()
self._timer_service_entry.drag_dest_unset()
self.add_buttons(translate("Cancel"), Gtk.ResponseType.CANCEL, translate("Save"), Gtk.ResponseType.OK)
self.get_content_area().pack_start(builder.get_object("timer_dialog_frame"), True, True, 5)
self.get_content_area().pack_start(builder.get_object("timer_dialog_frame"), True, True, 0)
if self._action is TimerTool.TimerAction.ADD:
self.set_timer_for_add()
@@ -363,9 +355,11 @@ class TimerTool(Gtk.Box):
if p_count == 1:
service = self._app.current_services.get(model[paths][Column.FAV_ID], None)
if service:
if service and service.picon_id:
self.add_timer({"e2servicename": service.service,
"e2servicereference": service.picon_id.rstrip(".png").replace("_", ":")})
else:
self._app.show_error_message("Not allowed in this context!")
elif p_count > 1:
self._app.show_error_message("Please, select only one item!")
else:

View File

@@ -120,7 +120,7 @@ HIDE_ICON = get_icon("go-jump", 16, _IMAGE_MISSING)
TV_ICON = get_icon("tv-symbolic", 16, _IMAGE_MISSING)
IPTV_ICON = get_icon("emblem-shared", 16, _IMAGE_MISSING)
EPG_ICON = get_icon("gtk-index", 16, _IMAGE_MISSING)
DEFAULT_ICON = get_icon("emblem-default", 16, _IMAGE_MISSING)
DEFAULT_ICON = get_icon("emblem-default", 16, get_icon("emblem-default-symbolic", 16, _IMAGE_MISSING))
@lru_cache(maxsize=1)

View File

@@ -3,7 +3,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
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2023 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2024 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<!-- n-columns=2 n-rows=4 -->
<object class="GtkGrid" id="cable_tr_box">
@@ -471,7 +471,7 @@ Author: Dmitriy Yefremov
<object class="GtkSpinButton" id="sat_position_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="width-chars">5</property>
<property name="width-chars">6</property>
<property name="secondary-icon-activatable">False</property>
<property name="secondary-icon-sensitive">False</property>
<property name="input-purpose">number</property>

View File

@@ -8,8 +8,14 @@ The best way to run this program from source is using of [MSYS2](https://www.msy
`pacman -S mingw-w64-x86_64-gtk3 mingw-w64-x86_64-python3 mingw-w64-x86_64-python3-gobject mingw-w64-x86_64-python3-pip mingw-w64-x86_64-python3-requests`
Optional: `pacman -S mingw-w64-x86_64-python3-pillow`
To support streams playback, install the following packages (the list may not be complete):
For [MPV](https://mpv.io/) `pacman -S mingw-w64-x86_64-mpv`,
For [GStreamer](https://gstreamer.freedesktop.org/) `pacman -S mingw-w64-x86_64-gst-libav mingw-w64-x86_64-gst-plugins-bad mingw-w64-x86_64-gst-plugins-base mingw-w64-x86_64-gst-plugins-good mingw-w64-x86_64-gstreamer`
* For [GStreamer](https://gstreamer.freedesktop.org/) `pacman -S mingw-w64-x86_64-gst-libav mingw-w64-x86_64-gst-plugins-bad mingw-w64-x86_64-gst-plugins-base mingw-w64-x86_64-gst-plugins-good mingw-w64-x86_64-gstreamer`
* For [MPV](https://mpv.io/) `pacman -S mingw-w64-x86_64-mpv`,
To reduce installation size or try the latest changes, we can install the *libmpv* [build](https://github.com/shinchiro/mpv-winbuild-cmake/releases) (**mpv-dev**-x86_64-v3-*.7z) by [shinchiro](https://github.com/shinchiro).
* Download and extract 7z archive.
* Copy libmpv-2.dll to *C:\msys64\mingw64\bin*
* libmpv.dll.a to *C:\msys64\mingw64\lib*
and folder *include\mpv to *C:\msys64\mingw64\include* path.
5. Download and unzip the archive with sources from preferred branch (e.g. [master](https://github.com/DYefremov/DemonEditor/archive/refs/heads/master.zip)) in to folder where MSYS2 is installed. E.g: `c:\msys64\home\username\`
6. Run mingw64 shell. Go to the folder where the program was unpacked. E.g: `cd DemonEditor/`
And run: `./start.py`

View File

@@ -1,5 +1,5 @@
#!/bin/bash
VER="3.9.0_Alpha"
VER="3.10.2_Beta"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"

View File

@@ -1,5 +1,5 @@
Package: demon-editor
Version: 3.9.0-Alpha
Version: 3.10.2-Beta
Section: utils
Priority: optional
Architecture: all

View File

@@ -5,7 +5,7 @@ Source: https://github.com/DYefremov/DemonEditor
Files: *
MIT License
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

View File

@@ -2,14 +2,27 @@
Version=1.0
Name=DemonEditor
GenericName=Enigma2 bouquets editor
GenericName[it]=Editor di bouquet per Enigma2
GenericName[be]=Рэдактар букетаў Enigma2
GenericName[de]=Enigma2 Bouquet-Editor
GenericName[es]=Editor de ramos de Enigma2
GenericName[it]=Editor di bouquet Enigma2
GenericName[nl]=Enigma2 boeket editor
GenericName[pl]=Edytor bukietów Enigma2
GenericName[pt]=Editor de buquês Enigma2
GenericName[ru]=Редактор букетов Enigma2
GenericName[tr]=Enigma2 buket düzenleyici
GenericName[zh_CN]=Enigma2频道编辑器
Comment=Channel and satellite list editor for Enigma2
Comment[be]=Рэдактар спісу каналаў і супутнікаў для Enigma2
Comment[de]=Kanal- und Satellitenlisten-Editor für Enigma2
Comment[es]=Editor de lista de canales y satélites para Enigma2
Comment[it]=Editor di elenchi di canali e satelliti per Enigma2
Comment[nl]=Kanaal- en satellietlijsteditor voor Enigma2
Comment[pl]=Edytor list kanałów i satelitów dla Enigma2
Comment[pt]=Editor de lista de canais e satélites para Enigma2
Comment[ru]=Редактор списка каналов и спутников для Enigma2
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
Comment[it]=Editor di liste canali e satelliti per Enigma2
Comment[tr]=Enigma2 için kanal ve uydu listesi editörü
Comment[es]=Editor de listas de canales y satélites para Enigma2
Comment[tr]=Enigma2 için Kanal ve uydu listesi düzenleyici
Comment[zh_CN]=Enigma2频道和卫星列表编辑器
Icon=demon-editor
Exec=/usr/bin/demon-editor
Terminal=false

View File

@@ -81,8 +81,8 @@ app = BUNDLE(coll,
'CFBundleGetInfoString': "Enigma2 channel and satellite editor",
'LSApplicationCategoryType': 'public.app-category.utilities',
'LSMinimumSystemVersion': '10.13',
'CFBundleShortVersionString': f"3.9.0.{BUILD_DATE} Alpha",
'NSHumanReadableCopyright': u"Copyright © 2023, Dmitriy Yefremov",
'CFBundleShortVersionString': f"3.10.2.{BUILD_DATE} Beta",
'NSHumanReadableCopyright': u"Copyright © 2024, Dmitriy Yefremov",
'NSRequiresAquaSystemAppearance': 'false',
'NSHighResolutionCapable': 'true'
})

View File

@@ -2,14 +2,27 @@
Version=1.0
Name=DemonEditor
GenericName=Enigma2 bouquets editor
GenericName[it]=Editor di bouquet per Enigma2
GenericName[be]=Рэдактар букетаў Enigma2
GenericName[de]=Enigma2 Bouquet-Editor
GenericName[es]=Editor de ramos de Enigma2
GenericName[it]=Editor di bouquet Enigma2
GenericName[nl]=Enigma2 boeket editor
GenericName[pl]=Edytor bukietów Enigma2
GenericName[pt]=Editor de buquês Enigma2
GenericName[ru]=Редактор букетов Enigma2
GenericName[tr]=Enigma2 buket düzenleyici
GenericName[zh_CN]=Enigma2频道编辑器
Comment=Channel and satellite list editor for Enigma2
Comment[be]=Рэдактар спісу каналаў і супутнікаў для Enigma2
Comment[de]=Kanal- und Satellitenlisten-Editor für Enigma2
Comment[es]=Editor de lista de canales y satélites para Enigma2
Comment[it]=Editor di elenchi di canali e satelliti per Enigma2
Comment[nl]=Kanaal- en satellietlijsteditor voor Enigma2
Comment[pl]=Edytor list kanałów i satelitów dla Enigma2
Comment[pt]=Editor de lista de canais e satélites para Enigma2
Comment[ru]=Редактор списка каналов и спутников для Enigma2
Comment[be]=Рэдактар спіса каналаў і спадарожнікаў для Enigma2
Comment[de]=Programm- und Satellitenlisten-Editor für Enigma2
Comment[it]=Editor di liste canali e satelliti per Enigma2
Comment[tr]=Enigma2 için kanal ve uydu listesi editörü
Comment[es]=Editor de listas de canales y satélites para Enigma2
Comment[tr]=Enigma2 için Kanal ve uydu listesi düzenleyici
Comment[zh_CN]=Enigma2频道和卫星列表编辑器
Icon=demon-editor
Exec=bash -c 'cd $(dirname %k) && ./start.py'
Terminal=false

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2023 Dmitriy Yefremov
# Copyright (C) 2018-2024 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -1503,3 +1503,42 @@ msgstr "Скарыстаць агульную тэчку для піконаў"
msgid "Activates single folder use for several profiles."
msgstr "Актывуе выкарыстанне адзінай тэчкі для некалькіх профіляў."
msgid "Events"
msgstr "Падзеі"
msgid "Markers"
msgstr "Маркеры"
msgid "IPTV only"
msgstr "Толькі IPTV"
msgid "No"
msgstr "Не"
msgid "Not found."
msgstr "Не знойдзена."
msgid "Current EPG cache contents."
msgstr "Змесціва бягучага EPG кэша."
msgid "Source error!"
msgstr "Памылка крыніцы!"
msgid "The EPG source for the favorites list is not set!"
msgstr "Не ўсталявана крыніца EPG для спіса абранага!"
msgid "Add to EPG sources list"
msgstr "Дадаць у спіс крыніц EPG"
msgid "Current bouquet"
msgstr "Абраны букет"
msgid "Single bouquet"
msgstr "Адзінкавы букет"
msgid "Split by groups"
msgstr "Падзел па групах"
msgid "Create sub-bouquets"
msgstr "Стварыць падбукеты"

View File

@@ -1,8 +1,8 @@
# Copyright (C) 2018-2021 Dmitriy Yefremov
# Copyright (C) 2018-2024 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
# Charly, 2019.
# Dmitriy Yefremov, 2020-2023.
# Dmitriy Yefremov, 2020-2024.
# Thomas Schmidt, 2021.
msgid ""
msgstr ""
@@ -1517,3 +1517,42 @@ msgstr "Gemeinsamen Ordner für Picons verwenden"
msgid "Activates single folder use for several profiles."
msgstr "Aktiviert die Verwendung eines einzelnen Ordners für mehrere Profile."
msgid "Events"
msgstr "Events"
msgid "Markers"
msgstr "Markers"
msgid "IPTV only"
msgstr "Nur IPTV"
msgid "No"
msgstr "Nein"
msgid "Not found."
msgstr "Nicht gefunden."
msgid "Current EPG cache contents."
msgstr "Aktueller EPG-Cache-Inhalt."
msgid "Source error!"
msgstr "Quellfehler!"
msgid "The EPG source for the favorites list is not set!"
msgstr "Die EPG-Quelle für die Favoritenliste ist nicht eingestellt!"
msgid "Add to EPG sources list"
msgstr "Zur EPG-Quellenliste hinzufügen"
msgid "Current bouquet"
msgstr "Aktuelles Bouquet"
msgid "Single bouquet"
msgstr "Einzel Bouquet"
msgid "Split by groups"
msgstr "Aufteilung nach Gruppen"
msgid "Create sub-bouquets"
msgstr "Sub-Bouquets erstellen"

View File

@@ -1,11 +1,11 @@
# Copyright (C) 2018-2022 Dmitriy Yefremov
# Copyright (C) 2018-2024 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
# SPDX-FileCopyrightText: 2022, 2023 Massimo Pissarello <mapi68@gmail.com>
# SPDX-FileCopyrightText: 2022, 2023, 2024 Massimo Pissarello <mapi68@gmail.com>
msgid ""
msgstr ""
"Project-Id-Version: \n"
"PO-Revision-Date: 2023-12-18 05:40+0100\n"
"PO-Revision-Date: 2024-02-20 21:33+0100\n"
"Last-Translator: Massimo Pissarello <mapi68@gmail.com>\n"
"Language-Team: Italian <>\n"
"Language: it\n"
@@ -824,7 +824,7 @@ msgstr "Abilita barra di riproduzione diretta"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr ""
"Consenti l'invio diretto e la riproduzione di collegamenti multimediali sul"
"Abilita l'invio e la riproduzione diretta di collegamenti multimediali sul"
" ricevitore"
msgid "Watch the channel in the program"
@@ -1412,8 +1412,8 @@ msgstr "Riproduci dall'elenco principale"
msgid "Enables URL parsing using yt-dlp to get direct links to media."
msgstr ""
"Abilita l'analisi degli URL usando yt-dlp per ottenere collegamenti"
" diretti ai media."
"Abilita l'analisi degli URL utilizzando yt-dlp per ottenere collegamenti"
" diretti ai contenuti multimediali."
msgid "Permissions..."
msgstr "Permessi..."
@@ -1509,10 +1509,11 @@ msgid "Removed"
msgstr "Rimosso"
msgid "Enables overwriting existing main list services."
msgstr "Consente di sovrascrivere i servizi esistenti dell'elenco principale."
msgstr ""
"Abilita la sovrascrittura dei servizi esistenti dell'elenco principale."
msgid "Enables skipping services import from lamedb."
msgstr "Consente di saltare l'importazione dei servizi da lamedb."
msgstr "Abilita il salto dell'importazione dei servizi da lamedb."
msgid "Bouquets data only"
msgstr "Bouquet solo dati"
@@ -1565,4 +1566,41 @@ msgstr "Usa la cartella comune per i picon"
msgid "Activates single folder use for several profiles."
msgstr "Attiva l'uso di una singola cartella per diversi profili."
msgid "Events"
msgstr "Eventi"
msgid "Markers"
msgstr "Marcatori"
msgid "IPTV only"
msgstr "Solo IPTV"
msgid "No"
msgstr "No"
msgid "Not found."
msgstr "Non trovato."
msgid "Current EPG cache contents."
msgstr "Contenuto attuale cache EPG."
msgid "Source error!"
msgstr "Errore fonte!"
msgid "The EPG source for the favorites list is not set!"
msgstr "La fonte EPG per l'elenco dei preferiti non è impostata!"
msgid "Add to EPG sources list"
msgstr "Aggiungi all'elenco delle fonti EPG"
msgid "Current bouquet"
msgstr "Bouquet attuale"
msgid "Single bouquet"
msgstr "Bouquet singolo"
msgid "Split by groups"
msgstr "Dividi per gruppi"
msgid "Create sub-bouquets"
msgstr "Crea sotto-bouquet"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2018-2023 Dmitriy Yefremov
# Copyright (C) 2018-2024 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
@@ -1311,7 +1311,7 @@ msgid "Sets the profile folder as default to store picons, backups, etc."
msgstr "Устанавливает папку профиля по умолчанию для хранения пиконов, резервных копий и т. п."
msgid "New sub-bouquet"
msgstr "Создать суббукет"
msgstr "Создать подбукет"
msgid "Mark not presented in Bouquets"
msgstr "Отметить отсутствующие в букетах"
@@ -1500,3 +1500,42 @@ msgstr "Использовать общую папку для пиконов"
msgid "Activates single folder use for several profiles."
msgstr "Активирует использование одной папки для нескольких профилей."
msgid "Events"
msgstr "События"
msgid "Markers"
msgstr "Маркеры"
msgid "IPTV only"
msgstr "Только IPTV"
msgid "No"
msgstr "Нет"
msgid "Not found."
msgstr "Не найдено."
msgid "Current EPG cache contents."
msgstr "Содержимое текущего EPG кэша."
msgid "Source error!"
msgstr "Ошибка источника!"
msgid "The EPG source for the favorites list is not set!"
msgstr "Не установлен источник EPG для списка избранного!"
msgid "Add to EPG sources list"
msgstr "Добавить в список источников EPG"
msgid "Current bouquet"
msgstr "Текущий букет"
msgid "Single bouquet"
msgstr "Одиночный букет"
msgid "Split by groups"
msgstr "Разбить по группам"
msgid "Create sub-bouquets"
msgstr "Создать подбукеты"