Compare commits

...

91 Commits

Author SHA1 Message Date
DYefremov
0e50f1952d drag on icon fix 2020-05-23 00:03:53 +03:00
DYefremov
8686e15446 German translation update 2020-05-19 14:28:36 +03:00
DYefremov
5fee00a150 Russian translation update 2020-05-19 14:27:44 +03:00
DYefremov
0ec9873940 minor log changes 2020-05-19 11:39:12 +03:00
DYefremov
0600737319 Dutch translation update 2020-05-18 16:28:10 +03:00
DYefremov
cf51a5bfd8 small dnd fix 2020-05-18 12:34:28 +03:00
DYefremov
533d8fae25 minor fixes 2020-05-17 13:08:02 +03:00
DYefremov
16167b1b13 scaling picons on loading 2020-05-12 20:44:37 +03:00
DYefremov
38f6c06292 copy tr *.mo file 2020-05-11 21:58:58 +03:00
audi06
4170864a74 Turkish translation update (#22) 2020-05-11 21:48:43 +03:00
DYefremov
3171540bf2 minor optimization and fix 2020-05-10 21:17:24 +03:00
DYefremov
0ed4f6d7f9 German translation update 2020-05-10 18:31:27 +03:00
DYefremov
637becaa59 small fix to prevent (#12) 2020-05-10 18:30:06 +03:00
DYefremov
fd5a0d23a8 added picons filter by service name 2020-05-10 13:22:13 +03:00
DYefremov
22626ea03d Russian translation update 2020-05-09 14:02:19 +03:00
DYefremov
2e34568f74 fix to prevent #12 2020-05-08 17:13:17 +03:00
DYefremov
f42b2f3c75 added skip upload if file not found 2020-05-05 00:02:52 +03:00
DYefremov
f88b36cee7 fix use colors 2020-05-04 22:32:02 +03:00
DYefremov
4421f767e1 fix to prevent (#21) 2020-05-04 19:17:16 +03:00
DYefremov
898b32ca5f minor fixes for yt 2020-05-03 02:04:51 +03:00
DYefremov
7c2840c570 changed data dir creation 2020-05-03 00:22:16 +03:00
DYefremov
1c9c58d48a added accelerators and tooltips 2020-04-28 22:08:30 +03:00
DYefremov
45a1c79808 added download/upload of [terrestrial, cable].xml 2020-04-28 14:49:10 +03:00
DYefremov
19fbc753c5 slight optimization of loading/deleting data 2020-04-28 11:23:22 +03:00
DYefremov
c8c424750b setting text for wait dialog 2020-04-27 17:00:52 +03:00
DYefremov
3d769a5e18 path resolve fix 2020-04-27 12:51:10 +03:00
DYefremov
1b7d2f15b0 reworking of picons dialog 2020-04-26 19:26:36 +03:00
DYefremov
58cf299097 minor fixes for filter and search 2020-04-23 10:32:18 +03:00
DYefremov
af8fe227e0 added group style 2020-04-22 09:53:06 +03:00
DYefremov
3160ec2455 small refactoring of chooser dialog 2020-04-21 14:43:57 +03:00
DYefremov
04fd8b7182 upd README 2020-04-20 20:40:07 +03:00
DYefremov
ee90d11557 added picons assignment by drag on icon 2020-04-19 23:34:42 +03:00
DYefremov
b312816804 added hints support for the main list 2020-04-19 17:20:51 +03:00
DYefremov
39647cb811 minor refactoring 2020-04-19 13:23:18 +03:00
audi06
e64cd66977 added Turkish selection (#20) 2020-04-16 21:02:57 +03:00
DYefremov
754c586fb1 copy tr *.mo file 2020-04-16 18:49:35 +03:00
audi06
d5a2b22819 added Turkish translation (#19)
(cherry picked from commit 8d96f02e2e)
2020-04-16 18:20:03 +03:00
DYefremov
b20bcce5fa added bouquet file naming option 2020-04-16 11:55:48 +03:00
DYefremov
aba82c7120 added appearance settings 2020-04-13 13:46:02 +03:00
DYefremov
e0beeef2a3 top separator revert 2020-04-10 15:09:58 +03:00
DYefremov
c8a9d3f4a0 added basic hints support 2020-04-06 16:50:11 +03:00
DYefremov
9f7c713712 added option for hints 2020-04-05 18:19:12 +03:00
DYefremov
f89196041b epg options fix 2020-04-02 16:50:58 +03:00
DYefremov
efa2d94239 changed some player args 2020-03-29 12:57:33 +03:00
DYefremov
f53f483dce basic implementation of the play mode 2020-03-28 17:56:39 +03:00
DYefremov
ebcf0a90b5 small cleaning 2020-03-28 17:38:40 +03:00
DYefremov
2311b046e7 copy *.mo file 2020-03-24 15:36:08 +03:00
wwns
9544b6028f Polish translation update (#18) 2020-03-24 15:26:30 +03:00
DYefremov
e04144b10f wrap m3u data 2020-03-22 23:26:01 +03:00
DYefremov
93f68a7fe2 added play streams mode options 2020-03-22 14:13:01 +03:00
DYefremov
947ea21ed1 copy .mo file 2020-03-21 16:45:19 +03:00
Víctor Pont
d20d40c19b Spanish translation update and corrections (#17) 2020-03-21 16:31:07 +03:00
DYefremov
bb349a3fe9 changed record button update 2020-03-13 14:36:16 +03:00
DYefremov
bb3ecf975b added transcoding options 2020-03-09 14:53:03 +03:00
DYefremov
bbc693e5e6 added record of current service 2020-03-07 18:33:51 +03:00
DYefremov
cb29cf0155 added new paths settings 2020-03-06 11:55:34 +03:00
DYefremov
0c8592fb0d fix service status info 2020-02-29 21:15:24 +03:00
DYefremov
66f067340d version update 2020-02-28 21:10:13 +03:00
DYefremov
b0ec8e5483 added picons multiple assignment 2020-02-28 20:59:53 +03:00
DYefremov
aa4b31edfc copy .mo file 2020-02-27 23:53:28 +03:00
DYefremov
3d627b57a4 German translation update 2020-02-27 23:50:54 +03:00
DYefremov
6b1bec500c upd README 2020-02-24 12:48:17 +03:00
DYefremov
7444db7e21 small fix 2020-02-24 12:17:10 +03:00
DYefremov
8be92a9c7e copy .mo file 2020-02-20 14:20:09 +03:00
DYefremov
7554f40c6a Russian translation update 2020-02-20 14:07:35 +03:00
DYefremov
17ab321e44 gui changes for send to 2020-02-20 11:38:45 +03:00
DYefremov
ac345d4ef3 fix getting sats 2020-02-19 12:04:02 +03:00
DYefremov
e2a56a316d .mo files update 2020-02-17 09:19:39 +03:00
wwns
9b79bf2b81 Polish translation update (#11)
Polish translation update.
2020-02-17 08:54:17 +03:00
DYefremov
ee2a9bda90 added appindicator support 2020-02-15 12:51:16 +03:00
DYefremov
da0c5fa8a6 updated .mo files 2020-02-14 22:50:35 +03:00
wwns
e202ec6abe Polish translation update (#10) 2020-02-14 22:28:15 +03:00
DYefremov
e87be79f42 minor fix 2020-02-13 20:15:18 +03:00
DYefremov
6372ac474c toolbar changes 2020-02-13 01:09:40 +03:00
DYefremov
c6b0f70c8e update of data path 2020-02-12 13:51:40 +03:00
DYefremov
6a921ad394 added Polish selection 2020-02-12 12:39:12 +03:00
DYefremov
4c8743517f copy .mo file 2020-02-11 22:02:20 +03:00
wwns
74ec0fe956 added a Polish translation (#5)
* added a Polish translation

* added a Polish translation

* name change
2020-02-11 21:52:54 +03:00
DYefremov
041f717a01 hotkey refactoring 2020-02-11 13:18:14 +03:00
DYefremov
67dbdb19d7 fix bq deletion 2020-02-10 19:24:48 +03:00
DYefremov
de49179dd2 changing profile on data download 2020-02-10 17:00:46 +03:00
DYefremov
2723d255fe fix profile edit 2020-02-10 14:45:05 +03:00
DYefremov
4515b2538b moved get yt icon 2020-02-07 16:56:01 +03:00
DYefremov
88e3a22cf0 auto rename bouquets with duplicate names 2020-01-31 15:09:56 +03:00
DYefremov
7ac63b81c0 added checking for bouquet names duplicate 2020-01-29 14:50:02 +03:00
DYefremov
234611b686 added controls to the transmitter 2020-01-28 15:08:57 +03:00
DYefremov
fdb2691430 added player requests 2020-01-28 14:51:23 +03:00
DYefremov
d81700c30c minor gui changes 2020-01-24 00:53:42 +03:00
DYefremov
e91c4c33a5 added reset button hiding 2020-01-24 00:04:43 +03:00
DYefremov
40bf54e94f fix size of picons 2020-01-23 23:42:28 +03:00
DYefremov
4a50c36ab4 added remove and download to picons 2020-01-23 00:47:01 +03:00
56 changed files with 7376 additions and 1839 deletions

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2018-2019 Dmitriy Yefremov
Copyright (c) 2018-2020 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

@@ -1,6 +1,6 @@
# <img src="app/ui/icons/hicolor/96x96/apps/demon-editor.png" width="32" /> DemonEditor
## Enigma2 channel and satellites list editor for GNU/Linux.
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
## Enigma2 channel and satellites list editor for GNU/Linux.
Experimental support of Neutrino-MP or others on the same basis (BPanther, etc).
Focused on the convenience of working in lists from the keyboard. The mouse is also fully supported (Drag and Drop etc)
@@ -10,18 +10,19 @@ Focused on the convenience of working in lists from the keyboard. The mouse is a
* Backup function.
* Extended support of IPTV.
* Support of picons.
* Downloading of picons and updating of satellites (transponders) from the Internet.
* Downloading of picons and updating of satellites (transponders) from the web.
* Import to bouquet(Neutrino WEBTV) from m3u.
* Export of bouquets with IPTV services in m3u.
* Assignment of EPGs from DVB or XML for IPTV services (only Enigma2, experimental).
* Preview (playback) of IPTV or other streams directly from the bouquet list (should be installed [VLC](https://www.videolan.org/vlc/)).
### Keyboard shortcuts:
### Keyboard shortcuts:
* **Ctrl + X** - only in bouquet list.
* **Ctrl + C** - only in services list.
Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Ctrl + Insert** - copies the selected channels from the main list to the the bouquet beginning
or inserts (creates) a new bouquet.
* **Ctrl + BackSpace** - copies the selected channels from the main list to the bouquet end.
* **Ctrl + X** - only in bouquet list. **Ctrl + C** - only in services list.
Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Ctrl + BackSpace** - copies the selected channels from the main list to the bouquet end.
* **Ctrl + E** - edit.
* **Ctrl + R, F2** - rename.
* **Ctrl + S, T** in Satellites edit tool for create satellite or transponder.
@@ -36,6 +37,10 @@ Clipboard is **"rubber"**. There is an accumulation before the insertion!
* **Ctrl + O** - (re)load user data from current dir.
* **Ctrl + D** - load data from receiver.
* **Ctrl + U/B** upload data/bouquets to receiver.
* **Ctrl + F** - show/hide search bar.
* **Ctrl + Shift + F** - show/hide filter bar.
For multiple mouse selection (including Drag and Drop), press and hold the **Ctrl** key!
### Minimum requirements:
Python >= 3.5.2 and GTK+ >= 3.16 with PyGObject bindings, python3-requests.
@@ -50,12 +55,15 @@ Extra folders can be deleted, excluding the *app* folder and root files like *De
To create a simple **debian package**, you can use the *build-deb.sh.*
Users of **LTS** versions of [Ubuntu](https://ubuntu.com/) or those based on them can use [PPA](https://launchpad.net/~dmitriy-yefremov/+archive/ubuntu/demon-editor) repository.
The program is tested only with [openATV](https://www.opena.tv/) image and **Formuler F1** receiver in my favourite Linux distributions
The program is tested only with [openATV](https://www.opena.tv/) image and **Formuler F1** receiver in my favourite Linux distributions
(the latest versions of [Linux Mint](https://linuxmint.com/) 18.* and 19* MATE 64-bit)!
### Important:
Terrestrial(DVB-T/T2) and cable(DVB-C) channels are only supported for Enigma2!
Main supported **lamedb** format is version **4**. Versions **3** and **5** has only **experimental** support!
For version **3** is only read mode available. When saving, version **4** format is used instead!
Main supported *lamedb* format is version **4**. Versions **3** and **5** has only **experimental** support!
For version **3** is only read mode available. When saving, version **4** format is used instead!
When using the multiple import feature, from *lamedb* will be taken data **only for channels that are in the
selected bouquets!** If you need full set of the data, including *[satellites, terrestrial, cables].xml* (current files will be overwritten),
just load your data via *"File/Open"* and press *"Save"*. When importing separate bouquet files, only those services
(excluding IPTV) that are in the **current open lamedb** (main list of services) will be imported.

View File

@@ -6,28 +6,22 @@ from gi.repository import GLib
_LOG_FILE = "demon-editor.log"
_DATE_FORMAT = "%d-%m-%y %H:%M:%S"
_LOGGER_NAME = "main_logger"
_USE_LOG = False
_LOGGER_NAME = None
def init_logger():
global _USE_LOG
_USE_LOG = True
global _LOGGER_NAME
_LOGGER_NAME = "main_logger"
logging.Logger(_LOGGER_NAME)
logging.basicConfig(level=logging.INFO,
format="%(asctime)s %(message)s",
datefmt=_DATE_FORMAT,
handlers=[logging.FileHandler(_LOG_FILE),
logging.StreamHandler()])
handlers=[logging.FileHandler(_LOG_FILE), logging.StreamHandler()])
log("Logging is enabled.", level=logging.INFO)
def get_logger():
return logging.getLogger(_LOGGER_NAME)
def log(message, level=logging.ERROR):
get_logger().log(level, message) if _USE_LOG else print(message)
logging.getLogger(_LOGGER_NAME).log(level, message)
def run_idle(func):

View File

@@ -10,19 +10,20 @@ from http.client import RemoteDisconnected
from telnetlib import Telnet
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import urlopen, HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, \
build_opener, install_opener, Request
from urllib.request import (urlopen, HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, build_opener,
install_opener, Request)
from app.commons import log, run_task
from app.settings import SettingsType
_BQ_FILES_LIST = ("tv", "radio", # enigma 2
"myservices.xml", "bouquets.xml", "ubouquets.xml") # neutrino
BQ_FILES_LIST = ("tv", "radio", # enigma 2
"myservices.xml", "bouquets.xml", "ubouquets.xml") # neutrino
_DATA_FILES_LIST = ("lamedb", "lamedb5", "services.xml", "blacklist", "whitelist",)
DATA_FILES_LIST = ("lamedb", "lamedb5", "blacklist", "whitelist",)
_SAT_XML_FILE = "satellites.xml"
_WEBTV_XML_FILE = "webtv.xml"
STC_XML_FILE = ("satellites.xml", "terrestrial.xml", "cables.xml")
WEB_TV_XML_FILE = ("webtv.xml",)
PICONS_SUF = (".jpg", ".png")
class DownloadType(Enum):
@@ -41,9 +42,15 @@ class HttpRequestType(Enum):
STREAM = "stream.m3u?ref="
STREAM_CURRENT = "streamcurrent.m3u"
CURRENT = "getcurrent"
PLAY = "mediaplayerplay?file=4097:0:1:0:0:0:0:0:0:0:"
TEST = None
TOKEN = "session"
PLAY = "mediaplayerplay?file="
PLAYER_LIST = "mediaplayerlist?path=playlist"
PLAYER_PLAY = "mediaplayercmd?command=play"
PLAYER_NEXT = "mediaplayercmd?command=next"
PLAYER_PREV = "mediaplayercmd?command=previous"
PLAYER_STOP = "mediaplayercmd?command=stop"
PLAYER_REMOVE = "mediaplayerremove?file="
class TestException(Exception):
@@ -60,43 +67,33 @@ def download_data(*, settings, download_type=DownloadType.ALL, callback=print):
callback("FTP OK.\n")
save_path = settings.data_local_path
os.makedirs(os.path.dirname(save_path), exist_ok=True)
files = []
# bouquets
if download_type is DownloadType.ALL or download_type is DownloadType.BOUQUETS:
ftp.cwd(settings.services_path)
ftp.dir(files.append)
file_list = _BQ_FILES_LIST + _DATA_FILES_LIST if download_type is DownloadType.ALL else _BQ_FILES_LIST
for file in files:
name = str(file).strip()
if name.endswith(file_list):
name = name.split()[-1]
download_file(ftp, name, save_path, callback)
# satellites.xml and webtv
if download_type in (DownloadType.ALL, DownloadType.SATELLITES, DownloadType.WEBTV):
ftp.cwd(settings.satellites_xml_path)
files.clear()
ftp.dir(files.append)
file_list = BQ_FILES_LIST + DATA_FILES_LIST if download_type is DownloadType.ALL else BQ_FILES_LIST
for file in filter(lambda f: f.endswith(file_list), ftp.nlst()):
download_file(ftp, file, save_path, callback)
# *.xml and webtv
if download_type in (DownloadType.ALL, DownloadType.SATELLITES):
download_xml(ftp, save_path, settings.satellites_xml_path, STC_XML_FILE, callback)
if download_type in (DownloadType.ALL, DownloadType.WEBTV):
download_xml(ftp, save_path, settings.satellites_xml_path, WEB_TV_XML_FILE, callback)
for file in files:
name = str(file).strip()
if download_type in (DownloadType.ALL, DownloadType.SATELLITES) and name.endswith(_SAT_XML_FILE):
download_file(ftp, _SAT_XML_FILE, save_path, callback)
if download_type in (DownloadType.ALL, DownloadType.WEBTV) and name.endswith(_WEBTV_XML_FILE):
download_file(ftp, _WEBTV_XML_FILE, save_path, callback)
if download_type is DownloadType.PICONS:
picons_path = settings.picons_local_path
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
download_picons(ftp, settings.picons_path, picons_path, callback)
# epg.dat
if download_type is DownloadType.EPG:
stb_path = settings.services_path
epg_options = settings.epg_options
if epg_options:
stb_path = epg_options.epg_dat_stb_path or stb_path
save_path = epg_options.epg_dat_path or save_path
stb_path = epg_options.get("epg_dat_stb_path", stb_path)
save_path = epg_options.get("epg_dat_path", save_path)
ftp.cwd(stb_path)
ftp.dir(files.append)
for file in files:
name = str(file).strip()
if name.endswith("epg.dat"):
name = name.split()[-1]
download_file(ftp, name, save_path, callback)
for file in filter(lambda f: f.endswith("epg.dat"), ftp.nlst()):
download_file(ftp, file, save_path, callback)
callback("\nDone.\n")
@@ -146,23 +143,23 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
services_path = settings.services_path
if download_type is DownloadType.SATELLITES:
upload_xml(ftp, data_path, sat_xml_path, _SAT_XML_FILE, callback)
upload_xml(ftp, data_path, sat_xml_path, STC_XML_FILE, callback)
if s_type is SettingsType.NEUTRINO_MP and download_type is DownloadType.WEBTV:
upload_xml(ftp, data_path, sat_xml_path, _WEBTV_XML_FILE, callback)
upload_xml(ftp, data_path, sat_xml_path, WEB_TV_XML_FILE, callback)
if download_type is DownloadType.BOUQUETS:
ftp.cwd(services_path)
upload_bouquets(ftp, data_path, remove_unused, callback)
if download_type is DownloadType.ALL:
upload_xml(ftp, data_path, sat_xml_path, _SAT_XML_FILE, callback)
upload_xml(ftp, data_path, sat_xml_path, STC_XML_FILE, callback)
if s_type is SettingsType.NEUTRINO_MP:
upload_xml(ftp, data_path, sat_xml_path, _WEBTV_XML_FILE, callback)
upload_xml(ftp, data_path, sat_xml_path, WEB_TV_XML_FILE, callback)
ftp.cwd(services_path)
upload_bouquets(ftp, data_path, remove_unused, callback)
upload_files(ftp, data_path, _DATA_FILES_LIST, callback)
upload_files(ftp, data_path, DATA_FILES_LIST, callback)
if download_type is DownloadType.PICONS:
upload_picons(ftp, settings.picons_local_path, settings.picons_path, callback)
@@ -189,12 +186,12 @@ def upload_data(*, settings, download_type=DownloadType.ALL, remove_unused=False
def upload_bouquets(ftp, data_path, remove_unused, callback):
if remove_unused:
remove_unused_bouquets(ftp, callback)
upload_files(ftp, data_path, _BQ_FILES_LIST, callback)
upload_files(ftp, data_path, BQ_FILES_LIST, callback)
def upload_files(ftp, data_path, file_list, callback):
for file_name in os.listdir(data_path):
if file_name == _SAT_XML_FILE or file_name == _WEBTV_XML_FILE:
if file_name in STC_XML_FILE or file_name in WEB_TV_XML_FILE:
continue
if file_name.endswith(file_list):
send_file(file_name, data_path, ftp, callback)
@@ -210,12 +207,21 @@ def remove_unused_bouquets(ftp, callback):
callback("Deleting file: {}. Status: {}\n".format(name, ftp.delete(name)))
def upload_xml(ftp, data_path, xml_path, xml_file, callback):
""" Used for transfer satellites.xml or webtv.xml files """
def upload_xml(ftp, data_path, xml_path, xml_files, callback):
""" Used for transfer *.xml files. """
ftp.cwd(xml_path)
send_file(xml_file, data_path, ftp, callback)
for xml_file in xml_files:
send_file(xml_file, data_path, ftp, callback)
def download_xml(ftp, data_path, xml_path, xml_files, callback):
""" Used for download *.xml files. """
ftp.cwd(xml_path)
list(map(lambda f: download_file(ftp, f, data_path, callback), (f for f in ftp.nlst() if f.endswith(xml_files))))
# ***************** Picons *******************#
def upload_picons(ftp, src, dest, callback):
try:
ftp.cwd(dest)
@@ -223,17 +229,49 @@ def upload_picons(ftp, src, dest, callback):
if str(e).startswith("550"):
ftp.mkd(dest) # if not exist
ftp.cwd(dest)
delete_picons(ftp, callback)
for file_name in os.listdir(src):
if file_name.endswith(PICONS_SUF):
send_file(file_name, src, ftp, callback)
def download_picons(ftp, src, dest, callback):
try:
ftp.cwd(src)
except error_perm as e:
callback(str(e))
return
for file in filter(lambda f: f.endswith(PICONS_SUF), ftp.nlst()):
download_file(ftp, file, dest, callback)
def delete_picons(ftp, callback, dest=None):
if dest:
try:
ftp.cwd(dest)
except error_perm as e:
callback(str(e))
return
files = []
ftp.dir(files.append)
picons_suf = (".jpg", ".png")
for file in files:
name = str(file).strip()
if name.endswith(picons_suf):
if name.endswith(PICONS_SUF):
name = name.split()[-1]
ftp.delete(name)
for file_name in os.listdir(src):
if file_name.endswith(picons_suf):
send_file(file_name, src, ftp, callback)
callback("Delete file: {}. Status: {}\n".format(name, ftp.delete(name)))
def remove_picons(*, settings, callback, done_callback=None):
with FTP(host=settings.host, user=settings.user, passwd=settings.password) as ftp:
ftp.encoding = "utf-8"
callback("FTP OK.\n")
delete_picons(ftp, callback, settings.picons_path)
if done_callback:
done_callback()
def download_file(ftp, name, save_path, callback):
@@ -243,7 +281,12 @@ def download_file(ftp, name, save_path, callback):
def send_file(file_name, path, ftp, callback):
""" Opens the file in binary mode and transfers into receiver """
with open(path + file_name, "rb") as f:
file_src = path + file_name
if not os.path.isfile(file_src):
log("Uploading file: '{}'. File not found. Skipping.".format(file_src))
return
with open(file_src, "rb") as f:
callback("Uploading file: {}. Status: {}\n".format(file_name, str(ftp.storbinary("STOR " + file_name, f))))
@@ -298,7 +341,7 @@ class HttpAPI:
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
self._executor = PoolExecutor(max_workers=self.__MAX_WORKERS)
def send(self, req_type, ref, callback=print):
def send(self, req_type, ref, callback=print, ref_prefix=""):
if self._shutdown:
return
@@ -306,11 +349,14 @@ class HttpAPI:
if req_type is HttpRequestType.ZAP or req_type is HttpRequestType.STREAM:
url += urllib.parse.quote(ref)
elif req_type is HttpRequestType.PLAY:
url += urllib.parse.quote(ref).replace("%3A", "%253A")
elif req_type is HttpRequestType.PLAY or req_type is HttpRequestType.PLAYER_REMOVE:
url += "{}{}".format(ref_prefix, urllib.parse.quote(ref).replace("%3A", "%253A"))
def done_callback(f):
callback(f.result())
future = self._executor.submit(get_response, req_type, url, self._data)
future.add_done_callback(lambda f: callback(f.result()))
future.add_done_callback(done_callback)
@run_task
def init(self):
@@ -334,10 +380,13 @@ def get_response(req_type, url, data=None):
try:
with urlopen(Request(url, data=data), timeout=10) as f:
if req_type is HttpRequestType.STREAM or req_type is HttpRequestType.STREAM_CURRENT:
return f.read().decode("utf-8")
return {"m3u": f.read().decode("utf-8")}
elif req_type is HttpRequestType.CURRENT:
for el in ETree.fromstring(f.read().decode("utf-8")).iter("e2event"):
return {el.tag: el.text for el in el.iter()} # return first[current] event from the list
elif req_type is HttpRequestType.PLAYER_LIST:
return [{el.tag: el.text for el in el.iter()} for el in
ETree.fromstring(f.read().decode("utf-8")).iter("e2file")]
else:
return {el.tag: el.text for el in ETree.fromstring(f.read().decode("utf-8")).iter()}
except HTTPError as e:

View File

@@ -33,9 +33,9 @@ def get_bouquets(path, s_type):
@run_task
def write_bouquets(path, bouquets, s_type):
def write_bouquets(path, bouquets, s_type, force_bq_names=False):
if s_type is SettingsType.ENIGMA_2:
write_enigma_bouquets(path, bouquets)
write_enigma_bouquets(path, bouquets, force_bq_names)
elif s_type is SettingsType.NEUTRINO_MP:
write_neutrino_bouquets(path, bouquets)

View File

@@ -1,6 +1,8 @@
""" Module for parsing bouquets """
""" Module for working with Enigma2 bouquets. """
import re
from collections import Counter
from app.commons import log
from app.eparser.ecommons import BqServiceType, BouquetService, Bouquets, Bouquet, BqType
_TV_ROOT_FILE_NAME = "bouquets.tv"
@@ -13,7 +15,12 @@ def get_bouquets(path):
BqType.RADIO.value)
def write_bouquets(path, bouquets):
def write_bouquets(path, bouquets, force_bq_names=False):
""" Creating and writing bouquets files.
If "force_bq_names" then naming the files using the name of the bouquet.
Some images may have problems displaying the favorites list!
"""
srv_line = '#SERVICE 1:7:{}:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.{}.{}" ORDER BY bouquet\n'
line = []
pattern = re.compile("[^\\w_()]+")
@@ -23,12 +30,12 @@ def write_bouquets(path, bouquets):
line.clear()
line.append("#NAME {}\n".format(bqs.name))
for bq in bqs.bouquets:
for index, bq in enumerate(bqs.bouquets):
bq_name = bq.name
if bq_name == "Favourites (TV)" or bq_name == "Favourites (Radio)":
bq_name = _DEFAULT_BOUQUET_NAME
else:
bq_name = re.sub(pattern, "_", bq.name)
bq_name = re.sub(pattern, "_", bq.name) if force_bq_names else "de{0:02d}".format(index)
line.append(srv_line.format(2 if bq.type == BqType.RADIO.value else 1, bq_name, bq.type))
write_bouquet(path + "userbouquet.{}.{}".format(bq_name, bq.type), bq.name, bq.services, current_marker)
@@ -60,7 +67,7 @@ def write_bouquet(path, name, services, current_marker):
def to_bouquet_id(srv):
""" Creates bouquet channel id """
""" Creates bouquet channel id. """
data_type = srv.data_id
if data_type and len(data_type) > 4:
data_type = int(srv.data_id.split(":")[4])
@@ -69,7 +76,7 @@ def to_bouquet_id(srv):
def get_bouquet(path, name, bq_type):
""" Parsing services ids from bouquet file """
""" Parsing services ids from bouquet file. """
with open(path + "userbouquet.{}.{}".format(name, bq_type), encoding="utf-8", errors="replace") as file:
chs_list = file.read()
services = []
@@ -77,16 +84,16 @@ def get_bouquet(path, name, bq_type):
for ch in srvs[1:]:
ch_data = ch.strip().split(":")
if ch_data[1] == "64":
marker_data = ch.split("#DESCRIPTION", 1)
services.append(BouquetService(marker_data[1].strip(), BqServiceType.MARKER, ch, ch_data[2]))
m_data, sep, desc = ch.partition("#DESCRIPTION")
services.append(BouquetService(desc.strip() if desc else "", BqServiceType.MARKER, ch, ch_data[2]))
elif "http" in ch:
stream_data = ch.split("#DESCRIPTION", 1)
services.append(BouquetService(stream_data[-1].strip(":").strip(), BqServiceType.IPTV, ch, 0))
stream_data, sep, desc = ch.partition("#DESCRIPTION")
services.append(BouquetService(desc.lstrip(":").strip() if desc else "", BqServiceType.IPTV, ch, 0))
else:
fav_id = "{}:{}:{}:{}".format(ch_data[3], ch_data[4], ch_data[5], ch_data[6])
name = None
if len(ch_data) == 12:
name, desc = str(ch_data[-1]).split("\n#DESCRIPTION")
name, sep, desc = str(ch_data[-1]).partition("\n#DESCRIPTION")
services.append(BouquetService(name, BqServiceType.DEFAULT, fav_id.upper(), 0))
return srvs[0].lstrip("#NAME").strip(), services
@@ -98,6 +105,8 @@ def parse_bouquets(path, bq_name, bq_type):
bouquets = None
nm_sep = "#NAME"
bq_pattern = re.compile(".*userbouquet\\.+(.*)\\.+[tv|radio].*")
b_names = set()
real_b_names = Counter()
for line in lines:
if nm_sep in line:
@@ -106,8 +115,21 @@ def parse_bouquets(path, bq_name, bq_type):
if bouquets and "#SERVICE" in line:
name = re.match(bq_pattern, line)
if name:
b_name, services = get_bouquet(path, name.group(1), bq_type)
bouquets[2].append(Bouquet(name=b_name,
b_name = name.group(1)
if b_name in b_names:
log("The list of bouquets contains duplicate [{}] names!".format(b_name))
else:
b_names.add(b_name)
rb_name, services = get_bouquet(path, b_name, bq_type)
if rb_name in real_b_names:
log("Bouquet file 'userbouquet.{}.{}' has duplicate name: {}".format(b_name, bq_type, rb_name))
real_b_names[rb_name] += 1
rb_name = "{} {}".format(rb_name, real_b_names[rb_name])
else:
real_b_names[rb_name] = 0
bouquets[2].append(Bouquet(name=rb_name,
type=bq_type,
services=services,
locked=None,

View File

@@ -2,14 +2,19 @@ import copy
import json
import locale
import os
import sys
from enum import Enum, IntEnum
from functools import lru_cache
from pathlib import Path
from pprint import pformat
from textwrap import dedent
CONFIG_PATH = str(Path.home()) + "/.config/demon-editor/"
HOME_PATH = str(Path.home())
CONFIG_PATH = HOME_PATH + "/.config/demon-editor/"
CONFIG_FILE = CONFIG_PATH + "config.json"
DATA_PATH = "data/"
DATA_PATH = HOME_PATH + "/DemonEditor/data/"
IS_DARWIN = sys.platform == "darwin"
class Defaults(Enum):
@@ -18,6 +23,7 @@ class Defaults(Enum):
BACKUP_BEFORE_DOWNLOADING = True
BACKUP_BEFORE_SAVE = True
V5_SUPPORT = False
FORCE_BQ_NAMES = False
HTTP_API_SUPPORT = False
ENABLE_YT_DL = False
ENABLE_SEND_TO = False
@@ -25,6 +31,21 @@ class Defaults(Enum):
NEW_COLOR = "rgb(255,230,204)"
EXTRA_COLOR = "rgb(179,230,204)"
FAV_CLICK_MODE = 0
PLAY_STREAMS_MODE = 1 if IS_DARWIN else 0
PROFILE_FOLDER_DEFAULT = False
RECORDS_PATH = DATA_PATH + "records/"
ACTIVATE_TRANSCODING = False
ACTIVE_TRANSCODING_PRESET = "720p TV/device"
def get_settings():
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True)
if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
write_settings(get_default_settings())
with open(CONFIG_FILE, "r") as config_file:
return json.load(config_file)
def get_default_settings(profile_name="default"):
@@ -42,14 +63,32 @@ def get_default_settings(profile_name="default"):
"use_colors": Defaults.USE_COLORS.value,
"new_color": Defaults.NEW_COLOR.value,
"extra_color": Defaults.EXTRA_COLOR.value,
"fav_click_mode": Defaults.FAV_CLICK_MODE.value
"fav_click_mode": Defaults.FAV_CLICK_MODE.value,
"profile_folder_is_default": Defaults.PROFILE_FOLDER_DEFAULT.value,
"records_path": Defaults.RECORDS_PATH.value
}
def set_local_paths(settings, profile_name):
settings["data_local_path"] = "{}{}/".format(settings["data_local_path"], profile_name)
settings["picons_local_path"] = "{}{}/".format(settings["picons_local_path"], profile_name)
settings["backup_local_path"] = "{}{}/".format(settings["backup_local_path"], profile_name)
def get_default_transcoding_presets():
return {"720p TV/device": {"vcodec": "h264", "vb": "1500", "width": "1280", "height": "720", "acodec": "mp3",
"ab": "192", "channels": "2", "samplerate": "44100", "scodec": "none"},
"1080p TV/device": {"vcodec": "h264", "vb": "3500", "width": "1920", "height": "1080", "acodec": "mp3",
"ab": "192", "channels": "2", "samplerate": "44100", "scodec": "none"}}
def write_settings(config):
with open(CONFIG_FILE, "w") as config_file:
json.dump(config, config_file, indent=" ")
def set_local_paths(settings, profile_name, data_path=DATA_PATH, use_profile_folder=False):
settings["data_local_path"] = "{}{}/".format(data_path, profile_name)
if use_profile_folder:
settings["picons_local_path"] = "{}{}/{}/".format(data_path, profile_name, "picons")
settings["backup_local_path"] = "{}{}/{}/".format(data_path, profile_name, "backup")
else:
settings["picons_local_path"] = "{}{}/{}/".format(data_path, "picons", profile_name)
settings["backup_local_path"] = "{}{}/{}/".format(data_path, "backup", profile_name)
class SettingsType(IntEnum):
@@ -86,6 +125,13 @@ class SettingsException(Exception):
pass
class PlayStreamsMode(IntEnum):
""" Behavior mode when opening streams. """
BUILT_IN = 0
VLC = 1
M3U = 2
class Settings:
__INSTANCE = None
__VERSION = 1
@@ -99,7 +145,9 @@ class Settings:
self._settings = settings
self._current_profile = self._settings.get("default_profile", "default")
self._profiles = self._settings.get("profiles", {"default": SettingsType.ENIGMA_2.get_default_settings()})
self._cp_settings = self._profiles.get(self._current_profile) # Current profile settings
self._cp_settings = self._profiles.get(self._current_profile, None) # Current profile settings
if not self._cp_settings:
raise SettingsException("Error reading settings [current profile].")
def __str__(self):
return dedent(""" Current profile: {}
@@ -123,7 +171,10 @@ class Settings:
def reset(self, force_write=False):
for k, v in self.setting_type.get_default_settings().items():
self._cp_settings[k] = v
set_local_paths(self._cp_settings, self._current_profile)
def_path = self.default_data_path
def_path += "enigma2/" if self.setting_type is SettingsType.ENIGMA_2 else "neutrino/"
set_local_paths(self._cp_settings, self._current_profile, def_path, self.profile_folder_is_default)
if force_write:
self.save()
@@ -188,21 +239,7 @@ class Settings:
def setting_type(self, s_type):
self._cp_settings["setting_type"] = s_type.value
@property
def language(self):
return self._settings.get("language", locale.getlocale()[0] or "en_US")
@language.setter
def language(self, value):
self._settings["language"] = value
@property
def load_last_config(self):
return self._settings.get("load_last_config", False)
@load_last_config.setter
def load_last_config(self, value):
self._settings["load_last_config"] = value
# ******* Network ******** #
@property
def host(self):
@@ -332,14 +369,6 @@ class Settings:
def satellites_xml_path(self, value):
self._cp_settings["satellites_xml_path"] = value
@property
def data_local_path(self):
return self._cp_settings.get("data_local_path", self.get_default("data_local_path"))
@data_local_path.setter
def data_local_path(self, value):
self._cp_settings["data_local_path"] = value
@property
def picons_path(self):
return self._cp_settings.get("picons_path", self.get_default("picons_path"))
@@ -348,6 +377,32 @@ class Settings:
def picons_path(self, value):
self._cp_settings["picons_path"] = value
# ***** Local paths ***** #
@property
def profile_folder_is_default(self):
return self._settings.get("profile_folder_is_default", Defaults.PROFILE_FOLDER_DEFAULT.value)
@profile_folder_is_default.setter
def profile_folder_is_default(self, value):
self._settings["profile_folder_is_default"] = value
@property
def default_data_path(self):
return self._settings.get("default_data_path", DATA_PATH)
@default_data_path.setter
def default_data_path(self, value):
self._settings["default_data_path"] = value
@property
def data_local_path(self):
return self._cp_settings.get("data_local_path", self.get_default("data_local_path"))
@data_local_path.setter
def data_local_path(self, value):
self._cp_settings["data_local_path"] = value
@property
def picons_local_path(self):
return self._cp_settings.get("picons_local_path", self.get_default("picons_local_path"))
@@ -364,7 +419,60 @@ class Settings:
def backup_local_path(self, value):
self._cp_settings["backup_local_path"] = value
# ***** Program settings *****
@property
def records_path(self):
return self._settings.get("records_path", Defaults.RECORDS_PATH.value)
@records_path.setter
def records_path(self, value):
self._settings["records_path"] = value
# ******** Streaming ********* #
@property
def activate_transcoding(self):
return self._settings.get("activate_transcoding", Defaults.ACTIVATE_TRANSCODING.value)
@activate_transcoding.setter
def activate_transcoding(self, value):
self._settings["activate_transcoding"] = value
@property
def active_preset(self):
return self._settings.get("active_preset", Defaults.ACTIVE_TRANSCODING_PRESET.value)
@active_preset.setter
def active_preset(self, value):
self._settings["active_preset"] = value
@property
def transcoding_presets(self):
return self._settings.get("transcoding_presets", get_default_transcoding_presets())
@transcoding_presets.setter
def transcoding_presets(self, value):
self._settings["transcoding_presets"] = value
@property
def play_streams_mode(self):
return PlayStreamsMode(self._settings.get("play_streams_mode", Defaults.PLAY_STREAMS_MODE.value))
@play_streams_mode.setter
def play_streams_mode(self, value):
self._settings["play_streams_mode"] = value
# *********** EPG ************ #
@property
def epg_options(self):
""" Options used by the EPG dialog. """
return self._cp_settings.get("epg_options", None)
@epg_options.setter
def epg_options(self, value):
self._cp_settings["epg_options"] = value
# ***** Program settings ***** #
@property
def backup_before_save(self):
@@ -390,6 +498,14 @@ class Settings:
def v5_support(self, value):
self._settings["v5_support"] = value
@property
def force_bq_names(self):
return self._settings.get("force_bq_names", Defaults.FORCE_BQ_NAMES.value)
@force_bq_names.setter
def force_bq_names(self, value):
self._settings["force_bq_names"] = value
@property
def http_api_support(self):
return self._settings.get("http_api_support", Defaults.HTTP_API_SUPPORT.value)
@@ -446,21 +562,79 @@ class Settings:
def fav_click_mode(self, value):
self._settings["fav_click_mode"] = value
@property
def language(self):
return self._settings.get("language", locale.getlocale()[0] or "en_US")
def get_settings():
os.makedirs(os.path.dirname(CONFIG_PATH), exist_ok=True) # create dir if not exist
os.makedirs(os.path.dirname(DATA_PATH), exist_ok=True)
@language.setter
def language(self, value):
self._settings["language"] = value
if not os.path.isfile(CONFIG_FILE) or os.stat(CONFIG_FILE).st_size == 0:
write_settings(get_default_settings())
@property
def load_last_config(self):
return self._settings.get("load_last_config", False)
with open(CONFIG_FILE, "r") as config_file:
return json.load(config_file)
@load_last_config.setter
def load_last_config(self, value):
self._settings["load_last_config"] = value
@property
def show_srv_hints(self):
""" Show short info as hints in the main services list. """
return self._settings.get("show_srv_hints", True)
def write_settings(config):
with open(CONFIG_FILE, "w") as config_file:
json.dump(config, config_file, indent=" ")
@show_srv_hints.setter
def show_srv_hints(self, value):
self._settings["show_srv_hints"] = value
@property
def show_bq_hints(self):
""" Show detailed info as hints in the bouquet list. """
return self._settings.get("show_bq_hints", True)
@show_bq_hints.setter
def show_bq_hints(self, value):
self._settings["show_bq_hints"] = value
# *********** Appearance *********** #
@property
def is_themes_support(self):
return self._settings.get("is_themes_support", False)
@is_themes_support.setter
def is_themes_support(self, value):
self._settings["is_themes_support"] = value
@property
def theme(self):
return self._settings.get("theme", "Default")
@theme.setter
def theme(self, value):
self._settings["theme"] = value
@property
@lru_cache(1)
def themes_path(self):
return "{}/.themes/".format(HOME_PATH)
@property
def icon_theme(self):
return self._settings.get("icon_theme", "Adwaita")
@icon_theme.setter
def icon_theme(self, value):
self._settings["icon_theme"] = value
@property
@lru_cache(1)
def icon_themes_path(self):
return "{}/.icons/".format(HOME_PATH)
@property
def is_darwin(self):
return IS_DARWIN
if __name__ == "__main__":

View File

@@ -1,10 +1,17 @@
import os
import subprocess
import sys
from datetime import datetime
from enum import Enum
from urllib.request import urlopen
from app.commons import run_task, log
from app.commons import run_task, log, _DATE_FORMAT
from app.settings import PlayStreamsMode
class Player:
__VLC_INSTANCE = None
__PLAY_STREAMS_MODE = PlayStreamsMode.BUILT_IN
def __init__(self, rewind_callback, position_callback, error_callback, playing_callback):
try:
@@ -44,6 +51,10 @@ class Player:
cls.__VLC_INSTANCE = Player(rewind_callback, position_callback, error_callback, playing_callback)
return cls.__VLC_INSTANCE
@staticmethod
def get_play_mode():
return Player.__PLAY_STREAMS_MODE
@run_task
def play(self, mrl=None):
if mrl:
@@ -103,5 +114,157 @@ class Player:
self._player.set_fullscreen(full)
class HttpPlayer:
""" Simple wrapper for VLC media player to interact over http. """
__VLC_INSTANCE = None
__PLAY_STREAMS_MODE = PlayStreamsMode.VLC
class Commands(Enum):
STATUS = "http://127.0.0.1:{}/requests/status.xml"
PLAY = "http://127.0.0.1:{}/requests/status.xml?command=in_play&input={}"
STOP = "http://127.0.0.1:{}/requests/status.xml?command=pl_stop"
CLEAR = "http://127.0.0.1:{}/requests/status.xml?command=pl_empty"
def __init__(self, exe, port, is_darwin):
from concurrent.futures import ThreadPoolExecutor as PoolExecutor
self._executor = PoolExecutor(max_workers=1)
self._cmd = [exe, "--no-stats", "--verbose=-1", "--extraintf", "http", "--http-port", port, "--quiet"]
if not is_darwin:
self._cmd.append("--one-instance")
self._p = None
self._state = None
self._port = port
@classmethod
def get_instance(cls, settings):
if not cls.__VLC_INSTANCE:
import shutil
is_darwin = settings.is_darwin
# TODO Add options[vlc_exe and port] to the settings!
exe = "/Applications/VLC.app/Contents/MacOS/VLC" if is_darwin else "/usr/bin/vlc"
if shutil.which(exe) is None:
raise ImportError
cls.__VLC_INSTANCE = HttpPlayer(exe=exe, port=str(9090), is_darwin=is_darwin)
return cls.__VLC_INSTANCE
@staticmethod
def get_play_mode():
return HttpPlayer.__PLAY_STREAMS_MODE
@run_task
def play(self, mrl=None):
if not self._p or self._p and self._p.poll() is not None:
self._p = subprocess.Popen(self._cmd + [mrl], preexec_fn=os.setsid)
self._p.communicate()
else:
self._executor.submit(self.open_command, self.Commands.CLEAR)
self._executor.submit(self.open_command, self.Commands.PLAY, mrl)
def open_command(self, command, url=None):
if command is self.Commands.PLAY:
url = self.Commands.PLAY.value.format(self._port, url)
else:
url = command.value.format(self._port)
try:
with urlopen(url, timeout=5) as f:
self._state = command
except Exception as e:
log("{}[open_command, {}] error: {}".format(__class__.__name__, command, e))
def stop(self):
if self._state is self.Commands.PLAY:
self._executor.submit(self.open_command, self.Commands.STOP)
def pause(self):
pass
def set_time(self, time):
pass
@run_task
def release(self):
if self._p and self._p.poll() is None:
import signal
# Good explanation here: https://stackoverflow.com/a/4791612
os.killpg(os.getpgid(self._p.pid), signal.SIGTERM)
def is_playing(self):
return self._state is self.Commands.PLAY
def set_full_screen(self, full):
pass
class Recorder:
__VLC_REC_INSTANCE = None
_CMD = "sout=#std{{access=file,mux=ts,dst={}.ts}}"
_TR_CMD = "sout=#transcode{{{}}}:file{{mux=mp4,dst={}.mp4}}"
def __init__(self, settings):
try:
from app.tools import vlc
from app.tools.vlc import EventType
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError
else:
self._settings = settings
self._is_record = False
args = "--quiet {}".format("" if sys.platform == "darwin" else "--no-xlib")
self._recorder = vlc.Instance(args).media_player_new()
@classmethod
def get_instance(cls, settings):
if not cls.__VLC_REC_INSTANCE:
cls.__VLC_REC_INSTANCE = Recorder(settings)
return cls.__VLC_REC_INSTANCE
@run_task
def record(self, url, name):
if self._recorder:
self._recorder.stop()
path = self._settings.records_path
os.makedirs(os.path.dirname(path), exist_ok=True)
d_now = datetime.now().strftime(_DATE_FORMAT)
path = "{}{}_{}".format(path, name.replace(" ", "_"), d_now.replace(" ", "_"))
cmd = self.get_transcoding_cmd(path) if self._settings.activate_transcoding else self._CMD.format(path)
media = self._recorder.get_instance().media_new(url, cmd)
media.get_mrl()
self._recorder.set_media(media)
self._is_record = True
self._recorder.play()
log("Record started {}".format(d_now))
@run_task
def stop(self):
self._recorder.stop()
self._is_record = False
log("Recording stopped.")
def is_record(self):
return self._is_record
@run_task
def release(self):
if self._recorder:
self._recorder.stop()
self._recorder.release()
self._is_record = False
log("Recording stopped. Releasing...")
def get_transcoding_cmd(self, path):
presets = self._settings.transcoding_presets
prs = presets.get(self._settings.active_preset)
return self._TR_CMD.format(",".join("{}={}".format(k, v) for k, v in prs.items()), path)
if __name__ == "__main__":
pass

View File

@@ -125,7 +125,7 @@ class ProviderParser(HTMLParser):
_POSITION_PATTERN = re.compile("at\s\d+\..*(?:E|W)']")
_ONID_TID_PATTERN = re.compile("^\d+-\d+.*")
_TRANSPONDER_FREQUENCY_PATTERN = re.compile("^\d+ [HVLR]+")
_DOMAIN = "https://www.lyngsat.com"
_DOMAIN = "http://www.lyngsat.com"
_TV_DOMAIN = _DOMAIN + "/tvchannels/"
_RADIO_DOMAIN = _DOMAIN + "/radiochannels/"
_PKG_DOMAIN = _DOMAIN + "/packages/"

View File

@@ -99,6 +99,7 @@ class SatellitesParser(HTMLParser):
return list(map(get_sat, filter(lambda x: all(x) and len(x) == 5, self._rows)))
elif self._source is SatelliteSource.LYNGSAT:
extra_pattern = re.compile("^https://www\.lyngsat\.com/[\w-]+\.html")
base_url = "https://www.lyngsat.com/"
sats = []
current_pos = "0"
for row in filter(lambda x: len(x) in (5, 7, 8), self._rows):
@@ -106,8 +107,8 @@ class SatellitesParser(HTMLParser):
if r_len == 7:
current_pos = self.parse_position(row[2])
name = row[1].rsplit("/")[-1].rstrip(".html").replace("-", " ")
sats.append((name, current_pos, row[5], row[1], False)) # coupled [all in one] satellites
sats.append((row[4], current_pos, row[5], row[3], False))
sats.append((name, current_pos, row[5], base_url + row[1], False)) # [all in one] satellites
sats.append((row[4], current_pos, row[5], base_url + row[3], False))
if r_len == 8: # for a very limited number of satellites
data = list(filter(None, row))
urls = set()
@@ -121,9 +122,9 @@ class SatellitesParser(HTMLParser):
current_pos = self.parse_position(data[1])
for url in urls:
name = url.rsplit("/")[-1].rstrip(".html").replace("-", " ")
sats.append((name, current_pos, sat_type, url, False))
sats.append((name, current_pos, sat_type, base_url + url, False))
elif r_len == 5:
sats.append((row[2], current_pos, row[3], row[1], False))
sats.append((row[2], current_pos, row[3], base_url + row[1], False))
return sats
def get_satellite(self, sat):

View File

@@ -67,7 +67,7 @@ class YouTube:
if fmts:
urls = {Quality[i["itag"]]: i["url"] for i in
filter(lambda i: i.get("itag", -1) in Quality, fmts)}
filter(lambda i: i.get("itag", -1) in Quality, fmts) if "url" in i}
if urls and title:
return urls, title.replace("+", " ")
@@ -121,7 +121,8 @@ class PlayListParser(HTMLParser):
ct = resp.get("contents", None)
if ct:
for d in [(d["title"]["simpleText"], d["videoId"]) for d in flat("playlistVideoRenderer", ct)]:
for d in [(d.get("title", {}).get("simpleText", ""),
d.get("videoId", "")) for d in flat("playlistVideoRenderer", ct)]:
self._playlist.append(d)
self._is_script = False

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018 Dmitriy Yefremov
Copyright (c) 2018-2020 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-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="main_list_store">
<columns>
@@ -144,6 +144,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Details</property>
<property name="draw_indicator">False</property>
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
<child>
@@ -153,6 +154,7 @@ Author: Dmitriy Yefremov
<property name="stock">gtk-dialog-info</property>
</object>
</child>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="pack_type">end</property>

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018 Dmitriy Yefremov
Copyright (c) 2018-2020 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
@@ -26,12 +26,12 @@ THE SOFTWARE.
Author: Dmitriy Yefremov
-->
<interface>
<interface domain="demon-editor">
<requires lib="gtk+" version="3.16"/>
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkAboutDialog" id="about_dialog">
<property name="can_focus">False</property>
@@ -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">0.4.7 Pre-alpha</property>
<property name="version">0.4.8 Pre-alpha</property>
<property name="copyright">2018-2020 Dmitriy Yefremov
</property>
<property name="comments" translatable="yes">Enigma2 channel and satellites list editor for GNU/Linux</property>
@@ -194,6 +194,8 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="wait_dialog_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="label" translatable="yes">Loading data...</property>
</object>
<packing>

View File

@@ -52,12 +52,17 @@ class WaitDialog:
builder, dialog = get_dialog_from_xml(DialogType.WAIT, transient)
self._dialog = dialog
self._dialog.set_transient_for(transient)
if text is not None:
builder.get_object("wait_dialog_label").set_text(text)
self._label = builder.get_object("wait_dialog_label")
self._default_text = text or self._label.get_text()
def show(self):
def show(self, text=None):
self.set_text(text)
self._dialog.show()
@run_idle
def set_text(self, text):
self._label.set_text(get_message(text or self._default_text))
@run_idle
def hide(self):
self._dialog.hide()
@@ -81,10 +86,11 @@ def show_dialog(dialog_type: DialogType, transient, text=None, settings=None, ac
return get_about_dialog(transient)
def get_chooser_dialog(transient, settings, pattern, name):
def get_chooser_dialog(transient, settings, name, patterns):
file_filter = Gtk.FileFilter()
file_filter.add_pattern(pattern)
file_filter.set_name(name)
for p in patterns:
file_filter.add_pattern(p)
return show_dialog(dialog_type=DialogType.CHOOSER,
transient=transient,

View File

@@ -241,25 +241,9 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="focus_on_click">False</property>
<property name="halign">center</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<property name="active">0</property>
<property name="has_frame">False</property>
<property name="has_entry">True</property>
<signal name="changed" handler="on_profile_changed" swapped="no"/>
<child internal-child="entry">
<object class="GtkEntry">
<property name="can_focus">True</property>
<property name="has_tooltip">True</property>
<property name="halign">center</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<property name="editable">False</property>
<property name="has_frame">False</property>
<property name="max_width_chars">9</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -620,6 +604,9 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<style>
<class name="group"/>
</style>
</object>
</child>
</object>
@@ -730,4 +717,11 @@ Author: Dmitriy Yefremov
</object>
</child>
</object>
<object class="GtkSizeGroup" id="settings_size_group">
<widgets>
<widget name="ftp_radio_button"/>
<widget name="http_radio_button"/>
<widget name="telnet_radio_button"/>
</widgets>
</object>
</interface>

View File

@@ -96,7 +96,7 @@ class DownloadDialog:
elif self._satellites_radio_button.get_active():
download_type = DownloadType.SATELLITES
elif self._webtv_radio_button.get_active():
download_type = DownloadType.WEB_TV
download_type = DownloadType.WEBTV
return download_type
def destroy(self):
@@ -140,6 +140,7 @@ class DownloadDialog:
if active in self._settings.profiles:
self._settings.current_profile = active
self._profile_combo_box.set_active_id(active)
self._s_type = self._settings.setting_type
self.init_ui_settings()
def on_info_bar_close(self, bar=None, resp=None):

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2019 Dmitriy Yefremov
Copyright (c) 2018-2020 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-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="bouquet_list_store">
<columns>
@@ -653,6 +653,7 @@ Author: Dmitriy Yefremov
<property name="stock">gtk-spell-check</property>
</object>
</child>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">False</property>

View File

@@ -486,7 +486,7 @@ class EpgDialog:
epg_dat_path = self._settings.data_local_path + "epg/"
self._epg_dat_path_entry.set_text(epg_dat_path)
default_epg_data_stb_path = "/etc/enigma2"
epg_options = self._settings.get("epg_options")
epg_options = self._settings.epg_options
if epg_options:
self._refs_source = RefsSource.XML if epg_options.get("xml_source", False) else RefsSource.SERVICES
self._xml_radiobutton.set_active(self._refs_source is RefsSource.XML)
@@ -506,15 +506,14 @@ class EpgDialog:
os.makedirs(os.path.dirname(self._epg_dat_path_entry.get_text()), exist_ok=True)
def on_options_save(self, item=None):
epg_options = {"xml_source": self._xml_radiobutton.get_active(),
"use_web_source": self._use_web_source_switch.get_active(),
"local_path_to_xml": self._xml_chooser_button.get_filename(),
"url_to_xml": self._url_to_xml_entry.get_text(),
"enable_filtering": self._enable_filtering_switch.get_active(),
"epg_dat_path": self._epg_dat_path_entry.get_text(),
"epg_dat_stb_path": self._epg_dat_stb_path_entry.get_text(),
"epg_data_update_on_start": self._update_on_start_switch.get_active()}
self._settings.add("epg_options", epg_options)
self._settings.epg_options = {"xml_source": self._xml_radiobutton.get_active(),
"use_web_source": self._use_web_source_switch.get_active(),
"local_path_to_xml": self._xml_chooser_button.get_filename(),
"url_to_xml": self._url_to_xml_entry.get_text(),
"enable_filtering": self._enable_filtering_switch.get_active(),
"epg_dat_path": self._epg_dat_path_entry.get_text(),
"epg_dat_stb_path": self._epg_dat_stb_path_entry.get_text(),
"epg_data_update_on_start": self._update_on_start_switch.get_active()}
def on_resize(self, window):
if self._settings:

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2019 Dmitriy Yefremov
Copyright (c) 2018-2020 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-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="main_list_store">
<columns>
@@ -119,6 +119,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">Details</property>
<property name="draw_indicator">False</property>
<signal name="toggled" handler="on_info_button_toggled" swapped="no"/>
<child>
@@ -128,6 +129,7 @@ Author: Dmitriy Yefremov
<property name="stock">gtk-dialog-info</property>
</object>
</child>
<accelerator key="i" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="pack_type">end</property>

View File

@@ -30,7 +30,7 @@ def import_bouquet(transient, model, path, settings, services, appender):
elif bq_type is BqType.WEBTV:
f_pattern = "webtv.xml"
file_path = get_chooser_dialog(transient, settings, f_pattern, "bouquet files")
file_path = get_chooser_dialog(transient, settings, "bouquet files", (f_pattern,))
if file_path == Gtk.ResponseType.CANCEL:
return

View File

@@ -1,23 +1,21 @@
import concurrent.futures
import glob
import os
import re
import urllib
from functools import lru_cache
from urllib.error import HTTPError
from urllib.parse import urlparse
from urllib.request import Request, urlopen
from gi.repository import GLib
from app.commons import run_idle, run_task, log
from app.commons import run_idle, run_task
from app.eparser.ecommons import BqServiceType, Service
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT
from app.settings import SettingsType
from app.tools.yt import YouTube, PlayListParser
from .dialogs import Action, show_dialog, DialogType, get_dialogs_string, get_message
from .main_helper import get_base_model, get_iptv_url, on_popup_menu
from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION, KeyboardKey
from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION, KeyboardKey, \
get_yt_icon
_DIGIT_ENTRY_NAME = "digit-entry"
_ENIGMA2_REFERENCE = "{}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
@@ -43,22 +41,6 @@ def get_stream_type(box):
return StreamType.NONE_REC_2.value
@lru_cache(maxsize=1)
def get_yt_icon(icon_name, size=24):
""" Getting YouTube icon. If the icon is not found in the icon themes, the "Info" icon is returned by default! """
default_theme = Gtk.IconTheme.get_default()
if default_theme.has_icon(icon_name):
return default_theme.load_icon(icon_name, size, 0)
theme = Gtk.IconTheme.new()
for theme_name in map(os.path.basename, filter(os.path.isdir, glob.glob("/usr/share/icons/*"))):
theme.set_custom_theme(theme_name)
if theme.has_icon(icon_name):
return theme.load_icon(icon_name, size, 0)
return default_theme.load_icon("info", size, 0)
class IptvDialog:
def __init__(self, transient, view, services, bouquet, profile=SettingsType.ENIGMA_2, action=Action.ADD):

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -362,39 +362,44 @@ def append_picons(picons, model):
GLib.idle_add(lambda: next(app, False), priority=GLib.PRIORITY_LOW)
def assign_picon(target, srv_view, fav_view, transient, picons, settings, services):
def assign_picon(target, srv_view, fav_view, transient, picons, settings, services, p_path=None):
view = srv_view if target is ViewTarget.SERVICES else fav_view
model, paths = view.get_selection().get_selected_rows()
if not is_only_one_item_selected(paths, transient):
return
response = get_chooser_dialog(transient, settings, "*.png", "png files")
if response == Gtk.ResponseType.CANCEL:
return
if not p_path:
p_path = get_chooser_dialog(transient, settings, "*.png files", ("*.png",))
if p_path == Gtk.ResponseType.CANCEL:
return
if not str(response).endswith(".png"):
if not str(p_path).endswith(".png") or not os.path.isfile(p_path):
show_dialog(DialogType.ERROR, transient, text="No png file is selected!")
return
picon_pos = Column.SRV_PICON
model = get_base_model(model)
itr = model.get_iter(paths)
fav_id = model.get_value(itr, Column.SRV_FAV_ID if target is ViewTarget.SERVICES else Column.FAV_ID)
picon_id = services.get(fav_id)[Column.SRV_PICON_ID]
p_pos = Column.SRV_PICON
col_num = Column.SRV_FAV_ID if target is ViewTarget.SERVICES else Column.FAV_ID
itrs = [model.get_iter(p) for p in paths]
if picon_id:
if os.path.isfile(response):
if target is ViewTarget.SERVICES:
f_model = model.get_model()
itrs = [f_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr)) for itr in itrs]
model = get_base_model(model)
for itr in itrs:
fav_id = model.get_value(itr, col_num)
picon_id = services.get(fav_id)[Column.SRV_PICON_ID]
if picon_id:
picons_path = settings.picons_local_path
os.makedirs(os.path.dirname(picons_path), exist_ok=True)
picon_file = picons_path + picon_id
shutil.copy(response, picon_file)
shutil.copy(p_path, picon_file)
picon = get_picon_pixbuf(picon_file)
picons[picon_id] = picon
model.set_value(itr, picon_pos, picon)
model.set_value(itr, p_pos, picon)
if target is ViewTarget.SERVICES:
set_picon(fav_id, fav_view.get_model(), picon, Column.FAV_ID, picon_pos)
set_picon(fav_id, fav_view.get_model(), picon, Column.FAV_ID, p_pos)
else:
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, Column.SRV_FAV_ID, picon_pos)
set_picon(fav_id, get_base_model(srv_view.get_model()), picon, Column.SRV_FAV_ID, p_pos)
def set_picon(fav_id, model, picon, fav_id_pos, picon_pos):
@@ -407,14 +412,19 @@ def set_picon(fav_id, model, picon, fav_id_pos, picon_pos):
def remove_picon(target, srv_view, fav_view, picons, settings):
view = srv_view if target is ViewTarget.SERVICES else fav_view
model, paths = view.get_selection().get_selected_rows()
model = get_base_model(model)
fav_ids = []
picon_ids = []
picon_pos = Column.SRV_PICON # picon position is equal for services and fav
for path in paths:
itr = model.get_iter(path)
itrs = [model.get_iter(p) for p in paths]
if target is ViewTarget.SERVICES:
f_model = model.get_model()
itrs = [f_model.convert_iter_to_child_iter(model.convert_iter_to_child_iter(itr)) for itr in itrs]
model = get_base_model(model)
for itr in itrs:
model.set_value(itr, picon_pos, None)
if target is ViewTarget.SERVICES:
fav_ids.append(model.get_value(itr, Column.SRV_FAV_ID))
@@ -426,8 +436,10 @@ def remove_picon(target, srv_view, fav_view, picons, settings):
else:
fav_ids.append(fav_id)
fav_id_column = Column.FAV_ID if target is ViewTarget.SERVICES else Column.SRV_FAV_ID
def remove(md, path, it):
if md.get_value(it, Column.FAV_ID if target is ViewTarget.SERVICES else Column.SRV_FAV_ID) in fav_ids:
if md.get_value(it, fav_id_column) in fav_ids:
md.set_value(it, picon_pos, None)
if target is ViewTarget.FAV:
picon_ids.append(md.get_value(it, Column.SRV_PICON_ID))
@@ -488,9 +500,9 @@ def is_only_one_item_selected(paths, transient):
return True
def get_picon_pixbuf(path):
def get_picon_pixbuf(path, size=32):
try:
return GdkPixbuf.Pixbuf.new_from_file_at_scale(filename=path, width=32, height=32, preserve_aspect_ratio=True)
return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, width=size, height=size, preserve_aspect_ratio=True)
except GLib.GError as e:
pass

View File

@@ -264,7 +264,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_bouquets_edit" object="bouquets_tree_view" swapped="no"/>
<signal name="activate" handler="on_edit" swapped="no"/>
<accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
@@ -444,8 +444,8 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">app.on_data_open</property>
<property name="text" translatable="yes">Open</property>
<signal name="clicked" handler="on_data_open" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -455,11 +455,10 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkModelButton" id="save_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</property>
<property name="text" translatable="yes">Save</property>
<signal name="clicked" handler="on_data_save" swapped="no"/>
<accelerator key="s" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
@@ -485,8 +484,8 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="action_name">app.on_download</property>
<property name="text" translatable="yes">FTP-transfer</property>
<signal name="clicked" handler="on_download" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@@ -800,7 +799,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_service_edit" object="services_tree_view" swapped="no"/>
<signal name="activate" handler="on_edit" swapped="no"/>
<accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>
@@ -976,7 +975,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="title" translatable="yes">DemonEditor</property>
<property name="subtitle" translatable="yes">Profile:</property>
<property name="spacing">1</property>
<property name="spacing">0</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkMenuButton" id="file_header_button">
@@ -1005,37 +1004,64 @@ Author: Dmitriy Yefremov
</packing>
</child>
<child>
<object class="GtkButton" id="download_header_button">
<object class="GtkComboBoxText" id="profile_combo_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">FTP-transfer</property>
<signal name="clicked" handler="on_download" swapped="no"/>
<child>
<object class="GtkImage" id="download_header_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-wired</property>
</object>
</child>
<property name="focus_on_click">False</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
<property name="active">0</property>
<signal name="changed" handler="on_profile_changed" swapped="no"/>
</object>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkBox" id="left_header_box">
<object class="GtkBox" id="main_header_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="spacing">1</property>
<child>
<object class="GtkButton" id="save_header_button">
<object class="GtkSeparator" id="main_header_box_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="download_header_button">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">FTP-transfer</property>
<property name="action_name">app.on_download</property>
<child>
<object class="GtkImage" id="download_header_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">network-wired</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="save_header_button">
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Save</property>
<signal name="clicked" handler="on_data_save" swapped="no"/>
<property name="action_name">app.on_data_save</property>
<child>
<object class="GtkImage" id="save_header_button_image">
<property name="visible">True</property>
@@ -1051,10 +1077,41 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="backup_tool_header_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Backup</property>
<signal name="clicked" handler="on_backup_tool_show" swapped="no"/>
<child>
<object class="GtkImage" id="backup_tool_header_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-revert-to-saved</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkBox" id="left_header_box">
<property name="can_focus">False</property>
<property name="spacing">1</property>
<child>
<object class="GtkSeparator" id="left_header_box_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">1</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1066,10 +1123,11 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkToggleButton" id="filter_header_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Filter</property>
<signal name="toggled" handler="on_filter_toggled" swapped="no"/>
<property name="action_name">win.filter</property>
<child>
<object class="GtkImage" id="filter_header_button_image">
<property name="visible">True</property>
@@ -1087,10 +1145,11 @@ Author: Dmitriy Yefremov
<child>
<object class="GtkToggleButton" id="search_header_button">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Search</property>
<signal name="toggled" handler="on_search_toggled" swapped="no"/>
<property name="action_name">win.search</property>
<child>
<object class="GtkImage" id="search_header_button_image">
<property name="visible">True</property>
@@ -1109,6 +1168,8 @@ Author: Dmitriy Yefremov
<object class="GtkSeparator" id="left_header_box_separator_2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1124,7 +1185,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Parent lock On/Off Ctrl + L</property>
<signal name="clicked" handler="on_locked" swapped="no"/>
<property name="action_name">app.on_locked</property>
<child>
<object class="GtkImage" id="locked_tool_button_image">
<property name="visible">True</property>
@@ -1146,7 +1207,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Hide/Skip On/Off Ctrl + H</property>
<signal name="clicked" handler="on_hide" swapped="no"/>
<property name="action_name">app.on_hide</property>
<child>
<object class="GtkImage">
<property name="visible">True</property>
@@ -1163,13 +1224,11 @@ Author: Dmitriy Yefremov
</child>
</object>
<packing>
<property name="position">4</property>
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkBox" id="right_header_box">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="spacing">1</property>
<child>
@@ -1183,7 +1242,7 @@ Author: Dmitriy Yefremov
<object class="GtkImage" id="sat_editor_header_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-select-all</property>
<property name="stock">gtk-edit</property>
</object>
</child>
</object>
@@ -1198,8 +1257,8 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Picons downloader</property>
<signal name="clicked" handler="on_picons_loader_show" swapped="no"/>
<property name="tooltip_text" translatable="yes">Picons manager</property>
<signal name="clicked" handler="on_picons_manager_show" swapped="no"/>
<child>
<object class="GtkImage" id="picons_downloader_header_button_image">
<property name="visible">True</property>
@@ -1214,31 +1273,12 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="backup_tool_header_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Backup</property>
<signal name="clicked" handler="on_backup_tool_show" swapped="no"/>
<child>
<object class="GtkImage" id="backup_tool_header_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-revert-to-saved</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkSeparator" id="right_header_box_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1541,13 +1581,11 @@ Author: Dmitriy Yefremov
<property name="orientation">vertical</property>
<child>
<object class="GtkSearchBar" id="search_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkBox" id="search_bar_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">1</property>
<child>
<object class="GtkSearchEntry" id="search_entry">
<property name="height_request">32</property>
@@ -1610,6 +1648,9 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<style>
<class name="group"/>
</style>
</object>
</child>
</object>
@@ -1712,7 +1753,7 @@ Author: Dmitriy Yefremov
<object class="GtkSeparator" id="top_separator">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_top">1</property>
</object>
<packing>
<property name="expand">False</property>
@@ -1722,11 +1763,13 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkPaned" id="main_data_paned">
<property name="width_request">320</property>
<property name="height_request">250</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">1</property>
<property name="margin_right">1</property>
<property name="margin_top">2</property>
<property name="wide_handle">True</property>
<child>
<object class="GtkBox" id="services_main_box">
@@ -1737,6 +1780,8 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="services_header_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="label" translatable="yes">Services</property>
<attributes>
<attribute name="weight" value="bold"/>
@@ -1758,6 +1803,7 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">services_model_tree_model_sort</property>
<property name="enable_search">False</property>
<property name="search_column">3</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property>
@@ -1767,9 +1813,12 @@ Author: Dmitriy Yefremov
<signal name="button-press-event" handler="on_view_press" swapped="no"/>
<signal name="drag-begin" handler="on_view_drag_begin" swapped="no"/>
<signal name="drag-data-get" handler="on_view_drag_data_get" swapped="no"/>
<signal name="drag-data-received" handler="on_services_view_drag_data_received" swapped="no"/>
<signal name="drag-drop" handler="on_services_view_drag_drop" swapped="no"/>
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="key-release-event" handler="on_tree_view_key_release" swapped="no"/>
<signal name="query-tooltip" handler="on_services_view_query_tooltip" swapped="no"/>
<signal name="row-activated" handler="on_services_selection" object="services_list_store" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="services_selection">
@@ -1807,7 +1856,6 @@ Author: Dmitriy Yefremov
<property name="title" translatable="yes">Service</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">3</property>
<child>
<object class="GtkCellRendererPixbuf" id="coded_cellrendererpixbuf">
@@ -1855,7 +1903,6 @@ Author: Dmitriy Yefremov
<property name="title" translatable="yes">Package</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">6</property>
<child>
<object class="GtkCellRendererText" id="package_cellrenderertext">
@@ -1876,7 +1923,6 @@ Author: Dmitriy Yefremov
<property name="title" translatable="yes">Type</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">7</property>
<child>
<object class="GtkCellRendererText" id="service_type_cellrenderertex">
@@ -1895,7 +1941,6 @@ Author: Dmitriy Yefremov
<property name="min_width">25</property>
<property name="title" translatable="yes">Picon</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">9</property>
<child>
<object class="GtkCellRendererPixbuf" id="picon_cellrendererpixbuf"/>
@@ -1927,7 +1972,6 @@ Author: Dmitriy Yefremov
<property name="title">SID</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">10</property>
<child>
<object class="GtkCellRendererText" id="ssid_cellrenderertext">
@@ -1947,7 +1991,6 @@ Author: Dmitriy Yefremov
<property name="title" translatable="yes">Freq</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">11</property>
<child>
<object class="GtkCellRendererText" id="freq_cellrenderertext">
@@ -1967,7 +2010,6 @@ Author: Dmitriy Yefremov
<property name="title" translatable="yes">Rate</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">12</property>
<child>
<object class="GtkCellRendererText" id="rate_cellrenderertext">
@@ -1987,7 +2029,6 @@ Author: Dmitriy Yefremov
<property name="title" translatable="yes">Pol</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">13</property>
<child>
<object class="GtkCellRendererText" id="pol_cellrenderertext">
@@ -2007,7 +2048,6 @@ Author: Dmitriy Yefremov
<property name="title">FEC</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">14</property>
<child>
<object class="GtkCellRendererText" id="fec_cellrenderertext">
@@ -2027,7 +2067,6 @@ Author: Dmitriy Yefremov
<property name="title" translatable="yes">System</property>
<property name="expand">True</property>
<property name="alignment">0.5</property>
<property name="reorderable">True</property>
<property name="sort_column_id">15</property>
<child>
<object class="GtkCellRendererText" id="system_cellrenderertex">
@@ -2285,6 +2324,8 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="fav_header_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="label" translatable="yes">Bouquet details</property>
<attributes>
<attribute name="weight" value="bold"/>
@@ -2307,9 +2348,11 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">fav_list_store</property>
<property name="enable_search">False</property>
<property name="search_column">2</property>
<property name="rubber_banding">True</property>
<property name="enable_grid_lines">both</property>
<property name="tooltip_column">9</property>
<property name="activate_on_single_click">True</property>
<signal name="button-press-event" handler="on_fav_press" object="fav_popup_menu" swapped="no"/>
<signal name="button-press-event" handler="on_view_press" swapped="yes"/>
@@ -2319,6 +2362,7 @@ Author: Dmitriy Yefremov
<signal name="focus-in-event" handler="on_view_focus" swapped="no"/>
<signal name="key-press-event" handler="on_tree_view_key_press" swapped="no"/>
<signal name="key-release-event" handler="on_tree_view_key_release" swapped="no"/>
<signal name="query-tooltip" handler="on_fav_view_query_tooltip" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="fav_selection">
<property name="mode">multiple</property>
@@ -2557,6 +2601,8 @@ Author: Dmitriy Yefremov
<object class="GtkLabel" id="bouquets_header_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="label" translatable="yes">Bouquets</property>
<attributes>
<attribute name="weight" value="bold"/>
@@ -2778,7 +2824,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" translatable="yes">0.4.7 Pre-alpha</property>
<property name="label" translatable="yes">0.4.8 Pre-alpha</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
@@ -2798,7 +2844,11 @@ Author: Dmitriy Yefremov
</child>
<child>
<object class="GtkBox" id="status_bar_box">
<property name="height_request">28</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<child>
<object class="GtkBox" id="receiver_info_box">
<property name="can_focus">False</property>
@@ -2833,6 +2883,21 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="current_ip_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Current IP:</property>
<attributes>
<attribute name="size" value="8000"/>
</attributes>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
@@ -2840,46 +2905,10 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child type="center">
<object class="GtkComboBoxText" id="profile_combo_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="focus_on_click">False</property>
<property name="tooltip_text" translatable="yes">Current IP:</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<property name="active">0</property>
<property name="has_entry">True</property>
<child internal-child="entry">
<object class="GtkEntry" id="profile_entry">
<property name="can_focus">False</property>
<property name="has_tooltip">True</property>
<property name="halign">baseline</property>
<property name="valign">baseline</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<property name="editable">False</property>
<property name="has_frame">False</property>
<property name="max_width_chars">9</property>
<property name="overwrite_mode">True</property>
<property name="caps_lock_warning">False</property>
<property name="primary_icon_stock">gtk-connect</property>
<signal name="changed" handler="on_profile_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkImage" id="http_status_image">
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">No connection to the receiver</property>
<property name="tooltip_text" translatable="yes">No connection to the receiver!</property>
<property name="margin_left">10</property>
<property name="margin_right">10</property>
<property name="icon_name">network-offline</property>
@@ -2910,7 +2939,28 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="record_button">
<property name="name">status-bar-button</property>
<property name="can_focus">False</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Record</property>
<signal name="clicked" handler="on_record" swapped="no"/>
<child>
<object class="GtkImage" id="record_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-record</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
@@ -2936,7 +2986,7 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">3</property>
</packing>
</child>
<child>
@@ -2946,6 +2996,7 @@ Author: Dmitriy Yefremov
<property name="tooltip_text" translatable="yes">Current service</property>
<property name="justify">fill</property>
<property name="ellipsize">end</property>
<property name="max_width_chars">35</property>
<property name="xalign">1</property>
<attributes>
<attribute name="size" value="8000"/>
@@ -2954,21 +3005,21 @@ Author: Dmitriy Yefremov
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkLevelBar" id="signal_level_bar">
<property name="width_request">70</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Tuner signal</property>
<property name="tooltip_text" translatable="yes">Signal level</property>
<property name="valign">center</property>
<property name="max_value">100</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
<property name="position">5</property>
</packing>
</child>
</object>
@@ -2979,9 +3030,6 @@ Author: Dmitriy Yefremov
<property name="position">2</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
@@ -3050,7 +3098,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_service_edit" object="fav_tree_view" swapped="no"/>
<signal name="activate" handler="on_edit" swapped="no"/>
<accelerator key="e" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</object>
</child>

File diff suppressed because it is too large Load Diff

1287
app/ui/picons_manager.glade Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,20 +6,21 @@ import tempfile
from gi.repository import GLib, GdkPixbuf
from app.commons import run_idle, run_task
from app.connections import upload_data, DownloadType
from app.commons import run_idle, run_task, run_with_delay
from app.connections import upload_data, DownloadType, download_data, remove_picons
from app.settings import SettingsType, Settings
from app.tools.picons import PiconsParser, parse_providers, Provider, convert_to
from app.settings import SettingsType
from app.tools.satellites import SatellitesParser, SatelliteSource
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, TV_ICON
from .dialogs import show_dialog, DialogType, get_message
from .main_helper import update_entry_data, append_text_to_tview, scroll_to, on_popup_menu
from .main_helper import update_entry_data, append_text_to_tview, scroll_to, on_popup_menu, get_base_model
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TV_ICON, Column
class PiconsDialog:
def __init__(self, transient, settings, picon_ids, sat_positions):
def __init__(self, transient, settings, picon_ids, sat_positions, app):
self._picon_ids = picon_ids
self._sat_positions = sat_positions
self._app = app
self._TMP_DIR = tempfile.gettempdir() + "/"
self._BASE_URL = "www.lyngsat.com/packages/"
self._PATTERN = re.compile(r"^https://www\.lyngsat\.com/[\w-]+\.html$")
@@ -32,31 +33,46 @@ class PiconsDialog:
"on_cancel": self.on_cancel,
"on_close": self.on_close,
"on_send": self.on_send,
"on_download": self.on_download,
"on_remove": self.on_remove,
"on_info_bar_close": self.on_info_bar_close,
"on_picons_dir_open": self.on_picons_dir_open,
"on_selected_toggled": self.on_selected_toggled,
"on_url_changed": self.on_url_changed,
"on_picons_filter_changed": self.on_picons_filter_changed,
"on_position_edited": self.on_position_edited,
"on_notebook_switch_page": self.on_notebook_switch_page,
"on_visible_page": self.on_visible_page,
"on_convert": self.on_convert,
"on_picons_folder_changed": self.on_picons_folder_changed,
"on_picons_view_drag_drop": self.on_picons_view_drag_drop,
"on_picons_view_drag_data_received": self.on_picons_view_drag_data_received,
"on_picons_view_drag_data_get": self.on_picons_view_drag_data_get,
"on_picons_view_realize": self.on_picons_view_realize,
"on_satellites_view_realize": self.on_satellites_view_realize,
"on_satellite_selection": self.on_satellite_selection,
"on_select_all": self.on_select_all,
"on_unselect_all": self.on_unselect_all,
"on_filter_toggled": self.on_filter_toggled,
"on_popup_menu": on_popup_menu}
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_file(UI_RESOURCES_PATH + "picons_dialog.glade")
builder.add_from_file(UI_RESOURCES_PATH + "picons_manager.glade")
builder.connect_signals(handlers)
self._dialog = builder.get_object("picons_dialog")
self._dialog.set_transient_for(transient)
self._providers_tree_view = builder.get_object("providers_tree_view")
self._satellites_tree_view = builder.get_object("satellites_tree_view")
self._picons_view = builder.get_object("picons_view")
self._providers_view = builder.get_object("providers_view")
self._satellites_view = builder.get_object("satellites_view")
self._picons_filter_model = builder.get_object("picons_filter_model")
self._picons_filter_model.set_visible_func(self.picons_filter_function)
self._explorer_path_button = builder.get_object("explorer_path_button")
self._expander = builder.get_object("expander")
self._text_view = builder.get_object("text_view")
self._info_bar = builder.get_object("info_bar")
self._filter_bar = builder.get_object("filter_bar")
self._filter_button = builder.get_object("filter_button")
self._picons_filter_entry = builder.get_object("picons_filter_entry")
self._ip_entry = builder.get_object("ip_entry")
self._picons_entry = builder.get_object("picons_entry")
self._url_entry = builder.get_object("url_entry")
@@ -77,10 +93,18 @@ class PiconsDialog:
self._resize_220_132_radio_button = builder.get_object("resize_220_132_radio_button")
self._resize_100_60_radio_button = builder.get_object("resize_100_60_radio_button")
self._satellite_label = builder.get_object("satellite_label")
self._header_download_box = builder.get_object("header_download_box")
self._satellite_label.bind_property("visible", builder.get_object("loading_data_label"), "visible", 4)
self._satellite_label.bind_property("visible", builder.get_object("loading_data_spinner"), "visible", 4)
self._cancel_button.bind_property("visible", builder.get_object("header_download_box"), "visible", 4)
# style
self._cancel_button.bind_property("visible", self._header_download_box, "visible", 4)
self._convert_button.bind_property("visible", self._header_download_box, "visible", 4)
self._load_providers_button.bind_property("visible", self._receive_button, "visible")
self._load_providers_button.bind_property("visible", builder.get_object("download_box_separator"), "visible")
self._filter_bar.bind_property("search-mode-enabled", self._filter_bar, "visible")
self._explorer_path_button.bind_property("sensitive", builder.get_object("picons_view_sw"), "sensitive")
# Init drag-and-drop
self.init_drag_and_drop()
# Style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
@@ -89,8 +113,11 @@ class PiconsDialog:
self._s_type = settings.setting_type
self._ip_entry.set_text(self._settings.host)
self._picons_entry.set_text(self._settings.picons_path)
self._picons_path = self._settings.picons_local_path
self._picons_dir_entry.set_text(self._picons_path)
self._picons_dir_entry.set_text(self._settings.picons_local_path)
window_size = self._settings.get("picons_downloader_window_size")
if window_size:
self._dialog.resize(*window_size)
if not len(self._picon_ids) and self._s_type is SettingsType.ENIGMA_2:
message = get_message("To automatically set the identifiers for picons,\n"
@@ -99,7 +126,110 @@ class PiconsDialog:
self._satellite_label.show()
def show(self):
self._dialog.run()
self._dialog.show()
def on_picons_view_realize(self, view):
self._explorer_path_button.set_current_folder(self._settings.picons_local_path)
def on_picons_folder_changed(self, button):
path = button.get_filename()
if not path or not os.path.exists(path):
return
GLib.idle_add(self._explorer_path_button.set_sensitive, False)
gen = self.update_picons(path)
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def update_picons(self, path):
p_model = self._picons_view.get_model()
if not p_model:
return
model = get_base_model(p_model)
self._picons_view.set_model(None)
factor = self._app.DEL_FACTOR
for index, itr in enumerate([row.iter for row in model]):
model.remove(itr)
if index % factor == 0:
yield True
for file in os.listdir(path):
if self._terminate:
return
try:
p = GdkPixbuf.Pixbuf.new_from_file_at_scale("{}/{}".format(path, file), 100, 60, True)
except GLib.GError as e:
pass
else:
yield model.append((p, file))
self._picons_view.set_model(p_model)
self._explorer_path_button.set_sensitive(True)
yield True
# ***************** Drag-and-drop ********************* #
def init_drag_and_drop(self):
self._picons_view.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
self._picons_view.drag_source_add_uri_targets()
self._picons_view.enable_model_drag_dest([], Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
self._picons_view.drag_dest_add_text_targets()
def on_picons_view_drag_drop(self, view, drag_context, x, y, time):
view.stop_emission_by_name("drag_drop")
targets = drag_context.list_targets()
view.drag_get_data(drag_context, targets[-1] if targets else Gdk.atom_intern("text/plain", False), time)
def on_picons_view_drag_data_received(self, view, drag_context, x, y, data, info, time):
view.stop_emission_by_name("drag_data_received")
txt = data.get_text()
if not txt:
return
if txt.startswith("file://"):
self.show_info_message("Not implemented yet!", Gtk.MessageType.WARNING)
return
itr_str, sep, src = txt.partition("::::")
if src == self._app.BQ_MODEL_NAME:
return
path, pos = view.get_dest_item_at_pos(x, y) or (None, None)
if not path:
return
model = view.get_model()
p_path = "{}/{}".format(self._explorer_path_button.get_filename(), model.get_value(model.get_iter(path), 1))
if src == self._app.FAV_MODEL_NAME:
target_view = self._app.fav_view
c_id = Column.FAV_ID
else:
target_view = self._app.services_view
c_id = Column.SRV_FAV_ID
t_mod = target_view.get_model()
self._app.on_assign_picon(target_view, p_path)
self.show_assign_info([t_mod.get_value(t_mod.get_iter_from_string(itr), c_id) for itr in itr_str.split(",")])
@run_idle
def show_assign_info(self, fav_ids):
self._expander.set_expanded(True)
self._text_view.get_buffer().set_text("")
for i in fav_ids:
srv = self._app.current_services.get(i, None)
if srv:
info = self._app.get_hint_for_srv_list(srv)
self.append_output("Picon assignment for the service:\n{}\n{}\n".format(info, " * " * 30))
def on_picons_view_drag_data_get(self, view, drag_context, data, info, time):
model = view.get_model()
path = view.get_selected_items()[0]
p_path = "{}/{}".format(self._explorer_path_button.get_filename(), model.get_value(model.get_iter(path), 1))
data.set_uris([p_path])
# ******************** ####### ************************* #
def on_satellites_view_realize(self, view):
self.get_satellites(view)
@@ -135,14 +265,19 @@ class PiconsDialog:
self._cancel_button.show()
url = self._url_entry.get_text()
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer)
model = self._providers_tree_view.get_model()
model.clear()
self.append_providers(url, model)
try:
self._current_process = subprocess.Popen(["wget", "-pkP", self._TMP_DIR, url],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
except FileNotFoundError as e:
self._cancel_button.hide()
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer)
model = self._providers_view.get_model()
model.clear()
self.append_providers(url, model)
@run_task
def append_providers(self, url, model):
@@ -185,7 +320,7 @@ class PiconsDialog:
if not self._POS_PATTERN.match(prv[2]):
self.show_info_message(
get_message("Specify the correct position value for the provider!"), Gtk.MessageType.ERROR)
scroll_to(prv.path, self._providers_tree_view)
scroll_to(prv.path, self._providers_view)
return
try:
@@ -195,7 +330,7 @@ class PiconsDialog:
self.process_provider(Provider(*prv))
if self._resize_no_radio_button.get_active():
self.resize(self._picons_path)
self.resize(self._picons_dir_entry.get_text())
self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO)
finally:
@@ -212,7 +347,8 @@ class PiconsDialog:
GLib.io_add_watch(self._current_process.stderr, GLib.IO_IN, self.write_to_buffer)
self._current_process.wait()
path = self._TMP_DIR + (url[url.find("//") + 2:] if prv.single else self._BASE_URL + url[url.rfind("/") + 1:])
PiconsParser.parse(path, self._picons_path, self._TMP_DIR, prv, self._picon_ids, self.get_picons_format())
PiconsParser.parse(path, self._picons_dir_entry.get_text(),
self._TMP_DIR, prv, self._picon_ids, self.get_picons_format())
def write_to_buffer(self, fd, condition):
if condition == GLib.IO_IN:
@@ -228,7 +364,7 @@ class PiconsDialog:
def resize(self, path):
self.show_info_message(get_message("Resizing..."), Gtk.MessageType.INFO)
command = "mogrify -resize {}! *.png".format(
"320x240" if self._resize_220_132_radio_button.get_active() else "100x60").split()
"220x132" if self._resize_220_132_radio_button.get_active() else "100x60").split()
try:
self._current_process = subprocess.Popen(command, universal_newlines=True, cwd=path)
self._current_process.wait()
@@ -253,9 +389,17 @@ class PiconsDialog:
if self.on_cancel():
return True
self._terminate = True
self.save_window_size(window)
self.clean_data()
self._app.update_picons()
GLib.idle_add(self._dialog.destroy)
def save_window_size(self, window):
size = window.get_size()
height = size.height - self._text_view.get_allocated_height() - self._info_bar.get_allocated_height()
self._settings.add("picons_downloader_window_size", (size.width, height))
@run_task
def clean_data(self):
path = self._TMP_DIR + "www.lyngsat.com"
@@ -266,23 +410,46 @@ class PiconsDialog:
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
settings = Settings(self._settings.settings)
settings.picons_local_path = self._explorer_path_button.get_filename() + "/"
self.show_info_message(get_message("Please, wait..."), Gtk.MessageType.INFO)
self.upload_picons()
self.run_func(lambda: upload_data(settings=settings,
download_type=DownloadType.PICONS,
callback=self.append_output,
done_callback=lambda: self.show_info_message(get_message("Done!"),
Gtk.MessageType.INFO)))
@run_task
def upload_picons(self):
if self.is_task_running():
self.show_dialog("The task is already running!", DialogType.ERROR)
def on_download(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
settings = Settings(self._settings.settings)
settings.picons_local_path = self._explorer_path_button.get_filename() + "/"
self.run_func(lambda: download_data(settings=settings,
download_type=DownloadType.PICONS,
callback=self.append_output), True)
def on_remove(self, item):
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
self.run_func(lambda: remove_picons(settings=self._settings,
callback=self.append_output,
done_callback=lambda: self.show_info_message(get_message("Done!"),
Gtk.MessageType.INFO)))
@run_task
def run_func(self, func, update=False):
try:
GLib.idle_add(self._expander.set_expanded, True)
upload_data(settings=self._settings,
download_type=DownloadType.PICONS,
callback=self.append_output,
done_callback=lambda: self.show_info_message(get_message("Done!"), Gtk.MessageType.INFO))
GLib.idle_add(self._header_download_box.set_sensitive, False)
func()
except OSError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
finally:
GLib.idle_add(self._header_download_box.set_sensitive, True)
if update:
self.on_picons_folder_changed(self._explorer_path_button)
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
@@ -291,14 +458,14 @@ class PiconsDialog:
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
self._message_label.set_text(get_message(text))
def on_picons_dir_open(self, entry, icon, event_button):
update_entry_data(entry, self._dialog, settings=self._settings)
@run_idle
def on_selected_toggled(self, toggle, path):
model = self._providers_tree_view.get_model()
model = self._providers_view.get_model()
model.set_value(model.get_iter(path), 7, not toggle.get_active())
self.update_receive_button_state()
@@ -312,21 +479,46 @@ class PiconsDialog:
view.get_model().foreach(lambda mod, path, itr: mod.set_value(itr, 7, select))
self.update_receive_button_state()
def on_filter_toggled(self, button):
active = button.get_active()
self._filter_bar.set_search_mode(active)
if not active:
self._picons_filter_entry.set_text("")
def on_url_changed(self, entry):
suit = self._PATTERN.search(entry.get_text())
entry.set_name("GtkEntry" if suit else "digit-entry")
self._load_providers_button.set_sensitive(suit if suit else False)
@run_with_delay(1)
def on_picons_filter_changed(self, entry):
GLib.idle_add(self._picons_filter_model.refilter, priority=GLib.PRIORITY_LOW)
def picons_filter_function(self, model, itr, data):
if self._picons_filter_model is None or self._picons_filter_model == "None":
return True
t = model.get_value(itr, 1)
if not t:
return True
txt = self._picons_filter_entry.get_text().upper()
return txt in t.upper() or t in (
map(lambda s: s.picon_id, filter(lambda s: txt in s.service.upper(), self._app.current_services.values())))
def on_position_edited(self, render, path, value):
model = self._providers_tree_view.get_model()
model = self._providers_view.get_model()
model.set_value(model.get_iter(path), 2, value)
@run_idle
def on_notebook_switch_page(self, nb, box, tab_num):
self._load_providers_button.set_visible(not tab_num)
self._receive_button.set_visible(not tab_num)
self._convert_button.set_visible(tab_num)
self._send_button.set_visible(not tab_num)
def on_visible_page(self, stack: Gtk.Stack, param):
name = stack.get_visible_child_name()
self._convert_button.set_visible(name == "converter")
self._load_providers_button.set_visible(name == "downloader")
is_explorer = name == "explorer"
self._filter_button.set_visible(is_explorer)
if is_explorer:
self.on_picons_folder_changed(self._explorer_path_button)
@run_idle
def on_convert(self, item):
@@ -355,7 +547,7 @@ class PiconsDialog:
def get_selected_providers(self):
""" returns selected providers """
return [r for r in self._providers_tree_view.get_model() if r[7]]
return [r for r in self._providers_view.get_model() if r[7]]
@run_idle
def show_dialog(self, message, dialog_type):

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2019 Dmitriy Yefremov
Copyright (c) 2018-2020 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
@@ -55,9 +55,10 @@ Author: Dmitriy Yefremov
-->
<interface>
<requires lib="gtk+" version="3.16"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkListStore" id="fec_store">
<columns>
@@ -1479,6 +1480,7 @@ Author: Dmitriy Yefremov
<property name="stock">gtk-select-all</property>
</object>
</child>
<accelerator key="f" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1501,6 +1503,7 @@ Author: Dmitriy Yefremov
<property name="stock">gtk-find</property>
</object>
</child>
<accelerator key="f" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</object>
<packing>
<property name="expand">False</property>
@@ -1593,6 +1596,9 @@ Author: Dmitriy Yefremov
</child>
</object>
</child>
<style>
<class name="group"/>
</style>
</object>
<packing>
<property name="expand">False</property>

View File

@@ -9,10 +9,10 @@ from app.commons import run_idle, run_task
from app.eparser import get_satellites, write_satellites, Satellite, Transponder
from app.eparser.ecommons import PLS_MODE, get_key_by_value
from app.tools.satellites import SatellitesParser, SatelliteSource
from .search import SearchProvider
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS, KeyboardKey, IS_GNOME_SESSION
from .dialogs import show_dialog, DialogType, get_dialogs_string
from .dialogs import show_dialog, DialogType, get_dialogs_string, get_chooser_dialog
from .main_helper import move_items, scroll_to, append_text_to_tview, get_base_model, on_popup_menu
from .search import SearchProvider
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN, MOVE_KEYS, KeyboardKey, IS_GNOME_SESSION, MOD_MASK
_UI_PATH = UI_RESOURCES_PATH + "satellites_dialog.glade"
@@ -84,27 +84,17 @@ class SatellitesDialog:
@run_idle
def on_open(self, model):
response = self.get_file_dialog_response(Gtk.FileChooserAction.OPEN)
if response == Gtk.ResponseType.CANCEL:
response = get_chooser_dialog(self._window, self._settings, "satellites.xml", ("*.xml",))
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
if not str(response).endswith("satellites.xml"):
show_dialog(DialogType.ERROR, self._window, text="No satellites.xml file is selected!")
return
self._data_path = response
self.load_satellites_list(model)
def get_file_dialog_response(self, action: Gtk.FileChooserAction):
file_filter = Gtk.FileFilter()
file_filter.add_pattern("satellites.xml")
file_filter.set_name("satellites.xml")
response = show_dialog(dialog_type=DialogType.CHOOSER,
transient=self._window,
settings=self._settings,
action_type=action,
file_filter=file_filter)
return response
@staticmethod
def on_row_activated(view, path, column):
if view.row_expanded(path):
@@ -124,7 +114,7 @@ class SatellitesDialog:
if not KeyboardKey.value_exist(key_code):
return
key = KeyboardKey(key_code)
ctrl = event.state & Gdk.ModifierType.CONTROL_MASK
ctrl = event.state & MOD_MASK
if key is KeyboardKey.DELETE:
self.on_remove(view)

View File

@@ -31,6 +31,8 @@ class SearchProvider:
if self._max_indexes > 0:
self.on_search_down()
self.update_navigation_buttons()
def scroll_to(self, index):
view, path = self._paths[index]
view.scroll_to_cell(path, None)

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
import os
import re
from enum import Enum
from pathlib import Path
from app.commons import run_task, run_idle
from app.commons import run_task, run_idle, log
from app.connections import test_telnet, test_ftp, TestException, test_http, HttpApiException
from app.settings import SettingsType, Settings
from app.ui.dialogs import show_dialog, DialogType
from .main_helper import update_entry_data, scroll_to
from app.settings import SettingsType, Settings, PlayStreamsMode
from app.ui.dialogs import show_dialog, DialogType, get_message, get_chooser_dialog
from .main_helper import update_entry_data, scroll_to, get_picon_pixbuf
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, FavClickMode, DEFAULT_ICON
@@ -21,6 +21,8 @@ class Property(Enum):
class SettingsDialog:
_DIGIT_ENTRY_NAME = "digit-entry"
_DIGIT_PATTERN = re.compile("(?:^[\\s]*$|\\D)")
def __init__(self, transient, settings: Settings):
handlers = {"on_field_icon_press": self.on_field_icon_press,
@@ -31,10 +33,12 @@ class SettingsDialog:
"on_apply_profile_settings": self.on_apply_profile_settings,
"on_connection_test": self.on_connection_test,
"on_info_bar_close": self.on_info_bar_close,
"on_set_color_switch_state": self.on_set_color_switch_state,
"on_http_mode_switch_state": self.on_http_mode_switch_state,
"on_yt_dl_switch_state": self.on_yt_dl_switch_state,
"on_send_to_switch_state": self.on_send_to_switch_state,
"on_set_color_switch": self.on_set_color_switch,
"on_force_bq_name": self.on_force_bq_name,
"on_http_mode_switch": self.on_http_mode_switch,
"on_yt_dl_switch": self.on_yt_dl_switch,
"on_default_path_mode_switch": self.on_default_path_mode_switch,
"on_default_data_path_changed": self.on_default_data_path_changed,
"on_profile_add": self.on_profile_add,
"on_profile_edit": self.on_profile_edit,
"on_profile_remove": self.on_profile_remove,
@@ -48,7 +52,23 @@ class SettingsDialog:
"on_network_settings_visible": self.on_network_settings_visible,
"on_http_use_ssl_toggled": self.on_http_use_ssl_toggled,
"on_click_mode_togged": self.on_click_mode_togged,
"on_view_popup_menu": self.on_view_popup_menu}
"on_play_mode_changed": self.on_play_mode_changed,
"on_transcoding_preset_changed": self.on_transcoding_preset_changed,
"on_apply_presets": self.on_apply_presets,
"on_digit_entry_changed": self.on_digit_entry_changed,
"on_view_popup_menu": self.on_view_popup_menu,
"on_theme_changed": self.on_theme_changed,
"on_theme_add": self.on_theme_add,
"on_theme_remove": self.on_theme_remove,
"on_icon_theme_changed": self.on_icon_theme_changed,
"on_icon_theme_add": self.on_icon_theme_add,
"on_icon_theme_remove": self.on_icon_theme_remove}
# Settings
self._ext_settings = settings
self._settings = Settings(settings.settings)
self._profiles = self._settings.profiles
self._s_type = self._settings.setting_type
builder = Gtk.Builder()
builder.add_from_file(UI_RESOURCES_PATH + "settings_dialog.glade")
@@ -80,6 +100,9 @@ class SettingsDialog:
self._picons_field = builder.get_object("picons_field")
self._picons_dir_field = builder.get_object("picons_dir_field")
self._backup_dir_field = builder.get_object("backup_dir_field")
self._default_data_dir_field = builder.get_object("default_data_dir_field")
self._record_data_dir_field = builder.get_object("record_data_dir_field")
self._default_data_paths_switch = builder.get_object("default_data_paths_switch")
# Info bar
self._info_bar = builder.get_object("info_bar")
self._message_label = builder.get_object("info_bar_message_label")
@@ -88,6 +111,27 @@ class SettingsDialog:
self._enigma_radio_button = builder.get_object("enigma_radio_button")
self._neutrino_radio_button = builder.get_object("neutrino_radio_button")
self._support_ver5_switch = builder.get_object("support_ver5_switch")
self._force_bq_name_switch = builder.get_object("force_bq_name_switch")
# Streaming
header_separator = builder.get_object("header_separator")
self._apply_presets_button = builder.get_object("apply_presets_button")
self._transcoding_switch = builder.get_object("transcoding_switch")
self._edit_preset_switch = builder.get_object("edit_preset_switch")
self._presets_combo_box = builder.get_object("presets_combo_box")
self._video_bitrate_field = builder.get_object("video_bitrate_field")
self._video_width_field = builder.get_object("video_width_field")
self._video_height_field = builder.get_object("video_height_field")
self._audio_bitrate_field = builder.get_object("audio_bitrate_field")
self._audio_channels_combo_box = builder.get_object("audio_channels_combo_box")
self._audio_sample_rate_combo_box = builder.get_object("audio_sample_rate_combo_box")
self._apply_presets_button.bind_property("visible", header_separator, "visible")
self._transcoding_switch.bind_property("active", builder.get_object("record_box"), "sensitive")
self._edit_preset_switch.bind_property("active", self._apply_presets_button, "sensitive")
self._edit_preset_switch.bind_property("active", builder.get_object("video_options_frame"), "sensitive")
self._edit_preset_switch.bind_property("active", builder.get_object("audio_options_frame"), "sensitive")
self._play_in_built_radio_button = builder.get_object("play_in_built_radio_button")
self._play_in_vlc_radio_button = builder.get_object("play_in_vlc_radio_button")
self._get_m3u_radio_button = builder.get_object("get_m3u_radio_button")
# Program
self._before_save_switch = builder.get_object("before_save_switch")
self._before_downloading_switch = builder.get_object("before_downloading_switch")
@@ -98,6 +142,9 @@ class SettingsDialog:
self._new_color_button = builder.get_object("new_color_button")
self._extra_color_button = builder.get_object("extra_color_button")
self._load_on_startup_switch = builder.get_object("load_on_startup_switch")
self._bouquet_hints_switch = builder.get_object("bouquet_hints_switch")
self._services_hints_switch = builder.get_object("services_hints_switch")
self._lang_combo_box = builder.get_object("lang_combo_box")
# HTTP API
self._support_http_api_switch = builder.get_object("support_http_api_switch")
self._enable_y_dl_switch = builder.get_object("enable_y_dl_switch")
@@ -112,23 +159,37 @@ class SettingsDialog:
self._click_mode_zap_button.bind_property("sensitive", self._enable_send_to_switch, "sensitive")
self._enable_send_to_switch.bind_property("sensitive", builder.get_object("enable_send_to_label"), "sensitive")
self._extra_support_grid.bind_property("sensitive", builder.get_object("v5_support_grid"), "sensitive")
self._extra_support_grid.bind_property("sensitive", builder.get_object("bq_naming_grid"), "sensitive")
# Profiles
self._profile_view = builder.get_object("profile_tree_view")
self._profile_add_button = builder.get_object("profile_add_button")
self._profile_remove_button = builder.get_object("profile_remove_button")
self._apply_profile_button = builder.get_object("apply_profile_button")
self._apply_profile_button.bind_property("visible", builder.get_object("header_separator"), "visible")
# Language
self._lang_combo_box = builder.get_object("lang_combo_box")
# Settings
self._ext_settings = settings
self._settings = Settings(settings.settings)
self._profiles = self._settings.profiles
self._s_type = self._settings.setting_type
self.set_settings()
self._apply_profile_button.bind_property("visible", header_separator, "visible")
self._apply_profile_button.bind_property("visible", builder.get_object("reset_button"), "visible")
# Style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
self._digit_elems = (self._port_field, self._http_port_field, self._telnet_port_field, self._video_width_field,
self._video_bitrate_field, self._video_height_field, self._audio_bitrate_field)
for el in self._digit_elems:
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
Gtk.STYLE_PROVIDER_PRIORITY_USER)
self.init_ui_elements(self._s_type)
self.init_profiles()
if self._settings.is_darwin:
# Appearance
self._appearance_box = builder.get_object("appearance_box")
self._appearance_box.set_visible(True)
self._theme_thumbnail_image = builder.get_object("theme_thumbnail_image")
self._theme_combo_box = builder.get_object("theme_combo_box")
self._icon_theme_combo_box = builder.get_object("icon_theme_combo_box")
self._themes_support_switch = builder.get_object("themes_support_switch")
self._themes_support_switch.bind_property("active", builder.get_object("gtk_theme_frame"), "sensitive")
self._themes_support_switch.bind_property("active", builder.get_object("icon_theme_frame"), "sensitive")
self.init_appearance()
@run_idle
def init_ui_elements(self, s_type):
is_enigma_profile = s_type is SettingsType.ENIGMA_2
@@ -139,14 +200,18 @@ class SettingsDialog:
self._extra_support_grid.set_sensitive(is_enigma_profile)
http_active = self._support_http_api_switch.get_active()
self._click_mode_zap_button.set_sensitive(is_enigma_profile and http_active)
self._lang_combo_box.set_active_id(self._settings.language)
self._lang_combo_box.set_active_id(self._ext_settings.language)
self.on_info_bar_close() if is_enigma_profile else self.show_info_message(
"The Neutrino has only experimental support. Not all features are supported!", Gtk.MessageType.WARNING)
def init_profiles(self):
p_def = self._settings.default_profile
for p in self._profiles:
self._profile_view.get_model().append((p, DEFAULT_ICON if p == p_def else None))
model = self._profile_view.get_model()
for ind, p in enumerate(self._profiles):
icon = DEFAULT_ICON if p == p_def else None
model.append((p, icon))
if icon:
scroll_to(ind, self._profile_view)
self._profile_remove_button.set_sensitive(len(self._profile_view.get_model()) > 1)
def update_header_bar(self):
@@ -170,17 +235,19 @@ class SettingsDialog:
update_entry_data(entry, self._dialog, self._settings)
def on_settings_type_changed(self, item):
profile = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
self._s_type = profile
self._settings.setting_type = profile
self.on_reset()
self.init_ui_elements(profile)
s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
if s_type is not self._s_type:
self._settings.setting_type = s_type
self._s_type = s_type
self.on_reset()
self.init_ui_elements(s_type)
def on_reset(self, item=None):
self._settings.reset()
self.set_settings()
def set_settings(self):
self._s_type = self._settings.setting_type
self._host_field.set_text(self._settings.host)
self._port_field.set_text(self._settings.port)
self._login_field.set_text(self._settings.user)
@@ -200,13 +267,23 @@ class SettingsDialog:
self._data_dir_field.set_text(self._settings.data_local_path)
self._picons_dir_field.set_text(self._settings.picons_local_path)
self._backup_dir_field.set_text(self._settings.backup_local_path)
self._default_data_dir_field.set_text(self._settings.default_data_path)
self._record_data_dir_field.set_text(self._settings.records_path)
self._before_save_switch.set_active(self._settings.backup_before_save)
self._before_downloading_switch.set_active(self._settings.backup_before_downloading)
self.set_fav_click_mode(self._settings.fav_click_mode)
self.set_play_stream_mode(self._settings.play_streams_mode)
self._load_on_startup_switch.set_active(self._settings.load_last_config)
self._bouquet_hints_switch.set_active(self._settings.show_bq_hints)
self._services_hints_switch.set_active(self._settings.show_srv_hints)
self._default_data_paths_switch.set_active(self._settings.profile_folder_is_default)
self._transcoding_switch.set_active(self._settings.activate_transcoding)
self._presets_combo_box.set_active_id(self._settings.active_preset)
self.on_transcoding_preset_changed(self._presets_combo_box)
if self._s_type is SettingsType.ENIGMA_2:
self._support_ver5_switch.set_active(self._settings.v5_support)
self._force_bq_name_switch.set_active(self._settings.force_bq_names)
self._support_http_api_switch.set_active(self._settings.http_api_support)
self._enable_y_dl_switch.set_active(self._settings.enable_yt_dl)
self._enable_send_to_switch.set_active(self._settings.enable_send_to)
@@ -218,7 +295,16 @@ class SettingsDialog:
self._new_color_button.set_rgba(new_rgb)
self._extra_color_button.set_rgba(extra_rgb)
if self._s_type is SettingsType.ENIGMA_2:
self._enigma_radio_button.activate()
else:
self._neutrino_radio_button.activate()
def on_apply_profile_settings(self, item):
if not self.is_data_correct(self._digit_elems):
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
self._s_type = SettingsType.ENIGMA_2 if self._enigma_radio_button.get_active() else SettingsType.NEUTRINO_MP
self._settings.setting_type = self._s_type
self._settings.host = self._host_field.get_text()
@@ -249,14 +335,28 @@ class SettingsDialog:
self._ext_settings.backup_before_save = self._before_save_switch.get_active()
self._ext_settings.backup_before_downloading = self._before_downloading_switch.get_active()
self._ext_settings.fav_click_mode = self.get_fav_click_mode()
self._ext_settings.play_streams_mode = self.get_play_stream_mode()
self._ext_settings.language = self._lang_combo_box.get_active_id()
self._ext_settings.load_last_config = self._load_on_startup_switch.get_active()
self._ext_settings.show_bq_hints = self._bouquet_hints_switch.get_active()
self._ext_settings.show_srv_hints = self._services_hints_switch.get_active()
self._ext_settings.profile_folder_is_default = self._default_data_paths_switch.get_active()
self._ext_settings.default_data_path = self._default_data_dir_field.get_text()
self._ext_settings.records_path = self._record_data_dir_field.get_text()
self._ext_settings.activate_transcoding = self._transcoding_switch.get_active()
self._ext_settings.active_preset = self._presets_combo_box.get_active_id()
if self._ext_settings.is_darwin:
self._ext_settings.is_themes_support = self._themes_support_switch.get_active()
self._ext_settings.theme = self._theme_combo_box.get_active_id()
self._ext_settings.icon_theme = self._icon_theme_combo_box.get_active_id()
if self._s_type is SettingsType.ENIGMA_2:
self._ext_settings.use_colors = self._set_color_switch.get_active()
self._ext_settings.new_color = self._new_color_button.get_rgba().to_string()
self._ext_settings.extra_color = self._extra_color_button.get_rgba().to_string()
self._ext_settings.v5_support = self._support_ver5_switch.get_active()
self._ext_settings.force_bq_names = self._force_bq_name_switch.get_active()
self._ext_settings.http_api_support = self._support_http_api_switch.get_active()
self._ext_settings.enable_yt_dl = self._enable_y_dl_switch.get_active()
self._ext_settings.enable_send_to = self._enable_send_to_switch.get_active()
@@ -316,7 +416,7 @@ class SettingsDialog:
def show_info_message(self, text, message_type):
self._info_bar.set_visible(True)
self._info_bar.set_message_type(message_type)
self._message_label.set_text(text)
self._message_label.set_text(get_message(text))
@run_idle
def show_spinner(self, show):
@@ -326,21 +426,34 @@ class SettingsDialog:
def on_info_bar_close(self, bar=None, resp=None):
self._info_bar.set_visible(False)
def on_set_color_switch_state(self, switch, state):
def on_set_color_switch(self, switch, state):
self._colors_grid.set_sensitive(state)
def on_http_mode_switch_state(self, switch, state):
def on_http_mode_switch(self, switch, state):
self._click_mode_zap_button.set_sensitive(state)
if any((self._click_mode_play_button.get_active(),
self._click_mode_zap_button.get_active(),
self._click_mode_zap_and_play_button.get_active())):
self._click_mode_disabled_button.set_active(True)
def on_yt_dl_switch_state(self, switch, state):
def on_force_bq_name(self, switch, state):
if self._main_stack.get_visible_child_name() != "extra":
return
if state:
msg = "Some images may have problems displaying the favorites list!"
self.show_info_message(msg, Gtk.MessageType.WARNING)
else:
self.on_info_bar_close()
def on_yt_dl_switch(self, switch, state):
self.show_info_message("Not implemented yet!", Gtk.MessageType.WARNING)
def on_send_to_switch_state(self, switch, state):
self.show_info_message("Not implemented yet!", Gtk.MessageType.WARNING)
def on_default_path_mode_switch(self, switch, state):
self._settings.profile_folder_is_default = state
def on_default_data_path_changed(self, entry):
self._settings.default_data_path = entry.get_text()
def on_profile_add(self, item):
model = self._profile_view.get_model()
@@ -354,10 +467,6 @@ class SettingsDialog:
model.append((name, None))
scroll_to(len(model) - 1, self._profile_view)
self.on_profile_selected(self._profile_view)
p = name + "/"
self._settings.data_local_path += p
self._settings.picons_local_path += p
self._settings.backup_local_path += p
self.on_reset()
def on_profile_edit(self, item=None):
@@ -379,25 +488,30 @@ class SettingsDialog:
self._profile_remove_button.set_sensitive(len(model) > 1)
def on_profile_edited(self, render, path, new_value):
p_name = render.get_property("text")
p_name = self._profiles.pop(p_name, None)
if p_name:
row = self._profile_view.get_model()[path]
row[0] = new_value
self._profiles[new_value] = p_name
row = self._profile_view.get_model()[path]
old_name = row[0]
if old_name == new_value:
return
if p_name != new_value:
self.update_local_paths(new_value)
if new_value in self._profiles:
show_dialog(DialogType.ERROR, self._dialog, "A profile with that name exists!")
return
p_settings = self._profiles.pop(old_name, None)
if p_settings:
row[0] = new_value
self._profiles[new_value] = p_settings
self.update_local_paths(new_value, old_name)
self.on_profile_selected(self._profile_view)
def update_local_paths(self, p_name, force_rename=False):
def update_local_paths(self, p_name, old_name, force_rename=False):
data_path = self._settings.data_local_path
picons_path = self._settings.picons_local_path
backup_path = self._settings.backup_local_path
self._settings.data_local_path = "{}/{}/".format(Path(data_path).parent, p_name)
self._settings.picons_local_path = "{}/{}/".format(Path(picons_path).parent, p_name)
self._settings.backup_local_path = "{}/{}/".format(Path(backup_path).parent, p_name)
self._settings.data_local_path = p_name.join(data_path.rsplit(old_name, 1))
self._settings.picons_local_path = p_name.join(picons_path.rsplit(old_name, 1))
self._settings.backup_local_path = p_name.join(backup_path.rsplit(old_name, 1))
if force_rename:
try:
@@ -415,10 +529,6 @@ class SettingsDialog:
if paths:
profile = model.get_value(model.get_iter(paths), 0)
self._settings.current_profile = profile
if self._settings.setting_type is SettingsType.ENIGMA_2:
self._enigma_radio_button.activate()
else:
self._neutrino_radio_button.activate()
self.set_settings()
def on_profile_set_default(self, item):
@@ -437,7 +547,9 @@ class SettingsDialog:
self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)
def on_main_settings_visible(self, stack, param):
self._apply_profile_button.set_visible(stack.get_visible_child_name() == "profiles")
name = stack.get_visible_child_name()
self._apply_profile_button.set_visible(name == "profiles")
self._apply_presets_button.set_visible(name == "streaming")
def on_network_settings_visible(self, stack, param):
self._http_use_ssl_check_button.set_visible(Property(stack.get_visible_child_name()) is Property.HTTP)
@@ -480,10 +592,179 @@ class SettingsDialog:
return FavClickMode.DISABLED
def on_play_mode_changed(self, button):
if self._main_stack.get_visible_child_name() != "streaming":
return
if button.get_active():
self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)
@run_idle
def set_play_stream_mode(self, mode):
self._play_in_built_radio_button.set_sensitive(not self._settings.is_darwin)
self._play_in_built_radio_button.set_active(mode is PlayStreamsMode.BUILT_IN)
self._play_in_vlc_radio_button.set_active(mode is PlayStreamsMode.VLC)
self._get_m3u_radio_button.set_active(mode is PlayStreamsMode.M3U)
def get_play_stream_mode(self):
if self._play_in_built_radio_button.get_active():
return PlayStreamsMode.BUILT_IN
if self._play_in_vlc_radio_button.get_active():
return PlayStreamsMode.VLC
if self._get_m3u_radio_button.get_active():
return PlayStreamsMode.M3U
return self._settings.play_streams_mode
def on_transcoding_preset_changed(self, button):
presets = self._settings.transcoding_presets
prs = presets.get(button.get_active_id())
self._video_bitrate_field.set_text(prs.get("vb", "0"))
self._video_width_field.set_text(prs.get("width", "0"))
self._video_height_field.set_text(prs.get("height", "0"))
self._audio_bitrate_field.set_text(prs.get("ab", "0"))
self._audio_channels_combo_box.set_active_id(prs.get("channels", "2"))
self._audio_sample_rate_combo_box.set_active_id(prs.get("samplerate", "44100"))
def on_apply_presets(self, item):
if not self.is_data_correct(self._digit_elems):
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
return
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
return
presets = self._settings.transcoding_presets
prs = presets.get(self._presets_combo_box.get_active_id())
prs["vb"] = self._video_bitrate_field.get_text()
prs["width"] = self._video_width_field.get_text()
prs["height"] = self._video_height_field.get_text()
prs["ab"] = self._audio_bitrate_field.get_text()
prs["channels"] = self._audio_channels_combo_box.get_active_id()
prs["samplerate"] = self._audio_sample_rate_combo_box.get_active_id()
self._ext_settings.transcoding_presets = presets
self._edit_preset_switch.set_active(False)
def on_digit_entry_changed(self, entry):
if self._DIGIT_PATTERN.search(entry.get_text()):
entry.set_name(self._DIGIT_ENTRY_NAME)
else:
entry.set_name("GtkEntry")
def is_data_correct(self, elems):
return not any(elem.get_name() == self._DIGIT_ENTRY_NAME for elem in elems)
def on_view_popup_menu(self, menu, event):
if event.get_event_type() == Gdk.EventType.BUTTON_PRESS and event.button == Gdk.BUTTON_SECONDARY:
menu.popup(None, None, None, None, event.button, event.time)
def on_theme_changed(self, button):
if self._main_stack.get_visible_child_name() != "appearance":
return
self.set_theme_thumbnail_image(button.get_active_id())
self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)
@run_idle
def set_theme_thumbnail_image(self, theme_name):
img_path = "{}{}/gtk-3.0/thumbnail.png".format(self._ext_settings.themes_path, theme_name)
self._theme_thumbnail_image.set_from_pixbuf(get_picon_pixbuf(img_path, 96))
def on_theme_add(self, button):
self.add_theme(self._ext_settings.themes_path, self._theme_combo_box)
def on_theme_remove(self, button):
self.remove_theme(self._theme_combo_box, self._ext_settings.themes_path)
def on_icon_theme_changed(self, button, state=False):
if self._main_stack.get_visible_child_name() != "appearance":
return
self.show_info_message("Save and restart the program to apply the settings.", Gtk.MessageType.WARNING)
def on_icon_theme_add(self, button):
self.add_theme(self._ext_settings.icon_themes_path, self._icon_theme_combo_box)
def on_icon_theme_remove(self, button):
self.remove_theme(self._icon_theme_combo_box, self._ext_settings.icon_themes_path)
@run_idle
def add_theme(self, path, button):
response = get_chooser_dialog(self._dialog, self._settings, "Themes Archive [*.xz, *.zip]", ("*.xz", "*.zip"))
if response in (Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT):
return
self._appearance_box.set_sensitive(False)
self.unpack_theme(response, path, button)
@run_task
def unpack_theme(self, src, dst, button):
try:
from shutil import unpack_archive
unpack_archive(src, dst)
except (KeyError, EOFError) as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
self.update_theme_button(button, dst)
finally:
self._appearance_box.set_sensitive(True)
@run_idle
def update_theme_button(self, button, dst):
exist = set(os.listdir(dst))
current = {r[0] for r in button.get_model()}
added = exist - current
if added:
theme = added.pop()
if theme not in current:
button.append(theme, theme)
button.set_active_id(theme)
self.show_info_message("Done!", Gtk.MessageType.INFO)
@run_idle
def remove_theme(self, button, path):
theme = button.get_active_id()
if not theme:
self.show_info_message("No selected item!", Gtk.MessageType.ERROR)
return
if show_dialog(DialogType.QUESTION, self._dialog) != Gtk.ResponseType.OK:
return
from shutil import rmtree
try:
rmtree(path + theme, ignore_errors=True)
except OSError as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
else:
button.remove(button.get_active())
button.set_active(0)
@run_idle
def init_appearance(self):
t_support = self._ext_settings.is_themes_support
self._themes_support_switch.set_active(t_support)
if t_support:
# GTK
try:
for t in os.listdir(self._ext_settings.themes_path):
self._theme_combo_box.append(t, t)
self._theme_combo_box.set_active_id(self._ext_settings.theme)
self.set_theme_thumbnail_image(self._ext_settings.theme)
except FileNotFoundError:
pass
except PermissionError as e:
log("{}".format(e))
# Icons
try:
for t in os.listdir(self._ext_settings.icon_themes_path):
self._icon_theme_combo_box.append(t, t)
self._icon_theme_combo_box.set_active_id(self._ext_settings.icon_theme)
except FileNotFoundError:
pass
except PermissionError as e:
log("{}".format(e))
if __name__ == "__main__":
pass

View File

@@ -1,8 +1,26 @@
#digit-entry {
border-color: Red;
border-color: Red;
}
#status-bar-button {
padding: 1px;
margin: 1px;
}
.group {}
.group :first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.group :last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.group :not(:first-child):not(:last-child) {
border-radius: 0;
border-left-width: 0;
border-right-width: 0;
}

View File

@@ -3,7 +3,7 @@
The MIT License (MIT)
Copyright (c) 2018-2019 Dmitriy Yefremov
Copyright (c) 2018-2020 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
@@ -26,20 +26,19 @@ THE SOFTWARE.
Author: Dmitriy Yefremov
-->
<interface>
<interface domain="demon-editor">
<requires lib="gtk+" version="3.16"/>
<!-- interface-css-provider-path style.css -->
<!-- interface-license-type mit -->
<!-- interface-name DemonEditor -->
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
<!-- interface-authors Dmitriy Yefremov -->
<object class="GtkWindow" id="main_window">
<property name="can_focus">False</property>
<property name="resizable">False</property>
<property name="window_position">mouse</property>
<property name="destroy_with_parent">True</property>
<property name="type_hint">splashscreen</property>
<property name="skip_taskbar_hint">True</property>
<property name="skip_pager_hint">True</property>
<property name="decorated">False</property>
@@ -49,20 +48,29 @@ Author: Dmitriy Yefremov
<placeholder/>
</child>
<child>
<object class="GtkBox" id="main_box">
<object class="GtkBox" id="tool_bar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">1</property>
<child>
<object class="GtkEntry" id="url_entry">
<object class="GtkButton" id="previous_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="primary_icon_stock">gtk-dnd-multiple</property>
<signal name="drag-data-received" handler="on_drag_data_received" swapped="no"/>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Previous stream in the list</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_left">1</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<signal name="clicked" handler="on_previous" swapped="no"/>
<child>
<object class="GtkImage" id="previous_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-previous</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
@@ -70,31 +78,156 @@ Author: Dmitriy Yefremov
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="next_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Next stream in the list</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<signal name="clicked" handler="on_next" swapped="no"/>
<child>
<object class="GtkImage" id="next_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-next</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="url_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Drag or paste the link here</property>
<property name="margin_left">2</property>
<property name="margin_right">2</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<property name="primary_icon_stock">gtk-paste</property>
<signal name="activate" handler="on_url_activate" swapped="no"/>
<signal name="changed" handler="on_url_changed" swapped="no"/>
<signal name="drag-data-received" handler="on_drag_data_received" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="play_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Play</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<signal name="clicked" handler="on_play" swapped="no"/>
<child>
<object class="GtkImage" id="play_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-play</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkButton" id="stop_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Stop playback</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<signal name="clicked" handler="on_stop" swapped="no"/>
<child>
<object class="GtkImage" id="stop_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-media-stop</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkButton" id="clear_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Remove added links in the playlist</property>
<property name="halign">center</property>
<property name="valign">center</property>
<property name="margin_right">1</property>
<property name="margin_top">1</property>
<property name="margin_bottom">1</property>
<signal name="clicked" handler="on_clear" swapped="no"/>
<child>
<object class="GtkImage" id="clear_button_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-clear</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
</packing>
</child>
<style>
<class name="primary-toolbar"/>
</style>
</object>
</child>
</object>
<object class="GtkStatusIcon" id="status_icon">
<property name="icon_name">insert-link</property>
<property name="has_tooltip">True</property>
<signal name="activate" handler="on_status_icon_activate" object="main_window" swapped="no"/>
<signal name="popup-menu" handler="on_popup_menu" object="staus_popup_menu" swapped="no"/>
<signal name="query-tooltip" handler="on_query_tooltip" swapped="no"/>
<object class="GtkImage" id="show_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon_name">view-restore</property>
</object>
<object class="GtkMenu" id="staus_popup_menu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="exit_menu_item">
<property name="label">gtk-quit</property>
<object class="GtkImageMenuItem" id="show_menu_item">
<property name="label" translatable="yes">Show/Hide</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_exit" swapped="no"/>
<property name="image">show_image</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_status_icon_activate" object="main_window" swapped="no"/>
</object>
</child>
</object>
<object class="GtkStatusIcon" id="status_icon">
<property name="icon_name">demon-editor</property>
<property name="has_tooltip">True</property>
<signal name="activate" handler="on_status_icon_activate" object="main_window" swapped="no"/>
<signal name="popup-menu" handler="on_popup_menu" object="staus_popup_menu" swapped="no"/>
</object>
</interface>

View File

@@ -1,31 +1,69 @@
from pathlib import Path
from urllib.parse import urlparse
import gi
from gi.repository import GLib
from app.commons import log
from app.connections import HttpRequestType
from app.tools.yt import YouTube
from app.ui.iptv import get_yt_icon
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH, TEXT_DOMAIN
from .uicommons import Gtk, Gdk, UI_RESOURCES_PATH
class LinksTransmitter:
""" The main class for the "send to" function.
It used for direct playback of media links by the enigma2 media player.
"""
__STREAM_PREFIX = "4097:0:1:0:0:0:0:0:0:0:"
def __init__(self, http_api, app_window):
handlers = {"on_popup_menu": self.on_popup_menu,
"on_status_icon_activate": self.on_status_icon_activate,
"on_query_tooltip": self.on_query_tooltip,
"on_url_changed": self.on_url_changed,
"on_url_activate": self.on_url_activate,
"on_drag_data_received": self.on_drag_data_received,
"on_exit": self.on_exit}
"on_previous": self.on_previous,
"on_next": self.on_next,
"on_stop": self.on_stop,
"on_clear": self.on_clear,
"on_play": self.on_play}
self._http_api = http_api
self._app_window = app_window
self._is_status_icon = True
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_from_file(UI_RESOURCES_PATH + "transmitter.glade")
builder.connect_signals(handlers)
self._tray = builder.get_object("status_icon")
self._main_window = builder.get_object("main_window")
self._url_entry = builder.get_object("url_entry")
self._tool_bar = builder.get_object("tool_bar")
self._popup_menu = builder.get_object("staus_popup_menu")
self._restore_menu_item = builder.get_object("restore_menu_item")
self._status_active = None
self._status_passive = None
try:
gi.require_version("AppIndicator3", "0.1")
from gi.repository import AppIndicator3
except (ImportError, ValueError) as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
self._tray = builder.get_object("status_icon")
else:
self._is_status_icon = False
self._status_active = AppIndicator3.IndicatorStatus.ACTIVE
self._status_passive = AppIndicator3.IndicatorStatus.PASSIVE
category = AppIndicator3.IndicatorCategory.APPLICATION_STATUS
path = Path(UI_RESOURCES_PATH + "/icons/hicolor/scalable/apps/demon-editor.svg")
path = str(path.resolve()) if path.is_file() else "demon-editor"
self._tray = AppIndicator3.Indicator.new("DemonEditor", path, category)
self._tray.set_status(self._status_active)
self._tray.set_secondary_activate_target(builder.get_object("show_menu_item"))
self._tray.set_menu(self._popup_menu)
style_provider = Gtk.CssProvider()
style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
@@ -33,7 +71,10 @@ class LinksTransmitter:
Gtk.STYLE_PROVIDER_PRIORITY_USER)
def show(self, show):
self._tray.set_visible(show)
if self._is_status_icon:
self._tray.set_visible(show)
elif self._status_active:
self._tray.set_status(self._status_active if show else self._status_passive)
if not show:
self.hide()
@@ -48,12 +89,12 @@ class LinksTransmitter:
window.hide() if visible else window.show()
self._app_window.present() if visible else self._app_window.iconify()
def on_query_tooltip(self, icon, g, x, y, tooltip: Gtk.Tooltip):
if self._main_window.get_visible() or not self._url_entry.get_text():
return False
def on_url_changed(self, entry):
entry.set_name("GtkEntry" if self.is_url(entry.get_text()) else "digit-entry")
tooltip.set_text(self._url_entry.get_text())
return True
def on_url_activate(self, entry):
gen = self.activate_url(entry.get_text())
GLib.idle_add(lambda: next(gen, False), priority=GLib.PRIORITY_LOW)
def on_drag_data_received(self, entry, drag_context, x, y, data, info, time):
url = data.get_text()
@@ -63,10 +104,10 @@ class LinksTransmitter:
def activate_url(self, url):
self._url_entry.set_name("GtkEntry")
result = urlparse(url)
self._url_entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
if result.scheme and result.netloc:
self._url_entry.set_sensitive(False)
if self.is_url(url):
self._tool_bar.set_sensitive(False)
yt_id = YouTube.get_yt_id(url)
yield True
@@ -77,22 +118,56 @@ class LinksTransmitter:
if links:
url = links[sorted(links, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
else:
self.on_play(links)
self.on_done(links)
return
else:
self._url_entry.set_icon_from_stock(Gtk.EntryIconPosition.SECONDARY, None)
self._http_api.send(HttpRequestType.PLAY, url, self.on_play)
self._http_api.send(HttpRequestType.PLAY, url, self.on_done, self.__STREAM_PREFIX)
yield True
def on_play(self, res):
def on_done(self, res):
""" Play callback """
GLib.idle_add(self._url_entry.set_sensitive, True)
res = res.get("e2state", None) if res else res
self._url_entry.set_name("GtkEntry" if res else "digit-entry")
GLib.idle_add(self._tool_bar.set_sensitive, True)
def on_exit(self, item=None):
self.show(False)
def on_previous(self, item):
self._http_api.send(HttpRequestType.PLAYER_PREV, None, self.on_done)
def on_next(self, item):
self._http_api.send(HttpRequestType.PLAYER_NEXT, None, self.on_done)
def on_play(self, item):
self._http_api.send(HttpRequestType.PLAYER_PLAY, None, self.on_done)
def on_stop(self, item):
self._http_api.send(HttpRequestType.PLAYER_STOP, None, self.on_done)
def on_clear(self, item):
""" Remove added links in the playlist. """
GLib.idle_add(self._tool_bar.set_sensitive, False)
self._http_api.send(HttpRequestType.PLAYER_LIST, None, self.clear_playlist)
def clear_playlist(self, res):
GLib.idle_add(self._tool_bar.set_sensitive, not res)
if "error_code" in res:
log("Error clearing playlist. There may be no http connection.")
self.on_done(res)
return
for ref in res:
GLib.idle_add(self._tool_bar.set_sensitive, False)
self._http_api.send(HttpRequestType.PLAYER_REMOVE,
ref.get("e2servicereference", ""),
self.on_done,
self.__STREAM_PREFIX)
@staticmethod
def is_url(text):
""" Simple url checking. """
result = urlparse(text)
return result.scheme and result.netloc
if __name__ == "__main__":

View File

@@ -1,20 +1,23 @@
import locale
import os
from enum import Enum, IntEnum
from functools import lru_cache
from app.settings import Settings, SettingsException, IS_DARWIN
import gi
from app.settings import Settings, SettingsException
gi.require_version('Gtk', '3.0')
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
from gi.repository import Gtk, Gdk
# path to *.glade files
# Setting mod mask for the keyboard depending on the platform.
MOD_MASK = Gdk.ModifierType.MOD2_MASK if IS_DARWIN else Gdk.ModifierType.CONTROL_MASK
# Path to *.glade files.
UI_RESOURCES_PATH = "app/ui/" if os.path.exists("app/ui/") else "/usr/share/demoneditor/app/ui/"
IS_GNOME_SESSION = int(bool(os.environ.get("GNOME_DESKTOP_SESSION_ID")))
# translation
# Translation.
TEXT_DOMAIN = "demon-editor"
try:
settings = Settings.get_instance()
except SettingsException:
@@ -24,6 +27,11 @@ else:
if UI_RESOURCES_PATH == "app/ui/":
locale.bindtextdomain(TEXT_DOMAIN, UI_RESOURCES_PATH + "lang")
if settings.is_themes_support:
st = Gtk.Settings().get_default()
st.set_property("gtk-theme-name", settings.theme)
st.set_property("gtk-icon-theme-name", settings.icon_theme)
theme = Gtk.IconTheme.get_default()
theme.append_search_path(UI_RESOURCES_PATH + "icons")
@@ -39,23 +47,35 @@ EPG_ICON = theme.load_icon("gtk-index", 16, 0) if theme.lookup_icon("gtk-index",
DEFAULT_ICON = theme.load_icon("emblem-default", 16, 0) if theme.lookup_icon("emblem-default", 16, 0) else None
@lru_cache(maxsize=1)
def get_yt_icon(icon_name, size=24):
""" Getting YouTube icon. If the icon is not found in the icon themes, the "Info" icon is returned by default! """
default_theme = Gtk.IconTheme.get_default()
if default_theme.has_icon(icon_name):
return default_theme.load_icon(icon_name, size, 0)
n_theme = Gtk.IconTheme.new()
import glob
for theme_name in map(os.path.basename, filter(os.path.isdir, glob.glob("/usr/share/icons/*"))):
n_theme.set_custom_theme(theme_name)
if n_theme.has_icon(icon_name):
return n_theme.load_icon(icon_name, size, 0)
return default_theme.load_icon("info", size, 0)
class KeyboardKey(Enum):
""" The raw(hardware) codes of the keyboard keys. """
Q = 24
E = 26
R = 27
T = 28
U = 30
O = 32
P = 33
S = 39
D = 40
H = 43
L = 46
F = 41
X = 53
C = 54
V = 55
B = 56
W = 25
Z = 52
INSERT = 118

View File

@@ -1,5 +1,5 @@
#!/bin/bash
VER="0.4.7_Pre-alpha"
VER="0.4.8_Pre-alpha"
B_PATH="dist/DemonEditor"
DEB_PATH="$B_PATH/usr/share/demoneditor"

View File

@@ -1,5 +1,5 @@
Package: demon-editor
Version: 0.4.7-Pre-alpha
Version: 0.4.8-Pre-alpha
Section: utils
Priority: optional
Architecture: all

Binary file not shown.

Binary file not shown.

View File

@@ -1,18 +1,18 @@
# Copyright (C) 2018-2019 Dmitriy Yefremov
# Copyright (C) 2018-2020 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#Charly, 2019.
#
# Charly, 2019.
# Dmitriy Yefremov, 2020.
msgid ""
msgstr ""
"Last-Translator: Charly\n"
"Last-Translator: Dmitriy Yefremov\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "translator-credits"
msgstr "Charly"
msgstr "Charly, Dmitriy Yefremov"
# Main
msgid "Service"
@@ -672,7 +672,7 @@ msgid "Disabled"
msgstr "Ausgeschaltet"
msgid "Enable ver. 5 support (experimental)"
msgstr "Ver.5 Unterstützung aktivieren (experimentell)"
msgstr "Lamedb ver. 5 Unterstützung aktivieren (experimentell)"
msgid "Enable HTTP API (experimental)"
msgstr "HTTP-API aktivieren (experimentell)"
@@ -684,4 +684,306 @@ msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Kanal wechseln und im Programm ansehen(Strg + W)"
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Wiedergabe von IPTV oder anderen Streams im Programm(Strg + P)"
msgstr "Wiedergabe von IPTV oder anderen Streams im Programm(Strg + P)"
msgid "Export to m3u"
msgstr "Export nach m3u"
msgid "EPG configuration"
msgstr "EPG Konfiguration"
msgid "Apply"
msgstr "Anwenden"
msgid "EPG source"
msgstr "EPG Quelle"
msgid "Service names source:"
msgstr "Quelle der Dienstnamen:"
msgid "Main service list"
msgstr "Hauptdienstliste"
msgid "XML file"
msgstr "XML-Datei"
msgid "Use web source"
msgstr "Web-Quelle verwenden"
msgid "Url to *.xml.gz file:"
msgstr "Url zur *.xml.gz Datei:"
msgid "Enable filtering"
msgstr "Filterung einschalten"
msgid "Filter by presence in the epg.dat file."
msgstr "Filtern nach dem Vorhandensein in der epg.dat Datei."
msgid "Paths to the epg.dat file:"
msgstr "Pfade zur epg.dat Datei:"
msgid "Local path:"
msgstr "Local path:"
msgid "STB path:"
msgstr "STB-Pfad:"
msgid "Update on start"
msgstr "Update beim Start"
msgid "Auto configuration by service names."
msgstr "Automatische Konfiguration nach Dienstnamen."
msgid "Save list to xml."
msgstr "Liste in XML speichern."
msgid "Download XML file error."
msgstr "Fehler beim Herunterladen der XML-Datei."
msgid "Unsupported file type:"
msgstr "Nicht unterstützter Dateityp:"
msgid "Unpacking data error."
msgstr "Fehler beim Entpacken von Daten."
msgid "XML parsing error:"
msgstr "XML Parsing-Fehler:"
msgid "Count of successfully configured services:"
msgstr "Anzahl der erfolgreich konfigurierten Dienste:"
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
msgstr "Die aktuelle epg.dat Datei enthält keine Referenzen für die Dienste dieses Bouquets!"
msgid "Use HTTP"
msgstr "HTTP verwenden"
msgid "Close playback"
msgstr "Wiedergabe schliessen"
msgid "Import YouTube playlist"
msgstr "YouTube-Wiedergabeliste importieren"
msgid ""
"Found a link to the YouTube resource!\n"
"Try to get a direct link to the video?"
msgstr ""
"Ich habe einen Link zur YouTube-Ressource gefunden!\n"
"Versuchen einen direkten Link zum Video zu bekommen?"
msgid "Playlist import"
msgstr "Playlist-Import"
msgid "Getting link error:"
msgstr "Link-Fehler erhalten:"
msgid "Extra"
msgstr "Extra"
msgid "Apply profile settings"
msgstr "Profileinstellungen anwenden"
msgid "Settings type:"
msgstr "Art der Einstellungen:"
msgid "Set default"
msgstr "Standard setzen"
msgid "Language:"
msgstr "Sprache:"
msgid "Load the last open configuration at program startup"
msgstr "Laden der zuletzt geöffneten Konfiguration beim Programmstart"
msgid "Enable direct playback bar (experimental)"
msgstr "Aktivieren der direkten Wiedergabeleiste (experimentell)"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Ermöglicht das direkte Senden und Abspielen von Medienlinks auf dem Box"
msgid "Watch the channel in the program"
msgstr "Gucken den Kanal im Programm an"
msgid "Zap and Play"
msgstr "Zap und Abspielen"
msgid "Drag or paste the link here"
msgstr "Ziehe den Link hierher oder füge ihn ein"
msgid "Remove added links in the playlist"
msgstr "Hinzugefügte Links in der Wiedergabeliste entfernen"
msgid "A bouquet with that name exists!"
msgstr "Bouquet mit diesem Namen existiert!"
msgid "Details"
msgstr "Details"
msgid "Profile"
msgstr "Profil"
msgid "Reset"
msgstr "Reset"
msgid "File"
msgstr "Ablage"
msgid "Picons manager"
msgstr "Picons-Manager"
msgid "Explorer"
msgstr ""
msgid "Satellite url:"
msgstr "Satellit URL:"
msgid "Cut"
msgstr "Ausschneiden"
msgid "Paste"
msgstr "Einfügen"
msgid "To the top"
msgstr "Zum Anfang"
msgid "To the end"
msgstr "Zum Ende"
msgid "View"
msgstr "Darstellung"
msgid "Lock"
msgstr "Sperre"
msgid "Parent lock"
msgstr "Elternsperre"
msgid "Hide/Skip"
msgstr "Ausblenden/Überspringen"
msgid "IPTV tools"
msgstr "IPTV-Tools"
msgid "Make profile folder as default for the additional data"
msgstr "Profilordner als Standard für die zusätzlichen Daten festlegen"
msgid "Default data path:"
msgstr "Standard-Datenpfad:"
msgid "Streams record path:"
msgstr "Streams Aufnahmepfad:"
msgid "Record"
msgstr "Aufnahme"
msgid "Record:"
msgstr "Aufnahme:"
msgid "Record to disk:"
msgstr "Aufnahme auf Festplatte:"
msgid "Streaming"
msgstr "Streaming"
msgid "Activate transcoding"
msgstr "Aktivieren der Transkodierung"
msgid "Presets:"
msgstr "Voreinstellungen:"
msgid "Video options:"
msgstr "Video-Optionen:"
msgid "Audio options:"
msgstr "Audio-Optionen:"
msgid "Bitrate (kb/s):"
msgstr "Bitrate (kb/s):"
msgid "Codec:"
msgstr "Codec:"
msgid "Width (px):"
msgstr "Breite (px):"
msgid "Height (px):"
msgstr "Höhe (px):"
msgid "Channels:"
msgstr "Kanälen:"
msgid "Sample rate (Hz):"
msgstr "Samplerate (Hz):"
msgid "Play streams mode:"
msgstr "Streams Abspielen-Modus:"
msgid "Built-in player"
msgstr "Integrierter Player"
msgid "VLC media player"
msgstr "VLC Media Player"
msgid "Only get m3u file"
msgstr "Nur m3u-Datei erhalten"
msgid "Save and restart the program to apply the settings."
msgstr "Speicher und starte das Programm neu, um die Einstellungen zu übernehmen."
msgid "Some images may have problems displaying the favorites list!"
msgstr "Einige Images können Probleme mit der Anzeige der Favoritenliste haben!"
msgid "Operates in standby mode or current active transponder!"
msgstr "Arbeitet im Standby-Modus oder auf dem aktuell aktiven Transponder!"
msgid "No connection to the receiver!"
msgstr "Keine Verbindung zum Box!"
msgid "Signal level"
msgstr "Signalpegel"
msgid "Receiver info"
msgstr "Box-Info"
msgid "A profile with that name exists!"
msgstr "Ein Profil mit diesem Namen existiert!"
msgid "Show short info as hints in the main services list"
msgstr "Kurzinfos als Tooltips in der Hauptliste anzeigen"
msgid "Show detailed info as hints in the bouquet list"
msgstr "Detaillierteinfos als Tooltips in der Bouquetliste anzeigen"
msgid "Enable alternate bouquet file naming"
msgstr "Aktivieren der Alternativerbenennung für Bouquet-Dateien"
msgid "Allows you to name bouquet files using their names."
msgstr "Ermöglicht Bouquet-Dateien mit ihren Namen zu benennen."
msgid "Appearance"
msgstr "Aussehen"
msgid "Enable Themes support"
msgstr "Unterstützung von Themen aktivieren"
msgid "Gtk3 Theme:"
msgstr "Gtk3-Theme:"
msgid "Icon Theme:"
msgstr "Icon-Theme:"
msgid "Gtk3 Themes and Icons:"
msgstr "Gtk3 Themes and Icons:"
msgid "Deleting data..."
msgstr "Daten löschen..."
msgid "Download from the receiver"
msgstr "Downloaden vom Receiver"
msgid "Remove all picons from the receiver"
msgstr "Alle Picons aus dem Receiver entfernen"
msgid "Service reference"
msgstr "Kanalreferenz"

View File

@@ -486,7 +486,7 @@ msgid "STB file paths:"
msgstr "Rutas de ficheros del receptor:"
msgid "Local file paths:"
msgstr "Rutas de ficheros local:"
msgstr "Rutas de ficheros locales:"
# Dialogs messages
msgid "Error. No bouquet is selected!"
@@ -620,7 +620,7 @@ msgid "Before downloading from the receiver"
msgstr "Antes de recibir del receptor"
msgid "Set background color for the services"
msgstr "Determinar color de fondo de los servicios"
msgstr "Fijar color de fondo de los servicios"
msgid "Marked as new:"
msgstr "Marcado como nuevo:"
@@ -666,7 +666,7 @@ msgid "Test connection"
msgstr "Probar conexión"
msgid "Double click on the service in the bouquet list:"
msgstr "Haga doble clic en el servicio en la lista de bouquet:"
msgstr "Al hacer doble clic en el servicio en la lista de bouquet:"
msgid "Zap"
msgstr "Zapear"
@@ -771,10 +771,49 @@ msgid "Import YouTube playlist"
msgstr "Importar lista de reproducción de YouTube"
msgid "Found a link to the YouTube resource!\nTry to get a direct link to the video?"
msgstr "¡Encontrado enlace al recurso de YouTube!\n¿Intentar obtener un enlace directo al vídeo?"
msgstr "¡Se ha encontrado enlace al recurso de YouTube!\n¿Intentar obtener un enlace directo al vídeo?"
msgid "Playlist import"
msgstr "Importar lista de reproducción"
msgid "Getting link error:"
msgstr "Error en el enlace:"
msgid "Extra"
msgstr "Extra"
msgid "Apply profile settings"
msgstr "Aplicar ajustes de perfil"
msgid "Settings type:"
msgstr "Tipo de ajustes:"
msgid "Set default"
msgstr "Por defecto"
msgid "Language:"
msgstr "Idioma:"
msgid "Load the last open configuration at program startup"
msgstr "Cargar la última configuración abierta al iniciar el programa"
msgid "Enable direct playback bar (experimental)"
msgstr "Habilitar la barra de reproducción directa (experimental)"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Habilita el envío directo y la reproducción de enlaces de medios en el receptor"
msgid "Watch the channel in the program"
msgstr "Ver el canal en el programa"
msgid "Zap and Play"
msgstr "Zapear y reproducir"
msgid "Drag or paste the link here"
msgstr "Soltar o pegar en enlace aquí"
msgid "Remove added links in the playlist"
msgstr "Quitar los enlaces añadidos en la lista de reproducción"
msgid "A bouquet with that name exists!"
msgstr "¡Ya existe un bouquet con ese nombre!"

View File

@@ -1,7 +1,7 @@
# Copyright (C) 2018-2019 Frank Neirynck
# Copyright (C) 2018-2020 Frank Neirynck
# This file is distributed under the MIT license.
#
# Frank Neirynck <frank@insink.be>, 2018-2019.
# Frank Neirynck <frank@insink.be>, 2018-2020.
#
msgid ""
msgstr ""
@@ -10,11 +10,6 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Language-Team: \n"
"X-Generator: Poedit 2.2.1\n"
msgid "translator-credits"
msgstr "Frank Neirynck <frank@insink.be>"
@@ -776,4 +771,202 @@ msgid "Playlist import"
msgstr "Importeer playlist"
msgid "Getting link error:"
msgstr "Volgende Link error gekregen:"
msgstr "Volgende Link error gekregen:"
msgid "Extra"
msgstr "Extra"
msgid "Apply profile settings"
msgstr "Pas profiel settings toe"
msgid "Settings type:"
msgstr "Settings type:"
msgid "Set default"
msgstr "Stel standaard in"
msgid "Language:"
msgstr "Taal:"
msgid "Load the last open configuration at program startup"
msgstr "Laad de laatst geopende configuratie op bij opstart programma"
msgid "Enable direct playback bar (experimental)"
msgstr "Laat onmiddelijk playback bar toe (experimenteel)"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Laat rechtstreeks versturen van and playback en media links op de ontvanger toe"
msgid "Watch the channel in the program"
msgstr "Bekijk het kanaal in het programma"
msgid "Zap and Play"
msgstr "Zap en speel af"
msgid "Drag or paste the link here"
msgstr "Sleep of plak link naar hier"
msgid "Remove added links in the playlist"
msgstr "Verwijder toegevoegde links uit playlist"
msgid "A bouquet with that name exists!"
msgstr "Er bestaat al een boeket met deze naam!"
msgid "Details"
msgstr "Details"
msgid "Profile"
msgstr "Profiel"
msgid "Reset"
msgstr "Reset"
msgid "File"
msgstr "File"
msgid "Picons manager"
msgstr "Picons manager"
msgid "Explorer"
msgstr "Explorer"
msgid "Satellite url:"
msgstr "Satelliet url:"
msgid "Cut"
msgstr "Knip"
msgid "Paste"
msgstr "Plak"
msgid "To the top"
msgstr "Naar de top"
msgid "To the end"
msgstr "Naar het einde"
msgid "View"
msgstr "View"
msgid "Lock"
msgstr "Slot"
msgid "Parent lock"
msgstr "Ouderlijk Slot"
msgid "Hide/Skip"
msgstr "Verberg/Spring"
msgid "IPTV tools"
msgstr "IPTV instrumenten"
msgid "Make profile folder as default for the additional data"
msgstr "Maak profiel map standaard voor addionele data"
msgid "Default data path:"
msgstr "Standaard data pad:"
msgid "Streams record path:"
msgstr "Opnamepad streams:"
msgid "Record"
msgstr "Opnemen"
msgid "Record:"
msgstr "Opnemen:"
msgid "Record to disk:"
msgstr "Opnemen op schijf:"
msgid "Streaming"
msgstr "Streaming"
msgid "Activate transcoding"
msgstr "Activeer transcodering"
msgid "Presets:"
msgstr "Onstellingen:"
msgid "Video options:"
msgstr "Video opties:"
msgid "Audio options:"
msgstr "Audio opties:"
msgid "Bitrate (kb/s):"
msgstr "Bitrate (kb/s):"
msgid "Codec:"
msgstr "Codec:"
msgid "Width (px):"
msgstr "Breedte (px):"
msgid "Height (px):"
msgstr "Hoogte (px):"
msgid "Channels:"
msgstr "Kanalen:"
msgid "Sample rate (Hz):"
msgstr "Sample rate (Hz):"
msgid "Play streams mode:"
msgstr "Speel in streams mode:"
msgid "Built-in player"
msgstr "Ingebouwde speler"
msgid "VLC media player"
msgstr "VLC Media speler"
msgid "Only get m3u file"
msgstr "Enkel de m3u file ophalen"
msgid "Save and restart the program to apply the settings."
msgstr "Het programma opslaan en herstarten om settings toe te passen."
msgid "Some images may have problems displaying the favorites list!"
msgstr "Sommige afbeeldingen kunnen problemen opleveren bij vertonen in de favorietenlijst!"
msgid "Operates in standby mode or current active transponder!"
msgstr "Werkt in standby mode of op dehuiduge actieve transponder!"
msgid "No connection to the receiver!"
msgstr "Geen verbinding met de ontvanger!"
msgid "Signal level"
msgstr "Signaal niveau"
msgid "Receiver info"
msgstr "Informatie over de ontvanger"
msgid "A profile with that name exists!"
msgstr "Er bestaat al een profiel met die naam!"
msgid "Show short info as hints in the main services list"
msgstr "Toon korte info als hints in de hoofd servicelijst"
msgid "Show detailed info as hints in the bouquet list"
msgstr "Toon gedetalleerde info als hints in de boeket lijst"
msgid "Enable alternate bouquet file naming"
msgstr "Laat alternatieve boeket benamingen toe"
msgid "Allows you to name bouquet files using their names."
msgstr "Laat toe om boeket te noemen naar bestandsbenaming."
msgid "Appearance"
msgstr "Uitzicht"
msgid "Enable Themes support"
msgstr "Laat themaondersteuning toe"
msgid "Gtk3 Theme:"
msgstr "Gtk3 Thema:"
msgid "Icon Theme:"
msgstr "Icoon Thema:"
msgid "Gtk3 Themes and Icons:"
msgstr "Gtk3 en Icoon Themas:"

840
po/pl/demon-editor.po Executable file
View File

@@ -0,0 +1,840 @@
# Copyright (C) 2018-2019 Dmitriy Yefremov
# This file is distributed under the MIT license.
#
#
msgid ""
msgstr ""
"Last-Translator: wwns\n"
"Language: pl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "translator-credits"
msgstr "wwns"
# Main
msgid "File"
msgstr "Plik"
msgid "View"
msgstr "Widok"
msgid "Lock"
msgstr "Zablokuj"
msgid "Service"
msgstr "Serwis"
msgid "Package"
msgstr "Pakiet"
msgid "Type"
msgstr "Typ"
msgid "Picon"
msgstr "Pikon"
msgid "Freq"
msgstr "Freq"
msgid "Rate"
msgstr "Rate"
msgid "Pol"
msgstr "Pol"
msgid "System"
msgstr "System"
msgid "Pos"
msgstr "Pos"
msgid "Num"
msgstr "Num"
msgid "Current IP:"
msgstr "Adres IP:"
msgid "Assign"
msgstr "Przypisz"
msgid "Bouquet details"
msgstr "Bukiet szczegóły"
msgid "Bouquets"
msgstr "Bukiety"
msgid "Copy"
msgstr "Kopiuj"
msgid "Copy reference"
msgstr "Kopiuj odniesienie"
msgid "Download"
msgstr "Pobierz"
msgid "Edit"
msgstr "Edytuj"
msgid "Edit mаrker text"
msgstr "Edytuj tekst znacznika"
msgid "FTP-transfer"
msgstr "Transfer FTP"
msgid "Global search"
msgstr "Globalne wyszukiwanie"
msgid "Hide"
msgstr "Ukryj"
msgid "Hide/Skip On/Off Ctrl + H"
msgstr "Ukryj/Pomiń wł./wył Ctrl + H"
msgid "Add IPTV or stream service"
msgstr "Dodaj strumień IPTV"
msgid "Import m3u"
msgstr "Importuj m3u"
msgid "Import m3u file"
msgstr "Importuj plik m3u"
msgid "List configuration"
msgstr "Konfiguracja listy"
msgid "Rename for this bouquet"
msgstr "Zmień nazwę tego serwisu"
msgid "Set default name"
msgstr "Ustaw domyślną nazwę"
msgid "Insert marker"
msgstr "Wstaw znacznik"
msgid "Locate in services"
msgstr "Znajdź w usługach"
msgid "Locked"
msgstr "Zablokowany"
msgid "Move"
msgstr "Przenieś"
msgid "New"
msgstr "Nowy"
msgid "New bouquet"
msgstr "Nowy bukiet"
msgid "Create bouquet"
msgstr "Utwórz bukiet"
msgid "For current satellite"
msgstr "Dla bieżącego satelity"
msgid "For current package"
msgstr "Dla bieżącego pakietu"
msgid "For current type"
msgstr "Dla bieżącego typu"
msgid "For each satellite"
msgstr "Dla każdego satelity"
msgid "For each package"
msgstr "Dla każdego pakietu"
msgid "For each type"
msgstr "Dla każdego typu"
msgid "Open"
msgstr "Otwórz"
msgid "Parent lock On/Off Ctrl + L"
msgstr "Blokada rodzicielska wł./wył Ctrl + L"
msgid "Picons"
msgstr "Pikony"
msgid "Picons downloader"
msgstr "Pobieranie pikonów"
msgid "Satellites downloader"
msgstr "Pobierania satelitów"
msgid "Remove"
msgstr "Usuń"
msgid "Remove all unavailable"
msgstr "Usuń wszystkie niedostępne"
msgid "Satellites editor"
msgstr "Edytor satelitów"
msgid "Save"
msgstr "Zapisz"
msgid "Search"
msgstr "Szukaj"
msgid "Services"
msgstr "Kanały"
msgid "Services filter"
msgstr "Filtr kanałów"
msgid "Settings"
msgstr "Ustawienia"
msgid "Up"
msgstr "Góra"
msgid "Down"
msgstr "Dół"
msgid "Active profile:"
msgstr "Aktywny Profil:"
msgid "All"
msgstr "Wszystko"
msgid "Are you sure?"
msgstr "Czy na pewno?"
msgid "Current data path:"
msgstr "Aktualna ścieżka danych:"
msgid "Data:"
msgstr "Dane:"
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
msgstr "Edytor kanałów Enigma2 i listy satelitów dla GNU/Linux"
msgid "Host:"
msgstr "Host:"
msgid "Loading data..."
msgstr "Ładowanie danych…"
msgid "Receive"
msgstr "Pobierz"
msgid "Receive files from receiver"
msgstr "Pobieranie plików z odbiornika"
msgid "Receiver IP:"
msgstr "Odbiornik IP:"
msgid "Remove unused bouquets"
msgstr "Usuń nieużywany bouquet"
msgid "Reset profile"
msgstr "Reset profilu"
msgid "Satellites"
msgstr "Satelity"
msgid "Satellites.xml file:"
msgstr "Plik Satellites.xml:"
msgid "Selected"
msgstr "Wybrany"
msgid "Send"
msgstr "Wyślij"
msgid "Send files to receiver"
msgstr "Wysyłanie plików do odbiornika"
msgid "Services and Bouquets files:"
msgstr "Usługi i bukiety plików:"
msgid "User bouquet files:"
msgstr "Pliki bukietu użytkownika:"
msgid "Extra:"
msgstr "Dodatkowe"
# Filter bar
msgid "Only free"
msgstr "Tylko FTA"
msgid "All positions"
msgstr "Wszystkie pozycje"
msgid "All types"
msgstr "Wszystkie typy"
# Streams player
msgid "Play"
msgstr "Odtwarzaj"
msgid "Stop playback"
msgstr "Zatrzymaj odtwarzanie"
msgid "Previous stream in the list"
msgstr "Poprzedni strumień na liście"
msgid "Next stream in the list"
msgstr "Następny strumień na liście"
msgid "Toggle in fullscreen"
msgstr "Przełącz na pełny ekran"
msgid "Close"
msgstr "Zamknij"
# Picons dialog
msgid "Load providers"
msgstr "Załaduj dostawców"
msgid "Providers"
msgstr "Dostawca"
msgid "Receive picons"
msgstr "Pobierz pikony"
msgid "Picons name format:"
msgstr "Format nazw pikon:"
msgid "Resize:"
msgstr "Zmień rozmiar:"
msgid "Current picons path:"
msgstr "Aktualna ścieżka pikon:"
msgid "Receiver picons path:"
msgstr "Ścieżka pikon odbiornika:"
msgid "Picons download tool"
msgstr "Narzędzie pobierania pikon"
msgid "Transfer to receiver"
msgstr "Wyślij do odbiornika"
msgid "Downloader"
msgstr "Pobieranie"
msgid "Converter"
msgstr "Konwerter"
msgid "Convert"
msgstr "Konwertuj"
msgid "Path to save:"
msgstr "Zapisz do:"
msgid "Path to Enigma2 picons:"
msgstr "Ścieżka do pikon Enigma2:"
msgid "Specify the correct position value for the provider!"
msgstr "Podaj poprawną wartość pozycji dla dostawcy!"
msgid "Converter between name formats"
msgstr "Konwerter między formatami nazw"
msgid "Receive picons for providers"
msgstr "Pobierz pikony od nadawcy"
msgid "Load satellite providers."
msgstr "Załaduj dostawców satelitarnych."
msgid ""
"To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window."
msgstr ""
"Aby automatycznie ustawić identyfikatory pikon,\n"
"najpierw załaduj listę wymaganych usług do głównego okna aplikacji."
# Satellites editor
msgid "Satellites edit tool"
msgstr "Narzędzie do edycji satelitów"
msgid "Add"
msgstr "Dodaj"
msgid "Satellite"
msgstr "Satelita"
msgid "Transponder"
msgstr "Transponder"
msgid "Satellite properties:"
msgstr "Właściwości satelity:"
msgid "Transponder properties:"
msgstr "Właściwości transpondera:"
msgid "Name"
msgstr "Nazwa"
msgid "Position"
msgstr "Pozycja"
# Satellites update dialog
msgid "Satellites update"
msgstr "Aktualizacja satelitów"
msgid "Remove selection"
msgstr "Usuń wybrane"
# Service details dialog
msgid "Service data:"
msgstr "Dane usług:"
msgid "Transponder data:"
msgstr "Dane transpondera:"
msgid "Service data"
msgstr "Dane usług"
msgid "Transponder details"
msgstr "Szczegóły transpondera"
msgid ""
"Changes will be applied to all services of this transponder!\n"
"Continue?"
msgstr ""
"Zmiany zostaną zastosowane do wszystkich usług transpondera!\n"
"kontynuować?"
msgid "Reference"
msgstr "Odniesienie"
msgid "Namespace"
msgstr "Namespace"
msgid "Flags:"
msgstr "Flagi:"
msgid "Delays (ms):"
msgstr "Zwłoka (ms):"
msgid "Bitstream"
msgstr "Bitstream"
msgid "Description"
msgstr "Opis"
msgid "Source:"
msgstr "Źródło:"
msgid "Cancel"
msgstr "Anuluj"
msgid "Update"
msgstr "Uaktualnienie"
msgid "Filter"
msgstr "Filtr"
msgid "Find"
msgstr "Znajdź"
# IPTV dialog
msgid "Stream data"
msgstr "Przesyłanie danych"
# IPTV list configuration dialog
msgid "Starting values"
msgstr "Wartości początkowe"
msgid "Reset to default"
msgstr "Ustawienia domyślne"
msgid "IPTV streams list configuration"
msgstr "Konfiguracja listy strumieni IPTV"
# Settings dialog
msgid "Preferences"
msgstr "Preferencje"
msgid "Profile:"
msgstr "Profil:"
msgid "Timeout between commands in seconds"
msgstr "Limit czasu między poleceniami w sekundach"
msgid "Timeout:"
msgstr "Koniec czasu:"
msgid "Login:"
msgstr "Login:"
msgid "Options"
msgstr "Opcje"
msgid "Password:"
msgstr "Hasło:"
msgid "Picons:"
msgstr "Pikony:"
msgid "Port:"
msgstr "Port:"
msgid "Data path:"
msgstr "Ścieżka danych:"
msgid "Picons path:"
msgstr "Ścieżka pikon:"
msgid "Network settings:"
msgstr "Ustawienia sieci:"
msgid "STB file paths:"
msgstr "Ścieżki do plików w STB:"
msgid "Local file paths:"
msgstr "Lokalne ścieżki plików:"
# Dialogs messages
msgid "Error. No bouquet is selected!"
msgstr "Błąd. Nie wybrano żadnego bukietu!"
msgid "This item is not allowed to be removed!"
msgstr "Tego elementu nie można usunąć!"
msgid "This item is not allowed to edit!"
msgstr "Tego elementu nie można edytować!"
msgid "Not allowed in this context!"
msgstr "Niedozwolone w tym kontekście!"
msgid "Please, download files from receiver or setup your path for read data!"
msgstr "Pobierz pliki z odbiornika lub ustaw ścieżkę do odczytu danych!"
msgid "Reading data error!"
msgstr "Błąd odczytu danych!"
msgid "No m3u file is selected!"
msgstr "Nie wybrano pliku m3u!"
msgid "Not implemented yet!"
msgstr "Jeszcze niezaimplementowane!"
msgid "The text of marker is empty, please try again!"
msgstr "Tekst znacznika jest pusty, spróbuj ponownie!"
msgid "Please, select only one item!"
msgstr "Wybierz tylko jeden element!"
msgid "No png file is selected!"
msgstr "Nie wybrano pliku png!"
msgid "No reference is present!"
msgstr "Brak referencji!"
msgid "No selected item!"
msgstr "Brak wybranego elementu!"
msgid "The task is already running!"
msgstr "Zadanie już działa!"
msgid "Done!"
msgstr "Zrobione!"
msgid "Please, wait..."
msgstr "Proszę czekać…"
msgid "Resizing..."
msgstr "Zmiana rozmiaru…"
msgid "Select paths!"
msgstr "Wybierz ścieżki!"
msgid "No satellite is selected!"
msgstr "Nie wybrano satelity!"
msgid "Please, select only one satellite!"
msgstr "Wybierz tylko jednego satelitę!"
msgid "Please check your parameters and try again."
msgstr "Sprawdź parametry i spróbuj ponownie."
msgid "No satellites.xml file is selected!"
msgstr "Nie wybrano pliku satellites.xml!"
msgid "Error. Verify the data!"
msgstr "Błąd. Zweryfikuj dane!"
msgid "Operation not allowed in this context!"
msgstr "Operacja niedozwolona w tym kontekście!"
msgid "No VLC is found. Check that it is installed!"
msgstr "Nie znaleziono VLC. Sprawdź, czy jest zainstalowany!"
# Search unavailable streams dialog
msgid "Please wait, streams testing in progress..."
msgstr "Proszę czekać, trwa testowanie strumieni…"
msgid "Found"
msgstr "Znaleziono"
msgid "unavailable streams."
msgstr "niedostępne strumienie."
msgid "No changes required!"
msgstr "Nie wymaga zmian!"
msgid "This list does not contains IPTV streams!"
msgstr "Ta lista nie zawiera strumieni IPTV!"
msgid "New empty configuration"
msgstr "Nowa pusta konfiguracja"
msgid "No data to save!"
msgstr "Brak danych do zapisania!"
msgid "Network"
msgstr "Sieć"
msgid "Paths"
msgstr "Ścieżki"
msgid "Program"
msgstr "Program"
msgid "Backup:"
msgstr "Kopia:"
msgid "Backup"
msgstr "Kopia"
msgid "Backups"
msgstr "Kopie zapasowe"
msgid "Backup path:"
msgstr "Ścieżka kopii:"
msgid "Restore bouquets"
msgstr "Przywróć bukiety"
msgid "Restore all"
msgstr "Przywrócić wszystko"
msgid "Before saving"
msgstr "Przed zapisaniem"
msgid "Before downloading from the receiver"
msgstr "Przed pobraniem z odbiornika"
msgid "Set background color for the services"
msgstr "Ustaw kolor tła dla usług"
msgid "Marked as new:"
msgstr "Oznacz jako nowy:"
msgid "With an extra name in the bouquet:"
msgstr "Z dodatkową nazwą w bukiecie:"
msgid "Select"
msgstr "Wybierz"
msgid "About"
msgstr "Wersja"
msgid "Exit"
msgstr "Wyjście"
msgid "Tools"
msgstr "Narzędzia"
# Import
msgid "Import"
msgstr "Importuj"
msgid "Bouquet"
msgstr "Bukiet"
msgid "Bouquets and services"
msgstr "Bukiety i kanały"
msgid "The main list does not contain services for this bouquet!"
msgstr "Główna lista nie zawiera kanałów dla tego bukietu!"
msgid "No bouquet file is selected!"
msgstr "Nie wybrano pliku bukietu!"
msgid "Remove all unused"
msgstr "Usuń wszystkie nieużywane"
msgid "Test"
msgstr "Test"
msgid "Test connection"
msgstr "Testuj połączenie"
msgid "Double click on the service in the bouquet list:"
msgstr "Kliknij dwukrotnie usługę na liście bukietów:"
msgid "Zap"
msgstr "Przełącz"
msgid "Play stream"
msgstr "Odtwórz strumień"
msgid "Disabled"
msgstr "Wyłączone"
msgid "Enable ver. 5 support (experimental)"
msgstr "Włącz wer. 5 wsparcie (eksperymentalne)"
msgid "Enable HTTP API (experimental)"
msgstr "Włącz API HTTP (eksperymentalne)"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Przełącz(zap) kanał(Ctrl + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Przełącz kanał i oglądaj w programie(Ctrl + W)"
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Odtwórz IPTV lub inny strumień w programie(Ctrl + P)"
msgid "Export to m3u"
msgstr "Eksportuj do m3u"
msgid "EPG configuration"
msgstr "Koniguruj EPG"
msgid "Apply"
msgstr "Zatwierdź"
msgid "EPG source"
msgstr "Źródło EPG"
msgid "Service names source:"
msgstr "Źródło nazw usług:"
msgid "Main service list"
msgstr "Główna lista usług"
msgid "XML file"
msgstr "Plik XML"
msgid "Use web source"
msgstr "Użyj źródła internetowego"
msgid "Url to *.xml.gz file:"
msgstr "URL do pliku *.xml.gz:"
msgid "Enable filtering"
msgstr "Włącz filtrowanie"
msgid "Filter by presence in the epg.dat file."
msgstr "Filtruj według ustawień w pliku epg.dat."
msgid "Paths to the epg.dat file:"
msgstr "Ścieżka do pliku epg.dat:"
msgid "Local path:"
msgstr "Ścieżka lokalna:"
msgid "STB path:"
msgstr "Ścieżka STB:"
msgid "Update on start"
msgstr "Aktualizuj przy starcie"
msgid "Auto configuration by service names."
msgstr "Automatyczna konfiguracja serwisu według nazw."
msgid "Save list to xml."
msgstr "Zapisz listę do XML."
msgid "Download XML file error."
msgstr "Błąd pobierania pliku XML."
msgid "Unsupported file type:"
msgstr "Nieobsługiwany typ pliku:"
msgid "Unpacking data error."
msgstr "Błąd rozpakowywania danych."
msgid "XML parsing error:"
msgstr "Błąd analizy XML:"
msgid "Count of successfully configured services:"
msgstr "Liczba pomyślnie skonfigurowanych usług:"
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
msgstr "Bieżący plik epg.dat nie zawiera odniesień do usług tego bukietu!"
msgid "Use HTTP"
msgstr "Użyj HTTP"
msgid "Close playback"
msgstr "Zamknij odtwarzanie"
msgid "Import YouTube playlist"
msgstr "Importuj listę odtwarzania YouTube"
msgid ""
"Found a link to the YouTube resource!\n"
"Try to get a direct link to the video?"
msgstr ""
"Znaleziono link do zasobu YouTube!\n"
"Chcesz uzyskać bezpośredni link do filmu?"
msgid "Playlist import"
msgstr "Import listy odtwarzania"
msgid "Getting link error:"
msgstr "Błąd pobierania łącza:"
msgid "Extra"
msgstr "Ekstra"
msgid "Apply profile settings"
msgstr "Zastosuj ustawienia profilu"
msgid "Settings type:"
msgstr "Ustawienia dla:"
msgid "Set default"
msgstr "Uataw domyślnie"
msgid "Language:"
msgstr "Język:"
msgid "Load the last open configuration at program startup"
msgstr "Załaduj ostatnią otwartą konfigurację podczas uruchamiania programu"
msgid "Enable direct playback bar (experimental)"
msgstr "Włącz pasek bezpośredniego odtwarzania (eksperymentalnie)"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Umożliwia bezpośrednie wysyłanie i odtwarzanie łączy multimedialnych w odbiorniku"
msgid "Watch the channel in the program"
msgstr "Obejrzyj kanał w programie"
msgid "Receiver info"
msgstr "Informacje o odbiorniku"
msgid "Operates in standby mode or current active transponder!"
msgstr "Działa w trybie gotowości lub aktualnie jest aktywny transponder!"
msgid "Download picons from the receiver"
msgstr "Pobierz pikony z odbiornika"
msgid "Remove picons from the receiver"
msgstr "Usuń pikony z odbiornika"
msgid "Use http to reload data in the receiver."
msgstr "Użyj http aby ponownie załadować dane do odbiornika."
msgid "Zap and Play"
msgstr "Przełącz i Odtwórz"
msgid "Drag or paste the link here"
msgstr "Przeciągnij lub wklej tutaj link"
msgid "Remove added links in the playlist"
msgstr "Usuń dodane linki z listy odtwarzania"
msgid "A bouquet with that name exists!"
msgstr "Istnieje bukiet o tej nazwie!"

View File

@@ -663,7 +663,7 @@ msgid "Disabled"
msgstr "Выкл."
msgid "Enable ver. 5 support (experimental)"
msgstr "Включить поддержку вер. 5 (экспериментально)"
msgstr "Включить поддержку lamedb вер. 5 (экспериментально)"
msgid "Enable HTTP API (experimental)"
msgstr "Включить HTTP API (экспериментально)"
@@ -763,3 +763,214 @@ msgstr "Импорт плейлиста"
msgid "Getting link error:"
msgstr "Ошибка получения ссылки:"
msgid "Extra"
msgstr "Дополнительно"
msgid "Apply profile settings"
msgstr "Применить настройки профиля"
msgid "Settings type:"
msgstr "Тип настроек:"
msgid "Set default"
msgstr "Установить по умолчанию"
msgid "Language:"
msgstr "Язык:"
msgid "Load the last open configuration at program startup"
msgstr "Загружать последнюю открытую конфигурацию при запуске программы"
msgid "Enable direct playback bar (experimental)"
msgstr "Включить панель прямого воспроизведения (экспериментально)"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Включает прямую отправку и воспроизведение медиа-ссылок на ресивере"
msgid "Watch the channel in the program"
msgstr "Просмотр канала в программе"
msgid "Zap and Play"
msgstr "Перекл. и просмотр"
msgid "Drag or paste the link here"
msgstr "Перетащите или вставьте ссылку здесь"
msgid "Remove added links in the playlist"
msgstr "Удалить добавленные ссылки из плейлиста"
msgid "A bouquet with that name exists!"
msgstr "Букет с таким именем существует!"
msgid "Details"
msgstr "Подробно"
msgid "Profile"
msgstr "Профиль"
msgid "Reset"
msgstr "Сброс"
msgid "File"
msgstr "Файл"
msgid "Picons manager"
msgstr "Менеджер пиконов"
msgid "Explorer"
msgstr "Проводник"
msgid "Satellite url:"
msgstr "URL cпутника:"
msgid "Cut"
msgstr "Вырезать"
msgid "Paste"
msgstr "Вставить"
msgid "To the top"
msgstr "В начало"
msgid "To the end"
msgstr "В конец"
msgid "View"
msgstr "Вид"
msgid "Lock"
msgstr "Замок"
msgid "Parent lock"
msgstr "Родительский замок"
msgid "Hide/Skip"
msgstr "Скрыть/Пропустить"
msgid "IPTV tools"
msgstr "Инструменты IPTV"
msgid "Make profile folder as default for the additional data"
msgstr "Установить папку профиля по умолчанию для доп. данных"
msgid "Default data path:"
msgstr "Путь к данным по умолчанию:"
msgid "Streams record path:"
msgstr "Путь к записям потоков:"
msgid "Record"
msgstr "Запись"
msgid "Record:"
msgstr "Запись:"
msgid "Record to disk:"
msgstr "Запись на диск:"
msgid "Streaming"
msgstr "Потоки"
msgid "Activate transcoding"
msgstr "Активировать перекодировку"
msgid "Presets:"
msgstr "Предустановки:"
msgid "Video options:"
msgstr "Опции видео:"
msgid "Audio options:"
msgstr "Опции аудио:"
msgid "Bitrate (kb/s):"
msgstr "Битрейт (kb/s):"
msgid "Codec:"
msgstr "Кодек:"
msgid "Width (px):"
msgstr "Ширина (px):"
msgid "Height (px):"
msgstr "Высота (px):"
msgid "Channels:"
msgstr "Каналы:"
msgid "Sample rate (Hz):"
msgstr "Частота дискр. (Гц):"
msgid "Play streams mode:"
msgstr "Режим воспроизведения потоков:"
msgid "Built-in player"
msgstr "Встроенный плеер"
msgid "VLC media player"
msgstr "VLC медиаплеер"
msgid "Only get m3u file"
msgstr "Получить файл *.m3u"
msgid "Save and restart the program to apply the settings."
msgstr "Сохраните и перезапустите программу, чтобы применить настройки."
msgid "Some images may have problems displaying the favorites list!"
msgstr "Некоторые образы могут иметь проблемы с отображением списка избранного!"
msgid "Operates in standby mode or current active transponder!"
msgstr "Работает в режиме ожидания или текущем активном транспондере!"
msgid "No connection to the receiver!"
msgstr "Нет соединение с ресивером!"
msgid "Signal level"
msgstr "Уровень сигнала"
msgid "Receiver info"
msgstr "Информация о ресивере"
msgid "A profile with that name exists!"
msgstr "Профиль с таким именем существует!"
msgid "Show short info as hints in the main services list"
msgstr "Показывать краткую информацию в виде подсказок в основном списке услуг"
msgid "Show detailed info as hints in the bouquet list"
msgstr "Показывать подробную информацию в виде подсказок в списке букетов"
msgid "Enable alternate bouquet file naming"
msgstr "Включить альтернативное именование файлов букета"
msgid "Allows you to name bouquet files using their names."
msgstr "Позволяет называть файлы букетов, используя их имена."
msgid "Appearance"
msgstr "Внешний вид"
msgid "Enable Themes support"
msgstr "Включить поддержку тем"
msgid "Gtk3 Theme:"
msgstr "Тема Gtk3:"
msgid "Icon Theme:"
msgstr "Тема значков:"
msgid "Gtk3 Themes and Icons:"
msgstr "Gtk3 темы и иконки:"
msgid "Deleting data..."
msgstr "Удаление данных ..."
msgid "Download from the receiver"
msgstr "Загрузить с ресивера"
msgid "Remove all picons from the receiver"
msgstr "Удалить все пиконы с ресивера"
msgid "Service reference"
msgstr "Сервисная ссылка"

978
po/tr/demon-editor.po Normal file
View File

@@ -0,0 +1,978 @@
msgid ""
msgstr ""
"Project-Id-Version: DemonEditor\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-16 15:59+0300\n"
"PO-Revision-Date: 2020-05-11 20:02+0300\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: audi06_19 <info@dreamosat-forum.com>\n"
"Language-Team: \n"
"X-Generator: Poedit 2.2.1\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"Language: tr\n"
msgid "translator-credits"
msgstr "audi06_19 <info@dreamosat-forum.com>"
# Main
msgid "Service"
msgstr "Hizmet"
msgid "Package"
msgstr "Paket"
msgid "Type"
msgstr "Tür"
msgid "Picon"
msgstr "Picon"
msgid "Freq"
msgstr "Frekans"
msgid "Rate"
msgstr "Oran"
msgid "Pol"
msgstr "Pol"
msgid "System"
msgstr "Sistem"
msgid "Pos"
msgstr "Pos"
msgid "Num"
msgstr "Num"
msgid "Current IP:"
msgstr "Geçerli IP:"
msgid "Assign"
msgstr "Ata"
msgid "Bouquet details"
msgstr "Buket detayları"
msgid "Bouquets"
msgstr "Buketler"
msgid "Copy"
msgstr "Kopya"
msgid "Copy reference"
msgstr "Referansı kopyala"
msgid "Download"
msgstr "İndir"
msgid "Edit"
msgstr "Düzelt"
msgid "Edit mаrker text"
msgstr "Marker metnini Düzenle"
msgid "FTP-transfer"
msgstr "FTP aktarımı"
msgid "Global search"
msgstr "Global arama"
msgid "Hide"
msgstr "Gizle"
msgid "Hide/Skip On/Off Ctrl + H"
msgstr "Gizle/Atla Açık/Kapalı Ctrl + H"
msgid "Add IPTV or stream service"
msgstr "IPTV veya akış hizmeti ekle"
msgid "Import m3u"
msgstr "M3u aktar"
msgid "Import m3u file"
msgstr "İçe aktar M3U"
msgid "List configuration"
msgstr "Liste yapılandırması"
msgid "Rename for this bouquet"
msgstr "Bu buketi yeniden adlandır"
msgid "Set default name"
msgstr "Varsayılan adı ayarla"
msgid "Insert marker"
msgstr "İşaretçi ekle"
msgid "Locate in services"
msgstr "Hizmetlerde bulun"
msgid "Locked"
msgstr "Kilitli"
msgid "Move"
msgstr "Taçı"
msgid "New"
msgstr "Yeni"
msgid "New bouquet"
msgstr "Yeni buket"
msgid "Create bouquet"
msgstr "Buket oluştur"
msgid "For current satellite"
msgstr "Mevcut uydu için"
msgid "For current package"
msgstr "Mevcut paket için"
msgid "For current type"
msgstr "Mevcut tip için"
msgid "For each satellite"
msgstr "Her uydu için"
msgid "For each package"
msgstr "Her paket için"
msgid "For each type"
msgstr "Her tip için"
msgid "Open"
msgstr "Aç"
msgid "Parent lock On/Off Ctrl + L"
msgstr "Ebeveyn kilidi Açık/Kapalı Ctrl + L"
msgid "Picons"
msgstr "Piconlar"
msgid "Picons downloader"
msgstr "Piconları Güncelle/indir"
msgid "Satellites downloader"
msgstr "Satellites Güncelle/indir"
msgid "Remove"
msgstr "Kaldır"
msgid "Remove all unavailable"
msgstr "Tüm mevcut olmayanları kaldır"
msgid "Satellites editor"
msgstr "Uydular editörü"
msgid "Save"
msgstr "Kaydet"
msgid "Search"
msgstr "Arama"
msgid "Services"
msgstr "Hizmetler"
msgid "Services filter"
msgstr "Hizmet filtresi"
msgid "Settings"
msgstr "Ayarlar"
msgid "Up"
msgstr "Yukarı"
msgid "Down"
msgstr "Aşağı"
msgid "Active profile:"
msgstr "Etkin profil:"
msgid "All"
msgstr "Tümü"
msgid "Are you sure?"
msgstr "Emin misin?"
msgid "Current data path:"
msgstr "Mevcut veri yolu:"
msgid "Data:"
msgstr "Veri:"
msgid "Enigma2 channel and satellites list editor for GNU/Linux"
msgstr "GNU/Linux için Enigma2 kanalı ve uydu listesi editörü"
msgid "Host:"
msgstr "Ana bilgisayar:"
msgid "Loading data..."
msgstr "Veriler yükleniyor ..."
msgid "Receive"
msgstr "Al"
msgid "Receive files from receiver"
msgstr "Alıcıdan dosya al"
msgid "Receiver IP:"
msgstr "Alıcı IP'si:"
msgid "Remove unused bouquets"
msgstr "Kullanılmayan buketleri kaldır"
msgid "Reset profile"
msgstr "Profili sıfırla"
msgid "Satellites"
msgstr "Uydular"
msgid "Satellites.xml file:"
msgstr "Satellites.xml dosyası:"
msgid "Selected"
msgstr "Seçildi"
msgid "Send"
msgstr "Gönder"
msgid "Send files to receiver"
msgstr "Alıcıya dosya gönder"
msgid "Services and Bouquets files:"
msgstr "Hizmetler ve Buketler dosyaları:"
msgid "User bouquet files:"
msgstr "Kullanıcı buketi dosyaları:"
msgid "Extra:"
msgstr "Ekstra:"
# Filter bar
msgid "Only free"
msgstr "Sadece ücretsiz"
msgid "All positions"
msgstr "Tüm pozisyonlar"
msgid "All types"
msgstr "Tüm türler"
# Streams player
msgid "Play"
msgstr "Oynat"
msgid "Stop playback"
msgstr "Oynatmayı durdur"
msgid "Previous stream in the list"
msgstr "Listedeki önceki akış"
msgid "Next stream in the list"
msgstr "Listedeki sonraki akış"
msgid "Toggle in fullscreen"
msgstr "Tam ekranda geçiş yap"
msgid "Close"
msgstr "Kapat"
# Picons dialog
msgid "Load providers"
msgstr "Yük sağlayıcılar"
msgid "Providers"
msgstr "Yayıncılar"
msgid "Receive picons"
msgstr "Piconları al"
msgid "Picons name format:"
msgstr "Piconların adı biçimi:"
msgid "Resize:"
msgstr "Yeniden boyutlandır:"
msgid "Current picons path:"
msgstr "Geçerli picon yolları:"
msgid "Receiver picons path:"
msgstr "Alıcı picon yolu:"
msgid "Picons download tool"
msgstr "Picon indirme aracı"
msgid "Transfer to receiver"
msgstr "Alıcıya aktar"
msgid "Downloader"
msgstr "İndirici"
msgid "Converter"
msgstr "Dönüştürücü"
msgid "Convert"
msgstr "Dönüştür"
msgid "Path to save:"
msgstr "Kaydetme yolu:"
msgid "Path to Enigma2 picons:"
msgstr "Enigma2 piconların yolu:"
msgid "Specify the correct position value for the provider!"
msgstr "Sağlayıcı için doğru pozisyon değerini belirtin!"
msgid "Converter between name formats"
msgstr "İsim formatları arasında dönüştürücü"
msgid "Receive picons for providers"
msgstr "Sağlayıcılar için picon alma"
msgid "Load satellite providers."
msgstr "Uydu sağlayıcılarını yükle."
msgid ""
"To automatically set the identifiers for picons,\n"
"first load the required services list into the main application window."
msgstr ""
"Picon tanımlayıcılarını otomatik olarak ayarlamak için \n"
"önce gerekli uygulama listesini ana uygulama penceresine yükleyin."
# Satellites editor
msgid "Satellites edit tool"
msgstr "Uydular düzenleme aracı"
msgid "Add"
msgstr "Ekle"
msgid "Satellite"
msgstr "Uydu"
msgid "Transponder"
msgstr "Transponder"
msgid "Satellite properties:"
msgstr "Uydu özellikleri:"
msgid "Transponder properties:"
msgstr "Transponder özellikleri:"
msgid "Name"
msgstr "Ad"
msgid "Position"
msgstr "Konum"
# Satellites update dialog
msgid "Satellites update"
msgstr "Uydular güncelleme"
msgid "Remove selection"
msgstr "Seçimi kaldır"
# Service details dialog
msgid "Service data:"
msgstr "Servis verileri:"
msgid "Transponder data:"
msgstr "Transponder verileri:"
msgid "Service data"
msgstr "Servis verileri"
msgid "Transponder details"
msgstr "Transponder detayları"
msgid ""
"Changes will be applied to all services of this transponder!\n"
"Continue?"
msgstr ""
"Değişiklikler bu transponderin tüm servislerine uygulanacak!\n"
"Devam edilsinmi?"
msgid "Reference"
msgstr "Referans"
msgid "Namespace"
msgstr "Ad alanı"
msgid "Flags:"
msgstr "Bayraklar:"
msgid "Delays (ms):"
msgstr "Gecikmeler (ms):"
msgid "Bitstream"
msgstr "Bit akımı"
msgid "Description"
msgstr "Açıklama"
msgid "Source:"
msgstr "Kaynak:"
msgid "Cancel"
msgstr "İptal"
msgid "Update"
msgstr "Güncelle"
msgid "Filter"
msgstr "Filtre"
msgid "Find"
msgstr "Bul"
# IPTV dialog
msgid "Stream data"
msgstr "Veri akışı"
# IPTV list configuration dialog
msgid "Starting values"
msgstr "Başlangıç değerleri"
msgid "Reset to default"
msgstr "Varsayılana sıfırla"
msgid "IPTV streams list configuration"
msgstr "IPTV akış listesi yapılandırması"
# Settings dialog
msgid "Preferences"
msgstr "Tercihler"
msgid "Profile:"
msgstr "Profil:"
msgid "Timeout between commands in seconds"
msgstr "Komutlar arasında saniye cinsinden zaman aşımı"
msgid "Timeout:"
msgstr "Zaman aşımı:"
msgid "Login:"
msgstr "Giriş:"
msgid "Options"
msgstr "Seçenekler"
msgid "Password:"
msgstr "Parola:"
msgid "Picons:"
msgstr "Piconlar:"
msgid "Port:"
msgstr "Port:"
msgid "Data path:"
msgstr "Veri yolu:"
msgid "Picons path:"
msgstr "Picon yolu:"
msgid "Network settings:"
msgstr "Ağ ayarları:"
msgid "STB file paths:"
msgstr "STB dosya yolları:"
msgid "Local file paths:"
msgstr "Yerel dosya yolları:"
# Dialogs messages
msgid "Error. No bouquet is selected!"
msgstr "Hata. Buket seçilmedi!"
msgid "This item is not allowed to be removed!"
msgstr "Bu öğenin kaldırılmasına izin verilmiyor!"
msgid "This item is not allowed to edit!"
msgstr "Bu öğenin düzenlenmesine izin verilmiyor!"
msgid "Not allowed in this context!"
msgstr "Bu bağlamda izin verilmiyor!"
msgid "Please, download files from receiver or setup your path for read data!"
msgstr "Lütfen, alıcıdan dosya indirin veya veri okumak için yolunuzu ayarlayın!"
msgid "Reading data error!"
msgstr "Veri hatası okunuyor!"
msgid "No m3u file is selected!"
msgstr "Hiçbir m3u dosyası seçilmedi!"
msgid "Not implemented yet!"
msgstr "Henüz uygulanmadı!"
msgid "The text of marker is empty, please try again!"
msgstr "İşaretçi metni boş, lütfen tekrar deneyin!"
msgid "Please, select only one item!"
msgstr "Lütfen sadece bir ürün seçiniz!"
msgid "No png file is selected!"
msgstr "Hiçbir png dosyası seçilmedi!"
msgid "No reference is present!"
msgstr "Referans yok!"
msgid "No selected item!"
msgstr "Seçili öğe yok!"
msgid "The task is already running!"
msgstr "Görev zaten çalışıyor!"
msgid "Done!"
msgstr "Bitti!"
msgid "Please, wait..."
msgstr "Lütfen bekleyin ..."
msgid "Resizing..."
msgstr "Yeniden boyutlandırılıyor ..."
msgid "Select paths!"
msgstr "Yolları seç!"
msgid "No satellite is selected!"
msgstr "Uydu seçilmedi!"
msgid "Please, select only one satellite!"
msgstr "Lütfen sadece bir uydu seçin!"
msgid "Please check your parameters and try again."
msgstr "Lütfen parametrelerinizi kontrol edip tekrar deneyin."
msgid "No satellites.xml file is selected!"
msgstr "Hiçbir satellites.xml dosyası seçilmedi!"
msgid "Error. Verify the data!"
msgstr "Hata. Verileri doğrulayın!"
msgid "Operation not allowed in this context!"
msgstr "Bu bağlamda işleme izin verilmiyor!"
msgid "No VLC is found. Check that it is installed!"
msgstr "VLC bulunamadı. Yüklü olduğundan emin olun!"
# Search unavailable streams dialog
msgid "Please wait, streams testing in progress..."
msgstr "Lütfen bekleyin, akış testi devam ediyor ..."
msgid "Found"
msgstr "Bulundu"
msgid "unavailable streams."
msgstr "mevcut olmayan akışlar."
msgid "No changes required!"
msgstr "Hiçbir değişiklik gerekli!"
msgid "This list does not contains IPTV streams!"
msgstr "Bu liste IPTV akışı içermiyor!"
msgid "New empty configuration"
msgstr "Yeni boş yapılandırma"
msgid "No data to save!"
msgstr "Kaydedilecek veri yok!"
msgid "Network"
msgstr "Ağ"
msgid "Paths"
msgstr "Yollar"
msgid "Program"
msgstr "Program"
msgid "Backup:"
msgstr "Yedekleme:"
msgid "Backup"
msgstr "Yedekleme"
msgid "Backups"
msgstr "Yedeklemeler"
msgid "Backup path:"
msgstr "Yedekleme yolu:"
msgid "Restore bouquets"
msgstr "Buketleri geri yükle"
msgid "Restore all"
msgstr "Tümünü geri yükle"
msgid "Before saving"
msgstr "Kaydetmeden önce"
msgid "Before downloading from the receiver"
msgstr "Alıcıdan indirmeden önce"
msgid "Set background color for the services"
msgstr "Hizmetler için arka plan rengini ayarla"
msgid "Marked as new:"
msgstr "Yeni olarak işaretlendi:"
msgid "With an extra name in the bouquet:"
msgstr "Bukette fazladan bir isim ile:"
msgid "Select"
msgstr "Seç"
msgid "About"
msgstr "Hakkında"
msgid "Exit"
msgstr "Çıkış"
msgid "Tools"
msgstr "Araçlar"
# Import
msgid "Import"
msgstr "İçe aktar"
msgid "Bouquet"
msgstr "Buket"
msgid "Bouquets and services"
msgstr "Buketler ve hizmetler"
msgid "The main list does not contain services for this bouquet!"
msgstr "Ana liste bu buket için hizmet içermiyor!"
msgid "No bouquet file is selected!"
msgstr "Hiçbir buket dosyası seçilmedi!"
msgid "Remove all unused"
msgstr "Kullanılmayanların tümünü kaldır"
msgid "Test"
msgstr "Test"
msgid "Test connection"
msgstr "Bağlantıyı test et"
msgid "Double click on the service in the bouquet list:"
msgstr "Buket listesindeki hizmete çift tıklayın:"
msgid "Zap"
msgstr "Zap"
msgid "Play stream"
msgstr "Akışı oynat"
msgid "Disabled"
msgstr "Devre dışı"
msgid "Enable ver. 5 support (experimental)"
msgstr "Sürüm 5 desteğini etkinleştir (deneysel)"
msgid "Enable HTTP API (experimental)"
msgstr "HTTP API'sini etkinleştir (deneysel)"
msgid "Switch(zap) the channel(Ctrl + Z)"
msgstr "Kanalı değiştir (zap) (Ctrl + Z)"
msgid "Switch the channel and watch in the program(Ctrl + W)"
msgstr "Kanal değiştirme ve programda izleme (Ctrl + W)"
msgid "Play IPTV or other stream in the program(Ctrl + P)"
msgstr "Programdaki IPTV veya diğer akışları oynat (Ctrl + P)"
msgid "Export to m3u"
msgstr "Dışa aktar M3U"
msgid "EPG configuration"
msgstr "EPG yapılandırması"
msgid "Apply"
msgstr "Uygula"
msgid "EPG source"
msgstr "EPG kaynağı"
msgid "Service names source:"
msgstr "Hizmet adları kaynağı:"
msgid "Main service list"
msgstr "Ana hizmet listesi"
msgid "XML file"
msgstr "XML dosyası"
msgid "Use web source"
msgstr "Web kaynağını kullan"
msgid "Url to *.xml.gz file:"
msgstr "URL. * .xml.gz dosyasına:"
msgid "Enable filtering"
msgstr "Filtrelemeyi etkinleştir"
msgid "Filter by presence in the epg.dat file."
msgstr "Epg.dat dosyasındaki varlığına göre filtreleyin."
msgid "Paths to the epg.dat file:"
msgstr "Epg.dat dosyasının yolları:"
msgid "Local path:"
msgstr "Yerel yol:"
msgid "STB path:"
msgstr "STB yolu:"
msgid "Update on start"
msgstr "Başlangıçta güncelleme"
msgid "Auto configuration by service names."
msgstr "Hizmet adlarına göre otomatik yapılandırma."
msgid "Save list to xml."
msgstr "Listeyi xml'ye kaydet."
msgid "Download XML file error."
msgstr "XML dosyası indir hatası."
msgid "Unsupported file type:"
msgstr "Desteklenmeyen dosya türü:"
msgid "Unpacking data error."
msgstr "Veri paketi açılırken hata oluştu."
msgid "XML parsing error:"
msgstr "XML ayrıştırma hatası:"
msgid "Count of successfully configured services:"
msgstr "Başarılı bir şekilde yapılandırılmış hizmetlerin sayısı:"
msgid "Current epg.dat file does not contains references for the services of this bouquet!"
msgstr "Şu anki epg.dat dosyası bu buketin hizmetleri için referans içermiyor!"
msgid "Use HTTP"
msgstr "HTTP kullan"
msgid "Close playback"
msgstr "Oynatmayı kapat"
msgid "Import YouTube playlist"
msgstr "YouTube oynatma listesini içe aktar"
msgid ""
"Found a link to the YouTube resource!\n"
"Try to get a direct link to the video?"
msgstr ""
"YouTube kaynağına bir bağlantı bulundu!\n"
"Videoya doğrudan bağlantı almaya çalışılsın mı?"
msgid "Playlist import"
msgstr "Oynatma listesi içe aktarma"
msgid "Getting link error:"
msgstr "Bağlantı hatası alınıyor:"
msgid "Extra"
msgstr "Ekstra"
msgid "Apply profile settings"
msgstr "Profil ayarlarını uygula"
msgid "Settings type:"
msgstr "Ayarlar türü:"
msgid "Set default"
msgstr "Varsayılanı ayarla"
msgid "Language:"
msgstr "Dil:"
msgid "Load the last open configuration at program startup"
msgstr "Program açılışında son açık yapılandırmayı yükle"
msgid "Enable direct playback bar (experimental)"
msgstr "Doğrudan oynatma çubuğunu etkinleştir (deneysel)"
msgid "Enables direct sending and playback of media links on the receiver"
msgstr "Alıcıdaki medya bağlantılarının doğrudan gönderilmesini ve oynatılmasını sağlar"
msgid "Watch the channel in the program"
msgstr "Programdaki kanalı izle"
msgid "Zap and Play"
msgstr "Zap ve Oyna"
msgid "Drag or paste the link here"
msgstr "Bağlantıyı buraya sürükleyin veya yapıştırın"
msgid "Remove added links in the playlist"
msgstr "Oynatma listesine eklenen bağlantıları kaldır"
msgid "A bouquet with that name exists!"
msgstr "Bu isimli bir buket var!"
msgid "Details"
msgstr "Detaylar"
msgid "Profile"
msgstr "Profil"
msgid "Reset"
msgstr "Y.başlat"
msgid "File"
msgstr "Dosya"
msgid "Picons manager"
msgstr "Picon yöneticisi"
msgid "Explorer"
msgstr "Explorer"
msgid "Satellite url:"
msgstr "Uydu url:"
msgid "Cut"
msgstr "Kes"
msgid "Paste"
msgstr "Yapıştır"
msgid "To the top"
msgstr "Başlangıçta"
msgid "To the end"
msgstr "Sonunda"
msgid "View"
msgstr "Görünüm"
msgid "Lock"
msgstr "Kilit"
msgid "Parent lock"
msgstr "Ebeveyn kilidi"
msgid "Hide/Skip"
msgstr "Gizle/Atla"
msgid "IPTV tools"
msgstr "IPTV araçları"
msgid "Make profile folder as default for the additional data"
msgstr "Ek veriler için profil klasörünü varsayılan yap"
msgid "Default data path:"
msgstr "Varsayılan veri yolu:"
msgid "Streams record path:"
msgstr "Akış kayıt yolu:"
msgid "Record"
msgstr "Kayıt"
msgid "Record:"
msgstr "Kayıt:"
msgid "Record to disk:"
msgstr "Diske kaydet:"
msgid "Streaming"
msgstr "Yayın Akışı"
msgid "Activate transcoding"
msgstr "Kod dönüştürmeyi etkinleştir"
msgid "Presets:"
msgstr "Ön ayarlar:"
msgid "Video options:"
msgstr "Video seçenekleri:"
msgid "Audio options:"
msgstr "Ses seçenekleri:"
msgid "Bitrate (kb/s):"
msgstr "Bit hızı (kb/s):"
msgid "Codec:"
msgstr "Codec:"
msgid "Width (px):"
msgstr "Genişlik (px):"
msgid "Height (px):"
msgstr "Yükseklik (px):"
msgid "Channels:"
msgstr "Kanallar:"
msgid "Sample rate (Hz):"
msgstr "Örnekleme hızı (Hz):"
msgid "Play streams mode:"
msgstr "Akışları oynatma modu:"
msgid "Built-in player"
msgstr "Dahili oynatıcı"
msgid "VLC media player"
msgstr "VLC media player"
msgid "Only get m3u file"
msgstr "Sadece m3u dosyası al"
msgid "Save and restart the program to apply the settings."
msgstr "Ayarları uygulamak için programı kaydedin ve yeniden başlatın."
msgid "Some images may have problems displaying the favorites list!"
msgstr "Bazı resimler sık kullanılanlar listesini görüntülerken sorun yaşayabilir!"
msgid "Operates in standby mode or current active transponder!"
msgstr "Bekleme modunda veya geçerli aktif transponderde çalışır!"
msgid "No connection to the receiver!"
msgstr "Alıcıya bağlantı yok!"
msgid "Signal level"
msgstr "Sinyal seviyesi"
msgid "Receiver info"
msgstr "Alıcı bilgisi"
msgid "A profile with that name exists!"
msgstr "Bu ada sahip bir profil var!"
msgid "Show short info as hints in the main services list"
msgstr "Kısa bilgileri ana hizmetler listesinde ipuçları olarak göster"
msgid "Show detailed info as hints in the bouquet list"
msgstr "Ayrıntılı bilgileri buket listesinde ipuçları olarak göster"
msgid "Enable alternate bouquet file naming"
msgstr "Alternatif buket dosyası adlandırmasını etkinleştir"
msgid "Allows you to name bouquet files using their names."
msgstr "Buket dosyalarını isimlerini kullanarak isimlendirmenizi sağlar."
msgid "Appearance"
msgstr "Görünüm"
msgid "Enable Themes support"
msgstr "Temalar desteğini etkinleştir"
msgid "Gtk3 Theme:"
msgstr "Gtk3 Teması:"
msgid "Icon Theme:"
msgstr "Simge Teması:"
msgid "Gtk3 Themes and Icons:"
msgstr "Gtk3 Tema ve Simgeler:"