From c53e926cda0b29a4fa2b7b5c619ec75191993755 Mon Sep 17 00:00:00 2001 From: jbleyel Date: Mon, 24 Feb 2025 23:02:10 +0100 Subject: [PATCH 01/14] Refactor Step 1 --- src/EPGImport/EPGConfig.py | 301 +++++--- src/EPGImport/EPGImport.py | 895 ++++++++++++----------- src/EPGImport/ExpandableSelectionList.py | 20 +- src/EPGImport/OfflineImport.py | 19 +- src/EPGImport/__init__.py | 17 +- src/EPGImport/boot.py | 48 +- src/EPGImport/epgdat.py | 159 ++-- src/EPGImport/epgdat_importer.py | 7 +- src/EPGImport/filterCustomChannel.py | 110 +++ src/EPGImport/filtersServices.py | 27 +- src/EPGImport/gen_xmltv.py | 42 +- src/EPGImport/locale/EPGImport.pot | 431 +++++++---- src/EPGImport/locale/it.mo | Bin 5633 -> 11653 bytes src/EPGImport/locale/it.po | 677 +++++++++++------ src/EPGImport/plugin.py | 421 +++++++---- src/EPGImport/xmltvconverter.py | 102 ++- 16 files changed, 2035 insertions(+), 1241 deletions(-) create mode 100644 src/EPGImport/filterCustomChannel.py mode change 100755 => 100644 src/EPGImport/locale/it.po diff --git a/src/EPGImport/EPGConfig.py b/src/EPGImport/EPGConfig.py index 1b2bf63..dc34209 100755 --- a/src/EPGImport/EPGConfig.py +++ b/src/EPGImport/EPGConfig.py @@ -1,15 +1,16 @@ # -*- coding: UTF-8 -*- -from __future__ import absolute_import -from __future__ import print_function -import os -from . import log -from xml.etree.cElementTree import ElementTree, Element, SubElement, tostring, iterparse import gzip -import time +import os import random -import six +import time +import re +from html import unescape +from re import sub +from pickle import dump, load, HIGHEST_PROTOCOL +from xml.etree.cElementTree import iterparse -from six.moves import cPickle as pickle +from . import log +from . filterCustomChannel import filterCustomChannel # User selection stored here, so it goes into a user settings backup @@ -29,10 +30,7 @@ def getChannels(path, name, offset): return channelCache[name] dirname, filename = os.path.split(path) if name: - if isLocalFile(name): - channelfile = os.path.join(dirname, name) - else: - channelfile = name + channelfile = os.path.join(dirname, name) if isLocalFile(name) else name else: channelfile = os.path.join(dirname, filename.split('.', 1)[0] + '.channels.xml') try: @@ -44,6 +42,127 @@ def getChannels(path, name, offset): return c +def enumerateXML(fp, tag=None): + """Enumerates ElementTree nodes from file object 'fp'""" + doc = iterparse(fp, events=('start', 'end')) + _, root = next(doc) # Ottiene la radice + depth = 0 + + for event, element in doc: + if element.tag == tag: + if event == 'start': + depth += 1 + elif event == 'end': + if depth == 1: + yield element + element.clear() + depth -= 1 + + if event == 'end' and element.tag != tag: + element.clear() + + root.clear() + + +def xml_unescape(text): + """ + Unescapes XML/HTML entities in the given text. + + :param text: The text that needs to be unescaped. + :type text: str + :rtype: str + """ + + if not isinstance(text, str): + return '' + + text = text.strip() + + # Custom entity replacements + entity_map = { + "«": "«", + "«": "«", + "»": "»", + "»": "»", + "'": "'", + } + + # First, apply standard unescape + text = unescape(text) + + # Replace specific entities + for entity, char in entity_map.items(): + text = text.replace(entity, char) + + # Normalize whitespace (replace ` `, ` `, and multiple spaces with a single space) + text = sub(r' | |\s+', ' ', text) + + return text + + +def openStream(filename): + fd = open(filename, 'rb') + if not os.fstat(fd.fileno()).st_size: + raise Exception("File is empty") + if filename.endswith('.gz'): + fd = gzip.GzipFile(fileobj=fd, mode='rb') + elif filename.endswith('.xz') or filename.endswith('.lzma'): + try: + import lzma + except ImportError: + from backports import lzma + fd = lzma.open(filename, 'rb') + elif filename.endswith('.zip'): + import zipfile + from six import BytesIO + zip_obj = zipfile.ZipFile(filename, 'r') + fd = BytesIO(zip_obj.open(zip_obj.namelist()[0]).read()) + return fd + + +def set_channel_id_filter(): + full_filter = "" + try: + with open('/etc/epgimport/channel_id_filter.conf', 'r') as channel_id_file: + for channel_id_line in channel_id_file: + # Skipping comments in channel_id_filter.conf + if not channel_id_line.startswith("#"): + clean_channel_id_line = channel_id_line.strip() + # Blank line in channel_id_filter.conf will produce a full match so we need to skip them. + if clean_channel_id_line: + try: + # We compile indivually every line just to report error + full_filter = re.compile(clean_channel_id_line) + except re.error: + print("[EPGImport] ERROR: " + clean_channel_id_line + " is not a valid regex. It will be ignored.", file=log) + else: + full_filter = full_filter + clean_channel_id_line + "|" + except IOError: + print("[EPGImport] INFO: no channel_id_filter.conf file found.", file=log) + # Return a dummy filter (empty line filter) all accepted except empty channel id + compiled_filter = re.compile("^$") + return (compiled_filter) + # Last char is | so remove it + full_filter = full_filter[:-1] + # all channel id are matched in lower case so creating the filter in lowercase too + full_filter = full_filter.lower() + # channel_id_filter.conf file exist but is empty, it has only comments, or only invalid regex + if len(full_filter) == 0: + # full_filter is empty returning dummy filter + compiled_filter = re.compile("^$") + else: + try: + compiled_filter = re.compile(full_filter) + except re.error: + print("[EPGImport] ERROR: final regex " + full_filter + " doesn't compile properly.", file=log) + # Return a dummy filter (empty line filter) all accepted except empty channel id + compiled_filter = re.compile("^$") + else: + print("[EPGImport] INFO : final regex " + full_filter + " compiled successfully.", file=log) + + return (compiled_filter) + + class EPGChannel: def __init__(self, filename, urls=None, offset=0): self.mtime = None @@ -55,38 +174,44 @@ def __init__(self, filename, urls=None, offset=0): self.items = None self.offset = offset - def openStream(self, filename): - fd = open(filename, 'rb') - if not os.fstat(fd.fileno()).st_size: - raise Exception("File is empty") - if filename.endswith('.gz'): - fd = gzip.GzipFile(fileobj=fd, mode='rb') - elif filename.endswith('.xz') or filename.endswith('.lzma'): - try: - import lzma - except ImportError: - from backports import lzma - fd = lzma.open(filename, 'rb') - return fd - - def parse(self, filterCallback, downloadedFile): + def parse(self, filterCallback, downloadedFile, FilterChannelEnabled): print("[EPGImport] Parsing channels from '%s'" % self.name, file=log) + channel_id_filter = set_channel_id_filter() if self.items is None: self.items = {} try: - context = iterparse(self.openStream(downloadedFile)) + context = iterparse(openStream(downloadedFile)) for event, elem in context: if elem.tag == 'channel': id = elem.get('id') id = id.lower() - ref = elem.text - if id and ref: - ref = six.ensure_str(ref) - if filterCallback(ref): - if id in self.items: - self.items[id].append(ref) - else: - self.items[id] = [ref] + filter_result = channel_id_filter.match(id) + if filter_result and FilterChannelEnabled: + # Just to avoid false positive in logging since the same parse function is used in two different cases. + if filter_result.group(): + print("[EPGImport] INFO : skipping", filter_result.group(), "due to channel_id_filter.conf", file=log) + ref = str(elem.text) + if id and ref: + if filterCallback(ref): + if id in self.items: + try: + if ref in self.items[id]: + # remove only remove the first occurrence turning list into dict will make the reference unique so remove will work as expected. + self.items[id] = list(dict.fromkeys(self.items[id])) + self.items[id].remove(ref) + except Exception as e: + print("[EPGImport] failed to remove from list ", self.items[id], " ref ", ref, "Error:", e, file=log) + else: + # print("[EPGImport] INFO : processing", id, file=log) + ref = str(elem.text) + if id and ref: + if filterCallback(ref): + if id in self.items: + self.items[id].append(ref) + # turning list into dict will make the reference unique to avoid loading twice the same EPG data. + self.items[id] = list(dict.fromkeys(self.items[id])) + else: + self.items[id] = [ref] elem.clear() except Exception as e: print("[EPGImport] failed to parse", downloadedFile, "Error:", e, file=log) @@ -96,16 +221,22 @@ def update(self, filterCallback, downloadedFile=None): customFile = '/etc/epgimport/custom.channels.xml' # Always read custom file since we don't know when it was last updated # and we don't have multiple download from server problem since it is always a local file. + if not os.path.exists(customFile): + customFile = '/etc/epgimport/rytec.channels.xml' + if os.path.exists(customFile): print("[EPGImport] Parsing channels from '%s'" % customFile, file=log) - self.parse(filterCallback, customFile) + self.parse(filterCallback, customFile, filterCustomChannel) if downloadedFile is not None: self.mtime = time.time() - return self.parse(filterCallback, downloadedFile) + return self.parse(filterCallback, downloadedFile, True) elif (len(self.urls) == 1) and isLocalFile(self.urls[0]): - mtime = os.path.getmtime(self.urls[0]) - if (not self.mtime) or (self.mtime < mtime): - self.parse(filterCallback, self.urls[0]) + try: + mtime = os.path.getmtime(self.urls[0]) + except: + mtime = None + if (not self.mtime) or (mtime is not None and self.mtime < mtime): + self.parse(filterCallback, self.urls[0], True) self.mtime = mtime def downloadables(self): @@ -116,7 +247,7 @@ def downloadables(self): now = time.time() if (not self.mtime) or (self.mtime + 86400 < now): return self.urls - return None + return [] def __repr__(self): return "EPGChannel(urls=%s, channels=%s, mtime=%s)" % (self.urls, self.items and len(self.items), self.mtime) @@ -124,14 +255,8 @@ def __repr__(self): class EPGSource: def __init__(self, path, elem, category=None, offset=0): - self.parser = elem.get('type') - nocheck = elem.get('nocheck') - if nocheck == None: - self.nocheck = 0 - elif nocheck == "1": - self.nocheck = 1 - else: - self.nocheck = 0 + self.parser = elem.get('type', 'gen_xmltv') + self.nocheck = int(elem.get('nocheck', 0)) self.urls = [e.text.strip() for e in elem.findall('url')] self.url = random.choice(self.urls) self.description = elem.findtext('description') @@ -146,37 +271,41 @@ def __init__(self, path, elem, category=None, offset=0): def enumSourcesFile(sourcefile, filter=None, categories=False): global channelCache category = None - for event, elem in iterparse(open(sourcefile, 'rb'), events=("start", "end")): - if event == 'end': - if elem.tag == 'source': - # calculate custom time offset in minutes - offset = int(elem.get('offset', '+0000')) * 3600 // 100 - s = EPGSource(sourcefile, elem, category, offset) - elem.clear() - if (filter is None) or (s.description in filter): - yield s - elif elem.tag == 'channel': - name = elem.get('name') - urls = [e.text.strip() for e in elem.findall('url')] - if name in channelCache: - channelCache[name].urls = urls - else: - channelCache[name] = EPGChannel(name, urls) - elif elem.tag == 'sourcecat': - category = None - elif event == 'start': - # Need the category name sooner than the contents, hence "start" - if elem.tag == 'sourcecat': - category = elem.get('sourcecatname') - if categories: - yield category + try: + for event, elem in iterparse(open(sourcefile, 'rb'), events=("start", "end")): + if event == 'end': + if elem.tag == 'source': + # calculate custom time offset in minutes + offset = int(elem.get('offset', '+0000')) * 3600 // 100 + s = EPGSource(sourcefile, elem, category, offset) + elem.clear() + if (filter is None) or (s.description in filter): + yield s + elif elem.tag == 'channel': + name = xml_unescape(elem.get('name')) + + urls = [xml_unescape(e.text) for e in elem.findall('url')] + try: + channelCache[name].urls = urls + except: + channelCache[name] = EPGChannel(name, urls) + elif elem.tag == 'sourcecat': + category = None + elif event == 'start': + # Need the category name sooner than the contents, hence "start" + if elem.tag == 'sourcecat': + category = elem.get('sourcecatname') + if categories: + yield category + except Exception as e: + print("[EPGConfig] EPGConfig enumSourcesFile:", e) def enumSources(path, filter=None, categories=False): try: - for sourcefile in os.listdir(path): - if sourcefile.endswith('.sources.xml'): - sourcefile = os.path.join(path, sourcefile) + for filename in os.listdir(path): + if filename.endswith('.sources.xml'): + sourcefile = os.path.join(path, filename) try: for s in enumSourcesFile(sourcefile, filter, categories): yield s @@ -188,27 +317,27 @@ def enumSources(path, filter=None, categories=False): def loadUserSettings(filename=SETTINGS_FILE): try: - return pickle.load(open(filename, 'rb')) + return load(open(filename, 'rb')) except Exception as e: - print("[EPGImport] No settings", e, file=log) + print("[EPGImport]loadUserSettings No settings", e, file=log) return {"sources": []} def storeUserSettings(filename=SETTINGS_FILE, sources=None): - container = {"sources": sources} - pickle.dump(container, open(filename, 'wb'), pickle.HIGHEST_PROTOCOL) + container = {"[EPGImport]loadUserSettings sources": sources} + dump(container, open(filename, 'wb'), HIGHEST_PROTOCOL) if __name__ == '__main__': import sys x = [] - l = [] + lx = [] path = '.' if len(sys.argv) > 1: path = sys.argv[1] for p in enumSources(path): t = (p.description, p.urls, p.parser, p.format, p.channels, p.nocheck) - l.append(t) + lx.append(t) print(t) x.append(p.description) storeUserSettings('settings.pkl', [1, "twee"]) @@ -216,9 +345,9 @@ def storeUserSettings(filename=SETTINGS_FILE, sources=None): os.remove('settings.pkl') for p in enumSources(path, x): t = (p.description, p.urls, p.parser, p.format, p.channels, p.nocheck) - assert t in l - l.remove(t) - assert not l + assert t in lx + lx.remove(t) + assert not lx for name, c in channelCache.items(): print("Update:", name) c.update() diff --git a/src/EPGImport/EPGImport.py b/src/EPGImport/EPGImport.py index b255b36..f73a59d 100755 --- a/src/EPGImport/EPGImport.py +++ b/src/EPGImport/EPGImport.py @@ -3,465 +3,518 @@ # This file no longer has a direct link to Enigma2, allowing its use anywhere # you can supply a similar interface. See plugin.py and OfflineImport.py for # the contract. + + from datetime import datetime -from os.path import exists, getsize, join, splitext from os import statvfs, symlink, unlink +from os.path import exists, getsize, join, splitext +from requests import packages, Session +from requests.exceptions import HTTPError, RequestException +from twisted.internet import reactor, threads +from twisted.internet.reactor import callInThread import gzip -import random +from random import choices, choice +from string import ascii_lowercase import time +import twisted.python.runtime + +from Components.config import config + from . import log -import random -import string -from socket import getaddrinfo, AF_INET6, has_ipv6 -from twisted.internet import reactor, threads -import twisted.python.runtime +def maybe_encode(text, encoding="utf-8"): + """ + Ensures that the text is properly encoded in Python 2 and 3. + :param text: The input text (assumed to be a string). + :param encoding: The encoding format (default: utf-8). + :return: Encoded text (if necessary). + """ + if isinstance(text, bytes): + return text.decode(encoding) + return text + -from requests import packages, Session packages.urllib3.disable_warnings(packages.urllib3.exceptions.InsecureRequestWarning) -from requests.exceptions import HTTPError, RequestException -from twisted.internet.reactor import callInThread -from twisted.internet import ssl -from twisted.internet._sslverify import ClientTLSOptions -sslverify = False +# Used to check server validity +HDD_EPG_DAT = '/hdd/epg.dat' +date_format = "%Y-%m-%d" +now = datetime.now() +alloweddelta = 2 +CheckFile = "LastUpdate.txt" +ServerStatusList = {} +PARSERS = {'xmltv': 'gen_xmltv', 'genxmltv': 'gen_xmltv'} +# sslverify = False + +# Used to check server validity def threadGetPage(url=None, file=None, urlheaders=None, success=None, fail=None, *args, **kwargs): -# print('[EPGImport][threadGetPage] url, file, args, kwargs', url, " ", file, " ", args, " ", kwargs) - try: - s = Session() - s.headers = {} - response = s.get(url, verify=False, headers=urlheaders, timeout=15, allow_redirects=True) - response.raise_for_status() - # check here for content-disposition header so to extract the actual filename (if the url doesnt contain it) - content_disp = response.headers.get('Content-Disposition', '') - filename = content_disp.split('filename="')[-1].split('"')[0] - ext = splitext(file)[1] - if filename: - ext = splitext(filename)[1] - if ext and len(ext) < 6: - file += ext - if not ext: - ext = splitext(response.url)[1] - if ext and len(ext) < 6: - file += ext - - with open(file, "wb") as f: - f.write(response.content) -# print('[EPGImport][threadGetPage] file completed: ', file) - success(file, deleteFile=True) - - except HTTPError as httperror: - print('EPGImport][threadGetPage] Http error: ', httperror) - fail(httperror) # E0602 undefined name 'error' - - except RequestException as error: - print('[EPGImport][threadGetPage] error: ', error) -# if fail is not None: - fail(error) + print('[EPGImport][threadGetPage] url, file, args, kwargs', url, " ", file, " ", args, " ", kwargs) + try: + s = Session() + s.headers = {} + response = s.get(url, verify=False, headers=urlheaders, timeout=15, allow_redirects=True) + response.raise_for_status() + # check here for content-disposition header so to extract the actual filename (if the url doesnt contain it) + content_disp = response.headers.get('Content-Disposition', '') + filename = content_disp.split('filename="')[-1].split('"')[0] + ext = splitext(file)[1] + if filename: + ext = splitext(filename)[1] + if ext and len(ext) < 6: + file += ext + if not ext: + ext = splitext(response.url)[1] + if ext and len(ext) < 6: + file += ext + + with open(file, "wb") as f: + f.write(response.content) + # print('[EPGImport][threadGetPage] file completed: ', file) + success(file, deleteFile=True) + + except HTTPError as httperror: + print('EPGImport][threadGetPage] Http error: ', httperror) + fail(httperror) # E0602 undefined name 'error' + + except RequestException as error: + print('[EPGImport][threadGetPage] error: ', error) + # if fail is not None: + fail(error) # Used to check server validity -HDD_EPG_DAT = '/hdd/epg.dat' - -PARSERS = {'xmltv': 'gen_xmltv', 'genxmltv': 'gen_xmltv'} +if config.misc.epgcache_filename.value: + HDD_EPG_DAT = config.misc.epgcache_filename.value +else: + config.misc.epgcache_filename.setValue(HDD_EPG_DAT) def relImport(name): - fullname = __name__.split('.') - fullname[-1] = name - mod = __import__('.'.join(fullname)) - for n in fullname[1:]: - mod = getattr(mod, n) + fullname = __name__.split('.') + fullname[-1] = name + mod = __import__('.'.join(fullname)) + for n in fullname[1:]: + mod = getattr(mod, n) - return mod + return mod def getParser(name): - module = PARSERS.get(name, name) - mod = relImport(module) - return mod.new() + module = PARSERS.get(name, name) + mod = relImport(module) + return mod.new() def getTimeFromHourAndMinutes(hour, minute): - now = time.localtime() - begin = int(time.mktime((now.tm_year, now.tm_mon, now.tm_mday, hour, minute, 0, now.tm_wday, now.tm_yday, now.tm_isdst))) - return begin + # Check if the hour and minute are within valid ranges + if not (0 <= hour < 24): + raise ValueError("Hour must be between 0 and 23") + if not (0 <= minute < 60): + raise ValueError("Minute must be between 0 and 59") + + # Get the current local time + now = time.localtime() + + # Calculate the timestamp for the specified time (today with the given hour and minute) + begin = int(time.mktime(( + now.tm_year, # Current year + now.tm_mon, # Current month + now.tm_mday, # Current day + hour, # Specified hour + minute, # Specified minute + 0, # Seconds (set to 0) + now.tm_wday, # Day of the week + now.tm_yday, # Day of the year + now.tm_isdst # Daylight saving time (DST) + ))) + + return begin def bigStorage(minFree, default, *candidates): - try: - diskstat = statvfs(default) - free = diskstat.f_bfree * diskstat.f_bsize - if free > minFree and free > 50000000: - return default - except Exception as e: - print("[EPGImport][bigStorage] Failed to stat %s:" % default, e) - with open('/proc/mounts', 'rb') as f: - # format: device mountpoint fstype options # - mountpoints = [x.decode().split(' ', 2)[1] for x in f.readlines()] - for candidate in candidates: - if candidate in mountpoints: - try: - diskstat = statvfs(candidate) - free = diskstat.f_bfree * diskstat.f_bsize - if free > minFree: - return candidate - except Exception as e: - print("[EPGImport][bigStorage] Failed to stat %s:" % default, e) - continue - raise Exception("[EPGImport][bigStorage] Insufficient storage for download") + try: + diskstat = statvfs(default) + free = diskstat.f_bfree * diskstat.f_bsize + if free > minFree and free > 50000000: + return default + except Exception as e: + print("[EPGImport][bigStorage] Failed to stat %s:" % default, e) + + with open('/proc/mounts', 'rb') as f: + # format: device mountpoint fstype options # + mountpoints = [x.decode().split(' ', 2)[1] for x in f.readlines()] + + for candidate in candidates: + if candidate in mountpoints: + try: + diskstat = statvfs(candidate) + free = diskstat.f_bfree * diskstat.f_bsize + if free > minFree: + return candidate + except Exception as e: + print("[EPGImport][bigStorage] Failed to stat %s:" % default, e) + continue + raise Exception("[EPGImport][bigStorage] Insufficient storage for download") class OudeisImporter: - """Wrapper to convert original patch to new one that accepts multiple services""" + """Wrapper to convert original patch to new one that accepts multiple services""" - def __init__(self, epgcache): - self.epgcache = epgcache + def __init__(self, epgcache): + self.epgcache = epgcache - # difference with old patch is that services is a list or tuple, this - # wrapper works around it. + # difference with old patch is that services is a list or tuple, this + # wrapper works around it. - def importEvents(self, services, events): - for service in services: - try: - self.epgcache.importEvent(service, events) - except Exception as e: - import traceback - traceback.print_exc() - print("[EPGImport][importEvents] ### importEvents exception:", e) + def importEvents(self, services, events): + for service in services: + try: + self.epgcache.importEvents(maybe_encode(service, events)) + except Exception as e: + import traceback + traceback.print_exc() + print("[EPGImport][OudeisImporter][importEvents] ### importEvents exception:", e) def unlink_if_exists(filename): - try: - unlink(filename) - except: - pass + try: + unlink(filename) + except Exception as e: + print("[EPGImport] warning: Could not remove '%s' intermediate" % filename, repr(e)) class EPGImport: - """Simple Class to import EPGData""" - - def __init__(self, epgcache, channelFilter): - self.eventCount = None - self.epgcache = None - self.storage = None - self.sources = [] - self.source = None - self.epgsource = None - self.fd = None - self.iterator = None - self.onDone = None - self.epgcache = epgcache - self.channelFilter = channelFilter - return - - def beginImport(self, longDescUntil=None): - """Starts importing using Enigma reactor. Set self.sources before calling this.""" - if hasattr(self.epgcache, 'importEvents'): - print('[EPGImport][beginImport] using importEvents.') - self.storage = self.epgcache - elif hasattr(self.epgcache, 'importEvent'): - print('[EPGImport][beginImport] using importEvent(Oudis).') - self.storage = OudeisImporter(self.epgcache) - else: - print('[EPGImport][beginImport] oudeis patch not detected, using using epgdat_importer.epgdatclass/epg.dat instead.') - from . import epgdat_importer - self.storage = epgdat_importer.epgdatclass() - self.eventCount = 0 - if longDescUntil is None: - # default to 7 days ahead - self.longDescUntil = time.time() + 24 * 3600 * 7 - else: - self.longDescUntil = longDescUntil - self.nextImport() - - def nextImport(self): - self.closeReader() - if not self.sources: - self.closeImport() - return - self.source = self.sources.pop() - print("[EPGImport][nextImport], source =", self.source.description) - self.fetchUrl(self.source.url) - - def fetchUrl(self, filename): - if filename.startswith('http:') or filename.startswith('https:') or filename.startswith('ftp:'): -# print("[EPGImport][fetchurl] download Basic ...url filename", filename) - self.urlDownload(filename, self.afterDownload, self.downloadFail) - else: - self.afterDownload(filename, deleteFile=False) - - def urlDownload(self, sourcefile, afterDownload, downloadFail): - host = ''.join(random.choices(string.ascii_lowercase, k=5)) - check_mount = False - if exists("/media/hdd"): - with open('/proc/mounts', 'r') as f: - for line in f: - l = line.split() - if len(l) > 1 and l[1] == '/media/hdd': - check_mount = True - # print("[EPGImport][urlDownload]2 check_mount ", check_mount) - pathDefault = "/media/hdd" if check_mount else "/tmp" - path = bigStorage(9000000, pathDefault, '/media/usb', '/media/cf') # lets use HDD and flash as main backup media - filename = join(path, host) - ext = splitext(sourcefile)[1] - # Keep sensible extension, in particular the compression type - if ext and len(ext) < 6: - filename += ext - Headers = { - 'User-Agent': 'Twisted Client', - 'Accept-Encoding': 'gzip, deflate', - 'Accept': '*/*', - 'Connection': 'keep-alive'} - print("[EPGImport][urlDownload] Downloading: " + sourcefile + " to local path: " + filename) - callInThread(threadGetPage, url=sourcefile, file=filename, urlheaders=Headers, success=afterDownload, fail=downloadFail) - - def afterDownload(self, filename, deleteFile=False): -# print("[EPGImport][afterDownload] filename", filename) - try: - if not getsize(filename): - raise Exception("[EPGImport][afterDownload] File is empty") - except Exception as e: - print("[EPGImport][afterDownload] Exception filename 0", filename) - self.downloadFail(e) - return - - if self.source.parser == 'epg.dat': - if twisted.python.runtime.platform.supportsThreads(): - print("[EPGImport][afterDownload] Using twisted thread for DAT file") - threads.deferToThread(self.readEpgDatFile, filename, deleteFile).addCallback(lambda ignore: self.nextImport()) - else: - self.readEpgDatFile(filename, deleteFile) - return - - if filename.endswith('.gz'): - self.fd = gzip.open(filename, 'rb') - try: # read a bit to make sure it's a gzip file - file_content = self.fd.peek(1) - except gzip.BadGzipFile as e: - print("[EPGImport][afterDownload] File downloaded is not a valid gzip file", filename) - self.downloadFail(e) - return - - elif filename.endswith('.xz') or filename.endswith('.lzma'): - try: - import lzma - except ImportError: - from backports import lzma - - self.fd = lzma.open(filename, 'rb') - try: # read a bit to make sure it's an xz file - file_content = self.fd.peek(1) - except lzma.LZMAError as e: - print("[EPGImport][afterDownload] File downloaded is not a valid xz file", filename) - try: - print("[EPGImport][afterDownload] unlink", filename) - unlink(filename) - except Exception as e: - print("[EPGImport][afterDownload] warning: Could not remove '%s' intermediate" % filename, e) - self.downloadFail(e) - return - - else: - self.fd = open(filename, 'rb') - - if deleteFile and self.source.parser != 'epg.dat': - try: - print("[EPGImport][afterDownload] unlink", filename) - unlink(filename) - except Exception as e: - print("[EPGImport][afterDownload] warning: Could not remove '%s' intermediate" % filename, e) - - self.channelFiles = self.source.channels.downloadables() -# print("[EPGImport][afterDownload] self.source, self.channelFiles", self.source, " ", self.channelFiles) - if not self.channelFiles: - self.afterChannelDownload(None, None) - else: - filename = random.choice(self.channelFiles) - self.channelFiles.remove(filename) -# print("[EPGImport][afterDownload] download Channels ...filename", filename) - self.urlDownload(filename, self.afterChannelDownload, self.channelDownloadFail) - return - - def downloadFail(self, failure): - print("[EPGImport][downloadFail] download failed:", failure) - self.source.urls.remove(self.source.url) - if self.source.urls: - print("[EPGImport][downloadFail] Attempting alternative URL for Basic") - self.source.url = random.choice(self.source.urls) - print("[EPGImport][downloadFail] try alternative download url", self.source.url) - self.fetchUrl(self.source.url) - else: - self.nextImport() - - def afterChannelDownload(self, filename, deleteFile=True): -# print("[EPGImport][afterChannelDownload] filename", filename) - if filename: - try: - if not getsize(filename): - raise Exception("File is empty") - except Exception as e: - print("[EPGImport][afterChannelDownload] Exception filename", filename) - self.channelDownloadFail(e) - return - - if twisted.python.runtime.platform.supportsThreads(): - print("[EPGImport][afterChannelDownload] Using twisted thread - filename ", filename) - threads.deferToThread(self.doThreadRead, filename).addCallback(lambda ignore: self.nextImport()) - deleteFile = False # Thread will delete it - else: - self.iterator = self.createIterator(filename) - reactor.addReader(self) - if deleteFile and filename: - try: - unlink(filename) - except Exception as e: - print("[EPGImport][afterChannelDownload] warning: Could not remove '%s' intermediate" % filename, e) - - def channelDownloadFail(self, failure): - print("[EPGImport][channelDownloadFail] download channel failed:", failure) - if self.channelFiles: - filename = random.choice(self.channelFiles) - self.channelFiles.remove(filename) - print("[EPGImport][channelDownloadFail] retry alternative download channel - new url filename", filename) - self.urlDownload(filename, self.afterChannelDownload, self.channelDownloadFail) - else: - print("[EPGImport][channelDownloadFail] no more alternatives for channels") - self.nextImport() - - def createIterator(self, filename): -# print("[EPGImport][createIterator], filename", filename) - self.source.channels.update(self.channelFilter, filename) - return getParser(self.source.parser).iterator(self.fd, self.source.channels.items, self.source.offset) - - def readEpgDatFile(self, filename, deleteFile=False): - if not hasattr(self.epgcache, 'load'): - print("[EPGImport] Cannot load EPG.DAT files on unpatched enigma. Need CrossEPG patch.") - return - unlink_if_exists(HDD_EPG_DAT) - try: - if filename.endswith('.gz'): - print("[EPGImport] Uncompressing", filename) - import shutil - fd = gzip.open(filename, 'rb') - epgdat = open(HDD_EPG_DAT, 'wb') - shutil.copyfileobj(fd, epgdat) - del fd - epgdat.close() - del epgdat - elif filename != HDD_EPG_DAT: - symlink(filename, HDD_EPG_DAT) - print("[EPGImport] Importing", HDD_EPG_DAT) - self.epgcache.load() - if deleteFile: - unlink_if_exists(filename) - except Exception as e: - print("[EPGImport] Failed to import %s:" % filename, e) - - def fileno(self): - if self.fd is not None: - return self.fd.fileno() - else: - return - - def doThreadRead(self, filename): - """This is used on PLi with threading""" - for data in self.createIterator(filename): - if data is not None: - self.eventCount += 1 - r, d = data - if d[0] > self.longDescUntil: - # Remove long description (save RAM memory) - d = d[:4] + ('',) + d[5:] - try: - self.storage.importEvents(r, (d,)) - except Exception as e: - import traceback - print("[EPGImport][doThreadRead] ### importEvents exception:", e) - print("[EPGImport][doThreadRead] ### thread is ready ### Events:", self.eventCount) - if filename: - try: - unlink(filename) - except Exception as e: - print("[EPGImport] warning: Could not remove '%s' intermediate" % filename, e) - - return - - def doRead(self): - """called from reactor to read some data""" - try: - # returns tuple (ref, data) or None when nothing available yet. - # data = self.iterator.next() - data = next(self.iterator) - - if data is not None: - self.eventCount += 1 - try: - r, d = data - if d[0] > self.longDescUntil: - # Remove long description (save RAM memory) - d = d[:4] + ('',) + d[5:] - self.storage.importEvents(r, (d,)) - except Exception as e: - print("[EPGImport][doRead] importEvents exception:", e) - - except StopIteration: - self.nextImport() - - return - - def connectionLost(self, failure): - """called from reactor on lost connection""" - # This happens because enigma calls us after removeReader - print("[EPGImport] connectionLost", failure) - - def closeReader(self): - if self.fd is not None: - reactor.removeReader(self) - self.fd.close() - self.fd = None - self.iterator = None - return - - def closeImport(self): - self.closeReader() - self.iterator = None - self.source = None - if hasattr(self.storage, 'epgfile'): - needLoad = self.storage.epgfile - else: - needLoad = None - self.storage = None - if self.eventCount is not None: - print("[EPGImport] imported %d events" % self.eventCount) - reboot = False - if self.eventCount: - if needLoad: - print("[EPGImport] no Oudeis patch, load(%s) required" % needLoad) - reboot = True - try: - if hasattr(self.epgcache, 'load'): - print("[EPGImport] attempt load() patch") - if needLoad != HDD_EPG_DAT: - symlink(needLoad, HDD_EPG_DAT) - self.epgcache.load() - reboot = False - unlink_if_exists(needLoad) - except Exception as e: - print("[EPGImport] load() failed:", e) - - elif hasattr(self.epgcache, 'save'): - self.epgcache.save() - elif hasattr(self.epgcache, 'timeUpdated'): - self.epgcache.timeUpdated() - if self.onDone: - self.onDone(reboot=reboot, epgfile=needLoad) - self.eventCount = None - print("[EPGImport] #### Finished ####") - return - - def isImportRunning(self): - return self.source is not None + """Simple Class to import EPGData""" + + def __init__(self, epgcache, channelFilter): + self.eventCount = None + self.epgcache = None + self.storage = None + self.sources = [] + self.source = None + self.epgsource = None + self.fd = None + self.iterator = None + self.onDone = None + self.epgcache = epgcache + self.channelFilter = channelFilter + return + + def beginImport(self, longDescUntil=None): + """Starts importing using Enigma reactor. Set self.sources before calling this.""" + if hasattr(self.epgcache, 'importEvents'): + print('[EPGImport][beginImport] using importEvents.') + self.storage = self.epgcache + elif hasattr(self.epgcache, 'importEvent'): + print('[EPGImport][beginImport] using importEvent(Oudis).') + self.storage = OudeisImporter(self.epgcache) + else: + print('[EPGImport][beginImport] oudeis patch not detected, using using epgdat_importer.epgdatclass/epg.dat instead.') + from . import epgdat_importer + self.storage = epgdat_importer.epgdatclass() + + self.eventCount = 0 + if longDescUntil is None: + # default to 7 days ahead + self.longDescUntil = time.time() + 24 * 3600 * 7 + else: + self.longDescUntil = longDescUntil + self.nextImport() + + def nextImport(self): + self.closeReader() + if not self.sources: + self.closeImport() + return + + self.source = self.sources.pop() + + print("[EPGImport][nextImport], source=", self.source.description, file=log) + self.fetchUrl(self.source.url) + + def fetchUrl(self, filename): + if filename.startswith('http:') or filename.startswith('https:') or filename.startswith('ftp:'): + # print("[EPGImport][fetchurl]Attempting to download from: ", filename) + self.urlDownload(filename, self.afterDownload, self.downloadFail) + else: + self.afterDownload(filename, deleteFile=False) + + def urlDownload(self, sourcefile, afterDownload, downloadFail): + host = ''.join(choices(ascii_lowercase, k=5)) + check_mount = False + if exists("/media/hdd"): + with open('/proc/mounts', 'r') as f: + for line in f: + ln = line.split() + if len(ln) > 1 and ln[1] == '/media/hdd': + check_mount = True + + # print("[EPGImport][urlDownload]2 check_mount ", check_mount) + pathDefault = "/media/hdd" if check_mount else "/tmp" + path = bigStorage(9000000, pathDefault, '/media/usb', '/media/cf') # lets use HDD and flash as main backup media + + filename = join(path, host) + if isinstance(sourcefile, list): + sourcefile = sourcefile[0] + + print("[EPGImport][urlDownload] Downloading: " + str(sourcefile) + " to local path: " + str(filename)) + ext = splitext(sourcefile)[1] + # Keep sensible extension, in particular the compression type + if ext and len(ext) < 6: + filename += ext + # sourcefile = sourcefile.encode('utf-8') + sourcefile = str(sourcefile) + print('sourcfile str=', sourcefile) + Headers = { + 'User-Agent': 'Twisted Client', + 'Accept-Encoding': 'gzip, deflate', + 'Accept': '*/*', + 'Connection': 'keep-alive'} + + print("[EPGImport][urlDownload] Downloading: " + sourcefile + " to local path: " + filename) + callInThread(threadGetPage, url=sourcefile, file=filename, urlheaders=Headers, success=afterDownload, fail=downloadFail) + + def afterDownload(self, filename, deleteFile=False): + # print("[EPGImport][afterDownload] filename", filename) + if not exists(filename): + self.downloadFail("File not exists") + return + try: + if not getsize(filename): + raise Exception("[EPGImport][afterDownload] File is empty") + except Exception as e: + print("[EPGImport][afterDownload] Exception filename 0", filename) + self.downloadFail(e) + return + + if self.source.parser == 'epg.dat': + if twisted.python.runtime.platform.supportsThreads(): + print("[EPGImport][afterDownload] Using twisted thread for DAT file", file=log) + threads.deferToThread(self.readEpgDatFile, filename, deleteFile).addCallback(lambda ignore: self.nextImport()) + else: + self.readEpgDatFile(filename, deleteFile) + return + + if filename.endswith('.gz'): + self.fd = gzip.open(filename, 'rb') + try: # read a bit to make sure it's a gzip file + self.fd.read(10) + self.fd.seek(0, 0) + except gzip.BadGzipFile as e: + print("[EPGImport][afterDownload] File downloaded is not a valid gzip file", filename, file=log) + self.downloadFail(e) + return + + elif filename.endswith('.xz') or filename.endswith('.lzma'): + try: + import lzma + except ImportError: + from backports import lzma + self.fd = lzma.open(filename, 'rb') + try: # read a bit to make sure it's an xz file + self.fd.read(10) + self.fd.seek(0, 0) + except lzma.LZMAError as e: + print("[EPGImport[afterDownload]] File downloaded is not a valid xz file", filename, file=log) + self.downloadFail(e) + return + + else: + self.fd = open(filename, 'rb') + + if deleteFile and self.source.parser != 'epg.dat': + try: + print("[EPGImport][afterDownload] unlink", filename, file=log) + unlink_if_exists(filename) + except Exception as e: + print("[EPGImport][afterDownload] warning: Could not remove '%s' intermediate" % filename, e, file=log) + + self.channelFiles = self.source.channels.downloadables() + if not self.channelFiles: + self.afterChannelDownload(None, None) + else: + filename = choice(self.channelFiles) + self.channelFiles.remove(filename) + self.urlDownload(filename, self.afterChannelDownload, self.channelDownloadFail) + return + + def downloadFail(self, failure): + print("[EPGImport][downloadFail] download failed:", failure, file=log) + if self.source.url in self.source.urls: + self.source.urls.remove(self.source.url) + if self.source.urls: + print("[EPGImport] Attempting alternative URL", file=log) + self.source.url = choice(self.source.urls) + print("[EPGImport][downloadFail] try alternative download url", self.source.url) + self.fetchUrl(self.source.url) + else: + self.nextImport() + + def afterChannelDownload(self, filename, deleteFile=True): + print("[EPGImport] afterChannelDownload filename", filename, file=log) + if filename: + try: + if not getsize(filename): + raise Exception("File is empty") + except Exception as e: + print("[EPGImport][afterChannelDownload] Exception filename", filename) + self.channelDownloadFail(e) + return + if twisted.python.runtime.platform.supportsThreads(): + print("[EPGImport] Using twisted thread", file=log) + threads.deferToThread(self.doThreadRead, filename).addCallback(lambda ignore: self.nextImport()) + deleteFile = False # Thread will delete it + else: + self.iterator = self.createIterator(filename) + reactor.addReader(self) + if deleteFile and filename: + try: + unlink_if_exists(filename) + except Exception as e: + print("[EPGImport][afterChannelDownload] warning: Could not remove '%s' intermediate" % filename, e, file=log) + + def channelDownloadFail(self, failure): + print("[EPGImport][channelDownloadFail] download channel failed:", failure, file=log) + if self.channelFiles: + filename = choice(self.channelFiles) + self.channelFiles.remove(filename) + self.urlDownload(filename, self.afterChannelDownload, self.channelDownloadFail) + else: + print("[EPGImport][channelDownloadFail] no more alternatives for channels", file=log) + self.nextImport() + + def createIterator(self, filename): + # print("[EPGImport][createIterator], filename", filename) + self.source.channels.update(self.channelFilter, filename) + return getParser(self.source.parser).iterator(self.fd, self.source.channels.items, self.source.offset) + + def readEpgDatFile(self, filename, deleteFile=False): + if not hasattr(self.epgcache, 'load'): + print("[EPGImport][readEpgDatFile] Cannot load EPG.DAT files on unpatched enigma. Need CrossEPG patch.", file=log) + return + + unlink_if_exists(HDD_EPG_DAT) + + try: + if filename.endswith('.gz'): + print("[EPGImport][readEpgDatFile] Uncompressing", filename, file=log) + import shutil + fd = gzip.open(filename, 'rb') + epgdat = open(HDD_EPG_DAT, 'wb') + shutil.copyfileobj(fd, epgdat) + del fd + epgdat.close() + del epgdat + + elif filename != HDD_EPG_DAT: + symlink(filename, HDD_EPG_DAT) + + print("[EPGImport][readEpgDatFile] Importing", HDD_EPG_DAT, file=log) + self.epgcache.load() + + if deleteFile: + unlink_if_exists(filename) + except Exception as e: + print("[EPGImport][readEpgDatFile] Failed to import %s:" % filename, e, file=log) + + def fileno(self): + if self.fd is not None: + return self.fd.fileno() + else: + return + + def doThreadRead(self, filename): + """This is used on PLi with threading""" + for data in self.createIterator(filename): + if data is not None: + self.eventCount += 1 + r, d = data + if d[0] > self.longDescUntil: + # Remove long description (save RAM memory) + d = d[:4] + ('',) + d[5:] + try: + self.storage.importEvents(r, (d,)) + except Exception as e: + print("[EPGImport][doThreadRead] ### importEvents exception:", e, file=log) + print("[EPGImport][doThreadRead] ### thread is ready ### Events:", self.eventCount, file=log) + if filename: + try: + unlink(filename) + except Exception as e: + print("[EPGImport][doThreadRead] warning: Could not remove '%s' intermediate" % filename, e, file=log) + return + + def doRead(self): + """called from reactor to read some data""" + try: + # returns tuple (ref, data) or None when nothing available yet. + data = next(self.iterator) + if data is not None: + self.eventCount += 1 + try: + r, d = data + if d[0] > self.longDescUntil: + # Remove long description (save RAM memory) + d = d[:4] + ('',) + d[5:] + self.storage.importEvents(r, (d,)) + except Exception as e: + print("[EPGImport][doRead] importEvents exception:", e, file=log) + except StopIteration: + self.nextImport() + return + + def connectionLost(self, failure): + """called from reactor on lost connection""" + # This happens because enigma calls us after removeReader + print("[EPGImport][connectionLost]failure", failure, file=log) + + def closeReader(self): + if self.fd is not None: + reactor.removeReader(self) + self.fd.close() + self.fd = None + self.iterator = None + return + + def closeImport(self): + self.closeReader() + self.iterator = None + self.source = None + if hasattr(self.storage, 'epgfile'): + needLoad = self.storage.epgfile + else: + needLoad = None + + self.storage = None + + if self.eventCount is not None: + print("[EPGImport] imported %d events" % self.eventCount, file=log) + reboot = False + if self.eventCount: + if needLoad: + print("[EPGImport] no Oudeis patch, load(%s) required" % needLoad, file=log) + reboot = True + try: + if hasattr(self.epgcache, 'load'): + print("[EPGImport] attempt load() patch", file=log) + if needLoad != HDD_EPG_DAT: + symlink(needLoad, HDD_EPG_DAT) + self.epgcache.load() + reboot = False + unlink_if_exists(needLoad) + except Exception as e: + print("[EPGImport] load() failed:", e, file=log) + elif hasattr(self.epgcache, 'save'): + self.epgcache.save() + elif hasattr(self.epgcache, 'timeUpdated'): + self.epgcache.timeUpdated() + if self.onDone: + self.onDone(reboot=reboot, epgfile=needLoad) + self.eventCount = None + log.write("[EPGImport] #### Finished ####\n") + + def isImportRunning(self): + return self.source is not None diff --git a/src/EPGImport/ExpandableSelectionList.py b/src/EPGImport/ExpandableSelectionList.py index 73eb368..e28b2fe 100644 --- a/src/EPGImport/ExpandableSelectionList.py +++ b/src/EPGImport/ExpandableSelectionList.py @@ -1,6 +1,6 @@ from Components.MenuList import MenuList +from enigma import eListboxPythonMultiContent, gFont, RT_HALIGN_LEFT from Tools.Directories import resolveFilename, SCOPE_CURRENT_SKIN -from enigma import eListboxPythonMultiContent, eListbox, gFont, RT_HALIGN_LEFT from Tools.LoadPixmap import LoadPixmap import skin @@ -12,20 +12,18 @@ def loadSettings(): global cat_desc_loc, entry_desc_loc, cat_icon_loc, entry_icon_loc x, y, w, h = skin.parameters.get("EPGImportSelectionListDescr", skin.parameters.get("SelectionListDescr", (25, 3, 650, 30))) - ind = x # Indent the entries by the same amount as the icon. + ind = x # Indent the entries by the same amount as the icon. cat_desc_loc = (x, y, w, h) entry_desc_loc = (x + ind, y, w - ind, h) x, y, w, h = skin.parameters.get("EPGImportSelectionListLock", skin.parameters.get("SelectionListLock", (0, 2, 25, 24))) - cat_icon_loc = (x, 0, w, y + y + h) # The category icon is larger + cat_icon_loc = (x, 0, w, y + y + h) # The category icon is larger entry_icon_loc = (x + ind, y, w, h) def category(description, isExpanded=False): global cat_desc_loc, cat_icon_loc - if isExpanded: - icon = expandedIcon - else: - icon = expandableIcon + icon = expandedIcon if isExpanded else expandableIcon + return [ (description, isExpanded, []), (eListboxPythonMultiContent.TYPE_TEXT,) + cat_desc_loc + (0, RT_HALIGN_LEFT, description), @@ -80,13 +78,13 @@ def __init__(self, tree=None, enableWrapAround=False): def updateFlatList(self): # Update the view of the items by flattening the tree - l = [] + ls = [] for cat in self.tree: - l.append(cat) + ls.append(cat) if isExpanded(cat): for item in cat[0][2]: - l.append(entry(*item)) - self.setList(l) + ls.append(entry(*item)) + self.setList(ls) def toggleSelection(self): idx = self.getSelectedIndex() diff --git a/src/EPGImport/OfflineImport.py b/src/EPGImport/OfflineImport.py index 287e748..8b178a5 100755 --- a/src/EPGImport/OfflineImport.py +++ b/src/EPGImport/OfflineImport.py @@ -18,7 +18,6 @@ # 6) Reinstate your renamed __init__.py # # called modules EPGImport, epgdat, epgdat_importer, log -import os import sys import time from . import EPGConfig @@ -32,10 +31,12 @@ class FakeEnigma: def getInstance(self): return self -# def load(self): -# print("...load...") -# def importEvents(self, *args): -# print(args) + """ + def load(self): + print("...load...") + def importEvents(self, *args): + print(args) + """ def importFrom(epgimport, sourceXml): @@ -69,14 +70,16 @@ def stop(self): epgimport.beginImport(longDescUntil=time.time() + (5 * 24 * 3600)) EPGImport.reactor.run() -#---------------------------------------------- +# ---------------------------------------------- def done(reboot=False, epgfile=None): EPGImport.reactor.stop() print("Done, data is in", epgfile) - ### When code arrives here, EPG data is stored in filename EPGImport.HDD_EPG_DAT - ### So to copy it to FTP or whatever, this is the place to add that code. + """ + When code arrives here, EPG data is stored in filename EPGImport.HDD_EPG_DAT + So to copy it to FTP or whatever, this is the place to add that code. + """ if len(sys.argv) <= 1: diff --git a/src/EPGImport/__init__.py b/src/EPGImport/__init__.py index 2e4b2d9..93e3774 100755 --- a/src/EPGImport/__init__.py +++ b/src/EPGImport/__init__.py @@ -1,24 +1,23 @@ # -*- coding: utf-8 -*- -from __future__ import print_function from Components.Language import language -from Tools.Directories import resolveFilename, SCOPE_PLUGINS, SCOPE_LANGUAGE -import os -import gettext +from Tools.Directories import resolveFilename, SCOPE_PLUGINS +from gettext import bindtextdomain, dgettext, gettext PluginLanguageDomain = "EPGImport" PluginLanguagePath = "Extensions/EPGImport/locale" def localeInit(): - gettext.bindtextdomain(PluginLanguageDomain, resolveFilename(SCOPE_PLUGINS, PluginLanguagePath)) + bindtextdomain(PluginLanguageDomain, resolveFilename(SCOPE_PLUGINS, PluginLanguagePath)) def _(txt): - if gettext.dgettext(PluginLanguageDomain, txt): - return gettext.dgettext(PluginLanguageDomain, txt) + if dgettext(PluginLanguageDomain, txt): + return dgettext(PluginLanguageDomain, txt) else: - print("[" + PluginLanguageDomain + "] fallback to default translation for " + txt) - return gettext.gettext(txt) + print(f"[{PluginLanguageDomain}] fallback to default translation for {txt}") + return gettext(txt) + localeInit() language.addCallback(localeInit) diff --git a/src/EPGImport/boot.py b/src/EPGImport/boot.py index e8acf2b..2459ea9 100644 --- a/src/EPGImport/boot.py +++ b/src/EPGImport/boot.py @@ -1,8 +1,8 @@ #!/usr/bin/python -from __future__ import print_function -import os -import time -import shutil +from os import listdir, unlink +from os.path import exists, getctime, join +from time import time +from shutil import copy2 MEDIA = ("/media/hdd/", "/media/usb/", "/media/mmc/", "/media/cf/", "/tmp") @@ -11,16 +11,16 @@ def findEpg(): candidates = [] for path in MEDIA: try: - if os.path.exists(path): - for fn in os.listdir(path): + if exists(path): + for fn in listdir(path): if "epg.dat" in fn: - ffn = os.path.join(path, fn) - candidates.append((os.path.getctime(ffn), ffn)) + ffn = join(path, fn) + candidates.append((getctime(ffn), ffn)) except: - pass # ignore errors. + pass # ignore errors. if not candidates: return None - candidates.sort() # order by ctime... + candidates.sort() # order by ctime... # best candidate is most recent filename. return candidates[-1][1] @@ -28,14 +28,14 @@ def findEpg(): def checkCrashLog(): for path in MEDIA[:-1]: try: - dirList = os.listdir(path) + dirList = listdir(path) for fname in dirList: - if fname[0:13] == 'enigma2_crash': + if fname[0:13] == "enigma2_crash": try: crashtime = 0 crashtime = int(fname[14:24]) - howold = time.time() - crashtime - except: + howold = time() - crashtime + except Exception: print("no time found in filename") if howold < 120: print("recent crashfile found analysing") @@ -45,34 +45,34 @@ def checkCrashLog(): if (crashtext.find("FATAL: LINE ") != -1): print("string found, deleting epg.dat") return True - except: + except Exception: pass return False def findNewEpg(): for path in MEDIA: - fn = os.path.join(path, 'epg_new.dat') - if os.path.exists(fn): + fn = join(path, "epg_new.dat") + if exists(fn): return fn epg = findEpg() newepg = findNewEpg() -print("Epg.dat found at : ", epg) -print("newepg found at : ", newepg) +print(f"Epg.dat found at : {epg}") +print(f"newepg found at : {newepg}") -##Delete epg.dat if last crash was because of error in epg.dat +# Delete epg.dat if last crash was because of error in epg.dat if checkCrashLog(): try: - os.unlink(epg) + unlink(epg) except: print("delete error") -##if excists cp epg_new.dat epg.dat +# if excists cp epg_new.dat epg.dat if newepg: if epg: print("replacing epg.dat with newmade version") - os.unlink(epg) - shutil.copy2(newepg, epg) + unlink(epg) + copy2(newepg, epg) diff --git a/src/EPGImport/epgdat.py b/src/EPGImport/epgdat.py index ec2e2a3..d547a34 100755 --- a/src/EPGImport/epgdat.py +++ b/src/EPGImport/epgdat.py @@ -3,18 +3,19 @@ # Heavily modified by MiLo http://www.sat4all.com/ # Lots of stuff removed by Mike Looijmans # Updated for python3 by TwolDE & Huevos with testing input by Thawtes - +from datetime import datetime import os import struct -from datetime import datetime -# EpgDatV8 = os.path.isfile("/etc/image-version") and "distro=openvix" in open("/etc/image-version").read() + EpgDatV8 = True try: from . import dreamcrc - crc32_dreambox = lambda d, t: dreamcrc.crc32(d, t) & 0xffffffff + + def crc32_dreambox(d, t): + return dreamcrc.crc32(d, t) & 0xffffffff print("[EPGImport] using C module, yay") -except: +except ImportError: print("[EPGImport] failed to load C implementation, sorry") # this table is used by CRC32 routine below (used by Dreambox for @@ -94,27 +95,27 @@ def crc32_dreambox(crcdata, crctype, crctable=CRCTABLE): # ML Optimized: local CRCTABLE (locals are faster), remove self, remove code that has no effect, faster loop - #crc=0x00000000 - #crc=((crc << 8 ) & 0xffffff00) ^ crctable[((crc >> 24) ^ crctype) & 0x000000ff] + # crc=0x00000000 + # crc=((crc << 8 ) & 0xffffff00) ^ crctable[((crc >> 24) ^ crctype) & 0x000000ff] crc = crctable[crctype & 0x000000ff] crc = ((crc << 8) & 0xffffff00) ^ crctable[((crc >> 24) ^ len(crcdata)) & 0x000000ff] for d in crcdata: - crc = ((crc << 8) & 0xffffff00) ^ crctable[((crc >> 24) ^ d) & 0x000000ff] + crc = ((crc << 8) & 0xffffff00) ^ crctable[((crc >> 24) ^ ord(d)) & 0x000000ff] return crc -# convert time or duration from datetime format to 3 bytes hex value -# this doesn't convert to hex but obviously the originators thought it did, and is part of EPG structure definitions. +# convert time or length from datetime format to 3 bytes hex value +# i.e. 20:25:30 -> 0x20 , 0x25 , 0x30 + + def TL_hexconv(dt): return ( (dt.hour % 10) + (16 * (dt.hour // 10)), (dt.minute % 10) + (16 * (dt.minute // 10)), (dt.second % 10) + (16 * (dt.second // 10)) - ) + ) class epgdat_class: - # temp files used for EPG.DAT creation - LAMEDB = '/etc/enigma2/lamedb' EPGDAT_FILENAME = 'epgtest.dat' @@ -173,43 +174,39 @@ def set_excludedsid(self, exsidlist): self.EXCLUDED_SID = exsidlist # assembling short description (type 0x4d , it's the Title) and compute its crc - def shortDescription(self, sd): - sdbytes = sd.encode() - beng = "eng".encode() - b0 = "\0".encode() + def short_desc(self, s): # 0x15 is UTF-8 encoding. - sdbin = self.s_3sBB.pack(beng, int(len(sdbytes) + 1), 0x15) + sdbytes + b0 - return (crc32_dreambox(sdbin, 0x4d), sdbin) + res = self.s_3sBB.pack('eng', len(s) + 1, 0x15) + str(s) + "\0" + return (crc32_dreambox(res, 0x4d), res) # assembling long description (type 0x4e) and compute its crc - def longDescription(self, ld): - beng = "eng".encode() - ldres = [] + def long_desc(self, s): + r = [] # compute total number of descriptions, block 245 bytes each # number of descriptions start to index 0 - ldbytes = ld.encode() - num_tot_desc = (len(ldbytes) + 244) // 245 + num_tot_desc = (len(s) + 244) // 245 for i in range(num_tot_desc): - ssub = ldbytes[i * 245:i * 245 + 245] - ldbin = self.s_B3sBBB.pack((i << 4) + (num_tot_desc-1), beng, 0x00, int(len(ssub) + 1), 0x15) + ssub - ldres.append((crc32_dreambox(ldbin, 0x4e), ldbin)) - return ldres + ssub = s[i * 245: i * 245 + 245] + sres = self.s_B3sHBB.pack((i << 4) + (num_tot_desc - 1), 'eng', 0x0000, len(ssub) + 1, 0x15) + str(ssub) + r.append((crc32_dreambox(sres, 0x4e), sres)) + return r def add_event(self, starttime, duration, title, description): - # print("[epgdat][add_event]add event:- starttime, duration, title, description", starttime, duration, title, description) - self.events.append((starttime, duration, self.shortDescription(title[:240]), self.longDescription(description))) + # print("add event : ", event_starttime_unix_gmt, "title : ", event_title) + self.events.append((starttime, duration, self.short_desc(title[:240]), self.long_desc(description))) def preprocess_events_channel(self, services): EPG_EVENT_DATA_id = 0 for service in services: - # print("[epgdat][preprocess_events_channel] service : ", service) # skip empty lines, they make a mess if not service.strip(): continue # prepare and write CHANNEL INFO record ssid = service.split(":") # write CHANNEL INFO record (sid, onid, tsid, eventcount) - self.EPG_TMP_FD.write(self.s_IIII.pack(int(ssid[3], 16), int(ssid[5], 16), int(ssid[4], 16), int(len(self.events)))) + self.EPG_TMP_FD.write(self.s_IIII.pack( + int(ssid[3], 16), int(ssid[5], 16), + int(ssid[4], 16), len(self.events))) self.EPG_HEADER1_channel_count += 1 # event_dict.keys() are numeric so indexing is possibile # key is the same thing as counter and is more simple to manage last-1 item @@ -219,60 +216,61 @@ def preprocess_events_channel(self, services): s_I = self.s_I for event in events: # **** (1) : create DESCRIPTION HEADER / DATA **** - EPG_EVENT_HEADER_datasize = 0 - - # short description (title) type 0x4d self.shortDescription(title[:240]) = event[2] - shortDescription = event[2] # (crc32, short description packed) - EPG_EVENT_HEADER_datasize += 4 # add 4 bytes for a single REF DESC (CRC32) - - if shortDescription[0] not in list(self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER.keys()): - pack_1 = s_BB.pack(0x4d, int(len(shortDescription[1]))) + shortDescription[1] # DESCRIPTION DATA + # short description (title) type 0x4d + short_d = event[2] + EPG_EVENT_HEADER_datasize += 4 # add 4 bytes for a sigle REF DESC (CRC32) + # if short_d[0] not in epg_event_description_dict: + # if not exist_event(short_d[0]) : + if short_d[0] not in self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER: + # DESCRIPTION DATA + pack_1 = s_BB.pack(0x4d, len(short_d[1])) + short_d[1] # DESCRIPTION HEADER (2 int) will be computed at the end just before EPG.DAT write - # because it needs the total number of the same descriptions called by any channel section - self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[shortDescription[0]] = [pack_1, 1] # save CRC32 and short description data packed + # because it need the total number of the same description called by many channel section + # save_event(short_d[0],[pack_1,1]) + self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[short_d[0]] = [pack_1, 1] self.EPG_HEADER2_description_count += 1 else: - #increment_event(shortDescription[0]) - self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[shortDescription[0]][1] += 1 - - # long description type 0x4e self.longDescription(description) = event[3] - longDescription = event[3] # (crc32, long description(s) packed) - EPG_EVENT_HEADER_datasize += 4 * len(longDescription) # add 4 bytes for each CRC32 - for desc in longDescription: # desc = crc + packed long desc - if desc[0] not in list(self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER.keys()): + # increment_event(short_d[0]) + self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[short_d[0]][1] += 1 + # long description type 0x4e + long_d = event[3] + EPG_EVENT_HEADER_datasize += 4 * len(long_d) # add 4 bytes for a single REF DESC (CRC32) + for desc in long_d: + # if long_d[i][0] not in epg_event_description_dict: + # if not exist_event(long_d[i][0]) : + if desc[0] not in self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER: # DESCRIPTION DATA - pack_2 = s_BB.pack(0x4e, int(len(desc[1]))) + desc[1] # short description + pack_1 = s_BB.pack(0x4e, len(desc[1])) + desc[1] self.EPG_HEADER2_description_count += 1 # DESCRIPTION HEADER (2 int) will be computed at the end just before EPG.DAT write # because it need the total number of the same description called by different channel section - #save_event(longDescription[i][0],[pack_1,1]) - self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[desc[0]] = [pack_2, 1] # save crc32 and description packed + # save_event(long_d[i][0],[pack_1,1]) + self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[desc[0]] = [pack_1, 1] else: - #increment_event(longDescription[i][0]) + # increment_event(long_d[i][0]) self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[desc[0]][1] += 1 - - # **** (2) : have all crc32's and now can create EVENT HEADER / DATA **** - # EVENT HEADER (3 bytes: 0x01 , 0x00, 10 bytes + number of CRC32 * 4) - pack_3 = s_BBB.pack(0x01, 0x00, 0x0a + EPG_EVENT_HEADER_datasize) - self.EPG_TMP_FD.write(pack_3) - - # extract date and time from event numbers are seconds + # **** (2) : have REF DESC and now can create EVENT HEADER / DATA **** + # EVENT HEADER (2 bytes: 0x01 , 10 bytes + number of CRC32 * 4) + pack_1 = s_BB.pack(0x01, 0x0a + EPG_EVENT_HEADER_datasize) + self.EPG_TMP_FD.write(pack_1) + # extract date and time from # unix format (second since 1970) and already GMT corrected - event_time_HMS = datetime.utcfromtimestamp(event[0]) # actually YYYY-MM-DD HH:MM:SS - dvb_date = event_time_HMS.toordinal() - self.EPG_PROLEPTIC_ZERO_DAY # epg.dat date is = (proleptic date - epg_zero_day) - event_duration_HMS = datetime.utcfromtimestamp(event[1]) # actually 1970-01-01 HH:MM:SS + event_time_HMS = datetime.utcfromtimestamp(event[0]) + event_length_HMS = datetime.utcfromtimestamp(event[1]) + # epg.dat date is = (proleptic date - epg_zero_day) + dvb_date = event_time_HMS.toordinal() - self.EPG_PROLEPTIC_ZERO_DAY # EVENT DATA - # simply create an incremental ID, starting from '1' + # simply create an incremental ID, starting from '1' # event_id appears to be per channel, so this should be okay. EPG_EVENT_DATA_id += 1 - pack_4 = self.s_b_HH.pack(EPG_EVENT_DATA_id, dvb_date) # ID and DATE , always in BIG_ENDIAN - pack_5 = s_BBB.pack(*TL_hexconv(event_time_HMS)) # Start time - pack_6 = s_BBB.pack(*TL_hexconv(event_duration_HMS)) # Duration - pack_7 = s_I.pack(shortDescription[0]) # REF DESC crc short (title) - for description in longDescription: - pack_7 += s_I.pack(description[0]) # REF DESC long - self.EPG_TMP_FD.write(pack_4 + pack_5 + pack_6 + pack_7) + pack_1 = self.s_b_HH.pack(EPG_EVENT_DATA_id, dvb_date) # ID and DATE , always in BIG_ENDIAN + pack_2 = s_BBB.pack(*TL_hexconv(event_time_HMS)) # START TIME + pack_3 = s_BBB.pack(*TL_hexconv(event_length_HMS)) # LENGTH + pack_4 = s_I.pack(short_d[0]) # REF DESC short (title) + for d in long_d: + pack_4 += s_I.pack(d[0]) # REF DESC long + self.EPG_TMP_FD.write(pack_1 + pack_2 + pack_3 + pack_4) # reset again event container self.EPG_TOTAL_EVENTS += len(self.events) self.events = [] @@ -282,10 +280,7 @@ def final_process(self): self.EPG_TMP_FD.close() epgdat_fd = open(self.EPGDAT_FILENAME, "wb") # HEADER 1 - if EpgDatV8: - pack_1 = struct.pack(self.LB_ENDIAN + "I13sI", 0x98765432, b'ENIGMA_EPG_V8', int(self.EPG_HEADER1_channel_count)) - else: - pack_1 = struct.pack(self.LB_ENDIAN + "I13sI", 0x98765432, b'ENIGMA_EPG_V7', int(self.EPG_HEADER1_channel_count)) + pack_1 = struct.pack(self.LB_ENDIAN + "I13sI", 0x98765432, 'ENIGMA_EPG_V8', self.EPG_HEADER1_channel_count) epgdat_fd.write(pack_1) # write first EPG.DAT section EPG_TMP_FD = open(self.EPGDAT_TMP_FILENAME, "rb") @@ -297,14 +292,14 @@ def final_process(self): EPG_TMP_FD.close() # HEADER 2 s_ii = self.s_II - pack_2 = self.s_I.pack(self.EPG_HEADER2_description_count) - epgdat_fd.write(pack_2) + pack_1 = self.s_I.pack(self.EPG_HEADER2_description_count) + epgdat_fd.write(pack_1) # event MUST BE WRITTEN IN ASCENDING ORDERED using HASH CODE as index for temp in sorted(list(self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER.keys())): - temp_crc_data = self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[temp] - #pack_4=struct.pack(LB_ENDIAN+"II",int(temp,16),temp_crc_data[1]) - pack_4 = s_ii.pack(temp, temp_crc_data[1]) # crc and packed data - epgdat_fd.write(pack_4 + temp_crc_data[0]) # packed (crc, packed data) & crc + pack_2 = self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[temp] + # pack_1=struct.pack(LB_ENDIAN+"II",int(temp,16),pack_2[1]) + pack_1 = s_ii.pack(temp, pack_2[1]) + epgdat_fd.write(pack_1 + pack_2[0]) epgdat_fd.close() # *** cleanup ** if os.path.exists(self.EPGDAT_TMP_FILENAME): diff --git a/src/EPGImport/epgdat_importer.py b/src/EPGImport/epgdat_importer.py index 5361204..1a27cb4 100755 --- a/src/EPGImport/epgdat_importer.py +++ b/src/EPGImport/epgdat_importer.py @@ -2,7 +2,6 @@ import os import sys -import sys # Hack to make this test run on Windows (where the reactor cannot handle files) if sys.platform.startswith('win'): tmppath = '.' @@ -29,7 +28,7 @@ def __init__(self): self.epg = epgdat.epgdat_class(path, settingspath, self.epgfile) def importEvents(self, services, dataTupleList): - 'This method is called repeatedly for each bit of data' + '''This method is called repeatedly for each bit of data''' if services != self.services: self.commitService() self.services = services @@ -56,8 +55,8 @@ def epg_done(self): def checkPath(self, path): f = os.popen('mount', "r") - for l in f: - if l.find(path) != -1: + for lx in f.xreadlines(): + if lx.find(path) != - 1: return True return False diff --git a/src/EPGImport/filterCustomChannel.py b/src/EPGImport/filterCustomChannel.py new file mode 100644 index 0000000..ab12f09 --- /dev/null +++ b/src/EPGImport/filterCustomChannel.py @@ -0,0 +1,110 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +from Components.config import config +from re import sub +from xml.sax.saxutils import unescape +from xml.etree.cElementTree import iterparse + + +global filterCustomChannel + + +# Verifica che la configurazione epgimport sia definita +if hasattr(config.plugins, "epgimport") and hasattr(config.plugins.epgimport, "filter_custom_channel"): + filterCustomChannel = config.plugins.epgimport.filter_custom_channel.value +else: + filterCustomChannel = False # Fallback se non è definito + + +def get_xml_rating_string(elem): + r = '' + try: + for node in elem.findall("rating"): + for val in node.findall("value"): + txt = val.text.replace("+", "") + if not r: + r = txt + except Exception as e: + print("[XMLTVConverter] get_xml_rating_string error:", e) + return r.decode() if isinstance(r, bytes) else r + + +def xml_unescape(text): + if not isinstance(text, str): + return '' + return sub( + r' | |\s+', + ' ', + unescape( + text.strip(), + entities={ + r"«": "«", + r"«": "«", + r"»": "»", + r"»": "»", + r"'": r"'", + r""": r'"', + r"|": r"|", + r"[": r"[", + r"]": r"]", + } + ) + ) + + +def get_xml_string(elem, name): + r = '' + try: + for node in elem.findall(name): + txt = node.text + lang = node.get('lang', None) + if not r and txt is not None: + r = txt + elif lang == "nl": + r = txt + except Exception as e: + print("[XMLTVConverter] get_xml_string error:", e) + + # Ora ritorniamo UTF-8 di default + r = unescape(r, entities={ + r"'": r"'", + r""": r'"', + r"|": r"|", + r" ": r" ", + r"[": r"[", + r"]": r"]", + }) + + try: + # Assicura che il risultato sia una stringa + return r.encode('utf-8').decode('utf-8') # Compatibile con Python 2 e 3 + except UnicodeEncodeError as e: + print("[XMLTVConverter] Encoding error:", e) + return r # Ritorna come fallback + + +def enumerateXML(fp, tag=None): + """ + Enumerates ElementTree nodes from file object 'fp' for a specific tag. + Args: + fp: File-like object containing XML data. + tag: The XML tag to search for. If None, processes all nodes. + Yields: + ElementTree.Element objects matching the specified tag. + """ + doc = iterparse(fp, events=('start', 'end')) + _, root = next(doc) # Get the root element + depth = 0 + + for event, element in doc: + if tag is None or element.tag == tag: # Process all nodes if no tag is specified + if event == 'start': + depth += 1 + elif event == 'end': + depth -= 1 + if depth == 0: # Tag is fully parsed + yield element + element.clear() # Free memory for the element + elif event == 'end': # Clear other elements to free memory + root.clear() diff --git a/src/EPGImport/filtersServices.py b/src/EPGImport/filtersServices.py index 696a185..096ee00 100644 --- a/src/EPGImport/filtersServices.py +++ b/src/EPGImport/filtersServices.py @@ -1,19 +1,16 @@ -from __future__ import absolute_import from . import _ from Screens.Screen import Screen from Screens.MessageBox import MessageBox from Screens.ChoiceBox import ChoiceBox from Components.ActionMap import ActionMap from ServiceReference import ServiceReference -from Screens.ChannelSelection import service_types_radio, service_types_tv, ChannelSelection, ChannelSelectionBase -from enigma import eServiceReference, eServiceCenter, iServiceInformation +from Screens.ChannelSelection import service_types_radio, service_types_tv, ChannelSelectionBase +from enigma import eServiceReference, eServiceCenter from Components.Sources.List import List from Components.Label import Label from . import EPGConfig import os -from six.moves import reload_module - OFF = 0 EDIT_BOUQUET = 1 @@ -27,14 +24,14 @@ def getProviderName(ref): provider_root = eServiceReference(rootstr) serviceHandler = eServiceCenter.getInstance() providerlist = serviceHandler.list(provider_root) - if not providerlist is None: + if providerlist is not None: while True: provider = providerlist.getNext() if not provider.valid(): break if provider.flags & eServiceReference.isDirectory: servicelist = serviceHandler.list(provider) - if not servicelist is None: + if servicelist is not None: while True: service = servicelist.getNext() if not service.valid(): @@ -62,7 +59,7 @@ def loadFrom(self, filename): if line[0] in '#;\n': continue ref = line.strip() - if not ref in self.services: + if ref not in self.services: self.services.append(ref) cfg.close() @@ -92,13 +89,13 @@ def save(self): def addService(self, ref): if isinstance(ref, str): - if not ref in self.services: + if ref not in self.services: self.services.append(ref) def addServices(self, services): if isinstance(services, list): for s in services: - if not s in self.services: + if s not in self.services: self.services.append(s) def delService(self, ref): @@ -132,8 +129,8 @@ class filtersServicesSetup(Screen): MultiContentEntryText(pos = (50, 25), size = (380, 20), font = 1, flags = RT_HALIGN_LEFT, text = 1), MultiContentEntryText(pos = (100, 47), size = (400, 17), font = 2, flags = RT_HALIGN_LEFT, text = 2), ], - "fonts": [gFont("Regular", 21), gFont("Regular", 19), gFont("Regular", 16)], - "itemHeight": 65 + "fonts": [gFont("Regular", 21), gFont("Regular", 19), gFont("Regular", 16)], + "itemHeight": 65 } @@ -187,7 +184,7 @@ def addServiceCallback(self, *service): self.RefList.addServices(ref) else: refstr = ':'.join(ref.toString().split(':')[:11]) - if '1:0:' in refstr: + if any(x in refstr for x in ('1:0:', '4097:0:', '5001:0:', '5002:0:')): self.RefList.addService(refstr) self.updateList() self.updateButtons() @@ -268,7 +265,7 @@ def addAction(choice): if choice[1] == "providerlist": serviceHandler = eServiceCenter.getInstance() servicelist = serviceHandler.list(ref) - if not servicelist is None: + if servicelist is not None: providerlist = [] while True: service = servicelist.getNext() @@ -285,7 +282,7 @@ def addAction(choice): self.enterPath(ref) elif (ref.flags & 7) == 7: self.enterPath(ref) - elif not 'provider' in ref.toString() and not self.providers and not (ref.flags & (64 | 128)) and '%3a//' not in ref.toString(): + elif 'provider' not in ref.toString() and not self.providers and not (ref.flags & (64 | 128)) and '%3a//' not in ref.toString(): if ref.valid(): self.close(ref) diff --git a/src/EPGImport/gen_xmltv.py b/src/EPGImport/gen_xmltv.py index b6b87bc..2ec1fec 100644 --- a/src/EPGImport/gen_xmltv.py +++ b/src/EPGImport/gen_xmltv.py @@ -4,27 +4,27 @@ date_format = '%Y%m%d%H%M%S' gen_categories = { -'Hobbies': (0x30, 0), -'Talk': (0x33, 0), -'GameShow': (0x31, 0), -'Reality': (0x34, 0), -'Animated': (0x55, 0), -'Comedy': (0x14, 0), -'Documentary': (0x23, 0), -'Educational': (0x90, 0), -'Film': (0x10, 0), -'Children': (0x50, 0), -'Arts/Culture': (0x70, 0), -'Crime/Mystery': (0x10, 85), -'Music': (0x60, 0), -'Nature': (0x91, 0), -'News': (0x20, 0), -'Unknown': (0x00, 0), -'Religion': (0x73, 0), -'Drama': (0x15, 0), -'Sports': (0x40, 0), -'Science/Nature': (0x90, 0), -'Adult': (0x18, 0) + 'Adult': (0x18, 0), + 'Animated': (0x55, 0), + 'Arts/Culture': (0x70, 0), + 'Children': (0x50, 0), + 'Comedy': (0x14, 0), + 'Crime/Mystery': (0x10, 85), + 'Documentary': (0x23, 0), + 'Drama': (0x15, 0), + 'Educational': (0x90, 0), + 'Film': (0x10, 0), + 'GameShow': (0x31, 0), + 'Hobbies': (0x30, 0), + 'Music': (0x60, 0), + 'Nature': (0x91, 0), + 'News': (0x20, 0), + 'Reality': (0x34, 0), + 'Religion': (0x73, 0), + 'Science/Nature': (0x90, 0), + 'Sports': (0x40, 0), + 'Talk': (0x33, 0), + 'Unknown': (0x00, 0), } diff --git a/src/EPGImport/locale/EPGImport.pot b/src/EPGImport/locale/EPGImport.pot index f45f63d..201ce82 100644 --- a/src/EPGImport/locale/EPGImport.pot +++ b/src/EPGImport/locale/EPGImport.pot @@ -6,304 +6,433 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-16 06:40+0000\n" +"POT-Creation-Date: 2025-02-24 11:56+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" -"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.5\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-SearchPath-0: .\n" -#: ../plugin.py:739 -msgid "" -"\n" -"Import of epg data will start.\n" -"This may take a few minutes.\n" -"Is this ok?" +#: filtersServices.py:151 +msgid "Add Provider" msgstr "" -#: ../plugin.py:739 -msgid " events\n" +#: filtersServices.py:152 +msgid "Add Channel" msgstr "" -#: ../filtersServices.py:153 -msgid "Add Channel" +#: filtersServices.py:154 +msgid "press OK to save list" msgstr "" -#: ../filtersServices.py:152 -msgid "Add Provider" +#: filtersServices.py:173 plugin.py:603 +msgid "Ignore services list" msgstr "" -#: ../filtersServices.py:264 +#: filtersServices.py:202 +msgid "Really delete all list?" +msgstr "" + +#: filtersServices.py:235 +msgid "Delete selected" +msgstr "" + +#: filtersServices.py:236 +msgid "Delete all" +msgstr "" + +#: filtersServices.py:261 +msgid "Select service to add..." +msgstr "" + +#: filtersServices.py:269 msgid "All services provider" msgstr "" -#: ../plugin.py:1095 -msgid "Automated EPG Importer" +#: filtersServices.py:288 plugin.py:604 +msgid "Select action" msgstr "" -#: ../plugin.py:366 -msgid "Automatic import EPG" +#: plugin.py:75 +msgid "always" msgstr "" -#: ../plugin.py:367 -msgid "Automatic start time" +#: plugin.py:76 +msgid "only manual boot" msgstr "" -#: ../plugin.py:309 ../plugin.py:557 ../plugin.py:637 -msgid "Cancel" +#: plugin.py:77 +msgid "only automatic boot" msgstr "" -#: ../plugin.py:371 -msgid "Choice days for start import" +#: plugin.py:78 +msgid "never" msgstr "" -#: ../plugin.py:693 -msgid "Clear" +#: plugin.py:90 +msgid "wake up and import" msgstr "" -#: ../plugin.py:382 -msgid "Clearing current EPG before import" +#: plugin.py:91 +msgid "skip the import" msgstr "" -#: ../plugin.py:375 -msgid "Consider setting \"Days Profile\"" +#: plugin.py:108 +msgid "Press OK" msgstr "" -#: ../plugin.py:650 -msgid "Days Profile" +#: plugin.py:117 +msgid "Monday" msgstr "" -#: ../filtersServices.py:231 -msgid "Delete all" +#: plugin.py:118 +msgid "Tuesday" msgstr "" -#: ../filtersServices.py:230 -msgid "Delete selected" +#: plugin.py:119 +msgid "Wednesday" msgstr "" -#: ../plugin.py:305 -msgid "EPG Import Configuration" +#: plugin.py:120 +msgid "Thursday" msgstr "" -#: ../plugin.py:718 -msgid "EPG Import Log" +#: plugin.py:121 +msgid "Friday" msgstr "" -#: ../plugin.py:592 -msgid "EPG Import Sources" +#: plugin.py:122 +msgid "Saturday" +msgstr "" + +#: plugin.py:123 +msgid "Sunday" +msgstr "" + +#: plugin.py:330 +msgid "EPG Import Configuration" msgstr "" -#: ../plugin.py:790 +#: plugin.py:333 plugin.py:555 #, python-format -msgid "EPG Import finished, %d events" +msgid "Last import: %s events" msgstr "" -#: ../plugin.py:1097 ../plugin.py:1098 ../plugin.py:1104 ../plugin.py:1109 -#: ../plugin.py:1114 ../plugin.py:1119 ../plugin.py:1127 ../plugin.py:1137 -msgid "EPG-Importer" +#: plugin.py:334 plugin.py:639 plugin.py:726 +msgid "Cancel" msgstr "" -#: ../plugin.py:483 -msgid "" -"EPGImport\n" -"Import of epg data is still in progress. Please wait." +#: plugin.py:335 plugin.py:640 plugin.py:727 plugin.py:794 +msgid "Save" msgstr "" -#: ../plugin.py:498 +#: plugin.py:336 +msgid "Manual" +msgstr "" + +#: plugin.py:337 +msgid "Sources" +msgstr "" + +#: plugin.py:364 +#, python-format msgid "" -"EPGImport\n" -"Import of epg data will start.\n" -"This may take a few minutes.\n" -"Is this ok?" +"Filtering:\n" +"%s Please wait!" msgstr "" -#: ../plugin.py:507 +#: plugin.py:365 +#, python-format msgid "" -"EPGImport Plugin\n" -"Failed to start:\n" +"Importing:\n" +"%s %s events" msgstr "" -#: ../plugin.py:105 -msgid "Friday" +#: plugin.py:404 +msgid "Automatic import EPG" msgstr "" -#: ../filtersServices.py:168 ../plugin.py:520 -msgid "Ignore services list" +#: plugin.py:404 +msgid "When enabled, it allows you to schedule an automatic EPG update at the given days and time." msgstr "" -#: ../plugin.py:580 -msgid "Import current source" +#: plugin.py:405 +msgid "Automatic start time" msgstr "" -#: ../plugin.py:330 -#, python-format -msgid "" -"Importing: %s\n" -"%s events" +#: plugin.py:405 +msgid "Specify the time for the automatic EPG update." msgstr "" -#: ../plugin.py:739 -msgid "Last import: " +#: plugin.py:406 +msgid "When in deep standby" msgstr "" -#: ../plugin.py:478 -#, python-format -msgid "Last import: %s events" +#: plugin.py:406 +msgid "Choose the action to perform when the box is in deep standby and the automatic EPG update should normally start." msgstr "" -#: ../plugin.py:473 -#, python-format -msgid "Last: %s %s, %d events" +#: plugin.py:407 +msgid "Return to deep standby after import" msgstr "" -#: ../plugin.py:374 -msgid "Load EPG only for IPTV channels" +#: plugin.py:407 +msgid "This will turn back waked up box into deep-standby after automatic EPG import." +msgstr "" + +#: plugin.py:408 +msgid "Standby at startup" +msgstr "" + +#: plugin.py:408 +msgid "The waked up box will be turned into standby after automatic EPG import wake up." +msgstr "" + +#: plugin.py:409 +msgid "Choice days for start import" +msgstr "" + +#: plugin.py:409 +msgid "You can select the day(s) when the EPG update must be performed." +msgstr "" + +#: plugin.py:410 +msgid "Start import after booting up" +msgstr "" + +#: plugin.py:410 +msgid "Specify in which case the EPG must be automatically updated after the box has booted." msgstr "" -#: ../plugin.py:373 +#: plugin.py:411 msgid "Load EPG only services in bouquets" msgstr "" -#: ../plugin.py:380 -msgid "Load long descriptions up to X days" +#: plugin.py:411 +msgid "To save memory you can decide to only load EPG data for the services that you have in your bouquet files." msgstr "" -#: ../plugin.py:311 -msgid "Manual" +#: plugin.py:412 +msgid "Load EPG only for IPTV channels" msgstr "" -#: ../plugin.py:101 -msgid "Monday" +#: plugin.py:412 +msgid "Specify in this case the EPG should be imported only for iptv lists." msgstr "" -#: ../plugin.py:493 -msgid "No active EPG sources found, nothing to do" +#: plugin.py:413 +msgid "Consider setting \"Days Profile\"" msgstr "" -#: ../plugin.py:88 -msgid "Press OK" +#: plugin.py:413 +msgid "When you decide to import the EPG after the box booted mention if the \"days profile\" must be take into consideration or not." msgstr "" -#: ../filtersServices.py:197 -msgid "Really delete all list?" +#: plugin.py:414 +msgid "Skip import on restart GUI" msgstr "" -#: ../plugin.py:369 -msgid "Return to deep standby after import" +#: plugin.py:414 +msgid "When you restart the GUI you can decide to skip or not the EPG data import." msgstr "" -#: ../plugin.py:381 +#: plugin.py:415 +msgid "Show \"EPG import now\" in extensions" +msgstr "" + +#: plugin.py:415 +msgid "Display a shortcut \"EPG import now\" in the extension menu. This menu entry will immediately start the EPG update process when selected." +msgstr "" + +#: plugin.py:416 +msgid "Show \"EPGImport\" in plugins" +msgstr "" + +#: plugin.py:416 +msgid "Display a shortcut \"EPG import\" in the browser plugin.." +msgstr "" + +#: plugin.py:418 +msgid "Load long descriptions up to X days" +msgstr "" + +#: plugin.py:418 +msgid "Define the number of days that you want to get the full EPG data, reducing this number can help you to save memory usage on your box. But you are also limited with the EPG provider available data. You will not have 15 days EPG if it only provide 7 days data." +msgstr "" + +#: plugin.py:419 msgid "Run AutoTimer after import" msgstr "" -#: ../plugin.py:106 -msgid "Saturday" +#: plugin.py:419 +msgid "You can start automatically the plugin AutoTimer after the EPG data update to have it refreshing its scheduling after EPG data refresh." msgstr "" -#: ../plugin.py:310 ../plugin.py:558 ../plugin.py:638 ../plugin.py:696 -msgid "Save" +#: plugin.py:420 +msgid "Clearing current EPG before import" msgstr "" -#: ../filtersServices.py:283 ../plugin.py:521 -msgid "Select action" +#: plugin.py:420 +msgid "This will clear the current EPG data in memory before updating the EPG data. This allows you to always have a clean new EPG with the latest EPG data, for example in case of program changes between refresh, otherwise EPG data are cumulative." msgstr "" -#: ../filtersServices.py:256 -msgid "Select service to add..." +#: plugin.py:421 +msgid "Also apply \"channel id\" filtering on custom.channels.xml" msgstr "" -#: ../plugin.py:377 -msgid "Show \"EPGImport\" in extensions" +#: plugin.py:421 +msgid "This is for advanced users that are using the channel id filtering feature. If enabled, the filter rules defined into /etc/epgimport/channel_id_filter.conf will also be applied on your /etc/epgimport/custom.channels.xml file." msgstr "" -#: ../plugin.py:378 -msgid "Show \"EPGImport\" in plugins" +#: plugin.py:422 +msgid "Execute shell command before import EPG" msgstr "" -#: ../plugin.py:520 -msgid "Show log" +#: plugin.py:422 +msgid "When enabled, then you can run the desired script before starting the import, after which the import of the EPG will begin." msgstr "" -#: ../plugin.py:376 -msgid "Skip import on restart GUI" +#: plugin.py:423 +msgid "Shell command name" msgstr "" -#: ../plugin.py:312 -msgid "Sources" +#: plugin.py:423 +msgid "Enter shell command name." msgstr "" -#: ../plugin.py:370 -msgid "Standby at startup" +#: plugin.py:424 +msgid "Start import after standby" msgstr "" -#: ../plugin.py:372 -msgid "Start import after booting up" +#: plugin.py:424 +msgid "Start import after resuming from standby mode." msgstr "" -#: ../plugin.py:107 -msgid "Sunday" +#: plugin.py:425 +msgid "Hours after which the import is repeated" msgstr "" -#: ../plugin.py:104 -msgid "Thursday" +#: plugin.py:425 +msgid "Number of hours (1-23, or 0 for no repeat) after which the import is repeated. This value is not saved and will be reset when the GUI restarts." msgstr "" -#: ../plugin.py:102 -msgid "Tuesday" +#: plugin.py:503 +msgid "Settings saved successfully !" msgstr "" -#: ../plugin.py:103 -msgid "Wednesday" +#: plugin.py:549 +msgid "unknown" msgstr "" -#: ../plugin.py:368 -msgid "When in deep standby" +#: plugin.py:550 +#, python-format +msgid "Last: %s %s, %d events" msgstr "" -#: ../plugin.py:724 -msgid "Write to /tmp/epgimport.log" +#: plugin.py:560 +msgid "" +"EPGImport\n" +"Import of epg data is still in progress. Please wait." +msgstr "" + +#: plugin.py:571 +msgid "No active EPG sources found, nothing to do" +msgstr "" + +#: plugin.py:575 +msgid "" +"EPGImport\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" msgstr "" -#: ../plugin.py:660 +#: plugin.py:587 +msgid "" +"EPGImport Plugin\n" +"Failed to start:\n" +msgstr "" + +#: plugin.py:603 +msgid "Show log" +msgstr "" + +#: plugin.py:641 +msgid "Import" +msgstr "" + +#: plugin.py:681 +msgid "EPG Import Sources" +msgstr "" + +#: plugin.py:721 plugin.py:745 +msgid "Days Profile" +msgstr "" + +#: plugin.py:751 msgid "" "You may not use this settings!\n" "At least one day a week should be included!" msgstr "" -#: ../plugin.py:790 -msgid "" -"You must restart Enigma2 to load the EPG data,\n" -"is this OK?" +#: plugin.py:791 +msgid "Clear" msgstr "" -#: ../plugin.py:60 -msgid "always" +#: plugin.py:824 +msgid "EPG Import Log" msgstr "" -#: ../plugin.py:63 -msgid "never" +#: plugin.py:830 +msgid "Write to /tmp/epgimport.log" msgstr "" -#: ../plugin.py:62 -msgid "only automatic boot" +#: plugin.py:846 +msgid "Last import: " msgstr "" -#: ../plugin.py:61 -msgid "only manual boot" +#: plugin.py:846 +msgid " events\n" msgstr "" -#: ../filtersServices.py:155 -msgid "press OK to save list" +#: plugin.py:846 +msgid "" +"\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" msgstr "" -#: ../plugin.py:77 -msgid "skip the import" +#: plugin.py:896 +#, python-format +msgid "EPG Import finished, %d events" msgstr "" -#: ../plugin.py:76 -msgid "wake up and import" +#: plugin.py:896 +msgid "" +"You must restart Enigma2 to load the EPG data,\n" +"is this OK?" +msgstr "" + +#: plugin.py:1038 +msgid "No source file found !" +msgstr "" + +#: plugin.py:1255 +msgid "Automated EPG Importer" +msgstr "" + +#: plugin.py:1257 +msgid "EPG import now" +msgstr "" + +#: plugin.py:1258 plugin.py:1264 plugin.py:1269 plugin.py:1274 plugin.py:1279 +#: plugin.py:1287 plugin.py:1297 +msgid "EPG-Importer" msgstr "" diff --git a/src/EPGImport/locale/it.mo b/src/EPGImport/locale/it.mo index bb68ec55796f3acfc83c1ebecadfa4abb5801abd..7812d17e50134bfe6b52bc31b6f19ddf5a6edc29 100644 GIT binary patch literal 11653 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

|J7yJs{?n|zciTEkSd(jZMjyZFvomE}rkpxb z+6-HK>82IY(LASYj;+>~$fyl6@d`=PxBAHL6(#g=*og*B82j%7NIZGpf2`OnXBgFE=Fz|ML=lJl0ci1tY(^oT@F zy2we$Uk{Ts-1D8Lv>Kpr8Z`}v8xxwJVRcTEx~-8CWc86E&_Ebhw7AmQ#w@Z z3(1*{hLtAGMF`OV#T@U zRk#SX4by(}kGoS8=o{+Z=FTWC< z;l*Jwp&;?;Aq{b&Y~#dM%B%S1gg!`37*?Ar<|Qnjgy|!Cr=M0&W*U?N*0u88t~5HI zaV97J&X@pQuWR0Ao@Jmnm}JXZGc3GyjQBr@-3ZTzYwyI=meV|?-Wajd-ZB@L@AB)# z3w}aQSP5;h_ATV$yJF%+0i~{E)~B8!p^-ddw>#g{2Ff)u9t1>>HJpi&syB9#5|b`z zm*qqs++xehAr3*6LPPBR;RJ;oY6QFtL>tu zDfpr~*z>>9Y=}RFqnoDXMX}d?6=%?0xWrM6Di)QAbVw#q{>rcopLLbLVx29lt46X& z)K^ISKZAduyRRbDoOw0zatVI+TvXUhNZ&Rpc9 z$+iwr02I$9axdoTEhRKx^d81uAB5J%|4NfYwVW5GAAA4i9j zxaUK?7SOM7Wo%ItOX|XWdi8sHhXTq1MAbfqJZs0rSjC*jsB~%tULn0?45Qvs($H1& z<(Cy-d7E-+NZ{hWy5LL!L70DgDv%z{DP%`bE3G8fPHtOjaxTJ`>PJLz?X_A>$SAtL zomD-pK07n9bUsIIwo6gP_J8XFX{K{W#bm1n^YArJYcqadM5z!AU(NO3Y&OF4ZNKO^W|1ontoYgxMalgo-$^WqE0NQhx^np-Ad%rG_|%F$=_j>$ z{ZD19P}h$_-A@@R=_vy8!-rBWzqsWus1}nYH2i*}(uBYTe;ZA%9RiQIo!WFhO(^SR zySh@n@7CzTsHE~48TBh`0_`lkRjojyGj~_E@%(SVS9Sucm_wIhs6Q;gqOa))ChBfn zwaV2;g^q8WnBRRfFWTAK(<)=qVdw-BJ3^5>t(N2f-IOl=Nn#Yu7J5}$5%kpX8egdLEk!05}RIQp`epLod4Dw>R${6QtaJE;~dg^SBp7CaH z9Akx@%SL6CDeD#|y#1v`>+erAq7>iRr-6CS!S_F{qeG;cup?-{h^K z*yracn9{bVl5&6<>g&9P&gza5*^AbzZeFmRBgulp$xFM2vjG_}kv9>SN}16Rzt%}$ zemA*YQB^v0D5!=LBK(EcqB@Nfn-&558BG?YQ=RlWE9NDWJ-cjPewkN%s8Ali;=PVo zxxf_%(Q1P1iuQih%3snCTiLcU-Zp(&6{<>;6V{Sr0e;)@34~J-p>q)#&K?ulewsd{ zEpOQEDbok8LWdVh>p&oc_>`r(kzOVL<02k#HgYIOo9PTam11Wz5J2|jbjbfaum!J{ z#MFvV48LI3w8$+}U86&J`I9r7bmt-#PqV#2>jhurrkrYL+4OqF)vr9fnJ6 zu9@?iKLQ->ekW{IFn4y7Gqfk@C5v7Ky#@;v{yhqG9g7vtf^mD9zvXIus+N3S)NNKk z2zE60sV1}t(j5!OY2yXc=N0Q=6pB#hBHnFuBqzslN{M$wXRXzwHWZfARw**BP^nmo z>f*qZ(dB6?$_LtL9H)`R312gG;w)zG`u1lMVTR*Wtxsh@E7+hGDOq(HnGIcGI#IPH M=Z>jqgJVSh4bYDDmjD0& delta 2425 zcmZ{kU2GIp6vuC|Vq02Dp#`=DdfQSzXs7N+p>zPDd|E=KK;su`neN_hM|Nj6Gqa^7 zhE);e1&LlIhL|AnL5W03G`{$O3C73^K_Ap$z>oOQ_+UX_h%wRse>)Xkdb4MKXJ*fx zbME<{-6MOSYcE`zU;4Vh+6Y?$Yo8^=W8m6ST(I7{Q-}w_(_k6+J2(gY3#%l>A7kCOp0&xSxR#A)USy%(ef}`LfAWx79c^Kpi zPJ%7qXCPnnOUMau1@0$7d_;XYUx-+c@oT_(a1iA82SGOVFvxsI!NtfgPQc+wJ^&eb z3FOA_K@>0k1bO1W!KGjo+QUNHK*q;G#;pMnA~u0MX&=}C4uRXjL!b#>1i8NyIu@wK z95@TW8gL%i3bIA3KuWL!WC2fr&K*Ly;N!Tz20fU#8$pcg!KDV=1ya%^$N~}0A`~6@I$mcyE6YmG9{UMNr9s@Z{r@;vLIY{Y#0PhATLC(ZN zRI?6j0aKs_Hi2J(%r{Yk{&V9EJis1NhHR`b0;`$FRQUYGkjF#50kV+OAcylTNNK(V_ko4q;B17m1%k7pqaY*}$3f13#)XBQ0Xcl1 zg4iOifGl7VECH!=CyeiU0LHGdqg?zIt%vo%C@Y7Q3xyQKS~&N@n4F70k+z~Ym7oJY zwML#|%lXwTST~H?@>DEnC9DfZIqre2fuVWD6~mRqb5Y(^Fv?Qg&9?FnhNt8(aaHMW zW-rh`&n_)&z#SEBhj9cc3L8GX;&6(i7CxZ5(+fY7;y6=aE(*sbVeGL9qd1uU;y(!> z3|N#7S4FSBRJl#RTGp*g%ay)T{)q0J6VX+bPwMf?^BcB$cFGzTy=lkwl=PK1YA2O1 zGqxXy-fYhG0-4Nvo^k@|yLm6E!l$;AHf5_{p(m>bbaJkzpQ%34FybDN&0G4nO>JzJ zwj>8_`Z98k4auGuqh}jYXps{X31_YSc!STXC^&BbvZR#-e6t zye*1VvBUBMW5BZ1XQDpea_nGS8dgS)9k4T5D~Tv4oww4;7^qWL)|8|9Tqf@u zNjIC#Ggz;P?AKpJYU|@2@s3!C z$c^8M+@mZnIns|%*ABRAsm2>qO944sEuE zGHQ>?s3g0)18UFo+Lfjpwljh9{5|1t%oMrGlxgJ*j%72!=u}K0Y|U*2beS^a&e)FC zrrbF^oUt7>H9|9`>NX`rN!!j$0Z?6zWouAL(sf3ahx!>R^wGw>`uY0py1e0tZdo#_ z-(K3H-(Qy3iNQWwtZ_he%O#@)klg6V;`4x&*QLC*YDxlI2f`?TA1QeSR9 zU)SgQfoBy*-r@EBzcQaP^k9COy(VSkj@tZXYY@*FDDO{{Z$}!;t_0 diff --git a/src/EPGImport/locale/it.po b/src/EPGImport/locale/it.po old mode 100755 new mode 100644 index 1b95c0a..ccf4240 --- a/src/EPGImport/locale/it.po +++ b/src/EPGImport/locale/it.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: Italian Translation EPGImport Enigma2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-01-16 06:40+0000\n" -"PO-Revision-Date: 2025-02-03 17:06+0100\n" +"POT-Creation-Date: 2025-02-24 11:56+0100\n" +"PO-Revision-Date: 2025-02-24 12:06+0100\n" "Last-Translator: madhouse\n" "Language-Team: egami-team\n" "Language: it_IT\n" @@ -17,287 +17,458 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.5\n" -"X-Poedit-Basepath: .\n" +"X-Poedit-Basepath: ..\n" "X-Poedit-SourceCharset: UTF-8\n" -"X-Poedit-SearchPath-0: EPGConfig.py\n" -"X-Poedit-SearchPath-1: epgdat.py\n" -"X-Poedit-SearchPath-2: epgdat_importer.py\n" -"X-Poedit-SearchPath-3: EPGImport.py\n" -"X-Poedit-SearchPath-4: ExpandableSelectionList.py\n" -"X-Poedit-SearchPath-5: filtersServices.py\n" -"X-Poedit-SearchPath-6: gen_xmltv.py\n" -"X-Poedit-SearchPath-7: log.py\n" -"X-Poedit-SearchPath-8: OfflineImport.py\n" -"X-Poedit-SearchPath-9: plugin.py\n" -"X-Poedit-SearchPath-10: xmltvconverter.py\n" - -#: ../plugin.py:739 -msgid "" -"\n" -"Import of epg data will start.\n" -"This may take a few minutes.\n" -"Is this ok?" -msgstr "" -"\n" -"Eseguo importazione dati EPG.\n" -"Questo processo potrebbe richiedere qualche minuto\n" -"Procedo?" +"X-Poedit-SearchPath-0: .\n" -#: ../plugin.py:739 -msgid " events\n" -msgstr " eventi\n" +#: filtersServices.py:151 +msgid "Add Provider" +msgstr "Aggiungi provider" -#: ../filtersServices.py:153 +#: filtersServices.py:152 msgid "Add Channel" msgstr "Aggiungi canale" -#: ../filtersServices.py:152 -msgid "Add Provider" -msgstr "Aggiungi provider" +#: filtersServices.py:154 +msgid "press OK to save list" +msgstr "premi OK per salvare la lista" + +#: filtersServices.py:173 plugin.py:603 +msgid "Ignore services list" +msgstr "Ignora l'elenco canali" + +#: filtersServices.py:202 +msgid "Really delete all list?" +msgstr "Sicuro di eliminare tutta la lista?" + +#: filtersServices.py:235 +msgid "Delete selected" +msgstr "Rimuovi selezionato" -#: ../filtersServices.py:264 +#: filtersServices.py:236 +msgid "Delete all" +msgstr "Cancella tutto" + +#: filtersServices.py:261 +msgid "Select service to add..." +msgstr "Seleziona il canale da aggiungere..." + +#: filtersServices.py:269 msgid "All services provider" msgstr "Tutti i canali di ogni provider" -#: ../plugin.py:1095 -msgid "Automated EPG Importer" -msgstr "Importazione EPG automatica" +#: filtersServices.py:288 plugin.py:604 +msgid "Select action" +msgstr "Selezionare azione" -#: ../plugin.py:366 -msgid "Automatic import EPG" -msgstr "Importazione EPG automatica" +#: plugin.py:75 +msgid "always" +msgstr "sempre" -#: ../plugin.py:367 -msgid "Automatic start time" -msgstr "Orario di avvio automatico" +#: plugin.py:76 +msgid "only manual boot" +msgstr "solo avvio manuale" -#: ../plugin.py:309 ../plugin.py:557 ../plugin.py:637 -msgid "Cancel" -msgstr "Annulla" +#: plugin.py:77 +msgid "only automatic boot" +msgstr "solo avvio automatico" -#: ../plugin.py:371 -msgid "Choice days for start import" -msgstr "Seleziona il giorno di avvio importazione" +#: plugin.py:78 +msgid "never" +msgstr "mai" -#: ../plugin.py:693 -msgid "Clear" -msgstr "Cancella" +#: plugin.py:90 +msgid "wake up and import" +msgstr "sveglia e importa" -#: ../plugin.py:382 -msgid "Delete current EPG before import" -msgstr "Cancella EPG corrente prima dell’importazione" +#: plugin.py:91 +msgid "skip the import" +msgstr "salta l'importazione" -#: ../plugin.py:375 -msgid "Consider setting \"Days Profile\"" -msgstr "Considera l'impostazione \"Profilo giorni\"" +#: plugin.py:108 +msgid "Press OK" +msgstr "Premere OK" -#: ../plugin.py:650 -msgid "Days Profile" -msgstr "Profilo dei giorni" +#: plugin.py:117 +msgid "Monday" +msgstr "Lunedì" -#: ../filtersServices.py:231 -msgid "Delete all" -msgstr "Cancella tutto" +#: plugin.py:118 +msgid "Tuesday" +msgstr "Martedì" -#: ../filtersServices.py:230 -msgid "Delete selected" -msgstr "Rimuovi selezionato" +#: plugin.py:119 +msgid "Wednesday" +msgstr "Mercoledì" + +#: plugin.py:120 +msgid "Thursday" +msgstr "Giovedì" + +#: plugin.py:121 +msgid "Friday" +msgstr "Venerdì" + +#: plugin.py:122 +msgid "Saturday" +msgstr "Sabato" + +#: plugin.py:123 +msgid "Sunday" +msgstr "Domenica" -#: ../plugin.py:305 +#: plugin.py:330 msgid "EPG Import Configuration" msgstr "Configurazione di EPG Import" -#: ../plugin.py:718 -msgid "EPG Import Log" -msgstr "Registro importazione EPG" +#: plugin.py:333 plugin.py:555 +#, python-format +msgid "Last import: %s events" +msgstr "Ultima importazione: %s canali" -#: ../plugin.py:592 -msgid "EPG Import Sources" -msgstr "Fonti importazione EPG" +#: plugin.py:334 plugin.py:639 plugin.py:726 +msgid "Cancel" +msgstr "Annulla" -#: ../plugin.py:790 -#, python-format -msgid "EPG Import finished, %d events" -msgstr "Importazione EPG completata, %d eventi" +#: plugin.py:335 plugin.py:640 plugin.py:727 plugin.py:794 +msgid "Save" +msgstr "Salva" -#: ../plugin.py:1097 ../plugin.py:1098 ../plugin.py:1104 ../plugin.py:1109 -#: ../plugin.py:1114 ../plugin.py:1119 ../plugin.py:1127 ../plugin.py:1137 -msgid "EPG-Importer" -msgstr "EPG-Importer" +#: plugin.py:336 +msgid "Manual" +msgstr "Scarica" -#: ../plugin.py:483 +#: plugin.py:337 +msgid "Sources" +msgstr "Sorgenti" + +#: plugin.py:364 +#, python-format msgid "" -"EPGImport\n" -"Import of epg data is still in progress. Please wait." +"Filtering:\n" +"%s Please wait!" msgstr "" -"EPG-Import\n" -"Importazione dati EPG in corso...\n" -"Attendere prego." +"Filtraggio:\n" +"%s Attendi!" -#: ../plugin.py:498 +#: plugin.py:365 +#, python-format msgid "" -"EPGImport\n" -"Import of epg data will start.\n" -"This may take a few minutes.\n" -"Is this ok?" +"Importing:\n" +"%s %s events" msgstr "" -"EPG-Import\n" -"Eseguo importazione dati EPG.\n" -"Questo processo potrebbe richiedere qualche minuto\n" -"Procedo?" +"Importazione: \n" +"%s %s eventi" + +#: plugin.py:404 +msgid "Automatic import EPG" +msgstr "Importazione EPG automatica" -#: ../plugin.py:507 +#: plugin.py:404 msgid "" -"EPGImport Plugin\n" -"Failed to start:\n" +"When enabled, it allows you to schedule an automatic EPG update at the given " +"days and time." msgstr "" -"Plugin EPG-Import\n" -"Avvio fallito:\n" +"Se abilitata, consente di programmare un aggiornamento automatico dell'EPG " +"nei giorni e negli orari indicati." -#: ../plugin.py:105 -msgid "Friday" -msgstr "Venerdì" +#: plugin.py:405 +msgid "Automatic start time" +msgstr "Orario di avvio automatico" -#: ../filtersServices.py:168 ../plugin.py:520 -msgid "Ignore services list" -msgstr "Ignora l'elenco canali" +#: plugin.py:405 +msgid "Specify the time for the automatic EPG update." +msgstr "Specificare l'ora per l'aggiornamento automatico dell'EPG." -#: ../plugin.py:580 -msgid "Import current source" -msgstr "Importa sorgente" +#: plugin.py:406 +msgid "When in deep standby" +msgstr "Quando il box è spento" -#: ../plugin.py:330 -#, python-format +#: plugin.py:406 msgid "" -"Importing: %s\n" -"%s events" +"Choose the action to perform when the box is in deep standby and the " +"automatic EPG update should normally start." msgstr "" -"Importazione: %s\n" -"%s eventi" +"Selezionare l'azione da eseguire quando il box è in modalità standby " +"profondo; normalmente, l'aggiornamento automatico dell'EPG dovrebbe avviarsi." -#: ../plugin.py:739 -msgid "Last import: " -msgstr "Ultima importazione: " +#: plugin.py:407 +msgid "Return to deep standby after import" +msgstr "Spegni di nuovo dopo l'importazione" -#: ../plugin.py:478 -#, python-format -msgid "Last import: %s events" -msgstr "Ultima importazione: %s canali" +#: plugin.py:407 +msgid "" +"This will turn back waked up box into deep-standby after automatic EPG " +"import." +msgstr "" +"Ciò riporterà il box riattivato in modalità deep standby dopo l'importazione " +"automatica dell'EPG." -#: ../plugin.py:473 -#, python-format -msgid "Last: %s %s, %d events" -msgstr "Ultima importazione: %s %s, %d canali" +#: plugin.py:408 +msgid "Standby at startup" +msgstr "Standby all'avvio" -#: ../plugin.py:374 -msgid "Load EPG only for IPTV channels" -msgstr "Carica gli EPG solo per i canali IPTV" +#: plugin.py:408 +msgid "" +"The waked up box will be turned into standby after automatic EPG import wake " +"up." +msgstr "" +"Il box riattivato passerà in modalità standby dopo la riattivazione tramite " +"importazione automatica EPG." -#: ../plugin.py:373 +#: plugin.py:409 +msgid "Choice days for start import" +msgstr "Seleziona il giorno di avvio importazione" + +#: plugin.py:409 +msgid "You can select the day(s) when the EPG update must be performed." +msgstr "" +"È possibile selezionare il/i giorno/i in cui deve essere eseguito " +"l'aggiornamento EPG." + +#: plugin.py:410 +msgid "Start import after booting up" +msgstr "Avvia l'importazione dopo il boot" + +#: plugin.py:410 +msgid "" +"Specify in which case the EPG must be automatically updated after the box " +"has booted." +msgstr "" +"Specifica in quale caso l'EPG deve essere aggiornato automaticamente dopo " +"l'avvio del box." + +#: plugin.py:411 msgid "Load EPG only services in bouquets" msgstr "Carica solo i servizi EPG nei bouquet" -#: ../plugin.py:380 -msgid "Load long descriptions up to X days" -msgstr "Carica le descrizioni dettagliate fino a (n) giorni" +#: plugin.py:411 +msgid "" +"To save memory you can decide to only load EPG data for the services that " +"you have in your bouquet files." +msgstr "" +"Per risparmiare memoria puoi decidere di caricare solo i dati EPG dei " +"servizi presenti nei tuoi file bouquet." -#: ../plugin.py:311 -msgid "Manual" -msgstr "Scarica" +#: plugin.py:412 +msgid "Load EPG only for IPTV channels" +msgstr "Carica gli EPG solo per i canali IPTV" -#: ../plugin.py:101 -msgid "Monday" -msgstr "Lunedì" +#: plugin.py:412 +msgid "Specify in this case the EPG should be imported only for iptv lists." +msgstr "" +"Specificare che in questo caso l'EPG deve essere importato solo per le liste " +"IPTV." -#: ../plugin.py:493 -msgid "No active EPG sources found, nothing to do" -msgstr "Fonti EPG attive non trovate: nessuna operazione svolta" +#: plugin.py:413 +msgid "Consider setting \"Days Profile\"" +msgstr "Considera l'impostazione \"Profilo giorni\"" -#: ../plugin.py:88 -msgid "Press OK" -msgstr "Premere OK" +#: plugin.py:413 +msgid "" +"When you decide to import the EPG after the box booted mention if the \"days " +"profile\" must be take into consideration or not." +msgstr "" +"Quando si decide di importare l'EPG dopo l'avvio del box, specificare se il " +"\"profilo giorni\" deve essere preso in considerazione o meno." -#: ../filtersServices.py:197 -msgid "Really delete all list?" -msgstr "Sicuro di eliminare tutta la lista?" +#: plugin.py:414 +msgid "Skip import on restart GUI" +msgstr "Salta importazione su riavvio sistema" -#: ../plugin.py:369 -msgid "Return to deep standby after import" -msgstr "Spegni di nuovo dopo l'importazione" +#: plugin.py:414 +msgid "" +"When you restart the GUI you can decide to skip or not the EPG data import." +msgstr "" +"Quando si riavvia l'interfaccia grafica è possibile decidere se saltare o " +"meno l'importazione dei dati EPG." + +#: plugin.py:415 +msgid "Show \"EPG import now\" in extensions" +msgstr "Mostra \"EPGImport ora\" nelle estensioni" -#: ../plugin.py:381 +#: plugin.py:415 +msgid "" +"Display a shortcut \"EPG import now\" in the extension menu. This menu entry " +"will immediately start the EPG update process when selected." +msgstr "" +"Visualizza una scorciatoia \"EPG import now\" nel menu estensione. Questa " +"voce di menu avvierà immediatamente il processo di aggiornamento EPG quando " +"selezionata." + +#: plugin.py:416 +msgid "Show \"EPGImport\" in plugins" +msgstr "Mostra \"EPGImport\" nei plugins" + +#: plugin.py:416 +msgid "Display a shortcut \"EPG import\" in the browser plugin.." +msgstr "" +"Visualizza una scorciatoia \"Importazione EPG\" nel plugin del browser." + +#: plugin.py:418 +msgid "Load long descriptions up to X days" +msgstr "Carica le descrizioni dettagliate fino a (n) giorni" + +#: plugin.py:418 +msgid "" +"Define the number of days that you want to get the full EPG data, reducing " +"this number can help you to save memory usage on your box. But you are also " +"limited with the EPG provider available data. You will not have 15 days EPG " +"if it only provide 7 days data." +msgstr "" +"Definisci il numero di giorni in cui vuoi ottenere i dati EPG completi, " +"ridurre questo numero può aiutarti a risparmiare memoria sul tuo box. Ma sei " +"anche limitato dai dati disponibili del provider EPG. Non avrai 15 giorni di " +"EPG se fornisce solo 7 giorni di dati." + +#: plugin.py:419 msgid "Run AutoTimer after import" msgstr "Avvia AutoTimer dopo l'aggiornamento" -#: ../plugin.py:106 -msgid "Saturday" -msgstr "Sabato" +#: plugin.py:419 +msgid "" +"You can start automatically the plugin AutoTimer after the EPG data update " +"to have it refreshing its scheduling after EPG data refresh." +msgstr "" +"È possibile avviare automaticamente il plugin AutoTimer dopo l'aggiornamento " +"dei dati EPG, in modo che aggiorni la sua programmazione dopo " +"l'aggiornamento dei dati EPG." -#: ../plugin.py:310 ../plugin.py:558 ../plugin.py:638 ../plugin.py:696 -msgid "Save" -msgstr "Salva" +#: plugin.py:420 +msgid "Clearing current EPG before import" +msgstr "Cancella EPG corrente prima dell’importazione" -#: ../filtersServices.py:283 ../plugin.py:521 -msgid "Select action" -msgstr "Selezionare azione" +#: plugin.py:420 +msgid "" +"This will clear the current EPG data in memory before updating the EPG data. " +"This allows you to always have a clean new EPG with the latest EPG data, for " +"example in case of program changes between refresh, otherwise EPG data are " +"cumulative." +msgstr "" +"Questo cancellerà i dati EPG correnti in memoria prima di aggiornare i dati " +"EPG. Questo ti consente di avere sempre un nuovo EPG pulito con i dati EPG " +"più recenti, ad esempio in caso di modifiche al programma tra un " +"aggiornamento e l'altro, altrimenti i dati EPG sono cumulativi." -#: ../filtersServices.py:256 -msgid "Select service to add..." -msgstr "Seleziona il canale da aggiungere..." +#: plugin.py:421 +msgid "Also apply \"channel id\" filtering on custom.channels.xml" +msgstr "Applicare anche il filtro \"ID canale\" su custom.channels.xml" -#: ../plugin.py:377 -msgid "Show \"EPGImport\" in extensions" -msgstr "Mostra \"EPGImport\" nelle estensioni" +#: plugin.py:421 +msgid "" +"This is for advanced users that are using the channel id filtering feature. " +"If enabled, the filter rules defined into /etc/epgimport/channel_id_filter." +"conf will also be applied on your /etc/epgimport/custom.channels.xml file." +msgstr "" +"Questo è per gli utenti avanzati che utilizzano la funzionalità di " +"filtraggio dell'ID canale. Se abilitata, le regole di filtro definite in /" +"etc/epgimport/channel_id_filter.conf saranno applicate anche al file /etc/" +"epgimport/custom.channels.xml." -#: ../plugin.py:378 -msgid "Show \"EPGImport\" in plugins" -msgstr "Mostra \"EPGImport\" nei plugins" +#: plugin.py:422 +msgid "Execute shell command before import EPG" +msgstr "Eseguire il comando shell prima di importare EPG" -#: ../plugin.py:520 -msgid "Show log" -msgstr "Mostra il log" +#: plugin.py:422 +msgid "" +"When enabled, then you can run the desired script before starting the " +"import, after which the import of the EPG will begin." +msgstr "" +"Se abilitata, è possibile eseguire lo script desiderato prima di avviare " +"l'importazione, dopodiché avrà inizio l'importazione dell'EPG." -#: ../plugin.py:376 -msgid "Skip import on restart GUI" -msgstr "Salta importazione su riavvio sistema" +#: plugin.py:423 +msgid "Shell command name" +msgstr "Nome Comando Shell" -#: ../plugin.py:312 -msgid "Sources" -msgstr "Sorgenti" +#: plugin.py:423 +msgid "Enter shell command name." +msgstr "Inserisci il nome del comando shell." -#: ../plugin.py:370 -msgid "Standby at startup" -msgstr "Standby all'avvio" +#: plugin.py:424 +msgid "Start import after standby" +msgstr "Avvia l'importazione dopo lo standby" -#: ../plugin.py:372 -msgid "Start import after booting up" -msgstr "Avvia l'importazione dopo il boot" +#: plugin.py:424 +msgid "Start import after resuming from standby mode." +msgstr "Avvia l'importazione dopo la ripresa dalla modalità standby." -#: ../plugin.py:107 -msgid "Sunday" -msgstr "Domenica" +#: plugin.py:425 +msgid "Hours after which the import is repeated" +msgstr "Ore dopo le quali l'importazione viene ripetuta" -#: ../plugin.py:104 -msgid "Thursday" -msgstr "Giovedì" +#: plugin.py:425 +msgid "" +"Number of hours (1-23, or 0 for no repeat) after which the import is " +"repeated. This value is not saved and will be reset when the GUI restarts." +msgstr "" +"Numero di ore (1-23, o 0 per nessuna ripetizione) dopo le quali " +"l'importazione viene ripetuta. Questo valore non viene salvato e verrà " +"reimpostato al riavvio della GUI." -#: ../plugin.py:102 -msgid "Tuesday" -msgstr "Martedì" +#: plugin.py:503 +msgid "Settings saved successfully !" +msgstr "Impostazioni salvate con successo!" -#: ../plugin.py:103 -msgid "Wednesday" -msgstr "Mercoledì" +#: plugin.py:549 +msgid "unknown" +msgstr "" -#: ../plugin.py:368 -msgid "When in deep standby" -msgstr "Quando il box è spento" +#: plugin.py:550 +#, python-format +msgid "Last: %s %s, %d events" +msgstr "Ultima importazione: %s %s, %d canali" -#: ../plugin.py:724 -msgid "Write to /tmp/epgimport.log" -msgstr "Scrittura in /tmp/epgimport.log" +#: plugin.py:560 +msgid "" +"EPGImport\n" +"Import of epg data is still in progress. Please wait." +msgstr "" +"EPG-Import\n" +"Importazione dati EPG in corso...\n" +"Attendere prego." + +#: plugin.py:571 +msgid "No active EPG sources found, nothing to do" +msgstr "Fonti EPG attive non trovate: nessuna operazione svolta" + +#: plugin.py:575 +msgid "" +"EPGImport\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" +msgstr "" +"EPG-Import\n" +"Eseguo importazione dati EPG.\n" +"Questo processo potrebbe richiedere qualche minuto\n" +"Procedo?" -#: ../plugin.py:660 +#: plugin.py:587 +msgid "" +"EPGImport Plugin\n" +"Failed to start:\n" +msgstr "" +"Plugin EPG-Import\n" +"Avvio fallito:\n" + +#: plugin.py:603 +msgid "Show log" +msgstr "Mostra il log" + +#: plugin.py:641 +msgid "Import" +msgstr "Importa" + +#: plugin.py:681 +msgid "EPG Import Sources" +msgstr "Fonti importazione EPG" + +#: plugin.py:721 plugin.py:745 +msgid "Days Profile" +msgstr "Profilo dei giorni" + +#: plugin.py:751 msgid "" "You may not use this settings!\n" "At least one day a week should be included!" @@ -305,7 +476,44 @@ msgstr "" "Non è possibile utilizzare queste impostazioni!\n" "Devi indicare almeno un giorno della settimana!" -#: ../plugin.py:790 +#: plugin.py:791 +msgid "Clear" +msgstr "Cancella" + +#: plugin.py:824 +msgid "EPG Import Log" +msgstr "Registro importazione EPG" + +#: plugin.py:830 +msgid "Write to /tmp/epgimport.log" +msgstr "Scrittura in /tmp/epgimport.log" + +#: plugin.py:846 +msgid "Last import: " +msgstr "Ultima importazione: " + +#: plugin.py:846 +msgid " events\n" +msgstr " Eventi\n" + +#: plugin.py:846 +msgid "" +"\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" +msgstr "" +"\n" +"Eseguo importazione dati EPG.\n" +"Questo processo potrebbe richiedere qualche minuto\n" +"Procedo?" + +#: plugin.py:896 +#, python-format +msgid "EPG Import finished, %d events" +msgstr "Importazione EPG completata, %d eventi" + +#: plugin.py:896 msgid "" "You must restart Enigma2 to load the EPG data,\n" "is this OK?" @@ -313,30 +521,43 @@ msgstr "" "Richiesto il riavvio del sistema per caricare i dati EPG:\n" "Procedo?" -#: ../plugin.py:60 -msgid "always" -msgstr "sempre" +#: plugin.py:1038 +msgid "No source file found !" +msgstr "Nessun file sorgente trovato!" -#: ../plugin.py:63 -msgid "never" -msgstr "mai" +#: plugin.py:1255 +msgid "Automated EPG Importer" +msgstr "Importazione EPG automatica" -#: ../plugin.py:62 -msgid "only automatic boot" -msgstr "solo avvio automatico" +#: plugin.py:1257 +msgid "EPG import now" +msgstr "Importa EPG ora" -#: ../plugin.py:61 -msgid "only manual boot" -msgstr "solo avvio manuale" +#: plugin.py:1258 plugin.py:1264 plugin.py:1269 plugin.py:1274 plugin.py:1279 +#: plugin.py:1287 plugin.py:1297 +msgid "EPG-Importer" +msgstr "EPG-Importer" -#: ../filtersServices.py:155 -msgid "press OK to save list" -msgstr "premi OK per salvare la lista" +#~ msgid "Path DB EPG" +#~ msgstr "Percorso DB EPG" -#: ../plugin.py:77 -msgid "skip the import" -msgstr "salta l'importazione" +#~ msgid "Choose Directory:" +#~ msgstr "Scegli la Directory:" -#: ../plugin.py:76 -msgid "wake up and import" -msgstr "sveglia e importa" +#~ msgid "Choose directory" +#~ msgstr "Scegli la directory" + +#~ msgid "Import from Git" +#~ msgstr "Import from Git" + +#~ msgid "Do you want to update Source now?" +#~ msgstr "Do you want to update Source now?" + +#~ msgid "Update Aborted!" +#~ msgstr "Update Aborted!" + +#~ msgid "Source files Imported and List Updated!" +#~ msgstr "Source files Imported and List Updated!" + +#~ msgid "Import current source" +#~ msgstr "Importa sorgente" diff --git a/src/EPGImport/plugin.py b/src/EPGImport/plugin.py index 61ca6f1..c01434a 100644 --- a/src/EPGImport/plugin.py +++ b/src/EPGImport/plugin.py @@ -1,3 +1,8 @@ +import os +import time + +from enigma import eConsoleAppContainer, eServiceCenter, eServiceReference, eEPGCache, eTimer, getDesktop + # for localized messages from . import _ from . import log @@ -8,11 +13,6 @@ from . import EPGConfig -import os -import time - -import enigma - try: from Components.SystemInfo import BoxInfo IMAGEDISTRO = BoxInfo.getItem("distro") @@ -23,9 +23,10 @@ # Config from Components.ActionMap import ActionMap from Components.Button import Button -from Components.config import config, configfile, ConfigEnableDisable, ConfigSubsection, ConfigYesNo, ConfigClock, getConfigListEntry, ConfigText, ConfigSelection, ConfigNumber, ConfigSubDict, NoSave +from Components.config import config, configfile, ConfigDirectory, ConfigEnableDisable, ConfigInteger, ConfigSubsection, ConfigYesNo, ConfigClock, getConfigListEntry, ConfigText, ConfigSelection, ConfigNumber, ConfigSubDict, NoSave from Components.ConfigList import ConfigListScreen from Components.Label import Label +from Components.Console import Console import Components.PluginComponent from Components.ScrollLabel import ScrollLabel from Components.Sources.StaticText import StaticText @@ -33,9 +34,10 @@ from Screens.ChoiceBox import ChoiceBox from Screens.MessageBox import MessageBox from Screens.Screen import Screen +from Screens.VirtualKeyBoard import VirtualKeyBoard import Screens.Standby from Tools import Notifications -from Tools.Directories import fileExists +from Tools.Directories import fileExists, SCOPE_PLUGINS, resolveFilename, isPluginInstalled from Tools.FuzzyDate import FuzzyTime try: from Tools.StbHardware import getFPWasTimerWakeup @@ -63,7 +65,8 @@ def calcDefaultStarttime(): #Set default configuration config.plugins.epgimport = ConfigSubsection() -config.plugins.epgimport.enabled = ConfigEnableDisable(default=False) +config.plugins.epgimport.enabled = ConfigEnableDisable(default=True) +config.plugins.epgimport.repeat_import = ConfigInteger(default=0, limits=(0, 23)) config.plugins.epgimport.runboot = ConfigSelection(default="4", choices=[ ("1", _("always")), ("2", _("only manual boot")), @@ -72,7 +75,7 @@ def calcDefaultStarttime(): ]) config.plugins.epgimport.runboot_restart = ConfigYesNo(default=False) config.plugins.epgimport.runboot_day = ConfigYesNo(default=False) -config.plugins.epgimport.wakeupsleep = ConfigEnableDisable(default=False) +# config.plugins.epgimport.wakeupsleep = ConfigEnableDisable(default=False) config.plugins.epgimport.wakeup = ConfigClock(default=calcDefaultStarttime()) # Different default in OpenATV: config.plugins.epgimport.showinplugins = ConfigYesNo(default=IMAGEDISTRO != "openatv") @@ -81,18 +84,23 @@ def calcDefaultStarttime(): ("wakeup", _("wake up and import")), ("skip", _("skip the import")) ]) +config.plugins.epgimport.pathdb = ConfigDirectory(default='/etc/enigma2/epg.dat') +config.plugins.epgimport.execute_shell = ConfigYesNo(default=False) +config.plugins.epgimport.shell_name = ConfigText(default="") config.plugins.epgimport.standby_afterwakeup = ConfigYesNo(default=False) +config.plugins.epgimport.run_after_standby = ConfigYesNo(default=False) config.plugins.epgimport.shutdown = ConfigYesNo(default=False) config.plugins.epgimport.longDescDays = ConfigNumber(default=5) -#config.plugins.epgimport.showinmainmenu = ConfigYesNo(default = False) +# config.plugins.epgimport.showinmainmenu = ConfigYesNo(default=False) config.plugins.epgimport.deepstandby_afterimport = NoSave(ConfigYesNo(default=False)) config.plugins.epgimport.parse_autotimer = ConfigYesNo(default=False) config.plugins.epgimport.import_onlybouquet = ConfigYesNo(default=False) config.plugins.epgimport.import_onlyiptv = ConfigYesNo(default=False) config.plugins.epgimport.clear_oldepg = ConfigYesNo(default=False) +config.plugins.epgimport.filter_custom_channel = ConfigYesNo(default=True) config.plugins.epgimport.day_profile = ConfigSelection(choices=[("1", _("Press OK"))], default="1") config.plugins.extra_epgimport = ConfigSubsection() -config.plugins.extra_epgimport.last_import = ConfigText(default="none") +config.plugins.extra_epgimport.last_import = ConfigText(default="0") config.plugins.extra_epgimport.day_import = ConfigSubDict() for i in range(7): @@ -108,6 +116,7 @@ def calcDefaultStarttime(): _("Sunday"), ] + # historically located (not a problem, we want to update it) CONFIG_PATH = '/etc/epgimport' @@ -116,44 +125,49 @@ def calcDefaultStarttime(): _session = None BouquetChannelListList = None serviceIgnoreList = None +filterCounter = 0 +isFilterRunning = 0 def getAlternatives(service): if not service: return None - alternativeServices = enigma.eServiceCenter.getInstance().list(service) + alternativeServices = eServiceCenter.getInstance().list(service) return alternativeServices and alternativeServices.getContent("S", True) def getRefNum(ref): - ref = ref.split(':')[3:7] - try: - return int(ref[0], 16) << 48 | int(ref[1], 16) << 32 | int(ref[2], 16) << 16 | int(ref[3], 16) >> 16 - except: - return + ref = ref.split(':')[3:7] + try: + return int(ref[0], 16) << 48 | int(ref[1], 16) << 32 | int(ref[2], 16) << 16 | int(ref[3], 16) >> 16 + except: + return def getBouquetChannelList(): channels = [] - serviceHandler = enigma.eServiceCenter.getInstance() - mask = (enigma.eServiceReference.isMarker | enigma.eServiceReference.isDirectory) - altrernative = enigma.eServiceReference.isGroup + global isFilterRunning, filterCounter + isFilterRunning = 1 + serviceHandler = eServiceCenter.getInstance() + mask = (eServiceReference.isMarker | eServiceReference.isDirectory) + altrernative = eServiceReference.isGroup if config.usage.multibouquet.value: bouquet_rootstr = '1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "bouquets.tv" ORDER BY bouquet' - bouquet_root = enigma.eServiceReference(bouquet_rootstr) + bouquet_root = eServiceReference(bouquet_rootstr) list = serviceHandler.list(bouquet_root) if list: while True: s = list.getNext() if not s.valid(): break - if s.flags & enigma.eServiceReference.isDirectory: + if s.flags & eServiceReference.isDirectory: info = serviceHandler.info(s) if info: clist = serviceHandler.list(s) if clist: while True: service = clist.getNext() + filterCounter += 1 if not service.valid(): break if not (service.flags & mask): @@ -170,11 +184,12 @@ def getBouquetChannelList(): channels.append(refnum) else: bouquet_rootstr = '1:7:1:0:0:0:0:0:0:0:FROM BOUQUET "userbouquet.favourites.tv" ORDER BY bouquet' - bouquet_root = enigma.eServiceReference(bouquet_rootstr) + bouquet_root = eServiceReference(bouquet_rootstr) services = serviceHandler.list(bouquet_root) - if not services is None: + if services is not None: while True: service = services.getNext() + filterCounter += 1 if not service.valid(): break if not (service.flags & mask): @@ -189,6 +204,7 @@ def getBouquetChannelList(): refnum = getRefNum(service.toString()) if refnum and refnum not in channels: channels.append(refnum) + isFilterRunning = 0 return channels # Filter servicerefs that this box can display by starting a fake recording. @@ -200,7 +216,7 @@ def channelFilter(ref): # ignore non IPTV if config.plugins.epgimport.import_onlyiptv.value and ("%3a//" not in ref.lower() or ref.startswith("1")): return False - sref = enigma.eServiceReference(ref) + sref = eServiceReference(ref) refnum = getRefNum(sref.toString()) if config.plugins.epgimport.import_onlybouquet.value: global BouquetChannelListList @@ -223,37 +239,47 @@ def channelFilter(ref): fakeRecResult = fakeRecService.start(True) NavigationInstance.instance.stopRecordService(fakeRecService) # -7 (errNoSourceFound) occurs when tuner is disconnected. - r = fakeRecResult in (0, -7) - #if not r: - # print>>log, "Rejected (%d): %s" % (fakeRecResult, ref) - return r + # r = fakeRecResult in (0, -7) + return fakeRecResult in (0, -5, -7) + # return r print("Invalid serviceref string:", ref, file=log) return False -epgimport = EPGImport.EPGImport(enigma.eEPGCache.getInstance(), channelFilter) - +try: + epgcache_instance = eEPGCache.getInstance() + if not epgcache_instance: + print("[EPGImport] Failed to get valid EPGCache instance.", file=log) + else: + print("[EPGImport] EPGCache instance obtained successfully.", file=log) + epgimport = EPGImport.EPGImport(epgcache_instance, channelFilter) +except Exception as e: + print("[EPGImport] Error obtaining EPGCache instance: %s" % e, file=log) + +# epgimport = EPGImport.EPGImport(eEPGCache.getInstance(), channelFilter) lastImportResult = None def startImport(): - EPGImport.HDD_EPG_DAT = config.misc.epgcache_filename.value - if config.plugins.epgimport.clear_oldepg.value and hasattr(epgimport.epgcache, 'flushEPG'): - EPGImport.unlink_if_exists(EPGImport.HDD_EPG_DAT) - EPGImport.unlink_if_exists(EPGImport.HDD_EPG_DAT + '.backup') - epgimport.epgcache.flushEPG() - epgimport.onDone = doneImport - epgimport.beginImport(longDescUntil=config.plugins.epgimport.longDescDays.value * 24 * 3600 + time.time()) + if not epgimport.isImportRunning(): + EPGImport.HDD_EPG_DAT = config.misc.epgcache_filename.value + if config.plugins.epgimport.filter_custom_channel.value: + EPGConfig.filterCustomChannel = True + else: + EPGConfig.filterCustomChannel = False + if config.plugins.epgimport.clear_oldepg.value and hasattr(epgimport.epgcache, 'flushEPG'): + EPGImport.unlink_if_exists(EPGImport.HDD_EPG_DAT) + EPGImport.unlink_if_exists(EPGImport.HDD_EPG_DAT + '.backup') + epgimport.epgcache.flushEPG() + epgimport.onDone = doneImport + epgimport.beginImport(longDescUntil=config.plugins.epgimport.longDescDays.value * 24 * 3600 + time.time()) + else: + print("[startImport] Already running, won't start again", file=log) ################################## # Configuration GUI -HD = False -try: - if enigma.getDesktop(0).size().width() >= 1280: - HD = True -except: - pass +HD = True if getDesktop(0).size().width() >= 1280 else False class EPGImportConfig(ConfigListScreen, Screen): @@ -300,13 +326,12 @@ class EPGImportConfig(ConfigListScreen, Screen): """ - def __init__(self, session, args=0): - self.session = session + def __init__(self, session): self.skin = EPGImportConfig.skin self.setup_title = _("EPG Import Configuration") Screen.__init__(self, session) self["status"] = Label() - self["statusbar"] = Label() + self["statusbar"] = Label(_("Last import: %s events") % config.plugins.extra_epgimport.last_import.value) self["key_red"] = Button(_("Cancel")) self["key_green"] = Button(_("Save")) self["key_yellow"] = Button(_("Manual")) @@ -322,20 +347,22 @@ def __init__(self, session, args=0): "log": self.keyInfo, "contextMenu": self.openMenu, }, -1) - ConfigListScreen.__init__(self, [], session=self.session) self.lastImportResult = None + list = [] self.onChangedEntry = [] + ConfigListScreen.__init__(self, list, session=self.session, on_change=self.changedEntry) self.prev_onlybouquet = config.plugins.epgimport.import_onlybouquet.value self.initConfig() self.createSetup() - self.importStatusTemplate = _("Importing: %s\n%s events") - self.updateTimer = enigma.eTimer() + self.filterStatusTemplate = _("Filtering:\n%s Please wait!") + self.importStatusTemplate = _("Importing:\n%s %s events") + self.updateTimer = eTimer() self.updateTimer.callback.append(self.updateStatus) self.updateTimer.start(2000) self.updateStatus() + self.onLayoutFinish.append(self.createSummary) self.onLayoutFinish.append(self.__layoutFinished) - # for summary: def changedEntry(self): for x in self.onChangedEntry: x() @@ -354,6 +381,8 @@ def __layoutFinished(self): self.setTitle(self.setup_title) def initConfig(self): + dx = 4 * " " + def getPrevValues(section): res = {} for (key, val) in section.content.items.items(): @@ -362,25 +391,31 @@ def getPrevValues(section): else: res[key] = val.value return res + self.EPG = config.plugins.epgimport self.prev_values = getPrevValues(self.EPG) - self.cfg_enabled = getConfigListEntry(_("Automatic import EPG"), self.EPG.enabled) - self.cfg_wakeup = getConfigListEntry(_("Automatic start time"), self.EPG.wakeup) - self.cfg_deepstandby = getConfigListEntry(_("When in deep standby"), self.EPG.deepstandby) - self.cfg_shutdown = getConfigListEntry(_("Return to deep standby after import"), self.EPG.shutdown) - self.cfg_standby_afterwakeup = getConfigListEntry(_("Standby at startup"), self.EPG.standby_afterwakeup) - self.cfg_day_profile = getConfigListEntry(_("Choice days for start import"), self.EPG.day_profile) - self.cfg_runboot = getConfigListEntry(_("Start import after booting up"), self.EPG.runboot) - self.cfg_import_onlybouquet = getConfigListEntry(_("Load EPG only services in bouquets"), self.EPG.import_onlybouquet) - self.cfg_import_onlyiptv = getConfigListEntry(_("Load EPG only for IPTV channels"), self.EPG.import_onlyiptv) - self.cfg_runboot_day = getConfigListEntry(_("Consider setting \"Days Profile\""), self.EPG.runboot_day) - self.cfg_runboot_restart = getConfigListEntry(_("Skip import on restart GUI"), self.EPG.runboot_restart) - self.cfg_showinextensions = getConfigListEntry(_("Show \"EPGImport\" in extensions"), self.EPG.showinextensions) - self.cfg_showinplugins = getConfigListEntry(_("Show \"EPGImport\" in plugins"), self.EPG.showinplugins) -# self.cfg_showinmainmenu = getConfigListEntry(_("Show \"EPG Importer\" in main menu"), self.EPG.showinmainmenu) - self.cfg_longDescDays = getConfigListEntry(_("Load long descriptions up to X days"), self.EPG.longDescDays) - self.cfg_parse_autotimer = getConfigListEntry(_("Run AutoTimer after import"), self.EPG.parse_autotimer) - self.cfg_clear_oldepg = getConfigListEntry(_("Delete current EPG before import"), config.plugins.epgimport.clear_oldepg) + self.cfg_enabled = getConfigListEntry(_("Automatic import EPG"), self.EPG.enabled, _("When enabled, it allows you to schedule an automatic EPG update at the given days and time.")) + self.cfg_wakeup = getConfigListEntry(dx + _("Automatic start time"), self.EPG.wakeup, _("Specify the time for the automatic EPG update.")) + self.cfg_deepstandby = getConfigListEntry(dx + _("When in deep standby"), self.EPG.deepstandby, _("Choose the action to perform when the box is in deep standby and the automatic EPG update should normally start.")) + self.cfg_shutdown = getConfigListEntry(dx + _("Return to deep standby after import"), self.EPG.shutdown, _("This will turn back waked up box into deep-standby after automatic EPG import.")) + self.cfg_standby_afterwakeup = getConfigListEntry(dx + _("Standby at startup"), self.EPG.standby_afterwakeup, _("The waked up box will be turned into standby after automatic EPG import wake up.")) + self.cfg_day_profile = getConfigListEntry(_("Choice days for start import"), self.EPG.day_profile, _("You can select the day(s) when the EPG update must be performed.")) + self.cfg_runboot = getConfigListEntry(_("Start import after booting up"), self.EPG.runboot, _("Specify in which case the EPG must be automatically updated after the box has booted.")) + self.cfg_import_onlybouquet = getConfigListEntry(dx + _("Load EPG only services in bouquets"), self.EPG.import_onlybouquet, _("To save memory you can decide to only load EPG data for the services that you have in your bouquet files.")) + self.cfg_import_onlyiptv = getConfigListEntry(_("Load EPG only for IPTV channels"), self.EPG.import_onlyiptv, _("Specify in this case the EPG should be imported only for iptv lists.")) + self.cfg_runboot_day = getConfigListEntry(dx + _("Consider setting \"Days Profile\""), self.EPG.runboot_day, _("When you decide to import the EPG after the box booted mention if the \"days profile\" must be take into consideration or not.")) + self.cfg_runboot_restart = getConfigListEntry(dx + _("Skip import on restart GUI"), self.EPG.runboot_restart, _("When you restart the GUI you can decide to skip or not the EPG data import.")) + self.cfg_showinextensions = getConfigListEntry(_("Show \"EPG import now\" in extensions"), self.EPG.showinextensions, _("Display a shortcut \"EPG import now\" in the extension menu. This menu entry will immediately start the EPG update process when selected.")) + self.cfg_showinplugins = getConfigListEntry(_("Show \"EPGImport\" in plugins"), self.EPG.showinplugins, _("Display a shortcut \"EPG import\" in the browser plugin..")) + # self.cfg_showinmainmenu = getConfigListEntry(_("Show \"EPG import\" in epg menu"), self.EPG.showinmainmenu, _("Display a shortcut \"EPG import\" in your STB epg menu screen. This allows you to access the configuration.")) + self.cfg_longDescDays = getConfigListEntry(_("Load long descriptions up to X days"), self.EPG.longDescDays, _("Define the number of days that you want to get the full EPG data, reducing this number can help you to save memory usage on your box. But you are also limited with the EPG provider available data. You will not have 15 days EPG if it only provide 7 days data.")) + self.cfg_parse_autotimer = getConfigListEntry(_("Run AutoTimer after import"), self.EPG.parse_autotimer, _("You can start automatically the plugin AutoTimer after the EPG data update to have it refreshing its scheduling after EPG data refresh.")) + self.cfg_clear_oldepg = getConfigListEntry(_("Clearing current EPG before import"), config.plugins.epgimport.clear_oldepg, _("This will clear the current EPG data in memory before updating the EPG data. This allows you to always have a clean new EPG with the latest EPG data, for example in case of program changes between refresh, otherwise EPG data are cumulative.")) + self.cfg_filter_custom_channel = getConfigListEntry(_("Also apply \"channel id\" filtering on custom.channels.xml"), self.EPG.filter_custom_channel, _("This is for advanced users that are using the channel id filtering feature. If enabled, the filter rules defined into /etc/epgimport/channel_id_filter.conf will also be applied on your /etc/epgimport/custom.channels.xml file.")) + self.cfg_execute_shell = getConfigListEntry(_("Execute shell command before import EPG"), self.EPG.execute_shell, _("When enabled, then you can run the desired script before starting the import, after which the import of the EPG will begin.")) + self.cfg_shell_name = getConfigListEntry(dx + _("Shell command name"), self.EPG.shell_name, _("Enter shell command name.")) + self.cfg_run_after_standby = getConfigListEntry(_("Start import after standby"), self.EPG.run_after_standby, _("Start import after resuming from standby mode.")) + self.cfg_repeat_import = getConfigListEntry(dx + _("Hours after which the import is repeated"), self.EPG.repeat_import, _("Number of hours (1-23, or 0 for no repeat) after which the import is repeated. This value is not saved and will be reset when the GUI restarts.")) def createSetup(self): list = [self.cfg_enabled] @@ -391,25 +426,33 @@ def createSetup(self): list.append(self.cfg_shutdown) if not self.EPG.shutdown.value: list.append(self.cfg_standby_afterwakeup) + list.append(self.cfg_repeat_import) + else: + list.append(self.cfg_repeat_import) list.append(self.cfg_day_profile) list.append(self.cfg_runboot) if self.EPG.runboot.value != "4": list.append(self.cfg_runboot_day) if self.EPG.runboot.value == "1" or self.EPG.runboot.value == "2": list.append(self.cfg_runboot_restart) - list.append(self.cfg_showinextensions) - list.append(self.cfg_showinplugins) + list.append(self.cfg_run_after_standby) list.append(self.cfg_import_onlybouquet) list.append(self.cfg_import_onlyiptv) - if hasattr(enigma.eEPGCache, 'flushEPG'): + if hasattr(eEPGCache, 'flushEPG'): list.append(self.cfg_clear_oldepg) + list.append(self.cfg_filter_custom_channel) list.append(self.cfg_longDescDays) - if fileExists("/usr/lib/enigma2/python/Plugins/Extensions/AutoTimer/plugin.py"): + list.append(self.cfg_execute_shell) + if self.EPG.execute_shell.value: + list.append(self.cfg_shell_name) + if fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyo")) or fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyc")): try: - from Plugins.Extensions.AutoTimer.AutoTimer import AutoTimer + # from Plugins.Extensions.AutoTimer.AutoTimer import AutoTimer list.append(self.cfg_parse_autotimer) except: print("[XMLTVImport] AutoTimer Plugin not installed", file=log) + list.append(self.cfg_showinextensions) + list.append(self.cfg_showinplugins) self["config"].list = list self["config"].l.setList(list) @@ -419,6 +462,7 @@ def newConfig(self): self.createSetup() def keyRed(self): + def setPrevValues(section, values): for (key, val) in section.content.items.items(): value = values.get(key, None) @@ -432,14 +476,25 @@ def setPrevValues(section, values): def keyGreen(self): self.updateTimer.stop() - if not fileExists("/usr/lib/enigma2/python/Plugins/Extensions/AutoTimer/plugin.py") and self.EPG.parse_autotimer.value: + if self.EPG.parse_autotimer.value and (not fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyo")) or not fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyc"))): self.EPG.parse_autotimer.value = False + if self.EPG.deepstandby.value == "skip" and self.EPG.shutdown.value: + self.EPG.shutdown.value = False if self.EPG.shutdown.value: self.EPG.standby_afterwakeup.value = False - self.EPG.save() + self.EPG.repeat_import.value = 0 if self.prev_onlybouquet != config.plugins.epgimport.import_onlybouquet.value or (autoStartTimer is not None and autoStartTimer.prev_multibouquet != config.usage.multibouquet.value): EPGConfig.channelCache = {} - self.close(True, self.session) + self.save() + + def save(self): + if self["config"].isChanged(): + for x in self["config"].list: + x[1].save() + configfile.save() + self.EPG.save() + self.session.open(MessageBox, _("Settings saved successfully !"), MessageBox.TYPE_INFO, timeout=5) + self.close(True) def keyLeft(self): ConfigListScreen.keyLeft(self) @@ -451,29 +506,46 @@ def keyRight(self): def keyOk(self): ConfigListScreen.keyOK(self) - sel = self["config"].getCurrent()[1] + sel = self["config"].getCurrent() and self["config"].getCurrent()[1] if sel and sel == self.EPG.day_profile: self.session.open(EPGImportProfile) + elif sel == self.EPG.shell_name: + self.session.openWithCallback(self.textEditCallback, VirtualKeyBoard, text=self.EPG.shell_name.value) + else: + return def updateStatus(self): text = "" - if epgimport.isImportRunning(): + global isFilterRunning, filterCounter + if isFilterRunning == 1: + text = self.filterStatusTemplate % (str(filterCounter)) + elif epgimport.isImportRunning(): src = epgimport.source text = self.importStatusTemplate % (src.description, epgimport.eventCount) self["status"].setText(text) if lastImportResult and (lastImportResult != self.lastImportResult): start, count = lastImportResult try: - d, t = FuzzyTime(start, inPast=True) - except: - # Not all images have inPast - d, t = FuzzyTime(start) + if isinstance(start, str): + start = time.mktime(time.strptime(start, "%Y-%m-%d %H:%M:%S")) + elif not isinstance(start, (int, float)): + raise ValueError("Start value is not a valid timestamp or string") + + # Chiama FuzzyTime con il timestamp corretto + d, t = FuzzyTime(int(start), inPast=True) + except Exception as e: + print("[EPGImport] Errore con FuzzyTime:", e) + try: + d, t = FuzzyTime(int(start)) + except Exception as e: + print("[EPGImport] Fallito anche il fallback con FuzzyTime:", e) + d, t = _("unknown"), _("unknown") self["statusbar"].setText(_("Last: %s %s, %d events") % (d, t, count)) self.lastImportResult = lastImportResult def keyInfo(self): last_import = config.plugins.extra_epgimport.last_import.value - self.session.open(MessageBox, _("Last import: %s events") % (last_import), type=MessageBox.TYPE_INFO) + self.session.open(MessageBox, _("Last import: %s events") % last_import, type=MessageBox.TYPE_INFO) def doimport(self, one_source=None): if epgimport.isImportRunning(): @@ -487,10 +559,10 @@ def doimport(self, one_source=None): else: cfg = one_source sources = [s for s in EPGConfig.enumSources(CONFIG_PATH, filter=cfg["sources"])] + EPGImport.ServerStatusList = {} if not sources: self.session.open(MessageBox, _("No active EPG sources found, nothing to do"), MessageBox.TYPE_INFO, timeout=10, close_on_any_key=True) return - # make it a stack, first on top. sources.reverse() epgimport.sources = sources self.session.openWithCallback(self.do_import_callback, MessageBox, _("EPGImport\nImport of epg data will start.\nThis may take a few minutes.\nIs this ok?"), MessageBox.TYPE_YESNO, timeout=15, default=True) @@ -499,12 +571,18 @@ def do_import_callback(self, confirmed): if not confirmed: return try: - startImport() + if config.plugins.epgimport.execute_shell.value and config.plugins.epgimport.shell_name.value: + Console().eBatch([config.plugins.epgimport.shell_name.value], self.executeShellEnd, debug=True) + else: + startImport() except Exception as e: print("[XMLTVImport] Error at start:", e, file=log) self.session.open(MessageBox, _("EPGImport Plugin\nFailed to start:\n") + str(e), MessageBox.TYPE_ERROR, timeout=15, close_on_any_key=True) self.updateStatus() + def executeShellEnd(self, *args, **kwargs): + startImport() + def dosources(self): self.session.openWithCallback(self.sourcesDone, EPGImportSources) @@ -550,10 +628,10 @@ class EPGImportSources(Screen): """ def __init__(self, session): - self.session = session Screen.__init__(self, session) self["key_red"] = Button(_("Cancel")) self["key_green"] = Button(_("Save")) + self["key_yellow"] = Button(_('Import')) self["key_blue"] = Button() cfg = EPGConfig.loadUserSettings() filter = cfg["sources"] @@ -575,9 +653,9 @@ def __init__(self, session): tree.append(cat) self["list"] = ExpandableSelectionList.ExpandableSelectionList(tree, enableWrapAround=True) if tree: - self["key_yellow"] = Button(_("Import current source")) + self["key_yellow"].show() else: - self["key_yellow"] = Button() + self["key_yellow"].hide() self["setupActions"] = ActionMap(["SetupActions", "ColorActions"], { "red": self.cancel, @@ -590,7 +668,7 @@ def __init__(self, session): self.setTitle(_("EPG Import Sources")) def save(self): - # Make the entries unique through a set + ''' Make the entries unique through a set ''' sources = list(set([item[1] for item in self["list"].enumSelected()])) print("[XMLTVImport] Selected sources:", sources, file=log) EPGConfig.storeUserSettings(sources=sources) @@ -625,9 +703,9 @@ class EPGImportProfile(ConfigListScreen, Screen): """ - def __init__(self, session, args=0): - self.session = session + def __init__(self, session): Screen.__init__(self, session) + self.setTitle(_("Days Profile")) self.list = [] for i in range(7): self.list.append(getConfigListEntry(weekdays[i], config.plugins.extra_epgimport.day_import[i])) @@ -648,18 +726,20 @@ def setCustomTitle(self): self.setTitle(_("Days Profile")) def save(self): - if not config.plugins.extra_epgimport.day_import[0].value: - if not config.plugins.extra_epgimport.day_import[1].value: - if not config.plugins.extra_epgimport.day_import[2].value: - if not config.plugins.extra_epgimport.day_import[3].value: - if not config.plugins.extra_epgimport.day_import[4].value: - if not config.plugins.extra_epgimport.day_import[5].value: - if not config.plugins.extra_epgimport.day_import[6].value: - self.session.open(MessageBox, _("You may not use this settings!\nAt least one day a week should be included!"), MessageBox.TYPE_INFO, timeout=6) - return - for x in self["config"].list: - x[1].save() - self.close() + if all(not config.plugins.extra_epgimport.day_import[i].value for i in range(7)): + self.session.open( + MessageBox, + _("You may not use this settings!\nAt least one day a week should be included!"), + MessageBox.TYPE_INFO, + timeout=6 + ) + return + ConfigListScreen.keySave(self) + """ + # for x in self["config"].list: + # x[1].save() + # self.close() + """ def cancel(self): for x in self["config"].list: @@ -688,11 +768,12 @@ class EPGImportLog(Screen): def __init__(self, session): self.session = session Screen.__init__(self, session) + self.log = log self["key_red"] = Button(_("Clear")) self["key_green"] = Button() self["key_yellow"] = Button() self["key_blue"] = Button(_("Save")) - self["list"] = ScrollLabel(log.getvalue()) + self["list"] = ScrollLabel(self.log.getvalue()) self["actions"] = ActionMap(["DirectionActions", "OkCancelActions", "ColorActions", "MenuActions"], { "red": self.clear, @@ -717,10 +798,9 @@ def setCustomTitle(self): def save(self): try: - f = open('/tmp/epgimport.log', 'w') - f.write(log.getvalue()) + with open('/tmp/epgimport.log', 'w') as f: + f.write(self.log.getvalue()) self.session.open(MessageBox, _("Write to /tmp/epgimport.log"), MessageBox.TYPE_INFO, timeout=5, close_on_any_key=True) - f.close() except Exception as e: self["list"].setText("Failed to write /tmp/epgimport.log:str" + str(e)) self.close(True) @@ -729,6 +809,8 @@ def cancel(self): self.close(False) def clear(self): + self.log.logfile.write("") + self.log.logfile.truncate() self.close(False) @@ -753,24 +835,23 @@ def start_import(session, **kwargs): def main(session, **kwargs): session.openWithCallback(doneConfiguring, EPGImportConfig) -#def main_menu(menuid, **kwargs): -# if menuid == "mainmenu" and config.plugins.epgimport.showinmainmenu.getValue(): -# return [(_("EPG Importer"), start_import, "epgimporter", 45)] -# else: -# return [] - -def doneConfiguring(session, retval): +def doneConfiguring(session, retval=False): "user has closed configuration, check new values...." - if autoStartTimer is not None: - autoStartTimer.update() + if retval is True: + if autoStartTimer is not None: + autoStartTimer.update(clock=True) def doneImport(reboot=False, epgfile=None): global _session, lastImportResult, BouquetChannelListList, serviceIgnoreList BouquetChannelListList = None serviceIgnoreList = None - lastImportResult = (time.time(), epgimport.eventCount) + # + timestamp = time.time() + formatted_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) + lastImportResult = (formatted_time, epgimport.eventCount) + # lastImportResult = (time.time(), epgimport.eventCount) try: start, count = lastImportResult localtime = time.asctime(time.localtime(time.time())) @@ -789,7 +870,7 @@ def doneImport(reboot=False, epgfile=None): _session.openWithCallback(restartEnigma, MessageBox, msg, MessageBox.TYPE_YESNO, timeout=15, default=True) print("[XMLTVImport] Need restart enigma2", file=log) else: - if config.plugins.epgimport.parse_autotimer.value and fileExists("/usr/lib/enigma2/python/Plugins/Extensions/AutoTimer/plugin.py"): + if config.plugins.epgimport.parse_autotimer.value and isPluginInstalled("AutoTimer"): try: from Plugins.Extensions.AutoTimer.plugin import autotimer if autotimer is None: @@ -809,13 +890,14 @@ def doneImport(reboot=False, epgfile=None): class checkDeepstandby: def __init__(self, session, parse=False): self.session = session - if parse: - self.FirstwaitCheck = enigma.eTimer() - self.FirstwaitCheck.callback.append(self.runCheckDeepstandby) - self.FirstwaitCheck.startLongTimer(600) - print("[XMLTVImport] Wait for parse autotimers 30 sec.", file=log) - else: - self.runCheckDeepstandby() + if config.plugins.epgimport.enabled.value: + if parse: + self.FirstwaitCheck = eTimer() + self.FirstwaitCheck.callback.append(self.runCheckDeepstandby) + self.FirstwaitCheck.startLongTimer(600) + print("[XMLTVImport] Wait for parse autotimers 600 sec.", file=log) + else: + self.runCheckDeepstandby() def runCheckDeepstandby(self): print("[XMLTVImport] Run check deep standby after import", file=log) @@ -847,7 +929,6 @@ def restartEnigma(confirmed): _session.open(Screens.Standby.TryQuitMainloop, 3) -################################## # Autostart section class AutoStartTimer: @@ -855,11 +936,16 @@ def __init__(self, session): self.session = session self.prev_onlybouquet = config.plugins.epgimport.import_onlybouquet.value self.prev_multibouquet = config.usage.multibouquet.value - self.timer = enigma.eTimer() + self.clock = config.plugins.epgimport.wakeup.value + self.timer = eTimer() self.timer.callback.append(self.onTimer) - self.pauseAfterFinishImportCheck = enigma.eTimer() + self.onceRepeatImport = eTimer() + self.onceRepeatImport.callback.append(self.runImport) + self.pauseAfterFinishImportCheck = eTimer() self.pauseAfterFinishImportCheck.callback.append(self.afterFinishImportCheck) self.pauseAfterFinishImportCheck.startLongTimer(30) + self.container = None + config.misc.standbyCounter.addNotifier(self.standbyCounterChangedRunImport) self.update() def getWakeTime(self): @@ -871,8 +957,11 @@ def getWakeTime(self): else: return -1 - def update(self, atLeast=0): + def update(self, atLeast=0, clock=False): self.timer.stop() + if clock and self.clock != config.plugins.epgimport.wakeup.value: + self.clock = config.plugins.epgimport.wakeup.value + self.onceRepeatImport.stop() wake = self.getWakeTime() now_t = time.time() now = int(now_t) @@ -881,8 +970,8 @@ def update(self, atLeast=0): cur_day = int(now_day.tm_wday) wakeup_day = WakeupDayOfWeek() if wakeup_day == -1: - return -1 print("[XMLTVImport] wakeup day of week disabled", file=log) + return -1 if wake < now + atLeast: wake += 86400 * wakeup_day else: @@ -891,8 +980,12 @@ def update(self, atLeast=0): next = wake - now self.timer.startLongTimer(next) else: + self.onceRepeatImport.stop() wake = -1 - print("[XMLTVImport] WakeUpTime now set to", wake, "(now=%s)" % now, file=log) + now_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(now)) + wake_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(wake)) if wake > 0 else "Not set" + + print("[XMLTVImport] WakeUpTime now set to", wake_str, "(now=%s)" % now_str, file=log) return wake def runImport(self): @@ -905,6 +998,27 @@ def runImport(self): if sources: sources.reverse() epgimport.sources = sources + if config.plugins.epgimport.execute_shell.value and config.plugins.epgimport.shell_name.value: + if self.container: + del self.container + self.container = eConsoleAppContainer() + self.container.appClosed.append(self.executeShellEnd) + if self.container.execute(config.plugins.epgimport.shell_name.value): + self.executeShellEnd(-1) + else: + startImport() + else: + self.session.open(MessageBox, _("No source file found !"), MessageBox.TYPE_INFO, timeout=5) + + def executeShellEnd(self, retval): + if retval: + if self.container: + try: + self.container.appClosed.remove(self.executeShellEnd) + except: + self.container.appClosed_conn = None + self.container.kill() + self.container = None startImport() def onTimer(self): @@ -916,6 +1030,10 @@ def onTimer(self): atLeast = 0 if wake - now < 60: self.runImport() + repeat_time = config.plugins.epgimport.repeat_import.value + if repeat_time: + self.onceRepeatImport.startLongTimer(repeat_time * 3600) + print("[EPGImport] start once repeat timer, wait in nours -", repeat_time, file=log) atLeast = 60 self.update(atLeast) @@ -935,8 +1053,8 @@ def getStatus(self): cur_day = int(now_day.tm_wday) wakeup_day = WakeupDayOfWeek() if wakeup_day == -1: - return -1 print("[XMLTVImport] wakeup day of week disabled", file=log) + return -1 if wake_up < now: wake_up += 86400 * wakeup_day else: @@ -966,18 +1084,32 @@ def afterFinishImportCheck(self): print("[XMLTVImport] Run to standby after wake up for checking", file=log) if not config.plugins.epgimport.deepstandby_afterimport.value: config.plugins.epgimport.deepstandby_afterimport.value = True - self.wait_timer = enigma.eTimer() + self.wait_timer = eTimer() self.wait_timer.timeout.get().append(self.startStandby) print("[XMLTVImport] start wait_timer (10sec) for goto standby", file=log) self.wait_timer.start(10000, True) + def afterStandbyRunImport(self): + if config.plugins.epgimport.run_after_standby.value: + print("[EPGImport] start import after standby", file=log) + self.runImport() + + def standbyCounterChangedRunImport(self, configElement): + if Screens.Standby.inStandby: + try: + if self.afterStandbyRunImport not in Screens.Standby.inStandby.onClose: + Screens.Standby.inStandby.onClose.append(self.afterStandbyRunImport) + except: + print("[EPGImport] error inStandby.onClose append afterStandbyRunImport", file=log) + def startStandby(self): if Screens.Standby.inStandby: print("[XMLTVImport] add checking standby", file=log) try: - Screens.Standby.inStandby.onClose.append(self.onLeaveStandby) + if self.onLeaveStandbyFinishImportCheck not in Screens.Standby.inStandby.onClose: + Screens.Standby.inStandby.onClose.append(self.onLeaveStandbyFinishImportCheck) except: - pass + print("[EPGImport] error inStandby.onClose append .onLeaveStandbyFinishImportCheck") def onLeaveStandby(self): if config.plugins.epgimport.deepstandby_afterimport.value: @@ -1007,18 +1139,19 @@ def onBootStartCheck(): wake = autoStartTimer.getStatus() print("[XMLTVImport] now=%d wake=%d wake-now=%d" % (now, wake, wake - now), file=log) if (wake < 0) or (wake - now > 600): + runboot = config.plugins.epgimport.runboot.value on_start = False - if config.plugins.epgimport.runboot.value == "1": + if runboot == "1": on_start = True print("[XMLTVImport] is boot", file=log) - elif config.plugins.epgimport.runboot.value == "2" and not getFPWasTimerWakeup(): + elif runboot == "2" and not getFPWasTimerWakeup(): on_start = True print("[XMLTVImport] is manual boot", file=log) - elif config.plugins.epgimport.runboot.value == "3" and getFPWasTimerWakeup(): + elif runboot == "3" and getFPWasTimerWakeup(): on_start = True print("[XMLTVImport] is automatic boot", file=log) flag = '/tmp/.EPGImportAnswerBoot' - if config.plugins.epgimport.runboot_restart.value and config.plugins.epgimport.runboot.value != "3": + if config.plugins.epgimport.runboot_restart.value and runboot != "3": if os.path.exists(flag): on_start = False print("[XMLTVImport] not starting import - is restart enigma2", file=log) @@ -1046,6 +1179,10 @@ def autostart(reason, session=None, **kwargs): global _session print("[XMLTVImport] autostart (%s) occured at" % reason, time.time(), file=log) if reason == 0 and _session is None: + nms = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) + # log.write("[EPGImport] autostart (%s) occured at (%s)" % (reason, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))) + # print("[EPGImport] autostart (%s) occured at" % reason, time.time(), file=log) + print("[EPGImport] autostart (%s) occured at" % reason, nms, file=log) if session is not None: _session = session if autoStartTimer is None: @@ -1068,13 +1205,11 @@ def autostart(reason, session=None, **kwargs): def getNextWakeup(): "returns timestamp of next time when autostart should be called" if autoStartTimer: - if config.plugins.epgimport.deepstandby.value == 'wakeup' and autoStartTimer.getSources(): + if config.plugins.epgimport.enabled.value and config.plugins.epgimport.deepstandby.value == 'wakeup' and autoStartTimer.getSources(): print("[XMLTVImport] Will wake up from deep sleep", file=log) return autoStartTimer.getStatus() return -1 -# we need this helper function to identify the descriptor - def extensionsmenu(session, **kwargs): main(session, **kwargs) @@ -1092,7 +1227,7 @@ def setExtensionsmenu(el): description = _("Automated EPG Importer") config.plugins.epgimport.showinextensions.addNotifier(setExtensionsmenu, initial_call=False, immediate_feedback=False) -extDescriptor = PluginDescriptor(name=_("EPG-Importer"), description=description, where=PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=extensionsmenu) +extDescriptor = PluginDescriptor(name=_("EPG import now"), description=description, where=PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=start_import) pluginlist = PluginDescriptor(name=_("EPG-Importer"), description=description, where=PluginDescriptor.WHERE_PLUGINMENU, icon='plugin.png', fnc=main) diff --git a/src/EPGImport/xmltvconverter.py b/src/EPGImport/xmltvconverter.py index 25fae4f..c95b639 100755 --- a/src/EPGImport/xmltvconverter.py +++ b/src/EPGImport/xmltvconverter.py @@ -1,34 +1,42 @@ -from __future__ import absolute_import -from __future__ import print_function -import time -import calendar -import six +from xml.etree.cElementTree import iterparse +from xml.sax.saxutils import unescape +from calendar import timegm +from time import strptime, struct_time from . import log -#from pprint import pprint -from xml.etree.cElementTree import ElementTree, Element, SubElement, tostring, iterparse # %Y%m%d%H%M%S -def quickptime(str): - return time.struct_time((int(str[0:4]), int(str[4:6]), int(str[6:8]), - int(str[8:10]), int(str[10:12]), 0, - -1, -1, 0)) +def quickptime(date_str): + return struct_time( + ( + int(date_str[0:4]), # Year + int(date_str[4:6]), # Month + int(date_str[6:8]), # Day + int(date_str[8:10]), # Hour + int(date_str[10:12]), # Minute + 0, # Second (set to 0) + -1, # Weekday (set to -1 as unknown) + -1, # Julian day (set to -1 as unknown) + 0 # DST (Daylight Saving Time, set to 0 as unknown) + ) + ) def get_time_utc(timestring, fdateparse): - #print "get_time_utc", timestring, format + # print "get_time_utc", timestring, format try: values = timestring.split(' ') tm = fdateparse(values[0]) - timegm = calendar.timegm(tm) - #suppose file says +0300 => that means we have to substract 3 hours from localtime to get gmt - timegm -= (3600 * int(values[1]) // 100) - return timegm + timeGm = timegm(tm) + # suppose file says +0300 => that means we have to substract 3 hours from localtime to get gmt + timeGm -= (3600 * int(values[1]) // 100) + return timeGm except Exception as e: - print("[XMLTVConverter] get_time_utc error:", e) + print(f"[XMLTVConverter] get_time_utc error:{e}") return 0 + # Preferred language should be configurable, but for now, # we just like Dutch better! @@ -39,14 +47,28 @@ def get_xml_string(elem, name): for node in elem.findall(name): txt = node.text lang = node.get('lang', None) - if not r: + if not r and txt is not None: r = txt elif lang == "nl": r = txt except Exception as e: print("[XMLTVConverter] get_xml_string error:", e) + """ # Now returning UTF-8 by default, the epgdat/oudeis must be adjusted to make this work. - return six.ensure_str(r) + # Note that the default xml.sax.saxutils.unescape() function don't unescape + # some characters and we have to manually add them to the entities dictionary. + """ + + r = unescape(r, entities={ + r"'": r"'", + r""": r'"', + r"|": r"|", + r" ": r" ", + r"[": r"[", + r"]": r"]", + }) + return r.decode() if isinstance(r, bytes) else r + def get_xml_rating_string(elem): r = '' @@ -57,18 +79,22 @@ def get_xml_rating_string(elem): if not r: r = txt except Exception as e: - print("[XMLTVConverter] get_xml_rating_string error:", e) - return six.ensure_str(r) + print(f"[XMLTVConverter] get_xml_rating_string error:{e}") + return r.decode() if isinstance(r, bytes) else r + def enumerateProgrammes(fp): """Enumerates programme ElementTree nodes from file object 'fp'""" for event, elem in iterparse(fp): - if elem.tag == 'programme': - yield elem - elem.clear() - elif elem.tag == 'channel': - # Throw away channel elements, save memory - elem.clear() + try: + if elem.tag == 'programme': + yield elem + elem.clear() + elif elem.tag == 'channel': + # Throw away channel elements, save memory + elem.clear() + except Exception as e: + print(f"[XMLTVConverter] enumerateProgrammes error:{e}") class XMLTVConverter: @@ -78,9 +104,9 @@ def __init__(self, channels_dict, category_dict, dateformat='%Y%m%d%H%M%S %Z', o if dateformat.startswith('%Y%m%d%H%M%S'): self.dateParser = quickptime else: - self.dateParser = lambda x: time.strptime(x, dateformat) + self.dateParser = lambda x: strptime(x, dateformat) self.offset = offset - print("[XMLTVConverter] Using a custom time offset of %d" % offset) + print(f"[XMLTVConverter] Using a custom time offset of {offset}") def enumFile(self, fileobj): print("[XMLTVConverter] Enumerating event information", file=log) @@ -91,9 +117,9 @@ def enumFile(self, fileobj): for elem in enumerateProgrammes(fileobj): channel = elem.get('channel') channel = channel.lower() - if not channel in self.channels: + if channel not in self.channels: if lastUnknown != channel: - print("Unknown channel: ", channel, file=log) + print(f"Unknown channel: {channel}", file=log) lastUnknown = channel # return a None object to give up time to the reactor. yield None @@ -120,7 +146,7 @@ def enumFile(self, fileobj): rating_str = get_xml_rating_string(elem) # hardcode country as ENG since there is no handling for parental certification systems per country yet # also we support currently only number like values like "12+" since the epgcache works only with bytes right now - rating = [("eng", int(rating_str)-3)] + rating = [("eng", int(rating_str) - 3)] except: rating = None @@ -132,16 +158,16 @@ def enumFile(self, fileobj): else: yield (services, (start, stop - start, title, subtitle, description, cat_nr)) except Exception as e: - print("[XMLTVConverter] parsing event error:", e) + print(f"[XMLTVConverter] parsing event error: {e}") - def get_category(self, str, duration): - if (not str) or (not isinstance(str, type('str'))): + def get_category(self, cat, duration): + if (not cat) or (not isinstance(cat, type('str'))): return 0 - if str in self.categories: - category = self.categories[str] + if cat in self.categories: + category = self.categories[cat] if len(category) > 1: if duration > 60 * category[1]: return category[0] elif len(category) > 0: - return category + return category[0] return 0 From 8045abcb4f173aa6e84ed3ce3be75480293a76bf Mon Sep 17 00:00:00 2001 From: jbleyel Date: Mon, 24 Feb 2025 23:10:44 +0100 Subject: [PATCH 02/14] revert tab spaces --- src/EPGImport/EPGImport.py | 892 ++++++++++++++++++------------------- 1 file changed, 446 insertions(+), 446 deletions(-) diff --git a/src/EPGImport/EPGImport.py b/src/EPGImport/EPGImport.py index f73a59d..affbd00 100755 --- a/src/EPGImport/EPGImport.py +++ b/src/EPGImport/EPGImport.py @@ -24,15 +24,15 @@ def maybe_encode(text, encoding="utf-8"): - """ - Ensures that the text is properly encoded in Python 2 and 3. - :param text: The input text (assumed to be a string). - :param encoding: The encoding format (default: utf-8). - :return: Encoded text (if necessary). - """ - if isinstance(text, bytes): - return text.decode(encoding) - return text + """ + Ensures that the text is properly encoded in Python 2 and 3. + :param text: The input text (assumed to be a string). + :param encoding: The encoding format (default: utf-8). + :return: Encoded text (if necessary). + """ + if isinstance(text, bytes): + return text.decode(encoding) + return text packages.urllib3.disable_warnings(packages.urllib3.exceptions.InsecureRequestWarning) @@ -51,470 +51,470 @@ def maybe_encode(text, encoding="utf-8"): def threadGetPage(url=None, file=None, urlheaders=None, success=None, fail=None, *args, **kwargs): - print('[EPGImport][threadGetPage] url, file, args, kwargs', url, " ", file, " ", args, " ", kwargs) - try: - s = Session() - s.headers = {} - response = s.get(url, verify=False, headers=urlheaders, timeout=15, allow_redirects=True) - response.raise_for_status() - # check here for content-disposition header so to extract the actual filename (if the url doesnt contain it) - content_disp = response.headers.get('Content-Disposition', '') - filename = content_disp.split('filename="')[-1].split('"')[0] - ext = splitext(file)[1] - if filename: - ext = splitext(filename)[1] - if ext and len(ext) < 6: - file += ext - if not ext: - ext = splitext(response.url)[1] - if ext and len(ext) < 6: - file += ext - - with open(file, "wb") as f: - f.write(response.content) - # print('[EPGImport][threadGetPage] file completed: ', file) - success(file, deleteFile=True) - - except HTTPError as httperror: - print('EPGImport][threadGetPage] Http error: ', httperror) - fail(httperror) # E0602 undefined name 'error' - - except RequestException as error: - print('[EPGImport][threadGetPage] error: ', error) - # if fail is not None: - fail(error) + print('[EPGImport][threadGetPage] url, file, args, kwargs', url, " ", file, " ", args, " ", kwargs) + try: + s = Session() + s.headers = {} + response = s.get(url, verify=False, headers=urlheaders, timeout=15, allow_redirects=True) + response.raise_for_status() + # check here for content-disposition header so to extract the actual filename (if the url doesnt contain it) + content_disp = response.headers.get('Content-Disposition', '') + filename = content_disp.split('filename="')[-1].split('"')[0] + ext = splitext(file)[1] + if filename: + ext = splitext(filename)[1] + if ext and len(ext) < 6: + file += ext + if not ext: + ext = splitext(response.url)[1] + if ext and len(ext) < 6: + file += ext + + with open(file, "wb") as f: + f.write(response.content) + # print('[EPGImport][threadGetPage] file completed: ', file) + success(file, deleteFile=True) + + except HTTPError as httperror: + print('EPGImport][threadGetPage] Http error: ', httperror) + fail(httperror) # E0602 undefined name 'error' + + except RequestException as error: + print('[EPGImport][threadGetPage] error: ', error) + # if fail is not None: + fail(error) # Used to check server validity if config.misc.epgcache_filename.value: - HDD_EPG_DAT = config.misc.epgcache_filename.value + HDD_EPG_DAT = config.misc.epgcache_filename.value else: - config.misc.epgcache_filename.setValue(HDD_EPG_DAT) + config.misc.epgcache_filename.setValue(HDD_EPG_DAT) def relImport(name): - fullname = __name__.split('.') - fullname[-1] = name - mod = __import__('.'.join(fullname)) - for n in fullname[1:]: - mod = getattr(mod, n) + fullname = __name__.split('.') + fullname[-1] = name + mod = __import__('.'.join(fullname)) + for n in fullname[1:]: + mod = getattr(mod, n) - return mod + return mod def getParser(name): - module = PARSERS.get(name, name) - mod = relImport(module) - return mod.new() + module = PARSERS.get(name, name) + mod = relImport(module) + return mod.new() def getTimeFromHourAndMinutes(hour, minute): - # Check if the hour and minute are within valid ranges - if not (0 <= hour < 24): - raise ValueError("Hour must be between 0 and 23") - if not (0 <= minute < 60): - raise ValueError("Minute must be between 0 and 59") - - # Get the current local time - now = time.localtime() - - # Calculate the timestamp for the specified time (today with the given hour and minute) - begin = int(time.mktime(( - now.tm_year, # Current year - now.tm_mon, # Current month - now.tm_mday, # Current day - hour, # Specified hour - minute, # Specified minute - 0, # Seconds (set to 0) - now.tm_wday, # Day of the week - now.tm_yday, # Day of the year - now.tm_isdst # Daylight saving time (DST) - ))) - - return begin + # Check if the hour and minute are within valid ranges + if not (0 <= hour < 24): + raise ValueError("Hour must be between 0 and 23") + if not (0 <= minute < 60): + raise ValueError("Minute must be between 0 and 59") + + # Get the current local time + now = time.localtime() + + # Calculate the timestamp for the specified time (today with the given hour and minute) + begin = int(time.mktime(( + now.tm_year, # Current year + now.tm_mon, # Current month + now.tm_mday, # Current day + hour, # Specified hour + minute, # Specified minute + 0, # Seconds (set to 0) + now.tm_wday, # Day of the week + now.tm_yday, # Day of the year + now.tm_isdst # Daylight saving time (DST) + ))) + + return begin def bigStorage(minFree, default, *candidates): - try: - diskstat = statvfs(default) - free = diskstat.f_bfree * diskstat.f_bsize - if free > minFree and free > 50000000: - return default - except Exception as e: - print("[EPGImport][bigStorage] Failed to stat %s:" % default, e) - - with open('/proc/mounts', 'rb') as f: - # format: device mountpoint fstype options # - mountpoints = [x.decode().split(' ', 2)[1] for x in f.readlines()] - - for candidate in candidates: - if candidate in mountpoints: - try: - diskstat = statvfs(candidate) - free = diskstat.f_bfree * diskstat.f_bsize - if free > minFree: - return candidate - except Exception as e: - print("[EPGImport][bigStorage] Failed to stat %s:" % default, e) - continue - raise Exception("[EPGImport][bigStorage] Insufficient storage for download") + try: + diskstat = statvfs(default) + free = diskstat.f_bfree * diskstat.f_bsize + if free > minFree and free > 50000000: + return default + except Exception as e: + print("[EPGImport][bigStorage] Failed to stat %s:" % default, e) + + with open('/proc/mounts', 'rb') as f: + # format: device mountpoint fstype options # + mountpoints = [x.decode().split(' ', 2)[1] for x in f.readlines()] + + for candidate in candidates: + if candidate in mountpoints: + try: + diskstat = statvfs(candidate) + free = diskstat.f_bfree * diskstat.f_bsize + if free > minFree: + return candidate + except Exception as e: + print("[EPGImport][bigStorage] Failed to stat %s:" % default, e) + continue + raise Exception("[EPGImport][bigStorage] Insufficient storage for download") class OudeisImporter: - """Wrapper to convert original patch to new one that accepts multiple services""" + """Wrapper to convert original patch to new one that accepts multiple services""" - def __init__(self, epgcache): - self.epgcache = epgcache + def __init__(self, epgcache): + self.epgcache = epgcache - # difference with old patch is that services is a list or tuple, this - # wrapper works around it. + # difference with old patch is that services is a list or tuple, this + # wrapper works around it. - def importEvents(self, services, events): - for service in services: - try: - self.epgcache.importEvents(maybe_encode(service, events)) - except Exception as e: - import traceback - traceback.print_exc() - print("[EPGImport][OudeisImporter][importEvents] ### importEvents exception:", e) + def importEvents(self, services, events): + for service in services: + try: + self.epgcache.importEvents(maybe_encode(service, events)) + except Exception as e: + import traceback + traceback.print_exc() + print("[EPGImport][OudeisImporter][importEvents] ### importEvents exception:", e) def unlink_if_exists(filename): - try: - unlink(filename) - except Exception as e: - print("[EPGImport] warning: Could not remove '%s' intermediate" % filename, repr(e)) + try: + unlink(filename) + except Exception as e: + print("[EPGImport] warning: Could not remove '%s' intermediate" % filename, repr(e)) class EPGImport: - """Simple Class to import EPGData""" - - def __init__(self, epgcache, channelFilter): - self.eventCount = None - self.epgcache = None - self.storage = None - self.sources = [] - self.source = None - self.epgsource = None - self.fd = None - self.iterator = None - self.onDone = None - self.epgcache = epgcache - self.channelFilter = channelFilter - return - - def beginImport(self, longDescUntil=None): - """Starts importing using Enigma reactor. Set self.sources before calling this.""" - if hasattr(self.epgcache, 'importEvents'): - print('[EPGImport][beginImport] using importEvents.') - self.storage = self.epgcache - elif hasattr(self.epgcache, 'importEvent'): - print('[EPGImport][beginImport] using importEvent(Oudis).') - self.storage = OudeisImporter(self.epgcache) - else: - print('[EPGImport][beginImport] oudeis patch not detected, using using epgdat_importer.epgdatclass/epg.dat instead.') - from . import epgdat_importer - self.storage = epgdat_importer.epgdatclass() - - self.eventCount = 0 - if longDescUntil is None: - # default to 7 days ahead - self.longDescUntil = time.time() + 24 * 3600 * 7 - else: - self.longDescUntil = longDescUntil - self.nextImport() - - def nextImport(self): - self.closeReader() - if not self.sources: - self.closeImport() - return - - self.source = self.sources.pop() - - print("[EPGImport][nextImport], source=", self.source.description, file=log) - self.fetchUrl(self.source.url) - - def fetchUrl(self, filename): - if filename.startswith('http:') or filename.startswith('https:') or filename.startswith('ftp:'): - # print("[EPGImport][fetchurl]Attempting to download from: ", filename) - self.urlDownload(filename, self.afterDownload, self.downloadFail) - else: - self.afterDownload(filename, deleteFile=False) - - def urlDownload(self, sourcefile, afterDownload, downloadFail): - host = ''.join(choices(ascii_lowercase, k=5)) - check_mount = False - if exists("/media/hdd"): - with open('/proc/mounts', 'r') as f: - for line in f: - ln = line.split() - if len(ln) > 1 and ln[1] == '/media/hdd': - check_mount = True - - # print("[EPGImport][urlDownload]2 check_mount ", check_mount) - pathDefault = "/media/hdd" if check_mount else "/tmp" - path = bigStorage(9000000, pathDefault, '/media/usb', '/media/cf') # lets use HDD and flash as main backup media - - filename = join(path, host) - if isinstance(sourcefile, list): - sourcefile = sourcefile[0] - - print("[EPGImport][urlDownload] Downloading: " + str(sourcefile) + " to local path: " + str(filename)) - ext = splitext(sourcefile)[1] - # Keep sensible extension, in particular the compression type - if ext and len(ext) < 6: - filename += ext - # sourcefile = sourcefile.encode('utf-8') - sourcefile = str(sourcefile) - print('sourcfile str=', sourcefile) - Headers = { - 'User-Agent': 'Twisted Client', - 'Accept-Encoding': 'gzip, deflate', - 'Accept': '*/*', - 'Connection': 'keep-alive'} - - print("[EPGImport][urlDownload] Downloading: " + sourcefile + " to local path: " + filename) - callInThread(threadGetPage, url=sourcefile, file=filename, urlheaders=Headers, success=afterDownload, fail=downloadFail) - - def afterDownload(self, filename, deleteFile=False): - # print("[EPGImport][afterDownload] filename", filename) - if not exists(filename): - self.downloadFail("File not exists") - return - try: - if not getsize(filename): - raise Exception("[EPGImport][afterDownload] File is empty") - except Exception as e: - print("[EPGImport][afterDownload] Exception filename 0", filename) - self.downloadFail(e) - return - - if self.source.parser == 'epg.dat': - if twisted.python.runtime.platform.supportsThreads(): - print("[EPGImport][afterDownload] Using twisted thread for DAT file", file=log) - threads.deferToThread(self.readEpgDatFile, filename, deleteFile).addCallback(lambda ignore: self.nextImport()) - else: - self.readEpgDatFile(filename, deleteFile) - return - - if filename.endswith('.gz'): - self.fd = gzip.open(filename, 'rb') - try: # read a bit to make sure it's a gzip file - self.fd.read(10) - self.fd.seek(0, 0) - except gzip.BadGzipFile as e: - print("[EPGImport][afterDownload] File downloaded is not a valid gzip file", filename, file=log) - self.downloadFail(e) - return - - elif filename.endswith('.xz') or filename.endswith('.lzma'): - try: - import lzma - except ImportError: - from backports import lzma - self.fd = lzma.open(filename, 'rb') - try: # read a bit to make sure it's an xz file - self.fd.read(10) - self.fd.seek(0, 0) - except lzma.LZMAError as e: - print("[EPGImport[afterDownload]] File downloaded is not a valid xz file", filename, file=log) - self.downloadFail(e) - return - - else: - self.fd = open(filename, 'rb') - - if deleteFile and self.source.parser != 'epg.dat': - try: - print("[EPGImport][afterDownload] unlink", filename, file=log) - unlink_if_exists(filename) - except Exception as e: - print("[EPGImport][afterDownload] warning: Could not remove '%s' intermediate" % filename, e, file=log) - - self.channelFiles = self.source.channels.downloadables() - if not self.channelFiles: - self.afterChannelDownload(None, None) - else: - filename = choice(self.channelFiles) - self.channelFiles.remove(filename) - self.urlDownload(filename, self.afterChannelDownload, self.channelDownloadFail) - return - - def downloadFail(self, failure): - print("[EPGImport][downloadFail] download failed:", failure, file=log) - if self.source.url in self.source.urls: - self.source.urls.remove(self.source.url) - if self.source.urls: - print("[EPGImport] Attempting alternative URL", file=log) - self.source.url = choice(self.source.urls) - print("[EPGImport][downloadFail] try alternative download url", self.source.url) - self.fetchUrl(self.source.url) - else: - self.nextImport() - - def afterChannelDownload(self, filename, deleteFile=True): - print("[EPGImport] afterChannelDownload filename", filename, file=log) - if filename: - try: - if not getsize(filename): - raise Exception("File is empty") - except Exception as e: - print("[EPGImport][afterChannelDownload] Exception filename", filename) - self.channelDownloadFail(e) - return - if twisted.python.runtime.platform.supportsThreads(): - print("[EPGImport] Using twisted thread", file=log) - threads.deferToThread(self.doThreadRead, filename).addCallback(lambda ignore: self.nextImport()) - deleteFile = False # Thread will delete it - else: - self.iterator = self.createIterator(filename) - reactor.addReader(self) - if deleteFile and filename: - try: - unlink_if_exists(filename) - except Exception as e: - print("[EPGImport][afterChannelDownload] warning: Could not remove '%s' intermediate" % filename, e, file=log) - - def channelDownloadFail(self, failure): - print("[EPGImport][channelDownloadFail] download channel failed:", failure, file=log) - if self.channelFiles: - filename = choice(self.channelFiles) - self.channelFiles.remove(filename) - self.urlDownload(filename, self.afterChannelDownload, self.channelDownloadFail) - else: - print("[EPGImport][channelDownloadFail] no more alternatives for channels", file=log) - self.nextImport() - - def createIterator(self, filename): - # print("[EPGImport][createIterator], filename", filename) - self.source.channels.update(self.channelFilter, filename) - return getParser(self.source.parser).iterator(self.fd, self.source.channels.items, self.source.offset) - - def readEpgDatFile(self, filename, deleteFile=False): - if not hasattr(self.epgcache, 'load'): - print("[EPGImport][readEpgDatFile] Cannot load EPG.DAT files on unpatched enigma. Need CrossEPG patch.", file=log) - return - - unlink_if_exists(HDD_EPG_DAT) - - try: - if filename.endswith('.gz'): - print("[EPGImport][readEpgDatFile] Uncompressing", filename, file=log) - import shutil - fd = gzip.open(filename, 'rb') - epgdat = open(HDD_EPG_DAT, 'wb') - shutil.copyfileobj(fd, epgdat) - del fd - epgdat.close() - del epgdat - - elif filename != HDD_EPG_DAT: - symlink(filename, HDD_EPG_DAT) - - print("[EPGImport][readEpgDatFile] Importing", HDD_EPG_DAT, file=log) - self.epgcache.load() - - if deleteFile: - unlink_if_exists(filename) - except Exception as e: - print("[EPGImport][readEpgDatFile] Failed to import %s:" % filename, e, file=log) - - def fileno(self): - if self.fd is not None: - return self.fd.fileno() - else: - return - - def doThreadRead(self, filename): - """This is used on PLi with threading""" - for data in self.createIterator(filename): - if data is not None: - self.eventCount += 1 - r, d = data - if d[0] > self.longDescUntil: - # Remove long description (save RAM memory) - d = d[:4] + ('',) + d[5:] - try: - self.storage.importEvents(r, (d,)) - except Exception as e: - print("[EPGImport][doThreadRead] ### importEvents exception:", e, file=log) - print("[EPGImport][doThreadRead] ### thread is ready ### Events:", self.eventCount, file=log) - if filename: - try: - unlink(filename) - except Exception as e: - print("[EPGImport][doThreadRead] warning: Could not remove '%s' intermediate" % filename, e, file=log) - return - - def doRead(self): - """called from reactor to read some data""" - try: - # returns tuple (ref, data) or None when nothing available yet. - data = next(self.iterator) - if data is not None: - self.eventCount += 1 - try: - r, d = data - if d[0] > self.longDescUntil: - # Remove long description (save RAM memory) - d = d[:4] + ('',) + d[5:] - self.storage.importEvents(r, (d,)) - except Exception as e: - print("[EPGImport][doRead] importEvents exception:", e, file=log) - except StopIteration: - self.nextImport() - return - - def connectionLost(self, failure): - """called from reactor on lost connection""" - # This happens because enigma calls us after removeReader - print("[EPGImport][connectionLost]failure", failure, file=log) - - def closeReader(self): - if self.fd is not None: - reactor.removeReader(self) - self.fd.close() - self.fd = None - self.iterator = None - return - - def closeImport(self): - self.closeReader() - self.iterator = None - self.source = None - if hasattr(self.storage, 'epgfile'): - needLoad = self.storage.epgfile - else: - needLoad = None - - self.storage = None - - if self.eventCount is not None: - print("[EPGImport] imported %d events" % self.eventCount, file=log) - reboot = False - if self.eventCount: - if needLoad: - print("[EPGImport] no Oudeis patch, load(%s) required" % needLoad, file=log) - reboot = True - try: - if hasattr(self.epgcache, 'load'): - print("[EPGImport] attempt load() patch", file=log) - if needLoad != HDD_EPG_DAT: - symlink(needLoad, HDD_EPG_DAT) - self.epgcache.load() - reboot = False - unlink_if_exists(needLoad) - except Exception as e: - print("[EPGImport] load() failed:", e, file=log) - elif hasattr(self.epgcache, 'save'): - self.epgcache.save() - elif hasattr(self.epgcache, 'timeUpdated'): - self.epgcache.timeUpdated() - if self.onDone: - self.onDone(reboot=reboot, epgfile=needLoad) - self.eventCount = None - log.write("[EPGImport] #### Finished ####\n") - - def isImportRunning(self): - return self.source is not None + """Simple Class to import EPGData""" + + def __init__(self, epgcache, channelFilter): + self.eventCount = None + self.epgcache = None + self.storage = None + self.sources = [] + self.source = None + self.epgsource = None + self.fd = None + self.iterator = None + self.onDone = None + self.epgcache = epgcache + self.channelFilter = channelFilter + return + + def beginImport(self, longDescUntil=None): + """Starts importing using Enigma reactor. Set self.sources before calling this.""" + if hasattr(self.epgcache, 'importEvents'): + print('[EPGImport][beginImport] using importEvents.') + self.storage = self.epgcache + elif hasattr(self.epgcache, 'importEvent'): + print('[EPGImport][beginImport] using importEvent(Oudis).') + self.storage = OudeisImporter(self.epgcache) + else: + print('[EPGImport][beginImport] oudeis patch not detected, using using epgdat_importer.epgdatclass/epg.dat instead.') + from . import epgdat_importer + self.storage = epgdat_importer.epgdatclass() + + self.eventCount = 0 + if longDescUntil is None: + # default to 7 days ahead + self.longDescUntil = time.time() + 24 * 3600 * 7 + else: + self.longDescUntil = longDescUntil + self.nextImport() + + def nextImport(self): + self.closeReader() + if not self.sources: + self.closeImport() + return + + self.source = self.sources.pop() + + print("[EPGImport][nextImport], source=", self.source.description, file=log) + self.fetchUrl(self.source.url) + + def fetchUrl(self, filename): + if filename.startswith('http:') or filename.startswith('https:') or filename.startswith('ftp:'): + # print("[EPGImport][fetchurl]Attempting to download from: ", filename) + self.urlDownload(filename, self.afterDownload, self.downloadFail) + else: + self.afterDownload(filename, deleteFile=False) + + def urlDownload(self, sourcefile, afterDownload, downloadFail): + host = ''.join(choices(ascii_lowercase, k=5)) + check_mount = False + if exists("/media/hdd"): + with open('/proc/mounts', 'r') as f: + for line in f: + ln = line.split() + if len(ln) > 1 and ln[1] == '/media/hdd': + check_mount = True + + # print("[EPGImport][urlDownload]2 check_mount ", check_mount) + pathDefault = "/media/hdd" if check_mount else "/tmp" + path = bigStorage(9000000, pathDefault, '/media/usb', '/media/cf') # lets use HDD and flash as main backup media + + filename = join(path, host) + if isinstance(sourcefile, list): + sourcefile = sourcefile[0] + + print("[EPGImport][urlDownload] Downloading: " + str(sourcefile) + " to local path: " + str(filename)) + ext = splitext(sourcefile)[1] + # Keep sensible extension, in particular the compression type + if ext and len(ext) < 6: + filename += ext + # sourcefile = sourcefile.encode('utf-8') + sourcefile = str(sourcefile) + print('sourcfile str=', sourcefile) + Headers = { + 'User-Agent': 'Twisted Client', + 'Accept-Encoding': 'gzip, deflate', + 'Accept': '*/*', + 'Connection': 'keep-alive'} + + print("[EPGImport][urlDownload] Downloading: " + sourcefile + " to local path: " + filename) + callInThread(threadGetPage, url=sourcefile, file=filename, urlheaders=Headers, success=afterDownload, fail=downloadFail) + + def afterDownload(self, filename, deleteFile=False): + # print("[EPGImport][afterDownload] filename", filename) + if not exists(filename): + self.downloadFail("File not exists") + return + try: + if not getsize(filename): + raise Exception("[EPGImport][afterDownload] File is empty") + except Exception as e: + print("[EPGImport][afterDownload] Exception filename 0", filename) + self.downloadFail(e) + return + + if self.source.parser == 'epg.dat': + if twisted.python.runtime.platform.supportsThreads(): + print("[EPGImport][afterDownload] Using twisted thread for DAT file", file=log) + threads.deferToThread(self.readEpgDatFile, filename, deleteFile).addCallback(lambda ignore: self.nextImport()) + else: + self.readEpgDatFile(filename, deleteFile) + return + + if filename.endswith('.gz'): + self.fd = gzip.open(filename, 'rb') + try: # read a bit to make sure it's a gzip file + self.fd.read(10) + self.fd.seek(0, 0) + except gzip.BadGzipFile as e: + print("[EPGImport][afterDownload] File downloaded is not a valid gzip file", filename, file=log) + self.downloadFail(e) + return + + elif filename.endswith('.xz') or filename.endswith('.lzma'): + try: + import lzma + except ImportError: + from backports import lzma + self.fd = lzma.open(filename, 'rb') + try: # read a bit to make sure it's an xz file + self.fd.read(10) + self.fd.seek(0, 0) + except lzma.LZMAError as e: + print("[EPGImport[afterDownload]] File downloaded is not a valid xz file", filename, file=log) + self.downloadFail(e) + return + + else: + self.fd = open(filename, 'rb') + + if deleteFile and self.source.parser != 'epg.dat': + try: + print("[EPGImport][afterDownload] unlink", filename, file=log) + unlink_if_exists(filename) + except Exception as e: + print("[EPGImport][afterDownload] warning: Could not remove '%s' intermediate" % filename, e, file=log) + + self.channelFiles = self.source.channels.downloadables() + if not self.channelFiles: + self.afterChannelDownload(None, None) + else: + filename = choice(self.channelFiles) + self.channelFiles.remove(filename) + self.urlDownload(filename, self.afterChannelDownload, self.channelDownloadFail) + return + + def downloadFail(self, failure): + print("[EPGImport][downloadFail] download failed:", failure, file=log) + if self.source.url in self.source.urls: + self.source.urls.remove(self.source.url) + if self.source.urls: + print("[EPGImport] Attempting alternative URL", file=log) + self.source.url = choice(self.source.urls) + print("[EPGImport][downloadFail] try alternative download url", self.source.url) + self.fetchUrl(self.source.url) + else: + self.nextImport() + + def afterChannelDownload(self, filename, deleteFile=True): + print("[EPGImport] afterChannelDownload filename", filename, file=log) + if filename: + try: + if not getsize(filename): + raise Exception("File is empty") + except Exception as e: + print("[EPGImport][afterChannelDownload] Exception filename", filename) + self.channelDownloadFail(e) + return + if twisted.python.runtime.platform.supportsThreads(): + print("[EPGImport] Using twisted thread", file=log) + threads.deferToThread(self.doThreadRead, filename).addCallback(lambda ignore: self.nextImport()) + deleteFile = False # Thread will delete it + else: + self.iterator = self.createIterator(filename) + reactor.addReader(self) + if deleteFile and filename: + try: + unlink_if_exists(filename) + except Exception as e: + print("[EPGImport][afterChannelDownload] warning: Could not remove '%s' intermediate" % filename, e, file=log) + + def channelDownloadFail(self, failure): + print("[EPGImport][channelDownloadFail] download channel failed:", failure, file=log) + if self.channelFiles: + filename = choice(self.channelFiles) + self.channelFiles.remove(filename) + self.urlDownload(filename, self.afterChannelDownload, self.channelDownloadFail) + else: + print("[EPGImport][channelDownloadFail] no more alternatives for channels", file=log) + self.nextImport() + + def createIterator(self, filename): + # print("[EPGImport][createIterator], filename", filename) + self.source.channels.update(self.channelFilter, filename) + return getParser(self.source.parser).iterator(self.fd, self.source.channels.items, self.source.offset) + + def readEpgDatFile(self, filename, deleteFile=False): + if not hasattr(self.epgcache, 'load'): + print("[EPGImport][readEpgDatFile] Cannot load EPG.DAT files on unpatched enigma. Need CrossEPG patch.", file=log) + return + + unlink_if_exists(HDD_EPG_DAT) + + try: + if filename.endswith('.gz'): + print("[EPGImport][readEpgDatFile] Uncompressing", filename, file=log) + import shutil + fd = gzip.open(filename, 'rb') + epgdat = open(HDD_EPG_DAT, 'wb') + shutil.copyfileobj(fd, epgdat) + del fd + epgdat.close() + del epgdat + + elif filename != HDD_EPG_DAT: + symlink(filename, HDD_EPG_DAT) + + print("[EPGImport][readEpgDatFile] Importing", HDD_EPG_DAT, file=log) + self.epgcache.load() + + if deleteFile: + unlink_if_exists(filename) + except Exception as e: + print("[EPGImport][readEpgDatFile] Failed to import %s:" % filename, e, file=log) + + def fileno(self): + if self.fd is not None: + return self.fd.fileno() + else: + return + + def doThreadRead(self, filename): + """This is used on PLi with threading""" + for data in self.createIterator(filename): + if data is not None: + self.eventCount += 1 + r, d = data + if d[0] > self.longDescUntil: + # Remove long description (save RAM memory) + d = d[:4] + ('',) + d[5:] + try: + self.storage.importEvents(r, (d,)) + except Exception as e: + print("[EPGImport][doThreadRead] ### importEvents exception:", e, file=log) + print("[EPGImport][doThreadRead] ### thread is ready ### Events:", self.eventCount, file=log) + if filename: + try: + unlink(filename) + except Exception as e: + print("[EPGImport][doThreadRead] warning: Could not remove '%s' intermediate" % filename, e, file=log) + return + + def doRead(self): + """called from reactor to read some data""" + try: + # returns tuple (ref, data) or None when nothing available yet. + data = next(self.iterator) + if data is not None: + self.eventCount += 1 + try: + r, d = data + if d[0] > self.longDescUntil: + # Remove long description (save RAM memory) + d = d[:4] + ('',) + d[5:] + self.storage.importEvents(r, (d,)) + except Exception as e: + print("[EPGImport][doRead] importEvents exception:", e, file=log) + except StopIteration: + self.nextImport() + return + + def connectionLost(self, failure): + """called from reactor on lost connection""" + # This happens because enigma calls us after removeReader + print("[EPGImport][connectionLost]failure", failure, file=log) + + def closeReader(self): + if self.fd is not None: + reactor.removeReader(self) + self.fd.close() + self.fd = None + self.iterator = None + return + + def closeImport(self): + self.closeReader() + self.iterator = None + self.source = None + if hasattr(self.storage, 'epgfile'): + needLoad = self.storage.epgfile + else: + needLoad = None + + self.storage = None + + if self.eventCount is not None: + print("[EPGImport] imported %d events" % self.eventCount, file=log) + reboot = False + if self.eventCount: + if needLoad: + print("[EPGImport] no Oudeis patch, load(%s) required" % needLoad, file=log) + reboot = True + try: + if hasattr(self.epgcache, 'load'): + print("[EPGImport] attempt load() patch", file=log) + if needLoad != HDD_EPG_DAT: + symlink(needLoad, HDD_EPG_DAT) + self.epgcache.load() + reboot = False + unlink_if_exists(needLoad) + except Exception as e: + print("[EPGImport] load() failed:", e, file=log) + elif hasattr(self.epgcache, 'save'): + self.epgcache.save() + elif hasattr(self.epgcache, 'timeUpdated'): + self.epgcache.timeUpdated() + if self.onDone: + self.onDone(reboot=reboot, epgfile=needLoad) + self.eventCount = None + log.write("[EPGImport] #### Finished ####\n") + + def isImportRunning(self): + return self.source is not None From b9b75ed1eb03bb6c0b6c66630ff6714f63bef44e Mon Sep 17 00:00:00 2001 From: jbleyel Date: Mon, 24 Feb 2025 23:30:06 +0100 Subject: [PATCH 03/14] cleanup imports --- src/EPGImport/EPGConfig.py | 65 ++++++++++++++-------------- src/EPGImport/plugin.py | 88 ++++++++++++++------------------------ 2 files changed, 64 insertions(+), 89 deletions(-) diff --git a/src/EPGImport/EPGConfig.py b/src/EPGImport/EPGConfig.py index dc34209..f411ef4 100755 --- a/src/EPGImport/EPGConfig.py +++ b/src/EPGImport/EPGConfig.py @@ -1,11 +1,11 @@ # -*- coding: UTF-8 -*- -import gzip -import os -import random -import time -import re +from gzip import GzipFile +from os import fstat, listdir, remove +from os.path import exists, getmtime, join, split +from random import choice +from time import time from html import unescape -from re import sub +from re import compile, sub, error as reError from pickle import dump, load, HIGHEST_PROTOCOL from xml.etree.cElementTree import iterparse @@ -28,11 +28,11 @@ def getChannels(path, name, offset): global channelCache if name in channelCache: return channelCache[name] - dirname, filename = os.path.split(path) + dirname, filename = split(path) if name: - channelfile = os.path.join(dirname, name) if isLocalFile(name) else name + channelfile = join(dirname, name) if isLocalFile(name) else name else: - channelfile = os.path.join(dirname, filename.split('.', 1)[0] + '.channels.xml') + channelfile = join(dirname, filename.split('.', 1)[0] + '.channels.xml') try: return channelCache[channelfile] except KeyError: @@ -102,20 +102,17 @@ def xml_unescape(text): def openStream(filename): fd = open(filename, 'rb') - if not os.fstat(fd.fileno()).st_size: + if not fstat(fd.fileno()).st_size: raise Exception("File is empty") if filename.endswith('.gz'): - fd = gzip.GzipFile(fileobj=fd, mode='rb') + fd = GzipFile(fileobj=fd, mode='rb') elif filename.endswith('.xz') or filename.endswith('.lzma'): - try: - import lzma - except ImportError: - from backports import lzma + import lzma fd = lzma.open(filename, 'rb') elif filename.endswith('.zip'): - import zipfile - from six import BytesIO - zip_obj = zipfile.ZipFile(filename, 'r') + from zipfile import ZipFile + from io import BytesIO + zip_obj = ZipFile(filename, 'r') fd = BytesIO(zip_obj.open(zip_obj.namelist()[0]).read()) return fd @@ -132,15 +129,15 @@ def set_channel_id_filter(): if clean_channel_id_line: try: # We compile indivually every line just to report error - full_filter = re.compile(clean_channel_id_line) - except re.error: + full_filter = compile(clean_channel_id_line) + except reError: print("[EPGImport] ERROR: " + clean_channel_id_line + " is not a valid regex. It will be ignored.", file=log) else: full_filter = full_filter + clean_channel_id_line + "|" except IOError: print("[EPGImport] INFO: no channel_id_filter.conf file found.", file=log) # Return a dummy filter (empty line filter) all accepted except empty channel id - compiled_filter = re.compile("^$") + compiled_filter = compile("^$") return (compiled_filter) # Last char is | so remove it full_filter = full_filter[:-1] @@ -149,14 +146,14 @@ def set_channel_id_filter(): # channel_id_filter.conf file exist but is empty, it has only comments, or only invalid regex if len(full_filter) == 0: # full_filter is empty returning dummy filter - compiled_filter = re.compile("^$") + compiled_filter = compile("^$") else: try: - compiled_filter = re.compile(full_filter) - except re.error: + compiled_filter = compile(full_filter) + except reError: print("[EPGImport] ERROR: final regex " + full_filter + " doesn't compile properly.", file=log) # Return a dummy filter (empty line filter) all accepted except empty channel id - compiled_filter = re.compile("^$") + compiled_filter = compile("^$") else: print("[EPGImport] INFO : final regex " + full_filter + " compiled successfully.", file=log) @@ -221,18 +218,18 @@ def update(self, filterCallback, downloadedFile=None): customFile = '/etc/epgimport/custom.channels.xml' # Always read custom file since we don't know when it was last updated # and we don't have multiple download from server problem since it is always a local file. - if not os.path.exists(customFile): + if not exists(customFile): customFile = '/etc/epgimport/rytec.channels.xml' - if os.path.exists(customFile): + if exists(customFile): print("[EPGImport] Parsing channels from '%s'" % customFile, file=log) self.parse(filterCallback, customFile, filterCustomChannel) if downloadedFile is not None: - self.mtime = time.time() + self.mtime = time() return self.parse(filterCallback, downloadedFile, True) elif (len(self.urls) == 1) and isLocalFile(self.urls[0]): try: - mtime = os.path.getmtime(self.urls[0]) + mtime = getmtime(self.urls[0]) except: mtime = None if (not self.mtime) or (mtime is not None and self.mtime < mtime): @@ -244,7 +241,7 @@ def downloadables(self): return None else: # Check at most once a day - now = time.time() + now = time() if (not self.mtime) or (self.mtime + 86400 < now): return self.urls return [] @@ -258,7 +255,7 @@ def __init__(self, path, elem, category=None, offset=0): self.parser = elem.get('type', 'gen_xmltv') self.nocheck = int(elem.get('nocheck', 0)) self.urls = [e.text.strip() for e in elem.findall('url')] - self.url = random.choice(self.urls) + self.url = choice(self.urls) self.description = elem.findtext('description') self.category = category self.offset = offset @@ -303,9 +300,9 @@ def enumSourcesFile(sourcefile, filter=None, categories=False): def enumSources(path, filter=None, categories=False): try: - for filename in os.listdir(path): + for filename in listdir(path): if filename.endswith('.sources.xml'): - sourcefile = os.path.join(path, filename) + sourcefile = join(path, filename) try: for s in enumSourcesFile(sourcefile, filter, categories): yield s @@ -342,7 +339,7 @@ def storeUserSettings(filename=SETTINGS_FILE, sources=None): x.append(p.description) storeUserSettings('settings.pkl', [1, "twee"]) assert loadUserSettings('settings.pkl') == {"sources": [1, "twee"]} - os.remove('settings.pkl') + remove('settings.pkl') for p in enumSources(path, x): t = (p.description, p.urls, p.parser, p.format, p.channels, p.nocheck) assert t in lx diff --git a/src/EPGImport/plugin.py b/src/EPGImport/plugin.py index c01434a..ee1f604 100644 --- a/src/EPGImport/plugin.py +++ b/src/EPGImport/plugin.py @@ -1,5 +1,6 @@ -import os -import time +from os import remove +from os.path import exists +from time import asctime, localtime, mktime, strftime, strptime, time from enigma import eConsoleAppContainer, eServiceCenter, eServiceReference, eEPGCache, eTimer, getDesktop @@ -272,7 +273,7 @@ def startImport(): EPGImport.unlink_if_exists(EPGImport.HDD_EPG_DAT + '.backup') epgimport.epgcache.flushEPG() epgimport.onDone = doneImport - epgimport.beginImport(longDescUntil=config.plugins.epgimport.longDescDays.value * 24 * 3600 + time.time()) + epgimport.beginImport(longDescUntil=config.plugins.epgimport.longDescDays.value * 24 * 3600 + time()) else: print("[startImport] Already running, won't start again", file=log) @@ -527,7 +528,7 @@ def updateStatus(self): start, count = lastImportResult try: if isinstance(start, str): - start = time.mktime(time.strptime(start, "%Y-%m-%d %H:%M:%S")) + start = mktime(strptime(start, "%Y-%m-%d %H:%M:%S")) elif not isinstance(start, (int, float)): raise ValueError("Start value is not a valid timestamp or string") @@ -848,13 +849,13 @@ def doneImport(reboot=False, epgfile=None): BouquetChannelListList = None serviceIgnoreList = None # - timestamp = time.time() - formatted_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp)) + timestamp = time() + formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime(timestamp)) lastImportResult = (formatted_time, epgimport.eventCount) - # lastImportResult = (time.time(), epgimport.eventCount) + # lastImportResult = (time(), epgimport.eventCount) try: start, count = lastImportResult - localtime = time.asctime(time.localtime(time.time())) + localtime = asctime(localtime(time())) lastimport = "%s, %d" % (localtime, count) config.plugins.extra_epgimport.last_import.value = lastimport config.plugins.extra_epgimport.last_import.save() @@ -922,7 +923,7 @@ def restartEnigma(confirmed): print("Failed to create /tmp/enigmastandby", file=log) else: try: - os.remove("/tmp/enigmastandby") + remove("/tmp/enigmastandby") except: pass # now reboot @@ -951,9 +952,9 @@ def __init__(self, session): def getWakeTime(self): if config.plugins.epgimport.enabled.value: clock = config.plugins.epgimport.wakeup.value - nowt = time.time() - now = time.localtime(nowt) - return int(time.mktime((now.tm_year, now.tm_mon, now.tm_mday, clock[0], clock[1], lastMACbyte() // 5, 0, now.tm_yday, now.tm_isdst))) + nowt = time() + now = localtime(nowt) + return int(mktime((now.tm_year, now.tm_mon, now.tm_mday, clock[0], clock[1], lastMACbyte() // 5, 0, now.tm_yday, now.tm_isdst))) else: return -1 @@ -963,9 +964,9 @@ def update(self, atLeast=0, clock=False): self.clock = config.plugins.epgimport.wakeup.value self.onceRepeatImport.stop() wake = self.getWakeTime() - now_t = time.time() + now_t = time() now = int(now_t) - now_day = time.localtime(now_t) + now_day = localtime(now_t) if wake > 0: cur_day = int(now_day.tm_wday) wakeup_day = WakeupDayOfWeek() @@ -982,8 +983,8 @@ def update(self, atLeast=0, clock=False): else: self.onceRepeatImport.stop() wake = -1 - now_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(now)) - wake_str = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(wake)) if wake > 0 else "Not set" + now_str = strftime("%Y-%m-%d %H:%M:%S", localtime(now)) + wake_str = strftime("%Y-%m-%d %H:%M:%S", localtime(wake)) if wake > 0 else "Not set" print("[XMLTVImport] WakeUpTime now set to", wake_str, "(now=%s)" % now_str, file=log) return wake @@ -1023,7 +1024,7 @@ def executeShellEnd(self, retval): def onTimer(self): self.timer.stop() - now = int(time.time()) + now = int(time()) print("[XMLTVImport] onTimer occured at", now, file=log) wake = self.getWakeTime() # If we're close enough, we're okay... @@ -1046,9 +1047,9 @@ def getSources(self): def getStatus(self): wake_up = self.getWakeTime() - now_t = time.time() + now_t = time() now = int(now_t) - now_day = time.localtime(now_t) + now_day = localtime(now_t) if wake_up > 0: cur_day = int(now_day.tm_wday) wakeup_day = WakeupDayOfWeek() @@ -1066,11 +1067,11 @@ def getStatus(self): def afterFinishImportCheck(self): if config.plugins.epgimport.deepstandby.value == 'wakeup' and getFPWasTimerWakeup(): - if os.path.exists("/tmp/enigmastandby") or os.path.exists("/tmp/.EPGImportAnswerBoot"): + if exists("/tmp/enigmastandby") or exists("/tmp/.EPGImportAnswerBoot"): print("[XMLTVImport] is restart enigma2", file=log) else: wake = self.getStatus() - now_t = time.time() + now_t = time() now = int(now_t) if 0 < wake - now <= 60 * 5: if config.plugins.epgimport.standby_afterwakeup.value: @@ -1120,8 +1121,8 @@ def onLeaveStandby(self): def WakeupDayOfWeek(): start_day = -1 try: - now = time.time() - now_day = time.localtime(now) + now = time() + now_day = localtime(now) cur_day = int(now_day.tm_wday) except: cur_day = -1 @@ -1135,7 +1136,7 @@ def WakeupDayOfWeek(): def onBootStartCheck(): global autoStartTimer print("[XMLTVImport] onBootStartCheck", file=log) - now = int(time.time()) + now = int(time()) wake = autoStartTimer.getStatus() print("[XMLTVImport] now=%d wake=%d wake-now=%d" % (now, wake, wake - now), file=log) if (wake < 0) or (wake - now > 600): @@ -1152,7 +1153,7 @@ def onBootStartCheck(): print("[XMLTVImport] is automatic boot", file=log) flag = '/tmp/.EPGImportAnswerBoot' if config.plugins.epgimport.runboot_restart.value and runboot != "3": - if os.path.exists(flag): + if exists(flag): on_start = False print("[XMLTVImport] not starting import - is restart enigma2", file=log) else: @@ -1161,7 +1162,7 @@ def onBootStartCheck(): except: print("Failed to create /tmp/.EPGImportAnswerBoot", file=log) if config.plugins.epgimport.runboot_day.value: - now = time.localtime() + now = localtime() cur_day = int(now.tm_wday) if not config.plugins.extra_epgimport.day_import[cur_day].value: on_start = False @@ -1177,11 +1178,11 @@ def autostart(reason, session=None, **kwargs): "called with reason=1 to during shutdown, with reason=0 at startup?" global autoStartTimer global _session - print("[XMLTVImport] autostart (%s) occured at" % reason, time.time(), file=log) + print("[XMLTVImport] autostart (%s) occured at" % reason, time(), file=log) if reason == 0 and _session is None: - nms = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())) - # log.write("[EPGImport] autostart (%s) occured at (%s)" % (reason, time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))) - # print("[EPGImport] autostart (%s) occured at" % reason, time.time(), file=log) + nms = strftime("%Y-%m-%d %H:%M:%S", localtime(time())) + # log.write("[EPGImport] autostart (%s) occured at (%s)" % (reason, strftime("%Y-%m-%d %H:%M:%S", localtime()))) + # print("[EPGImport] autostart (%s) occured at" % reason, time(), file=log) print("[EPGImport] autostart (%s) occured at" % reason, nms, file=log) if session is not None: _session = session @@ -1190,12 +1191,12 @@ def autostart(reason, session=None, **kwargs): if config.plugins.epgimport.runboot.value != "4": onBootStartCheck() # If WE caused the reboot, put the box back in standby. - if os.path.exists("/tmp/enigmastandby"): + if exists("/tmp/enigmastandby"): print("[XMLTVImport] Returning to standby", file=log) if not Screens.Standby.inStandby: Notifications.AddNotification(Screens.Standby.Standby) try: - os.remove("/tmp/enigmastandby") + remove("/tmp/enigmastandby") except: pass else: @@ -1278,26 +1279,3 @@ def Plugins(**kwargs): if config.plugins.epgimport.showinplugins.value: result.append(pluginlist) return result - - -class SetupSummary(Screen): - def __init__(self, session, parent): - Screen.__init__(self, session, parent=parent) - self["SetupTitle"] = StaticText(_(parent.setup_title)) - self["SetupEntry"] = StaticText("") - self["SetupValue"] = StaticText("") - self.onShow.append(self.addWatcher) - self.onHide.append(self.removeWatcher) - - def addWatcher(self): - self.parent.onChangedEntry.append(self.selectionChanged) - self.parent["list"].onSelectionChanged.append(self.selectionChanged) - self.selectionChanged() - - def removeWatcher(self): - self.parent.onChangedEntry.remove(self.selectionChanged) - self.parent["list"].onSelectionChanged.remove(self.selectionChanged) - - def selectionChanged(self): - self["SetupEntry"].text = self.parent.getCurrentEntry() - self["SetupValue"].text = self.parent.getCurrentValue() From 82af202296f2830274b8b9c6210eb0e9af5241ae Mon Sep 17 00:00:00 2001 From: jbleyel Date: Tue, 25 Feb 2025 21:26:40 +0100 Subject: [PATCH 04/14] revert epgdat --- src/EPGImport/epgdat.py | 151 +++++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 73 deletions(-) diff --git a/src/EPGImport/epgdat.py b/src/EPGImport/epgdat.py index d547a34..2ff49af 100755 --- a/src/EPGImport/epgdat.py +++ b/src/EPGImport/epgdat.py @@ -3,19 +3,18 @@ # Heavily modified by MiLo http://www.sat4all.com/ # Lots of stuff removed by Mike Looijmans # Updated for python3 by TwolDE & Huevos with testing input by Thawtes -from datetime import datetime + import os import struct - +from datetime import datetime +# EpgDatV8 = os.path.isfile("/etc/image-version") and "distro=openvix" in open("/etc/image-version").read() EpgDatV8 = True try: from . import dreamcrc - - def crc32_dreambox(d, t): - return dreamcrc.crc32(d, t) & 0xffffffff + crc32_dreambox = lambda d, t: dreamcrc.crc32(d, t) & 0xffffffff print("[EPGImport] using C module, yay") -except ImportError: +except: print("[EPGImport] failed to load C implementation, sorry") # this table is used by CRC32 routine below (used by Dreambox for @@ -100,13 +99,12 @@ def crc32_dreambox(crcdata, crctype, crctable=CRCTABLE): crc = crctable[crctype & 0x000000ff] crc = ((crc << 8) & 0xffffff00) ^ crctable[((crc >> 24) ^ len(crcdata)) & 0x000000ff] for d in crcdata: - crc = ((crc << 8) & 0xffffff00) ^ crctable[((crc >> 24) ^ ord(d)) & 0x000000ff] + crc = ((crc << 8) & 0xffffff00) ^ crctable[((crc >> 24) ^ d) & 0x000000ff] return crc -# convert time or length from datetime format to 3 bytes hex value -# i.e. 20:25:30 -> 0x20 , 0x25 , 0x30 - +# convert time or duration from datetime format to 3 bytes hex value +# this doesn't convert to hex but obviously the originators thought it did, and is part of EPG structure definitions. def TL_hexconv(dt): return ( (dt.hour % 10) + (16 * (dt.hour // 10)), @@ -116,6 +114,7 @@ def TL_hexconv(dt): class epgdat_class: + LAMEDB = '/etc/enigma2/lamedb' EPGDAT_FILENAME = 'epgtest.dat' @@ -174,39 +173,43 @@ def set_excludedsid(self, exsidlist): self.EXCLUDED_SID = exsidlist # assembling short description (type 0x4d , it's the Title) and compute its crc - def short_desc(self, s): + def shortDescription(self, sd): + sdbytes = sd.encode() + beng = "eng".encode() + b0 = "\0".encode() # 0x15 is UTF-8 encoding. - res = self.s_3sBB.pack('eng', len(s) + 1, 0x15) + str(s) + "\0" - return (crc32_dreambox(res, 0x4d), res) + sdbin = self.s_3sBB.pack(beng, int(len(sdbytes) + 1), 0x15) + sdbytes + b0 + return (crc32_dreambox(sdbin, 0x4d), sdbin) # assembling long description (type 0x4e) and compute its crc - def long_desc(self, s): - r = [] + def longDescription(self, ld): + beng = "eng".encode() + ldres = [] # compute total number of descriptions, block 245 bytes each # number of descriptions start to index 0 - num_tot_desc = (len(s) + 244) // 245 + ldbytes = ld.encode() + num_tot_desc = (len(ldbytes) + 244) // 245 for i in range(num_tot_desc): - ssub = s[i * 245: i * 245 + 245] - sres = self.s_B3sHBB.pack((i << 4) + (num_tot_desc - 1), 'eng', 0x0000, len(ssub) + 1, 0x15) + str(ssub) - r.append((crc32_dreambox(sres, 0x4e), sres)) - return r + ssub = ldbytes[i * 245:i * 245 + 245] + ldbin = self.s_B3sBBB.pack((i << 4) + (num_tot_desc - 1), beng, 0x00, int(len(ssub) + 1), 0x15) + ssub + ldres.append((crc32_dreambox(ldbin, 0x4e), ldbin)) + return ldres def add_event(self, starttime, duration, title, description): - # print("add event : ", event_starttime_unix_gmt, "title : ", event_title) - self.events.append((starttime, duration, self.short_desc(title[:240]), self.long_desc(description))) + # print("[epgdat][add_event]add event:- starttime, duration, title, description", starttime, duration, title, description) + self.events.append((starttime, duration, self.shortDescription(title[:240]), self.longDescription(description))) def preprocess_events_channel(self, services): EPG_EVENT_DATA_id = 0 for service in services: + # print("[epgdat][preprocess_events_channel] service : ", service) # skip empty lines, they make a mess if not service.strip(): continue # prepare and write CHANNEL INFO record ssid = service.split(":") # write CHANNEL INFO record (sid, onid, tsid, eventcount) - self.EPG_TMP_FD.write(self.s_IIII.pack( - int(ssid[3], 16), int(ssid[5], 16), - int(ssid[4], 16), len(self.events))) + self.EPG_TMP_FD.write(self.s_IIII.pack(int(ssid[3], 16), int(ssid[5], 16), int(ssid[4], 16), int(len(self.events)))) self.EPG_HEADER1_channel_count += 1 # event_dict.keys() are numeric so indexing is possibile # key is the same thing as counter and is more simple to manage last-1 item @@ -216,61 +219,60 @@ def preprocess_events_channel(self, services): s_I = self.s_I for event in events: # **** (1) : create DESCRIPTION HEADER / DATA **** + EPG_EVENT_HEADER_datasize = 0 - # short description (title) type 0x4d - short_d = event[2] - EPG_EVENT_HEADER_datasize += 4 # add 4 bytes for a sigle REF DESC (CRC32) - # if short_d[0] not in epg_event_description_dict: - # if not exist_event(short_d[0]) : - if short_d[0] not in self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER: - # DESCRIPTION DATA - pack_1 = s_BB.pack(0x4d, len(short_d[1])) + short_d[1] + + # short description (title) type 0x4d self.shortDescription(title[:240]) = event[2] + shortDescription = event[2] # (crc32, short description packed) + EPG_EVENT_HEADER_datasize += 4 # add 4 bytes for a single REF DESC (CRC32) + + if shortDescription[0] not in list(self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER.keys()): + pack_1 = s_BB.pack(0x4d, int(len(shortDescription[1]))) + shortDescription[1] # DESCRIPTION DATA # DESCRIPTION HEADER (2 int) will be computed at the end just before EPG.DAT write - # because it need the total number of the same description called by many channel section - # save_event(short_d[0],[pack_1,1]) - self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[short_d[0]] = [pack_1, 1] + # because it needs the total number of the same descriptions called by any channel section + self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[shortDescription[0]] = [pack_1, 1] # save CRC32 and short description data packed self.EPG_HEADER2_description_count += 1 else: - # increment_event(short_d[0]) - self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[short_d[0]][1] += 1 - # long description type 0x4e - long_d = event[3] - EPG_EVENT_HEADER_datasize += 4 * len(long_d) # add 4 bytes for a single REF DESC (CRC32) - for desc in long_d: - # if long_d[i][0] not in epg_event_description_dict: - # if not exist_event(long_d[i][0]) : - if desc[0] not in self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER: + # increment_event(shortDescription[0]) + self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[shortDescription[0]][1] += 1 + + # long description type 0x4e self.longDescription(description) = event[3] + longDescription = event[3] # (crc32, long description(s) packed) + EPG_EVENT_HEADER_datasize += 4 * len(longDescription) # add 4 bytes for each CRC32 + for desc in longDescription: # desc = crc + packed long desc + if desc[0] not in list(self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER.keys()): # DESCRIPTION DATA - pack_1 = s_BB.pack(0x4e, len(desc[1])) + desc[1] + pack_2 = s_BB.pack(0x4e, int(len(desc[1]))) + desc[1] # short description self.EPG_HEADER2_description_count += 1 # DESCRIPTION HEADER (2 int) will be computed at the end just before EPG.DAT write # because it need the total number of the same description called by different channel section - # save_event(long_d[i][0],[pack_1,1]) - self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[desc[0]] = [pack_1, 1] + # save_event(longDescription[i][0],[pack_1,1]) + self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[desc[0]] = [pack_2, 1] # save crc32 and description packed else: - # increment_event(long_d[i][0]) + # increment_event(longDescription[i][0]) self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[desc[0]][1] += 1 - # **** (2) : have REF DESC and now can create EVENT HEADER / DATA **** - # EVENT HEADER (2 bytes: 0x01 , 10 bytes + number of CRC32 * 4) - pack_1 = s_BB.pack(0x01, 0x0a + EPG_EVENT_HEADER_datasize) - self.EPG_TMP_FD.write(pack_1) - # extract date and time from + + # **** (2) : have all crc32's and now can create EVENT HEADER / DATA **** + # EVENT HEADER (3 bytes: 0x01 , 0x00, 10 bytes + number of CRC32 * 4) + pack_3 = s_BBB.pack(0x01, 0x00, 0x0a + EPG_EVENT_HEADER_datasize) + self.EPG_TMP_FD.write(pack_3) + + # extract date and time from event numbers are seconds # unix format (second since 1970) and already GMT corrected - event_time_HMS = datetime.utcfromtimestamp(event[0]) - event_length_HMS = datetime.utcfromtimestamp(event[1]) - # epg.dat date is = (proleptic date - epg_zero_day) - dvb_date = event_time_HMS.toordinal() - self.EPG_PROLEPTIC_ZERO_DAY + event_time_HMS = datetime.utcfromtimestamp(event[0]) # actually YYYY-MM-DD HH:MM:SS + dvb_date = event_time_HMS.toordinal() - self.EPG_PROLEPTIC_ZERO_DAY # epg.dat date is = (proleptic date - epg_zero_day) + event_duration_HMS = datetime.utcfromtimestamp(event[1]) # actually 1970-01-01 HH:MM:SS # EVENT DATA - # simply create an incremental ID, starting from '1' + # simply create an incremental ID, starting from '1' # event_id appears to be per channel, so this should be okay. EPG_EVENT_DATA_id += 1 - pack_1 = self.s_b_HH.pack(EPG_EVENT_DATA_id, dvb_date) # ID and DATE , always in BIG_ENDIAN - pack_2 = s_BBB.pack(*TL_hexconv(event_time_HMS)) # START TIME - pack_3 = s_BBB.pack(*TL_hexconv(event_length_HMS)) # LENGTH - pack_4 = s_I.pack(short_d[0]) # REF DESC short (title) - for d in long_d: - pack_4 += s_I.pack(d[0]) # REF DESC long - self.EPG_TMP_FD.write(pack_1 + pack_2 + pack_3 + pack_4) + pack_4 = self.s_b_HH.pack(EPG_EVENT_DATA_id, dvb_date) # ID and DATE , always in BIG_ENDIAN + pack_5 = s_BBB.pack(*TL_hexconv(event_time_HMS)) # Start time + pack_6 = s_BBB.pack(*TL_hexconv(event_duration_HMS)) # Duration + pack_7 = s_I.pack(shortDescription[0]) # REF DESC crc short (title) + for description in longDescription: + pack_7 += s_I.pack(description[0]) # REF DESC long + self.EPG_TMP_FD.write(pack_4 + pack_5 + pack_6 + pack_7) # reset again event container self.EPG_TOTAL_EVENTS += len(self.events) self.events = [] @@ -280,7 +282,10 @@ def final_process(self): self.EPG_TMP_FD.close() epgdat_fd = open(self.EPGDAT_FILENAME, "wb") # HEADER 1 - pack_1 = struct.pack(self.LB_ENDIAN + "I13sI", 0x98765432, 'ENIGMA_EPG_V8', self.EPG_HEADER1_channel_count) + if EpgDatV8: + pack_1 = struct.pack(self.LB_ENDIAN + "I13sI", 0x98765432, b'ENIGMA_EPG_V8', int(self.EPG_HEADER1_channel_count)) + else: + pack_1 = struct.pack(self.LB_ENDIAN + "I13sI", 0x98765432, b'ENIGMA_EPG_V7', int(self.EPG_HEADER1_channel_count)) epgdat_fd.write(pack_1) # write first EPG.DAT section EPG_TMP_FD = open(self.EPGDAT_TMP_FILENAME, "rb") @@ -292,14 +297,14 @@ def final_process(self): EPG_TMP_FD.close() # HEADER 2 s_ii = self.s_II - pack_1 = self.s_I.pack(self.EPG_HEADER2_description_count) - epgdat_fd.write(pack_1) + pack_2 = self.s_I.pack(self.EPG_HEADER2_description_count) + epgdat_fd.write(pack_2) # event MUST BE WRITTEN IN ASCENDING ORDERED using HASH CODE as index for temp in sorted(list(self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER.keys())): - pack_2 = self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[temp] - # pack_1=struct.pack(LB_ENDIAN+"II",int(temp,16),pack_2[1]) - pack_1 = s_ii.pack(temp, pack_2[1]) - epgdat_fd.write(pack_1 + pack_2[0]) + temp_crc_data = self.EPGDAT_HASH_EVENT_MEMORY_CONTAINER[temp] + # pack_4=struct.pack(LB_ENDIAN+"II",int(temp,16),temp_crc_data[1]) + pack_4 = s_ii.pack(temp, temp_crc_data[1]) # crc and packed data + epgdat_fd.write(pack_4 + temp_crc_data[0]) # packed (crc, packed data) & crc epgdat_fd.close() # *** cleanup ** if os.path.exists(self.EPGDAT_TMP_FILENAME): From 04cd27ffac2a29a0aa3abf3c5dc651d5943810b9 Mon Sep 17 00:00:00 2001 From: jbleyel Date: Tue, 25 Feb 2025 22:47:56 +0100 Subject: [PATCH 05/14] update --- src/EPGImport/EPGConfig.py | 352 +++++++++-------------- src/EPGImport/EPGImport.py | 275 +++++++++--------- src/EPGImport/ExpandableSelectionList.py | 23 +- src/EPGImport/__init__.py | 1 - src/EPGImport/boot.py | 78 ----- src/EPGImport/epgdat_importer.py | 45 +-- src/EPGImport/filterCustomChannel.py | 110 ------- src/EPGImport/gen_xmltv.py | 2 +- src/EPGImport/plugin.py | 309 ++++++-------------- src/EPGImport/xmltvconverter.py | 54 ++-- 10 files changed, 406 insertions(+), 843 deletions(-) delete mode 100644 src/EPGImport/boot.py delete mode 100644 src/EPGImport/filterCustomChannel.py diff --git a/src/EPGImport/EPGConfig.py b/src/EPGImport/EPGConfig.py index f411ef4..f51a37d 100755 --- a/src/EPGImport/EPGConfig.py +++ b/src/EPGImport/EPGConfig.py @@ -1,27 +1,23 @@ -# -*- coding: UTF-8 -*- from gzip import GzipFile from os import fstat, listdir, remove from os.path import exists, getmtime, join, split +from pickle import dump, load, HIGHEST_PROTOCOL from random import choice from time import time -from html import unescape -from re import compile, sub, error as reError -from pickle import dump, load, HIGHEST_PROTOCOL from xml.etree.cElementTree import iterparse +from zipfile import ZipFile from . import log -from . filterCustomChannel import filterCustomChannel - # User selection stored here, so it goes into a user settings backup -SETTINGS_FILE = '/etc/enigma2/epgimport.conf' +SETTINGS_FILE = "/etc/enigma2/epgimport.conf" channelCache = {} def isLocalFile(filename): - # we check on a '://' as a silly way to check local file - return '://' not in filename + # we check on a "://" as a silly way to check local file + return "://" not in filename def getChannels(path, name, offset): @@ -32,7 +28,7 @@ def getChannels(path, name, offset): if name: channelfile = join(dirname, name) if isLocalFile(name) else name else: - channelfile = join(dirname, filename.split('.', 1)[0] + '.channels.xml') + channelfile = join(dirname, filename.split(".", 1)[0] + ".channels.xml") try: return channelCache[channelfile] except KeyError: @@ -42,122 +38,14 @@ def getChannels(path, name, offset): return c -def enumerateXML(fp, tag=None): - """Enumerates ElementTree nodes from file object 'fp'""" - doc = iterparse(fp, events=('start', 'end')) - _, root = next(doc) # Ottiene la radice - depth = 0 - - for event, element in doc: - if element.tag == tag: - if event == 'start': - depth += 1 - elif event == 'end': - if depth == 1: - yield element - element.clear() - depth -= 1 - - if event == 'end' and element.tag != tag: - element.clear() - - root.clear() - - -def xml_unescape(text): - """ - Unescapes XML/HTML entities in the given text. - - :param text: The text that needs to be unescaped. - :type text: str - :rtype: str - """ - - if not isinstance(text, str): - return '' - - text = text.strip() - - # Custom entity replacements - entity_map = { - "«": "«", - "«": "«", - "»": "»", - "»": "»", - "'": "'", - } - - # First, apply standard unescape - text = unescape(text) - - # Replace specific entities - for entity, char in entity_map.items(): - text = text.replace(entity, char) - - # Normalize whitespace (replace ` `, ` `, and multiple spaces with a single space) - text = sub(r' | |\s+', ' ', text) - - return text - - -def openStream(filename): - fd = open(filename, 'rb') - if not fstat(fd.fileno()).st_size: - raise Exception("File is empty") - if filename.endswith('.gz'): - fd = GzipFile(fileobj=fd, mode='rb') - elif filename.endswith('.xz') or filename.endswith('.lzma'): - import lzma - fd = lzma.open(filename, 'rb') - elif filename.endswith('.zip'): - from zipfile import ZipFile - from io import BytesIO - zip_obj = ZipFile(filename, 'r') - fd = BytesIO(zip_obj.open(zip_obj.namelist()[0]).read()) - return fd - - -def set_channel_id_filter(): - full_filter = "" - try: - with open('/etc/epgimport/channel_id_filter.conf', 'r') as channel_id_file: - for channel_id_line in channel_id_file: - # Skipping comments in channel_id_filter.conf - if not channel_id_line.startswith("#"): - clean_channel_id_line = channel_id_line.strip() - # Blank line in channel_id_filter.conf will produce a full match so we need to skip them. - if clean_channel_id_line: - try: - # We compile indivually every line just to report error - full_filter = compile(clean_channel_id_line) - except reError: - print("[EPGImport] ERROR: " + clean_channel_id_line + " is not a valid regex. It will be ignored.", file=log) - else: - full_filter = full_filter + clean_channel_id_line + "|" - except IOError: - print("[EPGImport] INFO: no channel_id_filter.conf file found.", file=log) - # Return a dummy filter (empty line filter) all accepted except empty channel id - compiled_filter = compile("^$") - return (compiled_filter) - # Last char is | so remove it - full_filter = full_filter[:-1] - # all channel id are matched in lower case so creating the filter in lowercase too - full_filter = full_filter.lower() - # channel_id_filter.conf file exist but is empty, it has only comments, or only invalid regex - if len(full_filter) == 0: - # full_filter is empty returning dummy filter - compiled_filter = compile("^$") - else: - try: - compiled_filter = compile(full_filter) - except reError: - print("[EPGImport] ERROR: final regex " + full_filter + " doesn't compile properly.", file=log) - # Return a dummy filter (empty line filter) all accepted except empty channel id - compiled_filter = compile("^$") - else: - print("[EPGImport] INFO : final regex " + full_filter + " compiled successfully.", file=log) - - return (compiled_filter) +""" +elem.clear() +When you parse an XML file with iterparse(), +the elements are loaded into memory one at a time. However, +if you don't explicitly clear them, +the parser will keep everything in memory until the end of parsing, +which can consume a lot of RAM, especially with large files. +""" class EPGChannel: @@ -171,69 +59,73 @@ def __init__(self, filename, urls=None, offset=0): self.items = None self.offset = offset - def parse(self, filterCallback, downloadedFile, FilterChannelEnabled): - print("[EPGImport] Parsing channels from '%s'" % self.name, file=log) - channel_id_filter = set_channel_id_filter() + def openStream(self, filename): + fd = open(filename, "rb") + if not fstat(fd.fileno()).st_size: + raise Exception("File is empty") + if filename.endswith(".gz"): + fd = GzipFile(fileobj=fd, mode="rb") + elif filename.endswith(".xz") or filename.endswith(".lzma"): + try: + import lzma + except ImportError: + from backports import lzma + fd = lzma.open(filename, "rb") + elif filename.endswith(".zip"): + from io import BytesIO + zip_obj = ZipFile(filename, "r") + fd = BytesIO(zip_obj.open(zip_obj.namelist()[0]).read()) + return fd + + def parse(self, filterCallback, downloadedFile): + print(f"[EPGImport] Parsing channels from '{self.name}'", file=log) + if self.items is None: self.items = {} + try: - context = iterparse(openStream(downloadedFile)) + stream = self.openStream(downloadedFile) + if stream is None: + print(f"[EPGImport] Error: Unable to open stream for {downloadedFile}", file=log) + return + + context = iterparse(stream) for event, elem in context: - if elem.tag == 'channel': - id = elem.get('id') - id = id.lower() - filter_result = channel_id_filter.match(id) - if filter_result and FilterChannelEnabled: - # Just to avoid false positive in logging since the same parse function is used in two different cases. - if filter_result.group(): - print("[EPGImport] INFO : skipping", filter_result.group(), "due to channel_id_filter.conf", file=log) - ref = str(elem.text) - if id and ref: - if filterCallback(ref): - if id in self.items: - try: - if ref in self.items[id]: - # remove only remove the first occurrence turning list into dict will make the reference unique so remove will work as expected. - self.items[id] = list(dict.fromkeys(self.items[id])) - self.items[id].remove(ref) - except Exception as e: - print("[EPGImport] failed to remove from list ", self.items[id], " ref ", ref, "Error:", e, file=log) - else: - # print("[EPGImport] INFO : processing", id, file=log) - ref = str(elem.text) - if id and ref: + if elem.tag == "channel": + id = elem.get("id") + if id: + id = id.lower() + ref = elem.text + if ref: + ref = str(ref) if filterCallback(ref): if id in self.items: self.items[id].append(ref) - # turning list into dict will make the reference unique to avoid loading twice the same EPG data. - self.items[id] = list(dict.fromkeys(self.items[id])) else: self.items[id] = [ref] elem.clear() except Exception as e: - print("[EPGImport] failed to parse", downloadedFile, "Error:", e, file=log) - pass + print(f"[EPGImport] Failed to parse {downloadedFile} Error: {e}", file=log) + import traceback + traceback.print_exc() def update(self, filterCallback, downloadedFile=None): - customFile = '/etc/epgimport/custom.channels.xml' + customFile = "/etc/epgimport/custom.channels.xml" # Always read custom file since we don't know when it was last updated # and we don't have multiple download from server problem since it is always a local file. if not exists(customFile): - customFile = '/etc/epgimport/rytec.channels.xml' + customFile = "/etc/epgimport/rytec.channels.xml" if exists(customFile): - print("[EPGImport] Parsing channels from '%s'" % customFile, file=log) - self.parse(filterCallback, customFile, filterCustomChannel) + print(f"[EPGImport] Parsing channels from '{customFile}'", file=log) + self.parse(filterCallback, customFile) if downloadedFile is not None: self.mtime = time() - return self.parse(filterCallback, downloadedFile, True) + return self.parse(filterCallback, downloadedFile) elif (len(self.urls) == 1) and isLocalFile(self.urls[0]): - try: - mtime = getmtime(self.urls[0]) - except: - mtime = None - if (not self.mtime) or (mtime is not None and self.mtime < mtime): - self.parse(filterCallback, self.urls[0], True) + mtime = getmtime(self.urls[0]) + if (not self.mtime) or (self.mtime < mtime): + self.parse(filterCallback, self.urls[0]) self.mtime = mtime def downloadables(self): @@ -244,108 +136,126 @@ def downloadables(self): now = time() if (not self.mtime) or (self.mtime + 86400 < now): return self.urls - return [] + return None def __repr__(self): - return "EPGChannel(urls=%s, channels=%s, mtime=%s)" % (self.urls, self.items and len(self.items), self.mtime) + return f"EPGChannel(urls={self.urls}, channels={self.items and len(self.items)}, mtime={self.mtime})" class EPGSource: def __init__(self, path, elem, category=None, offset=0): - self.parser = elem.get('type', 'gen_xmltv') - self.nocheck = int(elem.get('nocheck', 0)) - self.urls = [e.text.strip() for e in elem.findall('url')] + self.parser = elem.get("type", "gen_xmltv") + self.nocheck = int(elem.get("nocheck", 0)) + """ + self.parser = elem.get("type") + nocheck = elem.get("nocheck") + if nocheck is None: + self.nocheck = 0 + elif nocheck == "1": + self.nocheck = 1 + else: + self.nocheck = 0 + """ + self.urls = [e.text.strip() for e in elem.findall("url")] self.url = choice(self.urls) - self.description = elem.findtext('description') + self.description = elem.findtext("description") self.category = category self.offset = offset if not self.description: self.description = self.url - self.format = elem.get('format', 'xml') - self.channels = getChannels(path, elem.get('channels'), offset) + self.format = elem.get("format", "xml") + self.channels = getChannels(path, elem.get("channels"), offset) def enumSourcesFile(sourcefile, filter=None, categories=False): global channelCache category = None try: - for event, elem in iterparse(open(sourcefile, 'rb'), events=("start", "end")): - if event == 'end': - if elem.tag == 'source': - # calculate custom time offset in minutes - offset = int(elem.get('offset', '+0000')) * 3600 // 100 - s = EPGSource(sourcefile, elem, category, offset) - elem.clear() - if (filter is None) or (s.description in filter): - yield s - elif elem.tag == 'channel': - name = xml_unescape(elem.get('name')) - - urls = [xml_unescape(e.text) for e in elem.findall('url')] - try: - channelCache[name].urls = urls - except: - channelCache[name] = EPGChannel(name, urls) - elif elem.tag == 'sourcecat': - category = None - elif event == 'start': - # Need the category name sooner than the contents, hence "start" - if elem.tag == 'sourcecat': - category = elem.get('sourcecatname') + with open(sourcefile, "rb") as f: + for event, elem in iterparse(f, events=("start", "end")): + if event == "end": + if elem.tag == "source": + # Calculate custom time offset in minutes + try: + offset = int(elem.get("offset", "+0000")) * 3600 // 100 + except ValueError: + offset = 0 # Default offset if parsing fails + + s = EPGSource(sourcefile, elem, category, offset) + elem.clear() + if filter is None or s.description in filter: + yield s + + elif elem.tag == "channel": + name = elem.get("name") + if name: + urls = [e.text.strip() for e in elem.findall("url")] + if name in channelCache: + channelCache[name].urls = urls + else: + channelCache[name] = EPGChannel(name, urls) + elem.clear() + + elif elem.tag == "sourcecat": + category = None + elem.clear() + + elif event == "start" and elem.tag == "sourcecat": + category = elem.get("sourcecatname") if categories: yield category except Exception as e: - print("[EPGConfig] EPGConfig enumSourcesFile:", e) + print(f"[EPGImport] Error reading source file: {sourcefile} Error: {e}") def enumSources(path, filter=None, categories=False): try: - for filename in listdir(path): - if filename.endswith('.sources.xml'): - sourcefile = join(path, filename) + for sourcefile in listdir(path): + if sourcefile.endswith(".sources.xml"): + sourcefile = join(path, sourcefile) try: for s in enumSourcesFile(sourcefile, filter, categories): yield s except Exception as e: - print("[EPGImport] failed to open", sourcefile, "Error:", e, file=log) + print(f"[EPGImport] failed to open {sourcefile} Error: {e}", file=log) except Exception as e: - print("[EPGImport] failed to list", path, "Error:", e, file=log) + print(f"[EPGImport] failed to list {path} Error: {e}", file=log) def loadUserSettings(filename=SETTINGS_FILE): try: - return load(open(filename, 'rb')) + return load(open(filename, "rb")) except Exception as e: - print("[EPGImport]loadUserSettings No settings", e, file=log) + print(f"[EPGImport] No settings {e}", file=log) return {"sources": []} def storeUserSettings(filename=SETTINGS_FILE, sources=None): - container = {"[EPGImport]loadUserSettings sources": sources} - dump(container, open(filename, 'wb'), HIGHEST_PROTOCOL) + container = {"sources": sources} + dump(container, open(filename, "wb"), HIGHEST_PROTOCOL) -if __name__ == '__main__': +if __name__ == "__main__": import sys x = [] - lx = [] - path = '.' + ln = [] + path = "." if len(sys.argv) > 1: path = sys.argv[1] for p in enumSources(path): t = (p.description, p.urls, p.parser, p.format, p.channels, p.nocheck) - lx.append(t) + ln.append(t) print(t) x.append(p.description) - storeUserSettings('settings.pkl', [1, "twee"]) - assert loadUserSettings('settings.pkl') == {"sources": [1, "twee"]} - remove('settings.pkl') + storeUserSettings("settings.pkl", [1, "twee"]) + assert loadUserSettings("settings.pkl") == {"sources": [1, "twee"]} + remove("settings.pkl") for p in enumSources(path, x): t = (p.description, p.urls, p.parser, p.format, p.channels, p.nocheck) - assert t in lx - lx.remove(t) - assert not lx + assert t in ln + ln.remove(t) + assert not ln for name, c in channelCache.items(): - print("Update:", name) + print(f"Update:{name}") c.update() - print("# of channels:", len(c.items)) + print(f"# of channels: {len(c.items)}") diff --git a/src/EPGImport/EPGImport.py b/src/EPGImport/EPGImport.py index affbd00..dbdb3db 100755 --- a/src/EPGImport/EPGImport.py +++ b/src/EPGImport/EPGImport.py @@ -5,7 +5,6 @@ # the contract. -from datetime import datetime from os import statvfs, symlink, unlink from os.path import exists, getsize, join, splitext from requests import packages, Session @@ -20,82 +19,58 @@ from Components.config import config -from . import log - - -def maybe_encode(text, encoding="utf-8"): - """ - Ensures that the text is properly encoded in Python 2 and 3. - :param text: The input text (assumed to be a string). - :param encoding: The encoding format (default: utf-8). - :return: Encoded text (if necessary). - """ - if isinstance(text, bytes): - return text.decode(encoding) - return text - packages.urllib3.disable_warnings(packages.urllib3.exceptions.InsecureRequestWarning) # Used to check server validity -HDD_EPG_DAT = '/hdd/epg.dat' -date_format = "%Y-%m-%d" -now = datetime.now() -alloweddelta = 2 -CheckFile = "LastUpdate.txt" -ServerStatusList = {} -PARSERS = {'xmltv': 'gen_xmltv', 'genxmltv': 'gen_xmltv'} +HDD_EPG_DAT = "/hdd/epg.dat" +if config.misc.epgcache_filename.value: + HDD_EPG_DAT = config.misc.epgcache_filename.value +else: + config.misc.epgcache_filename.setValue(HDD_EPG_DAT) +PARSERS = {"xmltv": "gen_xmltv", "genxmltv": "gen_xmltv"} # sslverify = False -# Used to check server validity - def threadGetPage(url=None, file=None, urlheaders=None, success=None, fail=None, *args, **kwargs): - print('[EPGImport][threadGetPage] url, file, args, kwargs', url, " ", file, " ", args, " ", kwargs) - try: - s = Session() - s.headers = {} - response = s.get(url, verify=False, headers=urlheaders, timeout=15, allow_redirects=True) - response.raise_for_status() - # check here for content-disposition header so to extract the actual filename (if the url doesnt contain it) - content_disp = response.headers.get('Content-Disposition', '') - filename = content_disp.split('filename="')[-1].split('"')[0] - ext = splitext(file)[1] - if filename: - ext = splitext(filename)[1] - if ext and len(ext) < 6: - file += ext - if not ext: - ext = splitext(response.url)[1] - if ext and len(ext) < 6: - file += ext - - with open(file, "wb") as f: - f.write(response.content) - # print('[EPGImport][threadGetPage] file completed: ', file) - success(file, deleteFile=True) - - except HTTPError as httperror: - print('EPGImport][threadGetPage] Http error: ', httperror) - fail(httperror) # E0602 undefined name 'error' - - except RequestException as error: - print('[EPGImport][threadGetPage] error: ', error) - # if fail is not None: - fail(error) - - -# Used to check server validity -if config.misc.epgcache_filename.value: - HDD_EPG_DAT = config.misc.epgcache_filename.value -else: - config.misc.epgcache_filename.setValue(HDD_EPG_DAT) + # print("[EPGImport][threadGetPage] url, file, args, kwargs", url, " ", file, " ", args, " ", kwargs) + try: + s = Session() + s.headers = {} + response = s.get(url, verify=False, headers=urlheaders, timeout=15, allow_redirects=True) + response.raise_for_status() + # check here for content-disposition header so to extract the actual filename (if the url doesnt contain it) + content_disp = response.headers.get("Content-Disposition", "") + filename = content_disp.split('filename="')[-1].split('"')[0] + ext = splitext(file)[1] + if filename: + ext = splitext(filename)[1] + if ext and len(ext) < 6: + file += ext + if not ext: + ext = splitext(response.url)[1] + if ext and len(ext) < 6: + file += ext + + with open(file, "wb") as f: + f.write(response.content) + # print("[EPGImport][threadGetPage] file completed: ", file) + success(file, deleteFile=True) + + except HTTPError as httperror: + print("EPGImport][threadGetPage] Http error: ", httperror) + fail(httperror) # E0602 undefined name "error" + + except RequestException as error: + print("[EPGImport][threadGetPage] error: ", error) + # if fail is not None: + fail(error) def relImport(name): - fullname = __name__.split('.') + fullname = __name__.split(".") fullname[-1] = name - mod = __import__('.'.join(fullname)) + mod = __import__(".".join(fullname)) for n in fullname[1:]: mod = getattr(mod, n) @@ -141,11 +116,11 @@ def bigStorage(minFree, default, *candidates): if free > minFree and free > 50000000: return default except Exception as e: - print("[EPGImport][bigStorage] Failed to stat %s:" % default, e) + print(f"[EPGImport][bigStorage] Failed to stat {default}:", e) - with open('/proc/mounts', 'rb') as f: + with open("/proc/mounts", "rb") as f: # format: device mountpoint fstype options # - mountpoints = [x.decode().split(' ', 2)[1] for x in f.readlines()] + mountpoints = [x.decode().split(" ", 2)[1] for x in f.readlines()] for candidate in candidates: if candidate in mountpoints: @@ -155,7 +130,7 @@ def bigStorage(minFree, default, *candidates): if free > minFree: return candidate except Exception as e: - print("[EPGImport][bigStorage] Failed to stat %s:" % default, e) + print(f"[EPGImport][bigStorage] Failed to stat {default}:", e) continue raise Exception("[EPGImport][bigStorage] Insufficient storage for download") @@ -172,18 +147,18 @@ def __init__(self, epgcache): def importEvents(self, services, events): for service in services: try: - self.epgcache.importEvents(maybe_encode(service, events)) + self.epgcache.importEvent(service, events) except Exception as e: import traceback traceback.print_exc() - print("[EPGImport][OudeisImporter][importEvents] ### importEvents exception:", e) + print("[EPGImport][importEvents] ### importEvents exception:", e) def unlink_if_exists(filename): try: unlink(filename) except Exception as e: - print("[EPGImport] warning: Could not remove '%s' intermediate" % filename, repr(e)) + print(f"[EPGImport] warning: Could not remove '{filename}' intermediate", repr(e)) class EPGImport: @@ -205,14 +180,14 @@ def __init__(self, epgcache, channelFilter): def beginImport(self, longDescUntil=None): """Starts importing using Enigma reactor. Set self.sources before calling this.""" - if hasattr(self.epgcache, 'importEvents'): - print('[EPGImport][beginImport] using importEvents.') + if hasattr(self.epgcache, "importEvents"): + print("[EPGImport][beginImport] using importEvents.") self.storage = self.epgcache - elif hasattr(self.epgcache, 'importEvent'): - print('[EPGImport][beginImport] using importEvent(Oudis).') + elif hasattr(self.epgcache, "importEvent"): + print("[EPGImport][beginImport] using importEvent(Oudis).") self.storage = OudeisImporter(self.epgcache) else: - print('[EPGImport][beginImport] oudeis patch not detected, using using epgdat_importer.epgdatclass/epg.dat instead.') + print("[EPGImport][beginImport] oudeis patch not detected, using using epgdat_importer.epgdatclass/epg.dat instead.") from . import epgdat_importer self.storage = epgdat_importer.epgdatclass() @@ -232,139 +207,148 @@ def nextImport(self): self.source = self.sources.pop() - print("[EPGImport][nextImport], source=", self.source.description, file=log) + print(f"[EPGImport][nextImport], source= {self.source.description}") self.fetchUrl(self.source.url) def fetchUrl(self, filename): - if filename.startswith('http:') or filename.startswith('https:') or filename.startswith('ftp:'): - # print("[EPGImport][fetchurl]Attempting to download from: ", filename) + if filename.startswith("http:") or filename.startswith("https:") or filename.startswith("ftp:"): + # print("[EPGImport][fetchurl] download Basic ...url filename", filename) self.urlDownload(filename, self.afterDownload, self.downloadFail) else: self.afterDownload(filename, deleteFile=False) def urlDownload(self, sourcefile, afterDownload, downloadFail): - host = ''.join(choices(ascii_lowercase, k=5)) + host = "".join(choices(ascii_lowercase, k=5)) check_mount = False if exists("/media/hdd"): - with open('/proc/mounts', 'r') as f: + with open("/proc/mounts", "r") as f: for line in f: ln = line.split() - if len(ln) > 1 and ln[1] == '/media/hdd': + if len(ln) > 1 and ln[1] == "/media/hdd": check_mount = True # print("[EPGImport][urlDownload]2 check_mount ", check_mount) pathDefault = "/media/hdd" if check_mount else "/tmp" - path = bigStorage(9000000, pathDefault, '/media/usb', '/media/cf') # lets use HDD and flash as main backup media + path = bigStorage(9000000, pathDefault, "/media/usb", "/media/cf") # lets use HDD and flash as main backup media filename = join(path, host) - if isinstance(sourcefile, list): - sourcefile = sourcefile[0] - print("[EPGImport][urlDownload] Downloading: " + str(sourcefile) + " to local path: " + str(filename)) ext = splitext(sourcefile)[1] # Keep sensible extension, in particular the compression type if ext and len(ext) < 6: filename += ext - # sourcefile = sourcefile.encode('utf-8') - sourcefile = str(sourcefile) - print('sourcfile str=', sourcefile) Headers = { - 'User-Agent': 'Twisted Client', - 'Accept-Encoding': 'gzip, deflate', - 'Accept': '*/*', - 'Connection': 'keep-alive'} + "User-Agent": "Twisted Client", + "Accept-Encoding": "gzip, deflate", + "Accept": "*/*", + "Connection": "keep-alive"} - print("[EPGImport][urlDownload] Downloading: " + sourcefile + " to local path: " + filename) + print(f"[EPGImport][urlDownload] Downloading: {sourcefile} to local path: {filename}") callInThread(threadGetPage, url=sourcefile, file=filename, urlheaders=Headers, success=afterDownload, fail=downloadFail) def afterDownload(self, filename, deleteFile=False): - # print("[EPGImport][afterDownload] filename", filename) + # print("[EPGImport][afterDownload] filename", filename) if not exists(filename): + print(f"[EPGImport][afterDownload] File not found: {filename}") self.downloadFail("File not exists") return try: if not getsize(filename): raise Exception("[EPGImport][afterDownload] File is empty") except Exception as e: - print("[EPGImport][afterDownload] Exception filename 0", filename) + print(f"[EPGImport][afterDownload] Exception filename 0 {filename}") self.downloadFail(e) return - if self.source.parser == 'epg.dat': + if self.source.parser == "epg.dat": if twisted.python.runtime.platform.supportsThreads(): - print("[EPGImport][afterDownload] Using twisted thread for DAT file", file=log) + print("[EPGImport][afterDownload] Using twisted thread for DAT file") threads.deferToThread(self.readEpgDatFile, filename, deleteFile).addCallback(lambda ignore: self.nextImport()) else: self.readEpgDatFile(filename, deleteFile) return - if filename.endswith('.gz'): - self.fd = gzip.open(filename, 'rb') + if filename.endswith(".gz"): + self.fd = gzip.open(filename, "rb") try: # read a bit to make sure it's a gzip file + # file_content = self.fd.peek(1) # file_content ??? self.fd.read(10) self.fd.seek(0, 0) except gzip.BadGzipFile as e: - print("[EPGImport][afterDownload] File downloaded is not a valid gzip file", filename, file=log) + print(f"[EPGImport][afterDownload] File downloaded is not a valid gzip file {filename}") + try: + print(f"[EPGImport][afterDownload] unlink {filename}") + unlink_if_exists(filename) + except Exception as e: + print(f"[EPGImport][afterDownload] warning: Could not remove '{filename}' intermediate", str(e)) self.downloadFail(e) return - elif filename.endswith('.xz') or filename.endswith('.lzma'): + elif filename.endswith(".xz") or filename.endswith(".lzma"): try: import lzma except ImportError: from backports import lzma - self.fd = lzma.open(filename, 'rb') + self.fd = lzma.open(filename, "rb") try: # read a bit to make sure it's an xz file + # file_content = self.fd.peek(1) # file_content ??? self.fd.read(10) self.fd.seek(0, 0) except lzma.LZMAError as e: - print("[EPGImport[afterDownload]] File downloaded is not a valid xz file", filename, file=log) + print(f"[EPGImport][afterDownload] File downloaded is not a valid xz file {filename}") + try: + print(f"[EPGImport][afterDownload] unlink {filename}") + unlink_if_exists(filename) + except Exception as e: + print(f"[EPGImport][afterDownload] warning: Could not remove '{filename}' intermediate", e) self.downloadFail(e) return else: - self.fd = open(filename, 'rb') + self.fd = open(filename, "rb") - if deleteFile and self.source.parser != 'epg.dat': + if deleteFile and self.source.parser != "epg.dat": try: - print("[EPGImport][afterDownload] unlink", filename, file=log) + print(f"[EPGImport][afterDownload] unlink {filename}") unlink_if_exists(filename) except Exception as e: - print("[EPGImport][afterDownload] warning: Could not remove '%s' intermediate" % filename, e, file=log) + print(f"[EPGImport][afterDownload] warning: Could not remove '{filename}' intermediate", e) self.channelFiles = self.source.channels.downloadables() + # print("[EPGImport][afterDownload] self.source, self.channelFiles", self.source, " ", self.channelFiles) if not self.channelFiles: self.afterChannelDownload(None, None) else: filename = choice(self.channelFiles) self.channelFiles.remove(filename) + # print("[EPGImport][afterDownload] download Channels ...filename", filename) self.urlDownload(filename, self.afterChannelDownload, self.channelDownloadFail) return def downloadFail(self, failure): - print("[EPGImport][downloadFail] download failed:", failure, file=log) - if self.source.url in self.source.urls: - self.source.urls.remove(self.source.url) + print(f"[EPGImport][downloadFail] download failed: {failure}") + # if self.source.url in self.source.urls: # use this ;) + self.source.urls.remove(self.source.url) if self.source.urls: - print("[EPGImport] Attempting alternative URL", file=log) + print("[EPGImport][downloadFail] Attempting alternative URL for Basic") self.source.url = choice(self.source.urls) - print("[EPGImport][downloadFail] try alternative download url", self.source.url) + print(f"[EPGImport][downloadFail] try alternative download url {self.source.url}") self.fetchUrl(self.source.url) else: self.nextImport() def afterChannelDownload(self, filename, deleteFile=True): - print("[EPGImport] afterChannelDownload filename", filename, file=log) + # print("[EPGImport][afterChannelDownload] filename", filename) if filename: try: if not getsize(filename): raise Exception("File is empty") except Exception as e: - print("[EPGImport][afterChannelDownload] Exception filename", filename) + print(f"[EPGImport][afterChannelDownload] Exception filename {filename}") self.channelDownloadFail(e) return if twisted.python.runtime.platform.supportsThreads(): - print("[EPGImport] Using twisted thread", file=log) + print(f"[EPGImport][afterChannelDownload] Using twisted thread - filename {filename}") threads.deferToThread(self.doThreadRead, filename).addCallback(lambda ignore: self.nextImport()) deleteFile = False # Thread will delete it else: @@ -374,16 +358,17 @@ def afterChannelDownload(self, filename, deleteFile=True): try: unlink_if_exists(filename) except Exception as e: - print("[EPGImport][afterChannelDownload] warning: Could not remove '%s' intermediate" % filename, e, file=log) + print(f"[EPGImport][afterChannelDownload] warning: Could not remove '{filename}' intermediate", e, file=log) def channelDownloadFail(self, failure): - print("[EPGImport][channelDownloadFail] download channel failed:", failure, file=log) + print(f"[EPGImport][channelDownloadFail] download channel failed: {failure}") if self.channelFiles: filename = choice(self.channelFiles) self.channelFiles.remove(filename) + print(f"[EPGImport][channelDownloadFail] retry alternative download channel - new url filename {filename}") self.urlDownload(filename, self.afterChannelDownload, self.channelDownloadFail) else: - print("[EPGImport][channelDownloadFail] no more alternatives for channels", file=log) + print("[EPGImport][channelDownloadFail] no more alternatives for channels") self.nextImport() def createIterator(self, filename): @@ -392,18 +377,18 @@ def createIterator(self, filename): return getParser(self.source.parser).iterator(self.fd, self.source.channels.items, self.source.offset) def readEpgDatFile(self, filename, deleteFile=False): - if not hasattr(self.epgcache, 'load'): - print("[EPGImport][readEpgDatFile] Cannot load EPG.DAT files on unpatched enigma. Need CrossEPG patch.", file=log) + if not hasattr(self.epgcache, "load"): + print("[EPGImport] Cannot load EPG.DAT files on unpatched enigma. Need CrossEPG patch.") return unlink_if_exists(HDD_EPG_DAT) try: - if filename.endswith('.gz'): - print("[EPGImport][readEpgDatFile] Uncompressing", filename, file=log) + if filename.endswith(".gz"): + print(f"[EPGImport] Uncompressing {filename}") import shutil - fd = gzip.open(filename, 'rb') - epgdat = open(HDD_EPG_DAT, 'wb') + fd = gzip.open(filename, "rb") + epgdat = open(HDD_EPG_DAT, "wb") shutil.copyfileobj(fd, epgdat) del fd epgdat.close() @@ -412,13 +397,13 @@ def readEpgDatFile(self, filename, deleteFile=False): elif filename != HDD_EPG_DAT: symlink(filename, HDD_EPG_DAT) - print("[EPGImport][readEpgDatFile] Importing", HDD_EPG_DAT, file=log) + print(f"[EPGImport] Importing {HDD_EPG_DAT}") self.epgcache.load() if deleteFile: unlink_if_exists(filename) except Exception as e: - print("[EPGImport][readEpgDatFile] Failed to import %s:" % filename, e, file=log) + print(f"[EPGImport] Failed to import {filename}:", e) def fileno(self): if self.fd is not None: @@ -434,17 +419,17 @@ def doThreadRead(self, filename): r, d = data if d[0] > self.longDescUntil: # Remove long description (save RAM memory) - d = d[:4] + ('',) + d[5:] + d = d[:4] + ("",) + d[5:] try: self.storage.importEvents(r, (d,)) except Exception as e: - print("[EPGImport][doThreadRead] ### importEvents exception:", e, file=log) - print("[EPGImport][doThreadRead] ### thread is ready ### Events:", self.eventCount, file=log) + print(f"[EPGImport][doThreadRead] ### importEvents exception: {e}") + print("[EPGImport][doThreadRead] ### thread is ready ### Events:", self.eventCount) if filename: try: - unlink(filename) + unlink_if_exists(filename) except Exception as e: - print("[EPGImport][doThreadRead] warning: Could not remove '%s' intermediate" % filename, e, file=log) + print(f"[EPGImport] warning: Could not remove '{filename}' intermediate %s" % e) return def doRead(self): @@ -458,10 +443,10 @@ def doRead(self): r, d = data if d[0] > self.longDescUntil: # Remove long description (save RAM memory) - d = d[:4] + ('',) + d[5:] + d = d[:4] + ("",) + d[5:] self.storage.importEvents(r, (d,)) except Exception as e: - print("[EPGImport][doRead] importEvents exception:", e, file=log) + print(f"[EPGImport][doRead] importEvents exception: {e}") except StopIteration: self.nextImport() return @@ -469,7 +454,7 @@ def doRead(self): def connectionLost(self, failure): """called from reactor on lost connection""" # This happens because enigma calls us after removeReader - print("[EPGImport][connectionLost]failure", failure, file=log) + print(f"[EPGImport] connectionLost {failure}") def closeReader(self): if self.fd is not None: @@ -483,7 +468,7 @@ def closeImport(self): self.closeReader() self.iterator = None self.source = None - if hasattr(self.storage, 'epgfile'): + if hasattr(self.storage, "epgfile"): needLoad = self.storage.epgfile else: needLoad = None @@ -491,30 +476,30 @@ def closeImport(self): self.storage = None if self.eventCount is not None: - print("[EPGImport] imported %d events" % self.eventCount, file=log) + print(f"[EPGImport] imported {self.eventCount} events") reboot = False if self.eventCount: if needLoad: - print("[EPGImport] no Oudeis patch, load(%s) required" % needLoad, file=log) + print(f"[EPGImport] no Oudeis patch, load({needLoad}) required") reboot = True try: - if hasattr(self.epgcache, 'load'): - print("[EPGImport] attempt load() patch", file=log) + if hasattr(self.epgcache, "load"): + print("[EPGImport] attempt load() patch") if needLoad != HDD_EPG_DAT: symlink(needLoad, HDD_EPG_DAT) self.epgcache.load() reboot = False unlink_if_exists(needLoad) except Exception as e: - print("[EPGImport] load() failed:", e, file=log) - elif hasattr(self.epgcache, 'save'): + print(f"[EPGImport] load() failed: {e}") + elif hasattr(self.epgcache, "save"): self.epgcache.save() - elif hasattr(self.epgcache, 'timeUpdated'): + elif hasattr(self.epgcache, "timeUpdated"): self.epgcache.timeUpdated() if self.onDone: self.onDone(reboot=reboot, epgfile=needLoad) self.eventCount = None - log.write("[EPGImport] #### Finished ####\n") + print("[EPGImport] #### Finished ####") def isImportRunning(self): return self.source is not None diff --git a/src/EPGImport/ExpandableSelectionList.py b/src/EPGImport/ExpandableSelectionList.py index e28b2fe..25d68cd 100644 --- a/src/EPGImport/ExpandableSelectionList.py +++ b/src/EPGImport/ExpandableSelectionList.py @@ -1,9 +1,9 @@ -from Components.MenuList import MenuList from enigma import eListboxPythonMultiContent, gFont, RT_HALIGN_LEFT +from Components.MenuList import MenuList +from skin import fonts, parameters from Tools.Directories import resolveFilename, SCOPE_CURRENT_SKIN from Tools.LoadPixmap import LoadPixmap -import skin expandableIcon = LoadPixmap(resolveFilename(SCOPE_CURRENT_SKIN, "icons/expandable.png")) expandedIcon = LoadPixmap(resolveFilename(SCOPE_CURRENT_SKIN, "icons/expanded.png")) @@ -11,11 +11,11 @@ def loadSettings(): global cat_desc_loc, entry_desc_loc, cat_icon_loc, entry_icon_loc - x, y, w, h = skin.parameters.get("EPGImportSelectionListDescr", skin.parameters.get("SelectionListDescr", (25, 3, 650, 30))) + x, y, w, h = parameters.get("EPGImportSelectionListDescr", parameters.get("SelectionListDescr", (25, 3, 650, 30))) ind = x # Indent the entries by the same amount as the icon. cat_desc_loc = (x, y, w, h) entry_desc_loc = (x + ind, y, w - ind, h) - x, y, w, h = skin.parameters.get("EPGImportSelectionListLock", skin.parameters.get("SelectionListLock", (0, 2, 25, 24))) + x, y, w, h = parameters.get("EPGImportSelectionListLock", parameters.get("SelectionListLock", (0, 2, 25, 24))) cat_icon_loc = (x, 0, w, y + y + h) # The category icon is larger entry_icon_loc = (x + ind, y, w, h) @@ -23,7 +23,6 @@ def loadSettings(): def category(description, isExpanded=False): global cat_desc_loc, cat_icon_loc icon = expandedIcon if isExpanded else expandableIcon - return [ (description, isExpanded, []), (eListboxPythonMultiContent.TYPE_TEXT,) + cat_desc_loc + (0, RT_HALIGN_LEFT, description), @@ -63,14 +62,14 @@ def isExpanded(cat): def isCategory(item): # Return whether list enty is a Category - return hasattr(item[0][2], 'append') + return hasattr(item[0][2], "append") class ExpandableSelectionList(MenuList): def __init__(self, tree=None, enableWrapAround=False): - 'tree is expected to be a list of categories' + "tree is expected to be a list of categories" MenuList.__init__(self, [], enableWrapAround, content=eListboxPythonMultiContent) - font = skin.fonts.get("SelectionList", ("Regular", 20, 30)) + font = fonts.get("SelectionList", ("Regular", 20, 30)) self.l.setFont(0, gFont(font[0], font[1])) self.l.setItemHeight(font[2]) self.tree = tree or [] @@ -78,13 +77,13 @@ def __init__(self, tree=None, enableWrapAround=False): def updateFlatList(self): # Update the view of the items by flattening the tree - ls = [] + ln = [] for cat in self.tree: - ls.append(cat) + ln.append(cat) if isExpanded(cat): for item in cat[0][2]: - ls.append(entry(*item)) - self.setList(ls) + ln.append(entry(*item)) + self.setList(ln) def toggleSelection(self): idx = self.getSelectedIndex() diff --git a/src/EPGImport/__init__.py b/src/EPGImport/__init__.py index 93e3774..ea769c7 100755 --- a/src/EPGImport/__init__.py +++ b/src/EPGImport/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from Components.Language import language from Tools.Directories import resolveFilename, SCOPE_PLUGINS from gettext import bindtextdomain, dgettext, gettext diff --git a/src/EPGImport/boot.py b/src/EPGImport/boot.py deleted file mode 100644 index 2459ea9..0000000 --- a/src/EPGImport/boot.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/python -from os import listdir, unlink -from os.path import exists, getctime, join -from time import time -from shutil import copy2 - -MEDIA = ("/media/hdd/", "/media/usb/", "/media/mmc/", "/media/cf/", "/tmp") - - -def findEpg(): - candidates = [] - for path in MEDIA: - try: - if exists(path): - for fn in listdir(path): - if "epg.dat" in fn: - ffn = join(path, fn) - candidates.append((getctime(ffn), ffn)) - except: - pass # ignore errors. - if not candidates: - return None - candidates.sort() # order by ctime... - # best candidate is most recent filename. - return candidates[-1][1] - - -def checkCrashLog(): - for path in MEDIA[:-1]: - try: - dirList = listdir(path) - for fname in dirList: - if fname[0:13] == "enigma2_crash": - try: - crashtime = 0 - crashtime = int(fname[14:24]) - howold = time() - crashtime - except Exception: - print("no time found in filename") - if howold < 120: - print("recent crashfile found analysing") - crashfile = open(path + fname, "r") - crashtext = crashfile.read() - crashfile.close() - if (crashtext.find("FATAL: LINE ") != -1): - print("string found, deleting epg.dat") - return True - except Exception: - pass - return False - - -def findNewEpg(): - for path in MEDIA: - fn = join(path, "epg_new.dat") - if exists(fn): - return fn - - -epg = findEpg() -newepg = findNewEpg() - -print(f"Epg.dat found at : {epg}") -print(f"newepg found at : {newepg}") - -# Delete epg.dat if last crash was because of error in epg.dat -if checkCrashLog(): - try: - unlink(epg) - except: - print("delete error") - -# if excists cp epg_new.dat epg.dat -if newepg: - if epg: - print("replacing epg.dat with newmade version") - unlink(epg) - copy2(newepg, epg) diff --git a/src/EPGImport/epgdat_importer.py b/src/EPGImport/epgdat_importer.py index 1a27cb4..fbbbd0d 100755 --- a/src/EPGImport/epgdat_importer.py +++ b/src/EPGImport/epgdat_importer.py @@ -1,14 +1,16 @@ +from os import popen +from os.path import join +from sys import platform from . import epgdat -import os -import sys + # Hack to make this test run on Windows (where the reactor cannot handle files) -if sys.platform.startswith('win'): - tmppath = '.' - settingspath = '.' +if platform.startswith("win"): + tmppath = "." + settingspath = "." else: - tmppath = '/tmp' - settingspath = '/etc/enigma2' + tmppath = "/tmp" + settingspath = "/etc/enigma2" class epgdatclass: @@ -16,15 +18,16 @@ def __init__(self): self.data = None self.services = None path = tmppath - if self.checkPath('/media/cf'): - path = '/media/cf' - if self.checkPath('/media/mmc'): - path = '/media/mmc' - if self.checkPath('/media/usb'): - path = '/media/usb' - if self.checkPath('/media/hdd'): - path = '/media/hdd' - self.epgfile = os.path.join(path, 'epg_new.dat') + if self.checkPath("/media/cf"): + path = "/media/cf" + if self.checkPath("/media/mmc"): + path = "/media/mmc" + if self.checkPath("/media/usb"): + path = "/media/usb" + if self.checkPath("/media/hdd"): + path = "/media/hdd" + self.epgfile = join(path, "epg_new.dat") + self.epg = epgdat.epgdat_class(path, settingspath, self.epgfile) def importEvents(self, services, dataTupleList): @@ -34,7 +37,7 @@ def importEvents(self, services, dataTupleList): self.services = services for program in dataTupleList: if program[3]: - desc = program[3] + '\n' + program[4] + desc = f"{program[3]}\n{program[4]}" else: desc = program[4] self.epg.add_event(program[0], program[1], program[2], desc) @@ -54,13 +57,13 @@ def epg_done(self): self.epg = None def checkPath(self, path): - f = os.popen('mount', "r") - for lx in f.xreadlines(): - if lx.find(path) != - 1: + f = popen("mount", "r") + for ln in f: + if ln.find(path) != - 1: return True return False def __del__(self): - 'Destructor - finalize the file when done' + """Destructor - finalize the file when done""" if self.epg is not None: self.epg_done() diff --git a/src/EPGImport/filterCustomChannel.py b/src/EPGImport/filterCustomChannel.py deleted file mode 100644 index ab12f09..0000000 --- a/src/EPGImport/filterCustomChannel.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -from Components.config import config -from re import sub -from xml.sax.saxutils import unescape -from xml.etree.cElementTree import iterparse - - -global filterCustomChannel - - -# Verifica che la configurazione epgimport sia definita -if hasattr(config.plugins, "epgimport") and hasattr(config.plugins.epgimport, "filter_custom_channel"): - filterCustomChannel = config.plugins.epgimport.filter_custom_channel.value -else: - filterCustomChannel = False # Fallback se non è definito - - -def get_xml_rating_string(elem): - r = '' - try: - for node in elem.findall("rating"): - for val in node.findall("value"): - txt = val.text.replace("+", "") - if not r: - r = txt - except Exception as e: - print("[XMLTVConverter] get_xml_rating_string error:", e) - return r.decode() if isinstance(r, bytes) else r - - -def xml_unescape(text): - if not isinstance(text, str): - return '' - return sub( - r' | |\s+', - ' ', - unescape( - text.strip(), - entities={ - r"«": "«", - r"«": "«", - r"»": "»", - r"»": "»", - r"'": r"'", - r""": r'"', - r"|": r"|", - r"[": r"[", - r"]": r"]", - } - ) - ) - - -def get_xml_string(elem, name): - r = '' - try: - for node in elem.findall(name): - txt = node.text - lang = node.get('lang', None) - if not r and txt is not None: - r = txt - elif lang == "nl": - r = txt - except Exception as e: - print("[XMLTVConverter] get_xml_string error:", e) - - # Ora ritorniamo UTF-8 di default - r = unescape(r, entities={ - r"'": r"'", - r""": r'"', - r"|": r"|", - r" ": r" ", - r"[": r"[", - r"]": r"]", - }) - - try: - # Assicura che il risultato sia una stringa - return r.encode('utf-8').decode('utf-8') # Compatibile con Python 2 e 3 - except UnicodeEncodeError as e: - print("[XMLTVConverter] Encoding error:", e) - return r # Ritorna come fallback - - -def enumerateXML(fp, tag=None): - """ - Enumerates ElementTree nodes from file object 'fp' for a specific tag. - Args: - fp: File-like object containing XML data. - tag: The XML tag to search for. If None, processes all nodes. - Yields: - ElementTree.Element objects matching the specified tag. - """ - doc = iterparse(fp, events=('start', 'end')) - _, root = next(doc) # Get the root element - depth = 0 - - for event, element in doc: - if tag is None or element.tag == tag: # Process all nodes if no tag is specified - if event == 'start': - depth += 1 - elif event == 'end': - depth -= 1 - if depth == 0: # Tag is fully parsed - yield element - element.clear() # Free memory for the element - elif event == 'end': # Clear other elements to free memory - root.clear() diff --git a/src/EPGImport/gen_xmltv.py b/src/EPGImport/gen_xmltv.py index 2ec1fec..cf8f59f 100644 --- a/src/EPGImport/gen_xmltv.py +++ b/src/EPGImport/gen_xmltv.py @@ -24,7 +24,7 @@ 'Science/Nature': (0x90, 0), 'Sports': (0x40, 0), 'Talk': (0x33, 0), - 'Unknown': (0x00, 0), + 'Unknown': (0x00, 0) } diff --git a/src/EPGImport/plugin.py b/src/EPGImport/plugin.py index ee1f604..a4dc08f 100644 --- a/src/EPGImport/plugin.py +++ b/src/EPGImport/plugin.py @@ -1,8 +1,8 @@ from os import remove from os.path import exists -from time import asctime, localtime, mktime, strftime, strptime, time +from time import localtime, mktime, time -from enigma import eConsoleAppContainer, eServiceCenter, eServiceReference, eEPGCache, eTimer, getDesktop +from enigma import eServiceCenter, eServiceReference, eEPGCache, eTimer, getDesktop # for localized messages from . import _ @@ -24,10 +24,9 @@ # Config from Components.ActionMap import ActionMap from Components.Button import Button -from Components.config import config, configfile, ConfigDirectory, ConfigEnableDisable, ConfigInteger, ConfigSubsection, ConfigYesNo, ConfigClock, getConfigListEntry, ConfigText, ConfigSelection, ConfigNumber, ConfigSubDict, NoSave +from Components.config import config, ConfigEnableDisable, ConfigSubsection, ConfigYesNo, ConfigClock, getConfigListEntry, ConfigText, ConfigSelection, ConfigNumber, ConfigSubDict, NoSave from Components.ConfigList import ConfigListScreen from Components.Label import Label -from Components.Console import Console import Components.PluginComponent from Components.ScrollLabel import ScrollLabel from Components.Sources.StaticText import StaticText @@ -35,22 +34,18 @@ from Screens.ChoiceBox import ChoiceBox from Screens.MessageBox import MessageBox from Screens.Screen import Screen -from Screens.VirtualKeyBoard import VirtualKeyBoard import Screens.Standby from Tools import Notifications from Tools.Directories import fileExists, SCOPE_PLUGINS, resolveFilename, isPluginInstalled from Tools.FuzzyDate import FuzzyTime -try: - from Tools.StbHardware import getFPWasTimerWakeup -except: - from Tools.DreamboxHardware import getFPWasTimerWakeup +from Tools.StbHardware import getFPWasTimerWakeup import NavigationInstance def lastMACbyte(): try: - return int(open('/sys/class/net/eth0/address').readline().strip()[-2:], 16) + return int(open("/sys/class/net/eth0/address").readline().strip()[-2:], 16) except: return 256 @@ -64,10 +59,18 @@ def calcDefaultStarttime(): return (5 * 60 * 60) + offset -#Set default configuration +# historically located (not a problem, we want to update it) +CONFIG_PATH = "/etc/epgimport" + +# Global variable +autoStartTimer = None +_session = None +BouquetChannelListList = None +serviceIgnoreList = None + +# Set default configuration config.plugins.epgimport = ConfigSubsection() -config.plugins.epgimport.enabled = ConfigEnableDisable(default=True) -config.plugins.epgimport.repeat_import = ConfigInteger(default=0, limits=(0, 23)) +config.plugins.epgimport.enabled = ConfigEnableDisable(default=False) config.plugins.epgimport.runboot = ConfigSelection(default="4", choices=[ ("1", _("always")), ("2", _("only manual boot")), @@ -76,7 +79,7 @@ def calcDefaultStarttime(): ]) config.plugins.epgimport.runboot_restart = ConfigYesNo(default=False) config.plugins.epgimport.runboot_day = ConfigYesNo(default=False) -# config.plugins.epgimport.wakeupsleep = ConfigEnableDisable(default=False) +config.plugins.epgimport.wakeupsleep = ConfigEnableDisable(default=False) config.plugins.epgimport.wakeup = ConfigClock(default=calcDefaultStarttime()) # Different default in OpenATV: config.plugins.epgimport.showinplugins = ConfigYesNo(default=IMAGEDISTRO != "openatv") @@ -85,11 +88,7 @@ def calcDefaultStarttime(): ("wakeup", _("wake up and import")), ("skip", _("skip the import")) ]) -config.plugins.epgimport.pathdb = ConfigDirectory(default='/etc/enigma2/epg.dat') -config.plugins.epgimport.execute_shell = ConfigYesNo(default=False) -config.plugins.epgimport.shell_name = ConfigText(default="") config.plugins.epgimport.standby_afterwakeup = ConfigYesNo(default=False) -config.plugins.epgimport.run_after_standby = ConfigYesNo(default=False) config.plugins.epgimport.shutdown = ConfigYesNo(default=False) config.plugins.epgimport.longDescDays = ConfigNumber(default=5) # config.plugins.epgimport.showinmainmenu = ConfigYesNo(default=False) @@ -98,7 +97,6 @@ def calcDefaultStarttime(): config.plugins.epgimport.import_onlybouquet = ConfigYesNo(default=False) config.plugins.epgimport.import_onlyiptv = ConfigYesNo(default=False) config.plugins.epgimport.clear_oldepg = ConfigYesNo(default=False) -config.plugins.epgimport.filter_custom_channel = ConfigYesNo(default=True) config.plugins.epgimport.day_profile = ConfigSelection(choices=[("1", _("Press OK"))], default="1") config.plugins.extra_epgimport = ConfigSubsection() config.plugins.extra_epgimport.last_import = ConfigText(default="0") @@ -115,19 +113,7 @@ def calcDefaultStarttime(): _("Friday"), _("Saturday"), _("Sunday"), - ] - - -# historically located (not a problem, we want to update it) -CONFIG_PATH = '/etc/epgimport' - -# Global variable -autoStartTimer = None -_session = None -BouquetChannelListList = None -serviceIgnoreList = None -filterCounter = 0 -isFilterRunning = 0 +] def getAlternatives(service): @@ -138,7 +124,7 @@ def getAlternatives(service): def getRefNum(ref): - ref = ref.split(':')[3:7] + ref = ref.split(":")[3:7] try: return int(ref[0], 16) << 48 | int(ref[1], 16) << 32 | int(ref[2], 16) << 16 | int(ref[3], 16) >> 16 except: @@ -147,8 +133,6 @@ def getRefNum(ref): def getBouquetChannelList(): channels = [] - global isFilterRunning, filterCounter - isFilterRunning = 1 serviceHandler = eServiceCenter.getInstance() mask = (eServiceReference.isMarker | eServiceReference.isDirectory) altrernative = eServiceReference.isGroup @@ -168,7 +152,6 @@ def getBouquetChannelList(): if clist: while True: service = clist.getNext() - filterCounter += 1 if not service.valid(): break if not (service.flags & mask): @@ -190,7 +173,6 @@ def getBouquetChannelList(): if services is not None: while True: service = services.getNext() - filterCounter += 1 if not service.valid(): break if not (service.flags & mask): @@ -205,7 +187,6 @@ def getBouquetChannelList(): refnum = getRefNum(service.toString()) if refnum and refnum not in channels: channels.append(refnum) - isFilterRunning = 0 return channels # Filter servicerefs that this box can display by starting a fake recording. @@ -240,9 +221,8 @@ def channelFilter(ref): fakeRecResult = fakeRecService.start(True) NavigationInstance.instance.stopRecordService(fakeRecService) # -7 (errNoSourceFound) occurs when tuner is disconnected. - # r = fakeRecResult in (0, -7) - return fakeRecResult in (0, -5, -7) - # return r + r = fakeRecResult in (0, -7) + return r print("Invalid serviceref string:", ref, file=log) return False @@ -264,18 +244,14 @@ def channelFilter(ref): def startImport(): if not epgimport.isImportRunning(): EPGImport.HDD_EPG_DAT = config.misc.epgcache_filename.value - if config.plugins.epgimport.filter_custom_channel.value: - EPGConfig.filterCustomChannel = True - else: - EPGConfig.filterCustomChannel = False - if config.plugins.epgimport.clear_oldepg.value and hasattr(epgimport.epgcache, 'flushEPG'): + if config.plugins.epgimport.clear_oldepg.value and hasattr(epgimport.epgcache, "flushEPG"): EPGImport.unlink_if_exists(EPGImport.HDD_EPG_DAT) - EPGImport.unlink_if_exists(EPGImport.HDD_EPG_DAT + '.backup') + EPGImport.unlink_if_exists(EPGImport.HDD_EPG_DAT + ".backup") epgimport.epgcache.flushEPG() epgimport.onDone = doneImport epgimport.beginImport(longDescUntil=config.plugins.epgimport.longDescDays.value * 24 * 3600 + time()) else: - print("[startImport] Already running, won't start again", file=log) + print("[startImport] Already running, won't start again") ################################## @@ -327,7 +303,7 @@ class EPGImportConfig(ConfigListScreen, Screen): """ - def __init__(self, session): + def __init__(self, session, args=0): self.skin = EPGImportConfig.skin self.setup_title = _("EPG Import Configuration") Screen.__init__(self, session) @@ -348,22 +324,20 @@ def __init__(self, session): "log": self.keyInfo, "contextMenu": self.openMenu, }, -1) + ConfigListScreen.__init__(self, [], session=self.session) self.lastImportResult = None - list = [] self.onChangedEntry = [] - ConfigListScreen.__init__(self, list, session=self.session, on_change=self.changedEntry) self.prev_onlybouquet = config.plugins.epgimport.import_onlybouquet.value self.initConfig() self.createSetup() - self.filterStatusTemplate = _("Filtering:\n%s Please wait!") self.importStatusTemplate = _("Importing:\n%s %s events") self.updateTimer = eTimer() self.updateTimer.callback.append(self.updateStatus) self.updateTimer.start(2000) self.updateStatus() - self.onLayoutFinish.append(self.createSummary) self.onLayoutFinish.append(self.__layoutFinished) + # for summary: def changedEntry(self): for x in self.onChangedEntry: x() @@ -382,8 +356,6 @@ def __layoutFinished(self): self.setTitle(self.setup_title) def initConfig(self): - dx = 4 * " " - def getPrevValues(section): res = {} for (key, val) in section.content.items.items(): @@ -395,28 +367,23 @@ def getPrevValues(section): self.EPG = config.plugins.epgimport self.prev_values = getPrevValues(self.EPG) - self.cfg_enabled = getConfigListEntry(_("Automatic import EPG"), self.EPG.enabled, _("When enabled, it allows you to schedule an automatic EPG update at the given days and time.")) - self.cfg_wakeup = getConfigListEntry(dx + _("Automatic start time"), self.EPG.wakeup, _("Specify the time for the automatic EPG update.")) - self.cfg_deepstandby = getConfigListEntry(dx + _("When in deep standby"), self.EPG.deepstandby, _("Choose the action to perform when the box is in deep standby and the automatic EPG update should normally start.")) - self.cfg_shutdown = getConfigListEntry(dx + _("Return to deep standby after import"), self.EPG.shutdown, _("This will turn back waked up box into deep-standby after automatic EPG import.")) - self.cfg_standby_afterwakeup = getConfigListEntry(dx + _("Standby at startup"), self.EPG.standby_afterwakeup, _("The waked up box will be turned into standby after automatic EPG import wake up.")) - self.cfg_day_profile = getConfigListEntry(_("Choice days for start import"), self.EPG.day_profile, _("You can select the day(s) when the EPG update must be performed.")) - self.cfg_runboot = getConfigListEntry(_("Start import after booting up"), self.EPG.runboot, _("Specify in which case the EPG must be automatically updated after the box has booted.")) - self.cfg_import_onlybouquet = getConfigListEntry(dx + _("Load EPG only services in bouquets"), self.EPG.import_onlybouquet, _("To save memory you can decide to only load EPG data for the services that you have in your bouquet files.")) - self.cfg_import_onlyiptv = getConfigListEntry(_("Load EPG only for IPTV channels"), self.EPG.import_onlyiptv, _("Specify in this case the EPG should be imported only for iptv lists.")) - self.cfg_runboot_day = getConfigListEntry(dx + _("Consider setting \"Days Profile\""), self.EPG.runboot_day, _("When you decide to import the EPG after the box booted mention if the \"days profile\" must be take into consideration or not.")) - self.cfg_runboot_restart = getConfigListEntry(dx + _("Skip import on restart GUI"), self.EPG.runboot_restart, _("When you restart the GUI you can decide to skip or not the EPG data import.")) - self.cfg_showinextensions = getConfigListEntry(_("Show \"EPG import now\" in extensions"), self.EPG.showinextensions, _("Display a shortcut \"EPG import now\" in the extension menu. This menu entry will immediately start the EPG update process when selected.")) - self.cfg_showinplugins = getConfigListEntry(_("Show \"EPGImport\" in plugins"), self.EPG.showinplugins, _("Display a shortcut \"EPG import\" in the browser plugin..")) - # self.cfg_showinmainmenu = getConfigListEntry(_("Show \"EPG import\" in epg menu"), self.EPG.showinmainmenu, _("Display a shortcut \"EPG import\" in your STB epg menu screen. This allows you to access the configuration.")) - self.cfg_longDescDays = getConfigListEntry(_("Load long descriptions up to X days"), self.EPG.longDescDays, _("Define the number of days that you want to get the full EPG data, reducing this number can help you to save memory usage on your box. But you are also limited with the EPG provider available data. You will not have 15 days EPG if it only provide 7 days data.")) - self.cfg_parse_autotimer = getConfigListEntry(_("Run AutoTimer after import"), self.EPG.parse_autotimer, _("You can start automatically the plugin AutoTimer after the EPG data update to have it refreshing its scheduling after EPG data refresh.")) - self.cfg_clear_oldepg = getConfigListEntry(_("Clearing current EPG before import"), config.plugins.epgimport.clear_oldepg, _("This will clear the current EPG data in memory before updating the EPG data. This allows you to always have a clean new EPG with the latest EPG data, for example in case of program changes between refresh, otherwise EPG data are cumulative.")) - self.cfg_filter_custom_channel = getConfigListEntry(_("Also apply \"channel id\" filtering on custom.channels.xml"), self.EPG.filter_custom_channel, _("This is for advanced users that are using the channel id filtering feature. If enabled, the filter rules defined into /etc/epgimport/channel_id_filter.conf will also be applied on your /etc/epgimport/custom.channels.xml file.")) - self.cfg_execute_shell = getConfigListEntry(_("Execute shell command before import EPG"), self.EPG.execute_shell, _("When enabled, then you can run the desired script before starting the import, after which the import of the EPG will begin.")) - self.cfg_shell_name = getConfigListEntry(dx + _("Shell command name"), self.EPG.shell_name, _("Enter shell command name.")) - self.cfg_run_after_standby = getConfigListEntry(_("Start import after standby"), self.EPG.run_after_standby, _("Start import after resuming from standby mode.")) - self.cfg_repeat_import = getConfigListEntry(dx + _("Hours after which the import is repeated"), self.EPG.repeat_import, _("Number of hours (1-23, or 0 for no repeat) after which the import is repeated. This value is not saved and will be reset when the GUI restarts.")) + self.cfg_enabled = getConfigListEntry(_("Automatic import EPG"), self.EPG.enabled) + self.cfg_wakeup = getConfigListEntry(_("Automatic start time"), self.EPG.wakeup) + self.cfg_deepstandby = getConfigListEntry(_("When in deep standby"), self.EPG.deepstandby) + self.cfg_shutdown = getConfigListEntry(_("Return to deep standby after import"), self.EPG.shutdown) + self.cfg_standby_afterwakeup = getConfigListEntry(_("Standby at startup"), self.EPG.standby_afterwakeup) + self.cfg_day_profile = getConfigListEntry(_("Choice days for start import"), self.EPG.day_profile) + self.cfg_runboot = getConfigListEntry(_("Start import after booting up"), self.EPG.runboot) + self.cfg_import_onlybouquet = getConfigListEntry(_("Load EPG only services in bouquets"), self.EPG.import_onlybouquet) + self.cfg_import_onlyiptv = getConfigListEntry(_("Load EPG only for IPTV channels"), self.EPG.import_onlyiptv) + self.cfg_runboot_day = getConfigListEntry(_("Consider setting \"Days Profile\""), self.EPG.runboot_day) + self.cfg_runboot_restart = getConfigListEntry(_("Skip import on restart GUI"), self.EPG.runboot_restart) + self.cfg_showinextensions = getConfigListEntry(_("Show \"EPGImport\" in extensions"), self.EPG.showinextensions) + self.cfg_showinplugins = getConfigListEntry(_("Show \"EPGImport\" in plugins"), self.EPG.showinplugins) +# self.cfg_showinmainmenu = getConfigListEntry(_("Show \"EPG Importer\" in main menu"), self.EPG.showinmainmenu) + self.cfg_longDescDays = getConfigListEntry(_("Load long descriptions up to X days"), self.EPG.longDescDays) + self.cfg_parse_autotimer = getConfigListEntry(_("Run AutoTimer after import"), self.EPG.parse_autotimer) + self.cfg_clear_oldepg = getConfigListEntry(_("Delete current EPG before import"), config.plugins.epgimport.clear_oldepg) def createSetup(self): list = [self.cfg_enabled] @@ -427,25 +394,17 @@ def createSetup(self): list.append(self.cfg_shutdown) if not self.EPG.shutdown.value: list.append(self.cfg_standby_afterwakeup) - list.append(self.cfg_repeat_import) - else: - list.append(self.cfg_repeat_import) - list.append(self.cfg_day_profile) + list.append(self.cfg_day_profile) list.append(self.cfg_runboot) if self.EPG.runboot.value != "4": list.append(self.cfg_runboot_day) if self.EPG.runboot.value == "1" or self.EPG.runboot.value == "2": list.append(self.cfg_runboot_restart) - list.append(self.cfg_run_after_standby) list.append(self.cfg_import_onlybouquet) list.append(self.cfg_import_onlyiptv) - if hasattr(eEPGCache, 'flushEPG'): + if hasattr(eEPGCache, "flushEPG"): list.append(self.cfg_clear_oldepg) - list.append(self.cfg_filter_custom_channel) list.append(self.cfg_longDescDays) - list.append(self.cfg_execute_shell) - if self.EPG.execute_shell.value: - list.append(self.cfg_shell_name) if fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyo")) or fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyc")): try: # from Plugins.Extensions.AutoTimer.AutoTimer import AutoTimer @@ -479,11 +438,8 @@ def keyGreen(self): self.updateTimer.stop() if self.EPG.parse_autotimer.value and (not fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyo")) or not fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyc"))): self.EPG.parse_autotimer.value = False - if self.EPG.deepstandby.value == "skip" and self.EPG.shutdown.value: - self.EPG.shutdown.value = False if self.EPG.shutdown.value: self.EPG.standby_afterwakeup.value = False - self.EPG.repeat_import.value = 0 if self.prev_onlybouquet != config.plugins.epgimport.import_onlybouquet.value or (autoStartTimer is not None and autoStartTimer.prev_multibouquet != config.usage.multibouquet.value): EPGConfig.channelCache = {} self.save() @@ -492,10 +448,9 @@ def save(self): if self["config"].isChanged(): for x in self["config"].list: x[1].save() - configfile.save() self.EPG.save() self.session.open(MessageBox, _("Settings saved successfully !"), MessageBox.TYPE_INFO, timeout=5) - self.close(True) + self.close(True, self.session) def keyLeft(self): ConfigListScreen.keyLeft(self) @@ -506,41 +461,24 @@ def keyRight(self): self.newConfig() def keyOk(self): - ConfigListScreen.keyOK(self) - sel = self["config"].getCurrent() and self["config"].getCurrent()[1] + # ConfigListScreen.keyOK(self) + sel = self["config"].getCurrent()[1] if sel and sel == self.EPG.day_profile: self.session.open(EPGImportProfile) - elif sel == self.EPG.shell_name: - self.session.openWithCallback(self.textEditCallback, VirtualKeyBoard, text=self.EPG.shell_name.value) - else: - return def updateStatus(self): text = "" - global isFilterRunning, filterCounter - if isFilterRunning == 1: - text = self.filterStatusTemplate % (str(filterCounter)) - elif epgimport.isImportRunning(): + if epgimport.isImportRunning(): src = epgimport.source text = self.importStatusTemplate % (src.description, epgimport.eventCount) self["status"].setText(text) if lastImportResult and (lastImportResult != self.lastImportResult): start, count = lastImportResult try: - if isinstance(start, str): - start = mktime(strptime(start, "%Y-%m-%d %H:%M:%S")) - elif not isinstance(start, (int, float)): - raise ValueError("Start value is not a valid timestamp or string") - - # Chiama FuzzyTime con il timestamp corretto - d, t = FuzzyTime(int(start), inPast=True) - except Exception as e: - print("[EPGImport] Errore con FuzzyTime:", e) - try: - d, t = FuzzyTime(int(start)) - except Exception as e: - print("[EPGImport] Fallito anche il fallback con FuzzyTime:", e) - d, t = _("unknown"), _("unknown") + d, t = FuzzyTime(start, inPast=True) + except: + # Not all images have inPast + d, t = FuzzyTime(start) self["statusbar"].setText(_("Last: %s %s, %d events") % (d, t, count)) self.lastImportResult = lastImportResult @@ -560,10 +498,10 @@ def doimport(self, one_source=None): else: cfg = one_source sources = [s for s in EPGConfig.enumSources(CONFIG_PATH, filter=cfg["sources"])] - EPGImport.ServerStatusList = {} if not sources: self.session.open(MessageBox, _("No active EPG sources found, nothing to do"), MessageBox.TYPE_INFO, timeout=10, close_on_any_key=True) return + # make it a stack, first on top. sources.reverse() epgimport.sources = sources self.session.openWithCallback(self.do_import_callback, MessageBox, _("EPGImport\nImport of epg data will start.\nThis may take a few minutes.\nIs this ok?"), MessageBox.TYPE_YESNO, timeout=15, default=True) @@ -572,18 +510,12 @@ def do_import_callback(self, confirmed): if not confirmed: return try: - if config.plugins.epgimport.execute_shell.value and config.plugins.epgimport.shell_name.value: - Console().eBatch([config.plugins.epgimport.shell_name.value], self.executeShellEnd, debug=True) - else: - startImport() + startImport() except Exception as e: print("[XMLTVImport] Error at start:", e, file=log) self.session.open(MessageBox, _("EPGImport Plugin\nFailed to start:\n") + str(e), MessageBox.TYPE_ERROR, timeout=15, close_on_any_key=True) self.updateStatus() - def executeShellEnd(self, *args, **kwargs): - startImport() - def dosources(self): self.session.openWithCallback(self.sourcesDone, EPGImportSources) @@ -632,14 +564,14 @@ def __init__(self, session): Screen.__init__(self, session) self["key_red"] = Button(_("Cancel")) self["key_green"] = Button(_("Save")) - self["key_yellow"] = Button(_('Import')) + self["key_yellow"] = Button(_("Import")) self["key_blue"] = Button() cfg = EPGConfig.loadUserSettings() filter = cfg["sources"] tree = [] cat = None for x in EPGConfig.enumSources(CONFIG_PATH, filter=None, categories=True): - if hasattr(x, 'description'): + if hasattr(x, "description"): sel = (filter is None) or (x.description in filter) entry = (x.description, x.description, sel) if cat is None: @@ -669,7 +601,7 @@ def __init__(self, session): self.setTitle(_("EPG Import Sources")) def save(self): - ''' Make the entries unique through a set ''' + """ Make the entries unique through a set """ sources = list(set([item[1] for item in self["list"].enumSelected()])) print("[XMLTVImport] Selected sources:", sources, file=log) EPGConfig.storeUserSettings(sources=sources) @@ -704,9 +636,8 @@ class EPGImportProfile(ConfigListScreen, Screen): """ - def __init__(self, session): + def __init__(self, session, args=0): Screen.__init__(self, session) - self.setTitle(_("Days Profile")) self.list = [] for i in range(7): self.list.append(getConfigListEntry(weekdays[i], config.plugins.extra_epgimport.day_import[i])) @@ -735,12 +666,9 @@ def save(self): timeout=6 ) return - ConfigListScreen.keySave(self) - """ - # for x in self["config"].list: - # x[1].save() - # self.close() - """ + for x in self["config"].list: + x[1].save() + self.close() def cancel(self): for x in self["config"].list: @@ -769,12 +697,11 @@ class EPGImportLog(Screen): def __init__(self, session): self.session = session Screen.__init__(self, session) - self.log = log self["key_red"] = Button(_("Clear")) self["key_green"] = Button() self["key_yellow"] = Button() self["key_blue"] = Button(_("Save")) - self["list"] = ScrollLabel(self.log.getvalue()) + self["list"] = ScrollLabel(log.getvalue()) self["actions"] = ActionMap(["DirectionActions", "OkCancelActions", "ColorActions", "MenuActions"], { "red": self.clear, @@ -799,7 +726,7 @@ def setCustomTitle(self): def save(self): try: - with open('/tmp/epgimport.log', 'w') as f: + with open("/tmp/epgimport.log", "w") as f: f.write(self.log.getvalue()) self.session.open(MessageBox, _("Write to /tmp/epgimport.log"), MessageBox.TYPE_INFO, timeout=5, close_on_any_key=True) except Exception as e: @@ -810,8 +737,6 @@ def cancel(self): self.close(False) def clear(self): - self.log.logfile.write("") - self.log.logfile.truncate() self.close(False) @@ -837,25 +762,20 @@ def main(session, **kwargs): session.openWithCallback(doneConfiguring, EPGImportConfig) -def doneConfiguring(session, retval=False): - "user has closed configuration, check new values...." - if retval is True: - if autoStartTimer is not None: - autoStartTimer.update(clock=True) +def doneConfiguring(session, retval): + """user has closed configuration, check new values....""" + if autoStartTimer is not None: + autoStartTimer.update() def doneImport(reboot=False, epgfile=None): global _session, lastImportResult, BouquetChannelListList, serviceIgnoreList BouquetChannelListList = None serviceIgnoreList = None - # - timestamp = time() - formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime(timestamp)) - lastImportResult = (formatted_time, epgimport.eventCount) - # lastImportResult = (time(), epgimport.eventCount) + lastImportResult = (time(), epgimport.eventCount) try: start, count = lastImportResult - localtime = asctime(localtime(time())) + # localtime = asctime(localtime(time())) lastimport = "%s, %d" % (localtime, count) config.plugins.extra_epgimport.last_import.value = lastimport config.plugins.extra_epgimport.last_import.save() @@ -902,7 +822,7 @@ def __init__(self, session, parse=False): def runCheckDeepstandby(self): print("[XMLTVImport] Run check deep standby after import", file=log) - if config.plugins.epgimport.shutdown.value and config.plugins.epgimport.deepstandby.value == 'wakeup': + if config.plugins.epgimport.shutdown.value and config.plugins.epgimport.deepstandby.value == "wakeup": if config.plugins.epgimport.deepstandby_afterimport.value and getFPWasTimerWakeup(): config.plugins.epgimport.deepstandby_afterimport.value = False if Screens.Standby.inStandby and not self.session.nav.getRecordings() and not Screens.Standby.inTryQuitMainloop: @@ -918,7 +838,7 @@ def restartEnigma(confirmed): # save state of enigma, so we can return to previeus state if Screens.Standby.inStandby: try: - open('/tmp/enigmastandby', 'wb').close() + open("/tmp/enigmastandby", "wb").close() except: print("Failed to create /tmp/enigmastandby", file=log) else: @@ -930,6 +850,7 @@ def restartEnigma(confirmed): _session.open(Screens.Standby.TryQuitMainloop, 3) +# ################################# # Autostart section class AutoStartTimer: @@ -937,16 +858,11 @@ def __init__(self, session): self.session = session self.prev_onlybouquet = config.plugins.epgimport.import_onlybouquet.value self.prev_multibouquet = config.usage.multibouquet.value - self.clock = config.plugins.epgimport.wakeup.value self.timer = eTimer() self.timer.callback.append(self.onTimer) - self.onceRepeatImport = eTimer() - self.onceRepeatImport.callback.append(self.runImport) self.pauseAfterFinishImportCheck = eTimer() self.pauseAfterFinishImportCheck.callback.append(self.afterFinishImportCheck) self.pauseAfterFinishImportCheck.startLongTimer(30) - self.container = None - config.misc.standbyCounter.addNotifier(self.standbyCounterChangedRunImport) self.update() def getWakeTime(self): @@ -958,11 +874,8 @@ def getWakeTime(self): else: return -1 - def update(self, atLeast=0, clock=False): + def update(self, atLeast=0): self.timer.stop() - if clock and self.clock != config.plugins.epgimport.wakeup.value: - self.clock = config.plugins.epgimport.wakeup.value - self.onceRepeatImport.stop() wake = self.getWakeTime() now_t = time() now = int(now_t) @@ -981,12 +894,8 @@ def update(self, atLeast=0, clock=False): next = wake - now self.timer.startLongTimer(next) else: - self.onceRepeatImport.stop() wake = -1 - now_str = strftime("%Y-%m-%d %H:%M:%S", localtime(now)) - wake_str = strftime("%Y-%m-%d %H:%M:%S", localtime(wake)) if wake > 0 else "Not set" - - print("[XMLTVImport] WakeUpTime now set to", wake_str, "(now=%s)" % now_str, file=log) + print("[XMLTVImport] WakeUpTime now set to", wake, "(now=%s)" % now, file=log) return wake def runImport(self): @@ -999,27 +908,6 @@ def runImport(self): if sources: sources.reverse() epgimport.sources = sources - if config.plugins.epgimport.execute_shell.value and config.plugins.epgimport.shell_name.value: - if self.container: - del self.container - self.container = eConsoleAppContainer() - self.container.appClosed.append(self.executeShellEnd) - if self.container.execute(config.plugins.epgimport.shell_name.value): - self.executeShellEnd(-1) - else: - startImport() - else: - self.session.open(MessageBox, _("No source file found !"), MessageBox.TYPE_INFO, timeout=5) - - def executeShellEnd(self, retval): - if retval: - if self.container: - try: - self.container.appClosed.remove(self.executeShellEnd) - except: - self.container.appClosed_conn = None - self.container.kill() - self.container = None startImport() def onTimer(self): @@ -1031,10 +919,6 @@ def onTimer(self): atLeast = 0 if wake - now < 60: self.runImport() - repeat_time = config.plugins.epgimport.repeat_import.value - if repeat_time: - self.onceRepeatImport.startLongTimer(repeat_time * 3600) - print("[EPGImport] start once repeat timer, wait in nours -", repeat_time, file=log) atLeast = 60 self.update(atLeast) @@ -1066,7 +950,7 @@ def getStatus(self): return wake_up def afterFinishImportCheck(self): - if config.plugins.epgimport.deepstandby.value == 'wakeup' and getFPWasTimerWakeup(): + if config.plugins.epgimport.deepstandby.value == "wakeup" and getFPWasTimerWakeup(): if exists("/tmp/enigmastandby") or exists("/tmp/.EPGImportAnswerBoot"): print("[XMLTVImport] is restart enigma2", file=log) else: @@ -1090,27 +974,13 @@ def afterFinishImportCheck(self): print("[XMLTVImport] start wait_timer (10sec) for goto standby", file=log) self.wait_timer.start(10000, True) - def afterStandbyRunImport(self): - if config.plugins.epgimport.run_after_standby.value: - print("[EPGImport] start import after standby", file=log) - self.runImport() - - def standbyCounterChangedRunImport(self, configElement): - if Screens.Standby.inStandby: - try: - if self.afterStandbyRunImport not in Screens.Standby.inStandby.onClose: - Screens.Standby.inStandby.onClose.append(self.afterStandbyRunImport) - except: - print("[EPGImport] error inStandby.onClose append afterStandbyRunImport", file=log) - def startStandby(self): if Screens.Standby.inStandby: print("[XMLTVImport] add checking standby", file=log) try: - if self.onLeaveStandbyFinishImportCheck not in Screens.Standby.inStandby.onClose: - Screens.Standby.inStandby.onClose.append(self.onLeaveStandbyFinishImportCheck) + Screens.Standby.inStandby.onClose.append(self.onLeaveStandby) except: - print("[EPGImport] error inStandby.onClose append .onLeaveStandbyFinishImportCheck") + pass def onLeaveStandby(self): if config.plugins.epgimport.deepstandby_afterimport.value: @@ -1127,7 +997,7 @@ def WakeupDayOfWeek(): except: cur_day = -1 if cur_day >= 0: - for i in (1, 2, 3, 4, 5, 6, 7): + for i in range(1, 8): if config.plugins.extra_epgimport.day_import[(cur_day + i) % 7].value: return i return start_day @@ -1151,14 +1021,14 @@ def onBootStartCheck(): elif runboot == "3" and getFPWasTimerWakeup(): on_start = True print("[XMLTVImport] is automatic boot", file=log) - flag = '/tmp/.EPGImportAnswerBoot' + flag = "/tmp/.EPGImportAnswerBoot" if config.plugins.epgimport.runboot_restart.value and runboot != "3": if exists(flag): on_start = False print("[XMLTVImport] not starting import - is restart enigma2", file=log) else: try: - open(flag, 'wb').close() + open(flag, "wb").close() except: print("Failed to create /tmp/.EPGImportAnswerBoot", file=log) if config.plugins.epgimport.runboot_day.value: @@ -1175,15 +1045,11 @@ def onBootStartCheck(): def autostart(reason, session=None, **kwargs): - "called with reason=1 to during shutdown, with reason=0 at startup?" + """called with reason=1 to during shutdown, with reason=0 at startup?""" global autoStartTimer global _session print("[XMLTVImport] autostart (%s) occured at" % reason, time(), file=log) if reason == 0 and _session is None: - nms = strftime("%Y-%m-%d %H:%M:%S", localtime(time())) - # log.write("[EPGImport] autostart (%s) occured at (%s)" % (reason, strftime("%Y-%m-%d %H:%M:%S", localtime()))) - # print("[EPGImport] autostart (%s) occured at" % reason, time(), file=log) - print("[EPGImport] autostart (%s) occured at" % reason, nms, file=log) if session is not None: _session = session if autoStartTimer is None: @@ -1204,14 +1070,15 @@ def autostart(reason, session=None, **kwargs): def getNextWakeup(): - "returns timestamp of next time when autostart should be called" + """returns timestamp of next time when autostart should be called""" if autoStartTimer: - if config.plugins.epgimport.enabled.value and config.plugins.epgimport.deepstandby.value == 'wakeup' and autoStartTimer.getSources(): + if config.plugins.epgimport.enabled.value and config.plugins.epgimport.deepstandby.value == "wakeup" and autoStartTimer.getSources(): print("[XMLTVImport] Will wake up from deep sleep", file=log) return autoStartTimer.getStatus() return -1 +# we need this helper function to identify the descriptor def extensionsmenu(session, **kwargs): main(session, **kwargs) @@ -1228,8 +1095,8 @@ def setExtensionsmenu(el): description = _("Automated EPG Importer") config.plugins.epgimport.showinextensions.addNotifier(setExtensionsmenu, initial_call=False, immediate_feedback=False) -extDescriptor = PluginDescriptor(name=_("EPG import now"), description=description, where=PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=start_import) -pluginlist = PluginDescriptor(name=_("EPG-Importer"), description=description, where=PluginDescriptor.WHERE_PLUGINMENU, icon='plugin.png', fnc=main) +extDescriptor = PluginDescriptor(name=_("EPG-Importer Now"), description=description, where=PluginDescriptor.WHERE_EXTENSIONSMENU, fnc=start_import) +pluginlist = PluginDescriptor(name=_("EPG-Importer"), description=description, where=PluginDescriptor.WHERE_PLUGINMENU, icon="plugin.png", fnc=main) def epgmenu(menuid, **kwargs): diff --git a/src/EPGImport/xmltvconverter.py b/src/EPGImport/xmltvconverter.py index c95b639..5548ab2 100755 --- a/src/EPGImport/xmltvconverter.py +++ b/src/EPGImport/xmltvconverter.py @@ -1,11 +1,8 @@ from xml.etree.cElementTree import iterparse -from xml.sax.saxutils import unescape from calendar import timegm from time import strptime, struct_time from . import log -# %Y%m%d%H%M%S - def quickptime(date_str): return struct_time( @@ -26,7 +23,7 @@ def quickptime(date_str): def get_time_utc(timestring, fdateparse): # print "get_time_utc", timestring, format try: - values = timestring.split(' ') + values = timestring.split(" ") tm = fdateparse(values[0]) timeGm = timegm(tm) # suppose file says +0300 => that means we have to substract 3 hours from localtime to get gmt @@ -42,36 +39,27 @@ def get_time_utc(timestring, fdateparse): def get_xml_string(elem, name): - r = '' + r = "" try: for node in elem.findall(name): txt = node.text - lang = node.get('lang', None) + lang = node.get("lang", None) if not r and txt is not None: r = txt elif lang == "nl": r = txt except Exception as e: - print("[XMLTVConverter] get_xml_string error:", e) + print(f"[XMLTVConverter] get_xml_string error: {e}") """ # Now returning UTF-8 by default, the epgdat/oudeis must be adjusted to make this work. # Note that the default xml.sax.saxutils.unescape() function don't unescape # some characters and we have to manually add them to the entities dictionary. """ - - r = unescape(r, entities={ - r"'": r"'", - r""": r'"', - r"|": r"|", - r" ": r" ", - r"[": r"[", - r"]": r"]", - }) return r.decode() if isinstance(r, bytes) else r def get_xml_rating_string(elem): - r = '' + r = "" try: for node in elem.findall("rating"): for val in node.findall("value"): @@ -87,10 +75,10 @@ def enumerateProgrammes(fp): """Enumerates programme ElementTree nodes from file object 'fp'""" for event, elem in iterparse(fp): try: - if elem.tag == 'programme': + if elem.tag == "programme": yield elem elem.clear() - elif elem.tag == 'channel': + elif elem.tag == "channel": # Throw away channel elements, save memory elem.clear() except Exception as e: @@ -98,10 +86,10 @@ def enumerateProgrammes(fp): class XMLTVConverter: - def __init__(self, channels_dict, category_dict, dateformat='%Y%m%d%H%M%S %Z', offset=0): + def __init__(self, channels_dict, category_dict, dateformat="%Y%m%d%H%M%S %Z", offset=0): self.channels = channels_dict self.categories = category_dict - if dateformat.startswith('%Y%m%d%H%M%S'): + if dateformat.startswith("%Y%m%d%H%M%S"): self.dateParser = quickptime else: self.dateParser = lambda x: strptime(x, dateformat) @@ -115,7 +103,7 @@ def enumFile(self, fileobj): if not self.channels: return for elem in enumerateProgrammes(fileobj): - channel = elem.get('channel') + channel = elem.get("channel") channel = channel.lower() if channel not in self.channels: if lastUnknown != channel: @@ -126,20 +114,20 @@ def enumFile(self, fileobj): continue try: services = self.channels[channel] - start = get_time_utc(elem.get('start'), self.dateParser) + self.offset - stop = get_time_utc(elem.get('stop'), self.dateParser) + self.offset - title = get_xml_string(elem, 'title') + start = get_time_utc(elem.get("start"), self.dateParser) + self.offset + stop = get_time_utc(elem.get("stop"), self.dateParser) + self.offset + title = get_xml_string(elem, "title") # try/except for EPG XML files with program entries containing try: - subtitle = get_xml_string(elem, 'sub-title') + subtitle = get_xml_string(elem, "sub-title") except: - subtitle = '' + subtitle = "" # try/except for EPG XML files with program entries containing try: - description = get_xml_string(elem, 'desc') + description = get_xml_string(elem, "desc") except: - description = '' - category = get_xml_string(elem, 'category') + description = "" + category = get_xml_string(elem, "category") cat_nr = self.get_category(category, stop - start) try: @@ -152,7 +140,7 @@ def enumFile(self, fileobj): # data_tuple = (data.start, data.duration, data.title, data.short_description, data.long_description, data.type) if not stop or not start or (stop <= start): - print("[XMLTVConverter] Bad start/stop time: %s (%s) - %s (%s) [%s]" % (elem.get('start'), start, elem.get('stop'), stop, title)) + print(f"[XMLTVConverter] Bad start/stop time: {elem.get('start')} ({start}) - {elem.get('stop')} ({stop}) [{title}]") if rating: yield (services, (start, stop - start, title, subtitle, description, cat_nr, 0, rating)) else: @@ -161,7 +149,7 @@ def enumFile(self, fileobj): print(f"[XMLTVConverter] parsing event error: {e}") def get_category(self, cat, duration): - if (not cat) or (not isinstance(cat, type('str'))): + if (not cat) or (not isinstance(cat, type("str"))): return 0 if cat in self.categories: category = self.categories[cat] @@ -169,5 +157,5 @@ def get_category(self, cat, duration): if duration > 60 * category[1]: return category[0] elif len(category) > 0: - return category[0] + return category return 0 From 5acb8dfc143ba9727fc47b2446aab0fb8acb1570 Mon Sep 17 00:00:00 2001 From: jbleyel Date: Tue, 25 Feb 2025 22:49:44 +0100 Subject: [PATCH 06/14] update pot --- src/EPGImport/locale/EPGImport.pot | 431 +++++++----------- src/EPGImport/locale/it.mo | Bin 11653 -> 5633 bytes src/EPGImport/locale/it.po | 677 ++++++++++------------------- 3 files changed, 379 insertions(+), 729 deletions(-) mode change 100644 => 100755 src/EPGImport/locale/it.po diff --git a/src/EPGImport/locale/EPGImport.pot b/src/EPGImport/locale/EPGImport.pot index 201ce82..f45f63d 100644 --- a/src/EPGImport/locale/EPGImport.pot +++ b/src/EPGImport/locale/EPGImport.pot @@ -6,433 +6,304 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: \n" +"Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-24 11:56+0100\n" +"POT-Creation-Date: 2023-01-16 06:40+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 3.5\n" -"X-Poedit-Basepath: ..\n" -"X-Poedit-SearchPath-0: .\n" -#: filtersServices.py:151 -msgid "Add Provider" -msgstr "" - -#: filtersServices.py:152 -msgid "Add Channel" -msgstr "" - -#: filtersServices.py:154 -msgid "press OK to save list" -msgstr "" - -#: filtersServices.py:173 plugin.py:603 -msgid "Ignore services list" -msgstr "" - -#: filtersServices.py:202 -msgid "Really delete all list?" +#: ../plugin.py:739 +msgid "" +"\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" msgstr "" -#: filtersServices.py:235 -msgid "Delete selected" +#: ../plugin.py:739 +msgid " events\n" msgstr "" -#: filtersServices.py:236 -msgid "Delete all" +#: ../filtersServices.py:153 +msgid "Add Channel" msgstr "" -#: filtersServices.py:261 -msgid "Select service to add..." +#: ../filtersServices.py:152 +msgid "Add Provider" msgstr "" -#: filtersServices.py:269 +#: ../filtersServices.py:264 msgid "All services provider" msgstr "" -#: filtersServices.py:288 plugin.py:604 -msgid "Select action" -msgstr "" - -#: plugin.py:75 -msgid "always" -msgstr "" - -#: plugin.py:76 -msgid "only manual boot" -msgstr "" - -#: plugin.py:77 -msgid "only automatic boot" -msgstr "" - -#: plugin.py:78 -msgid "never" +#: ../plugin.py:1095 +msgid "Automated EPG Importer" msgstr "" -#: plugin.py:90 -msgid "wake up and import" +#: ../plugin.py:366 +msgid "Automatic import EPG" msgstr "" -#: plugin.py:91 -msgid "skip the import" +#: ../plugin.py:367 +msgid "Automatic start time" msgstr "" -#: plugin.py:108 -msgid "Press OK" +#: ../plugin.py:309 ../plugin.py:557 ../plugin.py:637 +msgid "Cancel" msgstr "" -#: plugin.py:117 -msgid "Monday" +#: ../plugin.py:371 +msgid "Choice days for start import" msgstr "" -#: plugin.py:118 -msgid "Tuesday" +#: ../plugin.py:693 +msgid "Clear" msgstr "" -#: plugin.py:119 -msgid "Wednesday" +#: ../plugin.py:382 +msgid "Clearing current EPG before import" msgstr "" -#: plugin.py:120 -msgid "Thursday" +#: ../plugin.py:375 +msgid "Consider setting \"Days Profile\"" msgstr "" -#: plugin.py:121 -msgid "Friday" +#: ../plugin.py:650 +msgid "Days Profile" msgstr "" -#: plugin.py:122 -msgid "Saturday" +#: ../filtersServices.py:231 +msgid "Delete all" msgstr "" -#: plugin.py:123 -msgid "Sunday" +#: ../filtersServices.py:230 +msgid "Delete selected" msgstr "" -#: plugin.py:330 +#: ../plugin.py:305 msgid "EPG Import Configuration" msgstr "" -#: plugin.py:333 plugin.py:555 -#, python-format -msgid "Last import: %s events" -msgstr "" - -#: plugin.py:334 plugin.py:639 plugin.py:726 -msgid "Cancel" +#: ../plugin.py:718 +msgid "EPG Import Log" msgstr "" -#: plugin.py:335 plugin.py:640 plugin.py:727 plugin.py:794 -msgid "Save" +#: ../plugin.py:592 +msgid "EPG Import Sources" msgstr "" -#: plugin.py:336 -msgid "Manual" +#: ../plugin.py:790 +#, python-format +msgid "EPG Import finished, %d events" msgstr "" -#: plugin.py:337 -msgid "Sources" +#: ../plugin.py:1097 ../plugin.py:1098 ../plugin.py:1104 ../plugin.py:1109 +#: ../plugin.py:1114 ../plugin.py:1119 ../plugin.py:1127 ../plugin.py:1137 +msgid "EPG-Importer" msgstr "" -#: plugin.py:364 -#, python-format +#: ../plugin.py:483 msgid "" -"Filtering:\n" -"%s Please wait!" +"EPGImport\n" +"Import of epg data is still in progress. Please wait." msgstr "" -#: plugin.py:365 -#, python-format +#: ../plugin.py:498 msgid "" -"Importing:\n" -"%s %s events" -msgstr "" - -#: plugin.py:404 -msgid "Automatic import EPG" -msgstr "" - -#: plugin.py:404 -msgid "When enabled, it allows you to schedule an automatic EPG update at the given days and time." -msgstr "" - -#: plugin.py:405 -msgid "Automatic start time" -msgstr "" - -#: plugin.py:405 -msgid "Specify the time for the automatic EPG update." -msgstr "" - -#: plugin.py:406 -msgid "When in deep standby" -msgstr "" - -#: plugin.py:406 -msgid "Choose the action to perform when the box is in deep standby and the automatic EPG update should normally start." -msgstr "" - -#: plugin.py:407 -msgid "Return to deep standby after import" -msgstr "" - -#: plugin.py:407 -msgid "This will turn back waked up box into deep-standby after automatic EPG import." +"EPGImport\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" msgstr "" -#: plugin.py:408 -msgid "Standby at startup" +#: ../plugin.py:507 +msgid "" +"EPGImport Plugin\n" +"Failed to start:\n" msgstr "" -#: plugin.py:408 -msgid "The waked up box will be turned into standby after automatic EPG import wake up." +#: ../plugin.py:105 +msgid "Friday" msgstr "" -#: plugin.py:409 -msgid "Choice days for start import" +#: ../filtersServices.py:168 ../plugin.py:520 +msgid "Ignore services list" msgstr "" -#: plugin.py:409 -msgid "You can select the day(s) when the EPG update must be performed." +#: ../plugin.py:580 +msgid "Import current source" msgstr "" -#: plugin.py:410 -msgid "Start import after booting up" +#: ../plugin.py:330 +#, python-format +msgid "" +"Importing: %s\n" +"%s events" msgstr "" -#: plugin.py:410 -msgid "Specify in which case the EPG must be automatically updated after the box has booted." +#: ../plugin.py:739 +msgid "Last import: " msgstr "" -#: plugin.py:411 -msgid "Load EPG only services in bouquets" +#: ../plugin.py:478 +#, python-format +msgid "Last import: %s events" msgstr "" -#: plugin.py:411 -msgid "To save memory you can decide to only load EPG data for the services that you have in your bouquet files." +#: ../plugin.py:473 +#, python-format +msgid "Last: %s %s, %d events" msgstr "" -#: plugin.py:412 +#: ../plugin.py:374 msgid "Load EPG only for IPTV channels" msgstr "" -#: plugin.py:412 -msgid "Specify in this case the EPG should be imported only for iptv lists." -msgstr "" - -#: plugin.py:413 -msgid "Consider setting \"Days Profile\"" -msgstr "" - -#: plugin.py:413 -msgid "When you decide to import the EPG after the box booted mention if the \"days profile\" must be take into consideration or not." -msgstr "" - -#: plugin.py:414 -msgid "Skip import on restart GUI" -msgstr "" - -#: plugin.py:414 -msgid "When you restart the GUI you can decide to skip or not the EPG data import." -msgstr "" - -#: plugin.py:415 -msgid "Show \"EPG import now\" in extensions" -msgstr "" - -#: plugin.py:415 -msgid "Display a shortcut \"EPG import now\" in the extension menu. This menu entry will immediately start the EPG update process when selected." -msgstr "" - -#: plugin.py:416 -msgid "Show \"EPGImport\" in plugins" -msgstr "" - -#: plugin.py:416 -msgid "Display a shortcut \"EPG import\" in the browser plugin.." +#: ../plugin.py:373 +msgid "Load EPG only services in bouquets" msgstr "" -#: plugin.py:418 +#: ../plugin.py:380 msgid "Load long descriptions up to X days" msgstr "" -#: plugin.py:418 -msgid "Define the number of days that you want to get the full EPG data, reducing this number can help you to save memory usage on your box. But you are also limited with the EPG provider available data. You will not have 15 days EPG if it only provide 7 days data." -msgstr "" - -#: plugin.py:419 -msgid "Run AutoTimer after import" -msgstr "" - -#: plugin.py:419 -msgid "You can start automatically the plugin AutoTimer after the EPG data update to have it refreshing its scheduling after EPG data refresh." +#: ../plugin.py:311 +msgid "Manual" msgstr "" -#: plugin.py:420 -msgid "Clearing current EPG before import" +#: ../plugin.py:101 +msgid "Monday" msgstr "" -#: plugin.py:420 -msgid "This will clear the current EPG data in memory before updating the EPG data. This allows you to always have a clean new EPG with the latest EPG data, for example in case of program changes between refresh, otherwise EPG data are cumulative." +#: ../plugin.py:493 +msgid "No active EPG sources found, nothing to do" msgstr "" -#: plugin.py:421 -msgid "Also apply \"channel id\" filtering on custom.channels.xml" +#: ../plugin.py:88 +msgid "Press OK" msgstr "" -#: plugin.py:421 -msgid "This is for advanced users that are using the channel id filtering feature. If enabled, the filter rules defined into /etc/epgimport/channel_id_filter.conf will also be applied on your /etc/epgimport/custom.channels.xml file." +#: ../filtersServices.py:197 +msgid "Really delete all list?" msgstr "" -#: plugin.py:422 -msgid "Execute shell command before import EPG" +#: ../plugin.py:369 +msgid "Return to deep standby after import" msgstr "" -#: plugin.py:422 -msgid "When enabled, then you can run the desired script before starting the import, after which the import of the EPG will begin." +#: ../plugin.py:381 +msgid "Run AutoTimer after import" msgstr "" -#: plugin.py:423 -msgid "Shell command name" +#: ../plugin.py:106 +msgid "Saturday" msgstr "" -#: plugin.py:423 -msgid "Enter shell command name." +#: ../plugin.py:310 ../plugin.py:558 ../plugin.py:638 ../plugin.py:696 +msgid "Save" msgstr "" -#: plugin.py:424 -msgid "Start import after standby" +#: ../filtersServices.py:283 ../plugin.py:521 +msgid "Select action" msgstr "" -#: plugin.py:424 -msgid "Start import after resuming from standby mode." +#: ../filtersServices.py:256 +msgid "Select service to add..." msgstr "" -#: plugin.py:425 -msgid "Hours after which the import is repeated" +#: ../plugin.py:377 +msgid "Show \"EPGImport\" in extensions" msgstr "" -#: plugin.py:425 -msgid "Number of hours (1-23, or 0 for no repeat) after which the import is repeated. This value is not saved and will be reset when the GUI restarts." +#: ../plugin.py:378 +msgid "Show \"EPGImport\" in plugins" msgstr "" -#: plugin.py:503 -msgid "Settings saved successfully !" +#: ../plugin.py:520 +msgid "Show log" msgstr "" -#: plugin.py:549 -msgid "unknown" +#: ../plugin.py:376 +msgid "Skip import on restart GUI" msgstr "" -#: plugin.py:550 -#, python-format -msgid "Last: %s %s, %d events" +#: ../plugin.py:312 +msgid "Sources" msgstr "" -#: plugin.py:560 -msgid "" -"EPGImport\n" -"Import of epg data is still in progress. Please wait." +#: ../plugin.py:370 +msgid "Standby at startup" msgstr "" -#: plugin.py:571 -msgid "No active EPG sources found, nothing to do" +#: ../plugin.py:372 +msgid "Start import after booting up" msgstr "" -#: plugin.py:575 -msgid "" -"EPGImport\n" -"Import of epg data will start.\n" -"This may take a few minutes.\n" -"Is this ok?" +#: ../plugin.py:107 +msgid "Sunday" msgstr "" -#: plugin.py:587 -msgid "" -"EPGImport Plugin\n" -"Failed to start:\n" +#: ../plugin.py:104 +msgid "Thursday" msgstr "" -#: plugin.py:603 -msgid "Show log" +#: ../plugin.py:102 +msgid "Tuesday" msgstr "" -#: plugin.py:641 -msgid "Import" +#: ../plugin.py:103 +msgid "Wednesday" msgstr "" -#: plugin.py:681 -msgid "EPG Import Sources" +#: ../plugin.py:368 +msgid "When in deep standby" msgstr "" -#: plugin.py:721 plugin.py:745 -msgid "Days Profile" +#: ../plugin.py:724 +msgid "Write to /tmp/epgimport.log" msgstr "" -#: plugin.py:751 +#: ../plugin.py:660 msgid "" "You may not use this settings!\n" "At least one day a week should be included!" msgstr "" -#: plugin.py:791 -msgid "Clear" -msgstr "" - -#: plugin.py:824 -msgid "EPG Import Log" -msgstr "" - -#: plugin.py:830 -msgid "Write to /tmp/epgimport.log" -msgstr "" - -#: plugin.py:846 -msgid "Last import: " -msgstr "" - -#: plugin.py:846 -msgid " events\n" +#: ../plugin.py:790 +msgid "" +"You must restart Enigma2 to load the EPG data,\n" +"is this OK?" msgstr "" -#: plugin.py:846 -msgid "" -"\n" -"Import of epg data will start.\n" -"This may take a few minutes.\n" -"Is this ok?" +#: ../plugin.py:60 +msgid "always" msgstr "" -#: plugin.py:896 -#, python-format -msgid "EPG Import finished, %d events" +#: ../plugin.py:63 +msgid "never" msgstr "" -#: plugin.py:896 -msgid "" -"You must restart Enigma2 to load the EPG data,\n" -"is this OK?" +#: ../plugin.py:62 +msgid "only automatic boot" msgstr "" -#: plugin.py:1038 -msgid "No source file found !" +#: ../plugin.py:61 +msgid "only manual boot" msgstr "" -#: plugin.py:1255 -msgid "Automated EPG Importer" +#: ../filtersServices.py:155 +msgid "press OK to save list" msgstr "" -#: plugin.py:1257 -msgid "EPG import now" +#: ../plugin.py:77 +msgid "skip the import" msgstr "" -#: plugin.py:1258 plugin.py:1264 plugin.py:1269 plugin.py:1274 plugin.py:1279 -#: plugin.py:1287 plugin.py:1297 -msgid "EPG-Importer" +#: ../plugin.py:76 +msgid "wake up and import" msgstr "" diff --git a/src/EPGImport/locale/it.mo b/src/EPGImport/locale/it.mo index 7812d17e50134bfe6b52bc31b6f19ddf5a6edc29..bb68ec55796f3acfc83c1ebecadfa4abb5801abd 100644 GIT binary patch delta 2425 zcmZ{kU2GIp6vuC|Vq02Dp#`=DdfQSzXs7N+p>zPDd|E=KK;su`neN_hM|Nj6Gqa^7 zhE);e1&LlIhL|AnL5W03G`{$O3C73^K_Ap$z>oOQ_+UX_h%wRse>)Xkdb4MKXJ*fx zbME<{-6MOSYcE`zU;4Vh+6Y?$Yo8^=W8m6ST(I7{Q-}w_(_k6+J2(gY3#%l>A7kCOp0&xSxR#A)USy%(ef}`LfAWx79c^Kpi zPJ%7qXCPnnOUMau1@0$7d_;XYUx-+c@oT_(a1iA82SGOVFvxsI!NtfgPQc+wJ^&eb z3FOA_K@>0k1bO1W!KGjo+QUNHK*q;G#;pMnA~u0MX&=}C4uRXjL!b#>1i8NyIu@wK z95@TW8gL%i3bIA3KuWL!WC2fr&K*Ly;N!Tz20fU#8$pcg!KDV=1ya%^$N~}0A`~6@I$mcyE6YmG9{UMNr9s@Z{r@;vLIY{Y#0PhATLC(ZN zRI?6j0aKs_Hi2J(%r{Yk{&V9EJis1NhHR`b0;`$FRQUYGkjF#50kV+OAcylTNNK(V_ko4q;B17m1%k7pqaY*}$3f13#)XBQ0Xcl1 zg4iOifGl7VECH!=CyeiU0LHGdqg?zIt%vo%C@Y7Q3xyQKS~&N@n4F70k+z~Ym7oJY zwML#|%lXwTST~H?@>DEnC9DfZIqre2fuVWD6~mRqb5Y(^Fv?Qg&9?FnhNt8(aaHMW zW-rh`&n_)&z#SEBhj9cc3L8GX;&6(i7CxZ5(+fY7;y6=aE(*sbVeGL9qd1uU;y(!> z3|N#7S4FSBRJl#RTGp*g%ay)T{)q0J6VX+bPwMf?^BcB$cFGzTy=lkwl=PK1YA2O1 zGqxXy-fYhG0-4Nvo^k@|yLm6E!l$;AHf5_{p(m>bbaJkzpQ%34FybDN&0G4nO>JzJ zwj>8_`Z98k4auGuqh}jYXps{X31_YSc!STXC^&BbvZR#-e6t zye*1VvBUBMW5BZ1XQDpea_nGS8dgS)9k4T5D~Tv4oww4;7^qWL)|8|9Tqf@u zNjIC#Ggz;P?AKpJYU|@2@s3!C z$c^8M+@mZnIns|%*ABRAsm2>qO944sEuE zGHQ>?s3g0)18UFo+Lfjpwljh9{5|1t%oMrGlxgJ*j%72!=u}K0Y|U*2beS^a&e)FC zrrbF^oUt7>H9|9`>NX`rN!!j$0Z?6zWouAL(sf3ahx!>R^wGw>`uY0py1e0tZdo#_ z-(K3H-(Qy3iNQWwtZ_he%O#@)klg6V;`4x&*QLC*YDxlI2f`?TA1QeSR9 zU)SgQfoBy*-r@EBzcQaP^k9COy(VSkj@tZXYY@*FDDO{{Z$}!;t_0 literal 11653 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

|J7yJs{?n|zciTEkSd(jZMjyZFvomE}rkpxb z+6-HK>82IY(LASYj;+>~$fyl6@d`=PxBAHL6(#g=*og*B82j%7NIZGpf2`OnXBgFE=Fz|ML=lJl0ci1tY(^oT@F zy2we$Uk{Ts-1D8Lv>Kpr8Z`}v8xxwJVRcTEx~-8CWc86E&_Ebhw7AmQ#w@Z z3(1*{hLtAGMF`OV#T@U zRk#SX4by(}kGoS8=o{+Z=FTWC< z;l*Jwp&;?;Aq{b&Y~#dM%B%S1gg!`37*?Ar<|Qnjgy|!Cr=M0&W*U?N*0u88t~5HI zaV97J&X@pQuWR0Ao@Jmnm}JXZGc3GyjQBr@-3ZTzYwyI=meV|?-Wajd-ZB@L@AB)# z3w}aQSP5;h_ATV$yJF%+0i~{E)~B8!p^-ddw>#g{2Ff)u9t1>>HJpi&syB9#5|b`z zm*qqs++xehAr3*6LPPBR;RJ;oY6QFtL>tu zDfpr~*z>>9Y=}RFqnoDXMX}d?6=%?0xWrM6Di)QAbVw#q{>rcopLLbLVx29lt46X& z)K^ISKZAduyRRbDoOw0zatVI+TvXUhNZ&Rpc9 z$+iwr02I$9axdoTEhRKx^d81uAB5J%|4NfYwVW5GAAA4i9j zxaUK?7SOM7Wo%ItOX|XWdi8sHhXTq1MAbfqJZs0rSjC*jsB~%tULn0?45Qvs($H1& z<(Cy-d7E-+NZ{hWy5LL!L70DgDv%z{DP%`bE3G8fPHtOjaxTJ`>PJLz?X_A>$SAtL zomD-pK07n9bUsIIwo6gP_J8XFX{K{W#bm1n^YArJYcqadM5z!AU(NO3Y&OF4ZNKO^W|1ontoYgxMalgo-$^WqE0NQhx^np-Ad%rG_|%F$=_j>$ z{ZD19P}h$_-A@@R=_vy8!-rBWzqsWus1}nYH2i*}(uBYTe;ZA%9RiQIo!WFhO(^SR zySh@n@7CzTsHE~48TBh`0_`lkRjojyGj~_E@%(SVS9Sucm_wIhs6Q;gqOa))ChBfn zwaV2;g^q8WnBRRfFWTAK(<)=qVdw-BJ3^5>t(N2f-IOl=Nn#Yu7J5}$5%kpX8egdLEk!05}RIQp`epLod4Dw>R${6QtaJE;~dg^SBp7CaH z9Akx@%SL6CDeD#|y#1v`>+erAq7>iRr-6CS!S_F{qeG;cup?-{h^K z*yracn9{bVl5&6<>g&9P&gza5*^AbzZeFmRBgulp$xFM2vjG_}kv9>SN}16Rzt%}$ zemA*YQB^v0D5!=LBK(EcqB@Nfn-&558BG?YQ=RlWE9NDWJ-cjPewkN%s8Ali;=PVo zxxf_%(Q1P1iuQih%3snCTiLcU-Zp(&6{<>;6V{Sr0e;)@34~J-p>q)#&K?ulewsd{ zEpOQEDbok8LWdVh>p&oc_>`r(kzOVL<02k#HgYIOo9PTam11Wz5J2|jbjbfaum!J{ z#MFvV48LI3w8$+}U86&J`I9r7bmt-#PqV#2>jhurrkrYL+4OqF)vr9fnJ6 zu9@?iKLQ->ekW{IFn4y7Gqfk@C5v7Ky#@;v{yhqG9g7vtf^mD9zvXIus+N3S)NNKk z2zE60sV1}t(j5!OY2yXc=N0Q=6pB#hBHnFuBqzslN{M$wXRXzwHWZfARw**BP^nmo z>f*qZ(dB6?$_LtL9H)`R312gG;w)zG`u1lMVTR*Wtxsh@E7+hGDOq(HnGIcGI#IPH M=Z>jqgJVSh4bYDDmjD0& diff --git a/src/EPGImport/locale/it.po b/src/EPGImport/locale/it.po old mode 100644 new mode 100755 index ccf4240..1b95c0a --- a/src/EPGImport/locale/it.po +++ b/src/EPGImport/locale/it.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: Italian Translation EPGImport Enigma2\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-24 11:56+0100\n" -"PO-Revision-Date: 2025-02-24 12:06+0100\n" +"POT-Creation-Date: 2023-01-16 06:40+0000\n" +"PO-Revision-Date: 2025-02-03 17:06+0100\n" "Last-Translator: madhouse\n" "Language-Team: egami-team\n" "Language: it_IT\n" @@ -17,458 +17,287 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.5\n" -"X-Poedit-Basepath: ..\n" +"X-Poedit-Basepath: .\n" "X-Poedit-SourceCharset: UTF-8\n" -"X-Poedit-SearchPath-0: .\n" +"X-Poedit-SearchPath-0: EPGConfig.py\n" +"X-Poedit-SearchPath-1: epgdat.py\n" +"X-Poedit-SearchPath-2: epgdat_importer.py\n" +"X-Poedit-SearchPath-3: EPGImport.py\n" +"X-Poedit-SearchPath-4: ExpandableSelectionList.py\n" +"X-Poedit-SearchPath-5: filtersServices.py\n" +"X-Poedit-SearchPath-6: gen_xmltv.py\n" +"X-Poedit-SearchPath-7: log.py\n" +"X-Poedit-SearchPath-8: OfflineImport.py\n" +"X-Poedit-SearchPath-9: plugin.py\n" +"X-Poedit-SearchPath-10: xmltvconverter.py\n" + +#: ../plugin.py:739 +msgid "" +"\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" +msgstr "" +"\n" +"Eseguo importazione dati EPG.\n" +"Questo processo potrebbe richiedere qualche minuto\n" +"Procedo?" -#: filtersServices.py:151 -msgid "Add Provider" -msgstr "Aggiungi provider" +#: ../plugin.py:739 +msgid " events\n" +msgstr " eventi\n" -#: filtersServices.py:152 +#: ../filtersServices.py:153 msgid "Add Channel" msgstr "Aggiungi canale" -#: filtersServices.py:154 -msgid "press OK to save list" -msgstr "premi OK per salvare la lista" - -#: filtersServices.py:173 plugin.py:603 -msgid "Ignore services list" -msgstr "Ignora l'elenco canali" - -#: filtersServices.py:202 -msgid "Really delete all list?" -msgstr "Sicuro di eliminare tutta la lista?" - -#: filtersServices.py:235 -msgid "Delete selected" -msgstr "Rimuovi selezionato" - -#: filtersServices.py:236 -msgid "Delete all" -msgstr "Cancella tutto" - -#: filtersServices.py:261 -msgid "Select service to add..." -msgstr "Seleziona il canale da aggiungere..." +#: ../filtersServices.py:152 +msgid "Add Provider" +msgstr "Aggiungi provider" -#: filtersServices.py:269 +#: ../filtersServices.py:264 msgid "All services provider" msgstr "Tutti i canali di ogni provider" -#: filtersServices.py:288 plugin.py:604 -msgid "Select action" -msgstr "Selezionare azione" - -#: plugin.py:75 -msgid "always" -msgstr "sempre" - -#: plugin.py:76 -msgid "only manual boot" -msgstr "solo avvio manuale" - -#: plugin.py:77 -msgid "only automatic boot" -msgstr "solo avvio automatico" - -#: plugin.py:78 -msgid "never" -msgstr "mai" +#: ../plugin.py:1095 +msgid "Automated EPG Importer" +msgstr "Importazione EPG automatica" -#: plugin.py:90 -msgid "wake up and import" -msgstr "sveglia e importa" +#: ../plugin.py:366 +msgid "Automatic import EPG" +msgstr "Importazione EPG automatica" -#: plugin.py:91 -msgid "skip the import" -msgstr "salta l'importazione" +#: ../plugin.py:367 +msgid "Automatic start time" +msgstr "Orario di avvio automatico" -#: plugin.py:108 -msgid "Press OK" -msgstr "Premere OK" +#: ../plugin.py:309 ../plugin.py:557 ../plugin.py:637 +msgid "Cancel" +msgstr "Annulla" -#: plugin.py:117 -msgid "Monday" -msgstr "Lunedì" +#: ../plugin.py:371 +msgid "Choice days for start import" +msgstr "Seleziona il giorno di avvio importazione" -#: plugin.py:118 -msgid "Tuesday" -msgstr "Martedì" +#: ../plugin.py:693 +msgid "Clear" +msgstr "Cancella" -#: plugin.py:119 -msgid "Wednesday" -msgstr "Mercoledì" +#: ../plugin.py:382 +msgid "Delete current EPG before import" +msgstr "Cancella EPG corrente prima dell’importazione" -#: plugin.py:120 -msgid "Thursday" -msgstr "Giovedì" +#: ../plugin.py:375 +msgid "Consider setting \"Days Profile\"" +msgstr "Considera l'impostazione \"Profilo giorni\"" -#: plugin.py:121 -msgid "Friday" -msgstr "Venerdì" +#: ../plugin.py:650 +msgid "Days Profile" +msgstr "Profilo dei giorni" -#: plugin.py:122 -msgid "Saturday" -msgstr "Sabato" +#: ../filtersServices.py:231 +msgid "Delete all" +msgstr "Cancella tutto" -#: plugin.py:123 -msgid "Sunday" -msgstr "Domenica" +#: ../filtersServices.py:230 +msgid "Delete selected" +msgstr "Rimuovi selezionato" -#: plugin.py:330 +#: ../plugin.py:305 msgid "EPG Import Configuration" msgstr "Configurazione di EPG Import" -#: plugin.py:333 plugin.py:555 -#, python-format -msgid "Last import: %s events" -msgstr "Ultima importazione: %s canali" - -#: plugin.py:334 plugin.py:639 plugin.py:726 -msgid "Cancel" -msgstr "Annulla" - -#: plugin.py:335 plugin.py:640 plugin.py:727 plugin.py:794 -msgid "Save" -msgstr "Salva" - -#: plugin.py:336 -msgid "Manual" -msgstr "Scarica" - -#: plugin.py:337 -msgid "Sources" -msgstr "Sorgenti" +#: ../plugin.py:718 +msgid "EPG Import Log" +msgstr "Registro importazione EPG" -#: plugin.py:364 -#, python-format -msgid "" -"Filtering:\n" -"%s Please wait!" -msgstr "" -"Filtraggio:\n" -"%s Attendi!" +#: ../plugin.py:592 +msgid "EPG Import Sources" +msgstr "Fonti importazione EPG" -#: plugin.py:365 +#: ../plugin.py:790 #, python-format -msgid "" -"Importing:\n" -"%s %s events" -msgstr "" -"Importazione: \n" -"%s %s eventi" - -#: plugin.py:404 -msgid "Automatic import EPG" -msgstr "Importazione EPG automatica" - -#: plugin.py:404 -msgid "" -"When enabled, it allows you to schedule an automatic EPG update at the given " -"days and time." -msgstr "" -"Se abilitata, consente di programmare un aggiornamento automatico dell'EPG " -"nei giorni e negli orari indicati." - -#: plugin.py:405 -msgid "Automatic start time" -msgstr "Orario di avvio automatico" - -#: plugin.py:405 -msgid "Specify the time for the automatic EPG update." -msgstr "Specificare l'ora per l'aggiornamento automatico dell'EPG." +msgid "EPG Import finished, %d events" +msgstr "Importazione EPG completata, %d eventi" -#: plugin.py:406 -msgid "When in deep standby" -msgstr "Quando il box è spento" +#: ../plugin.py:1097 ../plugin.py:1098 ../plugin.py:1104 ../plugin.py:1109 +#: ../plugin.py:1114 ../plugin.py:1119 ../plugin.py:1127 ../plugin.py:1137 +msgid "EPG-Importer" +msgstr "EPG-Importer" -#: plugin.py:406 +#: ../plugin.py:483 msgid "" -"Choose the action to perform when the box is in deep standby and the " -"automatic EPG update should normally start." +"EPGImport\n" +"Import of epg data is still in progress. Please wait." msgstr "" -"Selezionare l'azione da eseguire quando il box è in modalità standby " -"profondo; normalmente, l'aggiornamento automatico dell'EPG dovrebbe avviarsi." - -#: plugin.py:407 -msgid "Return to deep standby after import" -msgstr "Spegni di nuovo dopo l'importazione" +"EPG-Import\n" +"Importazione dati EPG in corso...\n" +"Attendere prego." -#: plugin.py:407 +#: ../plugin.py:498 msgid "" -"This will turn back waked up box into deep-standby after automatic EPG " -"import." +"EPGImport\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" msgstr "" -"Ciò riporterà il box riattivato in modalità deep standby dopo l'importazione " -"automatica dell'EPG." - -#: plugin.py:408 -msgid "Standby at startup" -msgstr "Standby all'avvio" +"EPG-Import\n" +"Eseguo importazione dati EPG.\n" +"Questo processo potrebbe richiedere qualche minuto\n" +"Procedo?" -#: plugin.py:408 +#: ../plugin.py:507 msgid "" -"The waked up box will be turned into standby after automatic EPG import wake " -"up." +"EPGImport Plugin\n" +"Failed to start:\n" msgstr "" -"Il box riattivato passerà in modalità standby dopo la riattivazione tramite " -"importazione automatica EPG." +"Plugin EPG-Import\n" +"Avvio fallito:\n" -#: plugin.py:409 -msgid "Choice days for start import" -msgstr "Seleziona il giorno di avvio importazione" +#: ../plugin.py:105 +msgid "Friday" +msgstr "Venerdì" -#: plugin.py:409 -msgid "You can select the day(s) when the EPG update must be performed." -msgstr "" -"È possibile selezionare il/i giorno/i in cui deve essere eseguito " -"l'aggiornamento EPG." +#: ../filtersServices.py:168 ../plugin.py:520 +msgid "Ignore services list" +msgstr "Ignora l'elenco canali" -#: plugin.py:410 -msgid "Start import after booting up" -msgstr "Avvia l'importazione dopo il boot" +#: ../plugin.py:580 +msgid "Import current source" +msgstr "Importa sorgente" -#: plugin.py:410 +#: ../plugin.py:330 +#, python-format msgid "" -"Specify in which case the EPG must be automatically updated after the box " -"has booted." +"Importing: %s\n" +"%s events" msgstr "" -"Specifica in quale caso l'EPG deve essere aggiornato automaticamente dopo " -"l'avvio del box." +"Importazione: %s\n" +"%s eventi" -#: plugin.py:411 -msgid "Load EPG only services in bouquets" -msgstr "Carica solo i servizi EPG nei bouquet" +#: ../plugin.py:739 +msgid "Last import: " +msgstr "Ultima importazione: " -#: plugin.py:411 -msgid "" -"To save memory you can decide to only load EPG data for the services that " -"you have in your bouquet files." -msgstr "" -"Per risparmiare memoria puoi decidere di caricare solo i dati EPG dei " -"servizi presenti nei tuoi file bouquet." +#: ../plugin.py:478 +#, python-format +msgid "Last import: %s events" +msgstr "Ultima importazione: %s canali" -#: plugin.py:412 +#: ../plugin.py:473 +#, python-format +msgid "Last: %s %s, %d events" +msgstr "Ultima importazione: %s %s, %d canali" + +#: ../plugin.py:374 msgid "Load EPG only for IPTV channels" msgstr "Carica gli EPG solo per i canali IPTV" -#: plugin.py:412 -msgid "Specify in this case the EPG should be imported only for iptv lists." -msgstr "" -"Specificare che in questo caso l'EPG deve essere importato solo per le liste " -"IPTV." - -#: plugin.py:413 -msgid "Consider setting \"Days Profile\"" -msgstr "Considera l'impostazione \"Profilo giorni\"" - -#: plugin.py:413 -msgid "" -"When you decide to import the EPG after the box booted mention if the \"days " -"profile\" must be take into consideration or not." -msgstr "" -"Quando si decide di importare l'EPG dopo l'avvio del box, specificare se il " -"\"profilo giorni\" deve essere preso in considerazione o meno." - -#: plugin.py:414 -msgid "Skip import on restart GUI" -msgstr "Salta importazione su riavvio sistema" - -#: plugin.py:414 -msgid "" -"When you restart the GUI you can decide to skip or not the EPG data import." -msgstr "" -"Quando si riavvia l'interfaccia grafica è possibile decidere se saltare o " -"meno l'importazione dei dati EPG." - -#: plugin.py:415 -msgid "Show \"EPG import now\" in extensions" -msgstr "Mostra \"EPGImport ora\" nelle estensioni" - -#: plugin.py:415 -msgid "" -"Display a shortcut \"EPG import now\" in the extension menu. This menu entry " -"will immediately start the EPG update process when selected." -msgstr "" -"Visualizza una scorciatoia \"EPG import now\" nel menu estensione. Questa " -"voce di menu avvierà immediatamente il processo di aggiornamento EPG quando " -"selezionata." - -#: plugin.py:416 -msgid "Show \"EPGImport\" in plugins" -msgstr "Mostra \"EPGImport\" nei plugins" - -#: plugin.py:416 -msgid "Display a shortcut \"EPG import\" in the browser plugin.." -msgstr "" -"Visualizza una scorciatoia \"Importazione EPG\" nel plugin del browser." +#: ../plugin.py:373 +msgid "Load EPG only services in bouquets" +msgstr "Carica solo i servizi EPG nei bouquet" -#: plugin.py:418 +#: ../plugin.py:380 msgid "Load long descriptions up to X days" msgstr "Carica le descrizioni dettagliate fino a (n) giorni" -#: plugin.py:418 -msgid "" -"Define the number of days that you want to get the full EPG data, reducing " -"this number can help you to save memory usage on your box. But you are also " -"limited with the EPG provider available data. You will not have 15 days EPG " -"if it only provide 7 days data." -msgstr "" -"Definisci il numero di giorni in cui vuoi ottenere i dati EPG completi, " -"ridurre questo numero può aiutarti a risparmiare memoria sul tuo box. Ma sei " -"anche limitato dai dati disponibili del provider EPG. Non avrai 15 giorni di " -"EPG se fornisce solo 7 giorni di dati." - -#: plugin.py:419 -msgid "Run AutoTimer after import" -msgstr "Avvia AutoTimer dopo l'aggiornamento" - -#: plugin.py:419 -msgid "" -"You can start automatically the plugin AutoTimer after the EPG data update " -"to have it refreshing its scheduling after EPG data refresh." -msgstr "" -"È possibile avviare automaticamente il plugin AutoTimer dopo l'aggiornamento " -"dei dati EPG, in modo che aggiorni la sua programmazione dopo " -"l'aggiornamento dei dati EPG." +#: ../plugin.py:311 +msgid "Manual" +msgstr "Scarica" -#: plugin.py:420 -msgid "Clearing current EPG before import" -msgstr "Cancella EPG corrente prima dell’importazione" +#: ../plugin.py:101 +msgid "Monday" +msgstr "Lunedì" -#: plugin.py:420 -msgid "" -"This will clear the current EPG data in memory before updating the EPG data. " -"This allows you to always have a clean new EPG with the latest EPG data, for " -"example in case of program changes between refresh, otherwise EPG data are " -"cumulative." -msgstr "" -"Questo cancellerà i dati EPG correnti in memoria prima di aggiornare i dati " -"EPG. Questo ti consente di avere sempre un nuovo EPG pulito con i dati EPG " -"più recenti, ad esempio in caso di modifiche al programma tra un " -"aggiornamento e l'altro, altrimenti i dati EPG sono cumulativi." +#: ../plugin.py:493 +msgid "No active EPG sources found, nothing to do" +msgstr "Fonti EPG attive non trovate: nessuna operazione svolta" -#: plugin.py:421 -msgid "Also apply \"channel id\" filtering on custom.channels.xml" -msgstr "Applicare anche il filtro \"ID canale\" su custom.channels.xml" +#: ../plugin.py:88 +msgid "Press OK" +msgstr "Premere OK" -#: plugin.py:421 -msgid "" -"This is for advanced users that are using the channel id filtering feature. " -"If enabled, the filter rules defined into /etc/epgimport/channel_id_filter." -"conf will also be applied on your /etc/epgimport/custom.channels.xml file." -msgstr "" -"Questo è per gli utenti avanzati che utilizzano la funzionalità di " -"filtraggio dell'ID canale. Se abilitata, le regole di filtro definite in /" -"etc/epgimport/channel_id_filter.conf saranno applicate anche al file /etc/" -"epgimport/custom.channels.xml." +#: ../filtersServices.py:197 +msgid "Really delete all list?" +msgstr "Sicuro di eliminare tutta la lista?" -#: plugin.py:422 -msgid "Execute shell command before import EPG" -msgstr "Eseguire il comando shell prima di importare EPG" +#: ../plugin.py:369 +msgid "Return to deep standby after import" +msgstr "Spegni di nuovo dopo l'importazione" -#: plugin.py:422 -msgid "" -"When enabled, then you can run the desired script before starting the " -"import, after which the import of the EPG will begin." -msgstr "" -"Se abilitata, è possibile eseguire lo script desiderato prima di avviare " -"l'importazione, dopodiché avrà inizio l'importazione dell'EPG." +#: ../plugin.py:381 +msgid "Run AutoTimer after import" +msgstr "Avvia AutoTimer dopo l'aggiornamento" -#: plugin.py:423 -msgid "Shell command name" -msgstr "Nome Comando Shell" +#: ../plugin.py:106 +msgid "Saturday" +msgstr "Sabato" -#: plugin.py:423 -msgid "Enter shell command name." -msgstr "Inserisci il nome del comando shell." +#: ../plugin.py:310 ../plugin.py:558 ../plugin.py:638 ../plugin.py:696 +msgid "Save" +msgstr "Salva" -#: plugin.py:424 -msgid "Start import after standby" -msgstr "Avvia l'importazione dopo lo standby" +#: ../filtersServices.py:283 ../plugin.py:521 +msgid "Select action" +msgstr "Selezionare azione" -#: plugin.py:424 -msgid "Start import after resuming from standby mode." -msgstr "Avvia l'importazione dopo la ripresa dalla modalità standby." +#: ../filtersServices.py:256 +msgid "Select service to add..." +msgstr "Seleziona il canale da aggiungere..." -#: plugin.py:425 -msgid "Hours after which the import is repeated" -msgstr "Ore dopo le quali l'importazione viene ripetuta" +#: ../plugin.py:377 +msgid "Show \"EPGImport\" in extensions" +msgstr "Mostra \"EPGImport\" nelle estensioni" -#: plugin.py:425 -msgid "" -"Number of hours (1-23, or 0 for no repeat) after which the import is " -"repeated. This value is not saved and will be reset when the GUI restarts." -msgstr "" -"Numero di ore (1-23, o 0 per nessuna ripetizione) dopo le quali " -"l'importazione viene ripetuta. Questo valore non viene salvato e verrà " -"reimpostato al riavvio della GUI." +#: ../plugin.py:378 +msgid "Show \"EPGImport\" in plugins" +msgstr "Mostra \"EPGImport\" nei plugins" -#: plugin.py:503 -msgid "Settings saved successfully !" -msgstr "Impostazioni salvate con successo!" +#: ../plugin.py:520 +msgid "Show log" +msgstr "Mostra il log" -#: plugin.py:549 -msgid "unknown" -msgstr "" +#: ../plugin.py:376 +msgid "Skip import on restart GUI" +msgstr "Salta importazione su riavvio sistema" -#: plugin.py:550 -#, python-format -msgid "Last: %s %s, %d events" -msgstr "Ultima importazione: %s %s, %d canali" +#: ../plugin.py:312 +msgid "Sources" +msgstr "Sorgenti" -#: plugin.py:560 -msgid "" -"EPGImport\n" -"Import of epg data is still in progress. Please wait." -msgstr "" -"EPG-Import\n" -"Importazione dati EPG in corso...\n" -"Attendere prego." +#: ../plugin.py:370 +msgid "Standby at startup" +msgstr "Standby all'avvio" -#: plugin.py:571 -msgid "No active EPG sources found, nothing to do" -msgstr "Fonti EPG attive non trovate: nessuna operazione svolta" +#: ../plugin.py:372 +msgid "Start import after booting up" +msgstr "Avvia l'importazione dopo il boot" -#: plugin.py:575 -msgid "" -"EPGImport\n" -"Import of epg data will start.\n" -"This may take a few minutes.\n" -"Is this ok?" -msgstr "" -"EPG-Import\n" -"Eseguo importazione dati EPG.\n" -"Questo processo potrebbe richiedere qualche minuto\n" -"Procedo?" +#: ../plugin.py:107 +msgid "Sunday" +msgstr "Domenica" -#: plugin.py:587 -msgid "" -"EPGImport Plugin\n" -"Failed to start:\n" -msgstr "" -"Plugin EPG-Import\n" -"Avvio fallito:\n" +#: ../plugin.py:104 +msgid "Thursday" +msgstr "Giovedì" -#: plugin.py:603 -msgid "Show log" -msgstr "Mostra il log" +#: ../plugin.py:102 +msgid "Tuesday" +msgstr "Martedì" -#: plugin.py:641 -msgid "Import" -msgstr "Importa" +#: ../plugin.py:103 +msgid "Wednesday" +msgstr "Mercoledì" -#: plugin.py:681 -msgid "EPG Import Sources" -msgstr "Fonti importazione EPG" +#: ../plugin.py:368 +msgid "When in deep standby" +msgstr "Quando il box è spento" -#: plugin.py:721 plugin.py:745 -msgid "Days Profile" -msgstr "Profilo dei giorni" +#: ../plugin.py:724 +msgid "Write to /tmp/epgimport.log" +msgstr "Scrittura in /tmp/epgimport.log" -#: plugin.py:751 +#: ../plugin.py:660 msgid "" "You may not use this settings!\n" "At least one day a week should be included!" @@ -476,44 +305,7 @@ msgstr "" "Non è possibile utilizzare queste impostazioni!\n" "Devi indicare almeno un giorno della settimana!" -#: plugin.py:791 -msgid "Clear" -msgstr "Cancella" - -#: plugin.py:824 -msgid "EPG Import Log" -msgstr "Registro importazione EPG" - -#: plugin.py:830 -msgid "Write to /tmp/epgimport.log" -msgstr "Scrittura in /tmp/epgimport.log" - -#: plugin.py:846 -msgid "Last import: " -msgstr "Ultima importazione: " - -#: plugin.py:846 -msgid " events\n" -msgstr " Eventi\n" - -#: plugin.py:846 -msgid "" -"\n" -"Import of epg data will start.\n" -"This may take a few minutes.\n" -"Is this ok?" -msgstr "" -"\n" -"Eseguo importazione dati EPG.\n" -"Questo processo potrebbe richiedere qualche minuto\n" -"Procedo?" - -#: plugin.py:896 -#, python-format -msgid "EPG Import finished, %d events" -msgstr "Importazione EPG completata, %d eventi" - -#: plugin.py:896 +#: ../plugin.py:790 msgid "" "You must restart Enigma2 to load the EPG data,\n" "is this OK?" @@ -521,43 +313,30 @@ msgstr "" "Richiesto il riavvio del sistema per caricare i dati EPG:\n" "Procedo?" -#: plugin.py:1038 -msgid "No source file found !" -msgstr "Nessun file sorgente trovato!" - -#: plugin.py:1255 -msgid "Automated EPG Importer" -msgstr "Importazione EPG automatica" - -#: plugin.py:1257 -msgid "EPG import now" -msgstr "Importa EPG ora" - -#: plugin.py:1258 plugin.py:1264 plugin.py:1269 plugin.py:1274 plugin.py:1279 -#: plugin.py:1287 plugin.py:1297 -msgid "EPG-Importer" -msgstr "EPG-Importer" - -#~ msgid "Path DB EPG" -#~ msgstr "Percorso DB EPG" - -#~ msgid "Choose Directory:" -#~ msgstr "Scegli la Directory:" +#: ../plugin.py:60 +msgid "always" +msgstr "sempre" -#~ msgid "Choose directory" -#~ msgstr "Scegli la directory" +#: ../plugin.py:63 +msgid "never" +msgstr "mai" -#~ msgid "Import from Git" -#~ msgstr "Import from Git" +#: ../plugin.py:62 +msgid "only automatic boot" +msgstr "solo avvio automatico" -#~ msgid "Do you want to update Source now?" -#~ msgstr "Do you want to update Source now?" +#: ../plugin.py:61 +msgid "only manual boot" +msgstr "solo avvio manuale" -#~ msgid "Update Aborted!" -#~ msgstr "Update Aborted!" +#: ../filtersServices.py:155 +msgid "press OK to save list" +msgstr "premi OK per salvare la lista" -#~ msgid "Source files Imported and List Updated!" -#~ msgstr "Source files Imported and List Updated!" +#: ../plugin.py:77 +msgid "skip the import" +msgstr "salta l'importazione" -#~ msgid "Import current source" -#~ msgstr "Importa sorgente" +#: ../plugin.py:76 +msgid "wake up and import" +msgstr "sveglia e importa" From d78da0ff54de8015254bddd97fba0a200c7efa9b Mon Sep 17 00:00:00 2001 From: jbleyel Date: Wed, 26 Feb 2025 17:28:36 +0100 Subject: [PATCH 07/14] Update po small fixes --- src/EPGImport/EPGConfig.py | 29 ++- src/EPGImport/OfflineImport.py | 3 +- src/EPGImport/gen_xmltv.py | 4 +- src/EPGImport/locale/cs.mo | Bin 1011 -> 5279 bytes src/EPGImport/locale/cs.po | 162 +++++++------- src/EPGImport/locale/hu.mo | Bin 5599 -> 5702 bytes src/EPGImport/locale/hu.po | 49 ++--- src/EPGImport/locale/nb.mo | Bin 0 -> 355 bytes src/EPGImport/locale/nb.po | 320 +++++++++++++++++++++++++++ src/EPGImport/locale/pl.mo | Bin 1905 -> 5177 bytes src/EPGImport/locale/pl.po | 100 ++++----- src/EPGImport/locale/ru.mo | Bin 6737 -> 6482 bytes src/EPGImport/locale/ru.po | 130 +++++------ src/EPGImport/locale/sk.mo | Bin 5021 -> 5294 bytes src/EPGImport/locale/sk.po | 164 +++++++------- src/EPGImport/locale/sv.mo | Bin 0 -> 4778 bytes src/EPGImport/locale/sv.po | 378 ++++++++++++++++++++++++++++++++ src/EPGImport/plugin.py | 64 +++--- src/EPGImport/xmltvconverter.py | 2 +- 19 files changed, 1060 insertions(+), 345 deletions(-) create mode 100644 src/EPGImport/locale/nb.mo create mode 100644 src/EPGImport/locale/nb.po create mode 100644 src/EPGImport/locale/sv.mo create mode 100644 src/EPGImport/locale/sv.po diff --git a/src/EPGImport/EPGConfig.py b/src/EPGImport/EPGConfig.py index f51a37d..0b4c0cb 100755 --- a/src/EPGImport/EPGConfig.py +++ b/src/EPGImport/EPGConfig.py @@ -92,17 +92,24 @@ def parse(self, filterCallback, downloadedFile): context = iterparse(stream) for event, elem in context: if elem.tag == "channel": - id = elem.get("id") - if id: - id = id.lower() - ref = elem.text - if ref: - ref = str(ref) - if filterCallback(ref): - if id in self.items: - self.items[id].append(ref) - else: - self.items[id] = [ref] + channel_id = elem.get("id").lower() + ref = str(elem.text or '').strip() + + if not channel_id or not ref: + continue # Skip empty values + if ref: + if filterCallback(ref): + """ + if channel_id in self.items: + self.items[channel_id].append(ref) + else: + self.items[channel_id] = [ref] + """ + if channel_id in self.items: + self.items[channel_id].append(ref) + self.items[channel_id] = list(dict.fromkeys(self.items[channel_id])) # Ensure uniqueness + else: + self.items[channel_id] = [ref] elem.clear() except Exception as e: print(f"[EPGImport] Failed to parse {downloadedFile} Error: {e}", file=log) diff --git a/src/EPGImport/OfflineImport.py b/src/EPGImport/OfflineImport.py index 8b178a5..5df8394 100755 --- a/src/EPGImport/OfflineImport.py +++ b/src/EPGImport/OfflineImport.py @@ -14,7 +14,7 @@ # 2) At the command line go to the parent directory of EPGImport: # 3) cd /usr/lib/enigma2/python/Plugins/Extensions # 4) Now run as a module from the command line: -# 5) python -m EPGImport.OfflineImport e.g. python -m EPGImport.OfflineImport /etc/rytec.sources.xml (> /tmp.log) +# 5) python -m EPGImport.OfflineImport e.g. python -m EPGImport.OfflineImport /etc/rytec.sources.xml (> /tmp.log) # 6) Reinstate your renamed __init__.py # # called modules EPGImport, epgdat, epgdat_importer, log @@ -31,6 +31,7 @@ class FakeEnigma: def getInstance(self): return self + """ def load(self): print("...load...") diff --git a/src/EPGImport/gen_xmltv.py b/src/EPGImport/gen_xmltv.py index cf8f59f..679c405 100644 --- a/src/EPGImport/gen_xmltv.py +++ b/src/EPGImport/gen_xmltv.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import -from __future__ import print_function from . import xmltvconverter date_format = '%Y%m%d%H%M%S' @@ -29,7 +27,7 @@ def new(): - 'Factory method to return main class instance' + """Factory method to return main class instance""" return Gen_Xmltv() diff --git a/src/EPGImport/locale/cs.mo b/src/EPGImport/locale/cs.mo index 81796a2b9401f21a316743f7a1f4ae75915cf316..2c3de3de878395549d38302521ff91ef8b2a94ed 100644 GIT binary patch literal 5279 zcmb`KTZ|-C8GsLn7a9-*!5iS&kyV84-r2>4b+`=6?f}E?T-X_QarJ@IU1z#$ySh$W zx1syVXyPR}KA32N8!;0e2*#0w#Q3y}NlynQ;mrp^B4~WjcuCY4jXe4NRneM;^{jYiB+RMJ9IF51N$a&ADN*VZt%lP5=^}CgN82$#n58n13rLKT?z$@V_ zydIu_H^4glAncX=7UWMo%a5%8L&#FmNX9mR74!4D_zV>Je^hb+MXuMO ztn(K91ib0;V!b0JABMLvz66<~`cT&W3Y7Wh;78$0<@48}nD)0&_WeB+{r&+(pQ};+ zW3UEgy+@$DzXDN7g;4a*;7#xwQ1p8a%KRTgnfFu3lIkTWdjA5-x^KWo;9GD8&f!dX z|4UH(@-=uJ+<@1>O(^zy5sE*44rTw>;8m9>^+&kK{l9R6?Ef%I<$i+l-V;#lm_gb9 zyHM=&VmbZ_6nXyu#a@3a?{8p{+ql0OYB&$sN}Vm4L9xr1p{)0PD0=<`ihQp?@!PMU z#D@rfFSrVxhR5Nj;j>Wme-%o6{u0W%e}%H{l^8+Ry9J7W?t&ucsgiy8N$#I1IfR&2 z{RxWw{|RN^OFmHedlqW$7vNzS!fE&%6ggglVvjeW$o)Gga{e94J~tzbhI3G!ufY>A zhVuT;;9mGLl=%2Fl=ZH_sp6mOpxobB@)js|+6P6yCAbPhI0pw%;^K0QE4&(tKd*-$ zfrsF&a0#-NdJ^)AdI}OAc}|L4;(IynE0Dh*$szm6v5)hf{7R{V zP-@i?&RaM!OMdLl~^6TZuWxi)4bwpOR zQRX*nth?jUG;tl1SWnL_Kcow3WO6a=H?&^}$kU0zTsocj9jlI-u)$VGTaJZMpcm^k z7ZviCFRP=0HBs^FhfUo`qX=R19qX3ac0AFAu?UNxNg`9Hj>&@Pu;vGLN=@FWV>Yk} zMhSw^Wz0weUDS>)`lt)n{AL=N#CPH3gA=YfdAs7$2+K?kt@)uJx2(5M@AXRi%H!(T zO57CJ7jq+yi4$?9ABwq~k&WY;Ud9x$)$7JjYO>fnzyCjl!FWYBN}GOIdC*`uPbaSM z$V^2&82NZf%{N0a{5T>4KTcF3&DcS4Zj*8pcg*O$ab<5jvc(A#C#C0RwAyvIdy>4z z-=rN+I8%h23xi%BG4so-r*xx;+*s{?K9&+&)?NC=)F$PWz!4>$jT@2Q6%WTc?TWUK z=b@+;OqiNLEx1s0UUb?t5xN#vr_EYI`1*O$sFP`^C6iW3lxX*(6~pTy(~8NgTFC=97mgq; z?@Fa*3*&jUT1~CA+`66``*=!1%RZS{k_OE$`na2iLHR5oR+*bO~m}1OY^#iS9D6kfA=ujHht!?YL<{zH{c#;oGMV zOix!NC9A8E3FE*dE}GG&O(fOzo`&C!&Hb_Mho)0&^zUP4*i22+R#&a*u*2wnMmOTh z!u-PAj!*|`(-n#;QODZ7E}JLB;Qnr4{P1o`;0QO}``GG()x$gUM20mRRp-Kn^YTh^ zxb7#F$Ey$7&{CU4xMgQOKhby84pmfTrHjV~Thymf)Rp~_p6GOE_`>i4K@nvp(c$*F zwhR1r(HLu$NAj4;bk_}^9&CE;liK!_6d@Or43i_7in1?CET9zP^1fN~LY+zG)3M&swA}mrK>DUdQxJQgE;-k4!sB zQHV>{mEzVNK26y#1h5@-CNskeBwI*C5S8-ERMAzn7Y<*TqTWf1&2Q97zP!$cn^rA( zaf0*kOV+D%A!cxC-9GFH`?|r3NsiFwmOr&_Wv7Nh*&C8cHBjB z)-%LefQM5_lV&Ft}*zJ-Tmb0^7Hw z#_$3eEkTHd{7lv?tRrI5!t-4kG;rP~K~@q!Zc`qJ13LLqH!_)*>Ubx-$?_gf5{L9= z)#6|?qZSd3x)l)PMV5A5i+Ik6N46uoB0Id`cSue*ie^PbjS~4kb9{Tl#CVz5-t~g3 zyTqu+0>@gC$aNbbq-i(&@l9dWbf0eXML{=gv>NicOJYj}lh1)Oj*RS+nvEDcz!I#~kqyqwr|cY$2A;#5l^O)_lNR)g11%; z3sX%3bP!S32j>$h+Y$e!?NJLU^@laej}PfFdh`K8abmyijWRpr31M8JP9n&z29^yk zcs&*RZGMX^o9rbN86+IFlHz%kZf{6_Oj?*iL_VbS)s9YrJbRP2Avbg?k%*K literal 1011 zcmZ9K?@JUx7{{kFtApN1L{Q;FffzEjyIv`4SCpv;YL{LnK`+MJac^yJX4#oNJbK-` zd|41e;G00g|Dadh`EU3))PK--yfdv~zkGI{d49}~=V)l)6T>J$x1bcd3jKiG_yt{q zenU5)!E=lafy3ZkFaQU^HSiMnw!bdH+n8^IF8(w40s0ONWA0yI>^k@obomKq z@_Yqd-fy6bKLBrnKl}3|5UGxR@yjkk=b_8c0CWX%!=1|wm+N>uEv#+|o7Blzkz2A! zQ?l9WJS{XcjrojLn@L>caM9}Q!m2Lvx-@K2i-=OKVv3}x=Sk)q4HX$#P3shiwqZ*` z6(VIPA*)uXXroQS_KFr6g9*iwUQMxSWlkGf+XQK``nn+%B?=W}=0aRFtLA!3)-7L% z_?pZOs)i(05;sM_v$Tj4>|$a(823sm(jA2_nK+5~vm!RUrb8-KU)1*r5$4_}%52Vwc)m>0lrQ5ef>xlkq*R_7sY z$vkhvtl~n%lGmhY{%;JaZc0lFOS7kk@UdlFyOj#r+8MTG<+i<_r6N(&RBwpfNLzVT zo8wcbey-}K%=xUU>j>8t($q#`OVt!lm-0ECH%&+i_kyXZOiWYnw=$|K75^SRA&){D zRgWtHjf{|bR4F^74`VQ0TIcgpN%Vj_R8`9;v4o2q4@#{2Q5X^@P4up_-_T^T;&9J) zcU9+rE#po^OX9cxx&@)T*qg|`-an>g(cS4B7)w#Ai$-UkGMy!+4Y)Q^vff}TMR!Ma vcl*)omC4XTSU+2^*SEUWTT#Nbk#2SN+-9nWUlMAyGga8me$#bQt6+Zs0h}Cv diff --git a/src/EPGImport/locale/cs.po b/src/EPGImport/locale/cs.po index 6a4937e..cf647ce 100644 --- a/src/EPGImport/locale/cs.po +++ b/src/EPGImport/locale/cs.po @@ -1,20 +1,24 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-01 17:07+0100\n" -"PO-Revision-Date: 2023-01-19 15:36+0100\n" +"POT-Creation-Date: 2023-01-16 06:40+0000\n" +"PO-Revision-Date: 2025-02-26 06:58+0100\n" "Last-Translator: Warder \n" "Language-Team: \n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : 2);\n" -"X-Generator: Poedit 3.2.2\n" -"X-Poedit-Basepath: .\n" +"X-Generator: Poedit 3.5\n" -#: ../plugin.py:743 +#: ../plugin.py:739 msgid "" "\n" "Import of epg data will start.\n" @@ -26,89 +30,89 @@ msgstr "" "To může trvat několik minut.\n" "Je to v pořádku?" -#: ../plugin.py:743 +#: ../plugin.py:739 msgid " events\n" msgstr " dění\n" -#: ../filtersServices.py:149 +#: ../filtersServices.py:153 msgid "Add Channel" msgstr "Přidat kanál" -#: ../filtersServices.py:148 +#: ../filtersServices.py:152 msgid "Add Provider" msgstr "Přidat poskytovatele" -#: ../filtersServices.py:260 +#: ../filtersServices.py:264 msgid "All services provider" msgstr "Všichni poskytovatelé služeb" -#: ../plugin.py:1099 -msgid "Automated EPG Import" -msgstr "Automatizovaný EPG Import" +#: ../plugin.py:1095 +msgid "Automated EPG Importer" +msgstr "Automatizovaný dovozce EPG" -#: ../plugin.py:368 +#: ../plugin.py:366 msgid "Automatic import EPG" msgstr "Automatický import EPG" -#: ../plugin.py:369 +#: ../plugin.py:367 msgid "Automatic start time" msgstr "Čas automatického spuštění" -#: ../plugin.py:311 ../plugin.py:559 ../plugin.py:639 +#: ../plugin.py:309 ../plugin.py:557 ../plugin.py:637 msgid "Cancel" msgstr "Zrušit" -#: ../plugin.py:373 +#: ../plugin.py:371 msgid "Choice days for start import" msgstr "Dny výběru pro zahájení importu" -#: ../plugin.py:695 +#: ../plugin.py:693 msgid "Clear" msgstr "Vymazat" -#: ../plugin.py:384 +#: ../plugin.py:382 msgid "Clearing current EPG before import" msgstr "Vymazání aktuálního EPG před importem" -#: ../plugin.py:377 +#: ../plugin.py:375 msgid "Consider setting \"Days Profile\"" msgstr "Zvažte nastavení \"Profil dnů\"" -#: ../plugin.py:652 +#: ../plugin.py:650 msgid "Days Profile" msgstr "Profil dnů" -#: ../filtersServices.py:227 +#: ../filtersServices.py:231 msgid "Delete all" msgstr "Smazat vše" -#: ../filtersServices.py:226 +#: ../filtersServices.py:230 msgid "Delete selected" msgstr "Odstranit vybrané soubory" -#: ../plugin.py:307 +#: ../plugin.py:305 msgid "EPG Import Configuration" msgstr "Konfigurace importu EPG" -#: ../plugin.py:720 +#: ../plugin.py:718 msgid "EPG Import Log" msgstr "Protokol importu EPG" -#: ../plugin.py:594 +#: ../plugin.py:592 msgid "EPG Import Sources" msgstr "Zdroje importu EPG" -#: ../plugin.py:794 +#: ../plugin.py:790 #, python-format msgid "EPG Import finished, %d events" msgstr "Import EPG dokončen, %d události" -#: ../plugin.py:1101 ../plugin.py:1102 ../plugin.py:1108 ../plugin.py:1113 -#: ../plugin.py:1118 ../plugin.py:1123 ../plugin.py:1131 ../plugin.py:1141 -msgid "EPG-Import" -msgstr "EPG-Import" +#: ../plugin.py:1097 ../plugin.py:1098 ../plugin.py:1104 ../plugin.py:1109 +#: ../plugin.py:1114 ../plugin.py:1119 ../plugin.py:1127 ../plugin.py:1137 +msgid "EPG-Importer" +msgstr "EPG-Importer" -#: ../plugin.py:485 +#: ../plugin.py:483 msgid "" "EPGImport\n" "Import of epg data is still in progress. Please wait." @@ -116,7 +120,7 @@ msgstr "" "EPGImport\n" "Import dat epg stále probíhá. Počkejte prosím." -#: ../plugin.py:500 +#: ../plugin.py:498 msgid "" "EPGImport\n" "Import of epg data will start.\n" @@ -128,7 +132,7 @@ msgstr "" "To může trvat několik minut.\n" "Je to v pořádku?" -#: ../plugin.py:509 +#: ../plugin.py:507 msgid "" "EPGImport Plugin\n" "Failed to start:\n" @@ -136,19 +140,19 @@ msgstr "" "EPGImport Plugin\n" "Nepodařilo se spustit:\n" -#: ../plugin.py:100 +#: ../plugin.py:105 msgid "Friday" msgstr "Pátek" -#: ../filtersServices.py:164 ../plugin.py:522 +#: ../filtersServices.py:168 ../plugin.py:520 msgid "Ignore services list" msgstr "Seznam ignorovaných služeb" -#: ../plugin.py:582 +#: ../plugin.py:580 msgid "Import current source" msgstr "Import aktuálního zdroje" -#: ../plugin.py:332 +#: ../plugin.py:330 #, python-format msgid "" "Importing: %s\n" @@ -157,129 +161,129 @@ msgstr "" "Import: %s\n" "%s události" -#: ../plugin.py:743 +#: ../plugin.py:739 msgid "Last import: " msgstr "Poslední import: " -#: ../plugin.py:480 +#: ../plugin.py:478 #, python-format msgid "Last import: %s events" msgstr "Poslední import: %s události" -#: ../plugin.py:475 +#: ../plugin.py:473 #, python-format msgid "Last: %s %s, %d events" msgstr "Poslední cena: %s %s, %d události" -#: ../plugin.py:376 +#: ../plugin.py:374 msgid "Load EPG only for IPTV channels" msgstr "Načíst EPG pouze pro IPTV kanály" -#: ../plugin.py:375 +#: ../plugin.py:373 msgid "Load EPG only services in bouquets" msgstr "Načíst služby pouze EPG v kyticích" -#: ../plugin.py:382 +#: ../plugin.py:380 msgid "Load long descriptions up to X days" msgstr "Načtení dlouhých popisů až X dní" -#: ../plugin.py:313 +#: ../plugin.py:311 msgid "Manual" msgstr "Manuální" -#: ../plugin.py:96 +#: ../plugin.py:101 msgid "Monday" msgstr "Pondělí" -#: ../plugin.py:495 +#: ../plugin.py:493 msgid "No active EPG sources found, nothing to do" msgstr "Nebyly nalezeny žádné aktivní zdroje EPG, není co dělat" -#: ../plugin.py:83 +#: ../plugin.py:88 msgid "Press OK" msgstr "Stiskněte OK" -#: ../filtersServices.py:193 +#: ../filtersServices.py:197 msgid "Really delete all list?" msgstr "Opravdu smazat všechny seznamy?" -#: ../plugin.py:371 +#: ../plugin.py:369 msgid "Return to deep standby after import" msgstr "Návrat do hlubokého pohotovostního režimu po importu" -#: ../plugin.py:383 +#: ../plugin.py:381 msgid "Run AutoTimer after import" msgstr "Spuštění automatického časovače po importu" -#: ../plugin.py:101 +#: ../plugin.py:106 msgid "Saturday" msgstr "Sobota" -#: ../plugin.py:312 ../plugin.py:560 ../plugin.py:640 ../plugin.py:698 +#: ../plugin.py:310 ../plugin.py:558 ../plugin.py:638 ../plugin.py:696 msgid "Save" msgstr "Uložit" -#: ../filtersServices.py:279 ../plugin.py:523 +#: ../filtersServices.py:283 ../plugin.py:521 msgid "Select action" msgstr "Vyberte akci" -#: ../filtersServices.py:252 +#: ../filtersServices.py:256 msgid "Select service to add..." msgstr "Vyberte službu, kterou chcete přidat..." -#: ../plugin.py:379 +#: ../plugin.py:377 msgid "Show \"EPGImport\" in extensions" msgstr "Zobrazit \"EPGImport\" v rozšířeních" -#: ../plugin.py:380 +#: ../plugin.py:378 msgid "Show \"EPGImport\" in plugins" msgstr "Zobrazit \"EPGImport\" v pluginech" -#: ../plugin.py:522 +#: ../plugin.py:520 msgid "Show log" msgstr "Zobrazit log" -#: ../plugin.py:378 +#: ../plugin.py:376 msgid "Skip import on restart GUI" msgstr "Přeskočit import při restartování grafického uživatelského rozhraní" -#: ../plugin.py:314 +#: ../plugin.py:312 msgid "Sources" msgstr "Zdroje" -#: ../plugin.py:372 +#: ../plugin.py:370 msgid "Standby at startup" msgstr "Pohotovostní režim při spuštění" -#: ../plugin.py:374 +#: ../plugin.py:372 msgid "Start import after booting up" msgstr "Spustit import po spuštění" -#: ../plugin.py:102 +#: ../plugin.py:107 msgid "Sunday" msgstr "Neděle" -#: ../plugin.py:99 +#: ../plugin.py:104 msgid "Thursday" msgstr "Čtvrtek" -#: ../plugin.py:97 +#: ../plugin.py:102 msgid "Tuesday" msgstr "Úterý" -#: ../plugin.py:98 +#: ../plugin.py:103 msgid "Wednesday" msgstr "Středa" -#: ../plugin.py:370 +#: ../plugin.py:368 msgid "When in deep standby" msgstr "V hlubokém pohotovostním režimu" -#: ../plugin.py:726 +#: ../plugin.py:724 msgid "Write to /tmp/epgimport.log" msgstr "Napište do /tmp/epgimport.log" -#: ../plugin.py:662 +#: ../plugin.py:660 msgid "" "You may not use this settings!\n" "At least one day a week should be included!" @@ -287,7 +291,7 @@ msgstr "" "Toto nastavení nesmíte použít!\n" "Alespoň jeden den v týdnu by měl být zahrnut!" -#: ../plugin.py:794 +#: ../plugin.py:790 msgid "" "You must restart Enigma2 to load the EPG data,\n" "is this OK?" @@ -295,30 +299,30 @@ msgstr "" "Musíte restartovat Enigma2 pro načtení dat EPG,\n" "je to v pořádku?" -#: ../plugin.py:55 +#: ../plugin.py:60 msgid "always" msgstr "vždy" -#: ../plugin.py:58 +#: ../plugin.py:63 msgid "never" msgstr "nikdy" -#: ../plugin.py:57 +#: ../plugin.py:62 msgid "only automatic boot" msgstr "pouze automatické spouštění" -#: ../plugin.py:56 +#: ../plugin.py:61 msgid "only manual boot" msgstr "pouze ruční spuštění" -#: ../filtersServices.py:151 +#: ../filtersServices.py:155 msgid "press OK to save list" msgstr "stisknutím tlačítka OK uložte seznam" -#: ../plugin.py:72 +#: ../plugin.py:77 msgid "skip the import" -msgstr "Přeskočit import" +msgstr "přeskočit import" -#: ../plugin.py:71 +#: ../plugin.py:76 msgid "wake up and import" -msgstr "Probuzení a import" +msgstr "probuzení a import" diff --git a/src/EPGImport/locale/hu.mo b/src/EPGImport/locale/hu.mo index a13748930708aa7af68615b63e5693ac63d0a2ab..d1947243568c05d4c7c0e87fc7538b2b49c606ce 100644 GIT binary patch delta 1880 zcmYk+TTC2P9LMpqT-s8)3oKPCwsu5|EwH$NP^9!i1xrayX(aVhHI6VY8} zVoYpI)U+LIGzw-@6Ri;+x)0W;8n;%Bn)px?LzHSwjEN>SsWG8Xet*l_^n}Cb%y8z+ z`TzfCxZHfPC9~u&J7sA7^o{i15@T$9qKsd(U&@Uc#9wg@Hmx$I5<74m?#5g20N#qv z;Ch@X`Wo_MPV-Cae~2t)zQhJ&GUhu5o4N5fYQgH&#;nCnScO}019qbi51}8Q#yc>H zdhR6d#&_`$UPfME+SoNqn-SE;j-WO?iFLf+yu`rEjd@fA9~I4`Hnxab=sMnon<`co z>?=BmP24|-OfhM!!xvHW-^O}8U;O(bD!IR*m-m}L7%0MjQ4v)U{_WU`T5t%pz{5yT zCWgwuQQU+tqau7CHUDGOyibrN&3RPhU!&Gt!XdnlnH~lM6jTe$qH1#tYw=aA#w;pj zXHg~j3blbB(d#khPaNlZnK~5C0O?fZGpLQv;hlI68*vev(BmWj+EELI)s6@8ZX8BM zGL5bHJgNi>s9JxIHTW|s)mMwIW_P;385MCGZp8;tnTVqzpTQ}-P(}XR8FUe*HV{T# zPoZl69^Qi&P?7zG0W9S$y5CmxG2F%V3#jKmLuK+BYTae*Ko4othC5LidMLx-UItUB zlw?s4d|UJq>VcoI6aT?>Y@$A0_#`UQ*Kq*Ppd!7B58yIt{!S+8{v;}cY1ARi9Alu` zzk;pkqIUKRu(igzHOpty*U7O&57MfD2rWf=UMk`VEGw!47%#h(iQ#SRFW;|dT74n+40q5u#D&N@P&?O&{lD#DKqigPttqd~+{Q@XO$l91AMH+hn=WXbbWWJL zkG_SjLaFIU-`sSP_!J6-*@h}uGhIzN)qzkWg*Ugx!bfQpZ>ebA^u5JvQA%&j&6QV{ zRzDk0So?=Z4qKz+b}Z&Za&NBL;aT4>FuZSnbTXbuHCW-8H5r)>g>&Cjob$LRD(l@# zmHx-uI|IS?Kzo-J+};xmHV4~+!LJKAI2xbCsWr`^$YN9rC8>~msHVr7}(xHA?`SsksNC%p&V z4b>anF>i~{ia6s=>WdS}{FzX~agTe?dZO-L-xfDYKjrH$$-m()`I)FRF&&sFdjA<@ig^L|7?U+G(%41ERn!Z=#&W!coAEYo$2AOK&0WUSqJ{Ti zD{9^-9>j4xfo~xTm|u{oW((0PupOv?!+1CAnGB$-8{~RjtmvJ|~j}PJvY{K7B0a=`*0_X{le?1tWLpyOXf(xkB zFQd-(E7Xp@L#6m9)WW4iSA#oI&-dY8JdDaj5|7~mK7*fQ6x-OPG8V5U|LRE5aSy(O z58)@M1%AK~{QI->LZ!L^Td)}w&`CUn*({AG zXsnVmIy7{Zyq@N7zjzDSnCYsm-V; z_ztSFp!H1y=pPK3_U{1rzfbw!a)^#jsxrYH^p8q6{hy+vQ2KRbDi5sVfAXX8?|^=B zD!TCg&MhpW>bF@=<&yfB1pLbvRwH+-q@pOdsVrEK8!o?E5EvYJ_>tsHYBpodM_O9F zI~DcWvCzP*V`rvQZs@R`abi|99F2y;ZJ}_)itLYt+k)XpI9xt592#}zr_=u)7wHT| z!l7t~73qw%w+F*f#vQlQnb6p*?WPlUCN&$g`V!8h)ng}|XCw1={9Nknp?K1sPP9y> zk_Q;w<;LgixDy(4>}1Sh*#8Y)9~-ky&y62_+6qot_+Qh# z(Qa$K*}K=ZcK2G5#%^!5a%)kvCF=cAIb1jDxKk63Yt7rP~# zsIsR0)UMCXt<@A4)TdnQbZT)izhqC@?o>W!O{SFCY?BMbKLxu OOny1-, YEAR. # +#, fuzzy msgid "" msgstr "" -"Project-Id-Version: EPGImport v1.3\n" +"Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-01-16 06:40+0000\n" -"PO-Revision-Date: 2018-10-26 18:55+0200\n" -"Last-Translator: Alec \n" -"Language-Team: alec \n" -"Language: hu_HU\n" +"PO-Revision-Date: 2025-02-26 07:00+0100\n" +"Last-Translator: Warder \n" +"Language-Team: \n" +"Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.2\n" +"X-Generator: Poedit 3.5\n" #: ../plugin.py:739 msgid "" @@ -28,7 +28,7 @@ msgstr "" "\n" "Az epg adatok importálása megkezdődik.\n" "Ez néhány percet vesz igénybe.\n" -"Rendben van ez így?" +"Kezdődhet az importálás?" #: ../plugin.py:739 msgid " events\n" @@ -36,7 +36,7 @@ msgstr " műsor információk\n" #: ../filtersServices.py:153 msgid "Add Channel" -msgstr "Csatorna hozzáadása" +msgstr "Csatorna hozzáadása lehetőségre" #: ../filtersServices.py:152 msgid "Add Provider" @@ -60,11 +60,11 @@ msgstr "Importálás kezdési ideje" #: ../plugin.py:309 ../plugin.py:557 ../plugin.py:637 msgid "Cancel" -msgstr "Mégse" +msgstr "Mégsem" #: ../plugin.py:371 msgid "Choice days for start import" -msgstr "Milyen napokon történjen az Importálás" +msgstr "Milyen napokon történjen az Import" #: ../plugin.py:693 msgid "Clear" @@ -72,7 +72,7 @@ msgstr "Törlés" #: ../plugin.py:382 msgid "Clearing current EPG before import" -msgstr "Az aktuális EPG törlése az importálás előtt" +msgstr "Az aktuális EPG törlése importálás előtt" #: ../plugin.py:375 msgid "Consider setting \"Days Profile\"" @@ -84,7 +84,7 @@ msgstr "Napok profil" #: ../filtersServices.py:231 msgid "Delete all" -msgstr "Az összes törlése" +msgstr "Összes törlése" #: ../filtersServices.py:230 msgid "Delete selected" @@ -92,7 +92,7 @@ msgstr "Kiválasztott törlése" #: ../plugin.py:305 msgid "EPG Import Configuration" -msgstr "EPGImport beállítás" +msgstr "EPG Import beállítás" #: ../plugin.py:718 msgid "EPG Import Log" @@ -150,7 +150,7 @@ msgstr "Csatornák listájának figyelmen kívül hagyása" #: ../plugin.py:580 msgid "Import current source" -msgstr "Kijelöltek importálása" +msgstr "Jelenlegi forrás importálása" #: ../plugin.py:330 #, python-format @@ -177,7 +177,7 @@ msgstr "Utolsó: %s %s, %d műsor információ" #: ../plugin.py:374 msgid "Load EPG only for IPTV channels" -msgstr "" +msgstr "EPG betöltése csak IPTV csatornákhoz" #: ../plugin.py:373 msgid "Load EPG only services in bouquets" @@ -189,7 +189,7 @@ msgstr "Részletes műsorleírást használó napok száma" #: ../plugin.py:311 msgid "Manual" -msgstr "Kézi importálás" +msgstr "Kézi" #: ../plugin.py:101 msgid "Monday" @@ -229,20 +229,19 @@ msgstr "Művelet kiválasztása" #: ../filtersServices.py:256 msgid "Select service to add..." -msgstr "Válasszon hozzáadandó csatornát..." +msgstr "Válassza ki a hozzáadni kívánt szolgáltatást..." #: ../plugin.py:377 msgid "Show \"EPGImport\" in extensions" msgstr "Az \"EPGImport\" megjelenítése a bővítményekben" #: ../plugin.py:378 -#, fuzzy msgid "Show \"EPGImport\" in plugins" -msgstr "Az \"EPGImport\" megjelenítése a bővítményekben" +msgstr "Az „EPGImport” megjelenítése a bővítményekben" #: ../plugin.py:520 msgid "Show log" -msgstr "Napló megjelenítése" +msgstr "Naplózás mutatása" #: ../plugin.py:376 msgid "Skip import on restart GUI" diff --git a/src/EPGImport/locale/nb.mo b/src/EPGImport/locale/nb.mo new file mode 100644 index 0000000000000000000000000000000000000000..3046063b15c24dc61012d9602a9e4c5e18432c23 GIT binary patch literal 355 zcmYk0!A`?45JZdM!3k*m+35XkVlO-`Kc4WH, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-02-26 14:56+0100\n" +"PO-Revision-Date: 2025-02-26 14:56+0100\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: nb_NO\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.4.4\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-SearchPath-0: .\n" + +#: filtersServices.py:149 +msgid "Add Provider" +msgstr "" + +#: filtersServices.py:150 +msgid "Add Channel" +msgstr "" + +#: filtersServices.py:152 +msgid "press OK to save list" +msgstr "" + +#: filtersServices.py:165 plugin.py:526 +msgid "Ignore services list" +msgstr "" + +#: filtersServices.py:194 +msgid "Really delete all list?" +msgstr "" + +#: filtersServices.py:227 +msgid "Delete selected" +msgstr "" + +#: filtersServices.py:228 +msgid "Delete all" +msgstr "" + +#: filtersServices.py:253 +msgid "Select service to add..." +msgstr "" + +#: filtersServices.py:261 +msgid "All services provider" +msgstr "" + +#: filtersServices.py:280 plugin.py:527 +msgid "Select action" +msgstr "" + +#: plugin.py:74 +msgid "always" +msgstr "" + +#: plugin.py:75 +msgid "only manual boot" +msgstr "" + +#: plugin.py:76 +msgid "only automatic boot" +msgstr "" + +#: plugin.py:77 +msgid "never" +msgstr "" + +#: plugin.py:87 +msgid "wake up and import" +msgstr "" + +#: plugin.py:88 +msgid "skip the import" +msgstr "" + +#: plugin.py:99 +msgid "Press OK" +msgstr "" + +#: plugin.py:108 +msgid "Monday" +msgstr "" + +#: plugin.py:109 +msgid "Tuesday" +msgstr "" + +#: plugin.py:110 +msgid "Wednesday" +msgstr "" + +#: plugin.py:111 +msgid "Thursday" +msgstr "" + +#: plugin.py:112 +msgid "Friday" +msgstr "" + +#: plugin.py:113 +msgid "Saturday" +msgstr "" + +#: plugin.py:114 +msgid "Sunday" +msgstr "" + +#: plugin.py:306 +msgid "EPG Import Configuration" +msgstr "" + +#: plugin.py:309 plugin.py:484 +#, python-format +msgid "Last import: %s events" +msgstr "" + +#: plugin.py:310 plugin.py:562 plugin.py:642 +msgid "Cancel" +msgstr "" + +#: plugin.py:311 plugin.py:563 plugin.py:643 plugin.py:701 +msgid "Save" +msgstr "" + +#: plugin.py:312 +msgid "Manual" +msgstr "" + +#: plugin.py:313 +msgid "Sources" +msgstr "" + +#: plugin.py:331 +#, python-format +msgid "" +"Importing:\n" +"%s %s events" +msgstr "" + +#: plugin.py:368 +msgid "Automatic import EPG" +msgstr "" + +#: plugin.py:369 +msgid "Automatic start time" +msgstr "" + +#: plugin.py:370 +msgid "When in deep standby" +msgstr "" + +#: plugin.py:371 +msgid "Return to deep standby after import" +msgstr "" + +#: plugin.py:372 +msgid "Standby at startup" +msgstr "" + +#: plugin.py:373 +msgid "Choice days for start import" +msgstr "" + +#: plugin.py:374 +msgid "Start import after booting up" +msgstr "" + +#: plugin.py:375 +msgid "Load EPG only services in bouquets" +msgstr "" + +#: plugin.py:376 +msgid "Load EPG only for IPTV channels" +msgstr "" + +#: plugin.py:377 +msgid "Consider setting \"Days Profile\"" +msgstr "" + +#: plugin.py:378 +msgid "Skip import on restart GUI" +msgstr "" + +#: plugin.py:379 +msgid "Show \"EPGImport\" in extensions" +msgstr "" + +#: plugin.py:380 +msgid "Show \"EPGImport\" in plugins" +msgstr "" + +#: plugin.py:382 +msgid "Load long descriptions up to X days" +msgstr "" + +#: plugin.py:383 +msgid "Run AutoTimer after import" +msgstr "" + +#: plugin.py:384 +msgid "Delete current EPG before import" +msgstr "" + +#: plugin.py:449 +msgid "Settings saved successfully !" +msgstr "" + +#: plugin.py:479 +#, python-format +msgid "Last: %s %s, %d events" +msgstr "" + +#: plugin.py:489 +msgid "" +"EPGImport\n" +"Import of epg data is still in progress. Please wait." +msgstr "" + +#: plugin.py:499 +msgid "No active EPG sources found, nothing to do" +msgstr "" + +#: plugin.py:504 +msgid "" +"EPGImport\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" +msgstr "" + +#: plugin.py:513 +msgid "" +"EPGImport Plugin\n" +"Failed to start:\n" +msgstr "" + +#: plugin.py:526 +msgid "Show log" +msgstr "" + +#: plugin.py:564 +msgid "Import" +msgstr "" + +#: plugin.py:598 +msgid "EPG Import Sources" +msgstr "" + +#: plugin.py:655 +msgid "Days Profile" +msgstr "" + +#: plugin.py:661 +msgid "" +"You may not use this settings!\n" +"At least one day a week should be included!" +msgstr "" + +#: plugin.py:698 +msgid "Clear" +msgstr "" + +#: plugin.py:723 +msgid "EPG Import Log" +msgstr "" + +#: plugin.py:729 +msgid "Write to /tmp/epgimport.log" +msgstr "" + +#: plugin.py:745 +msgid "Last import: " +msgstr "" + +#: plugin.py:745 +msgid " events\n" +msgstr "" + +#: plugin.py:745 +msgid "" +"\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" +msgstr "" + +#: plugin.py:793 +#, python-format +msgid "EPG Import finished, %d events" +msgstr "" + +#: plugin.py:793 +msgid "" +"You must restart Enigma2 to load the EPG data,\n" +"is this OK?" +msgstr "" + +#: plugin.py:1098 +msgid "Automated EPG Importer" +msgstr "" + +#: plugin.py:1100 +msgid "EPG-Importer Now" +msgstr "" + +#: plugin.py:1101 plugin.py:1107 plugin.py:1112 plugin.py:1117 plugin.py:1122 +#: plugin.py:1130 plugin.py:1140 +msgid "EPG-Importer" +msgstr "" diff --git a/src/EPGImport/locale/pl.mo b/src/EPGImport/locale/pl.mo index 8cba21240982240bf8ceae252fa83a5650210bfc..2a8ed015b43eb8932cbc3d69c138452922496135 100644 GIT binary patch literal 5177 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 XSynzi$uR7x;=q1VsjElbfT8~Ya=WwU literal 1905 zcmai!&u<$=6vqcBEf@;4lnM}3kD^r2c7yGPHf#x{HAxgTPNK$zlG}K9vYuo;vznca z*}V}Vdg9na)B_i;aN$&_5K`?6SME?Q{0(s9J6=2XjgiM6&#yP%_vStSaq`GLhV}~j z8|Z!XH__uK@P+mmJPrN@o&x^?kAlaaWbA416!o&~=I zoxShCx51x5XYWt&JoqYLptE-dbh>Jw)3FZvUP2YmAVcdi$cW(qb zyI+IO&Nrabc@KOM`~h@!egS8|-$AGIU+^4w6o;K14?GL5g3p2)yawhVKJ3A0{#Ve& z@Hg-r_!xBOj^Xkp@H_~a!&Vv50qzdl%Og6i&fjr6fgi`wVUEGYp+4jAtO%FG9W&)8;Pi;#j(~bx)(o9HVPbfQF_6~*Mn({Yo#91U&Wr@oe zYOOAn`4zUxQ|r@AiqEu}Rn0?+wTdVdX<%gH)TNXq?(7{3d7iQ+R~e7lnkF7txhKeZ zm6kGTTW1Qwl(roSD>xNuw#oMd8#}UTxeD7kaa)SX5{4;+OAg~md>gT(w$|1e!0Klm zp-3ty5{WzFPP5N-M%^s6WJDS4MJ1yyue%##=Y;K)!a{EO#fnTZfv@$O*Vu;9J0h^& zO6YwmObQ2m=Rz5=$}Qnu(`%-Y480p!lzJ`gQ)Oelgl+WY?8{Do>A``iO~EyGnBbE}O{O>ShC7PSwq zRO!R1!N*z&J1eq}a70BJkA&9}yz3*zO8n1GpOU!JTxl*&Jgv=EE66>ItnpfT0za(y z!oHWpT&k-Sbht@{UAWy^^5!S|oXl-uyhRo05YOV%d|TQIvdi$;ThgYRLQgUrrVI6} zxYn&Kyrbx?1**M!wQ|Q>7D^!7ovRyKgwj%dwmMtKN;&k(1Zk8Cd@3c3iz*Kal@BxR zFv~|(Sgr&GPs!*a(LvEa2qb6MRTgJEtWo5H`~AD@=D6O19X@3zW1~uONsQ8y)8oMd z^XSKLu+OI_3%;udUj>C$`LJ?&dB{y66t0AgNfA!j9aZ6)6qM+m)G&!kl>Gw~;yTjT zDv5WP6yuc1EspC$-Q)ecgkz{?K{}CXj=FLMspP00VS>2}bf|bN3Zx(EbJylQl?NRf z{Ny|lgMH^Z$`k)lI6z%Jc&IoPMzMaejwQzqz)eDh>FrZ gCunR4-ojSY?H>@we?>7J1&*|LUQ~8a`ooI;4@`;^BLDyZ diff --git a/src/EPGImport/locale/pl.po b/src/EPGImport/locale/pl.po index e0f1355..b0f8457 100755 --- a/src/EPGImport/locale/pl.po +++ b/src/EPGImport/locale/pl.po @@ -6,11 +6,11 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: EPG-importer\n" +"Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-01-16 06:40+0000\n" -"PO-Revision-Date: 2025-01-24 20:37+0000\n" -"Last-Translator: ChatGpt \n" +"PO-Revision-Date: 2025-02-26 07:02+0100\n" +"Last-Translator: Warder \n" "Language-Team: \n" "Language: pl\n" "MIME-Version: 1.0\n" @@ -26,29 +26,29 @@ msgid "" "Is this ok?" msgstr "" "\n" -"Rozpocznie się import danych EPG.\n" -"To może potrwać kilka minut.\n" -"Czy to w porządku?" +"Import danych EPG rozpocznie się.\n" +"To może zająć chwile.\n" +"Czy jest ok?" #: ../plugin.py:739 msgid " events\n" -msgstr " wydarzeń\n" +msgstr " wydarzenia\n" #: ../filtersServices.py:153 msgid "Add Channel" -msgstr "Dodaj kanał" +msgstr "Dodaj Kanał" #: ../filtersServices.py:152 msgid "Add Provider" -msgstr "Dodaj dostawcę" +msgstr "Dodaj Nadawcę" #: ../filtersServices.py:264 msgid "All services provider" -msgstr "Wszyscy dostawcy usług" +msgstr "Wszystkie usługi" #: ../plugin.py:1095 msgid "Automated EPG Importer" -msgstr "Zautomatyzowany importer EPG" +msgstr "Automatyczny importer EPG" #: ../plugin.py:366 msgid "Automatic import EPG" @@ -64,23 +64,23 @@ msgstr "Anuluj" #: ../plugin.py:371 msgid "Choice days for start import" -msgstr "Wybierz dni dla rozpoczęcia importu" +msgstr "Wybór dni do rozpoczęcie importu" #: ../plugin.py:693 msgid "Clear" msgstr "Wyczyść" #: ../plugin.py:382 -msgid "Delete current EPG before import" -msgstr "Usuń bieżący EPG przed importem" +msgid "Clearing current EPG before import" +msgstr "Wyczyść obecne EPG przed importem" #: ../plugin.py:375 msgid "Consider setting \"Days Profile\"" -msgstr "Rozważ ustawienie \"Profilu dni\"" +msgstr "Rozważyć ustawienie \"dni profil\"" #: ../plugin.py:650 msgid "Days Profile" -msgstr "Profil dni" +msgstr "Profil" #: ../filtersServices.py:231 msgid "Delete all" @@ -88,7 +88,7 @@ msgstr "Usuń wszystko" #: ../filtersServices.py:230 msgid "Delete selected" -msgstr "Usuń zaznaczone" +msgstr "Usuń wybrane" #: ../plugin.py:305 msgid "EPG Import Configuration" @@ -105,12 +105,12 @@ msgstr "Źródła importu EPG" #: ../plugin.py:790 #, python-format msgid "EPG Import finished, %d events" -msgstr "Import EPG zakończony, %d wydarzeń" +msgstr "Zakończono importowanie EPG, %d zdarzeń" #: ../plugin.py:1097 ../plugin.py:1098 ../plugin.py:1104 ../plugin.py:1109 #: ../plugin.py:1114 ../plugin.py:1119 ../plugin.py:1127 ../plugin.py:1137 msgid "EPG-Importer" -msgstr "Importer EPG" +msgstr "EPG-Import" #: ../plugin.py:483 msgid "" @@ -118,7 +118,7 @@ msgid "" "Import of epg data is still in progress. Please wait." msgstr "" "EPGImport\n" -"Import danych EPG nadal trwa. Proszę czekać." +"Import danych EPG jest nadal w toku. Proszę czekać." #: ../plugin.py:498 msgid "" @@ -128,8 +128,8 @@ msgid "" "Is this ok?" msgstr "" "EPGImport\n" -"Import danych EPG rozpocznie się.\n" -"To może potrwać kilka minut.\n" +"Import danych EPG zostanie uruchomiony.\n" +"Może to potrwać kilka minut.\n" "Czy to w porządku?" #: ../plugin.py:507 @@ -137,8 +137,8 @@ msgid "" "EPGImport Plugin\n" "Failed to start:\n" msgstr "" -"Wtyczka EPGImport\n" -"Nie udało się uruchomić:\n" +"Wtyczka EPGImport \n" +"Nie udało się uruchomić\n" #: ../plugin.py:105 msgid "Friday" @@ -146,7 +146,7 @@ msgstr "Piątek" #: ../filtersServices.py:168 ../plugin.py:520 msgid "Ignore services list" -msgstr "Ignoruj listę usług" +msgstr "Lista usług ignorowanych" #: ../plugin.py:580 msgid "Import current source" @@ -158,8 +158,8 @@ msgid "" "Importing: %s\n" "%s events" msgstr "" -"Importowanie: %s\n" -"%s wydarzeń" +"Importowanie: %s\\n \n" +"%s zdarzeń" #: ../plugin.py:739 msgid "Last import: " @@ -168,24 +168,24 @@ msgstr "Ostatni import: " #: ../plugin.py:478 #, python-format msgid "Last import: %s events" -msgstr "Ostatni import: %s wydarzeń" +msgstr "Ostatni import: %s zdarzeń" #: ../plugin.py:473 #, python-format msgid "Last: %s %s, %d events" -msgstr "Ostatni: %s %s, %d wydarzeń" +msgstr "Ostatnie: %s %s, %d zdarzenia" #: ../plugin.py:374 msgid "Load EPG only for IPTV channels" -msgstr "Ładuj EPG tylko dla kanałów IPTV" +msgstr "Załaduj EPG tylko dla kanałów IPTV" #: ../plugin.py:373 msgid "Load EPG only services in bouquets" -msgstr "Ładuj EPG tylko dla usług w listach kanałów" +msgstr "Załaduj usługi EPG tylko w bukietach" #: ../plugin.py:380 msgid "Load long descriptions up to X days" -msgstr "Ładuj długie opisy do X dni" +msgstr "Załaduj długie opisy do X dni" #: ../plugin.py:311 msgid "Manual" @@ -197,23 +197,23 @@ msgstr "Poniedziałek" #: ../plugin.py:493 msgid "No active EPG sources found, nothing to do" -msgstr "Nie znaleziono aktywnych źródeł EPG, nic do zrobienia" +msgstr "Nie znaleziono aktywnych źródeł EPG, nic do roboty" #: ../plugin.py:88 msgid "Press OK" -msgstr "Naciśnij OK" +msgstr "Wybierz" #: ../filtersServices.py:197 msgid "Really delete all list?" -msgstr "Naprawdę chcesz usunąć całą listę?" +msgstr "Naprawdę usunąć całą listę?" #: ../plugin.py:369 msgid "Return to deep standby after import" -msgstr "Powrót do głębokiego stanu uśpienia po imporcie" +msgstr "Powrót do głębokiego czuwania po imporcie" #: ../plugin.py:381 msgid "Run AutoTimer after import" -msgstr "Uruchom AutoTimer po imporcie" +msgstr "Uruchom AutoTimer po zaimportowaniu" #: ../plugin.py:106 msgid "Saturday" @@ -229,15 +229,15 @@ msgstr "Wybierz akcję" #: ../filtersServices.py:256 msgid "Select service to add..." -msgstr "Wybierz usługę do dodania..." +msgstr "Wybierz usługę, którą chcesz dodać..." #: ../plugin.py:377 msgid "Show \"EPGImport\" in extensions" -msgstr "Pokaż \"EPGImport\" w rozszerzeniach" +msgstr "Pokaż „EPGImport” w rozszerzeniach" #: ../plugin.py:378 msgid "Show \"EPGImport\" in plugins" -msgstr "Pokaż \"EPGImport\" we wtyczkach" +msgstr "Pokaż „EPGImport” w wtyczkach" #: ../plugin.py:520 msgid "Show log" @@ -253,11 +253,11 @@ msgstr "Źródła" #: ../plugin.py:370 msgid "Standby at startup" -msgstr "Czuwanie przy uruchamianiu" +msgstr "Czuwanie przy starcie" #: ../plugin.py:372 msgid "Start import after booting up" -msgstr "Rozpocznij import po uruchomieniu dekodera" +msgstr "Rozpocznij import po uruchomieniu" #: ../plugin.py:107 msgid "Sunday" @@ -277,11 +277,11 @@ msgstr "Środa" #: ../plugin.py:368 msgid "When in deep standby" -msgstr "Gdy w głębokim stanie uśpienia" +msgstr "W głębokim stanie gotowości" #: ../plugin.py:724 msgid "Write to /tmp/epgimport.log" -msgstr "Zapisz do /tmp/epgimport.log" +msgstr "Zapisz w /tmp/epgimport.log" #: ../plugin.py:660 msgid "" @@ -289,7 +289,7 @@ msgid "" "At least one day a week should be included!" msgstr "" "Nie możesz używać tych ustawień!\n" -"Co najmniej jeden dzień w tygodniu powinien być uwzględniony!" +"Należy uwzględnić co najmniej jeden dzień w tygodniu!" #: ../plugin.py:790 msgid "" @@ -297,7 +297,7 @@ msgid "" "is this OK?" msgstr "" "Musisz ponownie uruchomić Enigma2, aby załadować dane EPG,\n" -"Czy to w porządku?" +"czy to w porządku?" #: ../plugin.py:60 msgid "always" @@ -309,11 +309,11 @@ msgstr "nigdy" #: ../plugin.py:62 msgid "only automatic boot" -msgstr "tylko automatyczne uruchomienie" +msgstr "tylko automatyczny rozruch" #: ../plugin.py:61 msgid "only manual boot" -msgstr "tylko ręczne uruchomienie" +msgstr "tylko ręczny rozruch" #: ../filtersServices.py:155 msgid "press OK to save list" @@ -321,8 +321,8 @@ msgstr "naciśnij OK, aby zapisać listę" #: ../plugin.py:77 msgid "skip the import" -msgstr "pomiń import" +msgstr "nie importuj" #: ../plugin.py:76 msgid "wake up and import" -msgstr "obudź i zaimportuj" +msgstr "uruchom i importuj" diff --git a/src/EPGImport/locale/ru.mo b/src/EPGImport/locale/ru.mo index f32b96da53241368d9fca5bdaeb1d652fbae88c4..cfa9b6d62a5b09b0af795197a017e493aa8e55eb 100644 GIT binary patch delta 2563 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, YEAR. +# +#, fuzzy msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-01-16 06:40+0000\n" -"PO-Revision-Date: 2020-07-14 22:58+0300\n" -"Last-Translator: \n" -"Language-Team: LANGUAGE \n" -"Language: ru_RU\n" +"PO-Revision-Date: 2025-02-26 07:03+0100\n" +"Last-Translator: Warder \n" +"Language-Team: \n" +"Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-SourceCharset: UTF-8\n" -"X-Generator: Poedit 2.3.1\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.5\n" #: ../plugin.py:739 msgid "" @@ -22,29 +26,29 @@ msgid "" "Is this ok?" msgstr "" "\n" -"Начинаем импорт данных EPG.\n" +"Начнётся импорт epg данных.\n" "Это может занять несколько минут.\n" -"Вас устраивает?" +"Можем продолжить?" #: ../plugin.py:739 msgid " events\n" -msgstr " событий\n" +msgstr " события\n" #: ../filtersServices.py:153 msgid "Add Channel" -msgstr "Добавить канал" +msgstr "Добавить Канал" #: ../filtersServices.py:152 msgid "Add Provider" -msgstr "Добавить провайдер" +msgstr "Добавить Оператора" #: ../filtersServices.py:264 msgid "All services provider" -msgstr "Все сервисы провайдера" +msgstr "Все сервисы оператора" #: ../plugin.py:1095 msgid "Automated EPG Importer" -msgstr "Автоматический импортер EPG" +msgstr "Автоматический EPG Импортер" #: ../plugin.py:366 msgid "Automatic import EPG" @@ -52,7 +56,7 @@ msgstr "Автоматический импорт EPG" #: ../plugin.py:367 msgid "Automatic start time" -msgstr "Начало автоматического старта" +msgstr "Время автозапуска" #: ../plugin.py:309 ../plugin.py:557 ../plugin.py:637 msgid "Cancel" @@ -60,23 +64,23 @@ msgstr "Отмена" #: ../plugin.py:371 msgid "Choice days for start import" -msgstr "Выбор дней для старта импорта" +msgstr "Выбор дней для начала импорта" #: ../plugin.py:693 msgid "Clear" -msgstr "Очистка" +msgstr "Очистить" #: ../plugin.py:382 msgid "Clearing current EPG before import" -msgstr "Очистка текущего EPG перед импортом" +msgstr "Очистить текущий EPG перед импортом" #: ../plugin.py:375 msgid "Consider setting \"Days Profile\"" -msgstr "Учесть настройки \"Профиль дней\"" +msgstr "Учесть настройки \"Профиль Дней\"" #: ../plugin.py:650 msgid "Days Profile" -msgstr "Профиль дней" +msgstr "Профиль Дней" #: ../filtersServices.py:231 msgid "Delete all" @@ -88,33 +92,33 @@ msgstr "Удалить выбранное" #: ../plugin.py:305 msgid "EPG Import Configuration" -msgstr "Импортер EPG - Настройки" +msgstr "EPG Импорт Конфигурация" #: ../plugin.py:718 msgid "EPG Import Log" -msgstr "Импортер EPG - Журнал" +msgstr "EPG Импорт Журнал" #: ../plugin.py:592 msgid "EPG Import Sources" -msgstr "Источники импорта" +msgstr "EPG Импорт Источники" #: ../plugin.py:790 #, python-format msgid "EPG Import finished, %d events" -msgstr "Импорт EPG закончен, %d событий" +msgstr "EPG Импорт завершён, %d событий" #: ../plugin.py:1097 ../plugin.py:1098 ../plugin.py:1104 ../plugin.py:1109 #: ../plugin.py:1114 ../plugin.py:1119 ../plugin.py:1127 ../plugin.py:1137 msgid "EPG-Importer" -msgstr "Импортер EPG" +msgstr "EPG-Импортер" #: ../plugin.py:483 msgid "" "EPGImport\n" "Import of epg data is still in progress. Please wait." msgstr "" -"Импортер EPG\n" -"Импорт данных EPG все еще продолжается. Пожалуйста, подождите." +"EPGИмпорт\n" +"Импорт epg данных всё ещё продолжается. Пожалуйста, подождите." #: ../plugin.py:498 msgid "" @@ -123,18 +127,18 @@ msgid "" "This may take a few minutes.\n" "Is this ok?" msgstr "" -"Импортер EPG\n" -"Начинаем импорт данных EPG.\n" +"EPGИмпорт\n" +"Начнётся импорт epg данных.\n" "Это может занять несколько минут.\n" -"Вас устраивает?" +"Согласны с этим?" #: ../plugin.py:507 msgid "" "EPGImport Plugin\n" "Failed to start:\n" msgstr "" -"Импортер EPG\n" -"Ошибка старта:\n" +"EPGИмпорт Плагин\n" +"Сбой запуска:\n" #: ../plugin.py:105 msgid "Friday" @@ -142,7 +146,7 @@ msgstr "Пятница" #: ../filtersServices.py:168 ../plugin.py:520 msgid "Ignore services list" -msgstr "Список игногируемых сервисов" +msgstr "Список игнор-сервисов" #: ../plugin.py:580 msgid "Import current source" @@ -154,38 +158,38 @@ msgid "" "Importing: %s\n" "%s events" msgstr "" -"Импортируем: %s\n" -"%s событий" +"Импорт: %s\n" +"%s события" #: ../plugin.py:739 msgid "Last import: " -msgstr "Последний импорт: " +msgstr "Прошлый импорт: " #: ../plugin.py:478 #, python-format msgid "Last import: %s events" -msgstr "Последний импорт: %s событий" +msgstr "Последний импорт: %s события" #: ../plugin.py:473 #, python-format msgid "Last: %s %s, %d events" -msgstr "Последний импорт: %s %s, %d событий" +msgstr "Последнее: %s %s, %d событий" #: ../plugin.py:374 msgid "Load EPG only for IPTV channels" -msgstr "Загрузка EPG только для IPTV" +msgstr "Загрузить EPG только для каналов IPTV" #: ../plugin.py:373 msgid "Load EPG only services in bouquets" -msgstr "Загрузка EPG только для сервисов из букетов" +msgstr "Загружать EPG только в букеты" #: ../plugin.py:380 msgid "Load long descriptions up to X days" -msgstr "Загрузка длинных описаний не больше чем на Х дней" +msgstr "Загружать длинные описания до X дней" #: ../plugin.py:311 msgid "Manual" -msgstr "Вручную" +msgstr "Руководство" #: ../plugin.py:101 msgid "Monday" @@ -193,23 +197,23 @@ msgstr "Понедельник" #: ../plugin.py:493 msgid "No active EPG sources found, nothing to do" -msgstr "Активные источники EPG не найдены." +msgstr "Активных источников EPG не найдено, делать нечего" #: ../plugin.py:88 msgid "Press OK" -msgstr "Нажмите ОК" +msgstr "Нажмите OK" #: ../filtersServices.py:197 msgid "Really delete all list?" -msgstr "Удалить весь список?" +msgstr "Удаляем весь список?" #: ../plugin.py:369 msgid "Return to deep standby after import" -msgstr "Возврат в режим глубокого ожидания после импорта" +msgstr "Возврат в режим глубокого сна после импорта" #: ../plugin.py:381 msgid "Run AutoTimer after import" -msgstr "По окончании импорта запустить Автотаймер" +msgstr "Запуск АвтоТаймера после импорта" #: ../plugin.py:106 msgid "Saturday" @@ -221,19 +225,19 @@ msgstr "Сохранить" #: ../filtersServices.py:283 ../plugin.py:521 msgid "Select action" -msgstr "Выбор действия" +msgstr "Выбрать действие" #: ../filtersServices.py:256 msgid "Select service to add..." -msgstr "Выберите канал для добавления..." +msgstr "Выберите сервис для добавления..." #: ../plugin.py:377 msgid "Show \"EPGImport\" in extensions" -msgstr "Показать \"Импортер EPG\" в меню дополнений" +msgstr "Показать \"EPGИмпорт\" в расширениях" #: ../plugin.py:378 msgid "Show \"EPGImport\" in plugins" -msgstr "Показать \"Импортер EPG\" в списке плагинов" +msgstr "Показать «EPGImport» в плагинах" #: ../plugin.py:520 msgid "Show log" @@ -241,7 +245,7 @@ msgstr "Показать журнал" #: ../plugin.py:376 msgid "Skip import on restart GUI" -msgstr "Пропускать импорт после рестарта GUI" +msgstr "Пропустить импорт при перезапуске GUI" #: ../plugin.py:312 msgid "Sources" @@ -249,11 +253,11 @@ msgstr "Источники" #: ../plugin.py:370 msgid "Standby at startup" -msgstr "После загрузки, перейти в режим ожидания" +msgstr "Ожидание при запуске" #: ../plugin.py:372 msgid "Start import after booting up" -msgstr "Старт импорта после загрузки" +msgstr "Начать импорт после загрузки" #: ../plugin.py:107 msgid "Sunday" @@ -273,7 +277,7 @@ msgstr "Среда" #: ../plugin.py:368 msgid "When in deep standby" -msgstr "Поведение в глубоком ожидании" +msgstr "Когда в режиме глубокого сна" #: ../plugin.py:724 msgid "Write to /tmp/epgimport.log" @@ -285,15 +289,15 @@ msgid "" "At least one day a week should be included!" msgstr "" "Вы не можете использовать эти настройки!\n" -"По крайней мере один день недели должен быть активирован!" +"По крайней мере, один день в неделю должен быть включен!" #: ../plugin.py:790 msgid "" "You must restart Enigma2 to load the EPG data,\n" "is this OK?" msgstr "" -"Для загрузки данных EPG требуется перезагрузка,\n" -"Вас устраивает?" +"Необходимо перезапустить Enigma2, чтобы загрузить данные EPG,\n" +"согласны с ЭТИМ?" #: ../plugin.py:60 msgid "always" @@ -305,20 +309,20 @@ msgstr "никогда" #: ../plugin.py:62 msgid "only automatic boot" -msgstr "только при авто загрузке" +msgstr "только автоматическая загрузка" #: ../plugin.py:61 msgid "only manual boot" -msgstr "только при ручной загрузке" +msgstr "только ручная загрузка" #: ../filtersServices.py:155 msgid "press OK to save list" -msgstr "Нажмите ОК для сохранения списка" +msgstr "нажмите OK для сохранения списка" #: ../plugin.py:77 msgid "skip the import" -msgstr "пропускать импорт" +msgstr "пропустить импорт" #: ../plugin.py:76 msgid "wake up and import" -msgstr "разбудить для импорта" +msgstr "пробуждение и импорт" diff --git a/src/EPGImport/locale/sk.mo b/src/EPGImport/locale/sk.mo index f9946149eb4a5bb06b7a7df7a1acffb5143a04b8..d335ae4beff1e717dc132dc15968a687a6615427 100644 GIT binary patch literal 5294 zcmbW4U2Ggz6~}K&`C2F~1zHNUy)mTpBkMTnS8zX4H+EAuapJ~~QvwL??p%9sW_Io} zA8TxZRH+gL3G)Er1rT7N2#PQd6%taUxDV?U@K7W`NL3{Y6$l~mK%ap21%78{cDf-0e%(A`hEnZ|IeWG`vqi3^(>Ti{~F4;FTzLR zt8fy|5KMXh2`F)S3ce3+z^maV6n{MfB_6+mGXL}N%FC4c2NZw5LJ$~I9b-`vrzI%< zv{3x`MJV=u7m9x_RQJDwV)x7NWAGIyHm0E>j$CL2A@SE^@cpi$~ z&qA^9_fY)*Hz@Dbc^Wl(W@?sbefqY z8?P<4T&mkcNXa~~d7+IDT6r8PAb1o}wJ#IcQv>-ofY zU0@S6C8OG;>zX#zondR9`L@Yypl9Zf>e3qNTsFI=cBO$l9ce7A)0t~qb;QI?raIE{ z49o)7QoZbx(*EjYbtJMTDSutOqMLb=U~DmC!_wOhJNh^kV=*+#r0dwU49FUmU1Z19 z=$)Fjk1@$s@-$rF<~ACEpb?pH={7kr-JnbD?Y7rV4&gME5$ zQ2AFL*N0x>ro4W=FcO$FlTf-?+`W?6G#%ITxFWTB)wpb227BZ8-;@l7BQjCG;^NxF z2FC?D^JPRPYwF>|5hXRd5{u)9iiljAsnVKZfYQPzRV(3`)O*v~-gMxL<0j3j$W3Z> z$=#)$okIEs`k7rH>X2uZv9!rfbzg zA>6_^L|WZdiQ?$vcWBI+e?+7Siy(NKtu;16j&qD^JqmBDV$4*dVU1S~eDI zgAre;Gl?UAS>FDv-Puq5E5{!fGe7C`qV5qDos%#{p{uGwx~DdkX-OmsXIvB=O5>`v zp-x*qj{@DGqQuQ84{Wff94e>pV68K;TWOnvGIJy{%vz-&r26iwk+;QUCyyOgCR(Nb zsF>oKs6we`SQusfWm~%t8N#HYQ|U$qPYG?I7L|(AWtvJDt5VGoDw-KIQRB{0yy~+- zeaR*g&`Di8Wo37$&!sCas6Ujir1eEl^Zd#BDcg0@sXk5dp40~?4&GItI9NY;x1P9X za^lY0Ck{+Z)FdVAi;0QT$Yeg5)Mrd0)%Cun3sdu8YS&}a9&fHcK+kw3H!HTjXib|L z2KSRX4Qq3=b2Ga}9T=aeQB+YKW3P3XJVS%~JCSkmy^_EQVY>hH;=}bryZyw5Wt-Gz z;-(LZN^+>-vfAVIqc*nGW-)Hw+rVY|j`6!{s3v_w3c-+;v&zT_1*&t5`Ch}C|8o5mcRoJD_UPWzI*74BDs|OsAfj9?t?g`3i=_YA z-i4hFu^m@v6J|LJi!EbIq2uj=%zRs&?e;eN+az9$I#8&*F;?!dIg|Ca#_)*jx5bS* z+cxXstgg5t^h`QvSZff~-qY$>wZrx{oA{?(YhE~E+Qy6*Ueb0p5TRevG{cwAZ26fH z_|V5Y8y1HayG33FO|g!8;JbdkY2>=>KG=2#(jW$`+sc9)&bAEOZQ|43R+}L1Y=q`q zW=A*O*J`YHAu#8BSB!s?GMo0DDE6U@P}z8b91cw4A}I)E6p0uc@w`L#VYv|#IefhC zB_VeuRyk!fp6lX1Q*FtE{g?^zb9~?!HSbfldy%6n9Lm1@+9o#Rp?ksWM)}in$cZ4_0O~H!0T4j@MWfUe9Mz%lo5na zb|n^D+PNx}RAdqrg$W4J$j09pj?5O@@R?DkBhQPH=aNL2V#21AgeK{kv*YD?nCk^8 zHVv~+hg8VkrWn=+ow9(*C&5|I0PC`v(eHw;PW*a*v$w@}cH#Rs>Q;Upc%rl0W4*U2 zGNcOFl(M=ZgO_TOiDk{3@-?QrtwkzKf%G zop`ZwVKH)5;Sj*CKg)@8h8`3^9-pv5QKI`l$hsI3s?)d!EM!zVDt8$%FKIuVin}se z7AbUkTP#9K;Ol*REIP~XJuHs~Ii+pdBrsC^`{(!6=1io~d1DRdteaNCzhr31q#fV? ziav*Z@~y*VSJU3#rikxRQz6@9Q^tv_yyE4ejOBY0jWYM|k4@1h_P}I}^Q-U-# M_`E8%n@Sz(fA~23#Q*>R literal 5021 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, 2022. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy msgid "" msgstr "" -"Project-Id-Version: epgimport italian translation\n" +"Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-04-01 17:07+0100\n" -"PO-Revision-Date: 2023-01-19 15:32+0100\n" +"POT-Creation-Date: 2023-01-16 06:40+0000\n" +"PO-Revision-Date: 2025-02-26 07:04+0100\n" "Last-Translator: Warder \n" -"Language-Team: Italian <>\n" +"Language-Team: \n" "Language: sk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : 2);\n" -"X-Generator: Poedit 3.2.2\n" -"X-Poedit-Basepath: .\n" -"X-Poedit-SourceCharset: UTF-8\n" +"X-Generator: Poedit 3.5\n" -#: ../plugin.py:743 +#: ../plugin.py:739 msgid "" "\n" "Import of epg data will start.\n" @@ -28,89 +30,89 @@ msgstr "" "Môže to trvať niekoľko minút.\n" "Je to v poriadku?" -#: ../plugin.py:743 +#: ../plugin.py:739 msgid " events\n" msgstr " Udalosti\n" -#: ../filtersServices.py:149 +#: ../filtersServices.py:153 msgid "Add Channel" msgstr "Pridať kanál" -#: ../filtersServices.py:148 +#: ../filtersServices.py:152 msgid "Add Provider" msgstr "Pridať poskytovateľa" -#: ../filtersServices.py:260 +#: ../filtersServices.py:264 msgid "All services provider" msgstr "Všetci poskytovatelia služieb" -#: ../plugin.py:1099 -msgid "Automated EPG Import" -msgstr "Automatizovaný EPG Import" +#: ../plugin.py:1095 +msgid "Automated EPG Importer" +msgstr "Automatizovaný stahovač EPG" -#: ../plugin.py:368 +#: ../plugin.py:366 msgid "Automatic import EPG" msgstr "Automatický import EPG" -#: ../plugin.py:369 +#: ../plugin.py:367 msgid "Automatic start time" msgstr "Čas automatického spustenia" -#: ../plugin.py:311 ../plugin.py:559 ../plugin.py:639 +#: ../plugin.py:309 ../plugin.py:557 ../plugin.py:637 msgid "Cancel" msgstr "Zrušiť" -#: ../plugin.py:373 +#: ../plugin.py:371 msgid "Choice days for start import" msgstr "Výberové dni pre spustenie importu" -#: ../plugin.py:695 +#: ../plugin.py:693 msgid "Clear" msgstr "Vyčistiť" -#: ../plugin.py:384 +#: ../plugin.py:382 msgid "Clearing current EPG before import" msgstr "Zúčtovanie aktuálneho EPG pred importom" -#: ../plugin.py:377 +#: ../plugin.py:375 msgid "Consider setting \"Days Profile\"" msgstr "Zvážte nastavenie \"Profilu dní\"" -#: ../plugin.py:652 +#: ../plugin.py:650 msgid "Days Profile" msgstr "Profil dní" -#: ../filtersServices.py:227 +#: ../filtersServices.py:231 msgid "Delete all" msgstr "Zmazať všetko" -#: ../filtersServices.py:226 +#: ../filtersServices.py:230 msgid "Delete selected" msgstr "Vymazať vybrané" -#: ../plugin.py:307 +#: ../plugin.py:305 msgid "EPG Import Configuration" msgstr "Konfigurácia importu EPG" -#: ../plugin.py:720 +#: ../plugin.py:718 msgid "EPG Import Log" msgstr "Denník importu EPG" -#: ../plugin.py:594 +#: ../plugin.py:592 msgid "EPG Import Sources" msgstr "Zdroje importu EPG" -#: ../plugin.py:794 +#: ../plugin.py:790 #, python-format msgid "EPG Import finished, %d events" msgstr "Epg Import dokončený, %d udalosti" -#: ../plugin.py:1101 ../plugin.py:1102 ../plugin.py:1108 ../plugin.py:1113 -#: ../plugin.py:1118 ../plugin.py:1123 ../plugin.py:1131 ../plugin.py:1141 -msgid "EPG-Import" -msgstr "EPG-Import" +#: ../plugin.py:1097 ../plugin.py:1098 ../plugin.py:1104 ../plugin.py:1109 +#: ../plugin.py:1114 ../plugin.py:1119 ../plugin.py:1127 ../plugin.py:1137 +msgid "EPG-Importer" +msgstr "EPG-dovozca" -#: ../plugin.py:485 +#: ../plugin.py:483 msgid "" "EPGImport\n" "Import of epg data is still in progress. Please wait." @@ -118,7 +120,7 @@ msgstr "" "EPGImport\n" "Import epg dát stále prebieha. Prosím počkajte." -#: ../plugin.py:500 +#: ../plugin.py:498 msgid "" "EPGImport\n" "Import of epg data will start.\n" @@ -130,7 +132,7 @@ msgstr "" "Môže to trvať niekoľko minút.\n" "Je to v poriadku?" -#: ../plugin.py:509 +#: ../plugin.py:507 msgid "" "EPGImport Plugin\n" "Failed to start:\n" @@ -138,19 +140,19 @@ msgstr "" "Doplnok EPGImport\n" "Nepodarilo sa spustiť:\n" -#: ../plugin.py:100 +#: ../plugin.py:105 msgid "Friday" msgstr "Piatok" -#: ../filtersServices.py:164 ../plugin.py:522 +#: ../filtersServices.py:168 ../plugin.py:520 msgid "Ignore services list" msgstr "Ignorovať zoznam služieb" -#: ../plugin.py:582 +#: ../plugin.py:580 msgid "Import current source" msgstr "Import aktuálneho zdroja" -#: ../plugin.py:332 +#: ../plugin.py:330 #, python-format msgid "" "Importing: %s\n" @@ -159,129 +161,129 @@ msgstr "" "Import: %s\n" "%s podujatia" -#: ../plugin.py:743 +#: ../plugin.py:739 msgid "Last import: " msgstr "Posledný import: " -#: ../plugin.py:480 +#: ../plugin.py:478 #, python-format msgid "Last import: %s events" msgstr "Posledný import: %s udalosti" -#: ../plugin.py:475 +#: ../plugin.py:473 #, python-format msgid "Last: %s %s, %d events" msgstr "Posledný: %s %s, %d udalosti" -#: ../plugin.py:376 +#: ../plugin.py:374 msgid "Load EPG only for IPTV channels" msgstr "Načítajte EPG iba pre kanály IPTV" -#: ../plugin.py:375 +#: ../plugin.py:373 msgid "Load EPG only services in bouquets" msgstr "Načítajte služby EPG iba v kyticiach" -#: ../plugin.py:382 +#: ../plugin.py:380 msgid "Load long descriptions up to X days" msgstr "Načítanie dlhých popisov až na X dní" -#: ../plugin.py:313 +#: ../plugin.py:311 msgid "Manual" msgstr "Manuálna" -#: ../plugin.py:96 +#: ../plugin.py:101 msgid "Monday" msgstr "Pondelok" -#: ../plugin.py:495 +#: ../plugin.py:493 msgid "No active EPG sources found, nothing to do" msgstr "Nenašli sa žiadne aktívne zdroje EPG, nič nerobiť" -#: ../plugin.py:83 +#: ../plugin.py:88 msgid "Press OK" msgstr "Stlač OK" -#: ../filtersServices.py:193 +#: ../filtersServices.py:197 msgid "Really delete all list?" msgstr "Naozaj odstrániť celý zoznam?" -#: ../plugin.py:371 +#: ../plugin.py:369 msgid "Return to deep standby after import" msgstr "Po importe sa vráťte do hlbokého pohotovostného režimu" -#: ../plugin.py:383 +#: ../plugin.py:381 msgid "Run AutoTimer after import" msgstr "Spustenie nástroja AutoTimer po importe" -#: ../plugin.py:101 +#: ../plugin.py:106 msgid "Saturday" msgstr "Sobota" -#: ../plugin.py:312 ../plugin.py:560 ../plugin.py:640 ../plugin.py:698 +#: ../plugin.py:310 ../plugin.py:558 ../plugin.py:638 ../plugin.py:696 msgid "Save" msgstr "Uložiť" -#: ../filtersServices.py:279 ../plugin.py:523 +#: ../filtersServices.py:283 ../plugin.py:521 msgid "Select action" msgstr "Vybrať akciu" -#: ../filtersServices.py:252 +#: ../filtersServices.py:256 msgid "Select service to add..." msgstr "Vyberte položku Služba, ktorá sa má pridať..." -#: ../plugin.py:379 +#: ../plugin.py:377 msgid "Show \"EPGImport\" in extensions" msgstr "Zobraziť \"EPGImport\" v rozšíreniach" -#: ../plugin.py:380 +#: ../plugin.py:378 msgid "Show \"EPGImport\" in plugins" msgstr "Zobraziť \"EPGImport\" v pluginoch" -#: ../plugin.py:522 +#: ../plugin.py:520 msgid "Show log" msgstr "Zobraziť záznam" -#: ../plugin.py:378 +#: ../plugin.py:376 msgid "Skip import on restart GUI" msgstr "Preskočiť import pri reštartovaní grafického používateľského rozhrania" -#: ../plugin.py:314 +#: ../plugin.py:312 msgid "Sources" msgstr "Zdrojov" -#: ../plugin.py:372 +#: ../plugin.py:370 msgid "Standby at startup" msgstr "Pohotovostný režim pri spustení" -#: ../plugin.py:374 +#: ../plugin.py:372 msgid "Start import after booting up" msgstr "Spustenie importu po spustení" -#: ../plugin.py:102 +#: ../plugin.py:107 msgid "Sunday" msgstr "Nedeľa" -#: ../plugin.py:99 +#: ../plugin.py:104 msgid "Thursday" msgstr "Štvrtok" -#: ../plugin.py:97 +#: ../plugin.py:102 msgid "Tuesday" msgstr "Utorok" -#: ../plugin.py:98 +#: ../plugin.py:103 msgid "Wednesday" msgstr "Streda" -#: ../plugin.py:370 +#: ../plugin.py:368 msgid "When in deep standby" msgstr "V hlbokom pohotovostnom režime" -#: ../plugin.py:726 +#: ../plugin.py:724 msgid "Write to /tmp/epgimport.log" msgstr "Napíšte na /tmp/epgimport.log" -#: ../plugin.py:662 +#: ../plugin.py:660 msgid "" "You may not use this settings!\n" "At least one day a week should be included!" @@ -289,7 +291,7 @@ msgstr "" "Tieto nastavenia nesmiete použiť!\n" "Mal by byť zahrnutý aspoň jeden deň v týždni!" -#: ../plugin.py:794 +#: ../plugin.py:790 msgid "" "You must restart Enigma2 to load the EPG data,\n" "is this OK?" @@ -297,30 +299,30 @@ msgstr "" "Ak chcete načítať údaje EPG, musíte reštartovať Enigma2,\n" "je to v poriadku?" -#: ../plugin.py:55 +#: ../plugin.py:60 msgid "always" msgstr "vždy" -#: ../plugin.py:58 +#: ../plugin.py:63 msgid "never" msgstr "nikdy" -#: ../plugin.py:57 +#: ../plugin.py:62 msgid "only automatic boot" msgstr "iba automatické spustenie" -#: ../plugin.py:56 +#: ../plugin.py:61 msgid "only manual boot" msgstr "iba manuálne spustenie" -#: ../filtersServices.py:151 +#: ../filtersServices.py:155 msgid "press OK to save list" msgstr "stlačením tlačidla OK uložte zoznam" -#: ../plugin.py:72 +#: ../plugin.py:77 msgid "skip the import" msgstr "preskočiť import" -#: ../plugin.py:71 +#: ../plugin.py:76 msgid "wake up and import" msgstr "prebudenie a import" diff --git a/src/EPGImport/locale/sv.mo b/src/EPGImport/locale/sv.mo new file mode 100644 index 0000000000000000000000000000000000000000..5a61e82858c464633c4393747509fa12e7530b67 GIT binary patch literal 4778 zcmbW4ON<;x8Gwr<5Euv#hlEESE-$f3;+^q&;{<1u;5dG z4gHv97LX8;A_N$52@r8%C5-UFNT8e|c@NBv5GV&Capr`OKp+7^;s6J}zq@C4tVA5r z+U~FG(N+JuX8*A3wpSF_Q{4A*-*$^qb@O0cf-9<);R$y@ErUQd=Y*ecHsx%%WxNb1>O&T z29LmB!{^}bw<|RRUx0L}uR@XIC3pw?Ar$$3TJi>z`M-ysg8zo{-90ENoPiI+hoI=a z%Hw13MJV$#_(}LGI*zXpEkS)ehoegUxf?sk5InfjSI7|5f(i$z)mQ9w_TRP;x&MJI|Lq2QSi&;dAh}Q2cu@$_i(o z=>H%Te;k9N@8=Lngx*Y88zoX5&xC^2-9yT(1f4wNrqANfY?Kg*5kx$Q&`@kNze zY%KfAC2~k?ian+;@vFpup6Wcr`@XU*arH>~{uwBGi*MyZ&D>t{KF2M#l(@Qu`%&&& zx#zhtXYMD_RpLu>P_F&ld-Av9AwHH%a#H*`eTm(kEISC(JWs?Y;vd2$x2M#uz1)v; z=T~L1+w*a%{hGGDmJUp6^tuZ}ounpCYn9cuOLW%^bZRY|pO zIWDsiaLt8wMoqWWaU0qcjl*#Kn$XikP_-kdX2~_z%3_l`A59OO@vZ4*-DfdsPj{`k z$R%wX%<8>CX*?M&Ebw-2!O%%6u5po=uNB)Qsp%C|ORQcuF0K9l@86MIClawy)^bth zq`_8!PJLnFg^D^EJFKo2Tal0Lj)y~+r0R@G($WbF`sUW&WURGJTF|ujCey|^<4qA1 zJ_-jr>!N+bXJ5)}ny7qA=y7FWlViSlT z7n8y*1WXp;h{zMpEXK!yS1V#*efG3EXNiIVTZ|(@i*85gPqR46XItA7dqhEFpv_uJ zj1^(4&SjC7SY9PmCvbsZ6Ot(1!PUGRP=aK%Uih7AN^jW^-P&K~%oA zZ6xs*Oew4`#*R!u#sg`$cYrcdtX~r`U+`I8J}|A$h||1Yl=+wJsT@tU#4q(H%5#?r zvuc*y6D6TZeY~JAnpo=ClTFu2%+rb8h)lQE-1sarqgG~Gwz_If zmmS9K1)cQ2cyh6_w77I)N3Mglxe665rEphQ2R-&r?U&O7z0kPm3Ega)7*9QQe)VMa z@XkCDW6j3Z6H(I#WXpm++;C~-`RXYfSqh=Zx8iN!QvFEnQ0)*SMYH-0g{o)L_JXd} zc6#fS=4N|^{^}ef6;-MC@bd6Fb{|J-el!g?IggfxuZ_0yU`yk^8C}(pvmL*^)$wvH z4PR%8)AXJ=-BbBH0~HQ@a^!LI$a)W!_6j27Yby#X2)&BdYzm4 zE(H?tH+7xhm&9UmYzMyMqwOmeE9JF4D?RbnqiXD9jEQHg=-qI(Z8+j$pA4^e@y7O* zj=7xL>4WaA!7_Y3FqeH_1b&C{a@_aA$aiFgLXFBY2^^T%g;EU)cVK!BzZFNscZ$Q0 z2*9ozUb1l~R#tq%fhkc^Sdw$_tz8^`6U$6Sr_aR6=rU0@yq<~~iaj`SkW;&c(S1!2 zJDk>RtMNREh9GPYZ#3H&u;&siV@6v9iyV-6Ge01)cw~?(;*dj~WgD5%X6Qs&BHmE` zwds(PeO@*ShqEasrX%J_d*6^qtf!&m07vGs@f+r{_Cb=y!_A06&5tx}P*~>(Tb1TO z3j}A<=qefoUbn-BFLI;j+n$^xafwQDCAP@Y%~bC4Jj}^qHt?6t)EV2Gtjs~!@TpPf zL(hkjSrXxhVmeJHDMXsFXV1%cC)0IF?}nMx9g5g+Qv~aRUQsgS^u6R+U_-Ro7;cK2 z$t;^tf38Sh;a{0AqHfAjo7acyx)qx>M>!BJdY)adWL|2LqA&IvZE{blfEd^JvCod~ zH%iU(mCI6q-*(JUY@D(5xKc2@Y=gX(jeeZ=5u~HeqZO#9u?;A3g`vHq)nqGLg^?{D z>l!=*9{uY?6P>PUiy88E7 zd7vWK;VD@%U4h5;ayB25UjUK{Qx#Vwc~Dcps6hFv3qnJT>rBE1L5h%8@{8f-NRX}u Mzg;8+$8}Ww4=u%J@Bjb+ literal 0 HcmV?d00001 diff --git a/src/EPGImport/locale/sv.po b/src/EPGImport/locale/sv.po new file mode 100644 index 0000000..f4dc3f2 --- /dev/null +++ b/src/EPGImport/locale/sv.po @@ -0,0 +1,378 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-02-26 15:00+0100\n" +"PO-Revision-Date: 2025-02-26 15:00+0100\n" +"Last-Translator: Warder \n" +"Language-Team: \n" +"Language: sv_FI\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 3.4.4\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-SearchPath-0: .\n" + +#: filtersServices.py:149 +msgid "Add Provider" +msgstr "Pridať poskytovateľa" + +#: filtersServices.py:150 +msgid "Add Channel" +msgstr "Pridať kanál" + +#: filtersServices.py:152 +msgid "press OK to save list" +msgstr "stlačením tlačidla OK uložte zoznam" + +#: filtersServices.py:165 plugin.py:526 +msgid "Ignore services list" +msgstr "Ignorovať zoznam služieb" + +#: filtersServices.py:194 +msgid "Really delete all list?" +msgstr "Naozaj odstrániť celý zoznam?" + +#: filtersServices.py:227 +msgid "Delete selected" +msgstr "Vymazať vybrané" + +#: filtersServices.py:228 +msgid "Delete all" +msgstr "Zmazať všetko" + +#: filtersServices.py:253 +msgid "Select service to add..." +msgstr "Vyberte položku Služba, ktorá sa má pridať..." + +#: filtersServices.py:261 +msgid "All services provider" +msgstr "Všetci poskytovatelia služieb" + +#: filtersServices.py:280 plugin.py:527 +msgid "Select action" +msgstr "Vybrať akciu" + +#: plugin.py:74 +msgid "always" +msgstr "vždy" + +#: plugin.py:75 +msgid "only manual boot" +msgstr "iba manuálne spustenie" + +#: plugin.py:76 +msgid "only automatic boot" +msgstr "iba automatické spustenie" + +#: plugin.py:77 +msgid "never" +msgstr "nikdy" + +#: plugin.py:87 +msgid "wake up and import" +msgstr "prebudenie a import" + +#: plugin.py:88 +msgid "skip the import" +msgstr "preskočiť import" + +#: plugin.py:99 +msgid "Press OK" +msgstr "Stlač OK" + +#: plugin.py:108 +msgid "Monday" +msgstr "Pondelok" + +#: plugin.py:109 +msgid "Tuesday" +msgstr "Utorok" + +#: plugin.py:110 +msgid "Wednesday" +msgstr "Streda" + +#: plugin.py:111 +msgid "Thursday" +msgstr "Štvrtok" + +#: plugin.py:112 +msgid "Friday" +msgstr "Piatok" + +#: plugin.py:113 +msgid "Saturday" +msgstr "Sobota" + +#: plugin.py:114 +msgid "Sunday" +msgstr "Nedeľa" + +#: plugin.py:306 +msgid "EPG Import Configuration" +msgstr "Konfigurácia importu EPG" + +#: plugin.py:309 plugin.py:484 +#, python-format +msgid "Last import: %s events" +msgstr "Posledný import: %s udalosti" + +#: plugin.py:310 plugin.py:562 plugin.py:642 +msgid "Cancel" +msgstr "Zrušiť" + +#: plugin.py:311 plugin.py:563 plugin.py:643 plugin.py:701 +msgid "Save" +msgstr "Uložiť" + +#: plugin.py:312 +msgid "Manual" +msgstr "Manuálna" + +#: plugin.py:313 +msgid "Sources" +msgstr "Zdrojov" + +#: plugin.py:331 +#, fuzzy, python-format +#| msgid "" +#| "Importing: %s\n" +#| "%s events" +msgid "" +"Importing:\n" +"%s %s events" +msgstr "" +"Import: %s\n" +"%s podujatia" + +#: plugin.py:368 +msgid "Automatic import EPG" +msgstr "Automatický import EPG" + +#: plugin.py:369 +msgid "Automatic start time" +msgstr "Čas automatického spustenia" + +#: plugin.py:370 +msgid "When in deep standby" +msgstr "V hlbokom pohotovostnom režime" + +#: plugin.py:371 +msgid "Return to deep standby after import" +msgstr "Po importe sa vráťte do hlbokého pohotovostného režimu" + +#: plugin.py:372 +msgid "Standby at startup" +msgstr "Pohotovostný režim pri spustení" + +#: plugin.py:373 +msgid "Choice days for start import" +msgstr "Výberové dni pre spustenie importu" + +#: plugin.py:374 +msgid "Start import after booting up" +msgstr "Spustenie importu po spustení" + +#: plugin.py:375 +msgid "Load EPG only services in bouquets" +msgstr "Načítajte služby EPG iba v kyticiach" + +#: plugin.py:376 +#, fuzzy +#| msgid "Load EPG only for IPTV channels" +msgid "Load EPG only for IPTV channels" +msgstr "Načítajte EPG iba pre kanály IPTV" + +#: plugin.py:377 +msgid "Consider setting \"Days Profile\"" +msgstr "Zvážte nastavenie \"Profilu dní\"" + +#: plugin.py:378 +msgid "Skip import on restart GUI" +msgstr "Preskočiť import pri reštartovaní grafického používateľského rozhrania" + +#: plugin.py:379 +#, fuzzy +#| msgid "Show \"EPGImport\" in extensions" +msgid "Show \"EPGImport\" in extensions" +msgstr "Zobraziť \"EPGImport\" v rozšíreniach" + +#: plugin.py:380 +#, fuzzy +#| msgid "Show \"EPGImport\" in plugins" +msgid "Show \"EPGImport\" in plugins" +msgstr "Zobraziť \"EPGImport\" v pluginoch" + +#: plugin.py:382 +msgid "Load long descriptions up to X days" +msgstr "Načítanie dlhých popisov až na X dní" + +#: plugin.py:383 +msgid "Run AutoTimer after import" +msgstr "Spustenie nástroja AutoTimer po importe" + +#: plugin.py:384 +#, fuzzy +#| msgid "Clearing current EPG before import" +msgid "Delete current EPG before import" +msgstr "Zúčtovanie aktuálneho EPG pred importom" + +#: plugin.py:449 +msgid "Settings saved successfully !" +msgstr "" + +#: plugin.py:479 +#, python-format +msgid "Last: %s %s, %d events" +msgstr "Posledný: %s %s, %d udalosti" + +#: plugin.py:489 +msgid "" +"EPGImport\n" +"Import of epg data is still in progress. Please wait." +msgstr "" +"EPGImport\n" +"Import epg dát stále prebieha. Prosím počkajte." + +#: plugin.py:499 +msgid "No active EPG sources found, nothing to do" +msgstr "Nenašli sa žiadne aktívne zdroje EPG, nič nerobiť" + +#: plugin.py:504 +msgid "" +"EPGImport\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" +msgstr "" +"EPGImport\n" +"Spustí sa import údajov EPG.\n" +"Môže to trvať niekoľko minút.\n" +"Je to v poriadku?" + +#: plugin.py:513 +msgid "" +"EPGImport Plugin\n" +"Failed to start:\n" +msgstr "" +"Doplnok EPGImport\n" +"Nepodarilo sa spustiť:\n" + +#: plugin.py:526 +msgid "Show log" +msgstr "Zobraziť záznam" + +#: plugin.py:564 +#, fuzzy +#| msgid "EPG-Importer" +msgid "Import" +msgstr "EPG-dovozca" + +#: plugin.py:598 +msgid "EPG Import Sources" +msgstr "Zdroje importu EPG" + +#: plugin.py:655 +msgid "Days Profile" +msgstr "Profil dní" + +#: plugin.py:661 +msgid "" +"You may not use this settings!\n" +"At least one day a week should be included!" +msgstr "" +"Tieto nastavenia nesmiete použiť!\n" +"Mal by byť zahrnutý aspoň jeden deň v týždni!" + +#: plugin.py:698 +msgid "Clear" +msgstr "Vyčistiť" + +#: plugin.py:723 +msgid "EPG Import Log" +msgstr "Denník importu EPG" + +#: plugin.py:729 +msgid "Write to /tmp/epgimport.log" +msgstr "Napíšte na /tmp/epgimport.log" + +#: plugin.py:745 +msgid "Last import: " +msgstr "Posledný import: " + +#: plugin.py:745 +msgid " events\n" +msgstr " Udalosti\n" + +#: plugin.py:745 +msgid "" +"\n" +"Import of epg data will start.\n" +"This may take a few minutes.\n" +"Is this ok?" +msgstr "" +"\n" +"Spustí sa import epg dát.\n" +"Môže to trvať niekoľko minút.\n" +"Je to v poriadku?" + +#: plugin.py:793 +#, python-format +msgid "EPG Import finished, %d events" +msgstr "Epg Import dokončený, %d udalosti" + +#: plugin.py:793 +msgid "" +"You must restart Enigma2 to load the EPG data,\n" +"is this OK?" +msgstr "" +"Ak chcete načítať údaje EPG, musíte reštartovať Enigma2,\n" +"je to v poriadku?" + +#: plugin.py:1098 +msgid "Automated EPG Importer" +msgstr "Automatizovaný stahovač EPG" + +#: plugin.py:1100 +#, fuzzy +#| msgid "EPG-Importer" +msgid "EPG-Importer Now" +msgstr "EPG-dovozca" + +#: plugin.py:1101 plugin.py:1107 plugin.py:1112 plugin.py:1117 plugin.py:1122 +#: plugin.py:1130 plugin.py:1140 +#, fuzzy +#| msgid "EPG-Importer" +msgid "EPG-Importer" +msgstr "EPG-dovozca" + +#, fuzzy +#~| msgid "Add Channel" +#~ msgid "all channels" +#~ msgstr "Pridať kanál" + +#, fuzzy +#~| msgid "Start import after booting up" +#~ msgid "Start import after standby" +#~ msgstr "Spustenie importu po spustení" + +#, fuzzy +#~| msgid "Start import after booting up" +#~ msgid "Start import after resuming from standby mode." +#~ msgstr "Spustenie importu po spustení" + +#, fuzzy +#~| msgid "EPG Import Log" +#~ msgid "EPG Import" +#~ msgstr "Denník importu EPG" + +#~ msgid "Import current source" +#~ msgstr "Import aktuálneho zdroja" diff --git a/src/EPGImport/plugin.py b/src/EPGImport/plugin.py index a4dc08f..af83b47 100644 --- a/src/EPGImport/plugin.py +++ b/src/EPGImport/plugin.py @@ -1,6 +1,6 @@ from os import remove from os.path import exists -from time import localtime, mktime, time +from time import localtime, mktime, strftime, time, asctime from enigma import eServiceCenter, eServiceReference, eEPGCache, eTimer, getDesktop @@ -15,11 +15,11 @@ try: - from Components.SystemInfo import BoxInfo - IMAGEDISTRO = BoxInfo.getItem("distro") + from Components.SystemInfo import BoxInfo + IMAGEDISTRO = BoxInfo.getItem("distro") except: - from boxbranding import getImageDistro - IMAGEDISTRO = getImageDistro() + from boxbranding import getImageDistro + IMAGEDISTRO = getImageDistro() # Config from Components.ActionMap import ActionMap @@ -29,7 +29,6 @@ from Components.Label import Label import Components.PluginComponent from Components.ScrollLabel import ScrollLabel -from Components.Sources.StaticText import StaticText from Plugins.Plugin import PluginDescriptor from Screens.ChoiceBox import ChoiceBox from Screens.MessageBox import MessageBox @@ -214,7 +213,7 @@ def channelFilter(ref): print("Serviceref is in ignore list:", sref.toString(), file=log) return False if "%3a//" in ref.lower(): - # print>>log, "URL detected in serviceref, not checking fake recording on serviceref:", ref + # print("URL detected in serviceref, not checking fake recording on serviceref:", ref, file=log) return True fakeRecService = NavigationInstance.instance.recordService(sref, True) if fakeRecService: @@ -228,16 +227,15 @@ def channelFilter(ref): try: - epgcache_instance = eEPGCache.getInstance() - if not epgcache_instance: - print("[EPGImport] Failed to get valid EPGCache instance.", file=log) - else: - print("[EPGImport] EPGCache instance obtained successfully.", file=log) - epgimport = EPGImport.EPGImport(epgcache_instance, channelFilter) + epgcache_instance = eEPGCache.getInstance() + if not epgcache_instance: + print("[EPGImport] Failed to get valid EPGCache instance.", file=log) + else: + print("[EPGImport] EPGCache instance obtained successfully.", file=log) + epgimport = EPGImport.EPGImport(epgcache_instance, channelFilter) except Exception as e: - print("[EPGImport] Error obtaining EPGCache instance: %s" % e, file=log) + print("[EPGImport] Error obtaining EPGCache instance: %s" % e, file=log) -# epgimport = EPGImport.EPGImport(eEPGCache.getInstance(), channelFilter) lastImportResult = None @@ -407,7 +405,6 @@ def createSetup(self): list.append(self.cfg_longDescDays) if fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyo")) or fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyc")): try: - # from Plugins.Extensions.AutoTimer.AutoTimer import AutoTimer list.append(self.cfg_parse_autotimer) except: print("[XMLTVImport] AutoTimer Plugin not installed", file=log) @@ -697,11 +694,12 @@ class EPGImportLog(Screen): def __init__(self, session): self.session = session Screen.__init__(self, session) + self.log = log self["key_red"] = Button(_("Clear")) self["key_green"] = Button() self["key_yellow"] = Button() self["key_blue"] = Button(_("Save")) - self["list"] = ScrollLabel(log.getvalue()) + self["list"] = ScrollLabel(self.log.getvalue()) self["actions"] = ActionMap(["DirectionActions", "OkCancelActions", "ColorActions", "MenuActions"], { "red": self.clear, @@ -737,6 +735,8 @@ def cancel(self): self.close(False) def clear(self): + self.log.logfile.seek(0) + self.log.logfile.truncate(0) self.close(False) @@ -762,21 +762,24 @@ def main(session, **kwargs): session.openWithCallback(doneConfiguring, EPGImportConfig) -def doneConfiguring(session, retval): +def doneConfiguring(session, retval=False): """user has closed configuration, check new values....""" - if autoStartTimer is not None: - autoStartTimer.update() + if retval is True: + if autoStartTimer is not None: + autoStartTimer.update() def doneImport(reboot=False, epgfile=None): global _session, lastImportResult, BouquetChannelListList, serviceIgnoreList BouquetChannelListList = None serviceIgnoreList = None - lastImportResult = (time(), epgimport.eventCount) + timestamp = time() + formatted_time = strftime("%Y-%m-%d %H:%M:%S", localtime(timestamp)) + lastImportResult = (formatted_time, epgimport.eventCount) try: start, count = lastImportResult - # localtime = asctime(localtime(time())) - lastimport = "%s, %d" % (localtime, count) + current_time = asctime(localtime(time())) + lastimport = "%s, %d" % (current_time, count) config.plugins.extra_epgimport.last_import.value = lastimport config.plugins.extra_epgimport.last_import.save() print("[XMLTVImport] Save last import date and count event", file=log) @@ -850,7 +853,6 @@ def restartEnigma(confirmed): _session.open(Screens.Standby.TryQuitMainloop, 3) -# ################################# # Autostart section class AutoStartTimer: @@ -858,8 +860,9 @@ def __init__(self, session): self.session = session self.prev_onlybouquet = config.plugins.epgimport.import_onlybouquet.value self.prev_multibouquet = config.usage.multibouquet.value - self.timer = eTimer() - self.timer.callback.append(self.onTimer) + self.clock = config.plugins.epgimport.wakeup.value + self.autoStartImport = eTimer() + self.autoStartImport.callback.append(self.onTimer) self.pauseAfterFinishImportCheck = eTimer() self.pauseAfterFinishImportCheck.callback.append(self.afterFinishImportCheck) self.pauseAfterFinishImportCheck.startLongTimer(30) @@ -867,15 +870,14 @@ def __init__(self, session): def getWakeTime(self): if config.plugins.epgimport.enabled.value: - clock = config.plugins.epgimport.wakeup.value nowt = time() now = localtime(nowt) - return int(mktime((now.tm_year, now.tm_mon, now.tm_mday, clock[0], clock[1], lastMACbyte() // 5, 0, now.tm_yday, now.tm_isdst))) + return int(mktime((now.tm_year, now.tm_mon, now.tm_mday, self.clock[0], self.clock[1], lastMACbyte() // 5, 0, now.tm_yday, now.tm_isdst))) else: return -1 def update(self, atLeast=0): - self.timer.stop() + self.autoStartImport.stop() wake = self.getWakeTime() now_t = time() now = int(now_t) @@ -892,7 +894,7 @@ def update(self, atLeast=0): if not config.plugins.extra_epgimport.day_import[cur_day].value: wake += 86400 * wakeup_day next = wake - now - self.timer.startLongTimer(next) + self.autoStartImport.startLongTimer(next) else: wake = -1 print("[XMLTVImport] WakeUpTime now set to", wake, "(now=%s)" % now, file=log) @@ -911,7 +913,7 @@ def runImport(self): startImport() def onTimer(self): - self.timer.stop() + self.autoStartImport.stop() now = int(time()) print("[XMLTVImport] onTimer occured at", now, file=log) wake = self.getWakeTime() diff --git a/src/EPGImport/xmltvconverter.py b/src/EPGImport/xmltvconverter.py index 5548ab2..a015ad3 100755 --- a/src/EPGImport/xmltvconverter.py +++ b/src/EPGImport/xmltvconverter.py @@ -21,7 +21,7 @@ def quickptime(date_str): def get_time_utc(timestring, fdateparse): - # print "get_time_utc", timestring, format + # print("get_time_utc", timestring, format) try: values = timestring.split(" ") tm = fdateparse(values[0]) From e911ecbd2806e45c44c01c405039a4b0ac04f22e Mon Sep 17 00:00:00 2001 From: jbleyel Date: Wed, 26 Feb 2025 17:31:45 +0100 Subject: [PATCH 08/14] remove mo files --- src/EPGImport/locale/ar.mo | Bin 2534 -> 0 bytes src/EPGImport/locale/cs.mo | Bin 5279 -> 0 bytes src/EPGImport/locale/de.mo | Bin 5147 -> 0 bytes src/EPGImport/locale/el.mo | Bin 6570 -> 0 bytes src/EPGImport/locale/en.mo | Bin 4770 -> 0 bytes src/EPGImport/locale/en_GB.mo | Bin 1748 -> 0 bytes src/EPGImport/locale/es.mo | Bin 4972 -> 0 bytes src/EPGImport/locale/et.mo | Bin 4758 -> 0 bytes src/EPGImport/locale/fi.mo | Bin 5187 -> 0 bytes src/EPGImport/locale/fr.mo | Bin 4443 -> 0 bytes src/EPGImport/locale/hu.mo | Bin 5702 -> 0 bytes src/EPGImport/locale/it.mo | Bin 5633 -> 0 bytes src/EPGImport/locale/lt.mo | Bin 5150 -> 0 bytes src/EPGImport/locale/nb.mo | Bin 355 -> 0 bytes src/EPGImport/locale/nl.mo | Bin 5180 -> 0 bytes src/EPGImport/locale/pl.mo | Bin 5177 -> 0 bytes src/EPGImport/locale/pt.mo | Bin 4916 -> 0 bytes src/EPGImport/locale/ru.mo | Bin 6482 -> 0 bytes src/EPGImport/locale/sk.mo | Bin 5294 -> 0 bytes src/EPGImport/locale/sv.mo | Bin 4778 -> 0 bytes src/EPGImport/locale/tr.mo | Bin 5704 -> 0 bytes src/EPGImport/locale/uk.mo | Bin 6108 -> 0 bytes 22 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/EPGImport/locale/ar.mo delete mode 100644 src/EPGImport/locale/cs.mo delete mode 100644 src/EPGImport/locale/de.mo delete mode 100644 src/EPGImport/locale/el.mo delete mode 100644 src/EPGImport/locale/en.mo delete mode 100644 src/EPGImport/locale/en_GB.mo delete mode 100644 src/EPGImport/locale/es.mo delete mode 100644 src/EPGImport/locale/et.mo delete mode 100644 src/EPGImport/locale/fi.mo delete mode 100644 src/EPGImport/locale/fr.mo delete mode 100644 src/EPGImport/locale/hu.mo delete mode 100644 src/EPGImport/locale/it.mo delete mode 100644 src/EPGImport/locale/lt.mo delete mode 100644 src/EPGImport/locale/nb.mo delete mode 100644 src/EPGImport/locale/nl.mo delete mode 100644 src/EPGImport/locale/pl.mo delete mode 100644 src/EPGImport/locale/pt.mo delete mode 100644 src/EPGImport/locale/ru.mo delete mode 100644 src/EPGImport/locale/sk.mo delete mode 100644 src/EPGImport/locale/sv.mo delete mode 100644 src/EPGImport/locale/tr.mo delete mode 100644 src/EPGImport/locale/uk.mo diff --git a/src/EPGImport/locale/ar.mo b/src/EPGImport/locale/ar.mo deleted file mode 100644 index 2cc59ec340f5a62efc410a36991012fd62c40398..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2534 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 diff --git a/src/EPGImport/locale/de.mo b/src/EPGImport/locale/de.mo deleted file mode 100644 index c3c09a1cb508a4c872540e637dba08e27defc15f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5147 zcmb`KO>87b6~}9ng@l0+5+DQ!q-?T~kDXcDF(m6{*?7IXiL>5aXYI`@Zez?}W6a~=%0Yg(e)JY&9s++1UJt$wz8$>wt;W0q zyb*jacpG>NI0wE5T(0pmAb%$3NAjuD|r2N z#=H}p0&fOqzz>5$-C)o72XQo&GYRbOXguva@RoZ-vB=Vex+W29+Yc;0_xnKg0k<| zpzL`Cl$?Kodha@%fhlGhl-+lO9|Dhq(&N*h_P0Up+W>jhRJorKI>)^xSi{JwI zPf+jA6HLY982D~*5quYT36!6@pyKc(sPmr&I|qz;7F^}|T8xnY*FmjEpzJTeo4}_* z+3`J4di)HOe!s8r6;OWqD=0f|AjsNx3n;(OfqMTE$QCmKrPmig`Ry5yKl1`VI{zy8 zG4Qvb^!ht^6g-F#;`OIM$^AAcfBhW11Ka_1?q5Lh^dL@BuYw;1hv3J- zXF#3zGAMbkf%5C?;4F9mC*Ue`FQ{`)fcJngD7jw+_1=p$z6A1Te!M+xzy1ckA3VSVvj*w5GV*MQ>krx@a#^!Wtieui|vlkrIgDu!#0U-?{@Y`Kr2 znCMbW#V6bl>;VGbgm1hSnGmO%4Coc&FHViGd%01pR8Mp%UgJw#m|@7rx)d8-mT@iP z7(;P`hJh!5X)$IQikE3D4@YTM*tBcisAuD-i0oD}7}&gsvZB>k?*J2t;TY03$3RcCyaHQ&KJ~)#W=Q0{m6Sa2=}X5x|zf-GmDbyvdyI9ayy#L zmPI;@3K!ecs}I^r8*Q$pla5U)1zFuQ8ML!SGIVAs@*R#^>Zb_CfbHCN)2!0Je%UMy zT$EM6iSOA?nPn&&PHa1NyUE7X=Ta6$i$YtcPe~v)bd!ObHhZ7UDK~HhP8kfww>dK% zY%#mG*rn8WlU|ucMUwix3oB`F@8eopW_V`rR5$TS-gog~dnm5`tL5gzOFUHfZx%)Z zlNSnQ;^ppM=JLE{S8+w|>{gT%EeZSY@BdFUm`LQHvX}VAnFztST+4r=6liuTRuOC;^sG?lz z2it)$%d6{;+D^sX-0WYUXo)Y|Y5CdG74?=u3QJ;_cd}%p2nuavgDo#S(^?sS<;ihx}FT2>+cYWujQM|_==5m&hqnL1{7>S zvTycib9RnDJ3GHHceL84WNofzk-1{D?xB5r$xUIm0aG8#mQKc;NXi0AQ3>t~wx@9xvFU6(aa`%W5% zvT}brDH>F)EE+V=q}ed%WE{g|ethnje8lbXJH4GcZfEa0)_AP>p!1HJF4b347bk_C zYt6Oh8>T^e)D@aen#HzPvELPkZJfBg5hmL(;7Vy76}|^+&xMu>b)(W{UY)hzUinXF zJ)OCvM<>W#gVgkDPZWwPWq0Q)@77ZrG%UAoo__0WwLdKSN&n0fm#IqZYN}3P$F$2k z{hBcB(*erMD=sO_V!NHW4ryl2m(&w#A+{yMA?~;91da3yg{OLNW#=VCv01`5>B+Gh z)*aps#%qgZ-g8?!&-DkWFB!K> zP|dm*j~^c?9fE`6<4{Iz;@IgAQo`UvBiRj$46OH&!aOVoFN~YA0;V0x8;`cnmE8hj zcls@RGEvmF@UQcG+wrcz{||>II*r@%d3kr2nxQQd;izX;=q{8ybcUZhSBXYG%19Cs z&)0l1iyK97k*z$nFYJ}^qQJOa@A1@%E3Sxo)h2cAH%!o||6}{!_Pu8jqpXLAt*?U@Kp(j2_{DW>gzYbAM-Tc_@0fvusz zLl$*-dG9P4%_rf8n!FEDuepS;yL2#d$IJI2{fp9@3ukQ{2oK*bC)vh;RTT;aCnB?K z6_7EVPnh!&8!G=*-HzN#Tvz0bx&$o<+i+-1n(o=V`ymjzF{^9QkXza{M^bhaRq8f% zAP)&Cg_M}%DuV2xeQ3Ax{OPG5Sfj6jn)}nXq!7dPYzr#6ta4Z6DIH2cjefg!xqPshX4_>T4rc baU>Lmz{qh~-XF2Wvb#k{umZC0D+~Vyzyx~k diff --git a/src/EPGImport/locale/el.mo b/src/EPGImport/locale/el.mo deleted file mode 100644 index ad68f43f2e8325ee35142cc251b6a0f645e10364..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6570 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 diff --git a/src/EPGImport/locale/en.mo b/src/EPGImport/locale/en.mo deleted file mode 100644 index f3c1f857d06c9d10108e4d9d604eab38bb81ca09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4770 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 diff --git a/src/EPGImport/locale/et.mo b/src/EPGImport/locale/et.mo deleted file mode 100644 index 8d1507fbdad5d19b6e5e08ba45d5f726f9fcafcd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4758 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 diff --git a/src/EPGImport/locale/fr.mo b/src/EPGImport/locale/fr.mo deleted file mode 100644 index 8cdb55d4742dad88c3d4fef1fd8701f658023f73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4443 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 U6dgHGIg%p;7+xBE@(>gM21KCaV*mgE diff --git a/src/EPGImport/locale/hu.mo b/src/EPGImport/locale/hu.mo deleted file mode 100644 index d1947243568c05d4c7c0e87fc7538b2b49c606ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5702 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 diff --git a/src/EPGImport/locale/lt.mo b/src/EPGImport/locale/lt.mo deleted file mode 100644 index 1d033010fa39f06ab8a56639992675ea64fdddd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5150 zcmbuCUx-{+9mkJpZ0%@lY-+XEKWDd1HZhsW%qE&R$z~Hbo4EPYth?JZ75Z>y?%CP1 zbLZUg+;e9;p%D8J@I@??5)i80=ZFy^f=VNW8Cnz(twNrRPd-IN5kaNk_j~W1*;$%^ z=w;7*?md5h|L@&DJi7HYf$KQhr`fg$@jST|Ke)c}K_R{Zz6^c{`~~=7@LljD;2rSe z;9c->aLa>2JO)0IvjpN#%;1Onp8+vdG{AAN1%3{E1>}A|1PkDs;3MEW;3vV`;77r~ zf{%jYAt62s>;SoL5}XF7!LNc>K<@t=5KF}0LCW=Skn(PaGM@lh(GZ!{BXYq9?s@J34RLC(;&yc2GXQ;kms#{l)saYzXo#t zcR`3Kegaa@x4_SUzXN&x-$3s7PmuGrz*x8y1(5nZ20jVy0ndQ5U=@4?g0$y$2#q0813w0~K(4<6Qtwwmo_ignzupGv?>~Z+ z@4bBdAvlrGPk}qZBDfb^1bP0IoZkki&-Xyu=|>>c%AlKJG?)M_dc>E6dH271n2;Ko{_ia#~@&7nj0mngz zB9=hTuY;8Pn;`dl9fa!Q=OFF(t9<-6Nd5l|Vr#JjM(6scLC%{4_kl|w^?w=Uxo?0x z@B8`l&p__~HpqQ`4>Df<1YQES!b#){ApQSika6=SNIUdF#{C@-OT<4w%KtEa;QrKR z2f*WKQ)rZh=kQ{F97AK?(bn`UFWQg3*p0?Bc`^3sXI@Y_wJGgS+fSnL+Jbf%?I;>! zigFx8`yv`;odA5+MBlBg+|{Et_p4# zL$nhw`e|^{CnYrI6fedIFNyX5+LzGiPlRDwF91owUjHt{+x73N~;IUY5qh14BweOwSHf5O3GS+0$JBK!+r!?(M=j-bAZO=K%S;+h&bm!qnoK~9j&!EdNpD(O%qUyO zQ8OzMHiiP-ST08{lRsZ9WTxmpBu?s3dl)4Q2e&bL>d-}O=ptt#yKEYXQ{F^&bl_an7=2uf5(mqSb}bug;uRh2 zlRJalz8o$Nt%Qf{{$6f`V`5KN8cTCGoQ~s?oQEl5E!ULsC2n^A_5V{C40psqNyFH} zDFwp?(npy`ss(Y%8F)#|H7pH3jEK<0US!e?9TcZF$$R0Bs@xeDcE$r+oKvySJy(@t z)6?cjT!+6=JD!VF7IKjdyBku#R<$VkdZK+S(kWqtC0CjQ1AebaAF^zto zIGfJax=riZpw^XY*&{qnniOIov67jxghX(ghZYrI1L(PA+T%v3l8;o?fPCdY@a z8fS#)7rjR8L&0n~M074TO*=nIMpi;%76p0w**P(Y*2RHXKJ#zV7U;sHOu}5IS`?+( z#8KD+QZ20{E>3R}o$@&y*!*rqTS^p+s4bpz2B`+q?f0$rew3j0AZti|TMv^!2V>b*3FZ!TowcUXQjLx4Yb>#d zE;xG7g~GFnic*{liWjw`d#VhrxzzoI1J^6RTkziEEQ**P76-) zBx-9_PL?Mpi{*pGaz$1SRm%tWmMi5lCKeZT$8g)>yvm_s`B1SkDJzv~1@jJI9y6=B z7rO`+C_i74M?2|#VvfU3CM?yX)-f!wjYKtcaY?Jze?Pq{Lti*QcYbz5 z*-EKgKn2Bdkf7U!Djt!#za1)LkI4Fpa&Yw2vrDInhd1U?(q-+6v$h@uY1uejGrn-C zcv@SHLQECrBOMqoCrjnhWC69xsjzq|a;>;3Z99F8rw$yE+3nPByR%ZBnyScMyCiSR zV^fuioR(#Y>3BXiHOZlLTKQn&9!#uY;^dwqkRdy`m?aQilvJQ9GYv$6 zbt6ckmbU$yc@QYREfrr&fx-(}8QD;wkxBoScS+M5*<|VHT}|K1y>FTR%~E0Bsi1ew z8@!i$H#*&>QKlezKk!avE0k4X@9Id8*QVLKwu-)sCdScC&H%m9>{s#d<$F1aJVc_P zS=^3{F(&Ra5XrU*wFyiMnWRjUt9*Ywl}F4&J!fo_IUX2ov46IAV#Hj3y`#deHz=7# z_t*0g=xYWeQpd{r-VL6OU1QU~)zM)Cra|AtNo)vB@Cw8`+(Z-7CJ_o5ln|STVgY$V zW3diN(S$9A&+S}5pJXrSf#$L(o9X+#f8D8$X<^S%{W09Xg(|MXdko+#6h(Mu^OBk} z4akjQI^D$CHv!@cF`dU>+`H;je;vNaYF;{s=Npo~f&F?n zh9j%0r3POJL(3twnhEVZ2q}dOXTZI`mmdl5U#HCI*TW#2kUB^^2}rk1Wf=8}o;@ofh*^9t>cA zeKoO2p3%-Uci}L}cN)_dpX{)Fe;oSir&=;>7oGQ z;vWmwMww-`vQSJcWZik*VA*_HH~O~>a-7mhtCH0S-%~oT5j?Q}#xR?p%SNebYnN%m zQcz1!6jxb+oW_2XY4hZ~Y7R?`h&43LgR`l9$GD-kkOtGUxb;q>@D85NXngMv?TE}@ zp!=aJM7YQ2%bEW57|O7y!)H(~U;^~lQ!nFNMEP#3e{-y`lsF@6{p;yQStVEadVx_o zkV45=b?{N&yB239bWEJZ_sSsPX!`8EVo+LCTj4c`5ODB~QJKBy-l2K%3ZdEAx6z3R zt0^}vP#smEgvGmoWC{$vb*xE-)=$zAyo?#uqupvy_%{qaDEBDSsNS$7!c_bRxtPH1 diff --git a/src/EPGImport/locale/nb.mo b/src/EPGImport/locale/nb.mo deleted file mode 100644 index 3046063b15c24dc61012d9602a9e4c5e18432c23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 355 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 XSynzi$uR7x;=q1VsjElbfT8~Ya=WwU diff --git a/src/EPGImport/locale/pt.mo b/src/EPGImport/locale/pt.mo deleted file mode 100644 index b099ae0d8af9afb761b35388fc307dcc5f88b44d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4916 zcmcJSON=E|8OIMWid?`4B8m?<-8#r)Zck5xJbD<%rl)&om}Z)}jSNvEr*ECUT}<6N zmwI$FCK?tRSGaRy3_2kZNn}9?k+=}eb>Rkyac2wxq6;M&gKlKy?_X8-_H7t8CRX~~ zUp-Ep^PTVepYN-F^@@GpHylT4pQc@Tp)n7^%l7fZF?gRbcf&{F2jDN@rEmx@gRj7k z!9T&P;9KxYc**-~UIY0vH}j+M=OJCpgK!#t4PFZ$hZ^t4umzumAA&E!Pr%>855m{s z74V<%8hFvg^?TD$Yd8q+hXHE*UqiZ@KSSB|CX~He%*Vl1@Nzf5VFdNwkD&bi6Zld1Gx%ZnD=0tz z9%{bV;pPR#{2NLKUm++O{{^UiFG2Nt1x~>?pzPkqq7=t#p~gQ7WoHLk_*E!7z6n1E zzuoW|sBwP>8Opp06^A#W{C^=q*ZBLP#{Ck!0j@*-%(wVC1)qSY;cM_Zcn5<@rw>ED zpFoZKT_`<038jOdLknMq>h}+*_bWFCeGVG8A!C!yB!Q#c1-hWnxU zNFAT+8qPrZ?N%titw7m#2I~DCsCj+}Wye!ccKi}5&VPaO&)*xq-SA=t)q1Xkr(nC` z<51)M0V?i)gzEPilpp`o@NzCVHB;~c_$k`WH0`B3Y0@Z5st(2D2I&R~^0h%-&hErXV)Mdf#Y}5`E6#1-owbEK5Zn-q|Np-%Kh3z=&B{7k#C>lTUD{=;#jU`=EZ}TzH$zt2-@I%V6YsM6 zH%@!DTV@%?Rx@sRdi!z5kmfQLLyJOPr;lks*032Te%ef4nPWcj1x`tl(P_?|E^9Hn zYq5(V-Hdx>=88C^lMhaW-sEK`lo_6xytNspalYlF19pGZ_*akHV=r-0pPw&`1ST&O z$~cv~dzsJk8M}rna&LEBT+C>&_kRC>lEHXHCMtVz+FEuvF0w_aBQoDI%UMj6%t|kn zoy`A(Pf0KSZ5nL^~kS1q$ zt-x0sp?tLTMQ*AtNk9dW&%0SXPy};Z4z!xnmDJ3tOG}rS)sSjU_XX>^MZE1Tez3Lm zl4F??!!)p27>t*rVAd24d-7g$$|In&OfeFZl_!pJzbLb`>h1kN-b#jgakq)N#Bnn>udWn@%On$~Z*W*$O*$dK{{0Da1ckc(l&~>&FS|?3{@Q z%~Up|7K@b>?C}Ry%t)=Bky%9@-*RBMMW@=r#-@!Bs@6$#2Rylx7wd4I6n{S0OiR=a&JnbfB z;^(>`vukYsM>O`t2w(9sP86Ie{jjG8`-h0%zWQTNMQgU5pD&e?sp3)lY zd;Lli=!hG+T&Mw?i0vqiq9B4fxsuD2I6U*(eaG1BI|otvGTYtP=&o;_p_-8Mga%T2R~W@qWxKIOM#jn?#QX@9iW zGneLxD?&DJ(~^a|*zqLom9FR8>)!R}?Ok_Uos4hK+d=X5we{BO%IeatiHBxpTjVxW z!>DHmSW=*&gM-Ay>0#U5av9+{^1%9X`}W;_ntjt}?WMFEqH06mz7ZF#)9vFv^`uCd zwHAC77j|xDc4n?c7Rg-FUJhA5$KpYCnID-uZ0pk_*Qa*sh&^<}VFsz+Y}eYP2FZeX zTbp273r%%#!>5N&u?HoTD3DC3d)qFy-O%^Lr(LGHh7<;Y9*N&}Y^+wP%7mO|{ zPf#`GHr*bV%O*;DZLC&F;UskLh~_&Sv?*oglTK^KtNJ0;jA({(w;~YNk$dHNd3&57 zo9cQ#;UIQQAu%Ed-`BRT0upBI0$VDJ`~mcw*=4sKvZ)#Kf79Se?FZ_u3}2~=1C=)+ z`w>T%vF#{n)ZPwTR3CA1=IR1WI8<(&f;(2^$PrRV<;YqFSU-yYWF01G!yhDki|q~7 z{Q2*y5?7y!yRW^gI`e1|qrJq!dMZVYjgy7vZMV`FZ^1YY)N1W~ZypMm! zveu+bFNfx;;MF%5i*UiNB6>M_Dc4r#Y!Ij{4Boi3lBZdE%y&!b=VVnM44=>U_Ua7J zZLnF)aVd<=SXFm6kUr3xy}TOoB4eD%D$gS8DlaE7RZLk7R-d6AmuS*SQ!LWS{3uhX zdNs!ad#@MF{bih@yyZ%b>pmm-HW&A^u<;Wv zQl`ZE(bT^3p{c?bAtt^EuwGv$N>a*3wGgISYd#4?DJ9F4eJMd6&pj1dA>d79Reu@` zpN+y4apm6bvPl-rw?UORvAz;zq|BM}e?xzi5~{4y6plI>jH?5)4Ki;iwq0lP;q&9| ZxF<{Rc~E~qnEc&QF^+Kwxt&PG{{@X>Qpf-R diff --git a/src/EPGImport/locale/ru.mo b/src/EPGImport/locale/ru.mo deleted file mode 100644 index cfa9b6d62a5b09b0af795197a017e493aa8e55eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6482 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 M_`E8%n@Sz(fA~23#Q*>R diff --git a/src/EPGImport/locale/sv.mo b/src/EPGImport/locale/sv.mo deleted file mode 100644 index 5a61e82858c464633c4393747509fa12e7530b67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4778 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 Mzg;8+$8}Ww4=u%J@Bjb+ diff --git a/src/EPGImport/locale/tr.mo b/src/EPGImport/locale/tr.mo deleted file mode 100644 index 0555314232b1c7bd8a19454273b5c6758742fd9f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5704 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 U7k0~}j2iJWNQP7IPW07(0G(d4iU0rr diff --git a/src/EPGImport/locale/uk.mo b/src/EPGImport/locale/uk.mo deleted file mode 100644 index b73854b7b09fe1a90319fe0539f845e9ad79a5c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6108 zcmcJSU2q*$9l(!(XfG;=;s>If7Hci^-rj_kwn1n^T1qUnu}z^q!6x@4S#z@+_M;)A z5B&yaG9XYYA0veaV0=r`?=}gJFT6P8?l_L)3lF@?1LHUjj4%HF=j^??p-HLCxYNDA z?Cv@L^Z!1l@85jgONy(5`!?v^7lBIiZOKQ+nE3V0UY4*w3n0zZM@f!8zXYw&h>GpxWLz)rXsJ`R5l-+&_LpOAm* zmM^*WZ-ck++y=i5x5KZ($DrsrQt$|r^P z_;&{sxsSu2!e2v))qBwDU2=Y22EW1k8{tjx4tOJ62W8!@u#4C}3SZ{=wHsU<))90!fkLIir?Rb;-|mCAHn~?^>77~I^jNe9KH(w z0N0}2eefgrbyz{^D%=8>!zW+`z6e*tKSHtlLn!iAF=;#W;XXJ8M_>u1QB^$&MgODx z$h?=K==m2|hyQ?E;69ut<6nV@R&PVG_Z-{;{|#l{27)2^xC>%}dJgV|ufuilUr^+% zCK-kIK#|u~JoiG8>p}6)8*mD~2cL%HB&*E3hQ)+8!Zq+V*ao{{8U6~k!{0-~X)n=7 zE?(K35x-%c(%g-Ubg`GzrpOW9B=1<>`WGy?2%n9I#6m8KZR7eOf7skBUfc~ON5v0P z3yn+iPipSR+*0dza!V{E-=sFb%Y7}kTw<%#)2-a%pN-s&i&kN4R-V^#ua`f#q+T{~ zlhQVaNNMXELZ()7OFrMlE%hcom1`5X_*0d-M@GXa(cyqLql3EUC7vGh>vbI`UX)Zy zy+eMiN4#;Jc*90}dcch75kE*16IV*zu}c)%QuOAHR{xphM z-LBYY7;TyZ!yxu+hUq3rWax^mBCsb42mHEOp<18Ryu!LG3N}hfHmaURf)=BA=Zn7V|eAj1%#>AL!929E?mHS9A|1#zv2Mep0#e z{LdAyrbaB34*EgqK@W%2bP_sGca+qFk&pLP_h29njZL(lfW*4)$B8P^rs4S5x}kWB zn>+N%xU@2EnE$XBCq+a$v|8}AaFER7U#sgL4n3DjVNf5pnb6(S`-tuz@`AwBW3}*m zQ%ii=7p707CMky0L!w_Zaew5GN>F2+j*7MWY>KI!UXXfqwKELF=AVVy>redsMoUgO z>m&`+fUpK3S&uL#*Fx1J;nKTysNE(>qrkp3W>mZw)cVG?H;|BiF6q_oG|*BYy(D_H z@Y5cT>9Y17Z@*D{hQcwuq8X$Wk{jm9#E{LXzUb#s8^_{XoiyDu?2i@;>oCyhDK)LP z@9kENwB6HCGI3E)M-{IaR1~RGzAp@I?Z{U=fR4RGX%yQ(Qe*k3sRhMfLnaVyYb{Pt zk4AoC#5HS^kn;%zYP`PcBZd>2c)_zy@ZpKNOm3t#Ei0fVwMjd*1zaQX;pY(aPcsx$b$d+_C zsstvvpRvJU>J6H5ukl72Bb#qKbUOUl-pE449PndfEvne1YIBD<7NWEZnZogYQ+`RtM`*_bs_boIuc?+eH%IpHEK;zQJ*L+aW5@O(v8boM;{KAlf!>;8k1Lu+i76MISoB`%Ol zXDXkq)c>Vq|EGd|LGs`E*V^iG#uokY%~xdr)h2czud2M1oU-9y^UvHUre z9is?sk7!Bc{2+0pW^naIZQW-xTT1V81qQomu*mJ@0*%|C-(3Ror577K*?{ zIzM8IOIkbrUpz2P!PC@$>2 z_D2n}2}swbYGkV2Q)n{TMQLDmzmmkcK+%daG@nWKE7FAuZr$fZY_mt%-Y0{|Ke|2E zQP!_#uew#R1)Evajc~grn~3!5vk0Ea4`X5e6ee6;(1U3oCy;p6+lGYuU6zsDbSXZ6 z{JjcU>|`uIg(_@rQW8@|4?ZRGoGsFqI19)Ny-FIpJCB)Ym6p)Vh|}_C#Wr?lqH*3@ zIh#*h&Gcps;|2*c`%-oauhh^VwpXiE^!22 zQzG>xEK@}l=Eq!P;lzmSbL@z2wXHj+3(6m|AC03!<295(KFT?-I5v95RikW*Av?ujWx~sVV;joqpO@ From 9aeb36b952407190512a76520a4eeb691fcb2fd9 Mon Sep 17 00:00:00 2001 From: jbleyel Date: Wed, 26 Feb 2025 17:32:04 +0100 Subject: [PATCH 09/14] ignore mo files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0d20b64..80ea821 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.pyc +*.mo \ No newline at end of file From 934f39075c38bdcd1dd1dc9e769c9f1a2cd02649 Mon Sep 17 00:00:00 2001 From: jbleyel Date: Wed, 26 Feb 2025 17:40:54 +0100 Subject: [PATCH 10/14] add sonar and build actions --- .github/workflows/build.yml | 28 +++++++++++++ .github/workflows/buildbot.yml | 28 +++++++++++++ CI/PEP8.sh | 74 ++++++++++++++++++++++++++++++++++ CI/build.sh | 29 +++++++++++++ CI/chmod.sh | 27 +++++++++++++ CI/dos2unix.sh | 25 ++++++++++++ README.md | 15 ++++++- 7 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/buildbot.yml create mode 100755 CI/PEP8.sh create mode 100755 CI/build.sh create mode 100755 CI/chmod.sh create mode 100755 CI/dos2unix.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..04ffc0a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,28 @@ +name: XMLTV-Import + +on: + push: + branches: [ python3 ] + pull_request: + branches: + - "*" + +jobs: + build: + name: Build XMLTV-Import + runs-on: ubuntu-22.04 + strategy: + matrix: + python: ['3.10','3.11','3.12','3.13'] + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Build XMLTV-Import, python ${{ matrix.python }} + run: | + python -m compileall . diff --git a/.github/workflows/buildbot.yml b/.github/workflows/buildbot.yml new file mode 100644 index 0000000..9923dfa --- /dev/null +++ b/.github/workflows/buildbot.yml @@ -0,0 +1,28 @@ +name: buildbot + +on: + push: + branches: [ python3 ] + + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-22.04 + + steps: + + - uses: actions/checkout@v3 + with: + ref: 'python3' + + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Build python CI + run: | + sudo apt-get -q update + sudo apt-get install dos2unix + pip install --upgrade pip autopep8 + ./CI/build.sh diff --git a/CI/PEP8.sh b/CI/PEP8.sh new file mode 100755 index 0000000..24096b5 --- /dev/null +++ b/CI/PEP8.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +echo "" +echo "PEP8 double aggressive safe cleanup by Persian Prince" +# Script by Persian Prince for https://github.com/OpenVisionE2 +# You're not allowed to remove my copyright or reuse this script without putting this header. +echo "" +echo "Changing py files, please wait ..." +begin=$(date +"%s") + +echo "" +echo "PEP8 double aggressive E401" +autopep8 . -a -a -j 0 --recursive --select=E401 --in-place +git add -u +git add * +git commit -m "PEP8 double aggressive E401" + +echo "" +echo "PEP8 double aggressive E701, E70 and E502" +autopep8 . -a -a -j 0 --recursive --select=E701,E70,E502 --in-place +git add -u +git add * +git commit -m "PEP8 double aggressive E701, E70 and E502" + +echo "" +echo "PEP8 double aggressive E251 and E252" +autopep8 . -a -a -j 0 --recursive --select=E251,E252 --in-place +git add -u +git add * +git commit -m "PEP8 double aggressive E251 and E252" + +echo "" +echo "PEP8 double aggressive E20 and E211" +autopep8 . -a -a -j 0 --recursive --select=E20,E211 --in-place +git add -u +git add * +git commit -m "PEP8 double aggressive E20 and E211" + +echo "" +echo "PEP8 double aggressive E22, E224, E241, E242 and E27" +autopep8 . -a -a -j 0 --recursive --select=E22,E224,E241,E242,E27 --in-place +git add -u +git add * +git commit -m "PEP8 double aggressive E22, E224, E241, E242 and E27" + +echo "" +echo "PEP8 double aggressive E225 ~ E228 and E231 and E261" +autopep8 . -a -a -j 0 --recursive --select=E225,E226,E227,E228,E231,E261 --in-place +git add -u +git add * +git commit -m "PEP8 double aggressive E225 ~ E228 and E231" + +echo "" +echo "PEP8 double aggressive E301 ~ E306" +autopep8 . -a -a -j 0 --recursive --select=E301,E302,E303,E304,E305,E306 --in-place +git add -u +git add * +git commit -m "PEP8 double aggressive E301 ~ E306" + +echo "" +echo "PEP8 double aggressive W291 ~ W293 and W391" +autopep8 . -a -a -j 0 --recursive --select=W291,W292,W293,W391 --in-place +git add -u +git add * +git commit -m "PEP8 double aggressive W291 ~ W293 and W391" + +echo "" +finish=$(date +"%s") +timediff=$(($finish-$begin)) +echo -e "Change time was $(($timediff / 60)) minutes and $(($timediff % 60)) seconds." +echo -e "Fast changing would be less than 1 minute." +echo "" +echo "PEP8 Done!" +echo "" diff --git a/CI/build.sh b/CI/build.sh new file mode 100755 index 0000000..3a724d7 --- /dev/null +++ b/CI/build.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# Script by Persian Prince for https://github.com/OpenVisionE2 +# You're not allowed to remove my copyright or reuse this script without putting this header. + +setup_git() { + git config --global user.email "bot@oe-alliance.com" + git config --global user.name "XMLTV-Import python bot" +} + +commit_files() { + git clean -fd + rm -rf *.pyc + rm -rf *.pyo + rm -rf *.mo + git checkout python3 + ./CI/chmod.sh + ./CI/dos2unix.sh + ./CI/PEP8.sh +} + +upload_files() { + git remote add upstream https://${GITHUB_TOKEN}@github.com/oe-alliance/XMLTV-Import.git > /dev/null 2>&1 + git push --quiet upstream python3 || echo "failed to push with error $?" +} + +setup_git +commit_files +upload_files diff --git a/CI/chmod.sh b/CI/chmod.sh new file mode 100755 index 0000000..c16008b --- /dev/null +++ b/CI/chmod.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +echo "" +echo "chmod safe cleanup by Persian Prince" +# Script by Persian Prince for https://github.com/OpenVisionE2 +# You're not allowed to remove my copyright or reuse this script without putting this header. +echo "" +echo "Changing py files, please wait ..." +begin=$(date +"%s") + +echo "" +echo "chmod files" +find . -type d -print0 | xargs -0 chmod 0755 +find . -type f -print0 | xargs -0 chmod 0644 +find . -type f -name "*.sh" -exec chmod +x {} \; +git add -u +git add * +git commit -m "chmod files" + +echo "" +finish=$(date +"%s") +timediff=$(($finish-$begin)) +echo -e "Change time was $(($timediff / 60)) minutes and $(($timediff % 60)) seconds." +echo -e "Fast changing would be less than 1 minute." +echo "" +echo "chmod Done!" +echo "" diff --git a/CI/dos2unix.sh b/CI/dos2unix.sh new file mode 100755 index 0000000..2c86ed7 --- /dev/null +++ b/CI/dos2unix.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +echo "" +echo "dos2unix safe cleanup by Persian Prince" +# Script by Persian Prince for https://github.com/OpenVisionE2 +# You're not allowed to remove my copyright or reuse this script without putting this header. +echo "" +echo "Changing py files, please wait ..." +begin=$(date +"%s") + +echo "" +echo "dos2unix files" +find . -type f \( -iname \*.bb -o -iname \*.conf -o -iname \*.c -o -iname \*.h -o -iname \*.po -o -iname \*.am -o -iname \*.inc -o -iname \*.py -o -iname \*.xml -o -iname \*.sh -o -iname \*.bbappend -o -iname \*.md \) -exec dos2unix {} + +git add -u +git add * +git commit -m "dos2unix files" + +echo "" +finish=$(date +"%s") +timediff=$(($finish-$begin)) +echo -e "Change time was $(($timediff / 60)) minutes and $(($timediff % 60)) seconds." +echo -e "Fast changing would be less than 1 minute." +echo "" +echo "dos2unix Done!" +echo "" diff --git a/README.md b/README.md index 705c90d..da457f0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,17 @@ XMTV-Import =========== -Import's EPG data from rytec xml data sources. \ No newline at end of file +Import's EPG data from rytec xml data sources. + + +## Build status + +[![build](https://github.com/oe-alliance/XMLTV-Import/actions/workflows/build.yml/badge.svg)](https://github.com/oe-alliance/XMLTV-Import/actions/workflows/build.yml.yml) + +## SonarCloud status +[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=oe-alliance_XMLTV-Import&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=oe-alliance_XMLTV-Import) +[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=oe-alliance_XMLTV-Import&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=oe-alliance_XMLTV-Import) +[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=oe-alliance_XMLTV-Import&metric=bugs)](https://sonarcloud.io/summary/new_code?id=oe-alliance_XMLTV-Import) +[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=oe-alliance_XMLTV-Import&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=oe-alliance_XMLTV-Import) +[![Maintainability Rating](https://sonarcloud.io/api/project_badges/measure?project=oe-alliance_XMLTV-Import&metric=sqale_rating)](https://sonarcloud.io/summary/new_code?id=oe-alliance_XMLTV-Import) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=oe-alliance_XMLTV-Import&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=oe-alliance_XMLTV-Import) From 44755f5d6509a21ec5ccbaf7584f772f4a722940 Mon Sep 17 00:00:00 2001 From: jbleyel Date: Wed, 26 Feb 2025 17:57:51 +0100 Subject: [PATCH 11/14] remove random --- src/EPGImport/EPGConfig.py | 2 +- src/EPGImport/EPGImport.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/EPGImport/EPGConfig.py b/src/EPGImport/EPGConfig.py index 0b4c0cb..c508d2d 100755 --- a/src/EPGImport/EPGConfig.py +++ b/src/EPGImport/EPGConfig.py @@ -2,7 +2,7 @@ from os import fstat, listdir, remove from os.path import exists, getmtime, join, split from pickle import dump, load, HIGHEST_PROTOCOL -from random import choice +from secrets import choice from time import time from xml.etree.cElementTree import iterparse from zipfile import ZipFile diff --git a/src/EPGImport/EPGImport.py b/src/EPGImport/EPGImport.py index dbdb3db..6f9f630 100755 --- a/src/EPGImport/EPGImport.py +++ b/src/EPGImport/EPGImport.py @@ -5,16 +5,16 @@ # the contract. +import gzip from os import statvfs, symlink, unlink from os.path import exists, getsize, join, splitext from requests import packages, Session from requests.exceptions import HTTPError, RequestException +from secrets import choice +from string import ascii_lowercase +from time import localtime, mktime, time from twisted.internet import reactor, threads from twisted.internet.reactor import callInThread -import gzip -from random import choices, choice -from string import ascii_lowercase -import time import twisted.python.runtime from Components.config import config @@ -91,10 +91,10 @@ def getTimeFromHourAndMinutes(hour, minute): raise ValueError("Minute must be between 0 and 59") # Get the current local time - now = time.localtime() + now = localtime() # Calculate the timestamp for the specified time (today with the given hour and minute) - begin = int(time.mktime(( + begin = int(mktime(( now.tm_year, # Current year now.tm_mon, # Current month now.tm_mday, # Current day @@ -194,7 +194,7 @@ def beginImport(self, longDescUntil=None): self.eventCount = 0 if longDescUntil is None: # default to 7 days ahead - self.longDescUntil = time.time() + 24 * 3600 * 7 + self.longDescUntil = time() + 24 * 3600 * 7 else: self.longDescUntil = longDescUntil self.nextImport() @@ -218,7 +218,7 @@ def fetchUrl(self, filename): self.afterDownload(filename, deleteFile=False) def urlDownload(self, sourcefile, afterDownload, downloadFail): - host = "".join(choices(ascii_lowercase, k=5)) + host = "".join([choice(ascii_lowercase) for i in range(5)]) check_mount = False if exists("/media/hdd"): with open("/proc/mounts", "r") as f: From 6b6533aab436c471c23babe7a948919664cb4151 Mon Sep 17 00:00:00 2001 From: jbleyel Date: Wed, 26 Feb 2025 17:59:46 +0100 Subject: [PATCH 12/14] update de.po --- src/EPGImport/locale/de.po | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/EPGImport/locale/de.po b/src/EPGImport/locale/de.po index afd773a..41a92c5 100755 --- a/src/EPGImport/locale/de.po +++ b/src/EPGImport/locale/de.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: plugins 0.0.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-01-16 06:40+0000\n" -"PO-Revision-Date: 2019-12-12 14:29+0100\n" +"PO-Revision-Date: 2025-02-26 17:59+0100\n" "Last-Translator: jbleyel\n" "Language-Team: none\n" "Language: de\n" @@ -16,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 2.2.4\n" +"X-Generator: Poedit 3.5\n" #: ../plugin.py:739 msgid "" @@ -229,16 +229,15 @@ msgstr "Aktion auswählen" #: ../filtersServices.py:256 msgid "Select service to add..." -msgstr "Wähle Sender zum Hinzufügen… " +msgstr "Wähle Sender zum Hinzufügen…" #: ../plugin.py:377 msgid "Show \"EPGImport\" in extensions" -msgstr "\"EPG-Import\" unter Erweiterungen anzeigen" +msgstr "\"EPG-Import\" in Erweiterungen anzeigen" #: ../plugin.py:378 -#, fuzzy msgid "Show \"EPGImport\" in plugins" -msgstr "\"EPG-Import\" unter Erweiterungen anzeigen" +msgstr "\"EPG-Import\" im PluginBrowser anzeigen" #: ../plugin.py:520 msgid "Show log" From 43c79c067f714d7f872d4808a7d51f7602754f01 Mon Sep 17 00:00:00 2001 From: jbleyel Date: Thu, 27 Feb 2025 12:42:11 +0100 Subject: [PATCH 13/14] small fixes --- src/EPGImport/EPGConfig.py | 79 +++++++++++++++------------------ src/EPGImport/epgdat.py | 5 ++- src/EPGImport/plugin.py | 79 ++++++++++++++++++++------------- src/EPGImport/xmltvconverter.py | 10 ++--- 4 files changed, 90 insertions(+), 83 deletions(-) diff --git a/src/EPGImport/EPGConfig.py b/src/EPGImport/EPGConfig.py index c508d2d..e0da592 100755 --- a/src/EPGImport/EPGConfig.py +++ b/src/EPGImport/EPGConfig.py @@ -1,4 +1,5 @@ from gzip import GzipFile +import lzma from os import fstat, listdir, remove from os.path import exists, getmtime, join, split from pickle import dump, load, HIGHEST_PROTOCOL @@ -6,6 +7,7 @@ from time import time from xml.etree.cElementTree import iterparse from zipfile import ZipFile +from collections import defaultdict from . import log @@ -56,7 +58,8 @@ def __init__(self, filename, urls=None, offset=0): self.urls = [filename] else: self.urls = urls - self.items = None + # self.items = None + self.items = defaultdict(set) self.offset = offset def openStream(self, filename): @@ -66,10 +69,6 @@ def openStream(self, filename): if filename.endswith(".gz"): fd = GzipFile(fileobj=fd, mode="rb") elif filename.endswith(".xz") or filename.endswith(".lzma"): - try: - import lzma - except ImportError: - from backports import lzma fd = lzma.open(filename, "rb") elif filename.endswith(".zip"): from io import BytesIO @@ -79,9 +78,7 @@ def openStream(self, filename): def parse(self, filterCallback, downloadedFile): print(f"[EPGImport] Parsing channels from '{self.name}'", file=log) - - if self.items is None: - self.items = {} + self.items = defaultdict(set) try: stream = self.openStream(downloadedFile) @@ -89,28 +86,36 @@ def parse(self, filterCallback, downloadedFile): print(f"[EPGImport] Error: Unable to open stream for {downloadedFile}", file=log) return - context = iterparse(stream) - for event, elem in context: - if elem.tag == "channel": - channel_id = elem.get("id").lower() - ref = str(elem.text or '').strip() - - if not channel_id or not ref: - continue # Skip empty values - if ref: - if filterCallback(ref): - """ - if channel_id in self.items: - self.items[channel_id].append(ref) - else: - self.items[channel_id] = [ref] - """ - if channel_id in self.items: - self.items[channel_id].append(ref) - self.items[channel_id] = list(dict.fromkeys(self.items[channel_id])) # Ensure uniqueness - else: - self.items[channel_id] = [ref] - elem.clear() + # here is a problem in the List of supported formats by iterparse: crash on file corrupt + # _lzma.LZMAError: Input format not supported by decoder + supported_formats = ['.xml', '.xml.gz', '.xml.xz'] # fixed + # Make sure the file is in a compatible format + if any(downloadedFile.endswith(ext) for ext in supported_formats): + + context = iterparse(stream) + for event, elem in context: + if elem.tag == "channel": + channel_id = elem.get("id").lower() + ref = str(elem.text or '').strip() + + if not channel_id or not ref: + continue # Skip empty values + if ref: + if filterCallback(ref): + """ + if channel_id in self.items: + self.items[channel_id].append(ref) + else: + self.items[channel_id] = [ref] + """ + if channel_id in self.items: + self.items[channel_id].append(ref) + self.items[channel_id] = list(dict.fromkeys(self.items[channel_id])) # Ensure uniqueness + else: + self.items[channel_id] = [ref] + + elem.clear() + except Exception as e: print(f"[EPGImport] Failed to parse {downloadedFile} Error: {e}", file=log) import traceback @@ -153,23 +158,11 @@ class EPGSource: def __init__(self, path, elem, category=None, offset=0): self.parser = elem.get("type", "gen_xmltv") self.nocheck = int(elem.get("nocheck", 0)) - """ - self.parser = elem.get("type") - nocheck = elem.get("nocheck") - if nocheck is None: - self.nocheck = 0 - elif nocheck == "1": - self.nocheck = 1 - else: - self.nocheck = 0 - """ self.urls = [e.text.strip() for e in elem.findall("url")] self.url = choice(self.urls) - self.description = elem.findtext("description") + self.description = elem.findtext("description", self.url) self.category = category self.offset = offset - if not self.description: - self.description = self.url self.format = elem.get("format", "xml") self.channels = getChannels(path, elem.get("channels"), offset) diff --git a/src/EPGImport/epgdat.py b/src/EPGImport/epgdat.py index 2ff49af..8ad6e35 100755 --- a/src/EPGImport/epgdat.py +++ b/src/EPGImport/epgdat.py @@ -14,7 +14,7 @@ from . import dreamcrc crc32_dreambox = lambda d, t: dreamcrc.crc32(d, t) & 0xffffffff print("[EPGImport] using C module, yay") -except: +except ImportError: print("[EPGImport] failed to load C implementation, sorry") # this table is used by CRC32 routine below (used by Dreambox for @@ -261,7 +261,8 @@ def preprocess_events_channel(self, services): # unix format (second since 1970) and already GMT corrected event_time_HMS = datetime.utcfromtimestamp(event[0]) # actually YYYY-MM-DD HH:MM:SS dvb_date = event_time_HMS.toordinal() - self.EPG_PROLEPTIC_ZERO_DAY # epg.dat date is = (proleptic date - epg_zero_day) - event_duration_HMS = datetime.utcfromtimestamp(event[1]) # actually 1970-01-01 HH:MM:SS + # event_duration_HMS = datetime.utcfromtimestamp(event[1]) # actually 1970-01-01 HH:MM:SS + event_duration_HMS = datetime.datetime(*time.gmtime(event[1])[:6]) # actually 1970-01-01 HH:MM:SS # EVENT DATA # simply create an incremental ID, starting from '1' # event_id appears to be per channel, so this should be okay. diff --git a/src/EPGImport/plugin.py b/src/EPGImport/plugin.py index af83b47..a914ddd 100644 --- a/src/EPGImport/plugin.py +++ b/src/EPGImport/plugin.py @@ -1,6 +1,6 @@ from os import remove from os.path import exists -from time import localtime, mktime, strftime, time, asctime +from time import localtime, mktime, strftime, strptime, time, asctime from enigma import eServiceCenter, eServiceReference, eEPGCache, eTimer, getDesktop @@ -71,11 +71,11 @@ def calcDefaultStarttime(): config.plugins.epgimport = ConfigSubsection() config.plugins.epgimport.enabled = ConfigEnableDisable(default=False) config.plugins.epgimport.runboot = ConfigSelection(default="4", choices=[ - ("1", _("always")), - ("2", _("only manual boot")), - ("3", _("only automatic boot")), - ("4", _("never")) - ]) + ("1", _("always")), + ("2", _("only manual boot")), + ("3", _("only automatic boot")), + ("4", _("never")) +]) config.plugins.epgimport.runboot_restart = ConfigYesNo(default=False) config.plugins.epgimport.runboot_day = ConfigYesNo(default=False) config.plugins.epgimport.wakeupsleep = ConfigEnableDisable(default=False) @@ -84,9 +84,9 @@ def calcDefaultStarttime(): config.plugins.epgimport.showinplugins = ConfigYesNo(default=IMAGEDISTRO != "openatv") config.plugins.epgimport.showinextensions = ConfigYesNo(default=True) config.plugins.epgimport.deepstandby = ConfigSelection(default="skip", choices=[ - ("wakeup", _("wake up and import")), - ("skip", _("skip the import")) - ]) + ("wakeup", _("wake up and import")), + ("skip", _("skip the import")) +]) config.plugins.epgimport.standby_afterwakeup = ConfigYesNo(default=False) config.plugins.epgimport.shutdown = ConfigYesNo(default=False) config.plugins.epgimport.longDescDays = ConfigNumber(default=5) @@ -210,7 +210,7 @@ def channelFilter(ref): if serviceIgnoreList is None: serviceIgnoreList = [getRefNum(x) for x in filtersServices.filtersServicesList.servicesList()] if refnum in serviceIgnoreList: - print("Serviceref is in ignore list:", sref.toString(), file=log) + print(f"Serviceref is in ignore list:{sref.toString()}", file=log) return False if "%3a//" in ref.lower(): # print("URL detected in serviceref, not checking fake recording on serviceref:", ref, file=log) @@ -222,7 +222,7 @@ def channelFilter(ref): # -7 (errNoSourceFound) occurs when tuner is disconnected. r = fakeRecResult in (0, -7) return r - print("Invalid serviceref string:", ref, file=log) + print(f"Invalid serviceref string: {ref}", file=log) return False @@ -234,7 +234,7 @@ def channelFilter(ref): print("[EPGImport] EPGCache instance obtained successfully.", file=log) epgimport = EPGImport.EPGImport(epgcache_instance, channelFilter) except Exception as e: - print("[EPGImport] Error obtaining EPGCache instance: %s" % e, file=log) + print(f"[EPGImport] Error obtaining EPGCache instance: {e}", file=log) lastImportResult = None @@ -244,7 +244,7 @@ def startImport(): EPGImport.HDD_EPG_DAT = config.misc.epgcache_filename.value if config.plugins.epgimport.clear_oldepg.value and hasattr(epgimport.epgcache, "flushEPG"): EPGImport.unlink_if_exists(EPGImport.HDD_EPG_DAT) - EPGImport.unlink_if_exists(EPGImport.HDD_EPG_DAT + ".backup") + EPGImport.unlink_if_exists(f"{EPGImport.HDD_EPG_DAT}.backup") epgimport.epgcache.flushEPG() epgimport.onDone = doneImport epgimport.beginImport(longDescUntil=config.plugins.epgimport.longDescDays.value * 24 * 3600 + time()) @@ -403,7 +403,7 @@ def createSetup(self): if hasattr(eEPGCache, "flushEPG"): list.append(self.cfg_clear_oldepg) list.append(self.cfg_longDescDays) - if fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyo")) or fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyc")): + if isPluginInstalled("AutoTimer"): try: list.append(self.cfg_parse_autotimer) except: @@ -433,7 +433,7 @@ def setPrevValues(section, values): def keyGreen(self): self.updateTimer.stop() - if self.EPG.parse_autotimer.value and (not fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyo")) or not fileExists(resolveFilename(SCOPE_PLUGINS, "Extensions/AutoTimer/plugin.pyc"))): + if self.EPG.parse_autotimer.value and not isPluginInstalled("AutoTimer"): self.EPG.parse_autotimer.value = False if self.EPG.shutdown.value: self.EPG.standby_afterwakeup.value = False @@ -471,13 +471,30 @@ def updateStatus(self): self["status"].setText(text) if lastImportResult and (lastImportResult != self.lastImportResult): start, count = lastImportResult + """ + ## issue crash trhead + # try: + # d, t = FuzzyTime(start, inPast=True) + # except: + # # Not all images have inPast + # d, t = FuzzyTime(start) + """ try: - d, t = FuzzyTime(start, inPast=True) - except: - # Not all images have inPast - d, t = FuzzyTime(start) - self["statusbar"].setText(_("Last: %s %s, %d events") % (d, t, count)) - self.lastImportResult = lastImportResult + if isinstance(start, str): + start = mktime(strptime(start, "%Y-%m-%d %H:%M:%S")) + elif not isinstance(start, (int, float)): + raise ValueError("Start value is not a valid timestamp or string") + + d, t = FuzzyTime(int(start), inPast=True) + except Exception as e: + print(f"[EPGImport] Error FuzzyTime: {e}") + try: + d, t = FuzzyTime(int(start)) + except Exception as e: + print(f"[EPGImport] Fallback with FuzzyTime also failed: {e}") + + self["statusbar"].setText(_(f"Last import: {d} {t}, {count} events")) + self.lastImportResult = lastImportResult def keyInfo(self): last_import = config.plugins.extra_epgimport.last_import.value @@ -600,7 +617,7 @@ def __init__(self, session): def save(self): """ Make the entries unique through a set """ sources = list(set([item[1] for item in self["list"].enumSelected()])) - print("[XMLTVImport] Selected sources:", sources, file=log) + print(f"[XMLTVImport] Selected sources:{sources}", file=log) EPGConfig.storeUserSettings(sources=sources) self.close(True, sources, None) @@ -615,9 +632,9 @@ def do_import(self): item = self["list"].list[idx][0] source = [item[1] or ""] cfg = {"sources": source} - print("[XMLTVImport] Selected source: ", source, file=log) + print(f"[XMLTVImport] Selected source: {source}", file=log) except Exception as e: - print("[XMLTVImport] Error at selected source:", e, file=log) + print(f"[XMLTVImport] Error at selected source:{e}", file=log) else: if cfg["sources"] != "": self.close(False, None, cfg) @@ -728,7 +745,7 @@ def save(self): f.write(self.log.getvalue()) self.session.open(MessageBox, _("Write to /tmp/epgimport.log"), MessageBox.TYPE_INFO, timeout=5, close_on_any_key=True) except Exception as e: - self["list"].setText("Failed to write /tmp/epgimport.log:str" + str(e)) + self["list"].setText(f"Failed to write /tmp/epgimport.log:str{str(e)}") self.close(True) def cancel(self): @@ -886,7 +903,6 @@ def update(self, atLeast=0): cur_day = int(now_day.tm_wday) wakeup_day = WakeupDayOfWeek() if wakeup_day == -1: - print("[XMLTVImport] wakeup day of week disabled", file=log) return -1 if wake < now + atLeast: wake += 86400 * wakeup_day @@ -897,7 +913,7 @@ def update(self, atLeast=0): self.autoStartImport.startLongTimer(next) else: wake = -1 - print("[XMLTVImport] WakeUpTime now set to", wake, "(now=%s)" % now, file=log) + print(f"[XMLTVImport] WakeUpTime now set to {wake} (now={now})", file=log) return wake def runImport(self): @@ -915,7 +931,7 @@ def runImport(self): def onTimer(self): self.autoStartImport.stop() now = int(time()) - print("[XMLTVImport] onTimer occured at", now, file=log) + print(f"[XMLTVImport] onTimer occured at {now}", file=log) wake = self.getWakeTime() # If we're close enough, we're okay... atLeast = 0 @@ -940,7 +956,6 @@ def getStatus(self): cur_day = int(now_day.tm_wday) wakeup_day = WakeupDayOfWeek() if wakeup_day == -1: - print("[XMLTVImport] wakeup day of week disabled", file=log) return -1 if wake_up < now: wake_up += 86400 * wakeup_day @@ -1010,7 +1025,7 @@ def onBootStartCheck(): print("[XMLTVImport] onBootStartCheck", file=log) now = int(time()) wake = autoStartTimer.getStatus() - print("[XMLTVImport] now=%d wake=%d wake-now=%d" % (now, wake, wake - now), file=log) + print(f"[XMLTVImport] now={now} wake={wake} wake-now={wake - now}", file=log) if (wake < 0) or (wake - now > 600): runboot = config.plugins.epgimport.runboot.value on_start = False @@ -1050,7 +1065,7 @@ def autostart(reason, session=None, **kwargs): """called with reason=1 to during shutdown, with reason=0 at startup?""" global autoStartTimer global _session - print("[XMLTVImport] autostart (%s) occured at" % reason, time(), file=log) + print(f"[XMLTVImport] autostart ({reason}) occured at {int(time())}", file=log) if reason == 0 and _session is None: if session is not None: _session = session @@ -1092,7 +1107,7 @@ def setExtensionsmenu(el): else: Components.PluginComponent.plugins.removePlugin(extDescriptor) except Exception as e: - print("[EPGImport] Failed to update extensions menu:", e) + print(f"[EPGImport] Failed to update extensions menu:{e}") description = _("Automated EPG Importer") diff --git a/src/EPGImport/xmltvconverter.py b/src/EPGImport/xmltvconverter.py index a015ad3..cc5f2e3 100755 --- a/src/EPGImport/xmltvconverter.py +++ b/src/EPGImport/xmltvconverter.py @@ -25,10 +25,10 @@ def get_time_utc(timestring, fdateparse): try: values = timestring.split(" ") tm = fdateparse(values[0]) - timeGm = timegm(tm) - # suppose file says +0300 => that means we have to substract 3 hours from localtime to get gmt - timeGm -= (3600 * int(values[1]) // 100) - return timeGm + time_gm = timegm(tm) + # suppose file says +0300 => that means we have to substract 3 hours from localtime to get GMT + time_gm -= (3600 * int(values[1]) // 100) + return time_gm except Exception as e: print(f"[XMLTVConverter] get_time_utc error:{e}") return 0 @@ -36,8 +36,6 @@ def get_time_utc(timestring, fdateparse): # Preferred language should be configurable, but for now, # we just like Dutch better! - - def get_xml_string(elem, name): r = "" try: From 2eaeb1d8a5b140e4dcf7118965644de33efc9481 Mon Sep 17 00:00:00 2001 From: jbleyel Date: Thu, 27 Feb 2025 12:53:39 +0100 Subject: [PATCH 14/14] fix sonar warnings --- src/EPGImport/EPGConfig.py | 32 ++++++++++++++++---------------- src/EPGImport/EPGImport.py | 7 ++++--- src/EPGImport/filtersServices.py | 10 ++++------ src/EPGImport/plugin.py | 18 +++++++++--------- 4 files changed, 33 insertions(+), 34 deletions(-) diff --git a/src/EPGImport/EPGConfig.py b/src/EPGImport/EPGConfig.py index e0da592..8d92999 100755 --- a/src/EPGImport/EPGConfig.py +++ b/src/EPGImport/EPGConfig.py @@ -100,19 +100,18 @@ def parse(self, filterCallback, downloadedFile): if not channel_id or not ref: continue # Skip empty values - if ref: - if filterCallback(ref): - """ - if channel_id in self.items: - self.items[channel_id].append(ref) - else: - self.items[channel_id] = [ref] - """ - if channel_id in self.items: - self.items[channel_id].append(ref) - self.items[channel_id] = list(dict.fromkeys(self.items[channel_id])) # Ensure uniqueness - else: - self.items[channel_id] = [ref] + if ref and filterCallback(ref): + """ + if channel_id in self.items: + self.items[channel_id].append(ref) + else: + self.items[channel_id] = [ref] + """ + if channel_id in self.items: + self.items[channel_id].append(ref) + self.items[channel_id] = list(dict.fromkeys(self.items[channel_id])) # Ensure uniqueness + else: + self.items[channel_id] = [ref] elem.clear() @@ -237,6 +236,7 @@ def storeUserSettings(filename=SETTINGS_FILE, sources=None): if __name__ == "__main__": import sys + SETTINGS_FILE = "settings.pkl" x = [] ln = [] path = "." @@ -247,9 +247,9 @@ def storeUserSettings(filename=SETTINGS_FILE, sources=None): ln.append(t) print(t) x.append(p.description) - storeUserSettings("settings.pkl", [1, "twee"]) - assert loadUserSettings("settings.pkl") == {"sources": [1, "twee"]} - remove("settings.pkl") + storeUserSettings(SETTINGS_FILE, [1, "twee"]) + assert loadUserSettings(SETTINGS_FILE) == {"sources": [1, "twee"]} + remove(SETTINGS_FILE) for p in enumSources(path, x): t = (p.description, p.urls, p.parser, p.format, p.channels, p.nocheck) assert t in ln diff --git a/src/EPGImport/EPGImport.py b/src/EPGImport/EPGImport.py index 6f9f630..ff4aa84 100755 --- a/src/EPGImport/EPGImport.py +++ b/src/EPGImport/EPGImport.py @@ -218,17 +218,18 @@ def fetchUrl(self, filename): self.afterDownload(filename, deleteFile=False) def urlDownload(self, sourcefile, afterDownload, downloadFail): + media_path = "/media/hdd" host = "".join([choice(ascii_lowercase) for i in range(5)]) check_mount = False - if exists("/media/hdd"): + if exists(media_path): with open("/proc/mounts", "r") as f: for line in f: ln = line.split() - if len(ln) > 1 and ln[1] == "/media/hdd": + if len(ln) > 1 and ln[1] == media_path: check_mount = True # print("[EPGImport][urlDownload]2 check_mount ", check_mount) - pathDefault = "/media/hdd" if check_mount else "/tmp" + pathDefault = media_path if check_mount else "/tmp" path = bigStorage(9000000, pathDefault, "/media/usb", "/media/cf") # lets use HDD and flash as main backup media filename = join(path, host) diff --git a/src/EPGImport/filtersServices.py b/src/EPGImport/filtersServices.py index 096ee00..5e7748e 100644 --- a/src/EPGImport/filtersServices.py +++ b/src/EPGImport/filtersServices.py @@ -88,9 +88,8 @@ def save(self): self.saveTo('/etc/epgimport/ignore.conf') def addService(self, ref): - if isinstance(ref, str): - if ref not in self.services: - self.services.append(ref) + if isinstance(ref, str) and ref not in self.services: + self.services.append(ref) def addServices(self, services): if isinstance(services, list): @@ -99,9 +98,8 @@ def addServices(self, services): self.services.append(s) def delService(self, ref): - if isinstance(ref, str): - if ref in self.services: - self.services.remove(ref) + if isinstance(ref, str) and ref in self.services: + self.services.remove(ref) def delAll(self): self.services = [] diff --git a/src/EPGImport/plugin.py b/src/EPGImport/plugin.py index a914ddd..9524184 100644 --- a/src/EPGImport/plugin.py +++ b/src/EPGImport/plugin.py @@ -60,6 +60,7 @@ def calcDefaultStarttime(): # historically located (not a problem, we want to update it) CONFIG_PATH = "/etc/epgimport" +STANDBY_FLAG_FILE = "/tmp/enigmastandby" # Global variable autoStartTimer = None @@ -781,9 +782,8 @@ def main(session, **kwargs): def doneConfiguring(session, retval=False): """user has closed configuration, check new values....""" - if retval is True: - if autoStartTimer is not None: - autoStartTimer.update() + if retval is True and autoStartTimer is not None: + autoStartTimer.update() def doneImport(reboot=False, epgfile=None): @@ -858,12 +858,12 @@ def restartEnigma(confirmed): # save state of enigma, so we can return to previeus state if Screens.Standby.inStandby: try: - open("/tmp/enigmastandby", "wb").close() + open(STANDBY_FLAG_FILE, "wb").close() except: - print("Failed to create /tmp/enigmastandby", file=log) + print(f"Failed to create {STANDBY_FLAG_FILE}", file=log) else: try: - remove("/tmp/enigmastandby") + remove(STANDBY_FLAG_FILE) except: pass # now reboot @@ -968,7 +968,7 @@ def getStatus(self): def afterFinishImportCheck(self): if config.plugins.epgimport.deepstandby.value == "wakeup" and getFPWasTimerWakeup(): - if exists("/tmp/enigmastandby") or exists("/tmp/.EPGImportAnswerBoot"): + if exists(STANDBY_FLAG_FILE) or exists("/tmp/.EPGImportAnswerBoot"): print("[XMLTVImport] is restart enigma2", file=log) else: wake = self.getStatus() @@ -1074,12 +1074,12 @@ def autostart(reason, session=None, **kwargs): if config.plugins.epgimport.runboot.value != "4": onBootStartCheck() # If WE caused the reboot, put the box back in standby. - if exists("/tmp/enigmastandby"): + if exists(STANDBY_FLAG_FILE): print("[XMLTVImport] Returning to standby", file=log) if not Screens.Standby.inStandby: Notifications.AddNotification(Screens.Standby.Standby) try: - remove("/tmp/enigmastandby") + remove(STANDBY_FLAG_FILE) except: pass else: